From e5beca4ea52670be7a3339d8500f8283cc9fc54c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 17 Sep 2012 22:59:41 +0200 Subject: [PATCH] And the PacketConstructor class itself. --- .../protocol/injector/PacketConstructor.java | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/PacketConstructor.java diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketConstructor.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketConstructor.java new file mode 100644 index 00000000..1f9d7701 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketConstructor.java @@ -0,0 +1,219 @@ +package com.comphenix.protocol.injector; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import net.minecraft.server.Packet; + +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * A packet constructor that uses an internal Minecraft. + * @author Kristian + * + */ +public class PacketConstructor { + + /** + * A packet constructor that automatically converts Bukkit types to their NMS conterpart. + *

+ * Remember to call withPacket(). + */ + public static PacketConstructor DEFAUALT = new PacketConstructor(null); + + // The constructor method that's actually responsible for creating the packet + private Constructor constructorMethod; + + // The packet ID + private int packetID; + + // Used to unwrap Bukkit objects + private List unwrappers; + + private PacketConstructor(Constructor constructorMethod) { + this.constructorMethod = constructorMethod; + this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper()); + } + + private PacketConstructor(Constructor constructorMethod, List unwrappers) { + this.constructorMethod = constructorMethod; + this.unwrappers = unwrappers; + } + + public ImmutableList getUnwrappers() { + return ImmutableList.copyOf(unwrappers); + } + + /** + * Retrieve the id of the packets this constructor creates. + * @return The ID of the packets this constructor will create. + */ + public int getPacketID() { + return packetID; + } + + /** + * Return a copy of the current constructor with a different list of unwrappers. + * @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes. + * @return + */ + public PacketConstructor withUnwrappers(List unwrappers) { + return new PacketConstructor(constructorMethod, unwrappers); + } + + /** + * Create a packet constructor that creates packets using the given types. + * @param id - packet ID. + * @param types - types to create. + * @return A packet constructor with these types. + * @throws IllegalArgumentException If no packet constructor could be created with these types. + */ + public PacketConstructor withPacket(int id, Class[] types) { + + for (Unwrapper unwrapper : unwrappers) { + for (int i = 0; i < types.length; i++) { + Class result =unwrapper.unwrapType(types[i]); + + // Update type we're searching for + if (result != null) { + types[i] = result; + } + } + } + + Class packetType = MinecraftRegistry.getPacketClassFromID(id); + + if (packetType == null) + throw new IllegalArgumentException("Could not find a packet by the id " + id); + + // Find the correct constructor + for (Constructor constructor : packetType.getConstructors()) { + Class[] params = constructor.getParameterTypes(); + + if (isCompatible(types, params)) { + // Right, we've found our type + return new PacketConstructor(constructor, unwrappers); + } + } + + throw new IllegalArgumentException("No suitable constructor could be found."); + } + + /** + * Construct a packet using the special builtin Minecraft constructors. + * @param values - values containing Bukkit wrapped items to pass to Minecraft. + * @return The created packet. + * @throws FieldAccessException Failure due to a security limitation. + * @throws IllegalArgumentException Arguments doesn't match the constructor. + * @throws RuntimeException Minecraft threw an exception. + */ + public PacketContainer createPacket(Object... values) throws FieldAccessException { + + try { + Packet nmsPacket = (Packet) constructorMethod.newInstance(values); + return new PacketContainer(packetID, nmsPacket); + + } catch (IllegalArgumentException e) { + throw e; + } catch (InstantiationException e) { + throw new FieldAccessException("Cannot construct an abstract packet.", e); + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot construct packet due to a security limitation.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Minecraft error.", e); + } + } + + // Determine if a method with the types 'params' can be called with 'types' + private static boolean isCompatible(Class[] types, Class[] params) { + + // Determine if the types are similar + if (params.length == types.length) { + for (int i = 0; i < params.length; i++) { + if (!params[i].isAssignableFrom(types[i])) { + return false; + } + } + } + + return true; + } + + public static class BukkitUnwrapper implements Unwrapper { + + private static Map, Method> cache = new ConcurrentHashMap, Method>(); + + @Override + public Object unwrapItem(Object wrappedObject) { + + Class currentClass = wrappedObject.getClass(); + Method cachedMethod = initializeCache(currentClass); + + try { + // Retrieve the handle + if (cachedMethod != null) + return cachedMethod.invoke(wrappedObject); + else + return null; + + } catch (IllegalArgumentException e) { + // Impossible + return null; + } catch (IllegalAccessException e) { + return null; + } catch (InvocationTargetException e) { + // This is REALLY bad + throw new RuntimeException("Minecraft error.", e); + } + } + + @Override + public Class unwrapType(Class type) { + + if (type == null) + throw new IllegalArgumentException("type cannot be null."); + + Method unwrapped = initializeCache(type); + + // Determine the unwrapped type + if (unwrapped != null) + return unwrapped.getReturnType(); + else + return null; + } + + private Method initializeCache(Class type) { + + // See if we're already determined this + if (cache.containsKey(type)) { + // We will never remove from the cache, so this ought to be thread safe + return cache.get(type); + } + + try { + Method find = type.getMethod("getHandle"); + + // It's thread safe, as getMethod should return the same handle + cache.put(type, find); + return find; + + } catch (SecurityException e) { + return null; + } catch (NoSuchMethodException e) { + return null; + } + } + } + + public static interface Unwrapper { + public Object unwrapItem(Object wrappedObject); + public Class unwrapType(Class type); + } +}