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