fix cloning of packet containers

This commit is contained in:
derklaro 2022-07-30 11:07:51 +02:00 committed by Dan Mulloy
parent 7e137cbfc5
commit 4b9575a402
2 changed files with 131 additions and 82 deletions

View File

@ -17,17 +17,26 @@
package com.comphenix.protocol.events;
import com.comphenix.protocol.ProtocolLogger;
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.instances.DefaultInstances;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.logging.Level;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.PacketType;
@ -43,11 +52,9 @@ import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.Converters;
import com.google.common.base.Function;
import com.google.common.collect.Sets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;
/**
* Represents a Minecraft packet indirectly.
@ -55,14 +62,13 @@ import io.netty.buffer.UnpooledByteBufAllocator;
* @author Kristian
*/
@SuppressWarnings("unused")
public class PacketContainer extends AbstractStructure implements Serializable {
public class PacketContainer extends AbstractStructure implements Serializable { // TODO: remove Serializable
private static final long serialVersionUID = 3;
private PacketType type;
// Support for serialization
private static ConcurrentMap<Class<?>, Method> writeMethods = new ConcurrentHashMap<>();
private static ConcurrentMap<Class<?>, Method> readMethods = new ConcurrentHashMap<>();
private static final Map<PacketType, Function<Object, Object>> PACKET_DESERIALIZER_METHODS = new ConcurrentHashMap<>();
// Used to clone packets
private static final AggregateCloner DEEP_CLONER = AggregateCloner
@ -217,28 +223,33 @@ public class PacketContainer extends AbstractStructure implements Serializable {
* @return A deep copy of the current packet.
*/
public PacketContainer deepClone() {
Object clonedPacket = null;
Object handle = this.getHandle();
if (handle == null) {
// nothing to clone, just carry on (this should normally not happen)
return this;
}
if (!FAST_CLONE_UNSUPPORTED.contains(type)) {
// try fast cloning first
if (!FAST_CLONE_UNSUPPORTED.contains(this.type)) {
try {
clonedPacket = DEEP_CLONER.clone(getHandle());
Object cloned = DEEP_CLONER.clone(handle);
return new PacketContainer(this.type, cloned);
} catch (Exception ex) {
FAST_CLONE_UNSUPPORTED.add(type);
FAST_CLONE_UNSUPPORTED.add(this.type);
}
}
// Fall back on the slower alternative method of reading and writing back the packet
if (clonedPacket == null) {
clonedPacket = SerializableCloner.clone(this).getHandle();
}
Object serialized = this.serializeToBuffer();
Object deserialized = deserializeFromBuffer(this.type, serialized);
return new PacketContainer(getType(), clonedPacket);
return new PacketContainer(this.type, deserialized);
}
// To save space, we'll skip copying the inflated buffers in packet 51 and 56
private static Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
private static com.google.common.base.Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
// Look at what you've made me do Java, look at it!!
return new Function<BuilderParameters, Cloner>() {
return new com.google.common.base.Function<BuilderParameters, Cloner>() {
@Override
public Cloner apply(@Nullable BuilderParameters param) {
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
@ -257,91 +268,117 @@ public class PacketContainer extends AbstractStructure implements Serializable {
}
};
}
@Deprecated
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
// Default serialization
output.defaultWriteObject();
// We'll take care of NULL packets as well
output.writeBoolean(handle != null);
try {
ByteBuf buffer = createPacketBuffer();
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, buffer);
// serialize the packet
ByteBuf buffer = (ByteBuf) this.serializeToBuffer();
if (buffer != null) {
output.writeBoolean(true);
output.writeInt(buffer.readableBytes());
buffer.readBytes(output, buffer.readableBytes());
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
} else {
output.writeBoolean(false);
}
}
@Deprecated
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
// Default deserialization
input.defaultReadObject();
// Get structure modifier
structureModifier = StructureCache.getStructure(type);
// Don't read NULL packets
if (input.readBoolean()) {
ByteBuf buffer = createPacketBuffer();
buffer.writeBytes(input, input.readInt());
// Create a default instance of the packet
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
Object serializer = MinecraftReflection.getPacketDataSerializer(buffer);
// Deserialize the packet from the stream (if present)
this.structureModifier = StructureCache.getStructure(this.type);
if (input.readBoolean()) {
int dataLength = input.readInt();
try {
handle = type.getPacketClass()
.getConstructor(MinecraftReflection.getPacketDataSerializerClass())
.newInstance(serializer);
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException ex) {
// they might have a static method to create them instead
Method method = FuzzyReflection.fromClass(type.getPacketClass(), true)
.getMethod(FuzzyMethodContract
.newBuilder()
.requireModifier(Modifier.STATIC)
.returnTypeExact(type.getPacketClass())
.parameterExactArray(MinecraftReflection.getPacketDataSerializerClass())
.build());
try {
handle = method.invoke(null, serializer);
} catch (ReflectiveOperationException exception) {
throw new RuntimeException("Failed to construct packet for " + type, exception);
}
} catch (InvocationTargetException ex) {
throw new RuntimeException("Unable to clone packet " + type + " using constructor", ex.getCause());
}
} else {
handle = StructureCache.newPacket(type);
ByteBuf byteBuf = (ByteBuf) MinecraftReflection.createPacketDataSerializer(dataLength);
byteBuf.writeBytes(input, dataLength);
// Call the read method
try {
MinecraftMethods.getPacketReadByteBufMethod().invoke(handle, buffer);
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataInputStream", e);
}
}
// And we're done
structureModifier = structureModifier.withTarget(handle);
}
Object packet = deserializeFromBuffer(this.type, byteBuf);
this.handle = packet;
this.structureModifier = this.structureModifier.withTarget(packet);
}
ProtocolLogger.log(
Level.SEVERE,
"Using ObjectInputStream to deserialize a PacketContainer is deprecated and will be removed in a "
+ "future version as it can lead to unexpected results! Please DO NOT report this to ProtocolLib "
+ "this is not a bug! Report it to the plugin you see in the following stack trace:",
new Throwable());
}
/**
* Construct a new packet data serializer.
* @return The packet data serializer.
* @deprecated use {@link MinecraftReflection#createPacketDataSerializer(int)} instead
*/
@Deprecated
public static ByteBuf createPacketBuffer() {
ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer();
Class<?> packetSerializer = MinecraftReflection.getPacketDataSerializerClass();
return (ByteBuf) MinecraftReflection.createPacketDataSerializer(0);
}
try {
return (ByteBuf) packetSerializer.getConstructor(ByteBuf.class).newInstance(buffer);
} catch (Exception e) {
throw new RuntimeException("Cannot construct packet serializer.", e);
// ---- Cloning
private static Object deserializeFromBuffer(PacketType packetType, Object buffer) {
if (buffer == null) {
return null;
}
Function<Object, Object> deserializer = PACKET_DESERIALIZER_METHODS.computeIfAbsent(packetType, type -> {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
// best guess - a constructor which takes a buffer as the only argument
ConstructorAccessor bufferConstructor = Accessors.getConstructorAccessorOrNull(
type.getPacketClass(),
MinecraftReflection.getPacketDataSerializerClass());
if (bufferConstructor != null) {
return bufferConstructor::invoke;
}
// they might have a static method to create them instead
List<Method> methods = FuzzyReflection.fromClass(type.getPacketClass(), true)
.getMethodList(FuzzyMethodContract.newBuilder()
.requireModifier(Modifier.STATIC)
.returnTypeExact(type.getPacketClass())
.parameterExactArray(MinecraftReflection.getPacketDataSerializerClass())
.build());
if (!methods.isEmpty()) {
MethodAccessor accessor = Accessors.getMethodAccessor(methods.get(0));
return buf -> accessor.invoke(null, buf);
}
}
// try to construct a packet instance using a no-args constructor and invoke the read method
MethodAccessor readMethod = MinecraftMethods.getPacketReadByteBufMethod();
Objects.requireNonNull(readMethod,
"Unable to find the Packet#read(ByteBuf) method, cannot deserialize " + type);
Object checkInstance = DefaultInstances.DEFAULT.create(type.getPacketClass());
Objects.requireNonNull(checkInstance, "Unable to construct empty packet, cannot deserialize " + type);
// okay, Packet#read exists
return buf -> {
Object packet = DefaultInstances.DEFAULT.create(type.getPacketClass());
readMethod.invoke(packet, buf);
return packet;
};
});
return deserializer.apply(buffer);
}
private Object serializeToBuffer() {
Object handle = this.getHandle();
if (handle == null) {
return null;
}
Object targetBuffer = MinecraftReflection.createPacketDataSerializer(0);
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, targetBuffer);
return targetBuffer;
}
// ---- Metadata

View File

@ -17,6 +17,7 @@
package com.comphenix.protocol.utility;
import io.netty.buffer.Unpooled;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@ -1434,6 +1435,7 @@ public final class MinecraftReflection {
*/
public static Object getPacketDataSerializer(Object buffer) {
try {
// TODO: move this to MinecraftMethods, or at least, cache the constructor accessor
Class<?> packetSerializer = getPacketDataSerializerClass();
return packetSerializer.getConstructor(getByteBufClass()).newInstance(buffer);
} catch (Exception e) {
@ -1441,6 +1443,16 @@ public final class MinecraftReflection {
}
}
public static Object createPacketDataSerializer(int initialSize) {
// validate the initial size
if (initialSize <= 0) {
initialSize = 256;
}
Object buffer = Unpooled.buffer(initialSize);
return getPacketDataSerializer(buffer);
}
public static Class<?> getNbtTagTypes() {
return getMinecraftClass("nbt.NBTTagTypes", "NBTTagTypes");
}