summaryrefslogtreecommitdiff
path: root/enigma
diff options
context:
space:
mode:
Diffstat (limited to 'enigma')
-rw-r--r--enigma/build.gradle1
-rw-r--r--enigma/src/main/java/cuchaz/enigma/EnigmaProject.java12
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java6
-rw-r--r--enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java7
-rw-r--r--enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java7
-rw-r--r--enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java12
-rw-r--r--enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java1
-rw-r--r--enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java7
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/Decompilers.java4
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDumper.java (renamed from enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java)20
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerContextSource.java127
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerDecompiler.java24
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerJavadocProvider.java139
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerSource.java119
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerTextTokenCollector.java151
17 files changed, 625 insertions, 17 deletions
diff --git a/enigma/build.gradle b/enigma/build.gradle
index ffea473..44f2680 100644
--- a/enigma/build.gradle
+++ b/enigma/build.gradle
@@ -10,6 +10,7 @@ dependencies {
10 10
11 implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.6.0' 11 implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.6.0'
12 implementation 'net.fabricmc:cfr:0.2.2' 12 implementation 'net.fabricmc:cfr:0.2.2'
13 implementation 'org.vineflower:vineflower:1.10.0'
13 14
14 proGuard 'com.guardsquare:proguard-base:7.4.0-beta02' 15 proGuard 'com.guardsquare:proguard-base:7.4.0-beta02'
15 16
diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
index 48d6736..b3a7274 100644
--- a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
+++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
@@ -274,7 +274,17 @@ public class EnigmaProject {
274 progress.init(classes.size(), I18n.translate("progress.classes.decompiling")); 274 progress.init(classes.size(), I18n.translate("progress.classes.decompiling"));
275 275
276 //create a common instance outside the loop as mappings shouldn't be changing while this is happening 276 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
277 Decompiler decompiler = decompilerService.create(compiled::get, new SourceSettings(false, false)); 277 Decompiler decompiler = decompilerService.create(new ClassProvider() {
278 @Override
279 public Collection<String> getClassNames() {
280 return compiled.keySet();
281 }
282
283 @Override
284 public ClassNode get(String name) {
285 return compiled.get(name);
286 }
287 }, new SourceSettings(false, false));
278 288
279 AtomicInteger count = new AtomicInteger(); 289 AtomicInteger count = new AtomicInteger();
280 290
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java
index 45dac2c..10bc436 100644
--- a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java
@@ -55,8 +55,9 @@ public final class BuiltinPlugin implements EnigmaPlugin {
55 } 55 }
56 56
57 private void registerDecompilerServices(EnigmaPluginContext ctx) { 57 private void registerDecompilerServices(EnigmaPluginContext ctx) {
58 ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON); 58 ctx.registerService("enigma:vineflower", DecompilerService.TYPE, ctx1 -> Decompilers.VINEFLOWER);
59 ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR); 59 ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR);
60 ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON);
60 ctx.registerService("enigma:bytecode", DecompilerService.TYPE, ctx1 -> Decompilers.BYTECODE); 61 ctx.registerService("enigma:bytecode", DecompilerService.TYPE, ctx1 -> Decompilers.BYTECODE);
61 } 62 }
62 63
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java
index eaba6df..28a9392 100644
--- a/enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java
+++ b/enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java
@@ -1,5 +1,6 @@
1package cuchaz.enigma.classprovider; 1package cuchaz.enigma.classprovider;
2 2
3import java.util.Collection;
3import java.util.Optional; 4import java.util.Optional;
4import java.util.concurrent.ExecutionException; 5import java.util.concurrent.ExecutionException;
5import java.util.concurrent.TimeUnit; 6import java.util.concurrent.TimeUnit;
@@ -22,6 +23,11 @@ public class CachingClassProvider implements ClassProvider {
22 } 23 }
23 24
24 @Override 25 @Override
26 public Collection<String> getClassNames() {
27 return classProvider.getClassNames();
28 }
29
30 @Override
25 @Nullable 31 @Nullable
26 public ClassNode get(String name) { 32 public ClassNode get(String name) {
27 try { 33 try {
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java
index 6eec0f3..069e0a8 100644
--- a/enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java
+++ b/enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java
@@ -1,11 +1,18 @@
1package cuchaz.enigma.classprovider; 1package cuchaz.enigma.classprovider;
2 2
3import java.util.Collection;
4
3import javax.annotation.Nullable; 5import javax.annotation.Nullable;
4 6
5import org.objectweb.asm.tree.ClassNode; 7import org.objectweb.asm.tree.ClassNode;
6 8
7public interface ClassProvider { 9public interface ClassProvider {
8 /** 10 /**
11 * @return Internal names of all contained classes. May be empty if the provider is lazy.
12 */
13 Collection<String> getClassNames();
14
15 /**
9 * Gets the {@linkplain ClassNode} for a class. The class provider may return a cached result, 16 * Gets the {@linkplain ClassNode} for a class. The class provider may return a cached result,
10 * so it's important to not mutate it. 17 * so it's important to not mutate it.
11 * 18 *
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java
index 224093f..b035cee 100644
--- a/enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java
+++ b/enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java
@@ -2,6 +2,8 @@ package cuchaz.enigma.classprovider;
2 2
3import java.io.IOException; 3import java.io.IOException;
4import java.io.InputStream; 4import java.io.InputStream;
5import java.util.Collection;
6import java.util.Collections;
5 7
6import javax.annotation.Nullable; 8import javax.annotation.Nullable;
7 9
@@ -12,6 +14,11 @@ import org.objectweb.asm.tree.ClassNode;
12 * Provides classes by loading them from the classpath. 14 * Provides classes by loading them from the classpath.
13 */ 15 */
14public class ClasspathClassProvider implements ClassProvider { 16public class ClasspathClassProvider implements ClassProvider {
17 @Override
18 public Collection<String> getClassNames() {
19 return Collections.emptyList();
20 }
21
15 @Nullable 22 @Nullable
16 @Override 23 @Override
17 public ClassNode get(String name) { 24 public ClassNode get(String name) {
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java
index 6856540..1b20b8f 100644
--- a/enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java
+++ b/enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java
@@ -1,5 +1,9 @@
1package cuchaz.enigma.classprovider; 1package cuchaz.enigma.classprovider;
2 2
3import java.util.Arrays;
4import java.util.Collection;
5import java.util.stream.Collectors;
6
3import javax.annotation.Nullable; 7import javax.annotation.Nullable;
4 8
5import org.objectweb.asm.tree.ClassNode; 9import org.objectweb.asm.tree.ClassNode;
@@ -16,6 +20,14 @@ public class CombiningClassProvider implements ClassProvider {
16 } 20 }
17 21
18 @Override 22 @Override
23 public Collection<String> getClassNames() {
24 return Arrays.stream(classProviders)
25 .map(ClassProvider::getClassNames)
26 .flatMap(Collection::stream)
27 .collect(Collectors.toSet());
28 }
29
30 @Override
19 @Nullable 31 @Nullable
20 public ClassNode get(String name) { 32 public ClassNode get(String name) {
21 for (ClassProvider cp : classProviders) { 33 for (ClassProvider cp : classProviders) {
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java
index 900a0c8..5dec5be 100644
--- a/enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java
+++ b/enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java
@@ -41,6 +41,7 @@ public class JarClassProvider implements AutoCloseable, ClassProvider {
41 return classNames.build(); 41 return classNames.build();
42 } 42 }
43 43
44 @Override
44 public Set<String> getClassNames() { 45 public Set<String> getClassNames() {
45 return classNames; 46 return classNames;
46 } 47 }
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java
index 604bf49..543ce48 100644
--- a/enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java
+++ b/enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java
@@ -1,5 +1,7 @@
1package cuchaz.enigma.classprovider; 1package cuchaz.enigma.classprovider;
2 2
3import java.util.Collection;
4
3import javax.annotation.Nullable; 5import javax.annotation.Nullable;
4 6
5import org.objectweb.asm.ClassVisitor; 7import org.objectweb.asm.ClassVisitor;
@@ -39,6 +41,11 @@ public class ObfuscationFixClassProvider implements ClassProvider {
39 } 41 }
40 42
41 @Override 43 @Override
44 public Collection<String> getClassNames() {
45 return classProvider.getClassNames();
46 }
47
48 @Override
42 @Nullable 49 @Nullable
43 public ClassNode get(String name) { 50 public ClassNode get(String name) {
44 ClassNode node = classProvider.get(name); 51 ClassNode node = classProvider.get(name);
diff --git a/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java b/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java
index 0e3244d..1219030 100644
--- a/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java
+++ b/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java
@@ -3,9 +3,11 @@ package cuchaz.enigma.source;
3import cuchaz.enigma.source.bytecode.BytecodeDecompiler; 3import cuchaz.enigma.source.bytecode.BytecodeDecompiler;
4import cuchaz.enigma.source.cfr.CfrDecompiler; 4import cuchaz.enigma.source.cfr.CfrDecompiler;
5import cuchaz.enigma.source.procyon.ProcyonDecompiler; 5import cuchaz.enigma.source.procyon.ProcyonDecompiler;
6import cuchaz.enigma.source.vineflower.VineflowerDecompiler;
6 7
7public class Decompilers { 8public class Decompilers {
8 public static final DecompilerService PROCYON = ProcyonDecompiler::new; 9 public static final DecompilerService VINEFLOWER = VineflowerDecompiler::new;
9 public static final DecompilerService CFR = CfrDecompiler::new; 10 public static final DecompilerService CFR = CfrDecompiler::new;
11 public static final DecompilerService PROCYON = ProcyonDecompiler::new;
10 public static final DecompilerService BYTECODE = BytecodeDecompiler::new; 12 public static final DecompilerService BYTECODE = BytecodeDecompiler::new;
11} 13}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDumper.java
index fb5c4a7..950f518 100644
--- a/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java
+++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDumper.java
@@ -40,7 +40,7 @@ import cuchaz.enigma.translation.representation.entry.FieldEntry;
40import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; 40import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
41import cuchaz.enigma.translation.representation.entry.MethodEntry; 41import cuchaz.enigma.translation.representation.entry.MethodEntry;
42 42
43public class EnigmaDumper extends StringStreamDumper { 43public class CfrDumper extends StringStreamDumper {
44 private final StringBuilder sb; 44 private final StringBuilder sb;
45 private final SourceSettings sourceSettings; 45 private final SourceSettings sourceSettings;
46 private final SourceIndex index; 46 private final SourceIndex index;
@@ -51,11 +51,11 @@ public class EnigmaDumper extends StringStreamDumper {
51 private boolean muteLine = false; 51 private boolean muteLine = false;
52 private MethodEntry contextMethod = null; 52 private MethodEntry contextMethod = null;
53 53
54 public EnigmaDumper(StringBuilder sb, SourceSettings sourceSettings, TypeUsageInformation typeUsage, Options options, @Nullable EntryRemapper mapper) { 54 public CfrDumper(StringBuilder sb, SourceSettings sourceSettings, TypeUsageInformation typeUsage, Options options, @Nullable EntryRemapper mapper) {
55 this(sb, sourceSettings, typeUsage, options, mapper, new SourceIndex(), new MovableDumperContext()); 55 this(sb, sourceSettings, typeUsage, options, mapper, new SourceIndex(), new MovableDumperContext());
56 } 56 }
57 57
58 protected EnigmaDumper(StringBuilder sb, SourceSettings sourceSettings, TypeUsageInformation typeUsage, Options options, @Nullable EntryRemapper mapper, SourceIndex index, MovableDumperContext context) { 58 protected CfrDumper(StringBuilder sb, SourceSettings sourceSettings, TypeUsageInformation typeUsage, Options options, @Nullable EntryRemapper mapper, SourceIndex index, MovableDumperContext context) {
59 super((m, e) -> { 59 super((m, e) -> {
60 }, sb, typeUsage, options, IllegalIdentifierDump.Nop.getInstance(), context); 60 }, sb, typeUsage, options, IllegalIdentifierDump.Nop.getInstance(), context);
61 this.sb = sb; 61 this.sb = sb;
@@ -149,21 +149,15 @@ public class EnigmaDumper extends StringStreamDumper {
149 } 149 }
150 150
151 EntryMapping mapping = mapper.getDeobfMapping(getFieldEntry(owner, field.getFieldName(), field.getField().getDescriptor())); 151 EntryMapping mapping = mapper.getDeobfMapping(getFieldEntry(owner, field.getFieldName(), field.getField().getDescriptor()));
152 String javadoc = mapping.javadoc();
152 153
153 if (mapping == null) { 154 if (javadoc != null) {
154 continue; 155 recordComponentDocs.add(String.format("@param %s %s", mapping.targetName(), javadoc));
155 }
156
157 String javaDoc = mapping.javadoc();
158
159 if (javaDoc != null) {
160 recordComponentDocs.add(String.format("@param %s %s", mapping.targetName(), javaDoc));
161 } 156 }
162 } 157 }
163 } 158 }
164 159
165 EntryMapping mapping = mapper.getDeobfMapping(getClassEntry(owner)); 160 EntryMapping mapping = mapper.getDeobfMapping(getClassEntry(owner));
166
167 String javadoc = null; 161 String javadoc = null;
168 162
169 if (mapping != null) { 163 if (mapping != null) {
@@ -399,7 +393,7 @@ public class EnigmaDumper extends StringStreamDumper {
399 */ 393 */
400 @Override 394 @Override
401 public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { 395 public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) {
402 return new EnigmaDumper(this.sb, sourceSettings, innerclassTypeUsageInformation, options, mapper, index, dumperContext); 396 return new CfrDumper(this.sb, sourceSettings, innerclassTypeUsageInformation, options, mapper, index, dumperContext);
403 } 397 }
404 398
405 @Override 399 @Override
diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java
index 70b43ac..cf6c52f 100644
--- a/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java
+++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java
@@ -82,7 +82,7 @@ public class CfrSource implements Source {
82 TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree); 82 TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree);
83 tree.analyseTop(state, typeUsageCollector); 83 tree.analyseTop(state, typeUsageCollector);
84 84
85 EnigmaDumper dumper = new EnigmaDumper(new StringBuilder(), settings, typeUsageCollector.getRealTypeUsageInformation(), options, mapper); 85 CfrDumper dumper = new CfrDumper(new StringBuilder(), settings, typeUsageCollector.getRealTypeUsageInformation(), options, mapper);
86 tree.dump(state.getObfuscationMapping().wrap(dumper)); 86 tree.dump(state.getObfuscationMapping().wrap(dumper));
87 index = dumper.getIndex(); 87 index = dumper.getIndex();
88 } 88 }
diff --git a/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerContextSource.java b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerContextSource.java
new file mode 100644
index 0000000..e0f2b05
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerContextSource.java
@@ -0,0 +1,127 @@
1package cuchaz.enigma.source.vineflower;
2
3import java.io.ByteArrayInputStream;
4import java.io.InputStream;
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.List;
8
9import org.jetbrains.java.decompiler.main.extern.IContextSource;
10import org.jetbrains.java.decompiler.main.extern.IResultSaver;
11import org.objectweb.asm.tree.ClassNode;
12
13import cuchaz.enigma.classprovider.ClassProvider;
14import cuchaz.enigma.utils.AsmUtil;
15
16class VineflowerContextSource implements IContextSource {
17 private final IContextSource classpathSource = new ClasspathSource();
18 private final ClassProvider classProvider;
19 private final String className;
20 private Entries entries;
21
22 VineflowerContextSource(ClassProvider classProvider, String className) {
23 this.classProvider = classProvider;
24 this.className = className;
25 }
26
27 public IContextSource getClasspath() {
28 return classpathSource;
29 }
30
31 @Override
32 public String getName() {
33 return "Enigma-provided context for class " + className;
34 }
35
36 @Override
37 public Entries getEntries() {
38 computeEntriesIfNecessary();
39 return entries;
40 }
41
42 private void computeEntriesIfNecessary() {
43 if (entries != null) {
44 return;
45 }
46
47 synchronized (this) {
48 if (entries != null) return;
49
50 List<String> classNames = new ArrayList<>();
51 classNames.add(className);
52
53 int dollarIndex = className.indexOf('$');
54 String outermostClass = dollarIndex == -1 ? className : className.substring(0, className.indexOf('$'));
55 String outermostClassSuffixed = outermostClass + "$";
56
57 for (String currentClass : classProvider.getClassNames()) {
58 if (currentClass.startsWith(outermostClassSuffixed) && !currentClass.equals(className)) {
59 classNames.add(currentClass);
60 }
61 }
62
63 List<Entry> classes = classNames.stream()
64 .map(Entry::atBase)
65 .toList();
66
67 entries = new Entries(classes, Collections.emptyList(), Collections.emptyList());
68 }
69 }
70
71 @Override
72 public InputStream getInputStream(String resource) {
73 ClassNode node = classProvider.get(resource.substring(0, resource.lastIndexOf(".class")));
74
75 if (node == null) {
76 return null;
77 }
78
79 return new ByteArrayInputStream(AsmUtil.nodeToBytes(node));
80 }
81
82 @Override
83 public IOutputSink createOutputSink(IResultSaver saver) {
84 return new IOutputSink() {
85 @Override
86 public void begin() { }
87
88 @Override
89 public void acceptClass(String qualifiedName, String fileName, String content, int[] mapping) {
90 if (qualifiedName.equals(VineflowerContextSource.this.className)) {
91 saver.saveClassFile(null, qualifiedName, fileName, content, mapping);
92 }
93 }
94
95 @Override
96 public void acceptDirectory(String directory) { }
97
98 @Override
99 public void acceptOther(String path) { }
100
101 @Override
102 public void close() { }
103 };
104 }
105
106 public class ClasspathSource implements IContextSource {
107 @Override
108 public String getName() {
109 return "Enigma-provided classpath context for " + VineflowerContextSource.this.className;
110 }
111
112 @Override
113 public Entries getEntries() {
114 return Entries.EMPTY;
115 }
116
117 @Override
118 public boolean isLazy() {
119 return true;
120 }
121
122 @Override
123 public InputStream getInputStream(String resource) {
124 return VineflowerContextSource.this.getInputStream(resource);
125 }
126 }
127}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerDecompiler.java
new file mode 100644
index 0000000..56fd0b9
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerDecompiler.java
@@ -0,0 +1,24 @@
1package cuchaz.enigma.source.vineflower;
2
3import org.checkerframework.checker.nullness.qual.Nullable;
4
5import cuchaz.enigma.classprovider.ClassProvider;
6import cuchaz.enigma.source.Decompiler;
7import cuchaz.enigma.source.Source;
8import cuchaz.enigma.source.SourceSettings;
9import cuchaz.enigma.translation.mapping.EntryRemapper;
10
11public class VineflowerDecompiler implements Decompiler {
12 private final ClassProvider classProvider;
13 private final SourceSettings settings;
14
15 public VineflowerDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) {
16 this.settings = sourceSettings;
17 this.classProvider = classProvider;
18 }
19
20 @Override
21 public Source getSource(String className, @Nullable EntryRemapper remapper) {
22 return new VineflowerSource(new VineflowerContextSource(classProvider, className), remapper, settings);
23 }
24}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerJavadocProvider.java b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerJavadocProvider.java
new file mode 100644
index 0000000..3757b02
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerJavadocProvider.java
@@ -0,0 +1,139 @@
1package cuchaz.enigma.source.vineflower;
2
3import java.util.Collection;
4import java.util.LinkedList;
5import java.util.List;
6
7import net.fabricmc.fernflower.api.IFabricJavadocProvider;
8import org.jetbrains.java.decompiler.struct.StructClass;
9import org.jetbrains.java.decompiler.struct.StructField;
10import org.jetbrains.java.decompiler.struct.StructMethod;
11import org.jetbrains.java.decompiler.struct.StructRecordComponent;
12import org.objectweb.asm.Opcodes;
13
14import cuchaz.enigma.translation.mapping.EntryMapping;
15import cuchaz.enigma.translation.mapping.EntryRemapper;
16import cuchaz.enigma.translation.representation.entry.ClassEntry;
17import cuchaz.enigma.translation.representation.entry.Entry;
18import cuchaz.enigma.translation.representation.entry.FieldEntry;
19import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21
22class VineflowerJavadocProvider implements IFabricJavadocProvider {
23 private final EntryRemapper remapper;
24
25 VineflowerJavadocProvider(EntryRemapper remapper) {
26 this.remapper = remapper;
27 }
28
29 @Override
30 public String getClassDoc(StructClass cls) {
31 if (remapper == null) return null;
32
33 List<String> recordComponentDocs = new LinkedList<>();
34
35 if (isRecord(cls)) {
36 for (StructRecordComponent component : cls.getRecordComponents()) {
37 EntryMapping mapping = remapper.getDeobfMapping(fieldEntryOf(cls, component));
38 String javadoc = mapping.javadoc();
39
40 if (javadoc != null) {
41 recordComponentDocs.add(String.format("@param %s %s", mapping.targetName(), javadoc));
42 }
43 }
44 }
45
46 EntryMapping mapping = remapper.getDeobfMapping(classEntryOf(cls));
47 StringBuilder builder = new StringBuilder();
48 String javadoc = mapping.javadoc();
49
50 if (javadoc != null) {
51 builder.append(javadoc);
52 }
53
54 if (!recordComponentDocs.isEmpty()) {
55 if (javadoc != null) {
56 builder.append('\n');
57 }
58
59 for (String recordComponentDoc : recordComponentDocs) {
60 builder.append('\n').append(recordComponentDoc);
61 }
62 }
63
64 javadoc = builder.toString();
65
66 return javadoc.isBlank() ? null : javadoc.trim();
67 }
68
69 @Override
70 public String getFieldDoc(StructClass cls, StructField fld) {
71 boolean isRecordComponent = isRecord(cls) && !fld.hasModifier(Opcodes.ACC_STATIC);
72
73 if (remapper == null || isRecordComponent) {
74 return null;
75 }
76
77 EntryMapping mapping = remapper.getDeobfMapping(fieldEntryOf(cls, fld));
78 String javadoc = mapping.javadoc();
79
80 return javadoc == null || javadoc.isBlank() ? null : javadoc.trim();
81 }
82
83 @Override
84 public String getMethodDoc(StructClass cls, StructMethod mth) {
85 if (remapper == null) return null;
86
87 MethodEntry entry = methodEntryOf(cls, mth);
88 EntryMapping mapping = remapper.getDeobfMapping(entry);
89 StringBuilder builder = new StringBuilder();
90 String javadoc = mapping.javadoc();
91
92 if (javadoc != null) {
93 builder.append(javadoc);
94 }
95
96 Collection<Entry<?>> children = remapper.getObfChildren(entry);
97 boolean addedLf = false;
98
99 if (children != null && !children.isEmpty()) {
100 for (Entry<?> each : children) {
101 if (each instanceof LocalVariableEntry) {
102 mapping = remapper.getDeobfMapping(each);
103 javadoc = mapping.javadoc();
104
105 if (javadoc != null) {
106 if (!addedLf) {
107 addedLf = true;
108 builder.append('\n');
109 }
110
111 builder.append(String.format("\n@param %s %s", mapping.targetName(), javadoc));
112 }
113 }
114 }
115 }
116
117 javadoc = builder.toString();
118
119 return javadoc.isBlank() ? null : javadoc.trim();
120 }
121
122 private boolean isRecord(StructClass cls) {
123 if (cls.superClass == null) return false;
124
125 return cls.superClass.getString().equals("java/lang/Record");
126 }
127
128 private ClassEntry classEntryOf(StructClass cls) {
129 return ClassEntry.parse(cls.qualifiedName);
130 }
131
132 private FieldEntry fieldEntryOf(StructClass cls, StructField fld) {
133 return FieldEntry.parse(cls.qualifiedName, fld.getName(), fld.getDescriptor());
134 }
135
136 private MethodEntry methodEntryOf(StructClass cls, StructMethod mth) {
137 return MethodEntry.parse(cls.qualifiedName, mth.getName(), mth.getDescriptor());
138 }
139}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerSource.java b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerSource.java
new file mode 100644
index 0000000..27b6edc
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerSource.java
@@ -0,0 +1,119 @@
1package cuchaz.enigma.source.vineflower;
2
3import java.util.HashMap;
4import java.util.Map;
5import java.util.concurrent.atomic.AtomicReference;
6import java.util.jar.Manifest;
7
8import net.fabricmc.fernflower.api.IFabricJavadocProvider;
9import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler;
10import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger;
11import org.jetbrains.java.decompiler.main.extern.IContextSource;
12import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
13import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
14import org.jetbrains.java.decompiler.main.extern.IResultSaver;
15import org.jetbrains.java.decompiler.main.extern.TextTokenVisitor;
16
17import cuchaz.enigma.source.Source;
18import cuchaz.enigma.source.SourceIndex;
19import cuchaz.enigma.source.SourceSettings;
20import cuchaz.enigma.translation.mapping.EntryRemapper;
21
22class VineflowerSource implements Source {
23 private final IContextSource contextSource;
24 private final IContextSource librarySource;
25 private final SourceSettings settings;
26 private EntryRemapper remapper;
27 private SourceIndex index;
28
29 VineflowerSource(VineflowerContextSource contextSource, EntryRemapper remapper, SourceSettings settings) {
30 this.contextSource = contextSource;
31 this.librarySource = contextSource.getClasspath();
32 this.remapper = remapper;
33 this.settings = settings;
34 }
35
36 @Override
37 public String asString() {
38 ensureDecompiled();
39 return index.getSource();
40 }
41
42 @Override
43 public Source withJavadocs(EntryRemapper remapper) {
44 this.remapper = remapper;
45 this.index = null;
46 return this;
47 }
48
49 @Override
50 public SourceIndex index() {
51 ensureDecompiled();
52 return index;
53 }
54
55 private void ensureDecompiled() {
56 if (index != null) {
57 return;
58 }
59
60 Map<String, Object> preferences = new HashMap<>(IFernflowerPreferences.DEFAULTS);
61 preferences.put(IFernflowerPreferences.INDENT_STRING, "\t");
62 preferences.put(IFernflowerPreferences.LOG_LEVEL, IFernflowerLogger.Severity.WARN.name());
63 preferences.put(IFernflowerPreferences.THREADS, String.valueOf(Math.max(1, Runtime.getRuntime().availableProcessors() - 2)));
64 preferences.put(IFabricJavadocProvider.PROPERTY_NAME, new VineflowerJavadocProvider(remapper));
65
66 if (settings.removeImports) {
67 preferences.put(IFernflowerPreferences.REMOVE_IMPORTS, "1");
68 }
69
70 index = new SourceIndex();
71 IResultSaver saver = new ResultSaver(index);
72 IFernflowerLogger logger = new PrintStreamLogger(System.out);
73 BaseDecompiler decompiler = new BaseDecompiler(saver, preferences, logger);
74
75 AtomicReference<VineflowerTextTokenCollector> tokenCollector = new AtomicReference<>();
76 TextTokenVisitor.addVisitor(next -> {
77 tokenCollector.set(new VineflowerTextTokenCollector(next));
78 return tokenCollector.get();
79 });
80
81 decompiler.addSource(contextSource);
82
83 if (librarySource != null) {
84 decompiler.addLibrary(librarySource);
85 }
86
87 decompiler.decompileContext();
88 tokenCollector.get().accept(index);
89 }
90
91 private class ResultSaver implements IResultSaver {
92 private final SourceIndex index;
93
94 private ResultSaver(SourceIndex index) {
95 this.index = index;
96 }
97
98 @Override
99 public void saveFolder(String path) { }
100 @Override
101 public void copyFile(String source, String path, String entryName) { }
102
103 @Override
104 public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) {
105 index.setSource(content);
106 }
107
108 @Override
109 public void createArchive(String path, String archiveName, Manifest manifest) { }
110 @Override
111 public void saveDirEntry(String path, String archiveName, String entryName) { }
112 @Override
113 public void copyEntry(String source, String path, String archiveName, String entry) { }
114 @Override
115 public void closeArchive(String path, String archiveName) { }
116 @Override
117 public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) { }
118 }
119}
diff --git a/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerTextTokenCollector.java b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerTextTokenCollector.java
new file mode 100644
index 0000000..3d45712
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/source/vineflower/VineflowerTextTokenCollector.java
@@ -0,0 +1,151 @@
1package cuchaz.enigma.source.vineflower;
2
3import java.util.HashMap;
4import java.util.LinkedHashSet;
5import java.util.Map;
6import java.util.Set;
7
8import org.jetbrains.java.decompiler.main.extern.TextTokenVisitor;
9import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor;
10import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor;
11import org.jetbrains.java.decompiler.util.Pair;
12import org.jetbrains.java.decompiler.util.token.TextRange;
13
14import cuchaz.enigma.source.SourceIndex;
15import cuchaz.enigma.source.Token;
16import cuchaz.enigma.translation.representation.entry.ClassEntry;
17import cuchaz.enigma.translation.representation.entry.Entry;
18import cuchaz.enigma.translation.representation.entry.FieldEntry;
19import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21
22class VineflowerTextTokenCollector extends TextTokenVisitor {
23 private final Map<Token, Entry<?>> declarations = new HashMap<>();
24 private final Map<Token, Pair<Entry<?>, Entry<?>>> references = new HashMap<>();
25 private final Set<Token> tokens = new LinkedHashSet<>();
26 private String content;
27 private MethodEntry currentMethod;
28
29 VineflowerTextTokenCollector(TextTokenVisitor next) {
30 super(next);
31 }
32
33 @Override
34 public void start(String content) {
35 this.content = content;
36 }
37
38 @Override
39 public void visitClass(TextRange range, boolean declaration, String name) {
40 super.visitClass(range, declaration, name);
41 Token token = getToken(range);
42
43 if (declaration) {
44 addDeclaration(token, classEntryOf(name));
45 } else {
46 addReference(token, classEntryOf(name), currentMethod);
47 }
48 }
49
50 @Override
51 public void visitField(TextRange range, boolean declaration, String className, String name, FieldDescriptor descriptor) {
52 super.visitField(range, declaration, className, name, descriptor);
53 Token token = getToken(range);
54
55 if (declaration) {
56 addDeclaration(token, fieldEntryOf(className, name, descriptor));
57 } else {
58 addReference(token, fieldEntryOf(className, name, descriptor), currentMethod);
59 }
60 }
61
62 @Override
63 public void visitMethod(TextRange range, boolean declaration, String className, String name, MethodDescriptor descriptor) {
64 super.visitMethod(range, declaration, className, name, descriptor);
65 Token token = getToken(range);
66
67 if (token.text.equals("new")) {
68 return;
69 }
70
71 MethodEntry entry = methodEntryOf(className, name, descriptor);
72
73 if (declaration) {
74 addDeclaration(token, entry);
75 currentMethod = entry;
76 } else {
77 addReference(token, entry, currentMethod);
78 }
79 }
80
81 @Override
82 public void visitParameter(TextRange range, boolean declaration, String className, String methodName, MethodDescriptor methodDescriptor, int lvIndex, String name) {
83 super.visitParameter(range, declaration, className, methodName, methodDescriptor, lvIndex, name);
84 Token token = getToken(range);
85 MethodEntry parent = methodEntryOf(className, methodName, methodDescriptor);
86
87 if (declaration) {
88 addDeclaration(token, argEntryOf(parent, lvIndex, name));
89 } else {
90 addReference(token, argEntryOf(parent, lvIndex, name), currentMethod);
91 }
92 }
93
94 @Override
95 public void visitLocal(TextRange range, boolean declaration, String className, String methodName, MethodDescriptor methodDescriptor, int lvIndex, String name) {
96 super.visitLocal(range, declaration, className, methodName, methodDescriptor, lvIndex, name);
97 Token token = getToken(range);
98 MethodEntry parent = methodEntryOf(className, methodName, methodDescriptor);
99
100 if (declaration) {
101 addDeclaration(token, varEntryOf(parent, lvIndex, name));
102 } else {
103 addReference(token, varEntryOf(parent, lvIndex, name), currentMethod);
104 }
105 }
106
107 private ClassEntry classEntryOf(String name) {
108 return ClassEntry.parse(name);
109 }
110
111 private FieldEntry fieldEntryOf(String className, String name, FieldDescriptor descriptor) {
112 return FieldEntry.parse(className, name, descriptor.descriptorString);
113 }
114
115 private MethodEntry methodEntryOf(String className, String name, MethodDescriptor descriptor) {
116 return MethodEntry.parse(className, name, descriptor.toString());
117 }
118
119 private LocalVariableEntry argEntryOf(MethodEntry className, int lvIndex, String name) {
120 return new LocalVariableEntry(className, lvIndex, name, true, null);
121 }
122
123 private LocalVariableEntry varEntryOf(MethodEntry className, int lvIndex, String name) {
124 return new LocalVariableEntry(className, lvIndex, name, false, null);
125 }
126
127 private Token getToken(TextRange range) {
128 return new Token(range.start, range.start + range.length, content.substring(range.start, range.start + range.length));
129 }
130
131 private void addDeclaration(Token token, Entry<?> entry) {
132 declarations.put(token, entry);
133 tokens.add(token);
134 }
135
136 private void addReference(Token token, Entry<?> entry, Entry<?> context) {
137 references.put(token, Pair.of(entry, context));
138 tokens.add(token);
139 }
140
141 public void accept(SourceIndex index) {
142 for (Token token : tokens) {
143 if (declarations.get(token) != null) {
144 index.addDeclaration(token, declarations.get(token));
145 } else {
146 Pair<Entry<?>, Entry<?>> reference = references.get(token);
147 index.addReference(token, reference.a, reference.b);
148 }
149 }
150 }
151}