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 {
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);
}
......
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);
......
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();
}
}
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