From af3e278e06850b19b8ae02d6213d672fdde32a55 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 4 Oct 2012 21:56:39 +0200 Subject: [PATCH] Moved all player related injection methods into a separate package. This should make the code a little bit clearer. --- ProtocolLib/.classpath | 1 + .../protocol/injector/ListenerInvoker.java | 27 ++ .../injector/PacketFilterManager.java | 245 +++----------- .../protocol/injector/PacketInjector.java | 24 +- .../{ => player}/InjectedArrayList.java | 6 +- .../InjectedServerConnection.java | 2 +- .../{ => player}/NetworkFieldInjector.java | 32 +- .../{ => player}/NetworkObjectInjector.java | 22 +- .../{ => player}/NetworkServerInjector.java | 35 +- .../injector/player/NetworkSpoutHook.java | 60 ++++ .../player/PlayerInjectionHandler.java | 306 ++++++++++++++++++ .../injector/{ => player}/PlayerInjector.java | 51 ++- .../{ => player}/ReplacedArrayList.java | 2 +- 13 files changed, 572 insertions(+), 241 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/ListenerInvoker.java rename ProtocolLib/src/com/comphenix/protocol/injector/{ => player}/InjectedArrayList.java (93%) rename ProtocolLib/src/com/comphenix/protocol/injector/{ => player}/InjectedServerConnection.java (95%) rename ProtocolLib/src/com/comphenix/protocol/injector/{ => player}/NetworkFieldInjector.java (79%) rename ProtocolLib/src/com/comphenix/protocol/injector/{ => player}/NetworkObjectInjector.java (81%) rename ProtocolLib/src/com/comphenix/protocol/injector/{ => player}/NetworkServerInjector.java (81%) create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkSpoutHook.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java rename ProtocolLib/src/com/comphenix/protocol/injector/{ => player}/PlayerInjector.java (84%) rename ProtocolLib/src/com/comphenix/protocol/injector/{ => player}/ReplacedArrayList.java (94%) diff --git a/ProtocolLib/.classpath b/ProtocolLib/.classpath index 0bc481ca..408271fe 100644 --- a/ProtocolLib/.classpath +++ b/ProtocolLib/.classpath @@ -10,5 +10,6 @@ + diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/ListenerInvoker.java b/ProtocolLib/src/com/comphenix/protocol/injector/ListenerInvoker.java new file mode 100644 index 00000000..e5bc0e4b --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/ListenerInvoker.java @@ -0,0 +1,27 @@ +package com.comphenix.protocol.injector; + +import net.minecraft.server.Packet; + +import com.comphenix.protocol.events.PacketEvent; + +public interface ListenerInvoker { + + /** + * Invokes the given packet event for every registered listener. + * @param event - the packet event to invoke. + */ + public abstract void invokePacketRecieving(PacketEvent event); + + /** + * Invokes the given packet event for every registered listener. + * @param event - the packet event to invoke. + */ + public abstract void invokePacketSending(PacketEvent event); + + /** + * Retrieve the associated ID of a packet. + * @param packet - the packet. + * @return The packet ID. + */ + public abstract int getPacketID(Packet packet); +} \ No newline at end of file diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 68333b7d..3d4c4c0e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -17,13 +17,10 @@ package com.comphenix.protocol.injector; -import java.io.DataInputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; @@ -51,12 +48,13 @@ import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.events.*; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; -public final class PacketFilterManager implements ProtocolManager { +public final class PacketFilterManager implements ProtocolManager, ListenerInvoker { /** * Sets the inject hook type. Different types allow for maximum compatibility. @@ -86,22 +84,12 @@ public final class PacketFilterManager implements ProtocolManager { // Create a concurrent set private Set packetListeners = Collections.newSetFromMap(new ConcurrentHashMap()); - - // Player injection - private Map connectionLookup = new ConcurrentHashMap(); - private Map playerInjection = new HashMap(); - - // Player injection type - private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; - + // Packet injection private PacketInjector packetInjector; - // Server connection injection - private InjectedServerConnection serverInjection; - - // Enabled packet filters - private Set sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap()); + // Player injection + private PlayerInjectionHandler playerInjection; // The two listener containers private SortedPacketListenerList recievedListeners = new SortedPacketListenerList(); @@ -112,10 +100,7 @@ public final class PacketFilterManager implements ProtocolManager { // The default class loader private ClassLoader classLoader; - - // The last successful player hook - private PlayerInjector lastSuccessfulHook; - + // Error logger private Logger logger; @@ -135,9 +120,9 @@ public final class PacketFilterManager implements ProtocolManager { // Initialize values this.classLoader = classLoader; this.logger = logger; - this.packetInjector = new PacketInjector(classLoader, this, connectionLookup); + this.playerInjection = new PlayerInjectionHandler(classLoader, logger, this, server); + this.packetInjector = new PacketInjector(classLoader, this, playerInjection); this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this); - this.serverInjection = new InjectedServerConnection(logger, server); } catch (IllegalAccessException e) { logger.log(Level.SEVERE, "Unable to initialize packet injector.", e); } @@ -153,7 +138,7 @@ public final class PacketFilterManager implements ProtocolManager { * @return Injection method for reading server packets. */ public PlayerInjectHooks getPlayerHook() { - return playerHook; + return playerInjection.getPlayerHook(); } /** @@ -161,18 +146,10 @@ public final class PacketFilterManager implements ProtocolManager { * @param playerHook - the new injection method for reading server packets. */ public void setPlayerHook(PlayerInjectHooks playerHook) { - this.playerHook = playerHook; + playerInjection.setPlayerHook(playerHook); // Make sure the current listeners are compatible - if (lastSuccessfulHook != null) { - for (PacketListener listener : packetListeners) { - try { - checkListener(listener); - } catch (IllegalStateException e) { - logger.log(Level.WARNING, "Unsupported listener.", e); - } - } - } + playerInjection.checkListener(packetListeners); } public Logger getLogger() { @@ -204,14 +181,14 @@ public final class PacketFilterManager implements ProtocolManager { verifyWhitelist(listener, sending); sendingListeners.addListener(listener, sending); enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist()); + + // Make sure this is possible + playerInjection.checkListener(listener); } if (hasReceiving) { verifyWhitelist(listener, receiving); recievedListeners.addListener(listener, receiving); enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); - - // We don't know if we've hooked any players yet - checkListener(listener); } // Inform our injected hooks @@ -234,21 +211,7 @@ public final class PacketFilterManager implements ProtocolManager { } } } - - /** - * Determine if a listener is valid or not. - * @param listener - listener to check. - * @throws IllegalStateException If the given listener's whitelist cannot be fulfilled. - */ - public void checkListener(PacketListener listener) { - try { - if (lastSuccessfulHook != null) - lastSuccessfulHook.checkListener(listener); - } catch (Exception e) { - throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e); - } - } - + @Override public void removePacketListener(PacketListener listener) { if (listener == null) @@ -292,18 +255,12 @@ public final class PacketFilterManager implements ProtocolManager { asyncFilterManager.unregisterAsyncHandlers(plugin); } - /** - * Invokes the given packet event for every registered listener. - * @param event - the packet event to invoke. - */ + @Override public void invokePacketRecieving(PacketEvent event) { handlePacket(recievedListeners, event, false); } - - /** - * Invokes the given packet event for every registered listener. - * @param event - the packet event to invoke. - */ + + @Override public void invokePacketSending(PacketEvent event) { handlePacket(sendingListeners, event, true); } @@ -356,8 +313,8 @@ public final class PacketFilterManager implements ProtocolManager { for (int packetID : packets) { if (side.isForServer()) - sendingFilters.add(packetID); - if (side.isForClient() && packetInjector != null) + playerInjection.addPacketHandler(packetID); + if (side.isForClient() && packetInjector != null) packetInjector.addPacketHandler(packetID); } } @@ -373,7 +330,7 @@ public final class PacketFilterManager implements ProtocolManager { for (int packetID : packets) { if (side.isForServer()) - sendingFilters.remove(packetID); + playerInjection.removePacketHandler(packetID); if (side.isForClient() && packetInjector != null) packetInjector.removePacketHandler(packetID); } @@ -391,7 +348,7 @@ public final class PacketFilterManager implements ProtocolManager { if (packet == null) throw new IllegalArgumentException("packet cannot be NULL."); - getInjector(reciever).sendServerPacket(packet.getHandle(), filters); + playerInjection.sendServerPacket(reciever, packet, filters); } @Override @@ -407,17 +364,21 @@ public final class PacketFilterManager implements ProtocolManager { if (packet == null) throw new IllegalArgumentException("packet cannot be NULL."); - PlayerInjector injector = getInjector(sender); Packet mcPacket = packet.getHandle(); // Make sure the packet isn't cancelled packetInjector.undoCancel(packet.getID(), mcPacket); if (filters) { - mcPacket = injector.handlePacketRecieved(mcPacket); + PacketEvent event = packetInjector.packetRecieved(packet, sender); + + if (!event.isCancelled()) + mcPacket = event.getPacket().getHandle(); + else + return; } - injector.processPacket(mcPacket); + playerInjection.processPacket(sender, mcPacket); } @Override @@ -448,7 +409,7 @@ public final class PacketFilterManager implements ProtocolManager { @Override public Set getSendingFilters() { - return ImmutableSet.copyOf(sendingFilters); + return playerInjection.getSendingFilters(); } @Override @@ -467,94 +428,9 @@ public final class PacketFilterManager implements ProtocolManager { */ public void initializePlayers(Player[] players) { for (Player player : players) - injectPlayer(player); + playerInjection.injectPlayer(player); } - - /** - * Used to construct a player hook. - * @param player - the player to hook. - * @param hook - the hook type. - * @return A new player hoook - * @throws IllegalAccessException Unable to do our reflection magic. - */ - protected PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException { - // Construct the correct player hook - switch (hook) { - case NETWORK_HANDLER_FIELDS: - return new NetworkFieldInjector(player, this, sendingFilters); - case NETWORK_MANAGER_OBJECT: - return new NetworkObjectInjector(player, this, sendingFilters); - case NETWORK_SERVER_OBJECT: - return new NetworkServerInjector(player, this, sendingFilters, serverInjection); - default: - throw new IllegalArgumentException("Cannot construct a player injector."); - } - } - - /** - * Initialize a player hook, allowing us to read server packets. - * @param player - player to hook. - */ - protected void injectPlayer(Player player) { - PlayerInjector injector = null; - PlayerInjectHooks currentHook = playerHook; - boolean firstPlayer = lastSuccessfulHook == null; - - // Don't inject if the class has closed - if (!hasClosed && player != null && !playerInjection.containsKey(player)) { - while (true) { - try { - injector = getHookInstance(player, currentHook); - injector.injectManager(); - - DataInputStream inputStream = injector.getInputStream(false); - - if (!player.isOnline() || inputStream == null) { - throw new PlayerLoggedOutException(); - } - - playerInjection.put(player, injector); - connectionLookup.put(inputStream, player); - break; - - - } catch (PlayerLoggedOutException e) { - throw e; - - } catch (Exception e) { - - // Mark this injection attempt as a failure - logger.log(Level.SEVERE, "Player hook " + currentHook.toString() + " failed.", e); - - // Clean up as much as possible - try { - if (injector != null) - injector.cleanupAll(); - } catch (Exception e2) { - logger.log(Level.WARNING, "Cleaing up after player hook failed.", e); - } - - if (currentHook.ordinal() > 0) { - // Choose the previous player hook type - currentHook = PlayerInjectHooks.values()[currentHook.ordinal() - 1]; - logger.log(Level.INFO, "Switching to " + currentHook.toString() + " instead."); - } else { - // UTTER FAILURE - playerInjection.put(player, null); - return; - } - } - } - - // Update values - if (injector != null) - lastSuccessfulHook = injector; - if (currentHook != playerHook || firstPlayer) - setPlayerHook(currentHook); - } - } - /** * Register this protocol manager on Bukkit. * @param manager - Bukkit plugin manager that provides player join/leave events. @@ -567,12 +443,12 @@ public final class PacketFilterManager implements ProtocolManager { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPlayerJoin(PlayerJoinEvent event) { - injectPlayer(event.getPlayer()); + playerInjection.injectPlayer(event.getPlayer()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPlayerQuit(PlayerQuitEvent event) { - uninjectPlayer(event.getPlayer()); + playerInjection.uninjectPlayer(event.getPlayer()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @@ -591,6 +467,14 @@ public final class PacketFilterManager implements ProtocolManager { } } + @Override + public int getPacketID(Packet packet) { + if (packet == null) + throw new IllegalArgumentException("Packet cannot be NULL."); + + return MinecraftRegistry.getPacketToID().get(packet.getClass()); + } + // Yes, this is crazy. @SuppressWarnings({ "unchecked", "rawtypes" }) private void registerOld(PluginManager manager, Plugin plugin) { @@ -632,9 +516,9 @@ public final class PacketFilterManager implements ProtocolManager { // Check for the correct event if (event instanceof PlayerJoinEvent) - injectPlayer(((PlayerJoinEvent) event).getPlayer()); + playerInjection.injectPlayer(((PlayerJoinEvent) event).getPlayer()); else if (event instanceof PlayerQuitEvent) - uninjectPlayer(((PlayerQuitEvent) event).getPlayer()); + playerInjection.uninjectPlayer(((PlayerQuitEvent) event).getPlayer()); } return null; } @@ -676,37 +560,7 @@ public final class PacketFilterManager implements ProtocolManager { e.printStackTrace(); } } - - private void uninjectPlayer(Player player) { - if (!hasClosed && player != null) { - - PlayerInjector injector = playerInjection.get(player); - - if (injector != null) { - DataInputStream input = injector.getInputStream(true); - injector.cleanupAll(); - - playerInjection.remove(player); - connectionLookup.remove(input); - } - } - } - - private PlayerInjector getInjector(Player player) { - if (!playerInjection.containsKey(player)) { - // What? Try to inject again. - injectPlayer(player); - } - PlayerInjector injector = playerInjection.get(player); - - // Check that the injector was sucessfully added - if (injector != null) - return injector; - else - throw new IllegalArgumentException("Player has no injected handler."); - } - /** * Retrieves the current plugin class loader. * @return Class loader. @@ -722,28 +576,19 @@ public final class PacketFilterManager implements ProtocolManager { public void close() { // Guard - if (hasClosed || playerInjection == null) + if (hasClosed) return; - // Remove everything - for (PlayerInjector injection : playerInjection.values()) { - if (injection != null) { - injection.cleanupAll(); - } - } - // Remove packet handlers if (packetInjector != null) packetInjector.cleanupAll(); // Remove server handler - serverInjection.cleanupAll(); + playerInjection.close(); hasClosed = true; // Remove listeners packetListeners.clear(); - playerInjection.clear(); - connectionLookup.clear(); // Clean up async handlers. We have to do this last. asyncFilterManager.cleanupAll(); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java index 998eb5a7..9e042885 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java @@ -33,6 +33,7 @@ import net.sf.cglib.proxy.Enhancer; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -48,10 +49,10 @@ class PacketInjector { private static Object intHashMap; // The packet filter manager - private PacketFilterManager manager; + private ListenerInvoker manager; // Allows us to determine the sender - private Map playerLookup; + private PlayerInjectionHandler playerInjection; // Allows us to look up read packet injectors private Map readModifier; @@ -59,12 +60,12 @@ class PacketInjector { // Class loader private ClassLoader classLoader; - public PacketInjector(ClassLoader classLoader, PacketFilterManager manager, - Map playerLookup) throws IllegalAccessException { + public PacketInjector(ClassLoader classLoader, ListenerInvoker manager, + PlayerInjectionHandler playerInjection) throws IllegalAccessException { this.classLoader = classLoader; this.manager = manager; - this.playerLookup = playerLookup; + this.playerInjection = playerInjection; this.readModifier = new ConcurrentHashMap(); initialize(); } @@ -194,7 +195,18 @@ class PacketInjector { // Called from the ReadPacketModified monitor PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { - Player client = playerLookup.get(input); + Player client = playerInjection.getPlayerByConnection(input); + return packetRecieved(packet, client); + } + + /** + * Let the packet listeners process the given packet. + * @param packet - a packet to process. + * @param client - the client that sent the packet. + * @return The resulting packet event. + */ + public PacketEvent packetRecieved(PacketContainer packet, Player client) { + PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client); manager.invokePacketRecieving(event); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/InjectedArrayList.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/InjectedArrayList.java similarity index 93% rename from ProtocolLib/src/com/comphenix/protocol/injector/InjectedArrayList.java rename to ProtocolLib/src/com/comphenix/protocol/injector/player/InjectedArrayList.java index 5273ce57..4562d2c5 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/InjectedArrayList.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/InjectedArrayList.java @@ -1,17 +1,17 @@ -package com.comphenix.protocol.injector; +package com.comphenix.protocol.injector.player; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Set; +import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket; + import net.minecraft.server.Packet; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; -import com.comphenix.protocol.injector.NetworkFieldInjector.FakePacket; - /** * The array list that notifies when packets are sent by the server. * diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/InjectedServerConnection.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/InjectedServerConnection.java similarity index 95% rename from ProtocolLib/src/com/comphenix/protocol/injector/InjectedServerConnection.java rename to ProtocolLib/src/com/comphenix/protocol/injector/player/InjectedServerConnection.java index ea987263..4ed76815 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/InjectedServerConnection.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector; +package com.comphenix.protocol.injector.player; import java.lang.reflect.Field; import java.lang.reflect.Method; diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkFieldInjector.java similarity index 79% rename from ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java rename to ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkFieldInjector.java index 56903e35..35395725 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkFieldInjector.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector; +package com.comphenix.protocol.injector.player; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -7,12 +7,14 @@ import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; @@ -45,13 +47,28 @@ class NetworkFieldInjector extends PlayerInjector { // Sync field private static Field syncField; private Object syncObject; + + // Determine if we're listening + private Set sendingFilters; + + // Used to construct proxy objects + private ClassLoader classLoader; - public NetworkFieldInjector(Player player, PacketFilterManager manager, Set sendingFilters) throws IllegalAccessException { - super(player, manager, sendingFilters); + public NetworkFieldInjector(ClassLoader classLoader, Logger logger, Player player, + ListenerInvoker manager, Set sendingFilters) throws IllegalAccessException { + + super(logger, player, manager); + this.classLoader = classLoader; + this.sendingFilters = sendingFilters; } @Override - protected synchronized void initialize() throws IllegalAccessException { + protected boolean hasListener(int packetID) { + return sendingFilters.contains(packetID); + } + + @Override + public synchronized void initialize() throws IllegalAccessException { super.initialize(); // Get the sync field as well @@ -112,7 +129,7 @@ class NetworkFieldInjector extends PlayerInjector { synchronized(syncObject) { // The list we'll be inserting - List hackedList = new InjectedArrayList(manager.getClassLoader(), this, ignoredPackets); + List hackedList = new InjectedArrayList(classLoader, this, ignoredPackets); // Add every previously stored packet for (Packet packet : minecraftList) { @@ -154,4 +171,9 @@ class NetworkFieldInjector extends PlayerInjector { } overridenLists.clear(); } + + @Override + public boolean canInject() { + return true; + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkObjectInjector.java similarity index 81% rename from ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java rename to ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index 9091390d..6d6bba12 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector; +package com.comphenix.protocol.injector.player; import java.lang.reflect.InvocationTargetException; @@ -8,12 +8,14 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.lang.reflect.Method; import java.util.Set; +import java.util.logging.Logger; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.ListenerInvoker; /** * Injection method that overrides the NetworkHandler itself, and it's sendPacket-method. @@ -21,10 +23,19 @@ import com.comphenix.protocol.events.PacketListener; * @author Kristian */ class NetworkObjectInjector extends PlayerInjector { - public NetworkObjectInjector(Player player, PacketFilterManager manager, Set sendingFilters) throws IllegalAccessException { - super(player, manager, sendingFilters); + // Determine if we're listening + private Set sendingFilters; + + public NetworkObjectInjector(Logger logger, Player player, ListenerInvoker invoker, Set sendingFilters) throws IllegalAccessException { + super(logger, player, invoker); + this.sendingFilters = sendingFilters; } + @Override + protected boolean hasListener(int packetID) { + return sendingFilters.contains(packetID); + } + @Override public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException { Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue(); @@ -107,4 +118,9 @@ class NetworkObjectInjector extends PlayerInjector { // Clean up networkManagerRef.revertValue(); } + + @Override + public boolean canInject() { + return true; + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkServerInjector.java similarity index 81% rename from ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java rename to ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkServerInjector.java index 50f311ad..243c0fc7 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -1,8 +1,9 @@ -package com.comphenix.protocol.injector; +package com.comphenix.protocol.injector.player; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Set; +import java.util.logging.Logger; import net.minecraft.server.Packet; import net.sf.cglib.proxy.Callback; @@ -16,6 +17,7 @@ import net.sf.cglib.proxy.NoOp; import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.ObjectCloner; @@ -32,14 +34,30 @@ public class NetworkServerInjector extends PlayerInjector { private static Method sendPacketMethod; private InjectedServerConnection serverInjection; - public NetworkServerInjector(Player player, PacketFilterManager manager, - Set sendingFilters, InjectedServerConnection serverInjection) throws IllegalAccessException { - super(player, manager, sendingFilters); + // Determine if we're listening + private Set sendingFilters; + + // Used to create proxy objects + private ClassLoader classLoader; + + public NetworkServerInjector( + ClassLoader classLoader, Logger logger, Player player, + ListenerInvoker invoker, Set sendingFilters, + InjectedServerConnection serverInjection) throws IllegalAccessException { + + super(logger, player, invoker); + this.classLoader = classLoader; + this.sendingFilters = sendingFilters; this.serverInjection = serverInjection; } @Override - protected void initialize() throws IllegalAccessException { + protected boolean hasListener(int packetID) { + return sendingFilters.contains(packetID); + } + + @Override + public void initialize() throws IllegalAccessException { super.initialize(); // Get the send packet method! @@ -104,7 +122,7 @@ public class NetworkServerInjector extends PlayerInjector { }; Callback noOpCallback = NoOp.INSTANCE; - ex.setClassLoader(manager.getClassLoader()); + ex.setClassLoader(classLoader); ex.setSuperclass(serverClass); ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback }); ex.setCallbackFilter(new CallbackFilter() { @@ -165,4 +183,9 @@ public class NetworkServerInjector extends PlayerInjector { public void checkListener(PacketListener listener) { // We support everything } + + @Override + public boolean canInject() { + return true; + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkSpoutHook.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkSpoutHook.java new file mode 100644 index 00000000..e71bacef --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/NetworkSpoutHook.java @@ -0,0 +1,60 @@ +package com.comphenix.protocol.injector.player; + +import java.lang.reflect.InvocationTargetException; +import java.util.logging.Logger; + +import net.minecraft.server.Packet; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.ListenerInvoker; + +public class NetworkSpoutHook extends PlayerInjector { + + public NetworkSpoutHook(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException { + super(logger, player, invoker); + } + + @Override + protected boolean hasListener(int packetID) { + return false; + } + + @Override + public boolean canInject() { + return getSpout() != null; + } + + private Plugin getSpout() { + // Spout must be loaded + try { + return Bukkit.getServer().getPluginManager().getPlugin("Spout"); + } catch (Throwable e) { + return null; + } + } + + @Override + public void injectManager() { + + } + + @Override + public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException { + + } + + @Override + public void cleanupAll() { + // TODO Auto-generated method stub + + } + + @Override + public void checkListener(PacketListener listener) { + // We support everything Spout does + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java new file mode 100644 index 00000000..1ceb0725 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -0,0 +1,306 @@ +package com.comphenix.protocol.injector.player; + +import java.io.DataInputStream; +import java.lang.reflect.InvocationTargetException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.minecraft.server.Packet; + +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.ListenerInvoker; +import com.comphenix.protocol.injector.PlayerLoggedOutException; +import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.google.common.collect.ImmutableSet; + +public class PlayerInjectionHandler { + + // Server connection injection + private InjectedServerConnection serverInjection; + + // The last successful player hook + private PlayerInjector lastSuccessfulHook; + + // Player injection + private Map connectionLookup = new ConcurrentHashMap(); + private Map playerInjection = new HashMap(); + + // Player injection type + private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; + + // Error logger + private Logger logger; + + // Whether or not we're closing + private boolean hasClosed; + + // Used to invoke events + private ListenerInvoker invoker; + + // Enabled packet filters + private Set sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap()); + + // The class loader we're using + private ClassLoader classLoader; + + public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, ListenerInvoker invoker, Server server) { + this.classLoader = classLoader; + this.logger = logger; + this.invoker = invoker; + this.serverInjection = new InjectedServerConnection(logger, server); + } + + /** + * Retrieves how the server packets are read. + * @return Injection method for reading server packets. + */ + public PlayerInjectHooks getPlayerHook() { + return playerHook; + } + + /** + * Add an underlying packet handler of the given ID. + * @param packetID - packet ID to register. + */ + public void addPacketHandler(int packetID) { + sendingFilters.add(packetID); + } + + /** + * Remove an underlying packet handler of ths ID. + * @param packetID - packet ID to unregister. + */ + public void removePacketHandler(int packetID) { + sendingFilters.remove(packetID); + } + + /** + * Sets how the server packets are read. + * @param playerHook - the new injection method for reading server packets. + */ + public void setPlayerHook(PlayerInjectHooks playerHook) { + this.playerHook = playerHook; + } + + /** + * Used to construct a player hook. + * @param player - the player to hook. + * @param hook - the hook type. + * @return A new player hoook + * @throws IllegalAccessException Unable to do our reflection magic. + */ + private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException { + // Construct the correct player hook + switch (hook) { + case NETWORK_HANDLER_FIELDS: + return new NetworkFieldInjector(classLoader, logger, player, invoker, sendingFilters); + case NETWORK_MANAGER_OBJECT: + return new NetworkObjectInjector(logger, player, invoker, sendingFilters); + case NETWORK_SERVER_OBJECT: + return new NetworkServerInjector(classLoader, logger, player, invoker, sendingFilters, serverInjection); + default: + throw new IllegalArgumentException("Cannot construct a player injector."); + } + } + + public Player getPlayerByConnection(DataInputStream inputStream) { + return connectionLookup.get(inputStream); + } + + /** + * Initialize a player hook, allowing us to read server packets. + * @param manager - the main packet filter manager. + * @param player - player to hook. + */ + public void injectPlayer(Player player) { + + PlayerInjector injector = null; + PlayerInjectHooks currentHook = playerHook; + boolean firstPlayer = lastSuccessfulHook == null; + + // Don't inject if the class has closed + if (!hasClosed && player != null && !playerInjection.containsKey(player)) { + while (true) { + try { + injector = getHookInstance(player, currentHook); + injector.initialize(); + injector.injectManager(); + + DataInputStream inputStream = injector.getInputStream(false); + + if (!player.isOnline() || inputStream == null) { + throw new PlayerLoggedOutException(); + } + + playerInjection.put(player, injector); + connectionLookup.put(inputStream, player); + break; + + + } catch (PlayerLoggedOutException e) { + throw e; + + } catch (Exception e) { + + // Mark this injection attempt as a failure + logger.log(Level.SEVERE, "Player hook " + currentHook.toString() + " failed.", e); + + // Clean up as much as possible + try { + if (injector != null) + injector.cleanupAll(); + } catch (Exception e2) { + logger.log(Level.WARNING, "Cleaing up after player hook failed.", e); + } + + if (currentHook.ordinal() > 0) { + + // Choose the previous player hook type + currentHook = PlayerInjectHooks.values()[currentHook.ordinal() - 1]; + logger.log(Level.INFO, "Switching to " + currentHook.toString() + " instead."); + } else { + // UTTER FAILURE + playerInjection.put(player, null); + return; + } + } + } + + // Update values + if (injector != null) + lastSuccessfulHook = injector; + if (currentHook != playerHook || firstPlayer) + setPlayerHook(currentHook); + } + } + + /** + * Unregisters the given player. + * @param player - player to unregister. + */ + public void uninjectPlayer(Player player) { + if (!hasClosed && player != null) { + + PlayerInjector injector = playerInjection.get(player); + + if (injector != null) { + DataInputStream input = injector.getInputStream(true); + injector.cleanupAll(); + + playerInjection.remove(player); + connectionLookup.remove(input); + } + } + } + + public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { + getInjector(reciever).sendServerPacket(packet.getHandle(), filters); + } + + private PlayerInjector getInjector(Player player) { + if (!playerInjection.containsKey(player)) { + // What? Try to inject again. + injectPlayer(player); + } + + PlayerInjector injector = playerInjection.get(player); + + // Check that the injector was sucessfully added + if (injector != null) + return injector; + else + throw new IllegalArgumentException("Player has no injected handler."); + } + + /** + * Determine if the given listeners are valid. + * @param listeners - listeners to check. + */ + public void checkListener(Set listeners) { + // Make sure the current listeners are compatible + if (lastSuccessfulHook != null) { + for (PacketListener listener : listeners) { + try { + checkListener(listener); + } catch (IllegalStateException e) { + logger.log(Level.WARNING, "Unsupported listener.", e); + } + } + } + } + + /** + * Determine if a listener is valid or not. + * @param listener - listener to check. + * @throws IllegalStateException If the given listener's whitelist cannot be fulfilled. + */ + public void checkListener(PacketListener listener) { + try { + if (lastSuccessfulHook != null) + lastSuccessfulHook.checkListener(listener); + } catch (Exception e) { + throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e); + } + } + + /** + * Process a packet as if it were sent by the given player. + * @param player - the sender. + * @param mcPacket - the packet to process. + * @throws IllegalAccessException If the reflection machinery failed. + * @throws InvocationTargetException If the underlying method caused an error. + */ + public void processPacket(Player player, Packet mcPacket) throws IllegalAccessException, InvocationTargetException { + + PlayerInjector injector = getInjector(player); + injector.processPacket(mcPacket); + } + + /** + * Retrieve the current list of registered sending listeners. + * @return List of the sending listeners's packet IDs. + */ + public Set getSendingFilters() { + return ImmutableSet.copyOf(sendingFilters); + } + + /** + * Retrieve the current logger. + * @return Error logger. + */ + public Logger getLogger() { + return logger; + } + + public void close() { + + // Guard + if (hasClosed || playerInjection == null) + return; + + // Remove everything + for (PlayerInjector injection : playerInjection.values()) { + if (injection != null) { + injection.cleanupAll(); + } + } + + // Remove server handler + serverInjection.cleanupAll(); + hasClosed = true; + + playerInjection.clear(); + connectionLookup.clear(); + invoker = null; + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/PlayerInjector.java similarity index 84% rename from ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java rename to ProtocolLib/src/com/comphenix/protocol/injector/player/PlayerInjector.java index 7d88e25f..9a1779ec 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -15,14 +15,14 @@ * 02111-1307 USA */ -package com.comphenix.protocol.injector; +package com.comphenix.protocol.injector.player; import java.io.DataInputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Set; import java.util.logging.Level; +import java.util.logging.Logger; import net.minecraft.server.EntityPlayer; import net.minecraft.server.Packet; @@ -34,6 +34,7 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; @@ -69,17 +70,18 @@ abstract class PlayerInjector { protected Object netHandler; // The packet manager and filters - protected PacketFilterManager manager; - protected Set sendingFilters; + protected ListenerInvoker invoker; // Previous data input protected DataInputStream cachedInput; + + // Handle errors + protected Logger logger; - public PlayerInjector(Player player, PacketFilterManager manager, Set sendingFilters) throws IllegalAccessException { + public PlayerInjector(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException { + this.logger = logger; this.player = player; - this.manager = manager; - this.sendingFilters = sendingFilters; - initialize(); + this.invoker = invoker; } /** @@ -91,7 +93,11 @@ abstract class PlayerInjector { return craft.getHandle(); } - protected void initialize() throws IllegalAccessException { + /** + * Initialize all fields for this player injector, if it hasn't already. + * @throws IllegalAccessException An error has occured. + */ + public void initialize() throws IllegalAccessException { EntityPlayer notchEntity = getEntityPlayer(); @@ -156,12 +162,12 @@ abstract class PlayerInjector { return reflection.getFieldByType(".*NetServerHandler"); } catch (RuntimeException e) { - manager.getLogger().log(Level.WARNING, "Server handler is a proxy type.", e); + logger.log(Level.WARNING, "Server handler is a proxy type.", e); } } } catch (IllegalAccessException e) { - manager.getLogger().warning("Unable to load server handler from proxy type."); + logger.warning("Unable to load server handler from proxy type."); } // Nope, just go with it @@ -250,6 +256,12 @@ abstract class PlayerInjector { */ public abstract void cleanupAll(); + /** + * Determine if this inject method can even be attempted. + * @return TRUE if can be attempted, though possibly with failure, FALSE otherwise. + */ + public abstract boolean canInject(); + /** * Invoked before a new listener is registered. *

@@ -263,16 +275,16 @@ abstract class PlayerInjector { * @param packet - packet to recieve. * @return The given packet, or the packet replaced by the listeners. */ - Packet handlePacketRecieved(Packet packet) { + public Packet handlePacketRecieved(Packet packet) { // Get the packet ID too - Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass()); + Integer id = invoker.getPacketID(packet); // Make sure we're listening - if (id != null && sendingFilters.contains(id)) { + if (id != null && hasListener(id)) { // A packet has been sent guys! PacketContainer container = new PacketContainer(id, packet); - PacketEvent event = PacketEvent.fromServer(manager, container, player); - manager.invokePacketSending(event); + PacketEvent event = PacketEvent.fromServer(invoker, container, player); + invoker.invokePacketSending(event); // Cancelling is pretty simple. Just ignore the packet. if (event.isCancelled()) @@ -285,6 +297,13 @@ abstract class PlayerInjector { return packet; } + /** + * Determine if the given injector is listening for this packet ID. + * @param packetID - packet ID to check. + * @return TRUE if it is, FALSE oterhwise. + */ + protected abstract boolean hasListener(int packetID); + /** * Retrieve the current player's input stream. * @param cache - whether or not to cache the result of this method. diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java b/ProtocolLib/src/com/comphenix/protocol/injector/player/ReplacedArrayList.java similarity index 94% rename from ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java rename to ProtocolLib/src/com/comphenix/protocol/injector/player/ReplacedArrayList.java index 2923785c..ed239f3c 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/player/ReplacedArrayList.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector; +package com.comphenix.protocol.injector.player; import java.util.Collection; import java.util.List;