diff --git a/bukkit/src/de/lixfel/altauth/bukkit/AltAuthBukkit.java b/bukkit/src/de/lixfel/altauth/bukkit/AltAuthBukkit.java index d81290f..8311c9e 100644 --- a/bukkit/src/de/lixfel/altauth/bukkit/AltAuthBukkit.java +++ b/bukkit/src/de/lixfel/altauth/bukkit/AltAuthBukkit.java @@ -2,7 +2,6 @@ package de.lixfel.altauth.bukkit; -import de.lixfel.tinyprotocol.TinyProtocol; import lombok.Getter; import lombok.Setter; import org.bukkit.plugin.java.JavaPlugin; @@ -27,7 +26,7 @@ public class AltAuthBukkit extends JavaPlugin { saveDefaultConfig(); String altAuthServer = getConfig().getString("altauth-proxy"); - TinyProtocol.init(); + ProtocolInjector.init(); serviceInjector = new SessionServiceInjector(altAuthServer); requestInjector = new EncryptionRequestInjector(altAuthServer); } @@ -36,6 +35,6 @@ public class AltAuthBukkit extends JavaPlugin { public void onDisable() { requestInjector.remove(); serviceInjector.revert(); - TinyProtocol.instance.close(); + ProtocolInjector.instance.close(); } } diff --git a/bukkit/src/de/lixfel/altauth/bukkit/EncryptionRequestInjector.java b/bukkit/src/de/lixfel/altauth/bukkit/EncryptionRequestInjector.java index e1f9558..a284bcb 100644 --- a/bukkit/src/de/lixfel/altauth/bukkit/EncryptionRequestInjector.java +++ b/bukkit/src/de/lixfel/altauth/bukkit/EncryptionRequestInjector.java @@ -2,30 +2,29 @@ package de.lixfel.altauth.bukkit; -import de.lixfel.tinyprotocol.Reflection; -import de.lixfel.tinyprotocol.TinyProtocol; +import de.lixfel.ReflectionUtil; import org.bukkit.entity.Player; public class EncryptionRequestInjector { - private static final Class EncryptionRequest = Reflection.getClass("{nms.network.protocol.login}.PacketLoginOutEncryptionBegin"); - private static final Reflection.FieldAccessor ServerID = Reflection.getField(EncryptionRequest, String.class, 0); + private static final Class EncryptionRequest = ReflectionUtil.getClass("net.minecraft.network.protocol.login.PacketLoginOutEncryptionBegin"); + private static final ReflectionUtil.FieldWrapper ServerID = ReflectionUtil.getField(EncryptionRequest, String.class, 0); private final String altAuthServer; public EncryptionRequestInjector(String altAuthServer) { this.altAuthServer = altAuthServer; - TinyProtocol.instance.addFilter(EncryptionRequest, this::handleEncryptionRequest); + ProtocolInjector.instance.addFilter(EncryptionRequest, this::handleEncryptionRequest); } public Object handleEncryptionRequest(Player player, Object packet) { ServerID.set(packet, altAuthServer); - TinyProtocol.instance.getInterceptor(player).ifPresent(TinyProtocol.PacketInterceptor::close); + ProtocolInjector.instance.getInterceptor(player).ifPresent(ProtocolInjector.PacketInterceptor::close); return packet; } public void remove() { - TinyProtocol.instance.removeFilter(EncryptionRequest, this::handleEncryptionRequest); + ProtocolInjector.instance.removeFilter(EncryptionRequest, this::handleEncryptionRequest); } } diff --git a/bukkit/src/de/lixfel/tinyprotocol/TinyProtocol.java b/bukkit/src/de/lixfel/altauth/bukkit/ProtocolInjector.java similarity index 70% rename from bukkit/src/de/lixfel/tinyprotocol/TinyProtocol.java rename to bukkit/src/de/lixfel/altauth/bukkit/ProtocolInjector.java index 43ef546..4f269ef 100644 --- a/bukkit/src/de/lixfel/tinyprotocol/TinyProtocol.java +++ b/bukkit/src/de/lixfel/altauth/bukkit/ProtocolInjector.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT -package de.lixfel.tinyprotocol; +package de.lixfel.altauth.bukkit; -import de.lixfel.tinyprotocol.Reflection.FieldAccessor; -import de.lixfel.altauth.bukkit.AltAuthBukkit; +import de.lixfel.ReflectionUtil; +import de.lixfel.ReflectionUtil.FieldWrapper; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; @@ -22,21 +22,21 @@ import java.util.*; import java.util.function.BiFunction; import java.util.logging.Level; -public class TinyProtocol implements Listener { +// ReflectionUtil heavily inspired by TinyProtocol +public class ProtocolInjector implements Listener { - private static final Class craftServer = Reflection.getClass("{obc}.CraftServer"); - private static final Class dedicatedPlayerList = Reflection.getClass("{nms.server.dedicated}.DedicatedPlayerList"); - private static final FieldAccessor getPlayerList = Reflection.getField(craftServer, dedicatedPlayerList, 0); - private static final Class playerList = Reflection.getClass("{nms.server.players}.PlayerList"); - private static final Class minecraftServer = Reflection.getClass("{nms.server}.MinecraftServer"); - private static final FieldAccessor getMinecraftServer = Reflection.getField(playerList, minecraftServer, 0); - private static final Class serverConnection = Reflection.getClass("{nms.server.network}.ServerConnection"); - private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServer, serverConnection, 0); - private static final Class networkManager = Reflection.getClass("{nms.network}.NetworkManager"); - private static final FieldAccessor getConnections = Reflection.getField(serverConnection, List.class, 0, networkManager); + private static final Class craftServer = ReflectionUtil.getClass("org.bukkit.craftbukkit.CraftServer"); + private static final Class dedicatedPlayerList = ReflectionUtil.getClass("net.minecraft.server.dedicated.DedicatedPlayerList"); + private static final FieldWrapper getPlayerList = ReflectionUtil.getField(craftServer, dedicatedPlayerList, 0); + private static final Class playerList = ReflectionUtil.getClass("net.minecraft.server.players.PlayerList"); + private static final Class minecraftServer = ReflectionUtil.getClass("net.minecraft.server.MinecraftServer"); + private static final FieldWrapper getMinecraftServer = ReflectionUtil.getField(playerList, minecraftServer, 0); + private static final Class serverConnection = ReflectionUtil.getClass("net.minecraft.server.network.ServerConnection"); + private static final FieldWrapper getServerConnection = ReflectionUtil.getField(minecraftServer, serverConnection, 0); + private static final Class networkManager = ReflectionUtil.getClass("net.minecraft.network.NetworkManager"); + private static final FieldWrapper getConnections = ReflectionUtil.getField(serverConnection, List.class, 0, networkManager); - public static final TinyProtocol instance = new TinyProtocol(AltAuthBukkit.getInstance()); - private static int id = 0; + public static final ProtocolInjector instance = new ProtocolInjector(AltAuthBukkit.getInstance()); public static void init() { //enforce init @@ -50,9 +50,9 @@ public class TinyProtocol implements Listener { private final Map, List>> packetFilters = new HashMap<>(); private final Map playerInterceptors = new HashMap<>(); - private TinyProtocol(final Plugin plugin) { + private ProtocolInjector(final Plugin plugin) { this.plugin = plugin; - this.handlerName = "tiny-" + plugin.getName() + "-" + ++id; + this.handlerName = "altauth"; this.connections = getConnections.get(getServerConnection.get(getMinecraftServer.get(getPlayerList.get(plugin.getServer())))); plugin.getServer().getPluginManager().registerEvents(this, plugin); @@ -89,14 +89,6 @@ public class TinyProtocol implements Listener { packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter); } - public void sendPacket(Player player, Object packet) { - getInterceptor(player).ifPresent(i -> i.sendPacket(packet)); - } - - public void receivePacket(Player player, Object packet) { - getInterceptor(player).ifPresent(i -> i.receivePacket(packet)); - } - public final void close() { if(closed) return; @@ -115,8 +107,8 @@ public class TinyProtocol implements Listener { } } - private static final FieldAccessor getChannel = Reflection.getField(networkManager, Channel.class, 0); - private static final FieldAccessor getUUID = Reflection.getField(networkManager, UUID.class, 0); + private static final FieldWrapper getChannel = ReflectionUtil.getField(networkManager, Channel.class, 0); + private static final FieldWrapper getUUID = ReflectionUtil.getField(networkManager, UUID.class, 0); public final class PacketInterceptor extends ChannelDuplexHandler { private final Player player; diff --git a/bukkit/src/de/lixfel/altauth/bukkit/SessionServiceInjector.java b/bukkit/src/de/lixfel/altauth/bukkit/SessionServiceInjector.java index e0984b9..2161d80 100644 --- a/bukkit/src/de/lixfel/altauth/bukkit/SessionServiceInjector.java +++ b/bukkit/src/de/lixfel/altauth/bukkit/SessionServiceInjector.java @@ -2,7 +2,7 @@ package de.lixfel.altauth.bukkit; -import de.lixfel.tinyprotocol.Reflection; +import de.lixfel.ReflectionUtil; import java.net.MalformedURLException; import java.net.URL; @@ -10,10 +10,10 @@ import java.util.logging.Level; public class SessionServiceInjector { - private static final Class SESSION_SERVICE = Reflection.getClass("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService"); + private static final Class SESSION_SERVICE = ReflectionUtil.getClass("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService"); - private static final Reflection.FieldAccessor JOIN_URL = Reflection.getField(SESSION_SERVICE, "JOIN_URL", URL.class); - private static final Reflection.FieldAccessor CHECK_URL = Reflection.getField(SESSION_SERVICE, "CHECK_URL", URL.class); + private static final ReflectionUtil.FieldWrapper JOIN_URL = ReflectionUtil.getField(SESSION_SERVICE, URL.class, "JOIN_URL"); + private static final ReflectionUtil.FieldWrapper CHECK_URL = ReflectionUtil.getField(SESSION_SERVICE, URL.class, "CHECK_URL"); private final URL joinUrlBackup; private final URL checkUrlBackup; diff --git a/bungee/src/de/lixfel/altauth/bungee/AltAuthHandler.java b/bungee/src/de/lixfel/altauth/bungee/AltAuthHandler.java index a1fc8f8..0c94f54 100644 --- a/bungee/src/de/lixfel/altauth/bungee/AltAuthHandler.java +++ b/bungee/src/de/lixfel/altauth/bungee/AltAuthHandler.java @@ -2,7 +2,7 @@ package de.lixfel.altauth.bungee; -import de.lixfel.tinyprotocol.Reflection; +import de.lixfel.ReflectionUtil; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageCodec; import net.md_5.bungee.BungeeCord; @@ -36,12 +36,12 @@ import java.util.logging.Level; public class AltAuthHandler extends MessageToMessageCodec { - private static final Class InitialHandler = Reflection.getClass("net.md_5.bungee.connection.InitialHandler"); - private static final Reflection.FieldAccessor CH = Reflection.getField(InitialHandler, ChannelWrapper.class, 0); - private static final Reflection.FieldAccessor LoginProfile = Reflection.getField(InitialHandler, "loginProfile", LoginResult.class); - private static final Reflection.FieldAccessor Name = Reflection.getField(InitialHandler, "name", String.class); - private static final Reflection.FieldAccessor UniqueId = Reflection.getField(InitialHandler, "uniqueId", UUID.class); - private static final Reflection.MethodInvoker Finish = Reflection.getMethod(InitialHandler, "finish"); + private static final Class InitialHandler = ReflectionUtil.getClass("net.md_5.bungee.connection.InitialHandler"); + private static final ReflectionUtil.FieldWrapper CH = ReflectionUtil.getField(InitialHandler, ChannelWrapper.class, 0); + private static final ReflectionUtil.FieldWrapper LoginProfile = ReflectionUtil.getField(InitialHandler, LoginResult.class, "loginProfile"); + private static final ReflectionUtil.FieldWrapper Name = ReflectionUtil.getField(InitialHandler, String.class, "name"); + private static final ReflectionUtil.FieldWrapper UniqueId = ReflectionUtil.getField(InitialHandler, UUID.class, "uniqueId"); + private static final ReflectionUtil.MethodWrapper Finish = ReflectionUtil.getMethod(InitialHandler, "finish"); private final ProxyServer bungee; private final String altAuthUrl; diff --git a/common/src/de/lixfel/ReflectionUtil.java b/common/src/de/lixfel/ReflectionUtil.java new file mode 100644 index 0000000..bc15be9 --- /dev/null +++ b/common/src/de/lixfel/ReflectionUtil.java @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MIT + +package de.lixfel; + +import org.bukkit.Bukkit; + +import java.lang.reflect.*; +import java.util.Arrays; + +// ReflectionUtil heavily inspired by TinyProtocol +public final class ReflectionUtil { + private ReflectionUtil() {} + + private static final String ORG_BUKKIT_CRAFTBUKKIT; + private static final boolean REPLACE_NET_MINECRAFT; + static { + String craftbukkitPackage; + boolean legacyNms; + try { + craftbukkitPackage = Bukkit.getServer().getClass().getPackage().getName(); + legacyNms = Integer.parseInt(craftbukkitPackage.split("[.](?=[^.]*$)")[1].split("_")[1]) < 17; + } catch (NoClassDefFoundError e) { + craftbukkitPackage = ""; + legacyNms = false; + } + ORG_BUKKIT_CRAFTBUKKIT = craftbukkitPackage; + REPLACE_NET_MINECRAFT = legacyNms; + } + + private static final String LEGACY_NET_MINECRAFT_SERVER = ORG_BUKKIT_CRAFTBUKKIT.replace("org.bukkit.craftbukkit", "net.minecraft.server"); + + public static FieldWrapper getField(Class target, Class fieldType, String name) { + return getField(target, fieldType, name, 0); + } + + public static FieldWrapper getField(Class target, Class fieldType, int index, Class... parameters) { + return getField(target, fieldType, null, index, parameters); + } + + private static FieldWrapper getField(Class target, Class fieldType, String name, int index, Class... parameters) { + for (final Field field : target.getDeclaredFields()) { + if(matching(field, name, fieldType, parameters) && index-- <= 0) { + field.setAccessible(true); + + return new FieldWrapper() { + @Override + @SuppressWarnings("unchecked") + public T get(Object target) { + try { + return (T) field.get(target); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Access denied", e); + } + } + + @Override + public void set(Object target, Object value) { + try { + field.set(target, value); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Access denied", e); + } + } + }; + } + } + + if (target.getSuperclass() != null) + return getField(target.getSuperclass(), fieldType, name, index); + + throw new IllegalArgumentException("Field not found"); + } + + private static boolean matching(Field field, String name, Class fieldType, Class... parameters) { + if(name != null && !field.getName().equals(name)) + return false; + + if(!fieldType.isAssignableFrom(field.getType())) + return false; + + if(parameters.length > 0) { + Type[] arguments = ((ParameterizedType)field.getGenericType()).getActualTypeArguments(); + + for(int i = 0; i < parameters.length; i++) { + if(arguments[i] != parameters[i]) + return false; + } + } + return true; + } + + public static MethodWrapper getMethod(Class clazz, String methodName, Class... args) { + return getMethod(clazz, null, methodName, args); + } + + public static MethodWrapper getMethod(Class clazz, Class returnType, String methodName, Class... args) { + for (final Method method : clazz.getDeclaredMethods()) { + if ((methodName == null || method.getName().equals(methodName)) + && (returnType == null || method.getReturnType().equals(returnType)) + && Arrays.equals(method.getParameterTypes(), args)) { + method.setAccessible(true); + + return (target, arguments) -> { + try { + return method.invoke(target, arguments); + } catch (Exception e) { + throw new RuntimeException("Exception on call", e); + } + }; + } + } + + if (clazz.getSuperclass() != null) + return getMethod(clazz.getSuperclass(), methodName, args); + + throw new IllegalStateException("Method not found"); + } + + public static Class getClass(String name) { + try { + if(name.startsWith("org.bukkit.craftbukkit")) { + return Class.forName(ORG_BUKKIT_CRAFTBUKKIT + name.substring(22)); + } else if(REPLACE_NET_MINECRAFT && name.startsWith("net.minecraft")) { + return Class.forName(LEGACY_NET_MINECRAFT_SERVER + "." + name.split("[.](?=[^.]*$)")[1]); + } else { + return Class.forName(name); + } + } catch (ClassNotFoundException e) { + throw new NoClassDefFoundError(); + } + } + + public interface MethodWrapper { + Object invoke(Object target, Object... arguments); + } + + public interface FieldWrapper { + T get(Object target); + + void set(Object target, Object value); + } +} diff --git a/common/src/de/lixfel/tinyprotocol/Reflection.java b/common/src/de/lixfel/tinyprotocol/Reflection.java deleted file mode 100644 index 1a54f24..0000000 --- a/common/src/de/lixfel/tinyprotocol/Reflection.java +++ /dev/null @@ -1,412 +0,0 @@ -// SPDX-License-Identifier: MIT - -package de.lixfel.tinyprotocol; - -import jdk.internal.misc.Unsafe; -import org.bukkit.Bukkit; - -import java.lang.reflect.*; -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * An utility class that simplifies reflection in Bukkit plugins. - * - * @author Kristian - */ -public final class Reflection { - - private static final int MINECRAFT_VERSION; - - static { - int mcVersion; - try { - mcVersion = Integer.parseInt(Bukkit.getServer().getClass().getPackage().getName().split("[.](?=[^.]*$)")[1].split("_")[1]); - } catch (NoClassDefFoundError e) { - mcVersion = 0; - } - MINECRAFT_VERSION = mcVersion; - } - - - /** - * An interface for invoking a specific constructor. - */ - public interface ConstructorInvoker { - /** - * Invoke a constructor for a specific class. - * - * @param arguments - the arguments to pass to the constructor. - * @return The constructed object. - */ - Object invoke(Object... arguments); - } - - /** - * An interface for invoking a specific method. - */ - public interface MethodInvoker { - /** - * Invoke a method on a specific target object. - * - * @param target - the target object, or NULL for a static method. - * @param arguments - the arguments to pass to the method. - * @return The return value, or NULL if is void. - */ - Object invoke(Object target, Object... arguments); - } - - /** - * An interface for retrieving the field content. - * - * @param - field type. - */ - public interface FieldAccessor { - /** - * Retrieve the content of a field. - * - * @param target - the target object, or NULL for a static field. - * @return The value of the field. - */ - T get(Object target); - - /** - * Set the content of a field. - * - * @param target - the target object, or NULL for a static field. - * @param value - the new value of the field. - */ - void set(Object target, Object value); - - /** - * Determine if the given object has this field. - * - * @param target - the object to test. - * @return TRUE if it does, FALSE otherwise. - */ - boolean hasField(Object target); - } - - // Deduce the net.minecraft.server.v* package - private static final String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName(); - private static final String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server"); - private static final String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", ""); - - // Variable replacement - private static final Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}"); - - private Reflection() { - // Seal class - } - - /** - * Retrieve a field accessor for a specific field type and name. - * - * @param target - the target type. - * @param name - the name of the field, or NULL to ignore. - * @param fieldType - a compatible field type. - * @return The field accessor. - */ - public static FieldAccessor getField(Class target, String name, Class fieldType) { - return getField(target, name, fieldType, 0); - } - - /** - * Retrieve a field accessor for a specific field type and name. - * - * @param className - lookup name of the class, see {@link #getClass(String)}. - * @param name - the name of the field, or NULL to ignore. - * @param fieldType - a compatible field type. - * @return The field accessor. - */ - public static FieldAccessor getField(String className, String name, Class fieldType) { - return getField(getClass(className), name, fieldType, 0); - } - - /** - * Retrieve a field accessor for a specific field type and name. - * - * @param target - the target type. - * @param fieldType - a compatible field type. - * @param index - the number of compatible fields to skip. - * @return The field accessor. - */ - public static FieldAccessor getField(Class target, Class fieldType, int index) { - return getField(target, null, fieldType, index); - } - - /** - * Retrieve a field accessor for a specific field type and name. - * - * @param className - lookup name of the class, see {@link #getClass(String)}. - * @param fieldType - a compatible field type. - * @param index - the number of compatible fields to skip. - * @return The field accessor. - */ - public static FieldAccessor getField(String className, Class fieldType, int index) { - return getField(getClass(className), fieldType, index); - } - - public static FieldAccessor getField(Class target, Class fieldType, int index, Class... parameters) { - return getField(target, null, fieldType, index, parameters); - } - - private static FieldAccessor getField(Class target, String name, Class fieldType, int index, Class... parameters) { - for (final Field field : target.getDeclaredFields()) { - if(matching(field, name, fieldType, parameters) && index-- <= 0) { - field.setAccessible(true); - - return new FieldAccessor() { - @Override - @SuppressWarnings("unchecked") - public T get(Object target) { - try { - return (T) field.get(target); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Cannot access reflection.", e); - } - } - - @Override - public void set(Object target, Object value) { - try { - field.set(target, value); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Cannot access reflection.", e); - } - } - - @Override - public boolean hasField(Object target) { - // target instanceof DeclaringClass - return field.getDeclaringClass().isAssignableFrom(target.getClass()); - } - }; - } - } - - // Search in parent classes - if (target.getSuperclass() != null) - return getField(target.getSuperclass(), name, fieldType, index); - - throw new IllegalArgumentException("Cannot find field with type " + fieldType); - } - - private static boolean matching(Field field, String name, Class fieldType, Class... parameters) { - if(name != null && !field.getName().equals(name)) - return false; - - if(!fieldType.isAssignableFrom(field.getType())) - return false; - - if(parameters.length > 0) { - Type[] arguments = ((ParameterizedType)field.getGenericType()).getActualTypeArguments(); - - for(int i = 0; i < parameters.length; i++) { - if(arguments[i] != parameters[i]) - return false; - } - } - return true; - } - - /** - * Search for the first publicly and privately defined method of the given name and parameter count. - * - * @param className - lookup name of the class, see {@link #getClass(String)}. - * @param methodName - the method name, or NULL to skip. - * @param params - the expected parameters. - * @return An object that invokes this specific method. - * @throws IllegalStateException If we cannot find this method. - */ - public static MethodInvoker getMethod(String className, String methodName, Class... params) { - return getTypedMethod(getClass(className), methodName, null, params); - } - - /** - * Search for the first publicly and privately defined method of the given name and parameter count. - * - * @param clazz - a class to start with. - * @param methodName - the method name, or NULL to skip. - * @param params - the expected parameters. - * @return An object that invokes this specific method. - * @throws IllegalStateException If we cannot find this method. - */ - public static MethodInvoker getMethod(Class clazz, String methodName, Class... params) { - return getTypedMethod(clazz, methodName, null, params); - } - - /** - * Search for the first publicly and privately defined method of the given name and parameter count. - * - * @param clazz - a class to start with. - * @param methodName - the method name, or NULL to skip. - * @param returnType - the expected return type, or NULL to ignore. - * @param params - the expected parameters. - * @return An object that invokes this specific method. - * @throws IllegalStateException If we cannot find this method. - */ - public static MethodInvoker getTypedMethod(Class clazz, String methodName, Class returnType, Class... params) { - for (final Method method : clazz.getDeclaredMethods()) { - if ((methodName == null || method.getName().equals(methodName)) - && (returnType == null || method.getReturnType().equals(returnType)) - && Arrays.equals(method.getParameterTypes(), params)) { - method.setAccessible(true); - - return (target, arguments) -> { - try { - return method.invoke(target, arguments); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot invoke method " + method, e); - } - }; - } - } - - // Search in every superclass - if (clazz.getSuperclass() != null) - return getMethod(clazz.getSuperclass(), methodName, params); - - throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params))); - } - - /** - * Search for the first publically and privately defined constructor of the given name and parameter count. - * - * @param className - lookup name of the class, see {@link #getClass(String)}. - * @param params - the expected parameters. - * @return An object that invokes this constructor. - * @throws IllegalStateException If we cannot find this method. - */ - public static ConstructorInvoker getConstructor(String className, Class... params) { - return getConstructor(getClass(className), params); - } - - /** - * Search for the first publically and privately defined constructor of the given name and parameter count. - * - * @param clazz - a class to start with. - * @param params - the expected parameters. - * @return An object that invokes this constructor. - * @throws IllegalStateException If we cannot find this method. - */ - public static ConstructorInvoker getConstructor(Class clazz, Class... params) { - for (final Constructor constructor : clazz.getDeclaredConstructors()) { - if (Arrays.equals(constructor.getParameterTypes(), params)) { - constructor.setAccessible(true); - - return arguments -> { - try { - return constructor.newInstance(arguments); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot invoke constructor " + constructor, e); - } - }; - } - } - - throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params))); - } - - /** - * Retrieve a class from its full name, without knowing its type on compile time. - *

- * This is useful when looking up fields by a NMS or OBC type. - *

- * - * @param lookupName - the class name with variables. - * @return The class. - */ - public static Class getUntypedClass(String lookupName) { - @SuppressWarnings({ "rawtypes", "unchecked" }) - Class clazz = (Class) getClass(lookupName); - return clazz; - } - - /** - * Retrieve a class from its full name. - *

- * Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table: - *

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
VariableContent
{nms}Actual package name of net.minecraft.server.VERSION
{obc}Actual pacakge name of org.bukkit.craftbukkit.VERSION
{version}The current Minecraft package VERSION, if any.
- * - * @param lookupName - the class name with variables. - * @return The looked up class. - * @throws IllegalArgumentException If a variable or class could not be found. - */ - public static Class getClass(String lookupName) { - try { - return Class.forName(expandVariables(lookupName)); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Cannot find " + expandVariables(lookupName), e); - } - } - - /** - * Expand variables such as "{nms}" and "{obc}" to their corresponding packages. - * - * @param name - the full name of the class. - * @return The expanded string. - */ - private static String expandVariables(String name) { - StringBuffer output = new StringBuffer(); - Matcher matcher = MATCH_VARIABLE.matcher(name); - - while (matcher.find()) { - String variable = matcher.group(1); - String replacement; - - // Expand all detected variables - if (variable.startsWith("nms")) { - if(MINECRAFT_VERSION >= 17) - replacement = "net.minecraft" + variable.substring(3); - else - replacement = NMS_PREFIX; - } else if ("obc".equals(variable)) - replacement = OBC_PREFIX; - else if ("version".equals(variable)) - replacement = VERSION; - else - throw new IllegalArgumentException("Unknown variable: " + variable); - - // Assume the expanded variables are all packages, and append a dot - if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.') - replacement += "."; - matcher.appendReplacement(output, Matcher.quoteReplacement(replacement)); - } - - matcher.appendTail(output); - return output.toString(); - } - - @SuppressWarnings("deprecation") - public static Object newInstance(Class clazz) { - try { - if (MINECRAFT_VERSION > 15) { - return Unsafe.getUnsafe().allocateInstance(clazz); - } else { - return clazz.newInstance(); - } - } catch (InstantiationException | IllegalAccessException e) { - throw new IllegalArgumentException("Could not create object", e); - } - } -}