Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Vineflower decompiler #541

Merged
merged 4 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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