diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java index 6f60277e..0ff5a162 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java @@ -270,7 +270,7 @@ public class CommandFilter extends CommandBase { } /* - * Description: Adds or removes a simple packet listener. + * Description: Adds or removes a simple packet filter. Usage: / add|remove name [packet IDs] */ @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 3d67453a..d130677d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -340,6 +340,8 @@ class CommandPacket extends CommandBase { supported.addAll(Packets.Client.getSupported()); else if (side.isForServer()) supported.addAll(Packets.Server.getSupported()); + + System.out.println("Supported for " + side + ": " + supported); return supported; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 1ddf25bf..d1e27b6f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -259,7 +259,7 @@ public class ProtocolLibrary extends JavaPlugin { } else { logger.info("Structure compiler thread has been disabled."); } - + // Set up command handlers registerCommand(CommandProtocol.NAME, commandProtocol); registerCommand(CommandPacket.NAME, commandPacket); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 44aa1e46..af7453cc 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -229,7 +229,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok reporter.reportWarning(this, "Cannot load server and client packet list.", e); } - } catch (IllegalAccessException e) { + } catch (FieldAccessException e) { reporter.reportWarning(this, "Unable to initialize packet injector.", e); } } @@ -757,7 +757,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (!MinecraftReflection.isPacketClass(packet)) throw new IllegalArgumentException("The given object " + packet + " is not a packet."); - return PacketRegistry.getPacketToID().get(packet.getClass()); + Integer id = PacketRegistry.getPacketToID().get(packet.getClass()); + + if (id != null) { + return id; + } else { + throw new IllegalArgumentException( + "Unable to find associated packet of " + packet + ": Lookup returned NULL."); + } } @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjectorBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjectorBuilder.java index c3720b77..a844cd09 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjectorBuilder.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjectorBuilder.java @@ -8,6 +8,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; +import com.comphenix.protocol.reflect.FieldAccessException; import com.google.common.base.Preconditions; /** @@ -100,9 +101,9 @@ public class PacketInjectorBuilder { *

* Note that any non-null builder parameters must be set. * @return The created injector. - * @throws IllegalAccessException If anything goes wrong in terms of reflection. + * @throws FieldAccessException If anything goes wrong in terms of reflection. */ - public PacketInjector buildInjector() throws IllegalAccessException { + public PacketInjector buildInjector() throws FieldAccessException { initializeDefaults(); return new ProxyPacketInjector(classLoader, invoker, playerInjection, reporter); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java index 918546f4..0cfea2ed 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java @@ -25,10 +25,15 @@ import java.util.Set; import net.sf.cglib.proxy.Factory; +import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.TroveWrapper; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; @@ -39,6 +44,8 @@ import com.google.common.collect.ImmutableSet; */ @SuppressWarnings("rawtypes") public class PacketRegistry { + private static final int MIN_SERVER_PACKETS = 5; + private static final int MIN_CLIENT_PACKETS = 5; // Fuzzy reflection private static FuzzyReflection packetRegistry; @@ -67,6 +74,14 @@ public class PacketRegistry { try { Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); packetToID = (Map) FieldUtils.readStaticField(packetsField, true); + } catch (IllegalArgumentException e) { + // Spigot 1.2.5 MCPC workaround + try { + packetToID = getSpigotWrapper(); + } catch (Exception e2) { + // Very bad indeed + throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2); + } } catch (IllegalAccessException e) { throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); @@ -76,6 +91,40 @@ public class PacketRegistry { return packetToID; } + private static Map getSpigotWrapper() throws IllegalAccessException { + // If it talks like a duck, etc. + // Perhaps it would be nice to have a proper duck typing library as well + FuzzyClassContract mapLike = FuzzyClassContract.newBuilder(). + method(FuzzyMethodContract.newBuilder(). + nameExact("size").returnTypeExact(int.class)). + method(FuzzyMethodContract.newBuilder(). + nameExact("put").parameterCount(2)). + method(FuzzyMethodContract.newBuilder(). + nameExact("get").parameterCount(1)). + build(); + + Field packetsField = getPacketRegistry().getField( + FuzzyFieldContract.newBuilder().typeMatches(mapLike).build()); + Object troveMap = FieldUtils.readStaticField(packetsField, true); + + // Check for stupid no_entry_values + try { + Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true); + Integer value = (Integer) FieldUtils.readField(field, troveMap, true); + + if (value >= 0 && value < 256) { + // Someone forgot to set the no entry value. Let's help them. + FieldUtils.writeField(field, troveMap, -1); + } + } catch (IllegalArgumentException e) { + // Whatever + ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, "Unable to correct no entry value.", e); + } + + // We'll assume this a Trove map + return TroveWrapper.getDecoratedMap(troveMap); + } + /** * Retrieve the cached fuzzy reflection instance allowing access to the packet registry. * @return Reflected packet registry. @@ -109,6 +158,10 @@ public class PacketRegistry { */ public static Set getServerPackets() throws FieldAccessException { initializeSets(); + + // Sanity check. This is impossible! + if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS) + throw new FieldAccessException("Server packet list is empty. Seems to be unsupported"); return serverPackets; } @@ -119,6 +172,10 @@ public class PacketRegistry { */ public static Set getClientPackets() throws FieldAccessException { initializeSets(); + + // As above + if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS) + throw new FieldAccessException("Client packet list is empty. Seems to be unsupported"); return clientPackets; } @@ -140,6 +197,14 @@ public class PacketRegistry { serverPackets = ImmutableSet.copyOf(serverPacketsRef); clientPackets = ImmutableSet.copyOf(clientPacketsRef); + // Check sizes + if (serverPackets.size() < MIN_SERVER_PACKETS) + ProtocolLibrary.getErrorReporter().reportWarning( + PacketRegistry.class, "Too few server packets detected: " + serverPackets.size()); + if (clientPackets.size() < MIN_CLIENT_PACKETS) + ProtocolLibrary.getErrorReporter().reportWarning( + PacketRegistry.class, "Too few client packets detected: " + clientPackets.size()); + } else { throw new FieldAccessException("Cannot retrieve packet client/server sets."); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java index 391c321f..a6744fb0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -37,6 +37,7 @@ import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; +import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.MethodInfo; @@ -49,6 +50,85 @@ import com.comphenix.protocol.utility.MinecraftReflection; * @author Kristian */ class ProxyPacketInjector implements PacketInjector { + /** + * Represents a way to update the packet ID to class lookup table. + * @author Kristian + */ + private static interface PacketClassLookup { + public void setLookup(int packetID, Class clazz); + } + + private static class IntHashMapLookup implements PacketClassLookup { + // The "put" method that associates a packet ID with a packet class + private Method putMethod; + private Object intHashMap; + + public IntHashMapLookup() throws IllegalAccessException { + initialize(); + } + + @Override + public void setLookup(int packetID, Class clazz) { + try { + putMethod.invoke(intHashMap, packetID, clazz); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Illegal argument.", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access method.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Exception occured in IntHashMap.put.", e); + } + } + + private void initialize() throws IllegalAccessException { + if (intHashMap == null) { + // We're looking for the first static field with a Minecraft-object. This should be a IntHashMap. + Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true). + getFieldByType(MinecraftReflection.getMinecraftObjectRegex()); + + try { + intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Minecraft is incompatible.", e); + } + + // Now, get the "put" method. + putMethod = FuzzyReflection.fromObject(intHashMap). + getMethodByParameters("put", int.class, Object.class); + } + } + } + + private static class ArrayLookup implements PacketClassLookup { + private Class[] array; + + public ArrayLookup() throws IllegalAccessException { + initialize(); + } + + @Override + public void setLookup(int packetID, Class clazz) { + array[packetID] = clazz; + } + + private void initialize() throws IllegalAccessException { + FuzzyReflection reflection = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()); + + // Is there a Class array with 256 elements instead? + for (Field field : reflection.getFieldListByType(Class[].class)) { + Class[] test = (Class[]) FieldUtils.readField(field, (Object)null); + + if (test.length == 256) { + array = test; + return; + } + } + throw new IllegalArgumentException( + "Unable to find an array with the type " + Class[].class + + " in " + MinecraftReflection.getPacketClass()); + } + } + /** * Matches the readPacketData(DataInputStream) method in Packet. */ @@ -58,9 +138,7 @@ class ProxyPacketInjector implements PacketInjector { parameterCount(1). build(); - // The "put" method that associates a packet ID with a packet class - private static Method putMethod; - private static Object intHashMap; + private static PacketClassLookup lookup; // The packet filter manager private ListenerInvoker manager; @@ -78,7 +156,7 @@ class ProxyPacketInjector implements PacketInjector { private CallbackFilter filter; public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager, - PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { + PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws FieldAccessException { this.classLoader = classLoader; this.manager = manager; @@ -100,20 +178,21 @@ class ProxyPacketInjector implements PacketInjector { } } - private void initialize() throws IllegalAccessException { - if (intHashMap == null) { - // We're looking for the first static field with a Minecraft-object. This should be a IntHashMap. - Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true). - getFieldByType(MinecraftReflection.getMinecraftObjectRegex()); - + private void initialize() throws FieldAccessException { + if (lookup == null) { try { - intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); - } catch (IllegalArgumentException e) { - throw new RuntimeException("Minecraft is incompatible.", e); + lookup = new IntHashMapLookup(); + } catch (Exception e1) { + + try { + lookup = new ArrayLookup(); + } catch (Exception e2) { + // Wow + throw new FieldAccessException(e1.getMessage() + ". Workaround failed too.", e2); + } } - // Now, get the "put" method. - putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class); + // Should work fine now } } @@ -173,21 +252,12 @@ class ProxyPacketInjector implements PacketInjector { // Add a static reference Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest }); - try { - // Override values - previous.put(packetID, old); - registry.put(proxy, packetID); - overwritten.put(packetID, proxy); - putMethod.invoke(intHashMap, packetID, proxy); - return true; - - } catch (IllegalArgumentException e) { - throw new RuntimeException("Illegal argument.", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access method.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Exception occured in IntHashMap.put.", e); - } + // Override values + previous.put(packetID, old); + registry.put(proxy, packetID); + overwritten.put(packetID, proxy); + lookup.setLookup(packetID, proxy); + return true; } @Override @@ -200,25 +270,14 @@ class ProxyPacketInjector implements PacketInjector { Map previous = PacketRegistry.getPreviousPackets(); Map overwritten = PacketRegistry.getOverwrittenPackets(); - // Use the old class definition - try { - Class old = previous.get(packetID); - Class proxy = PacketRegistry.getPacketClassFromID(packetID); - - putMethod.invoke(intHashMap, packetID, old); - previous.remove(packetID); - registry.remove(proxy); - overwritten.remove(packetID); - return true; - - // Handle some problems - } catch (IllegalArgumentException e) { - return false; - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access method.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Exception occured in IntHashMap.put.", e); - } + Class old = previous.get(packetID); + Class proxy = PacketRegistry.getPacketClassFromID(packetID); + + lookup.setLookup(packetID, old); + previous.remove(packetID); + registry.remove(proxy); + overwritten.remove(packetID); + return true; } @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java new file mode 100644 index 00000000..bef4b369 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java @@ -0,0 +1,104 @@ +package com.comphenix.protocol.wrappers; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nonnull; + +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; + +/** + * Wrap a GNU Trove Collection class with an equivalent Java Collection class. + * @author Kristian + */ +public class TroveWrapper { + private volatile static Class decorators; + + /** + * Retrieve a Java wrapper for the corresponding Trove map. + * @param troveMap - the trove map to wrap. + * @return The wrapped GNU Trove map. + * @throws IllegalStateException If GNU Trove cannot be found in the class map. + * @throws IllegalArgumentException If troveMap is NULL. + * @throws FieldAccessException Error in wrapper method or lack of reflection permissions. + */ + public static Map getDecoratedMap(@Nonnull Object troveMap) { + @SuppressWarnings("unchecked") + Map result = (Map) getDecorated(troveMap); + return result; + } + + /** + * Retrieve a Java wrapper for the corresponding Trove set. + * @param troveSet - the trove set to wrap. + * @return The wrapped GNU Trove set. + * @throws IllegalStateException If GNU Trove cannot be found in the class map. + * @throws IllegalArgumentException If troveSet is NULL. + * @throws FieldAccessException Error in wrapper method or lack of reflection permissions. + */ + public static Set getDecoratedSet(@Nonnull Object troveSet) { + @SuppressWarnings("unchecked") + Set result = (Set) getDecorated(troveSet); + return result; + } + + /** + * Retrieve a Java wrapper for the corresponding Trove list. + * @param troveList - the trove list to wrap. + * @return The wrapped GNU Trove list. + * @throws IllegalStateException If GNU Trove cannot be found in the class map. + * @throws IllegalArgumentException If troveList is NULL. + * @throws FieldAccessException Error in wrapper method or lack of reflection permissions. + */ + public static List getDecoratedList(@Nonnull Object troveList) { + @SuppressWarnings("unchecked") + List result = (List) getDecorated(troveList); + return result; + } + + private static Object getDecorated(@Nonnull Object trove) { + if (trove == null) + throw new IllegalArgumentException("trove instance cannot be non-null."); + + AbstractFuzzyMatcher> match = FuzzyMatchers.matchSuper(trove.getClass()); + + if (decorators == null) { + try { + // Attempt to get decorator class + decorators = TroveWrapper.class.getClassLoader().loadClass("gnu.trove.TDecorators"); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Cannot find TDecorators in Gnu Trove.", e); + } + } + + // Find an appropriate wrapper method in TDecorators + for (Method method : decorators.getMethods()) { + Class[] types = method.getParameterTypes(); + + if (types.length == 1 && match.isMatch(types[0], null)) { + try { + Object result = method.invoke(null, trove); + + if (result == null) + throw new FieldAccessException("Wrapper returned NULL."); + else + return result; + + } catch (IllegalArgumentException e) { + throw new FieldAccessException("Cannot invoke wrapper method.", e); + } catch (IllegalAccessException e) { + throw new FieldAccessException("Illegal access.", e); + } catch (InvocationTargetException e) { + throw new FieldAccessException("Error in invocation.", e); + } + } + } + + throw new IllegalArgumentException("Cannot find decorator for " + trove + " (" + trove.getClass() + ")"); + } +}