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; @SuppressWarnings("unchecked") final class ChannelProtocolUtil { public static final BiFunction PROTOCOL_RESOLVER; static { Class networkManagerClass = MinecraftReflection.getNetworkManagerClass(); List attributeKeys = FuzzyReflection.fromClass(networkManagerClass, true).getFieldList(FuzzyFieldContract.newBuilder() .typeExact(AttributeKey.class) .requireModifier(Modifier.STATIC) .declaringClassExactType(networkManagerClass) .build()); BiFunction baseResolver = null; 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); } else if (attributeKeys.size() > 1) { // most likely 1.20.2+: 1 protocol key per protocol direction AttributeKey serverBoundKey = null; AttributeKey clientBoundKey = null; for (Field keyField : attributeKeys) { AttributeKey key = (AttributeKey) Accessors.getFieldAccessor(keyField).get(null); if (key.name().equals("protocol")) { // legacy (pre 1.20.2 name) - fall back to the old behaviour baseResolver = new Pre1_20_2DirectResolver(key); break; } if (key.name().contains("protocol")) { // one of the two protocol keys for 1.20.2 if (key.name().contains("server")) { serverBoundKey = key; } else { clientBoundKey = key; } } } if (baseResolver == null) { if ((serverBoundKey == null || clientBoundKey == null)) { // neither pre 1.20.2 key nor 1.20.2+ keys are available throw new ExceptionInInitializerError("Unable to resolve protocol state attribute keys"); } else { baseResolver = new Post1_20_2WrappedResolver(serverBoundKey, clientBoundKey); } } } else { throw new ExceptionInInitializerError("Unable to resolve protocol state attribute key(s)"); } // decorate the base resolver by wrapping its return value into our packet type value PROTOCOL_RESOLVER = baseResolver.andThen(nmsProtocol -> PacketType.Protocol.fromVanilla((Enum) nmsProtocol)); } private static final class Pre1_20_2DirectResolver implements BiFunction { private final AttributeKey attributeKey; public Pre1_20_2DirectResolver(AttributeKey attributeKey) { this.attributeKey = attributeKey; } @Override public Object apply(Channel channel, PacketType.Sender sender) { return channel.attr(this.attributeKey).get(); } } private static final class Post1_20_2WrappedResolver implements BiFunction { private final AttributeKey serverBoundKey; private final AttributeKey clientBoundKey; // lazy initialized when needed private FieldAccessor protocolAccessor; public Post1_20_2WrappedResolver(AttributeKey serverBoundKey, AttributeKey clientBoundKey) { this.serverBoundKey = serverBoundKey; this.clientBoundKey = clientBoundKey; } @Override public Object apply(Channel channel, PacketType.Sender sender) { AttributeKey key = this.getKeyForSender(sender); Object codecData = channel.attr(key).get(); if (codecData == null) { return null; } FieldAccessor protocolAccessor = this.getProtocolAccessor(codecData.getClass()); return protocolAccessor.get(codecData); } private AttributeKey getKeyForSender(PacketType.Sender sender) { switch (sender) { case SERVER: return this.clientBoundKey; case CLIENT: return this.serverBoundKey; default: throw new IllegalArgumentException("Illegal packet sender " + sender.name()); } } private FieldAccessor getProtocolAccessor(Class codecClass) { if (this.protocolAccessor == null) { Class enumProtocolClass = MinecraftReflection.getEnumProtocolClass(); this.protocolAccessor = Accessors.getFieldAccessor(codecClass, enumProtocolClass, true); } 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); }; } } }