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.
This commit is contained in:
Kristian S. Stangeland 2012-10-16 22:24:30 +02:00
parent ecdc9b4b6c
commit 476a918794
15 changed files with 317 additions and 71 deletions

View File

@ -58,6 +58,9 @@ public class ProtocolLibrary extends JavaPlugin {
private int tickCounter = 0; private int tickCounter = 0;
private static final int ASYNC_PACKET_DELAY = 1; private static final int ASYNC_PACKET_DELAY = 1;
// Used for debugging
private boolean debugListener;
@Override @Override
public void onLoad() { public void onLoad() {
logger = getLoggerSafely(); logger = getLoggerSafely();
@ -81,13 +84,9 @@ public class ProtocolLibrary extends JavaPlugin {
// Player login and logout events // Player login and logout events
protocolManager.registerEvents(manager, this); protocolManager.registerEvents(manager, this);
// Inject our hook into already existing players
protocolManager.initializePlayers(server.getOnlinePlayers());
// Worker that ensures that async packets are eventually sent // Worker that ensures that async packets are eventually sent
createAsyncTask(server); createAsyncTask(server);
//toggleDebugListener();
addDebugListener();
// Try to enable statistics // Try to enable statistics
try { try {
@ -99,19 +98,38 @@ public class ProtocolLibrary extends JavaPlugin {
} }
} }
private void addDebugListener() { /**
* Toggle a listener that prints every sent and received packet.
*/
void toggleDebugListener() {
if (debugListener) {
protocolManager.removePacketListeners(this);
} else {
// DEBUG DEBUG // DEBUG DEBUG
protocolManager.addPacketListener(new MonitorAdapter(this, ConnectionSide.BOTH, logger) { protocolManager.addPacketListener(new MonitorAdapter(this, ConnectionSide.BOTH, logger) {
@Override @Override
public void onPacketReceiving(PacketEvent event) { public void onPacketReceiving(PacketEvent event) {
System.out.println("RECEIVING " + event.getPacketID() + " from " + event.getPlayer().getName()); Object handle = event.getPacket().getHandle();
System.out.println(String.format(
"RECEIVING %s@%s from %s.",
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
));
}; };
@Override @Override
public void onPacketSending(PacketEvent event) { public void onPacketSending(PacketEvent event) {
System.out.println("SENDING " + event.getPacketID() + " to " + event.getPlayer().getName()); 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) { private void createAsyncTask(Server server) {
try { try {

View File

@ -67,7 +67,7 @@ class NullPacketListener implements PacketListener {
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) { private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
if (whitelist != null) if (whitelist != null)
return new ListeningWhitelist(priority, whitelist.getWhitelist()); return new ListeningWhitelist(priority, whitelist.getWhitelist(), whitelist.getGamePhase());
else else
return null; return null;
} }

View File

@ -19,6 +19,7 @@ package com.comphenix.protocol.events;
import java.util.Set; import java.util.Set;
import com.comphenix.protocol.injector.GamePhase;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@ -36,6 +37,7 @@ public class ListeningWhitelist {
private ListenerPriority priority; private ListenerPriority priority;
private Set<Integer> whitelist; private Set<Integer> whitelist;
private GamePhase gamePhase;
/** /**
* Creates a packet whitelist for a given priority with a set of packet IDs. * 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. * @param whitelist - set of IDs to observe/enable.
*/ */
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) { public ListeningWhitelist(ListenerPriority priority, Set<Integer> 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<Integer> whitelist, GamePhase gamePhase) {
this.priority = priority; this.priority = priority;
this.whitelist = whitelist; this.whitelist = whitelist;
this.gamePhase = gamePhase;
} }
/** /**
@ -55,6 +68,19 @@ public class ListeningWhitelist {
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) { public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
this.priority = priority; this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist); 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;
} }
/** /**
@ -81,9 +107,17 @@ public class ListeningWhitelist {
return whitelist; return whitelist;
} }
/**
* Retrieve which game phase this listener is active under.
* @return The active game phase.
*/
public GamePhase getGamePhase() {
return gamePhase;
}
@Override @Override
public int hashCode(){ public int hashCode(){
return Objects.hashCode(priority, whitelist); return Objects.hashCode(priority, whitelist, gamePhase);
} }
/** /**

View File

@ -9,8 +9,8 @@ import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
/** /**
@ -38,14 +38,14 @@ public abstract class MonitorAdapter implements PacketListener {
// Recover in case something goes wrong // Recover in case something goes wrong
try { try {
if (side.isForServer()) 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()) 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) { } catch (FieldAccessException e) {
if (side.isForServer()) 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()) 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); 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 @Override
public ListeningWhitelist getSendingWhitelist() { public ListeningWhitelist getSendingWhitelist() {
return sending; return sending;

View File

@ -21,9 +21,12 @@ import java.util.Set;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.GamePhase;
/** /**
* Represents a packet listener with useful constructors. * Represents a packet listener with useful constructors.
* * <p>
* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
* @author Kristian * @author Kristian
*/ */
public abstract class PacketAdapter implements PacketListener { 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. * @param packets - the packet IDs the listener is looking for.
*/ */
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> packets) { public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> 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.
* <p>
* 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<Integer> 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. * @param packets - the packet IDs the listener is looking for.
*/ */
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) { 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.
* <p>
* 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) if (plugin == null)
throw new IllegalArgumentException("plugin cannot be null"); throw new IllegalArgumentException("plugin cannot be null");
if (connectionSide == null) if (connectionSide == null)
throw new IllegalArgumentException("connectionSide cannot be null"); throw new IllegalArgumentException("connectionSide cannot be null");
if (listenerPriority == null) if (listenerPriority == null)
throw new IllegalArgumentException("listenerPriority cannot be null"); throw new IllegalArgumentException("listenerPriority cannot be null");
if (gamePhase == null)
throw new IllegalArgumentException("gamePhase cannot be NULL");
if (packets == null) if (packets == null)
throw new IllegalArgumentException("packets cannot be null"); throw new IllegalArgumentException("packets cannot be null");
// Add whitelists // Add whitelists
if (connectionSide.isForServer()) if (connectionSide.isForServer())
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets); sendingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
if (connectionSide.isForClient()) if (connectionSide.isForClient())
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets); receivingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
this.plugin = plugin; this.plugin = plugin;
this.connectionSide = connectionSide; this.connectionSide = connectionSide;
@ -83,12 +115,14 @@ public abstract class PacketAdapter implements PacketListener {
@Override @Override
public void onPacketReceiving(PacketEvent event) { 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 @Override
public void onPacketSending(PacketEvent event) { public void onPacketSending(PacketEvent event) {
// And here too // Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
} }
@Override @Override

View File

@ -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;
}
}

View File

@ -23,9 +23,12 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import net.minecraft.server.Packet; import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor; 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.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker { public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
@ -109,6 +113,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Error logger // Error logger
private Logger logger; private Logger logger;
// The current server
private Server server;
// The async packet handler // The async packet handler
private AsyncFilterManager asyncFilterManager; private AsyncFilterManager asyncFilterManager;
@ -116,6 +123,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
private Set<Integer> serverPackets; private Set<Integer> serverPackets;
private Set<Integer> clientPackets; private Set<Integer> 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. * 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) if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL."); throw new IllegalArgumentException("classLoader cannot be NULL.");
// Used to determine if injection is needed
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
@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 { try {
// Initialize values // Initialize values
this.server = server;
this.classLoader = classLoader; this.classLoader = classLoader;
this.logger = logger; 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.packetInjector = new PacketInjector(classLoader, this, playerInjection);
this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this); 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()); enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
} }
// Increment phases too
if (hasSending)
incrementPhases(sending.getGamePhase());
if (hasReceiving)
incrementPhases(receiving.getGamePhase());
// Inform our injected hooks // Inform our injected hooks
packetListeners.add(listener); 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. * Determine if the packet IDs in a whitelist is valid.
* @param listener - the listener that will be mentioned in the error. * @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)) if (!packetListeners.remove(listener))
return; return;
// Add listeners // Remove listeners and phases
if (sending != null && sending.isEnabled()) if (sending != null && sending.isEnabled()) {
sendingRemoved = sendingListeners.removeListener(listener, sending); sendingRemoved = sendingListeners.removeListener(listener, sending);
if (receiving != null && receiving.isEnabled()) decrementPhases(sending.getGamePhase());
}
if (receiving != null && receiving.isEnabled()) {
receivingRemoved = recievedListeners.removeListener(listener, receiving); receivingRemoved = recievedListeners.removeListener(listener, receiving);
decrementPhases(receiving.getGamePhase());
}
// Remove hooks, if needed // Remove hooks, if needed
if (sendingRemoved != null && sendingRemoved.size() > 0) if (sendingRemoved != null && sendingRemoved.size() > 0)
@ -471,6 +540,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
playerInjection.injectPlayer(player); 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. * Register this protocol manager on Bukkit.
* @param manager - Bukkit plugin manager that provides player join/leave events. * @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) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
// This call will be ignored if no listeners are registered
playerInjection.injectPlayer(event.getPlayer()); 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 @Override
public int getPacketID(Packet packet) { public int getPacketID(Packet packet) {
if (packet == null) if (packet == null)

View File

@ -198,9 +198,13 @@ class PacketInjector {
// Called from the ReadPacketModified monitor // Called from the ReadPacketModified monitor
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
Player client = playerInjection.getPlayerByConnection(input); Player client = playerInjection.getPlayerByConnection(input);
// Never invoke a event if we don't know where it's from
if (client != null)
return packetRecieved(packet, client); return packetRecieved(packet, client);
else
return null;
} }
/** /**

View File

@ -102,10 +102,11 @@ class ReadPacketModifier implements MethodInterceptor {
// Let the people know // Let the people know
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj); PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input); PacketEvent event = packetInjector.packetRecieved(container, input);
Packet result = event.getPacket().getHandle();
// Handle override // Handle override
if (event != null) { if (event != null) {
Packet result = event.getPacket().getHandle();
if (event.isCancelled()) { if (event.isCancelled()) {
override.put(thisObj, null); override.put(thisObj, null);
} else if (!objectEquals(thisObj, result)) { } else if (!objectEquals(thisObj, result)) {

View File

@ -4,11 +4,13 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; 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.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -25,12 +27,16 @@ class NetLoginInjector {
private PlayerInjectionHandler injectionHandler; private PlayerInjectionHandler injectionHandler;
private Server server; private Server server;
// The current logger
private Logger logger;
private ReadWriteLock injectionLock = new ReentrantReadWriteLock(); private ReadWriteLock injectionLock = new ReentrantReadWriteLock();
// Used to create fake players // Used to create fake players
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); 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.injectionHandler = injectionHandler;
this.server = server; this.server = server;
} }
@ -45,6 +51,10 @@ class NetLoginInjector {
injectionLock.writeLock().lock(); injectionLock.writeLock().lock();
try { try {
// Make sure we actually need to inject during this phase
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting;
Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server); Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server);
PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN); PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN);
injector.updateOnLogin = true; injector.updateOnLogin = true;
@ -56,6 +66,11 @@ class NetLoginInjector {
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting; 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 { } finally {
injectionLock.writeLock().unlock(); injectionLock.writeLock().unlock();
} }

View File

@ -31,9 +31,9 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; 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.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;

View File

@ -36,9 +36,9 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; 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. * Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.

View File

@ -34,9 +34,9 @@ import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; 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.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectCloner; import com.comphenix.protocol.reflect.ObjectCloner;
@ -264,7 +264,7 @@ public class NetworkServerInjector extends PlayerInjector {
@Override @Override
public boolean canInject(GamePhase phase) { public boolean canInject(GamePhase phase) {
// Doesn't work when logging in // Doesn't work when logging in
return phase == GamePhase.PLAYING || phase == GamePhase.CLOSING; return phase == GamePhase.PLAYING;
} }
@Override @Override

View File

@ -38,9 +38,11 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -51,16 +53,6 @@ import com.google.common.collect.Maps;
*/ */
public class PlayerInjectionHandler { public class PlayerInjectionHandler {
/**
* The current player phase.
* @author Kristian
*/
enum GamePhase {
LOGIN,
PLAYING,
CLOSING,
}
// Server connection injection // Server connection injection
private InjectedServerConnection serverInjection; private InjectedServerConnection serverInjection;
@ -93,11 +85,17 @@ public class PlayerInjectionHandler {
// The class loader we're using // The class loader we're using
private ClassLoader classLoader; private ClassLoader classLoader;
public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, ListenerInvoker invoker, Server server) { // Used to filter injection attempts
private Predicate<GamePhase> injectionFilter;
public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Server server) {
this.classLoader = classLoader; this.classLoader = classLoader;
this.logger = logger; this.logger = logger;
this.invoker = invoker; 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); this.serverInjection = new InjectedServerConnection(logger, server, netLoginInjector);
serverInjection.injectList(); serverInjection.injectList();
} }
@ -185,15 +183,31 @@ public class PlayerInjectionHandler {
/** /**
* Initialize a player hook, allowing us to read server packets. * Initialize a player hook, allowing us to read server packets.
* <p>
* This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook. * @param player - player to hook.
*/ */
public void injectPlayer(Player player) { public void injectPlayer(Player player) {
// Inject using the player instance itself // Inject using the player instance itself
if (isInjectionNecessary(GamePhase.PLAYING)) {
injectPlayer(player, player, 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. * Initialize a player hook, allowing us to read server packets.
* <p>
* This method will always perform the instructed injection.
*
* @param player - player to hook. * @param player - player to hook.
* @param injectionPoint - the object to use during the injection process. * @param injectionPoint - the object to use during the injection process.
* @param phase - the current game phase. * @param phase - the current game phase.
@ -205,6 +219,8 @@ public class PlayerInjectionHandler {
PlayerInjectHooks tempHook = playerHook; PlayerInjectHooks tempHook = playerHook;
PlayerInjectHooks permanentHook = tempHook; PlayerInjectHooks permanentHook = tempHook;
// The given player object may be fake, so be careful!
// See if we need to inject something else // See if we need to inject something else
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true; boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;

View File

@ -38,9 +38,9 @@ import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; 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.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;