From ffd920e5b2157d1a9e2627d2ca7902b49cdf1302 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 25 Feb 2013 01:59:48 +0100 Subject: [PATCH] Experimental: InputStream -> Socket lookup by intercepting accept(). Previously, we have used a BlockingHashMap to simply lock the packet read thread until we have had a chance to intercept the NetLoginHandler/PendingConnection and store InputStream -> PlayerInjector -> TemporaryPlayer. Problem is, this could potentially cause problems if, for some reason, a packet is intercepted after the player has logged out and the player injector has been removed from the lookup map. In that case, the read thread would wait until it reaches the default timeout of 2 seconds. Locking threads is fairly inefficient in general, and waiting for the server connection thread to update the NetLoginHandler list could take a while. Instead, ProtocolLib will now intercept any Socket accepted in the server's main ServerSocket, and record any calls to getInputStream(). That way, we can get a InputStream -> Socket mapping before the server thread ever creates the read and write threads in NetLoginHandler -> NetworkManager. Unfortunately, it's not trivial to swap out the ServerSocket in the DedicatedServerConnectionThread - we actually have to trigger the accept() thread and move through a cycle of the loop before our custom ServerSocket is used. To do this, we will actually connect to the server and read its MOTD manually, hopefully getting to it before any other players. This creates a slight overhead of a couple of threads per server start, but it's probably much better than locking the read thread. More testing is needed though before this can be confirmed. --- .../comphenix/protocol/ProtocolLibrary.java | 3 + .../injector/PacketFilterManager.java | 15 +- .../injector/packet/ProxyPacketInjector.java | 21 +- .../injector/packet/ReadPacketModifier.java | 6 - .../player/DelegatedServerSocket.java | 122 +++++ .../injector/player/DelegatedSocket.java | 241 ++++++++++ .../player/DelegatedSocketInjector.java | 64 +++ .../player/InjectedServerConnection.java | 25 +- .../injector/player/InjectedServerSocket.java | 429 ++++++++++++++++++ .../injector/player/NetLoginInjector.java | 54 ++- .../player/PlayerInjectionHandler.java | 17 +- .../injector/player/PlayerInjector.java | 42 +- .../player/ProxyPlayerInjectionHandler.java | 153 ++----- .../injector/player/SocketInjector.java | 61 +++ .../player/TemporaryPlayerFactory.java | 15 +- .../injector/spigot/DummyPlayerHandler.java | 19 +- ProtocolLib/src/main/resources/plugin.yml | 1 - 17 files changed, 1062 insertions(+), 226 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedServerSocket.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocket.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocketInjector.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerSocket.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/SocketInjector.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index cbeef78b..97a65df2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -217,6 +217,9 @@ public class ProtocolLibrary extends JavaPlugin { if (manager == null) return; + // Perform logic when the world has loaded + protocolManager.postWorldLoaded(); + // Initialize background compiler if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) { backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter); 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 0c62ebab..e373126d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -225,6 +225,13 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok } } + /** + * Initiate logic that is performed after the world has loaded. + */ + public void postWorldLoaded() { + playerInjection.postWorldLoaded(); + } + @Override public AsynchronousManager getAsynchronousManager() { return asyncFilterManager; @@ -275,15 +282,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Make sure this is possible playerInjection.checkListener(listener); } + if (hasSending) + incrementPhases(sending.getGamePhase()); + + // Handle receivers after senders if (hasReceiving) { verifyWhitelist(listener, receiving); recievedListeners.addListener(listener, receiving); enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); } - - // Increment phases too - if (hasSending) - incrementPhases(sending.getGamePhase()); if (hasReceiving) incrementPhases(receiving.getGamePhase()); 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 index bd3274a8..2c3e6a1b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -30,6 +30,7 @@ import org.bukkit.entity.Player; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; +import com.comphenix.protocol.Packets; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; @@ -211,18 +212,22 @@ class ProxyPacketInjector implements PacketInjector { 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) + if (client != null) { return packetRecieved(packet, client); - else + } else { + // Hack #2 - Caused by our server socket injector + if (packet.getID() != Packets.Client.GET_INFO) + System.out.println("[ProtocolLib] Unknown origin " + input + " for packet " + packet.getID()); 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; - } + } } /** @@ -253,12 +258,4 @@ class ProxyPacketInjector implements PacketInjector { 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 00eacf9c..1d1e6494 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 @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; -import com.comphenix.protocol.Packets; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; @@ -132,11 +131,6 @@ class ReadPacketModifier implements MethodInterceptor { } else if (!objectEquals(thisObj, result)) { override.put(thisObj, result); } - - // Update DataInputStream next time - if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) { - packetInjector.scheduleDataInputRefresh(event.getPlayer()); - } } } catch (Throwable e) { // Minecraft cannot handle this error diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedServerSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedServerSocket.java new file mode 100644 index 00000000..7b39f246 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedServerSocket.java @@ -0,0 +1,122 @@ +package com.comphenix.protocol.injector.player; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.ServerSocketChannel; + +class DelegatedServerSocket extends ServerSocket { + protected ServerSocket delegate; + + public DelegatedServerSocket(ServerSocket delegate) throws IOException { + super(); + this.delegate = delegate; + } + + @Override + public void close() throws IOException { + delegate.close(); + } + + @Override + public Socket accept() throws IOException { + return delegate.accept(); + } + + @Override + public void bind(SocketAddress endpoint) throws IOException { + delegate.bind(endpoint); + } + + @Override + public void bind(SocketAddress endpoint, int backlog) throws IOException { + delegate.bind(endpoint, backlog); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public ServerSocketChannel getChannel() { + return delegate.getChannel(); + } + + @Override + public InetAddress getInetAddress() { + return delegate.getInetAddress(); + } + + @Override + public int getLocalPort() { + return delegate.getLocalPort(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return delegate.getLocalSocketAddress(); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + return delegate.getReceiveBufferSize(); + } + + @Override + public boolean getReuseAddress() throws SocketException { + return delegate.getReuseAddress(); + } + + @Override + public synchronized int getSoTimeout() throws IOException { + return delegate.getSoTimeout(); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public boolean isBound() { + return delegate.isBound(); + } + + @Override + public boolean isClosed() { + return delegate.isClosed(); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + delegate.setPerformancePreferences(connectionTime, latency, bandwidth); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + delegate.setReceiveBufferSize(size); + } + + @Override + public void setReuseAddress(boolean on) throws SocketException { + delegate.setReuseAddress(on); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + delegate.setSoTimeout(timeout); + } + + @Override + public String toString() { + return delegate.toString(); + } + + public ServerSocket getDelegate() { + return delegate; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocket.java new file mode 100644 index 00000000..af4bf3ed --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocket.java @@ -0,0 +1,241 @@ +package com.comphenix.protocol.injector.player; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SocketChannel; + +// This is a fixed JVM class, so there's probably no need to use CGLib +class DelegatedSocket extends Socket { + protected Socket delegate; + + public DelegatedSocket(Socket delegate) { + super(); + this.delegate = delegate; + } + + @Override + public void bind(SocketAddress arg0) throws IOException { + delegate.bind(arg0); + } + + @Override + public synchronized void close() throws IOException { + delegate.close(); + } + + @Override + public void connect(SocketAddress endpoint) throws IOException { + delegate.connect(endpoint); + } + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + delegate.connect(endpoint, timeout); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public SocketChannel getChannel() { + return delegate.getChannel(); + } + + @Override + public InetAddress getInetAddress() { + return delegate.getInetAddress(); + } + + @Override + public InputStream getInputStream() throws IOException { + return delegate.getInputStream(); + } + + @Override + public boolean getKeepAlive() throws SocketException { + return delegate.getKeepAlive(); + } + + @Override + public InetAddress getLocalAddress() { + return delegate.getLocalAddress(); + } + + @Override + public int getLocalPort() { + return delegate.getLocalPort(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return delegate.getLocalSocketAddress(); + } + + @Override + public boolean getOOBInline() throws SocketException { + return delegate.getOOBInline(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return delegate.getOutputStream(); + } + + @Override + public int getPort() { + return delegate.getPort(); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + return delegate.getReceiveBufferSize(); + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return delegate.getRemoteSocketAddress(); + } + + @Override + public boolean getReuseAddress() throws SocketException { + return delegate.getReuseAddress(); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException { + return delegate.getSendBufferSize(); + } + + @Override + public int getSoLinger() throws SocketException { + return delegate.getSoLinger(); + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + return delegate.getSoTimeout(); + } + + @Override + public boolean getTcpNoDelay() throws SocketException { + return delegate.getTcpNoDelay(); + } + + @Override + public int getTrafficClass() throws SocketException { + return delegate.getTrafficClass(); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public boolean isBound() { + return delegate.isBound(); + } + + @Override + public boolean isClosed() { + return delegate.isClosed(); + } + + @Override + public boolean isConnected() { + return delegate.isConnected(); + } + + @Override + public boolean isInputShutdown() { + return delegate.isInputShutdown(); + } + + @Override + public boolean isOutputShutdown() { + return delegate.isOutputShutdown(); + } + + @Override + public void sendUrgentData(int data) throws IOException { + delegate.sendUrgentData(data); + } + + @Override + public void setKeepAlive(boolean on) throws SocketException { + delegate.setKeepAlive(on); + } + + @Override + public void setOOBInline(boolean on) throws SocketException { + delegate.setOOBInline(on); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + delegate.setPerformancePreferences(connectionTime, latency, bandwidth); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + delegate.setReceiveBufferSize(size); + } + + @Override + public void setReuseAddress(boolean on) throws SocketException { + delegate.setReuseAddress(on); + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException { + + delegate.setSendBufferSize(size); + } + + @Override + public void setSoLinger(boolean on, int linger) throws SocketException { + delegate.setSoLinger(on, linger); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + delegate.setSoTimeout(timeout); + } + + @Override + public void setTcpNoDelay(boolean on) throws SocketException { + delegate.setTcpNoDelay(on); + } + + @Override + public void setTrafficClass(int tc) throws SocketException { + delegate.setTrafficClass(tc); + } + + @Override + public void shutdownInput() throws IOException { + delegate.shutdownInput(); + } + + @Override + public void shutdownOutput() throws IOException { + delegate.shutdownOutput(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + public Socket getDelegate() { + return delegate; + } +} + diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocketInjector.java new file mode 100644 index 00000000..f8d4afc1 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocketInjector.java @@ -0,0 +1,64 @@ +package com.comphenix.protocol.injector.player; + +import java.lang.reflect.InvocationTargetException; +import java.net.Socket; +import java.net.SocketAddress; + +import org.bukkit.entity.Player; + +/** + * Represents a socket injector that delegates to a passed injector. + * @author Kristian + * + */ +class DelegatedSocketInjector implements SocketInjector { + private volatile SocketInjector delegate; + + public DelegatedSocketInjector(SocketInjector delegate) { + this.delegate = delegate; + } + + @Override + public void disconnect(String message) throws InvocationTargetException { + delegate.disconnect(message); + } + @Override + public SocketAddress getAddress() throws IllegalAccessException { + return delegate.getAddress(); + } + + @Override + public Player getPlayer() { + return delegate.getPlayer(); + } + + @Override + public Socket getSocket() throws IllegalAccessException { + return delegate.getSocket(); + } + + @Override + public Player getUpdatedPlayer() { + return delegate.getUpdatedPlayer(); + } + + @Override + public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { + delegate.sendServerPacket(packet, filtered); + } + + public SocketInjector getDelegate() { + return delegate; + } + + @Override + public void transferState(SocketInjector delegate) { + delegate.transferState(delegate); + } + + public synchronized void setDelegate(SocketInjector delegate) { + // Let the old delegate pass values to the new + this.delegate.transferState(delegate); + this.delegate = delegate; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index c6860ab6..d502ea9c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -53,6 +53,9 @@ class InjectedServerConnection { // Used to inject net handlers private NetLoginInjector netLoginInjector; + // Inject server connections + private InjectedServerSocket socketInjector; + private Server server; private ErrorReporter reporter; private boolean hasAttempted; @@ -60,11 +63,12 @@ class InjectedServerConnection { private Object minecraftServer = null; - public InjectedServerConnection(ErrorReporter reporter, Server server, NetLoginInjector netLoginInjector) { + public InjectedServerConnection(ErrorReporter reporter, InjectedServerSocket socketInjector, Server server, NetLoginInjector netLoginInjector) { this.listFields = new ArrayList(); this.replacedLists = new ArrayList>(); this.reporter = reporter; this.server = server; + this.socketInjector = socketInjector; this.netLoginInjector = netLoginInjector; } @@ -126,6 +130,9 @@ class InjectedServerConnection { return; } + // Inject the server socket too + injectServerSocket(listenerThread); + // Just inject every list field we can get injectEveryListField(listenerThread, 1); hasSuccess = true; @@ -147,7 +154,8 @@ class InjectedServerConnection { listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). getFieldByType("netServerHandlerList", List.class); if (dedicatedThreadField == null) { - List matches = FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(Thread.class); + List matches = FuzzyReflection.fromObject(serverConnection, true). + getFieldListByType(Thread.class); // Verify the field count if (matches.size() != 1) @@ -158,8 +166,13 @@ class InjectedServerConnection { // Next, try to get the dedicated thread try { - if (dedicatedThreadField != null) - injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1); + if (dedicatedThreadField != null) { + Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true); + + // Inject server socket and NetServerHandlers. + injectServerSocket(dedicatedThread); + injectEveryListField(dedicatedThread, 1); + } } catch (IllegalAccessException e) { reporter.reportWarning(this, "Unable to retrieve net handler thread.", e); } @@ -168,6 +181,10 @@ class InjectedServerConnection { hasSuccess = true; } + private void injectServerSocket(Object container) { + socketInjector.inject(container); + } + /** * Automatically inject into every List-compatible public or private field of the given object. * @param container - container object with the fields to inject. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerSocket.java new file mode 100644 index 00000000..7231bb2a --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerSocket.java @@ -0,0 +1,429 @@ +package com.comphenix.protocol.injector.player; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.VolatileField; +import com.google.common.collect.MapMaker; + +/** + * Injection hook used to determine which Socket, and thus address, created any given DataInputStream. + * + * @author Kristian + */ +public class InjectedServerSocket { + /** + * The read and connect timeout for our built-in MOTD reader. + */ + private static final int READ_TIMEOUT = 5000; + private static final int CONNECT_TIMEOUT = 1000; + + /** + * Represents a single send packet command. + * @author Kristian + */ + private static class SendPacketCommand { + private final Object packet; + private final boolean filtered; + + public SendPacketCommand(Object packet, boolean filtered) { + this.packet = packet; + this.filtered = filtered; + } + + public Object getPacket() { + return packet; + } + + public boolean isFiltered() { + return filtered; + } + } + + private static class TemporarySocketInjector implements SocketInjector { + private Player temporaryPlayer; + private Socket socket; + + // Queue of server packets + private List syncronizedQueue = Collections.synchronizedList(new ArrayList()); + + /** + * Represents a temporary socket injector. + * @param temporaryPlayer - temporary player instance. + * @param socket - the socket we are representing. + * @param fake - whether or not this connection should be ignored. + */ + public TemporarySocketInjector(Player temporaryPlayer, Socket socket) { + this.temporaryPlayer = temporaryPlayer; + this.socket = socket; + } + + @Override + public Socket getSocket() throws IllegalAccessException { + return socket; + } + + @Override + public SocketAddress getAddress() throws IllegalAccessException { + if (socket != null) + return socket.getRemoteSocketAddress(); + return null; + } + + @Override + public void disconnect(String message) throws InvocationTargetException { + // We have no choice - disregard message too + try { + socket.close(); + } catch (IOException e) { + throw new InvocationTargetException(e); + } + } + + @Override + public void sendServerPacket(Object packet, boolean filtered) + throws InvocationTargetException { + SendPacketCommand command = new SendPacketCommand(packet, filtered); + + // Queue until we can find something better + syncronizedQueue.add(command); + } + + @Override + public Player getPlayer() { + return temporaryPlayer; + } + + @Override + public Player getUpdatedPlayer() { + return temporaryPlayer; + } + + @Override + public void transferState(SocketInjector delegate) { + // Transmit all queued packets to a different injector. + try { + synchronized(syncronizedQueue) { + for (SendPacketCommand command : syncronizedQueue) { + delegate.sendServerPacket(command.getPacket(), command.isFiltered()); + } + syncronizedQueue.clear(); + } + } catch (InvocationTargetException e) { + throw new RuntimeException("Unable to transmit packets to " + delegate + " from old injector.", e); + } + } + } + + // Used to access the inner input stream of a filtered input stream + private static Field filteredInputField; + + // Using weak keys and values ensures that we will not hold up garbage collection + private ConcurrentMap ownerSocket = new MapMaker().weakKeys().makeMap(); + private ConcurrentMap addressLookup = new MapMaker().weakValues().makeMap(); + + // Fake connections + private Set fakeConnections = Collections.newSetFromMap( + new MapMaker().weakKeys().makeMap() + ); + + // The server socket that has been injected + private VolatileField injectedServerSocket; + + // Reference to the server itself + private final Server server; + + // Error reporter + private final ErrorReporter reporter; + + // Used to create fake players + private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); + + public InjectedServerSocket(ErrorReporter reporter, Server server) { + this.reporter = reporter; + this.server = server; + } + + /** + * Inject the given server thread or dedicated connection. + * @param container - class that contains a ServerSocket field. + */ + public void inject(Object container) { + if (injectedServerSocket != null) + throw new IllegalStateException("Can only inject once. Create a new object instead."); + + Field selected = FuzzyReflection.fromObject(container, true). + getFieldByType("serverSocket", ServerSocket.class); + injectedServerSocket = new VolatileField(selected, container, true); + + // Load socket + ServerSocket socket = (ServerSocket) injectedServerSocket.getValue(); + + // Make sure it exists + if (socket == null) { + throw new IllegalStateException("Cannot find socket to inject. Reference " + selected + " contains NULL."); + } + + // Next, let us create the injected server socket + try { + injectedServerSocket.setValue(new DelegatedServerSocket(socket) { + @Override + public Socket accept() throws IOException { + Socket accepted = super.accept(); + + if (fakeConnections.contains(accepted.getRemoteSocketAddress())) { + // Don't intercept this connection + return accepted; + } + + // Wrap the socket we return + return new DelegatedSocket(accepted) { + @Override + public InputStream getInputStream() throws IOException { + InputStream input = super.getInputStream(); + SocketAddress address = delegate.getRemoteSocketAddress(); + + // Make sure that the address is actually valid + if (address != null) { + InputStream previousStream = addressLookup. + putIfAbsent(delegate.getRemoteSocketAddress(), input); + + // Ensure that this is our first time + if (previousStream == null) { + // Create a new temporary player + Player temporaryPlayer = tempPlayerFactory.createTemporaryPlayer(server); + TemporarySocketInjector temporaryInjector = new TemporarySocketInjector(temporaryPlayer, delegate); + DelegatedSocketInjector socketInjector = new DelegatedSocketInjector(temporaryInjector); + + // Associate the socket with a given input stream + setSocketInjector(input, socketInjector); + } + } + return input; + } + }; + } + }); + + } catch (IOException e) { + throw new IllegalStateException("Unbound socket threw an exception. Should never occur.", e); + } + } + + /** + * Invoked when we need to cycle the injected server port. + *

+ * This uses a fairly significant hack - we connect to our own server. + */ + public void cycleServerPorts() { + final ServerSocket serverSocket = (ServerSocket) injectedServerSocket.getValue(); + final SocketAddress address = new InetSocketAddress("127.0.0.1", serverSocket.getLocalPort()); + + // Sorry + Thread consumeThread = new Thread("ProtocolLib - Hack Thread") { + @Override + public void run() { + Socket socket = null; + OutputStream output = null; + InputStream input = null; + InputStreamReader reader = null; + + try { + socket = new Socket(); + socket.connect(address, CONNECT_TIMEOUT); + + // Ignore packets from this connection + fakeConnections.add(socket.getLocalSocketAddress()); + + // Shouldn't take that long + socket.setSoTimeout(READ_TIMEOUT); + + // Retrieve sockets + output = socket.getOutputStream(); + input = socket.getInputStream(); + reader = new InputStreamReader(input, Charset.forName("UTF-16BE")); + + // Get the server to send a MOTD + output.write(new byte[] { (byte) 0xFE, (byte) 0x01 }); + + int packetId = input.read(); + int length = reader.read(); + + if (packetId != Packets.Server.KICK_DISCONNECT) { + throw new IOException("Invalid packet ID: " + packetId); + } + if (length <= 0) { + throw new IOException("Invalid string length."); + } + char[] chars = new char[length]; + + // Read all the characters + if (reader.read(chars, 0, length) != length) { + throw new IOException("Premature end of stream."); + } + + System.out.println("Read: " + new String(chars)); + + } catch (Exception e) { + reporter.reportWarning(this, "Cannot simulate MOTD.", e); + } finally { + try { + if (reader != null) + reader.close(); + if (input != null) + input.close(); + if (output != null) + output.close(); + if (socket != null) + socket.close(); + } catch (IOException e) { + reporter.reportWarning(this, "Cannot clean up socket.", e); + } + } + } + }; + consumeThread.start(); + } + + /** + * Retrieve the underlying input stream that is associated with a given filter input stream. + * @param filtered - the filter input stream. + * @return The underlying input stream that is being filtered. + * @throws FieldAccessException Unable to access input stream. + */ + private static InputStream getInputStream(FilterInputStream filtered) { + if (filteredInputField == null) + filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true). + getFieldByType("in", InputStream.class); + + InputStream current = filtered; + + try { + // Iterate until we find the real input stream + while (current instanceof FilterInputStream) { + current = (InputStream) FieldUtils.readField(filteredInputField, current, true); + } + return current; + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot access filtered input field.", e); + } + } + + /** + * Retrieve the associated socket injector for a player. + * @param filtered - the indentifying filtered input stream. + * @return The socket injector we have associated with this player. + * @throws FieldAccessException Unable to access input stream. + */ + public SocketInjector getSocketInjector(FilterInputStream filtered) { + return getSocketInjector(getInputStream(filtered)); + } + + /** + * Retrieve the associated socket injector for a player. + * @param filtered - the indentifying filtered input stream. + * @return The socket injector we have associated with this player. + */ + public SocketInjector getSocketInjector(InputStream input) { + return ownerSocket.get(input); + } + + /** + * Retrieve a injector by its address. + * @param address - the address of the socket. + * @return The socket injector. + */ + public SocketInjector getSocketInjector(SocketAddress address) { + InputStream input = addressLookup.get(address); + + if (input != null) { + return ownerSocket.get(input); + } else { + return null; + } + } + + /** + * Retrieve an injector by its socket. + * @param socket - the socket. + * @return The socket injector. + */ + public SocketInjector getSocketInjector(Socket socket) { + if (socket == null) + throw new IllegalArgumentException("The socket cannot be NULL."); + return getSocketInjector(socket.getRemoteSocketAddress()); + } + + /** + * Associate a given input stream with the provided socket injector. + * @param input - the filtered input stream to associate. + * @param injector - the injector. + * @throws FieldAccessException Unable to access input stream. + */ + public void setSocketInjector(FilterInputStream input, SocketInjector injector) { + setSocketInjector(getInputStream(input), injector); + } + + /** + * Associate a given input stream with the provided socket injector. + * @param input - the input stream to associate. + * @param injector - the injector. + */ + public void setSocketInjector(InputStream input, SocketInjector injector) { + SocketInjector previous = ownerSocket.put(input, injector); + + // Any previous temporary players will also be associated + if (previous != null) { + Player player = previous.getPlayer(); + + if (player instanceof InjectContainer) { + InjectContainer container = (InjectContainer) player; + container.setInjector(injector); + } + + // Update the reference to any previous injector + if (previous instanceof DelegatedSocketInjector) { + DelegatedSocketInjector delegated = (DelegatedSocketInjector) previous; + + // Update the delegate + delegated.setDelegate(injector); + } + } + } + + /** + * Invoked when the injection should be undone. + */ + public void cleanupAll() { + if (injectedServerSocket != null && injectedServerSocket.isCurrentSet()) { + injectedServerSocket.revertValue(); + + // This is going to suck + //cycleServerPorts(); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index f80bae7b..96c2e8e8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -17,14 +17,15 @@ package com.comphenix.protocol.injector.player; +import java.lang.reflect.Method; +import java.net.Socket; import java.util.concurrent.ConcurrentMap; -import org.bukkit.Server; import org.bukkit.entity.Player; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; +import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; @@ -34,23 +35,23 @@ import com.google.common.collect.Maps; * @author Kristian */ class NetLoginInjector { - private ConcurrentMap injectedLogins = Maps.newConcurrentMap(); + private static Method getSocketMethod; + // Handles every hook private ProxyPlayerInjectionHandler injectionHandler; - private Server server; + + // Associate input streams and injectors + private InjectedServerSocket serverSocket; // The current error rerporter private ErrorReporter reporter; - // Used to create fake players - private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); - - public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, Server server) { + public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, InjectedServerSocket serverSocket) { this.reporter = reporter; this.injectionHandler = injectionHandler; - this.server = server; + this.serverSocket = serverSocket; } /** @@ -64,16 +65,23 @@ class NetLoginInjector { if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) return inserting; - Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server); - PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN); - injector.updateOnLogin = true; + if (getSocketMethod == null) { + getSocketMethod = FuzzyReflection.fromObject(inserting). + getMethodByParameters("getSocket", Socket.class, new Class[0]); + } + + // Get the underlying socket + Socket socket = (Socket) getSocketMethod.invoke(inserting); + SocketInjector socketInjector = serverSocket.getSocketInjector(socket); - // Associate the injector too - InjectContainer container = (InjectContainer) fakePlayer; - container.setInjector(injector); - - // Save the login - injectedLogins.putIfAbsent(inserting, injector); + // This is the case if we're dealing with a connection initiated by the injected server socket + if (socketInjector != null) { + PlayerInjector injector = injectionHandler.injectPlayer(socketInjector.getPlayer(), inserting, GamePhase.LOGIN); + injector.updateOnLogin = true; + + // Save the login + injectedLogins.putIfAbsent(inserting, injector); + } // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler return inserting; @@ -108,15 +116,13 @@ class NetLoginInjector { // Hack to clean up other references newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager()); + injectionHandler.uninjectPlayer(player); // Update NetworkManager - if (newInjector == null) { - injectionHandler.uninjectPlayer(player); - } else { - injectionHandler.uninjectPlayer(player, false); - - if (injected instanceof NetworkObjectInjector) + if (newInjector != null) { + if (injected instanceof NetworkObjectInjector) { newInjector.setNetworkManager(injected.getNetworkManager(), true); + } } } catch (Throwable e) { 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 a1fe1b44..976bc341 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 @@ -4,8 +4,6 @@ import java.io.DataInputStream; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.util.Set; -import java.util.concurrent.TimeUnit; - import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketContainer; @@ -61,16 +59,6 @@ public interface PlayerInjectionHandler { public abstract Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException; - /** - * Retrieve a player by its DataInput connection. - * @param inputStream - the associated DataInput connection. - * @param playerTimeout - the amount of time to wait for a result. - * @param unit - unit of playerTimeout. - * @return The player. - * @throws InterruptedException If the thread was interrupted during the wait. - */ - public abstract Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException; - /** * Initialize a player hook, allowing us to read server packets. *

@@ -149,8 +137,7 @@ public interface PlayerInjectionHandler { public abstract void close(); /** - * Inform the current PlayerInjector that it should update the DataInputStream next. - * @param player - the player to update. + * Perform any action that must be delayed until the world(s) has loaded. */ - public abstract void scheduleDataInputRefresh(Player player); + public abstract void postWorldLoaded(); } \ No newline at end of file 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 26acc9e6..9ac43b29 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 @@ -24,8 +24,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; import java.net.SocketAddress; -import java.util.concurrent.Callable; - import net.sf.cglib.proxy.Factory; import org.bukkit.entity.Player; @@ -45,7 +43,7 @@ import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.utility.MinecraftReflection; -abstract class PlayerInjector { +abstract class PlayerInjector implements SocketInjector { // Net login handler stuff private static Field netLoginNetworkField; @@ -100,9 +98,6 @@ abstract class PlayerInjector { // Handle errors protected ErrorReporter reporter; - // Scheduled action on the next packet event - protected Callable scheduledAction; - // Whether or not the injector has been cleaned private boolean clean; @@ -250,6 +245,7 @@ abstract class PlayerInjector { * @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) @@ -268,6 +264,7 @@ abstract class PlayerInjector { * @return The associated address. * @throws IllegalAccessException If we're unable to read the socket field. */ + @Override public SocketAddress getAddress() throws IllegalAccessException { Socket socket = getSocket(); @@ -283,6 +280,7 @@ abstract class PlayerInjector { * @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; @@ -451,6 +449,7 @@ abstract class PlayerInjector { * @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; /** @@ -518,19 +517,7 @@ abstract class PlayerInjector { Integer id = invoker.getPacketID(packet); Player currentPlayer = player; - // Hack #1: Handle a single scheduled action - if (scheduledAction != null) { - try { - if (scheduledAction.call()) { - scheduledAction = null; - } - } catch (Exception e) { - reporter.reportDetailed(this, "Cannot perform hack #1.", e, scheduledAction, packet); - scheduledAction = null; - } - } - - // Hack #2 + // Hack #1 if (updateOnLogin) { if (id == Packets.Server.LOGIN) { try { @@ -601,19 +588,10 @@ abstract class PlayerInjector { } } - /** - * Schedule an action to occur on the next sent packet. - *

- * If the callable returns TRUE, the action is removed. - * @param action - action to execute. - */ - public void scheduleAction(Callable action) { - scheduledAction = action; - } - /** * Retrieve the hooked player. */ + @Override public Player getPlayer() { return player; } @@ -640,6 +618,7 @@ abstract class PlayerInjector { * 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; @@ -647,6 +626,11 @@ abstract class PlayerInjector { return player; } + @Override + public void transferState(SocketInjector delegate) { + // Do nothing + } + /** * Set the real Bukkit player that we will use. * @param updatedPlayer - the real Bukkit player. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 1c3945c6..4c2cf262 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -21,17 +21,12 @@ import java.io.DataInputStream; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.SocketAddress; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - import org.bukkit.Server; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; -import com.comphenix.protocol.concurrency.BlockingHashMap; import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketAdapter; @@ -51,14 +46,12 @@ import com.google.common.collect.Maps; * @author Kristian */ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { - /** - * The maximum number of milliseconds to wait until a player can be looked up by connection. - */ - private static final long TIMEOUT_PLAYER_LOOKUP = 2000; // ms - // Server connection injection private InjectedServerConnection serverInjection; + // Server socket injection + private InjectedServerSocket serverSocket; + // NetLogin injector private NetLoginInjector netLoginInjector; @@ -66,12 +59,8 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { private PlayerInjector lastSuccessfulHook; // Player injection - private Map addressLookup = Maps.newConcurrentMap(); private Map playerInjection = Maps.newConcurrentMap(); - // Lookup player by connection - private BlockingHashMap dataInputLookup = BlockingHashMap.create(); - // Player injection types private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; @@ -105,10 +94,19 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { this.invoker = invoker; this.injectionFilter = injectionFilter; this.packetListeners = packetListeners; - this.netLoginInjector = new NetLoginInjector(reporter, this, server); - this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector); + this.serverSocket = new InjectedServerSocket(reporter, server); + this.netLoginInjector = new NetLoginInjector(reporter, this, serverSocket); + this.serverInjection = new InjectedServerConnection(reporter, serverSocket, server, netLoginInjector); serverInjection.injectList(); } + + @Override + public void postWorldLoaded() { + // This will actually create a socket and a seperate thread ... + if (serverSocket != null) { + serverSocket.cycleServerPorts(); + } + } /** * Retrieves how the server packets are read. @@ -203,31 +201,16 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { /** * Retrieve a player by its DataInput connection. * @param inputStream - the associated DataInput connection. - * @return The player. - * @throws InterruptedException If the thread was interrupted during the wait. + * @return The player we found. */ @Override - public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { - return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS); - } - - /** - * Retrieve a player by its DataInput connection. - * @param inputStream - the associated DataInput connection. - * @param playerTimeout - the amount of time to wait for a result. - * @param unit - unit of playerTimeout. - * @return The player. - * @throws InterruptedException If the thread was interrupted during the wait. - */ - @Override - public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException { + public Player getPlayerByConnection(DataInputStream inputStream) { // Wait until the connection owner has been established - PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit); + SocketInjector injector = serverSocket.getSocketInjector(inputStream); if (injector != null) { return injector.getPlayer(); } else { - reporter.reportWarning(this, "Unable to find stream: " + inputStream); return null; } } @@ -310,24 +293,20 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { injector.initialize(injectionPoint); DataInputStream inputStream = injector.getInputStream(false); - Socket socket = injector.getSocket(); - SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null; // Guard against NPE here too - PlayerInjector previous = address != null ? addressLookup.get(address) : null; + SocketInjector previous = socket != null ? serverSocket.getSocketInjector(socket) : null; // Close any previously associated hooks before we proceed if (previous != null) { - uninjectPlayer(previous.getPlayer(), false, true); + uninjectPlayer(previous.getPlayer(), true); } injector.injectManager(); - if (inputStream != null) - dataInputLookup.put(inputStream, injector); - if (address != null) - addressLookup.put(address, injector); + // Save injector + serverSocket.setSocketInjector(inputStream, injector); break; } @@ -405,33 +384,21 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { */ @Override public boolean uninjectPlayer(Player player) { - return uninjectPlayer(player, true, false); + return uninjectPlayer(player, false); } /** * Unregisters the given player. * @param player - player to unregister. - * @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address. - * @return TRUE if a player has been uninjected, FALSE otherwise. - */ - public boolean uninjectPlayer(Player player, boolean removeAuxiliary) { - return uninjectPlayer(player, removeAuxiliary, false); - } - - /** - * Unregisters the given player. - * @param player - player to unregister. - * @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address. * @param prepareNextHook - whether or not we need to fix any lingering hooks. * @return TRUE if a player has been uninjected, FALSE otherwise. */ - private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) { + private boolean uninjectPlayer(Player player, boolean prepareNextHook) { if (!hasClosed && player != null) { PlayerInjector injector = playerInjection.remove(player); if (injector != null) { - InetSocketAddress address = player.getAddress(); injector.cleanupAll(); // Remove the "hooked" network manager in our instance as well @@ -447,12 +414,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { } } - // Clean up - if (removeAuxiliary) { - // Note that the dataInputLookup will clean itself - if (address != null) - addressLookup.remove(address); - } return true; } } @@ -472,11 +433,11 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { @Override public boolean uninjectPlayer(InetSocketAddress address) { if (!hasClosed && address != null) { - PlayerInjector injector = addressLookup.get(address); + SocketInjector injector = serverSocket.getSocketInjector(address); // Clean up if (injector != null) - uninjectPlayer(injector.getPlayer(), false, true); + uninjectPlayer(injector.getPlayer(), true); return true; } @@ -492,7 +453,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { */ @Override public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { - PlayerInjector injector = getInjector(reciever); + SocketInjector injector = getInjector(reciever); // Send the packet, or drop it completely if (injector != null) { @@ -537,30 +498,26 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { if (injector == null) { // Try getting it from the player itself - if (player instanceof InjectContainer) - return ((InjectContainer) player).getInjector(); + if (player instanceof InjectContainer) { + SocketInjector socket = ((InjectContainer) player).getInjector(); + + // It may be a player injector too + if (socket instanceof PlayerInjector) + return (PlayerInjector) socket; + } + + SocketInjector socket = serverSocket.getSocketInjector(player.getAddress()); + + // Ensure that it is a player injector + if (socket instanceof PlayerInjector) + return (PlayerInjector) socket; else - return searchAddressLookup(player); + return null; } else { return injector; } } - /** - * Find an injector by looking through the address map. - * @param player - player to find. - * @return The injector, or NULL if not found. - */ - private PlayerInjector searchAddressLookup(Player player) { - // See if we can find it anywhere - for (PlayerInjector injector : addressLookup.values()) { - if (player.equals(injector.getUpdatedPlayer())) { - return injector; - } - } - return null; - } - /** * Retrieve a player injector by looking for its NetworkManager. * @param networkManager - current network manager. @@ -641,42 +598,18 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { } // Remove server handler + if (serverSocket != null) + serverSocket.cleanupAll(); if (serverInjection != null) serverInjection.cleanupAll(); if (netLoginInjector != null) netLoginInjector.cleanupAll(); + serverSocket = null; serverInjection = null; netLoginInjector = null; hasClosed = true; playerInjection.clear(); - addressLookup.clear(); invoker = null; } - - /** - * Inform the current PlayerInjector that it should update the DataInputStream next. - * @param player - the player to update. - */ - @Override - public void scheduleDataInputRefresh(Player player) { - final PlayerInjector injector = getInjector(player); - - // Update the DataInputStream - if (injector != null) { - injector.scheduleAction(new Callable() { - @Override - public Boolean call() throws Exception { - DataInputStream inputStream = injector.getInputStream(false); - - if (inputStream != null) { - dataInputLookup.put(inputStream, injector); - return true; - } - // Try again - return false; - } - }); - } - } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/SocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/SocketInjector.java new file mode 100644 index 00000000..9b7fc814 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/SocketInjector.java @@ -0,0 +1,61 @@ +package com.comphenix.protocol.injector.player; + +import java.lang.reflect.InvocationTargetException; +import java.net.Socket; +import java.net.SocketAddress; + +import org.bukkit.entity.Player; + +/** + * Represents an injector that only gives access to a player's socket. + * + * @author Kristian + */ +interface SocketInjector { + /** + * Retrieve the associated socket of this player. + * @return The associated socket. + * @throws IllegalAccessException If we're unable to read the socket field. + */ + public abstract Socket getSocket() throws IllegalAccessException; + + /** + * Retrieve the associated address of this player. + * @return The associated address. + * @throws IllegalAccessException If we're unable to read the socket field. + */ + public abstract SocketAddress getAddress() throws IllegalAccessException; + + /** + * Attempt to disconnect the current client. + * @param message - the message to display. + * @throws InvocationTargetException If disconnection failed. + */ + public abstract void disconnect(String message) throws InvocationTargetException; + + /** + * 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(Object packet, boolean filtered) + throws InvocationTargetException; + + /** + * Retrieve the hooked player. + */ + public abstract Player getPlayer(); + + /** + * Retrieve the hooked player object OR the more up-to-date player instance. + * @return The hooked player, or a more up-to-date instance. + */ + public abstract Player getUpdatedPlayer(); + + /** + * Invoked when a delegated socket injector transfers the state of one injector to the next. + * @param delegate - the new injector. + */ + public abstract void transferState(SocketInjector delegate); +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java index 67336df5..c8df9197 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java @@ -37,21 +37,20 @@ import com.comphenix.protocol.reflect.FieldAccessException; * Create fake player instances that represents pre-authenticated clients. */ class TemporaryPlayerFactory { - /** - * Able to store a PlayerInjector. + * Able to store a socket injector. *

* A necessary hack. * @author Kristian */ public static class InjectContainer { - private PlayerInjector injector; + private SocketInjector injector; - public PlayerInjector getInjector() { + public SocketInjector getInjector() { return injector; } - public void setInjector(PlayerInjector injector) { + public void setInjector(SocketInjector injector) { this.injector = injector; } } @@ -80,7 +79,7 @@ class TemporaryPlayerFactory { *

  • kickPlayer(String)
  • * *

    - * Note that the player a player has not been assigned a name yet, and thus cannot be + * Note that a temporary player has not yet been assigned a name, and thus cannot be * uniquely identified. Use the address instead. * @param injector - the player injector used. * @param server - the current server. @@ -94,7 +93,7 @@ class TemporaryPlayerFactory { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String methodName = method.getName(); - PlayerInjector injector = ((InjectContainer) obj).getInjector(); + SocketInjector injector = ((InjectContainer) obj).getInjector(); if (injector == null) throw new IllegalStateException("Unable to find injector."); @@ -173,7 +172,7 @@ class TemporaryPlayerFactory { * @throws InvocationTargetException If the message couldn't be sent. * @throws FieldAccessException If we were unable to construct the message packet. */ - private Object sendMessage(PlayerInjector injector, String message) throws InvocationTargetException, FieldAccessException { + private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false); return null; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java index f48b01d3..73a1c5e2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java @@ -4,8 +4,6 @@ import java.io.DataInputStream; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.util.Set; -import java.util.concurrent.TimeUnit; - import org.bukkit.entity.Player; import com.comphenix.protocol.concurrency.IntegerSet; @@ -49,12 +47,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler { public void setPlayerHook(PlayerInjectHooks playerHook) { throw new UnsupportedOperationException("This is not needed in Spigot."); } - - @Override - public void scheduleDataInputRefresh(Player player) { - // Fine - } - + @Override public void addPacketHandler(int packetID) { sendingFilters.add(packetID); @@ -106,11 +99,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler { return PlayerInjectHooks.NETWORK_SERVER_OBJECT; } - @Override - public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException { - throw new UnsupportedOperationException("This is not needed in Spigot."); - } - @Override public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { throw new UnsupportedOperationException("This is not needed in Spigot."); @@ -125,4 +113,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler { public void checkListener(Set listeners) { // Yes, really } + + @Override + public void postWorldLoaded() { + // Do nothing + } } diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 2d752252..d717beb0 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -3,7 +3,6 @@ version: 2.2.1-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib -load: startup main: com.comphenix.protocol.ProtocolLibrary database: false