diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml
index bc1ff483..7e1c3e45 100644
--- a/ProtocolLib/pom.xml
+++ b/ProtocolLib/pom.xml
@@ -2,7 +2,7 @@
4.0.0
com.comphenix.protocol
ProtocolLib
- 1.3.3
+ 1.3.3-SNAPSHOT
jar
Provides read/write access to the Minecraft protocol.
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 14e2d620..66cd35ae 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java
@@ -1,656 +1,672 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Collections;
-import java.util.List;
-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 net.sf.cglib.proxy.Enhancer;
-import net.sf.cglib.proxy.MethodInterceptor;
-import net.sf.cglib.proxy.MethodProxy;
-
-import org.bukkit.Server;
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.event.player.PlayerQuitEvent;
-import org.bukkit.event.server.PluginDisableEvent;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.plugin.PluginManager;
-
-import com.comphenix.protocol.AsynchronousManager;
-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, ListenerInvoker {
-
- /**
- * Sets the inject hook type. Different types allow for maximum compatibility.
- * @author Kristian
- */
- public enum PlayerInjectHooks {
- /**
- * Override the network handler object itself. Only works in 1.3.
- *
- * Cannot intercept MapChunk packets.
- */
- NETWORK_MANAGER_OBJECT,
-
- /**
- * Override the packet queue lists in NetworkHandler.
- *
- * Cannot intercept MapChunk packets.
- */
- NETWORK_HANDLER_FIELDS,
-
- /**
- * Override the server handler object. Versatile, but a tad slower.
- */
- NETWORK_SERVER_OBJECT;
- }
-
- // Create a concurrent set
- private Set packetListeners =
- Collections.newSetFromMap(new ConcurrentHashMap());
-
- // Packet injection
- private PacketInjector packetInjector;
-
- // Player injection
- private PlayerInjectionHandler playerInjection;
-
- // The two listener containers
- private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
- private SortedPacketListenerList sendingListeners = new SortedPacketListenerList();
-
- // Whether or not this class has been closed
- private volatile boolean hasClosed;
-
- // The default class loader
- private ClassLoader classLoader;
-
- // Error logger
- private Logger logger;
-
- // The async packet handler
- private AsyncFilterManager asyncFilterManager;
-
- // Valid server and client packets
- private Set serverPackets;
- private Set clientPackets;
-
-
- /**
- * Only create instances of this class if protocol lib is disabled.
- */
- public PacketFilterManager(ClassLoader classLoader, Server server, Logger logger) {
- if (logger == null)
- throw new IllegalArgumentException("logger cannot be NULL.");
- if (classLoader == null)
- throw new IllegalArgumentException("classLoader cannot be NULL.");
-
- try {
- // Initialize values
- this.classLoader = classLoader;
- this.logger = logger;
- this.playerInjection = new PlayerInjectionHandler(classLoader, logger, this, server);
- this.packetInjector = new PacketInjector(classLoader, this, playerInjection);
- this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
-
- // Attempt to load the list of server and client packets
- try {
- this.serverPackets = MinecraftRegistry.getServerPackets();
- this.clientPackets = MinecraftRegistry.getClientPackets();
- } catch (FieldAccessException e) {
- logger.log(Level.WARNING, "Cannot load server and client packet list.", e);
- }
-
- } catch (IllegalAccessException e) {
- logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
- }
- }
-
- @Override
- public AsynchronousManager getAsynchronousManager() {
- return asyncFilterManager;
- }
-
- /**
- * Retrieves how the server packets are read.
- * @return Injection method for reading server packets.
- */
- public PlayerInjectHooks getPlayerHook() {
- return playerInjection.getPlayerHook();
- }
-
- /**
- * Sets how the server packets are read.
- * @param playerHook - the new injection method for reading server packets.
- */
- public void setPlayerHook(PlayerInjectHooks playerHook) {
- playerInjection.setPlayerHook(playerHook);
-
- // Make sure the current listeners are compatible
- playerInjection.checkListener(packetListeners);
- }
-
- public Logger getLogger() {
- return logger;
- }
-
- @Override
- public ImmutableSet getPacketListeners() {
- return ImmutableSet.copyOf(packetListeners);
- }
-
- @Override
- public void addPacketListener(PacketListener listener) {
- if (listener == null)
- throw new IllegalArgumentException("listener cannot be NULL.");
-
- // A listener can only be added once
- if (packetListeners.contains(listener))
- return;
-
- ListeningWhitelist sending = listener.getSendingWhitelist();
- ListeningWhitelist receiving = listener.getReceivingWhitelist();
- boolean hasSending = sending != null && sending.isEnabled();
- boolean hasReceiving = receiving != null && receiving.isEnabled();
-
- if (hasSending || hasReceiving) {
- // Add listeners and hooks
- if (hasSending) {
- verifyWhitelist(listener, sending);
- sendingListeners.addListener(listener, sending);
- enablePacketFilters(listener, ConnectionSide.SERVER_SIDE, sending.getWhitelist());
-
- // Make sure this is possible
- playerInjection.checkListener(listener);
- }
- if (hasReceiving) {
- verifyWhitelist(listener, receiving);
- recievedListeners.addListener(listener, receiving);
- enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
- }
-
- // Inform our injected hooks
- packetListeners.add(listener);
- }
- }
-
- /**
- * Determine if the packet IDs in a whitelist is valid.
- * @param listener - the listener that will be mentioned in the error.
- * @param whitelist - whitelist of packet IDs.
- * @throws IllegalArgumentException If the whitelist is illegal.
- */
- public static void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
- for (Integer id : whitelist.getWhitelist()) {
- if (id >= 256 || id < 0) {
- throw new IllegalArgumentException(String.format("Invalid packet id %s in listener %s.",
- id, PacketAdapter.getPluginName(listener))
- );
- }
- }
- }
-
- @Override
- public void removePacketListener(PacketListener listener) {
- if (listener == null)
- throw new IllegalArgumentException("listener cannot be NULL");
-
- List sendingRemoved = null;
- List receivingRemoved = null;
-
- ListeningWhitelist sending = listener.getSendingWhitelist();
- ListeningWhitelist receiving = listener.getReceivingWhitelist();
-
- // Remove from the overal list of listeners
- if (!packetListeners.remove(listener))
- return;
-
- // Add listeners
- if (sending != null && sending.isEnabled())
- sendingRemoved = sendingListeners.removeListener(listener, sending);
- if (receiving != null && receiving.isEnabled())
- receivingRemoved = recievedListeners.removeListener(listener, receiving);
-
- // Remove hooks, if needed
- if (sendingRemoved != null && sendingRemoved.size() > 0)
- disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
- if (receivingRemoved != null && receivingRemoved.size() > 0)
- disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
- }
-
- @Override
- public void removePacketListeners(Plugin plugin) {
-
- // Iterate through every packet listener
- for (PacketListener listener : packetListeners) {
- // Remove the listener
- if (Objects.equal(listener.getPlugin(), plugin)) {
- removePacketListener(listener);
- }
- }
-
- // Do the same for the asynchronous events
- asyncFilterManager.unregisterAsyncHandlers(plugin);
- }
-
- @Override
- public void invokePacketRecieving(PacketEvent event) {
- handlePacket(recievedListeners, event, false);
- }
-
- @Override
- public void invokePacketSending(PacketEvent event) {
- handlePacket(sendingListeners, event, true);
- }
-
- /**
- * Handle a packet sending or receiving event.
- *
- * Note that we also handle asynchronous events.
- * @param packetListeners - packet listeners that will receive this event.
- * @param event - the evnet to broadcast.
- */
- private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) {
-
- // By default, asynchronous packets are queued for processing
- if (asyncFilterManager.hasAsynchronousListeners(event)) {
- event.setAsyncMarker(asyncFilterManager.createAsyncMarker());
- }
-
- // Process synchronous events
- if (sending)
- packetListeners.invokePacketSending(logger, event);
- else
- packetListeners.invokePacketRecieving(logger, event);
-
- // To cancel asynchronous processing, use the async marker
- if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) {
- asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
-
- // The above makes a copy of the event, so it's safe to cancel it
- event.setCancelled(true);
- }
- }
-
- // NULL marker mean we're dealing with no asynchronous listeners
- private boolean hasAsyncCancelled(AsyncMarker marker) {
- return marker == null || marker.isAsyncCancelled();
- }
-
- /**
- * Enables packet events for a given packet ID.
- *
- * Note that all packets are disabled by default.
- *
- * @param listener - the listener that requested to enable these filters.
- * @param side - which side the event will arrive from.
- * @param packets - the packet id(s).
- */
- private void enablePacketFilters(PacketListener listener, ConnectionSide side, Iterable packets) {
- if (side == null)
- throw new IllegalArgumentException("side cannot be NULL.");
-
- // Note the difference between unsupported and valid.
- // Every packet ID between and including 0 - 255 is valid, but only a subset is supported.
-
- for (int packetID : packets) {
- // Only register server packets that are actually supported by Minecraft
- if (side.isForServer()) {
- if (serverPackets != null && serverPackets.contains(packetID))
- playerInjection.addPacketHandler(packetID);
- else
- logger.warning(String.format(
- "[%s] Unsupported server packet ID in current Minecraft version: %s",
- PacketAdapter.getPluginName(listener), packetID
- ));
- }
-
- // As above, only for client packets
- if (side.isForClient() && packetInjector != null) {
- if (clientPackets != null && clientPackets.contains(packetID))
- packetInjector.addPacketHandler(packetID);
- else
- logger.warning(String.format(
- "[%s] Unsupported client packet ID in current Minecraft version: %s",
- PacketAdapter.getPluginName(listener), packetID
- ));
- }
- }
- }
-
- /**
- * Disables packet events from a given packet ID.
- * @param packets - the packet id(s).
- * @param side - which side the event no longer should arrive from.
- */
- private void disablePacketFilters(ConnectionSide side, Iterable packets) {
- if (side == null)
- throw new IllegalArgumentException("side cannot be NULL.");
-
- for (int packetID : packets) {
- if (side.isForServer())
- playerInjection.removePacketHandler(packetID);
- if (side.isForClient() && packetInjector != null)
- packetInjector.removePacketHandler(packetID);
- }
- }
-
- @Override
- public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException {
- sendServerPacket(reciever, packet, true);
- }
-
- @Override
- public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
- if (reciever == null)
- throw new IllegalArgumentException("reciever cannot be NULL.");
- if (packet == null)
- throw new IllegalArgumentException("packet cannot be NULL.");
-
- playerInjection.sendServerPacket(reciever, packet, filters);
- }
-
- @Override
- public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
- recieveClientPacket(sender, packet, true);
- }
-
- @Override
- public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
-
- if (sender == null)
- throw new IllegalArgumentException("sender cannot be NULL.");
- if (packet == null)
- throw new IllegalArgumentException("packet cannot be NULL.");
-
- Packet mcPacket = packet.getHandle();
-
- // Make sure the packet isn't cancelled
- packetInjector.undoCancel(packet.getID(), mcPacket);
-
- if (filters) {
- PacketEvent event = packetInjector.packetRecieved(packet, sender);
-
- if (!event.isCancelled())
- mcPacket = event.getPacket().getHandle();
- else
- return;
- }
-
- playerInjection.processPacket(sender, mcPacket);
- }
-
- @Override
- public PacketContainer createPacket(int id) {
- return createPacket(id, true);
- }
-
- @Override
- public PacketContainer createPacket(int id, boolean forceDefaults) {
- PacketContainer packet = new PacketContainer(id);
-
- // Use any default values if possible
- if (forceDefaults) {
- try {
- packet.getModifier().writeDefaults();
- } catch (FieldAccessException e) {
- throw new RuntimeException("Security exception.", e);
- }
- }
-
- return packet;
- }
-
- @Override
- public PacketConstructor createPacketConstructor(int id, Object... arguments) {
- return PacketConstructor.DEFAULT.withPacket(id, arguments);
- }
-
- @Override
- public Set getSendingFilters() {
- return playerInjection.getSendingFilters();
- }
-
- @Override
- public Set getReceivingFilters() {
- return ImmutableSet.copyOf(packetInjector.getPacketHandlers());
- }
-
- @Override
- public void updateEntity(Entity entity, List observers) throws FieldAccessException {
- EntityUtilities.updateEntity(entity, observers);
- }
-
- /**
- * Initialize the packet injection for every player.
- * @param players - list of players to inject.
- */
- public void initializePlayers(Player[] players) {
- for (Player player : players)
- playerInjection.injectPlayer(player);
- }
-
- /**
- * Register this protocol manager on Bukkit.
- * @param manager - Bukkit plugin manager that provides player join/leave events.
- * @param plugin - the parent plugin.
- */
- public void registerEvents(PluginManager manager, final Plugin plugin) {
-
- try {
- manager.registerEvents(new Listener() {
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- public void onPlayerJoin(PlayerJoinEvent event) {
- playerInjection.injectPlayer(event.getPlayer());
- }
-
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- public void onPlayerQuit(PlayerQuitEvent event) {
- playerInjection.uninjectPlayer(event.getPlayer());
- }
-
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- public void onPluginDisabled(PluginDisableEvent event) {
- // Clean up in case the plugin forgets
- if (event.getPlugin() != plugin) {
- removePacketListeners(event.getPlugin());
- }
- }
-
- }, plugin);
-
- } catch (NoSuchMethodError e) {
- // Oh wow! We're running on 1.0.0 or older.
- registerOld(manager, plugin);
- }
- }
-
- @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) {
-
- try {
- ClassLoader loader = manager.getClass().getClassLoader();
-
- // The different enums we are going to need
- Class eventTypes = loader.loadClass("org.bukkit.event.Event$Type");
- Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
-
- // Get the priority
- Object priorityNormal = Enum.valueOf(eventPriority, "Monitor");
-
- // Get event types
- Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN");
- Object playerQuitType = Enum.valueOf(eventTypes, "PLAYER_QUIT");
- Object pluginDisabledType = Enum.valueOf(eventTypes, "PLUGIN_DISABLE");
-
- // The player listener! Good times.
- Class> playerListener = loader.loadClass("org.bukkit.event.player.PlayerListener");
- Class> serverListener = loader.loadClass("org.bukkit.event.server.ServerListener");
-
- // Find the register event method
- Method registerEvent = FuzzyReflection.fromObject(manager).getMethodByParameters("registerEvent",
- eventTypes, Listener.class, eventPriority, Plugin.class);
-
- Enhancer playerEx = new Enhancer();
- Enhancer serverEx = new Enhancer();
-
- playerEx.setSuperclass(playerListener);
- playerEx.setClassLoader(classLoader);
- playerEx.setCallback(new MethodInterceptor() {
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- // Must have a parameter
- if (args.length == 1) {
- Object event = args[0];
-
- // Check for the correct event
- if (event instanceof PlayerJoinEvent)
- playerInjection.injectPlayer(((PlayerJoinEvent) event).getPlayer());
- else if (event instanceof PlayerQuitEvent)
- playerInjection.uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
- }
- return null;
- }
- });
-
- serverEx.setSuperclass(serverListener);
- serverEx.setClassLoader(classLoader);
- serverEx.setCallback(new MethodInterceptor() {
- @Override
- public Object intercept(Object obj, Method method, Object[] args,
- MethodProxy proxy) throws Throwable {
- // Must have a parameter
- if (args.length == 1) {
- Object event = args[0];
-
- if (event instanceof PluginDisableEvent)
- removePacketListeners(((PluginDisableEvent) event).getPlugin());
- }
- return null;
- }
- });
-
- // Create our listener
- Object playerProxy = playerEx.create();
- Object serverProxy = serverEx.create();
-
- registerEvent.invoke(manager, playerJoinType, playerProxy, priorityNormal, plugin);
- registerEvent.invoke(manager, playerQuitType, playerProxy, priorityNormal, plugin);
- registerEvent.invoke(manager, pluginDisabledType, serverProxy, priorityNormal, plugin);
-
- // A lot can go wrong
- } catch (ClassNotFoundException e1) {
- e1.printStackTrace();
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Retrieve every known and supported server packet.
- * @return An immutable set of every known server packet.
- * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
- */
- public static Set getServerPackets() throws FieldAccessException {
- return MinecraftRegistry.getServerPackets();
- }
-
- /**
- * Retrieve every known and supported client packet.
- * @return An immutable set of every known client packet.
- * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
- */
- public static Set getClientPackets() throws FieldAccessException {
- return MinecraftRegistry.getClientPackets();
- }
-
- /**
- * Retrieves the current plugin class loader.
- * @return Class loader.
- */
- public ClassLoader getClassLoader() {
- return classLoader;
- }
-
- @Override
- public boolean isClosed() {
- return hasClosed;
- }
-
- /**
- * Called when ProtocolLib is closing.
- */
- public void close() {
- // Guard
- if (hasClosed)
- return;
-
- // Remove packet handlers
- if (packetInjector != null)
- packetInjector.cleanupAll();
-
- // Remove server handler
- playerInjection.close();
- hasClosed = true;
-
- // Remove listeners
- packetListeners.clear();
-
- // Clean up async handlers. We have to do this last.
- asyncFilterManager.cleanupAll();
- }
-
- @Override
- protected void finalize() throws Throwable {
- close();
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+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 net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+
+import org.bukkit.Server;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerLoginEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.server.PluginDisableEvent;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginManager;
+
+import com.comphenix.protocol.AsynchronousManager;
+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.injector.player.PlayerInjectionHandler.GamePhase;
+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, ListenerInvoker {
+
+ /**
+ * Sets the inject hook type. Different types allow for maximum compatibility.
+ * @author Kristian
+ */
+ public enum PlayerInjectHooks {
+ /**
+ * The injection hook that does nothing. Set when every other inject hook fails.
+ */
+ NONE,
+
+ /**
+ * Override the network handler object itself. Only works in 1.3.
+ *
+ * Cannot intercept MapChunk packets.
+ */
+ NETWORK_MANAGER_OBJECT,
+
+ /**
+ * Override the packet queue lists in NetworkHandler.
+ *
+ * Cannot intercept MapChunk packets.
+ */
+ NETWORK_HANDLER_FIELDS,
+
+ /**
+ * Override the server handler object. Versatile, but a tad slower.
+ */
+ NETWORK_SERVER_OBJECT;
+ }
+
+ // Create a concurrent set
+ private Set packetListeners =
+ Collections.newSetFromMap(new ConcurrentHashMap());
+
+ // Packet injection
+ private PacketInjector packetInjector;
+
+ // Player injection
+ private PlayerInjectionHandler playerInjection;
+
+ // The two listener containers
+ private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
+ private SortedPacketListenerList sendingListeners = new SortedPacketListenerList();
+
+ // Whether or not this class has been closed
+ private volatile boolean hasClosed;
+
+ // The default class loader
+ private ClassLoader classLoader;
+
+ // Error logger
+ private Logger logger;
+
+ // The async packet handler
+ private AsyncFilterManager asyncFilterManager;
+
+ // Valid server and client packets
+ private Set serverPackets;
+ private Set clientPackets;
+
+
+ /**
+ * Only create instances of this class if protocol lib is disabled.
+ */
+ public PacketFilterManager(ClassLoader classLoader, Server server, Logger logger) {
+ if (logger == null)
+ throw new IllegalArgumentException("logger cannot be NULL.");
+ if (classLoader == null)
+ throw new IllegalArgumentException("classLoader cannot be NULL.");
+
+ try {
+ // Initialize values
+ this.classLoader = classLoader;
+ this.logger = logger;
+ this.playerInjection = new PlayerInjectionHandler(classLoader, logger, this, server);
+ this.packetInjector = new PacketInjector(classLoader, this, playerInjection);
+ this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
+
+ // Attempt to load the list of server and client packets
+ try {
+ this.serverPackets = MinecraftRegistry.getServerPackets();
+ this.clientPackets = MinecraftRegistry.getClientPackets();
+ } catch (FieldAccessException e) {
+ logger.log(Level.WARNING, "Cannot load server and client packet list.", e);
+ }
+
+ } catch (IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
+ }
+ }
+
+ @Override
+ public AsynchronousManager getAsynchronousManager() {
+ return asyncFilterManager;
+ }
+
+ /**
+ * Retrieves how the server packets are read.
+ * @return Injection method for reading server packets.
+ */
+ public PlayerInjectHooks getPlayerHook() {
+ return playerInjection.getPlayerHook();
+ }
+
+ /**
+ * Sets how the server packets are read.
+ * @param playerHook - the new injection method for reading server packets.
+ */
+ public void setPlayerHook(PlayerInjectHooks playerHook) {
+ playerInjection.setPlayerHook(playerHook);
+
+ // Make sure the current listeners are compatible
+ playerInjection.checkListener(packetListeners);
+ }
+
+ public Logger getLogger() {
+ return logger;
+ }
+
+ @Override
+ public ImmutableSet getPacketListeners() {
+ return ImmutableSet.copyOf(packetListeners);
+ }
+
+ @Override
+ public void addPacketListener(PacketListener listener) {
+ if (listener == null)
+ throw new IllegalArgumentException("listener cannot be NULL.");
+
+ // A listener can only be added once
+ if (packetListeners.contains(listener))
+ return;
+
+ ListeningWhitelist sending = listener.getSendingWhitelist();
+ ListeningWhitelist receiving = listener.getReceivingWhitelist();
+ boolean hasSending = sending != null && sending.isEnabled();
+ boolean hasReceiving = receiving != null && receiving.isEnabled();
+
+ if (hasSending || hasReceiving) {
+ // Add listeners and hooks
+ if (hasSending) {
+ verifyWhitelist(listener, sending);
+ sendingListeners.addListener(listener, sending);
+ enablePacketFilters(listener, ConnectionSide.SERVER_SIDE, sending.getWhitelist());
+
+ // Make sure this is possible
+ playerInjection.checkListener(listener);
+ }
+ if (hasReceiving) {
+ verifyWhitelist(listener, receiving);
+ recievedListeners.addListener(listener, receiving);
+ enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
+ }
+
+ // Inform our injected hooks
+ packetListeners.add(listener);
+ }
+ }
+
+ /**
+ * Determine if the packet IDs in a whitelist is valid.
+ * @param listener - the listener that will be mentioned in the error.
+ * @param whitelist - whitelist of packet IDs.
+ * @throws IllegalArgumentException If the whitelist is illegal.
+ */
+ public static void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
+ for (Integer id : whitelist.getWhitelist()) {
+ if (id >= 256 || id < 0) {
+ throw new IllegalArgumentException(String.format("Invalid packet id %s in listener %s.",
+ id, PacketAdapter.getPluginName(listener))
+ );
+ }
+ }
+ }
+
+ @Override
+ public void removePacketListener(PacketListener listener) {
+ if (listener == null)
+ throw new IllegalArgumentException("listener cannot be NULL");
+
+ List sendingRemoved = null;
+ List receivingRemoved = null;
+
+ ListeningWhitelist sending = listener.getSendingWhitelist();
+ ListeningWhitelist receiving = listener.getReceivingWhitelist();
+
+ // Remove from the overal list of listeners
+ if (!packetListeners.remove(listener))
+ return;
+
+ // Add listeners
+ if (sending != null && sending.isEnabled())
+ sendingRemoved = sendingListeners.removeListener(listener, sending);
+ if (receiving != null && receiving.isEnabled())
+ receivingRemoved = recievedListeners.removeListener(listener, receiving);
+
+ // Remove hooks, if needed
+ if (sendingRemoved != null && sendingRemoved.size() > 0)
+ disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
+ if (receivingRemoved != null && receivingRemoved.size() > 0)
+ disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
+ }
+
+ @Override
+ public void removePacketListeners(Plugin plugin) {
+
+ // Iterate through every packet listener
+ for (PacketListener listener : packetListeners) {
+ // Remove the listener
+ if (Objects.equal(listener.getPlugin(), plugin)) {
+ removePacketListener(listener);
+ }
+ }
+
+ // Do the same for the asynchronous events
+ asyncFilterManager.unregisterAsyncHandlers(plugin);
+ }
+
+ @Override
+ public void invokePacketRecieving(PacketEvent event) {
+ handlePacket(recievedListeners, event, false);
+ }
+
+ @Override
+ public void invokePacketSending(PacketEvent event) {
+ handlePacket(sendingListeners, event, true);
+ }
+
+ /**
+ * Handle a packet sending or receiving event.
+ *
+ * Note that we also handle asynchronous events.
+ * @param packetListeners - packet listeners that will receive this event.
+ * @param event - the evnet to broadcast.
+ */
+ private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) {
+
+ // By default, asynchronous packets are queued for processing
+ if (asyncFilterManager.hasAsynchronousListeners(event)) {
+ event.setAsyncMarker(asyncFilterManager.createAsyncMarker());
+ }
+
+ // Process synchronous events
+ if (sending)
+ packetListeners.invokePacketSending(logger, event);
+ else
+ packetListeners.invokePacketRecieving(logger, event);
+
+ // To cancel asynchronous processing, use the async marker
+ if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) {
+ asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
+
+ // The above makes a copy of the event, so it's safe to cancel it
+ event.setCancelled(true);
+ }
+ }
+
+ // NULL marker mean we're dealing with no asynchronous listeners
+ private boolean hasAsyncCancelled(AsyncMarker marker) {
+ return marker == null || marker.isAsyncCancelled();
+ }
+
+ /**
+ * Enables packet events for a given packet ID.
+ *
+ * Note that all packets are disabled by default.
+ *
+ * @param listener - the listener that requested to enable these filters.
+ * @param side - which side the event will arrive from.
+ * @param packets - the packet id(s).
+ */
+ private void enablePacketFilters(PacketListener listener, ConnectionSide side, Iterable packets) {
+ if (side == null)
+ throw new IllegalArgumentException("side cannot be NULL.");
+
+ // Note the difference between unsupported and valid.
+ // Every packet ID between and including 0 - 255 is valid, but only a subset is supported.
+
+ for (int packetID : packets) {
+ // Only register server packets that are actually supported by Minecraft
+ if (side.isForServer()) {
+ if (serverPackets != null && serverPackets.contains(packetID))
+ playerInjection.addPacketHandler(packetID);
+ else
+ logger.warning(String.format(
+ "[%s] Unsupported server packet ID in current Minecraft version: %s",
+ PacketAdapter.getPluginName(listener), packetID
+ ));
+ }
+
+ // As above, only for client packets
+ if (side.isForClient() && packetInjector != null) {
+ if (clientPackets != null && clientPackets.contains(packetID))
+ packetInjector.addPacketHandler(packetID);
+ else
+ logger.warning(String.format(
+ "[%s] Unsupported client packet ID in current Minecraft version: %s",
+ PacketAdapter.getPluginName(listener), packetID
+ ));
+ }
+ }
+ }
+
+ /**
+ * Disables packet events from a given packet ID.
+ * @param packets - the packet id(s).
+ * @param side - which side the event no longer should arrive from.
+ */
+ private void disablePacketFilters(ConnectionSide side, Iterable packets) {
+ if (side == null)
+ throw new IllegalArgumentException("side cannot be NULL.");
+
+ for (int packetID : packets) {
+ if (side.isForServer())
+ playerInjection.removePacketHandler(packetID);
+ if (side.isForClient() && packetInjector != null)
+ packetInjector.removePacketHandler(packetID);
+ }
+ }
+
+ @Override
+ public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException {
+ sendServerPacket(reciever, packet, true);
+ }
+
+ @Override
+ public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
+ if (reciever == null)
+ throw new IllegalArgumentException("reciever cannot be NULL.");
+ if (packet == null)
+ throw new IllegalArgumentException("packet cannot be NULL.");
+
+ playerInjection.sendServerPacket(reciever, packet, filters);
+ }
+
+ @Override
+ public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
+ recieveClientPacket(sender, packet, true);
+ }
+
+ @Override
+ public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
+
+ if (sender == null)
+ throw new IllegalArgumentException("sender cannot be NULL.");
+ if (packet == null)
+ throw new IllegalArgumentException("packet cannot be NULL.");
+
+ Packet mcPacket = packet.getHandle();
+
+ // Make sure the packet isn't cancelled
+ packetInjector.undoCancel(packet.getID(), mcPacket);
+
+ if (filters) {
+ PacketEvent event = packetInjector.packetRecieved(packet, sender);
+
+ if (!event.isCancelled())
+ mcPacket = event.getPacket().getHandle();
+ else
+ return;
+ }
+
+ playerInjection.processPacket(sender, mcPacket);
+ }
+
+ @Override
+ public PacketContainer createPacket(int id) {
+ return createPacket(id, true);
+ }
+
+ @Override
+ public PacketContainer createPacket(int id, boolean forceDefaults) {
+ PacketContainer packet = new PacketContainer(id);
+
+ // Use any default values if possible
+ if (forceDefaults) {
+ try {
+ packet.getModifier().writeDefaults();
+ } catch (FieldAccessException e) {
+ throw new RuntimeException("Security exception.", e);
+ }
+ }
+
+ return packet;
+ }
+
+ @Override
+ public PacketConstructor createPacketConstructor(int id, Object... arguments) {
+ return PacketConstructor.DEFAULT.withPacket(id, arguments);
+ }
+
+ @Override
+ public Set getSendingFilters() {
+ return playerInjection.getSendingFilters();
+ }
+
+ @Override
+ public Set getReceivingFilters() {
+ return ImmutableSet.copyOf(packetInjector.getPacketHandlers());
+ }
+
+ @Override
+ public void updateEntity(Entity entity, List observers) throws FieldAccessException {
+ EntityUtilities.updateEntity(entity, observers);
+ }
+
+ /**
+ * Initialize the packet injection for every player.
+ * @param players - list of players to inject.
+ */
+ public void initializePlayers(Player[] players) {
+ for (Player player : players)
+ playerInjection.injectPlayer(player, GamePhase.PLAYING);
+ }
+
+ /**
+ * Register this protocol manager on Bukkit.
+ * @param manager - Bukkit plugin manager that provides player join/leave events.
+ * @param plugin - the parent plugin.
+ */
+ public void registerEvents(PluginManager manager, final Plugin plugin) {
+
+ try {
+ manager.registerEvents(new Listener() {
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onPlayerLogin(PlayerLoginEvent event) {
+ playerInjection.injectPlayer(event.getPlayer(), GamePhase.LOGIN);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ playerInjection.injectPlayer(event.getPlayer(), GamePhase.PLAYING);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ playerInjection.uninjectPlayer(event.getPlayer());
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onPluginDisabled(PluginDisableEvent event) {
+ // Clean up in case the plugin forgets
+ if (event.getPlugin() != plugin) {
+ removePacketListeners(event.getPlugin());
+ }
+ }
+
+ }, plugin);
+
+ } catch (NoSuchMethodError e) {
+ // Oh wow! We're running on 1.0.0 or older.
+ registerOld(manager, plugin);
+ }
+ }
+
+ @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) {
+
+ try {
+ ClassLoader loader = manager.getClass().getClassLoader();
+
+ // The different enums we are going to need
+ Class eventTypes = loader.loadClass("org.bukkit.event.Event$Type");
+ Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
+
+ // Get the priority
+ Object priorityMonitor = Enum.valueOf(eventPriority, "Monitor");
+
+ // Get event types
+ Object playerLoginType = Enum.valueOf(eventTypes, "PLAYER_LOGIN");
+ Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN");
+ Object playerQuitType = Enum.valueOf(eventTypes, "PLAYER_QUIT");
+ Object pluginDisabledType = Enum.valueOf(eventTypes, "PLUGIN_DISABLE");
+
+ // The player listener! Good times.
+ Class> playerListener = loader.loadClass("org.bukkit.event.player.PlayerListener");
+ Class> serverListener = loader.loadClass("org.bukkit.event.server.ServerListener");
+
+ // Find the register event method
+ Method registerEvent = FuzzyReflection.fromObject(manager).getMethodByParameters("registerEvent",
+ eventTypes, Listener.class, eventPriority, Plugin.class);
+
+ Enhancer playerEx = new Enhancer();
+ Enhancer serverEx = new Enhancer();
+
+ playerEx.setSuperclass(playerListener);
+ playerEx.setClassLoader(classLoader);
+ playerEx.setCallback(new MethodInterceptor() {
+ @Override
+ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
+ // Must have a parameter
+ if (args.length == 1) {
+ Object event = args[0];
+
+ // Check for the correct event
+ if (event instanceof PlayerLoginEvent)
+ playerInjection.injectPlayer(((PlayerJoinEvent) event).getPlayer(), GamePhase.LOGIN);
+ if (event instanceof PlayerJoinEvent)
+ playerInjection.injectPlayer(((PlayerJoinEvent) event).getPlayer(), GamePhase.PLAYING);
+ else if (event instanceof PlayerQuitEvent)
+ playerInjection.uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
+ }
+ return null;
+ }
+ });
+
+ serverEx.setSuperclass(serverListener);
+ serverEx.setClassLoader(classLoader);
+ serverEx.setCallback(new MethodInterceptor() {
+ @Override
+ public Object intercept(Object obj, Method method, Object[] args,
+ MethodProxy proxy) throws Throwable {
+ // Must have a parameter
+ if (args.length == 1) {
+ Object event = args[0];
+
+ if (event instanceof PluginDisableEvent)
+ removePacketListeners(((PluginDisableEvent) event).getPlugin());
+ }
+ return null;
+ }
+ });
+
+ // Create our listener
+ Object playerProxy = playerEx.create();
+ Object serverProxy = serverEx.create();
+
+ registerEvent.invoke(manager, playerLoginType, playerProxy, priorityMonitor, plugin);
+ registerEvent.invoke(manager, playerJoinType, playerProxy, priorityMonitor, plugin);
+ registerEvent.invoke(manager, playerQuitType, playerProxy, priorityMonitor, plugin);
+ registerEvent.invoke(manager, pluginDisabledType, serverProxy, priorityMonitor, plugin);
+
+ // A lot can go wrong
+ } catch (ClassNotFoundException e1) {
+ e1.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Retrieve every known and supported server packet.
+ * @return An immutable set of every known server packet.
+ * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
+ */
+ public static Set getServerPackets() throws FieldAccessException {
+ return MinecraftRegistry.getServerPackets();
+ }
+
+ /**
+ * Retrieve every known and supported client packet.
+ * @return An immutable set of every known client packet.
+ * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
+ */
+ public static Set getClientPackets() throws FieldAccessException {
+ return MinecraftRegistry.getClientPackets();
+ }
+
+ /**
+ * Retrieves the current plugin class loader.
+ * @return Class loader.
+ */
+ public ClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ @Override
+ public boolean isClosed() {
+ return hasClosed;
+ }
+
+ /**
+ * Called when ProtocolLib is closing.
+ */
+ public void close() {
+ // Guard
+ if (hasClosed)
+ return;
+
+ // Remove packet handlers
+ if (packetInjector != null)
+ packetInjector.cleanupAll();
+
+ // Remove server handler
+ playerInjection.close();
+ hasClosed = true;
+
+ // Remove listeners
+ packetListeners.clear();
+
+ // Clean up async handlers. We have to do this last.
+ asyncFilterManager.cleanupAll();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ }
+}
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 7ab5d1d6..1c199e9b 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
@@ -1,196 +1,204 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.player;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-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;
-import com.comphenix.protocol.reflect.VolatileField;
-import com.google.common.collect.Sets;
-
-import net.minecraft.server.Packet;
-
-/**
- * Injection hook that overrides the packet queue lists in NetworkHandler.
- *
- * @author Kristian
- */
-class NetworkFieldInjector extends PlayerInjector {
-
- /**
- * Marker interface that indicates a packet is fake and should not be processed.
- * @author Kristian
- */
- public interface FakePacket {
- // Nothing
- }
-
- // Packets to ignore
- private Set ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap());
-
- // Overridden fields
- private List overridenLists = new ArrayList();
-
- // 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(ClassLoader classLoader, Logger logger, Player player,
- ListenerInvoker manager, Set sendingFilters) throws IllegalAccessException {
-
- super(logger, player, manager);
- this.classLoader = classLoader;
- this.sendingFilters = sendingFilters;
- }
-
- @Override
- protected boolean hasListener(int packetID) {
- return sendingFilters.contains(packetID);
- }
-
- @Override
- public synchronized void initialize() throws IllegalAccessException {
- super.initialize();
-
- // Get the sync field as well
- if (hasInitialized) {
- if (syncField == null)
- syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
- syncObject = FieldUtils.readField(syncField, networkManager, true);
- }
- }
-
- @Override
- public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
-
- if (networkManager != null) {
- try {
- if (!filtered) {
- ignoredPackets.add(packet);
- }
-
- // Note that invocation target exception is a wrapper for a checked exception
- queueMethod.invoke(networkManager, packet);
-
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw e;
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Unable to access queue method.", e);
- }
- } else {
- throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
- }
- }
-
- @Override
- public void checkListener(PacketListener listener) {
- // Unfortunately, we don't support chunk packets
- if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
- Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
- throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
- }
- }
-
- @Override
- public void injectManager() {
-
- if (networkManager != null) {
-
- @SuppressWarnings("rawtypes")
- StructureModifier list = networkModifier.withType(List.class);
-
- // Subclass both send queues
- for (Field field : list.getFields()) {
- VolatileField overwriter = new VolatileField(field, networkManager, true);
-
- @SuppressWarnings("unchecked")
- List minecraftList = (List) overwriter.getOldValue();
-
- synchronized(syncObject) {
- // The list we'll be inserting
- List hackedList = new InjectedArrayList(classLoader, this, ignoredPackets);
-
- // Add every previously stored packet
- for (Packet packet : minecraftList) {
- hackedList.add(packet);
- }
-
- // Don' keep stale packets around
- minecraftList.clear();
- overwriter.setValue(Collections.synchronizedList(hackedList));
- }
-
- overridenLists.add(overwriter);
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- public void cleanupAll() {
- // Clean up
- for (VolatileField overriden : overridenLists) {
- List minecraftList = (List) overriden.getOldValue();
- List hacketList = (List) overriden.getValue();
-
- if (minecraftList == hacketList) {
- return;
- }
-
- // Get a lock before we modify the list
- synchronized(syncObject) {
- try {
- // Copy over current packets
- for (Packet packet : (List) overriden.getValue()) {
- minecraftList.add(packet);
- }
- } finally {
- overriden.revertValue();
- }
- }
- }
- overridenLists.clear();
- }
-
- @Override
- public boolean canInject() {
- return true;
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector.player;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+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.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;
+import com.comphenix.protocol.reflect.VolatileField;
+import com.google.common.collect.Sets;
+
+import net.minecraft.server.Packet;
+
+/**
+ * Injection hook that overrides the packet queue lists in NetworkHandler.
+ *
+ * @author Kristian
+ */
+class NetworkFieldInjector extends PlayerInjector {
+
+ /**
+ * Marker interface that indicates a packet is fake and should not be processed.
+ * @author Kristian
+ */
+ public interface FakePacket {
+ // Nothing
+ }
+
+ // Packets to ignore
+ private Set ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap());
+
+ // Overridden fields
+ private List overridenLists = new ArrayList();
+
+ // 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(ClassLoader classLoader, Logger logger, Player player,
+ ListenerInvoker manager, Set sendingFilters) throws IllegalAccessException {
+
+ super(logger, player, manager);
+ this.classLoader = classLoader;
+ this.sendingFilters = sendingFilters;
+ }
+
+ @Override
+ protected boolean hasListener(int packetID) {
+ return sendingFilters.contains(packetID);
+ }
+
+ @Override
+ public synchronized void initialize() throws IllegalAccessException {
+ super.initialize();
+
+ // Get the sync field as well
+ if (hasInitialized) {
+ if (syncField == null)
+ syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
+ syncObject = FieldUtils.readField(syncField, networkManager, true);
+ }
+ }
+
+ @Override
+ public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
+
+ if (networkManager != null) {
+ try {
+ if (!filtered) {
+ ignoredPackets.add(packet);
+ }
+
+ // Note that invocation target exception is a wrapper for a checked exception
+ queueMethod.invoke(networkManager, packet);
+
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw e;
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Unable to access queue method.", e);
+ }
+ } else {
+ throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
+ }
+ }
+
+ @Override
+ public void checkListener(PacketListener listener) {
+ // Unfortunately, we don't support chunk packets
+ if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
+ Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
+ throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
+ }
+ }
+
+ @Override
+ public void injectManager() {
+
+ if (networkManager != null) {
+
+ @SuppressWarnings("rawtypes")
+ StructureModifier list = networkModifier.withType(List.class);
+
+ // Subclass both send queues
+ for (Field field : list.getFields()) {
+ VolatileField overwriter = new VolatileField(field, networkManager, true);
+
+ @SuppressWarnings("unchecked")
+ List minecraftList = (List) overwriter.getOldValue();
+
+ synchronized(syncObject) {
+ // The list we'll be inserting
+ List hackedList = new InjectedArrayList(classLoader, this, ignoredPackets);
+
+ // Add every previously stored packet
+ for (Packet packet : minecraftList) {
+ hackedList.add(packet);
+ }
+
+ // Don' keep stale packets around
+ minecraftList.clear();
+ overwriter.setValue(Collections.synchronizedList(hackedList));
+ }
+
+ overridenLists.add(overwriter);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void cleanupAll() {
+ // Clean up
+ for (VolatileField overriden : overridenLists) {
+ List minecraftList = (List) overriden.getOldValue();
+ List hacketList = (List) overriden.getValue();
+
+ if (minecraftList == hacketList) {
+ return;
+ }
+
+ // Get a lock before we modify the list
+ synchronized(syncObject) {
+ try {
+ // Copy over current packets
+ for (Packet packet : (List) overriden.getValue()) {
+ minecraftList.add(packet);
+ }
+ } finally {
+ overriden.revertValue();
+ }
+ }
+ }
+ overridenLists.clear();
+ }
+
+ @Override
+ public boolean canInject(GamePhase phase) {
+ // All phases should work
+ return true;
+ }
+
+ @Override
+ public PlayerInjectHooks getHookType() {
+ return PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
+ }
+}
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 bbd6bd74..f2009525 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
@@ -1,143 +1,151 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.player;
-
-import java.lang.reflect.InvocationTargetException;
-
-import net.minecraft.server.Packet;
-
-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.
- *
- * @author Kristian
- */
-class NetworkObjectInjector extends PlayerInjector {
- // 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();
-
- if (networkDelegate != null) {
- try {
- // Note that invocation target exception is a wrapper for a checked exception
- queueMethod.invoke(networkDelegate, packet);
-
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw e;
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Unable to access queue method.", e);
- }
- } else {
- throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
- }
- }
-
- @Override
- public void checkListener(PacketListener listener) {
- // Unfortunately, we don't support chunk packets
- if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
- Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
- throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
- }
- }
-
- @Override
- public void injectManager() {
-
- if (networkManager != null) {
- final Class> networkInterface = networkManagerField.getType();
- final Object networkDelegate = networkManagerRef.getOldValue();
-
- if (!networkInterface.isInterface()) {
- throw new UnsupportedOperationException(
- "Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
- }
-
- // Create our proxy object
- Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
- new Class>[] { networkInterface }, new InvocationHandler() {
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // OH OH! The queue method!
- if (method.equals(queueMethod)) {
- Packet packet = (Packet) args[0];
-
- if (packet != null) {
- packet = handlePacketRecieved(packet);
-
- // A NULL packet indicate cancelling
- if (packet != null)
- args[0] = packet;
- else
- return null;
- }
- }
-
- // Delegate to our underlying class
- try {
- return method.invoke(networkDelegate, args);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- }
- });
-
- // Inject it, if we can.
- networkManagerRef.setValue(networkProxy);
- }
- }
-
- @Override
- public void cleanupAll() {
- // Clean up
- networkManagerRef.revertValue();
- }
-
- @Override
- public boolean canInject() {
- return true;
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector.player;
+
+import java.lang.reflect.InvocationTargetException;
+
+import net.minecraft.server.Packet;
+
+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;
+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.
+ *
+ * @author Kristian
+ */
+class NetworkObjectInjector extends PlayerInjector {
+ // 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();
+
+ if (networkDelegate != null) {
+ try {
+ // Note that invocation target exception is a wrapper for a checked exception
+ queueMethod.invoke(networkDelegate, packet);
+
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw e;
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Unable to access queue method.", e);
+ }
+ } else {
+ throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
+ }
+ }
+
+ @Override
+ public void checkListener(PacketListener listener) {
+ // Unfortunately, we don't support chunk packets
+ if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
+ Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
+ throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
+ }
+ }
+
+ @Override
+ public void injectManager() {
+
+ if (networkManager != null) {
+ final Class> networkInterface = networkManagerField.getType();
+ final Object networkDelegate = networkManagerRef.getOldValue();
+
+ if (!networkInterface.isInterface()) {
+ throw new UnsupportedOperationException(
+ "Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
+ }
+
+ // Create our proxy object
+ Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
+ new Class>[] { networkInterface }, new InvocationHandler() {
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // OH OH! The queue method!
+ if (method.equals(queueMethod)) {
+ Packet packet = (Packet) args[0];
+
+ if (packet != null) {
+ packet = handlePacketRecieved(packet);
+
+ // A NULL packet indicate cancelling
+ if (packet != null)
+ args[0] = packet;
+ else
+ return null;
+ }
+ }
+
+ // Delegate to our underlying class
+ try {
+ return method.invoke(networkDelegate, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+ });
+
+ // Inject it, if we can.
+ networkManagerRef.setValue(networkProxy);
+ }
+ }
+
+ @Override
+ public void cleanupAll() {
+ // Clean up
+ networkManagerRef.revertValue();
+ }
+
+ @Override
+ public boolean canInject(GamePhase phase) {
+ // Works for all phases
+ return true;
+ }
+
+ @Override
+ public PlayerInjectHooks getHookType() {
+ return PlayerInjectHooks.NETWORK_MANAGER_OBJECT;
+ }
+}
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 0daa0786..e08e2749 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
@@ -1,266 +1,274 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-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;
-import net.sf.cglib.proxy.CallbackFilter;
-import net.sf.cglib.proxy.Enhancer;
-import net.sf.cglib.proxy.Factory;
-import net.sf.cglib.proxy.MethodInterceptor;
-import net.sf.cglib.proxy.MethodProxy;
-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;
-import com.comphenix.protocol.reflect.VolatileField;
-import com.comphenix.protocol.reflect.instances.DefaultInstances;
-import com.comphenix.protocol.reflect.instances.ExistingGenerator;
-
-/**
- * Represents a player hook into the NetServerHandler class.
- *
- * @author Kristian
- */
-public class NetworkServerInjector extends PlayerInjector {
-
- private static Method sendPacketMethod;
- private InjectedServerConnection serverInjection;
-
- // 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 boolean hasListener(int packetID) {
- return sendingFilters.contains(packetID);
- }
-
- @Override
- public void initialize() throws IllegalAccessException {
- super.initialize();
-
- // Get the send packet method!
- if (hasInitialized) {
- if (sendPacketMethod == null)
- sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
- }
- }
-
- @Override
- public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
- Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
-
- if (serverDeleage != null) {
- try {
- // Note that invocation target exception is a wrapper for a checked exception
- sendPacketMethod.invoke(serverDeleage, packet);
-
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw e;
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Unable to access send packet method.", e);
- }
- } else {
- throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
- }
- }
-
- @Override
- public void injectManager() {
-
- if (serverHandlerRef == null)
- throw new IllegalStateException("Cannot find server handler.");
- // Don't inject twice
- if (serverHandlerRef.getValue() instanceof Factory)
- return;
-
- if (!tryInjectManager()) {
-
- // Try to override the proxied object
- if (proxyServerField != null) {
- serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
- serverHandler = serverHandlerRef.getValue();
-
- if (serverHandler == null)
- throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
-
- // Try again
- if (tryInjectManager()) {
- // It worked - probably
- return;
- }
- }
-
- throw new RuntimeException(
- "Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
- }
- }
-
- private boolean tryInjectManager() {
- Class> serverClass = serverHandler.getClass();
-
- Enhancer ex = new Enhancer();
- Callback sendPacketCallback = new MethodInterceptor() {
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
-
- Packet packet = (Packet) args[0];
-
- if (packet != null) {
- packet = handlePacketRecieved(packet);
-
- // A NULL packet indicate cancelling
- if (packet != null)
- args[0] = packet;
- else
- return null;
- }
-
- // Call the method directly
- return proxy.invokeSuper(obj, args);
- };
- };
- Callback noOpCallback = NoOp.INSTANCE;
-
- ex.setClassLoader(classLoader);
- ex.setSuperclass(serverClass);
- ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
- ex.setCallbackFilter(new CallbackFilter() {
- @Override
- public int accept(Method method) {
- if (method.equals(sendPacketMethod))
- return 0;
- else
- return 1;
- }
- });
-
- // Find the Minecraft NetServerHandler superclass
- Class> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
- ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
- DefaultInstances serverInstances = null;
-
- // Maybe the proxy instance can help?
- Object proxyInstance = getProxyServerHandler();
-
- // Use the existing server proxy when we create one
- if (proxyInstance != null && proxyInstance != serverHandler) {
- serverInstances = DefaultInstances.fromArray(generator,
- ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
- } else {
- serverInstances = DefaultInstances.fromArray(generator);
- }
-
- serverInstances.setNonNull(true);
- serverInstances.setMaximumRecursion(1);
-
- Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
-
- // Inject it now
- if (proxyObject != null) {
- // This will be done by InjectedServerConnection instead
- //copyTo(serverHandler, proxyObject);
- serverInjection.replaceServerHandler(serverHandler, proxyObject);
- serverHandlerRef.setValue(proxyObject);
- return true;
- } else {
- return false;
- }
- }
-
- private Object getProxyServerHandler() {
- if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
- try {
- return FieldUtils.readField(proxyServerField, serverHandler, true);
- } catch (Throwable e) {
- // Oh well
- }
- }
-
- return null;
- }
-
- private Class> getFirstMinecraftSuperClass(Class> clazz) {
- if (clazz.getName().startsWith("net.minecraft.server."))
- return clazz;
- else if (clazz.equals(Object.class))
- return clazz;
- else
- return getFirstMinecraftSuperClass(clazz.getSuperclass());
- }
-
- @Override
- public void cleanupAll() {
- if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
- ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
- serverHandlerRef.revertValue();
-
- try {
- if (getNetHandler() != null) {
- // Restore packet listener
- try {
- FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
- } catch (IllegalAccessException e) {
- // Oh well
- e.printStackTrace();
- }
- }
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
-
- serverInjection.revertServerHandler(serverHandler);
- }
-
- @Override
- public void checkListener(PacketListener listener) {
- // We support everything
- }
-
- @Override
- public boolean canInject() {
- return true;
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+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;
+import net.sf.cglib.proxy.CallbackFilter;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.Factory;
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+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.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;
+import com.comphenix.protocol.reflect.VolatileField;
+import com.comphenix.protocol.reflect.instances.DefaultInstances;
+import com.comphenix.protocol.reflect.instances.ExistingGenerator;
+
+/**
+ * Represents a player hook into the NetServerHandler class.
+ *
+ * @author Kristian
+ */
+public class NetworkServerInjector extends PlayerInjector {
+
+ private static Method sendPacketMethod;
+ private InjectedServerConnection serverInjection;
+
+ // 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 boolean hasListener(int packetID) {
+ return sendingFilters.contains(packetID);
+ }
+
+ @Override
+ public void initialize() throws IllegalAccessException {
+ super.initialize();
+
+ // Get the send packet method!
+ if (hasInitialized) {
+ if (sendPacketMethod == null)
+ sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
+ }
+ }
+
+ @Override
+ public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
+ Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
+
+ if (serverDeleage != null) {
+ try {
+ // Note that invocation target exception is a wrapper for a checked exception
+ sendPacketMethod.invoke(serverDeleage, packet);
+
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw e;
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Unable to access send packet method.", e);
+ }
+ } else {
+ throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
+ }
+ }
+
+ @Override
+ public void injectManager() {
+
+ if (serverHandlerRef == null)
+ throw new IllegalStateException("Cannot find server handler.");
+ // Don't inject twice
+ if (serverHandlerRef.getValue() instanceof Factory)
+ return;
+
+ if (!tryInjectManager()) {
+
+ // Try to override the proxied object
+ if (proxyServerField != null) {
+ serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
+ serverHandler = serverHandlerRef.getValue();
+
+ if (serverHandler == null)
+ throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
+
+ // Try again
+ if (tryInjectManager()) {
+ // It worked - probably
+ return;
+ }
+ }
+
+ throw new RuntimeException(
+ "Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
+ }
+ }
+
+ private boolean tryInjectManager() {
+ Class> serverClass = serverHandler.getClass();
+
+ Enhancer ex = new Enhancer();
+ Callback sendPacketCallback = new MethodInterceptor() {
+ @Override
+ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
+
+ Packet packet = (Packet) args[0];
+
+ if (packet != null) {
+ packet = handlePacketRecieved(packet);
+
+ // A NULL packet indicate cancelling
+ if (packet != null)
+ args[0] = packet;
+ else
+ return null;
+ }
+
+ // Call the method directly
+ return proxy.invokeSuper(obj, args);
+ };
+ };
+ Callback noOpCallback = NoOp.INSTANCE;
+
+ ex.setClassLoader(classLoader);
+ ex.setSuperclass(serverClass);
+ ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
+ ex.setCallbackFilter(new CallbackFilter() {
+ @Override
+ public int accept(Method method) {
+ if (method.equals(sendPacketMethod))
+ return 0;
+ else
+ return 1;
+ }
+ });
+
+ // Find the Minecraft NetServerHandler superclass
+ Class> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
+ ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
+ DefaultInstances serverInstances = null;
+
+ // Maybe the proxy instance can help?
+ Object proxyInstance = getProxyServerHandler();
+
+ // Use the existing server proxy when we create one
+ if (proxyInstance != null && proxyInstance != serverHandler) {
+ serverInstances = DefaultInstances.fromArray(generator,
+ ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
+ } else {
+ serverInstances = DefaultInstances.fromArray(generator);
+ }
+
+ serverInstances.setNonNull(true);
+ serverInstances.setMaximumRecursion(1);
+
+ Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
+
+ // Inject it now
+ if (proxyObject != null) {
+ // This will be done by InjectedServerConnection instead
+ //copyTo(serverHandler, proxyObject);
+ serverInjection.replaceServerHandler(serverHandler, proxyObject);
+ serverHandlerRef.setValue(proxyObject);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private Object getProxyServerHandler() {
+ if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
+ try {
+ return FieldUtils.readField(proxyServerField, serverHandler, true);
+ } catch (Throwable e) {
+ // Oh well
+ }
+ }
+
+ return null;
+ }
+
+ private Class> getFirstMinecraftSuperClass(Class> clazz) {
+ if (clazz.getName().startsWith("net.minecraft.server."))
+ return clazz;
+ else if (clazz.equals(Object.class))
+ return clazz;
+ else
+ return getFirstMinecraftSuperClass(clazz.getSuperclass());
+ }
+
+ @Override
+ public void cleanupAll() {
+ if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
+ ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
+ serverHandlerRef.revertValue();
+
+ try {
+ if (getNetHandler() != null) {
+ // Restore packet listener
+ try {
+ FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
+ } catch (IllegalAccessException e) {
+ // Oh well
+ e.printStackTrace();
+ }
+ }
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ serverInjection.revertServerHandler(serverHandler);
+ }
+
+ @Override
+ public void checkListener(PacketListener listener) {
+ // We support everything
+ }
+
+ @Override
+ public boolean canInject(GamePhase phase) {
+ // Doesn't work when logging in
+ return phase == GamePhase.PLAYING || phase == GamePhase.CLOSING;
+ }
+
+ @Override
+ public PlayerInjectHooks getHookType() {
+ return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
+ }
+}
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 6bb66dc0..00dc2289 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
@@ -1,327 +1,362 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-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;
-
-/**
- * Responsible for injecting into a player's sendPacket method.
- *
- * @author Kristian
- */
-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 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;
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+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;
+
+/**
+ * Responsible for injecting into a player's sendPacket method.
+ *
+ * @author Kristian
+ */
+public class PlayerInjectionHandler {
+
+ /**
+ * The current player phase.
+ * @author Kristian
+ */
+ public enum GamePhase {
+ LOGIN,
+ PLAYING,
+ CLOSING,
+ }
+
+ // 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);
+ }
+
+ private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
+ return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
+ }
+
+ /**
+ * Initialize a player hook, allowing us to read server packets.
+ * @param player - player to hook.
+ */
+ public void injectPlayer(Player player, GamePhase phase) {
+
+ PlayerInjector injector = playerInjection.get(player);
+ PlayerInjectHooks tempHook = playerHook;
+ PlayerInjectHooks permanentHook = tempHook;
+
+ // See if we need to inject something else
+ boolean invalidInjector = injector != null ? !injector.canInject(phase) : false;
+
+ // Don't inject if the class has closed
+ if (!hasClosed && player != null && (tempHook != getInjectorType(injector) || invalidInjector)) {
+ while (tempHook != PlayerInjectHooks.NONE) {
+ // Whether or not the current hook method failed completely
+ boolean hookFailed = false;
+
+ // Remove the previous hook, if any
+ cleanupHook(injector);
+
+ try {
+ injector = getHookInstance(player, tempHook);
+
+ // Make sure this injection method supports the current game phase
+ if (injector.canInject(phase)) {
+ injector.initialize();
+ injector.injectManager();
+
+ DataInputStream inputStream = injector.getInputStream(false);
+
+ if (!player.isOnline() || inputStream == null) {
+ throw new PlayerLoggedOutException();
+ }
+
+ 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 " + tempHook.toString() + " failed.", e);
+ hookFailed = true;
+ }
+
+ // Choose the previous player hook type
+ tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
+ logger.log(Level.INFO, "Switching to " + tempHook.toString() + " instead.");
+
+ // Check for UTTER FAILURE
+ if (tempHook == PlayerInjectHooks.NONE) {
+ cleanupHook(injector);
+ injector = null;
+ hookFailed = true;
+ }
+
+ // Should we set the default hook method too?
+ if (hookFailed) {
+ permanentHook = tempHook;
+ }
+ }
+
+ // Update values
+ if (injector != null)
+ lastSuccessfulHook = injector;
+ if (permanentHook != playerHook)
+ setPlayerHook(tempHook);
+
+ // Save last injector
+ playerInjection.put(player, injector);
+ }
+ }
+
+ private void cleanupHook(PlayerInjector injector) {
+ // 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.", e2);
+ }
+ }
+
+ /**
+ * 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/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java
index 62e01f33..19f23a84 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
@@ -1,340 +1,348 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-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.logging.Level;
-import java.util.logging.Logger;
-
-import net.minecraft.server.EntityPlayer;
-import net.minecraft.server.Packet;
-import net.sf.cglib.proxy.Factory;
-
-import org.bukkit.craftbukkit.entity.CraftPlayer;
-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;
-import com.comphenix.protocol.reflect.VolatileField;
-
-abstract class PlayerInjector {
-
- // Cache previously retrieved fields
- protected static Field serverHandlerField;
- protected static Field proxyServerField;
-
- protected static Field networkManagerField;
- protected static Field inputField;
- protected static Field netHandlerField;
-
- // Whether or not we're using a proxy type
- private static boolean hasProxyType;
-
- // To add our injected array lists
- protected static StructureModifier networkModifier;
-
- // And methods
- protected static Method queueMethod;
- protected static Method processMethod;
-
- protected Player player;
- protected boolean hasInitialized;
-
- // Reference to the player's network manager
- protected VolatileField networkManagerRef;
- protected VolatileField serverHandlerRef;
- protected Object networkManager;
-
- // Current net handler
- protected Object serverHandler;
- protected Object netHandler;
-
- // The packet manager and filters
- protected ListenerInvoker invoker;
-
- // Previous data input
- protected DataInputStream cachedInput;
-
- // Handle errors
- protected Logger logger;
-
- public PlayerInjector(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException {
- this.logger = logger;
- this.player = player;
- this.invoker = invoker;
- }
-
- /**
- * Retrieve the notch (NMS) entity player object.
- * @return Notch player object.
- */
- protected EntityPlayer getEntityPlayer() {
- CraftPlayer craft = (CraftPlayer) player;
- return craft.getHandle();
- }
-
- /**
- * 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();
-
- if (!hasInitialized) {
- // Do this first, in case we encounter an exception
- hasInitialized = true;
-
- // Retrieve the server handler
- if (serverHandlerField == null) {
- serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
- proxyServerField = getProxyField(notchEntity, serverHandlerField);
- }
-
- // Yo dawg
- serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
- serverHandler = serverHandlerRef.getValue();
-
- // Next, get the network manager
- if (networkManagerField == null)
- networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
- networkManagerRef = new VolatileField(networkManagerField, serverHandler);
- networkManager = networkManagerRef.getValue();
-
- // Create the network manager modifier from the actual object type
- if (networkManager != null && networkModifier == null)
- networkModifier = new StructureModifier(networkManager.getClass(), null, false);
-
- // And the queue method
- if (queueMethod == null)
- queueMethod = FuzzyReflection.fromClass(networkManagerField.getType()).
- getMethodByParameters("queue", Packet.class );
-
- // And the data input stream that we'll use to identify a player
- if (inputField == null)
- inputField = FuzzyReflection.fromObject(networkManager, true).
- getFieldByType("java\\.io\\.DataInputStream");
- }
- }
-
- /**
- * Retrieve whether or not the server handler is a proxy object.
- * @return TRUE if it is, FALSE otherwise.
- */
- protected boolean hasProxyServerHandler() {
- return hasProxyType;
- }
-
- private Field getProxyField(EntityPlayer notchEntity, Field serverField) {
-
- try {
- Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
-
- // Is this a Minecraft hook?
- if (handler != null && !handler.getClass().getName().startsWith("net.minecraft.server")) {
-
- // This is our proxy object
- if (handler instanceof Factory)
- return null;
-
- hasProxyType = true;
- logger.log(Level.WARNING, "Detected server handler proxy type by another plugin. Conflict may occur!");
-
- // No? Is it a Proxy type?
- try {
- FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true);
-
- // It might be
- return reflection.getFieldByType(".*NetServerHandler");
-
- } catch (RuntimeException e) {
- // Damn
- }
- }
-
- } catch (IllegalAccessException e) {
- logger.warning("Unable to load server handler from proxy type.");
- }
-
- // Nope, just go with it
- return null;
- }
-
- /**
- * Retrieves the current net handler for this player.
- * @return Current net handler.
- * @throws IllegalAccessException Unable to find or retrieve net handler.
- */
- protected Object getNetHandler() throws IllegalAccessException {
-
- // What a mess
- try {
- if (netHandlerField == null)
- netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
- getFieldByType("net\\.minecraft\\.NetHandler");
- } catch (RuntimeException e1) {
- // Swallow it
- }
-
- // Second attempt
- if (netHandlerField == null) {
- try {
- // Well, that sucks. Try just Minecraft objects then.
- netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
- getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
-
- } catch (RuntimeException e2) {
- throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
- }
- }
-
- // Get the handler
- if (netHandler == null)
- netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
- return netHandler;
- }
-
- /**
- * Processes the given packet as if it was transmitted by the current player.
- * @param packet - packet to process.
- * @throws IllegalAccessException If the reflection machinery failed.
- * @throws InvocationTargetException If the underlying method caused an error.
- */
- public void processPacket(Packet packet) throws IllegalAccessException, InvocationTargetException {
-
- Object netHandler = getNetHandler();
-
- // Get the process method
- if (processMethod == null) {
- try {
- processMethod = FuzzyReflection.fromClass(Packet.class).
- getMethodByParameters("processPacket", netHandlerField.getType());
- } catch (RuntimeException e) {
- throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage());
- }
- }
-
- // We're ready
- try {
- processMethod.invoke(packet, netHandler);
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Method " + processMethod.getName() + " is not compatible.");
- } catch (InvocationTargetException e) {
- throw e;
- }
- }
-
- /**
- * Send a packet to the client.
- * @param packet - server packet to send.
- * @param filtered - whether or not the packet will be filtered by our listeners.
- * @param InvocationTargetException If an error occured when sending the packet.
- */
- public abstract void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException;
-
- /**
- * Inject a hook to catch packets sent to the current player.
- */
- public abstract void injectManager();
-
- /**
- * Remove all hooks and modifications.
- */
- 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.
- *
- * The player injector should throw an exception if this listener cannot be properly supplied with packet events.
- * @param listener - the listener that is about to be registered.
- */
- public abstract void checkListener(PacketListener listener);
-
- /**
- * Allows a packet to be recieved by the listeners.
- * @param packet - packet to recieve.
- * @return The given packet, or the packet replaced by the listeners.
- */
- public Packet handlePacketRecieved(Packet packet) {
- // Get the packet ID too
- Integer id = invoker.getPacketID(packet);
-
- // Make sure we're listening
- if (id != null && hasListener(id)) {
- // A packet has been sent guys!
- PacketContainer container = new PacketContainer(id, packet);
- PacketEvent event = PacketEvent.fromServer(invoker, container, player);
- invoker.invokePacketSending(event);
-
- // Cancelling is pretty simple. Just ignore the packet.
- if (event.isCancelled())
- return null;
-
- // Right, remember to replace the packet again
- return event.getPacket().getHandle();
- }
-
- 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.
- * @return The player's input stream.
- */
- public DataInputStream getInputStream(boolean cache) {
- if (inputField == null)
- throw new IllegalStateException("Input field is NULL.");
- if (networkManager == null)
- throw new IllegalStateException("Network manager is NULL.");
-
- // Get the associated input stream
- try {
- if (cache && cachedInput != null)
- return cachedInput;
-
- // Save to cache
- cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
- return cachedInput;
-
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Unable to read input stream.", e);
- }
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+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.logging.Level;
+import java.util.logging.Logger;
+
+import net.minecraft.server.EntityPlayer;
+import net.minecraft.server.Packet;
+import net.sf.cglib.proxy.Factory;
+
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+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.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;
+import com.comphenix.protocol.reflect.VolatileField;
+
+abstract class PlayerInjector {
+
+ // Cache previously retrieved fields
+ protected static Field serverHandlerField;
+ protected static Field proxyServerField;
+
+ protected static Field networkManagerField;
+ protected static Field inputField;
+ protected static Field netHandlerField;
+
+ // Whether or not we're using a proxy type
+ private static boolean hasProxyType;
+
+ // To add our injected array lists
+ protected static StructureModifier networkModifier;
+
+ // And methods
+ protected static Method queueMethod;
+ protected static Method processMethod;
+
+ protected Player player;
+ protected boolean hasInitialized;
+
+ // Reference to the player's network manager
+ protected VolatileField networkManagerRef;
+ protected VolatileField serverHandlerRef;
+ protected Object networkManager;
+
+ // Current net handler
+ protected Object serverHandler;
+ protected Object netHandler;
+
+ // The packet manager and filters
+ protected ListenerInvoker invoker;
+
+ // Previous data input
+ protected DataInputStream cachedInput;
+
+ // Handle errors
+ protected Logger logger;
+
+ public PlayerInjector(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException {
+ this.logger = logger;
+ this.player = player;
+ this.invoker = invoker;
+ }
+
+ /**
+ * Retrieve the notch (NMS) entity player object.
+ * @return Notch player object.
+ */
+ protected EntityPlayer getEntityPlayer() {
+ CraftPlayer craft = (CraftPlayer) player;
+ return craft.getHandle();
+ }
+
+ /**
+ * 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();
+
+ if (!hasInitialized) {
+ // Do this first, in case we encounter an exception
+ hasInitialized = true;
+
+ // Retrieve the server handler
+ if (serverHandlerField == null) {
+ serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
+ proxyServerField = getProxyField(notchEntity, serverHandlerField);
+ }
+
+ // Yo dawg
+ serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
+ serverHandler = serverHandlerRef.getValue();
+
+ // Next, get the network manager
+ if (networkManagerField == null)
+ networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
+ networkManagerRef = new VolatileField(networkManagerField, serverHandler);
+ networkManager = networkManagerRef.getValue();
+
+ // Create the network manager modifier from the actual object type
+ if (networkManager != null && networkModifier == null)
+ networkModifier = new StructureModifier(networkManager.getClass(), null, false);
+
+ // And the queue method
+ if (queueMethod == null)
+ queueMethod = FuzzyReflection.fromClass(networkManagerField.getType()).
+ getMethodByParameters("queue", Packet.class );
+
+ // And the data input stream that we'll use to identify a player
+ if (inputField == null)
+ inputField = FuzzyReflection.fromObject(networkManager, true).
+ getFieldByType("java\\.io\\.DataInputStream");
+ }
+ }
+
+ /**
+ * Retrieve whether or not the server handler is a proxy object.
+ * @return TRUE if it is, FALSE otherwise.
+ */
+ protected boolean hasProxyServerHandler() {
+ return hasProxyType;
+ }
+
+ private Field getProxyField(EntityPlayer notchEntity, Field serverField) {
+
+ try {
+ Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
+
+ // Is this a Minecraft hook?
+ if (handler != null && !handler.getClass().getName().startsWith("net.minecraft.server")) {
+
+ // This is our proxy object
+ if (handler instanceof Factory)
+ return null;
+
+ hasProxyType = true;
+ logger.log(Level.WARNING, "Detected server handler proxy type by another plugin. Conflict may occur!");
+
+ // No? Is it a Proxy type?
+ try {
+ FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true);
+
+ // It might be
+ return reflection.getFieldByType(".*NetServerHandler");
+
+ } catch (RuntimeException e) {
+ // Damn
+ }
+ }
+
+ } catch (IllegalAccessException e) {
+ logger.warning("Unable to load server handler from proxy type.");
+ }
+
+ // Nope, just go with it
+ return null;
+ }
+
+ /**
+ * Retrieves the current net handler for this player.
+ * @return Current net handler.
+ * @throws IllegalAccessException Unable to find or retrieve net handler.
+ */
+ protected Object getNetHandler() throws IllegalAccessException {
+
+ // What a mess
+ try {
+ if (netHandlerField == null)
+ netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
+ getFieldByType("net\\.minecraft\\.NetHandler");
+ } catch (RuntimeException e1) {
+ // Swallow it
+ }
+
+ // Second attempt
+ if (netHandlerField == null) {
+ try {
+ // Well, that sucks. Try just Minecraft objects then.
+ netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
+ getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
+
+ } catch (RuntimeException e2) {
+ throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
+ }
+ }
+
+ // Get the handler
+ if (netHandler == null)
+ netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
+ return netHandler;
+ }
+
+ /**
+ * Processes the given packet as if it was transmitted by the current player.
+ * @param packet - packet to process.
+ * @throws IllegalAccessException If the reflection machinery failed.
+ * @throws InvocationTargetException If the underlying method caused an error.
+ */
+ public void processPacket(Packet packet) throws IllegalAccessException, InvocationTargetException {
+
+ Object netHandler = getNetHandler();
+
+ // Get the process method
+ if (processMethod == null) {
+ try {
+ processMethod = FuzzyReflection.fromClass(Packet.class).
+ getMethodByParameters("processPacket", netHandlerField.getType());
+ } catch (RuntimeException e) {
+ throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage());
+ }
+ }
+
+ // We're ready
+ try {
+ processMethod.invoke(packet, netHandler);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Method " + processMethod.getName() + " is not compatible.");
+ } catch (InvocationTargetException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Send a packet to the client.
+ * @param packet - server packet to send.
+ * @param filtered - whether or not the packet will be filtered by our listeners.
+ * @param InvocationTargetException If an error occured when sending the packet.
+ */
+ public abstract void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException;
+
+ /**
+ * Inject a hook to catch packets sent to the current player.
+ */
+ public abstract void injectManager();
+
+ /**
+ * Remove all hooks and modifications.
+ */
+ 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(GamePhase state);
+
+ /**
+ * Retrieve the hook type this class represents.
+ * @return Hook type this class represents.
+ */
+ public abstract PlayerInjectHooks getHookType();
+
+ /**
+ * Invoked before a new listener is registered.
+ *
+ * The player injector should throw an exception if this listener cannot be properly supplied with packet events.
+ * @param listener - the listener that is about to be registered.
+ */
+ public abstract void checkListener(PacketListener listener);
+
+ /**
+ * Allows a packet to be recieved by the listeners.
+ * @param packet - packet to recieve.
+ * @return The given packet, or the packet replaced by the listeners.
+ */
+ public Packet handlePacketRecieved(Packet packet) {
+ // Get the packet ID too
+ Integer id = invoker.getPacketID(packet);
+
+ // Make sure we're listening
+ if (id != null && hasListener(id)) {
+ // A packet has been sent guys!
+ PacketContainer container = new PacketContainer(id, packet);
+ PacketEvent event = PacketEvent.fromServer(invoker, container, player);
+ invoker.invokePacketSending(event);
+
+ // Cancelling is pretty simple. Just ignore the packet.
+ if (event.isCancelled())
+ return null;
+
+ // Right, remember to replace the packet again
+ return event.getPacket().getHandle();
+ }
+
+ 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.
+ * @return The player's input stream.
+ */
+ public DataInputStream getInputStream(boolean cache) {
+ if (inputField == null)
+ throw new IllegalStateException("Input field is NULL.");
+ if (networkManager == null)
+ throw new IllegalStateException("Network manager is NULL.");
+
+ // Get the associated input stream
+ try {
+ if (cache && cachedInput != null)
+ return cachedInput;
+
+ // Save to cache
+ cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
+ return cachedInput;
+
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Unable to read input stream.", e);
+ }
+ }
+}