add parameter + localvar remapping/renaming
All checks were successful
Publish to snapshot maven / build (push) Successful in 18s

This commit is contained in:
moehreag 2024-05-17 16:17:00 +02:00
parent 9c81fb61cc
commit e78696e425
6 changed files with 306 additions and 16 deletions

View file

@ -14,12 +14,8 @@ repositories {
dependencies {
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-util:9.7")
// Annotation Processor
annotationProcessor("org.ow2.asm:asm-tree:9.7")
}
publishing {

View file

@ -6,23 +6,38 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
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.ParameterClassRemapper;
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.provider.MojmapProvider;
import org.ecorous.esnesnon.nonsense_remapper.provider.ParchmentProvider;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.ClassRemapper;
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();
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);
System.out.println("Remapping...");
@ -45,15 +60,25 @@ public class NonsenseRemapper {
try {
if (path.getFileName().toString().endsWith(".class")) {
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();
byte[] bytes = Files.readAllBytes(path);
ClassReader reader = new ClassReader(bytes);
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.write(newClass, writer.toByteArray());
Files.write(newClass, output);
} else {
Path result = outFs.getPath(path.toString()).toAbsolutePath();
Files.createDirectories(result.getParent());
@ -78,17 +103,27 @@ public class NonsenseRemapper {
public static void main(String[] args) throws IOException, InterruptedException {
// temporary
if (args.length < 3 || args.length > 4) {
System.err.println("Usage: <minecraft-version> <minecraft.jar> <out.jar> [--remove-meta-inf]");
if (args.length < 3 || args.length > 5) {
System.err.println("Usage: <minecraft-version> <minecraft.jar> <out.jar> [--remove-meta-inf] [--remap-parameters]");
return;
}
String minecraftVersion = args[0];
String minecraftJar = args[1];
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;
}
}

View file

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

View file

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

View file

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

View file

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