mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Support the Vineflower decompiler (#951)
This commit is contained in:
76
build.gradle
76
build.gradle
@@ -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"
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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" }
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.benf.cfr.reader.mapping.NullMapping;
|
||||
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;
|
||||
@@ -66,7 +65,7 @@ public class CFRObfuscationMapping extends NullMapping {
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -37,23 +38,18 @@ import java.util.TreeMap;
|
||||
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;
|
||||
}
|
||||
@@ -72,7 +68,7 @@ public class CFRSinkFactory implements OutputSinkFactory {
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -83,7 +79,7 @@ public class CFRSinkFactory implements OutputSinkFactory {
|
||||
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);
|
||||
};
|
||||
@@ -45,10 +45,9 @@ import org.benf.cfr.reader.util.getopt.Options;
|
||||
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",
|
||||
@@ -56,16 +55,18 @@ public final class LoomCFRDecompiler implements LoomDecompiler {
|
||||
);
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -73,8 +74,8 @@ public final class LoomCFRDecompiler implements LoomDecompiler {
|
||||
|
||||
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();
|
||||
@@ -82,8 +83,8 @@ public final class LoomCFRDecompiler implements LoomDecompiler {
|
||||
|
||||
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);
|
||||
@@ -93,7 +94,7 @@ public final class LoomCFRDecompiler implements LoomDecompiler {
|
||||
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) {
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2019-2021 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.decompilers.fernflower;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.Fernflower;
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
|
||||
import org.jetbrains.java.decompiler.util.InterpreterUtil;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
|
||||
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
|
||||
|
||||
public final class FabricFernFlowerDecompiler implements LoomInternalDecompiler {
|
||||
@Override
|
||||
public void decompile(LoomInternalDecompiler.Context context) {
|
||||
Path sourcesDestination = context.sourcesDestination();
|
||||
Path linemapDestination = context.linemapDestination();
|
||||
|
||||
final Map<String, Object> options = new HashMap<>(
|
||||
Map.of(
|
||||
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1",
|
||||
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1",
|
||||
IFernflowerPreferences.REMOVE_SYNTHETIC, "1",
|
||||
IFernflowerPreferences.LOG_LEVEL, "trace",
|
||||
IFernflowerPreferences.THREADS, String.valueOf(context.numberOfThreads()),
|
||||
IFernflowerPreferences.INDENT_STRING, "\t",
|
||||
IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(context.javaDocs().toFile())
|
||||
)
|
||||
);
|
||||
|
||||
options.putAll(context.options());
|
||||
|
||||
IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile);
|
||||
Fernflower ff = new Fernflower((externalPath, internalPath) -> FabricFernFlowerDecompiler.this.getBytecode(externalPath, internalPath, context), saver, options, new FernflowerLogger(context.logger()));
|
||||
|
||||
for (Path library : context.libraries()) {
|
||||
ff.addLibrary(library.toFile());
|
||||
}
|
||||
|
||||
ff.addSource(context.compiledJar().toFile());
|
||||
|
||||
try {
|
||||
ff.decompileContext();
|
||||
} finally {
|
||||
ff.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getBytecode(String externalPath, String internalPath, LoomInternalDecompiler.Context context) throws IOException {
|
||||
File file = new File(externalPath);
|
||||
|
||||
if (internalPath == null) {
|
||||
return InterpreterUtil.getBytes(file);
|
||||
} else {
|
||||
return context.unpackZip(file.toPath(), internalPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,12 @@ import java.io.IOException;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
|
||||
|
||||
import net.fabricmc.loom.util.IOStringConsumer;
|
||||
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
|
||||
|
||||
public class FernflowerLogger extends IFernflowerLogger {
|
||||
private final IOStringConsumer logger;
|
||||
private final LoomInternalDecompiler.Logger logger;
|
||||
|
||||
public FernflowerLogger(IOStringConsumer logger) {
|
||||
public FernflowerLogger(LoomInternalDecompiler.Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -35,16 +35,17 @@ import org.jetbrains.java.decompiler.struct.StructClass;
|
||||
import org.jetbrains.java.decompiler.struct.StructField;
|
||||
import org.jetbrains.java.decompiler.struct.StructMethod;
|
||||
import org.jetbrains.java.decompiler.struct.StructRecordComponent;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
|
||||
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;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
public class TinyJavadocProvider implements IFabricJavadocProvider {
|
||||
private static final int ACC_STATIC = 0x0008;
|
||||
private static final int ACC_RECORD = 0x10000;
|
||||
|
||||
private final MappingTree mappingTree;
|
||||
|
||||
public TinyJavadocProvider(File tinyFile) {
|
||||
@@ -93,7 +94,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider {
|
||||
addedParam = true;
|
||||
}
|
||||
|
||||
parts.add(String.format("@param %s %s", fieldMapping.getName(MappingsNamespace.NAMED.toString()), comment));
|
||||
parts.add(String.format("@param %s %s", fieldMapping.getName("named"), comment));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider {
|
||||
addedParam = true;
|
||||
}
|
||||
|
||||
parts.add(String.format("@param %s %s", argMapping.getName(MappingsNamespace.NAMED.toString()), comment));
|
||||
parts.add(String.format("@param %s %s", argMapping.getName("named"), comment));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +169,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider {
|
||||
private static MappingTree readMappings(File input) {
|
||||
try (BufferedReader reader = Files.newBufferedReader(input.toPath())) {
|
||||
MemoryMappingTree mappingTree = new MemoryMappingTree();
|
||||
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString());
|
||||
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named");
|
||||
MappingReader.read(reader, nsSwitch);
|
||||
|
||||
return mappingTree;
|
||||
@@ -178,10 +179,10 @@ public class TinyJavadocProvider implements IFabricJavadocProvider {
|
||||
}
|
||||
|
||||
public static boolean isRecord(StructClass structClass) {
|
||||
return (structClass.getAccessFlags() & Opcodes.ACC_RECORD) != 0;
|
||||
return (structClass.getAccessFlags() & ACC_RECORD) != 0;
|
||||
}
|
||||
|
||||
public static boolean isStatic(StructField structField) {
|
||||
return (structField.getAccessFlags() & Opcodes.ACC_STATIC) != 0;
|
||||
return (structField.getAccessFlags() & ACC_STATIC) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2019-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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.decompilers.vineflower;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.DecompilerContext;
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
|
||||
|
||||
public class ThreadSafeResultSaver implements IResultSaver {
|
||||
private final Supplier<File> output;
|
||||
private final Supplier<File> lineMapFile;
|
||||
|
||||
public Map<String, ZipOutputStream> outputStreams = new HashMap<>();
|
||||
public Map<String, ExecutorService> saveExecutors = new HashMap<>();
|
||||
public PrintWriter lineMapWriter;
|
||||
|
||||
public ThreadSafeResultSaver(Supplier<File> output, Supplier<File> lineMapFile) {
|
||||
this.output = output;
|
||||
this.lineMapFile = lineMapFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createArchive(String path, String archiveName, Manifest manifest) {
|
||||
String key = path + "/" + archiveName;
|
||||
File file = output.get();
|
||||
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
ZipOutputStream zos = manifest == null ? new ZipOutputStream(fos) : new JarOutputStream(fos, manifest);
|
||||
outputStreams.put(key, zos);
|
||||
saveExecutors.put(key, Executors.newSingleThreadExecutor());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to create archive: " + file, e);
|
||||
}
|
||||
|
||||
if (lineMapFile.get() != null) {
|
||||
try {
|
||||
lineMapWriter = new PrintWriter(new FileWriter(lineMapFile.get()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to create line mapping file: " + lineMapFile.get(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) {
|
||||
this.saveClassEntry(path, archiveName, qualifiedName, entryName, content, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content, int[] mapping) {
|
||||
String key = path + "/" + archiveName;
|
||||
ExecutorService executor = saveExecutors.get(key);
|
||||
executor.submit(() -> {
|
||||
ZipOutputStream zos = outputStreams.get(key);
|
||||
|
||||
try {
|
||||
zos.putNextEntry(new ZipEntry(entryName));
|
||||
|
||||
if (content != null) {
|
||||
zos.write(content.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
DecompilerContext.getLogger().writeMessage("Cannot write entry " + entryName, e);
|
||||
}
|
||||
|
||||
if (mapping != null && lineMapWriter != null) {
|
||||
int maxLine = 0;
|
||||
int maxLineDest = 0;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < mapping.length; i += 2) {
|
||||
maxLine = Math.max(maxLine, mapping[i]);
|
||||
maxLineDest = Math.max(maxLineDest, mapping[i + 1]);
|
||||
builder.append("\t").append(mapping[i]).append("\t").append(mapping[i + 1]).append("\n");
|
||||
}
|
||||
|
||||
lineMapWriter.println(qualifiedName + "\t" + maxLine + "\t" + maxLineDest);
|
||||
lineMapWriter.println(builder.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeArchive(String path, String archiveName) {
|
||||
String key = path + "/" + archiveName;
|
||||
ExecutorService executor = saveExecutors.get(key);
|
||||
Future<?> closeFuture = executor.submit(() -> {
|
||||
ZipOutputStream zos = outputStreams.get(key);
|
||||
|
||||
try {
|
||||
zos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to close zip. " + key, e);
|
||||
}
|
||||
});
|
||||
executor.shutdown();
|
||||
|
||||
try {
|
||||
closeFuture.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
outputStreams.remove(key);
|
||||
saveExecutors.remove(key);
|
||||
|
||||
if (lineMapWriter != null) {
|
||||
lineMapWriter.flush();
|
||||
lineMapWriter.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveFolder(String path) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFile(String source, String path, String entryName) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveDirEntry(String path, String archiveName, String entryName) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyEntry(String source, String path, String archiveName, String entry) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2019-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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.decompilers.vineflower;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.java.decompiler.struct.StructClass;
|
||||
import org.jetbrains.java.decompiler.struct.StructField;
|
||||
import org.jetbrains.java.decompiler.struct.StructMethod;
|
||||
import org.jetbrains.java.decompiler.struct.StructRecordComponent;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
|
||||
import net.fabricmc.mappingio.MappingReader;
|
||||
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
|
||||
import net.fabricmc.mappingio.tree.MappingTree;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
public class TinyJavadocProvider implements IFabricJavadocProvider {
|
||||
private static final int ACC_STATIC = 0x0008;
|
||||
private static final int ACC_RECORD = 0x10000;
|
||||
|
||||
private final MappingTree mappingTree;
|
||||
|
||||
public TinyJavadocProvider(File tinyFile) {
|
||||
mappingTree = readMappings(tinyFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassDoc(StructClass structClass) {
|
||||
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
|
||||
|
||||
if (classMapping == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isRecord(structClass)) {
|
||||
return classMapping.getComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the record component docs here.
|
||||
*
|
||||
* Record components are mapped via the field name, thus take the docs from the fields and display them on then class.
|
||||
*/
|
||||
List<String> parts = new ArrayList<>();
|
||||
|
||||
if (classMapping.getComment() != null) {
|
||||
parts.add(classMapping.getComment());
|
||||
}
|
||||
|
||||
boolean addedParam = false;
|
||||
|
||||
for (StructRecordComponent component : structClass.getRecordComponents()) {
|
||||
// The component will always match the field name and descriptor
|
||||
MappingTree.FieldMapping fieldMapping = classMapping.getField(component.getName(), component.getDescriptor());
|
||||
|
||||
if (fieldMapping == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String comment = fieldMapping.getComment();
|
||||
|
||||
if (comment != null) {
|
||||
if (!addedParam && classMapping.getComment() != null) {
|
||||
//Add a blank line before components when the class has a comment
|
||||
parts.add("");
|
||||
addedParam = true;
|
||||
}
|
||||
|
||||
parts.add(String.format("@param %s %s", fieldMapping.getName("named"), comment));
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.join("\n", parts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFieldDoc(StructClass structClass, StructField structField) {
|
||||
// None static fields in records are handled in the class javadoc.
|
||||
if (isRecord(structClass) && !isStatic(structField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
|
||||
|
||||
if (classMapping == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MappingTree.FieldMapping fieldMapping = classMapping.getField(structField.getName(), structField.getDescriptor());
|
||||
|
||||
return fieldMapping != null ? fieldMapping.getComment() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodDoc(StructClass structClass, StructMethod structMethod) {
|
||||
MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
|
||||
|
||||
if (classMapping == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MappingTree.MethodMapping methodMapping = classMapping.getMethod(structMethod.getName(), structMethod.getDescriptor());
|
||||
|
||||
if (methodMapping != null) {
|
||||
List<String> parts = new ArrayList<>();
|
||||
|
||||
if (methodMapping.getComment() != null) {
|
||||
parts.add(methodMapping.getComment());
|
||||
}
|
||||
|
||||
boolean addedParam = false;
|
||||
|
||||
for (MappingTree.MethodArgMapping argMapping : methodMapping.getArgs()) {
|
||||
String comment = argMapping.getComment();
|
||||
|
||||
if (comment != null) {
|
||||
if (!addedParam && methodMapping.getComment() != null) {
|
||||
//Add a blank line before params when the method has a comment
|
||||
parts.add("");
|
||||
addedParam = true;
|
||||
}
|
||||
|
||||
parts.add(String.format("@param %s %s", argMapping.getName("named"), comment));
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.join("\n", parts);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MappingTree readMappings(File input) {
|
||||
try (BufferedReader reader = Files.newBufferedReader(input.toPath())) {
|
||||
MemoryMappingTree mappingTree = new MemoryMappingTree();
|
||||
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named");
|
||||
MappingReader.read(reader, nsSwitch);
|
||||
|
||||
return mappingTree;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to read mappings", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isRecord(StructClass structClass) {
|
||||
return (structClass.getAccessFlags() & ACC_RECORD) != 0;
|
||||
}
|
||||
|
||||
public static boolean isStatic(StructField structField) {
|
||||
return (structField.getAccessFlags() & ACC_STATIC) != 0;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2019-2021 FabricMC
|
||||
* Copyright (c) 2019-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
|
||||
@@ -22,7 +22,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.decompilers.fernflower;
|
||||
package net.fabricmc.loom.decompilers.vineflower;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
@@ -33,34 +33,36 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
|
||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
|
||||
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
|
||||
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
|
||||
|
||||
public final class FabricFernFlowerDecompiler implements LoomDecompiler {
|
||||
public final class VineflowerDecompiler implements LoomInternalDecompiler {
|
||||
@Override
|
||||
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
||||
public void decompile(Context context) {
|
||||
Path sourcesDestination = context.sourcesDestination();
|
||||
Path linemapDestination = context.linemapDestination();
|
||||
|
||||
final Map<String, Object> options = new HashMap<>(
|
||||
Map.of(
|
||||
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1",
|
||||
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1",
|
||||
IFernflowerPreferences.REMOVE_SYNTHETIC, "1",
|
||||
IFernflowerPreferences.LOG_LEVEL, "trace",
|
||||
IFernflowerPreferences.THREADS, String.valueOf(metaData.numberOfThreads()),
|
||||
IFernflowerPreferences.THREADS, String.valueOf(context.numberOfThreads()),
|
||||
IFernflowerPreferences.INDENT_STRING, "\t",
|
||||
IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(metaData.javaDocs().toFile())
|
||||
IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(context.javaDocs().toFile())
|
||||
)
|
||||
);
|
||||
|
||||
options.putAll(metaData.options());
|
||||
options.putAll(context.options());
|
||||
|
||||
IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile);
|
||||
Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, new FernflowerLogger(metaData.logger()));
|
||||
Fernflower ff = new Fernflower(saver, options, new VineflowerLogger(context.logger()));
|
||||
|
||||
for (Path library : metaData.libraries()) {
|
||||
for (Path library : context.libraries()) {
|
||||
ff.addLibrary(library.toFile());
|
||||
}
|
||||
|
||||
ff.addSource(compiledJar.toFile());
|
||||
ff.addSource(context.compiledJar().toFile());
|
||||
|
||||
try {
|
||||
ff.decompileContext();
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2021-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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.decompilers.vineflower;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
|
||||
|
||||
import net.fabricmc.loom.decompilers.LoomInternalDecompiler;
|
||||
|
||||
public class VineflowerLogger extends IFernflowerLogger {
|
||||
private final LoomInternalDecompiler.Logger logger;
|
||||
|
||||
public VineflowerLogger(LoomInternalDecompiler.Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMessage(String message, Severity severity) {
|
||||
if (severity.ordinal() < Severity.ERROR.ordinal()) return;
|
||||
|
||||
System.err.println(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMessage(String message, Severity severity, Throwable t) {
|
||||
if (severity.ordinal() < Severity.ERROR.ordinal()) return;
|
||||
|
||||
writeMessage(message, severity);
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
private void write(String data) {
|
||||
try {
|
||||
logger.accept(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to log", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startReadingClass(String className) {
|
||||
write("Decompiling " + className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startClass(String className) {
|
||||
write("Decompiling " + className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startWriteClass(String className) {
|
||||
// Nope
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMethod(String methodName) {
|
||||
// Nope
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endMethod() {
|
||||
// Nope
|
||||
}
|
||||
}
|
||||
@@ -24,17 +24,27 @@
|
||||
|
||||
package net.fabricmc.loom.decompilers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.NamedDomainObjectProvider;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
|
||||
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
|
||||
import net.fabricmc.loom.decompilers.cfr.LoomCFRDecompiler;
|
||||
import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler;
|
||||
import net.fabricmc.loom.decompilers.vineflower.VineflowerDecompiler;
|
||||
import net.fabricmc.loom.util.LoomVersions;
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
|
||||
public abstract class DecompilerConfiguration implements Runnable {
|
||||
@Inject
|
||||
@@ -44,9 +54,11 @@ public abstract class DecompilerConfiguration implements Runnable {
|
||||
public void run() {
|
||||
var fernflowerConfiguration = createConfiguration("fernflower", LoomVersions.FERNFLOWER);
|
||||
var cfrConfiguration = createConfiguration("cfr", LoomVersions.CFR);
|
||||
var vineflowerConfiguration = createConfiguration("vineflower", LoomVersions.VINEFLOWER);
|
||||
|
||||
registerDecompiler(getProject(), "fernFlower", FabricFernFlowerDecompiler.class, fernflowerConfiguration);
|
||||
registerDecompiler(getProject(), "cfr", LoomCFRDecompiler.class, cfrConfiguration);
|
||||
registerDecompiler(getProject(), "fernFlower", BuiltinFernflower.class, fernflowerConfiguration);
|
||||
registerDecompiler(getProject(), "cfr", BuiltinCfr.class, cfrConfiguration);
|
||||
registerDecompiler(getProject(), "vineflower", BuiltinVineflower.class, vineflowerConfiguration);
|
||||
}
|
||||
|
||||
private NamedDomainObjectProvider<Configuration> createConfiguration(String name, LoomVersions version) {
|
||||
@@ -62,4 +74,96 @@ public abstract class DecompilerConfiguration implements Runnable {
|
||||
options.getClasspath().from(configuration);
|
||||
});
|
||||
}
|
||||
|
||||
// We need to wrap the internal API with the public API.
|
||||
// This is needed as the sourceset containing fabric's decompilers do not have access to loom classes.
|
||||
private abstract static sealed class BuiltinDecompiler implements LoomDecompiler permits BuiltinFernflower, BuiltinCfr, BuiltinVineflower {
|
||||
private final LoomInternalDecompiler internalDecompiler;
|
||||
|
||||
BuiltinDecompiler(LoomInternalDecompiler internalDecompiler) {
|
||||
this.internalDecompiler = internalDecompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
||||
final Logger slf4jLogger = LoggerFactory.getLogger(internalDecompiler.getClass());
|
||||
|
||||
final var logger = new LoomInternalDecompiler.Logger() {
|
||||
@Override
|
||||
public void accept(String data) throws IOException {
|
||||
metaData.logger().accept(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg) {
|
||||
slf4jLogger.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
internalDecompiler.decompile(new LoomInternalDecompiler.Context() {
|
||||
@Override
|
||||
public Path compiledJar() {
|
||||
return compiledJar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path sourcesDestination() {
|
||||
return sourcesDestination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path linemapDestination() {
|
||||
return linemapDestination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int numberOfThreads() {
|
||||
return metaData.numberOfThreads();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path javaDocs() {
|
||||
return metaData.javaDocs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Path> libraries() {
|
||||
return metaData.libraries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoomInternalDecompiler.Logger logger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> options() {
|
||||
return metaData.options();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] unpackZip(Path zip, String path) throws IOException {
|
||||
return ZipUtils.unpack(zip, path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BuiltinFernflower extends BuiltinDecompiler {
|
||||
public BuiltinFernflower() {
|
||||
super(new FabricFernFlowerDecompiler());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BuiltinCfr extends BuiltinDecompiler {
|
||||
public BuiltinCfr() {
|
||||
super(new LoomCFRDecompiler());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BuiltinVineflower extends BuiltinDecompiler {
|
||||
public BuiltinVineflower() {
|
||||
super(new VineflowerDecompiler());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class DecompileTest extends Specification implements GradleProjectTestTrait {
|
||||
decompiler | task | version
|
||||
'fernflower' | "genSourcesWithFernFlower" | PRE_RELEASE_GRADLE
|
||||
'cfr' | "genSourcesWithCfr" | PRE_RELEASE_GRADLE
|
||||
'vineflower' | "genSourcesWithVineflower" | PRE_RELEASE_GRADLE
|
||||
}
|
||||
|
||||
@Unroll
|
||||
|
||||
Reference in New Issue
Block a user