fix custom payload

This commit is contained in:
Pasqual Koschmieder 2023-09-22 18:59:21 +02:00 committed by Dan Mulloy
parent 7fe644c787
commit 97323b2ceb
3 changed files with 254 additions and 15 deletions

View File

@ -867,6 +867,16 @@ public abstract class AbstractStructure {
MinecraftKey.getConverter());
}
/**
* Retrieve a read/write structure for custom packet payloads (available since Minecraft 1.20.2).
* @return A modifier for CustomPacketPayloads fields.
*/
public StructureModifier<CustomPacketPayloadWrapper> getCustomPacketPayloads() {
return structureModifier.withType(
CustomPacketPayloadWrapper.getCustomPacketPayloadClass(),
CustomPacketPayloadWrapper.getConverter());
}
/**
* Retrieve a read/write structure for dimension IDs in 1.13.1+
* @return A modifier for dimension IDs

View File

@ -0,0 +1,223 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
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.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ByteBuddyGenerated;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import io.netty.buffer.ByteBuf;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Objects;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Argument;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.matcher.ElementMatchers;
/**
* A wrapper for the CustomPacketPayload class in 1.20.2. Due to the nature of the class, not all types are supported
* by default. Constructing a new wrapper instance will give out a handle to a completely new implemented type, that
* allows to set a key and some kind of data of any choice.
* <p>
* Note that constructing this class from a generic handle is only possible for the spigot-specific UnknownPayload type.
* All other payloads should be accessed via a structure modifier directly.
*
* @author Pasqual Koschmieder
*/
public final class CustomPacketPayloadWrapper {
private static final Class<?> MINECRAFT_KEY_CLASS;
private static final Class<?> CUSTOM_PACKET_PAYLOAD_CLASS;
private static final MethodAccessor WRITE_BYTES_METHOD;
private static final ConstructorAccessor PAYLOAD_WRAPPER_CONSTRUCTOR;
private static final EquivalentConverter<CustomPacketPayloadWrapper> CONVERTER;
static {
try {
// using this method is a small hack to prevent fuzzy from finding the renamed "getBytes(byte[])" method
// the method we're extracting here is: writeBytes(byte[] data, int arrayStartInclusive, int arrayEndExclusive)
Class<?> packetDataSerializer = MinecraftReflection.getPacketDataSerializerClass();
Method writeBytes = FuzzyReflection.fromClass(packetDataSerializer, false).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.PUBLIC)
.parameterExactArray(byte[].class, int.class, int.class)
.returnTypeExact(packetDataSerializer)
.build());
WRITE_BYTES_METHOD = Accessors.getMethodAccessor(writeBytes);
MINECRAFT_KEY_CLASS = MinecraftReflection.getMinecraftKeyClass();
CUSTOM_PACKET_PAYLOAD_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.common.custom.CustomPacketPayload");
Constructor<?> payloadWrapperConstructor = makePayloadWrapper();
PAYLOAD_WRAPPER_CONSTRUCTOR = Accessors.getConstructorAccessor(payloadWrapperConstructor);
CONVERTER = new EquivalentConverter<CustomPacketPayloadWrapper>() {
@Override
public Object getGeneric(CustomPacketPayloadWrapper specific) {
return specific.newHandle();
}
@Override
public CustomPacketPayloadWrapper getSpecific(Object generic) {
return fromUnknownPayload(generic);
}
@Override
public Class<CustomPacketPayloadWrapper> getSpecificType() {
return CustomPacketPayloadWrapper.class;
}
};
} catch (Exception exception) {
throw new ExceptionInInitializerError(exception);
}
}
private static Constructor<?> makePayloadWrapper() throws Exception {
return new ByteBuddy()
.subclass(Object.class)
.name("com.comphenix.protocol.wrappers.ProtocolLibCustomPacketPayload")
.implement(CUSTOM_PACKET_PAYLOAD_CLASS, ByteBuddyGenerated.class)
.defineField("payload", byte[].class, Modifier.PRIVATE | Modifier.FINAL)
.defineField("id", MinecraftReflection.getMinecraftKeyClass(), Modifier.PRIVATE | Modifier.FINAL)
.defineConstructor(Modifier.PUBLIC)
.withParameters(MinecraftReflection.getMinecraftKeyClass(), byte[].class)
.intercept(MethodCall.invoke(Object.class.getConstructor())
.andThen(FieldAccessor.ofField("id").setsArgumentAt(0))
.andThen(FieldAccessor.ofField("payload").setsArgumentAt(1)))
.method(ElementMatchers.returns(MinecraftReflection.getMinecraftKeyClass()).and(ElementMatchers.takesNoArguments()))
.intercept(FieldAccessor.ofField("id"))
.method(ElementMatchers.returns(void.class).and(ElementMatchers.takesArguments(MinecraftReflection.getPacketDataSerializerClass())))
.intercept(MethodDelegation.to(CustomPacketPayloadInterceptionHandler.class))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getConstructor(MinecraftReflection.getMinecraftKeyClass(), byte[].class);
}
// ====== api methods ======
/**
* The wrapped payload in the message.
*/
private final byte[] payload;
/**
* The wrapped key of the message.
*/
private final MinecraftKey id;
/**
* The generic id of the message, lazy initialized when needed.
*/
private Object genericId;
/**
* Constructs a new payload wrapper instance using the given message payload and id.
*
* @param payload the payload of the message.
* @param id the id of the message.
* @throws NullPointerException if the given payload or id is null.
*/
public CustomPacketPayloadWrapper(byte[] payload, MinecraftKey id) {
this.payload = Objects.requireNonNull(payload, "payload");
this.id = Objects.requireNonNull(id, "id");
}
/**
* Get the CustomPacketPayload class that is backing this wrapper (available since Minecraft 1.20.2).
*
* @return the CustomPacketPayload class.
*/
public static Class<?> getCustomPacketPayloadClass() {
return CUSTOM_PACKET_PAYLOAD_CLASS;
}
/**
* Get a converter to convert this wrapper to a generic handle and an UnknownPayload type to this wrapper.
*
* @return a converter for this wrapper.
*/
public static EquivalentConverter<CustomPacketPayloadWrapper> getConverter() {
return CONVERTER;
}
/**
* Constructs this wrapper from an incoming ServerboundCustomPayloadPacket.UnknownPayload. All other types of
* payloads are not supported and will result in an exception.
* <p>
* Note: the buffer of the given UnknownPayload will <strong>NOT</strong> be released by this operation. Make sure
* to release the buffer manually if you discard the packet to prevent memory leaks.
*
* @param unknownPayload the instance of the unknown payload to convert to this wrapper.
* @return a wrapper holding the minecraft key and payload of the given UnknownPayload instance.
*/
public static CustomPacketPayloadWrapper fromUnknownPayload(Object unknownPayload) {
StructureModifier<Object> modifier = new StructureModifier<>(unknownPayload.getClass()).withTarget(unknownPayload);
Object messageId = modifier.withType(MINECRAFT_KEY_CLASS).read(0);
ByteBuf messagePayload = (ByteBuf) modifier.withType(ByteBuf.class).read(0);
MinecraftKey id = MinecraftKey.getConverter().getSpecific(messageId);
byte[] payload = StreamSerializer.getDefault().getBytesAndRelease(messagePayload.retain());
return new CustomPacketPayloadWrapper(payload, id);
}
/**
* Get the generic id of the wrapped message id.
*
* @return the generic key id.
*/
private Object getGenericId() {
if (this.genericId == null) {
this.genericId = MinecraftKey.getConverter().getGeneric(this.id);
}
return this.genericId;
}
/**
* Get the message payload of this wrapper. Changes made to the returned array will be reflected into this wrapper.
*
* @return the message payload.
*/
public byte[] getPayload() {
return this.payload;
}
/**
* Get the message id of this wrapper.
*
* @return the message id of this wrapper.
*/
public MinecraftKey getId() {
return this.id;
}
/**
* Constructs a <strong>NEW</strong> handle instance of a payload wrapper to use in a CustomPayload packet.
*
* @return a new payload wrapper instance using the provided message id and payload.
*/
public Object newHandle() {
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), this.payload);
}
/**
* Handles interception of the ProtocolLib specific CustomPayloadWrapper implementation. For internal use only.
*/
@SuppressWarnings("unused")
static final class CustomPacketPayloadInterceptionHandler {
public static void intercept(@FieldValue("payload") byte[] payload, @Argument(0) Object packetBuffer) {
WRITE_BYTES_METHOD.invoke(packetBuffer, payload, 0, payload.length);
}
}
}

View File

@ -15,6 +15,8 @@
*/
package com.comphenix.protocol.events;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@ -50,13 +52,13 @@ import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot;
@ -387,27 +389,31 @@ public class PacketContainerTest {
@Test
public void testBigPacketSerialization() {
PacketContainer payload = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD);
payload.getMinecraftKeys().write(0, new com.comphenix.protocol.wrappers.MinecraftKey("test"));
byte[] randomData = new byte[8192];
ThreadLocalRandom.current().nextBytes(randomData);
ByteBuf serializer = (ByteBuf) MinecraftReflection.createPacketDataSerializer(randomData.length);
serializer.writeBytes(randomData);
payload.getModifier().withType(MinecraftReflection.getPacketDataSerializerClass()).write(0, serializer);
CustomPacketPayloadWrapper payloadWrapper = new CustomPacketPayloadWrapper(randomData, new com.comphenix.protocol.wrappers.MinecraftKey("test"));
payload.getCustomPacketPayloads().write(0, payloadWrapper);
PacketContainer cloned = SerializableCloner.clone(payload);
com.comphenix.protocol.wrappers.MinecraftKey clonedKey = cloned.getMinecraftKeys().read(0);
Assertions.assertNotSame(payload, cloned);
}
byte[] clonedData = new byte[randomData.length];
ByteBuf clonedBuffer = (ByteBuf) cloned.getModifier()
.withType(MinecraftReflection.getPacketDataSerializerClass())
.read(0);
clonedBuffer.readBytes(clonedData);
@Test
public void testUnknownPayloadDeserialize() {
MinecraftKey id = new MinecraftKey("test");
byte[] payloadData = new byte[]{0x00, 0x01, 0x05, 0x07};
ByteBuf buffer = Unpooled.wrappedBuffer(payloadData);
ServerboundCustomPayloadPacket.UnknownPayload payload = new ServerboundCustomPayloadPacket.UnknownPayload(id, buffer);
ServerboundCustomPayloadPacket packet = new ServerboundCustomPayloadPacket(payload);
assertEquals("minecraft:test", clonedKey.getFullKey());
assertArrayEquals(randomData, clonedData);
PacketContainer packetContainer = new PacketContainer(PacketType.Play.Client.CUSTOM_PAYLOAD, packet);
CustomPacketPayloadWrapper payloadWrapper = packetContainer.getCustomPacketPayloads().read(0);
com.comphenix.protocol.wrappers.MinecraftKey key = payloadWrapper.getId();
Assertions.assertEquals("minecraft", key.getPrefix());
Assertions.assertEquals("test", key.getKey());
Assertions.assertArrayEquals(payloadData, payloadWrapper.getPayload());
}
@Test