Minify TinyProtocol and ReflectionUtil

Dieser Commit ist enthalten in:
Lixfel 2022-09-22 22:19:46 +02:00
Ursprung 5b2d86a0e8
Commit 9fbb8b4633
7 geänderte Dateien mit 181 neuen und 461 gelöschten Zeilen

Datei anzeigen

@ -2,7 +2,6 @@
package de.lixfel.altauth.bukkit; package de.lixfel.altauth.bukkit;
import de.lixfel.tinyprotocol.TinyProtocol;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -27,7 +26,7 @@ public class AltAuthBukkit extends JavaPlugin {
saveDefaultConfig(); saveDefaultConfig();
String altAuthServer = getConfig().getString("altauth-proxy"); String altAuthServer = getConfig().getString("altauth-proxy");
TinyProtocol.init(); ProtocolInjector.init();
serviceInjector = new SessionServiceInjector(altAuthServer); serviceInjector = new SessionServiceInjector(altAuthServer);
requestInjector = new EncryptionRequestInjector(altAuthServer); requestInjector = new EncryptionRequestInjector(altAuthServer);
} }
@ -36,6 +35,6 @@ public class AltAuthBukkit extends JavaPlugin {
public void onDisable() { public void onDisable() {
requestInjector.remove(); requestInjector.remove();
serviceInjector.revert(); serviceInjector.revert();
TinyProtocol.instance.close(); ProtocolInjector.instance.close();
} }
} }

Datei anzeigen

@ -2,30 +2,29 @@
package de.lixfel.altauth.bukkit; package de.lixfel.altauth.bukkit;
import de.lixfel.tinyprotocol.Reflection; import de.lixfel.ReflectionUtil;
import de.lixfel.tinyprotocol.TinyProtocol;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class EncryptionRequestInjector { public class EncryptionRequestInjector {
private static final Class<?> EncryptionRequest = Reflection.getClass("{nms.network.protocol.login}.PacketLoginOutEncryptionBegin"); private static final Class<?> EncryptionRequest = ReflectionUtil.getClass("net.minecraft.network.protocol.login.PacketLoginOutEncryptionBegin");
private static final Reflection.FieldAccessor<String> ServerID = Reflection.getField(EncryptionRequest, String.class, 0); private static final ReflectionUtil.FieldWrapper<String> ServerID = ReflectionUtil.getField(EncryptionRequest, String.class, 0);
private final String altAuthServer; private final String altAuthServer;
public EncryptionRequestInjector(String altAuthServer) { public EncryptionRequestInjector(String altAuthServer) {
this.altAuthServer = altAuthServer; this.altAuthServer = altAuthServer;
TinyProtocol.instance.addFilter(EncryptionRequest, this::handleEncryptionRequest); ProtocolInjector.instance.addFilter(EncryptionRequest, this::handleEncryptionRequest);
} }
public Object handleEncryptionRequest(Player player, Object packet) { public Object handleEncryptionRequest(Player player, Object packet) {
ServerID.set(packet, altAuthServer); ServerID.set(packet, altAuthServer);
TinyProtocol.instance.getInterceptor(player).ifPresent(TinyProtocol.PacketInterceptor::close); ProtocolInjector.instance.getInterceptor(player).ifPresent(ProtocolInjector.PacketInterceptor::close);
return packet; return packet;
} }
public void remove() { public void remove() {
TinyProtocol.instance.removeFilter(EncryptionRequest, this::handleEncryptionRequest); ProtocolInjector.instance.removeFilter(EncryptionRequest, this::handleEncryptionRequest);
} }
} }

Datei anzeigen

@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package de.lixfel.tinyprotocol; package de.lixfel.altauth.bukkit;
import de.lixfel.tinyprotocol.Reflection.FieldAccessor; import de.lixfel.ReflectionUtil;
import de.lixfel.altauth.bukkit.AltAuthBukkit; import de.lixfel.ReflectionUtil.FieldWrapper;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -22,21 +22,21 @@ import java.util.*;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.logging.Level; 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<?> craftServer = ReflectionUtil.getClass("org.bukkit.craftbukkit.CraftServer");
private static final Class<?> dedicatedPlayerList = Reflection.getClass("{nms.server.dedicated}.DedicatedPlayerList"); private static final Class<?> dedicatedPlayerList = ReflectionUtil.getClass("net.minecraft.server.dedicated.DedicatedPlayerList");
private static final FieldAccessor<?> getPlayerList = Reflection.getField(craftServer, dedicatedPlayerList, 0); private static final FieldWrapper<?> getPlayerList = ReflectionUtil.getField(craftServer, dedicatedPlayerList, 0);
private static final Class<?> playerList = Reflection.getClass("{nms.server.players}.PlayerList"); private static final Class<?> playerList = ReflectionUtil.getClass("net.minecraft.server.players.PlayerList");
private static final Class<?> minecraftServer = Reflection.getClass("{nms.server}.MinecraftServer"); private static final Class<?> minecraftServer = ReflectionUtil.getClass("net.minecraft.server.MinecraftServer");
private static final FieldAccessor<?> getMinecraftServer = Reflection.getField(playerList, minecraftServer, 0); private static final FieldWrapper<?> getMinecraftServer = ReflectionUtil.getField(playerList, minecraftServer, 0);
private static final Class<?> serverConnection = Reflection.getClass("{nms.server.network}.ServerConnection"); private static final Class<?> serverConnection = ReflectionUtil.getClass("net.minecraft.server.network.ServerConnection");
private static final FieldAccessor<?> getServerConnection = Reflection.getField(minecraftServer, serverConnection, 0); private static final FieldWrapper<?> getServerConnection = ReflectionUtil.getField(minecraftServer, serverConnection, 0);
private static final Class<?> networkManager = Reflection.getClass("{nms.network}.NetworkManager"); private static final Class<?> networkManager = ReflectionUtil.getClass("net.minecraft.network.NetworkManager");
private static final FieldAccessor<List> getConnections = Reflection.getField(serverConnection, List.class, 0, networkManager); private static final FieldWrapper<List> getConnections = ReflectionUtil.getField(serverConnection, List.class, 0, networkManager);
public static final TinyProtocol instance = new TinyProtocol(AltAuthBukkit.getInstance()); public static final ProtocolInjector instance = new ProtocolInjector(AltAuthBukkit.getInstance());
private static int id = 0;
public static void init() { public static void init() {
//enforce init //enforce init
@ -50,9 +50,9 @@ public class TinyProtocol implements Listener {
private final Map<Class<?>, List<BiFunction<Player, Object, Object>>> packetFilters = new HashMap<>(); private final Map<Class<?>, List<BiFunction<Player, Object, Object>>> packetFilters = new HashMap<>();
private final Map<Player, PacketInterceptor> playerInterceptors = new HashMap<>(); private final Map<Player, PacketInterceptor> playerInterceptors = new HashMap<>();
private TinyProtocol(final Plugin plugin) { private ProtocolInjector(final Plugin plugin) {
this.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())))); this.connections = getConnections.get(getServerConnection.get(getMinecraftServer.get(getPlayerList.get(plugin.getServer()))));
plugin.getServer().getPluginManager().registerEvents(this, plugin); plugin.getServer().getPluginManager().registerEvents(this, plugin);
@ -89,14 +89,6 @@ public class TinyProtocol implements Listener {
packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter); 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() { public final void close() {
if(closed) if(closed)
return; return;
@ -115,8 +107,8 @@ public class TinyProtocol implements Listener {
} }
} }
private static final FieldAccessor<Channel> getChannel = Reflection.getField(networkManager, Channel.class, 0); private static final FieldWrapper<Channel> getChannel = ReflectionUtil.getField(networkManager, Channel.class, 0);
private static final FieldAccessor<UUID> getUUID = Reflection.getField(networkManager, UUID.class, 0); private static final FieldWrapper<UUID> getUUID = ReflectionUtil.getField(networkManager, UUID.class, 0);
public final class PacketInterceptor extends ChannelDuplexHandler { public final class PacketInterceptor extends ChannelDuplexHandler {
private final Player player; private final Player player;

Datei anzeigen

@ -2,7 +2,7 @@
package de.lixfel.altauth.bukkit; package de.lixfel.altauth.bukkit;
import de.lixfel.tinyprotocol.Reflection; import de.lixfel.ReflectionUtil;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
@ -10,10 +10,10 @@ import java.util.logging.Level;
public class SessionServiceInjector { 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<URL> JOIN_URL = Reflection.getField(SESSION_SERVICE, "JOIN_URL", URL.class); private static final ReflectionUtil.FieldWrapper<URL> JOIN_URL = ReflectionUtil.getField(SESSION_SERVICE, URL.class, "JOIN_URL");
private static final Reflection.FieldAccessor<URL> CHECK_URL = Reflection.getField(SESSION_SERVICE, "CHECK_URL", URL.class); private static final ReflectionUtil.FieldWrapper<URL> CHECK_URL = ReflectionUtil.getField(SESSION_SERVICE, URL.class, "CHECK_URL");
private final URL joinUrlBackup; private final URL joinUrlBackup;
private final URL checkUrlBackup; private final URL checkUrlBackup;

Datei anzeigen

@ -2,7 +2,7 @@
package de.lixfel.altauth.bungee; package de.lixfel.altauth.bungee;
import de.lixfel.tinyprotocol.Reflection; import de.lixfel.ReflectionUtil;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec; import io.netty.handler.codec.MessageToMessageCodec;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
@ -36,12 +36,12 @@ import java.util.logging.Level;
public class AltAuthHandler extends MessageToMessageCodec<PacketWrapper, PacketWrapper> { public class AltAuthHandler extends MessageToMessageCodec<PacketWrapper, PacketWrapper> {
private static final Class<?> InitialHandler = Reflection.getClass("net.md_5.bungee.connection.InitialHandler"); private static final Class<?> InitialHandler = ReflectionUtil.getClass("net.md_5.bungee.connection.InitialHandler");
private static final Reflection.FieldAccessor<ChannelWrapper> CH = Reflection.getField(InitialHandler, ChannelWrapper.class, 0); private static final ReflectionUtil.FieldWrapper<ChannelWrapper> CH = ReflectionUtil.getField(InitialHandler, ChannelWrapper.class, 0);
private static final Reflection.FieldAccessor<LoginResult> LoginProfile = Reflection.getField(InitialHandler, "loginProfile", LoginResult.class); private static final ReflectionUtil.FieldWrapper<LoginResult> LoginProfile = ReflectionUtil.getField(InitialHandler, LoginResult.class, "loginProfile");
private static final Reflection.FieldAccessor<String> Name = Reflection.getField(InitialHandler, "name", String.class); private static final ReflectionUtil.FieldWrapper<String> Name = ReflectionUtil.getField(InitialHandler, String.class, "name");
private static final Reflection.FieldAccessor<UUID> UniqueId = Reflection.getField(InitialHandler, "uniqueId", UUID.class); private static final ReflectionUtil.FieldWrapper<UUID> UniqueId = ReflectionUtil.getField(InitialHandler, UUID.class, "uniqueId");
private static final Reflection.MethodInvoker Finish = Reflection.getMethod(InitialHandler, "finish"); private static final ReflectionUtil.MethodWrapper Finish = ReflectionUtil.getMethod(InitialHandler, "finish");
private final ProxyServer bungee; private final ProxyServer bungee;
private final String altAuthUrl; private final String altAuthUrl;

Datei anzeigen

@ -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 <T> FieldWrapper<T> getField(Class<?> target, Class<T> fieldType, String name) {
return getField(target, fieldType, name, 0);
}
public static <T> FieldWrapper<T> getField(Class<?> target, Class<T> fieldType, int index, Class<?>... parameters) {
return getField(target, fieldType, null, index, parameters);
}
private static <T> FieldWrapper<T> getField(Class<?> target, Class<T> 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<T>() {
@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 <T> boolean matching(Field field, String name, Class<T> 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> {
T get(Object target);
void set(Object target, Object value);
}
}

Datei anzeigen

@ -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 <T> - field type.
*/
public interface FieldAccessor<T> {
/**
* 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 <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> 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 <T> FieldAccessor<T> getField(String className, String name, Class<T> 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 <T> FieldAccessor<T> getField(Class<?> target, Class<T> 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 <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
return getField(getClass(className), fieldType, index);
}
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index, Class<?>... parameters) {
return getField(target, null, fieldType, index, parameters);
}
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> 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<T>() {
@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 <T> boolean matching(Field field, String name, Class<T> 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.
* <p>
* This is useful when looking up fields by a NMS or OBC type.
* <p>
*
* @param lookupName - the class name with variables.
* @return The class.
*/
public static Class<Object> getUntypedClass(String lookupName) {
@SuppressWarnings({ "rawtypes", "unchecked" })
Class<Object> clazz = (Class) getClass(lookupName);
return clazz;
}
/**
* Retrieve a class from its full name.
* <p>
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
* <p>
* <table border="1">
* <tr>
* <th>Variable</th>
* <th>Content</th>
* </tr>
* <tr>
* <td>{nms}</td>
* <td>Actual package name of net.minecraft.server.VERSION</td>
* </tr>
* <tr>
* <td>{obc}</td>
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
* </tr>
* <tr>
* <td>{version}</td>
* <td>The current Minecraft package VERSION, if any.</td>
* </tr>
* </table>
*
* @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);
}
}
}