Commit 3700ecb7 authored by Skylot's avatar Skylot

core: add resources methods to jadx API

parent 811b0e7f
......@@ -32,6 +32,12 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
protected boolean skipResources = false;
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
protected boolean skipSources = false;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
......@@ -47,9 +53,6 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false;
@Parameter(names = {"-x", "--xml"}, description = "try to decode the AndroidManifest.xml")
protected boolean xmlTest = false;
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
......@@ -168,8 +171,13 @@ public final class JadxCLIArgs implements IJadxArgs {
}
@Override
public boolean isXMLTest() {
return xmlTest;
public boolean isSkipResources() {
return skipResources;
}
@Override
public boolean isSkipSources() {
return skipSources;
}
@Override
......
......@@ -40,7 +40,12 @@ public class DefaultJadxArgs implements IJadxArgs {
}
@Override
public boolean isXMLTest() {
public boolean isSkipResources() {
return false;
}
@Override
public boolean isSkipSources() {
return false;
}
}
......@@ -17,5 +17,7 @@ public interface IJadxArgs {
boolean isVerbose();
boolean isXMLTest();
boolean isSkipResources();
boolean isSkipSources();
}
......@@ -2,6 +2,7 @@ package jadx.api;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
......@@ -55,6 +56,9 @@ public final class JadxDecompiler {
private RootNode root;
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser xmlParser;
public JadxDecompiler() {
this(new DefaultJadxArgs());
......@@ -82,6 +86,8 @@ public final class JadxDecompiler {
void reset() {
ClassInfo.clearCache();
classes = null;
resources = null;
xmlParser = null;
root = null;
}
......@@ -108,27 +114,21 @@ public final class JadxDecompiler {
parse();
}
public void parseAndSaveXML() {
if (this.args.isXMLTest()) {
InputFile inf = inputFiles.get(0);
try {
byte[] buffer = InputFile.loadXMLBuffer(inf.getFile());
if (buffer != null) {
File out = new File(outDir, "AndroidManifest.xml");
BinaryXMLParser bxp = new BinaryXMLParser(root);
bxp.parse(buffer, out);
}
} catch (Exception e) {
LOG.info("Decompiling AndroidManifest.xml failed!", e);
}
}
public void save() {
save(!args.isSkipSources(), !args.isSkipResources());
}
public void save() {
parseAndSaveXML();
public void saveSources() {
save(true, false);
}
public void saveResources() {
save(false, true);
}
private void save(boolean saveSources, boolean saveResources) {
try {
ExecutorService ex = getSaveExecutor();
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
......@@ -137,6 +137,10 @@ public final class JadxDecompiler {
}
public ExecutorService getSaveExecutor() {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
}
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
......@@ -145,14 +149,31 @@ public final class JadxDecompiler {
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (final JavaClass cls : getClasses()) {
executor.execute(new Runnable() {
@Override
public void run() {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
});
if (saveSources) {
for (final JavaClass cls : getClasses()) {
executor.execute(new Runnable() {
@Override
public void run() {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
});
}
}
if (saveResources) {
for (final ResourceFile resourceFile : getResources()) {
executor.execute(new Runnable() {
@Override
public void run() {
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
CodeWriter cw = resourceFile.getContent();
if (cw != null) {
cw.save(new File(outDir, resourceFile.getName()));
}
}
}
});
}
}
return executor;
}
......@@ -172,6 +193,16 @@ public final class JadxDecompiler {
return classes;
}
public List<ResourceFile> getResources() {
if (resources == null) {
if (root == null) {
return Collections.emptyList();
}
resources = new ResourcesLoader(this).load(inputFiles);
}
return resources;
}
public List<JavaPackage> getPackages() {
List<JavaClass> classList = getClasses();
if (classList.isEmpty()) {
......@@ -232,6 +263,13 @@ public final class JadxDecompiler {
return root;
}
BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
}
return xmlParser;
}
JavaClass findJavaClass(ClassNode cls) {
if (cls == null) {
return null;
......
package jadx.api;
import jadx.core.codegen.CodeWriter;
import java.io.File;
public class ResourceFile {
public static final class ZipRef {
private final File zipFile;
private final String entryName;
public ZipRef(File zipFile, String entryName) {
this.zipFile = zipFile;
this.entryName = entryName;
}
public File getZipFile() {
return zipFile;
}
public String getEntryName() {
return entryName;
}
@Override
public String toString() {
return "ZipRef{" + zipFile + ", '" + entryName + "'}";
}
}
private final JadxDecompiler decompiler;
private final String name;
private final ResourceType type;
private ZipRef zipRef;
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public ResourceType getType() {
return type;
}
public CodeWriter getContent() {
return ResourcesLoader.loadContent(decompiler, zipRef, type);
}
void setZipRef(ZipRef zipRef) {
this.zipRef = zipRef;
}
@Override
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
}
}
package jadx.api;
public enum ResourceType {
CODE(".dex", ".class"),
MANIFEST("AndroidManifest.xml"),
XML(".xml"), // TODO binary or not?
ARSC(".arsc"), // TODO decompile !!!
FONT(".ttf"),
IMG(".png", ".gif", ".jpg"),
LIB(".so"),
UNKNOWN;
private String[] exts;
ResourceType(String... exts) {
this.exts = exts;
}
public String[] getExts() {
return exts;
}
public static ResourceType getFileType(String fileName) {
for (ResourceType type : ResourceType.values()) {
for (String ext : type.getExts()) {
if (fileName.endsWith(ext)) {
return type;
}
}
}
return UNKNOWN;
}
public static boolean isSupportedForUnpack(ResourceType type) {
switch (type) {
case CODE:
case ARSC:
case LIB:
case XML:
case FONT:
case IMG:
case UNKNOWN:
return false;
case MANIFEST:
return true;
}
return false;
}
}
package jadx.api;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.files.InputFile;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// TODO: move to core package
final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private static final int READ_BUFFER_SIZE = 8 * 1024;
private static final int LOAD_SIZE_LIMIT = 500 * 1024;
private JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
this.jadxRef = jadxRef;
}
List<ResourceFile> load(List<InputFile> inputFiles) {
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
}
return list;
}
static CodeWriter loadContent(JadxDecompiler jadxRef, ZipRef zipRef, ResourceType type) {
if (zipRef == null) {
return null;
}
ZipFile zipFile = null;
InputStream inputStream = null;
try {
zipFile = new ZipFile(zipRef.getZipFile());
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry != null) {
if (entry.getSize() > LOAD_SIZE_LIMIT) {
return new CodeWriter().add("File too big, size: "
+ String.format("%.2f KB", entry.getSize() / 1024.));
}
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
return decode(jadxRef, type, inputStream);
} else {
LOG.warn("Zip entry not found: {}", zipRef);
}
} catch (IOException e) {
LOG.error("Error load: " + zipRef, e);
} finally {
try {
if (zipFile != null) {
zipFile.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
LOG.debug("Error close zip file: " + zipRef, e);
}
}
return null;
}
private static CodeWriter decode(JadxDecompiler jadxRef, ResourceType type,
InputStream inputStream) throws IOException {
switch (type) {
case MANIFEST:
case XML:
return jadxRef.getXmlParser().parse(inputStream);
}
return loadToCodeWriter(inputStream);
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
return;
}
ZipFile zip = null;
try {
zip = new ZipFile(file);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
addEntry(list, file, entry);
}
} catch (IOException e) {
LOG.debug("Not a zip file: " + file.getAbsolutePath());
} finally {
if (zip != null) {
try {
zip.close();
} catch (Exception e) {
LOG.error("Zip file close error: " + file.getAbsolutePath(), e);
}
}
}
}
private void addEntry(List<ResourceFile> list, File zipFile, ZipEntry entry) {
if (entry.isDirectory()) {
return;
}
String name = entry.getName();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = new ResourceFile(jadxRef, name, type);
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
// LOG.debug("Add resource entry: {}, size: {}", name, entry.getSize());
}
private static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
CodeWriter cw = new CodeWriter();
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
byte[] buffer = new byte[READ_BUFFER_SIZE];
int count;
try {
while ((count = is.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
} finally {
try {
is.close();
} catch (Exception ignore) {
}
}
cw.add(baos.toString("UTF-8"));
return cw;
}
}
......@@ -128,6 +128,11 @@ public abstract class IntegrationTest extends TestUtils {
public int getThreadsCount() {
return 1;
}
@Override
public boolean isSkipResources() {
return true;
}
}, new File(outDir));
}
......
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