Archiviert
13
0

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.
Dieser Commit ist enthalten in:
Kristian S. Stangeland 2013-02-25 01:59:48 +01:00
Ursprung 9b0fe540c2
Commit ffd920e5b2
17 geänderte Dateien mit 1062 neuen und 226 gelöschten Zeilen

Datei anzeigen

@ -217,6 +217,9 @@ public class ProtocolLibrary extends JavaPlugin {
if (manager == null) if (manager == null)
return; return;
// Perform logic when the world has loaded
protocolManager.postWorldLoaded();
// Initialize background compiler // Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) { if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter); backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);

Datei anzeigen

@ -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 @Override
public AsynchronousManager getAsynchronousManager() { public AsynchronousManager getAsynchronousManager() {
return asyncFilterManager; return asyncFilterManager;
@ -275,15 +282,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Make sure this is possible // Make sure this is possible
playerInjection.checkListener(listener); playerInjection.checkListener(listener);
} }
if (hasSending)
incrementPhases(sending.getGamePhase());
// Handle receivers after senders
if (hasReceiving) { if (hasReceiving) {
verifyWhitelist(listener, receiving); verifyWhitelist(listener, receiving);
recievedListeners.addListener(listener, receiving); recievedListeners.addListener(listener, receiving);
enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
} }
// Increment phases too
if (hasSending)
incrementPhases(sending.getGamePhase());
if (hasReceiving) if (hasReceiving)
incrementPhases(receiving.getGamePhase()); incrementPhases(receiving.getGamePhase());

Datei anzeigen

@ -30,6 +30,7 @@ import org.bukkit.entity.Player;
import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
@ -211,18 +212,22 @@ class ProxyPacketInjector implements PacketInjector {
public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
try { try {
Player client = playerInjection.getPlayerByConnection(input); Player client = playerInjection.getPlayerByConnection(input);
// Never invoke a event if we don't know where it's from // Never invoke a event if we don't know where it's from
if (client != null) if (client != null) {
return packetRecieved(packet, client); 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; return null;
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
// We will ignore this - it occurs when a player disconnects // We will ignore this - it occurs when a player disconnects
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input); //reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
return null; return null;
} }
} }
/** /**
@ -253,12 +258,4 @@ class ProxyPacketInjector implements PacketInjector {
overwritten.clear(); overwritten.clear();
previous.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);
}
} }

Datei anzeigen

@ -24,7 +24,6 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
@ -132,11 +131,6 @@ class ReadPacketModifier implements MethodInterceptor {
} else if (!objectEquals(thisObj, result)) { } else if (!objectEquals(thisObj, result)) {
override.put(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) { } catch (Throwable e) {
// Minecraft cannot handle this error // Minecraft cannot handle this error

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -53,6 +53,9 @@ class InjectedServerConnection {
// Used to inject net handlers // Used to inject net handlers
private NetLoginInjector netLoginInjector; private NetLoginInjector netLoginInjector;
// Inject server connections
private InjectedServerSocket socketInjector;
private Server server; private Server server;
private ErrorReporter reporter; private ErrorReporter reporter;
private boolean hasAttempted; private boolean hasAttempted;
@ -60,11 +63,12 @@ class InjectedServerConnection {
private Object minecraftServer = null; 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<VolatileField>(); this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>(); this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.reporter = reporter; this.reporter = reporter;
this.server = server; this.server = server;
this.socketInjector = socketInjector;
this.netLoginInjector = netLoginInjector; this.netLoginInjector = netLoginInjector;
} }
@ -126,6 +130,9 @@ class InjectedServerConnection {
return; return;
} }
// Inject the server socket too
injectServerSocket(listenerThread);
// Just inject every list field we can get // Just inject every list field we can get
injectEveryListField(listenerThread, 1); injectEveryListField(listenerThread, 1);
hasSuccess = true; hasSuccess = true;
@ -147,7 +154,8 @@ class InjectedServerConnection {
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("netServerHandlerList", List.class); getFieldByType("netServerHandlerList", List.class);
if (dedicatedThreadField == null) { if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(Thread.class); List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
getFieldListByType(Thread.class);
// Verify the field count // Verify the field count
if (matches.size() != 1) if (matches.size() != 1)
@ -158,8 +166,13 @@ class InjectedServerConnection {
// Next, try to get the dedicated thread // Next, try to get the dedicated thread
try { try {
if (dedicatedThreadField != null) if (dedicatedThreadField != null) {
injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1); Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
// Inject server socket and NetServerHandlers.
injectServerSocket(dedicatedThread);
injectEveryListField(dedicatedThread, 1);
}
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e); reporter.reportWarning(this, "Unable to retrieve net handler thread.", e);
} }
@ -168,6 +181,10 @@ class InjectedServerConnection {
hasSuccess = true; hasSuccess = true;
} }
private void injectServerSocket(Object container) {
socketInjector.inject(container);
}
/** /**
* Automatically inject into every List-compatible public or private field of the given object. * Automatically inject into every List-compatible public or private field of the given object.
* @param container - container object with the fields to inject. * @param container - container object with the fields to inject.

Datei anzeigen

@ -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<SendPacketCommand> syncronizedQueue = Collections.synchronizedList(new ArrayList<SendPacketCommand>());
/**
* 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<InputStream, SocketInjector> ownerSocket = new MapMaker().weakKeys().makeMap();
private ConcurrentMap<SocketAddress, InputStream> addressLookup = new MapMaker().weakValues().makeMap();
// Fake connections
private Set<SocketAddress> fakeConnections = Collections.newSetFromMap(
new MapMaker().weakKeys().<SocketAddress, Boolean>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.
* <p>
* 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();
}
}
}

Datei anzeigen

@ -17,14 +17,15 @@
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.GamePhase; 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.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -34,23 +35,23 @@ import com.google.common.collect.Maps;
* @author Kristian * @author Kristian
*/ */
class NetLoginInjector { class NetLoginInjector {
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap(); private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
private static Method getSocketMethod;
// Handles every hook // Handles every hook
private ProxyPlayerInjectionHandler injectionHandler; private ProxyPlayerInjectionHandler injectionHandler;
private Server server;
// Associate input streams and injectors
private InjectedServerSocket serverSocket;
// The current error rerporter // The current error rerporter
private ErrorReporter reporter; private ErrorReporter reporter;
// Used to create fake players public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, InjectedServerSocket serverSocket) {
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, Server server) {
this.reporter = reporter; this.reporter = reporter;
this.injectionHandler = injectionHandler; this.injectionHandler = injectionHandler;
this.server = server; this.serverSocket = serverSocket;
} }
/** /**
@ -64,16 +65,23 @@ class NetLoginInjector {
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting; return inserting;
Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server); if (getSocketMethod == null) {
PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN); getSocketMethod = FuzzyReflection.fromObject(inserting).
injector.updateOnLogin = true; 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 // This is the case if we're dealing with a connection initiated by the injected server socket
InjectContainer container = (InjectContainer) fakePlayer; if (socketInjector != null) {
container.setInjector(injector); PlayerInjector injector = injectionHandler.injectPlayer(socketInjector.getPlayer(), inserting, GamePhase.LOGIN);
injector.updateOnLogin = true;
// Save the login
injectedLogins.putIfAbsent(inserting, injector); // Save the login
injectedLogins.putIfAbsent(inserting, injector);
}
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting; return inserting;
@ -108,15 +116,13 @@ class NetLoginInjector {
// Hack to clean up other references // Hack to clean up other references
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager()); newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
injectionHandler.uninjectPlayer(player);
// Update NetworkManager // Update NetworkManager
if (newInjector == null) { if (newInjector != null) {
injectionHandler.uninjectPlayer(player); if (injected instanceof NetworkObjectInjector) {
} else {
injectionHandler.uninjectPlayer(player, false);
if (injected instanceof NetworkObjectInjector)
newInjector.setNetworkManager(injected.getNetworkManager(), true); newInjector.setNetworkManager(injected.getNetworkManager(), true);
}
} }
} catch (Throwable e) { } catch (Throwable e) {

Datei anzeigen

@ -4,8 +4,6 @@ import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
@ -61,16 +59,6 @@ public interface PlayerInjectionHandler {
public abstract Player getPlayerByConnection(DataInputStream inputStream) public abstract Player getPlayerByConnection(DataInputStream inputStream)
throws InterruptedException; 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. * Initialize a player hook, allowing us to read server packets.
* <p> * <p>
@ -149,8 +137,7 @@ public interface PlayerInjectionHandler {
public abstract void close(); public abstract void close();
/** /**
* Inform the current PlayerInjector that it should update the DataInputStream next. * Perform any action that must be delayed until the world(s) has loaded.
* @param player - the player to update.
*/ */
public abstract void scheduleDataInputRefresh(Player player); public abstract void postWorldLoaded();
} }

Datei anzeigen

@ -24,8 +24,6 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.concurrent.Callable;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import org.bukkit.entity.Player; 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.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
abstract class PlayerInjector { abstract class PlayerInjector implements SocketInjector {
// Net login handler stuff // Net login handler stuff
private static Field netLoginNetworkField; private static Field netLoginNetworkField;
@ -100,9 +98,6 @@ abstract class PlayerInjector {
// Handle errors // Handle errors
protected ErrorReporter reporter; protected ErrorReporter reporter;
// Scheduled action on the next packet event
protected Callable<Boolean> scheduledAction;
// Whether or not the injector has been cleaned // Whether or not the injector has been cleaned
private boolean clean; private boolean clean;
@ -250,6 +245,7 @@ abstract class PlayerInjector {
* @return The associated socket. * @return The associated socket.
* @throws IllegalAccessException If we're unable to read the socket field. * @throws IllegalAccessException If we're unable to read the socket field.
*/ */
@Override
public Socket getSocket() throws IllegalAccessException { public Socket getSocket() throws IllegalAccessException {
try { try {
if (socketField == null) if (socketField == null)
@ -268,6 +264,7 @@ abstract class PlayerInjector {
* @return The associated address. * @return The associated address.
* @throws IllegalAccessException If we're unable to read the socket field. * @throws IllegalAccessException If we're unable to read the socket field.
*/ */
@Override
public SocketAddress getAddress() throws IllegalAccessException { public SocketAddress getAddress() throws IllegalAccessException {
Socket socket = getSocket(); Socket socket = getSocket();
@ -283,6 +280,7 @@ abstract class PlayerInjector {
* @param message - the message to display. * @param message - the message to display.
* @throws InvocationTargetException If disconnection failed. * @throws InvocationTargetException If disconnection failed.
*/ */
@Override
public void disconnect(String message) throws InvocationTargetException { public void disconnect(String message) throws InvocationTargetException {
// Get a non-null handler // Get a non-null handler
boolean usingNetServer = serverHandler != null; 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 filtered - whether or not the packet will be filtered by our listeners.
* @param InvocationTargetException If an error occured when sending the packet. * @param InvocationTargetException If an error occured when sending the packet.
*/ */
@Override
public abstract void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException; public abstract void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException;
/** /**
@ -518,19 +517,7 @@ abstract class PlayerInjector {
Integer id = invoker.getPacketID(packet); Integer id = invoker.getPacketID(packet);
Player currentPlayer = player; Player currentPlayer = player;
// Hack #1: Handle a single scheduled action // Hack #1
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
if (updateOnLogin) { if (updateOnLogin) {
if (id == Packets.Server.LOGIN) { if (id == Packets.Server.LOGIN) {
try { try {
@ -601,19 +588,10 @@ abstract class PlayerInjector {
} }
} }
/**
* Schedule an action to occur on the next sent packet.
* <p>
* If the callable returns TRUE, the action is removed.
* @param action - action to execute.
*/
public void scheduleAction(Callable<Boolean> action) {
scheduledAction = action;
}
/** /**
* Retrieve the hooked player. * Retrieve the hooked player.
*/ */
@Override
public Player getPlayer() { public Player getPlayer() {
return player; return player;
} }
@ -640,6 +618,7 @@ abstract class PlayerInjector {
* Retrieve the hooked player object OR the more up-to-date player instance. * Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance. * @return The hooked player, or a more up-to-date instance.
*/ */
@Override
public Player getUpdatedPlayer() { public Player getUpdatedPlayer() {
if (updatedPlayer != null) if (updatedPlayer != null)
return updatedPlayer; return updatedPlayer;
@ -647,6 +626,11 @@ abstract class PlayerInjector {
return player; return player;
} }
@Override
public void transferState(SocketInjector delegate) {
// Do nothing
}
/** /**
* Set the real Bukkit player that we will use. * Set the real Bukkit player that we will use.
* @param updatedPlayer - the real Bukkit player. * @param updatedPlayer - the real Bukkit player.

Datei anzeigen

@ -21,17 +21,12 @@ import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketAdapter;
@ -51,14 +46,12 @@ import com.google.common.collect.Maps;
* @author Kristian * @author Kristian
*/ */
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { 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 // Server connection injection
private InjectedServerConnection serverInjection; private InjectedServerConnection serverInjection;
// Server socket injection
private InjectedServerSocket serverSocket;
// NetLogin injector // NetLogin injector
private NetLoginInjector netLoginInjector; private NetLoginInjector netLoginInjector;
@ -66,12 +59,8 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
private PlayerInjector lastSuccessfulHook; private PlayerInjector lastSuccessfulHook;
// Player injection // Player injection
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap(); private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
// Lookup player by connection
private BlockingHashMap<DataInputStream, PlayerInjector> dataInputLookup = BlockingHashMap.create();
// Player injection types // Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
@ -105,10 +94,19 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
this.invoker = invoker; this.invoker = invoker;
this.injectionFilter = injectionFilter; this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners; this.packetListeners = packetListeners;
this.netLoginInjector = new NetLoginInjector(reporter, this, server); this.serverSocket = new InjectedServerSocket(reporter, server);
this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector); this.netLoginInjector = new NetLoginInjector(reporter, this, serverSocket);
this.serverInjection = new InjectedServerConnection(reporter, serverSocket, server, netLoginInjector);
serverInjection.injectList(); 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. * Retrieves how the server packets are read.
@ -203,31 +201,16 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
/** /**
* Retrieve a player by its DataInput connection. * Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection. * @param inputStream - the associated DataInput connection.
* @return The player. * @return The player we found.
* @throws InterruptedException If the thread was interrupted during the wait.
*/ */
@Override @Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { public Player getPlayerByConnection(DataInputStream inputStream) {
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 {
// Wait until the connection owner has been established // Wait until the connection owner has been established
PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit); SocketInjector injector = serverSocket.getSocketInjector(inputStream);
if (injector != null) { if (injector != null) {
return injector.getPlayer(); return injector.getPlayer();
} else { } else {
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
return null; return null;
} }
} }
@ -310,24 +293,20 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
injector.initialize(injectionPoint); injector.initialize(injectionPoint);
DataInputStream inputStream = injector.getInputStream(false); DataInputStream inputStream = injector.getInputStream(false);
Socket socket = injector.getSocket(); Socket socket = injector.getSocket();
SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
// Guard against NPE here too // 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 // Close any previously associated hooks before we proceed
if (previous != null) { if (previous != null) {
uninjectPlayer(previous.getPlayer(), false, true); uninjectPlayer(previous.getPlayer(), true);
} }
injector.injectManager(); injector.injectManager();
if (inputStream != null) // Save injector
dataInputLookup.put(inputStream, injector); serverSocket.setSocketInjector(inputStream, injector);
if (address != null)
addressLookup.put(address, injector);
break; break;
} }
@ -405,33 +384,21 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
*/ */
@Override @Override
public boolean uninjectPlayer(Player player) { public boolean uninjectPlayer(Player player) {
return uninjectPlayer(player, true, false); return uninjectPlayer(player, false);
} }
/** /**
* Unregisters the given player. * Unregisters the given player.
* @param player - player to unregister. * @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. * @param prepareNextHook - whether or not we need to fix any lingering hooks.
* @return TRUE if a player has been uninjected, FALSE otherwise. * @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) { if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player); PlayerInjector injector = playerInjection.remove(player);
if (injector != null) { if (injector != null) {
InetSocketAddress address = player.getAddress();
injector.cleanupAll(); injector.cleanupAll();
// Remove the "hooked" network manager in our instance as well // 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; return true;
} }
} }
@ -472,11 +433,11 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
@Override @Override
public boolean uninjectPlayer(InetSocketAddress address) { public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) { if (!hasClosed && address != null) {
PlayerInjector injector = addressLookup.get(address); SocketInjector injector = serverSocket.getSocketInjector(address);
// Clean up // Clean up
if (injector != null) if (injector != null)
uninjectPlayer(injector.getPlayer(), false, true); uninjectPlayer(injector.getPlayer(), true);
return true; return true;
} }
@ -492,7 +453,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
*/ */
@Override @Override
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { 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 // Send the packet, or drop it completely
if (injector != null) { if (injector != null) {
@ -537,30 +498,26 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector == null) { if (injector == null) {
// Try getting it from the player itself // Try getting it from the player itself
if (player instanceof InjectContainer) if (player instanceof InjectContainer) {
return ((InjectContainer) player).getInjector(); 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 else
return searchAddressLookup(player); return null;
} else { } else {
return injector; 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. * Retrieve a player injector by looking for its NetworkManager.
* @param networkManager - current network manager. * @param networkManager - current network manager.
@ -641,42 +598,18 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} }
// Remove server handler // Remove server handler
if (serverSocket != null)
serverSocket.cleanupAll();
if (serverInjection != null) if (serverInjection != null)
serverInjection.cleanupAll(); serverInjection.cleanupAll();
if (netLoginInjector != null) if (netLoginInjector != null)
netLoginInjector.cleanupAll(); netLoginInjector.cleanupAll();
serverSocket = null;
serverInjection = null; serverInjection = null;
netLoginInjector = null; netLoginInjector = null;
hasClosed = true; hasClosed = true;
playerInjection.clear(); playerInjection.clear();
addressLookup.clear();
invoker = null; 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<Boolean>() {
@Override
public Boolean call() throws Exception {
DataInputStream inputStream = injector.getInputStream(false);
if (inputStream != null) {
dataInputLookup.put(inputStream, injector);
return true;
}
// Try again
return false;
}
});
}
}
} }

Datei anzeigen

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

Datei anzeigen

@ -37,21 +37,20 @@ import com.comphenix.protocol.reflect.FieldAccessException;
* Create fake player instances that represents pre-authenticated clients. * Create fake player instances that represents pre-authenticated clients.
*/ */
class TemporaryPlayerFactory { class TemporaryPlayerFactory {
/** /**
* Able to store a PlayerInjector. * Able to store a socket injector.
* <p> * <p>
* A necessary hack. * A necessary hack.
* @author Kristian * @author Kristian
*/ */
public static class InjectContainer { public static class InjectContainer {
private PlayerInjector injector; private SocketInjector injector;
public PlayerInjector getInjector() { public SocketInjector getInjector() {
return injector; return injector;
} }
public void setInjector(PlayerInjector injector) { public void setInjector(SocketInjector injector) {
this.injector = injector; this.injector = injector;
} }
} }
@ -80,7 +79,7 @@ class TemporaryPlayerFactory {
* <li>kickPlayer(String)</li> * <li>kickPlayer(String)</li>
* </ul> * </ul>
* <p> * <p>
* 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. * uniquely identified. Use the address instead.
* @param injector - the player injector used. * @param injector - the player injector used.
* @param server - the current server. * @param server - the current server.
@ -94,7 +93,7 @@ class TemporaryPlayerFactory {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName(); String methodName = method.getName();
PlayerInjector injector = ((InjectContainer) obj).getInjector(); SocketInjector injector = ((InjectContainer) obj).getInjector();
if (injector == null) if (injector == null)
throw new IllegalStateException("Unable to find injector."); throw new IllegalStateException("Unable to find injector.");
@ -173,7 +172,7 @@ class TemporaryPlayerFactory {
* @throws InvocationTargetException If the message couldn't be sent. * @throws InvocationTargetException If the message couldn't be sent.
* @throws FieldAccessException If we were unable to construct the message packet. * @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); injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
return null; return null;
} }

Datei anzeigen

@ -4,8 +4,6 @@ import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
@ -49,12 +47,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void setPlayerHook(PlayerInjectHooks playerHook) { public void setPlayerHook(PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot."); throw new UnsupportedOperationException("This is not needed in Spigot.");
} }
@Override
public void scheduleDataInputRefresh(Player player) {
// Fine
}
@Override @Override
public void addPacketHandler(int packetID) { public void addPacketHandler(int packetID) {
sendingFilters.add(packetID); sendingFilters.add(packetID);
@ -106,11 +99,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT; 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 @Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot."); throw new UnsupportedOperationException("This is not needed in Spigot.");
@ -125,4 +113,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void checkListener(Set<PacketListener> listeners) { public void checkListener(Set<PacketListener> listeners) {
// Yes, really // Yes, really
} }
@Override
public void postWorldLoaded() {
// Do nothing
}
} }

Datei anzeigen

@ -3,7 +3,6 @@ version: 2.2.1-SNAPSHOT
description: Provides read/write access to the Minecraft protocol. description: Provides read/write access to the Minecraft protocol.
author: Comphenix author: Comphenix
website: http://www.comphenix.net/ProtocolLib website: http://www.comphenix.net/ProtocolLib
load: startup
main: com.comphenix.protocol.ProtocolLibrary main: com.comphenix.protocol.ProtocolLibrary
database: false database: false