Add 1.17 support to TinyProtocol (#194)

This commit is contained in:
Jinyu Yu 2023-05-07 00:26:49 +02:00 committed by GitHub
parent 46f6e76f91
commit e77ed96957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 126 additions and 8 deletions

View File

@ -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.
* <p>
* This is useful when looking up fields by a NMS or OBC type.
* <p>
*
* @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<Object> getUntypedClass(String lookupName, String... aliases) {
@SuppressWarnings({ "rawtypes", "unchecked" })
Class<Object> clazz = (Class) getClass(lookupName, aliases);
return clazz;
}
/**
* Retrieve a class from its full name.
* <p>
@ -333,6 +377,61 @@ public final class Reflection {
return getCanonicalClass(expandVariables(lookupName));
}
/**
* Retrieve the first class that matches the full class 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.
* @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.
*

View File

@ -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<Object> getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class);
private static final FieldAccessor<Object> getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class);
private static final FieldAccessor<Channel> 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<Channel> getChannel = Reflection.getField(networkManagerClass, Channel.class, 0);
// Looking up ServerConnection
private static final Class<Object> minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer");
private static final Class<Object> serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection");
private static final Class<Object> minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer", "net.minecraft.server.MinecraftServer");
private static final Class<Object> serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection", "net.minecraft.server.network.ServerConnection");
private static final FieldAccessor<Object> getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0);
private static final FieldAccessor<Object> 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<GameProfile> 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<Object>) 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<Object>) 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<Object>) getNetworkMarkers.invoke(null, serverConnection);
createServerChannelHandler();
// Find the correct list, or implicitly throw an exception