Skip to content

Commit a7aa31a

Browse files
authored
improve support for custom payloads in 1.20.2 (#2553)
1 parent af33a2a commit a7aa31a

File tree

2 files changed

+103
-24
lines changed

2 files changed

+103
-24
lines changed

src/main/java/com/comphenix/protocol/wrappers/CustomPacketPayloadWrapper.java

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
import com.comphenix.protocol.utility.MinecraftReflection;
1313
import com.comphenix.protocol.utility.StreamSerializer;
1414
import io.netty.buffer.ByteBuf;
15+
import io.netty.buffer.Unpooled;
16+
1517
import java.lang.reflect.Constructor;
1618
import java.lang.reflect.Method;
1719
import java.lang.reflect.Modifier;
1820
import java.util.Objects;
21+
1922
import net.bytebuddy.ByteBuddy;
2023
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
2124
import net.bytebuddy.implementation.FieldAccessor;
@@ -40,26 +43,32 @@ public final class CustomPacketPayloadWrapper {
4043
private static final Class<?> MINECRAFT_KEY_CLASS;
4144
private static final Class<?> CUSTOM_PACKET_PAYLOAD_CLASS;
4245

43-
private static final MethodAccessor WRITE_BYTES_METHOD;
4446
private static final ConstructorAccessor PAYLOAD_WRAPPER_CONSTRUCTOR;
4547

48+
private static final MethodAccessor GET_ID_PAYLOAD_METHOD;
49+
private static final MethodAccessor SERIALIZE_PAYLOAD_METHOD;
50+
4651
private static final EquivalentConverter<CustomPacketPayloadWrapper> CONVERTER;
4752

4853
static {
4954
try {
50-
// using this method is a small hack to prevent fuzzy from finding the renamed "getBytes(byte[])" method
51-
// the method we're extracting here is: writeBytes(byte[] data, int arrayStartInclusive, int arrayEndExclusive)
52-
Class<?> packetDataSerializer = MinecraftReflection.getPacketDataSerializerClass();
53-
Method writeBytes = FuzzyReflection.fromClass(packetDataSerializer, false).getMethod(FuzzyMethodContract.newBuilder()
55+
MINECRAFT_KEY_CLASS = MinecraftReflection.getMinecraftKeyClass();
56+
CUSTOM_PACKET_PAYLOAD_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.common.custom.CustomPacketPayload");
57+
58+
Method getPayloadId = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
5459
.banModifier(Modifier.STATIC)
55-
.requireModifier(Modifier.PUBLIC)
56-
.parameterExactArray(byte[].class, int.class, int.class)
57-
.returnTypeExact(packetDataSerializer)
60+
.returnTypeExact(MINECRAFT_KEY_CLASS)
61+
.parameterCount(0)
5862
.build());
59-
WRITE_BYTES_METHOD = Accessors.getMethodAccessor(writeBytes);
63+
GET_ID_PAYLOAD_METHOD = Accessors.getMethodAccessor(getPayloadId);
6064

61-
MINECRAFT_KEY_CLASS = MinecraftReflection.getMinecraftKeyClass();
62-
CUSTOM_PACKET_PAYLOAD_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.common.custom.CustomPacketPayload");
65+
Method serializePayloadData = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
66+
.banModifier(Modifier.STATIC)
67+
.returnTypeVoid()
68+
.parameterCount(1)
69+
.parameterDerivedOf(ByteBuf.class, 0)
70+
.build());
71+
SERIALIZE_PAYLOAD_METHOD = Accessors.getMethodAccessor(serializePayloadData);
6372

6473
Constructor<?> payloadWrapperConstructor = makePayloadWrapper();
6574
PAYLOAD_WRAPPER_CONSTRUCTOR = Accessors.getConstructorAccessor(payloadWrapperConstructor);
@@ -153,23 +162,36 @@ public static EquivalentConverter<CustomPacketPayloadWrapper> getConverter() {
153162
}
154163

155164
/**
156-
* Constructs this wrapper from an incoming ServerboundCustomPayloadPacket.UnknownPayload. All other types of
157-
* payloads are not supported and will result in an exception.
165+
* Constructs this wrapper from any CustomPayload type.
158166
* <p>
159-
* Note: the buffer of the given UnknownPayload will <strong>NOT</strong> be released by this operation. Make sure
167+
* Note: the buffer of the given payload (if any) will <strong>NOT</strong> be released by this operation. Make sure
160168
* to release the buffer manually if you discard the packet to prevent memory leaks.
161169
*
162-
* @param unknownPayload the instance of the unknown payload to convert to this wrapper.
163-
* @return a wrapper holding the minecraft key and payload of the given UnknownPayload instance.
170+
* @param payload the instance of the custom payload to convert to this wrapper.
171+
* @return a wrapper holding the minecraft key and payload of the given custom payload instance.
164172
*/
165-
public static CustomPacketPayloadWrapper fromUnknownPayload(Object unknownPayload) {
166-
StructureModifier<Object> modifier = new StructureModifier<>(unknownPayload.getClass()).withTarget(unknownPayload);
167-
Object messageId = modifier.withType(MINECRAFT_KEY_CLASS).read(0);
168-
ByteBuf messagePayload = (ByteBuf) modifier.withType(ByteBuf.class).read(0);
169-
173+
public static CustomPacketPayloadWrapper fromUnknownPayload(Object payload) {
174+
Object messageId = GET_ID_PAYLOAD_METHOD.invoke(payload);
170175
MinecraftKey id = MinecraftKey.getConverter().getSpecific(messageId);
171-
byte[] payload = StreamSerializer.getDefault().getBytesAndRelease(messagePayload.retain());
172-
return new CustomPacketPayloadWrapper(payload, id);
176+
177+
// we read and retain the underlying buffer in case the class uses a buffer to store the data
178+
// this way, when passing the packet to further handling, the buffer is not released and can be re-used
179+
StructureModifier<Object> modifier = new StructureModifier<>(payload.getClass()).withTarget(payload);
180+
byte[] messagePayload = modifier.withType(ByteBuf.class).optionRead(0)
181+
.map(buffer -> {
182+
ByteBuf buf = (ByteBuf) buffer;
183+
byte[] data = StreamSerializer.getDefault().getBytesAndRelease(buf.markReaderIndex().retain());
184+
buf.resetReaderIndex();
185+
return data;
186+
})
187+
.orElseGet(() -> {
188+
ByteBuf buffer = Unpooled.buffer();
189+
Object serializer = MinecraftReflection.getPacketDataSerializer(buffer);
190+
SERIALIZE_PAYLOAD_METHOD.invoke(payload, serializer);
191+
return StreamSerializer.getDefault().getBytesAndRelease(buffer);
192+
});
193+
194+
return new CustomPacketPayloadWrapper(messagePayload, id);
173195
}
174196

175197
/**
@@ -217,7 +239,7 @@ public Object newHandle() {
217239
@SuppressWarnings("unused")
218240
static final class CustomPacketPayloadInterceptionHandler {
219241
public static void intercept(@FieldValue("payload") byte[] payload, @Argument(0) Object packetBuffer) {
220-
WRITE_BYTES_METHOD.invoke(packetBuffer, payload, 0, payload.length);
242+
((ByteBuf) packetBuffer).writeBytes(payload);
221243
}
222244
}
223245
}

src/test/java/com/comphenix/protocol/events/PacketContainerTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.reflect.Array;
2121
import java.lang.reflect.Field;
2222
import java.lang.reflect.Modifier;
23+
import java.nio.charset.StandardCharsets;
2324
import java.util.ArrayList;
2425
import java.util.Collection;
2526
import java.util.Collections;
@@ -58,7 +59,10 @@
5859
import net.md_5.bungee.api.chat.HoverEvent;
5960
import net.md_5.bungee.api.chat.hover.content.Text;
6061
import net.minecraft.core.registries.BuiltInRegistries;
62+
import net.minecraft.network.PacketDataSerializer;
63+
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
6164
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
65+
import net.minecraft.network.protocol.common.custom.BrandPayload;
6266
import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange;
6367
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
6468
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot;
@@ -416,6 +420,59 @@ public void testUnknownPayloadDeserialize() {
416420
Assertions.assertArrayEquals(payloadData, payloadWrapper.getPayload());
417421
}
418422

423+
@Test
424+
public void testCustomPayloadPacket() {
425+
byte[] customPayload = "Hello World, This is A Super-Cool-Test!!!!!".getBytes(StandardCharsets.UTF_8);
426+
com.comphenix.protocol.wrappers.MinecraftKey key = new com.comphenix.protocol.wrappers.MinecraftKey("protocollib", "test");
427+
CustomPacketPayloadWrapper payloadWrapper = new CustomPacketPayloadWrapper(customPayload, key);
428+
429+
PacketContainer container = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD);
430+
container.getCustomPacketPayloads().write(0, payloadWrapper);
431+
432+
PacketDataSerializer serializer = new PacketDataSerializer(Unpooled.buffer());
433+
ClientboundCustomPayloadPacket constructedHandle = (ClientboundCustomPayloadPacket) container.getHandle();
434+
constructedHandle.a(serializer);
435+
436+
ServerboundCustomPayloadPacket deserializedHandle = new ServerboundCustomPayloadPacket(serializer);
437+
PacketContainer serverContainer = new PacketContainer(PacketType.Play.Client.CUSTOM_PAYLOAD, deserializedHandle);
438+
439+
CustomPacketPayloadWrapper deserializedPayloadWrapper = serverContainer.getCustomPacketPayloads().read(0);
440+
Assertions.assertEquals(key, deserializedPayloadWrapper.getId());
441+
Assertions.assertArrayEquals(customPayload, deserializedPayloadWrapper.getPayload());
442+
}
443+
444+
@Test
445+
public void testSomeCustomPayloadRead() {
446+
BrandPayload payload = new BrandPayload("Hello World!");
447+
ClientboundCustomPayloadPacket handle = new ClientboundCustomPayloadPacket(payload);
448+
449+
PacketContainer container = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD, handle);
450+
CustomPacketPayloadWrapper payloadWrapper = container.getCustomPacketPayloads().read(0);
451+
452+
com.comphenix.protocol.wrappers.MinecraftKey payloadId = payloadWrapper.getId();
453+
Assertions.assertEquals(BrandPayload.a.toString(), payloadId.getFullKey());
454+
455+
PacketDataSerializer serializer = new PacketDataSerializer(Unpooled.wrappedBuffer(payloadWrapper.getPayload()));
456+
BrandPayload deserializedPayload = new BrandPayload(serializer);
457+
Assertions.assertEquals(payload.b(), deserializedPayload.b());
458+
}
459+
460+
@Test
461+
public void testUnknownPayloadNotReleasedOnRead() {
462+
MinecraftKey id = new MinecraftKey("plib", "main");
463+
ByteBuf data = Unpooled.wrappedBuffer("This is a Test!!".getBytes(StandardCharsets.UTF_8));
464+
ServerboundCustomPayloadPacket.UnknownPayload payload = new ServerboundCustomPayloadPacket.UnknownPayload(id, data);
465+
ServerboundCustomPayloadPacket handle = new ServerboundCustomPayloadPacket(payload);
466+
467+
PacketContainer container = new PacketContainer(PacketType.Play.Client.CUSTOM_PAYLOAD, handle);
468+
CustomPacketPayloadWrapper payloadWrapper = container.getCustomPacketPayloads().read(0);
469+
470+
Assertions.assertEquals(id.toString(), payloadWrapper.getId().getFullKey());
471+
Assertions.assertEquals("This is a Test!!", new String(payloadWrapper.getPayload()));
472+
Assertions.assertEquals(1, payload.data().refCnt());
473+
Assertions.assertEquals(0, payload.data().readerIndex());
474+
}
475+
419476
@Test
420477
public void testIntList() {
421478
PacketContainer destroy = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY);

0 commit comments

Comments
 (0)