From 392420c9093435c2510b473dfa7dec689e8d3dbf Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Fri, 14 Aug 2015 11:52:33 -0400 Subject: [PATCH] Adds doNotCompress list to apktool.yml This is the list of files (resources, assets, etc) that are stored in the .apk uncompressed. For apps that use AssetFileDescriptor.openFd(), the adding compression will break the call. Maintains support for the resourcesAreCompressed key, but no longer records it when decompiling (it instead records resources.arsc in the doNotCompress list). --- .../src/main/java/brut/androlib/Androlib.java | 43 ++++++++++++------- .../main/java/brut/androlib/ApkDecoder.java | 32 ++++---------- .../main/java/brut/androlib/ApkOptions.java | 3 ++ .../brut/androlib/res/AndrolibResources.java | 8 +++- .../brut/directory/AbstractDirectory.java | 19 +++++--- .../main/java/brut/directory/Directory.java | 3 ++ .../java/brut/directory/ZipRODirectory.java | 10 +++++ 7 files changed, 71 insertions(+), 47 deletions(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java index fef7a463..f82ecca1 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java @@ -32,6 +32,7 @@ import brut.util.OS; import java.io.*; import java.util.*; import java.util.logging.Logger; +import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -158,6 +159,22 @@ public class Androlib { } } + public void recordUncompressedFiles(ExtFile apkFile, Collection uncompressedFiles) throws AndrolibException { + try { + Directory unk = apkFile.getDirectory(); + Set files = unk.getFiles(true); + for (String file : files) { + if (isAPKFileNames(file) && !NO_COMPRESS_PATTERN.matcher(file).find()) { + if (unk.getCompressionLevel(file) == 0) { + uncompressedFiles.add(file); + } + } + } + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + private boolean isAPKFileNames(String file) { for (String apkFile : APK_STANDARD_ALL_FILENAMES) { if (apkFile.equals(file) || file.startsWith(apkFile + "/")) { @@ -171,13 +188,8 @@ public class Androlib { throws AndrolibException { LOGGER.info("Copying unknown files..."); File unknownOut = new File(outDir, UNK_DIRNAME); - ZipEntry invZipFile; - - // have to use container of ZipFile to help identify compression type - // with regular looping of apkFile for easy copy try { Directory unk = apkFile.getDirectory(); - ZipFile apkZipFile = new ZipFile(apkFile.getAbsolutePath()); // loop all items in container recursively, ignoring any that are pre-defined by aapt Set files = unk.getFiles(true); @@ -186,19 +198,12 @@ public class Androlib { // copy file out of archive into special "unknown" folder unk.copyToDir(unknownOut, file); - try { - invZipFile = apkZipFile.getEntry(file); - - // lets record the name of the file, and its compression type - // so that we may re-include it the same way - if (invZipFile != null) { - mResUnknownFiles.addUnknownFileInfo(invZipFile.getName(), String.valueOf(invZipFile.getMethod())); - } - } catch (NullPointerException ignored) { } + // lets record the name of the file, and its compression type + // so that we may re-include it the same way + mResUnknownFiles.addUnknownFileInfo(file, String.valueOf(unk.getCompressionLevel(file))); } } - apkZipFile.close(); - } catch (DirectoryException | IOException ex) { + } catch (DirectoryException ex) { throw new AndrolibException(ex); } } @@ -266,6 +271,7 @@ public class Androlib { apkOptions.resourcesAreCompressed = meta.get("compressionType") == null ? false : Boolean.valueOf(meta.get("compressionType").toString()); + apkOptions.doNotCompress = (Collection) meta.get("doNotCompress"); mAndRes.setSdkInfo((Map) meta.get("sdkInfo")); mAndRes.setPackageId((Map) meta.get("packageInfo")); @@ -739,4 +745,9 @@ public class Androlib { "AndroidManifest.xml" }; private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] { "classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "lib", "libs", "assets", "META-INF" }; + // Taken from AOSP's frameworks/base/tools/aapt/Package.cpp + private final static Pattern NO_COMPRESS_PATTERN = Pattern.compile("\\.(" + + "jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|rtttl|imy|xmf|mp4|" + + "m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv)$"); + } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java index 68f45451..b9e988d7 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java @@ -31,8 +31,6 @@ import java.io.File; import java.io.IOException; import java.util.*; import java.util.logging.Logger; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; /** * @author Ryszard Wiśniewski @@ -89,8 +87,6 @@ public class ApkDecoder { LOGGER.info("Using Apktool " + Androlib.getVersion() + " on " + mApkFile.getName()); if (hasResources()) { - setCompressionMode(); - switch (mDecodeResources) { case DECODE_RESOURCES_NONE: mAndrolib.decodeResourcesRaw(mApkFile, outDir); @@ -159,6 +155,8 @@ public class ApkDecoder { mAndrolib.decodeRawFiles(mApkFile, outDir); mAndrolib.decodeUnknownFiles(mApkFile, outDir, mResTable); + mUncompressedFiles = new ArrayList(); + mAndrolib.recordUncompressedFiles(mApkFile, mUncompressedFiles); mAndrolib.writeOriginalFiles(mApkFile, outDir); writeMetaFile(); } @@ -193,18 +191,6 @@ public class ApkDecoder { } } - public void setCompressionMode() throws AndrolibException, IOException { - // read the resources.arsc checking for STORED vs DEFLATE - // this will determine whether we compress on rebuild or not. - ZipFile zf = new ZipFile(mApkFile.getAbsolutePath()); - ZipEntry ze = zf.getEntry("resources.arsc"); - if (ze != null) { - int compression = ze.getMethod(); - mCompressResources = (compression == ZipEntry.DEFLATED); - } - zf.close(); - } - public void setTargetSdkVersion() throws AndrolibException, IOException { if (mResTable == null) { mResTable = mAndrolib.getResTable(mApkFile); @@ -320,10 +306,10 @@ public class ApkDecoder { putSdkInfo(meta); putPackageInfo(meta); putVersionInfo(meta); - putCompressionInfo(meta); putSharedLibraryInfo(meta); } putUnknownInfo(meta); + putFileCompressionInfo(meta); mAndrolib.writeMetaFile(mOutDir, meta); } @@ -391,18 +377,16 @@ public class ApkDecoder { } } - private void putCompressionInfo(Map meta) throws AndrolibException { - meta.put("compressionType", getCompressionType()); + private void putFileCompressionInfo(Map meta) throws AndrolibException { + if (!mUncompressedFiles.isEmpty()) { + meta.put("doNotCompress", mUncompressedFiles); + } } private void putSharedLibraryInfo(Map meta) throws AndrolibException { meta.put("sharedLibrary", mResTable.getSharedLibrary()); } - private boolean getCompressionType() { - return mCompressResources; - } - private final Androlib mAndrolib; private final static Logger LOGGER = Logger.getLogger(Androlib.class.getName()); @@ -417,7 +401,7 @@ public class ApkDecoder { private boolean mForceDelete = false; private boolean mKeepBrokenResources = false; private boolean mBakDeb = true; - private boolean mCompressResources = false; + private Collection mUncompressedFiles; private boolean mAnalysisMode = false; private int mApi = 15; } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkOptions.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkOptions.java index 77eced97..df4774fd 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkOptions.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkOptions.java @@ -15,6 +15,8 @@ */ package brut.androlib; +import java.util.Collection; + public class ApkOptions { public boolean forceBuildAll = false; public boolean debugMode = false; @@ -23,6 +25,7 @@ public class ApkOptions { public boolean updateFiles = false; public boolean isFramework = false; public boolean resourcesAreCompressed = false; + public Collection doNotCompress; public String frameworkFolderLocation = null; public String frameworkTag = null; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java index d8c987ce..c68e023f 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java @@ -383,7 +383,13 @@ final public class AndrolibResources { cmd.add("-x"); } - if (! apkOptions.resourcesAreCompressed) { + if (apkOptions.doNotCompress != null) { + for (String file : apkOptions.doNotCompress) { + cmd.add("-0"); + cmd.add(file); + } + } + if (!apkOptions.resourcesAreCompressed) { cmd.add("-0"); cmd.add("arsc"); } diff --git a/brut.j.dir/src/main/java/brut/directory/AbstractDirectory.java b/brut.j.dir/src/main/java/brut/directory/AbstractDirectory.java index 79e3488b..c2befcbe 100644 --- a/brut.j.dir/src/main/java/brut/directory/AbstractDirectory.java +++ b/brut.j.dir/src/main/java/brut/directory/AbstractDirectory.java @@ -26,6 +26,7 @@ import java.util.Set; public abstract class AbstractDirectory implements Directory { protected Set mFiles; + protected Set mFilesRecursive; protected Map mDirs; @Override @@ -41,14 +42,15 @@ public abstract class AbstractDirectory implements Directory { if (!recursive) { return mFiles; } - - Set files = new LinkedHashSet(mFiles); - for (Map.Entry dir : getAbstractDirs().entrySet()) { - for (String path : dir.getValue().getFiles(true)) { - files.add(dir.getKey() + separator + path); + if (mFilesRecursive == null) { + mFilesRecursive = new LinkedHashSet(mFiles); + for (Map.Entry dir : getAbstractDirs().entrySet()) { + for (String path : dir.getValue().getFiles(true)) { + mFilesRecursive.add(dir.getKey() + separator + path); + } } } - return files; + return mFilesRecursive; } @Override @@ -205,6 +207,11 @@ public abstract class AbstractDirectory implements Directory { DirUtil.copyToDir(this, out, fileName); } + public int getCompressionLevel(String fileName) + throws DirectoryException { + return -1; // Unknown + } + protected Map getAbstractDirs() { return getAbstractDirs(false); } diff --git a/brut.j.dir/src/main/java/brut/directory/Directory.java b/brut.j.dir/src/main/java/brut/directory/Directory.java index d59ff1fb..0199135d 100644 --- a/brut.j.dir/src/main/java/brut/directory/Directory.java +++ b/brut.j.dir/src/main/java/brut/directory/Directory.java @@ -47,5 +47,8 @@ public interface Directory { public void copyToDir(File out, String fileName) throws DirectoryException; + public int getCompressionLevel(String fileName) + throws DirectoryException; + public final char separator = '/'; } diff --git a/brut.j.dir/src/main/java/brut/directory/ZipRODirectory.java b/brut.j.dir/src/main/java/brut/directory/ZipRODirectory.java index 544fcbbe..bb76298a 100644 --- a/brut.j.dir/src/main/java/brut/directory/ZipRODirectory.java +++ b/brut.j.dir/src/main/java/brut/directory/ZipRODirectory.java @@ -100,6 +100,16 @@ public class ZipRODirectory extends AbstractDirectory { throw new UnsupportedOperationException(); } + @Override + public int getCompressionLevel(String fileName) + throws DirectoryException { + ZipEntry entry = mZipFile.getEntry(fileName); + if (entry == null) { + throw new PathNotExist("Entry not found: " + fileName); + } + return entry.getMethod(); + } + private void loadAll() { mFiles = new LinkedHashSet(); mDirs = new LinkedHashMap();