diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index dacb66f73..10be45755 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -14,7 +14,7 @@ dependencies { testImplementation 'org.apache.commons:commons-lang3:3.12.0' - testRuntimeOnly(project(':jadx-plugins:jadx-dex-input')) + testImplementation(project(':jadx-plugins:jadx-dex-input')) testRuntimeOnly(project(':jadx-plugins:jadx-smali-input')) testRuntimeOnly(project(':jadx-plugins:jadx-java-convert')) testRuntimeOnly(project(':jadx-plugins:jadx-java-input')) diff --git a/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java b/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java index 8f01d97a5..6819892aa 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java @@ -13,8 +13,9 @@ public class JadxArgsValidator { private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class); - public static void validate(JadxArgs args) { - checkInputFiles(args); + public static void validate(JadxDecompiler jadx) { + JadxArgs args = jadx.getArgs(); + checkInputFiles(jadx, args); validateOutDirs(args); if (LOG.isDebugEnabled()) { @@ -22,9 +23,9 @@ public class JadxArgsValidator { } } - private static void checkInputFiles(JadxArgs args) { + private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) { List inputFiles = args.getInputFiles(); - if (inputFiles.isEmpty()) { + if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) { throw new JadxArgsValidateException("Please specify input file"); } for (File inputFile : inputFiles) { @@ -66,19 +67,22 @@ public class JadxArgsValidator { @NotNull private static File makeDirFromInput(JadxArgs args) { - File outDir; String outDirName; - File file = args.getInputFiles().get(0); - String name = file.getName(); - int pos = name.lastIndexOf('.'); - if (pos != -1) { - outDirName = name.substring(0, pos); + List inputFiles = args.getInputFiles(); + if (inputFiles.isEmpty()) { + outDirName = JadxArgs.DEFAULT_OUT_DIR; } else { - outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR; + File file = inputFiles.get(0); + String name = file.getName(); + int pos = name.lastIndexOf('.'); + if (pos != -1) { + outDirName = name.substring(0, pos); + } else { + outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR; + } } LOG.info("output directory: {}", outDirName); - outDir = new File(outDirName); - return outDir; + return new File(outDirName); } private static void checkFile(File file) { diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index d0d39c593..8fb8a1003 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -98,6 +98,8 @@ public final class JadxDecompiler implements Closeable { private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this); + private final List customLoads = new ArrayList<>(); + public JadxDecompiler() { this(new JadxArgs()); } @@ -108,7 +110,7 @@ public final class JadxDecompiler implements Closeable { public void load() { reset(); - JadxArgsValidator.validate(args); + JadxArgsValidator.validate(this); LOG.info("loading ..."); loadPlugins(args); loadInputFiles(); @@ -132,11 +134,20 @@ public final class JadxDecompiler implements Closeable { loadedInputs.add(loadResult); } } + loadedInputs.addAll(customLoads); if (LOG.isDebugEnabled()) { LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start); } } + public void addCustomLoad(ILoadResult customLoad) { + customLoads.add(customLoad); + } + + public List getCustomLoads() { + return customLoads; + } + private void reset() { root = null; classes = null; diff --git a/jadx-core/src/test/java/jadx/api/JadxArgsValidatorOutDirsTest.java b/jadx-core/src/test/java/jadx/api/JadxArgsValidatorOutDirsTest.java index 729390900..7bb6cd15f 100644 --- a/jadx-core/src/test/java/jadx/api/JadxArgsValidatorOutDirsTest.java +++ b/jadx-core/src/test/java/jadx/api/JadxArgsValidatorOutDirsTest.java @@ -57,7 +57,7 @@ public class JadxArgsValidatorOutDirsTest { } private void checkOutDirs(String outDir, String srcDir, String resDir) { - JadxArgsValidator.validate(args); + JadxArgsValidator.validate(new JadxDecompiler(args)); LOG.debug("Got dirs: out={}, src={}, res={}", args.getOutDir(), args.getOutDirSrc(), args.getOutDirRes()); assertThat(args.getOutDir(), is(toFile(outDir))); assertThat(args.getOutDirSrc(), is(toFile(srcDir))); diff --git a/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java b/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java index 49c07bdbb..a061a6bab 100644 --- a/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java +++ b/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java @@ -1,12 +1,16 @@ package jadx.api; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import jadx.core.utils.files.FileUtils; +import jadx.plugins.input.dex.DexInputPlugin; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; @@ -38,6 +42,20 @@ public class JadxDecompilerTest { } } + @Test + public void testDirectDexInput() throws IOException { + try (JadxDecompiler jadx = new JadxDecompiler(); + InputStream in = new FileInputStream(getFileFromSampleDir("hello.dex"))) { + jadx.addCustomLoad(new DexInputPlugin().loadDexFromInputStream(in, "input")); + jadx.load(); + for (JavaClass cls : jadx.getClasses()) { + System.out.println(cls.getCode()); + } + assertThat(jadx.getClasses(), Matchers.hasSize(1)); + assertThat(jadx.getErrorsCount(), Matchers.is(0)); + } + } + private static final String TEST_SAMPLES_DIR = "test-samples/"; public static File getFileFromSampleDir(String fileName) { diff --git a/jadx-core/src/test/resources/test-samples/hello.dex b/jadx-core/src/test/resources/test-samples/hello.dex new file mode 100644 index 000000000..04417fbb2 Binary files /dev/null and b/jadx-core/src/test/resources/test-samples/hello.dex differ diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java index f1dcbb905..40c767c24 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java @@ -64,10 +64,7 @@ public class DexFileLoader { if (isStartWithBytes(magic, DexConsts.DEX_FILE_MAGIC) || fileName.endsWith(".dex")) { in.reset(); byte[] content = readAllBytes(in); - if (options.isVerifyChecksum()) { - DexCheckSum.verify(content); - } - DexReader dexReader = new DexReader(getNextUniqId(), fileName, content); + DexReader dexReader = loadDexReader(fileName, content); return Collections.singletonList(dexReader); } if (file != null) { @@ -80,6 +77,13 @@ public class DexFileLoader { } } + public DexReader loadDexReader(String fileName, byte[] content) { + if (options.isVerifyChecksum()) { + DexCheckSum.verify(content); + } + return new DexReader(getNextUniqId(), fileName, content); + } + private List collectDexFromZip(File file) { List result = new ArrayList<>(); try { diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java index bf944a2bd..fcdfef11e 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java @@ -1,7 +1,9 @@ package jadx.plugins.input.dex; import java.io.Closeable; +import java.io.InputStream; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -13,6 +15,7 @@ import jadx.api.plugins.input.data.ILoadResult; import jadx.api.plugins.input.data.impl.EmptyLoadResult; import jadx.api.plugins.options.JadxPluginOptions; import jadx.api.plugins.options.OptionDescription; +import jadx.api.plugins.utils.CommonFileUtils; public class DexInputPlugin implements JadxInputPlugin, JadxPluginOptions { public static final String PLUGIN_ID = "dex-input"; @@ -38,6 +41,20 @@ public class DexInputPlugin implements JadxInputPlugin, JadxPluginOptions { return new DexLoadResult(dexReaders, closeable); } + public ILoadResult loadDex(byte[] content, @Nullable String fileName) { + String fileLabel = fileName == null ? "input.dex" : fileName; + DexReader dexReader = loader.loadDexReader(fileLabel, content); + return new DexLoadResult(Collections.singletonList(dexReader), null); + } + + public ILoadResult loadDexFromInputStream(InputStream in, @Nullable String fileLabel) { + try { + return loadDex(CommonFileUtils.loadBytes(in), fileLabel); + } catch (Exception e) { + throw new DexException("Failed to read input stream", e); + } + } + @Override public void setOptions(Map options) { this.options.apply(options); diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java index 39e60aae9..2d1870d15 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java @@ -34,7 +34,6 @@ public class DexLoadResult implements ILoadResult { @Override public void close() throws IOException { - dexReaders.clear(); if (closeable != null) { closeable.close(); }