New NMS and OBC Reflection utility classes
This commit is contained in:
parent
774b5f9e47
commit
06b89bd271
@ -16,6 +16,10 @@
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>fabricmc</id>
|
||||
<url>https://maven.fabricmc.net/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
@ -36,20 +40,27 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Paper (1.16 and before) -->
|
||||
<!-- <dependency>
|
||||
<groupId>com.destroystokyo.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${paper.version}-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency> -->
|
||||
<!-- Paper (1.17+) -->
|
||||
<!-- Paper -->
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${paper.version}-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-mojangapi</artifactId>
|
||||
<version>${paper.version}-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Needed to read obfuscation mapping file. Already included in Paper -->
|
||||
<dependency>
|
||||
<groupId>net.fabricmc</groupId>
|
||||
<artifactId>mapping-io</artifactId>
|
||||
<version>0.3.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -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<String, ClassMapping> CLASSES_BY_OBF = new TreeMap<>();
|
||||
/* package */ static final Map<String, ClassMapping> 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<ClassMapping> 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<ClassMapping> 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<ClassMapping> 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("<!DOCTYPE html><html><head>\n"
|
||||
+ "<title>" + title + "</title>\n"
|
||||
+ """
|
||||
<style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 1880px;
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
tr:nth-child(2n) {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
tr:hover {
|
||||
background-color: lightgray;
|
||||
}
|
||||
tr > *:first-child {
|
||||
text-align: right;
|
||||
padding-right: .5em;
|
||||
width: 6em;
|
||||
}
|
||||
tr > *:not(:first-child) {
|
||||
font-family: monospace;
|
||||
}
|
||||
td {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
font-size: 1.1em;
|
||||
border-top: solid 1px black;
|
||||
}
|
||||
a, a:visited {
|
||||
color: #222;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
+ "</head><body>\n"
|
||||
+ "<h1>" + title + "</h1>\n"
|
||||
+ "<table>");
|
||||
out.println("<tr><th>Namespace</th><th>" + OBF_NAMESPACE + "</th><th>" + MOJ_NAMESPACE + "</th></tr>");
|
||||
for (ClassMapping clazz : CLASSES_BY_OBF.values()) {
|
||||
clazz.printHTML(out);
|
||||
}
|
||||
out.println("</table></body></html>");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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<MethodId, MemberMapping<MethodId>> methodsByObf = new TreeMap<>();
|
||||
private final Map<MethodId, MemberMapping<MethodId>> methodsByMoj = new TreeMap<>();
|
||||
private final Map<String, MemberMapping<String>> fieldsByObf = new TreeMap<>();
|
||||
private final Map<String, MemberMapping<String>> 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<MethodId> 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<String> 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 = "<a href='#c" + id + "'" + htmlTitle + ">" + classSimpleName + "</a>";
|
||||
|
||||
return typeHTML;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ Type toType(boolean obf) {
|
||||
return new Type(obf ? obfName : mojName, 0);
|
||||
}
|
||||
|
||||
|
||||
private void printHTML(PrintStream out) {
|
||||
out.println("<tr id='c" + id + "'><th>" + classKind() + "</th><th>" + nameToHTML(true) + "</th><th>" + nameToHTML(false) + "</th></tr>");
|
||||
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<Type> 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<Type> superInterfaces(boolean obf) {
|
||||
Class<?>[] interfaces = runtimeClass().getInterfaces();
|
||||
List<Type> 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<Type> parametersType) implements Comparable<MethodId> {
|
||||
@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 = "<b>" + name + "</b>(" + paramsHTML + ")";
|
||||
return identifierHTML;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
String paramsStr = parametersType.stream().map(Type::toString).collect(Collectors.joining(", "));
|
||||
return name + "(" + paramsStr + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static record MemberDesc<I extends Comparable<I>>(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 = "<b>" + n + "</b>";
|
||||
return returnType.toHTML(isObfClass) + " " + identifierHTML;
|
||||
}
|
||||
|
||||
private static MemberDesc<MethodId> 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<Type> 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<String> 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<I extends Comparable<I>> {
|
||||
private String type;
|
||||
/* package */ MemberDesc<I> obfDesc, mojDesc;
|
||||
private MemberMapping(String type, MemberDesc<I> obfDesc, MemberDesc<I> mojDesc) {
|
||||
this.obfDesc = obfDesc;
|
||||
this.mojDesc = mojDesc;
|
||||
}
|
||||
|
||||
/* package */ void printHTML(PrintStream out) {
|
||||
out.println("<tr><td>" + type + "</td><td>" + obfDesc.toHTML(true) + "</td><td>" + mojDesc.toHTML(false) + "</td></tr>");
|
||||
}
|
||||
|
||||
private static MemberMapping<MethodId> of(MappingTree.MethodMapping mioMapping) {
|
||||
return new MemberMapping<>("Method", MemberDesc.of(mioMapping, OBF_NAMESPACE), MemberDesc.of(mioMapping, MOJ_NAMESPACE));
|
||||
}
|
||||
|
||||
private static MemberMapping<String> 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('/', '.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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<? extends Command> OBC_VANILLACOMMANDWRAPPER_CONSTRUCTOR =
|
||||
(ReflectConstructor<? extends Command>) 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<T> {
|
||||
public T get() throws Exception;
|
||||
}
|
||||
|
||||
private static <T> T wrapEx(SupplierException<T> prv) {
|
||||
try {
|
||||
return prv.get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
196
Paper/src/main/java/fr/pandacube/lib/paper/reflect/Type.java
Normal file
196
Paper/src/main/java/fr/pandacube/lib/paper/reflect/Type.java
Normal file
@ -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<Type> {
|
||||
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 = "<span" + htmlTitle + ">" + classSimpleName + "</span>";
|
||||
}
|
||||
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<Type> toTypeList(List<Object> paramsType) {
|
||||
List<Type> 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<Type> 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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user