/** * Create a deep copy of the current packet. * * <p>This will perform a full copy of the entire object tree, only skipping known immutable * objects and primitive types. * * <p>Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory. * * @return A deep copy of the current packet. */ public PacketContainer deepClone() { Object clonedPacket = DEEP_CLONER.clone(getHandle()); return new PacketContainer(getID(), clonedPacket); }
/** * Represents a Minecraft packet indirectly. * * @author Kristian */ public class PacketContainer implements Serializable { /** Generated by Eclipse. */ private static final long serialVersionUID = 2074805748222377230L; protected int id; protected transient Object handle; // Current structure modifier protected transient StructureModifier<Object> structureModifier; // Support for serialization private static ConcurrentMap<Class<?>, Method> writeMethods = Maps.newConcurrentMap(); private static ConcurrentMap<Class<?>, Method> readMethods = Maps.newConcurrentMap(); // Used to clone packets private static final AggregateCloner DEEP_CLONER = AggregateCloner.newBuilder() .instanceProvider(DefaultInstances.DEFAULT) .andThen(BukkitCloner.class) .andThen(ImmutableDetector.class) .andThen(CollectionCloner.class) .andThen(getSpecializedDeepClonerFactory()) .build(); private static final AggregateCloner SHALLOW_CLONER = AggregateCloner.newBuilder() .instanceProvider(DefaultInstances.DEFAULT) .andThen( new Function<BuilderParameters, Cloner>() { @Override public Cloner apply(@Nullable BuilderParameters param) { return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) { { // Use a default writer with no concept of cloning writer = new ObjectWriter(); } }; } }) .build(); /** * Creates a packet container for a new packet. * * @param id - ID of the packet to create. */ public PacketContainer(int id) { this(id, StructureCache.newPacket(id)); } /** * Creates a packet container for an existing packet. * * @param id - ID of the given packet. * @param handle - contained packet. */ public PacketContainer(int id, Object handle) { this(id, handle, StructureCache.getStructure(id).withTarget(handle)); } /** * Creates a packet container for an existing packet. * * @param id - ID of the given packet. * @param handle - contained packet. * @param structure - structure modifier. */ public PacketContainer(int id, Object handle, StructureModifier<Object> structure) { if (handle == null) throw new IllegalArgumentException("handle cannot be null."); this.id = id; this.handle = handle; this.structureModifier = structure; } /** For serialization. */ protected PacketContainer() {} /** * Retrieves the underlying Minecraft packet. * * @return Underlying Minecraft packet. */ public Object getHandle() { return handle; } /** * Retrieves the generic structure modifier for this packet. * * @return Structure modifier. */ public StructureModifier<Object> getModifier() { return structureModifier; } /** * Retrieves a read/write structure for every field with the given type. * * @param primitiveType - the type to find. * @return A modifier for this specific type. */ public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) { return structureModifier.withType(primitiveType); } /** * Retrieves a read/write structure for every byte field. * * @return A modifier for every byte field. */ public StructureModifier<Byte> getBytes() { return structureModifier.withType(byte.class); } /** * Retrieves a read/write structure for every short field. * * @return A modifier for every short field. */ public StructureModifier<Short> getShorts() { return structureModifier.withType(short.class); } /** * Retrieves a read/write structure for every integer field. * * @return A modifier for every integer field. */ public StructureModifier<Integer> getIntegers() { return structureModifier.withType(int.class); } /** * Retrieves a read/write structure for every long field. * * @return A modifier for every long field. */ public StructureModifier<Long> getLongs() { return structureModifier.withType(long.class); } /** * Retrieves a read/write structure for every float field. * * @return A modifier for every float field. */ public StructureModifier<Float> getFloat() { return structureModifier.withType(float.class); } /** * Retrieves a read/write structure for every double field. * * @return A modifier for every double field. */ public StructureModifier<Double> getDoubles() { return structureModifier.withType(double.class); } /** * Retrieves a read/write structure for every String field. * * @return A modifier for every String field. */ public StructureModifier<String> getStrings() { return structureModifier.withType(String.class); } /** * Retrieves a read/write structure for every String array field. * * @return A modifier for every String array field. */ public StructureModifier<String[]> getStringArrays() { return structureModifier.withType(String[].class); } /** * Retrieves a read/write structure for every byte array field. * * @return A modifier for every byte array field. */ public StructureModifier<byte[]> getByteArrays() { return structureModifier.withType(byte[].class); } /** * Retrieve a serializer for reading and writing ItemStacks stored in a byte array. * * @return A instance of the serializer. */ public StreamSerializer getByteArraySerializer() { return new StreamSerializer(); } /** * Retrieves a read/write structure for every int array field. * * @return A modifier for every int array field. */ public StructureModifier<int[]> getIntegerArrays() { return structureModifier.withType(int[].class); } /** * Retrieves a read/write structure for ItemStack. * * <p>This modifier will automatically marshall between the Bukkit ItemStack and the internal * Minecraft ItemStack. * * @return A modifier for ItemStack fields. */ public StructureModifier<ItemStack> getItemModifier() { // Convert to and from the Bukkit wrapper return structureModifier.<ItemStack>withType( MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter()); } /** * Retrieves a read/write structure for arrays of ItemStacks. * * <p>This modifier will automatically marshall between the Bukkit ItemStack and the internal * Minecraft ItemStack. * * @return A modifier for ItemStack array fields. */ public StructureModifier<ItemStack[]> getItemArrayModifier() { final EquivalentConverter<ItemStack> stackConverter = BukkitConverters.getItemStackConverter(); // Convert to and from the Bukkit wrapper return structureModifier.<ItemStack[]>withType( MinecraftReflection.getItemStackArrayClass(), BukkitConverters.getIgnoreNull( new EquivalentConverter<ItemStack[]>() { public Object getGeneric(Class<?> genericType, ItemStack[] specific) { Class<?> nmsStack = MinecraftReflection.getItemStackClass(); Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length); // Unwrap every item for (int i = 0; i < result.length; i++) { result[i] = stackConverter.getGeneric(nmsStack, specific[i]); } return result; } @Override public ItemStack[] getSpecific(Object generic) { Object[] input = (Object[]) generic; ItemStack[] result = new ItemStack[input.length]; // Add the wrapper for (int i = 0; i < result.length; i++) { result[i] = stackConverter.getSpecific(input[i]); } return result; } @Override public Class<ItemStack[]> getSpecificType() { return ItemStack[].class; } })); } /** * Retrieves a read/write structure for the world type enum. * * <p>This modifier will automatically marshall between the Bukkit world type and the internal * Minecraft world type. * * @return A modifier for world type fields. */ public StructureModifier<WorldType> getWorldTypeModifier() { // Convert to and from the Bukkit wrapper return structureModifier.<WorldType>withType( MinecraftReflection.getWorldTypeClass(), BukkitConverters.getWorldTypeConverter()); } /** * Retrieves a read/write structure for data watchers. * * @return A modifier for data watchers. */ public StructureModifier<WrappedDataWatcher> getDataWatcherModifier() { // Convert to and from the Bukkit wrapper return structureModifier.<WrappedDataWatcher>withType( MinecraftReflection.getDataWatcherClass(), BukkitConverters.getDataWatcherConverter()); } /** * Retrieves a read/write structure for entity objects. * * <p>Note that entities are transmitted by integer ID, and the type may not be enough to * distinguish between entities and other values. Thus, this structure modifier MAY return null or * invalid entities for certain fields. Using the correct index is essential. * * @return A modifier entity types. */ public StructureModifier<Entity> getEntityModifier(World world) { // Convert to and from the Bukkit wrapper return structureModifier.<Entity>withType( int.class, BukkitConverters.getEntityConverter(world)); } /** * Retrieves a read/write structure for chunk positions. * * @return A modifier for a ChunkPosition. */ public StructureModifier<ChunkPosition> getPositionModifier() { // Convert to and from the Bukkit wrapper return structureModifier.withType( MinecraftReflection.getChunkPositionClass(), ChunkPosition.getConverter()); } /** * Retrieves a read/write structure for NBT classes. * * @return A modifier for NBT classes. */ public StructureModifier<NbtBase<?>> getNbtModifier() { // Allow access to the NBT class in packet 130 return structureModifier.withType( MinecraftReflection.getNBTBaseClass(), BukkitConverters.getNbtConverter()); } /** * Retrieves a read/write structure for collections of chunk positions. * * <p>This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and * the internal Minecraft ChunkPosition. * * @return A modifier for ChunkPosition list fields. */ public StructureModifier<List<ChunkPosition>> getPositionCollectionModifier() { // Convert to and from the ProtocolLib wrapper return structureModifier.withType( Collection.class, BukkitConverters.getListConverter( MinecraftReflection.getChunkPositionClass(), ChunkPosition.getConverter())); } /** * Retrieves a read/write structure for collections of watchable objects. * * <p>This modifier will automatically marshall between the visible WrappedWatchableObject and the * internal Minecraft WatchableObject. * * @return A modifier for watchable object list fields. */ public StructureModifier<List<WrappedWatchableObject>> getWatchableCollectionModifier() { // Convert to and from the ProtocolLib wrapper return structureModifier.withType( Collection.class, BukkitConverters.getListConverter( MinecraftReflection.getWatchableObjectClass(), BukkitConverters.getWatchableObjectConverter())); } /** * Retrieves the ID of this packet. * * @return Packet ID. */ public int getID() { return id; } /** * Create a shallow copy of the current packet. * * <p>This merely writes the content of each field to the new class directly, without performing * any expensive copies. * * @return A shallow copy of the current packet. */ public PacketContainer shallowClone() { Object clonedPacket = SHALLOW_CLONER.clone(getHandle()); return new PacketContainer(getID(), clonedPacket); } /** * Create a deep copy of the current packet. * * <p>This will perform a full copy of the entire object tree, only skipping known immutable * objects and primitive types. * * <p>Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory. * * @return A deep copy of the current packet. */ public PacketContainer deepClone() { Object clonedPacket = DEEP_CLONER.clone(getHandle()); return new PacketContainer(getID(), clonedPacket); } // To save space, we'll skip copying the inflated buffers in packet 51 and 56 private static Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() { // Look at what you've made me do Java, look at it!! return new Function<BuilderParameters, Cloner>() { @Override public Cloner apply(@Nullable BuilderParameters param) { return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) { { this.writer = new ObjectWriter() { protected void transformField( StructureModifier<Object> modifierSource, StructureModifier<Object> modifierDest, int fieldIndex) { // No need to clone inflated buffers if (modifierSource.getField(fieldIndex).getName().startsWith("inflatedBuffer")) modifierDest.write(fieldIndex, modifierSource.read(fieldIndex)); else defaultTransform( modifierSource, modifierDest, getDefaultCloner(), fieldIndex); }; }; } }; } }; } private void writeObject(ObjectOutputStream output) throws IOException { // Default serialization output.defaultWriteObject(); // We'll take care of NULL packets as well output.writeBoolean(handle != null); try { // Call the write-method getMethodLazily(writeMethods, handle.getClass(), "write", DataOutputStream.class) .invoke(handle, new DataOutputStream(output)); } catch (IllegalArgumentException e) { throw new IOException("Minecraft packet doesn't support DataOutputStream", e); } catch (IllegalAccessException e) { throw new RuntimeException("Insufficient security privileges.", e); } catch (InvocationTargetException e) { throw new IOException("Could not serialize Minecraft packet.", e); } } private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { // Default deserialization input.defaultReadObject(); // Get structure modifier structureModifier = StructureCache.getStructure(id); // Don't read NULL packets if (input.readBoolean()) { // Create a default instance of the packet handle = StructureCache.newPacket(id); // Call the read method try { getMethodLazily(readMethods, handle.getClass(), "read", DataInputStream.class) .invoke(handle, new DataInputStream(input)); } catch (IllegalArgumentException e) { throw new IOException("Minecraft packet doesn't support DataInputStream", e); } catch (IllegalAccessException e) { throw new RuntimeException("Insufficient security privileges.", e); } catch (InvocationTargetException e) { throw new IOException("Could not deserialize Minecraft packet.", e); } // And we're done structureModifier = structureModifier.withTarget(handle); } } /** * Retrieve the cached method concurrently. * * @param lookup - a lazy lookup cache. * @param handleClass - class type of the current packet. * @param methodName - name of method to retrieve. * @param parameterClass - the one parameter type in the method. * @return Reflected method. */ private Method getMethodLazily( ConcurrentMap<Class<?>, Method> lookup, Class<?> handleClass, String methodName, Class<?> parameterClass) { Method method = lookup.get(handleClass); // Atomic operation if (method == null) { Method initialized = FuzzyReflection.fromClass(handleClass).getMethodByParameters(methodName, parameterClass); method = lookup.putIfAbsent(handleClass, initialized); // Use our version if we succeeded if (method == null) { method = initialized; } } return method; } }
/** * Create a shallow copy of the current packet. * * <p>This merely writes the content of each field to the new class directly, without performing * any expensive copies. * * @return A shallow copy of the current packet. */ public PacketContainer shallowClone() { Object clonedPacket = SHALLOW_CLONER.clone(getHandle()); return new PacketContainer(getID(), clonedPacket); }