Merge pull request #90 from iBotPeaches/multiple_dex

Multiple Dex Support
This commit is contained in:
Connor Tumbleson 2014-08-16 09:38:31 -05:00
commit a6ce26622d
8 changed files with 179 additions and 103 deletions

View File

@ -46,16 +46,21 @@ import java.util.zip.ZipFile;
public final class DexFileFactory { public final class DexFileFactory {
@Nonnull @Nonnull
public static DexBackedDexFile loadDexFile(String path, int api) throws IOException { public static DexBackedDexFile loadDexFile(String path, int api) throws IOException {
return loadDexFile(new File(path), new Opcodes(api)); return loadDexFile(new File(path), "classes.dex", new Opcodes(api));
} }
@Nonnull @Nonnull
public static DexBackedDexFile loadDexFile(File dexFile, int api) throws IOException { public static DexBackedDexFile loadDexFile(File dexFile, int api) throws IOException {
return loadDexFile(dexFile, new Opcodes(api)); return loadDexFile(dexFile, "classes.dex", new Opcodes(api));
} }
@Nonnull @Nonnull
public static DexBackedDexFile loadDexFile(File dexFile, @Nonnull Opcodes opcodes) throws IOException { public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, int api) throws IOException {
return loadDexFile(dexFile, dexEntry, new Opcodes(api));
}
@Nonnull
public static DexBackedDexFile loadDexFile(File dexFile, String filename, @Nonnull Opcodes opcodes) throws IOException {
ZipFile zipFile = null; ZipFile zipFile = null;
boolean isZipFile = false; boolean isZipFile = false;
try { try {
@ -63,16 +68,16 @@ public final class DexFileFactory {
// if we get here, it's safe to assume we have a zip file // if we get here, it's safe to assume we have a zip file
isZipFile = true; isZipFile = true;
ZipEntry zipEntry = zipFile.getEntry("classes.dex"); ZipEntry zipEntry = zipFile.getEntry(filename);
if (zipEntry == null) { if (zipEntry == null) {
throw new NoClassesDexException("zip file %s does not contain a classes.dex file", dexFile.getName()); throw new NoClassesDexException("zip file %s does not contain a classes.dex file", dexFile.getName());
} }
long fileLength = zipEntry.getSize(); long fileLength = zipEntry.getSize();
if (fileLength < 40) { if (fileLength < 40) {
throw new ExceptionWithContext( throw new ExceptionWithContext(
"The classes.dex file in %s is too small to be a valid dex file", dexFile.getName()); "The " + filename + " file in %s is too small to be a valid dex file", dexFile.getName());
} else if (fileLength > Integer.MAX_VALUE) { } else if (fileLength > Integer.MAX_VALUE) {
throw new ExceptionWithContext("The classes.dex file in %s is too large to read in", dexFile.getName()); throw new ExceptionWithContext("The " + filename + " file in %s is too large to read in", dexFile.getName());
} }
byte[] dexBytes = new byte[(int)fileLength]; byte[] dexBytes = new byte[(int)fileLength];
ByteStreams.readFully(zipFile.getInputStream(zipEntry), dexBytes); ByteStreams.readFully(zipFile.getInputStream(zipEntry), dexBytes);

View File

@ -30,6 +30,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.logging.*; import java.util.logging.*;
import brut.directory.DirectoryException;
import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.HelpFormatter;
@ -187,6 +188,9 @@ public class Main {
} catch (IOException ex) { } catch (IOException ex) {
System.err.println("Could not modify file. Please ensure you have permission."); System.err.println("Could not modify file. Please ensure you have permission.");
System.exit(1); System.exit(1);
} catch (DirectoryException ex) {
System.err.println("Could not modify internal dex files. Please ensure you have permission.");
System.exit(1);
} }
} }

View File

@ -48,7 +48,8 @@ public class Androlib {
private final AndrolibResources mAndRes = new AndrolibResources(); private final AndrolibResources mAndRes = new AndrolibResources();
protected final ResUnknownFiles mResUnknownFiles = new ResUnknownFiles(); protected final ResUnknownFiles mResUnknownFiles = new ResUnknownFiles();
public ResTable getResTable(ExtFile apkFile) throws AndrolibException { public ResTable getResTable(ExtFile apkFile)
throws AndrolibException {
return mAndRes.getResTable(apkFile, true); return mAndRes.getResTable(apkFile, true);
} }
@ -57,25 +58,24 @@ public class Androlib {
return mAndRes.getResTable(apkFile, loadMainPkg); return mAndRes.getResTable(apkFile, loadMainPkg);
} }
public void decodeSourcesRaw(ExtFile apkFile, File outDir, boolean debug) public void decodeSourcesRaw(ExtFile apkFile, File outDir, String filename)
throws AndrolibException { throws AndrolibException {
try { try {
Directory apk = apkFile.getDirectory();
LOGGER.info("Copying raw classes.dex file..."); LOGGER.info("Copying raw classes.dex file...");
apkFile.getDirectory().copyToDir(outDir, "classes.dex"); apkFile.getDirectory().copyToDir(outDir, filename);
} catch (DirectoryException ex) { } catch (DirectoryException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
public void decodeSourcesSmali(File apkFile, File outDir, boolean debug, String debugLinePrefix, public void decodeSourcesSmali(File apkFile, File outDir, String filename, boolean debug, String debugLinePrefix,
boolean bakdeb, int api) throws AndrolibException { boolean bakdeb, int api) throws AndrolibException {
try { try {
File smaliDir = new File(outDir, SMALI_DIRNAME); File smaliDir = new File(outDir, SMALI_DIRNAME + "_" + filename.substring(0, filename.indexOf(".")));
OS.rmdir(smaliDir); OS.rmdir(smaliDir);
smaliDir.mkdirs(); smaliDir.mkdirs();
LOGGER.info("Baksmaling..."); LOGGER.info("Baksmaling " + filename + "...");
SmaliDecoder.decode(apkFile, smaliDir, debug, debugLinePrefix, bakdeb, api); SmaliDecoder.decode(apkFile, smaliDir, filename, debug, debugLinePrefix, bakdeb, api);
} catch (BrutException ex) { } catch (BrutException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
@ -98,15 +98,14 @@ public class Androlib {
} }
} }
public void decodeManifestFull(ExtFile apkFile, File outDir, public void decodeManifestFull(ExtFile apkFile, File outDir, ResTable resTable)
ResTable resTable) throws AndrolibException { throws AndrolibException {
mAndRes.decodeManifest(resTable, apkFile, outDir); mAndRes.decodeManifest(resTable, apkFile, outDir);
} }
public void decodeResourcesRaw(ExtFile apkFile, File outDir) public void decodeResourcesRaw(ExtFile apkFile, File outDir)
throws AndrolibException { throws AndrolibException {
try { try {
// Directory apk = apkFile.getDirectory();
LOGGER.info("Copying raw resources..."); LOGGER.info("Copying raw resources...");
apkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES); apkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES);
} catch (DirectoryException ex) { } catch (DirectoryException ex) {
@ -114,8 +113,8 @@ public class Androlib {
} }
} }
public void decodeResourcesFull(ExtFile apkFile, File outDir, public void decodeResourcesFull(ExtFile apkFile, File outDir, ResTable resTable)
ResTable resTable) throws AndrolibException { throws AndrolibException {
mAndRes.decode(resTable, apkFile, outDir); mAndRes.decode(resTable, apkFile, outDir);
} }
@ -162,15 +161,15 @@ public class Androlib {
// 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);
for (String file : files) { for (String file : files) {
if (!isAPKFileNames(file)) { if (!isAPKFileNames(file) && !file.endsWith(".dex")) {
// copy file out of archive into special "unknown" folder // copy file out of archive into special "unknown" folder
// to be re-included on build // to be re-included on build
unk.copyToDir(unknownOut, file); unk.copyToDir(unknownOut, file);
try { try {
// ignore encryption // ignore encryption
apkZipFile.getEntry(file.toString()).getGeneralPurposeBit().useEncryption(false); apkZipFile.getEntry(file).getGeneralPurposeBit().useEncryption(false);
invZipFile = apkZipFile.getEntry(file.toString()); invZipFile = apkZipFile.getEntry(file);
// lets record the name of the file, and its compression type // lets record the name of the file, and its compression type
// so that we may re-include it the same way // so that we may re-include it the same way
@ -256,24 +255,23 @@ public class Androlib {
} }
} }
public void build(File appDir, File outFile, public void build(File appDir, File outFile, HashMap<String, Boolean> flags, String aaptPath)
HashMap<String, Boolean> flags, String aaptPath)
throws BrutException { throws BrutException {
build(new ExtFile(appDir), outFile, flags, aaptPath); build(new ExtFile(appDir), outFile, flags, aaptPath);
} }
public void build(ExtFile appDir, File outFile, public void build(ExtFile appDir, File outFile, HashMap<String, Boolean> flags, String aaptPath)
HashMap<String, Boolean> flags, String aaptPath)
throws BrutException { throws BrutException {
LOGGER.info("Using Apktool " + Androlib.getVersion() + " on " + appDir.getName()); LOGGER.info("Using Apktool " + Androlib.getVersion() + " on " + appDir.getName());
mAaptPath = aaptPath; mAaptPath = aaptPath;
Map<String, Object> meta = readMetaFile(appDir); Map<String, Object> meta = readMetaFile(appDir);
Object t1 = meta.get("isFrameworkApk"); Object t1 = meta.get("isFrameworkApk");
flags.put("framework", t1 == null ? false : (Boolean) t1); flags.put("framework", t1 == null ? false : (Boolean) t1);
flags.put("compression", meta.get("compressionType") == null ? false flags.put("compression", meta.get("compressionType") == null
? false
: Boolean.valueOf(meta.get("compressionType").toString())); : Boolean.valueOf(meta.get("compressionType").toString()));
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"));
mAndRes.setPackageInfo((Map<String, String>) meta.get("packageInfo")); mAndRes.setPackageInfo((Map<String, String>) meta.get("packageInfo"));
@ -287,8 +285,8 @@ public class Androlib {
new File(appDir, APK_DIRNAME).mkdirs(); new File(appDir, APK_DIRNAME).mkdirs();
buildSources(appDir, flags); buildSources(appDir, flags);
buildResources(appDir, flags, buildNonDefaultSources(appDir, flags);
(Map<String, Object>) meta.get("usesFramework")); buildResources(appDir, flags, (Map<String, Object>) meta.get("usesFramework"));
buildLib(appDir, flags); buildLib(appDir, flags);
buildCopyOriginalFiles(appDir, flags); buildCopyOriginalFiles(appDir, flags);
buildApk(appDir, outFile, flags); buildApk(appDir, outFile, flags);
@ -300,44 +298,61 @@ public class Androlib {
public void buildSources(File appDir, HashMap<String, Boolean> flags) public void buildSources(File appDir, HashMap<String, Boolean> flags)
throws AndrolibException { throws AndrolibException {
if (!buildSourcesRaw(appDir, flags) if (!buildSourcesRaw(appDir, "classes.dex", flags) && !buildSourcesSmali(appDir, "smali", "classes.dex", flags) && !buildSourcesJava(appDir, flags)) {
&& !buildSourcesSmali(appDir, flags)
&& !buildSourcesJava(appDir, flags)) {
LOGGER.warning("Could not find sources"); LOGGER.warning("Could not find sources");
} }
} }
public boolean buildSourcesRaw(File appDir, HashMap<String, Boolean> flags) public void buildNonDefaultSources(ExtFile appDir, HashMap<String, Boolean> flags)
throws AndrolibException { throws AndrolibException {
try { try {
File working = new File(appDir, "classes.dex"); Map<String, Directory> dirs = appDir.getDirectory().getDirs();
for (Map.Entry<String, Directory> directory : dirs.entrySet()) {
String name = directory.getKey();
if (name.startsWith("smali_")) {
String filename = name.substring(name.indexOf("_") + 1) + ".dex";
if (!buildSourcesRaw(appDir, filename, flags) && !buildSourcesSmali(appDir, name, filename, flags) && !buildSourcesJava(appDir, flags)) {
LOGGER.warning("Could not find sources");
}
}
}
} catch(DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean buildSourcesRaw(File appDir, String filename, HashMap<String, Boolean> flags)
throws AndrolibException {
File working = new File(appDir, filename);
if (!working.exists()) { if (!working.exists()) {
return false; return false;
} }
File stored = new File(appDir, APK_DIRNAME + "/classes.dex"); File stored = new File(appDir, APK_DIRNAME + "/" + filename);
if (flags.get("forceBuildAll") || isModified(working, stored)) { if (flags.get("forceBuildAll") || isModified(working, stored)) {
LOGGER.info("Copying classes.dex file..."); LOGGER.info("Copying " + appDir.toString() + " " + filename + " file...");
BrutIO.copyAndClose(new FileInputStream(working), try {
new FileOutputStream(stored)); BrutIO.copyAndClose(new FileInputStream(working), new FileOutputStream(stored));
}
return true; return true;
} catch (IOException ex) { } catch (IOException ex) {
throw new AndrolibException(ex); throw new AndrolibException(ex);
} }
} }
return true;
}
public boolean buildSourcesSmali(File appDir, HashMap<String, Boolean> flags) public boolean buildSourcesSmali(File appDir, String folder, String filename, HashMap<String, Boolean> flags)
throws AndrolibException { throws AndrolibException {
ExtFile smaliDir = new ExtFile(appDir, "smali"); ExtFile smaliDir = new ExtFile(appDir, folder);
if (!smaliDir.exists()) { if (!smaliDir.exists()) {
return false; return false;
} }
File dex = new File(appDir, APK_DIRNAME + "/classes.dex"); File dex = new File(appDir, APK_DIRNAME + "/" + filename);
if (!flags.get("forceBuildAll")) { if (!flags.get("forceBuildAll")) {
LOGGER.info("Checking whether sources has changed..."); LOGGER.info("Checking whether sources has changed...");
} }
if (flags.get("forceBuildAll") || isModified(smaliDir, dex)) { if (flags.get("forceBuildAll") || isModified(smaliDir, dex)) {
LOGGER.info("Smaling..."); LOGGER.info("Smaling " + folder + " folder into " + filename +"...");
dex.delete(); dex.delete();
SmaliBuilder.build(smaliDir, dex, flags); SmaliBuilder.build(smaliDir, dex, flags);
} }
@ -362,17 +377,16 @@ public class Androlib {
return true; return true;
} }
public void buildResources(ExtFile appDir, HashMap<String, Boolean> flags, public void buildResources(ExtFile appDir, HashMap<String, Boolean> flags, Map<String, Object> usesFramework)
Map<String, Object> usesFramework) throws BrutException { throws BrutException {
if (!buildResourcesRaw(appDir, flags) if (!buildResourcesRaw(appDir, flags) && !buildResourcesFull(appDir, flags, usesFramework)
&& !buildResourcesFull(appDir, flags, usesFramework)
&& !buildManifest(appDir, flags, usesFramework)) { && !buildManifest(appDir, flags, usesFramework)) {
LOGGER.warning("Could not find resources"); LOGGER.warning("Could not find resources");
} }
} }
public boolean buildResourcesRaw(ExtFile appDir, public boolean buildResourcesRaw(ExtFile appDir, HashMap<String, Boolean> flags)
HashMap<String, Boolean> flags) throws AndrolibException { throws AndrolibException {
try { try {
if (!new File(appDir, "resources.arsc").exists()) { if (!new File(appDir, "resources.arsc").exists()) {
return false; return false;
@ -394,8 +408,7 @@ public class Androlib {
} }
} }
public boolean buildResourcesFull(File appDir, public boolean buildResourcesFull(File appDir, HashMap<String, Boolean> flags, Map<String, Object> usesFramework)
HashMap<String, Boolean> flags, Map<String, Object> usesFramework)
throws AndrolibException { throws AndrolibException {
try { try {
if (!new File(appDir, "res").exists()) { if (!new File(appDir, "res").exists()) {
@ -440,8 +453,8 @@ public class Androlib {
} }
} }
public boolean buildManifestRaw(ExtFile appDir, public boolean buildManifestRaw(ExtFile appDir, HashMap<String, Boolean> flags)
HashMap<String, Boolean> flags) throws AndrolibException { throws AndrolibException {
try { try {
File apkDir = new File(appDir, APK_DIRNAME); File apkDir = new File(appDir, APK_DIRNAME);
LOGGER.info("Copying raw AndroidManifest.xml..."); LOGGER.info("Copying raw AndroidManifest.xml...");
@ -452,8 +465,7 @@ public class Androlib {
} }
} }
public boolean buildManifest(ExtFile appDir, public boolean buildManifest(ExtFile appDir, HashMap<String, Boolean> flags, Map<String, Object> usesFramework)
HashMap<String, Boolean> flags, Map<String, Object> usesFramework)
throws BrutException { throws BrutException {
try { try {
if (!new File(appDir, "AndroidManifest.xml").exists()) { if (!new File(appDir, "AndroidManifest.xml").exists()) {
@ -519,8 +531,8 @@ public class Androlib {
} }
} }
public void buildCopyOriginalFiles(File appDir, public void buildCopyOriginalFiles(File appDir, HashMap<String, Boolean> flags)
HashMap<String, Boolean> flags) throws AndrolibException { throws AndrolibException {
if (flags.get("copyOriginal")) { if (flags.get("copyOriginal")) {
File originalDir = new File(appDir, "original"); File originalDir = new File(appDir, "original");
if(originalDir.exists()) { if(originalDir.exists()) {
@ -708,8 +720,7 @@ public class Androlib {
private String mAaptPath = null; private String mAaptPath = null;
private Path mPath = null; private Path mPath = null;
private final static Logger LOGGER = Logger.getLogger(Androlib.class private final static Logger LOGGER = Logger.getLogger(Androlib.class.getName());
.getName());
private final static String SMALI_DIRNAME = "smali"; private final static String SMALI_DIRNAME = "smali";
private final static String APK_DIRNAME = "build/apk"; private final static String APK_DIRNAME = "build/apk";

View File

@ -69,7 +69,7 @@ public class ApkDecoder {
mApi = api; mApi = api;
} }
public void decode() throws AndrolibException, IOException { public void decode() throws AndrolibException, IOException, DirectoryException {
File outDir = getOutDir(); File outDir = getOutDir();
if (!mForceDelete && outDir.exists()) { if (!mForceDelete && outDir.exists()) {
@ -134,10 +134,10 @@ public class ApkDecoder {
if (hasSources()) { if (hasSources()) {
switch (mDecodeSources) { switch (mDecodeSources) {
case DECODE_SOURCES_NONE: case DECODE_SOURCES_NONE:
mAndrolib.decodeSourcesRaw(mApkFile, outDir, mDebug); mAndrolib.decodeSourcesRaw(mApkFile, outDir, "classes.dex");
break; break;
case DECODE_SOURCES_SMALI: case DECODE_SOURCES_SMALI:
mAndrolib.decodeSourcesSmali(mApkFile, outDir, mDebug, mDebugLinePrefix, mBakDeb, mApi); mAndrolib.decodeSourcesSmali(mApkFile, outDir, "classes.dex", mDebug, mDebugLinePrefix, mBakDeb, mApi);
break; break;
case DECODE_SOURCES_JAVA: case DECODE_SOURCES_JAVA:
mAndrolib.decodeSourcesJava(mApkFile, outDir, mDebug); mAndrolib.decodeSourcesJava(mApkFile, outDir, mDebug);
@ -145,6 +145,28 @@ public class ApkDecoder {
} }
} }
if (hasMultipleSources()) {
// foreach unknown dex file in root, lets disassemble it
Set<String> files = mApkFile.getDirectory().getFiles(true);
for (String file : files) {
if (file.endsWith(".dex")) {
if (! file.equalsIgnoreCase("classes.dex")) {
switch(mDecodeSources) {
case DECODE_SOURCES_NONE:
mAndrolib.decodeSourcesRaw(mApkFile, outDir, file);
break;
case DECODE_SOURCES_SMALI:
mAndrolib.decodeSourcesSmali(mApkFile, outDir, file, mDebug, mDebugLinePrefix, mBakDeb, mApi);
break;
case DECODE_SOURCES_JAVA:
mAndrolib.decodeSourcesJava(mApkFile, outDir, mDebug);
break;
}
}
}
}
}
mAndrolib.decodeRawFiles(mApkFile, outDir); mAndrolib.decodeRawFiles(mApkFile, outDir);
mAndrolib.decodeUnknownFiles(mApkFile, outDir, mResTable); mAndrolib.decodeUnknownFiles(mApkFile, outDir, mResTable);
mAndrolib.writeOriginalFiles(mApkFile, outDir); mAndrolib.writeOriginalFiles(mApkFile, outDir);
@ -233,6 +255,23 @@ public class ApkDecoder {
} }
} }
public boolean hasMultipleSources() throws AndrolibException {
try {
Set<String> files = mApkFile.getDirectory().getFiles(true);
for (String file : files) {
if (file.endsWith(".dex")) {
if ( ! file.equalsIgnoreCase("classes.dex")) {
return true;
}
}
}
return false;
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean hasManifest() throws AndrolibException { public boolean hasManifest() throws AndrolibException {
try { try {
return mApkFile.getDirectory().containsFile("AndroidManifest.xml"); return mApkFile.getDirectory().containsFile("AndroidManifest.xml");
@ -268,10 +307,8 @@ public class ApkDecoder {
meta.put("version", Androlib.getVersion()); meta.put("version", Androlib.getVersion());
meta.put("apkFileName", mApkFile.getName()); meta.put("apkFileName", mApkFile.getName());
if (mDecodeResources != DECODE_RESOURCES_NONE if (mDecodeResources != DECODE_RESOURCES_NONE && (hasManifest() || hasResources())) {
&& (hasManifest() || hasResources())) { meta.put("isFrameworkApk", mAndrolib.isFrameworkApk(getResTable()));
meta.put("isFrameworkApk",
Boolean.valueOf(mAndrolib.isFrameworkApk(getResTable())));
putUsesFramework(meta); putUsesFramework(meta);
putSdkInfo(meta); putSdkInfo(meta);
putPackageInfo(meta); putPackageInfo(meta);

View File

@ -36,13 +36,12 @@ import org.jf.dexlib2.writer.io.FileDataStore;
*/ */
public class SmaliBuilder { public class SmaliBuilder {
public static void build(ExtFile smaliDir, File dexFile, public static void build(ExtFile smaliDir, File dexFile, HashMap<String, Boolean> flags)
HashMap<String, Boolean> flags) throws AndrolibException { throws AndrolibException {
new SmaliBuilder(smaliDir, dexFile, flags).build(); new SmaliBuilder(smaliDir, dexFile, flags).build();
} }
private SmaliBuilder(ExtFile smaliDir, File dexFile, private SmaliBuilder(ExtFile smaliDir, File dexFile, HashMap<String, Boolean> flags) {
HashMap<String, Boolean> flags) {
mSmaliDir = smaliDir; mSmaliDir = smaliDir;
mDexFile = dexFile; mDexFile = dexFile;
mFlags = flags; mFlags = flags;
@ -61,8 +60,8 @@ public class SmaliBuilder {
} }
} }
private void buildFile(String fileName, DexBuilder dexBuilder) throws AndrolibException, private void buildFile(String fileName, DexBuilder dexBuilder)
IOException { throws AndrolibException, IOException {
File inFile = new File(mSmaliDir, fileName); File inFile = new File(mSmaliDir, fileName);
InputStream inStream = new FileInputStream(inFile); InputStream inStream = new FileInputStream(inFile);
@ -96,8 +95,7 @@ public class SmaliBuilder {
out.append(".source \"").append(inFile.getName()).append("\"\n"); out.append(".source \"").append(inFile.getName()).append("\"\n");
while (it.hasNext()) { while (it.hasNext()) {
String line = it.next().split("//", 2)[1].trim(); String line = it.next().split("//", 2)[1].trim();
if (line.isEmpty() || line.charAt(0) == '#' if (line.isEmpty() || line.charAt(0) == '#' || line.startsWith(".source")) {
|| line.startsWith(".source")) {
continue; continue;
} }
if (line.startsWith(".method ")) { if (line.startsWith(".method ")) {
@ -123,6 +121,5 @@ public class SmaliBuilder {
private final File mDexFile; private final File mDexFile;
private final HashMap<String, Boolean> mFlags; private final HashMap<String, Boolean> mFlags;
private final static Logger LOGGER = Logger.getLogger(SmaliBuilder.class private final static Logger LOGGER = Logger.getLogger(SmaliBuilder.class.getName());
.getName());
} }

View File

@ -41,15 +41,16 @@ import java.nio.file.attribute.BasicFileAttributes;
*/ */
public class SmaliDecoder { public class SmaliDecoder {
public static void decode(File apkFile, File outDir, boolean debug, String debugLinePrefix, public static void decode(File apkFile, File outDir, String dexName, boolean debug, String debugLinePrefix,
boolean bakdeb, int api) throws AndrolibException { boolean bakdeb, int api) throws AndrolibException {
new SmaliDecoder(apkFile, outDir, debug, debugLinePrefix, bakdeb, api).decode(); new SmaliDecoder(apkFile, outDir, dexName, debug, debugLinePrefix, bakdeb, api).decode();
} }
private SmaliDecoder(File apkFile, File outDir, boolean debug, String debugLinePrefix, private SmaliDecoder(File apkFile, File outDir, String dexName, boolean debug, String debugLinePrefix,
boolean bakdeb, int api) { boolean bakdeb, int api) {
mApkFile = apkFile; mApkFile = apkFile;
mOutDir = outDir.toPath(); mOutDir = outDir.toPath();
mDexFile = dexName;
mDebug = debug; mDebug = debug;
mDebugLinePrefix = debugLinePrefix; mDebugLinePrefix = debugLinePrefix;
mBakDeb = bakdeb; mBakDeb = bakdeb;
@ -90,7 +91,7 @@ public class SmaliDecoder {
} }
// create the dex // create the dex
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(mApkFile, mApi); DexBackedDexFile dexFile = DexFileFactory.loadDexFile(mApkFile, mDexFile, mApi);
if (dexFile.isOdexFile()) { if (dexFile.isOdexFile()) {
throw new AndrolibException("Warning: You are disassembling an odex file without deodexing it."); throw new AndrolibException("Warning: You are disassembling an odex file without deodexing it.");
@ -115,10 +116,10 @@ public class SmaliDecoder {
private final Path mOutDir; private final Path mOutDir;
private final boolean mDebug; private final boolean mDebug;
private final String mDebugLinePrefix; private final String mDebugLinePrefix;
private final String mDexFile;
private final boolean mBakDeb; private final boolean mBakDeb;
private final int mApi; private final int mApi;
private class SmaliFileVisitor extends SimpleFileVisitor<Path> { private class SmaliFileVisitor extends SimpleFileVisitor<Path> {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

View File

@ -40,13 +40,11 @@ public class BuildAndDecodeTest {
sTestOrigDir = new ExtFile(sTmpDir, "testapp-orig"); sTestOrigDir = new ExtFile(sTmpDir, "testapp-orig");
sTestNewDir = new ExtFile(sTmpDir, "testapp-new"); sTestNewDir = new ExtFile(sTmpDir, "testapp-new");
LOGGER.info("Unpacking testapp..."); LOGGER.info("Unpacking testapp...");
TestUtils.copyResourceDir(BuildAndDecodeTest.class, TestUtils.copyResourceDir(BuildAndDecodeTest.class, "brut/apktool/testapp/", sTestOrigDir);
"brut/apktool/testapp/", sTestOrigDir);
LOGGER.info("Building testapp.apk..."); LOGGER.info("Building testapp.apk...");
File testApk = new File(sTmpDir, "testapp.apk"); File testApk = new File(sTmpDir, "testapp.apk");
new Androlib().build(sTestOrigDir, testApk, new Androlib().build(sTestOrigDir, testApk, TestUtils.returnStockHashMap(),"");
TestUtils.returnStockHashMap(),"");
LOGGER.info("Decoding testapp.apk..."); LOGGER.info("Decoding testapp.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk); ApkDecoder apkDecoder = new ApkDecoder(testApk);
@ -219,8 +217,19 @@ public class BuildAndDecodeTest {
compareUnknownFiles(); compareUnknownFiles();
} }
@Test
public void multipleDexTest() throws BrutException, IOException {
compareBinaryFolder("/smali_classes2", false);
}
@Test
public void singleDexTest() throws BrutException, IOException {
compareBinaryFolder("/smali", false);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void compareUnknownFiles() throws BrutException, IOException { private void compareUnknownFiles()
throws BrutException, IOException {
Map<String, Object> control = new Androlib().readMetaFile(sTestOrigDir); Map<String, Object> control = new Androlib().readMetaFile(sTestOrigDir);
Map<String, Object> test = new Androlib().readMetaFile(sTestNewDir); Map<String, Object> test = new Androlib().readMetaFile(sTestNewDir);
assertTrue(control.containsKey("unknownFiles")); assertTrue(control.containsKey("unknownFiles"));
@ -231,8 +240,8 @@ public class BuildAndDecodeTest {
assertTrue(control_files.size() == test_files.size()); assertTrue(control_files.size() == test_files.size());
} }
private boolean compareBinaryFolder(String path, boolean res) throws BrutException, IOException { private boolean compareBinaryFolder(String path, boolean res)
throws BrutException, IOException {
String tmp = ""; String tmp = "";
if (res) { if (res) {
tmp = File.separatorChar + "res" + File.separatorChar; tmp = File.separatorChar + "res" + File.separatorChar;
@ -265,8 +274,7 @@ public class BuildAndDecodeTest {
} }
private void compareValuesFiles(String path) throws BrutException { private void compareValuesFiles(String path) throws BrutException {
compareXmlFiles("res/" + path, new ElementNameAndAttributeQualifier( compareXmlFiles("res/" + path, new ElementNameAndAttributeQualifier("name"));
"name"));
} }
private void compareXmlFiles(String path) throws BrutException { private void compareXmlFiles(String path) throws BrutException {
@ -291,14 +299,12 @@ public class BuildAndDecodeTest {
diff.overrideElementQualifier(qualifier); diff.overrideElementQualifier(qualifier);
} }
assertTrue(path + ": " + diff.getAllDifferences().toString(), assertTrue(path + ": " + diff.getAllDifferences().toString(), diff.similar());
diff.similar());
} }
private static ExtFile sTmpDir; private static ExtFile sTmpDir;
private static ExtFile sTestOrigDir; private static ExtFile sTestOrigDir;
private static ExtFile sTestNewDir; private static ExtFile sTestNewDir;
private final static Logger LOGGER = Logger private final static Logger LOGGER = Logger.getLogger(BuildAndDecodeTest.class.getName());
.getLogger(BuildAndDecodeTest.class.getName());
} }

View File

@ -0,0 +1,15 @@
.class public LHelloDualDexSupport;
.super Ljava/lang/Object;
.method public static main([Ljava/lang/String;)V
.registers 2
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const/high16 v1, 0x7f020000
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method