diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/ChannelProtocolUtil.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/ChannelProtocolUtil.java index 3ab122657..69c0145fe 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/channel/ChannelProtocolUtil.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/ChannelProtocolUtil.java @@ -1,19 +1,23 @@ package com.comphenix.protocol.injector.netty.channel; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + import com.comphenix.protocol.PacketType; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; + import io.netty.channel.Channel; import io.netty.util.AttributeKey; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.List; -import java.util.function.BiFunction; - @SuppressWarnings("unchecked") final class ChannelProtocolUtil { @@ -28,7 +32,10 @@ final class ChannelProtocolUtil { .build()); BiFunction baseResolver = null; - if (attributeKeys.size() == 1) { + if (attributeKeys.isEmpty()) { + // since 1.20.5 the protocol is stored as final field in de-/encoder + baseResolver = new Post1_20_5WrappedResolver(); + } else if (attributeKeys.size() == 1) { // if there is only one attribute key we can assume it's the correct one (1.8 - 1.20.1) Object protocolKey = Accessors.getFieldAccessor(attributeKeys.get(0)).get(null); baseResolver = new Pre1_20_2DirectResolver((AttributeKey) protocolKey); @@ -130,4 +137,70 @@ private FieldAccessor getProtocolAccessor(Class codecClass) { return this.protocolAccessor; } } + + /** + * Since 1.20.5 the protocol is stored as final field in de-/encoder + */ + private static final class Post1_20_5WrappedResolver implements BiFunction { + + // lazy initialized when needed + private Function serverProtocolAccessor; + private Function clientProtocolAccessor; + + @Override + public Object apply(Channel channel, PacketType.Sender sender) { + String key = this.getKeyForSender(sender); + Object codecHandler = channel.pipeline().get(key); + if (codecHandler == null) { + return null; + } + + Function protocolAccessor = this.getProtocolAccessor(codecHandler.getClass(), sender); + return protocolAccessor.apply(codecHandler); + } + + private Function getProtocolAccessor(Class codecHandler, PacketType.Sender sender) { + switch (sender) { + case SERVER: + if (this.serverProtocolAccessor == null) { + this.serverProtocolAccessor = getProtocolAccessor(codecHandler); + } + return this.serverProtocolAccessor; + case CLIENT: + if (this.clientProtocolAccessor == null) { + this.clientProtocolAccessor = getProtocolAccessor(codecHandler); + } + return this.clientProtocolAccessor; + default: + throw new IllegalArgumentException("Illegal packet sender " + sender.name()); + } + } + + private String getKeyForSender(PacketType.Sender sender) { + switch (sender) { + case SERVER: + return "encoder"; + case CLIENT: + return "decoder"; + default: + throw new IllegalArgumentException("Illegal packet sender " + sender.name()); + } + } + + private Function getProtocolAccessor(Class codecHandler) { + Class protocolInfoClass = MinecraftReflection.getProtocolInfoClass(); + + MethodAccessor protocolAccessor = Accessors.getMethodAccessor(FuzzyReflection + .fromClass(protocolInfoClass) + .getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0])); + + FieldAccessor protocolInfoAccessor = Accessors.getFieldAccessor(codecHandler, protocolInfoClass, true); + + // get ProtocolInfo from handler and get EnumProtocol of ProtocolInfo + return (handler) -> { + Object protocolInfo = protocolInfoAccessor.get(handler); + return protocolAccessor.invoke(protocolInfo); + }; + } + } } diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java index 7dab34616..b3e08c0ab 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java @@ -31,6 +31,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoop; import io.netty.util.AttributeKey; import org.bukkit.Server; @@ -202,9 +203,18 @@ public boolean inject() { return false; } + ChannelPipeline pipeline = this.wrappedChannel.pipeline(); + + // since 1.20.5 the encoder is renamed to outbound_config only in the handshake phase + String encoderName = pipeline.get("outbound_config") != null + ? "outbound_config" : "encoder"; + // inject our handlers - this.wrappedChannel.pipeline().addAfter("encoder", WIRE_PACKET_ENCODER_NAME, WIRE_PACKET_ENCODER); - this.wrappedChannel.pipeline().addAfter( + pipeline.addAfter( + encoderName, + WIRE_PACKET_ENCODER_NAME, + WIRE_PACKET_ENCODER); + pipeline.addAfter( "decoder", INTERCEPTOR_NAME, new InboundPacketInterceptor(this, this.channelListener)); diff --git a/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java b/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java index d559e758a..1b205e93d 100644 --- a/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java +++ b/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java @@ -19,14 +19,28 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.*; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.ProtocolLogger; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; @@ -35,11 +49,12 @@ /** * Static packet registry in Minecraft. + * * @author Kristian */ public class PacketRegistry { - // Whether or not the registry has been initialized - private static volatile boolean INITIALIZED = false; + // Whether or not the registry has been initialized + private static volatile boolean INITIALIZED = false; static void reset() { synchronized (registryLock) { @@ -47,439 +62,604 @@ static void reset() { } } - /** - * Represents a register we are currently building. - * @author Kristian - */ - protected static class Register { - // The main lookup table - final Map>> typeToClass = new ConcurrentHashMap<>(); - - final Map, PacketType> classToType = new ConcurrentHashMap<>(); - final Map, PacketType>> protocolClassToType = new ConcurrentHashMap<>(); - - volatile Set serverPackets = new HashSet<>(); - volatile Set clientPackets = new HashSet<>(); - final List containers = new ArrayList<>(); - - public Register() {} - - public void registerPacket(PacketType type, Class clazz, Sender sender) { - typeToClass.put(type, Optional.of(clazz)); - - classToType.put(clazz, type); - protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(clazz, type); - - if (sender == Sender.CLIENT) { - clientPackets.add(type); - } else { - serverPackets.add(type); - } - } - - public void addContainer(MapContainer container) { - containers.add(container); - } - - /** - * Determine if the current register is outdated. - * @return TRUE if it is, FALSE otherwise. - */ - public boolean isOutdated() { - for (MapContainer container : containers) { - if (container.hasChanged()) { - return true; - } - } - return false; - } - } - - protected static final Class ENUM_PROTOCOL = MinecraftReflection.getEnumProtocolClass(); - - // Current register - protected static volatile Register REGISTER; - - /** - * Ensure that our local register is up-to-date with Minecraft. - *

- * This operation may block the calling thread. - */ - public static synchronized void synchronize() { - // Check if the packet registry has changed - if (REGISTER.isOutdated()) { - initialize(); - } - } - - protected static synchronized Register createOldRegister() { - Object[] protocols = ENUM_PROTOCOL.getEnumConstants(); - - // ID to Packet class maps - final Map>> serverMaps = new LinkedHashMap<>(); - final Map>> clientMaps = new LinkedHashMap<>(); - - Register result = new Register(); - StructureModifier modifier = null; - - // Iterate through the protocols - for (Object protocol : protocols) { - if (modifier == null) { - modifier = new StructureModifier<>(protocol.getClass().getSuperclass()); - } - - StructureModifier>>> maps = modifier.withTarget(protocol).withType(Map.class); - for (Map.Entry>> entry : maps.read(0).entrySet()) { - String direction = entry.getKey().toString(); - if (direction.contains("CLIENTBOUND")) { // Sent by Server - serverMaps.put(protocol, entry.getValue()); - } else if (direction.contains("SERVERBOUND")) { // Sent by Client - clientMaps.put(protocol, entry.getValue()); - } - } - } - - // Maps we have to occasionally check have changed - for (Object map : serverMaps.values()) { - result.addContainer(new MapContainer(map)); - } - - for (Object map : clientMaps.values()) { - result.addContainer(new MapContainer(map)); - } - - for (Object protocol : protocols) { - Enum enumProtocol = (Enum) protocol; - PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol); - - // Associate known types - if (serverMaps.containsKey(protocol)) - associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER); - if (clientMaps.containsKey(protocol)) - associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT); - } - - return result; - } - - @SuppressWarnings("unchecked") - private static synchronized Register createNewRegister() { - Object[] protocols = ENUM_PROTOCOL.getEnumConstants(); - - // ID to Packet class maps - final Map, Integer>> serverMaps = new LinkedHashMap<>(); - final Map, Integer>> clientMaps = new LinkedHashMap<>(); - - Register result = new Register(); - - Field mainMapField = null; - Field packetMapField = null; - Field holderClassField = null; // only 1.20.2+ - - // Iterate through the protocols - for (Object protocol : protocols) { - if (mainMapField == null) { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(protocol.getClass(), true); - mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder() - .banModifier(Modifier.STATIC) - .requireModifier(Modifier.FINAL) - .typeDerivedOf(Map.class) - .build()); - mainMapField.setAccessible(true); - } - - Map directionMap; - - try { - directionMap = (Map) mainMapField.get(protocol); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to access packet map", ex); - } - - for (Map.Entry entry : directionMap.entrySet()) { - Object holder = entry.getValue(); - if (packetMapField == null) { - Class packetHolderClass = holder.getClass(); - if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) { - FuzzyReflection holderFuzzy = FuzzyReflection.fromClass(packetHolderClass, true); - holderClassField = holderFuzzy.getField(FuzzyFieldContract.newBuilder() - .banModifier(Modifier.STATIC) - .requireModifier(Modifier.FINAL) - .typeMatches(FuzzyClassContract.newBuilder() - .method(FuzzyMethodContract.newBuilder() - .returnTypeExact(MinecraftReflection.getPacketClass()) - .parameterCount(2) - .parameterExactType(int.class, 0) - .parameterExactType(MinecraftReflection.getPacketDataSerializerClass(), 1) - .build()) - .build()) - .build()); - holderClassField.setAccessible(true); - packetHolderClass = holderClassField.getType(); - } - - FuzzyReflection fuzzy = FuzzyReflection.fromClass(packetHolderClass, true); - packetMapField = fuzzy.getField(FuzzyFieldContract.newBuilder() - .banModifier(Modifier.STATIC) - .requireModifier(Modifier.FINAL) - .typeDerivedOf(Map.class) - .build()); - packetMapField.setAccessible(true); - } - - Object holderInstance = holder; - if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) { - try { - holderInstance = holderClassField.get(holder); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to access packet map", ex); - } - } - - Map, Integer> packetMap; - try { - packetMap = (Map, Integer>) packetMapField.get(holderInstance); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to access packet map", ex); - } - - String direction = entry.getKey().toString(); - if (direction.contains("CLIENTBOUND")) { // Sent by Server - serverMaps.put(protocol, packetMap); - } else if (direction.contains("SERVERBOUND")) { // Sent by Client - clientMaps.put(protocol, packetMap); - } - } - } - - // Maps we have to occasionally check have changed - // TODO: Find equivalent in Object2IntMap - - /* for (Object map : serverMaps.values()) { - result.containers.add(new MapContainer(map)); - } - - for (Object map : clientMaps.values()) { - result.containers.add(new MapContainer(map)); - } */ - - for (Object protocol : protocols) { - Enum enumProtocol = (Enum) protocol; - PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol); - - // Associate known types - if (serverMaps.containsKey(protocol)) { - associatePackets(result, reverse(serverMaps.get(protocol)), equivalent, Sender.SERVER); - } - if (clientMaps.containsKey(protocol)) { - associatePackets(result, reverse(clientMaps.get(protocol)), equivalent, Sender.CLIENT); - } - } - - return result; - } - - /** - * Reverses a key->value map to value->key - * Non-deterministic behavior when multiple keys are mapped to the same value - */ - private static Map reverse(Map map) { - Map newMap = new HashMap<>(map.size()); - for (Map.Entry entry : map.entrySet()) { - newMap.put(entry.getValue(), entry.getKey()); - } - return newMap; - } - - protected static void associatePackets(Register register, Map> lookup, PacketType.Protocol protocol, Sender sender) { - for (Map.Entry> entry : lookup.entrySet()) { - int packetId = entry.getKey(); - Class packetClass = entry.getValue(); - - PacketType type = PacketType.fromCurrent(protocol, sender, packetId, packetClass); - - try { - register.registerPacket(type, packetClass, sender); - } catch (Exception ex) { - ProtocolLogger.debug("Encountered an exception associating packet " + type, ex); - } - } - } - - private static void associate(PacketType type, Class clazz) { - if (clazz != null) { - REGISTER.typeToClass.put(type, Optional.of(clazz)); - REGISTER.classToType.put(clazz, type); - } else { - REGISTER.typeToClass.put(type, Optional.empty()); - } - } - - private static final Object registryLock = new Object(); - - /** - * Initializes the packet registry. - */ - static void initialize() { - if (INITIALIZED) { - return; - } - - synchronized (registryLock) { - if (INITIALIZED) { - return; - } - - if (MinecraftVersion.BEE_UPDATE.atOrAbove()) { - REGISTER = createNewRegister(); - } else { - REGISTER = createOldRegister(); - } - - INITIALIZED = true; - } - } - - /** - * Determine if the given packet type is supported on the current server. - * @param type - the type to check. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isSupported(PacketType type) { - initialize(); - return tryGetPacketClass(type).isPresent(); - } - - /** - * Retrieve every known and supported server packet type. - * @return Every server packet type. - */ - public static Set getServerPacketTypes() { - initialize(); - synchronize(); - - return Collections.unmodifiableSet(REGISTER.serverPackets); - } - - /** - * Retrieve every known and supported server packet type. - * @return Every server packet type. - */ - public static Set getClientPacketTypes() { - initialize(); - synchronize(); - - return Collections.unmodifiableSet(REGISTER.clientPackets); - } - - private static Class searchForPacket(List classNames) { - for (String name : classNames) { - try { - Class clazz = MinecraftReflection.getMinecraftClass(name); - if (MinecraftReflection.getPacketClass().isAssignableFrom(clazz) - && !Modifier.isAbstract(clazz.getModifiers())) { - return clazz; - } - } catch (Exception ignored) {} - } - - return null; - } - - /** - * Retrieves the correct packet class from a given type. - * - * @param type - the packet type. - * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. - * @return The associated class. - * @deprecated forceVanilla no longer has any effect - */ - @Deprecated - public static Class getPacketClassFromType(PacketType type, boolean forceVanilla) { - return getPacketClassFromType(type); - } - - public static Optional> tryGetPacketClass(PacketType type) { - initialize(); - - // Try the lookup first (may be null, so check contains) - Optional> res = REGISTER.typeToClass.get(type); - if (res != null) { - if(res.isPresent() && MinecraftReflection.isBundleDelimiter(res.get())) { - return MinecraftReflection.getPackedBundlePacketClass(); - } - return res; - } - - // Then try looking up the class names - Class clazz = searchForPacket(type.getClassNames()); - if (clazz != null) { - // we'd like for it to be associated correctly from the get-go; this is OK on older versions though - ProtocolLogger.warnAbove(type.getCurrentVersion(), "Updating associated class for {0} to {1}", type.name(), clazz); - } - - // cache it for next time - associate(type, clazz); - if(clazz != null && MinecraftReflection.isBundleDelimiter(clazz)) { - clazz = MinecraftReflection.getPackedBundlePacketClass().orElseThrow(() -> new IllegalStateException("Packet bundle class not found.")); - } - return Optional.ofNullable(clazz); - } - - /** - * Get the packet class associated with a given type. First attempts to read from the - * type-to-class mapping, and tries - * @param type the packet type - * @return The associated class - */ - public static Class getPacketClassFromType(PacketType type) { - return tryGetPacketClass(type) - .orElseThrow(() -> new IllegalArgumentException("Could not find packet for type " + type.name())); - } - - /** - * Retrieve the packet type of a given packet. - * @param packet - the class of the packet. - * @return The packet type, or NULL if not found. - * @deprecated major issues due to packets with shared classes being registered in multiple states. - */ - @Deprecated - public static PacketType getPacketType(Class packet) { - initialize(); - - if (MinecraftReflection.isBundlePacket(packet)) { - return PacketType.Play.Server.BUNDLE; - } - - return REGISTER.classToType.get(packet); - } - - /** - * Retrieve the associated packet type for a packet class in the given protocol state. - * - * @param protocol the protocol state to retrieve the packet from. - * @param packet the class identifying the packet type. - * @return the packet type associated with the given class in the given protocol state, or null if not found. - */ - public static PacketType getPacketType(PacketType.Protocol protocol, Class packet) { - initialize(); - if (MinecraftReflection.isBundlePacket(packet)) { - return PacketType.Play.Server.BUNDLE; - } - - Map, PacketType> classToTypesForProtocol = REGISTER.protocolClassToType.get(protocol); - return classToTypesForProtocol == null ? null : classToTypesForProtocol.get(packet); - } - - /** - * Retrieve the packet type of a given packet. - * @param packet - the class of the packet. - * @param sender - the sender of the packet, or NULL. - * @return The packet type, or NULL if not found. - * @deprecated sender no longer has any effect - */ - @Deprecated - public static PacketType getPacketType(Class packet, Sender sender) { - return getPacketType(packet); - } + /** + * Represents a register we are currently building. + * + * @author Kristian + */ + protected static class Register { + // The main lookup table + final Map>> typeToClass = new ConcurrentHashMap<>(); + + final Map, PacketType> classToType = new ConcurrentHashMap<>(); + final Map, PacketType>> protocolClassToType = new ConcurrentHashMap<>(); + + volatile Set serverPackets = new HashSet<>(); + volatile Set clientPackets = new HashSet<>(); + final List containers = new ArrayList<>(); + + public Register() { + } + + public void registerPacket(PacketType type, Class clazz, Sender sender) { + typeToClass.put(type, Optional.of(clazz)); + + classToType.put(clazz, type); + protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(clazz, type); + + if (sender == Sender.CLIENT) { + clientPackets.add(type); + } else { + serverPackets.add(type); + } + } + + public void addContainer(MapContainer container) { + containers.add(container); + } + + /** + * Determine if the current register is outdated. + * + * @return TRUE if it is, FALSE otherwise. + */ + public boolean isOutdated() { + for (MapContainer container : containers) { + if (container.hasChanged()) { + return true; + } + } + return false; + } + } + + protected static final Class ENUM_PROTOCOL = MinecraftReflection.getEnumProtocolClass(); + + // Current register + protected static volatile Register REGISTER; + + /** + * Ensure that our local register is up-to-date with Minecraft. + *

+ * This operation may block the calling thread. + */ + public static synchronized void synchronize() { + // Check if the packet registry has changed + if (REGISTER.isOutdated()) { + initialize(); + } + } + + protected static synchronized Register createOldRegister() { + Object[] protocols = ENUM_PROTOCOL.getEnumConstants(); + + // ID to Packet class maps + final Map>> serverMaps = new LinkedHashMap<>(); + final Map>> clientMaps = new LinkedHashMap<>(); + + Register result = new Register(); + StructureModifier modifier = null; + + // Iterate through the protocols + for (Object protocol : protocols) { + if (modifier == null) { + modifier = new StructureModifier<>(protocol.getClass().getSuperclass()); + } + + StructureModifier>>> maps = modifier.withTarget(protocol) + .withType(Map.class); + for (Map.Entry>> entry : maps.read(0).entrySet()) { + String direction = entry.getKey().toString(); + if (direction.contains("CLIENTBOUND")) { // Sent by Server + serverMaps.put(protocol, entry.getValue()); + } else if (direction.contains("SERVERBOUND")) { // Sent by Client + clientMaps.put(protocol, entry.getValue()); + } + } + } + + // Maps we have to occasionally check have changed + for (Object map : serverMaps.values()) { + result.addContainer(new MapContainer(map)); + } + + for (Object map : clientMaps.values()) { + result.addContainer(new MapContainer(map)); + } + + for (Object protocol : protocols) { + Enum enumProtocol = (Enum) protocol; + PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol); + + // Associate known types + if (serverMaps.containsKey(protocol)) + associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER); + if (clientMaps.containsKey(protocol)) + associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT); + } + + return result; + } + + @SuppressWarnings("unchecked") + private static synchronized Register createRegisterV1_15_0() { + Object[] protocols = ENUM_PROTOCOL.getEnumConstants(); + + // ID to Packet class maps + final Map, Integer>> serverMaps = new LinkedHashMap<>(); + final Map, Integer>> clientMaps = new LinkedHashMap<>(); + + Register result = new Register(); + + Field mainMapField = null; + Field packetMapField = null; + Field holderClassField = null; // only 1.20.2+ + + // Iterate through the protocols + for (Object protocol : protocols) { + if (mainMapField == null) { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(protocol.getClass(), true); + mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder().banModifier(Modifier.STATIC) + .requireModifier(Modifier.FINAL).typeDerivedOf(Map.class).build()); + mainMapField.setAccessible(true); + } + + Map directionMap; + + try { + directionMap = (Map) mainMapField.get(protocol); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to access packet map", ex); + } + + for (Map.Entry entry : directionMap.entrySet()) { + Object holder = entry.getValue(); + if (packetMapField == null) { + Class packetHolderClass = holder.getClass(); + if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) { + FuzzyReflection holderFuzzy = FuzzyReflection.fromClass(packetHolderClass, true); + holderClassField = holderFuzzy + .getField(FuzzyFieldContract.newBuilder().banModifier(Modifier.STATIC) + .requireModifier(Modifier.FINAL) + .typeMatches(FuzzyClassContract.newBuilder().method(FuzzyMethodContract + .newBuilder().returnTypeExact(MinecraftReflection.getPacketClass()) + .parameterCount(2).parameterExactType(int.class, 0).parameterExactType( + MinecraftReflection.getPacketDataSerializerClass(), 1) + .build()).build()) + .build()); + holderClassField.setAccessible(true); + packetHolderClass = holderClassField.getType(); + } + + FuzzyReflection fuzzy = FuzzyReflection.fromClass(packetHolderClass, true); + packetMapField = fuzzy.getField(FuzzyFieldContract.newBuilder().banModifier(Modifier.STATIC) + .requireModifier(Modifier.FINAL).typeDerivedOf(Map.class).build()); + packetMapField.setAccessible(true); + } + + Object holderInstance = holder; + if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) { + try { + holderInstance = holderClassField.get(holder); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to access packet map", ex); + } + } + + Map, Integer> packetMap; + try { + packetMap = (Map, Integer>) packetMapField.get(holderInstance); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to access packet map", ex); + } + + String direction = entry.getKey().toString(); + if (direction.contains("CLIENTBOUND")) { // Sent by Server + serverMaps.put(protocol, packetMap); + } else if (direction.contains("SERVERBOUND")) { // Sent by Client + clientMaps.put(protocol, packetMap); + } + } + } + + // Maps we have to occasionally check have changed + // TODO: Find equivalent in Object2IntMap + + /* + * for (Object map : serverMaps.values()) { result.containers.add(new + * MapContainer(map)); } + * + * for (Object map : clientMaps.values()) { result.containers.add(new + * MapContainer(map)); } + */ + + for (Object protocol : protocols) { + Enum enumProtocol = (Enum) protocol; + PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol); + + // Associate known types + if (serverMaps.containsKey(protocol)) { + associatePackets(result, reverse(serverMaps.get(protocol)), equivalent, Sender.SERVER); + } + if (clientMaps.containsKey(protocol)) { + associatePackets(result, reverse(clientMaps.get(protocol)), equivalent, Sender.CLIENT); + } + } + + return result; + } + + @SuppressWarnings("unchecked") + private static synchronized Register createRegisterV1_20_5() { + Object[] protocols = ENUM_PROTOCOL.getEnumConstants(); + + // PacketType to class map + final Map> packetTypeMap = new HashMap<>(); + + // List of all class containing PacketTypes + String[] packetTypesClassNames = new String[] { "common.CommonPacketTypes", + "configuration.ConfigurationPacketTypes", "cookie.CookiePacketTypes", "game.GamePacketTypes", + "handshake.HandshakePacketTypes", "login.LoginPacketTypes", "ping.PingPacketTypes", + "status.StatusPacketTypes", }; + + Class packetTypeClass = MinecraftReflection.getMinecraftClass("network.protocol.PacketType"); + + for (String packetTypesClassName : packetTypesClassNames) { + Class packetTypesClass = MinecraftReflection + .getMinecraftClass("network.protocol." + packetTypesClassName); + + // check every field for "static final PacketType" + for (Field field : packetTypesClass.getDeclaredFields()) { + try { + if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) { + continue; + } + + Object packetType = field.get(null); + if (!packetTypeClass.isInstance(packetType)) { + continue; + } + + // retrieve the generic type T of the PacketType field + Type packet = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + if (packet instanceof Class) { + packetTypeMap.put(packetType, (Class) packet); + } + } catch (Exception e) { + e.printStackTrace(); + continue; + } + } + } + + // ID to Packet class maps + final Map, Integer>> serverMaps = new LinkedHashMap<>(); + final Map, Integer>> clientMaps = new LinkedHashMap<>(); + + // List of all class containing ProtocolInfos + String[] protocolClassNames = new String[] { "configuration.ConfigurationProtocols", "game.GameProtocols", + "handshake.HandshakeProtocols", "login.LoginProtocols", "status.StatusProtocols" }; + + Class protocolInfoClass = MinecraftReflection.getProtocolInfoClass(); + Class protocolInfoUnboundClass = MinecraftReflection.getMinecraftClass("network.ProtocolInfo$a"); + Class streamCodecClass = MinecraftReflection.getMinecraftClass("network.codec.StreamCodec"); + Class idDispatchCodecClass = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec"); + Class protocolDirectionClass = MinecraftReflection + .getMinecraftClass("network.protocol.EnumProtocolDirection"); + + Function emptyFunction = input -> null; + + FuzzyReflection protocolInfoReflection = FuzzyReflection.fromClass(protocolInfoClass); + + MethodAccessor protocolAccessor = Accessors.getMethodAccessor(protocolInfoReflection + .getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0])); + + MethodAccessor directionAccessor = Accessors.getMethodAccessor(protocolInfoReflection + .getMethodByReturnTypeAndParameters("flow", protocolDirectionClass, new Class[0])); + + MethodAccessor codecAccessor = Accessors.getMethodAccessor( + protocolInfoReflection.getMethodByReturnTypeAndParameters("codec", streamCodecClass, new Class[0])); + + MethodAccessor bindAccessor = Accessors.getMethodAccessor(FuzzyReflection.fromClass(protocolInfoUnboundClass) + .getMethodByReturnTypeAndParameters("bind", protocolInfoClass, new Class[] { Function.class })); + + FieldAccessor toIdAccessor = Accessors.getFieldAccessor(FuzzyReflection.fromClass(idDispatchCodecClass, true) + .getField(FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).build())); + + for (String protocolClassName : protocolClassNames) { + Class protocolClass = MinecraftReflection.getMinecraftClass("network.protocol." + protocolClassName); + + for (Field field : protocolClass.getDeclaredFields()) { + try { + // ignore none static and final fields + if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) { + continue; + } + + Object protocolInfo = field.get(null); + + // bind unbound ProtocolInfo to empty function to get real ProtocolInfo + if (protocolInfoUnboundClass.isInstance(protocolInfo)) { + protocolInfo = bindAccessor.invoke(protocolInfo, new Object[] { emptyFunction }); + } + + // ignore any field that isn't a ProtocolInfo + if (!protocolInfoClass.isInstance(protocolInfo)) { + continue; + } + + // get codec and check if codec is instance of IdDispatchCodec + // since that is the only support codec as of now + Object codec = codecAccessor.invoke(protocolInfo); + if (!idDispatchCodecClass.isInstance(codec)) { + continue; + } + + // retrieve packetTypeMap and convert it to packetIdMap + Map, Integer> packetMap = new HashMap<>(); + Map packetTypeIdMap = (Map) toIdAccessor.get(codec); + + for (Map.Entry entry : packetTypeIdMap.entrySet()) { + Class packet = packetTypeMap.get(entry.getKey()); + if (packet == null) { + throw new RuntimeException("packetType missing packet " + entry.getKey()); + } + + packetMap.put(packet, entry.getValue()); + } + + // get EnumProtocol and Direction of protocol info + Object protocol = protocolAccessor.invoke(protocolInfo); + String direction = directionAccessor.invoke(protocolInfo).toString(); + + if (direction.contains("CLIENTBOUND")) { // Sent by Server + serverMaps.put(protocol, packetMap); + } else if (direction.contains("SERVERBOUND")) { // Sent by Client + clientMaps.put(protocol, packetMap); + } + } catch (Exception e) { + e.printStackTrace(); + continue; + } + } + } + + Register result = new Register(); + + for (Object protocol : protocols) { + Enum enumProtocol = (Enum) protocol; + PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol); + + // Associate known types + if (serverMaps.containsKey(protocol)) { + associatePackets(result, reverse(serverMaps.get(protocol)), equivalent, Sender.SERVER); + } + if (clientMaps.containsKey(protocol)) { + associatePackets(result, reverse(clientMaps.get(protocol)), equivalent, Sender.CLIENT); + } + } + + return result; + } + + /** + * Reverses a key->value map to value->key Non-deterministic behavior when + * multiple keys are mapped to the same value + */ + private static Map reverse(Map map) { + Map newMap = new HashMap<>(map.size()); + for (Map.Entry entry : map.entrySet()) { + newMap.put(entry.getValue(), entry.getKey()); + } + return newMap; + } + + protected static void associatePackets(Register register, Map> lookup, + PacketType.Protocol protocol, Sender sender) { + for (Map.Entry> entry : lookup.entrySet()) { + int packetId = entry.getKey(); + Class packetClass = entry.getValue(); + + PacketType type = PacketType.fromCurrent(protocol, sender, packetId, packetClass); + + try { + register.registerPacket(type, packetClass, sender); + } catch (Exception ex) { + ProtocolLogger.debug("Encountered an exception associating packet " + type, ex); + } + } + } + + private static void associate(PacketType type, Class clazz) { + if (clazz != null) { + REGISTER.typeToClass.put(type, Optional.of(clazz)); + REGISTER.classToType.put(clazz, type); + } else { + REGISTER.typeToClass.put(type, Optional.empty()); + } + } + + private static final Object registryLock = new Object(); + + /** + * Initializes the packet registry. + */ + static void initialize() { + if (INITIALIZED) { + return; + } + + synchronized (registryLock) { + if (INITIALIZED) { + return; + } + + if (MinecraftVersion.v1_20_5.atOrAbove()) { + REGISTER = createRegisterV1_20_5(); + } else if (MinecraftVersion.BEE_UPDATE.atOrAbove()) { + REGISTER = createRegisterV1_15_0(); + } else { + REGISTER = createOldRegister(); + } + + INITIALIZED = true; + } + } + + /** + * Determine if the given packet type is supported on the current server. + * + * @param type - the type to check. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isSupported(PacketType type) { + initialize(); + return tryGetPacketClass(type).isPresent(); + } + + /** + * Retrieve every known and supported server packet type. + * + * @return Every server packet type. + */ + public static Set getServerPacketTypes() { + initialize(); + synchronize(); + + return Collections.unmodifiableSet(REGISTER.serverPackets); + } + + /** + * Retrieve every known and supported server packet type. + * + * @return Every server packet type. + */ + public static Set getClientPacketTypes() { + initialize(); + synchronize(); + + return Collections.unmodifiableSet(REGISTER.clientPackets); + } + + private static Class searchForPacket(List classNames) { + for (String name : classNames) { + try { + Class clazz = MinecraftReflection.getMinecraftClass(name); + if (MinecraftReflection.getPacketClass().isAssignableFrom(clazz) + && !Modifier.isAbstract(clazz.getModifiers())) { + return clazz; + } + } catch (Exception ignored) { + } + } + + return null; + } + + /** + * Retrieves the correct packet class from a given type. + * + * @param type - the packet type. + * @param forceVanilla - whether or not to look for vanilla classes, not + * injected classes. + * @return The associated class. + * @deprecated forceVanilla no longer has any effect + */ + @Deprecated + public static Class getPacketClassFromType(PacketType type, boolean forceVanilla) { + return getPacketClassFromType(type); + } + + public static Optional> tryGetPacketClass(PacketType type) { + initialize(); + + // Try the lookup first (may be null, so check contains) + Optional> res = REGISTER.typeToClass.get(type); + if (res != null) { + if (res.isPresent() && MinecraftReflection.isBundleDelimiter(res.get())) { + return MinecraftReflection.getPackedBundlePacketClass(); + } + return res; + } + + // Then try looking up the class names + Class clazz = searchForPacket(type.getClassNames()); + if (clazz != null) { + // we'd like for it to be associated correctly from the get-go; this is OK on + // older versions though + ProtocolLogger.warnAbove(type.getCurrentVersion(), "Updating associated class for {0} to {1}", type.name(), + clazz); + } + + // cache it for next time + associate(type, clazz); + if (clazz != null && MinecraftReflection.isBundleDelimiter(clazz)) { + clazz = MinecraftReflection.getPackedBundlePacketClass() + .orElseThrow(() -> new IllegalStateException("Packet bundle class not found.")); + } + return Optional.ofNullable(clazz); + } + + /** + * Get the packet class associated with a given type. First attempts to read + * from the type-to-class mapping, and tries + * + * @param type the packet type + * @return The associated class + */ + public static Class getPacketClassFromType(PacketType type) { + return tryGetPacketClass(type) + .orElseThrow(() -> new IllegalArgumentException("Could not find packet for type " + type.name())); + } + + /** + * Retrieve the packet type of a given packet. + * + * @param packet - the class of the packet. + * @return The packet type, or NULL if not found. + * @deprecated major issues due to packets with shared classes being registered + * in multiple states. + */ + @Deprecated + public static PacketType getPacketType(Class packet) { + initialize(); + + if (MinecraftReflection.isBundlePacket(packet)) { + return PacketType.Play.Server.BUNDLE; + } + + return REGISTER.classToType.get(packet); + } + + /** + * Retrieve the associated packet type for a packet class in the given protocol + * state. + * + * @param protocol the protocol state to retrieve the packet from. + * @param packet the class identifying the packet type. + * @return the packet type associated with the given class in the given protocol + * state, or null if not found. + */ + public static PacketType getPacketType(PacketType.Protocol protocol, Class packet) { + initialize(); + if (MinecraftReflection.isBundlePacket(packet)) { + return PacketType.Play.Server.BUNDLE; + } + + Map, PacketType> classToTypesForProtocol = REGISTER.protocolClassToType.get(protocol); + return classToTypesForProtocol == null ? null : classToTypesForProtocol.get(packet); + } + + /** + * Retrieve the packet type of a given packet. + * + * @param packet - the class of the packet. + * @param sender - the sender of the packet, or NULL. + * @return The packet type, or NULL if not found. + * @deprecated sender no longer has any effect + */ + @Deprecated + public static PacketType getPacketType(Class packet, Sender sender) { + return getPacketType(packet); + } } diff --git a/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java b/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java index 11592a457..37bfa8d08 100644 --- a/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java +++ b/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java @@ -62,7 +62,7 @@ public static List createChatPackets(String message) { // since 1.19 system chat is extracted into a separate packet packet = new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT); - packet.getStrings().write(0, component.getJson()); + packet.getChatComponents().write(0, component); packet.getBooleans().write(0, false); } else { packet = new PacketContainer(PacketType.Play.Server.CHAT); diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 69a0f2038..da64a8d60 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -638,7 +638,8 @@ public static boolean isBundlePacket(Class packetClass) { } public static boolean isBundleDelimiter(Class packetClass) { - return Optionals.Equals(getBundleDelimiterClass(), packetClass); + Class bundleDelimiterClass = getBundleDelimiterClass().orElse(null); + return bundleDelimiterClass != null && (packetClass.equals(bundleDelimiterClass) || bundleDelimiterClass.isAssignableFrom(packetClass)); } public static Optional> getBundleDelimiterClass() { @@ -1660,7 +1661,7 @@ public static Class getLibraryClass(String classname) { */ private static void useFallbackServer() { // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) - Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")) + Constructor selected = FuzzyReflection.fromClass(getCraftServer()) .getConstructor(FuzzyMethodContract.newBuilder() .parameterMatches(getMinecraftObjectMatcher(), 0) .parameterCount(2) @@ -1716,4 +1717,16 @@ public static Class getCodecClass() { public static Class getHolderClass() { return getMinecraftClass("core.Holder"); } + + public static Class getCraftServer() { + return getCraftBukkitClass("CraftServer"); + } + + public static Class getHolderLookupProviderClass() { + return getMinecraftClass("core.HolderLookup$a" /* Spigot Mappings */, "core.HolderLookup$Provider" /* Mojang Mappings */); + } + + public static Class getProtocolInfoClass() { + return getMinecraftClass("network.ProtocolInfo"); + } } diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftRegistryAccess.java b/src/main/java/com/comphenix/protocol/utility/MinecraftRegistryAccess.java new file mode 100644 index 000000000..5d1a01301 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftRegistryAccess.java @@ -0,0 +1,60 @@ +package com.comphenix.protocol.utility; + +import java.lang.reflect.Modifier; + +import org.bukkit.Bukkit; + +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; + +/** + * Static getter for the registry access accessor which is need for most of the methods + * since 1.20.5 that access the registry and in form. + */ +public class MinecraftRegistryAccess { + + private static MethodAccessor GET_SERVER = null; + private static MethodAccessor REGISTRY_ACCESS = null; + + // lazy initialized + private static Object registryAccess = null; + + static { + if (MinecraftVersion.v1_20_5.atOrAbove()) { + GET_SERVER = Accessors.getMethodAccessor( + FuzzyReflection.fromClass(MinecraftReflection.getCraftServer(), false) + .getMethod(FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .returnDerivedOf(MinecraftReflection.getMinecraftServerClass()) + .build())); + + REGISTRY_ACCESS = Accessors.getMethodAccessor( + FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass(), false) + .getMethod(FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .returnDerivedOf(MinecraftReflection.getHolderLookupProviderClass()) + .build())); + } + } + + /** + * Returns the composite global registry access. Equiv. of + *
((CraftServer) Bukkit.getServer()).getServer().registryAccess()
+ * + * @return composite registy acesss + */ + public static Object get() { + if (GET_SERVER == null || REGISTRY_ACCESS == null) { + return null; + } + + if (registryAccess == null) { + Object server = GET_SERVER.invoke(Bukkit.getServer()); + registryAccess = REGISTRY_ACCESS.invoke(server); + } + + return registryAccess; + } +} diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java b/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java index 5143510e9..550c623dd 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java @@ -36,6 +36,11 @@ * @author Kristian */ public final class MinecraftVersion implements Comparable, Serializable { + /** + * Version 1.20.5 - the cookie and transfer packet update + */ + public static final MinecraftVersion v1_20_5 = new MinecraftVersion("1.20.5"); + /** * Version 1.20.4 - the decorated pot update */ diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java index 20b810cf2..caf5114ea 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java @@ -1,6 +1,5 @@ package com.comphenix.protocol.wrappers; -import com.google.gson.JsonObject; import java.io.StringReader; import java.util.Optional; @@ -11,10 +10,10 @@ import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftRegistryAccess; import com.comphenix.protocol.utility.MinecraftVersion; - import com.google.common.base.Preconditions; -import net.minecraft.network.chat.IChatBaseComponent; +import com.google.gson.JsonObject; /** * Represents a chat component added in Minecraft 1.7.2 @@ -39,14 +38,22 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER, true); // Retrieve the correct methods - SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("serialize", /* a */ - String.class, new Class[] { COMPONENT })); + if (MinecraftVersion.v1_20_5.atOrAbove()) { + SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("serialize", /* a */ + String.class, new Class[] { COMPONENT, MinecraftReflection.getHolderLookupProviderClass() })); + } else { + SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("serialize", /* a */ + String.class, new Class[] { COMPONENT })); + } GSON = Accessors.getFieldAccessor(fuzzy.getFieldByType("gson", GSON_CLASS)).get(null); - if (MinecraftVersion.v1_20_4.atOrAbove()) { + if (MinecraftVersion.v1_20_5.atOrAbove()) { DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(SERIALIZER, false) - .getMethodByReturnTypeAndParameters("fromJsonLenient", MUTABLE_COMPONENT_CLASS.get(), String.class)); + .getMethodByReturnTypeAndParameters("fromJson", MUTABLE_COMPONENT_CLASS.get(), new Class[] { String.class, MinecraftReflection.getHolderLookupProviderClass() })); + } else if (MinecraftVersion.v1_20_4.atOrAbove()) { + DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(SERIALIZER, false) + .getMethodByReturnTypeAndParameters("fromJson", MUTABLE_COMPONENT_CLASS.get(), new Class[] { String.class })); } else { try { DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getChatDeserializer(), true) @@ -68,7 +75,19 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra } } + private static Object serialize(Object handle) { + if (MinecraftVersion.v1_20_5.atOrAbove()) { + return SERIALIZE_COMPONENT.invoke(null, handle, MinecraftRegistryAccess.get()); + } + + return SERIALIZE_COMPONENT.invoke(null, handle); + } + private static Object deserialize(String json) { + if (MinecraftVersion.v1_20_5.atOrAbove()) { + return DESERIALIZE.invoke(null, json, MinecraftRegistryAccess.get()); + } + if (MinecraftVersion.v1_20_4.atOrAbove()) { return DESERIALIZE.invoke(null, json); } @@ -167,7 +186,7 @@ public static WrappedChatComponent fromLegacyText(String message) { */ public String getJson() { if (cache == null) { - cache = (String) SERIALIZE_COMPONENT.invoke(null, handle); + cache = (String) serialize(handle); } return cache; }