Skip to content

Commit

Permalink
Add Vineflower decompiler (#541)
Browse files Browse the repository at this point in the history
* Add Vineflower integration

* Fix some confusing names

* Rename method to reflect visitor pattern naming schemes

* Remove dead null check
  • Loading branch information
NebelNidas authored Apr 6, 2024
1 parent b7c76f8 commit 31b6e9e
Show file tree
Hide file tree
Showing 21 changed files with 630 additions and 20 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ Enigma is distributed under the [LGPL-3.0](LICENSE).

Enigma includes the following open-source libraries:

- A [modified version](https://github.com/FabricMC/procyon) of [Procyon](https://bitbucket.org/mstrobel/procyon) (Apache-2.0)
- [Vineflower](https://github.com/Vineflower/vineflower) (Apache-2.0)
- A [modified version](https://github.com/FabricMC/cfr) of [CFR](https://github.com/leibnitz27/cfr) (MIT)
- A [modified version](https://github.com/FabricMC/procyon) of [Procyon](https://bitbucket.org/mstrobel/procyon) (Apache-2.0)
- [Guava](https://github.com/google/guava) (Apache-2.0)
- [SyntaxPane](https://github.com/Sciss/SyntaxPane) (Apache-2.0)
- [FlatLaf](https://github.com/JFormDesigner/FlatLaf) (Apache-2.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import cuchaz.enigma.source.Decompilers;

public enum Decompiler {
VINEFLOWER("Vineflower", Decompilers.VINEFLOWER),
CFR("CFR", Decompilers.CFR),
PROCYON("Procyon", Decompilers.PROCYON),
BYTECODE("Bytecode", Decompilers.BYTECODE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public static void setLookAndFeel(LookAndFeel laf) {
}

public static Decompiler getDecompiler() {
return ui.data().section("Decompiler").setIfAbsentEnum(Decompiler::valueOf, "Current", Decompiler.CFR);
return ui.data().section("Decompiler").setIfAbsentEnum(Decompiler::valueOf, "Current", Decompiler.VINEFLOWER);
}

public static void setDecompiler(Decompiler d) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public Color get() {

public float scaleFactor = 1.0f;

public Decompiler decompiler = Decompiler.CFR;
public Decompiler decompiler = Decompiler.VINEFLOWER;

public Config() {
gson = new GsonBuilder().registerTypeAdapter(Integer.class, new IntSerializer()).registerTypeAdapter(Integer.class, new IntDeserializer()).registerTypeAdapter(Config.class, (InstanceCreator<Config>) type -> this).setPrettyPrinting().create();
Expand Down
1 change: 1 addition & 0 deletions enigma/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {

implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.6.0'
implementation 'net.fabricmc:cfr:0.2.2'
implementation 'org.vineflower:vineflower:1.10.0'

proGuard 'com.guardsquare:proguard-base:7.4.0-beta02'

Expand Down
12 changes: 11 additions & 1 deletion enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,17 @@ public Stream<ClassSource> decompileStream(ProgressListener progress, Decompiler
progress.init(classes.size(), I18n.translate("progress.classes.decompiling"));

//create a common instance outside the loop as mappings shouldn't be changing while this is happening
Decompiler decompiler = decompilerService.create(compiled::get, new SourceSettings(false, false));
Decompiler decompiler = decompilerService.create(new ClassProvider() {
@Override
public Collection<String> getClassNames() {
return compiled.keySet();
}

@Override
public ClassNode get(String name) {
return compiled.get(name);
}
}, new SourceSettings(false, false));

AtomicInteger count = new AtomicInteger();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ private void registerEnumNamingService(EnigmaPluginContext ctx) {
}

private void registerDecompilerServices(EnigmaPluginContext ctx) {
ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON);
ctx.registerService("enigma:vineflower", DecompilerService.TYPE, ctx1 -> Decompilers.VINEFLOWER);
ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR);
ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON);
ctx.registerService("enigma:bytecode", DecompilerService.TYPE, ctx1 -> Decompilers.BYTECODE);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cuchaz.enigma.classprovider;

import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
Expand All @@ -21,6 +22,11 @@ public CachingClassProvider(ClassProvider classProvider) {
this.classProvider = classProvider;
}

@Override
public Collection<String> getClassNames() {
return classProvider.getClassNames();
}

@Override
@Nullable
public ClassNode get(String name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package cuchaz.enigma.classprovider;

import java.util.Collection;

import javax.annotation.Nullable;

import org.objectweb.asm.tree.ClassNode;

public interface ClassProvider {
/**
* @return Internal names of all contained classes. May be empty if the provider is lazy.
*/
Collection<String> getClassNames();

/**
* Gets the {@linkplain ClassNode} for a class. The class provider may return a cached result,
* so it's important to not mutate it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;

import javax.annotation.Nullable;

Expand All @@ -12,6 +14,11 @@
* Provides classes by loading them from the classpath.
*/
public class ClasspathClassProvider implements ClassProvider {
@Override
public Collection<String> getClassNames() {
return Collections.emptyList();
}

@Nullable
@Override
public ClassNode get(String name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package cuchaz.enigma.classprovider;

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import org.objectweb.asm.tree.ClassNode;
Expand All @@ -15,6 +19,14 @@ public CombiningClassProvider(ClassProvider... classProviders) {
this.classProviders = classProviders;
}

@Override
public Collection<String> getClassNames() {
return Arrays.stream(classProviders)
.map(ClassProvider::getClassNames)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}

@Override
@Nullable
public ClassNode get(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ private static ImmutableSet<String> collectClassNames(FileSystem fileSystem) thr
return classNames.build();
}

@Override
public Set<String> getClassNames() {
return classNames;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cuchaz.enigma.classprovider;

import java.util.Collection;

import javax.annotation.Nullable;

import org.objectweb.asm.ClassVisitor;
Expand Down Expand Up @@ -38,6 +40,11 @@ public ObfuscationFixClassProvider(ClassProvider classProvider, JarIndex jarInde
this.jarIndex = jarIndex;
}

@Override
public Collection<String> getClassNames() {
return classProvider.getClassNames();
}

@Override
@Nullable
public ClassNode get(String name) {
Expand Down
4 changes: 3 additions & 1 deletion enigma/src/main/java/cuchaz/enigma/source/Decompilers.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import cuchaz.enigma.source.bytecode.BytecodeDecompiler;
import cuchaz.enigma.source.cfr.CfrDecompiler;
import cuchaz.enigma.source.procyon.ProcyonDecompiler;
import cuchaz.enigma.source.vineflower.VineflowerDecompiler;

public class Decompilers {
public static final DecompilerService PROCYON = ProcyonDecompiler::new;
public static final DecompilerService VINEFLOWER = VineflowerDecompiler::new;
public static final DecompilerService CFR = CfrDecompiler::new;
public static final DecompilerService PROCYON = ProcyonDecompiler::new;
public static final DecompilerService BYTECODE = BytecodeDecompiler::new;
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
import cuchaz.enigma.translation.representation.entry.MethodEntry;

public class EnigmaDumper extends StringStreamDumper {
public class CfrDumper extends StringStreamDumper {
private final StringBuilder sb;
private final SourceSettings sourceSettings;
private final SourceIndex index;
Expand All @@ -51,11 +51,11 @@ public class EnigmaDumper extends StringStreamDumper {
private boolean muteLine = false;
private MethodEntry contextMethod = null;

public EnigmaDumper(StringBuilder sb, SourceSettings sourceSettings, TypeUsageInformation typeUsage, Options options, @Nullable EntryRemapper mapper) {
public CfrDumper(StringBuilder sb, SourceSettings sourceSettings, TypeUsageInformation typeUsage, Options options, @Nullable EntryRemapper mapper) {
this(sb, sourceSettings, typeUsage, options, mapper, new SourceIndex(), new MovableDumperContext());
}

protected EnigmaDumper(StringBuilder sb, SourceSettings sourceSettings, TypeUsageInformation typeUsage, Options options, @Nullable EntryRemapper mapper, SourceIndex index, MovableDumperContext context) {
protected CfrDumper(StringBuilder sb, SourceSettings sourceSettings, TypeUsageInformation typeUsage, Options options, @Nullable EntryRemapper mapper, SourceIndex index, MovableDumperContext context) {
super((m, e) -> {
}, sb, typeUsage, options, IllegalIdentifierDump.Nop.getInstance(), context);
this.sb = sb;
Expand Down Expand Up @@ -149,21 +149,15 @@ public Dumper dumpClassDoc(JavaTypeInstance owner) {
}

EntryMapping mapping = mapper.getDeobfMapping(getFieldEntry(owner, field.getFieldName(), field.getField().getDescriptor()));
String javadoc = mapping.javadoc();

if (mapping == null) {
continue;
}

String javaDoc = mapping.javadoc();

if (javaDoc != null) {
recordComponentDocs.add(String.format("@param %s %s", mapping.targetName(), javaDoc));
if (javadoc != null) {
recordComponentDocs.add(String.format("@param %s %s", mapping.targetName(), javadoc));
}
}
}

EntryMapping mapping = mapper.getDeobfMapping(getClassEntry(owner));

String javadoc = null;

if (mapping != null) {
Expand Down Expand Up @@ -399,7 +393,7 @@ private void dumpClass(TypeContext context, JavaTypeInstance type, boolean defin
*/
@Override
public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) {
return new EnigmaDumper(this.sb, sourceSettings, innerclassTypeUsageInformation, options, mapper, index, dumperContext);
return new CfrDumper(this.sb, sourceSettings, innerclassTypeUsageInformation, options, mapper, index, dumperContext);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private void ensureDecompiled() {
TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree);
tree.analyseTop(state, typeUsageCollector);

EnigmaDumper dumper = new EnigmaDumper(new StringBuilder(), settings, typeUsageCollector.getRealTypeUsageInformation(), options, mapper);
CfrDumper dumper = new CfrDumper(new StringBuilder(), settings, typeUsageCollector.getRealTypeUsageInformation(), options, mapper);
tree.dump(state.getObfuscationMapping().wrap(dumper));
index = dumper.getIndex();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package cuchaz.enigma.source.vineflower;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.jetbrains.java.decompiler.main.extern.IContextSource;
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
import org.objectweb.asm.tree.ClassNode;

import cuchaz.enigma.classprovider.ClassProvider;
import cuchaz.enigma.utils.AsmUtil;

class VineflowerContextSource implements IContextSource {
private final IContextSource classpathSource = new ClasspathSource();
private final ClassProvider classProvider;
private final String className;
private Entries entries;

VineflowerContextSource(ClassProvider classProvider, String className) {
this.classProvider = classProvider;
this.className = className;
}

public IContextSource getClasspath() {
return classpathSource;
}

@Override
public String getName() {
return "Enigma-provided context for class " + className;
}

@Override
public Entries getEntries() {
computeEntriesIfNecessary();
return entries;
}

private void computeEntriesIfNecessary() {
if (entries != null) {
return;
}

synchronized (this) {
if (entries != null) return;

List<String> classNames = new ArrayList<>();
classNames.add(className);

int dollarIndex = className.indexOf('$');
String outermostClass = dollarIndex == -1 ? className : className.substring(0, className.indexOf('$'));
String outermostClassSuffixed = outermostClass + "$";

for (String currentClass : classProvider.getClassNames()) {
if (currentClass.startsWith(outermostClassSuffixed) && !currentClass.equals(className)) {
classNames.add(currentClass);
}
}

List<Entry> classes = classNames.stream()
.map(Entry::atBase)
.toList();

entries = new Entries(classes, Collections.emptyList(), Collections.emptyList());
}
}

@Override
public InputStream getInputStream(String resource) {
ClassNode node = classProvider.get(resource.substring(0, resource.lastIndexOf(".class")));

if (node == null) {
return null;
}

return new ByteArrayInputStream(AsmUtil.nodeToBytes(node));
}

@Override
public IOutputSink createOutputSink(IResultSaver saver) {
return new IOutputSink() {
@Override
public void begin() { }

@Override
public void acceptClass(String qualifiedName, String fileName, String content, int[] mapping) {
if (qualifiedName.equals(VineflowerContextSource.this.className)) {
saver.saveClassFile(null, qualifiedName, fileName, content, mapping);
}
}

@Override
public void acceptDirectory(String directory) { }

@Override
public void acceptOther(String path) { }

@Override
public void close() { }
};
}

public class ClasspathSource implements IContextSource {
@Override
public String getName() {
return "Enigma-provided classpath context for " + VineflowerContextSource.this.className;
}

@Override
public Entries getEntries() {
return Entries.EMPTY;
}

@Override
public boolean isLazy() {
return true;
}

@Override
public InputStream getInputStream(String resource) {
return VineflowerContextSource.this.getInputStream(resource);
}
}
}
Loading

0 comments on commit 31b6e9e

Please sign in to comment.