/* * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * Copyright (C) 2012 Kristian S. Stangeland * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package com.comphenix.protocol.injector; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.error.RethrowErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.wrappers.BukkitConverters; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.primitives.Primitives; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; /** * 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 final PacketConstructor DEFAULT = new PacketConstructor(null); // The constructor method that's actually responsible for creating the packet private final Constructor constructorMethod; // Used to unwrap Bukkit objects private final List unwrappers; // The packet ID private PacketType type; // Parameters that need to be unwrapped private Unwrapper[] paramUnwrapper; private PacketConstructor(Constructor constructorMethod) { this.constructorMethod = constructorMethod; this.unwrappers = Lists.newArrayList(new BukkitUnwrapper(new RethrowErrorReporter())); this.unwrappers.addAll(BukkitConverters.getUnwrappers()); } private PacketConstructor(PacketType type, Constructor constructorMethod, List unwrappers, Unwrapper[] paramUnwrapper) { this.type = type; this.constructorMethod = constructorMethod; this.unwrappers = unwrappers; this.paramUnwrapper = paramUnwrapper; } // 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++) { Class inputType = types[i]; Class paramType = params[i]; // The input type is always wrapped if (!inputType.isPrimitive() && paramType.isPrimitive()) { // Wrap it paramType = Primitives.wrap(paramType); } // Compare assignability if (!paramType.isAssignableFrom(inputType)) { return false; } } return true; } // Parameter count must match return false; } /** * Retrieve the class of an object, or just the class if it already is a class object. * * @param obj - the object. * @return The class of an object. */ public static Class getClass(Object obj) { if (obj instanceof Class) { return (Class) obj; } return obj.getClass(); } public ImmutableList getUnwrappers() { return ImmutableList.copyOf(unwrappers); } /** * Retrieve the id of the packets this constructor creates. *

* Deprecated: Use {@link #getType()} instead. * * @return The ID of the packets this constructor will create. */ @Deprecated public int getPacketID() { return type.getCurrentId(); } /** * Retrieve the type of the packets this constructor creates. * * @return The type of the created packets. */ public PacketType getType() { return type; } /** * 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 A constructor with a different set of unwrappers. */ public PacketConstructor withUnwrappers(List unwrappers) { return new PacketConstructor(type, constructorMethod, unwrappers, paramUnwrapper); } /** * Create a packet constructor that creates packets using the given types. *

* Note that if you pass a Class as a value, it will use its type directly. * * @param type - the type of the packet to create. * @param values - the values that will match each parameter in the desired constructor. * @return A packet constructor with these types. * @throws IllegalArgumentException If no packet constructor could be created with these types. */ public PacketConstructor withPacket(PacketType type, Object[] values) { Class[] types = new Class[values.length]; Throwable lastException = null; Unwrapper[] paramUnwrapper = new Unwrapper[values.length]; for (int i = 0; i < types.length; i++) { // Default type if (values[i] != null) { types[i] = PacketConstructor.getClass(values[i]); for (Unwrapper unwrapper : unwrappers) { Object result = null; try { result = unwrapper.unwrapItem(values[i]); } catch (OutOfMemoryError e) { throw e; } catch (ThreadDeath e) { throw e; } catch (Throwable e) { lastException = e; } // Update type we're searching for if (result != null) { types[i] = PacketConstructor.getClass(result); paramUnwrapper[i] = unwrapper; break; } } } else { // Try it types[i] = Object.class; } } Class packetClass = PacketRegistry.getPacketClassFromType(type); // Find the correct constructor for (Constructor constructor : packetClass.getConstructors()) { Class[] params = constructor.getParameterTypes(); if (isCompatible(types, params)) { // Right, we've found our type return new PacketConstructor(type, constructor, unwrappers, paramUnwrapper); } } throw new IllegalArgumentException("No suitable constructor could be found.", lastException); } /** * 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 { // Convert types that needs to be converted for (int i = 0; i < values.length; i++) { if (paramUnwrapper[i] != null) { values[i] = paramUnwrapper[i].unwrapItem(values[i]); } } Object nmsPacket = constructorMethod.newInstance(values); return new PacketContainer(type, 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); } } /** * Represents a unwrapper for a constructor parameter. * * @author Kristian */ public static interface Unwrapper { /** * Convert the given wrapped object to the equivalent net.minecraft.server object. *

* Note that we may pass in a class instead of object - in that case, the unwrapper should return the equivalent NMS * class. * * @param wrappedObject - wrapped object or class. * @return The equivalent net.minecraft.server object or class. */ public Object unwrapItem(Object wrappedObject); } }