From 41ed3dca1106d60c0e6cb091b367fa0987cec50c Mon Sep 17 00:00:00 2001 From: REAndroid Date: Sun, 18 Dec 2022 08:23:21 -0500 Subject: [PATCH] V1.0.8 --- build.gradle | 3 +- libs/ArchiveUtil.jar | Bin 29252 -> 0 bytes .../com/reandroid/archive/APKArchive.java | 76 ++++ .../reandroid/archive/ByteInputSource.java | 27 ++ .../reandroid/archive/FileInputSource.java | 22 ++ .../com/reandroid/archive/InputSource.java | 108 ++++++ .../reandroid/archive/InputSourceUtil.java | 107 ++++++ .../com/reandroid/archive/WriteProgress.java | 5 + .../java/com/reandroid/archive/ZipAlign.java | 327 ++++++++++++++++++ .../com/reandroid/archive/ZipArchive.java | 93 +++++ .../com/reandroid/archive/ZipEntrySource.java | 21 ++ .../com/reandroid/archive/ZipSerializer.java | 65 ++++ .../java/com/reandroid/lib/apk/ApkBundle.java | 2 +- .../java/com/reandroid/lib/apk/ApkModule.java | 57 +-- .../java/com/reandroid/lib/apk/ResFile.java | 32 +- .../reandroid/lib/arsc/array/StringArray.java | 3 - .../lib/arsc/chunk/xml/BaseXmlChunk.java | 40 ++- .../lib/arsc/chunk/xml/ResXmlAttribute.java | 17 + .../lib/arsc/chunk/xml/ResXmlBlock.java | 12 +- .../lib/arsc/chunk/xml/ResXmlElement.java | 31 +- .../arsc/chunk/xml/ResXmlStartElement.java | 12 +- .../arsc/chunk/xml/ResXmlStartNamespace.java | 8 + .../reandroid/lib/arsc/item/ResXmlString.java | 7 + .../reandroid/lib/arsc/item/StringItem.java | 5 + .../lib/arsc/pool/BaseStringPool.java | 3 - .../lib/arsc/pool/builder/StyleBuilder.java | 1 - .../reandroid/lib/arsc/value/EntryBlock.java | 3 - .../reandroid/lib/common/TableEntryStore.java | 32 +- 28 files changed, 1028 insertions(+), 91 deletions(-) delete mode 100644 libs/ArchiveUtil.jar create mode 100644 src/main/java/com/reandroid/archive/APKArchive.java create mode 100644 src/main/java/com/reandroid/archive/ByteInputSource.java create mode 100644 src/main/java/com/reandroid/archive/FileInputSource.java create mode 100644 src/main/java/com/reandroid/archive/InputSource.java create mode 100644 src/main/java/com/reandroid/archive/InputSourceUtil.java create mode 100644 src/main/java/com/reandroid/archive/WriteProgress.java create mode 100644 src/main/java/com/reandroid/archive/ZipAlign.java create mode 100644 src/main/java/com/reandroid/archive/ZipArchive.java create mode 100644 src/main/java/com/reandroid/archive/ZipEntrySource.java create mode 100644 src/main/java/com/reandroid/archive/ZipSerializer.java diff --git a/build.gradle b/build.gradle index 5a93315..a6c3c6c 100755 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'java-library' group 'com.reandroid.lib.arsc' -version '1.0.7' +version '1.0.8' java { sourceCompatibility JavaVersion.VERSION_1_8 @@ -22,7 +22,6 @@ repositories { } dependencies { - compile(files("$rootProject.projectDir/libs/ArchiveUtil.jar")) } processResources { diff --git a/libs/ArchiveUtil.jar b/libs/ArchiveUtil.jar deleted file mode 100644 index 4975935905c78fc44d932ecd9d4ceaefffebc3e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29252 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 Y{GXTLci=B)iT3>3c;1q entriesMap){ + super(entriesMap); + } + public APKArchive(){ + super(); + } + public void sortApkFiles(){ + sortApkFiles(new ArrayList<>(listInputSources())); + } + public long writeApk(File outApk) throws IOException{ + ZipSerializer serializer=new ZipSerializer(listInputSources()); + return serializer.writeZip(outApk); + } + public long writeApk(OutputStream outputStream) throws IOException{ + ZipSerializer serializer=new ZipSerializer(listInputSources()); + return serializer.writeZip(outputStream); + } + public static APKArchive loadZippedApk(File zipFile) throws IOException { + return loadZippedApk(new ZipFile(zipFile)); + } + public static APKArchive loadZippedApk(ZipFile zipFile) { + Map entriesMap = InputSourceUtil.mapZipFileSources(zipFile); + return new APKArchive(entriesMap); + } + public static void repackApk(File apkFile) throws IOException{ + APKArchive apkArchive =loadZippedApk(apkFile); + apkArchive.writeApk(apkFile); + } + public static void sortApkFiles(List sourceList){ + Comparator cmp=new Comparator() { + @Override + public int compare(InputSource in1, InputSource in2) { + return getSortName(in1).compareTo(getSortName(in2)); + } + }; + sourceList.sort(cmp); + int i=0; + for(InputSource inputSource:sourceList){ + inputSource.setSort(i); + i++; + } + } + private static String getSortName(InputSource inputSource){ + String name=inputSource.getAlias(); + StringBuilder builder=new StringBuilder(); + if(name.equals("AndroidManifest.xml")){ + builder.append("0 "); + }else if(name.equals("resources.arsc")){ + builder.append("1 "); + }else if(name.startsWith("classes")){ + builder.append("2 "); + }else if(name.startsWith("res/")){ + builder.append("3 "); + }else if(name.startsWith("lib/")){ + builder.append("4 "); + }else if(name.startsWith("assets/")){ + builder.append("5 "); + }else { + builder.append("6 "); + } + builder.append(name.toLowerCase()); + return builder.toString(); + } +} diff --git a/src/main/java/com/reandroid/archive/ByteInputSource.java b/src/main/java/com/reandroid/archive/ByteInputSource.java new file mode 100644 index 0000000..32436cd --- /dev/null +++ b/src/main/java/com/reandroid/archive/ByteInputSource.java @@ -0,0 +1,27 @@ +package com.reandroid.archive; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class ByteInputSource extends InputSource{ + private final byte[] inBytes; + public ByteInputSource(byte[] inBytes, String name) { + super(name); + this.inBytes=inBytes; + } + @Override + public long write(OutputStream outputStream) throws IOException { + byte[] bts=getBytes(); + outputStream.write(bts); + return bts.length; + } + @Override + public InputStream openStream() throws IOException { + return new ByteArrayInputStream(getBytes()); + } + public byte[] getBytes() { + return inBytes; + } +} diff --git a/src/main/java/com/reandroid/archive/FileInputSource.java b/src/main/java/com/reandroid/archive/FileInputSource.java new file mode 100644 index 0000000..96f1770 --- /dev/null +++ b/src/main/java/com/reandroid/archive/FileInputSource.java @@ -0,0 +1,22 @@ +package com.reandroid.archive; + +import java.io.*; + +public class FileInputSource extends InputSource{ + private final File file; + public FileInputSource(File file, String name){ + super(name); + this.file=file; + } + @Override + public void close(InputStream inputStream) throws IOException { + inputStream.close(); + } + @Override + public FileInputStream openStream() throws IOException { + return new FileInputStream(this.file); + } + public File getFile(){ + return file; + } +} diff --git a/src/main/java/com/reandroid/archive/InputSource.java b/src/main/java/com/reandroid/archive/InputSource.java new file mode 100644 index 0000000..9a086df --- /dev/null +++ b/src/main/java/com/reandroid/archive/InputSource.java @@ -0,0 +1,108 @@ +package com.reandroid.archive; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; + +public abstract class InputSource { + private final String name; + private String alias; + private long mCrc; + private long mLength; + private int method = ZipEntry.DEFLATED; + private int sort; + public InputSource(String name){ + this.name = name; + this.alias = InputSourceUtil.sanitize(name); + } + public int getSort() { + return sort; + } + public void setSort(int sort) { + this.sort = sort; + } + public int getMethod() { + return method; + } + public void setMethod(int method) { + this.method = method; + } + + public String getAlias(){ + if(alias!=null){ + return alias; + } + return getName(); + } + public void setAlias(String alias) { + this.alias = alias; + } + public void close(InputStream inputStream) throws IOException { + inputStream.close(); + } + public long write(OutputStream outputStream) throws IOException { + return write(outputStream, openStream()); + } + private long write(OutputStream outputStream, InputStream inputStream) throws IOException { + long result=0; + byte[] buffer=new byte[10240]; + int len; + while ((len=inputStream.read(buffer))>0){ + outputStream.write(buffer, 0, len); + result+=len; + } + close(inputStream); + return result; + } + public String getName(){ + return name; + } + public long getLength() throws IOException{ + if(mLength==0){ + calculateCrc(); + } + return mLength; + } + public long getCrc() throws IOException{ + if(mCrc==0){ + calculateCrc(); + } + return mCrc; + } + public abstract InputStream openStream() throws IOException; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof InputSource)) { + return false; + } + InputSource that = (InputSource) o; + return getName().equals(that.getName()); + } + @Override + public int hashCode() { + return getName().hashCode(); + } + @Override + public String toString(){ + return getClass().getSimpleName()+": "+getName(); + } + private void calculateCrc() throws IOException { + InputStream inputStream=openStream(); + long length=0; + CRC32 crc = new CRC32(); + int bytesRead; + byte[] buffer = new byte[1024*64]; + while((bytesRead = inputStream.read(buffer)) != -1) { + crc.update(buffer, 0, bytesRead); + length+=bytesRead; + } + close(inputStream); + mCrc=crc.getValue(); + mLength=length; + } +} diff --git a/src/main/java/com/reandroid/archive/InputSourceUtil.java b/src/main/java/com/reandroid/archive/InputSourceUtil.java new file mode 100644 index 0000000..81598d0 --- /dev/null +++ b/src/main/java/com/reandroid/archive/InputSourceUtil.java @@ -0,0 +1,107 @@ +package com.reandroid.archive; + +import java.io.File; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class InputSourceUtil { + + public static String toRelative(File rootDir, File file){ + int len=rootDir.getAbsolutePath().length(); + String path=file.getAbsolutePath(); + path=path.substring(len); + path=sanitize(path); + return path; + } + public static String sanitize(String path){ + path=path.replace('\\', '/'); + while (path.startsWith("./")){ + path=path.substring(2); + } + while (path.startsWith("/")){ + path=path.substring(1); + } + while (path.startsWith(File.separator)){ + path=path.substring(1); + } + return path; + } + + public static Map mapZipFileSources(ZipFile zipFile){ + Map results=new HashMap<>(); + Enumeration entriesEnum = zipFile.entries(); + int i=0; + while (entriesEnum.hasMoreElements()){ + ZipEntry zipEntry = entriesEnum.nextElement(); + if(zipEntry.isDirectory()){ + continue; + } + ZipEntrySource source=new ZipEntrySource(zipFile, zipEntry); + source.setSort(i); + results.put(source.getName(), source); + i++; + } + return results; + } + public static List listZipFileSources(ZipFile zipFile){ + List results=new ArrayList<>(); + Enumeration entriesEnum = zipFile.entries(); + int i=0; + while (entriesEnum.hasMoreElements()){ + ZipEntry zipEntry = entriesEnum.nextElement(); + if(zipEntry.isDirectory()){ + continue; + } + ZipEntrySource source=new ZipEntrySource(zipFile, zipEntry); + source.setSort(i); + results.add(source); + } + return results; + } + public static List listDirectory(File dir){ + List results=new ArrayList<>(); + recursiveDirectory(results, dir, dir); + return results; + } + private static void recursiveDirectory(List results, File rootDir, File dir){ + if(dir.isFile()){ + String name; + if(rootDir.equals(dir)){ + name=dir.getName(); + }else { + name=toRelative(rootDir, dir); + } + results.add(new FileInputSource(dir, name)); + return; + } + File[] childFiles=dir.listFiles(); + if(childFiles==null){ + return; + } + for(File file:childFiles){ + recursiveDirectory(results, rootDir, file); + } + } + public static List sortString(List stringList){ + Comparator cmp=new Comparator() { + @Override + public int compare(String s1, String s2) { + return s1.compareTo(s2); + } + }; + stringList.sort(cmp); + return stringList; + } + + public static List sort(List sourceList){ + Comparator cmp=new Comparator() { + @Override + public int compare(InputSource in1, InputSource in2) { + return Integer.compare(in1.getSort(), in2.getSort()); + } + }; + sourceList.sort(cmp); + return sourceList; + } +} diff --git a/src/main/java/com/reandroid/archive/WriteProgress.java b/src/main/java/com/reandroid/archive/WriteProgress.java new file mode 100644 index 0000000..fe3a0e5 --- /dev/null +++ b/src/main/java/com/reandroid/archive/WriteProgress.java @@ -0,0 +1,5 @@ +package com.reandroid.archive; + +public interface WriteProgress { + void onCompressFile(String path, int mode, long writtenBytes); +} diff --git a/src/main/java/com/reandroid/archive/ZipAlign.java b/src/main/java/com/reandroid/archive/ZipAlign.java new file mode 100644 index 0000000..93e3044 --- /dev/null +++ b/src/main/java/com/reandroid/archive/ZipAlign.java @@ -0,0 +1,327 @@ +/* + This class is copied from "apksigner" and I couldn't find the + original repo/author to credit here. + */ + +package com.reandroid.archive; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + + +public class ZipAlign { + private static final int ZIP_ENTRY_HEADER_LEN = 30; + private static final int ZIP_ENTRY_VERSION = 20; + private static final int ZIP_ENTRY_USES_DATA_DESCR = 0x0008; + private static final int ZIP_ENTRY_DATA_DESCRIPTOR_LEN = 16; + private static final int ALIGNMENT_4 = 4; + + private static class XEntry { + public final ZipEntry entry; + public final long headerOffset; + public final int flags; + public final int padding; + + public XEntry(ZipEntry entry, long headerOffset, int flags, int padding) { + this.entry = entry; + this.headerOffset = headerOffset; + this.flags = flags; + this.padding = padding; + } + } + + + private static class FilterOutputStreamEx extends FilterOutputStream { + private long totalWritten = 0; + public FilterOutputStreamEx(OutputStream out) { + super(out); + } + @Override + public void write(byte[] b) throws IOException { + out.write(b); + totalWritten += b.length; + } + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + totalWritten += len; + } + @Override + public void write(int b) throws IOException { + out.write(b); + totalWritten += 1; + } + @Override + public void close() throws IOException { + super.close(); + } + public void writeInt(long v) throws IOException { + write((int) (v & 0xff)); + write((int) ((v >>> 8) & 0xff)); + write((int) ((v >>> 16) & 0xff)); + write((int) ((v >>> 24) & 0xff)); + } + public void writeShort(int v) throws IOException { + write((v) & 0xff); + write((v >>> 8) & 0xff); + } + } + + private File mInputFile; + private int mAlignment; + private File mOutputFile; + private ZipFile mZipFile; + private RandomAccessFile mRafInput; + private FilterOutputStreamEx mOutputStream; + private final List mXEntries = new ArrayList<>(); + private long mInputFileOffset = 0; + private int mTotalPadding = 0; + + public void zipAlign(File input, File output) throws IOException { + zipAlign(input, output, ALIGNMENT_4); + } + public void zipAlign(File input, File output, int alignment) throws IOException { + mInputFile = input; + mAlignment = alignment; + mOutputFile = output; + openFiles(); + copyAllEntries(); + buildCentralDirectory(); + closeFiles(); + } + private void openFiles() throws IOException { + mZipFile = new ZipFile(mInputFile); + mRafInput = new RandomAccessFile(mInputFile, "r"); + mOutputStream = new FilterOutputStreamEx(new BufferedOutputStream(new FileOutputStream(mOutputFile), 32 * 1024)); + } + private void copyAllEntries() throws IOException { + final int entryCount = mZipFile.size(); + if (entryCount == 0) { + return; + } + final Enumeration entries = mZipFile.entries(); + while (entries.hasMoreElements()) { + final ZipEntry entry = (ZipEntry) entries.nextElement(); + + int flags = entry.getMethod() == ZipEntry.STORED ? 0 : 1 << 3; + flags |= 1 << 11; + + final long outputEntryHeaderOffset = mOutputStream.totalWritten; + + final int inputEntryHeaderSize = ZIP_ENTRY_HEADER_LEN + (entry.getExtra() != null ? entry.getExtra().length : 0) + + entry.getName().getBytes(StandardCharsets.UTF_8).length; + final long inputEntryDataOffset = mInputFileOffset + inputEntryHeaderSize; + + final int padding; + + if (entry.getMethod() != ZipEntry.STORED) { + padding = 0; + } else { + long newOffset = inputEntryDataOffset + mTotalPadding; + padding = (int) ((mAlignment - (newOffset % mAlignment)) % mAlignment); + mTotalPadding += padding; + } + + final XEntry xentry = new XEntry(entry, outputEntryHeaderOffset, flags, padding); + mXEntries.add(xentry); + byte[] extra = entry.getExtra(); + if (extra == null) { + extra = new byte[padding]; + } else { + byte[] newExtra = new byte[extra.length + padding]; + System.arraycopy(extra, 0, newExtra, 0, extra.length); + Arrays.fill(newExtra, extra.length, newExtra.length, (byte) 0); + extra = newExtra; + } + entry.setExtra(extra); + mOutputStream.writeInt(ZipOutputStream.LOCSIG); + mOutputStream.writeShort(ZIP_ENTRY_VERSION); + mOutputStream.writeShort(flags); + mOutputStream.writeShort(entry.getMethod()); + + int modDate; + int time; + GregorianCalendar cal = new GregorianCalendar(); + cal.setTime(new Date(entry.getTime())); + int year = cal.get(Calendar.YEAR); + if (year < 1980) { + modDate = 0x21; + time = 0; + } else { + modDate = cal.get(Calendar.DATE); + modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate; + modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate; + time = cal.get(Calendar.SECOND) >> 1; + time = (cal.get(Calendar.MINUTE) << 5) | time; + time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time; + } + + mOutputStream.writeShort(time); + mOutputStream.writeShort(modDate); + + mOutputStream.writeInt(entry.getCrc()); + mOutputStream.writeInt(entry.getCompressedSize()); + mOutputStream.writeInt(entry.getSize()); + + mOutputStream.writeShort(entry.getName().getBytes(StandardCharsets.UTF_8).length); + mOutputStream.writeShort(entry.getExtra().length); + mOutputStream.write(entry.getName().getBytes(StandardCharsets.UTF_8)); + mOutputStream.write(entry.getExtra(), 0, entry.getExtra().length); + + mInputFileOffset += inputEntryHeaderSize; + + final long sizeToCopy; + if ((flags & ZIP_ENTRY_USES_DATA_DESCR) != 0) { + sizeToCopy = (entry.isDirectory() ? 0 : entry.getCompressedSize()) + ZIP_ENTRY_DATA_DESCRIPTOR_LEN; + } else { + sizeToCopy = entry.isDirectory() ? 0 : entry.getCompressedSize(); + } + + if (sizeToCopy > 0) { + mRafInput.seek(mInputFileOffset); + + long totalSizeCopied = 0; + final byte[] buf = new byte[32 * 1024]; + while (totalSizeCopied < sizeToCopy) { + int read = mRafInput.read(buf, 0, (int) Math.min(32 * 1024, sizeToCopy - totalSizeCopied)); + if (read <= 0) { + break; + } + mOutputStream.write(buf, 0, read); + totalSizeCopied += read; + } + } + + mInputFileOffset += sizeToCopy; + } + } + + private void buildCentralDirectory() throws IOException { + final long centralDirOffset = mOutputStream.totalWritten; + final int entryCount = mXEntries.size(); + for (int i = 0; i < entryCount; i++) { + XEntry xentry = mXEntries.get(i); + final ZipEntry entry = xentry.entry; + int modDate; + int time; + GregorianCalendar cal = new GregorianCalendar(); + cal.setTime(new Date(entry.getTime())); + int year = cal.get(Calendar.YEAR); + if (year < 1980) { + modDate = 0x21; + time = 0; + } else { + modDate = cal.get(Calendar.DATE); + modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate; + modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate; + time = cal.get(Calendar.SECOND) >> 1; + time = (cal.get(Calendar.MINUTE) << 5) | time; + time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time; + } + + mOutputStream.writeInt(ZipFile.CENSIG); // CEN header signature + mOutputStream.writeShort(ZIP_ENTRY_VERSION); // version made by + mOutputStream.writeShort(ZIP_ENTRY_VERSION); // version needed to extract + mOutputStream.writeShort(xentry.flags); // general purpose bit flag + mOutputStream.writeShort(entry.getMethod()); // compression method + mOutputStream.writeShort(time); + mOutputStream.writeShort(modDate); + mOutputStream.writeInt(entry.getCrc()); // crc-32 + mOutputStream.writeInt(entry.getCompressedSize()); // compressed size + mOutputStream.writeInt(entry.getSize()); // uncompressed size + final byte[] nameBytes = entry.getName().getBytes(StandardCharsets.UTF_8); + mOutputStream.writeShort(nameBytes.length); + mOutputStream.writeShort(entry.getExtra() != null ? entry.getExtra().length - xentry.padding : 0); + final byte[] commentBytes; + if (entry.getComment() != null) { + commentBytes = entry.getComment().getBytes(StandardCharsets.UTF_8); + mOutputStream.writeShort(Math.min(commentBytes.length, 0xffff)); + } else { + commentBytes = null; + mOutputStream.writeShort(0); + } + mOutputStream.writeShort(0); // starting disk number + mOutputStream.writeShort(0); // internal file attributes (unused) + mOutputStream.writeInt(0); // external file attributes (unused) + mOutputStream.writeInt(xentry.headerOffset); // relative offset of local header + mOutputStream.write(nameBytes); + if (entry.getExtra() != null) { + mOutputStream.write(entry.getExtra(), 0, entry.getExtra().length - xentry.padding); + } + if (commentBytes != null) { + mOutputStream.write(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); + } + } + final long centralDirSize = mOutputStream.totalWritten - centralDirOffset; + + mOutputStream.writeInt(ZipFile.ENDSIG); // END record signature + mOutputStream.writeShort(0); // number of this disk + mOutputStream.writeShort(0); // central directory start disk + mOutputStream.writeShort(entryCount); // number of directory entries on disk + mOutputStream.writeShort(entryCount); // total number of directory entries + mOutputStream.writeInt(centralDirSize); // length of central directory + mOutputStream.writeInt(centralDirOffset); // offset of central directory + mOutputStream.writeShort(0); + mOutputStream.flush(); + } + + private void closeFiles() throws IOException { + try { + mZipFile.close(); + } finally { + try { + mRafInput.close(); + } finally { + mOutputStream.close(); + } + } + + } + + public static void align4(File inFile) throws IOException{ + align(inFile, ALIGNMENT_4); + } + public static void align4(File inFile, File outFile) throws IOException{ + align(inFile, outFile, ALIGNMENT_4); + } + public static void align(File inFile, int alignment) throws IOException{ + File tmp=toTmpFile(inFile); + tmp.delete(); + align(inFile, tmp, alignment); + inFile.delete(); + tmp.renameTo(inFile); + } + public static void align(File inFile, File outFile, int alignment) throws IOException{ + ZipAlign zipAlign=new ZipAlign(); + File dir=outFile.getParentFile(); + if(dir!=null && !dir.exists()){ + dir.mkdirs(); + } + zipAlign.zipAlign(inFile, outFile, alignment); + } + private static File toTmpFile(File file){ + String name=file.getName()+".align.tmp"; + File dir=file.getParentFile(); + if(dir==null){ + return new File(name); + } + return new File(dir, name); + } +} + diff --git a/src/main/java/com/reandroid/archive/ZipArchive.java b/src/main/java/com/reandroid/archive/ZipArchive.java new file mode 100644 index 0000000..9478b26 --- /dev/null +++ b/src/main/java/com/reandroid/archive/ZipArchive.java @@ -0,0 +1,93 @@ +package com.reandroid.archive; + + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipFile; + +public class ZipArchive { + private final Map mEntriesMap; + public ZipArchive(Map entriesMap){ + this.mEntriesMap=entriesMap; + } + public ZipArchive(){ + this(new HashMap<>()); + } + + public void extract(File outDir) throws IOException { + for(InputSource inputSource:listInputSources()){ + extract(outDir, inputSource); + } + } + private void extract(File outDir, InputSource inputSource) throws IOException { + File file=toOutFile(outDir, inputSource.getAlias()); + File dir=file.getParentFile(); + if(dir!=null && !dir.exists()){ + dir.mkdirs(); + } + FileOutputStream outputStream=new FileOutputStream(file); + inputSource.write(outputStream); + outputStream.close(); + } + private File toOutFile(File outDir, String path){ + path=path.replace('/', File.separatorChar); + return new File(outDir, path); + } + public void removeDir(String dirName){ + if(!dirName.endsWith("/")){ + dirName=dirName+"/"; + } + for(InputSource inputSource:listInputSources()){ + if(inputSource.getName().startsWith(dirName)){ + remove(inputSource.getName()); + } + } + } + public void removeAll(Pattern patternAlias){ + for(InputSource inputSource:listInputSources()){ + Matcher matcher = patternAlias.matcher(inputSource.getAlias()); + if(matcher.matches()){ + mEntriesMap.remove(inputSource.getName()); + } + } + } + public InputSource remove(String name){ + InputSource inputSource=mEntriesMap.remove(name); + if(inputSource==null){ + return null; + } + return inputSource; + } + public void addArchive(File archiveFile) throws IOException { + ZipFile zipFile=new ZipFile(archiveFile); + add(zipFile); + } + public void addDirectory(File dir){ + addAll(InputSourceUtil.listDirectory(dir)); + } + public void add(ZipFile zipFile){ + List sourceList = InputSourceUtil.listZipFileSources(zipFile); + this.addAll(sourceList); + } + public void addAll(Collection inputSourceList){ + for(InputSource inputSource:inputSourceList){ + add(inputSource); + } + } + public void add(InputSource inputSource){ + String name=inputSource.getName(); + Map map=mEntriesMap; + map.remove(name); + map.put(name, inputSource); + } + public List listInputSources(){ + return InputSourceUtil.sort(new ArrayList<>(mEntriesMap.values())); + } + public InputSource getInputSource(String name){ + return mEntriesMap.get(name); + } +} diff --git a/src/main/java/com/reandroid/archive/ZipEntrySource.java b/src/main/java/com/reandroid/archive/ZipEntrySource.java new file mode 100644 index 0000000..6ec1ccb --- /dev/null +++ b/src/main/java/com/reandroid/archive/ZipEntrySource.java @@ -0,0 +1,21 @@ +package com.reandroid.archive; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class ZipEntrySource extends InputSource{ + private final ZipFile zipFile; + private final ZipEntry zipEntry; + public ZipEntrySource(ZipFile zipFile, ZipEntry zipEntry){ + super(zipEntry.getName()); + this.zipFile=zipFile; + this.zipEntry=zipEntry; + super.setMethod(zipEntry.getMethod()); + } + @Override + public InputStream openStream() throws IOException { + return zipFile.getInputStream(zipEntry); + } +} diff --git a/src/main/java/com/reandroid/archive/ZipSerializer.java b/src/main/java/com/reandroid/archive/ZipSerializer.java new file mode 100644 index 0000000..f4a2372 --- /dev/null +++ b/src/main/java/com/reandroid/archive/ZipSerializer.java @@ -0,0 +1,65 @@ +package com.reandroid.archive; + +import java.io.*; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class ZipSerializer { + private final List mSourceList; + private WriteProgress writeProgress; + public ZipSerializer(List sourceList){ + this.mSourceList=sourceList; + } + public void setWriteProgress(WriteProgress writeProgress){ + this.writeProgress=writeProgress; + } + public long writeZip(File outZip) throws IOException{ + File dir=outZip.getParentFile(); + if(dir!=null && !dir.exists()){ + dir.mkdirs(); + } + File tmp=toTmpFile(outZip); + FileOutputStream fileOutputStream=new FileOutputStream(tmp); + long length= writeZip(fileOutputStream); + fileOutputStream.close(); + outZip.delete(); + tmp.renameTo(outZip); + return length; + } + private File toTmpFile(File file){ + File dir=file.getParentFile(); + String name=file.getName()+".tmp"; + return new File(dir, name); + } + public long writeZip(OutputStream outputStream) throws IOException{ + long length=0; + WriteProgress progress=writeProgress; + ZipOutputStream zipOutputStream=new ZipOutputStream(outputStream); + for(InputSource inputSource:mSourceList){ + if(progress!=null){ + progress.onCompressFile(inputSource.getAlias(), inputSource.getMethod(), length); + } + length+=write(zipOutputStream, inputSource); + zipOutputStream.closeEntry(); + } + zipOutputStream.close(); + return length; + } + private long write(ZipOutputStream zipOutputStream, InputSource inputSource) throws IOException{ + ZipEntry zipEntry=createZipEntry(inputSource); + zipOutputStream.putNextEntry(zipEntry); + return inputSource.write(zipOutputStream); + } + private ZipEntry createZipEntry(InputSource inputSource) throws IOException { + String name=inputSource.getAlias(); + ZipEntry zipEntry=new ZipEntry(name); + int method = inputSource.getMethod(); + zipEntry.setMethod(method); + if(method==ZipEntry.STORED){ + zipEntry.setCrc(inputSource.getCrc()); + zipEntry.setSize(inputSource.getLength()); + } + return zipEntry; + } +} diff --git a/src/main/java/com/reandroid/lib/apk/ApkBundle.java b/src/main/java/com/reandroid/lib/apk/ApkBundle.java index ccf9192..91f5f1b 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkBundle.java +++ b/src/main/java/com/reandroid/lib/apk/ApkBundle.java @@ -38,7 +38,7 @@ public class ApkBundle { tableBlock.sortPackages(); tableBlock.refresh(); } - result.sortApkFiles(); + result.getApkArchive().sortApkFiles(); return result; } private ApkModule getLargestTableModule() throws IOException { diff --git a/src/main/java/com/reandroid/lib/apk/ApkModule.java b/src/main/java/com/reandroid/lib/apk/ApkModule.java index 265329c..d8ea701 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkModule.java +++ b/src/main/java/com/reandroid/lib/apk/ApkModule.java @@ -10,6 +10,7 @@ import com.reandroid.lib.arsc.group.StringGroup; import com.reandroid.lib.arsc.item.TableString; import com.reandroid.lib.arsc.pool.TableStringPool; import com.reandroid.lib.arsc.value.EntryBlock; +import sun.tools.jconsole.Tab; import java.io.File; import java.io.IOException; @@ -60,8 +61,12 @@ public class ApkModule { writeApk(file, null); } public void writeApk(File file, WriteProgress progress) throws IOException { - ZipArchive archive=new ZipArchive(); - archive.addAll(getApkArchive().listInputSources()); + uncompressNonXmlResFiles(); + APKArchive archive=getApkArchive(); + InputSource table=archive.getInputSource(TableBlock.FILE_NAME); + if(table!=null){ + table.setMethod(ZipEntry.STORED); + } UncompressedFiles uf=getUncompressedFiles(); uf.apply(archive); int i=1; @@ -75,10 +80,18 @@ public class ApkModule { if(manifest!=null){ manifest.setSort(0); } - ZipSerializer serializer=new ZipSerializer(archive.listInputSources(), false); + ZipSerializer serializer=new ZipSerializer(archive.listInputSources()); serializer.setWriteProgress(progress); serializer.writeZip(file); } + private void uncompressNonXmlResFiles() throws IOException { + for(ResFile resFile:listResFiles()){ + if(resFile.isBinaryXml()){ + continue; + } + resFile.getInputSource().setMethod(ZipEntry.STORED); + } + } public UncompressedFiles getUncompressedFiles(){ return mUncompressedFiles; } @@ -329,9 +342,6 @@ public class ApkModule { } } } - void sortApkFiles(){ - sortApkFiles(new ArrayList<>(getApkArchive().listInputSources())); - } public void setAPKLogger(APKLogger logger) { this.apkLogger = logger; } @@ -361,39 +371,4 @@ public class ApkModule { APKArchive archive=APKArchive.loadZippedApk(apkFile); return new ApkModule(moduleName, archive); } - private static void sortApkFiles(List sourceList){ - Comparator cmp=new Comparator() { - @Override - public int compare(InputSource in1, InputSource in2) { - return getSortName(in1).compareTo(getSortName(in2)); - } - }; - sourceList.sort(cmp); - int i=0; - for(InputSource inputSource:sourceList){ - inputSource.setSort(i); - i++; - } - } - private static String getSortName(InputSource inputSource){ - String name=inputSource.getAlias(); - StringBuilder builder=new StringBuilder(); - if(name.equals(AndroidManifestBlock.FILE_NAME)){ - builder.append("0 "); - }else if(name.equals(TableBlock.FILE_NAME)){ - builder.append("1 "); - }else if(name.startsWith("classes")){ - builder.append("2 "); - }else if(name.startsWith("res/")){ - builder.append("3 "); - }else if(name.startsWith("lib/")){ - builder.append("4 "); - }else if(name.startsWith("assets/")){ - builder.append("5 "); - }else { - builder.append("6 "); - } - builder.append(name.toLowerCase()); - return builder.toString(); - } } diff --git a/src/main/java/com/reandroid/lib/apk/ResFile.java b/src/main/java/com/reandroid/lib/apk/ResFile.java index e982c52..27a34c8 100644 --- a/src/main/java/com/reandroid/lib/apk/ResFile.java +++ b/src/main/java/com/reandroid/lib/apk/ResFile.java @@ -42,7 +42,7 @@ public class ResFile { String typeName=typeBlock.getTypeName()+typeBlock.getResConfig().getQualifiers(); return root+typeName+"/"+name; } - private EntryBlock pickOne(){ + public EntryBlock pickOne(){ List entryList = entryBlockList; if(entryList.size()==0){ return null; @@ -100,6 +100,36 @@ public class ResFile { jsonObject.write(file); return true; } + public File buildOutFile(File dir){ + String path=buildPath(); + path=path.replace('/', File.separatorChar); + return new File(dir, path); + } + public String buildPath(){ + EntryBlock entryBlock=pickOne(); + TypeBlock typeBlock=entryBlock.getTypeBlock(); + StringBuilder builder=new StringBuilder(); + builder.append(typeBlock.getTypeName()); + builder.append(typeBlock.getQualifiers()); + builder.append('/'); + builder.append(entryBlock.getName()); + String ext=getFileExtension(); + if(ext!=null){ + builder.append(ext); + } + return builder.toString(); + } + private String getFileExtension(){ + if(isBinaryXml()){ + return ".xml"; + } + String path=getFilePath(); + int i=path.lastIndexOf('.'); + if(i<0){ + return null; + } + return path.substring(0); + } @Override public String toString(){ return getFilePath(); diff --git a/src/main/java/com/reandroid/lib/arsc/array/StringArray.java b/src/main/java/com/reandroid/lib/arsc/array/StringArray.java index aaf5d8f..1240e45 100755 --- a/src/main/java/com/reandroid/lib/arsc/array/StringArray.java +++ b/src/main/java/com/reandroid/lib/arsc/array/StringArray.java @@ -92,9 +92,6 @@ public abstract class StringArray extends OffsetBlockArray if(jsonObject==null){ continue; } - if(i>750){ - i=i+0; - } jsonArray.put(i, jsonObject); i++; } diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/BaseXmlChunk.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/BaseXmlChunk.java index f64b196..d51623d 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/BaseXmlChunk.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/BaseXmlChunk.java @@ -31,8 +31,23 @@ public class BaseXmlChunk extends BaseChunk { addChild(mNamespaceReference); addChild(mStringReference); } - - + void linkStringReferences(){ + linkStringReference(mCommentReference); + linkStringReference(mNamespaceReference); + linkStringReference(mStringReference); + } + private void linkStringReference(IntegerItem item){ + ResXmlString xmlString = getResXmlString(item.get()); + if(xmlString!=null){ + xmlString.addReferenceIfAbsent(item); + } + } + void unLinkStringReference(IntegerItem item){ + ResXmlString xmlString = getResXmlString(item.get()); + if(xmlString!=null){ + xmlString.removeReference(item); + } + } public void setLineNumber(int val){ mLineNumber.set(val); } @@ -40,19 +55,25 @@ public class BaseXmlChunk extends BaseChunk { return mLineNumber.get(); } public void setCommentReference(int val){ + unLinkStringReference(mCommentReference); mCommentReference.set(val); + linkStringReference(mCommentReference); } public int getCommentReference(){ return mCommentReference.get(); } public void setNamespaceReference(int val){ + unLinkStringReference(mNamespaceReference); mNamespaceReference.set(val); + linkStringReference(mNamespaceReference); } public int getNamespaceReference(){ return mNamespaceReference.get(); } public void setStringReference(int val){ + unLinkStringReference(mStringReference); mStringReference.set(val); + linkStringReference(mStringReference); } public int getStringReference(){ return mStringReference.get(); @@ -66,8 +87,6 @@ public class BaseXmlChunk extends BaseChunk { setStringReference(xmlString.getIndex()); return xmlString; } - - public ResXmlStringPool getStringPool(){ Block parent=getParent(); while (parent!=null){ @@ -134,7 +153,6 @@ public class BaseXmlChunk extends BaseChunk { setCommentReference(xmlString.getIndex()); } } - public ResXmlElement getParentResXmlElement(){ Block parent=getParent(); while (parent!=null){ @@ -146,20 +164,8 @@ public class BaseXmlChunk extends BaseChunk { return null; } @Override - public void onReadBytes(BlockReader reader) throws IOException { - super.onReadBytes(reader); - } - @Override protected void onChunkRefreshed() { - } - @Override - public void onChunkLoaded(){ - super.onChunkLoaded(); - if(mCommentReference.get()!=-1){ - String junk=getString(mCommentReference.get()); - System.out.println(junk); - } } @Override public String toString(){ diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlAttribute.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlAttribute.java index 56f3c34..c4f981f 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlAttribute.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlAttribute.java @@ -3,12 +3,15 @@ package com.reandroid.lib.arsc.chunk.xml; import com.reandroid.lib.arsc.array.ResXmlIDArray; import com.reandroid.lib.arsc.base.Block; import com.reandroid.lib.arsc.container.FixedBlockContainer; +import com.reandroid.lib.arsc.io.BlockReader; import com.reandroid.lib.arsc.item.*; import com.reandroid.lib.arsc.pool.ResXmlStringPool; import com.reandroid.lib.arsc.value.ValueType; import com.reandroid.lib.json.JSONConvert; import com.reandroid.lib.json.JSONObject; +import java.io.IOException; + public class ResXmlAttribute extends FixedBlockContainer implements Comparable, JSONConvert { private final IntegerItem mNamespaceReference; @@ -35,6 +38,20 @@ public class ResXmlAttribute extends FixedBlockContainer addChild(5, mValueTypeByte); addChild(6, mRawValue); } + public void linkStringReferences(){ + linkStringReference(mNamespaceReference); + linkStringReference(mNameReference); + linkStringReference(mValueStringReference); + if(getValueType()==ValueType.STRING){ + linkStringReference(mRawValue); + } + } + private void linkStringReference(IntegerItem item){ + ResXmlString xmlString = getResXmlString(item.get()); + if(xmlString!=null){ + xmlString.addReferenceIfAbsent(item); + } + } public String getUri(){ return getString(getNamespaceReference()); } diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlBlock.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlBlock.java index e392f5e..dad592c 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlBlock.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlBlock.java @@ -33,6 +33,12 @@ public class ResXmlBlock extends BaseChunk implements JSONConvert { addChild(mResXmlIDMap); addChild(mResXmlElementContainer); } + void linkStringReferences(){ + ResXmlElement element=getResXmlElement(); + if(element!=null){ + element.linkStringReferences(); + } + } @Override public void onReadBytes(BlockReader reader) throws IOException { HeaderBlock headerBlock=reader.readHeaderBlock(); @@ -55,6 +61,11 @@ public class ResXmlBlock extends BaseChunk implements JSONConvert { chunkReader.close(); onChunkLoaded(); } + @Override + public void onChunkLoaded(){ + super.onChunkLoaded(); + linkStringReferences(); + } private boolean readNext(BlockReader reader) throws IOException { if(!reader.isAvailable()){ return false; @@ -146,7 +157,6 @@ public class ResXmlBlock extends BaseChunk implements JSONConvert { } return jsonObject; } - @Override public void fromJson(JSONObject json) { onFromJson(json); diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlElement.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlElement.java index 10b1282..ffc29f4 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlElement.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlElement.java @@ -28,12 +28,12 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert(); - this.mStartElementContainer=new SingleBlockContainer<>(); - this.mBody=new BlockList<>(); - this.mResXmlTextContainer=new SingleBlockContainer<>(); - this.mEndElementContainer=new SingleBlockContainer<>(); - this.mEndNamespaceList =new BlockList<>(); + this.mStartNamespaceList = new BlockList<>(); + this.mStartElementContainer= new SingleBlockContainer<>(); + this.mBody = new BlockList<>(); + this.mResXmlTextContainer = new SingleBlockContainer<>(); + this.mEndElementContainer = new SingleBlockContainer<>(); + this.mEndNamespaceList = new BlockList<>(); addChild(0, mStartNamespaceList); addChild(1, mStartElementContainer); addChild(2, mBody); @@ -41,6 +41,22 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert getStartNamespaceList(){ return mStartNamespaceList.getChildes(); } public void addStartNamespace(ResXmlStartNamespace item){ mStartNamespaceList.add(item); } - public List getEndNamespaceList(){ return mEndNamespaceList.getChildes(); } diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlStartElement.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlStartElement.java index 148197c..1676b1e 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlStartElement.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlStartElement.java @@ -3,6 +3,7 @@ package com.reandroid.lib.arsc.chunk.xml; import com.reandroid.lib.arsc.chunk.ChunkType; import com.reandroid.lib.arsc.array.ResXmlAttributeArray; import com.reandroid.lib.arsc.item.ShortItem; +import com.reandroid.lib.arsc.value.ValueType; import java.util.Collection; @@ -34,6 +35,13 @@ public class ResXmlStartElement extends BaseXmlChunk { addChild(mAttributeArray); } @Override + void linkStringReferences(){ + super.linkStringReferences(); + for(ResXmlAttribute attr:listResXmlAttributes()){ + attr.linkStringReferences(); + } + } + @Override protected void onPreRefreshRefresh(){ sortAttributes(); } @@ -225,8 +233,8 @@ public class ResXmlStartElement extends BaseXmlChunk { return builder.toString(); } - private static final short ATTRIBUTES_UNIT_SIZE=0x0014; - private static final short ATTRIBUTES_DEFAULT_START=0x0014; + private static final short ATTRIBUTES_UNIT_SIZE=20; + private static final short ATTRIBUTES_DEFAULT_START=20; /* * Find another way to mark an attribute is class, device actually relies on * value of mClassAttributePosition */ diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlStartNamespace.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlStartNamespace.java index 6a33f65..ae35fc4 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlStartNamespace.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlStartNamespace.java @@ -12,4 +12,12 @@ public class ResXmlStartNamespace extends ResXmlNamespace { public void setEnd(ResXmlEndNamespace namespace){ setPair(namespace); } + @Override + void linkStringReferences(){ + super.linkStringReferences(); + ResXmlEndNamespace end = getEnd(); + if(end!=null){ + end.linkStringReferences(); + } + } } diff --git a/src/main/java/com/reandroid/lib/arsc/item/ResXmlString.java b/src/main/java/com/reandroid/lib/arsc/item/ResXmlString.java index e4ff94e..c672615 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/ResXmlString.java +++ b/src/main/java/com/reandroid/lib/arsc/item/ResXmlString.java @@ -1,5 +1,7 @@ package com.reandroid.lib.arsc.item; +import java.util.List; + public class ResXmlString extends StringItem { public ResXmlString(boolean utf8) { super(utf8); @@ -8,4 +10,9 @@ public class ResXmlString extends StringItem { this(utf8); set(value); } + @Override + public String toString(){ + List refList = getReferencedList(); + return "USED BY="+refList.size()+"{"+super.toString()+"}"; + } } diff --git a/src/main/java/com/reandroid/lib/arsc/item/StringItem.java b/src/main/java/com/reandroid/lib/arsc/item/StringItem.java index 7969a18..4f4dd47 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/StringItem.java +++ b/src/main/java/com/reandroid/lib/arsc/item/StringItem.java @@ -43,6 +43,11 @@ public class StringItem extends BlockItem implements JSONConvert { mReferencedList.add(ref); } } + public void addReferenceIfAbsent(ReferenceItem ref){ + if(ref!=null && !mReferencedList.contains(ref)){ + mReferencedList.add(ref); + } + } public void addReference(Collection refList){ if(refList==null){ return; diff --git a/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java b/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java index 21b3f68..24b67b9 100755 --- a/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java +++ b/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java @@ -366,9 +366,6 @@ public abstract class BaseStringPool extends BaseChunk imp int length = jsonArray.length(); List results=new ArrayList<>(); for(int i=0;i