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()) {