Switch to a better InputStream -> Socket lookup for normal JVM.
Dieser Commit ist enthalten in:
Ursprung
9195e677ab
Commit
2cf265f8e8
@ -47,6 +47,8 @@ class ProtocolConfig {
|
|||||||
private static final String UPDATER_DELAY = "delay";
|
private static final String UPDATER_DELAY = "delay";
|
||||||
private static final String UPDATER_LAST_TIME = "last";
|
private static final String UPDATER_LAST_TIME = "last";
|
||||||
|
|
||||||
|
private static final String ALTERNATIVE_JVM = "alternate jvm";
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
private static final long DEFAULT_UPDATER_DELAY = 43200;
|
private static final long DEFAULT_UPDATER_DELAY = 43200;
|
||||||
|
|
||||||
@ -229,6 +231,22 @@ class ProtocolConfig {
|
|||||||
global.set(BACKGROUND_COMPILER_ENABLED, enabled);
|
global.set(BACKGROUND_COMPILER_ENABLED, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve whether the current JVM is a non-standard implementation and require some workarounds.
|
||||||
|
* @return TRUE if it does, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isAlternateJVM() {
|
||||||
|
return global.getBoolean(ALTERNATIVE_JVM, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the current JVM is a non-standard implementation and require some workarounds.
|
||||||
|
* @param value - TRUE if it is, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public void setAlternateJVM(boolean value) {
|
||||||
|
global.set(ALTERNATIVE_JVM, value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
||||||
* @param lastTimeSeconds - new last update time.
|
* @param lastTimeSeconds - new last update time.
|
||||||
|
@ -136,7 +136,10 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
||||||
|
|
||||||
unhookTask = new DelayedSingleTask(this);
|
unhookTask = new DelayedSingleTask(this);
|
||||||
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, detailedReporter);
|
protocolManager = new PacketFilterManager(
|
||||||
|
getClassLoader(), getServer(), unhookTask, detailedReporter, config.isAlternateJVM());
|
||||||
|
|
||||||
|
// Setup error reporter
|
||||||
detailedReporter.addGlobalParameter("manager", protocolManager);
|
detailedReporter.addGlobalParameter("manager", protocolManager);
|
||||||
|
|
||||||
// Update injection hook
|
// Update injection hook
|
||||||
|
@ -150,7 +150,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
* Only create instances of this class if protocol lib is disabled.
|
* Only create instances of this class if protocol lib is disabled.
|
||||||
* @param unhookTask
|
* @param unhookTask
|
||||||
*/
|
*/
|
||||||
public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter) {
|
public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter, boolean alternateJVM) {
|
||||||
if (reporter == null)
|
if (reporter == null)
|
||||||
throw new IllegalArgumentException("reporter cannot be NULL.");
|
throw new IllegalArgumentException("reporter cannot be NULL.");
|
||||||
if (classLoader == null)
|
if (classLoader == null)
|
||||||
@ -200,6 +200,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
classLoader(classLoader).
|
classLoader(classLoader).
|
||||||
packetListeners(packetListeners).
|
packetListeners(packetListeners).
|
||||||
injectionFilter(isInjectionNecessary).
|
injectionFilter(isInjectionNecessary).
|
||||||
|
alternativeJVM(alternateJVM).
|
||||||
buildHandler();
|
buildHandler();
|
||||||
|
|
||||||
this.packetInjector = PacketInjectorBuilder.newBuilder().
|
this.packetInjector = PacketInjectorBuilder.newBuilder().
|
||||||
|
@ -27,7 +27,7 @@ import net.sf.cglib.proxy.Factory;
|
|||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.injector.server.InputStreamPlayerLookup;
|
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||||
@ -55,7 +55,7 @@ class InjectedServerConnection {
|
|||||||
private NetLoginInjector netLoginInjector;
|
private NetLoginInjector netLoginInjector;
|
||||||
|
|
||||||
// Inject server connections
|
// Inject server connections
|
||||||
private InputStreamPlayerLookup socketInjector;
|
private AbstractInputStreamLookup socketInjector;
|
||||||
|
|
||||||
private Server server;
|
private Server server;
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
@ -64,7 +64,7 @@ class InjectedServerConnection {
|
|||||||
|
|
||||||
private Object minecraftServer = null;
|
private Object minecraftServer = null;
|
||||||
|
|
||||||
public InjectedServerConnection(ErrorReporter reporter, InputStreamPlayerLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
|
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup 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;
|
||||||
|
@ -25,7 +25,7 @@ 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.server.InputStreamPlayerLookup;
|
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
@ -45,15 +45,15 @@ class NetLoginInjector {
|
|||||||
private ProxyPlayerInjectionHandler injectionHandler;
|
private ProxyPlayerInjectionHandler injectionHandler;
|
||||||
|
|
||||||
// Associate input streams and injectors
|
// Associate input streams and injectors
|
||||||
private InputStreamPlayerLookup serverSocket;
|
private AbstractInputStreamLookup inputStreamLookup;
|
||||||
|
|
||||||
// The current error rerporter
|
// The current error rerporter
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
|
|
||||||
public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, InputStreamPlayerLookup serverSocket) {
|
public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, AbstractInputStreamLookup inputStreamLookup) {
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.injectionHandler = injectionHandler;
|
this.injectionHandler = injectionHandler;
|
||||||
this.serverSocket = serverSocket;
|
this.inputStreamLookup = inputStreamLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,7 +74,7 @@ class NetLoginInjector {
|
|||||||
|
|
||||||
// Get the underlying socket
|
// Get the underlying socket
|
||||||
Socket socket = (Socket) getSocketMethod.invoke(inserting);
|
Socket socket = (Socket) getSocketMethod.invoke(inserting);
|
||||||
SocketInjector socketInjector = serverSocket.getSocketInjector(socket);
|
SocketInjector socketInjector = inputStreamLookup.getSocketInjector(socket);
|
||||||
|
|
||||||
// This is the case if we're dealing with a connection initiated by the injected server socket
|
// This is the case if we're dealing with a connection initiated by the injected server socket
|
||||||
if (socketInjector != null) {
|
if (socketInjector != null) {
|
||||||
|
@ -37,6 +37,7 @@ public class PlayerInjectorBuilder {
|
|||||||
protected ListenerInvoker invoker;
|
protected ListenerInvoker invoker;
|
||||||
protected Set<PacketListener> packetListeners;
|
protected Set<PacketListener> packetListeners;
|
||||||
protected Server server;
|
protected Server server;
|
||||||
|
protected boolean alternativeJVM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the class loader to use during class generation.
|
* Set the class loader to use during class generation.
|
||||||
@ -107,6 +108,16 @@ public class PlayerInjectorBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not the current JVM implementation is alternative.
|
||||||
|
* @param value - TRUE if it is, FALSE otherwise.
|
||||||
|
* @return The current builder, for chaining.
|
||||||
|
*/
|
||||||
|
public PlayerInjectorBuilder alternativeJVM(boolean value) {
|
||||||
|
alternativeJVM = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before an object is created with this builder.
|
* Called before an object is created with this builder.
|
||||||
*/
|
*/
|
||||||
@ -140,6 +151,6 @@ public class PlayerInjectorBuilder {
|
|||||||
|
|
||||||
return new ProxyPlayerInjectionHandler(
|
return new ProxyPlayerInjectionHandler(
|
||||||
classLoader, reporter, injectionFilter,
|
classLoader, reporter, injectionFilter,
|
||||||
invoker, packetListeners, server);
|
invoker, packetListeners, server, alternativeJVM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,8 @@ import com.comphenix.protocol.injector.GamePhase;
|
|||||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||||
import com.comphenix.protocol.injector.server.InputStreamPlayerLookup;
|
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||||
|
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
|
||||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
@ -52,7 +53,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
private InjectedServerConnection serverInjection;
|
private InjectedServerConnection serverInjection;
|
||||||
|
|
||||||
// Server socket injection
|
// Server socket injection
|
||||||
private InputStreamPlayerLookup serverSocket;
|
private AbstractInputStreamLookup inputStreamLookup;
|
||||||
|
|
||||||
// NetLogin injector
|
// NetLogin injector
|
||||||
private NetLoginInjector netLoginInjector;
|
private NetLoginInjector netLoginInjector;
|
||||||
@ -88,25 +89,33 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
// Used to filter injection attempts
|
// Used to filter injection attempts
|
||||||
private Predicate<GamePhase> injectionFilter;
|
private Predicate<GamePhase> injectionFilter;
|
||||||
|
|
||||||
public ProxyPlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
|
public ProxyPlayerInjectionHandler(
|
||||||
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
|
ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
|
||||||
|
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server, boolean alternateJVM) {
|
||||||
|
|
||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.invoker = invoker;
|
this.invoker = invoker;
|
||||||
this.injectionFilter = injectionFilter;
|
this.injectionFilter = injectionFilter;
|
||||||
this.packetListeners = packetListeners;
|
this.packetListeners = packetListeners;
|
||||||
this.serverSocket = new InputStreamPlayerLookup(reporter, server);
|
|
||||||
this.netLoginInjector = new NetLoginInjector(reporter, this, serverSocket);
|
this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
|
||||||
this.serverInjection = new InjectedServerConnection(reporter, serverSocket, server, netLoginInjector);
|
server(server).
|
||||||
|
reporter(reporter).
|
||||||
|
alternativeJVM(alternateJVM).
|
||||||
|
build();
|
||||||
|
|
||||||
|
// Create net login injectors and the server connection injector
|
||||||
|
this.netLoginInjector = new NetLoginInjector(reporter, this, inputStreamLookup);
|
||||||
|
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
|
||||||
serverInjection.injectList();
|
serverInjection.injectList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postWorldLoaded() {
|
public void postWorldLoaded() {
|
||||||
// This will actually create a socket and a seperate thread ...
|
// This will actually create a socket and a seperate thread ...
|
||||||
if (serverSocket != null) {
|
if (inputStreamLookup != null) {
|
||||||
serverSocket.postWorldLoaded();
|
inputStreamLookup.postWorldLoaded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +217,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
@Override
|
@Override
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream) {
|
public Player getPlayerByConnection(DataInputStream inputStream) {
|
||||||
// Wait until the connection owner has been established
|
// Wait until the connection owner has been established
|
||||||
SocketInjector injector = serverSocket.getSocketInjector(inputStream);
|
SocketInjector injector = inputStreamLookup.getSocketInjector(inputStream);
|
||||||
|
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
return injector.getPlayer();
|
return injector.getPlayer();
|
||||||
@ -298,7 +307,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
Socket socket = injector.getSocket();
|
Socket socket = injector.getSocket();
|
||||||
|
|
||||||
// Guard against NPE here too
|
// Guard against NPE here too
|
||||||
SocketInjector previous = socket != null ? serverSocket.getSocketInjector(socket) : null;
|
SocketInjector previous = socket != null ? inputStreamLookup.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) {
|
||||||
@ -308,7 +317,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
injector.injectManager();
|
injector.injectManager();
|
||||||
|
|
||||||
// Save injector
|
// Save injector
|
||||||
serverSocket.setSocketInjector(inputStream, injector);
|
inputStreamLookup.setSocketInjector(inputStream, injector);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,7 +444,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
@Override
|
@Override
|
||||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||||
if (!hasClosed && address != null) {
|
if (!hasClosed && address != null) {
|
||||||
SocketInjector injector = serverSocket.getSocketInjector(address);
|
SocketInjector injector = inputStreamLookup.getSocketInjector(address);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
if (injector != null)
|
if (injector != null)
|
||||||
@ -504,7 +513,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
|
|
||||||
// Only accept it if it's a player injector
|
// Only accept it if it's a player injector
|
||||||
if (!(socket instanceof PlayerInjector)) {
|
if (!(socket instanceof PlayerInjector)) {
|
||||||
socket = serverSocket.getSocketInjector(player.getAddress());
|
socket = inputStreamLookup.getSocketInjector(player.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that it is a player injector
|
// Ensure that it is a player injector
|
||||||
@ -597,13 +606,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove server handler
|
// Remove server handler
|
||||||
if (serverSocket != null)
|
if (inputStreamLookup != null)
|
||||||
serverSocket.cleanupAll();
|
inputStreamLookup.cleanupAll();
|
||||||
if (serverInjection != null)
|
if (serverInjection != null)
|
||||||
serverInjection.cleanupAll();
|
serverInjection.cleanupAll();
|
||||||
if (netLoginInjector != null)
|
if (netLoginInjector != null)
|
||||||
netLoginInjector.cleanupAll();
|
netLoginInjector.cleanupAll();
|
||||||
serverSocket = null;
|
inputStreamLookup = null;
|
||||||
serverInjection = null;
|
serverInjection = null;
|
||||||
netLoginInjector = null;
|
netLoginInjector = null;
|
||||||
hasClosed = true;
|
hasClosed = true;
|
||||||
|
@ -0,0 +1,148 @@
|
|||||||
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
|
public abstract class AbstractInputStreamLookup {
|
||||||
|
// 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
|
||||||
|
protected ConcurrentMap<InputStream, SocketInjector> ownerSocket = new MapMaker().weakKeys().makeMap();
|
||||||
|
protected ConcurrentMap<SocketAddress, InputStream> addressLookup = new MapMaker().weakValues().makeMap();
|
||||||
|
|
||||||
|
// Error reporter
|
||||||
|
protected final ErrorReporter reporter;
|
||||||
|
|
||||||
|
// Reference to the server itself
|
||||||
|
protected final Server server;
|
||||||
|
|
||||||
|
protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
|
||||||
|
this.reporter = reporter;
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
protected 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject the given server thread or dedicated connection.
|
||||||
|
* @param container - class that contains a ServerSocket field.
|
||||||
|
*/
|
||||||
|
public abstract void inject(Object container);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the world has loaded.
|
||||||
|
*/
|
||||||
|
public abstract void postWorldLoaded();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 abstract SocketInjector getSocketInjector(InputStream input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a injector by its address.
|
||||||
|
* @param address - the address of the socket.
|
||||||
|
* @return The socket injector.
|
||||||
|
*/
|
||||||
|
public abstract SocketInjector getSocketInjector(SocketAddress address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
onPreviousSocketOverwritten(previous, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the injection should be undone.
|
||||||
|
*/
|
||||||
|
public abstract void cleanupAll();
|
||||||
|
}
|
@ -14,6 +14,8 @@ class InjectContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setInjector(SocketInjector injector) {
|
public void setInjector(SocketInjector injector) {
|
||||||
|
if (injector == null)
|
||||||
|
throw new IllegalArgumentException("Injector cannot be NULL.");
|
||||||
this.injector = injector;
|
this.injector = injector;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the appropriate input stream lookup for the current JVM and architecture.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class InputStreamLookupBuilder {
|
||||||
|
public static InputStreamLookupBuilder newBuilder() {
|
||||||
|
return new InputStreamLookupBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InputStreamLookupBuilder() {
|
||||||
|
// Use the static method.
|
||||||
|
}
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
private ErrorReporter reporter;
|
||||||
|
private boolean alternativeJVM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the server instance to use.
|
||||||
|
* @param server - server instance.
|
||||||
|
* @return The current builder, for chaining.
|
||||||
|
*/
|
||||||
|
public InputStreamLookupBuilder server(Server server) {
|
||||||
|
this.server = server;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the error reporter to pass on to the lookup.
|
||||||
|
* @param reporter - the error reporter.
|
||||||
|
* @return The current builder, for chaining.
|
||||||
|
*/
|
||||||
|
public InputStreamLookupBuilder reporter(ErrorReporter reporter) {
|
||||||
|
this.reporter = reporter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not the current JVM implementation is alternative.
|
||||||
|
* @param value - TRUE if it is, FALSE otherwise.
|
||||||
|
* @return The current builder, for chaining.
|
||||||
|
*/
|
||||||
|
public InputStreamLookupBuilder alternativeJVM(boolean value) {
|
||||||
|
alternativeJVM = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractInputStreamLookup build() {
|
||||||
|
if (alternativeJVM)
|
||||||
|
return new InputStreamProxyLookup(reporter, server);
|
||||||
|
else
|
||||||
|
return new InputStreamReflectLookup(reporter, server);
|
||||||
|
}
|
||||||
|
}
|
@ -1,435 +0,0 @@
|
|||||||
package com.comphenix.protocol.injector.server;
|
|
||||||
|
|
||||||
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.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 InputStreamPlayerLookup {
|
|
||||||
/**
|
|
||||||
* 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 InputStreamPlayerLookup(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 the world has loaded.
|
|
||||||
*/
|
|
||||||
public void postWorldLoaded() {
|
|
||||||
cycleServerPorts();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when we need to cycle the injected server port.
|
|
||||||
* <p>
|
|
||||||
* This uses a fairly significant hack - we connect to our own server.
|
|
||||||
*/
|
|
||||||
private 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,233 @@
|
|||||||
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.Packets;
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
class InputStreamProxyLookup extends AbstractInputStreamLookup {
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
// Fake connections
|
||||||
|
private Set<SocketAddress> fakeConnections = Collections.newSetFromMap(
|
||||||
|
new MapMaker().weakKeys().<SocketAddress, Boolean>makeMap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// The server socket that has been injected
|
||||||
|
private VolatileField injectedServerSocket;
|
||||||
|
|
||||||
|
// Used to create fake players
|
||||||
|
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
||||||
|
|
||||||
|
public InputStreamProxyLookup(ErrorReporter reporter, Server server) {
|
||||||
|
super(reporter, server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Update it
|
||||||
|
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, socketInjector);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketInjector getSocketInjector(InputStream input) {
|
||||||
|
return ownerSocket.get(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketInjector getSocketInjector(SocketAddress address) {
|
||||||
|
InputStream input = addressLookup.get(address);
|
||||||
|
|
||||||
|
if (input != null) {
|
||||||
|
return ownerSocket.get(input);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postWorldLoaded() {
|
||||||
|
cycleServerPorts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when we need to cycle the injected server port.
|
||||||
|
* <p>
|
||||||
|
* This uses a fairly significant hack - we connect to our own server.
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
|
||||||
|
if (previous instanceof DelegatedSocketInjector) {
|
||||||
|
DelegatedSocketInjector delegated = (DelegatedSocketInjector) previous;
|
||||||
|
|
||||||
|
// Update the delegate
|
||||||
|
delegated.setDelegate(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanupAll() {
|
||||||
|
if (injectedServerSocket != null && injectedServerSocket.isCurrentSet()) {
|
||||||
|
injectedServerSocket.revertValue();
|
||||||
|
|
||||||
|
// This is going to suck
|
||||||
|
cycleServerPorts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
|
||||||
|
class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
||||||
|
// Used to create fake players
|
||||||
|
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
||||||
|
|
||||||
|
public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
|
||||||
|
super(reporter, server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inject(Object container) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postWorldLoaded() {
|
||||||
|
// Nothing again
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketInjector getSocketInjector(InputStream input) {
|
||||||
|
SocketInjector injector = ownerSocket.get(input);
|
||||||
|
|
||||||
|
if (injector != null) {
|
||||||
|
return injector;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Socket socket = getSocket(input);
|
||||||
|
Player player = tempPlayerFactory.createTemporaryPlayer(server);
|
||||||
|
SocketInjector created = new TemporarySocketInjector(player, socket);
|
||||||
|
|
||||||
|
// Update injector
|
||||||
|
TemporaryPlayerFactory.setInjectorInPlayer(player, created);
|
||||||
|
|
||||||
|
// Save address too
|
||||||
|
addressLookup.put(socket.getRemoteSocketAddress(), input);
|
||||||
|
|
||||||
|
// Associate the socket with a given input stream
|
||||||
|
setSocketInjector(input, created);
|
||||||
|
return created;
|
||||||
|
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new FieldAccessException("Cannot find or access socket field for " + input, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketInjector getSocketInjector(SocketAddress address) {
|
||||||
|
InputStream input = addressLookup.get(address);
|
||||||
|
|
||||||
|
if (input != null)
|
||||||
|
return getSocketInjector(input);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanupAll() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Socket getSocket(InputStream stream) throws IllegalAccessException {
|
||||||
|
if (stream instanceof FilterInputStream) {
|
||||||
|
return getSocket(getInputStream((FilterInputStream) stream));
|
||||||
|
} else {
|
||||||
|
// Just do it
|
||||||
|
Field socketField = FuzzyReflection.fromObject(stream, true).
|
||||||
|
getFieldByType("socket", Socket.class);
|
||||||
|
|
||||||
|
return (Socket) FieldUtils.readField(socketField, stream, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -59,6 +59,15 @@ public class TemporaryPlayerFactory {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the player injector, if possible.
|
||||||
|
* @param player - the player to update.
|
||||||
|
* @param injector - the injector to store.
|
||||||
|
*/
|
||||||
|
public static void setInjectorInPlayer(Player player, SocketInjector injector) {
|
||||||
|
((InjectContainer) player).setInjector(injector);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a temporary player that supports a subset of every player command.
|
* Construct a temporary player that supports a subset of every player command.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
class TemporarySocketInjector implements SocketInjector {
|
||||||
|
/**
|
||||||
|
* Represents a single send packet command.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,8 @@ global:
|
|||||||
# Disable version checking for the given Minecraft version. Backup your world first!
|
# Disable version checking for the given Minecraft version. Backup your world first!
|
||||||
ignore version check:
|
ignore version check:
|
||||||
|
|
||||||
|
# Set to TRUE if you're on a custom JVM implementation and you're having problems
|
||||||
|
alternate jvm: false
|
||||||
|
|
||||||
# Override the starting injecting method
|
# Override the starting injecting method
|
||||||
injection method:
|
injection method:
|
||||||
|
|
In neuem Issue referenzieren
Einen Benutzer sperren