From 77346f8438f5b6a47bc23d02d30184bafd89579d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 31 May 2013 22:57:00 +0200 Subject: [PATCH] Fixed incorrect detection of "custom" net handlers. This should ensure that users on MCPC no longer recieves the REPORT_DETECTED_CUSTOM_SERVER_HANDLER warning just for using MCPC. --- ProtocolLib/.project | 4 +- .../injector/player/PlayerInjector.java | 1345 +++++++++-------- 2 files changed, 683 insertions(+), 666 deletions(-) diff --git a/ProtocolLib/.project b/ProtocolLib/.project index d36fcbe8..a1960c9e 100644 --- a/ProtocolLib/.project +++ b/ProtocolLib/.project @@ -11,12 +11,12 @@ - net.sourceforge.metrics.builder + org.eclipse.m2e.core.maven2Builder - org.eclipse.m2e.core.maven2Builder + net.sourceforge.metrics.builder 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 02036a07..f7278287 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,664 +1,681 @@ -/* - * 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.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.Socket; -import java.net.SocketAddress; -import net.sf.cglib.proxy.Factory; - -import org.bukkit.entity.Player; - -import com.comphenix.protocol.Packets; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.events.PacketListener; -import com.comphenix.protocol.injector.BukkitUnwrapper; -import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.ListenerInvoker; -import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; -import com.comphenix.protocol.injector.server.SocketInjector; -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.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.utility.MinecraftVersion; - -public abstract class PlayerInjector implements SocketInjector { - // Disconnect method related reports - public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s."); - public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s"); - public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method."); - - public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket."); - public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket."); - - public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER = - new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!"); - public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type."); - - public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent."); - public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet."); - - // Net login handler stuff - private static Field netLoginNetworkField; - - // Different disconnect methods - private static Method loginDisconnect; - private static Method serverDisconnect; - - // Cache previously retrieved fields - protected static Field serverHandlerField; - protected static Field proxyServerField; - - protected static Field networkManagerField; - protected static Field netHandlerField; - protected static Field socketField; - protected static Field socketAddressField; - - private static Field inputField; - private static Field entityPlayerField; - - // 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 loginHandler; - protected Object serverHandler; - protected Object netHandler; - - // Current socket and address - protected Socket socket; - protected SocketAddress socketAddress; - - // The packet manager and filters - protected ListenerInvoker invoker; - - // Previous data input - protected DataInputStream cachedInput; - - // Handle errors - protected ErrorReporter reporter; - - // Whether or not the injector has been cleaned - private boolean clean; - - // Whether or not to update the current player on the first Packet1Login - boolean updateOnLogin; - Player updatedPlayer; - - public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) throws IllegalAccessException { - this.reporter = reporter; - this.player = player; - this.invoker = invoker; - } - - /** - * Retrieve the notch (NMS) entity player object. - * @param player - the player to retrieve. - * @return Notch player object. - */ - protected Object getEntityPlayer(Player player) { - BukkitUnwrapper unwrapper = new BukkitUnwrapper(); - return unwrapper.unwrapItem(player); - } - - /** - * Initialize all fields for this player injector, if it hasn't already. - * @throws IllegalAccessException An error has occured. - */ - public void initialize(Object injectionSource) throws IllegalAccessException { - if (injectionSource == null) - throw new IllegalArgumentException("injectionSource cannot be NULL"); - - //Dispatch to the correct injection method - if (injectionSource instanceof Player) - initializePlayer((Player) injectionSource); - else if (MinecraftReflection.isLoginHandler(injectionSource)) - initializeLogin(injectionSource); - else - throw new IllegalArgumentException("Cannot initialize a player hook using a " + injectionSource.getClass().getName()); - } - - /** - * Initialize the player injector using an actual player instance. - * @param player - the player to hook. - */ - public void initializePlayer(Player player) { - Object notchEntity = getEntityPlayer((Player) player); - - // Save the player too - this.player = player; - - 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", MinecraftReflection.getNetServerHandlerClass()); - 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", MinecraftReflection.getNetworkManagerClass()); - initializeNetworkManager(networkManagerField, serverHandler); - } - } - - /** - * Initialize the player injector from a NetLoginHandler. - * @param netLoginHandler - the net login handler to inject. - */ - public void initializeLogin(Object netLoginHandler) { - if (!hasInitialized) { - // Just in case - if (!MinecraftReflection.isLoginHandler(netLoginHandler)) - throw new IllegalArgumentException("netLoginHandler (" + netLoginHandler + ") is not a " + - MinecraftReflection.getNetLoginHandlerName()); - - hasInitialized = true; - loginHandler = netLoginHandler; - - if (netLoginNetworkField == null) - netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler). - getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass()); - initializeNetworkManager(netLoginNetworkField, netLoginHandler); - } - } - - private void initializeNetworkManager(Field reference, Object container) { - networkManagerRef = new VolatileField(reference, container); - networkManager = networkManagerRef.getValue(); - - // No, don't do it - if (networkManager instanceof Factory) { - return; - } - - // 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(reference.getType()). - getMethodByParameters("queue", MinecraftReflection.getPacketClass()); - } - - /** - * Retrieve whether or not the server handler is a proxy object. - * @return TRUE if it is, FALSE otherwise. - */ - protected boolean hasProxyServerHandler() { - return hasProxyType; - } - - /** - * Retrieve the current network manager. - * @return Current network manager. - */ - public Object getNetworkManager() { - return networkManagerRef.getValue(); - } - - /** - * Set the current network manager. - * @param value - new network manager. - * @param force - whether or not to save this value. - */ - public void setNetworkManager(Object value, boolean force) { - networkManagerRef.setValue(value); - - if (force) - networkManagerRef.saveValue(); - initializeNetworkManager(networkManagerField, serverHandler); - } - - /** - * Retrieve the associated socket of this player. - * @return The associated socket. - * @throws IllegalAccessException If we're unable to read the socket field. - */ - @Override - public Socket getSocket() throws IllegalAccessException { - try { - if (socketField == null) - socketField = FuzzyReflection.fromObject(networkManager, true). - getFieldListByType(Socket.class).get(0); - if (socket == null) - socket = (Socket) FieldUtils.readField(socketField, networkManager, true); - return socket; - - } catch (IndexOutOfBoundsException e) { - throw new IllegalAccessException("Unable to read the socket field."); - } - } - - /** - * Retrieve the associated remote address of a player. - * @return The associated remote address.. - * @throws IllegalAccessException If we're unable to read the socket address field. - */ - @Override - public SocketAddress getAddress() throws IllegalAccessException { - try { - if (socketAddressField == null) - socketAddressField = FuzzyReflection.fromObject(networkManager, true). - getFieldListByType(SocketAddress.class).get(0); - if (socketAddress == null) - socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true); - return socketAddress; - - } catch (IndexOutOfBoundsException e) { - throw new IllegalAccessException("Unable to read the socket address field."); - } - } - - /** - * Attempt to disconnect the current client. - * @param message - the message to display. - * @throws InvocationTargetException If disconnection failed. - */ - @Override - public void disconnect(String message) throws InvocationTargetException { - // Get a non-null handler - boolean usingNetServer = serverHandler != null; - - Object handler = usingNetServer ? serverHandler : loginHandler; - Method disconnect = usingNetServer ? serverDisconnect : loginDisconnect; - - // Execute disconnect on it - if (handler != null) { - if (disconnect == null) { - try { - disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*"); - } catch (IllegalArgumentException e) { - // Just assume it's the first String method - disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class); - reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUME_DISCONNECT_METHOD).messageParam(disconnect)); - } - - // Save the method for later - if (usingNetServer) - serverDisconnect = disconnect; - else - loginDisconnect = disconnect; - } - - try { - disconnect.invoke(handler, message); - return; - } catch (IllegalArgumentException e) { - reporter.reportDetailed(this, Report.newBuilder(REPORT_INVALID_ARGUMENT_DISCONNECT).error(e).messageParam(message).callerParam(handler)); - } catch (IllegalAccessException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_ACCESS_DISCONNECT).error(e)); - } - } - - // Fuck it - try { - Socket socket = getSocket(); - - try { - socket.close(); - } catch (IOException e) { - reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CLOSE_SOCKET).error(e).callerParam(socket)); - } - - } catch (IllegalAccessException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_ACCESS_DENIED_CLOSE_SOCKET).error(e)); - } - } - - private Field getProxyField(Object notchEntity, Field serverField) { - try { - Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true); - - // Is this a Minecraft hook? - if (handler != null && !MinecraftReflection.isMinecraftObject(handler)) { - - // This is our proxy object - if (handler instanceof Factory) - return null; - - hasProxyType = true; - reporter.reportWarning(this, Report.newBuilder(REPORT_DETECTED_CUSTOM_SERVER_HANDLER).callerParam(serverField)); - - // No? Is it a Proxy type? - try { - FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true); - - // It might be - return reflection.getFieldByType("NetServerHandler", MinecraftReflection.getNetServerHandlerClass()); - - } catch (RuntimeException e) { - // Damn - } - } - - } catch (IllegalAccessException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PROXY_SERVER_HANDLER).error(e).callerParam(notchEntity, serverField)); - } - - // 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("NetHandler", MinecraftReflection.getNetHandlerClass()); - } 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(MinecraftReflection.getMinecraftObjectRegex()); - - } 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; - } - - /** - * Retrieve the stored entity player from a given NetHandler. - * @param netHandler - the nethandler to retrieve it from. - * @return The stored entity player. - * @throws IllegalAccessException If the reflection failed. - */ - private Object getEntityPlayer(Object netHandler) throws IllegalAccessException { - if (entityPlayerField == null) - entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType( - "EntityPlayer", MinecraftReflection.getEntityPlayerClass()); - return FieldUtils.readField(entityPlayerField, 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(Object packet) throws IllegalAccessException, InvocationTargetException { - - Object netHandler = getNetHandler(); - - // Get the process method - if (processMethod == null) { - try { - processMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). - 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. - */ - @Override - public abstract void sendServerPacket(Object 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 final void cleanupAll() { - if (!clean) - cleanHook(); - clean = true; - } - - /** - * Clean up after the player has disconnected. - */ - public abstract void handleDisconnect(); - - /** - * Override to add custom cleanup behavior. - */ - protected abstract void cleanHook(); - - /** - * Determine whether or not this hook has already been cleaned. - * @return TRUE if it has, FALSE otherwise. - */ - public boolean isClean() { - return clean; - } - - /** - * 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 only return a non-null value if some or all of the packet IDs are unsupported. - * @param version - * - * @param version - the current Minecraft version, or NULL if unknown. - * @param listener - the listener that is about to be registered. - * @return A error message with the unsupported packet IDs, or NULL if this listener is valid. - */ - public abstract UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener); - - /** - * Allows a packet to be sent by the listeners. - * @param packet - packet to sent. - * @return The given packet, or the packet replaced by the listeners. - */ - public Object handlePacketSending(Object packet) { - try { - // Get the packet ID too - Integer id = invoker.getPacketID(packet); - Player currentPlayer = player; - - // Hack #1 - if (updateOnLogin) { - if (id == Packets.Server.LOGIN) { - try { - updatedPlayer = (Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(getNetHandler())); - } catch (IllegalAccessException e) { - reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLAYER).error(e).callerParam(packet)); - } - } - - // This will only occur in the NetLoginHandler injection - if (updatedPlayer != null) - currentPlayer = updatedPlayer; - } - - // 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, currentPlayer); - 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(); - } - - } catch (Throwable e) { - reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet)); - } - - 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) { - // And the data input stream that we'll use to identify a player - if (networkManager == null) - throw new IllegalStateException("Network manager is NULL."); - if (inputField == null) - inputField = FuzzyReflection.fromObject(networkManager, true). - getFieldByType("java\\.io\\.DataInputStream"); - - // 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); - } - } - - /** - * Retrieve the hooked player. - */ - @Override - public Player getPlayer() { - return player; - } - - /** - * Set the hooked player. - *

- * Should only be called during the creation of the injector. - * @param player - the new hooked player. - */ - public void setPlayer(Player player) { - this.player = player; - } - - /** - * Object that can invoke the packet events. - * @return Packet event invoker. - */ - public ListenerInvoker getInvoker() { - return invoker; - } - - /** - * Retrieve the hooked player object OR the more up-to-date player instance. - * @return The hooked player, or a more up-to-date instance. - */ - @Override - public Player getUpdatedPlayer() { - if (updatedPlayer != null) - return updatedPlayer; - else - return player; - } - - @Override - public void transferState(SocketInjector delegate) { - // Do nothing - } - - @Override - public void setUpdatedPlayer(Player updatedPlayer) { - this.updatedPlayer = updatedPlayer; - } -} +/* + * 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.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.Socket; +import java.net.SocketAddress; +import net.sf.cglib.proxy.Factory; + +import org.bukkit.entity.Player; + +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.injector.GamePhase; +import com.comphenix.protocol.injector.ListenerInvoker; +import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.comphenix.protocol.injector.server.SocketInjector; +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.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; + +public abstract class PlayerInjector implements SocketInjector { + // Disconnect method related reports + public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s."); + public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s"); + public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method."); + + public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket."); + public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket."); + + public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER = + new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!"); + public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type."); + + public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent."); + public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet."); + + // Net login handler stuff + private static Field netLoginNetworkField; + + // Different disconnect methods + private static Method loginDisconnect; + private static Method serverDisconnect; + + // Cache previously retrieved fields + protected static Field serverHandlerField; + protected static Field proxyServerField; + + protected static Field networkManagerField; + protected static Field netHandlerField; + protected static Field socketField; + protected static Field socketAddressField; + + private static Field inputField; + private static Field entityPlayerField; + + // 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 loginHandler; + protected Object serverHandler; + protected Object netHandler; + + // Current socket and address + protected Socket socket; + protected SocketAddress socketAddress; + + // The packet manager and filters + protected ListenerInvoker invoker; + + // Previous data input + protected DataInputStream cachedInput; + + // Handle errors + protected ErrorReporter reporter; + + // Whether or not the injector has been cleaned + private boolean clean; + + // Whether or not to update the current player on the first Packet1Login + boolean updateOnLogin; + Player updatedPlayer; + + public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) throws IllegalAccessException { + this.reporter = reporter; + this.player = player; + this.invoker = invoker; + } + + /** + * Retrieve the notch (NMS) entity player object. + * @param player - the player to retrieve. + * @return Notch player object. + */ + protected Object getEntityPlayer(Player player) { + BukkitUnwrapper unwrapper = new BukkitUnwrapper(); + return unwrapper.unwrapItem(player); + } + + /** + * Initialize all fields for this player injector, if it hasn't already. + * @throws IllegalAccessException An error has occured. + */ + public void initialize(Object injectionSource) throws IllegalAccessException { + if (injectionSource == null) + throw new IllegalArgumentException("injectionSource cannot be NULL"); + + //Dispatch to the correct injection method + if (injectionSource instanceof Player) + initializePlayer((Player) injectionSource); + else if (MinecraftReflection.isLoginHandler(injectionSource)) + initializeLogin(injectionSource); + else + throw new IllegalArgumentException("Cannot initialize a player hook using a " + injectionSource.getClass().getName()); + } + + /** + * Initialize the player injector using an actual player instance. + * @param player - the player to hook. + */ + public void initializePlayer(Player player) { + Object notchEntity = getEntityPlayer((Player) player); + + // Save the player too + this.player = player; + + 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", MinecraftReflection.getNetServerHandlerClass()); + 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", MinecraftReflection.getNetworkManagerClass()); + initializeNetworkManager(networkManagerField, serverHandler); + } + } + + /** + * Initialize the player injector from a NetLoginHandler. + * @param netLoginHandler - the net login handler to inject. + */ + public void initializeLogin(Object netLoginHandler) { + if (!hasInitialized) { + // Just in case + if (!MinecraftReflection.isLoginHandler(netLoginHandler)) + throw new IllegalArgumentException("netLoginHandler (" + netLoginHandler + ") is not a " + + MinecraftReflection.getNetLoginHandlerName()); + + hasInitialized = true; + loginHandler = netLoginHandler; + + if (netLoginNetworkField == null) + netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler). + getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass()); + initializeNetworkManager(netLoginNetworkField, netLoginHandler); + } + } + + private void initializeNetworkManager(Field reference, Object container) { + networkManagerRef = new VolatileField(reference, container); + networkManager = networkManagerRef.getValue(); + + // No, don't do it + if (networkManager instanceof Factory) { + return; + } + + // 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(reference.getType()). + getMethodByParameters("queue", MinecraftReflection.getPacketClass()); + } + + /** + * Retrieve whether or not the server handler is a proxy object. + * @return TRUE if it is, FALSE otherwise. + */ + protected boolean hasProxyServerHandler() { + return hasProxyType; + } + + /** + * Retrieve the current network manager. + * @return Current network manager. + */ + public Object getNetworkManager() { + return networkManagerRef.getValue(); + } + + /** + * Set the current network manager. + * @param value - new network manager. + * @param force - whether or not to save this value. + */ + public void setNetworkManager(Object value, boolean force) { + networkManagerRef.setValue(value); + + if (force) + networkManagerRef.saveValue(); + initializeNetworkManager(networkManagerField, serverHandler); + } + + /** + * Retrieve the associated socket of this player. + * @return The associated socket. + * @throws IllegalAccessException If we're unable to read the socket field. + */ + @Override + public Socket getSocket() throws IllegalAccessException { + try { + if (socketField == null) + socketField = FuzzyReflection.fromObject(networkManager, true). + getFieldListByType(Socket.class).get(0); + if (socket == null) + socket = (Socket) FieldUtils.readField(socketField, networkManager, true); + return socket; + + } catch (IndexOutOfBoundsException e) { + throw new IllegalAccessException("Unable to read the socket field."); + } + } + + /** + * Retrieve the associated remote address of a player. + * @return The associated remote address.. + * @throws IllegalAccessException If we're unable to read the socket address field. + */ + @Override + public SocketAddress getAddress() throws IllegalAccessException { + try { + if (socketAddressField == null) + socketAddressField = FuzzyReflection.fromObject(networkManager, true). + getFieldListByType(SocketAddress.class).get(0); + if (socketAddress == null) + socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true); + return socketAddress; + + } catch (IndexOutOfBoundsException e) { + throw new IllegalAccessException("Unable to read the socket address field."); + } + } + + /** + * Attempt to disconnect the current client. + * @param message - the message to display. + * @throws InvocationTargetException If disconnection failed. + */ + @Override + public void disconnect(String message) throws InvocationTargetException { + // Get a non-null handler + boolean usingNetServer = serverHandler != null; + + Object handler = usingNetServer ? serverHandler : loginHandler; + Method disconnect = usingNetServer ? serverDisconnect : loginDisconnect; + + // Execute disconnect on it + if (handler != null) { + if (disconnect == null) { + try { + disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*"); + } catch (IllegalArgumentException e) { + // Just assume it's the first String method + disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class); + reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUME_DISCONNECT_METHOD).messageParam(disconnect)); + } + + // Save the method for later + if (usingNetServer) + serverDisconnect = disconnect; + else + loginDisconnect = disconnect; + } + + try { + disconnect.invoke(handler, message); + return; + } catch (IllegalArgumentException e) { + reporter.reportDetailed(this, Report.newBuilder(REPORT_INVALID_ARGUMENT_DISCONNECT).error(e).messageParam(message).callerParam(handler)); + } catch (IllegalAccessException e) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_ACCESS_DISCONNECT).error(e)); + } + } + + // Fuck it + try { + Socket socket = getSocket(); + + try { + socket.close(); + } catch (IOException e) { + reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CLOSE_SOCKET).error(e).callerParam(socket)); + } + + } catch (IllegalAccessException e) { + reporter.reportWarning(this, Report.newBuilder(REPORT_ACCESS_DENIED_CLOSE_SOCKET).error(e)); + } + } + + private Field getProxyField(Object notchEntity, Field serverField) { + try { + Object currentHandler = FieldUtils.readField(serverHandlerField, notchEntity, true); + + // This is bad + if (currentHandler == null) + throw new IllegalAccessError("Unable to fetch server handler: was NUll."); + + // See if this isn't a standard net handler class + if (!isStandardMinecraftNetHandler(currentHandler)) { + // This is our proxy object + if (currentHandler instanceof Factory) + return null; + + hasProxyType = true; + reporter.reportWarning(this, Report.newBuilder(REPORT_DETECTED_CUSTOM_SERVER_HANDLER).callerParam(serverField)); + + // No? Is it a Proxy type? + try { + FuzzyReflection reflection = FuzzyReflection.fromObject(currentHandler, true); + + // It might be + return reflection.getFieldByType("NetServerHandler", MinecraftReflection.getNetServerHandlerClass()); + + } catch (RuntimeException e) { + // Damn + } + } + + } catch (IllegalAccessException e) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PROXY_SERVER_HANDLER).error(e).callerParam(notchEntity, serverField)); + } + + // Nope, just go with it + return null; + } + + /** + * Determine if a given object is a standard Minecraft net handler. + * @param obj the object to test. + * @return TRUE if it is, FALSE otherwise. + */ + private boolean isStandardMinecraftNetHandler(Object obj) { + if (obj == null) + return false; + Class clazz = obj.getClass(); + + return MinecraftReflection.getNetLoginHandlerClass().equals(clazz) || + MinecraftReflection.getNetServerHandlerClass().equals(clazz); + } + + /** + * 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("NetHandler", MinecraftReflection.getNetHandlerClass()); + } 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(MinecraftReflection.getMinecraftObjectRegex()); + + } 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; + } + + /** + * Retrieve the stored entity player from a given NetHandler. + * @param netHandler - the nethandler to retrieve it from. + * @return The stored entity player. + * @throws IllegalAccessException If the reflection failed. + */ + private Object getEntityPlayer(Object netHandler) throws IllegalAccessException { + if (entityPlayerField == null) + entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType( + "EntityPlayer", MinecraftReflection.getEntityPlayerClass()); + return FieldUtils.readField(entityPlayerField, 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(Object packet) throws IllegalAccessException, InvocationTargetException { + + Object netHandler = getNetHandler(); + + // Get the process method + if (processMethod == null) { + try { + processMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). + 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. + */ + @Override + public abstract void sendServerPacket(Object 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 final void cleanupAll() { + if (!clean) + cleanHook(); + clean = true; + } + + /** + * Clean up after the player has disconnected. + */ + public abstract void handleDisconnect(); + + /** + * Override to add custom cleanup behavior. + */ + protected abstract void cleanHook(); + + /** + * Determine whether or not this hook has already been cleaned. + * @return TRUE if it has, FALSE otherwise. + */ + public boolean isClean() { + return clean; + } + + /** + * 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 only return a non-null value if some or all of the packet IDs are unsupported. + * @param version + * + * @param version - the current Minecraft version, or NULL if unknown. + * @param listener - the listener that is about to be registered. + * @return A error message with the unsupported packet IDs, or NULL if this listener is valid. + */ + public abstract UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener); + + /** + * Allows a packet to be sent by the listeners. + * @param packet - packet to sent. + * @return The given packet, or the packet replaced by the listeners. + */ + public Object handlePacketSending(Object packet) { + try { + // Get the packet ID too + Integer id = invoker.getPacketID(packet); + Player currentPlayer = player; + + // Hack #1 + if (updateOnLogin) { + if (id == Packets.Server.LOGIN) { + try { + updatedPlayer = (Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(getNetHandler())); + } catch (IllegalAccessException e) { + reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLAYER).error(e).callerParam(packet)); + } + } + + // This will only occur in the NetLoginHandler injection + if (updatedPlayer != null) + currentPlayer = updatedPlayer; + } + + // 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, currentPlayer); + 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(); + } + + } catch (Throwable e) { + reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet)); + } + + 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) { + // And the data input stream that we'll use to identify a player + if (networkManager == null) + throw new IllegalStateException("Network manager is NULL."); + if (inputField == null) + inputField = FuzzyReflection.fromObject(networkManager, true). + getFieldByType("java\\.io\\.DataInputStream"); + + // 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); + } + } + + /** + * Retrieve the hooked player. + */ + @Override + public Player getPlayer() { + return player; + } + + /** + * Set the hooked player. + *

+ * Should only be called during the creation of the injector. + * @param player - the new hooked player. + */ + public void setPlayer(Player player) { + this.player = player; + } + + /** + * Object that can invoke the packet events. + * @return Packet event invoker. + */ + public ListenerInvoker getInvoker() { + return invoker; + } + + /** + * Retrieve the hooked player object OR the more up-to-date player instance. + * @return The hooked player, or a more up-to-date instance. + */ + @Override + public Player getUpdatedPlayer() { + if (updatedPlayer != null) + return updatedPlayer; + else + return player; + } + + @Override + public void transferState(SocketInjector delegate) { + // Do nothing + } + + @Override + public void setUpdatedPlayer(Player updatedPlayer) { + this.updatedPlayer = updatedPlayer; + } +}