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.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<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) {
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<String> 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<String>) meta.get("doNotCompress");
mAndRes.setSdkInfo((Map<String, String>) meta.get("sdkInfo"));
mAndRes.setPackageId((Map<String, String>) 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)$");
}

View File

@ -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 <brut.alll@gmail.com>
@ -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<String>();
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<String, Object> meta) throws AndrolibException {
meta.put("compressionType", getCompressionType());
private void putFileCompressionInfo(Map<String, Object> meta) throws AndrolibException {
if (!mUncompressedFiles.isEmpty()) {
meta.put("doNotCompress", mUncompressedFiles);
}
}
private void putSharedLibraryInfo(Map<String, Object> 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<String> mUncompressedFiles;
private boolean mAnalysisMode = false;
private int mApi = 15;
}

View File

@ -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<String> doNotCompress;
public String frameworkFolderLocation = null;
public String frameworkTag = null;

View File

@ -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");
}

View File

@ -26,6 +26,7 @@ import java.util.Set;
public abstract class AbstractDirectory implements Directory {
protected Set<String> mFiles;
protected Set<String> mFilesRecursive;
protected Map<String, AbstractDirectory> mDirs;
@Override
@ -41,14 +42,15 @@ public abstract class AbstractDirectory implements Directory {
if (!recursive) {
return mFiles;
}
Set<String> files = new LinkedHashSet<String>(mFiles);
for (Map.Entry<String, ? extends Directory> dir : getAbstractDirs().entrySet()) {
for (String path : dir.getValue().getFiles(true)) {
files.add(dir.getKey() + separator + path);
if (mFilesRecursive == null) {
mFilesRecursive = new LinkedHashSet<String>(mFiles);
for (Map.Entry<String, ? extends Directory> 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<String, AbstractDirectory> getAbstractDirs() {
return getAbstractDirs(false);
}

View File

@ -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 = '/';
}

View File

@ -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<String>();
mDirs = new LinkedHashMap<String, AbstractDirectory>();