fix cloning of packet containers
This commit is contained in:
parent
7e137cbfc5
commit
4b9575a402
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue