mirror of
https://github.com/revanced/Apktool.git
synced 2025-02-09 18:36:48 +01:00
Merge pull request #90 from iBotPeaches/multiple_dex
Multiple Dex Support
This commit is contained in:
commit
a6ce26622d
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
@ -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);
|
||||||
|
@ -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());
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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());
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user