Skip to content

Commit af33a2a

Browse files
authored
fix invalid packet types due to state mismatch when calling packet events (#2568)
1 parent 03d7be1 commit af33a2a

File tree

10 files changed

+218
-38
lines changed

10 files changed

+218
-38
lines changed

src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public interface ListenerInvoker {
4646
*
4747
* @param packet - the packet.
4848
* @return The packet type.
49+
* @deprecated use {@link com.comphenix.protocol.injector.packet.PacketRegistry#getPacketType(PacketType.Protocol, Class)} instead.
4950
*/
51+
@Deprecated
5052
PacketType getPacketType(Object packet);
5153
}

src/main/java/com/comphenix/protocol/injector/StructureCache.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ public static Object newPacket(PacketType type) {
118118
*
119119
* @param packetType - packet type.
120120
* @return A structure modifier.
121+
* @deprecated use {@link #getStructure(PacketType)} instead.
121122
*/
123+
@Deprecated
122124
public static StructureModifier<Object> getStructure(Class<?> packetType) {
123125
// Get the ID from the class
124126
PacketType type = PacketRegistry.getPacketType(packetType);

src/main/java/com/comphenix/protocol/injector/netty/Injector.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.comphenix.protocol.injector.netty;
22

3+
import com.comphenix.protocol.PacketType;
34
import com.comphenix.protocol.PacketType.Protocol;
45
import com.comphenix.protocol.events.NetworkMarker;
56
import org.bukkit.entity.Player;
@@ -49,8 +50,21 @@ public interface Injector {
4950
* Retrieve the current protocol state.
5051
*
5152
* @return The current protocol.
53+
* @deprecated use {@link #getCurrentProtocol(PacketType.Sender)} instead.
5254
*/
53-
Protocol getCurrentProtocol();
55+
@Deprecated
56+
default Protocol getCurrentProtocol() {
57+
return this.getCurrentProtocol(PacketType.Sender.SERVER);
58+
}
59+
60+
/**
61+
* Retrieve the current protocol state. Note that since 1.20.2 the client and server direction can be in different
62+
* protocol states.
63+
*
64+
* @param sender the side for which the state should be resolved.
65+
* @return The current protocol.
66+
*/
67+
Protocol getCurrentProtocol(PacketType.Sender sender);
5468

5569
/**
5670
* Retrieve the network marker associated with a given packet.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.comphenix.protocol.injector.netty.channel;
2+
3+
import com.comphenix.protocol.PacketType;
4+
import com.comphenix.protocol.reflect.FuzzyReflection;
5+
import com.comphenix.protocol.reflect.accessors.Accessors;
6+
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
7+
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
8+
import com.comphenix.protocol.utility.MinecraftReflection;
9+
import io.netty.channel.Channel;
10+
import io.netty.util.AttributeKey;
11+
12+
import java.lang.reflect.Field;
13+
import java.lang.reflect.Modifier;
14+
import java.util.List;
15+
import java.util.function.BiFunction;
16+
17+
@SuppressWarnings("unchecked")
18+
final class ChannelProtocolUtil {
19+
20+
public static final BiFunction<Channel, PacketType.Sender, PacketType.Protocol> PROTOCOL_RESOLVER;
21+
22+
static {
23+
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
24+
List<Field> attributeKeys = FuzzyReflection.fromClass(networkManagerClass, true).getFieldList(FuzzyFieldContract.newBuilder()
25+
.typeExact(AttributeKey.class)
26+
.requireModifier(Modifier.STATIC)
27+
.declaringClassExactType(networkManagerClass)
28+
.build());
29+
30+
BiFunction<Channel, PacketType.Sender, Object> baseResolver = null;
31+
if (attributeKeys.size() == 1) {
32+
// if there is only one attribute key we can assume it's the correct one (1.8 - 1.20.1)
33+
Object protocolKey = Accessors.getFieldAccessor(attributeKeys.get(0)).get(null);
34+
baseResolver = new Pre1_20_2DirectResolver((AttributeKey<Object>) protocolKey);
35+
} else if (attributeKeys.size() > 1) {
36+
// most likely 1.20.2+: 1 protocol key per protocol direction
37+
AttributeKey<Object> serverBoundKey = null;
38+
AttributeKey<Object> clientBoundKey = null;
39+
40+
for (Field keyField : attributeKeys) {
41+
AttributeKey<Object> key = (AttributeKey<Object>) Accessors.getFieldAccessor(keyField).get(null);
42+
if (key.name().equals("protocol")) {
43+
// legacy (pre 1.20.2 name) - fall back to the old behaviour
44+
baseResolver = new Pre1_20_2DirectResolver(key);
45+
break;
46+
}
47+
48+
if (key.name().contains("protocol")) {
49+
// one of the two protocol keys for 1.20.2
50+
if (key.name().contains("server")) {
51+
serverBoundKey = key;
52+
} else {
53+
clientBoundKey = key;
54+
}
55+
}
56+
}
57+
58+
if (baseResolver == null) {
59+
if ((serverBoundKey == null || clientBoundKey == null)) {
60+
// neither pre 1.20.2 key nor 1.20.2+ keys are available
61+
throw new ExceptionInInitializerError("Unable to resolve protocol state attribute keys");
62+
} else {
63+
baseResolver = new Post1_20_2WrappedResolver(serverBoundKey, clientBoundKey);
64+
}
65+
}
66+
} else {
67+
throw new ExceptionInInitializerError("Unable to resolve protocol state attribute key(s)");
68+
}
69+
70+
// decorate the base resolver by wrapping its return value into our packet type value
71+
PROTOCOL_RESOLVER = baseResolver.andThen(nmsProtocol -> PacketType.Protocol.fromVanilla((Enum<?>) nmsProtocol));
72+
}
73+
74+
private static final class Pre1_20_2DirectResolver implements BiFunction<Channel, PacketType.Sender, Object> {
75+
76+
private final AttributeKey<Object> attributeKey;
77+
78+
public Pre1_20_2DirectResolver(AttributeKey<Object> attributeKey) {
79+
this.attributeKey = attributeKey;
80+
}
81+
82+
@Override
83+
public Object apply(Channel channel, PacketType.Sender sender) {
84+
return channel.attr(this.attributeKey).get();
85+
}
86+
}
87+
88+
private static final class Post1_20_2WrappedResolver implements BiFunction<Channel, PacketType.Sender, Object> {
89+
90+
private final AttributeKey<Object> serverBoundKey;
91+
private final AttributeKey<Object> clientBoundKey;
92+
93+
// lazy initialized when needed
94+
private FieldAccessor protocolAccessor;
95+
96+
public Post1_20_2WrappedResolver(AttributeKey<Object> serverBoundKey, AttributeKey<Object> clientBoundKey) {
97+
this.serverBoundKey = serverBoundKey;
98+
this.clientBoundKey = clientBoundKey;
99+
}
100+
101+
@Override
102+
public Object apply(Channel channel, PacketType.Sender sender) {
103+
AttributeKey<Object> key = this.getKeyForSender(sender);
104+
Object codecData = channel.attr(key).get();
105+
if (codecData == null) {
106+
return null;
107+
}
108+
109+
FieldAccessor protocolAccessor = this.getProtocolAccessor(codecData.getClass());
110+
return protocolAccessor.get(codecData);
111+
}
112+
113+
private AttributeKey<Object> getKeyForSender(PacketType.Sender sender) {
114+
switch (sender) {
115+
case SERVER:
116+
return this.clientBoundKey;
117+
case CLIENT:
118+
return this.serverBoundKey;
119+
default:
120+
throw new IllegalArgumentException("Illegal packet sender " + sender.name());
121+
}
122+
}
123+
124+
private FieldAccessor getProtocolAccessor(Class<?> codecClass) {
125+
if (this.protocolAccessor == null) {
126+
Class<?> enumProtocolClass = MinecraftReflection.getEnumProtocolClass();
127+
this.protocolAccessor = Accessors.getFieldAccessor(codecClass, enumProtocolClass, true);
128+
}
129+
130+
return this.protocolAccessor;
131+
}
132+
}
133+
}

src/main/java/com/comphenix/protocol/injector/netty/channel/EmptyInjector.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.comphenix.protocol.injector.netty.channel;
22

3+
import com.comphenix.protocol.PacketType;
34
import com.comphenix.protocol.PacketType.Protocol;
45
import com.comphenix.protocol.events.NetworkMarker;
56
import com.comphenix.protocol.injector.netty.Injector;
@@ -42,7 +43,7 @@ public void receiveClientPacket(Object packet) {
4243
}
4344

4445
@Override
45-
public Protocol getCurrentProtocol() {
46+
public Protocol getCurrentProtocol(PacketType.Sender sender) {
4647
return Protocol.HANDSHAKING;
4748
}
4849

src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import java.lang.reflect.Field;
44
import java.lang.reflect.Modifier;
5-
import java.util.Collections;
6-
import java.util.HashSet;
75
import java.util.Map;
86
import java.util.Map.Entry;
97
import java.util.NoSuchElementException;
@@ -110,7 +108,6 @@ public Field getField() {
110108

111109
// lazy initialized fields, if we don't need them we don't bother about them
112110
private Object playerConnection;
113-
private FieldAccessor protocolAccessor;
114111

115112
public NettyChannelInjector(
116113
Player player,
@@ -322,17 +319,8 @@ public void receiveClientPacket(Object packet) {
322319
}
323320

324321
@Override
325-
public Protocol getCurrentProtocol() {
326-
// ensure that the accessor to the protocol field is available
327-
if (this.protocolAccessor == null) {
328-
this.protocolAccessor = Accessors.getFieldAccessor(
329-
this.networkManager.getClass(),
330-
MinecraftReflection.getEnumProtocolClass(),
331-
true);
332-
}
333-
334-
Object nmsProtocol = this.protocolAccessor.get(this.networkManager);
335-
return Protocol.fromVanilla((Enum<?>) nmsProtocol);
322+
public Protocol getCurrentProtocol(PacketType.Sender sender) {
323+
return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.wrappedChannel, sender);
336324
}
337325

338326
@Override

src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
2929
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
3030
import com.comphenix.protocol.utility.MinecraftReflection;
31-
import com.comphenix.protocol.utility.Util;
3231
import com.comphenix.protocol.wrappers.Pair;
3332
import io.netty.channel.ChannelFuture;
3433
import org.bukkit.Server;
@@ -93,7 +92,8 @@ public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMark
9392
Class<?> packetClass = packet.getClass();
9493
if (marker != null || MinecraftReflection.isBundlePacket(packetClass) || outboundListeners.contains(packetClass)) {
9594
// wrap packet and construct the event
96-
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet);
95+
PacketType.Protocol currentProtocol = injector.getCurrentProtocol(PacketType.Sender.SERVER);
96+
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(currentProtocol, packetClass), packet);
9797
PacketEvent packetEvent = PacketEvent.fromServer(this, container, marker, injector.getPlayer());
9898

9999
// post to all listeners, then return the packet event we constructed
@@ -111,7 +111,8 @@ public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMa
111111
Class<?> packetClass = packet.getClass();
112112
if (marker != null || inboundListeners.contains(packetClass)) {
113113
// wrap the packet and construct the event
114-
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet);
114+
PacketType.Protocol currentProtocol = injector.getCurrentProtocol(PacketType.Sender.CLIENT);
115+
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(currentProtocol, packetClass), packet);
115116
PacketEvent packetEvent = PacketEvent.fromClient(this, container, marker, injector.getPlayer());
116117

117118
// post to all listeners, then return the packet event we constructed
@@ -238,7 +239,6 @@ public void close() {
238239
// just reset to the list we wrapped originally
239240
ListeningList ourList = (ListeningList) currentFieldValue;
240241
List<Object> original = ourList.getOriginal();
241-
//noinspection SynchronizationOnLocalVariableOrMethodParameter
242242
synchronized (original) {
243243
// revert the injection from all values of the list
244244
ourList.unProcessAll();

src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ public class PacketRegistry {
4848
protected static class Register {
4949
// The main lookup table
5050
final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
51+
5152
final Map<Class<?>, PacketType> classToType = new ConcurrentHashMap<>();
53+
final Map<PacketType.Protocol, Map<Class<?>, PacketType>> protocolClassToType = new ConcurrentHashMap<>();
5254

5355
volatile Set<PacketType> serverPackets = new HashSet<>();
5456
volatile Set<PacketType> clientPackets = new HashSet<>();
@@ -58,7 +60,10 @@ public Register() {}
5860

5961
public void registerPacket(PacketType type, Class<?> clazz, Sender sender) {
6062
typeToClass.put(type, Optional.of(clazz));
63+
6164
classToType.put(clazz, type);
65+
protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(clazz, type);
66+
6267
if (sender == Sender.CLIENT) {
6368
clientPackets.add(type);
6469
} else {
@@ -430,7 +435,9 @@ public static Class<?> getPacketClassFromType(PacketType type) {
430435
* Retrieve the packet type of a given packet.
431436
* @param packet - the class of the packet.
432437
* @return The packet type, or NULL if not found.
438+
* @deprecated major issues due to packets with shared classes being registered in multiple states.
433439
*/
440+
@Deprecated
434441
public static PacketType getPacketType(Class<?> packet) {
435442
initialize();
436443

@@ -440,7 +447,24 @@ public static PacketType getPacketType(Class<?> packet) {
440447

441448
return REGISTER.classToType.get(packet);
442449
}
443-
450+
451+
/**
452+
* Retrieve the associated packet type for a packet class in the given protocol state.
453+
*
454+
* @param protocol the protocol state to retrieve the packet from.
455+
* @param packet the class identifying the packet type.
456+
* @return the packet type associated with the given class in the given protocol state, or null if not found.
457+
*/
458+
public static PacketType getPacketType(PacketType.Protocol protocol, Class<?> packet) {
459+
initialize();
460+
if (MinecraftReflection.isBundlePacket(packet)) {
461+
return PacketType.Play.Server.BUNDLE;
462+
}
463+
464+
Map<Class<?>, PacketType> classToTypesForProtocol = REGISTER.protocolClassToType.get(protocol);
465+
return classToTypesForProtocol == null ? null : classToTypesForProtocol.get(packet);
466+
}
467+
444468
/**
445469
* Retrieve the packet type of a given packet.
446470
* @param packet - the class of the packet.

src/main/java/com/comphenix/protocol/reflect/ObjectWriter.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@
1717

1818
package com.comphenix.protocol.reflect;
1919

20-
import com.comphenix.protocol.PacketType;
21-
import com.comphenix.protocol.injector.StructureCache;
22-
import com.comphenix.protocol.injector.packet.PacketRegistry;
23-
import com.comphenix.protocol.utility.MinecraftReflection;
24-
import com.comphenix.protocol.utility.StreamSerializer;
2520
import java.lang.reflect.Field;
2621
import java.lang.reflect.Modifier;
2722
import java.util.HashMap;
@@ -46,18 +41,6 @@ public class ObjectWriter {
4641
* @return A structure modifier for the given type.
4742
*/
4843
private StructureModifier<Object> getModifier(Class<?> type) {
49-
Class<?> packetClass = MinecraftReflection.getPacketClass();
50-
51-
// Handle subclasses of the packet class with our custom structure cache, if possible
52-
if (!type.equals(packetClass) && packetClass.isAssignableFrom(type)) {
53-
// might be a packet, but some packets are not registered (for example PacketPlayInFlying, only the subtypes are present)
54-
PacketType packetType = PacketRegistry.getPacketType(type);
55-
if (packetType != null) {
56-
// packet is present, delegate to the cache
57-
return StructureCache.getStructure(packetType);
58-
}
59-
}
60-
6144
// Create the structure modifier if we haven't already
6245
StructureModifier<Object> modifier = CACHE.get(type);
6346
if (modifier == null) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.comphenix.protocol.injector.netty.channel;
2+
3+
import com.comphenix.protocol.BukkitInitialization;
4+
import com.comphenix.protocol.PacketType;
5+
import io.netty.channel.Channel;
6+
import io.netty.channel.local.LocalServerChannel;
7+
import net.minecraft.network.EnumProtocol;
8+
import net.minecraft.network.NetworkManager;
9+
import net.minecraft.network.protocol.EnumProtocolDirection;
10+
import org.junit.jupiter.api.Assertions;
11+
import org.junit.jupiter.api.BeforeAll;
12+
import org.junit.jupiter.api.Test;
13+
14+
public class ChannelProtocolUtilTest {
15+
16+
@BeforeAll
17+
public static void beforeClass() {
18+
BukkitInitialization.initializeAll();
19+
}
20+
21+
@Test
22+
public void testProtocolResolving() {
23+
Channel channel = new LocalServerChannel();
24+
channel.attr(NetworkManager.e).set(EnumProtocol.e.b(EnumProtocolDirection.a)); // ATTRIBUTE_SERVERBOUND_PROTOCOL -> Protocol.CONFIG.codec(SERVERBOUND)
25+
channel.attr(NetworkManager.f).set(EnumProtocol.b.b(EnumProtocolDirection.b)); // ATTRIBUTE_CLIENTBOUND_PROTOCOL -> Protocol.PLAY.codec(CLIENTBOUND)
26+
27+
PacketType.Protocol serverBoundProtocol = ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(channel, PacketType.Sender.CLIENT);
28+
Assertions.assertEquals(PacketType.Protocol.CONFIGURATION, serverBoundProtocol);
29+
30+
PacketType.Protocol clientBoundProtocol = ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(channel, PacketType.Sender.SERVER);
31+
Assertions.assertEquals(PacketType.Protocol.PLAY, clientBoundProtocol);
32+
}
33+
}

0 commit comments

Comments
 (0)