switch to fernflower, add line number remapping

This commit is contained in:
Adrian Siekierka 2018-12-01 10:52:17 +01:00
parent d40d886f57
commit 836b321107
6 changed files with 424 additions and 86 deletions

View file

@ -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) {

View file

@ -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<String> 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<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> collection) {
switch (sinkType) {
case PROGRESS:
return Collections.singletonList(SinkClass.STRING);
case JAVA:
return Collections.singletonList(SinkClass.DECOMPILED);
default:
return Collections.emptyList();
}
}
@Override
public <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {
switch (sinkType) {
case PROGRESS:
return (t) -> getLogger().debug((String) t);
case JAVA:
//noinspection unchecked
return (Sink<T>) new Sink<SinkReturns.Decompiled>() {
@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()));
}
}
*/
}

View file

@ -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<String> addedDirectories = new HashSet<>();
try (FileOutputStream fos = new FileOutputStream(sourcesJar);
JarOutputStream jos = new JarOutputStream(fos, manifest)) {
project.getLogger().lifecycle(":preparing sources JAR");
Map<String, Object> 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<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> 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<String, int[]> 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 <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {
switch (sinkType) {
case PROGRESS:
return (t) -> getLogger().debug((String) t);
case JAVA:
//noinspection unchecked
return (Sink<T>) new Sink<SinkReturns.Decompiled>() {
@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();
}
}
}

View file

@ -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<Integer, Integer> 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));
}
}

View file

@ -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<String, int[]> differingMappings = new HashMap<>();
private final String jarName;
public LoomFernflowerDecompiler(File destination, String jarName, Map<String, Object> options, IFernflowerLogger logger) {
super(destination, options, logger);
this.jarName = jarName;
}
public Map<String, int[]> 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);
}
}

View file

@ -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);
}
}
}