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).
This commit is contained in:
Andrew Grieve 2015-08-14 11:52:33 -04:00
parent 2033e305af
commit 392420c909
7 changed files with 71 additions and 47 deletions

View File

@ -32,6 +32,7 @@ import brut.util.OS;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@ -158,6 +159,22 @@ public class Androlib {
} }
} }
public void recordUncompressedFiles(ExtFile apkFile, Collection<String> uncompressedFiles) throws AndrolibException {
try {
Directory unk = apkFile.getDirectory();
Set<String> 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) { private boolean isAPKFileNames(String file) {
for (String apkFile : APK_STANDARD_ALL_FILENAMES) { for (String apkFile : APK_STANDARD_ALL_FILENAMES) {
if (apkFile.equals(file) || file.startsWith(apkFile + "/")) { if (apkFile.equals(file) || file.startsWith(apkFile + "/")) {
@ -171,13 +188,8 @@ public class Androlib {
throws AndrolibException { throws AndrolibException {
LOGGER.info("Copying unknown files..."); LOGGER.info("Copying unknown files...");
File unknownOut = new File(outDir, UNK_DIRNAME); 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 { try {
Directory unk = apkFile.getDirectory(); Directory unk = apkFile.getDirectory();
ZipFile apkZipFile = new ZipFile(apkFile.getAbsolutePath());
// loop all items in container recursively, ignoring any that are pre-defined by aapt // loop all items in container recursively, ignoring any that are pre-defined by aapt
Set<String> files = unk.getFiles(true); Set<String> files = unk.getFiles(true);
@ -186,19 +198,12 @@ public class Androlib {
// copy file out of archive into special "unknown" folder // copy file out of archive into special "unknown" folder
unk.copyToDir(unknownOut, file); unk.copyToDir(unknownOut, file);
try { // lets record the name of the file, and its compression type
invZipFile = apkZipFile.getEntry(file); // so that we may re-include it the same way
mResUnknownFiles.addUnknownFileInfo(file, String.valueOf(unk.getCompressionLevel(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) { }
} }
} }
apkZipFile.close(); } catch (DirectoryException ex) {
} catch (DirectoryException | IOException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
@ -266,6 +271,7 @@ public class Androlib {
apkOptions.resourcesAreCompressed = meta.get("compressionType") == null apkOptions.resourcesAreCompressed = meta.get("compressionType") == null
? false ? false
: Boolean.valueOf(meta.get("compressionType").toString()); : Boolean.valueOf(meta.get("compressionType").toString());
apkOptions.doNotCompress = (Collection<String>) meta.get("doNotCompress");
mAndRes.setSdkInfo((Map<String, String>) meta.get("sdkInfo")); mAndRes.setSdkInfo((Map<String, String>) meta.get("sdkInfo"));
mAndRes.setPackageId((Map<String, String>) meta.get("packageInfo")); mAndRes.setPackageId((Map<String, String>) meta.get("packageInfo"));
@ -739,4 +745,9 @@ public class Androlib {
"AndroidManifest.xml" }; "AndroidManifest.xml" };
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] { private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] {
"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "lib", "libs", "assets", "META-INF" }; "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)$");
} }

View File

@ -31,8 +31,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/** /**
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski <brut.alll@gmail.com>
@ -89,8 +87,6 @@ public class ApkDecoder {
LOGGER.info("Using Apktool " + Androlib.getVersion() + " on " + mApkFile.getName()); LOGGER.info("Using Apktool " + Androlib.getVersion() + " on " + mApkFile.getName());
if (hasResources()) { if (hasResources()) {
setCompressionMode();
switch (mDecodeResources) { switch (mDecodeResources) {
case DECODE_RESOURCES_NONE: case DECODE_RESOURCES_NONE:
mAndrolib.decodeResourcesRaw(mApkFile, outDir); mAndrolib.decodeResourcesRaw(mApkFile, outDir);
@ -159,6 +155,8 @@ public class ApkDecoder {
mAndrolib.decodeRawFiles(mApkFile, outDir); mAndrolib.decodeRawFiles(mApkFile, outDir);
mAndrolib.decodeUnknownFiles(mApkFile, outDir, mResTable); mAndrolib.decodeUnknownFiles(mApkFile, outDir, mResTable);
mUncompressedFiles = new ArrayList<String>();
mAndrolib.recordUncompressedFiles(mApkFile, mUncompressedFiles);
mAndrolib.writeOriginalFiles(mApkFile, outDir); mAndrolib.writeOriginalFiles(mApkFile, outDir);
writeMetaFile(); 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 { public void setTargetSdkVersion() throws AndrolibException, IOException {
if (mResTable == null) { if (mResTable == null) {
mResTable = mAndrolib.getResTable(mApkFile); mResTable = mAndrolib.getResTable(mApkFile);
@ -320,10 +306,10 @@ public class ApkDecoder {
putSdkInfo(meta); putSdkInfo(meta);
putPackageInfo(meta); putPackageInfo(meta);
putVersionInfo(meta); putVersionInfo(meta);
putCompressionInfo(meta);
putSharedLibraryInfo(meta); putSharedLibraryInfo(meta);
} }
putUnknownInfo(meta); putUnknownInfo(meta);
putFileCompressionInfo(meta);
mAndrolib.writeMetaFile(mOutDir, meta); mAndrolib.writeMetaFile(mOutDir, meta);
} }
@ -391,18 +377,16 @@ public class ApkDecoder {
} }
} }
private void putCompressionInfo(Map<String, Object> meta) throws AndrolibException { private void putFileCompressionInfo(Map<String, Object> meta) throws AndrolibException {
meta.put("compressionType", getCompressionType()); if (!mUncompressedFiles.isEmpty()) {
meta.put("doNotCompress", mUncompressedFiles);
}
} }
private void putSharedLibraryInfo(Map<String, Object> meta) throws AndrolibException { private void putSharedLibraryInfo(Map<String, Object> meta) throws AndrolibException {
meta.put("sharedLibrary", mResTable.getSharedLibrary()); meta.put("sharedLibrary", mResTable.getSharedLibrary());
} }
private boolean getCompressionType() {
return mCompressResources;
}
private final Androlib mAndrolib; private final Androlib mAndrolib;
private final static Logger LOGGER = Logger.getLogger(Androlib.class.getName()); private final static Logger LOGGER = Logger.getLogger(Androlib.class.getName());
@ -417,7 +401,7 @@ public class ApkDecoder {
private boolean mForceDelete = false; private boolean mForceDelete = false;
private boolean mKeepBrokenResources = false; private boolean mKeepBrokenResources = false;
private boolean mBakDeb = true; private boolean mBakDeb = true;
private boolean mCompressResources = false; private Collection<String> mUncompressedFiles;
private boolean mAnalysisMode = false; private boolean mAnalysisMode = false;
private int mApi = 15; private int mApi = 15;
} }

View File

@ -15,6 +15,8 @@
*/ */
package brut.androlib; package brut.androlib;
import java.util.Collection;
public class ApkOptions { public class ApkOptions {
public boolean forceBuildAll = false; public boolean forceBuildAll = false;
public boolean debugMode = false; public boolean debugMode = false;
@ -23,6 +25,7 @@ public class ApkOptions {
public boolean updateFiles = false; public boolean updateFiles = false;
public boolean isFramework = false; public boolean isFramework = false;
public boolean resourcesAreCompressed = false; public boolean resourcesAreCompressed = false;
public Collection<String> doNotCompress;
public String frameworkFolderLocation = null; public String frameworkFolderLocation = null;
public String frameworkTag = null; public String frameworkTag = null;

View File

@ -383,7 +383,13 @@ final public class AndrolibResources {
cmd.add("-x"); 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("-0");
cmd.add("arsc"); cmd.add("arsc");
} }

View File

@ -26,6 +26,7 @@ import java.util.Set;
public abstract class AbstractDirectory implements Directory { public abstract class AbstractDirectory implements Directory {
protected Set<String> mFiles; protected Set<String> mFiles;
protected Set<String> mFilesRecursive;
protected Map<String, AbstractDirectory> mDirs; protected Map<String, AbstractDirectory> mDirs;
@Override @Override
@ -41,14 +42,15 @@ public abstract class AbstractDirectory implements Directory {
if (!recursive) { if (!recursive) {
return mFiles; return mFiles;
} }
if (mFilesRecursive == null) {
Set<String> files = new LinkedHashSet<String>(mFiles); mFilesRecursive = new LinkedHashSet<String>(mFiles);
for (Map.Entry<String, ? extends Directory> dir : getAbstractDirs().entrySet()) { for (Map.Entry<String, ? extends Directory> dir : getAbstractDirs().entrySet()) {
for (String path : dir.getValue().getFiles(true)) { for (String path : dir.getValue().getFiles(true)) {
files.add(dir.getKey() + separator + path); mFilesRecursive.add(dir.getKey() + separator + path);
}
} }
} }
return files; return mFilesRecursive;
} }
@Override @Override
@ -205,6 +207,11 @@ public abstract class AbstractDirectory implements Directory {
DirUtil.copyToDir(this, out, fileName); DirUtil.copyToDir(this, out, fileName);
} }
public int getCompressionLevel(String fileName)
throws DirectoryException {
return -1; // Unknown
}
protected Map<String, AbstractDirectory> getAbstractDirs() { protected Map<String, AbstractDirectory> getAbstractDirs() {
return getAbstractDirs(false); return getAbstractDirs(false);
} }

View File

@ -47,5 +47,8 @@ public interface Directory {
public void copyToDir(File out, String fileName) public void copyToDir(File out, String fileName)
throws DirectoryException; throws DirectoryException;
public int getCompressionLevel(String fileName)
throws DirectoryException;
public final char separator = '/'; public final char separator = '/';
} }

View File

@ -100,6 +100,16 @@ public class ZipRODirectory extends AbstractDirectory {
throw new UnsupportedOperationException(); 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() { private void loadAll() {
mFiles = new LinkedHashSet<String>(); mFiles = new LinkedHashSet<String>();
mDirs = new LinkedHashMap<String, AbstractDirectory>(); mDirs = new LinkedHashMap<String, AbstractDirectory>();