add parameter + localvar remapping/renaming
All checks were successful
Publish to snapshot maven / build (push) Successful in 18s
All checks were successful
Publish to snapshot maven / build (push) Successful in 18s
This commit is contained in:
parent
9c81fb61cc
commit
e78696e425
|
@ -14,12 +14,8 @@ repositories {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
implementation("org.ow2.asm:asm-tree:9.7")
|
implementation("org.ow2.asm:asm:9.7")
|
||||||
implementation("org.ow2.asm:asm-commons:9.7")
|
implementation("org.ow2.asm:asm-commons:9.7")
|
||||||
implementation("org.ow2.asm:asm-util:9.7")
|
|
||||||
|
|
||||||
// Annotation Processor
|
|
||||||
annotationProcessor("org.ow2.asm:asm-tree:9.7")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
|
|
@ -6,23 +6,38 @@ import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import org.ecorous.esnesnon.nonsense_remapper.api.Mapper;
|
import org.ecorous.esnesnon.nonsense_remapper.api.Mapper;
|
||||||
|
import org.ecorous.esnesnon.nonsense_remapper.api.ParameterClassRemapper;
|
||||||
import org.ecorous.esnesnon.nonsense_remapper.api.data.MappingData;
|
import org.ecorous.esnesnon.nonsense_remapper.api.data.MappingData;
|
||||||
|
import org.ecorous.esnesnon.nonsense_remapper.api.data.Parchment;
|
||||||
import org.ecorous.esnesnon.nonsense_remapper.parser.ProguardParser;
|
import org.ecorous.esnesnon.nonsense_remapper.parser.ProguardParser;
|
||||||
import org.ecorous.esnesnon.nonsense_remapper.provider.MojmapProvider;
|
import org.ecorous.esnesnon.nonsense_remapper.provider.MojmapProvider;
|
||||||
|
import org.ecorous.esnesnon.nonsense_remapper.provider.ParchmentProvider;
|
||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
import org.objectweb.asm.ClassWriter;
|
import org.objectweb.asm.ClassWriter;
|
||||||
import org.objectweb.asm.commons.ClassRemapper;
|
import org.objectweb.asm.commons.ClassRemapper;
|
||||||
|
|
||||||
public class NonsenseRemapper {
|
public class NonsenseRemapper {
|
||||||
|
|
||||||
public static void run(String minecraftVersion, Path inputJar, Path outputJar, boolean skipMetaInf) throws IOException, InterruptedException {
|
public static void run(String minecraftVersion, Path inputJar, Path outputJar, boolean skipMetaInf, boolean renameParameters) throws IOException, InterruptedException {
|
||||||
MappingData data = ProguardParser.read(MojmapProvider.get(minecraftVersion).orElseThrow()).reverse();
|
MappingData data = ProguardParser.read(MojmapProvider.get(minecraftVersion).orElseThrow()).reverse();
|
||||||
|
|
||||||
Mapper mapper = new Mapper(data);
|
Mapper mapper = new Mapper(data);
|
||||||
|
|
||||||
|
|
||||||
|
Parchment paramMappings = null;
|
||||||
|
if (renameParameters) {
|
||||||
|
paramMappings = ParchmentProvider.getParchment(minecraftVersion, outputJar.getParent());
|
||||||
|
}
|
||||||
|
|
||||||
|
remap(mapper, inputJar, outputJar, skipMetaInf, paramMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remap(Mapper mapper, Path inputJar, Path outputJar, boolean skipMetaInf, Parchment paramMappings) throws IOException, InterruptedException {
|
||||||
Files.deleteIfExists(outputJar);
|
Files.deleteIfExists(outputJar);
|
||||||
|
|
||||||
System.out.println("Remapping...");
|
System.out.println("Remapping...");
|
||||||
|
@ -37,7 +52,7 @@ public class NonsenseRemapper {
|
||||||
Files.walkFileTree(inFs.getPath("/"), new SimpleFileVisitor<>() {
|
Files.walkFileTree(inFs.getPath("/"), new SimpleFileVisitor<>() {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) {
|
public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) {
|
||||||
if (skipMetaInf && path.startsWith("/META-INF")){
|
if (skipMetaInf && path.startsWith("/META-INF")) {
|
||||||
return FileVisitResult.SKIP_SUBTREE;
|
return FileVisitResult.SKIP_SUBTREE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,15 +60,25 @@ public class NonsenseRemapper {
|
||||||
try {
|
try {
|
||||||
if (path.getFileName().toString().endsWith(".class")) {
|
if (path.getFileName().toString().endsWith(".class")) {
|
||||||
String className = path.getFileName().toString().substring(0, path.getFileName().toString().lastIndexOf("."));
|
String className = path.getFileName().toString().substring(0, path.getFileName().toString().lastIndexOf("."));
|
||||||
Path result = outFs.getPath(path.toString()).resolveSibling(data.classes().getOrDefault(className, className) + ".class");
|
Path result = outFs.getPath(path.toString()).resolveSibling(mapper.map(className) + ".class");
|
||||||
Path newClass = result.toAbsolutePath();
|
Path newClass = result.toAbsolutePath();
|
||||||
byte[] bytes = Files.readAllBytes(path);
|
byte[] bytes = Files.readAllBytes(path);
|
||||||
ClassReader reader = new ClassReader(bytes);
|
ClassReader reader = new ClassReader(bytes);
|
||||||
ClassWriter writer = new ClassWriter(0);
|
ClassWriter writer = new ClassWriter(0);
|
||||||
reader.accept(new ClassRemapper(writer, mapper), 0);
|
ClassVisitor remapper = new ClassRemapper(writer, mapper);
|
||||||
|
|
||||||
|
ClassVisitor visitor;
|
||||||
|
if (paramMappings != null) {
|
||||||
|
visitor = new ParameterClassRemapper(remapper, mapper, paramMappings);
|
||||||
|
} else {
|
||||||
|
visitor = remapper;
|
||||||
|
}
|
||||||
|
reader.accept(visitor, 0);
|
||||||
|
|
||||||
|
byte[] output = writer.toByteArray();
|
||||||
Files.createDirectories(result.getParent());
|
Files.createDirectories(result.getParent());
|
||||||
Files.write(newClass, writer.toByteArray());
|
Files.write(newClass, output);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Path result = outFs.getPath(path.toString()).toAbsolutePath();
|
Path result = outFs.getPath(path.toString()).toAbsolutePath();
|
||||||
Files.createDirectories(result.getParent());
|
Files.createDirectories(result.getParent());
|
||||||
|
@ -78,17 +103,27 @@ public class NonsenseRemapper {
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException, InterruptedException {
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
// temporary
|
// temporary
|
||||||
if (args.length < 3 || args.length > 4) {
|
if (args.length < 3 || args.length > 5) {
|
||||||
System.err.println("Usage: <minecraft-version> <minecraft.jar> <out.jar> [--remove-meta-inf]");
|
System.err.println("Usage: <minecraft-version> <minecraft.jar> <out.jar> [--remove-meta-inf] [--remap-parameters]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String minecraftVersion = args[0];
|
String minecraftVersion = args[0];
|
||||||
String minecraftJar = args[1];
|
String minecraftJar = args[1];
|
||||||
String outJar = args[2];
|
String outJar = args[2];
|
||||||
boolean removeMetaInf = args.length > 3 && "--remove-meta-inf".equals(args[3]);
|
boolean removeMetaInf = args.length > 3 && hasArg(args, "--remove-meta-inf");
|
||||||
|
boolean remapParams = args.length > 3 && hasArg(args, "--remap-parameters");
|
||||||
|
|
||||||
run(minecraftVersion, Paths.get(minecraftJar), Paths.get(outJar), removeMetaInf);
|
run(minecraftVersion, Paths.get(minecraftJar), Paths.get(outJar), removeMetaInf, remapParams);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean hasArg(String[] arguments, String arg) {
|
||||||
|
for (String s : arguments) {
|
||||||
|
if (arg.equals(s)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.ecorous.esnesnon.nonsense_remapper.api;
|
||||||
|
|
||||||
|
import org.ecorous.esnesnon.nonsense_remapper.api.data.Parchment;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.commons.ClassRemapper;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
public class ParameterClassRemapper extends ClassRemapper {
|
||||||
|
private final Parchment parchment;
|
||||||
|
|
||||||
|
public ParameterClassRemapper(ClassVisitor classVisitor, Remapper remapper, Parchment parchment) {
|
||||||
|
super(Opcodes.ASM9, classVisitor, remapper);
|
||||||
|
this.parchment = parchment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||||
|
String remappedDescriptor = remapper.mapMethodDesc(descriptor);
|
||||||
|
MethodVisitor methodVisitor = cv.visitMethod(access, remapper.mapMethodName(className, name, descriptor), remappedDescriptor,
|
||||||
|
remapper.mapSignature(signature, false), exceptions == null ? null : remapper.mapTypes(exceptions));
|
||||||
|
|
||||||
|
Parchment.Method method = parchment.getClass(className).flatMap(c -> c.getMethod(remapper.mapMethodName(className, name, descriptor), remapper.mapMethodDesc(descriptor)))
|
||||||
|
.orElse(null);
|
||||||
|
return methodVisitor == null ? null : new ParameterMethodRemapper(methodVisitor, remapper, method);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package org.ecorous.esnesnon.nonsense_remapper.api;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.ecorous.esnesnon.nonsense_remapper.api.data.Parchment;
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.commons.MethodRemapper;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
public class ParameterMethodRemapper extends MethodRemapper {
|
||||||
|
private final Parchment.Method parchment;
|
||||||
|
private final Set<String> usedLocalNames = new HashSet<>();
|
||||||
|
|
||||||
|
public ParameterMethodRemapper(MethodVisitor methodVisitor, Remapper remapper, Parchment.Method parchment) {
|
||||||
|
super(Opcodes.ASM9, methodVisitor, remapper);
|
||||||
|
this.parchment = parchment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||||
|
if (!name.equals("this")) {
|
||||||
|
if (parchment != null) {
|
||||||
|
Optional<Parchment.Parameter> parameter = parchment.getParameter(index);
|
||||||
|
name = parameter.map(Parchment.Parameter::name).orElseGet(() -> getNewName(descriptor, signature));
|
||||||
|
} else {
|
||||||
|
name = getNewName(descriptor, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
super.visitLocalVariable(name, descriptor, signature, start, end, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNewName(String descriptor, String signature) {
|
||||||
|
String newName = switch (descriptor.charAt(0)) {
|
||||||
|
case 'I' -> "i";
|
||||||
|
case 'B' -> "b";
|
||||||
|
case 'C' -> "c";
|
||||||
|
case 'S' -> "s";
|
||||||
|
case 'D' -> "d";
|
||||||
|
case 'F' -> "f";
|
||||||
|
case 'J' -> "l";
|
||||||
|
case 'Z' -> "bl";
|
||||||
|
default -> {
|
||||||
|
String name = cleanType(descriptor);
|
||||||
|
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||||
|
yield name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int count = 1;
|
||||||
|
while (usedLocalNames.contains(newName)) {
|
||||||
|
if (signature != null) {
|
||||||
|
String type = cleanType(signature);
|
||||||
|
if (!newName.toLowerCase(Locale.ROOT).contains(type.toLowerCase(Locale.ROOT))) {
|
||||||
|
newName = Character.toLowerCase(type.charAt(0)) + type.substring(1) +
|
||||||
|
Character.toUpperCase(newName.charAt(0)) + newName.substring(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Integer.toString(count).equals(newName.substring(newName.length() - 1))) {
|
||||||
|
newName = newName.substring(0, newName.length() - 1);
|
||||||
|
}
|
||||||
|
newName = newName + ++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
usedLocalNames.add(newName);
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cleanType(String type) {
|
||||||
|
var cleaned = type;
|
||||||
|
if (cleaned.indexOf('<') != -1) {
|
||||||
|
cleaned = cleaned.substring(0, cleaned.indexOf('<'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleaned.indexOf('/') != -1) {
|
||||||
|
cleaned = cleaned.substring(cleaned.lastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleaned.indexOf('$') != -1) {
|
||||||
|
cleaned = cleaned.substring(cleaned.lastIndexOf('$') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaned = cleaned.replaceAll("\\[]", "").replace(";", "");
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.ecorous.esnesnon.nonsense_remapper.api.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record Parchment(String version, List<Package> packages, List<Class> classes) {
|
||||||
|
public Optional<Class> getClass(String name) {
|
||||||
|
return classes.stream().filter(c -> c.name().equals(name)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Package(String name, List<String> javadoc) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Class(String name, List<String> javadoc, List<Field> fields, List<Method> methods) {
|
||||||
|
public Optional<Field> getField(String name, String descriptor) {
|
||||||
|
return fields.stream()
|
||||||
|
.filter(f -> f.name.equals(name) &&
|
||||||
|
f.descriptor.equals(descriptor))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Method> getMethod(String name, String descriptor) {
|
||||||
|
return methods.stream()
|
||||||
|
.filter(method -> method.name.equals(name) &&
|
||||||
|
method.descriptor.equals(descriptor))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Field(String name, String descriptor, List<String> javadoc) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Method(String name, String descriptor, List<String> javadoc, List<Parameter> parameters) {
|
||||||
|
public Optional<Parameter> getParameter(int index) {
|
||||||
|
if (parameters == null){
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return parameters.stream()
|
||||||
|
.filter(f -> f.index == index)
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Parameter(int index, String name) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package org.ecorous.esnesnon.nonsense_remapper.provider;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import org.ecorous.esnesnon.nonsense_remapper.api.data.Parchment;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
public class ParchmentProvider {
|
||||||
|
|
||||||
|
private static final String PARCHMENT_URL = "https://maven.parchmentmc.org/org/parchmentmc/data";
|
||||||
|
|
||||||
|
private static String getParchmentUrl(String gameVersion) {
|
||||||
|
return PARCHMENT_URL + "/parchment-" + gameVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String findParchmentVersion(String gameVersion) throws IOException {
|
||||||
|
String url = getParchmentUrl(gameVersion) + "/maven-metadata.xml";
|
||||||
|
try (InputStream
|
||||||
|
stream = URI.create(url).toURL().openStream()) {
|
||||||
|
Document document = DocumentBuilderFactory.newInstance()
|
||||||
|
.newDocumentBuilder().parse(stream);
|
||||||
|
return document.getElementsByTagName("release").item(0).getTextContent();
|
||||||
|
} catch (IOException | SAXException | ParserConfigurationException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Parchment getParchment(String gameVersion, Path cacheDir) throws IOException {
|
||||||
|
return getParchment(gameVersion, findParchmentVersion(gameVersion), cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Parchment getParchment(String gameVersion, String parchmentVer, Path cacheDir) throws IOException {
|
||||||
|
return getParchment(gameVersion, parchmentVer, cacheDir, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Parchment getParchment(String gameVersion, String parchmentVer, Path cacheDir, boolean forceDownload) throws IOException {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
System.out.println("Loading Parchment mappings..");
|
||||||
|
var cachePath = cacheDir.resolve("parchment-" + gameVersion + "-" + parchmentVer + ".zip");
|
||||||
|
Files.createDirectories(cachePath.getParent());
|
||||||
|
|
||||||
|
var url = "%s/%s/parchment-%s-%s.zip".formatted(getParchmentUrl(gameVersion), parchmentVer, gameVersion, parchmentVer);
|
||||||
|
if (forceDownload || Files.notExists(cachePath)) {
|
||||||
|
Files.copy(URI.create(url).toURL().openStream(), cachePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try (InputStream input = URI.create(url+".sha512").toURL().openStream()) {
|
||||||
|
var hash = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)).readLine();
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||||
|
byte[] out = digest.digest(Files.readAllBytes(cachePath));
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : out) {
|
||||||
|
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
|
||||||
|
}
|
||||||
|
String local = sb.toString();
|
||||||
|
if (!hash.equals(local)) {
|
||||||
|
System.out.println("Hashes do not match for "+cachePath+": "+local+" != "+hash+"; redownloading!");
|
||||||
|
return getParchment(gameVersion, parchmentVer, cacheDir, true);
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException e){
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Parchment parchment;
|
||||||
|
try (FileSystem fs = FileSystems.newFileSystem(cachePath)) {
|
||||||
|
var mappings = Files.readString(fs.getPath("parchment.json"));
|
||||||
|
parchment = new Gson().fromJson(mappings, Parchment.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.printf("Finished loading in (%.2fs)\n", (System.currentTimeMillis() - time) / 1000F);
|
||||||
|
return parchment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String findForMinecraftVersion(String minecraftVersion) throws IOException {
|
||||||
|
return findParchmentVersion(minecraftVersion);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue