Skip to content

Commit aebefde

Browse files
authored
Packet filtering for bundled packets in 1.19.4 (#2258)
Since Minecraft 1.19.4, the protocol supports bundling consecutive packets to ensure the client processes them in one tick. However, Packet Events are not called for the individual packets in such a bundle in the current dev build of ProtocolLib. For example, no packet events are currently sent for the ENTITY_METADATA packet when an entity is first spawned as the packet is bundled with the ENTITY_SPAWN packet. However, if the entity metadata is changed later on, the event will be called. This PR proposes to fix this by unpacking the bundled packets and invoking the packet filtering for each packet. I also want to briefly explain how the bundling works. A bundle starts with a PACKET_DELIMITER (0x00, net.minecraft.network.protocol.BundleDelimiterPacket) packet followed by all packets that should be bundled and finished with another PACKET_DELIMITER (0x00). Within the Netty pipeline, this sequence is transformed into one synthesized packet found in net.minecraft.network.protocol.game.ClientboundBundlePacket, which is essentially just a list of packets. At the stage at which ProtocolLib injects into the clientbound netty pipeline, this packet has not been unpacked yet. Thus, we need to handle the ClientboundBundlePacket, which unfortunately is not registered in ProtocolLib. The fact that two different classes map to the same packet currently requires a dirty remapping in the packet structure modifier.
1 parent 64e1e7d commit aebefde

File tree

15 files changed

+231
-57
lines changed

15 files changed

+231
-57
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125

126126
<plugin>
127127
<artifactId>maven-javadoc-plugin</artifactId>
128-
<version>3.4.0</version>
128+
<version>3.5.0</version>
129129
<configuration>
130130
<failOnError>false</failOnError>
131131
<encoding>ISO-8859-1</encoding>

src/main/java/com/comphenix/protocol/events/AbstractStructure.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,12 @@ public <T> StructureModifier<Optional<T>> getOptionals(EquivalentConverter<T> co
11211121
return structureModifier.withType(Optional.class, Converters.optional(converter));
11221122
}
11231123

1124+
public StructureModifier<Iterable<PacketContainer>> getPacketBundles() {
1125+
return structureModifier.withType(Iterable.class, Converters.iterable(
1126+
BukkitConverters.getPacketContainerConverter(), ArrayList::new, ArrayList::new
1127+
));
1128+
}
1129+
11241130
/**
11251131
* Represents an equivalent converter for ItemStack arrays.
11261132
* @author Kristian

src/main/java/com/comphenix/protocol/events/PacketEvent.java

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import org.bukkit.entity.Player;
4141
import org.bukkit.event.Cancellable;
4242

43+
import javax.annotation.Nullable;
44+
4345
/**
4446
* Represents a packet sending or receiving event. Changes to the packet will be reflected in the final version to be
4547
* sent or received. It is also possible to cancel an event.
@@ -70,6 +72,9 @@ public class PacketEvent extends EventObject implements Cancellable {
7072
private boolean readOnly;
7173
private boolean filtered;
7274

75+
@Nullable
76+
private PacketEvent bundle;
77+
7378
/**
7479
* Use the static constructors to create instances of this event.
7580
*
@@ -81,27 +86,28 @@ public PacketEvent(Object source) {
8186
}
8287

8388
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
84-
this(source, packet, null, player, serverPacket, true);
89+
this(source, packet, null, player, serverPacket, true, null);
8590
}
8691

8792
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket,
88-
boolean filtered) {
93+
boolean filtered, @Nullable PacketEvent bundleEvent) {
8994
super(source);
9095
this.packet = packet;
9196
this.playerReference = new WeakReference<>(player);
9297
this.networkMarker = marker;
9398
this.serverPacket = serverPacket;
9499
this.filtered = filtered;
100+
this.bundle = bundleEvent;
95101
}
96102

97-
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
98-
super(origial.source);
99-
this.packet = origial.packet;
100-
this.playerReference = origial.getPlayerReference();
101-
this.cancel = origial.cancel;
102-
this.serverPacket = origial.serverPacket;
103-
this.filtered = origial.filtered;
104-
this.networkMarker = origial.networkMarker;
103+
private PacketEvent(PacketEvent original, AsyncMarker asyncMarker) {
104+
super(original.source);
105+
this.packet = original.packet;
106+
this.playerReference = original.getPlayerReference();
107+
this.cancel = original.cancel;
108+
this.serverPacket = original.serverPacket;
109+
this.filtered = original.filtered;
110+
this.networkMarker = original.networkMarker;
105111
this.asyncMarker = asyncMarker;
106112
this.asynchronous = true;
107113
}
@@ -128,7 +134,7 @@ public static PacketEvent fromClient(Object source, PacketContainer packet, Play
128134
* @return The event.
129135
*/
130136
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
131-
return new PacketEvent(source, packet, marker, client, false, true);
137+
return new PacketEvent(source, packet, marker, client, false, true, null);
132138
}
133139

134140
/**
@@ -145,7 +151,7 @@ public static PacketEvent fromClient(Object source, PacketContainer packet, Netw
145151
*/
146152
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client,
147153
boolean filtered) {
148-
return new PacketEvent(source, packet, marker, client, false, filtered);
154+
return new PacketEvent(source, packet, marker, client, false, filtered, null);
149155
}
150156

151157
/**
@@ -170,7 +176,7 @@ public static PacketEvent fromServer(Object source, PacketContainer packet, Play
170176
* @return The event.
171177
*/
172178
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
173-
return new PacketEvent(source, packet, marker, recipient, true, true);
179+
return new PacketEvent(source, packet, marker, recipient, true, true, null);
174180
}
175181

176182
/**
@@ -187,7 +193,25 @@ public static PacketEvent fromServer(Object source, PacketContainer packet, Netw
187193
*/
188194
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
189195
boolean filtered) {
190-
return new PacketEvent(source, packet, marker, recipient, true, filtered);
196+
return fromServer(source, packet, marker, recipient, filtered, null);
197+
}
198+
199+
/**
200+
* Creates an event representing a server packet transmission.
201+
* <p>
202+
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
203+
*
204+
* @param source - the event source.
205+
* @param packet - the packet.
206+
* @param marker - the network marker.
207+
* @param recipient - the client that will receieve the packet.
208+
* @param filtered - whether this packet event is processed by every packet listener.
209+
* @param bundle - The corresponding packet event of the bundle if this packet is part of a bundle. Otherwise, null.
210+
* @return The event.
211+
*/
212+
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
213+
boolean filtered, @Nullable PacketEvent bundle) {
214+
return new PacketEvent(source, packet, marker, recipient, true, filtered, bundle);
191215
}
192216

193217
/**
@@ -517,6 +541,15 @@ private void readObject(ObjectInputStream input) throws ClassNotFoundException,
517541
}
518542
}
519543

544+
/**
545+
* Returns the packet event corresponding to the bundle if this packet is sent as a part of a bundle, t. Otherwise, null.
546+
* @return Corresponding packet event or null.
547+
*/
548+
@Nullable
549+
public PacketEvent getBundle() {
550+
return bundle;
551+
}
552+
520553
@Override
521554
public String toString() {
522555
return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";

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

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,25 @@
1717

1818
package com.comphenix.protocol.injector;
1919

20+
import java.util.ArrayList;
2021
import java.util.Collection;
22+
import java.util.List;
2123

24+
import com.comphenix.protocol.PacketType;
2225
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
2326
import com.comphenix.protocol.error.ErrorReporter;
2427
import com.comphenix.protocol.events.ListenerPriority;
28+
import com.comphenix.protocol.events.PacketContainer;
2529
import com.comphenix.protocol.events.PacketEvent;
2630
import com.comphenix.protocol.events.PacketListener;
31+
import com.comphenix.protocol.injector.packet.PacketRegistry;
32+
import com.comphenix.protocol.reflect.StructureModifier;
2733
import com.comphenix.protocol.timing.TimedListenerManager;
2834
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
2935
import com.comphenix.protocol.timing.TimedTracker;
3036

37+
import javax.annotation.Nullable;
38+
3139
/**
3240
* Registry of synchronous packet listeners.
3341
*
@@ -108,7 +116,7 @@ public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event, Lis
108116
* @param event - the related packet event.
109117
* @param element - the listener to invoke.
110118
*/
111-
private final void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
119+
private void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
112120
try {
113121
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
114122
element.getListener().onPacketReceiving(event);
@@ -130,59 +138,63 @@ private final void invokeReceivingListener(ErrorReporter reporter, PacketEvent e
130138
* @param event - the packet event to invoke.
131139
*/
132140
public void invokePacketSending(ErrorReporter reporter, PacketEvent event) {
133-
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
134-
135-
if (list == null)
136-
return;
137-
138-
if (timedManager.isTiming()) {
139-
for (PrioritizedListener<PacketListener> element : list) {
140-
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
141-
long token = tracker.beginTracking();
142-
143-
// Measure and record the execution time
144-
invokeSendingListener(reporter, event, element);
145-
tracker.endTracking(token, event.getPacketType());
146-
}
147-
} else {
148-
for (PrioritizedListener<PacketListener> element : list) {
149-
invokeSendingListener(reporter, event, element);
150-
}
151-
}
141+
invokePacketSending(reporter, event, null);
152142
}
153143

154144
/**
155145
* Invokes the given packet event for every registered listener of the given priority.
156146
* @param reporter - the error reporter that will be used to inform about listener exceptions.
157147
* @param event - the packet event to invoke.
158-
* @param priorityFilter - the required priority for a listener to be invoked.
148+
* @param priorityFilter - the priority for a listener to be invoked. If null is provided, every registered listener will be invoked
159149
*/
160-
public void invokePacketSending(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) {
150+
public void invokePacketSending(ErrorReporter reporter, PacketEvent event, @Nullable ListenerPriority priorityFilter) {
151+
invokeUnpackedPacketSending(reporter, event, priorityFilter);
152+
if(event.getPacketType() == PacketType.Play.Server.DELIMITER && !event.isCancelled()) {
153+
// unpack the bundle and invoke for each packet in the bundle
154+
Iterable<PacketContainer> packets = event.getPacket().getPacketBundles().read(0);
155+
List<PacketContainer> outPackets = new ArrayList<>();
156+
for(PacketContainer subPacket : packets) {
157+
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(), event.getPlayer());
158+
invokeUnpackedPacketSending(reporter, subPacketEvent, priorityFilter);
159+
160+
if(!subPacketEvent.isCancelled()) {
161+
outPackets.add(subPacketEvent.getPacket()); // if the packet event has been cancelled, the packet will be removed from the bundle.
162+
}
163+
}
164+
if(packets.iterator().hasNext()) { // are there still packets in this bundle?
165+
event.getPacket().getPacketBundles().write(0, outPackets);
166+
} else {
167+
event.setCancelled(true); // cancel packet if each individual packet has been canceled
168+
}
169+
}
170+
}
171+
172+
private void invokeUnpackedPacketSending(ErrorReporter reporter, PacketEvent event, @org.jetbrains.annotations.Nullable ListenerPriority priorityFilter) {
161173
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
162-
174+
163175
if (list == null)
164176
return;
165-
177+
166178
if (timedManager.isTiming()) {
167179
for (PrioritizedListener<PacketListener> element : list) {
168-
if (element.getPriority() == priorityFilter) {
169-
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
170-
long token = tracker.beginTracking();
171-
172-
// Measure and record the execution time
173-
invokeSendingListener(reporter, event, element);
174-
tracker.endTracking(token, event.getPacketType());
180+
if (priorityFilter == null || element.getPriority() == priorityFilter) {
181+
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
182+
long token = tracker.beginTracking();
183+
184+
// Measure and record the execution time
185+
invokeSendingListener(reporter, event, element);
186+
tracker.endTracking(token, event.getPacketType());
175187
}
176188
}
177189
} else {
178190
for (PrioritizedListener<PacketListener> element : list) {
179-
if (element.getPriority() == priorityFilter) {
191+
if (priorityFilter == null || element.getPriority() == priorityFilter) {
180192
invokeSendingListener(reporter, event, element);
181193
}
182194
}
183195
}
184196
}
185-
197+
186198
/**
187199
* Invoke a particular sending listener.
188200
* @param reporter - the error reporter.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ public static StructureModifier<Object> getStructure(final PacketType packetType
135135

136136
return STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> {
137137
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
138+
139+
// We need to map the Bundle Delimiter to the synthetic bundle packet which contains a list of all packets in a bundle
140+
if(MinecraftVersion.atOrAbove(MinecraftVersion.FEATURE_PREVIEW_2) && packetClass.equals(MinecraftReflection.getBundleDelimiterClass())) {
141+
packetClass = MinecraftReflection.getPackedBundlePacketClass();
142+
}
138143
return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true);
139144
});
140145
}

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,7 @@
2727
import com.comphenix.protocol.reflect.accessors.Accessors;
2828
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
2929
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
30-
import com.comphenix.protocol.utility.ByteBuddyGenerated;
31-
import com.comphenix.protocol.utility.MinecraftFields;
32-
import com.comphenix.protocol.utility.MinecraftMethods;
33-
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
34-
import com.comphenix.protocol.utility.MinecraftReflection;
35-
import com.comphenix.protocol.utility.MinecraftVersion;
30+
import com.comphenix.protocol.utility.*;
3631
import com.comphenix.protocol.wrappers.WrappedGameProfile;
3732
import io.netty.channel.Channel;
3833
import io.netty.channel.ChannelHandler;
@@ -558,7 +553,7 @@ <T> T processOutbound(T action) {
558553
}
559554

560555
// no listener and no marker - no magic :)
561-
if (!this.channelListener.hasListener(packet.getClass()) && marker == null) {
556+
if (!this.channelListener.hasListener(packet.getClass()) && marker == null && !Util.isBundlePacket(packet.getClass())) {
562557
return action;
563558
}
564559

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
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;
3132
import com.comphenix.protocol.wrappers.Pair;
3233
import io.netty.channel.ChannelFuture;
3334
import org.bukkit.Server;
@@ -90,7 +91,7 @@ public NetworkManagerInjector(Plugin plugin, Server server, ListenerInvoker list
9091
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
9192
// check if we need to intercept the packet
9293
Class<?> packetClass = packet.getClass();
93-
if (this.outboundListeners.contains(packetClass) || marker != null) {
94+
if (this.outboundListeners.contains(packetClass) || marker != null || Util.isBundlePacket(packetClass)) {
9495
// wrap packet and construct the event
9596
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet);
9697
PacketEvent packetEvent = PacketEvent.fromServer(this, container, marker, injector.getPlayer());

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
3131
import com.comphenix.protocol.utility.MinecraftReflection;
3232
import com.comphenix.protocol.utility.MinecraftVersion;
33+
import com.comphenix.protocol.utility.Util;
3334

3435
/**
3536
* Static packet registry in Minecraft.
@@ -382,6 +383,9 @@ public static Class<?> getPacketClassFromType(PacketType type) {
382383
* @return The packet type, or NULL if not found.
383384
*/
384385
public static PacketType getPacketType(Class<?> packet) {
386+
if(Util.isBundlePacket(packet)) {
387+
return PacketType.Play.Server.DELIMITER;
388+
}
385389
initialize();
386390
return REGISTER.classToType.get(packet);
387391
}

src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,14 @@ public static Class<?> getIChatBaseComponentClass() {
628628
return getMinecraftClass("network.chat.IChatBaseComponent", "network.chat.IChatbaseComponent", "network.chat.Component", "IChatBaseComponent");
629629
}
630630

631+
public static Class<?> getPackedBundlePacketClass() {
632+
return getMinecraftClass("network.protocol.game.ClientboundBundlePacket", "ClientboundBundlePacket");
633+
}
634+
635+
public static Class<?> getBundleDelimiterClass() {
636+
return getMinecraftClass("network.protocol.BundleDelimiterPacket","BundleDelimiterPacket");
637+
}
638+
631639
public static Class<?> getIChatBaseComponentArrayClass() {
632640
return getArrayClass(getIChatBaseComponentClass());
633641
}

src/main/java/com/comphenix/protocol/utility/Util.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
public final class Util {
2323

2424
private static final boolean SPIGOT = classExists("org.spigotmc.SpigotConfig");
25+
private static Class<?> cachedBundleClass;
2526

2627
public static boolean classExists(String className) {
2728
try {
@@ -59,4 +60,14 @@ public static boolean isCurrentlyReloading() {
5960
}
6061
return false;
6162
}
63+
64+
public static boolean isBundlePacket(Class<?> packetClass) {
65+
if(!MinecraftVersion.atOrAbove(MinecraftVersion.FEATURE_PREVIEW_2)) {
66+
return false;
67+
}
68+
if(cachedBundleClass == null) {
69+
cachedBundleClass = MinecraftReflection.getPackedBundlePacketClass();
70+
}
71+
return packetClass.equals(cachedBundleClass);
72+
}
6273
}

0 commit comments

Comments
 (0)