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); + } + } +}