diff --git a/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/MappingParser.java b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/MappingParser.java new file mode 100644 index 000000000..c46249205 --- /dev/null +++ b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/MappingParser.java @@ -0,0 +1,55 @@ +package com.bgsoftware.superiorskyblock.nms.mapping; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MappingParser { + + private static final Pattern NEW_CLASS_REMAP_PATTERN = Pattern.compile("^([^ ]+) -> [^ ]+:$"); + private static final Pattern FIELD_REMAP_PATTERN = Pattern.compile("^[ ]{4}[^ ]+ ([^ (]+)+ -> ([^ ]+)$"); + private static final Pattern METHOD_REMAP_PATTERN = Pattern.compile("^[ ]{4}[^ ]+ ([^ ]+)\\(.*\\) -> ([^ ]+)$"); + + private MappingParser() { + + } + + public static Map parseRemappedMap(File mappingsFile) throws IOException { + Map remappedMap = new HashMap<>(); + + try (BufferedReader reader = new BufferedReader(new FileReader(mappingsFile))) { + Remapped currentRemapped = null; + String currentLine; + + while ((currentLine = reader.readLine()) != null) { + if (currentLine.startsWith("#")) // Comment + continue; + + Matcher matcher; + + if ((matcher = NEW_CLASS_REMAP_PATTERN.matcher(currentLine)).matches()) { + currentRemapped = new Remapped(); + remappedMap.put(matcher.group(1), currentRemapped); + } else if (currentRemapped != null) { + if ((matcher = FIELD_REMAP_PATTERN.matcher(currentLine)).matches()) { + String fieldName = matcher.group(1); + String obfuscatedName = matcher.group(2); + currentRemapped.put(Remap.Type.FIELD, fieldName, obfuscatedName); + } else if ((matcher = METHOD_REMAP_PATTERN.matcher(currentLine)).matches()) { + String methodName = matcher.group(1); + String obfuscatedName = matcher.group(2); + currentRemapped.put(Remap.Type.METHOD, methodName, obfuscatedName); + } + } + } + } + + return remappedMap; + } + +} diff --git a/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remap.java b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remap.java new file mode 100644 index 000000000..2da06165a --- /dev/null +++ b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remap.java @@ -0,0 +1,30 @@ +package com.bgsoftware.superiorskyblock.nms.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(Remaps.class) +public @interface Remap { + + String classPath(); + + String name(); + + Type type(); + + String remappedName() default ""; + + enum Type { + + FIELD, + METHOD + + } + + +} diff --git a/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/RemapFailure.java b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/RemapFailure.java new file mode 100644 index 000000000..e140b7878 --- /dev/null +++ b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/RemapFailure.java @@ -0,0 +1,9 @@ +package com.bgsoftware.superiorskyblock.nms.mapping; + +public class RemapFailure extends RuntimeException { + + public RemapFailure(String message) { + super(message); + } + +} diff --git a/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remapped.java b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remapped.java new file mode 100644 index 000000000..db3cde530 --- /dev/null +++ b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remapped.java @@ -0,0 +1,32 @@ +package com.bgsoftware.superiorskyblock.nms.mapping; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Remapped { + + private final EnumMap>> mappings = new EnumMap<>(Remap.Type.class); + + public List getObfuscatedNames(String name, Remap.Type type) { + Map> typeMappings = mappings.get(type); + + if (typeMappings != null) { + List obfuscatedNames = typeMappings.get(name); + if (obfuscatedNames != null) + return obfuscatedNames; + } + + return Collections.emptyList(); + } + + public void put(Remap.Type type, String name, String obfuscated) { + mappings.computeIfAbsent(type, t -> new HashMap<>()) + .computeIfAbsent(name, n -> new ArrayList<>()) + .add(obfuscated); + } + +} diff --git a/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remaps.java b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remaps.java new file mode 100644 index 000000000..1e49d83ca --- /dev/null +++ b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/Remaps.java @@ -0,0 +1,14 @@ +package com.bgsoftware.superiorskyblock.nms.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Remaps { + + Remap[] value(); + +} diff --git a/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/TestRemaps.java b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/TestRemaps.java new file mode 100644 index 000000000..640a3d3b1 --- /dev/null +++ b/src/main/java/com/bgsoftware/superiorskyblock/nms/mapping/TestRemaps.java @@ -0,0 +1,127 @@ +package com.bgsoftware.superiorskyblock.nms.mapping; + +import com.bgsoftware.common.reflection.ReflectField; +import com.bgsoftware.common.reflection.ReflectMethod; +import com.google.common.reflect.ClassPath; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.Map; +import java.util.function.IntFunction; +import java.util.logging.Logger; + +public class TestRemaps { + + private static final Logger logger = Logger.getLogger("TestRemaps"); + + private static final ReflectField REFLECT_FIELD_INNER_FIELD = new ReflectField<>(ReflectField.class, Field.class, "field"); + private static final ReflectField REFLECT_METHOD_INNER_METHOD = new ReflectField<>(ReflectMethod.class, Method.class, "method"); + + private TestRemaps() { + + } + + @SuppressWarnings("UnstableApiUsage") + public static void testRemapsForClassesInPackage(File mappingsFile, ClassLoader classLoader, String packageName) throws + IllegalAccessException, NullPointerException, IOException, RemapFailure { + Class[] classes = ClassPath.from(classLoader) + .getAllClasses() + .stream() + .filter(clazz -> clazz.getPackageName().startsWith(packageName)) + .map(ClassPath.ClassInfo::load) + .toArray((IntFunction[]>) Class[]::new); + testRemapsForClasses(mappingsFile, classes); + } + + public static void testRemapsForClasses(File mappingsFile, Class... classes) throws + IllegalAccessException, NullPointerException, IOException, RemapFailure { + for (Class clazz : classes) { + logger.info("Starting remaps test for " + clazz.getName()); + + Map remappedMap = MappingParser.parseRemappedMap(mappingsFile); + + try { + for (Field field : clazz.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) { + Remap[] remaps = field.getAnnotationsByType(Remap.class); + if (remaps.length > 0) { + logger.info("Testing field " + field.getName()); + for (Remap remap : remaps) { + try { + testRemap(remappedMap, remap, getRemappedName(field)); + } catch (Throwable error) { + error.printStackTrace(); + } + } + } + } + } + + for (Method method : clazz.getDeclaredMethods()) { + Remap[] remaps = method.getAnnotationsByType(Remap.class); + if (remaps.length > 0) { + logger.info("Testing method " + method.getName()); + for (Remap remap : remaps) { + try { + testRemap(remappedMap, remap, null); + } catch (Throwable error) { + error.printStackTrace(); + } + } + } + } + } catch (Throwable error) { + logger.info("Failed remaps test for " + clazz.getName() + ":"); + error.printStackTrace(); + } + + logger.info("Finished remaps tests for " + clazz.getName()); + } + } + + @Nullable + private static String getRemappedName(Field field) throws IllegalAccessException { + field.setAccessible(true); + Object fieldValue = field.get(null); + + if (fieldValue instanceof ReflectField) { + Field innerField = REFLECT_FIELD_INNER_FIELD.get(fieldValue); + if (innerField != null) + return innerField.getName(); + } else if (fieldValue instanceof ReflectMethod) { + Method innerMethod = REFLECT_METHOD_INNER_METHOD.get(fieldValue); + if (innerMethod != null) + return innerMethod.getName(); + } + + return null; + } + + private static void testRemap(Map remappedMap, Remap remap, @Nullable String remappedName) { + String classPath = remap.classPath(); + + Remapped remapped = remappedMap.get(classPath); + + if (remapped == null) + throw new NullPointerException("Cannot find remapped object for classPath " + classPath); + + String name = remap.name(); + Remap.Type type = remap.type(); + + List obfuscatedNames = remapped.getObfuscatedNames(name, type); + + if (obfuscatedNames.isEmpty()) + throw new NullPointerException("Cannot find obfuscated name for " + name + ":" + type); + + String remappedNameOrDefault = remappedName == null ? remap.remappedName() : remappedName; + + if (!obfuscatedNames.contains(remappedNameOrDefault)) + throw new RemapFailure("Incorrect remap: Expected " + obfuscatedNames + ", found " + remappedNameOrDefault); + } + +}