core: refactor file loading, add 'aar' support (fix #95)
This commit is contained in:
@@ -116,12 +116,7 @@ public final class JadxDecompiler {
|
||||
inputFiles.clear();
|
||||
for (File file : files) {
|
||||
try {
|
||||
InputFile inputFile = new InputFile(file);
|
||||
inputFiles.add(inputFile);
|
||||
while (inputFile.nextDexIndex != -1) {
|
||||
inputFile = new InputFile(file, inputFile.nextDexIndex);
|
||||
inputFiles.add(inputFile);
|
||||
}
|
||||
InputFile.addFilesFrom(file, inputFiles);
|
||||
} catch (IOException e) {
|
||||
throw new JadxException("Error load file: " + file, e);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package jadx.api;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".class"),
|
||||
CODE(".dex", ".jar", ".class"),
|
||||
MANIFEST("AndroidManifest.xml"),
|
||||
XML(".xml"), // TODO binary or not?
|
||||
ARSC(".arsc"), // TODO decompile !!!
|
||||
XML(".xml"),
|
||||
ARSC(".arsc"),
|
||||
FONT(".ttf"),
|
||||
IMG(".png", ".gif", ".jpg"),
|
||||
LIB(".so"),
|
||||
|
||||
@@ -85,12 +85,7 @@ public final class ResourcesLoader {
|
||||
return decodeStream(rf, new ResourceDecoder() {
|
||||
@Override
|
||||
public ResContainer decode(long size, InputStream is) throws IOException {
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
new CodeWriter().add("File too big, size: "
|
||||
+ String.format("%.2f KB", size / 1024.)));
|
||||
}
|
||||
return loadContent(jadxRef, rf, is);
|
||||
return loadContent(jadxRef, rf, is, size);
|
||||
}
|
||||
});
|
||||
} catch (JadxException e) {
|
||||
@@ -103,7 +98,7 @@ public final class ResourcesLoader {
|
||||
}
|
||||
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream) throws IOException {
|
||||
InputStream inputStream, long size) throws IOException {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
@@ -113,6 +108,10 @@ public final class ResourcesLoader {
|
||||
case ARSC:
|
||||
return new ResTableParser().decodeFiles(inputStream);
|
||||
}
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
|
||||
}
|
||||
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,7 @@ public class ConvertToClsSet {
|
||||
if (f.isDirectory()) {
|
||||
addFilesFromDirectory(f, inputFiles);
|
||||
} else {
|
||||
InputFile inputFile = new InputFile(f);
|
||||
inputFiles.add(inputFile);
|
||||
while (inputFile.nextDexIndex != -1) {
|
||||
inputFile = new InputFile(f, inputFile.nextDexIndex);
|
||||
inputFiles.add(inputFile);
|
||||
}
|
||||
InputFile.addFilesFrom(f, inputFiles);
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
@@ -58,8 +53,7 @@ public class ConvertToClsSet {
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
private static void addFilesFromDirectory(File dir,
|
||||
List<InputFile> inputFiles) throws IOException, DecodeException {
|
||||
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
@@ -67,19 +61,13 @@ public class ConvertToClsSet {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
addFilesFromDirectory(file, inputFiles);
|
||||
}
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(".dex")
|
||||
|| fileName.endsWith(".jar")
|
||||
|| fileName.endsWith(".apk")) {
|
||||
InputFile inputFile = new InputFile(file);
|
||||
inputFiles.add(inputFile);
|
||||
while (inputFile.nextDexIndex != -1) {
|
||||
inputFile = new InputFile(file, inputFile.nextDexIndex);
|
||||
inputFiles.add(inputFile);
|
||||
} else {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, inputFiles);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import jadx.core.dex.info.InfoStorage;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.utils.files.DexFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -34,7 +34,7 @@ public class DexNode {
|
||||
|
||||
private final RootNode root;
|
||||
private final Dex dexBuf;
|
||||
private final InputFile file;
|
||||
private final DexFile file;
|
||||
|
||||
private final List<ClassNode> classes = new ArrayList<ClassNode>();
|
||||
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>();
|
||||
@@ -43,10 +43,10 @@ public class DexNode {
|
||||
|
||||
private final InfoStorage infoStorage = new InfoStorage();
|
||||
|
||||
public DexNode(RootNode root, InputFile input) {
|
||||
public DexNode(RootNode root, DexFile input) {
|
||||
this.root = root;
|
||||
this.file = input;
|
||||
this.dexBuf = input.getDexBuffer();
|
||||
this.dexBuf = input.getDexBuf();
|
||||
}
|
||||
|
||||
public void loadClasses() throws DecodeException {
|
||||
@@ -163,7 +163,7 @@ public class DexNode {
|
||||
return infoStorage;
|
||||
}
|
||||
|
||||
public InputFile getInputFile() {
|
||||
public DexFile getDexFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.DexFile;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
@@ -42,16 +43,18 @@ public class RootNode {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void load(List<InputFile> dexFiles) throws DecodeException {
|
||||
dexNodes = new ArrayList<DexNode>(dexFiles.size());
|
||||
for (InputFile dex : dexFiles) {
|
||||
DexNode dexNode;
|
||||
try {
|
||||
dexNode = new DexNode(this, dex);
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException("Error decode file: " + dex, e);
|
||||
public void load(List<InputFile> inputFiles) throws DecodeException {
|
||||
dexNodes = new ArrayList<DexNode>();
|
||||
for (InputFile input : inputFiles) {
|
||||
for (DexFile dexFile : input.getDexFiles()) {
|
||||
try {
|
||||
LOG.debug("Load: {}", dexFile);
|
||||
DexNode dexNode = new DexNode(this, dexFile);
|
||||
dexNodes.add(dexNode);
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException("Error decode file: " + dexFile, e);
|
||||
}
|
||||
}
|
||||
dexNodes.add(dexNode);
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
dexNode.loadClasses();
|
||||
|
||||
@@ -14,11 +14,13 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOCase;
|
||||
|
||||
public class RenameVisitor extends AbstractVisitor {
|
||||
@@ -31,10 +33,10 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
public void init(RootNode root) {
|
||||
IJadxArgs args = root.getArgs();
|
||||
|
||||
final String firstInputFileName = root.getDexNodes().get(0).getInputFile().getFile().getAbsolutePath();
|
||||
final String inputPath = org.apache.commons.io.FilenameUtils.getFullPathNoEndSeparator(
|
||||
firstInputFileName);
|
||||
final String inputName = org.apache.commons.io.FilenameUtils.getBaseName(firstInputFileName);
|
||||
InputFile firstInputFile = root.getDexNodes().get(0).getDexFile().getInputFile();
|
||||
final String firstInputFileName = firstInputFile.getFile().getAbsolutePath();
|
||||
final String inputPath = FilenameUtils.getFullPathNoEndSeparator(firstInputFileName);
|
||||
final String inputName = FilenameUtils.getBaseName(firstInputFileName);
|
||||
|
||||
File deobfMapFile = new File(inputPath, inputName + ".jobf");
|
||||
deobfuscator = new Deobfuscator(args, root.getDexNodes(), deobfMapFile);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package jadx.core.utils.files;
|
||||
|
||||
import com.android.dex.Dex;
|
||||
|
||||
public class DexFile {
|
||||
private final InputFile inputFile;
|
||||
private final String name;
|
||||
private final Dex dexBuf;
|
||||
|
||||
public DexFile(InputFile inputFile, String name, Dex dexBuf) {
|
||||
this.inputFile = inputFile;
|
||||
this.name = name;
|
||||
this.dexBuf = dexBuf;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Dex getDexBuf() {
|
||||
return dexBuf;
|
||||
}
|
||||
|
||||
public InputFile getInputFile() {
|
||||
return inputFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return inputFile.toString() + (name.isEmpty() ? "" : ":" + name);
|
||||
}
|
||||
}
|
||||
@@ -48,4 +48,15 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static File createTempFile(String suffix) {
|
||||
File temp;
|
||||
try {
|
||||
temp = File.createTempFile("jadx-tmp-", System.nanoTime() + "-" + suffix);
|
||||
temp.deleteOnExit();
|
||||
} catch (IOException e) {
|
||||
throw new JadxRuntimeException("Failed to create temp file with suffix: " + suffix);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,19 @@ package jadx.core.utils.files;
|
||||
import jadx.core.utils.AsmUtils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -22,49 +25,95 @@ public class InputFile {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InputFile.class);
|
||||
|
||||
private final File file;
|
||||
private final Dex dexBuf;
|
||||
public int nextDexIndex = -1;
|
||||
private final int dexIndex;
|
||||
private final List<DexFile> dexFiles = new ArrayList<DexFile>();
|
||||
|
||||
public InputFile(File file) throws IOException, DecodeException {
|
||||
this(file, 0);
|
||||
public static void addFilesFrom(File file, List<InputFile> list) throws IOException, DecodeException {
|
||||
InputFile inputFile = new InputFile(file);
|
||||
inputFile.searchDexFiles();
|
||||
list.add(inputFile);
|
||||
}
|
||||
|
||||
public InputFile(File file, int dexIndex) throws IOException, DecodeException {
|
||||
private InputFile(File file) throws IOException, DecodeException {
|
||||
if (!file.exists()) {
|
||||
throw new IOException("File not found: " + file.getAbsolutePath());
|
||||
}
|
||||
this.dexIndex = dexIndex;
|
||||
this.file = file;
|
||||
this.dexBuf = loadDexBuffer();
|
||||
}
|
||||
|
||||
private Dex loadDexBuffer() throws IOException, DecodeException {
|
||||
private void searchDexFiles() throws IOException, DecodeException {
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(".dex")) {
|
||||
return new Dex(file);
|
||||
addDexFile(new Dex(file));
|
||||
return;
|
||||
}
|
||||
if (fileName.endsWith(".class")) {
|
||||
return loadFromClassFile(file);
|
||||
addDexFile(loadFromClassFile(file));
|
||||
return;
|
||||
}
|
||||
if (fileName.endsWith(".apk") || fileName.endsWith(".zip")) {
|
||||
Dex dex = loadFromZip(this,file);
|
||||
if (dex == null) {
|
||||
throw new IOException("File 'classes.dex' not found in file: " + file);
|
||||
}
|
||||
return dex;
|
||||
loadFromZip(".dex");
|
||||
return;
|
||||
}
|
||||
if (fileName.endsWith(".jar")) {
|
||||
// check if jar contains 'classes.dex'
|
||||
Dex dex = loadFromZip(this,file);
|
||||
if (dex != null) {
|
||||
return dex;
|
||||
// check if jar contains '.dex' files
|
||||
if (loadFromZip(".dex")) {
|
||||
return;
|
||||
}
|
||||
return loadFromJar(file);
|
||||
addDexFile(loadFromJar(file));
|
||||
return;
|
||||
}
|
||||
if (fileName.endsWith(".aar")) {
|
||||
loadFromZip(".jar");
|
||||
return;
|
||||
}
|
||||
throw new DecodeException("Unsupported input file format: " + file);
|
||||
}
|
||||
|
||||
private void addDexFile(Dex dexBuf) throws IOException {
|
||||
addDexFile("", dexBuf);
|
||||
}
|
||||
|
||||
private void addDexFile(String fileName, Dex dexBuf) throws IOException {
|
||||
dexFiles.add(new DexFile(this, fileName, dexBuf));
|
||||
}
|
||||
|
||||
private boolean loadFromZip(String ext) throws IOException, DecodeException {
|
||||
ZipFile zf = new ZipFile(file);
|
||||
int index = 0;
|
||||
while (true) {
|
||||
String entryName = "classes" + (index == 0 ? "" : index) + ext;
|
||||
ZipEntry entry = zf.getEntry(entryName);
|
||||
if (entry == null) {
|
||||
break;
|
||||
}
|
||||
InputStream inputStream = zf.getInputStream(entry);
|
||||
try {
|
||||
if (ext.equals(".dex")) {
|
||||
addDexFile(entryName, new Dex(inputStream));
|
||||
} else if (ext.equals(".jar")) {
|
||||
File jarFile = FileUtils.createTempFile(entryName);
|
||||
FileOutputStream fos = new FileOutputStream(jarFile);
|
||||
try {
|
||||
IOUtils.copy(inputStream, fos);
|
||||
} finally {
|
||||
fos.close();
|
||||
}
|
||||
addDexFile(entryName, loadFromJar(jarFile));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected extension in zip: " + ext);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
index++;
|
||||
if (index == 1) {
|
||||
index = 2;
|
||||
}
|
||||
}
|
||||
zf.close();
|
||||
return index > 0;
|
||||
}
|
||||
|
||||
private static Dex loadFromJar(File jarFile) throws DecodeException {
|
||||
try {
|
||||
LOG.info("converting to dex: {} ...", jarFile.getName());
|
||||
@@ -81,47 +130,8 @@ public class InputFile {
|
||||
}
|
||||
}
|
||||
|
||||
private static Dex loadFromZip(InputFile ipf, File file) throws IOException {
|
||||
ZipFile zf = new ZipFile(file);
|
||||
String dexName = "classes.dex";
|
||||
String futureDexName = "classes2.dex";
|
||||
if (ipf.dexIndex != 0) {
|
||||
dexName = "classes" + ipf.dexIndex + ".dex";
|
||||
futureDexName = "classes" + (ipf.dexIndex + 1) + ".dex";
|
||||
}
|
||||
ZipEntry dex = zf.getEntry(dexName);
|
||||
if (dex == null) {
|
||||
zf.close();
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
ZipEntry futureDex = zf.getEntry(futureDexName);
|
||||
if (futureDex != null) {
|
||||
ipf.nextDexIndex = ipf.dexIndex == 0 ? 2 : ipf.dexIndex + 1;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = zf.getInputStream(dex);
|
||||
byte[] buffer = new byte[8192];
|
||||
int count;
|
||||
while ((count = in.read(buffer)) != -1) {
|
||||
bytesOut.write(buffer, 0, count);
|
||||
}
|
||||
} finally {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
zf.close();
|
||||
}
|
||||
return new Dex(bytesOut.toByteArray());
|
||||
}
|
||||
|
||||
private static Dex loadFromClassFile(File file) throws IOException, DecodeException {
|
||||
File outFile = File.createTempFile("jadx-tmp-", System.nanoTime() + ".jar");
|
||||
outFile.deleteOnExit();
|
||||
File outFile = FileUtils.createTempFile("cls.jar");
|
||||
FileOutputStream out = null;
|
||||
JarOutputStream jo = null;
|
||||
try {
|
||||
@@ -147,12 +157,12 @@ public class InputFile {
|
||||
return file;
|
||||
}
|
||||
|
||||
public Dex getDexBuffer() {
|
||||
return dexBuf;
|
||||
public List<DexFile> getDexFiles() {
|
||||
return dexFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return file.toString();
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user