From 2cb3bbd8a4652c33ce907c1bb1d5356835ec11d5 Mon Sep 17 00:00:00 2001 From: NebelNidas <48808497+NebelNidas@users.noreply.github.com> Date: Sun, 10 Mar 2024 23:25:05 +0100 Subject: [PATCH] Add JADX decompiler (#27) * Add JADX decompiler * Use memory cache for JADX Speeds up things significantly. And yes, the memory is never reclaimed, but once we move the cache to the disk instead, that won't be an issue anymore * Much more efficient JADX implementation Now only passes the requested class instead of all input files. * Fix crash on second decompile * Un-`synchronize` JADX decompiling * New JADX implementation can decompile inner classes Also fix checkstyle issues * Fix JPMS issues * Put JADX menu entry underneath Vineflower * Small clean-up * Reduce JADX log spam * Remove JADX repackaging top-level classes workaround --- build.gradle | 11 +++ gradle.properties | 1 + src/main/java/matcher/Util.java | 11 +++ .../matcher/srcprocess/BuiltinDecompiler.java | 1 + src/main/java/matcher/srcprocess/Jadx.java | 75 +++++++++++++++++++ src/main/java/module-info.java | 3 + src/main/resources/tinylog-dev.properties | 2 + src/main/resources/tinylog.properties | 2 + 8 files changed, 106 insertions(+) create mode 100644 src/main/java/matcher/srcprocess/Jadx.java diff --git a/build.gradle b/build.gradle index 55a51f9d..2e1e89f4 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,12 @@ dependencies { implementation "net.fabricmc:cfr:${fabric_cfr_version}" implementation "org.vineflower:vineflower:${vineflower_version}" implementation "org.bitbucket.mstrobel:procyon-compilertools:${procyon_version}" + implementation ("io.github.skylot:jadx-core:${jadx_version}") { + exclude group: 'com.android.tools.build', module: 'aapt2-proto' + } + implementation ("io.github.skylot:jadx-java-input:${jadx_version}") { + exclude group: 'io.github.skylot', module: 'raung-disasm' + } runtimeOnly "org.tinylog:tinylog-impl:${tinylog_version}" runtimeOnly "org.tinylog:slf4j-tinylog:${tinylog_version}" @@ -97,6 +103,11 @@ extraJavaModuleInfo { // Procyon automaticModule("org.bitbucket.mstrobel:procyon-compilertools", "procyon.compilertools") + + // JADX + automaticModule("io.github.skylot:jadx-core", "jadx.core") + automaticModule("io.github.skylot:jadx-plugins-api", "jadx.plugins.api") + automaticModule("io.github.skylot:jadx-java-input", "jadx.plugins.java_input") } application { diff --git a/gradle.properties b/gradle.properties index 1bbdf7a5..457ad746 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,7 @@ asm_version = 9.6 fabric_cfr_version = 0.2.1 vineflower_version = 1.9.3 procyon_version = 0.6.0 +jadx_version = 1.4.7 mappingio_version = 0.5.0 javaparser_version = 3.25.6 javafx_version = 21.0.1 diff --git a/src/main/java/matcher/Util.java b/src/main/java/matcher/Util.java index 6670b9bf..33e1fb19 100644 --- a/src/main/java/matcher/Util.java +++ b/src/main/java/matcher/Util.java @@ -2,6 +2,8 @@ import java.io.Closeable; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.io.UncheckedIOException; import java.net.URI; import java.net.URISyntaxException; @@ -48,6 +50,15 @@ public static Set copySet(Set set) { } } + public static String getStackTrace(Throwable t) { + if (t == null) return null; + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + return sw.toString(); + } + public static FileSystem iterateJar(Path archive, boolean autoClose, Consumer handler) { boolean existing = false; FileSystem fs = null; diff --git a/src/main/java/matcher/srcprocess/BuiltinDecompiler.java b/src/main/java/matcher/srcprocess/BuiltinDecompiler.java index 5ccffd91..ff5c58d9 100644 --- a/src/main/java/matcher/srcprocess/BuiltinDecompiler.java +++ b/src/main/java/matcher/srcprocess/BuiltinDecompiler.java @@ -5,6 +5,7 @@ public enum BuiltinDecompiler { CFR("CFR", Cfr::new), VINEFLOWER("Vineflower", Vineflower::new), + JADX("JADX", Jadx::new), PROCYON("Procyon", Procyon::new); BuiltinDecompiler(String name, Supplier supplier) { diff --git a/src/main/java/matcher/srcprocess/Jadx.java b/src/main/java/matcher/srcprocess/Jadx.java new file mode 100644 index 00000000..7ccc9aa9 --- /dev/null +++ b/src/main/java/matcher/srcprocess/Jadx.java @@ -0,0 +1,75 @@ +package matcher.srcprocess; + +import java.io.IOException; +import java.util.function.Consumer; + +import jadx.api.CommentsLevel; +import jadx.api.JadxArgs; +import jadx.api.JadxDecompiler; +import jadx.api.impl.NoOpCodeCache; +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.IResourceData; +import jadx.plugins.input.java.JavaClassReader; +import jadx.plugins.input.java.data.JavaClassData; + +import matcher.NameType; +import matcher.Util; +import matcher.type.ClassFeatureExtractor; +import matcher.type.ClassInstance; + +public class Jadx implements Decompiler { + @Override + public String decompile(ClassInstance cls, ClassFeatureExtractor env, NameType nameType) { + String errorMessage = null; + final String fullClassName = cls.getName(NameType.PLAIN, true); + + try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) { + jadx.addCustomLoad(new ILoadResult() { + @Override + public void close() throws IOException { } + + @Override + public void visitClasses(Consumer consumer) { + consumer.accept(new JavaClassData(new JavaClassReader(0, + fullClassName + ".class", cls.serialize(nameType)))); + } + + @Override + public void visitResources(Consumer consumer) { } + + @Override + public boolean isEmpty() { + return false; + } + }); + jadx.load(); + + assert jadx.getClassesWithInners().size() == 1; + return jadx.getClassesWithInners().get(0).getCode(); + } catch (Exception e) { + errorMessage = Util.getStackTrace(e); + } + + throw new RuntimeException(errorMessage != null ? errorMessage : "JADX couldn't find the requested class"); + } + + private static final JadxArgs jadxArgs; + + static { + jadxArgs = new JadxArgs() { + @Override + public void close() { + return; + } + }; + jadxArgs.setCodeCache(NoOpCodeCache.INSTANCE); + jadxArgs.setShowInconsistentCode(true); + jadxArgs.setInlineAnonymousClasses(false); + jadxArgs.setInlineMethods(false); + jadxArgs.setSkipResources(true); + jadxArgs.setRenameValid(false); + jadxArgs.setRespectBytecodeAccModifiers(true); + jadxArgs.setCommentsLevel(CommentsLevel.INFO); + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index c5f2b99c..3ac85dbe 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -26,6 +26,9 @@ requires org.objectweb.asm.tree.analysis; requires org.objectweb.asm.util; requires procyon.compilertools; + requires jadx.core; + requires jadx.plugins.api; + requires jadx.plugins.java_input; requires transitive net.fabricmc.mappingio; uses matcher.Plugin; diff --git a/src/main/resources/tinylog-dev.properties b/src/main/resources/tinylog-dev.properties index 5688dfda..b7f71f24 100644 --- a/src/main/resources/tinylog-dev.properties +++ b/src/main/resources/tinylog-dev.properties @@ -1,3 +1,5 @@ writer = console writer.format = {date: HH:mm:ss.SSS} [{thread}/{level}]: {message} writer.level = debug + +level@jadx = warn diff --git a/src/main/resources/tinylog.properties b/src/main/resources/tinylog.properties index d90124ce..ddbcb23d 100644 --- a/src/main/resources/tinylog.properties +++ b/src/main/resources/tinylog.properties @@ -1,3 +1,5 @@ writer = console writer.format = {date: HH:mm:ss} [{level}]: {message} writer.level = info + +level@jadx = error