diff --git a/build.gradle b/build.gradle index af978ba29..e81b4f682 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,8 @@ subprojects { google() maven { url 'https://maven.quiltmc.org/repository/release/' } maven { url 'https://jitpack.io' } + maven { url 'https://www.jetbrains.com/intellij-repository/releases' } + maven { url 'https://maven.minecraftforge.net' } } // ======================= DEPENDENCIES ======================== diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a9e912771..fdea135fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,6 +37,9 @@ regex = "0.1.16" richtextfx = "0.11.3" treemapfx = "1.1.0" vineflower = "1.10.1" +fernflower = "242.23726.103" +forgeflower = "2.0.674.2" +annotations = "24.0.0" wordwrap = "0.1.12" benmanes-versions = "0.42.0" gradle-coverage-report-aggregator = "1.3.0" @@ -128,6 +131,12 @@ treemapfx = { module = "software.coley:treemap-fx", version.ref = "treemapfx" } vineflower = { module = "org.vineflower:vineflower", version.ref = "vineflower" } +fernflower = { module = "com.jetbrains.intellij.java:java-decompiler-engine", version.ref = "fernflower" } + +forgeflower = { module = "net.minecraftforge:forgeflower", version.ref = "forgeflower" } + +annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } + wordwrap = { module = "com.github.davidmoten:word-wrap", version.ref = "wordwrap" } [bundles] diff --git a/recaf-core/build.gradle b/recaf-core/build.gradle index 9d03caec7..91c691426 100644 --- a/recaf-core/build.gradle +++ b/recaf-core/build.gradle @@ -38,7 +38,8 @@ dependencies { api(libs.openrewrite) api(libs.regex) api(libs.bundles.jasm) - api(libs.vineflower) +// api(libs.vineflower) + api( project(':recaf-relocation')) api(libs.wordwrap) } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/FakeFile.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/FakeFile.java new file mode 100644 index 000000000..9d7a3c0b8 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/FakeFile.java @@ -0,0 +1,29 @@ +package software.coley.recaf.services.decompile; + +import java.io.File; + +/** + * FakeFile + * + * @author meiMingle + */ +public class FakeFile extends File { + + String absolutePath; + + + public FakeFile(String path) { + super(path); + this.absolutePath = path; + } + + @Override + public boolean isDirectory() { + return false; + } + + @Override + public String getAbsolutePath() { + return absolutePath; + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerConfig.java new file mode 100644 index 000000000..607d6b5a2 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerConfig.java @@ -0,0 +1,26 @@ +package software.coley.recaf.services.decompile.fernflower; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.slf4j.event.Level; +import software.coley.observables.ObservableObject; +import software.coley.recaf.services.decompile.BaseDecompilerConfig; + +/** + * FernflowerConfig + * + * @author meiMingle + */ +@ApplicationScoped +public class FernflowerConfig extends BaseDecompilerConfig { + + @Inject + public FernflowerConfig() { + super("decompiler-fernflower" + CONFIG_SUFFIX); + registerConfigValuesHashUpdates(); + } + + public ObservableObject getLoggingLevel() { + return new ObservableObject<>(Level.WARN); + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerDecompiler.java new file mode 100644 index 000000000..894b5df85 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerDecompiler.java @@ -0,0 +1,94 @@ +package software.coley.recaf.services.decompile.fernflower; + +import jakarta.annotation.Nonnull; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import recaf.relocation.libs.fernflower.org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler; +import recaf.relocation.libs.fernflower.org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import software.coley.recaf.info.InnerClassInfo; +import software.coley.recaf.info.JvmClassInfo; +import software.coley.recaf.services.decompile.AbstractJvmDecompiler; +import software.coley.recaf.services.decompile.DecompileResult; +import software.coley.recaf.services.decompile.FakeFile; +import software.coley.recaf.workspace.model.Workspace; +import software.coley.recaf.workspace.model.resource.BasicWorkspaceFileResource; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Fernflower decompiler implementation. + * + * @author meiMingle + */ +@ApplicationScoped +public class FernflowerDecompiler extends AbstractJvmDecompiler { + public static final String NAME = "Fernflower"; + private final FernflowerConfig config; + private final IFernflowerLogger logger; + + /** + * New Fernflower decompiler instance. + * + * @param config Decompiler configuration. + */ + @Inject + public FernflowerDecompiler(@Nonnull FernflowerConfig config) { + // Change this version to be dynamic when / if the Fernflower authors make a function that returns the version... + super(NAME, "242.23726.103", config); + this.config = config; + logger = new FernflowerLogger(config); + } + + @Nonnull + @Override + public DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo info) { + + + Map options = new HashMap<>(); + + options.put("hdc", "0"); + options.put("dgs", "1"); + options.put("rsy", "1"); + options.put("rbr", "1"); + options.put("nls", "1"); + options.put("ban", "//Recreated by Recaf (powered by FernFlower decompiler)\n\n"); + options.put("mpm", 60); + options.put("ind", " "); + options.put("iib", "1"); + options.put("vac", "1"); + options.put("cps", "1"); + options.put("crp", "1"); + + options.put("bsm", "1");// "decompiler.use.line.mapping" + options.put("__dump_original_lines__", "1");// "decompiler.dump.original.lines" + + MyResultSaver saver = new MyResultSaver(); + MyBytecodeProvider provider = new MyBytecodeProvider(workspace); + + BaseDecompiler decompiler = new BaseDecompiler( + provider, + saver, + options, + logger + ); + + try { + String path = ((BasicWorkspaceFileResource) workspace.getPrimaryResource()).getFileInfo().getName() + "!" + info.getName() + ".class"; + decompiler.addSource(new FakeFile(path)); + List innerClasses = info.getInnerClasses(); + innerClasses.forEach(inner -> decompiler.addSource(new FakeFile(((BasicWorkspaceFileResource) workspace.getPrimaryResource()).getFileInfo().getName() + "!" + inner.getName() + ".class"))); + decompiler.decompileContext(); + if (saver.getResult() == null || saver.getResult().isEmpty()) { + return new DecompileResult(new IllegalStateException("Missing decompilation output"), 0); + } + + return new DecompileResult(saver.getResult(), 0); + } catch (Exception e) { + return new DecompileResult(e, 0); + } + } + + +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerLogger.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerLogger.java new file mode 100644 index 000000000..9bfcb6c35 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/FernflowerLogger.java @@ -0,0 +1,44 @@ +package software.coley.recaf.services.decompile.fernflower; + +import jakarta.annotation.Nonnull; +import org.slf4j.Logger; +import org.slf4j.event.Level; +import recaf.relocation.libs.fernflower.org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import software.coley.observables.ObservableObject; +import software.coley.recaf.analytics.logging.Logging; + +/** + * Logger for Fernflower + * + * @author meiMingle + */ +public class FernflowerLogger extends IFernflowerLogger { + private static final Logger logger = Logging.get(FernflowerLogger.class); + private static final String VF_PREFIX = "FF: "; + private final ObservableObject level; + + public FernflowerLogger(@Nonnull FernflowerConfig config) { + this.level = config.getLoggingLevel(); + } + + @Override + public void writeMessage(String message, Severity severity) { + switch (severity) { + case TRACE -> { + if (level.getValue().compareTo(Level.TRACE) >= 0) logger.trace(VF_PREFIX + message); + } + case INFO -> { + if (level.getValue().compareTo(Level.INFO) >= 0) logger.info(VF_PREFIX + message); + } + case WARN -> { + if (level.getValue().compareTo(Level.WARN) >= 0) logger.warn(VF_PREFIX + message); + } + case ERROR -> logger.error(VF_PREFIX + message); + } + } + + @Override + public void writeMessage(String message, Severity severity, Throwable throwable) { + logger.error(VF_PREFIX + message, throwable); + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/MyBytecodeProvider.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/MyBytecodeProvider.java new file mode 100644 index 000000000..0c5a87f06 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/MyBytecodeProvider.java @@ -0,0 +1,29 @@ +package software.coley.recaf.services.decompile.fernflower; + +import recaf.relocation.libs.fernflower.org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import software.coley.recaf.path.ClassPathNode; +import software.coley.recaf.workspace.model.Workspace; + + +/** + * MyBytecodeProvider + * + * @author meiMingle + */ +public class MyBytecodeProvider implements IBytecodeProvider { + Workspace workspace; + + public MyBytecodeProvider(Workspace workspace) { + this.workspace = workspace; + } + + @Override + public byte[] getBytecode(String absolutePath, String internalPath) { + String[] split = absolutePath.split("!"); + String path = split[0]; + String name = split[1]; + + ClassPathNode aClass = workspace.findClass(name.substring(0, name.lastIndexOf('.'))); + return aClass.getValue().asJvmClass().getBytecode(); + } +} \ No newline at end of file diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/MyResultSaver.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/MyResultSaver.java new file mode 100644 index 000000000..9dc2a238d --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/fernflower/MyResultSaver.java @@ -0,0 +1,75 @@ +package software.coley.recaf.services.decompile.fernflower; + + +import recaf.relocation.libs.fernflower.org.jetbrains.java.decompiler.main.extern.IResultSaver; + +import java.util.jar.Manifest; + +/** + * MyResultSaver + * + * @author meiMingle + */ +public class MyResultSaver implements IResultSaver { + private String result = ""; + private int[] mapping; + + public final String getResult() { + return this.result; + } + + public final void setResult(String string) { + this.result = string; + } + + public final int[] getMapping() { + return this.mapping; + } + + public final void setMapping(int[] nArray) { + this.mapping = nArray; + } + + @Override + public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) { + if (((CharSequence) this.result).length() == 0) { + this.result = content; + this.mapping = mapping; + } + } + + @Override + public void saveFolder(String path) { + // no-op + } + + @Override + public void copyFile(String source, String path, String entryName) { + // no-op + } + + @Override + public void createArchive(String path, String archiveName, Manifest manifest) { + // no-op + } + + @Override + public void saveDirEntry(String s, String s1, String s2) { + // no-op + } + + @Override + public void copyEntry(String s, String s1, String s2, String s3) { + // no-op + } + + @Override + public void saveClassEntry(String s, String s1, String s2, String s3, String s4) { + // no-op + } + + @Override + public void closeArchive(String s, String s1) { + // no-op + } +} \ No newline at end of file diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerConfig.java new file mode 100644 index 000000000..57a651915 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerConfig.java @@ -0,0 +1,26 @@ +package software.coley.recaf.services.decompile.forgeflower; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.slf4j.event.Level; +import software.coley.observables.ObservableObject; +import software.coley.recaf.services.decompile.BaseDecompilerConfig; + +/** + * ForgeflowerConfig + * + * @author meiMingle + */ +@ApplicationScoped +public class ForgeflowerConfig extends BaseDecompilerConfig { + + @Inject + public ForgeflowerConfig() { + super("decompiler-forgeflower" + CONFIG_SUFFIX); + registerConfigValuesHashUpdates(); + } + + public ObservableObject getLoggingLevel() { + return new ObservableObject<>(Level.WARN); + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerDecompiler.java new file mode 100644 index 000000000..93f4e0f0f --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerDecompiler.java @@ -0,0 +1,94 @@ +package software.coley.recaf.services.decompile.forgeflower; + +import jakarta.annotation.Nonnull; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import recaf.relocation.libs.forgeflower.org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler; +import recaf.relocation.libs.forgeflower.org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import software.coley.recaf.info.InnerClassInfo; +import software.coley.recaf.info.JvmClassInfo; +import software.coley.recaf.services.decompile.AbstractJvmDecompiler; +import software.coley.recaf.services.decompile.DecompileResult; +import software.coley.recaf.services.decompile.FakeFile; +import software.coley.recaf.workspace.model.Workspace; +import software.coley.recaf.workspace.model.resource.BasicWorkspaceFileResource; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Forgeflower decompiler implementation. + * + * @author meiMingle + */ +@ApplicationScoped +public class ForgeflowerDecompiler extends AbstractJvmDecompiler { + public static final String NAME = "Forgeflower"; + private final ForgeflowerConfig config; + private final IFernflowerLogger logger; + + /** + * New Fernflower decompiler instance. + * + * @param config Decompiler configuration. + */ + @Inject + public ForgeflowerDecompiler(@Nonnull ForgeflowerConfig config) { + // Change this version to be dynamic when / if the Fernflower authors make a function that returns the version... + super(NAME, "2.0.674.2", config); + this.config = config; + logger = new ForgeflowerLogger(config); + } + + @Nonnull + @Override + public DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo info) { + + + Map options = new HashMap<>(); + + options.put("hdc", "0"); + options.put("dgs", "1"); + options.put("rsy", "1"); + options.put("rbr", "1"); + options.put("nls", "1"); + options.put("ban", List.of("//Recreated by Recaf (powered by ForgeFlower decompiler)\n")); + options.put("mpm", 60); + options.put("ind", " "); + options.put("iib", "1"); + options.put("vac", "1"); + options.put("cps", "1"); + options.put("crp", "1"); + + options.put("bsm", "1");// "decompiler.use.line.mapping" + options.put("__dump_original_lines__", "1");// "decompiler.dump.original.lines" + + MyResultSaver saver = new MyResultSaver(); + MyBytecodeProvider provider = new MyBytecodeProvider(workspace); + + BaseDecompiler decompiler = new BaseDecompiler( + provider, + saver, + options, + logger + ); + + try { + String path = ((BasicWorkspaceFileResource) workspace.getPrimaryResource()).getFileInfo().getName() + "!" + info.getName() + ".class"; + decompiler.addSource(new FakeFile(path)); + List innerClasses = info.getInnerClasses(); + innerClasses.forEach(inner -> decompiler.addSource(new FakeFile(((BasicWorkspaceFileResource) workspace.getPrimaryResource()).getFileInfo().getName() + "!" + inner.getName() + ".class"))); + decompiler.decompileContext(); + if (saver.getResult() == null || saver.getResult().isEmpty()) { + return new DecompileResult(new IllegalStateException("Missing decompilation output"), 0); + } + + return new DecompileResult(saver.getResult(), 0); + } catch (Exception e) { + return new DecompileResult(e, 0); + } + } + + +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerLogger.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerLogger.java new file mode 100644 index 000000000..8cd158ec0 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/ForgeflowerLogger.java @@ -0,0 +1,44 @@ +package software.coley.recaf.services.decompile.forgeflower; + +import jakarta.annotation.Nonnull; +import org.slf4j.Logger; +import org.slf4j.event.Level; +import recaf.relocation.libs.forgeflower.org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import software.coley.observables.ObservableObject; +import software.coley.recaf.analytics.logging.Logging; + +/** + * Logger for Forgeflower + * + * @author meiMingle + */ +public class ForgeflowerLogger extends IFernflowerLogger { + private static final Logger logger = Logging.get(ForgeflowerLogger.class); + private static final String VF_PREFIX = "FgF: "; + private final ObservableObject level; + + public ForgeflowerLogger(@Nonnull ForgeflowerConfig config) { + this.level = config.getLoggingLevel(); + } + + @Override + public void writeMessage(String message, Severity severity) { + switch (severity) { + case TRACE -> { + if (level.getValue().compareTo(Level.TRACE) >= 0) logger.trace(VF_PREFIX + message); + } + case INFO -> { + if (level.getValue().compareTo(Level.INFO) >= 0) logger.info(VF_PREFIX + message); + } + case WARN -> { + if (level.getValue().compareTo(Level.WARN) >= 0) logger.warn(VF_PREFIX + message); + } + case ERROR -> logger.error(VF_PREFIX + message); + } + } + + @Override + public void writeMessage(String message, Severity severity, Throwable throwable) { + logger.error(VF_PREFIX + message, throwable); + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/MyBytecodeProvider.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/MyBytecodeProvider.java new file mode 100644 index 000000000..633c7d0c8 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/MyBytecodeProvider.java @@ -0,0 +1,29 @@ +package software.coley.recaf.services.decompile.forgeflower; + +import recaf.relocation.libs.forgeflower.org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import software.coley.recaf.path.ClassPathNode; +import software.coley.recaf.workspace.model.Workspace; + + +/** + * MyBytecodeProvider + * + * @author meiMingle + */ +public class MyBytecodeProvider implements IBytecodeProvider { + Workspace workspace; + + public MyBytecodeProvider(Workspace workspace) { + this.workspace = workspace; + } + + @Override + public byte[] getBytecode(String absolutePath, String internalPath) { + String[] split = absolutePath.split("!"); + String path = split[0]; + String name = split[1]; + + ClassPathNode aClass = workspace.findClass(name.substring(0, name.lastIndexOf('.'))); + return aClass.getValue().asJvmClass().getBytecode(); + } +} \ No newline at end of file diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/MyResultSaver.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/MyResultSaver.java new file mode 100644 index 000000000..020174fce --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/forgeflower/MyResultSaver.java @@ -0,0 +1,75 @@ +package software.coley.recaf.services.decompile.forgeflower; + + +import recaf.relocation.libs.forgeflower.org.jetbrains.java.decompiler.main.extern.IResultSaver; + +import java.util.jar.Manifest; + +/** + * MyResultSaver + * + * @author meiMingle + */ +public class MyResultSaver implements IResultSaver { + private String result = ""; + private int[] mapping; + + public final String getResult() { + return this.result; + } + + public final void setResult(String string) { + this.result = string; + } + + public final int[] getMapping() { + return this.mapping; + } + + public final void setMapping(int[] nArray) { + this.mapping = nArray; + } + + @Override + public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) { + if (((CharSequence) this.result).length() == 0) { + this.result = content; + this.mapping = mapping; + } + } + + @Override + public void saveFolder(String path) { + // no-op + } + + @Override + public void copyFile(String source, String path, String entryName) { + // no-op + } + + @Override + public void createArchive(String path, String archiveName, Manifest manifest) { + // no-op + } + + @Override + public void saveDirEntry(String s, String s1, String s2) { + // no-op + } + + @Override + public void copyEntry(String s, String s1, String s2, String s3) { + // no-op + } + + @Override + public void saveClassEntry(String s, String s1, String s2, String s3, String s4) { + // no-op + } + + @Override + public void closeArchive(String s, String s1) { + // no-op + } +} \ No newline at end of file diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java index 7bc892e8e..6f0aa4597 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java @@ -1,7 +1,7 @@ package software.coley.recaf.services.decompile.vineflower; import jakarta.annotation.Nonnull; -import org.jetbrains.java.decompiler.main.extern.IContextSource; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.extern.IContextSource; import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.path.ClassPathNode; import software.coley.recaf.workspace.model.Workspace; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java index 23ceb2116..9eb876163 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java @@ -1,7 +1,7 @@ package software.coley.recaf.services.decompile.vineflower; import jakarta.annotation.Nonnull; -import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.extern.IResultSaver; import software.coley.recaf.info.InnerClassInfo; import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.workspace.model.Workspace; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java index 0ddc62cb6..0c5c2ee1e 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java @@ -1,7 +1,7 @@ package software.coley.recaf.services.decompile.vineflower; import jakarta.annotation.Nonnull; -import org.jetbrains.java.decompiler.main.extern.IContextSource; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.extern.IContextSource; import software.coley.recaf.info.JvmClassInfo; import java.io.IOException; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java index d94a89cc6..87ddd110f 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java @@ -1,6 +1,6 @@ package software.coley.recaf.services.decompile.vineflower; -import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.extern.IResultSaver; import java.util.jar.Manifest; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java index 574894221..5bc39f7dc 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java @@ -3,8 +3,8 @@ import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import org.jetbrains.java.decompiler.main.Fernflower; -import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.Fernflower; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.slf4j.event.Level; import software.coley.observables.ObservableBoolean; import software.coley.observables.ObservableObject; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java index 00461616f..aaf92e3b7 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java @@ -3,14 +3,16 @@ import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import org.jetbrains.java.decompiler.main.Fernflower; -import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; -import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.Fernflower; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.extern.IResultSaver; import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.services.decompile.AbstractJvmDecompiler; import software.coley.recaf.services.decompile.DecompileResult; import software.coley.recaf.workspace.model.Workspace; +import java.util.Map; + /** * Vineflower decompiler implementation. * @@ -40,7 +42,9 @@ public VineflowerDecompiler(@Nonnull VineflowerConfig config) { @Nonnull @Override public DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo info) { - Fernflower fernflower = new Fernflower(dummySaver, config.getFernflowerProperties(), logger); + Map customProperties = this.config.getFernflowerProperties(); + customProperties.put("banner", "//Recreated by Recaf (powered by VineFlower decompiler)\n\n"); + Fernflower fernflower = new Fernflower(dummySaver, customProperties, logger); try { ClassSource source = new ClassSource(workspace, info); diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java index 1167f117a..022602b12 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java @@ -1,7 +1,7 @@ package software.coley.recaf.services.decompile.vineflower; import jakarta.annotation.Nonnull; -import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import recaf.relocation.libs.vineflower.org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.slf4j.Logger; import org.slf4j.event.Level; import software.coley.observables.ObservableObject; diff --git a/recaf-relocation/build.gradle.kts b/recaf-relocation/build.gradle.kts new file mode 100644 index 000000000..099bc9299 --- /dev/null +++ b/recaf-relocation/build.gradle.kts @@ -0,0 +1,171 @@ +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.commons.Remapper +import java.io.BufferedReader +import java.io.ByteArrayOutputStream +import java.io.InputStreamReader +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +buildscript { + dependencies { + classpath(libs.bundles.asm) + } +} + +plugins { + id("java-library") +} + +val artifactTypeAttribute = Attribute.of("artifactType", String::class.java) +val repackagedAttribute = Attribute.of("repackaged", Boolean::class.javaObjectType) + + +val repackage: Configuration by configurations.creating { + attributes.attribute(repackagedAttribute, true) +} + +// Configure project's dependencies +abstract class MyAbsRepackager : TransformAction { + + @InputArtifact + abstract fun getInputArtifact(): Provider + + override fun transform(outputs: TransformOutputs) { + val input = getInputArtifact().get().asFile + val repackPrefix = if (input.name.contains(Regex("vineflower"))) { + "recaf/relocation/libs/vineflower/" + } else if (input.name.contains(Regex("java-decompiler-engine"))) { + "recaf/relocation/libs/fernflower/" + } else if (input.name.contains(Regex("forgeflower"))) { + "recaf/relocation/libs/forgeflower/" + } else { + "" + } + val output = outputs.file( + input.name.let { + if (it.endsWith(".jar")) + it.replaceRange(it.length - 4, it.length, "-repackaged.jar") + else + "$it-repackaged" + } + ) + println("Repackaging ${input.absolutePath} to ${output.absolutePath}") + ZipOutputStream(output.outputStream()).use { zipOut -> + relocate(zipOut, ZipFile(input), repackPrefix, null) + zipOut.flush() + } + } + + + fun relocate( + zipOut: ZipOutputStream, + zipFile: ZipFile, + repackPrefix: String, + prententriesSet: MutableSet? + ) { + zipFile.use { zipIn -> + val entriesList = zipIn.entries().toList() + val entriesSet = entriesList.mapTo(mutableSetOf()) { it.name } + if (prententriesSet != null) { + entriesSet.addAll(prententriesSet) + } + for (entry in entriesList) { + if (entry.name.endsWith("module-info.class")) { + continue + } + val newName = + if (!entry.isDirectory && entry.name.startsWith("META-INF/services/")) { + "META-INF/services/" + repackPrefix.replace( + '/', + '.' + ) + entry.name.substring(entry.name.lastIndexOf('/') + 1) + } else if (entry.name.contains("/") && !entry.name.startsWith("META-INF/")) { + repackPrefix + entry.name + } else { + entry.name + } + zipOut.putNextEntry(ZipEntry(newName)) + if (!entry.isDirectory) { + if (entry.name.endsWith(".class")) { + val writer = ClassWriter(0) + ClassReader(zipIn.getInputStream(entry)).accept( + ClassRemapper( + writer, + object : Remapper() { + override fun map(internalName: String?): String? { + if (internalName == null) return null + return if (entriesSet.contains("$internalName.class")) { + repackPrefix + internalName + } else { + internalName + } + } + } + ), + 0 + ) + zipOut.write( + writer.toByteArray() + ) + } else if (entry.name.endsWith(".jar")) { + val tempJar = File.createTempFile("recaf-relocate-", ".jar") + + // zipIn.getInputStream(entry).copyTo(fo) + tempJar.writeBytes(zipIn.getInputStream(entry).readBytes()) + ByteArrayOutputStream().use { bo -> + ZipOutputStream(bo).use { zos -> + relocate(zos, ZipFile(tempJar), repackPrefix, entriesSet) + zos.flush() + } + bo.flush() + zipOut.write(bo.toByteArray()) + } + if (tempJar.exists()) { + tempJar.delete() + } + } else if (entry.name.contains("/") && entry.name.startsWith("META-INF/services/")) { + val bufferedReader = BufferedReader(InputStreamReader(zipIn.getInputStream(entry))) + zipOut.write( + repackPrefix.replace( + '/', + '.' + ).toByteArray(Charsets.UTF_8) + ) + zipOut.write(bufferedReader.readLine().toByteArray(Charsets.UTF_8)) + } else { + zipIn.getInputStream(entry).copyTo(zipOut) + } + } + zipOut.closeEntry() + } + } + } +} + + + +dependencies { + attributesSchema { + attribute(repackagedAttribute) + // attribute(f_repackagedAttribute) + } + + artifactTypes.getByName("jar") { + attributes.attribute(repackagedAttribute, false) + // attributes.attribute(f_repackagedAttribute, false) + } + + registerTransform(MyAbsRepackager::class) { + from.attribute(repackagedAttribute, false).attribute(artifactTypeAttribute, "jar") + to.attribute(repackagedAttribute, true).attribute(artifactTypeAttribute, "jar") + } + + repackage(libs.vineflower) + repackage(libs.fernflower) + repackage(libs.forgeflower) + api(libs.annotations) + api(files(repackage.files)) +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index aa3c95e07..0ca243794 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,6 @@ rootProject.name = 'Recaf' +include 'recaf-relocation' include 'recaf-core' include 'recaf-ui'