diff --git a/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/Reflection.java b/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/Reflection.java index 14032a3f..304c8960 100644 --- a/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/Reflection.java +++ b/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/Reflection.java @@ -3,6 +3,8 @@ package com.comphenix.tinyprotocol; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -177,6 +179,31 @@ public final class Reflection { throw new IllegalArgumentException("Cannot find field with type " + fieldType); } + /** + * Retrieves a field with a given type and parameters. This is most useful + * when dealing with Collections. + * + * @param target the target class. + * @param fieldType Type of the field + * @param params Variable length array of type parameters + * @return The field + * + * @throws IllegalArgumentException If the field cannot be found + */ + public static Field getParameterizedField(Class target, Class fieldType, Class... params) { + for (Field field : target.getDeclaredFields()) { + if (field.getType().equals(fieldType)) { + Type type = field.getGenericType(); + if (type instanceof ParameterizedType) { + if (Arrays.equals(((ParameterizedType) type).getActualTypeArguments(), params)) + return field; + } + } + } + + throw new IllegalArgumentException("Unable to find a field with type " + fieldType + " and params " + Arrays.toString(params)); + } + /** * Search for the first publicly and privately defined method of the given name and parameter count. * @@ -301,6 +328,23 @@ public final class Reflection { return clazz; } + /** + * Retrieve a class from its full name with alternatives, without knowing its type on compile time. + *

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

+ * + * @see {@link #getClass()} for more information. + * @param lookupName - the class name with variables. + * @param aliases - alternative names for this class. + * @return The class. + */ + public static Class getUntypedClass(String lookupName, String... aliases) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + Class clazz = (Class) getClass(lookupName, aliases); + return clazz; + } + /** * Retrieve a class from its full name. *

@@ -333,6 +377,61 @@ public final class Reflection { return getCanonicalClass(expandVariables(lookupName)); } + /** + * Retrieve the first class that matches the full class 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. + * @param aliases - alternative names for this class. + * @return Class object. + * @throws RuntimeException If we are unable to find any of the given classes. + */ + public static Class getClass(String lookupName, String... aliases) { + try { + // Try the main class first + return getClass(lookupName); + } catch (RuntimeException e) { + Class success = null; + + // Try every alias too + for (String alias : aliases) { + try { + success = getClass(alias); + break; + } catch (RuntimeException e1) { + // e1.printStackTrace(); + } + } + + if (success != null) { + return success; + } else { + // Hack failed + throw new RuntimeException(String.format("Unable to find %s (%s)", lookupName, String.join(",", aliases))); + } + } + } + /** * Retrieve a class in the net.minecraft.server.VERSION.* package. * diff --git a/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java b/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java index 1af17ccc..a3583a4f 100644 --- a/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java @@ -9,6 +9,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; +import java.lang.reflect.Field; import java.util.Collections; import java.util.List; import java.util.Map; @@ -44,21 +45,25 @@ import com.mojang.authlib.GameProfile; public abstract class TinyProtocol { private static final AtomicInteger ID = new AtomicInteger(0); + // Required Minecraft classes + private static final Class entityPlayerClass = Reflection.getClass("{nms}.EntityPlayer", "net.minecraft.server.level.EntityPlayer"); + private static final Class playerConnectionClass = Reflection.getClass("{nms}.PlayerConnection", "net.minecraft.server.network.PlayerConnection"); + private static final Class networkManagerClass = Reflection.getClass("{nms}.NetworkManager", "net.minecraft.network.NetworkManager"); + // Used in order to lookup a channel private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle"); - private static final FieldAccessor getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class); - private static final FieldAccessor getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class); - private static final FieldAccessor getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0); + private static final FieldAccessor getConnection = Reflection.getField(entityPlayerClass, null, playerConnectionClass); + private static final FieldAccessor getManager = Reflection.getField(playerConnectionClass, null, networkManagerClass); + private static final FieldAccessor getChannel = Reflection.getField(networkManagerClass, Channel.class, 0); // Looking up ServerConnection - private static final Class minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer"); - private static final Class serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection"); + private static final Class minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer", "net.minecraft.server.MinecraftServer"); + private static final Class serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection", "net.minecraft.server.network.ServerConnection"); private static final FieldAccessor getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0); private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0); - private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass); // Packets we have to intercept - private static final Class PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart"); + private static final Class PACKET_LOGIN_IN_START = Reflection.getClass("{nms}.PacketLoginInStart", "net.minecraft.network.protocol.login.PacketLoginInStart"); private static final FieldAccessor getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0); // Speedup channel lookup @@ -199,8 +204,22 @@ public abstract class TinyProtocol { Object serverConnection = getServerConnection.get(mcServer); boolean looking = true; + try { + Field field = Reflection.getParameterizedField(serverConnectionClass, List.class, networkManagerClass); + field.setAccessible(true); + + networkManagers = (List) field.get(serverConnection); + } catch (Exception ex) { + plugin.getLogger().info("Encountered an exception checking list fields" + ex); + MethodInvoker method = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass); + + networkManagers = (List) method.invoke(null, serverConnection); + } + + if (networkManagers == null) { + throw new IllegalArgumentException("Failed to obtain list of network managers"); + } // We need to synchronize against this list - networkManagers = (List) getNetworkMarkers.invoke(null, serverConnection); createServerChannelHandler(); // Find the correct list, or implicitly throw an exception