diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
index d8721b7e5..2a378296b 100644
--- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
+++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
@@ -20,7 +20,7 @@ import com.beust.jcommander.ParameterException;
public final class JadxCLIArgs implements IJadxArgs {
- @Parameter(description = " (.dex, .apk or .jar)")
+ @Parameter(description = " (.dex, .apk, .jar or .class)")
protected List files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle
index 5358731e9..98850351b 100644
--- a/jadx-core/build.gradle
+++ b/jadx-core/build.gradle
@@ -2,6 +2,7 @@ ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile files('lib/dx-1.8.jar')
+ compile 'org.ow2.asm:asm:5.0.3'
runtime files(jadxClasspath)
}
diff --git a/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java b/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java
new file mode 100644
index 000000000..840b21d5d
--- /dev/null
+++ b/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java
@@ -0,0 +1,20 @@
+package jadx.core.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.objectweb.asm.ClassReader;
+
+public class AsmUtils {
+
+ private AsmUtils() {
+ }
+
+ public static String getNameFromClassFile(File file) throws IOException {
+ FileInputStream in = new FileInputStream(file);
+ ClassReader classReader = new ClassReader(in);
+ return classReader.getClassName();
+ }
+
+}
diff --git a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java
new file mode 100644
index 000000000..5e607d459
--- /dev/null
+++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java
@@ -0,0 +1,38 @@
+package jadx.core.utils.files;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+public class FileUtils {
+
+ private FileUtils() {
+ }
+
+ public static void addFileToJar(JarOutputStream jar, File source, String entryName) throws IOException {
+ BufferedInputStream in = null;
+ try {
+ JarEntry entry = new JarEntry(entryName);
+ entry.setTime(source.lastModified());
+ jar.putNextEntry(entry);
+ in = new BufferedInputStream(new FileInputStream(source));
+
+ byte[] buffer = new byte[8192];
+ while (true) {
+ int count = in.read(buffer);
+ if (count == -1) {
+ break;
+ }
+ jar.write(buffer, 0, count);
+ }
+ jar.closeEntry();
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ }
+ }
+}
diff --git a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java
index 8cb22ad19..22e5d719b 100644
--- a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java
+++ b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java
@@ -1,13 +1,15 @@
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.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -35,37 +37,44 @@ public class InputFile {
if (fileName.endsWith(".dex")) {
return new Dex(file);
}
+ if (fileName.endsWith(".class")) {
+ return loadFromClassFile(file);
+ }
if (fileName.endsWith(".apk")) {
- byte[] data = openDexFromZip(file);
- if (data == null) {
- throw new JadxRuntimeException("File 'classes.dex' not found in file: " + file);
+ Dex dex = loadFromZip(file);
+ if (dex == null) {
+ throw new IOException("File 'classes.dex' not found in file: " + file);
}
- return new Dex(data);
+ return dex;
}
if (fileName.endsWith(".jar")) {
// check if jar contains 'classes.dex'
- byte[] data = openDexFromZip(file);
- if (data != null) {
- return new Dex(data);
- }
- try {
- LOG.info("converting to dex: {} ...", fileName);
- JavaToDex j2d = new JavaToDex();
- byte[] ba = j2d.convert(file.getAbsolutePath());
- if (ba.length == 0) {
- throw new JadxException(j2d.isError() ? j2d.getDxErrors() : "Empty dx output");
- } else if (j2d.isError()) {
- LOG.warn("dx message: " + j2d.getDxErrors());
- }
- return new Dex(ba);
- } catch (Throwable e) {
- throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e);
+ Dex dex = loadFromZip(file);
+ if (dex != null) {
+ return dex;
}
+ return loadFromJar(file);
}
throw new DecodeException("Unsupported input file format: " + file);
}
- private byte[] openDexFromZip(File file) throws IOException {
+ private static Dex loadFromJar(File jarFile) throws DecodeException {
+ try {
+ LOG.info("converting to dex: {} ...", jarFile.getName());
+ JavaToDex j2d = new JavaToDex();
+ byte[] ba = j2d.convert(jarFile.getAbsolutePath());
+ if (ba.length == 0) {
+ throw new JadxException(j2d.isError() ? j2d.getDxErrors() : "Empty dx output");
+ } else if (j2d.isError()) {
+ LOG.warn("dx message: " + j2d.getDxErrors());
+ }
+ return new Dex(ba);
+ } catch (Throwable e) {
+ throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e);
+ }
+ }
+
+ private static Dex loadFromZip(File file) throws IOException {
ZipFile zf = new ZipFile(file);
ZipEntry dex = zf.getEntry("classes.dex");
if (dex == null) {
@@ -87,7 +96,19 @@ public class InputFile {
}
zf.close();
}
- return bytesOut.toByteArray();
+ 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();
+ FileOutputStream out = new FileOutputStream(outFile);
+ JarOutputStream jo = new JarOutputStream(out);
+ String clsName = AsmUtils.getNameFromClassFile(file);
+ FileUtils.addFileToJar(jo, file, clsName + ".class");
+ jo.close();
+ out.close();
+ return loadFromJar(outFile);
}
public File getFile() {
diff --git a/jadx-core/src/test/java/jadx/api/InternalJadxTest.java b/jadx-core/src/test/java/jadx/api/InternalJadxTest.java
index 4a01d40aa..98373cfbd 100644
--- a/jadx-core/src/test/java/jadx/api/InternalJadxTest.java
+++ b/jadx-core/src/test/java/jadx/api/InternalJadxTest.java
@@ -7,17 +7,15 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
+import jadx.core.utils.files.FileUtils;
-import java.io.BufferedInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
-import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import static org.hamcrest.CoreMatchers.containsString;
@@ -94,7 +92,7 @@ public abstract class InternalJadxTest extends TestUtils {
File temp = File.createTempFile("jadx-tmp-", System.nanoTime() + ".jar");
JarOutputStream jo = new JarOutputStream(new FileOutputStream(temp));
for (File file : list) {
- add(file, path + "/" + file.getName(), jo);
+ FileUtils.addFileToJar(jo, file, path + "/" + file.getName());
}
jo.close();
if (deleteTmpJar) {
@@ -127,29 +125,6 @@ public abstract class InternalJadxTest extends TestUtils {
return list;
}
- private void add(File source, String entryName, JarOutputStream target) throws IOException {
- BufferedInputStream in = null;
- try {
- JarEntry entry = new JarEntry(entryName);
- entry.setTime(source.lastModified());
- target.putNextEntry(entry);
- in = new BufferedInputStream(new FileInputStream(source));
-
- byte[] buffer = new byte[1024];
- while (true) {
- int count = in.read(buffer);
- if (count == -1) {
- break;
- }
- target.write(buffer, 0, count);
- }
- target.closeEntry();
- } finally {
- if (in != null) {
- in.close();
- }
- }
- }
// Use only for debug purpose
@Deprecated