From 476a9187949d669192a0ab1e8da1151a4b85b009 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 16 Oct 2012 22:24:30 +0200 Subject: [PATCH] Dynamically add or remove injected hooks depending on the listeners. This occurs whenever a listener is added or removed. A listener can now specify whether or not it's listening for packets sent BEFORE a player has logged in (every packet upto Packet1Login and a few more), or AFTER. By default, listeners only receive notifcation of packets sent and received after. ProtocolLib will now only hook NetLoginHandler if there's a login listener, and vice versa. Thus, the new login feature will only tax the server if another plugin is using it. In addition, ProtocolLib will not consume any resources when it's not serving any listeners. --- .../comphenix/protocol/ProtocolLibrary.java | 56 ++++++---- .../protocol/async/NullPacketListener.java | 2 +- .../protocol/events/ListeningWhitelist.java | 36 +++++- .../protocol/events/MonitorAdapter.java | 20 +--- .../protocol/events/PacketAdapter.java | 46 +++++++- .../protocol/injector/GamePhase.java | 39 +++++++ .../injector/PacketFilterManager.java | 105 +++++++++++++++++- .../protocol/injector/PacketInjector.java | 8 +- .../protocol/injector/ReadPacketModifier.java | 3 +- .../injector/player/NetLoginInjector.java | 19 +++- .../injector/player/NetworkFieldInjector.java | 2 +- .../player/NetworkObjectInjector.java | 2 +- .../player/NetworkServerInjector.java | 4 +- .../player/PlayerInjectionHandler.java | 44 +++++--- .../injector/player/PlayerInjector.java | 2 +- 15 files changed, 317 insertions(+), 71 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/GamePhase.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 1a83647b..5f6466b6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -58,6 +58,9 @@ public class ProtocolLibrary extends JavaPlugin { private int tickCounter = 0; private static final int ASYNC_PACKET_DELAY = 1; + // Used for debugging + private boolean debugListener; + @Override public void onLoad() { logger = getLoggerSafely(); @@ -80,14 +83,10 @@ public class ProtocolLibrary extends JavaPlugin { // Player login and logout events protocolManager.registerEvents(manager, this); - - // Inject our hook into already existing players - protocolManager.initializePlayers(server.getOnlinePlayers()); - + // Worker that ensures that async packets are eventually sent createAsyncTask(server); - - addDebugListener(); + //toggleDebugListener(); // Try to enable statistics try { @@ -98,19 +97,38 @@ public class ProtocolLibrary extends JavaPlugin { logger.log(Level.SEVERE, "Metrics cannot be enabled. Incompatible Bukkit version.", e); } } - - private void addDebugListener() { - // DEBUG DEBUG - protocolManager.addPacketListener(new MonitorAdapter(this, ConnectionSide.BOTH, logger) { - @Override - public void onPacketReceiving(PacketEvent event) { - System.out.println("RECEIVING " + event.getPacketID() + " from " + event.getPlayer().getName()); - }; - @Override - public void onPacketSending(PacketEvent event) { - System.out.println("SENDING " + event.getPacketID() + " to " + event.getPlayer().getName()); - } - }); + + /** + * Toggle a listener that prints every sent and received packet. + */ + void toggleDebugListener() { + + if (debugListener) { + protocolManager.removePacketListeners(this); + } else { + // DEBUG DEBUG + protocolManager.addPacketListener(new MonitorAdapter(this, ConnectionSide.BOTH, logger) { + @Override + public void onPacketReceiving(PacketEvent event) { + Object handle = event.getPacket().getHandle(); + + System.out.println(String.format( + "RECEIVING %s@%s from %s.", + handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName() + )); + }; + @Override + public void onPacketSending(PacketEvent event) { + Object handle = event.getPacket().getHandle(); + + System.out.println(String.format( + "SENDING %s@%s from %s.", + handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName() + )); + } + }); + } + debugListener = !debugListener; } private void createAsyncTask(Server server) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/NullPacketListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/NullPacketListener.java index a5077dbf..418d85cc 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/NullPacketListener.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/NullPacketListener.java @@ -67,7 +67,7 @@ class NullPacketListener implements PacketListener { private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) { if (whitelist != null) - return new ListeningWhitelist(priority, whitelist.getWhitelist()); + return new ListeningWhitelist(priority, whitelist.getWhitelist(), whitelist.getGamePhase()); else return null; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java index 8f8859b0..979c8ec6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java @@ -19,6 +19,7 @@ package com.comphenix.protocol.events; import java.util.Set; +import com.comphenix.protocol.injector.GamePhase; import com.google.common.base.Objects; import com.google.common.collect.Sets; @@ -36,6 +37,7 @@ public class ListeningWhitelist { private ListenerPriority priority; private Set whitelist; + private GamePhase gamePhase; /** * Creates a packet whitelist for a given priority with a set of packet IDs. @@ -43,8 +45,19 @@ public class ListeningWhitelist { * @param whitelist - set of IDs to observe/enable. */ public ListeningWhitelist(ListenerPriority priority, Set whitelist) { + this(priority, whitelist, GamePhase.PLAYING); + } + + /** + * Creates a packet whitelist for a given priority with a set of packet IDs. + * @param priority - the listener priority. + * @param whitelist - set of IDs to observe/enable. + * @param gamePhase - which game phase to receieve notifications on. + */ + public ListeningWhitelist(ListenerPriority priority, Set whitelist, GamePhase gamePhase) { this.priority = priority; this.whitelist = whitelist; + this.gamePhase = gamePhase; } /** @@ -55,6 +68,19 @@ public class ListeningWhitelist { public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) { this.priority = priority; this.whitelist = Sets.newHashSet(whitelist); + this.gamePhase = GamePhase.PLAYING; + } + + /** + * Creates a packet whitelist for a given priority with a set of packet IDs. + * @param priority - the listener priority. + * @param whitelist - list of packet IDs to observe/enable. + * @param gamePhase - which game phase to receieve notifications on. + */ + public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) { + this.priority = priority; + this.whitelist = Sets.newHashSet(whitelist); + this.gamePhase = gamePhase; } /** @@ -80,10 +106,18 @@ public class ListeningWhitelist { public Set getWhitelist() { return whitelist; } + + /** + * Retrieve which game phase this listener is active under. + * @return The active game phase. + */ + public GamePhase getGamePhase() { + return gamePhase; + } @Override public int hashCode(){ - return Objects.hashCode(priority, whitelist); + return Objects.hashCode(priority, whitelist, gamePhase); } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/MonitorAdapter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/MonitorAdapter.java index 9036971a..832c2d8f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/MonitorAdapter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/MonitorAdapter.java @@ -9,8 +9,8 @@ import com.comphenix.protocol.Packets; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.ListeningWhitelist; -import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.reflect.FieldAccessException; /** @@ -38,14 +38,14 @@ public abstract class MonitorAdapter implements PacketListener { // Recover in case something goes wrong try { if (side.isForServer()) - this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getSupported()); + this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getSupported(), GamePhase.BOTH); if (side.isForClient()) - this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getSupported()); + this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getSupported(), GamePhase.BOTH); } catch (FieldAccessException e) { if (side.isForServer()) - this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values()); + this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values(), GamePhase.BOTH); if (side.isForClient()) - this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values()); + this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values(), GamePhase.BOTH); logger.log(Level.WARNING, "Defaulting to 1.3 packets.", e); } } @@ -63,16 +63,6 @@ public abstract class MonitorAdapter implements PacketListener { } } - @Override - public void onPacketReceiving(PacketEvent event) { - // Empty for now - } - - @Override - public void onPacketSending(PacketEvent event) { - // Empty for now - } - @Override public ListeningWhitelist getSendingWhitelist() { return sending; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java index c49d74ec..270fa53e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java @@ -21,9 +21,12 @@ import java.util.Set; import org.bukkit.plugin.Plugin; +import com.comphenix.protocol.injector.GamePhase; + /** * Represents a packet listener with useful constructors. - * + *

+ * Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide. * @author Kristian */ public abstract class PacketAdapter implements PacketListener { @@ -51,7 +54,20 @@ public abstract class PacketAdapter implements PacketListener { * @param packets - the packet IDs the listener is looking for. */ public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set packets) { - this(plugin, connectionSide, listenerPriority, packets.toArray(new Integer[0])); + this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets.toArray(new Integer[0])); + } + + /** + * Initialize a packet listener for a single connection side. + *

+ * The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary. + * @param plugin - the plugin that spawned this listener. + * @param connectionSide - the packet type the listener is looking for. + * @param listenerPriority - the event priority. + * @param packets - the packet IDs the listener is looking for. + */ + public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Set packets) { + this(plugin, connectionSide, listenerPriority, gamePhase, packets.toArray(new Integer[0])); } /** @@ -62,20 +78,36 @@ public abstract class PacketAdapter implements PacketListener { * @param packets - the packet IDs the listener is looking for. */ public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) { + this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets); + } + + /** + * Initialize a packet listener for a single connection side. + *

+ * The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary. + * @param plugin - the plugin that spawned this listener. + * @param connectionSide - the packet type the listener is looking for. + * @param listenerPriority - the event priority. + * @param gamePhase - which game phase this listener is active under. + * @param packets - the packet IDs the listener is looking for. + */ + public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) { if (plugin == null) throw new IllegalArgumentException("plugin cannot be null"); if (connectionSide == null) throw new IllegalArgumentException("connectionSide cannot be null"); if (listenerPriority == null) throw new IllegalArgumentException("listenerPriority cannot be null"); + if (gamePhase == null) + throw new IllegalArgumentException("gamePhase cannot be NULL"); if (packets == null) throw new IllegalArgumentException("packets cannot be null"); // Add whitelists if (connectionSide.isForServer()) - sendingWhitelist = new ListeningWhitelist(listenerPriority, packets); + sendingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase); if (connectionSide.isForClient()) - receivingWhitelist = new ListeningWhitelist(listenerPriority, packets); + receivingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase); this.plugin = plugin; this.connectionSide = connectionSide; @@ -83,12 +115,14 @@ public abstract class PacketAdapter implements PacketListener { @Override public void onPacketReceiving(PacketEvent event) { - // Default is to do nothing + // Lets prevent some bugs + throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!"); } @Override public void onPacketSending(PacketEvent event) { - // And here too + // Lets prevent some bugs + throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!"); } @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/GamePhase.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/GamePhase.java new file mode 100644 index 00000000..07c7e7a5 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/GamePhase.java @@ -0,0 +1,39 @@ +package com.comphenix.protocol.injector; + +/** + * The current player phase. This is used to limit the number of different injections. + * + * @author Kristian + */ +public enum GamePhase { + /** + * Only listen for packets sent or received before a player has logged in. + */ + LOGIN, + + /** + * Only listen for packets sent or received after a player has logged in. + */ + PLAYING, + + /** + * Listen for every sent and received packet. + */ + BOTH; + + /** + * Determine if the current value represents the login phase. + * @return TRUE if it does, FALSE otherwise. + */ + public boolean hasLogin() { + return this == LOGIN || this == BOTH; + } + + /** + * Determine if the current value represents the playing phase. + * @return TRUE if it does, FALSE otherwise. + */ + public boolean hasPlaying() { + return this == PLAYING || this == BOTH; + } +} 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 0f9de6c1..2ce8ab6a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -23,9 +23,12 @@ import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nullable; + import net.minecraft.server.Packet; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; @@ -52,6 +55,7 @@ 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.base.Predicate; import com.google.common.collect.ImmutableSet; public final class PacketFilterManager implements ProtocolManager, ListenerInvoker { @@ -109,6 +113,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Error logger private Logger logger; + // The current server + private Server server; + // The async packet handler private AsyncFilterManager asyncFilterManager; @@ -116,6 +123,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok private Set serverPackets; private Set clientPackets; + // Ensure that we're not performing too may injections + private AtomicInteger phaseLoginCount = new AtomicInteger(0); + private AtomicInteger phasePlayingCount = new AtomicInteger(0); /** * Only create instances of this class if protocol lib is disabled. @@ -126,11 +136,26 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (classLoader == null) throw new IllegalArgumentException("classLoader cannot be NULL."); + // Used to determine if injection is needed + Predicate isInjectionNecessary = new Predicate() { + @Override + public boolean apply(@Nullable GamePhase phase) { + boolean result = true; + + if (phase.hasLogin()) + result &= getPhaseLoginCount() > 0; + if (phase.hasPlaying()) + result &= getPhasePlayingCount() > 0; + return result; + } + }; + try { // Initialize values + this.server = server; this.classLoader = classLoader; this.logger = logger; - this.playerInjection = new PlayerInjectionHandler(classLoader, logger, this, server); + this.playerInjection = new PlayerInjectionHandler(classLoader, logger, isInjectionNecessary, this, server); this.packetInjector = new PacketInjector(classLoader, this, playerInjection); this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this); @@ -210,11 +235,51 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); } + // Increment phases too + if (hasSending) + incrementPhases(sending.getGamePhase()); + if (hasReceiving) + incrementPhases(receiving.getGamePhase()); + // Inform our injected hooks packetListeners.add(listener); } } + /** + * Invoked to handle the different game phases of a added listener. + * @param phase - listener's game game phase. + */ + private void incrementPhases(GamePhase phase) { + if (phase.hasLogin()) + phaseLoginCount.incrementAndGet(); + + // We may have to inject into every current player + if (phase.hasPlaying()) { + if (phasePlayingCount.incrementAndGet() == 1) { + // Inject our hook into already existing players + initializePlayers(server.getOnlinePlayers()); + } + } + } + + /** + * Invoked to handle the different game phases of a removed listener. + * @param phase - listener's game game phase. + */ + private void decrementPhases(GamePhase phase) { + if (phase.hasLogin()) + phaseLoginCount.decrementAndGet(); + + // We may have to inject into every current player + if (phase.hasPlaying()) { + if (phasePlayingCount.decrementAndGet() == 0) { + // Inject our hook into already existing players + uninitializePlayers(server.getOnlinePlayers()); + } + } + } + /** * Determine if the packet IDs in a whitelist is valid. * @param listener - the listener that will be mentioned in the error. @@ -246,11 +311,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (!packetListeners.remove(listener)) return; - // Add listeners - if (sending != null && sending.isEnabled()) + // Remove listeners and phases + if (sending != null && sending.isEnabled()) { sendingRemoved = sendingListeners.removeListener(listener, sending); - if (receiving != null && receiving.isEnabled()) + decrementPhases(sending.getGamePhase()); + } + if (receiving != null && receiving.isEnabled()) { receivingRemoved = recievedListeners.removeListener(listener, receiving); + decrementPhases(receiving.getGamePhase()); + } // Remove hooks, if needed if (sendingRemoved != null && sendingRemoved.size() > 0) @@ -470,7 +539,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok for (Player player : players) playerInjection.injectPlayer(player); } - + + /** + * Uninitialize the packet injection of every player. + * @param players - list of players to uninject. + */ + public void uninitializePlayers(Player[] players) { + for (Player player : players) + playerInjection.uninjectPlayer(player); + } + /** * Register this protocol manager on Bukkit. * @param manager - Bukkit plugin manager that provides player join/leave events. @@ -488,6 +566,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerJoin(PlayerJoinEvent event) { + // This call will be ignored if no listeners are registered playerInjection.injectPlayer(event.getPlayer()); } @@ -512,6 +591,22 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok } } + /** + * Retrieve the number of listeners that expect packets during playing. + * @return Number of listeners. + */ + private int getPhasePlayingCount() { + return phasePlayingCount.get(); + } + + /** + * Retrieve the number of listeners that expect packets during login. + * @return Number of listeners + */ + private int getPhaseLoginCount() { + return phaseLoginCount.get(); + } + @Override public int getPacketID(Packet packet) { if (packet == null) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java index 84b0d30a..1361d64c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java @@ -198,9 +198,13 @@ class PacketInjector { // Called from the ReadPacketModified monitor PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { - Player client = playerInjection.getPlayerByConnection(input); - return packetRecieved(packet, client); + + // Never invoke a event if we don't know where it's from + if (client != null) + return packetRecieved(packet, client); + else + return null; } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java index ff3e5d51..54a75b54 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java @@ -102,10 +102,11 @@ class ReadPacketModifier implements MethodInterceptor { // Let the people know PacketContainer container = new PacketContainer(packetID, (Packet) thisObj); PacketEvent event = packetInjector.packetRecieved(container, input); - Packet result = event.getPacket().getHandle(); // Handle override if (event != null) { + Packet result = event.getPacket().getHandle(); + if (event.isCancelled()) { override.put(thisObj, null); } else if (!objectEquals(thisObj, result)) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index 8a9c221d..7f753fbb 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -4,11 +4,13 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; import org.bukkit.Server; import org.bukkit.entity.Player; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler.GamePhase; +import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; import com.google.common.collect.Maps; @@ -25,12 +27,16 @@ class NetLoginInjector { private PlayerInjectionHandler injectionHandler; private Server server; + // The current logger + private Logger logger; + private ReadWriteLock injectionLock = new ReentrantReadWriteLock(); // Used to create fake players private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); - public NetLoginInjector(PlayerInjectionHandler injectionHandler, Server server) { + public NetLoginInjector(Logger logger, PlayerInjectionHandler injectionHandler, Server server) { + this.logger = logger; this.injectionHandler = injectionHandler; this.server = server; } @@ -45,6 +51,10 @@ class NetLoginInjector { injectionLock.writeLock().lock(); try { + // Make sure we actually need to inject during this phase + if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) + return inserting; + Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server); PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN); injector.updateOnLogin = true; @@ -56,6 +66,11 @@ class NetLoginInjector { // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler return inserting; + } catch (Throwable e) { + // Minecraft can't handle this, so we'll deal with it here + logger.log(Level.WARNING, "Unable to hook NetLoginHandler.", e); + return inserting; + } finally { injectionLock.writeLock().unlock(); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java index 13f3edcc..c3c4f26d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java @@ -31,9 +31,9 @@ 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.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler.GamePhase; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; 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 d8a86078..e5140ab4 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 @@ -36,9 +36,9 @@ 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.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler.GamePhase; /** * Injection method that overrides the NetworkHandler itself, and it's sendPacket-method. 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 82513c00..01ea2fc9 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 @@ -34,9 +34,9 @@ import net.sf.cglib.proxy.NoOp; import org.bukkit.entity.Player; 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.PlayerInjectionHandler.GamePhase; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.ObjectCloner; @@ -264,7 +264,7 @@ public class NetworkServerInjector extends PlayerInjector { @Override public boolean canInject(GamePhase phase) { // Doesn't work when logging in - return phase == GamePhase.PLAYING || phase == GamePhase.CLOSING; + return phase == GamePhase.PLAYING; } @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index d08e39b1..707bef86 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -38,9 +38,11 @@ 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.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; @@ -51,16 +53,6 @@ import com.google.common.collect.Maps; */ public class PlayerInjectionHandler { - /** - * The current player phase. - * @author Kristian - */ - enum GamePhase { - LOGIN, - PLAYING, - CLOSING, - } - // Server connection injection private InjectedServerConnection serverInjection; @@ -93,11 +85,17 @@ public class PlayerInjectionHandler { // The class loader we're using private ClassLoader classLoader; - public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, ListenerInvoker invoker, Server server) { + // Used to filter injection attempts + private Predicate injectionFilter; + + public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, Predicate injectionFilter, + ListenerInvoker invoker, Server server) { + this.classLoader = classLoader; this.logger = logger; this.invoker = invoker; - this.netLoginInjector = new NetLoginInjector(this, server); + this.injectionFilter = injectionFilter; + this.netLoginInjector = new NetLoginInjector(logger, this, server); this.serverInjection = new InjectedServerConnection(logger, server, netLoginInjector); serverInjection.injectList(); } @@ -185,26 +183,44 @@ public class PlayerInjectionHandler { /** * Initialize a player hook, allowing us to read server packets. + *

+ * This call will be ignored if there's no listener that can receive the given events. * @param player - player to hook. */ public void injectPlayer(Player player) { // Inject using the player instance itself - injectPlayer(player, player, GamePhase.PLAYING); + if (isInjectionNecessary(GamePhase.PLAYING)) { + injectPlayer(player, player, GamePhase.PLAYING); + } + } + + /** + * Determine if it's truly necessary to perform the given player injection. + * @param phase - current game phase. + * @return TRUE if we should perform the injection, FALSE otherwise. + */ + public boolean isInjectionNecessary(GamePhase phase) { + return injectionFilter.apply(phase); } /** * Initialize a player hook, allowing us to read server packets. + *

+ * This method will always perform the instructed injection. + * * @param player - player to hook. * @param injectionPoint - the object to use during the injection process. * @param phase - the current game phase. * @return The resulting player injector, or NULL if the injection failed. */ PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) { - + PlayerInjector injector = playerInjection.get(player); PlayerInjectHooks tempHook = playerHook; PlayerInjectHooks permanentHook = tempHook; + // The given player object may be fake, so be careful! + // See if we need to inject something else boolean invalidInjector = injector != null ? !injector.canInject(phase) : true; 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 51dc949e..3e2457e9 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 @@ -38,9 +38,9 @@ import com.comphenix.protocol.Packets; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; 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.PlayerInjectionHandler.GamePhase; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier;