diff --git a/.gitignore b/.gitignore index be16e52f..3ff2362f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ bin/ # Tmp Files *.kate-swp *~ +*.DS_Store # IntelliJ *.iml @@ -23,3 +24,6 @@ bin/ # gradle/smali-patches patch smali/ into brut.apktool.smali/ brut.apktool.smali/ + +# Patches +*.patch \ No newline at end of file diff --git a/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java b/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java index ae509349..92962f0e 100644 --- a/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java +++ b/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java @@ -127,6 +127,9 @@ public class Main { if (cli.hasOption("force-manifest")) { decoder.setForceDecodeManifest(ApkDecoder.FORCE_DECODE_MANIFEST_FULL); } + if (cli.hasOption("manifest-only")) { + decoder.setManifestOnly(true); + } if (cli.hasOption("no-assets")) { decoder.setDecodeAssets(ApkDecoder.DECODE_ASSETS_NONE); } @@ -294,6 +297,11 @@ public class Main { .desc("Decode the APK's compiled manifest, even if decoding of resources is set to \"false\".") .build(); + Option manifestOnlyOption = Option.builder("mo") + .longOpt("manifest-only") + .desc("Only decode manifest.") + .build(); + Option noAssetOption = Option.builder() .longOpt("no-assets") .desc("Do not decode assets.") @@ -431,6 +439,7 @@ public class Main { DecodeOptions.addOption(forceDecOption); DecodeOptions.addOption(noSrcOption); DecodeOptions.addOption(noResOption); + DecodeOptions.addOption(manifestOnlyOption); // add basic build options BuildOptions.addOption(outputBuiOption); 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 eb03780d..5ef3f75c 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 @@ -100,6 +100,13 @@ public class ApkDecoder { LOGGER.info("Using Apktool " + Androlib.getVersion() + " on " + mApkFile.getName()); + if (mManifestOnly) { + setAnalysisMode(mAnalysisMode, true); + mAndrolib.decodeManifestWithResources(mApkFile, outDir, getResTable()); + writeMetaFile(); + return; + } + if (hasResources()) { switch (mDecodeResources) { case DECODE_RESOURCES_NONE: @@ -125,7 +132,7 @@ public class ApkDecoder { break; } } else { - // if there's no resources.asrc, decode the manifest without looking + // if there's no resources.arsc, decode the manifest without looking // up attribute references if (hasManifest()) { if (mDecodeResources == DECODE_RESOURCES_FULL @@ -242,6 +249,10 @@ public class ApkDecoder { mForceDelete = forceDelete; } + public void setManifestOnly(boolean manifestOnly) { + mManifestOnly = manifestOnly; + } + public void setFrameworkTag(String tag) throws AndrolibException { mAndrolib.apkOptions.frameworkTag = tag; } @@ -416,7 +427,7 @@ public class ApkDecoder { } private void putFileCompressionInfo(MetaInfo meta) throws AndrolibException { - if (!mUncompressedFiles.isEmpty()) { + if (mUncompressedFiles != null && !mUncompressedFiles.isEmpty()) { meta.doNotCompress = mUncompressedFiles; } } @@ -437,6 +448,7 @@ public class ApkDecoder { private short mForceDecodeManifest = FORCE_DECODE_MANIFEST_NONE; private short mDecodeAssets = DECODE_ASSETS_FULL; private boolean mForceDelete = false; + private boolean mManifestOnly = false; private boolean mKeepBrokenResources = false; private boolean mBakDeb = true; private Collection mUncompressedFiles; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java index 650e8119..566692f0 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java @@ -154,12 +154,12 @@ public class ARSCDecoder { while (type == Header.TYPE_TYPE) { readTableType(); - + // skip "TYPE 8 chunks" and/or padding data at the end of this chunk if (mCountIn.getCount() < mHeader.endPosition) { mCountIn.skip(mHeader.endPosition - mCountIn.getCount()); } - + type = nextChunk().type; addMissingResSpecs(); @@ -223,33 +223,55 @@ public class ARSCDecoder { } mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags); + HashMap offsetsToEntryData = + new HashMap(); + + for (int offset : entryOffsets) { + if (offset == -1 || offsetsToEntryData.containsKey(offset)) { + continue; + } + + offsetsToEntryData.put(offset, readEntryData()); + } for (int i = 0; i < entryOffsets.length; i++) { if (entryOffsets[i] != -1) { mMissingResSpecs[i] = false; mResId = (mResId & 0xffff0000) | i; - readEntry(); + EntryData entryData = offsetsToEntryData.get(entryOffsets[i]); + readEntry(entryData); } } return mType; } - private void readEntry() throws IOException, AndrolibException { + private class EntryData { + public short mFlags; + public int mSpecNamesId; + public ResValue mValue; + } + + private EntryData readEntryData() throws IOException, AndrolibException { short size = mIn.readShort(); if (size < 0) { throw new AndrolibException("Entry size is under 0 bytes."); } + short flags = mIn.readShort(); int specNamesId = mIn.readInt(); - - // If we are here, we probably already inserted any remaining dummy resources. No need to parse - // any resources that doesn't have type information - if (mCountIn.getCount() == mHeader.endPosition) { - return; - } - ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ? readValue() : readComplexEntry(); + EntryData entryData = new EntryData(); + entryData.mFlags = flags; + entryData.mSpecNamesId = specNamesId; + entryData.mValue = value; + return entryData; + } + + private void readEntry(EntryData entryData) throws AndrolibException { + short flags = entryData.mFlags; + int specNamesId = entryData.mSpecNamesId; + ResValue value = entryData.mValue; if (mTypeSpec.isString() && value instanceof ResFileValue) { value = new ResStringValue(value.toString(), ((ResFileValue) value).getRawIntValue());