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