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 c3a56fce..f1008d6e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -51,8 +51,9 @@ import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.*; -import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.packet.PacketInjector; +import com.comphenix.protocol.injector.packet.InjectorFactory; +import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -180,8 +181,10 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok try { // Initialize injection mangers - this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, packetListeners, server); - this.packetInjector = new PacketInjector(classLoader, this, playerInjection, reporter); + this.playerInjection = new PlayerInjectionHandler( + classLoader, reporter, isInjectionNecessary, this, packetListeners, server); + this.packetInjector = InjectorFactory.getInstance().createProxyInjector( + classLoader, this, playerInjection, reporter); this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this); // Attempt to load the list of server and client packets diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InjectorFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InjectorFactory.java new file mode 100644 index 00000000..e4bc18bc --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InjectorFactory.java @@ -0,0 +1,42 @@ +package com.comphenix.protocol.injector.packet; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.injector.ListenerInvoker; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; + +/** + * A singleton factory for creating incoming packet injectors. + * + * @author Kristian + */ +public class InjectorFactory { + private static final InjectorFactory INSTANCE = new InjectorFactory(); + + private InjectorFactory() { + // No need to construct this + } + + /** + * Retrieve the factory singleton. + * @return Factory singleton. + */ + public static InjectorFactory getInstance() { + return INSTANCE; + } + + /** + * Create a packet injector that intercepts packets by overriding the packet registry. + * @param classLoader - current class loader. + * @param manager - packet invoker. + * @param playerInjection - to lookup Player by DataInputStream. + * @param reporter - error reporter. + * @return A packet injector with these features. + * @throws IllegalAccessException If we fail to create the injector. + */ + public PacketInjector createProxyInjector( + ClassLoader classLoader, ListenerInvoker manager, + PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { + + return new ProxyPacketInjector(classLoader, manager, playerInjection, reporter); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java index 1911907e..4276212c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java @@ -1,257 +1,62 @@ -/* - * 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.packet; -import java.io.DataInputStream; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.bukkit.entity.Player; -import net.sf.cglib.proxy.Callback; -import net.sf.cglib.proxy.Enhancer; - -import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.injector.ListenerInvoker; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.utility.MinecraftReflection; /** - * This class is responsible for adding or removing proxy objects that intercepts recieved packets. + * Represents a incoming packet injector. * * @author Kristian */ -public class PacketInjector { - - // The "put" method that associates a packet ID with a packet class - private static Method putMethod; - private static Object intHashMap; - - // The packet filter manager - private ListenerInvoker manager; - - // Error reporter - private ErrorReporter reporter; - - // Allows us to determine the sender - private PlayerInjectionHandler playerInjection; - - // Allows us to look up read packet injectors - private Map readModifier; - - // Class loader - private ClassLoader classLoader; - - public PacketInjector(ClassLoader classLoader, ListenerInvoker manager, - PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { - - this.classLoader = classLoader; - this.manager = manager; - this.playerInjection = playerInjection; - this.reporter = reporter; - this.readModifier = new ConcurrentHashMap(); - initialize(); - } - +public interface PacketInjector { /** * Undo a packet cancel. * @param id - the id of the packet. * @param packet - packet to uncancel. */ - public void undoCancel(Integer id, Object packet) { - ReadPacketModifier modifier = readModifier.get(id); - - // See if this packet has been cancelled before - if (modifier != null && modifier.hasCancelled(packet)) { - modifier.removeOverride(packet); - } - } - - private void initialize() throws IllegalAccessException { - if (intHashMap == null) { - // We're looking for the first static field with a Minecraft-object. This should be a IntHashMap. - Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true). - getFieldByType(MinecraftReflection.getMinecraftObjectRegex()); - - try { - intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); - } catch (IllegalArgumentException e) { - throw new RuntimeException("Minecraft is incompatible.", e); - } - - // Now, get the "put" method. - putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class); - } - } - - @SuppressWarnings("rawtypes") - public boolean addPacketHandler(int packetID) { - if (hasPacketHandler(packetID)) - return false; - - Enhancer ex = new Enhancer(); - - // Unfortunately, we can't easily distinguish between these two functions: - // * Object lookup(int par1) - // * Object removeObject(int par1) - - // So, we'll use the classMapToInt registry instead. - Map overwritten = PacketRegistry.getOverwrittenPackets(); - Map previous = PacketRegistry.getPreviousPackets(); - Map registry = PacketRegistry.getPacketToID(); - Class old = PacketRegistry.getPacketClassFromID(packetID); - - // If this packet is not known - if (old == null) { - throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version."); - } - // Check for previous injections - if (!MinecraftReflection.isMinecraftClass(old)) { - throw new IllegalStateException("Packet " + packetID + " has already been injected."); - } - - // Subclass the specific packet class - ex.setSuperclass(old); - ex.setCallbackType(ReadPacketModifier.class); - ex.setClassLoader(classLoader); - Class proxy = ex.createClass(); - - // Create the proxy handler - ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter); - readModifier.put(packetID, modifier); - - // Add a static reference - Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier }); - - try { - // Override values - previous.put(packetID, old); - registry.put(proxy, packetID); - overwritten.put(packetID, proxy); - putMethod.invoke(intHashMap, packetID, proxy); - return true; - - } catch (IllegalArgumentException e) { - throw new RuntimeException("Illegal argument.", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access method.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Exception occured in IntHashMap.put.", e); - } - } - - @SuppressWarnings("rawtypes") - public boolean removePacketHandler(int packetID) { - if (!hasPacketHandler(packetID)) - return false; - - Map registry = PacketRegistry.getPacketToID(); - Map previous = PacketRegistry.getPreviousPackets(); - Map overwritten = PacketRegistry.getOverwrittenPackets(); - - // Use the old class definition - try { - Class old = previous.get(packetID); - Class proxy = PacketRegistry.getPacketClassFromID(packetID); - - putMethod.invoke(intHashMap, packetID, old); - previous.remove(packetID); - readModifier.remove(packetID); - registry.remove(proxy); - overwritten.remove(packetID); - return true; - - // Handle some problems - } catch (IllegalArgumentException e) { - return false; - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access method.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Exception occured in IntHashMap.put.", e); - } - } - - public boolean hasPacketHandler(int packetID) { - return PacketRegistry.getPreviousPackets().containsKey(packetID); - } - - public Set getPacketHandlers() { - return PacketRegistry.getPreviousPackets().keySet(); - } - - // Called from the ReadPacketModified monitor - public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { - try { - Player client = playerInjection.getPlayerByConnection(input); - - // Never invoke a event if we don't know where it's from - if (client != null) - return packetRecieved(packet, client); - else - return null; - - } catch (InterruptedException e) { - // We will ignore this - it occurs when a player disconnects - //reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input); - return null; - } - } - + public abstract void undoCancel(Integer id, Object packet); + + /** + * Start intercepting packets with the given packet ID. + * @param packetID - the ID of the packets to start intercepting. + * @return TRUE if we didn't already intercept these packets, FALSE otherwise. + */ + public abstract boolean addPacketHandler(int packetID); + + /** + * Stop intercepting packets with the given packet ID. + * @param packetID - the ID of the packets to stop intercepting. + * @return TRUE if we successfuly stopped intercepting a given packet ID, FALSE otherwise. + */ + public abstract boolean removePacketHandler(int packetID); + + /** + * Determine if packets with the given packet ID is being intercepted. + * @param packetID - the packet ID to lookup. + * @return TRUE if we do, FALSE otherwise. + */ + public abstract boolean hasPacketHandler(int packetID); + + /** + * Retrieve every intercepted packet ID. + * @return Every intercepted packet ID. + */ + public abstract Set getPacketHandlers(); + /** * Let the packet listeners process the given packet. * @param packet - a packet to process. * @param client - the client that sent the packet. * @return The resulting packet event. */ - public PacketEvent packetRecieved(PacketContainer packet, Player client) { - PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client); - - manager.invokePacketRecieving(event); - return event; - } - - @SuppressWarnings("rawtypes") - public synchronized void cleanupAll() { - Map overwritten = PacketRegistry.getOverwrittenPackets(); - Map previous = PacketRegistry.getPreviousPackets(); - - // Remove every packet handler - for (Integer id : previous.keySet().toArray(new Integer[0])) { - removePacketHandler(id); - } - - overwritten.clear(); - previous.clear(); - } + public abstract PacketEvent packetRecieved(PacketContainer packet, Player client); /** - * Inform the current PlayerInjector that it should update the DataInputStream next. - * @param player - the player to update. + * Perform any necessary cleanup before unloading ProtocolLib. */ - public void scheduleDataInputRefresh(Player player) { - playerInjection.scheduleDataInputRefresh(player); - } -} + public abstract void cleanupAll(); +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java new file mode 100644 index 00000000..bd3274a8 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -0,0 +1,264 @@ +/* + * 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.packet; + +import java.io.DataInputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.bukkit.entity.Player; + +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.Enhancer; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.ListenerInvoker; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftReflection; + +/** + * This class is responsible for adding or removing proxy objects that intercepts recieved packets. + * + * @author Kristian + */ +class ProxyPacketInjector implements PacketInjector { + + // The "put" method that associates a packet ID with a packet class + private static Method putMethod; + private static Object intHashMap; + + // The packet filter manager + private ListenerInvoker manager; + + // Error reporter + private ErrorReporter reporter; + + // Allows us to determine the sender + private PlayerInjectionHandler playerInjection; + + // Allows us to look up read packet injectors + private Map readModifier; + + // Class loader + private ClassLoader classLoader; + + public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager, + PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { + + this.classLoader = classLoader; + this.manager = manager; + this.playerInjection = playerInjection; + this.reporter = reporter; + this.readModifier = new ConcurrentHashMap(); + initialize(); + } + + /** + * Undo a packet cancel. + * @param id - the id of the packet. + * @param packet - packet to uncancel. + */ + @Override + public void undoCancel(Integer id, Object packet) { + ReadPacketModifier modifier = readModifier.get(id); + + // See if this packet has been cancelled before + if (modifier != null && modifier.hasCancelled(packet)) { + modifier.removeOverride(packet); + } + } + + private void initialize() throws IllegalAccessException { + if (intHashMap == null) { + // We're looking for the first static field with a Minecraft-object. This should be a IntHashMap. + Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true). + getFieldByType(MinecraftReflection.getMinecraftObjectRegex()); + + try { + intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Minecraft is incompatible.", e); + } + + // Now, get the "put" method. + putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class); + } + } + + @Override + @SuppressWarnings("rawtypes") + public boolean addPacketHandler(int packetID) { + if (hasPacketHandler(packetID)) + return false; + + Enhancer ex = new Enhancer(); + + // Unfortunately, we can't easily distinguish between these two functions: + // * Object lookup(int par1) + // * Object removeObject(int par1) + + // So, we'll use the classMapToInt registry instead. + Map overwritten = PacketRegistry.getOverwrittenPackets(); + Map previous = PacketRegistry.getPreviousPackets(); + Map registry = PacketRegistry.getPacketToID(); + Class old = PacketRegistry.getPacketClassFromID(packetID); + + // If this packet is not known + if (old == null) { + throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version."); + } + // Check for previous injections + if (!MinecraftReflection.isMinecraftClass(old)) { + throw new IllegalStateException("Packet " + packetID + " has already been injected."); + } + + // Subclass the specific packet class + ex.setSuperclass(old); + ex.setCallbackType(ReadPacketModifier.class); + ex.setClassLoader(classLoader); + Class proxy = ex.createClass(); + + // Create the proxy handler + ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter); + readModifier.put(packetID, modifier); + + // Add a static reference + Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier }); + + try { + // Override values + previous.put(packetID, old); + registry.put(proxy, packetID); + overwritten.put(packetID, proxy); + putMethod.invoke(intHashMap, packetID, proxy); + return true; + + } catch (IllegalArgumentException e) { + throw new RuntimeException("Illegal argument.", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access method.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Exception occured in IntHashMap.put.", e); + } + } + + @Override + @SuppressWarnings("rawtypes") + public boolean removePacketHandler(int packetID) { + if (!hasPacketHandler(packetID)) + return false; + + Map registry = PacketRegistry.getPacketToID(); + Map previous = PacketRegistry.getPreviousPackets(); + Map overwritten = PacketRegistry.getOverwrittenPackets(); + + // Use the old class definition + try { + Class old = previous.get(packetID); + Class proxy = PacketRegistry.getPacketClassFromID(packetID); + + putMethod.invoke(intHashMap, packetID, old); + previous.remove(packetID); + readModifier.remove(packetID); + registry.remove(proxy); + overwritten.remove(packetID); + return true; + + // Handle some problems + } catch (IllegalArgumentException e) { + return false; + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access method.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Exception occured in IntHashMap.put.", e); + } + } + + @Override + public boolean hasPacketHandler(int packetID) { + return PacketRegistry.getPreviousPackets().containsKey(packetID); + } + + @Override + public Set getPacketHandlers() { + return PacketRegistry.getPreviousPackets().keySet(); + } + + // Called from the ReadPacketModified monitor + public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { + try { + Player client = playerInjection.getPlayerByConnection(input); + + // Never invoke a event if we don't know where it's from + if (client != null) + return packetRecieved(packet, client); + else + return null; + + } catch (InterruptedException e) { + // We will ignore this - it occurs when a player disconnects + //reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input); + return null; + } + } + + /** + * Let the packet listeners process the given packet. + * @param packet - a packet to process. + * @param client - the client that sent the packet. + * @return The resulting packet event. + */ + @Override + public PacketEvent packetRecieved(PacketContainer packet, Player client) { + PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client); + + manager.invokePacketRecieving(event); + return event; + } + + @Override + @SuppressWarnings("rawtypes") + public synchronized void cleanupAll() { + Map overwritten = PacketRegistry.getOverwrittenPackets(); + Map previous = PacketRegistry.getPreviousPackets(); + + // Remove every packet handler + for (Integer id : previous.keySet().toArray(new Integer[0])) { + removePacketHandler(id); + } + + overwritten.clear(); + previous.clear(); + } + + /** + * Inform the current PlayerInjector that it should update the DataInputStream next. + * @param player - the player to update. + */ + public void scheduleDataInputRefresh(Player player) { + playerInjection.scheduleDataInputRefresh(player); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java index 3c7b897a..00eacf9c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java @@ -41,7 +41,7 @@ class ReadPacketModifier implements MethodInterceptor { private static final Object CANCEL_MARKER = new Object(); // Common for all packets of the same type - private PacketInjector packetInjector; + private ProxyPacketInjector packetInjector; private int packetID; // Report errors @@ -50,7 +50,7 @@ class ReadPacketModifier implements MethodInterceptor { // Whether or not a packet has been cancelled private static Map override = Collections.synchronizedMap(new WeakHashMap()); - public ReadPacketModifier(int packetID, PacketInjector packetInjector, ErrorReporter reporter) { + public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter) { this.packetID = packetID; this.packetInjector = packetInjector; this.reporter = reporter;