Commit bc9164b9 authored by Skylot's avatar Skylot

core: refactor file loading, add 'aar' support (fix #95)

parent 7c34be26
...@@ -116,12 +116,7 @@ public final class JadxDecompiler { ...@@ -116,12 +116,7 @@ public final class JadxDecompiler {
inputFiles.clear(); inputFiles.clear();
for (File file : files) { for (File file : files) {
try { try {
InputFile inputFile = new InputFile(file); InputFile.addFilesFrom(file, inputFiles);
inputFiles.add(inputFile);
while (inputFile.nextDexIndex != -1) {
inputFile = new InputFile(file, inputFile.nextDexIndex);
inputFiles.add(inputFile);
}
} catch (IOException e) { } catch (IOException e) {
throw new JadxException("Error load file: " + file, e); throw new JadxException("Error load file: " + file, e);
} }
......
package jadx.api; package jadx.api;
public enum ResourceType { public enum ResourceType {
CODE(".dex", ".class"), CODE(".dex", ".jar", ".class"),
MANIFEST("AndroidManifest.xml"), MANIFEST("AndroidManifest.xml"),
XML(".xml"), // TODO binary or not? XML(".xml"),
ARSC(".arsc"), // TODO decompile !!! ARSC(".arsc"),
FONT(".ttf"), FONT(".ttf"),
IMG(".png", ".gif", ".jpg"), IMG(".png", ".gif", ".jpg"),
LIB(".so"), LIB(".so"),
......
...@@ -85,12 +85,7 @@ public final class ResourcesLoader { ...@@ -85,12 +85,7 @@ public final class ResourcesLoader {
return decodeStream(rf, new ResourceDecoder() { return decodeStream(rf, new ResourceDecoder() {
@Override @Override
public ResContainer decode(long size, InputStream is) throws IOException { public ResContainer decode(long size, InputStream is) throws IOException {
if (size > LOAD_SIZE_LIMIT) { return loadContent(jadxRef, rf, is, size);
return ResContainer.singleFile(rf.getName(),
new CodeWriter().add("File too big, size: "
+ String.format("%.2f KB", size / 1024.)));
}
return loadContent(jadxRef, rf, is);
} }
}); });
} catch (JadxException e) { } catch (JadxException e) {
...@@ -103,7 +98,7 @@ public final class ResourcesLoader { ...@@ -103,7 +98,7 @@ public final class ResourcesLoader {
} }
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf, private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException { InputStream inputStream, long size) throws IOException {
switch (rf.getType()) { switch (rf.getType()) {
case MANIFEST: case MANIFEST:
case XML: case XML:
...@@ -113,6 +108,10 @@ public final class ResourcesLoader { ...@@ -113,6 +108,10 @@ public final class ResourcesLoader {
case ARSC: case ARSC:
return new ResTableParser().decodeFiles(inputStream); 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)); return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
} }
......
...@@ -36,12 +36,7 @@ public class ConvertToClsSet { ...@@ -36,12 +36,7 @@ public class ConvertToClsSet {
if (f.isDirectory()) { if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles); addFilesFromDirectory(f, inputFiles);
} else { } else {
InputFile inputFile = new InputFile(f); InputFile.addFilesFrom(f, inputFiles);
inputFiles.add(inputFile);
while (inputFile.nextDexIndex != -1) {
inputFile = new InputFile(f, inputFile.nextDexIndex);
inputFiles.add(inputFile);
}
} }
} }
for (InputFile inputFile : inputFiles) { for (InputFile inputFile : inputFiles) {
...@@ -58,8 +53,7 @@ public class ConvertToClsSet { ...@@ -58,8 +53,7 @@ public class ConvertToClsSet {
LOG.info("done"); LOG.info("done");
} }
private static void addFilesFromDirectory(File dir, private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
List<InputFile> inputFiles) throws IOException, DecodeException {
File[] files = dir.listFiles(); File[] files = dir.listFiles();
if (files == null) { if (files == null) {
return; return;
...@@ -67,19 +61,13 @@ public class ConvertToClsSet { ...@@ -67,19 +61,13 @@ public class ConvertToClsSet {
for (File file : files) { for (File file : files) {
if (file.isDirectory()) { if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles); addFilesFromDirectory(file, inputFiles);
} } else {
String fileName = file.getName(); try {
if (fileName.endsWith(".dex") InputFile.addFilesFrom(file, inputFiles);
|| fileName.endsWith(".jar") } catch (Exception e) {
|| fileName.endsWith(".apk")) { LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
InputFile inputFile = new InputFile(file);
inputFiles.add(inputFile);
while (inputFile.nextDexIndex != -1) {
inputFile = new InputFile(file, inputFile.nextDexIndex);
inputFiles.add(inputFile);
} }
} }
} }
} }
} }
...@@ -6,7 +6,7 @@ import jadx.core.dex.info.InfoStorage; ...@@ -6,7 +6,7 @@ import jadx.core.dex.info.InfoStorage;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.exceptions.DecodeException; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
...@@ -34,7 +34,7 @@ public class DexNode { ...@@ -34,7 +34,7 @@ public class DexNode {
private final RootNode root; private final RootNode root;
private final Dex dexBuf; private final Dex dexBuf;
private final InputFile file; private final DexFile file;
private final List<ClassNode> classes = new ArrayList<ClassNode>(); private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>(); private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>();
...@@ -43,10 +43,10 @@ public class DexNode { ...@@ -43,10 +43,10 @@ public class DexNode {
private final InfoStorage infoStorage = new InfoStorage(); private final InfoStorage infoStorage = new InfoStorage();
public DexNode(RootNode root, InputFile input) { public DexNode(RootNode root, DexFile input) {
this.root = root; this.root = root;
this.file = input; this.file = input;
this.dexBuf = input.getDexBuffer(); this.dexBuf = input.getDexBuf();
} }
public void loadClasses() throws DecodeException { public void loadClasses() throws DecodeException {
...@@ -163,7 +163,7 @@ public class DexNode { ...@@ -163,7 +163,7 @@ public class DexNode {
return infoStorage; return infoStorage;
} }
public InputFile getInputFile() { public DexFile getDexFile() {
return file; return file;
} }
......
...@@ -9,6 +9,7 @@ import jadx.core.dex.info.ClassInfo; ...@@ -9,6 +9,7 @@ import jadx.core.dex.info.ClassInfo;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.DexFile;
import jadx.core.utils.files.InputFile; import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser; import jadx.core.xmlgen.ResTableParser;
...@@ -42,16 +43,18 @@ public class RootNode { ...@@ -42,16 +43,18 @@ public class RootNode {
this.args = args; this.args = args;
} }
public void load(List<InputFile> dexFiles) throws DecodeException { public void load(List<InputFile> inputFiles) throws DecodeException {
dexNodes = new ArrayList<DexNode>(dexFiles.size()); dexNodes = new ArrayList<DexNode>();
for (InputFile dex : dexFiles) { for (InputFile input : inputFiles) {
DexNode dexNode; for (DexFile dexFile : input.getDexFiles()) {
try { try {
dexNode = new DexNode(this, dex); LOG.debug("Load: {}", dexFile);
DexNode dexNode = new DexNode(this, dexFile);
dexNodes.add(dexNode);
} catch (Exception e) { } catch (Exception e) {
throw new DecodeException("Error decode file: " + dex, e); throw new DecodeException("Error decode file: " + dexFile, e);
}
} }
dexNodes.add(dexNode);
} }
for (DexNode dexNode : dexNodes) { for (DexNode dexNode : dexNodes) {
dexNode.loadClasses(); dexNode.loadClasses();
......
...@@ -14,11 +14,13 @@ import jadx.core.dex.nodes.FieldNode; ...@@ -14,11 +14,13 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase; import org.apache.commons.io.IOCase;
public class RenameVisitor extends AbstractVisitor { public class RenameVisitor extends AbstractVisitor {
...@@ -31,10 +33,10 @@ public class RenameVisitor extends AbstractVisitor { ...@@ -31,10 +33,10 @@ public class RenameVisitor extends AbstractVisitor {
public void init(RootNode root) { public void init(RootNode root) {
IJadxArgs args = root.getArgs(); IJadxArgs args = root.getArgs();
final String firstInputFileName = root.getDexNodes().get(0).getInputFile().getFile().getAbsolutePath(); InputFile firstInputFile = root.getDexNodes().get(0).getDexFile().getInputFile();
final String inputPath = org.apache.commons.io.FilenameUtils.getFullPathNoEndSeparator( final String firstInputFileName = firstInputFile.getFile().getAbsolutePath();
firstInputFileName); final String inputPath = FilenameUtils.getFullPathNoEndSeparator(firstInputFileName);
final String inputName = org.apache.commons.io.FilenameUtils.getBaseName(firstInputFileName); final String inputName = FilenameUtils.getBaseName(firstInputFileName);
File deobfMapFile = new File(inputPath, inputName + ".jobf"); File deobfMapFile = new File(inputPath, inputName + ".jobf");
deobfuscator = new Deobfuscator(args, root.getDexNodes(), deobfMapFile); deobfuscator = new Deobfuscator(args, root.getDexNodes(), deobfMapFile);
......
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 { ...@@ -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; ...@@ -3,16 +3,19 @@ package jadx.core.utils.files;
import jadx.core.utils.AsmUtils; import jadx.core.utils.AsmUtils;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -22,49 +25,95 @@ public class InputFile { ...@@ -22,49 +25,95 @@ public class InputFile {
private static final Logger LOG = LoggerFactory.getLogger(InputFile.class); private static final Logger LOG = LoggerFactory.getLogger(InputFile.class);
private final File file; private final File file;
private final Dex dexBuf; private final List<DexFile> dexFiles = new ArrayList<DexFile>();
public int nextDexIndex = -1;
private final int dexIndex;
public InputFile(File file) throws IOException, DecodeException { public static void addFilesFrom(File file, List<InputFile> list) throws IOException, DecodeException {
this(file, 0); 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()) { if (!file.exists()) {
throw new IOException("File not found: " + file.getAbsolutePath()); throw new IOException("File not found: " + file.getAbsolutePath());
} }
this.dexIndex = dexIndex;
this.file = file; this.file = file;
this.dexBuf = loadDexBuffer();
} }
private Dex loadDexBuffer() throws IOException, DecodeException { private void searchDexFiles() throws IOException, DecodeException {
String fileName = file.getName(); String fileName = file.getName();
if (fileName.endsWith(".dex")) { if (fileName.endsWith(".dex")) {
return new Dex(file); addDexFile(new Dex(file));
return;
} }
if (fileName.endsWith(".class")) { if (fileName.endsWith(".class")) {
return loadFromClassFile(file); addDexFile(loadFromClassFile(file));
return;
} }
if (fileName.endsWith(".apk") || fileName.endsWith(".zip")) { if (fileName.endsWith(".apk") || fileName.endsWith(".zip")) {
Dex dex = loadFromZip(this,file); loadFromZip(".dex");
if (dex == null) { return;
throw new IOException("File 'classes.dex' not found in file: " + file);
}
return dex;
} }
if (fileName.endsWith(".jar")) { if (fileName.endsWith(".jar")) {
// check if jar contains 'classes.dex' // check if jar contains '.dex' files
Dex dex = loadFromZip(this,file); if (loadFromZip(".dex")) {
if (dex != null) { return;
return dex; }
addDexFile(loadFromJar(file));
return;
} }
return loadFromJar(file); if (fileName.endsWith(".aar")) {
loadFromZip(".jar");
return;
} }
throw new DecodeException("Unsupported input file format: " + file); 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 { private static Dex loadFromJar(File jarFile) throws DecodeException {
try { try {
LOG.info("converting to dex: {} ...", jarFile.getName()); LOG.info("converting to dex: {} ...", jarFile.getName());
...@@ -81,47 +130,8 @@ public class InputFile { ...@@ -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 { private static Dex loadFromClassFile(File file) throws IOException, DecodeException {
File outFile = File.createTempFile("jadx-tmp-", System.nanoTime() + ".jar"); File outFile = FileUtils.createTempFile("cls.jar");
outFile.deleteOnExit();
FileOutputStream out = null; FileOutputStream out = null;
JarOutputStream jo = null; JarOutputStream jo = null;
try { try {
...@@ -147,12 +157,12 @@ public class InputFile { ...@@ -147,12 +157,12 @@ public class InputFile {
return file; return file;
} }
public Dex getDexBuffer() { public List<DexFile> getDexFiles() {
return dexBuf; return dexFiles;
} }
@Override @Override
public String toString() { public String toString() {
return file.toString(); return file.getAbsolutePath();
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment