From 06b89bd27112017c9a3abf770cd58bca80f961e4 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Mon, 7 Feb 2022 12:45:18 +0100 Subject: [PATCH] New NMS and OBC Reflection utility classes --- Paper/pom.xml | 27 +- .../lib/paper/reflect/NMSReflect.java | 465 ++++++++++++++++++ .../lib/paper/reflect/OBCReflect.java | 31 ++ .../lib/paper/reflect/ReflectRegistry.java | 144 ++++++ .../fr/pandacube/lib/paper/reflect/Type.java | 196 ++++++++ 5 files changed, 855 insertions(+), 8 deletions(-) create mode 100644 Paper/src/main/java/fr/pandacube/lib/paper/reflect/NMSReflect.java create mode 100644 Paper/src/main/java/fr/pandacube/lib/paper/reflect/OBCReflect.java create mode 100644 Paper/src/main/java/fr/pandacube/lib/paper/reflect/ReflectRegistry.java create mode 100644 Paper/src/main/java/fr/pandacube/lib/paper/reflect/Type.java diff --git a/Paper/pom.xml b/Paper/pom.xml index 6a4c4f9..993bf8f 100644 --- a/Paper/pom.xml +++ b/Paper/pom.xml @@ -16,6 +16,10 @@ papermc https://papermc.io/repo/repository/maven-public/ + + fabricmc + https://maven.fabricmc.net/ + @@ -36,20 +40,27 @@ - - - + io.papermc.paper paper-api ${paper.version}-SNAPSHOT compile + + io.papermc.paper + paper-mojangapi + ${paper.version}-SNAPSHOT + compile + + + + + net.fabricmc + mapping-io + 0.3.0 + provided + \ No newline at end of file diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/reflect/NMSReflect.java b/Paper/src/main/java/fr/pandacube/lib/paper/reflect/NMSReflect.java new file mode 100644 index 0000000..148cf36 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/reflect/NMSReflect.java @@ -0,0 +1,465 @@ +package fr.pandacube.lib.paper.reflect; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; + +import fr.pandacube.lib.core.util.Log; +import fr.pandacube.lib.core.util.Reflect; +import fr.pandacube.lib.core.util.Reflect.ReflectClass; +import fr.pandacube.lib.core.util.Reflect.ReflectField; +import fr.pandacube.lib.core.util.Reflect.ReflectMethod; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.format.MappingFormat; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public class NMSReflect { + + private static ReflectClass PAPER_OBFHELPER_CLASS; + + private static String OBF_NAMESPACE; + private static String MOJ_NAMESPACE; + + /* package */ static final Map CLASSES_BY_OBF = new TreeMap<>(); + /* package */ static final Map CLASSES_BY_MOJ = new TreeMap<>(); + + private static Boolean IS_SERVER_OBFUSCATED; + + + static { + try { + PAPER_OBFHELPER_CLASS = Reflect.ofClass("io.papermc.paper.util.ObfHelper"); + + OBF_NAMESPACE = (String) PAPER_OBFHELPER_CLASS.field("SPIGOT_NAMESPACE").getStaticValue(); + MOJ_NAMESPACE = (String) PAPER_OBFHELPER_CLASS.field("MOJANG_PLUS_YARN_NAMESPACE").getStaticValue(); + + List mappings = loadMappings(); + for (ClassMapping clazz : mappings) { + CLASSES_BY_OBF.put(clazz.obfName, clazz); + CLASSES_BY_MOJ.put(clazz.mojName, clazz); + } + + // determine if the runtime server is obfuscated + ClassNotFoundException exIfUnableToDetermine = null; + for (ClassMapping clazz : CLASSES_BY_OBF.values()) { + if (clazz.obfName.equals(clazz.mojName) // avoid direct collision between obf and unobf class names + || CLASSES_BY_MOJ.containsKey(clazz.obfName) // avoid indirect collision + || CLASSES_BY_OBF.containsKey(clazz.mojName))// avoid indirect collision + continue; + + try { + Class.forName(clazz.obfName); + IS_SERVER_OBFUSCATED = true; + Log.info("NMS classes are obfuscated."); + break; + } catch (ClassNotFoundException e) { + try { + Class.forName(clazz.mojName); + IS_SERVER_OBFUSCATED = false; + Log.info("NMS classes are using mojang mapping."); + break; + } catch (ClassNotFoundException ee) { + ee.addSuppressed(e); + if (exIfUnableToDetermine == null) + exIfUnableToDetermine = ee; + } + } + } + + if (IS_SERVER_OBFUSCATED == null) { + CLASSES_BY_MOJ.clear(); + CLASSES_BY_OBF.clear(); + throw exIfUnableToDetermine; + } + + + for (ClassMapping clazz : mappings) { + clazz.cacheReflectClass(); + } + } catch (ReflectiveOperationException e) { + Log.severe(e); + } + } + + + /** + * @param mojName the binary name of the desired class, on the mojang mapping. + * @throws NullPointerException if there is no mapping for the provided Mojang mapped class. + * @throws ClassNotFoundException if there is a mapping, but the runtime class was not found. + */ + public static ClassMapping mojClass(String mojName) throws ClassNotFoundException { + ClassMapping cm = Objects.requireNonNull(CLASSES_BY_MOJ.get(mojName), "Unable to find the Mojang mapped class '" + mojName + "'"); + cm.cacheReflectClass(); + return cm; + } + + + + + + + + + + + private static List loadMappings() { + try (final InputStream mappingsInputStream = PAPER_OBFHELPER_CLASS.get().getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) { + if (mappingsInputStream == null) { + Log.severe("Unable to find the ofbuscation mapping file in the Paper jar."); + return Collections.emptyList(); + } + + MemoryMappingTree tree = new MemoryMappingTree(); + MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2, tree); + + List classes = new ArrayList<>(); + for (MappingTree.ClassMapping cls : tree.getClasses()) { + classes.add(new ClassMapping(cls)); + } + return classes; + } catch (IOException e) { + Log.severe("Failed to load mappings.", e); + return Collections.emptyList(); + } + } + + + + public static void printHTMLMapping(PrintStream out) { + String title = "Obfuscation mapping - " + Bukkit.getName() + " version " + Bukkit.getVersion(); + out.println("\n" + + "" + title + "\n" + + """ + + """ + + "\n" + + "

" + title + "

\n" + + ""); + out.println(""); + for (ClassMapping clazz : CLASSES_BY_OBF.values()) { + clazz.printHTML(out); + } + out.println("
Namespace" + OBF_NAMESPACE + "" + MOJ_NAMESPACE + "
"); + } + + + + + + public static class ClassMapping { + private static int nextID = 0; + + /* package */ final int id = nextID++; + /* package */ final String obfName; + /* package */ final String mojName; + + private final Map> methodsByObf = new TreeMap<>(); + private final Map> methodsByMoj = new TreeMap<>(); + private final Map> fieldsByObf = new TreeMap<>(); + private final Map> fieldsByMoj = new TreeMap<>(); + + private ReflectClass runtimeReflectClass = null; + + private ClassMapping(MappingTree.ClassMapping cls) { + obfName = binaryClassName(cls.getName(OBF_NAMESPACE)); + mojName = binaryClassName(cls.getName(MOJ_NAMESPACE)); + + cls.getMethods().stream().map(MemberMapping::of).forEach(method -> { + methodsByObf.put(method.obfDesc.identifier, method); + methodsByMoj.put(method.mojDesc.identifier, method); + }); + cls.getFields().stream().map(MemberMapping::of).forEach(field -> { + fieldsByObf.put(field.obfDesc.identifier, field); + fieldsByMoj.put(field.mojDesc.identifier, field); + }); + + } + + + + private synchronized void cacheReflectClass() throws ClassNotFoundException { + if (runtimeReflectClass == null) + runtimeReflectClass = Reflect.ofClass(IS_SERVER_OBFUSCATED ? obfName : mojName); + } + + + + + public ReflectClass runtimeReflect() { + return runtimeReflectClass; + } + + public Class runtimeClass() { + return runtimeReflectClass.get(); + } + + + + + /** + * + * @param mojName the Mojang mapped name of the method. + * @param mojParametersType the list of parameters of the method. + * Each parameter type must be an instance of one of the following type: + * {@link Type}, {@link Class}, {@link ReflectClass} or {@link ClassMapping}. + * @return + * @throws IllegalArgumentException if one of the parameter has an invalid type + * @throws NullPointerException if one of the parameter is null, or if there is no mapping for the provided Mojang mapped method. + * @throws ClassNotFoundException if there is no runtime class to represent one of the provided parametersType. + * @throws NoSuchMethodException if there is no runtime method to represent the provided method. + */ + public ReflectMethod mojMethod(String mojName, Object... mojParametersType) throws ClassNotFoundException, NoSuchMethodException { + MethodId mId = new MethodId(mojName, Type.toTypeList(Arrays.asList(mojParametersType))); + MemberMapping mm = methodsByMoj.get(mId); + Objects.requireNonNull(mm, "Unable to find the Mojang mapped method " + mId); + + MethodId reflectId = (IS_SERVER_OBFUSCATED ? mm.obfDesc : mm.mojDesc).identifier; + return runtimeReflectClass.method(reflectId.name, Type.toClassArray(reflectId.parametersType)); + } + + + + + /** + * + * @param mojName the Mojang mapped name of the field. + * @return + * @throws NullPointerException if there is no mapping for the provided Mojang mapped field. + * @throws NoSuchFieldException if there is no runtime method to represent the provided method. + */ + public ReflectField mojField(String mojName) throws NoSuchFieldException { + MemberMapping fm = fieldsByMoj.get(mojName); + Objects.requireNonNull(fm, "Unable to find the Mojang mapped field '" + mojName + "'"); + return runtimeReflectClass.field((IS_SERVER_OBFUSCATED ? fm.obfDesc : fm.mojDesc).identifier); + } + + + + + + /* package */ String toClickableHTML(boolean isObfClass) { + String classToPrint = isObfClass ? obfName : mojName; + String classSimpleName = classToPrint.substring(classToPrint.lastIndexOf('.') + 1); + String htmlTitle = classSimpleName.equals(classToPrint) ? "" : (" title='" + classToPrint + "'"); + String typeHTML = "" + classSimpleName + ""; + + return typeHTML; + } + + + + + /* package */ Type toType(boolean obf) { + return new Type(obf ? obfName : mojName, 0); + } + + + private void printHTML(PrintStream out) { + out.println("" + classKind() + "" + nameToHTML(true) + "" + nameToHTML(false) + ""); + fieldsByObf.values().forEach(f -> f.printHTML(out)); + methodsByObf.values().forEach(m -> m.printHTML(out)); + } + + private String nameToHTML(boolean obf) { + String name = obf ? obfName : mojName; + + Type superClass = superClass(obf); + String superClassHTML = superClass == null ? "" : (" extends " + superClass.toHTML(obf)); + + List superInterfaces = superInterfaces(obf); + String superInterfacesHTML = superInterfaces.isEmpty() ? "" + : (" implements " + superInterfaces.stream().map(t -> t.toHTML(obf)).collect(Collectors.joining(", "))); + + return name + superClassHTML + superInterfacesHTML; + } + + private Type superClass(boolean obf) { + Class superClass = runtimeClass().getSuperclass(); + if (superClass == null) + return null; + ClassMapping cm = (IS_SERVER_OBFUSCATED ? CLASSES_BY_OBF : CLASSES_BY_MOJ).get(superClass.getName()); + return (cm != null) ? cm.toType(obf) : Type.of(superClass); + } + + private List superInterfaces(boolean obf) { + Class[] interfaces = runtimeClass().getInterfaces(); + List types = new ArrayList<>(interfaces.length); + for (Class interfce : interfaces) { + ClassMapping cm = (IS_SERVER_OBFUSCATED ? CLASSES_BY_OBF : CLASSES_BY_MOJ).get(interfce.getName()); + types.add((cm != null) ? cm.toType(obf) : Type.of(interfce)); + } + return types; + } + + private String classKind() { + Class clazz = runtimeClass(); + if (clazz.isEnum()) + return "Enum"; + if (clazz.isAnnotation()) + return "Annotation"; + if (clazz.isInterface()) + return "Interface"; + if (clazz.isRecord()) + return "Record"; + if (clazz.isPrimitive()) + return "Primitive"; + return "Class"; + } + } + + + + + + + + + private static record MethodId(String name, List parametersType) implements Comparable { + @Override + public int compareTo(MethodId o) { + int cmp = name.compareTo(o.name); + if (cmp != 0) + return cmp; + return toString().compareTo(o.toString()); + } + + private String toHTML(boolean isObfClass) { + String paramsHTML = parametersType.stream().map(p -> p.toHTML(isObfClass)).collect(Collectors.joining(", ")); + String identifierHTML = "" + name + "(" + paramsHTML + ")"; + return identifierHTML; + } + + public String toString() { + String paramsStr = parametersType.stream().map(Type::toString).collect(Collectors.joining(", ")); + return name + "(" + paramsStr + ")"; + } + + } + + + + private static record MemberDesc>(I identifier, Type returnType) { + private String toHTML(boolean isObfClass) { + String identifierHTML = ""; + if (identifier instanceof MethodId mId) + identifierHTML = mId.toHTML(isObfClass); + else if (identifier instanceof String n) + identifierHTML = "" + n + ""; + return returnType.toHTML(isObfClass) + " " + identifierHTML; + } + + private static MemberDesc of(MappingTree.MethodMapping member, String namespace) { + String desc = member.getDesc(namespace); + try (StringReader descReader = new StringReader(desc)) { + char r = (char) descReader.read(); + if (r != '(') + throw new IllegalArgumentException("Invalid method description '" + desc + "'. Must start with '('."); + + List paramsType = new ArrayList<>(); + + while ((r = (char) descReader.read()) != ')') { + descReader.skip(-1); + paramsType.add(Type.parse(descReader)); + } + + Type retType = Type.parse(descReader); + return new MemberDesc<>(new MethodId(member.getName(namespace), Collections.unmodifiableList(paramsType)), retType); + } catch (IOException e) { + throw new RuntimeException("StringReader read error", e); + } + + } + + + private static MemberDesc of(MappingTree.FieldMapping member, String namespace) { + StringReader descReader = new StringReader(member.getDesc(namespace)); + return new MemberDesc<>(member.getName(namespace), Type.parse(descReader)); + } + } + + + + + private static class MemberMapping> { + private String type; + /* package */ MemberDesc obfDesc, mojDesc; + private MemberMapping(String type, MemberDesc obfDesc, MemberDesc mojDesc) { + this.obfDesc = obfDesc; + this.mojDesc = mojDesc; + } + + /* package */ void printHTML(PrintStream out) { + out.println("" + type + "" + obfDesc.toHTML(true) + "" + mojDesc.toHTML(false) + ""); + } + + private static MemberMapping of(MappingTree.MethodMapping mioMapping) { + return new MemberMapping<>("Method", MemberDesc.of(mioMapping, OBF_NAMESPACE), MemberDesc.of(mioMapping, MOJ_NAMESPACE)); + } + + private static MemberMapping of(MappingTree.FieldMapping mioMapping) { + return new MemberMapping<>("Field", MemberDesc.of(mioMapping, OBF_NAMESPACE), MemberDesc.of(mioMapping, MOJ_NAMESPACE)); + } + + } + + + + + + + + /* package */ static String binaryClassName(String cl) { + return cl.replace('/', '.'); + } + + + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/reflect/OBCReflect.java b/Paper/src/main/java/fr/pandacube/lib/paper/reflect/OBCReflect.java new file mode 100644 index 0000000..212e9b5 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/reflect/OBCReflect.java @@ -0,0 +1,31 @@ +package fr.pandacube.lib.paper.reflect; + +import org.bukkit.Bukkit; + +import fr.pandacube.lib.core.util.Reflect; +import fr.pandacube.lib.core.util.Reflect.ReflectClass; + +public class OBCReflect { + + private static final String OBC_PACKAGE_PREFIX = "org.bukkit.craftbukkit."; + + private static final String OBC_PACKAGE_VERSION; + + static { + String name = Bukkit.getServer().getClass().getName() + .substring(OBC_PACKAGE_PREFIX.length()); + name = name.substring(0, name.indexOf(".")); + + OBC_PACKAGE_VERSION = name; + } + + + + public static ReflectClass ofClass(String obcClass) throws ClassNotFoundException { + return Reflect.ofClass(OBC_PACKAGE_PREFIX + OBC_PACKAGE_VERSION + "." + obcClass); + } + + + + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/reflect/ReflectRegistry.java b/Paper/src/main/java/fr/pandacube/lib/paper/reflect/ReflectRegistry.java new file mode 100644 index 0000000..6771ad7 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/reflect/ReflectRegistry.java @@ -0,0 +1,144 @@ +package fr.pandacube.lib.paper.reflect; + +import org.bukkit.NamespacedKey; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import com.mojang.brigadier.tree.CommandNode; + +import fr.pandacube.lib.core.util.Reflect; +import fr.pandacube.lib.core.util.Reflect.ReflectClass; +import fr.pandacube.lib.core.util.Reflect.ReflectConstructor; +import fr.pandacube.lib.core.util.Reflect.ReflectField; +import fr.pandacube.lib.core.util.Reflect.ReflectMethod; +import fr.pandacube.lib.paper.reflect.NMSReflect.ClassMapping; + +public class ReflectRegistry { + + public static final ClassMapping NMS_SHAREDCONSTANTS = wrapEx(() -> NMSReflect.mojClass("net.minecraft.SharedConstants")); + public static final ReflectMethod NMS_SHAREDCONSTANTS_GETCURRENTVERSION = wrapEx(() -> NMS_SHAREDCONSTANTS.mojMethod("getCurrentVersion")); + + + public static final ClassMapping NMS_WORLDVERSION = wrapEx(() -> NMSReflect.mojClass("net.minecraft.WorldVersion")); + public static final ReflectMethod NMS_WORLDVERSION_GETPROTOCOLVERSION = wrapEx(() -> NMS_WORLDVERSION.runtimeReflect().method("getProtocolVersion")); + + + public static final ClassMapping NMS_DEDICATEDSERVER = wrapEx(() -> NMSReflect.mojClass("net.minecraft.server.dedicated.DedicatedServer")); + public static final ReflectField NMS_DEDICATEDSERVER_VANILLACOMMANDDISPATCHER = wrapEx(() -> NMS_DEDICATEDSERVER.runtimeReflect().field("vanillaCommandDispatcher")); + + + public static final ClassMapping NMS_COMMANDS = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.Commands")); + public static final ReflectField NMS_COMMANDS_DISPATCHER = wrapEx(() -> NMS_DEDICATEDSERVER.mojField("dispatcher")); + + + public static final ClassMapping NMS_VEC3 = wrapEx(() -> NMSReflect.mojClass("net.minecraft.world.phys.Vec3")); + + + public static final ClassMapping NMS_COMPONENT = wrapEx(() -> NMSReflect.mojClass("net.minecraft.network.chat.Component")); + + + public static final ClassMapping NMS_COMMANDSOURCESTACK = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.CommandSourceStack")); + + + public static final ClassMapping NMS_ENTITYARGUMENT = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.EntityArgument")); + public static final ReflectConstructor NMS_ENTITYARGUMENT_CONSTRUCTOR = wrapEx(() -> NMS_ENTITYARGUMENT.runtimeReflect().constructor(boolean.class, boolean.class)); + + + public static final ClassMapping NMS_ENTITYSELECTOR = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.selector.EntitySelector")); + public static final ReflectMethod NMS_ENTITYSELECTOR_FINDENTITIES = wrapEx(() -> NMS_ENTITYSELECTOR.mojMethod("findEntities", NMS_COMMANDSOURCESTACK)); + public static final ReflectMethod NMS_ENTITYSELECTOR_FINDPLAYERS = wrapEx(() -> NMS_ENTITYSELECTOR.mojMethod("findPlayers", NMS_COMMANDSOURCESTACK)); + public static final ReflectMethod NMS_ENTITYSELECTOR_FINDSINGLEENTITY = wrapEx(() -> NMS_ENTITYSELECTOR.mojMethod("findSingleEntity", NMS_COMMANDSOURCESTACK)); + public static final ReflectMethod NMS_ENTITYSELECTOR_FINDSINGLEPLAYER = wrapEx(() -> NMS_ENTITYSELECTOR.mojMethod("findSinglePlayer", NMS_COMMANDSOURCESTACK)); + + + public static final ClassMapping NMS_ENTITY = wrapEx(() -> NMSReflect.mojClass("net.minecraft.world.entity.Entity")); + public static final ReflectMethod NMS_ENTITY_GETBUKKITENTITY = wrapEx(() -> NMS_ENTITY.runtimeReflect().method("getBukkitEntity")); + + + + public static final ClassMapping NMS_COMPONENTARGUMENT = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.ComponentArgument")); + public static final ReflectConstructor NMS_COMPONENTARGUMENT_CONSTRUCTOR = wrapEx(() -> NMS_COMPONENTARGUMENT.runtimeReflect().constructor()); + + + public static final ClassMapping NMS_BLOCKSTATEARGUMENT = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.blocks.BlockStateArgument")); + public static final ReflectConstructor NMS_BLOCKSTATEARGUMENT_CONSTRUCTOR = wrapEx(() -> NMS_BLOCKSTATEARGUMENT.runtimeReflect().constructor()); + + + public static final ClassMapping NMS_VEC3ARGUMENT = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.coordinates.Vec3Argument")); + public static final ReflectMethod NMS_VEC3ARGUMENT_VEC3 = wrapEx(() -> NMS_VEC3ARGUMENT.mojMethod("vec3", boolean.class)); + + + public static final ClassMapping NMS_COORDINATES = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.coordinates.Coordinates")); + public static final ReflectMethod NMS_COORDINATES_GETPOSITION = wrapEx(() -> NMS_COORDINATES.mojMethod("getPosition", NMS_COMMANDSOURCESTACK)); + public static final ReflectMethod NMS_COORDINATES_GETBLOCKPOS = wrapEx(() -> NMS_COORDINATES.mojMethod("getBlockPos", NMS_COMMANDSOURCESTACK)); + + + public static final ClassMapping NMS_BLOCKPOSARGUMENT = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.coordinates.BlockPosArgument")); + public static final ReflectMethod NMS_BLOCKPOSARGUMENT_BLOCKPOS = wrapEx(() -> NMS_BLOCKPOSARGUMENT.mojMethod("blockPos")); + + + public static final ClassMapping NMS_BLOCKPOS = wrapEx(() -> NMSReflect.mojClass("net.minecraft.core.BlockPos")); + public static final ReflectMethod NMS_BLOCKPOS_GETX = wrapEx(() -> NMS_BLOCKPOS.mojMethod("getX")); + public static final ReflectMethod NMS_BLOCKPOS_GETY = wrapEx(() -> NMS_BLOCKPOS.mojMethod("getY")); + public static final ReflectMethod NMS_BLOCKPOS_GETZ = wrapEx(() -> NMS_BLOCKPOS.mojMethod("getZ")); + + + public static final ClassMapping NMS_RESOURCELOCATIONARGUMENT = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.ResourceLocationArgument")); + public static final ReflectConstructor NMS_RESOURCELOCATIONARGUMENT_CONSTRUCTOR = wrapEx(() -> NMS_RESOURCELOCATIONARGUMENT.runtimeReflect().constructor()); + + + public static final ClassMapping NMS_RESOURCELOCATION = wrapEx(() -> NMSReflect.mojClass("net.minecraft.resources.ResourceLocation")); + + + public static final ClassMapping NMS_GAMEPROFILEARGUMENT = wrapEx(() -> NMSReflect.mojClass("net.minecraft.commands.arguments.GameProfileArgument")); + public static final ReflectConstructor NMS_GAMEPROFILEARGUMENT_CONSTRUCTOR = wrapEx(() -> NMS_GAMEPROFILEARGUMENT.runtimeReflect().constructor()); + + + + public static final ReflectClass OBC_CRAFTSERVER = wrapEx(() -> OBCReflect.ofClass("CraftServer")); + public static final ReflectMethod OBC_CRAFTSERVER_GETSERVER = wrapEx(() -> OBC_CRAFTSERVER.method("getServer")); + + + public static final ReflectClass OBC_VANILLACOMMANDWRAPPER = wrapEx(() -> OBCReflect.ofClass("command.VanillaCommandWrapper")); + @SuppressWarnings("unchecked") + public static final ReflectConstructor OBC_VANILLACOMMANDWRAPPER_CONSTRUCTOR = + (ReflectConstructor) wrapEx(() -> OBC_VANILLACOMMANDWRAPPER.constructor( + NMS_COMMANDS.runtimeClass(), + CommandNode.class + )); + public static final ReflectField OBC_VANILLACOMMANDWRAPPER_VANILLACOMMAND = wrapEx(() -> OBC_VANILLACOMMANDWRAPPER.field("vanillaCommand")); + public static final ReflectMethod OBC_VANILLACOMMANDWRAPPER_GETLISTENER = wrapEx(() -> OBC_VANILLACOMMANDWRAPPER.method("getListener", CommandSender.class)); + + + public static final ReflectClass OBC_CRAFTNAMESPACEDKEY = wrapEx(() -> OBCReflect.ofClass("util.CraftNamespacedKey")); + public static final ReflectMethod OBC_CRAFTNAMESPACEDKEY_TOMINECRAFT = wrapEx(() -> OBC_CRAFTNAMESPACEDKEY.method("toMinecraft", NamespacedKey.class)); + public static final ReflectMethod OBC_CRAFTNAMESPACEDKEY_FROMMINECRAFT = wrapEx(() -> OBC_CRAFTNAMESPACEDKEY.method("fromMinecraft", NMS_RESOURCELOCATION.runtimeClass())); + + + public static final ReflectClass OBC_CRAFTVECTOR = wrapEx(() -> OBCReflect.ofClass("util.CraftVector")); + public static final ReflectMethod OBC_CRAFTVECTOR_TOBUKKIT_VEC3 = wrapEx(() -> OBC_VANILLACOMMANDWRAPPER.method("toBukkit", NMS_VEC3.runtimeClass())); + + + public static final ReflectClass PAPER_PAPERADVENTURE = wrapEx(() -> Reflect.ofClass("io.papermc.paper.adventure.PaperAdventure")); + public static final ReflectMethod PAPER_PAPERADVENTURE_ASADVENTURE = wrapEx(() -> PAPER_PAPERADVENTURE.method("asAdventure", NMS_COMPONENT.runtimeClass())); + + + public static final ReflectClass BRIGADIER_COMMANDNODE = Reflect.ofClass(CommandNode.class); + public static final ReflectMethod BRIGADIER_COMMANDNODE_REMOVECOMMAND = wrapEx(() -> BRIGADIER_COMMANDNODE.method("removeCommand", String.class)); + + + + + private interface SupplierException { + public T get() throws Exception; + } + + private static T wrapEx(SupplierException prv) { + try { + return prv.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/Paper/src/main/java/fr/pandacube/lib/paper/reflect/Type.java b/Paper/src/main/java/fr/pandacube/lib/paper/reflect/Type.java new file mode 100644 index 0000000..0d2a3a5 --- /dev/null +++ b/Paper/src/main/java/fr/pandacube/lib/paper/reflect/Type.java @@ -0,0 +1,196 @@ +package fr.pandacube.lib.paper.reflect; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import fr.pandacube.lib.core.util.Reflect.ReflectClass; +import fr.pandacube.lib.paper.reflect.NMSReflect.ClassMapping; + +public class Type implements Comparable { + private final String type; + private final int arrayDepth; + + /* package */ Type(String type, int arrayDepth) { + this.type = type; + this.arrayDepth = arrayDepth; + } + + @Override + public boolean equals(Object obj) { + return obj != null && obj instanceof Type ot && type.equals(ot.type) && arrayDepth == ot.arrayDepth; + } + @Override + public int hashCode() { + return type.hashCode() ^ arrayDepth; + } + + @Override + public int compareTo(Type o) { + return toString().compareTo(o.toString()); + } + + Class toClass() throws ClassNotFoundException { + + Class cl = switch(type) { + case "boolean" -> boolean.class; + case "byte" -> byte.class; + case "char" -> char.class; + case "double" -> double.class; + case "float" -> float.class; + case "int" -> int.class; + case "long" -> long.class; + case "short" -> short.class; + case "void" -> void.class; + default -> Class.forName(type); + }; + + for (int i = 0; i < arrayDepth; i++) { + cl = cl.arrayType(); + } + + return cl; + } + + public Type arrayType() { + return new Type(type, arrayDepth + 1); + } + + /* package */ static Type of(Class cl) { + int arrayDepth = 0; + while (cl.isArray()) { + cl = cl.getComponentType(); + arrayDepth++; + } + return new Type(cl.getName(), arrayDepth); + } + + public static Type of(ReflectClass rc) { + return arrayOf(rc, 0); + } + + public static Type arrayOf(ReflectClass rc, int arrayDepth) { + return new Type(rc.get().getName(), arrayDepth); + } + + public static Type mojOf(ClassMapping cm) { + return arrayMojOf(cm, 0); + } + + public static Type arrayMojOf(ClassMapping cm, int arrayDepth) { + return new Type(cm.mojName, arrayDepth); + } + + /* package */ static Type toType(Object typeObj) { + Objects.requireNonNull(typeObj, "typeObj cannot be null"); + if (typeObj instanceof Class cl) { + return of(cl); + } + else if (typeObj instanceof ClassMapping cm) { + return mojOf(cm); + } + else if (typeObj instanceof ReflectClass rc) { + return of(rc); + } + else if (typeObj instanceof Type t) { + return t; + } + else + throw new IllegalArgumentException("Unsupported object of type " + typeObj.getClass()); + } + + /* package */ String toHTML(boolean isObfClass) { + ClassMapping clMapping = (isObfClass ? NMSReflect.CLASSES_BY_OBF : NMSReflect.CLASSES_BY_MOJ).get(type); + String typeHTML; + if (clMapping != null) { + typeHTML = clMapping.toClickableHTML(isObfClass); + } + else { + String classToPrint = type; + String classSimpleName = classToPrint.substring(classToPrint.lastIndexOf('.') + 1); + String htmlTitle = classSimpleName.equals(classToPrint) ? "" : (" title='" + classToPrint + "'"); + if (!htmlTitle.equals("")) { + typeHTML = "" + classSimpleName + ""; + } + else { + typeHTML = classSimpleName; + } + } + + + return typeHTML + "[]".repeat(arrayDepth); + } + + public String toString() { + return type + "[]".repeat(arrayDepth); + } + + + + + + + /* package */ static Type parse(StringReader desc) { + try { + StringBuilder sbRaw = new StringBuilder(); + int arrayDepth = 0; + char c; + while ((c = (char) desc.read()) == '[') { + sbRaw.append(c); + arrayDepth++; + } + sbRaw.append(c); + String type = switch(c) { + case 'Z' -> "boolean"; + case 'B' -> "byte"; + case 'C' -> "char"; + case 'D' -> "double"; + case 'F' -> "float"; + case 'I' -> "int"; + case 'J' -> "long"; + case 'S' -> "short"; + case 'L' -> { + StringBuilder sbClass = new StringBuilder(); + char r; + while ((r = (char) desc.read()) != ';') { + sbRaw.append(c); + sbClass.append(r); + } + sbRaw.append(c); + yield NMSReflect.binaryClassName(sbClass.toString()); + } + default -> "void"; + }; + return new Type(type, arrayDepth); + } catch (IOException e) { + throw new RuntimeException("StringReader read error", e); + } + } + + + + + /* package */ static List toTypeList(List paramsType) { + List types = new ArrayList<>(paramsType.size()); + for (int i = 0; i < types.size(); i++) { + Object param = paramsType.get(i); + try { + types.add(Type.toType(param)); + } catch (NullPointerException|IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid parameterType at index " + i, e); + } + } + return types; + } + + /* package */ static Class[] toClassArray(List types) throws ClassNotFoundException { + Class[] classes = new Class[types.size()]; + for (int i = 0; i < types.size(); i++) { + classes[i] = types.get(i).toClass(); + } + return classes; + } + +} \ No newline at end of file