From 836b321107e44fa016721b28f3fb7dab1bebdb03 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 1 Dec 2018 10:52:17 +0100 Subject: [PATCH] switch to fernflower, add line number remapping --- build.gradle | 6 +- .../fabricmc/loom/task/GenSourcesCfrTask.java | 118 +++++++++++++ .../fabricmc/loom/task/GenSourcesTask.java | 157 ++++++++---------- .../task/LineNumberAdjustmentVisitor.java | 89 ++++++++++ .../loom/task/LoomFernflowerDecompiler.java | 96 +++++++++++ .../loom/task/LoomFernflowerLogger.java | 44 +++++ 6 files changed, 424 insertions(+), 86 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/task/GenSourcesCfrTask.java create mode 100644 src/main/java/net/fabricmc/loom/task/LineNumberAdjustmentVisitor.java create mode 100644 src/main/java/net/fabricmc/loom/task/LoomFernflowerDecompiler.java create mode 100644 src/main/java/net/fabricmc/loom/task/LoomFernflowerLogger.java diff --git a/build.gradle b/build.gradle index 94faef0..44e1825 100644 --- a/build.gradle +++ b/build.gradle @@ -35,10 +35,11 @@ dependencies { implementation ('com.google.code.gson:gson:2.8.5') implementation ('com.google.guava:guava:27.0.1-jre') implementation ('net.fabricmc:stitch:0.1.0.17') - implementation ('net.fabricmc:tiny-remapper:0.1.0.19') { + implementation ('net.fabricmc:tiny-remapper:0.1.0.20') { transitive = false } - implementation ('org.benf:cfr:0.136') +// implementation ('org.benf:cfr:0.136') + implementation ('org.jetbrains:intellij-fernflower:1.0.0.2') implementation ('net.fabricmc:sponge-mixin:0.7.11.3') { exclude module: 'launchwrapper' @@ -50,6 +51,7 @@ jar { manifest { attributes 'Implementation-Version': version + " Build(" + build + ")" } + from { zipTree("/home/asie/intellij-fernflower-1.0.0.2.jar") } } task sourcesJar(type: Jar, dependsOn: classes) { diff --git a/src/main/java/net/fabricmc/loom/task/GenSourcesCfrTask.java b/src/main/java/net/fabricmc/loom/task/GenSourcesCfrTask.java new file mode 100644 index 0000000..1188499 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/GenSourcesCfrTask.java @@ -0,0 +1,118 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.task; + +import org.gradle.api.DefaultTask; + +public class GenSourcesCfrTask extends DefaultTask { + /* + @TaskAction + public void genSources() throws IOException { + Project project = this.getProject(); + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + PomfProvider pomfProvider = extension.getPomfProvider(); + File mappedJar = pomfProvider.mappedProvider.getMappedJar(); + File sourcesJar = getSourcesJar(project); + + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + Set addedDirectories = new HashSet<>(); + + try (FileOutputStream fos = new FileOutputStream(sourcesJar); + JarOutputStream jos = new JarOutputStream(fos, manifest)) { + + project.getLogger().lifecycle(":generating sources JAR"); + CfrDriver driver = new CfrDriver.Builder() + .withOptions(ImmutableMap.of("renameillegalidents","true")) + .withOutputSink(new OutputSinkFactory() { + @Override + public List getSupportedSinks(SinkType sinkType, Collection collection) { + switch (sinkType) { + case PROGRESS: + return Collections.singletonList(SinkClass.STRING); + case JAVA: + return Collections.singletonList(SinkClass.DECOMPILED); + default: + return Collections.emptyList(); + } + } + + @Override + public Sink getSink(SinkType sinkType, SinkClass sinkClass) { + switch (sinkType) { + case PROGRESS: + return (t) -> getLogger().debug((String) t); + case JAVA: + //noinspection unchecked + return (Sink) new Sink() { + @Override + public void write(SinkReturns.Decompiled decompiled) { + String filename = decompiled.getPackageName().replace('.', '/'); + if (!filename.isEmpty()) filename += "/"; + filename += decompiled.getClassName() + ".java"; + + String[] path = filename.split("/"); + String pathPart = ""; + for (int i = 0; i < path.length - 1; i++) { + pathPart += path[i] + "/"; + if (addedDirectories.add(pathPart)) { + JarEntry entry = new JarEntry(pathPart); + entry.setTime(new Date().getTime()); + + try { + jos.putNextEntry(entry); + jos.closeEntry(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + byte[] data = decompiled.getJava().getBytes(Charsets.UTF_8); + JarEntry entry = new JarEntry(filename); + entry.setTime(new Date().getTime()); + entry.setSize(data.length); + + try { + jos.putNextEntry(entry); + jos.write(data); + jos.closeEntry(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + default: + return (t) -> {}; + } + } + }) + .build(); + + driver.analyse(Collections.singletonList(mappedJar.getAbsolutePath())); + } + } + */ +} diff --git a/src/main/java/net/fabricmc/loom/task/GenSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenSourcesTask.java index dbcd6af..47fbe8b 100644 --- a/src/main/java/net/fabricmc/loom/task/GenSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenSourcesTask.java @@ -24,33 +24,26 @@ package net.fabricmc.loom.task; -import com.google.common.base.Charsets; -import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteStreams; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.providers.MinecraftProvider; +import net.fabricmc.loom.providers.MinecraftLibraryProvider; import net.fabricmc.loom.providers.PomfProvider; -import net.fabricmc.loom.util.MinecraftVersionInfo; -import org.benf.cfr.reader.api.CfrDriver; -import org.benf.cfr.reader.api.ClassFileSource; -import org.benf.cfr.reader.api.OutputSinkFactory; -import org.benf.cfr.reader.api.SinkReturns; -import org.benf.cfr.reader.entities.Method; -import org.benf.cfr.reader.util.output.StreamDumper; import org.gradle.api.DefaultTask; import org.gradle.api.Project; import org.gradle.api.tasks.TaskAction; -import org.xml.sax.SAXException; +import org.jetbrains.java.decompiler.main.ClassReference14Processor; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.*; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; +import java.util.jar.*; public class GenSourcesTask extends DefaultTask { public static File getSourcesJar(Project project) { @@ -69,86 +62,82 @@ public class GenSourcesTask extends DefaultTask { public void genSources() throws IOException { Project project = this.getProject(); LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + MinecraftLibraryProvider libraryProvider = extension.getMinecraftProvider().libraryProvider; PomfProvider pomfProvider = extension.getPomfProvider(); File mappedJar = pomfProvider.mappedProvider.getMappedJar(); File sourcesJar = getSourcesJar(project); Manifest manifest = new Manifest(); manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - Set addedDirectories = new HashSet<>(); - try (FileOutputStream fos = new FileOutputStream(sourcesJar); - JarOutputStream jos = new JarOutputStream(fos, manifest)) { + project.getLogger().lifecycle(":preparing sources JAR"); + Map options = new HashMap<>(); + options.put(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1"); + options.put(IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1"); - project.getLogger().lifecycle(":generating sources JAR"); - CfrDriver driver = new CfrDriver.Builder() - .withOptions(ImmutableMap.of("renameillegalidents","true")) - .withOutputSink(new OutputSinkFactory() { - @Override - public List getSupportedSinks(SinkType sinkType, Collection collection) { - switch (sinkType) { - case PROGRESS: - return Collections.singletonList(SinkClass.STRING); - case JAVA: - return Collections.singletonList(SinkClass.DECOMPILED); - default: - return Collections.emptyList(); - } + LoomFernflowerDecompiler decompiler = new LoomFernflowerDecompiler(sourcesJar.getParentFile(), sourcesJar.getName(), options, new LoomFernflowerLogger()); + decompiler.addSource(mappedJar); + for (File lib : libraryProvider.getLibraries()) { + try { + decompiler.addLibrary(lib); + } catch (Exception e) { + // pass + } + } + + project.getLogger().lifecycle(":generating sources JAR"); + decompiler.decompileContext(); + + Map mapNumbers = decompiler.getDifferingMappings(); + if (!mapNumbers.isEmpty()) { + project.getLogger().lifecycle(":readjusting line numbers"); + + File tmpJar = new File(mappedJar.getAbsolutePath() + ".tmp"); + mappedJar.renameTo(tmpJar); + try ( + FileInputStream fis = new FileInputStream(tmpJar); + JarInputStream jis = new JarInputStream(fis); + FileOutputStream fos = new FileOutputStream(mappedJar); + JarOutputStream jos = new JarOutputStream(fos) + ) { + JarEntry entry; + + while ((entry = jis.getNextJarEntry()) != null) { + JarEntry outputEntry = new JarEntry(entry.getName()); + outputEntry.setTime(entry.getTime()); + outputEntry.setCreationTime(entry.getCreationTime()); + outputEntry.setLastAccessTime(entry.getLastAccessTime()); + outputEntry.setLastModifiedTime(entry.getLastModifiedTime()); + + if (!entry.getName().endsWith(".class")) { + jos.putNextEntry(outputEntry); + ByteStreams.copy(jis, jos); + jos.closeEntry(); + } else { + String idx = entry.getName().substring(0, entry.getName().length() - 6); + int dollarPos = idx.indexOf('$'); + if (dollarPos >= 0) { + idx = idx.substring(0, dollarPos); } - @Override - public Sink getSink(SinkType sinkType, SinkClass sinkClass) { - switch (sinkType) { - case PROGRESS: - return (t) -> getLogger().debug((String) t); - case JAVA: - //noinspection unchecked - return (Sink) new Sink() { - @Override - public void write(SinkReturns.Decompiled decompiled) { - String filename = decompiled.getPackageName().replace('.', '/'); - if (!filename.isEmpty()) filename += "/"; - filename += decompiled.getClassName() + ".java"; + byte[] data = ByteStreams.toByteArray(jis); + if (mapNumbers.containsKey(idx)) { + ClassReader reader = new ClassReader(data); + ClassWriter writer = new ClassWriter(0); - String[] path = filename.split("/"); - String pathPart = ""; - for (int i = 0; i < path.length - 1; i++) { - pathPart += path[i] + "/"; - if (addedDirectories.add(pathPart)) { - JarEntry entry = new JarEntry(pathPart); - entry.setTime(new Date().getTime()); - - try { - jos.putNextEntry(entry); - jos.closeEntry(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - byte[] data = decompiled.getJava().getBytes(Charsets.UTF_8); - JarEntry entry = new JarEntry(filename); - entry.setTime(new Date().getTime()); - entry.setSize(data.length); - - try { - jos.putNextEntry(entry); - jos.write(data); - jos.closeEntry(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }; - default: - return (t) -> {}; - } + reader.accept(new LineNumberAdjustmentVisitor(Opcodes.ASM7, writer, mapNumbers.get(idx)), 0); + data = writer.toByteArray(); } - }) - .build(); - driver.analyse(Collections.singletonList(mappedJar.getAbsolutePath())); + jos.putNextEntry(outputEntry); + jos.write(data); + jos.closeEntry(); + } + } + } + + //noinspection ResultOfMethodCallIgnored + tmpJar.delete(); } } } diff --git a/src/main/java/net/fabricmc/loom/task/LineNumberAdjustmentVisitor.java b/src/main/java/net/fabricmc/loom/task/LineNumberAdjustmentVisitor.java new file mode 100644 index 0000000..2b06e36 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/LineNumberAdjustmentVisitor.java @@ -0,0 +1,89 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.task; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import java.util.HashMap; +import java.util.Map; + +public class LineNumberAdjustmentVisitor extends ClassVisitor { + public class Method extends MethodVisitor { + public Method(int api, MethodVisitor methodVisitor) { + super(api, methodVisitor); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + int tLine = line; + /* while (tLine >= 1 && !lineNumberMap.containsKey(tLine)) { + tLine--; + } */ + while (tLine <= maxLine && !lineNumberMap.containsKey(tLine)) { + tLine++; + } + if (tLine <= 0) { + tLine = 1; + } else if (tLine >= maxLine) { + tLine = maxLineDst; + } else { + tLine = lineNumberMap.get(tLine); + } + super.visitLineNumber(tLine, start); + } + } + + private final Map lineNumberMap; + private int maxLine, maxLineDst; + + public LineNumberAdjustmentVisitor(int api, ClassVisitor classVisitor, int[] mapping) { + super(api, classVisitor); + + lineNumberMap = new HashMap<>(); + maxLine = 0; + + for (int i = 0; i < mapping.length; i += 2) { + lineNumberMap.put(mapping[i], mapping[i+1]); + if (mapping[i] > maxLine) { + maxLine = mapping[i]; + } + if (mapping[i+1] > maxLineDst) { + maxLineDst = mapping[i+1]; + } + } + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions) { + return new Method(api, super.visitMethod(access, name, descriptor, signature, exceptions)); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/LoomFernflowerDecompiler.java b/src/main/java/net/fabricmc/loom/task/LoomFernflowerDecompiler.java new file mode 100644 index 0000000..bab72af --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/LoomFernflowerDecompiler.java @@ -0,0 +1,96 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.task; + +import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler; +import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +public class LoomFernflowerDecompiler extends ConsoleDecompiler { + private final Map differingMappings = new HashMap<>(); + private final String jarName; + + public LoomFernflowerDecompiler(File destination, String jarName, Map options, IFernflowerLogger logger) { + super(destination, options, logger); + this.jarName = jarName; + } + + public Map getDifferingMappings() { + return differingMappings; + } + + @Override + public void saveFolder(String s) { + super.saveFolder(s); + } + + @Override + public void copyFile(String s, String s1, String s2) { + throw new RuntimeException("TODO copyFile " + s + " " + s1 + " " + s2); + } + + @Override + public void saveClassFile(String s, String s1, String s2, String s3, int[] ints) { + throw new RuntimeException("TODO saveClassFile " + s + " " + s1 + " " + s2 + " " + s3); + } + + @Override + public void createArchive(String s, String s1, Manifest manifest) { + super.createArchive(s, jarName, manifest); + } + + @Override + public void saveDirEntry(String s, String s1, String s2) { + super.saveDirEntry(s, jarName, s2); + } + + @Override + public void copyEntry(String s, String s1, String s2, String s3) { + super.copyEntry(s, s1, jarName, s3); + } + + @Override + public void saveClassEntry(String s, String s1, String s2, String s3, String s4, int[] mapping) { + if (mapping != null) { + differingMappings.put(s2, mapping); + } + + super.saveClassEntry(s, jarName, s2, s3, s4, mapping); + } + + @Override + public void closeArchive(String s, String s1) { + super.closeArchive(s, jarName); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/LoomFernflowerLogger.java b/src/main/java/net/fabricmc/loom/task/LoomFernflowerLogger.java new file mode 100644 index 0000000..55da6c0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/LoomFernflowerLogger.java @@ -0,0 +1,44 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.task; + +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; + +public class LoomFernflowerLogger extends IFernflowerLogger { + @Override + public void writeMessage(String s, Severity severity) { + if (severity == Severity.WARN || severity == Severity.ERROR) { + System.err.println(s); + } + } + + @Override + public void writeMessage(String s, Severity severity, Throwable throwable) { + if (severity == Severity.WARN || severity == Severity.ERROR) { + System.err.println(s); + throwable.printStackTrace(System.err); + } + } +}