From ed0da80e30996754679cecb9d0d918e9fed51deb Mon Sep 17 00:00:00 2001 From: Graham Date: Tue, 30 Jul 2019 15:00:30 +0100 Subject: [PATCH] Add initial Fernflower driver --- decompiler/pom.xml | 32 ++++++ .../dev/openrs2/decompiler/Decompiler.java | 73 ++++++++++++++ .../dev/openrs2/decompiler/DecompilerIo.java | 97 +++++++++++++++++++ .../decompiler/Slf4jFernflowerLogger.java | 52 ++++++++++ pom.xml | 1 + 5 files changed, 255 insertions(+) create mode 100644 decompiler/pom.xml create mode 100644 decompiler/src/main/java/dev/openrs2/decompiler/Decompiler.java create mode 100644 decompiler/src/main/java/dev/openrs2/decompiler/DecompilerIo.java create mode 100644 decompiler/src/main/java/dev/openrs2/decompiler/Slf4jFernflowerLogger.java diff --git a/decompiler/pom.xml b/decompiler/pom.xml new file mode 100644 index 00000000..2f67e781 --- /dev/null +++ b/decompiler/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + + dev.openrs2 + openrs2 + 1.0.0-SNAPSHOT + + + openrs2-decompiler + jar + + OpenRS2 Decompiler + + + + dev.openrs2 + openrs2-fernflower + ${project.version} + + + dev.openrs2 + openrs2-util + ${project.version} + + + ch.qos.logback + logback-classic + + + diff --git a/decompiler/src/main/java/dev/openrs2/decompiler/Decompiler.java b/decompiler/src/main/java/dev/openrs2/decompiler/Decompiler.java new file mode 100644 index 00000000..c04a5d89 --- /dev/null +++ b/decompiler/src/main/java/dev/openrs2/decompiler/Decompiler.java @@ -0,0 +1,73 @@ +package dev.openrs2.decompiler; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; + +public final class Decompiler implements Closeable { + private static final Map OPTIONS = Map.of( + IFernflowerPreferences.ASCII_STRING_CHARACTERS, "1", + IFernflowerPreferences.INDENT_STRING, "\t", + IFernflowerPreferences.SYNTHETIC_NOT_SET, "1" + ); + + private static Path getDestination(String archive) { + archive = archive.replaceAll("_gl[.]jar$", ""); + if (archive.equals("runescape")) { + archive = "client"; + } + return Paths.get("nonfree").resolve(archive).resolve("src/main/java"); + } + + public static void main(String[] args) throws IOException { + var libraries = List.of(Paths.get("nonfree/code/jogl.jar")); + + var deobOutput = Paths.get("nonfree/code/deob"); + var sources = List.of( + deobOutput.resolve("runescape_gl.jar"), + deobOutput.resolve("loader_gl.jar"), + deobOutput.resolve("signlink_gl.jar"), + deobOutput.resolve("unpack_gl.jar"), + deobOutput.resolve("unpacker_gl.jar") + ); + + try (var decompiler = new Decompiler(libraries, sources, Decompiler::getDestination)) { + decompiler.run(); + } + } + + private final DecompilerIo io; + private final Fernflower fernflower; + private final List libraries, sources; + + public Decompiler(List libraries, List sources, Function destination) { + this.io = new DecompilerIo(destination); + this.fernflower = new Fernflower(io, io, OPTIONS, Slf4jFernflowerLogger.INSTANCE); + this.libraries = libraries; + this.sources = sources; + } + + public void run() { + for (var library : libraries) { + fernflower.addLibrary(library.toFile()); + } + + for (var source : sources) { + fernflower.addSource(source.toFile()); + } + + fernflower.decompileContext(); + } + + @Override + public void close() throws IOException { + io.close(); + } +} diff --git a/decompiler/src/main/java/dev/openrs2/decompiler/DecompilerIo.java b/decompiler/src/main/java/dev/openrs2/decompiler/DecompilerIo.java new file mode 100644 index 00000000..69f95b67 --- /dev/null +++ b/decompiler/src/main/java/dev/openrs2/decompiler/DecompilerIo.java @@ -0,0 +1,97 @@ +package dev.openrs2.decompiler; + +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import com.google.common.io.ByteStreams; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; + +public final class DecompilerIo implements IBytecodeProvider, IResultSaver, Closeable { + private final Map inputJars = new HashMap<>(); + private final Function destination; + + public DecompilerIo(Function destination) { + this.destination = destination; + } + + @Override + public byte[] getBytecode(String externalPath, String internalPath) throws IOException { + if (internalPath == null) { + throw new UnsupportedOperationException(); + } + + var jar = inputJars.get(externalPath); + if (jar == null) { + jar = new JarFile(externalPath); + inputJars.put(externalPath, jar); + } + + try (var in = jar.getInputStream(jar.getJarEntry(internalPath))) { + return ByteStreams.toByteArray(in); + } + } + + @Override + public void saveFolder(String path) { + /* ignore */ + } + + @Override + public void copyFile(String source, String path, String entryName) { + throw new UnsupportedOperationException(); + } + + @Override + public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) { + throw new UnsupportedOperationException(); + } + + @Override + public void createArchive(String path, String archiveName, Manifest manifest) { + /* ignore */ + } + + @Override + public void saveDirEntry(String path, String archiveName, String entryName) { + /* ignore */ + } + + @Override + public void copyEntry(String source, String path, String archiveName, String entry) { + /* ignore */ + } + + @Override + public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) { + var p = destination.apply(archiveName).resolve(entryName); + try { + Files.createDirectories(p.getParent()); + try (var writer = Files.newBufferedWriter(p)) { + writer.write(content); + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + @Override + public void closeArchive(String path, String archiveName) { + /* ignore */ + } + + @Override + public void close() throws IOException { + for (var jar : inputJars.values()) { + jar.close(); + } + } +} diff --git a/decompiler/src/main/java/dev/openrs2/decompiler/Slf4jFernflowerLogger.java b/decompiler/src/main/java/dev/openrs2/decompiler/Slf4jFernflowerLogger.java new file mode 100644 index 00000000..f82bb159 --- /dev/null +++ b/decompiler/src/main/java/dev/openrs2/decompiler/Slf4jFernflowerLogger.java @@ -0,0 +1,52 @@ +package dev.openrs2.decompiler; + +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class Slf4jFernflowerLogger extends IFernflowerLogger { + private static final Logger logger = LoggerFactory.getLogger(Slf4jFernflowerLogger.class); + + public static final IFernflowerLogger INSTANCE = new Slf4jFernflowerLogger(); + + private Slf4jFernflowerLogger() { + /* empty */ + } + + @Override + public void startClass(String className) { + logger.info("Decompiling {}", className); + } + + @Override + public void writeMessage(String message, Severity severity) { + switch (severity) { + case TRACE: + logger.trace(message); + case INFO: + logger.info(message); + case WARN: + logger.warn(message); + case ERROR: + logger.error(message); + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void writeMessage(String message, Severity severity, Throwable t) { + switch (severity) { + case TRACE: + logger.trace(message, t); + case INFO: + logger.info(message, t); + case WARN: + logger.warn(message, t); + case ERROR: + logger.error(message, t); + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/pom.xml b/pom.xml index f134c2a4..5122367b 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ asm + decompiler deob deob-annotations fernflower