Skip to content

Commit

Permalink
Support the Vineflower decompiler (#951)
Browse files Browse the repository at this point in the history
  • Loading branch information
modmuss50 authored Sep 11, 2023
1 parent 0a3779f commit 71b7bea
Show file tree
Hide file tree
Showing 17 changed files with 790 additions and 64 deletions.
76 changes: 73 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,37 @@ plugins {
id 'checkstyle'
id 'jacoco'
id 'codenarc'
alias(libs.plugins.kotlin)
alias(libs.plugins.kotlin) apply false // Delay this so we can perform magic 🪄 first.
alias(libs.plugins.spotless)
alias(libs.plugins.retry)
}

/**
* Haha this is fun :) The Kotlin gradle plugin triggers deprecation warnings for custom configurations (https://youtrack.jetbrains.com/issue/KT-60879)
* We need to make DefaultConfiguration.isSpecialCaseOfChangingUsage think that our configurstion is a special case and not deprecated.
* We do this by setting DefaultConfiguration.roleAtCreation to LEGACY, thus isInLegacyRole will now return true.
*
* Yeah I know we can just ignore the deprecation warning, but doing so wouldn't alert us to issues when testing against pre-release Gradle versions. Also this is more fun :)
*/
def brokenConfigurations = [
"commonDecompilerRuntimeClasspath",
"fernflowerRuntimeClasspath",
"cfrRuntimeClasspath",
"vineflowerRuntimeClasspath"
]

configurations.configureEach {
if (brokenConfigurations.contains(it.name)) {
// For some reason Gradle stops us from using Groovy magic to do this, so lets do it the boring way.
def field = org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.class.getDeclaredField("roleAtCreation")
field.setAccessible(true)
field.set(it, ConfigurationRoles.LEGACY)
}
}

// Ensure we apply the Kotlin plugin after, to allow for the above configuration to take place first
apply plugin: libs.plugins.kotlin.get().pluginId

tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8"
}
Expand Down Expand Up @@ -62,6 +88,29 @@ configurations.all {
}
}

sourceSets {
commonDecompiler {
java {
srcDir("src/decompilers/common")
}
}
fernflower {
java {
srcDir("src/decompilers/fernflower")
}
}
cfr {
java {
srcDir("src/decompilers/cfr")
}
}
vineflower {
java {
srcDir("src/decompilers/vineflower")
}
}
}

dependencies {
implementation gradleApi()

Expand Down Expand Up @@ -89,8 +138,23 @@ dependencies {
}

// decompilers
compileOnly runtimeLibs.fernflower
compileOnly runtimeLibs.cfr
fernflowerCompileOnly runtimeLibs.fernflower
fernflowerCompileOnly libs.fabric.mapping.io

cfrCompileOnly runtimeLibs.cfr
cfrCompileOnly libs.fabric.mapping.io

vineflowerCompileOnly runtimeLibs.vineflower
vineflowerCompileOnly libs.fabric.mapping.io

fernflowerApi sourceSets.commonDecompiler.output
cfrApi sourceSets.commonDecompiler.output
vineflowerApi sourceSets.commonDecompiler.output

implementation sourceSets.commonDecompiler.output
implementation sourceSets.fernflower.output
implementation sourceSets.cfr.output
implementation sourceSets.vineflower.output

// source code remapping
implementation libs.fabric.mercury
Expand Down Expand Up @@ -130,6 +194,10 @@ jar {
}

from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) }
from sourceSets.commonDecompiler.output.classesDirs
from sourceSets.cfr.output.classesDirs
from sourceSets.fernflower.output.classesDirs
from sourceSets.vineflower.output.classesDirs
}

base {
Expand Down Expand Up @@ -222,6 +290,8 @@ test {
}
}


import org.gradle.api.internal.artifacts.configurations.ConfigurationRoles
import org.gradle.launcher.cli.KotlinDslVersion
import org.gradle.util.GradleVersion
import org.w3c.dom.Document
Expand Down
2 changes: 2 additions & 0 deletions gradle/runtime.libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Decompilers
fernflower = "2.0.0"
cfr = "0.2.1"
vineflower = "1.9.3"

# Runtime depedencies
mixin-compile-extensions = "0.6.0"
Expand All @@ -14,6 +15,7 @@ native-support = "1.0.1"
# Decompilers
fernflower = { module = "net.fabricmc:fabric-fernflower", version.ref = "fernflower" }
cfr = { module = "net.fabricmc:cfr", version.ref = "cfr" }
vineflower = { module = "org.vineflower:vineflower", version.ref = "vineflower" }

# Runtime depedencies
mixin-compile-extensions = { module = "net.fabricmc:fabric-mixin-compile-extensions", version.ref = "mixin-compile-extensions" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/test.libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mockito = "5.4.0"
java-debug = "0.48.0"
mixin = "0.11.4+mixin.0.8.5"

gradle-nightly = "8.4-20230821223421+0000"
gradle-nightly = "8.5-20230908221250+0000"
fabric-loader = "0.14.22"
fabric-installer = "0.11.1"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import org.benf.cfr.reader.util.output.DelegatingDumper;
import org.benf.cfr.reader.util.output.Dumper;

import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.tree.MappingTree;
Expand All @@ -66,7 +65,7 @@ public Dumper wrap(Dumper d) {
private static MappingTree readMappings(Path input) {
try (BufferedReader reader = Files.newBufferedReader(input)) {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString());
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named");
MappingReader.read(reader, nsSwitch);

return mappingTree;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package net.fabricmc.loom.decompilers.cfr;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
Expand All @@ -37,23 +38,18 @@
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;

import com.google.common.base.Charsets;
import org.benf.cfr.reader.api.OutputSinkFactory;
import org.benf.cfr.reader.api.SinkReturns;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.fabricmc.loom.util.IOStringConsumer;
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;

public class CFRSinkFactory implements OutputSinkFactory {
private static final Logger ERROR_LOGGER = LoggerFactory.getLogger(CFRSinkFactory.class);

private final JarOutputStream outputStream;
private final IOStringConsumer logger;
private final LoomInternalDecompiler.Logger logger;
private final Set<String> addedDirectories = new HashSet<>();
private final Map<String, Map<Integer, Integer>> lineMap = new TreeMap<>();

public CFRSinkFactory(JarOutputStream outputStream, IOStringConsumer logger) {
public CFRSinkFactory(JarOutputStream outputStream, LoomInternalDecompiler.Logger logger) {
this.outputStream = outputStream;
this.logger = logger;
}
Expand All @@ -72,7 +68,7 @@ public <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {
return switch (sinkType) {
case JAVA -> (Sink<T>) decompiledSink();
case LINENUMBER -> (Sink<T>) lineNumberMappingSink();
case EXCEPTION -> (e) -> ERROR_LOGGER.error((String) e);
case EXCEPTION -> (e) -> logger.error((String) e);
default -> null;
};
}
Expand All @@ -83,7 +79,7 @@ private Sink<SinkReturns.Decompiled> decompiledSink() {
if (!filename.isEmpty()) filename += "/";
filename += sinkable.getClassName() + ".java";

byte[] data = sinkable.getJava().getBytes(Charsets.UTF_8);
byte[] data = sinkable.getJava().getBytes(StandardCharsets.UTF_8);

writeToJar(filename, data);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,45 +45,46 @@
import org.benf.cfr.reader.util.getopt.OptionsImpl;
import org.benf.cfr.reader.util.output.SinkDumperFactory;

import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;

public final class LoomCFRDecompiler implements LoomDecompiler {
public final class LoomCFRDecompiler implements LoomInternalDecompiler {
private static final Map<String, String> DECOMPILE_OPTIONS = Map.of(
"renameillegalidents", "true",
"trackbytecodeloc", "true",
"comments", "false"
);

@Override
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
public void decompile(LoomInternalDecompiler.Context context) {
Path compiledJar = context.compiledJar();

final String path = compiledJar.toAbsolutePath().toString();
final Map<String, String> allOptions = new HashMap<>(DECOMPILE_OPTIONS);
allOptions.putAll(metaData.options());
allOptions.putAll(context.options());

final Options options = OptionsImpl.getFactory().create(allOptions);

ClassFileSourceImpl classFileSource = new ClassFileSourceImpl(options);

for (Path library : metaData.libraries()) {
for (Path library : context.libraries()) {
classFileSource.addJarContent(library.toAbsolutePath().toString(), AnalysisType.JAR);
}

classFileSource.informAnalysisRelativePathDetail(null, null);

DCCommonState state = new DCCommonState(options, classFileSource);

if (metaData.javaDocs() != null) {
state = new DCCommonState(state, new CFRObfuscationMapping(metaData.javaDocs()));
if (context.javaDocs() != null) {
state = new DCCommonState(state, new CFRObfuscationMapping(context.javaDocs()));
}

final Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");

Map<String, Map<Integer, Integer>> lineMap;

try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(sourcesDestination), manifest)) {
CFRSinkFactory cfrSinkFactory = new CFRSinkFactory(outputStream, metaData.logger());
try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(context.sourcesDestination()), manifest)) {
CFRSinkFactory cfrSinkFactory = new CFRSinkFactory(outputStream, context.logger());
SinkDumperFactory dumperFactory = new SinkDumperFactory(cfrSinkFactory, options);

Driver.doJar(state, path, AnalysisType.JAR, dumperFactory);
Expand All @@ -93,7 +94,7 @@ public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDes
throw new UncheckedIOException("Failed to decompile", e);
}

writeLineMap(linemapDestination, lineMap);
writeLineMap(context.linemapDestination(), lineMap);
}

private void writeLineMap(Path output, Map<String, Map<Integer, Integer>> lineMap) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2022 FabricMC
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -22,23 +22,40 @@
* SOFTWARE.
*/

package net.fabricmc.loom.decompilers.fernflower;
package net.fabricmc.loom.decompilers;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;

import org.jetbrains.java.decompiler.util.InterpreterUtil;
// This is an internal interface to loom, DO NOT USE this in your own plugins.
public interface LoomInternalDecompiler {
void decompile(Context context);

import net.fabricmc.loom.util.ZipUtils;
interface Context {
Path compiledJar();

public class FernFlowerUtils {
public static byte[] getBytecode(String externalPath, String internalPath) throws IOException {
File file = new File(externalPath);
Path sourcesDestination();

if (internalPath == null) {
return InterpreterUtil.getBytes(file);
} else {
return ZipUtils.unpack(file.toPath(), internalPath);
}
Path linemapDestination();

int numberOfThreads();

Path javaDocs();

Collection<Path> libraries();

Logger logger();

Map<String, String> options();

byte[] unpackZip(Path zip, String path) throws IOException;
}

interface Logger {
void accept(String data) throws IOException;

void error(String msg);
}
}
Loading

0 comments on commit 71b7bea

Please sign in to comment.