From 9e7c14cbcdce6ae0fa2d40f7a81cd75ee6b5a1bf Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Fri, 7 Jul 2023 17:28:04 +0200 Subject: [PATCH 01/21] Initial support for JADX decompiler --- build.gradle | 1 + .../cuchaz/enigma/gui/config/Decompiler.java | 1 + enigma/build.gradle | 2 + .../cuchaz/enigma/analysis/BuiltinPlugin.java | 1 + .../cuchaz/enigma/source/Decompilers.java | 2 + .../enigma/source/jadx/JadxDecompiler.java | 36 +++++++ .../cuchaz/enigma/source/jadx/JadxSource.java | 100 ++++++++++++++++++ 7 files changed, 143 insertions(+) create mode 100644 enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java diff --git a/build.gradle b/build.gradle index 09f7e91fb..c97d87fcd 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ subprojects { mavenLocal() mavenCentral() maven { url 'https://maven.fabricmc.net/' } + google() } dependencies { diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java index f9b5cbe12..a7d853e49 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Decompiler.java @@ -5,6 +5,7 @@ public enum Decompiler { CFR("CFR", Decompilers.CFR), + JADX("JADX", Decompilers.JADX), PROCYON("Procyon", Decompilers.PROCYON), BYTECODE("Bytecode", Decompilers.BYTECODE); diff --git a/enigma/build.gradle b/enigma/build.gradle index 8cbfec28a..e926ee7ed 100644 --- a/enigma/build.gradle +++ b/enigma/build.gradle @@ -10,6 +10,8 @@ dependencies { implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.6.0' implementation 'net.fabricmc:cfr:0.2.1' + implementation 'io.github.skylot:jadx-core:1.4.7' + implementation 'io.github.skylot:jadx-java-input:1.4.7' proGuard 'com.guardsquare:proguard-base:7.3.0' diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java index 45dac2c16..6a829a726 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java @@ -57,6 +57,7 @@ private void registerEnumNamingService(EnigmaPluginContext ctx) { private void registerDecompilerServices(EnigmaPluginContext ctx) { ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON); ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR); + ctx.registerService("enigma:jadx", DecompilerService.TYPE, ctx1 -> Decompilers.JADX); ctx.registerService("enigma:bytecode", DecompilerService.TYPE, ctx1 -> Decompilers.BYTECODE); } diff --git a/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java b/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java index 0e3244db4..63f82200c 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java +++ b/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java @@ -2,10 +2,12 @@ import cuchaz.enigma.source.bytecode.BytecodeDecompiler; import cuchaz.enigma.source.cfr.CfrDecompiler; +import cuchaz.enigma.source.jadx.JadxDecompiler; import cuchaz.enigma.source.procyon.ProcyonDecompiler; public class Decompilers { public static final DecompilerService PROCYON = ProcyonDecompiler::new; public static final DecompilerService CFR = CfrDecompiler::new; + public static final DecompilerService JADX = JadxDecompiler::new; public static final DecompilerService BYTECODE = BytecodeDecompiler::new; } diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java new file mode 100644 index 000000000..00d016e47 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java @@ -0,0 +1,36 @@ +package cuchaz.enigma.source.jadx; + +import org.checkerframework.checker.nullness.qual.Nullable; +import jadx.api.JadxArgs; +import jadx.api.impl.NoOpCodeCache; + +import cuchaz.enigma.classprovider.ClassProvider; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceSettings; +import cuchaz.enigma.translation.mapping.EntryRemapper; + +public class JadxDecompiler implements Decompiler { + private final SourceSettings settings; + private final ClassProvider classProvider; + + public JadxDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { + this.settings = sourceSettings; + this.classProvider = classProvider; + } + + @Override + public Source getSource(String className, @Nullable EntryRemapper mapper) { + return new JadxSource(settings, createJadxArgs(), classProvider.get(className), mapper); + } + + private JadxArgs createJadxArgs() { + JadxArgs args = new JadxArgs(); + args.setCodeCache(NoOpCodeCache.INSTANCE); + args.setShowInconsistentCode(true); + args.setRenameValid(false); + args.setThreadsCount(Runtime.getRuntime().availableProcessors() / 2); + + return args; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java new file mode 100644 index 000000000..8dc92e916 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -0,0 +1,100 @@ +package cuchaz.enigma.source.jadx; + +import java.io.IOException; +import java.util.function.Consumer; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.objectweb.asm.tree.ClassNode; +import jadx.api.JadxArgs; +import jadx.api.JadxDecompiler; +import jadx.api.JavaClass; +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 cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.SourceSettings; +import cuchaz.enigma.source.Token; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.utils.AsmUtil; + +public class JadxSource implements Source { + private final SourceSettings settings; + private final JadxArgs jadxArgs; + private final ClassNode classNode; + private final EntryRemapper mapper; + private SourceIndex index; + + public JadxSource(SourceSettings settings, JadxArgs jadxArgs, ClassNode classNode, @Nullable EntryRemapper mapper) { + this.settings = settings; + this.jadxArgs = jadxArgs; + this.classNode = classNode; + this.mapper = mapper; + } + + @Override + public Source withJavadocs(EntryRemapper mapper) { + return new JadxSource(settings, jadxArgs, classNode, mapper); + } + + @Override + public SourceIndex index() { + ensureDecompiled(); + return index; + } + + @Override + public String asString() { + ensureDecompiled(); + return index.getSource(); + } + + private void ensureDecompiled() { + if (index != null) { + return; + } + + try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) { + jadx.addCustomLoad(new ILoadResult() { + @Override + public void close() throws IOException { + return; + } + + @Override + public void visitClasses(Consumer<IClassData> consumer) { + consumer.accept(new JavaClassData(new JavaClassReader(0, classNode.name + ".class", AsmUtil.nodeToBytes(classNode)))); + } + + @Override + public void visitResources(Consumer<IResourceData> consumer) { + return; + } + + @Override + public boolean isEmpty() { + return false; + } + }); + jadx.load(); + + JavaClass decompiledClass = jadx.getClasses().get(0); + SourceIndex index = new SourceIndex(decompiledClass.getCode()); + int pos = decompiledClass.getDefPos() - 2; + + Token token = new Token(pos, pos + decompiledClass.getName().length(), decompiledClass.getName()); + index.addDeclaration(token, parse(decompiledClass)); + this.index = index; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private ClassEntry parse(JavaClass javaClass) { + return new ClassEntry(javaClass.getRawName()); + } +} From 64563fe5c6ca1f34e8a7f3f5c9874052d8ae82c0 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Fri, 7 Jul 2023 23:51:12 +0200 Subject: [PATCH 02/21] Fix JADX token positioning --- .../main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java | 1 + .../src/main/java/cuchaz/enigma/source/jadx/JadxSource.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java index 00d016e47..7eea4c36f 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java @@ -17,6 +17,7 @@ public class JadxDecompiler implements Decompiler { public JadxDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { this.settings = sourceSettings; this.classProvider = classProvider; + System.getProperties().setProperty("line.separator", "\n"); } @Override diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 8dc92e916..22deb0ff1 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -84,8 +84,8 @@ public boolean isEmpty() { JavaClass decompiledClass = jadx.getClasses().get(0); SourceIndex index = new SourceIndex(decompiledClass.getCode()); - int pos = decompiledClass.getDefPos() - 2; - + int pos = decompiledClass.getDefPos() - 1; + Token token = new Token(pos, pos + decompiledClass.getName().length(), decompiledClass.getName()); index.addDeclaration(token, parse(decompiledClass)); this.index = index; From d73cac63f48ac1cfa27d79af050d0083401e9f6e Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Sat, 8 Jul 2023 15:47:29 +0200 Subject: [PATCH 03/21] Support more tokens, initial Javadoc support --- .../enigma/source/jadx/JadxDecompiler.java | 15 +- .../cuchaz/enigma/source/jadx/JadxSource.java | 193 ++++++++++++++++-- 2 files changed, 191 insertions(+), 17 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java index 7eea4c36f..98ef602ff 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java @@ -2,7 +2,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import jadx.api.JadxArgs; -import jadx.api.impl.NoOpCodeCache; +import jadx.api.impl.InMemoryCodeCache; import cuchaz.enigma.classprovider.ClassProvider; import cuchaz.enigma.source.Decompiler; @@ -22,15 +22,22 @@ public JadxDecompiler(ClassProvider classProvider, SourceSettings sourceSettings @Override public Source getSource(String className, @Nullable EntryRemapper mapper) { - return new JadxSource(settings, createJadxArgs(), classProvider.get(className), mapper); + return new JadxSource(settings, this::createJadxArgs, classProvider.get(className), mapper); } private JadxArgs createJadxArgs() { JadxArgs args = new JadxArgs(); - args.setCodeCache(NoOpCodeCache.INSTANCE); + args.setCodeCache(new InMemoryCodeCache()); args.setShowInconsistentCode(true); + args.setInlineAnonymousClasses(false); + args.setInlineMethods(false); + args.setRespectBytecodeAccModifiers(true); args.setRenameValid(false); - args.setThreadsCount(Runtime.getRuntime().availableProcessors() / 2); + + if (settings.removeImports) { + // Commented out for now, since JADX would use full identifiers everywhere + // args.setUseImports(false); + } return args; } diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 22deb0ff1..cae409ae0 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -1,16 +1,45 @@ package cuchaz.enigma.source.jadx; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.function.Consumer; +import java.util.function.Supplier; +import org.benf.cfr.reader.bytecode.analysis.types.JavaTypeInstance; +import org.benf.cfr.reader.bytecode.analysis.types.MethodPrototype; import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.tree.ClassNode; + +import com.google.common.base.Strings; + +import jadx.api.ICodeInfo; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JavaClass; +import jadx.api.JavaField; +import jadx.api.JavaMethod; +import jadx.api.JavaNode; +import jadx.api.JavaVariable; +import jadx.api.metadata.ICodeAnnotation; +import jadx.api.metadata.ICodeMetadata; +import jadx.api.metadata.ICodeNodeRef; +import jadx.api.metadata.ICodeAnnotation.AnnType; +import jadx.api.metadata.annotations.NodeDeclareRef; +import jadx.api.metadata.annotations.VarNode; +import jadx.api.metadata.annotations.VarRef; import jadx.api.plugins.input.data.IClassData; import jadx.api.plugins.input.data.ILoadResult; import jadx.api.plugins.input.data.IResourceData; +import jadx.api.utils.CodeUtils; +import jadx.core.codegen.TypeGen; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.ICodeNode; +import jadx.core.dex.nodes.MethodNode; import jadx.plugins.input.java.JavaClassReader; import jadx.plugins.input.java.data.JavaClassData; @@ -19,26 +48,35 @@ import cuchaz.enigma.source.SourceSettings; import cuchaz.enigma.source.Token; import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import cuchaz.enigma.utils.AsmUtil; public class JadxSource implements Source { private final SourceSettings settings; - private final JadxArgs jadxArgs; + private final Supplier<JadxArgs> jadxArgsSupplier; private final ClassNode classNode; private final EntryRemapper mapper; private SourceIndex index; + private MethodEntry currentMethod = null; - public JadxSource(SourceSettings settings, JadxArgs jadxArgs, ClassNode classNode, @Nullable EntryRemapper mapper) { + public JadxSource(SourceSettings settings, Supplier<JadxArgs> jadxArgsSupplier, ClassNode classNode, @Nullable EntryRemapper mapper) { this.settings = settings; - this.jadxArgs = jadxArgs; + this.jadxArgsSupplier = jadxArgsSupplier; this.classNode = classNode; this.mapper = mapper; } @Override public Source withJavadocs(EntryRemapper mapper) { - return new JadxSource(settings, jadxArgs, classNode, mapper); + return new JadxSource(settings, jadxArgsSupplier, classNode, mapper); } @Override @@ -58,7 +96,7 @@ private void ensureDecompiled() { return; } - try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) { + try (JadxDecompiler jadx = new JadxDecompiler(jadxArgsSupplier.get())) { jadx.addCustomLoad(new ILoadResult() { @Override public void close() throws IOException { @@ -81,20 +119,149 @@ public boolean isEmpty() { } }); jadx.load(); + JavaClass cls = jadx.getClasses().get(0); + + // Javadocs + if (mapper != null) { + String comment; + + if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(classEntryOf(cls.getClassNode())).javadoc())) != null) { + cls.getClassNode().addAttr(AType.CODE_COMMENTS, Strings.emptyToNull(comment)); + } + + for (JavaField fld : cls.getFields()) { + if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(fieldEntryOf(fld.getFieldNode())).javadoc())) != null) { + fld.getFieldNode().addAttr(AType.CODE_COMMENTS, Strings.emptyToNull(comment)); + } + } + + for (JavaMethod mth : cls.getMethods()) { + if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(methodEntryOf(mth.getMethodNode())).javadoc())) != null) { + mth.getMethodNode().addAttr(AType.CODE_COMMENTS, Strings.emptyToNull(comment)); + } + } + } - JavaClass decompiledClass = jadx.getClasses().get(0); - SourceIndex index = new SourceIndex(decompiledClass.getCode()); - int pos = decompiledClass.getDefPos() - 1; + index = new SourceIndex(cls.getCode()); - Token token = new Token(pos, pos + decompiledClass.getName().length(), decompiledClass.getName()); - index.addDeclaration(token, parse(decompiledClass)); - this.index = index; + // Tokens + cls.getCodeInfo().getCodeMetadata().searchDown(0, (pos, ann) -> { + processAnnotatedElement(pos, ann, cls.getCodeInfo()); + return null; + }); } catch (Exception e) { throw new RuntimeException(e); } } - private ClassEntry parse(JavaClass javaClass) { - return new ClassEntry(javaClass.getRawName()); + private void processAnnotatedElement(int pos, ICodeAnnotation ann, ICodeInfo codeInfo) { + if (ann == null) return; + + if (ann instanceof NodeDeclareRef ref) { + processAnnotatedElement(pos, ref.getNode(), codeInfo); + } else if (ann instanceof jadx.core.dex.nodes.ClassNode cls) { + Token token = new Token(pos, pos + cls.getShortName().length(), cls.getShortName()); + + if (pos == cls.getDefPosition()) { + index.addDeclaration(token, classEntryOf(cls)); + } else { + index.addReference(token, classEntryOf(cls), classEntryOf(cls.getParentClass())); + } + } else if (ann instanceof FieldNode fld) { + Token token = new Token(pos, pos + fld.getName().length(), fld.getName()); + + if (pos == fld.getDefPosition()) { + index.addDeclaration(token, fieldEntryOf(fld)); + } else { + index.addReference(token, fieldEntryOf(fld), classEntryOf(fld.getParentClass())); + } + } else if (ann instanceof MethodNode mth) { + if (mth.getName().equals("<clinit>")) return; + Token token = new Token(pos, pos + mth.getName().length(), mth.getName()); + + if (mth.isConstructor()) { + processAnnotatedElement(pos, mth.getTopParentClass(), codeInfo); + } else if (pos == mth.getDefPosition()) { + index.addDeclaration(token, methodEntryOf(mth)); + } else { + index.addReference(token, methodEntryOf(mth), classEntryOf(mth.getParentClass())); + } + } else if (ann instanceof VarNode var) { + if (!getMethodArgs(var.getMth(), codeInfo).contains(var)) return; + Token token = new Token(pos, pos + var.getName().length(), var.getName()); + + if (pos == var.getDefPosition()) { + index.addDeclaration(token, paramEntryOf(var, codeInfo)); + } else { + index.addReference(token, paramEntryOf(var, codeInfo), methodEntryOf(var.getMth())); + } + } else if (ann instanceof VarRef varRef) { + processAnnotatedElement(pos, codeInfo.getCodeMetadata().getAt(varRef.getRefPos()), codeInfo); + } + } + + private Map<jadx.core.dex.nodes.ClassNode, String> internalNames = new HashMap<>(); + private Map<jadx.core.dex.nodes.ClassNode, ClassEntry> classMap = new HashMap<>(); + private Map<FieldNode, FieldEntry> fieldMap = new HashMap<>(); + private Map<MethodNode, MethodEntry> methodMap = new HashMap<>(); + private Map<VarNode, LocalVariableEntry> varMap = new HashMap<>(); + private Map<MethodNode, List<VarNode>> argMap = new HashMap<>(); + + private String internalNameOf(jadx.core.dex.nodes.ClassNode cls) { + return internalNames.computeIfAbsent(cls, (unused) -> cls.getClassInfo().makeRawFullName().replace('.', '/')); + } + + private ClassEntry classEntryOf(jadx.core.dex.nodes.ClassNode cls) { + if (cls == null) return null; + return classMap.computeIfAbsent(cls, (unused) -> new ClassEntry(internalNameOf(cls))); + } + + private FieldEntry fieldEntryOf(FieldNode fld) { + return fieldMap.computeIfAbsent(fld, (unused) -> + new FieldEntry(classEntryOf(fld.getParentClass()), fld.getName(), new TypeDescriptor(TypeGen.signature(fld.getType())))); + } + + private MethodEntry methodEntryOf(MethodNode mth) { + return methodMap.computeIfAbsent(mth, (unused) -> { + MethodInfo mthInfo = mth.getMethodInfo(); + MethodDescriptor desc = new MethodDescriptor(mthInfo.getShortId().substring(mthInfo.getName().length())); + return new MethodEntry(classEntryOf(mth.getParentClass()), mthInfo.getName(), desc); + }); + } + + private LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { + return varMap.computeIfAbsent(param, (unused) -> { + MethodEntry owner = methodEntryOf(param.getMth()); + int index = getMethodArgs(param.getMth(), codeInfo).indexOf(param); + return new LocalVariableEntry(owner, index, param.getName(), true, null); + }); + } + + private List<VarNode> getMethodArgs(MethodNode mth, ICodeInfo codeInfo) { + return argMap.computeIfAbsent(mth, (unused) -> { + int mthDefPos = mth.getDefPosition(); + int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); + List<VarNode> args = new ArrayList<>(); + codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> { + if (pos > lineEndPos) { + // Stop at line end + return Boolean.TRUE; + } + if (ann instanceof NodeDeclareRef) { + ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode(); + if (declRef instanceof VarNode) { + VarNode varNode = (VarNode) declRef; + if (!varNode.getMth().equals(mth)) { + // Stop if we've gone too far and have entered a different method + return Boolean.TRUE; + } + args.add(varNode); + } + } + return null; + }); + + return args; + }); } } From c22852e9b50af64d37054fb3a3b064648d5dbc85 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Mon, 10 Jul 2023 14:11:32 +0200 Subject: [PATCH 04/21] Fix reloading on Javadoc change --- .../cuchaz/enigma/source/jadx/JadxSource.java | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index cae409ae0..7c891abf9 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -8,25 +8,18 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import org.benf.cfr.reader.bytecode.analysis.types.JavaTypeInstance; -import org.benf.cfr.reader.bytecode.analysis.types.MethodPrototype; import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.tree.ClassNode; - import com.google.common.base.Strings; - import jadx.api.ICodeInfo; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JavaClass; import jadx.api.JavaField; import jadx.api.JavaMethod; -import jadx.api.JavaNode; -import jadx.api.JavaVariable; +import jadx.api.impl.InMemoryCodeCache; import jadx.api.metadata.ICodeAnnotation; -import jadx.api.metadata.ICodeMetadata; import jadx.api.metadata.ICodeNodeRef; -import jadx.api.metadata.ICodeAnnotation.AnnType; import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.api.metadata.annotations.VarNode; import jadx.api.metadata.annotations.VarRef; @@ -38,7 +31,6 @@ import jadx.core.dex.attributes.AType; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.ICodeNode; import jadx.core.dex.nodes.MethodNode; import jadx.plugins.input.java.JavaClassReader; import jadx.plugins.input.java.data.JavaClassData; @@ -48,11 +40,8 @@ import cuchaz.enigma.source.SourceSettings; import cuchaz.enigma.source.Token; import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.representation.AccessFlags; import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.Signature; import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.FieldEntry; import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; @@ -65,7 +54,6 @@ public class JadxSource implements Source { private final ClassNode classNode; private final EntryRemapper mapper; private SourceIndex index; - private MethodEntry currentMethod = null; public JadxSource(SourceSettings settings, Supplier<JadxArgs> jadxArgsSupplier, ClassNode classNode, @Nullable EntryRemapper mapper) { this.settings = settings; @@ -122,24 +110,38 @@ public boolean isEmpty() { JavaClass cls = jadx.getClasses().get(0); // Javadocs + // TODO: Make this less hacky if (mapper != null) { + int reload = 0; String comment; - if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(classEntryOf(cls.getClassNode())).javadoc())) != null) { - cls.getClassNode().addAttr(AType.CODE_COMMENTS, Strings.emptyToNull(comment)); - } - for (JavaField fld : cls.getFields()) { if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(fieldEntryOf(fld.getFieldNode())).javadoc())) != null) { - fld.getFieldNode().addAttr(AType.CODE_COMMENTS, Strings.emptyToNull(comment)); + fld.getFieldNode().addAttr(AType.CODE_COMMENTS, comment); + reload = 1; } } - + for (JavaMethod mth : cls.getMethods()) { if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(methodEntryOf(mth.getMethodNode())).javadoc())) != null) { - mth.getMethodNode().addAttr(AType.CODE_COMMENTS, Strings.emptyToNull(comment)); + mth.getMethodNode().addAttr(AType.CODE_COMMENTS, comment); + reload = 1; } } + + if (reload == 1) { + jadx.getArgs().setCodeCache(new InMemoryCodeCache()); + reload = 2; + } + + if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(classEntryOf(cls.getClassNode())).javadoc())) != null) { + cls.getClassNode().addAttr(AType.CODE_COMMENTS, comment); + if (reload != 2) reload = 1; + } + + if (reload == 1) { + jadx.getArgs().setCodeCache(new InMemoryCodeCache()); + } } index = new SourceIndex(cls.getCode()); @@ -247,17 +249,22 @@ private List<VarNode> getMethodArgs(MethodNode mth, ICodeInfo codeInfo) { // Stop at line end return Boolean.TRUE; } + if (ann instanceof NodeDeclareRef) { ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode(); + if (declRef instanceof VarNode) { VarNode varNode = (VarNode) declRef; + if (!varNode.getMth().equals(mth)) { // Stop if we've gone too far and have entered a different method return Boolean.TRUE; } + args.add(varNode); } } + return null; }); From 18aaca2a551b0c3b8d85ed7265dbaa3f2a0190d1 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Mon, 10 Jul 2023 14:28:55 +0200 Subject: [PATCH 05/21] Simplify casts with instanceof pattern matching --- .../cuchaz/enigma/source/jadx/JadxSource.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 7c891abf9..b9de2266e 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -250,19 +250,13 @@ private List<VarNode> getMethodArgs(MethodNode mth, ICodeInfo codeInfo) { return Boolean.TRUE; } - if (ann instanceof NodeDeclareRef) { - ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode(); - - if (declRef instanceof VarNode) { - VarNode varNode = (VarNode) declRef; - - if (!varNode.getMth().equals(mth)) { - // Stop if we've gone too far and have entered a different method - return Boolean.TRUE; - } - - args.add(varNode); + if (ann instanceof NodeDeclareRef ref && ref.getNode() instanceof VarNode varNode) { + if (!varNode.getMth().equals(mth)) { + // Stop if we've gone too far and have entered a different method + return Boolean.TRUE; } + + args.add(varNode); } return null; From d54e95ce48bac1159c7348631edcad96259bd7cd Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Mon, 10 Jul 2023 14:48:03 +0200 Subject: [PATCH 06/21] Remove google maven dependency --- build.gradle | 1 - enigma/build.gradle | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c97d87fcd..09f7e91fb 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,6 @@ subprojects { mavenLocal() mavenCentral() maven { url 'https://maven.fabricmc.net/' } - google() } dependencies { diff --git a/enigma/build.gradle b/enigma/build.gradle index e926ee7ed..8825d6ad9 100644 --- a/enigma/build.gradle +++ b/enigma/build.gradle @@ -10,7 +10,9 @@ dependencies { implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.6.0' implementation 'net.fabricmc:cfr:0.2.1' - implementation 'io.github.skylot:jadx-core:1.4.7' + implementation ('io.github.skylot:jadx-core:1.4.7') { + exclude group: 'com.android.tools.build', module: 'aapt2-proto' + } implementation 'io.github.skylot:jadx-java-input:1.4.7' proGuard 'com.guardsquare:proguard-base:7.3.0' From e9004f937a5261e823ff3561cb3cc68ade67ff31 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Tue, 11 Jul 2023 09:58:23 +0200 Subject: [PATCH 07/21] Fix JADX line separator without setting system property --- .../enigma/source/jadx/JadxDecompiler.java | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java index 98ef602ff..98b3f985e 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java @@ -1,6 +1,9 @@ package cuchaz.enigma.source.jadx; +import java.lang.reflect.Field; + import org.checkerframework.checker.nullness.qual.Nullable; +import jadx.api.ICodeWriter; import jadx.api.JadxArgs; import jadx.api.impl.InMemoryCodeCache; @@ -13,18 +16,52 @@ public class JadxDecompiler implements Decompiler { private final SourceSettings settings; private final ClassProvider classProvider; + private boolean lineEndingFixed; public JadxDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { this.settings = sourceSettings; this.classProvider = classProvider; - System.getProperties().setProperty("line.separator", "\n"); } @Override public Source getSource(String className, @Nullable EntryRemapper mapper) { + fixLineEnding(); return new JadxSource(settings, this::createJadxArgs, classProvider.get(className), mapper); } + /** + * JADX uses the system default line ending, but SyntaxPane does not (seems to be hardcoded to \n). + * This causes tokens to be offset by one char per preceding line, since Windows' \r\n is one char longer than plain \n or \r. + * Unfortunately, there's currently no way of telling JADX to use a different value (see https://github.com/skylot/jadx/issues/1948), + * and reflection doesn't help us either (can't override interface fields), hence why we need to use Unsafe. + */ + private void fixLineEnding() { + if (lineEndingFixed || System.getProperty("line.separator").equals("\n")) { + return; + } + + try { + // Get Unsafe reference + Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafe.get(null); + + // Force NL's value to be loaded from the system property + ICodeWriter.NL.toString(); + + // Get NL via reflection + Field NL = ICodeWriter.class.getDeclaredField("NL"); + + // Overwrite its value with \n + long offset = unsafe.staticFieldOffset(NL); + unsafe.putObject(ICodeWriter.class, offset, "\n"); + } catch (Throwable throwable) { + throw new RuntimeException("Failed to change JADX's line separator to '\\n'", throwable); + } + + lineEndingFixed = true; + } + private JadxArgs createJadxArgs() { JadxArgs args = new JadxArgs(); args.setCodeCache(new InMemoryCodeCache()); From f97b61ba935ccae30490478ee848e58072c1363c Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Tue, 11 Jul 2023 09:58:43 +0200 Subject: [PATCH 08/21] Don't forget to close old JADX code caches --- enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index b9de2266e..3009455b9 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -19,7 +19,6 @@ import jadx.api.JavaMethod; import jadx.api.impl.InMemoryCodeCache; import jadx.api.metadata.ICodeAnnotation; -import jadx.api.metadata.ICodeNodeRef; import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.api.metadata.annotations.VarNode; import jadx.api.metadata.annotations.VarRef; @@ -130,6 +129,7 @@ public boolean isEmpty() { } if (reload == 1) { + jadx.getArgs().getCodeCache().close(); jadx.getArgs().setCodeCache(new InMemoryCodeCache()); reload = 2; } @@ -140,6 +140,7 @@ public boolean isEmpty() { } if (reload == 1) { + jadx.getArgs().getCodeCache().close(); jadx.getArgs().setCodeCache(new InMemoryCodeCache()); } } From bcc0d28dd901514287acf218bd6aad7a45ed0941 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Wed, 12 Jul 2023 17:20:05 +0200 Subject: [PATCH 09/21] Revert usage of Unsafe, reset system property after usage --- .../enigma/source/jadx/JadxDecompiler.java | 40 +------- .../cuchaz/enigma/source/jadx/JadxSource.java | 95 ++++++++++++------- 2 files changed, 60 insertions(+), 75 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java index 98b3f985e..3e5892e20 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java @@ -1,11 +1,8 @@ package cuchaz.enigma.source.jadx; -import java.lang.reflect.Field; - -import org.checkerframework.checker.nullness.qual.Nullable; -import jadx.api.ICodeWriter; import jadx.api.JadxArgs; import jadx.api.impl.InMemoryCodeCache; +import org.checkerframework.checker.nullness.qual.Nullable; import cuchaz.enigma.classprovider.ClassProvider; import cuchaz.enigma.source.Decompiler; @@ -16,7 +13,6 @@ public class JadxDecompiler implements Decompiler { private final SourceSettings settings; private final ClassProvider classProvider; - private boolean lineEndingFixed; public JadxDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { this.settings = sourceSettings; @@ -25,43 +21,9 @@ public JadxDecompiler(ClassProvider classProvider, SourceSettings sourceSettings @Override public Source getSource(String className, @Nullable EntryRemapper mapper) { - fixLineEnding(); return new JadxSource(settings, this::createJadxArgs, classProvider.get(className), mapper); } - /** - * JADX uses the system default line ending, but SyntaxPane does not (seems to be hardcoded to \n). - * This causes tokens to be offset by one char per preceding line, since Windows' \r\n is one char longer than plain \n or \r. - * Unfortunately, there's currently no way of telling JADX to use a different value (see https://github.com/skylot/jadx/issues/1948), - * and reflection doesn't help us either (can't override interface fields), hence why we need to use Unsafe. - */ - private void fixLineEnding() { - if (lineEndingFixed || System.getProperty("line.separator").equals("\n")) { - return; - } - - try { - // Get Unsafe reference - Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - theUnsafe.setAccessible(true); - sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafe.get(null); - - // Force NL's value to be loaded from the system property - ICodeWriter.NL.toString(); - - // Get NL via reflection - Field NL = ICodeWriter.class.getDeclaredField("NL"); - - // Overwrite its value with \n - long offset = unsafe.staticFieldOffset(NL); - unsafe.putObject(ICodeWriter.class, offset, "\n"); - } catch (Throwable throwable) { - throw new RuntimeException("Failed to change JADX's line separator to '\\n'", throwable); - } - - lineEndingFixed = true; - } - private JadxArgs createJadxArgs() { JadxArgs args = new JadxArgs(); args.setCodeCache(new InMemoryCodeCache()); diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 3009455b9..a6c7db34a 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -108,44 +108,50 @@ public boolean isEmpty() { jadx.load(); JavaClass cls = jadx.getClasses().get(0); - // Javadocs - // TODO: Make this less hacky - if (mapper != null) { - int reload = 0; - String comment; - - for (JavaField fld : cls.getFields()) { - if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(fieldEntryOf(fld.getFieldNode())).javadoc())) != null) { - fld.getFieldNode().addAttr(AType.CODE_COMMENTS, comment); - reload = 1; + runWithFixedLineSeparator(() -> { + try { + // Javadocs + // TODO: Make this less hacky + if (mapper != null) { + int reload = 0; + String comment; + + for (JavaField fld : cls.getFields()) { + if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(fieldEntryOf(fld.getFieldNode())).javadoc())) != null) { + fld.getFieldNode().addAttr(AType.CODE_COMMENTS, comment); + reload = 1; + } + } + + for (JavaMethod mth : cls.getMethods()) { + if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(methodEntryOf(mth.getMethodNode())).javadoc())) != null) { + mth.getMethodNode().addAttr(AType.CODE_COMMENTS, comment); + reload = 1; + } + } + + if (reload == 1) { + jadx.getArgs().getCodeCache().close(); + jadx.getArgs().setCodeCache(new InMemoryCodeCache()); + reload = 2; + } + + if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(classEntryOf(cls.getClassNode())).javadoc())) != null) { + cls.getClassNode().addAttr(AType.CODE_COMMENTS, comment); + if (reload != 2) reload = 1; + } + + if (reload == 1) { + jadx.getArgs().getCodeCache().close(); + jadx.getArgs().setCodeCache(new InMemoryCodeCache()); + } } + + index = new SourceIndex(cls.getCode()); + } catch (Exception e) { + throw new RuntimeException(e); } - - for (JavaMethod mth : cls.getMethods()) { - if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(methodEntryOf(mth.getMethodNode())).javadoc())) != null) { - mth.getMethodNode().addAttr(AType.CODE_COMMENTS, comment); - reload = 1; - } - } - - if (reload == 1) { - jadx.getArgs().getCodeCache().close(); - jadx.getArgs().setCodeCache(new InMemoryCodeCache()); - reload = 2; - } - - if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(classEntryOf(cls.getClassNode())).javadoc())) != null) { - cls.getClassNode().addAttr(AType.CODE_COMMENTS, comment); - if (reload != 2) reload = 1; - } - - if (reload == 1) { - jadx.getArgs().getCodeCache().close(); - jadx.getArgs().setCodeCache(new InMemoryCodeCache()); - } - } - - index = new SourceIndex(cls.getCode()); + }); // Tokens cls.getCodeInfo().getCodeMetadata().searchDown(0, (pos, ann) -> { @@ -157,6 +163,23 @@ public boolean isEmpty() { } } + /** + * JADX uses the system default line ending, but JEditorPane does not (seems to be hardcoded to \n). + * This causes tokens to be offset by one char per preceding line, since Windows' \r\n is one char longer than plain \r or \n. + * Unfortunately, the only way of making JADX use a different value is by changing the system property, which may cause issues + * elsewhere in the program. That's why we immediately reset it to the default after the runnable has been executed. + * TODO: Remove once https://github.com/skylot/jadx/issues/1948 is addressed. + */ + private void runWithFixedLineSeparator(Runnable runnable) { + String propertyKey = "line.separator"; + String oldLineSeparator = System.getProperty(propertyKey); + System.setProperty(propertyKey, "\n"); + + runnable.run(); + + System.getProperties().setProperty(propertyKey, oldLineSeparator); + } + private void processAnnotatedElement(int pos, ICodeAnnotation ann, ICodeInfo codeInfo) { if (ann == null) return; From 7c27b6c18e5808e440ff49a668c2eb4422b235ff Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Wed, 12 Jul 2023 18:11:06 +0200 Subject: [PATCH 10/21] Fix checkstyle --- .../java/cuchaz/enigma/source/jadx/JadxSource.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index a6c7db34a..0523f03d2 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -115,38 +115,38 @@ public boolean isEmpty() { if (mapper != null) { int reload = 0; String comment; - + for (JavaField fld : cls.getFields()) { if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(fieldEntryOf(fld.getFieldNode())).javadoc())) != null) { fld.getFieldNode().addAttr(AType.CODE_COMMENTS, comment); reload = 1; } } - + for (JavaMethod mth : cls.getMethods()) { if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(methodEntryOf(mth.getMethodNode())).javadoc())) != null) { mth.getMethodNode().addAttr(AType.CODE_COMMENTS, comment); reload = 1; } } - + if (reload == 1) { jadx.getArgs().getCodeCache().close(); jadx.getArgs().setCodeCache(new InMemoryCodeCache()); reload = 2; } - + if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(classEntryOf(cls.getClassNode())).javadoc())) != null) { cls.getClassNode().addAttr(AType.CODE_COMMENTS, comment); if (reload != 2) reload = 1; } - + if (reload == 1) { jadx.getArgs().getCodeCache().close(); jadx.getArgs().setCodeCache(new InMemoryCodeCache()); } } - + index = new SourceIndex(cls.getCode()); } catch (Exception e) { throw new RuntimeException(e); From 16d2684a26a3697511d9c1a18ca81b8beb452a71 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Sat, 16 Mar 2024 08:39:30 +0100 Subject: [PATCH 11/21] Gradlew CRLF --- gradlew.bat | 184 ++++++++++++++++++++++++++-------------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..93e3f59f1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 629ad0e9094b4dfc88818ac8824f3291abe4c156 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Sat, 6 Apr 2024 10:21:59 +0200 Subject: [PATCH 12/21] Update to latest JADX snapshot --- build.gradle | 1 + enigma/build.gradle | 8 +++- .../cuchaz/enigma/source/jadx/JadxSource.java | 37 +++---------------- 3 files changed, 12 insertions(+), 34 deletions(-) diff --git a/build.gradle b/build.gradle index 09f7e91fb..b7a613903 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ subprojects { mavenLocal() mavenCentral() maven { url 'https://maven.fabricmc.net/' } + maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } } dependencies { diff --git a/enigma/build.gradle b/enigma/build.gradle index 8825d6ad9..3dc745821 100644 --- a/enigma/build.gradle +++ b/enigma/build.gradle @@ -10,10 +10,14 @@ dependencies { implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.6.0' implementation 'net.fabricmc:cfr:0.2.1' - implementation ('io.github.skylot:jadx-core:1.4.7') { + + implementation ('io.github.skylot:jadx-core:1.5.0-20240227.173751-9') { + exclude group: 'com.android.tools.build', module: 'aapt2-proto' + } + implementation ('io.github.skylot:jadx-java-input:1.5.0-20240227.173751-9') { exclude group: 'com.android.tools.build', module: 'aapt2-proto' + exclude group: 'io.github.skylot', module: 'raung-disasm' } - implementation 'io.github.skylot:jadx-java-input:1.4.7' proGuard 'com.guardsquare:proguard-base:7.3.0' diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 0523f03d2..12aa2fd90 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -1,11 +1,9 @@ package cuchaz.enigma.source.jadx; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.Nullable; @@ -22,17 +20,12 @@ import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.api.metadata.annotations.VarNode; import jadx.api.metadata.annotations.VarRef; -import jadx.api.plugins.input.data.IClassData; -import jadx.api.plugins.input.data.ILoadResult; -import jadx.api.plugins.input.data.IResourceData; import jadx.api.utils.CodeUtils; import jadx.core.codegen.TypeGen; -import jadx.core.dex.attributes.AType; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; -import jadx.plugins.input.java.JavaClassReader; -import jadx.plugins.input.java.data.JavaClassData; +import jadx.plugins.input.java.JavaInputPlugin; import cuchaz.enigma.source.Source; import cuchaz.enigma.source.SourceIndex; @@ -84,27 +77,7 @@ private void ensureDecompiled() { } try (JadxDecompiler jadx = new JadxDecompiler(jadxArgsSupplier.get())) { - jadx.addCustomLoad(new ILoadResult() { - @Override - public void close() throws IOException { - return; - } - - @Override - public void visitClasses(Consumer<IClassData> consumer) { - consumer.accept(new JavaClassData(new JavaClassReader(0, classNode.name + ".class", AsmUtil.nodeToBytes(classNode)))); - } - - @Override - public void visitResources(Consumer<IResourceData> consumer) { - return; - } - - @Override - public boolean isEmpty() { - return false; - } - }); + jadx.addCustomCodeLoader(JavaInputPlugin.loadSingleClass(AsmUtil.nodeToBytes(classNode), classNode.name)); jadx.load(); JavaClass cls = jadx.getClasses().get(0); @@ -118,14 +91,14 @@ public boolean isEmpty() { for (JavaField fld : cls.getFields()) { if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(fieldEntryOf(fld.getFieldNode())).javadoc())) != null) { - fld.getFieldNode().addAttr(AType.CODE_COMMENTS, comment); + fld.getFieldNode().addCodeComment(comment); reload = 1; } } for (JavaMethod mth : cls.getMethods()) { if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(methodEntryOf(mth.getMethodNode())).javadoc())) != null) { - mth.getMethodNode().addAttr(AType.CODE_COMMENTS, comment); + mth.getMethodNode().addCodeComment(comment); reload = 1; } } @@ -137,7 +110,7 @@ public boolean isEmpty() { } if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(classEntryOf(cls.getClassNode())).javadoc())) != null) { - cls.getClassNode().addAttr(AType.CODE_COMMENTS, comment); + cls.getClassNode().addCodeComment(comment); if (reload != 2) reload = 1; } From e4a5079285c21c47d8eb9c66e14055ee4e2734ad Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Sat, 6 Apr 2024 11:16:05 +0200 Subject: [PATCH 13/21] Pin all JADX artifacts' versions --- enigma/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enigma/build.gradle b/enigma/build.gradle index 3dc745821..47eb2954d 100644 --- a/enigma/build.gradle +++ b/enigma/build.gradle @@ -13,11 +13,13 @@ dependencies { implementation ('io.github.skylot:jadx-core:1.5.0-20240227.173751-9') { exclude group: 'com.android.tools.build', module: 'aapt2-proto' + exclude group: 'com.google.protobuf', module: 'protobuf-java' } implementation ('io.github.skylot:jadx-java-input:1.5.0-20240227.173751-9') { exclude group: 'com.android.tools.build', module: 'aapt2-proto' exclude group: 'io.github.skylot', module: 'raung-disasm' } + implementation 'io.github.skylot:jadx-input-api:1.5.0-20240227.173751-9' // Pin version (would pull 1.5.0-SNAPSHOT otherwise) proGuard 'com.guardsquare:proguard-base:7.3.0' From 7117882996843ccce9feb323799bc6efbb645468 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Sun, 7 Apr 2024 12:04:54 +0200 Subject: [PATCH 14/21] Use JADX plugin for Javadoc attachment --- .../enigma/source/jadx/CustomJadxArgs.java | 10 ++ .../enigma/source/jadx/JadxDecompiler.java | 14 +- .../cuchaz/enigma/source/jadx/JadxHelper.java | 97 +++++++++++++ .../source/jadx/JadxJavadocProvider.java | 129 ++++++++++++++++++ .../cuchaz/enigma/source/jadx/JadxSource.java | 125 ++--------------- .../services/jadx.api.plugins.JadxPlugin | 1 + 6 files changed, 260 insertions(+), 116 deletions(-) create mode 100644 enigma/src/main/java/cuchaz/enigma/source/jadx/CustomJadxArgs.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/jadx/JadxHelper.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/jadx/JadxJavadocProvider.java create mode 100644 enigma/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/CustomJadxArgs.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/CustomJadxArgs.java new file mode 100644 index 000000000..2e2086b65 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/CustomJadxArgs.java @@ -0,0 +1,10 @@ +package cuchaz.enigma.source.jadx; + +import jadx.api.JadxArgs; + +import cuchaz.enigma.translation.mapping.EntryRemapper; + +class CustomJadxArgs extends JadxArgs { + EntryRemapper mapper; + JadxHelper jadxHelper; +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java index 3e5892e20..e2b2151ec 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java @@ -1,7 +1,7 @@ package cuchaz.enigma.source.jadx; import jadx.api.JadxArgs; -import jadx.api.impl.InMemoryCodeCache; +import jadx.api.impl.NoOpCodeCache; import org.checkerframework.checker.nullness.qual.Nullable; import cuchaz.enigma.classprovider.ClassProvider; @@ -21,17 +21,21 @@ public JadxDecompiler(ClassProvider classProvider, SourceSettings sourceSettings @Override public Source getSource(String className, @Nullable EntryRemapper mapper) { - return new JadxSource(settings, this::createJadxArgs, classProvider.get(className), mapper); + JadxHelper jadxHelper = new JadxHelper(); + + return new JadxSource(settings, mapperX -> createJadxArgs(mapperX, jadxHelper), classProvider.get(className), mapper, jadxHelper); } - private JadxArgs createJadxArgs() { - JadxArgs args = new JadxArgs(); - args.setCodeCache(new InMemoryCodeCache()); + private JadxArgs createJadxArgs(EntryRemapper mapper, JadxHelper jadxHelper) { + CustomJadxArgs args = new CustomJadxArgs(); + args.setCodeCache(NoOpCodeCache.INSTANCE); args.setShowInconsistentCode(true); args.setInlineAnonymousClasses(false); args.setInlineMethods(false); args.setRespectBytecodeAccModifiers(true); args.setRenameValid(false); + args.mapper = mapper; + args.jadxHelper = jadxHelper; if (settings.removeImports) { // Commented out for now, since JADX would use full identifiers everywhere diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxHelper.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxHelper.java new file mode 100644 index 000000000..a69c47946 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxHelper.java @@ -0,0 +1,97 @@ +package cuchaz.enigma.source.jadx; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import jadx.api.ICodeInfo; +import jadx.api.metadata.annotations.NodeDeclareRef; +import jadx.api.metadata.annotations.VarNode; +import jadx.api.utils.CodeUtils; +import jadx.core.codegen.TypeGen; +import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; + +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +class JadxHelper { + private Map<jadx.core.dex.nodes.ClassNode, String> internalNames = new HashMap<>(); + private Map<jadx.core.dex.nodes.ClassNode, ClassEntry> classMap = new HashMap<>(); + private Map<FieldNode, FieldEntry> fieldMap = new HashMap<>(); + private Map<MethodNode, MethodEntry> methodMap = new HashMap<>(); + private Map<VarNode, LocalVariableEntry> varMap = new HashMap<>(); + private Map<MethodNode, List<VarNode>> argMap = new HashMap<>(); + + private String internalNameOf(jadx.core.dex.nodes.ClassNode cls) { + return internalNames.computeIfAbsent(cls, (unused) -> cls.getClassInfo().makeRawFullName().replace('.', '/')); + } + + ClassEntry classEntryOf(jadx.core.dex.nodes.ClassNode cls) { + if (cls == null) return null; + return classMap.computeIfAbsent(cls, (unused) -> new ClassEntry(internalNameOf(cls))); + } + + FieldEntry fieldEntryOf(FieldNode fld) { + return fieldMap.computeIfAbsent(fld, (unused) -> + new FieldEntry(classEntryOf(fld.getParentClass()), fld.getName(), new TypeDescriptor(TypeGen.signature(fld.getType())))); + } + + MethodEntry methodEntryOf(MethodNode mth) { + return methodMap.computeIfAbsent(mth, (unused) -> { + MethodInfo mthInfo = mth.getMethodInfo(); + MethodDescriptor desc = new MethodDescriptor(mthInfo.getShortId().substring(mthInfo.getName().length())); + return new MethodEntry(classEntryOf(mth.getParentClass()), mthInfo.getName(), desc); + }); + } + + LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { + return varMap.computeIfAbsent(param, (unused) -> { + MethodEntry owner = methodEntryOf(param.getMth()); + int index = getMethodArgs(param.getMth(), codeInfo).indexOf(param); + return new LocalVariableEntry(owner, index, param.getName(), true, null); + }); + } + + List<VarNode> getMethodArgs(MethodNode mth, ICodeInfo codeInfo) { + return argMap.computeIfAbsent(mth, (unused) -> { + int mthDefPos = mth.getDefPosition(); + int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); + List<VarNode> args = new ArrayList<>(); + codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> { + if (pos > lineEndPos) { + // Stop at line end + return Boolean.TRUE; + } + + if (ann instanceof NodeDeclareRef ref && ref.getNode() instanceof VarNode varNode) { + if (!varNode.getMth().equals(mth)) { + // Stop if we've gone too far and have entered a different method + return Boolean.TRUE; + } + + args.add(varNode); + } + + return null; + }); + + return args; + }); + } + + boolean isRecord(jadx.core.dex.nodes.ClassNode cls) { + if (cls.getSuperClass() == null || !cls.getSuperClass().isObject()) { + return false; + } + + return Objects.equals(cls.getSuperClass().getObject(), "java/lang/Record"); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxJavadocProvider.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxJavadocProvider.java new file mode 100644 index 000000000..30dc6b36b --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxJavadocProvider.java @@ -0,0 +1,129 @@ +package cuchaz.enigma.source.jadx; + +import java.util.Collection; + +import jadx.api.plugins.JadxPlugin; +import jadx.api.plugins.JadxPluginContext; +import jadx.api.plugins.JadxPluginInfo; +import jadx.api.plugins.pass.JadxPassInfo; +import jadx.api.plugins.pass.impl.OrderedJadxPassInfo; +import jadx.api.plugins.pass.types.JadxPreparePass; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; + +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; + +public class JadxJavadocProvider implements JadxPlugin { + public static final String PLUGIN_ID = "enigma-javadoc-provider"; + + @Override + public JadxPluginInfo getPluginInfo() { + return new JadxPluginInfo(PLUGIN_ID, "Enigma Javadoc Provider", "Applies Enigma-supplied Javadocs"); + } + + @SuppressWarnings("resource") + @Override + public void init(JadxPluginContext context) { + CustomJadxArgs args = (CustomJadxArgs) context.getArgs(); + + context.addPass(new JavadocProvidingPass(args.mapper, args.jadxHelper)); + } + + private static class JavadocProvidingPass implements JadxPreparePass { + private final EntryRemapper mapper; + private final JadxHelper jadxHelper; + + private JavadocProvidingPass(EntryRemapper mapper, JadxHelper jadxHelper) { + this.mapper = mapper; + this.jadxHelper = jadxHelper; + } + + @Override + public JadxPassInfo getInfo() { + return new OrderedJadxPassInfo("ApplyJavadocs", "Applies Enigma-supplied Javadocs") + .before("RenameVisitor"); + } + + @Override + public void init(RootNode root) { + process(root); + root.registerCodeDataUpdateListener(codeData -> process(root)); + } + + private void process(RootNode root) { + if (mapper == null) return; + + for (ClassNode cls : root.getClasses()) { + processClass(cls); + } + } + + private void processClass(ClassNode cls) { + EntryMapping mapping = mapper.getDeobfMapping(jadxHelper.classEntryOf(cls)); + + if (mapping.javadoc() != null && !mapping.javadoc().isBlank()) { + // TODO: Once JADX supports records, add @param tags for components + cls.addCodeComment(mapping.javadoc().trim()); + } + + for (FieldNode field : cls.getFields()) { + processField(field); + } + + for (MethodNode method : cls.getMethods()) { + processMethod(method); + } + } + + private void processField(FieldNode field) { + EntryMapping mapping = mapper.getDeobfMapping(jadxHelper.fieldEntryOf(field)); + + if (mapping.javadoc() != null && !mapping.javadoc().isBlank()) { + field.addCodeComment(mapping.javadoc().trim()); + } + } + + private void processMethod(MethodNode method) { + Entry<?> entry = jadxHelper.methodEntryOf(method); + EntryMapping mapping = mapper.getDeobfMapping(entry); + StringBuilder builder = new StringBuilder(); + String javadoc = mapping.javadoc(); + + if (javadoc != null) { + builder.append(javadoc); + } + + Collection<Entry<?>> children = mapper.getObfChildren(entry); + boolean addedLf = false; + + if (children != null && !children.isEmpty()) { + for (Entry<?> child : children) { + if (child instanceof LocalVariableEntry) { + mapping = mapper.getDeobfMapping(child); + javadoc = mapping.javadoc(); + + if (javadoc != null) { + if (!addedLf) { + addedLf = true; + builder.append('\n'); + } + + builder.append(String.format("\n@param %s %s", mapping.targetName(), javadoc)); + } + } + } + } + + javadoc = builder.toString().trim(); + + if (!javadoc.isBlank()) { + method.addCodeComment(javadoc); + } + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 12aa2fd90..4981dbc6b 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -1,28 +1,18 @@ package cuchaz.enigma.source.jadx; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.function.Supplier; +import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.tree.ClassNode; -import com.google.common.base.Strings; import jadx.api.ICodeInfo; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JavaClass; -import jadx.api.JavaField; -import jadx.api.JavaMethod; -import jadx.api.impl.InMemoryCodeCache; import jadx.api.metadata.ICodeAnnotation; import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.api.metadata.annotations.VarNode; import jadx.api.metadata.annotations.VarRef; -import jadx.api.utils.CodeUtils; -import jadx.core.codegen.TypeGen; -import jadx.core.dex.info.MethodInfo; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import jadx.plugins.input.java.JavaInputPlugin; @@ -32,8 +22,6 @@ import cuchaz.enigma.source.SourceSettings; import cuchaz.enigma.source.Token; import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.TypeDescriptor; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.FieldEntry; import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; @@ -42,21 +30,23 @@ public class JadxSource implements Source { private final SourceSettings settings; - private final Supplier<JadxArgs> jadxArgsSupplier; + private final Function<EntryRemapper, JadxArgs> jadxArgsFactory; private final ClassNode classNode; private final EntryRemapper mapper; + private final JadxHelper jadxHelper; private SourceIndex index; - public JadxSource(SourceSettings settings, Supplier<JadxArgs> jadxArgsSupplier, ClassNode classNode, @Nullable EntryRemapper mapper) { + public JadxSource(SourceSettings settings, Function<EntryRemapper, JadxArgs> jadxArgsFactory, ClassNode classNode, @Nullable EntryRemapper mapper, JadxHelper jadxHelper) { this.settings = settings; - this.jadxArgsSupplier = jadxArgsSupplier; + this.jadxArgsFactory = jadxArgsFactory; this.classNode = classNode; this.mapper = mapper; + this.jadxHelper = jadxHelper; } @Override public Source withJavadocs(EntryRemapper mapper) { - return new JadxSource(settings, jadxArgsSupplier, classNode, mapper); + return new JadxSource(settings, jadxArgsFactory, classNode, mapper, jadxHelper); } @Override @@ -76,54 +66,13 @@ private void ensureDecompiled() { return; } - try (JadxDecompiler jadx = new JadxDecompiler(jadxArgsSupplier.get())) { + try (JadxDecompiler jadx = new JadxDecompiler(jadxArgsFactory.apply(mapper))) { jadx.addCustomCodeLoader(JavaInputPlugin.loadSingleClass(AsmUtil.nodeToBytes(classNode), classNode.name)); jadx.load(); JavaClass cls = jadx.getClasses().get(0); runWithFixedLineSeparator(() -> { - try { - // Javadocs - // TODO: Make this less hacky - if (mapper != null) { - int reload = 0; - String comment; - - for (JavaField fld : cls.getFields()) { - if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(fieldEntryOf(fld.getFieldNode())).javadoc())) != null) { - fld.getFieldNode().addCodeComment(comment); - reload = 1; - } - } - - for (JavaMethod mth : cls.getMethods()) { - if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(methodEntryOf(mth.getMethodNode())).javadoc())) != null) { - mth.getMethodNode().addCodeComment(comment); - reload = 1; - } - } - - if (reload == 1) { - jadx.getArgs().getCodeCache().close(); - jadx.getArgs().setCodeCache(new InMemoryCodeCache()); - reload = 2; - } - - if ((comment = Strings.emptyToNull(mapper.getDeobfMapping(classEntryOf(cls.getClassNode())).javadoc())) != null) { - cls.getClassNode().addCodeComment(comment); - if (reload != 2) reload = 1; - } - - if (reload == 1) { - jadx.getArgs().getCodeCache().close(); - jadx.getArgs().setCodeCache(new InMemoryCodeCache()); - } - } - - index = new SourceIndex(cls.getCode()); - } catch (Exception e) { - throw new RuntimeException(e); - } + index = new SourceIndex(cls.getCode()); }); // Tokens @@ -131,8 +80,6 @@ private void ensureDecompiled() { processAnnotatedElement(pos, ann, cls.getCodeInfo()); return null; }); - } catch (Exception e) { - throw new RuntimeException(e); } } @@ -199,67 +146,23 @@ private void processAnnotatedElement(int pos, ICodeAnnotation ann, ICodeInfo cod } } - private Map<jadx.core.dex.nodes.ClassNode, String> internalNames = new HashMap<>(); - private Map<jadx.core.dex.nodes.ClassNode, ClassEntry> classMap = new HashMap<>(); - private Map<FieldNode, FieldEntry> fieldMap = new HashMap<>(); - private Map<MethodNode, MethodEntry> methodMap = new HashMap<>(); - private Map<VarNode, LocalVariableEntry> varMap = new HashMap<>(); - private Map<MethodNode, List<VarNode>> argMap = new HashMap<>(); - - private String internalNameOf(jadx.core.dex.nodes.ClassNode cls) { - return internalNames.computeIfAbsent(cls, (unused) -> cls.getClassInfo().makeRawFullName().replace('.', '/')); - } - private ClassEntry classEntryOf(jadx.core.dex.nodes.ClassNode cls) { - if (cls == null) return null; - return classMap.computeIfAbsent(cls, (unused) -> new ClassEntry(internalNameOf(cls))); + return jadxHelper.classEntryOf(cls); } private FieldEntry fieldEntryOf(FieldNode fld) { - return fieldMap.computeIfAbsent(fld, (unused) -> - new FieldEntry(classEntryOf(fld.getParentClass()), fld.getName(), new TypeDescriptor(TypeGen.signature(fld.getType())))); + return jadxHelper.fieldEntryOf(fld); } private MethodEntry methodEntryOf(MethodNode mth) { - return methodMap.computeIfAbsent(mth, (unused) -> { - MethodInfo mthInfo = mth.getMethodInfo(); - MethodDescriptor desc = new MethodDescriptor(mthInfo.getShortId().substring(mthInfo.getName().length())); - return new MethodEntry(classEntryOf(mth.getParentClass()), mthInfo.getName(), desc); - }); + return jadxHelper.methodEntryOf(mth); } private LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { - return varMap.computeIfAbsent(param, (unused) -> { - MethodEntry owner = methodEntryOf(param.getMth()); - int index = getMethodArgs(param.getMth(), codeInfo).indexOf(param); - return new LocalVariableEntry(owner, index, param.getName(), true, null); - }); + return jadxHelper.paramEntryOf(param, codeInfo); } private List<VarNode> getMethodArgs(MethodNode mth, ICodeInfo codeInfo) { - return argMap.computeIfAbsent(mth, (unused) -> { - int mthDefPos = mth.getDefPosition(); - int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); - List<VarNode> args = new ArrayList<>(); - codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> { - if (pos > lineEndPos) { - // Stop at line end - return Boolean.TRUE; - } - - if (ann instanceof NodeDeclareRef ref && ref.getNode() instanceof VarNode varNode) { - if (!varNode.getMth().equals(mth)) { - // Stop if we've gone too far and have entered a different method - return Boolean.TRUE; - } - - args.add(varNode); - } - - return null; - }); - - return args; - }); + return jadxHelper.getMethodArgs(mth, codeInfo); } } diff --git a/enigma/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/enigma/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 000000000..9add613b9 --- /dev/null +++ b/enigma/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +cuchaz.enigma.source.jadx.JadxJavadocProvider From eddd4b3caa34764751d91afac23ae19ed5a423aa Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Sun, 7 Apr 2024 12:29:05 +0200 Subject: [PATCH 15/21] Small clean-up --- .../cuchaz/enigma/source/jadx/JadxHelper.java | 41 +++---------------- .../cuchaz/enigma/source/jadx/JadxSource.java | 7 +--- 2 files changed, 7 insertions(+), 41 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxHelper.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxHelper.java index a69c47946..80730eb7a 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxHelper.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxHelper.java @@ -1,17 +1,15 @@ package cuchaz.enigma.source.jadx; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import jadx.api.ICodeInfo; -import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.api.metadata.annotations.VarNode; -import jadx.api.utils.CodeUtils; import jadx.core.codegen.TypeGen; import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; @@ -23,18 +21,18 @@ import cuchaz.enigma.translation.representation.entry.MethodEntry; class JadxHelper { - private Map<jadx.core.dex.nodes.ClassNode, String> internalNames = new HashMap<>(); - private Map<jadx.core.dex.nodes.ClassNode, ClassEntry> classMap = new HashMap<>(); + private Map<ClassNode, String> internalNames = new HashMap<>(); + private Map<ClassNode, ClassEntry> classMap = new HashMap<>(); private Map<FieldNode, FieldEntry> fieldMap = new HashMap<>(); private Map<MethodNode, MethodEntry> methodMap = new HashMap<>(); private Map<VarNode, LocalVariableEntry> varMap = new HashMap<>(); private Map<MethodNode, List<VarNode>> argMap = new HashMap<>(); - private String internalNameOf(jadx.core.dex.nodes.ClassNode cls) { + private String internalNameOf(ClassNode cls) { return internalNames.computeIfAbsent(cls, (unused) -> cls.getClassInfo().makeRawFullName().replace('.', '/')); } - ClassEntry classEntryOf(jadx.core.dex.nodes.ClassNode cls) { + ClassEntry classEntryOf(ClassNode cls) { if (cls == null) return null; return classMap.computeIfAbsent(cls, (unused) -> new ClassEntry(internalNameOf(cls))); } @@ -55,38 +53,11 @@ MethodEntry methodEntryOf(MethodNode mth) { LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { return varMap.computeIfAbsent(param, (unused) -> { MethodEntry owner = methodEntryOf(param.getMth()); - int index = getMethodArgs(param.getMth(), codeInfo).indexOf(param); + int index = param.getMth().collectArgsWithoutLoading().indexOf(param); // FIXME: This is just a placeholder (and obviously wrong), fix later return new LocalVariableEntry(owner, index, param.getName(), true, null); }); } - List<VarNode> getMethodArgs(MethodNode mth, ICodeInfo codeInfo) { - return argMap.computeIfAbsent(mth, (unused) -> { - int mthDefPos = mth.getDefPosition(); - int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); - List<VarNode> args = new ArrayList<>(); - codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> { - if (pos > lineEndPos) { - // Stop at line end - return Boolean.TRUE; - } - - if (ann instanceof NodeDeclareRef ref && ref.getNode() instanceof VarNode varNode) { - if (!varNode.getMth().equals(mth)) { - // Stop if we've gone too far and have entered a different method - return Boolean.TRUE; - } - - args.add(varNode); - } - - return null; - }); - - return args; - }); - } - boolean isRecord(jadx.core.dex.nodes.ClassNode cls) { if (cls.getSuperClass() == null || !cls.getSuperClass().isObject()) { return false; diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 4981dbc6b..4ae833712 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -1,6 +1,5 @@ package cuchaz.enigma.source.jadx; -import java.util.List; import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; @@ -133,7 +132,7 @@ private void processAnnotatedElement(int pos, ICodeAnnotation ann, ICodeInfo cod index.addReference(token, methodEntryOf(mth), classEntryOf(mth.getParentClass())); } } else if (ann instanceof VarNode var) { - if (!getMethodArgs(var.getMth(), codeInfo).contains(var)) return; + if (!var.getMth().collectArgsWithoutLoading().contains(var)) return; Token token = new Token(pos, pos + var.getName().length(), var.getName()); if (pos == var.getDefPosition()) { @@ -161,8 +160,4 @@ private MethodEntry methodEntryOf(MethodNode mth) { private LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { return jadxHelper.paramEntryOf(param, codeInfo); } - - private List<VarNode> getMethodArgs(MethodNode mth, ICodeInfo codeInfo) { - return jadxHelper.getMethodArgs(mth, codeInfo); - } } From f9ed75f9d1cecdf711e2d63dd054fd18c1f53539 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Sun, 7 Apr 2024 20:11:37 +0200 Subject: [PATCH 16/21] Fix token offsets in the presence of Javadocs --- .../cuchaz/enigma/source/jadx/JadxSource.java | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 4ae833712..58c7f8851 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -5,6 +5,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.tree.ClassNode; import jadx.api.ICodeInfo; +import jadx.api.ICodeWriter; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JavaClass; @@ -35,6 +36,26 @@ public class JadxSource implements Source { private final JadxHelper jadxHelper; private SourceIndex index; + /* + * JADX uses the system default line ending, but JEditorPane does not (seems to be hardcoded to \n). + * This causes tokens to be offset by one char per preceding line, since Windows' \r\n is one char longer than plain \r or \n. + * Unfortunately, the only way of making JADX use a different value is by changing the corresponding system property, + * which we can't leave changed as it may cause issues elsewhere in the program. + * Thus, we force a ICodeWriter class load here so the NL constant gets initialized to \n. + * TODO: Remove once https://github.com/skylot/jadx/issues/1948 is addressed. + */ + static { + String propertyKey = "line.separator"; + String oldLineSeparator = System.getProperty(propertyKey); + System.setProperty(propertyKey, "\n"); + + if (!ICodeWriter.NL.equals("\n")) { + throw new AssertionError("NL constant not initialized to \\n"); + } + + System.getProperties().setProperty(propertyKey, oldLineSeparator); + } + public JadxSource(SourceSettings settings, Function<EntryRemapper, JadxArgs> jadxArgsFactory, ClassNode classNode, @Nullable EntryRemapper mapper, JadxHelper jadxHelper) { this.settings = settings; this.jadxArgsFactory = jadxArgsFactory; @@ -70,35 +91,18 @@ private void ensureDecompiled() { jadx.load(); JavaClass cls = jadx.getClasses().get(0); - runWithFixedLineSeparator(() -> { - index = new SourceIndex(cls.getCode()); - }); + // Cache decompilation result to prevent https://github.com/skylot/jadx/issues/2141 + ICodeInfo codeInfo = cls.getCodeInfo(); + index = new SourceIndex(codeInfo.getCodeStr()); // Tokens - cls.getCodeInfo().getCodeMetadata().searchDown(0, (pos, ann) -> { + codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> { processAnnotatedElement(pos, ann, cls.getCodeInfo()); return null; }); } } - /** - * JADX uses the system default line ending, but JEditorPane does not (seems to be hardcoded to \n). - * This causes tokens to be offset by one char per preceding line, since Windows' \r\n is one char longer than plain \r or \n. - * Unfortunately, the only way of making JADX use a different value is by changing the system property, which may cause issues - * elsewhere in the program. That's why we immediately reset it to the default after the runnable has been executed. - * TODO: Remove once https://github.com/skylot/jadx/issues/1948 is addressed. - */ - private void runWithFixedLineSeparator(Runnable runnable) { - String propertyKey = "line.separator"; - String oldLineSeparator = System.getProperty(propertyKey); - System.setProperty(propertyKey, "\n"); - - runnable.run(); - - System.getProperties().setProperty(propertyKey, oldLineSeparator); - } - private void processAnnotatedElement(int pos, ICodeAnnotation ann, ICodeInfo codeInfo) { if (ann == null) return; From fa99b9c0c7c8bc14dd4faf8871ef37bc2cc37cbf Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Sun, 7 Apr 2024 20:18:58 +0200 Subject: [PATCH 17/21] Better wording in comment --- .../main/java/cuchaz/enigma/source/jadx/JadxSource.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 58c7f8851..aaee391d7 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -38,10 +38,10 @@ public class JadxSource implements Source { /* * JADX uses the system default line ending, but JEditorPane does not (seems to be hardcoded to \n). - * This causes tokens to be offset by one char per preceding line, since Windows' \r\n is one char longer than plain \r or \n. - * Unfortunately, the only way of making JADX use a different value is by changing the corresponding system property, - * which we can't leave changed as it may cause issues elsewhere in the program. - * Thus, we force a ICodeWriter class load here so the NL constant gets initialized to \n. + * This causes tokens to be offset by one char per preceding line, since Windows' \r\n is one char longer than plain \n. + * Unfortunately, the only way of making JADX use a different value is by adjusting the corresponding system property, + * which we can't leave in a modified as it may cause issues elsewhere in the program. + * Thus, we force-load the NL constant now to initialize it to \n and immediately reset the system property again. * TODO: Remove once https://github.com/skylot/jadx/issues/1948 is addressed. */ static { From 05614554a89b084fa871c9be0cf88d8f2dfb96ea Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Tue, 9 Apr 2024 00:04:51 +0200 Subject: [PATCH 18/21] Fix token offsets in the presence of Javadocs (2) --- .../main/java/cuchaz/enigma/source/jadx/JadxSource.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index aaee391d7..36e5c6dd4 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -97,7 +97,7 @@ private void ensureDecompiled() { // Tokens codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> { - processAnnotatedElement(pos, ann, cls.getCodeInfo()); + processAnnotatedElement(pos, ann, codeInfo); return null; }); } @@ -139,8 +139,8 @@ private void processAnnotatedElement(int pos, ICodeAnnotation ann, ICodeInfo cod if (!var.getMth().collectArgsWithoutLoading().contains(var)) return; Token token = new Token(pos, pos + var.getName().length(), var.getName()); - if (pos == var.getDefPosition()) { - index.addDeclaration(token, paramEntryOf(var, codeInfo)); + if (pos == var.getDefPosition()) { + index.addDeclaration(token, paramEntryOf(var, codeInfo)); } else { index.addReference(token, paramEntryOf(var, codeInfo), methodEntryOf(var.getMth())); } @@ -161,7 +161,7 @@ private MethodEntry methodEntryOf(MethodNode mth) { return jadxHelper.methodEntryOf(mth); } - private LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { + private LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { return jadxHelper.paramEntryOf(param, codeInfo); } } From bb16ac4008c8de05d064fb15d19b67f936925d2c Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Tue, 9 Apr 2024 00:07:51 +0200 Subject: [PATCH 19/21] Fix indentation --- .../src/main/java/cuchaz/enigma/source/jadx/JadxSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 36e5c6dd4..81a23b231 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -139,8 +139,8 @@ private void processAnnotatedElement(int pos, ICodeAnnotation ann, ICodeInfo cod if (!var.getMth().collectArgsWithoutLoading().contains(var)) return; Token token = new Token(pos, pos + var.getName().length(), var.getName()); - if (pos == var.getDefPosition()) { - index.addDeclaration(token, paramEntryOf(var, codeInfo)); + if (pos == var.getDefPosition()) { + index.addDeclaration(token, paramEntryOf(var, codeInfo)); } else { index.addReference(token, paramEntryOf(var, codeInfo), methodEntryOf(var.getMth())); } From 9eba54aa0b85351e8eda0e422070a6c86f4f7af9 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Tue, 9 Apr 2024 00:09:34 +0200 Subject: [PATCH 20/21] Fix indentation (2) --- enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 81a23b231..060e5a21c 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -161,7 +161,7 @@ private MethodEntry methodEntryOf(MethodNode mth) { return jadxHelper.methodEntryOf(mth); } - private LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { + private LocalVariableEntry paramEntryOf(VarNode param, ICodeInfo codeInfo) { return jadxHelper.paramEntryOf(param, codeInfo); } } From 7ad63f0344cc534cd6f60adcf39b7e9612c8fd96 Mon Sep 17 00:00:00 2001 From: NebelNidas <nebelnidas@gmail.com> Date: Tue, 9 Apr 2024 00:25:22 +0200 Subject: [PATCH 21/21] Upgrade to latest JADX snapshot --- enigma/build.gradle | 6 ++-- .../enigma/source/jadx/JadxDecompiler.java | 2 ++ .../source/jadx/JadxJavadocProvider.java | 14 +++++++--- .../cuchaz/enigma/source/jadx/JadxSource.java | 28 +++---------------- 4 files changed, 19 insertions(+), 31 deletions(-) diff --git a/enigma/build.gradle b/enigma/build.gradle index 8baccbd9f..af4e398a7 100644 --- a/enigma/build.gradle +++ b/enigma/build.gradle @@ -12,15 +12,15 @@ dependencies { implementation 'net.fabricmc:cfr:0.2.2' implementation 'org.vineflower:vineflower:1.10.0' - implementation ('io.github.skylot:jadx-core:1.5.0-20240227.173751-9') { + implementation ('io.github.skylot:jadx-core:1.5.0-20240408.212728-12') { exclude group: 'com.android.tools.build', module: 'aapt2-proto' exclude group: 'com.google.protobuf', module: 'protobuf-java' } - implementation ('io.github.skylot:jadx-java-input:1.5.0-20240227.173751-9') { + implementation ('io.github.skylot:jadx-java-input:1.5.0-20240408.212728-12') { exclude group: 'com.android.tools.build', module: 'aapt2-proto' exclude group: 'io.github.skylot', module: 'raung-disasm' } - implementation 'io.github.skylot:jadx-input-api:1.5.0-20240227.173751-9' // Pin version (would pull 1.5.0-SNAPSHOT otherwise) + implementation 'io.github.skylot:jadx-input-api:1.5.0-20240408.212728-12' // Pin version (would pull 1.5.0-SNAPSHOT otherwise) proGuard 'com.guardsquare:proguard-base:7.4.0-beta02' diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java index e2b2151ec..13f886c80 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxDecompiler.java @@ -34,6 +34,8 @@ private JadxArgs createJadxArgs(EntryRemapper mapper, JadxHelper jadxHelper) { args.setInlineMethods(false); args.setRespectBytecodeAccModifiers(true); args.setRenameValid(false); + args.setCodeIndentStr("\t"); + args.setCodeNewLineStr("\n"); // JEditorPane is hardcoded to \n args.mapper = mapper; args.jadxHelper = jadxHelper; diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxJavadocProvider.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxJavadocProvider.java index 30dc6b36b..3e491830a 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxJavadocProvider.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxJavadocProvider.java @@ -2,12 +2,14 @@ import java.util.Collection; +import jadx.api.data.CommentStyle; import jadx.api.plugins.JadxPlugin; import jadx.api.plugins.JadxPluginContext; import jadx.api.plugins.JadxPluginInfo; import jadx.api.plugins.pass.JadxPassInfo; import jadx.api.plugins.pass.impl.OrderedJadxPassInfo; import jadx.api.plugins.pass.types.JadxPreparePass; +import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; @@ -68,7 +70,7 @@ private void processClass(ClassNode cls) { if (mapping.javadoc() != null && !mapping.javadoc().isBlank()) { // TODO: Once JADX supports records, add @param tags for components - cls.addCodeComment(mapping.javadoc().trim()); + attachJavadoc(cls, mapping.javadoc()); } for (FieldNode field : cls.getFields()) { @@ -84,7 +86,7 @@ private void processField(FieldNode field) { EntryMapping mapping = mapper.getDeobfMapping(jadxHelper.fieldEntryOf(field)); if (mapping.javadoc() != null && !mapping.javadoc().isBlank()) { - field.addCodeComment(mapping.javadoc().trim()); + attachJavadoc(field, mapping.javadoc()); } } @@ -119,11 +121,15 @@ private void processMethod(MethodNode method) { } } - javadoc = builder.toString().trim(); + javadoc = builder.toString(); if (!javadoc.isBlank()) { - method.addCodeComment(javadoc); + attachJavadoc(method, javadoc); } } + + private void attachJavadoc(NotificationAttrNode target, String javadoc) { + target.addCodeComment(javadoc.trim(), CommentStyle.JAVADOC); + } } } diff --git a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java index 060e5a21c..c791cf7d0 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/jadx/JadxSource.java @@ -2,10 +2,9 @@ import java.util.function.Function; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.objectweb.asm.tree.ClassNode; +import javax.annotation.Nullable; + import jadx.api.ICodeInfo; -import jadx.api.ICodeWriter; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JavaClass; @@ -16,6 +15,7 @@ import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import jadx.plugins.input.java.JavaInputPlugin; +import org.objectweb.asm.tree.ClassNode; import cuchaz.enigma.source.Source; import cuchaz.enigma.source.SourceIndex; @@ -36,26 +36,6 @@ public class JadxSource implements Source { private final JadxHelper jadxHelper; private SourceIndex index; - /* - * JADX uses the system default line ending, but JEditorPane does not (seems to be hardcoded to \n). - * This causes tokens to be offset by one char per preceding line, since Windows' \r\n is one char longer than plain \n. - * Unfortunately, the only way of making JADX use a different value is by adjusting the corresponding system property, - * which we can't leave in a modified as it may cause issues elsewhere in the program. - * Thus, we force-load the NL constant now to initialize it to \n and immediately reset the system property again. - * TODO: Remove once https://github.com/skylot/jadx/issues/1948 is addressed. - */ - static { - String propertyKey = "line.separator"; - String oldLineSeparator = System.getProperty(propertyKey); - System.setProperty(propertyKey, "\n"); - - if (!ICodeWriter.NL.equals("\n")) { - throw new AssertionError("NL constant not initialized to \\n"); - } - - System.getProperties().setProperty(propertyKey, oldLineSeparator); - } - public JadxSource(SourceSettings settings, Function<EntryRemapper, JadxArgs> jadxArgsFactory, ClassNode classNode, @Nullable EntryRemapper mapper, JadxHelper jadxHelper) { this.settings = settings; this.jadxArgsFactory = jadxArgsFactory; @@ -136,7 +116,7 @@ private void processAnnotatedElement(int pos, ICodeAnnotation ann, ICodeInfo cod index.addReference(token, methodEntryOf(mth), classEntryOf(mth.getParentClass())); } } else if (ann instanceof VarNode var) { - if (!var.getMth().collectArgsWithoutLoading().contains(var)) return; + if (!var.getMth().collectArgNodes().contains(var)) return; Token token = new Token(pos, pos + var.getName().length(), var.getName()); if (pos == var.getDefPosition()) {