From 901ab1fddadf4b84b3a1f4f1abb09d384edd8431 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 5 Feb 2013 07:00:49 +0100 Subject: [PATCH] Add support for the Netty Spigot build. This required extensive reworking of the inner packet injection system in ProtocolLib. I've also fixed a minior bug in the fuzzy member contract class. --- .../protocol/CleanupStaticMembers.java | 6 +- .../protocol/concurrency/IntegerSet.java | 2 +- .../injector/PacketFilterManager.java | 46 +- .../player/NetworkObjectInjector.java | 24 +- .../player/NetworkServerInjector.java | 2 +- .../injector/player/PlayerInjector.java | 39 +- .../player/PlayerInjectorBuilder.java | 5 +- .../player/TemporaryPlayerFactory.java | 2 +- .../injector/spigot/DummyPacketInjector.java | 57 +++ .../injector/spigot/DummyPlayerHandler.java | 123 +++++ .../injector/spigot/SpigotPacketInjector.java | 447 ++++++++++++++++++ .../injector/spigot/SpigotPacketListener.java | 34 ++ .../reflect/fuzzy/AbstractFuzzyMember.java | 5 +- 13 files changed, 750 insertions(+), 42 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketListener.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index f4940bee..57f31fe7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -78,9 +78,9 @@ class CleanupStaticMembers { "com.comphenix.protocol.injector.player.PlayerInjector", "com.comphenix.protocol.injector.player.TemporaryPlayerFactory", "com.comphenix.protocol.injector.EntityUtilities", - "com.comphenix.protocol.injector.MinecraftRegistry", - "com.comphenix.protocol.injector.PacketInjector", - "com.comphenix.protocol.injector.ReadPacketModifier", + "com.comphenix.protocol.injector.packet.PacketRegistry", + "com.comphenix.protocol.injector.packet.PacketInjector", + "com.comphenix.protocol.injector.packet.ReadPacketModifier", "com.comphenix.protocol.injector.StructureCache", "com.comphenix.protocol.reflect.compiler.BoxingHelper", "com.comphenix.protocol.reflect.compiler.MethodDescriptor" diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/IntegerSet.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/IntegerSet.java index 2393fced..e2aaa3f6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/IntegerSet.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/IntegerSet.java @@ -44,7 +44,7 @@ public class IntegerSet { /** * Determine whether or not the given element exists in the set. - * @param value - the element to check. Must be in the range [0, count). + * @param element - the element to check. Must be in the range [0, count). * @return TRUE if the given element exists, FALSE otherwise. */ public boolean contains(int element) { 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 6cfc086f..eea43aae 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -56,6 +56,7 @@ import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectorBuilder; +import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; @@ -142,6 +143,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Whether or not plugins are using the send/receive methods private AtomicBoolean packetCreation = new AtomicBoolean(); + // Spigot listener, if in use + private SpigotPacketInjector spigotInjector; + /** * Only create instances of this class if protocol lib is disabled. * @param unhookTask @@ -181,22 +185,30 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok }; try { - // Initialize injection mangers - this.playerInjection = PlayerInjectorBuilder.newBuilder(). - invoker(this). - server(server). - reporter(reporter). - classLoader(classLoader). - packetListeners(packetListeners). - injectionFilter(isInjectionNecessary). - buildHandler(); - - this.packetInjector = PacketInjectorBuilder.newBuilder(). - invoker(this). - reporter(reporter). - classLoader(classLoader). - playerInjection(playerInjection). - buildInjector(); + // Spigot + if (SpigotPacketInjector.canUseSpigotListener()) { + spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server); + this.playerInjection = spigotInjector.getPlayerHandler(); + this.packetInjector = spigotInjector.getPacketInjector(); + + } else { + // Initialize standard injection mangers + this.playerInjection = PlayerInjectorBuilder.newBuilder(). + invoker(this). + server(server). + reporter(reporter). + classLoader(classLoader). + packetListeners(packetListeners). + injectionFilter(isInjectionNecessary). + buildHandler(); + + this.packetInjector = PacketInjectorBuilder.newBuilder(). + invoker(this). + reporter(reporter). + classLoader(classLoader). + playerInjection(playerInjection). + buildInjector(); + } this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this); @@ -618,6 +630,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * @param plugin - the parent plugin. */ public void registerEvents(PluginManager manager, final Plugin plugin) { + if (spigotInjector != null && !spigotInjector.register(plugin)) + throw new IllegalArgumentException("Spigot has already been registered."); try { manager.registerEvents(new Listener() { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index 8b9f44f8..8fa193a4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -28,6 +28,7 @@ import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; +import org.bukkit.Server; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; @@ -38,13 +39,14 @@ import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; /** - * Injection method that overrides the NetworkHandler itself, and it's sendPacket-method. + * Injection method that overrides the NetworkHandler itself, and it's queue-method. * * @author Kristian */ -class NetworkObjectInjector extends PlayerInjector { +public class NetworkObjectInjector extends PlayerInjector { // Determine if we're listening private IntegerSet sendingFilters; @@ -54,6 +56,9 @@ class NetworkObjectInjector extends PlayerInjector { // Shared callback filter - avoid creating a new class every time private static CallbackFilter callbackFilter; + // Temporary player factory + private static volatile TemporaryPlayerFactory tempPlayerFactory; + public NetworkObjectInjector(ClassLoader classLoader, ErrorReporter reporter, Player player, ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException { super(reporter, player, invoker); @@ -66,6 +71,21 @@ class NetworkObjectInjector extends PlayerInjector { return sendingFilters.contains(packetID); } + /** + * Create a temporary player for use during login. + * @param server - Bukkit server. + * @return The temporary player. + */ + public Player createTemporaryPlayer(Server server) { + if (tempPlayerFactory == null) + tempPlayerFactory = new TemporaryPlayerFactory(); + + // Create and associate this fake player with this network injector + Player player = tempPlayerFactory.createTemporaryPlayer(server); + ((InjectContainer) player).setInjector(this); + return player; + } + @Override public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index da328bd2..f81be360 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -52,7 +52,7 @@ import com.google.common.collect.Maps; * * @author Kristian */ -public class NetworkServerInjector extends PlayerInjector { +class NetworkServerInjector extends PlayerInjector { private volatile static CallbackFilter callbackFilter; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index da1e1834..d3f0109a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -58,10 +58,10 @@ abstract class PlayerInjector { protected static Field proxyServerField; protected static Field networkManagerField; - protected static Field inputField; protected static Field netHandlerField; protected static Field socketField; + private static Field inputField; private static Field entityPlayerField; // Whether or not we're using a proxy type @@ -206,11 +206,6 @@ abstract class PlayerInjector { if (queueMethod == null) queueMethod = FuzzyReflection.fromClass(reference.getType()). getMethodByParameters("queue", MinecraftReflection.getPacketClass()); - - // And the data input stream that we'll use to identify a player - if (inputField == null) - inputField = FuzzyReflection.fromObject(networkManager, true). - getFieldByType("java\\.io\\.DataInputStream"); } /** @@ -250,9 +245,9 @@ abstract class PlayerInjector { public Socket getSocket() throws IllegalAccessException { try { if (socketField == null) - socketField = FuzzyReflection.fromObject(networkManager).getFieldListByType(Socket.class).get(0); + socketField = FuzzyReflection.fromObject(networkManager, true).getFieldListByType(Socket.class).get(0); if (socket == null) - socket = (Socket) FieldUtils.readField(socketField, networkManager); + socket = (Socket) FieldUtils.readField(socketField, networkManager, true); return socket; } catch (IndexOutOfBoundsException e) { @@ -570,11 +565,13 @@ abstract class PlayerInjector { * @return The player's input stream. */ public DataInputStream getInputStream(boolean cache) { - if (inputField == null) - throw new IllegalStateException("Input field is NULL."); + // And the data input stream that we'll use to identify a player if (networkManager == null) - throw new IllegalStateException("Network manager is NULL."); - + throw new IllegalStateException("Network manager is NULL."); + if (inputField == null) + inputField = FuzzyReflection.fromObject(networkManager, true). + getFieldByType("java\\.io\\.DataInputStream"); + // Get the associated input stream try { if (cache && cachedInput != null) @@ -604,6 +601,16 @@ abstract class PlayerInjector { return player; } + /** + * Set the hooked player. + *

+ * Should only be called during the creation of the injector. + * @param player - the new hooked player. + */ + public void setPlayer(Player player) { + this.player = player; + } + /** * Object that can invoke the packet events. * @return Packet event invoker. @@ -622,4 +629,12 @@ abstract class PlayerInjector { else return player; } + + /** + * Set the real Bukkit player that we will use. + * @param updatedPlayer - the real Bukkit player. + */ + public void setUpdatedPlayer(Player updatedPlayer) { + this.updatedPlayer = updatedPlayer; + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java index 39448bd3..120d5dc9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java @@ -138,7 +138,8 @@ public class PlayerInjectorBuilder { // Fill any default fields initializeDefaults(); - return new ProxyPlayerInjectionHandler(classLoader, reporter, injectionFilter, invoker, - packetListeners, server); + return new ProxyPlayerInjectionHandler( + classLoader, reporter, injectionFilter, + invoker, packetListeners, server); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java index 45942257..67336df5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java @@ -81,7 +81,7 @@ class TemporaryPlayerFactory { * *

* Note that the player a player has not been assigned a name yet, and thus cannot be - * uniquely identified. Use the + * uniquely identified. Use the address instead. * @param injector - the player injector used. * @param server - the current server. * @return A temporary player instance. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java new file mode 100644 index 00000000..e77a32de --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java @@ -0,0 +1,57 @@ +package com.comphenix.protocol.injector.spigot; + +import java.util.Set; + +import org.bukkit.entity.Player; + +import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.packet.PacketInjector; + +public class DummyPacketInjector implements PacketInjector { + private SpigotPacketInjector injector; + private IntegerSet reveivedFilters; + + public DummyPacketInjector(SpigotPacketInjector injector, IntegerSet reveivedFilters) { + this.injector = injector; + this.reveivedFilters = reveivedFilters; + } + + @Override + public void undoCancel(Integer id, Object packet) { + // Do nothing yet + } + + @Override + public boolean addPacketHandler(int packetID) { + reveivedFilters.add(packetID); + return true; + } + + @Override + public boolean removePacketHandler(int packetID) { + reveivedFilters.remove(packetID); + return true; + } + + @Override + public boolean hasPacketHandler(int packetID) { + return reveivedFilters.contains(packetID); + } + + @Override + public Set getPacketHandlers() { + return reveivedFilters.toSet(); + } + + @Override + public PacketEvent packetRecieved(PacketContainer packet, Player client) { + return injector.packetReceived(packet, client); + } + + @Override + public void cleanupAll() { + reveivedFilters.clear(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java new file mode 100644 index 00000000..ee8139f0 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java @@ -0,0 +1,123 @@ +package com.comphenix.protocol.injector.spigot; + +import java.io.DataInputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.InetSocketAddress; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.bukkit.entity.Player; + +import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.GamePhase; +import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; + +class DummyPlayerHandler implements PlayerInjectionHandler { + private SpigotPacketInjector injector; + private IntegerSet sendingFilters; + + public DummyPlayerHandler(SpigotPacketInjector injector, IntegerSet sendingFilters) { + this.injector = injector; + this.sendingFilters = sendingFilters; + } + + @Override + public boolean uninjectPlayer(InetSocketAddress address) { + return true; + } + + @Override + public boolean uninjectPlayer(Player player) { + injector.uninjectPlayer(player); + return true; + } + + @Override + public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) { + throw new UnsupportedOperationException("This is not needed in Spigot."); + } + + @Override + public void setPlayerHook(PlayerInjectHooks playerHook) { + throw new UnsupportedOperationException("This is not needed in Spigot."); + } + + @Override + public void scheduleDataInputRefresh(Player player) { + // Fine + } + + @Override + public void addPacketHandler(int packetID) { + sendingFilters.add(packetID); + } + + @Override + public void removePacketHandler(int packetID) { + sendingFilters.remove(packetID); + } + + @Override + public Set getSendingFilters() { + return sendingFilters.toSet(); + } + + @Override + public void close() { + sendingFilters.clear(); + } + + @Override + public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { + injector.sendServerPacket(reciever, packet, filters); + } + + @Override + public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { + injector.processPacket(player, mcPacket); + } + + @Override + public void injectPlayer(Player player) { + injector.injectPlayer(player); + } + + @Override + public void handleDisconnect(Player player) { + // Just ignore + } + + @Override + public PlayerInjectHooks getPlayerHook(GamePhase phase) { + return PlayerInjectHooks.NETWORK_SERVER_OBJECT; + } + + @Override + public PlayerInjectHooks getPlayerHook() { + // Pretend that we do + return PlayerInjectHooks.NETWORK_SERVER_OBJECT; + } + + @Override + public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException("This is not needed in Spigot."); + } + + @Override + public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { + throw new UnsupportedOperationException("This is not needed in Spigot."); + } + + @Override + public void checkListener(PacketListener listener) { + // They're all fine! + } + + @Override + public void checkListener(Set listeners) { + // Yes, really + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java new file mode 100644 index 00000000..1ff5ca62 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java @@ -0,0 +1,447 @@ +package com.comphenix.protocol.injector.spigot; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.CallbackFilter; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; +import net.sf.cglib.proxy.NoOp; + +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.ListenerInvoker; +import com.comphenix.protocol.injector.PlayerLoggedOutException; +import com.comphenix.protocol.injector.packet.PacketInjector; +import com.comphenix.protocol.injector.player.NetworkObjectInjector; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; +import com.comphenix.protocol.reflect.MethodInfo; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.collect.MapMaker; + +/** + * Offload all the work to Spigot, if possible. + * + * @author Kristian + */ +public class SpigotPacketInjector implements SpigotPacketListener { + // Lazily retrieve the spigot listener class + private static volatile Class spigotListenerClass; + private static volatile boolean classChecked; + + // Packets that are not to be processed by the filters + private Set ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); + + /** + * The amount of ticks to wait before removing all traces of a player. + */ + private static final int CLEANUP_DELAY = 100; + + /** + * Retrieve the spigot packet listener class. + * @return The listener class. + */ + private static Class getSpigotListenerClass() { + if (!classChecked) { + try { + spigotListenerClass = SpigotPacketInjector.class.getClassLoader().loadClass("org.spigotmc.netty.PacketListener"); + } catch (ClassNotFoundException e) { + return null; + } finally { + // We've given it a try now + classChecked = true; + } + } + return spigotListenerClass; + } + + /** + * Retrieve the register packet listener method. + * @return The method used to register a packet listener. + */ + private static Method getRegisterMethod() { + Class clazz = getSpigotListenerClass(); + + if (clazz != null) { + try { + return clazz.getMethod("register", clazz, Plugin.class); + } catch (SecurityException e) { + // If this happens, then ... we're doomed + throw new RuntimeException("Reflection is not allowed.", e); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Cannot find register() method in " + clazz, e); + } + } + + // Also bad + throw new IllegalStateException("Spigot could not be found!"); + } + + /** + * Determine if there is a Spigot packet listener. + * @return Spigot packet listener. + */ + public static boolean canUseSpigotListener() { + return getSpigotListenerClass() != null; + } + + // The listener we will register on Spigot. + // Unfortunately, due to the use of PlayerConnection, INetworkManager and Packet, we're + // unable to reference it directly. But with CGLib, it shouldn't cost us much. + private Object dynamicListener; + + // Reference to ProtocolLib + private Plugin plugin; + + // Different sending filters + private IntegerSet queuedFilters; + private IntegerSet reveivedFilters; + + // NetworkManager to injector and player + private ConcurrentMap networkManagerInjector = new ConcurrentHashMap(); + + // Player to injector + private ConcurrentMap playerInjector = new ConcurrentHashMap(); + + // Responsible for informing the PL packet listeners + private ListenerInvoker invoker; + private ErrorReporter reporter; + private Server server; + private ClassLoader classLoader; + + /** + * Create a new spigot injector. + */ + public SpigotPacketInjector(ClassLoader classLoader, ErrorReporter reporter, ListenerInvoker invoker, Server server) { + this.classLoader = classLoader; + this.reporter = reporter; + this.invoker = invoker; + this.server = server; + this.queuedFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1); + this.reveivedFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1); + } + + public boolean register(Plugin plugin) { + if (hasRegistered()) + return false; + + // Save the plugin too + this.plugin = plugin; + + final Callback[] callbacks = new Callback[3]; + final boolean[] found = new boolean[3]; + + // Packets received from the clients + callbacks[0] = new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + return SpigotPacketInjector.this.packetReceived(args[0], args[1], args[2]); + } + }; + // Packet sent/queued + callbacks[1] = new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + return SpigotPacketInjector.this.packetQueued(args[0], args[1], args[2]); + } + }; + + // Don't care for everything else + callbacks[2] = NoOp.INSTANCE; + + Enhancer enhancer = new Enhancer(); + enhancer.setClassLoader(classLoader); + enhancer.setSuperclass(getSpigotListenerClass()); + enhancer.setCallbacks(callbacks); + enhancer.setCallbackFilter(new CallbackFilter() { + @Override + public int accept(Method method) { + // We'll be pretty stringent + if (matchMethod("packetReceived", method)) { + found[0] = true; + return 0; + } else if (matchMethod("packetQueued", method)) { + found[1] = true; + return 1; + } else { + found[2] = true; + return 2; + } + } + }); + dynamicListener = enhancer.create(); + + // Verify methods + if (!found[0]) + throw new IllegalStateException("Unable to find a valid packet receiver in Spigot."); + if (!found[1]) + throw new IllegalStateException("Unable to find a valid packet queue in Spigot."); + + // Lets register it too + try { + getRegisterMethod().invoke(null, dynamicListener, plugin); + } catch (Exception e) { + throw new RuntimeException("Cannot register Spigot packet listener.", e); + } + + // If we succeed + return true; + } + + /** + * Determine if the given method is a valid packet receiver or queued method. + * @param methodName - the expected name of the method. + * @param method - the method we're testing. + * @return TRUE if this is a correct method, FALSE otherwise. + */ + private boolean matchMethod(String methodName, Method method) { + return FuzzyMethodContract.newBuilder(). + nameExact(methodName). + parameterCount(3). + parameterSuperOf(MinecraftReflection.getNetHandlerClass(), 1). + parameterSuperOf(MinecraftReflection.getPacketClass(), 2). + returnTypeExact(MinecraftReflection.getPacketClass()). + build(). + isMatch(MethodInfo.fromMethod(method), null); + } + + public boolean hasRegistered() { + return dynamicListener != null; + } + + public PlayerInjectionHandler getPlayerHandler() { + return new DummyPlayerHandler(this, queuedFilters); + } + + public PacketInjector getPacketInjector() { + return new DummyPacketInjector(this, reveivedFilters); + } + + /** + * Retrieve the currently registered injector for the given player. + * @param player - injected player. + * @return The injector. + */ + NetworkObjectInjector getInjector(Player player) { + return playerInjector.get(player); + } + + /** + * Retrieve or create a registered injector for the given network manager and connection. + * @param networkManager - a INetworkManager object. + * @param connection - a Connection (PlayerConnection, PendingConnection) object. + * @return The created NetworkObjectInjector with a temporary player. + */ + NetworkObjectInjector getInjector(Object networkManager, Object connection) { + NetworkObjectInjector dummyInjector = networkManagerInjector.get(networkManager); + + if (dummyInjector == null) { + // Inject the network manager + try { + NetworkObjectInjector created = new NetworkObjectInjector(classLoader, reporter, null, invoker, null); + + created.initializeLogin(connection); + created.setPlayer(created.createTemporaryPlayer(server)); + dummyInjector = saveInjector(networkManager, created); + + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot create dummy injector.", e); + } + } + + return dummyInjector; + } + + /** + * Save a given player injector for later. + * @param networkManager - the associated network manager. + * @param created - the created network object creator. + * @return Any other network injector that came before us. + */ + private NetworkObjectInjector saveInjector(Object networkManager, NetworkObjectInjector created) { + // Concurrency - use the same injector! + NetworkObjectInjector result = networkManagerInjector.putIfAbsent(networkManager, created); + + if (result == null) { + result = created; + } + + // Save the player as well + playerInjector.put(created.getPlayer(), created); + return result; + } + + @Override + public Object packetReceived(Object networkManager, Object connection, Object packet) { + Integer id = invoker.getPacketID(packet); + + if (id != null && reveivedFilters.contains(id)) { + // Check for ignored packets + if (ignoredPackets.remove(packet)) { + return packet; + } + + Player sender = getInjector(networkManager, connection).getUpdatedPlayer(); + PacketContainer container = new PacketContainer(id, packet); + PacketEvent event = packetReceived(container, sender); + + if (!event.isCancelled()) + return event.getPacket().getHandle(); + else + return null; // Cancel + } + // Don't change anything + return packet; + } + + @Override + public Object packetQueued(Object networkManager, Object connection, Object packet) { + Integer id = invoker.getPacketID(packet); + + if (id != null & queuedFilters.contains(id)) { + // Check for ignored packets + if (ignoredPackets.remove(packet)) { + return packet; + } + + Player reciever = getInjector(networkManager, connection).getUpdatedPlayer(); + PacketContainer container = new PacketContainer(id, packet); + PacketEvent event = packetQueued(container, reciever); + + if (!event.isCancelled()) + return event.getPacket().getHandle(); + else + return null; // Cancel + } + // Don't change anything + return packet; + } + + /** + * Called to inform the event listeners of a queued packet. + * @param packet - the packet that is to be sent. + * @param reciever - the reciever of this packet. + * @return The packet event that was used. + */ + PacketEvent packetQueued(PacketContainer packet, Player reciever) { + PacketEvent event = PacketEvent.fromServer(this, packet, reciever); + + invoker.invokePacketSending(event); + return event; + } + + /** + * Called to inform the event listeners of a received packet. + * @param packet - the packet that has been receieved. + * @param sender - the client packet. + * @return The packet event that was used. + */ + PacketEvent packetReceived(PacketContainer packet, Player sender) { + PacketEvent event = PacketEvent.fromClient(this, packet, sender); + + invoker.invokePacketRecieving(event); + return event; + } + + /** + * Called when a player has logged in properly. + * @param player - the player that has logged in. + */ + void injectPlayer(Player player) { + try { + NetworkObjectInjector dummy = new NetworkObjectInjector(classLoader, reporter, player, invoker, null); + dummy.initializePlayer(player); + + // Save this player for the network manager + NetworkObjectInjector realInjector = networkManagerInjector.get(dummy.getNetworkManager()); + + if (realInjector != null) { + // Update all future references + realInjector.setUpdatedPlayer(player); + playerInjector.put(player, realInjector); + } else { + // Ah - in that case, save this injector + saveInjector(dummy.getNetworkManager(), dummy); + } + + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot inject " + player); + } + } + + /** + * Uninject the given player. + * @param player - the player to uninject. + */ + void uninjectPlayer(Player player) { + final NetworkObjectInjector injector = getInjector(player); + + if (player != null) { + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + @Override + public void run() { + // Clean up + playerInjector.remove(injector.getPlayer()); + playerInjector.remove(injector.getUpdatedPlayer()); + networkManagerInjector.remove(injector); + } + }, CLEANUP_DELAY); + } + } + + /** + * Invoked when a plugin wants to sent a packet. + * @param reciever - the packet receiver. + * @param packet - the packet to transmit. + * @param filters - whether or not to invoke the packet listeners. + * @throws InvocationTargetException If anything went wrong. + */ + void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { + NetworkObjectInjector networkObject = getInjector(reciever); + + // If TRUE, process this packet like any other + if (filters) + ignoredPackets.remove(packet.getHandle()); + else + ignoredPackets.add(packet.getHandle()); + + if (networkObject != null) + networkObject.sendServerPacket(packet.getHandle(), filters); + else + throw new PlayerLoggedOutException("Player " + reciever + " has logged out"); + } + + /** + * Invoked when a plugin wants to simulate receiving a packet. + * @param player - the supposed sender. + * @param mcPacket - the packet to receieve. + * @throws IllegalAccessException Reflection is not permitted. + * @throws InvocationTargetException Minecraft threw an exception. + */ + void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { + NetworkObjectInjector networkObject = getInjector(player); + + // We will always ignore this packet + ignoredPackets.add(mcPacket); + + if (networkObject != null) + networkObject.processPacket(mcPacket); + else + throw new PlayerLoggedOutException("Player " + player + " has logged out"); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketListener.java new file mode 100644 index 00000000..6a38fb68 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketListener.java @@ -0,0 +1,34 @@ +package com.comphenix.protocol.injector.spigot; + +/** + * Represents a proxy for a Spigot packet listener. + * + * @author Kristian + */ +interface SpigotPacketListener { + /** + * Called when a packet has been received and is about to be handled by the + * current Connection. + *

+ * The returned packet will be the packet passed on for handling, or in the case of + * null being returned, not handled at all. + * + * @param networkManager the NetworkManager receiving the packet + * @param connection the connection which will handle the packet + * @param packet the received packet + * @return the packet to be handled, or null to cancel + */ + public Object packetReceived(Object networkManager, Object connection, Object packet); + + /** + * Called when a packet is queued to be sent.The returned packet will be + * the packet sent. In the case of null being returned, the packet will not + * be sent. + * + * @param networkManager the NetworkManager which will send the packet + * @param connection the connection which queued the packet + * @param packet the queue packet + * @return the packet to be sent, or null if the packet will not be sent. + */ + public Object packetQueued(Object networkManager, Object connection, Object packet); +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMember.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMember.java index 6cc0b107..40147caf 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMember.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMember.java @@ -153,10 +153,7 @@ public abstract class AbstractFuzzyMember extends AbstractFuzz * Use this to prepare any special values. */ protected void prepareBuild() { - // Permit any modifier if we havent's specified anything - if (modifiersRequired == 0) { - modifiersRequired = Integer.MAX_VALUE; - } + // No need to prepare anything } // Clone a given contract