From 224b5d7f3eebc325e6d162cf7a667d7527b38b58 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 6 Feb 2013 22:09:37 +0100 Subject: [PATCH 01/37] Increment to 2.2.1-SNAPSHOT for development towards the next version --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index e1268256..9e6ff3c3 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 2.2.0 + 2.2.1-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 50beb220..2d752252 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 2.2.0 +version: 2.2.1-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 351dc7f3df94e92a3efcde57c70fcb541286b398 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 6 Feb 2013 22:40:17 +0100 Subject: [PATCH 02/37] Ensure that ProtocolLib discoveres mod-specific packets. --- .../injector/PacketFilterManager.java | 13 ++++----- .../injector/packet/PacketRegistry.java | 27 +++++++++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index eea43aae..0c62ebab 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -133,8 +133,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok private AsyncFilterManager asyncFilterManager; // Valid server and client packets - private Set serverPackets; - private Set clientPackets; + private boolean knowsServerPackets; + private boolean knowsClientPackets; // Ensure that we're not performing too may injections private AtomicInteger phaseLoginCount = new AtomicInteger(0); @@ -214,8 +214,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Attempt to load the list of server and client packets try { - this.serverPackets = PacketRegistry.getServerPackets(); - this.clientPackets = PacketRegistry.getClientPackets(); + knowsServerPackets = PacketRegistry.getServerPackets() != null; + knowsClientPackets = PacketRegistry.getClientPackets() != null; } catch (FieldAccessException e) { reporter.reportWarning(this, "Cannot load server and client packet list.", e); } @@ -466,7 +466,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok for (int packetID : packets) { // Only register server packets that are actually supported by Minecraft if (side.isForServer()) { - if (serverPackets != null && serverPackets.contains(packetID)) + // Note that we may update the packet list here + if (!knowsServerPackets || PacketRegistry.getServerPackets().contains(packetID)) playerInjection.addPacketHandler(packetID); else reporter.reportWarning(this, String.format( @@ -477,7 +478,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // As above, only for client packets if (side.isForClient() && packetInjector != null) { - if (clientPackets != null && clientPackets.contains(packetID)) + if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID)) packetInjector.addPacketHandler(packetID); else reporter.reportWarning(this, String.format( diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java index 73f4e587..918546f4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java @@ -47,8 +47,12 @@ public class PacketRegistry { private static Map packetToID; // Whether or not certain packets are sent by the client or the server - private static Set serverPackets; - private static Set clientPackets; + private static ImmutableSet serverPackets; + private static ImmutableSet clientPackets; + + // The underlying sets + private static Set serverPacketsRef; + private static Set clientPacketsRef; // New proxy values private static Map overwrittenPackets = new HashMap(); @@ -120,21 +124,21 @@ public class PacketRegistry { @SuppressWarnings("unchecked") private static void initializeSets() throws FieldAccessException { - if (serverPackets == null || clientPackets == null) { + if (serverPacketsRef == null || clientPacketsRef == null) { List sets = getPacketRegistry().getFieldListByType(Set.class); try { if (sets.size() > 1) { - serverPackets = (Set) FieldUtils.readStaticField(sets.get(0), true); - clientPackets = (Set) FieldUtils.readStaticField(sets.get(1), true); + serverPacketsRef = (Set) FieldUtils.readStaticField(sets.get(0), true); + clientPacketsRef = (Set) FieldUtils.readStaticField(sets.get(1), true); // Impossible - if (serverPackets == null || clientPackets == null) + if (serverPacketsRef == null || clientPacketsRef == null) throw new FieldAccessException("Packet sets are in an illegal state."); // NEVER allow callers to modify the underlying sets - serverPackets = ImmutableSet.copyOf(serverPackets); - clientPackets = ImmutableSet.copyOf(clientPackets); + serverPackets = ImmutableSet.copyOf(serverPacketsRef); + clientPackets = ImmutableSet.copyOf(clientPacketsRef); } else { throw new FieldAccessException("Cannot retrieve packet client/server sets."); @@ -143,6 +147,13 @@ public class PacketRegistry { } catch (IllegalAccessException e) { throw new FieldAccessException("Cannot access field.", e); } + + } else { + // Copy over again if it has changed + if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size()) + serverPackets = ImmutableSet.copyOf(serverPacketsRef); + if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size()) + clientPackets = ImmutableSet.copyOf(clientPacketsRef); } } From 32282bbe9f9880d308ffe783a7df6cc8eeecef77 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 7 Feb 2013 00:23:22 +0100 Subject: [PATCH 03/37] Fixed a bug overwriting the NetLoginHandler class with NetServerHandler --- .../com/comphenix/protocol/utility/MinecraftReflection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 3717fe1d..a1f5c711 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -553,7 +553,7 @@ public class MinecraftReflection { return getMinecraftClass("NetServerHandler", "PlayerConnection"); } catch (RuntimeException e) { // Use the player connection field - return setMinecraftClass("NetLoginHandler", + return setMinecraftClass("NetServerHandler", FuzzyReflection.fromClass(getEntityPlayerClass()). getFieldByType("playerConnection", getNetHandlerClass()).getType() ); From bc1955bff31c94614023cf0eff55ebee78f7edf0 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 7 Feb 2013 20:07:33 +0100 Subject: [PATCH 04/37] Don't attempt to clean up static members during an update. --- .../main/java/com/comphenix/protocol/ProtocolLibrary.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 28c1ef9d..cbeef78b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -40,6 +40,7 @@ import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.metrics.Updater; +import com.comphenix.protocol.metrics.Updater.UpdateResult; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; /** @@ -430,8 +431,10 @@ public class ProtocolLibrary extends JavaPlugin { reporter = null; // Leaky ClassLoader begone! - CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter); - cleanup.resetAll(); + if (updater == null || updater.getResult() != UpdateResult.SUCCESS) { + CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter); + cleanup.resetAll(); + } } // Get the Bukkit logger first, before we try to create our own From dd3cd6c768c5107cd9ca0c880073c2ff56c26a69 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 9 Feb 2013 13:19:36 +0100 Subject: [PATCH 05/37] Fix an NPE reported in ticket-38. --- .../protocol/injector/spigot/SpigotPacketInjector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java index 8dcc6014..ea98e3c3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java @@ -427,7 +427,7 @@ public class SpigotPacketInjector implements SpigotPacketListener { void uninjectPlayer(Player player) { final NetworkObjectInjector injector = getInjector(player); - if (player != null) { + if (player != null && injector != null) { Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { From 02d0a9afd56811e9c555c0a3f91cc3a4476d38b3 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 9 Feb 2013 16:50:42 +0100 Subject: [PATCH 06/37] Add some debug information to a OutOfMemory exception. --- .../protocol/reflect/compiler/StructureCompiler.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index 579c069a..13d90652 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.reflect.StructureModifier; import com.google.common.base.Objects; import com.google.common.primitives.Primitives; @@ -206,6 +207,11 @@ public final class StructureCompiler { return (StructureModifier) compiledClass.getConstructor( StructureModifier.class, StructureCompiler.class). newInstance(source, this); + } catch (OutOfMemoryError e) { + // Print the number of generated classes by the current instances + ProtocolLibrary.getErrorReporter().reportWarning( + this, "May have generated too many classes (count: " + compiledCache.size() + ")"); + throw e; } catch (IllegalArgumentException e) { throw new IllegalStateException("Used invalid parameters in instance creation", e); } catch (SecurityException e) { @@ -218,7 +224,7 @@ public final class StructureCompiler { throw new RuntimeException("Error occured while instancing generated class.", e); } catch (NoSuchMethodException e) { throw new IllegalStateException("Cannot happen.", e); - } + } } /** From 0a3c1b13ed92fd0db5a00922154c03e81b10e782 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 9 Feb 2013 23:22:33 +0100 Subject: [PATCH 07/37] Add the ability to load from the default package as well. --- .../protocol/utility/CachedPackage.java | 14 +++- .../protocol/utility/MinecraftReflection.java | 66 ++++++++++++------- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java index 9081705e..a629ba1b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java @@ -58,7 +58,7 @@ class CachedPackage { if (result == null) { // Look up the class dynamically result = CachedPackage.class.getClassLoader(). - loadClass(packageName + "." + className); + loadClass(combine(packageName, className)); cache.put(className, result); } @@ -68,4 +68,16 @@ class CachedPackage { throw new RuntimeException("Cannot find class " + className, e); } } + + /** + * Correctly combine a package name and the child class we're looking for. + * @param packageName - name of the package, or an empty string for the default package. + * @param className - the class name. + * @return We full class path. + */ + private String combine(String packageName, String className) { + if (packageName.length() == 0) + return className; + return packageName + "." + className; + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index a1f5c711..6f12d76b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -73,7 +73,7 @@ public class MinecraftReflection { private static String MINECRAFT_FULL_PACKAGE = null; private static String CRAFTBUKKIT_PACKAGE = null; - + private static CachedPackage minecraftPackage; private static CachedPackage craftbukkitPackage; @@ -125,15 +125,15 @@ public class MinecraftReflection { // This server should have a "getHandle" method that we can use if (craftServer != null) { try { - Class craftClass = craftServer.getClass(); - Method getHandle = craftClass.getMethod("getHandle"); - - Class returnType = getHandle.getReturnType(); - String returnName = returnType.getCanonicalName(); - // The return type will tell us the full package, regardless of formating + Class craftClass = craftServer.getClass(); CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName()); - MINECRAFT_FULL_PACKAGE = getPackage(returnName); + + // Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package + Class craftEntity = getCraftEntityClass(); + Method getHandle = craftEntity.getMethod("getHandle"); + + MINECRAFT_FULL_PACKAGE = getPackage(getHandle.getReturnType().getCanonicalName()); // Pretty important invariant if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) { @@ -165,7 +165,7 @@ public class MinecraftReflection { throw new IllegalStateException("Could not find Bukkit. Is it running?"); } } - + /** * Used during debugging and testing. * @param minecraftPackage - the current Minecraft package. @@ -185,7 +185,8 @@ public class MinecraftReflection { */ public static String getCraftBukkitPackage() { // Ensure it has been initialized - getMinecraftPackage(); + if (CRAFTBUKKIT_PACKAGE == null) + getMinecraftPackage(); return CRAFTBUKKIT_PACKAGE; } @@ -490,22 +491,29 @@ public class MinecraftReflection { public static Class getMinecraftServerClass() { try { return getMinecraftClass("MinecraftServer"); - } catch (RuntimeException e) { - // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) - Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")). - getConstructor(FuzzyMethodContract.newBuilder(). - parameterMatches(getMinecraftObjectMatcher(), 0). - parameterCount(2). - build() - ); - Class[] params = selected.getParameterTypes(); - - // Jackpot - two classes at the same time! - setMinecraftClass("MinecraftServer", params[0]); - setMinecraftClass("ServerConfigurationManager", params[1]); - return params[0]; + } catch (RuntimeException e) { + useFallbackServer(); + return getMinecraftClass("MinecraftServer"); } } + + /** + * Fallback method that can determine the MinecraftServer and the ServerConfigurationManager. + */ + private static void useFallbackServer() { + // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) + Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")). + getConstructor(FuzzyMethodContract.newBuilder(). + parameterMatches(getMinecraftObjectMatcher(), 0). + parameterCount(2). + build() + ); + Class[] params = selected.getParameterTypes(); + + // Jackpot - two classes at the same time! + setMinecraftClass("MinecraftServer", params[0]); + setMinecraftClass("ServerConfigurationManager", params[1]); + } /** * Retrieve the player list class (or ServerConfigurationManager), @@ -516,7 +524,7 @@ public class MinecraftReflection { return getMinecraftClass("ServerConfigurationManager", "PlayerList"); } catch (RuntimeException e) { // Try again - getMinecraftServerClass(); + useFallbackServer(); return getMinecraftClass("ServerConfigurationManager"); } } @@ -908,6 +916,14 @@ public class MinecraftReflection { public static Class getCraftPlayerClass() { return getCraftBukkitClass("entity.CraftPlayer"); } + + /** + * Retrieve the CraftEntity class. + * @return CraftEntity class. + */ + public static Class getCraftEntityClass() { + return getCraftBukkitClass("entity.CraftEntity"); + } /** * Retrieve a CraftItemStack from a given ItemStack. From b8b39b47850cfc72751a0e862f69c8d9875fc390 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 10 Feb 2013 15:23:12 +0100 Subject: [PATCH 08/37] Set a maximum perm gen space usage for our background compiler. --- ItemDisguise/.classpath | 6 - .../reflect/compiler/BackgroundCompiler.java | 110 +++++++++++++++++- .../reflect/compiler/StructureCompiler.java | 2 +- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/ItemDisguise/.classpath b/ItemDisguise/.classpath index 2bda6dc7..71e70473 100644 --- a/ItemDisguise/.classpath +++ b/ItemDisguise/.classpath @@ -7,12 +7,6 @@ - - - - - - diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java index 4359e080..836a45d1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java @@ -17,6 +17,10 @@ package com.comphenix.protocol.reflect.compiler; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -29,6 +33,9 @@ import javax.annotation.Nullable; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.util.concurrent.ThreadFactoryBuilder; /** @@ -48,15 +55,26 @@ public class BackgroundCompiler { // How long to wait for a shutdown public static final int SHUTDOWN_DELAY_MS = 2000; + /** + * The default fraction of perm gen space after which the background compiler will be disabled. + */ + public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65; + // The single background compiler we're using private static BackgroundCompiler backgroundCompiler; + // Classes we're currently compiling + private Map>> listeners = Maps.newHashMap(); + private Object listenerLock = new Object(); + private StructureCompiler compiler; private boolean enabled; private boolean shuttingDown; private ExecutorService executor; private ErrorReporter reporter; + + private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN; /** * Retrieves the current background compiler. @@ -139,26 +157,61 @@ public class BackgroundCompiler { * @param uncompiled - structure modifier to compile. * @param listener - listener responsible for responding to the compilation. */ + @SuppressWarnings({"rawtypes", "unchecked"}) public void scheduleCompilation(final StructureModifier uncompiled, final CompileListener listener) { - // Only schedule if we're enabled if (enabled && !shuttingDown) { + // Check perm gen + if (getPermGenUsage() > disablePermGenFraction) + return; // Don't try to schedule anything if (executor == null || executor.isShutdown()) return; + + // Use to look up structure modifiers + final StructureKey key = new StructureKey(uncompiled); + + // Allow others to listen in too + synchronized (listenerLock) { + List list = listeners.get(key); + + if (!listeners.containsKey(key)) { + listeners.put(key, (List) Lists.newArrayList(listener)); + } else { + // We're currently compiling + list.add(listener); + return; + } + } // Create the worker that will compile our modifier Callable worker = new Callable() { @Override public Object call() throws Exception { StructureModifier modifier = uncompiled; + List list = null; // Do our compilation try { modifier = compiler.compile(modifier); - listener.onCompiled(modifier); - + + synchronized (listenerLock) { + list = listeners.get(key); + } + + // Only execute the listeners if there is a list + if (list != null) { + for (Object compileListener : list) { + ((CompileListener) compileListener).onCompiled(modifier); + } + + // Remove it when we're done + synchronized (listenerLock) { + list = listeners.remove(key); + } + } + } catch (Throwable e) { // Disable future compilations! setEnabled(false); @@ -205,6 +258,41 @@ public class BackgroundCompiler { } } + /** + * Add a compile listener if we are still waiting for the structure modifier to be compiled. + * @param uncompiled - the structure modifier that may get compiled. + * @param listener - the listener to invoke in that case. + */ + @SuppressWarnings("unchecked") + public void addListener(final StructureModifier uncompiled, final CompileListener listener) { + synchronized (listenerLock) { + StructureKey key = new StructureKey(uncompiled); + + @SuppressWarnings("rawtypes") + List list = listeners.get(key); + + if (list != null) { + list.add(listener); + } + } + } + + /** + * Retrieve the current usage of the Perm Gen space in percentage. + * @return Usage of the perm gen space. + */ + private double getPermGenUsage() { + for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) { + if (item.getName().contains("Perm Gen")) { + MemoryUsage usage = item.getUsage(); + return usage.getUsed() / (double) usage.getCommitted(); + } + } + + // Unknown + return 0; + } + /** * Clean up after ourselves using the default timeout. */ @@ -246,6 +334,22 @@ public class BackgroundCompiler { this.enabled = enabled; } + /** + * Retrieve the fraction of perm gen space used after which the background compiler will be disabled. + * @return The fraction after which the background compiler is disabled. + */ + public double getDisablePermGenFraction() { + return disablePermGenFraction; + } + + /** + * Set the fraction of perm gen space used after which the background compiler will be disabled. + * @param fraction - the maximum use of perm gen space. + */ + public void setDisablePermGenFraction(double fraction) { + this.disablePermGenFraction = fraction; + } + /** * Retrieve the current structure compiler. * @return Current structure compiler. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index 13d90652..4a4eedd3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -95,7 +95,7 @@ public final class StructureCompiler { // Used to store generated classes of different types @SuppressWarnings("rawtypes") - private static class StructureKey { + static class StructureKey { private Class targetType; private Class fieldType; From 3ae10d9123fc3a2ebd70c89c4d3c021301aaab58 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 14 Feb 2013 20:14:34 +0100 Subject: [PATCH 09/37] Retry again if the hack isn't ready. --- .../injector/player/PlayerInjector.java | 18 ++++++++++++++---- .../player/ProxyPlayerInjectionHandler.java | 14 +++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 8f84cdd0..26acc9e6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -24,6 +24,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; import java.net.SocketAddress; +import java.util.concurrent.Callable; import net.sf.cglib.proxy.Factory; @@ -100,7 +101,7 @@ abstract class PlayerInjector { protected ErrorReporter reporter; // Scheduled action on the next packet event - protected Runnable scheduledAction; + protected Callable scheduledAction; // Whether or not the injector has been cleaned private boolean clean; @@ -519,9 +520,16 @@ abstract class PlayerInjector { // Hack #1: Handle a single scheduled action if (scheduledAction != null) { - scheduledAction.run(); - 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 (id == Packets.Server.LOGIN) { @@ -595,9 +603,11 @@ abstract class PlayerInjector { /** * Schedule an action to occur on the next sent packet. + *

+ * If the callable returns TRUE, the action is removed. * @param action - action to execute. */ - public void scheduleAction(Runnable action) { + public void scheduleAction(Callable action) { scheduledAction = action; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 053fe0ef..1c3945c6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -24,6 +24,7 @@ import java.net.Socket; import java.net.SocketAddress; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.bukkit.Server; @@ -663,10 +664,17 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // Update the DataInputStream if (injector != null) { - injector.scheduleAction(new Runnable() { + injector.scheduleAction(new Callable() { @Override - public void run() { - dataInputLookup.put(injector.getInputStream(false), injector); + public Boolean call() throws Exception { + DataInputStream inputStream = injector.getInputStream(false); + + if (inputStream != null) { + dataInputLookup.put(inputStream, injector); + return true; + } + // Try again + return false; } }); } From 8c0a671078b6af74eeb5e23c657b29dc54b8e839 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 16 Feb 2013 12:33:14 +0100 Subject: [PATCH 10/37] Correctly print the content of map objects in packets. --- .../protocol/reflect/PrettyPrinter.java | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java index f61c2727..d8df1d37 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java @@ -21,6 +21,8 @@ import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import com.google.common.primitives.Primitives; @@ -80,7 +82,7 @@ public class PrettyPrinter { // Start and stop output.append("{ "); - printObject(output, object, start, stop, previous, hierachyDepth); + printObject(output, object, start, stop, previous, hierachyDepth, true); output.append(" }"); return output.toString(); @@ -99,15 +101,42 @@ public class PrettyPrinter { else output.append(", "); - // Handle exceptions - if (value != null) - printValue(output, value, value.getClass(), stop, previous, hierachyIndex - 1); - else - output.append("NULL"); + // Print value + printValue(output, value, stop, previous, hierachyIndex - 1); } output.append(")"); } + + /** + * Print the content of a maps entries. + * @param output - the output string builder. + * @param map - the map to print. + * @param current - the type of this map. + * @param stop - the class that indicates we should stop printing. + * @param previous - previous objects printed. + * @param hierachyIndex - hierachy index. + * @throws IllegalAccessException If any reflection went wrong. + */ + private static void printMap(StringBuilder output, Map map, Class current, Class stop, + Set previous, int hierachyIndex) throws IllegalAccessException { + + boolean first = true; + output.append("["); + + for (Entry entry : map.entrySet()) { + if (first) + first = false; + else + output.append(", "); + + printValue(output, entry.getKey(), stop, previous, hierachyIndex - 1); + output.append(": "); + printValue(output, entry.getValue(), stop, previous, hierachyIndex - 1); + } + + output.append("]"); + } private static void printArray(StringBuilder output, Object array, Class current, Class stop, Set previous, int hierachyIndex) throws IllegalAccessException { @@ -142,10 +171,8 @@ public class PrettyPrinter { // Internal recursion method private static void printObject(StringBuilder output, Object object, Class current, Class stop, - Set previous, int hierachyIndex) throws IllegalAccessException { - // Trickery - boolean first = true; - + Set previous, int hierachyIndex, boolean first) throws IllegalAccessException { + // See if we're supposed to skip this class if (current == Object.class || (stop != null && current.equals(stop))) { return; @@ -168,10 +195,11 @@ public class PrettyPrinter { Class type = field.getType(); Object value = FieldUtils.readField(field, object, true); - if (first) + if (first) { first = false; - else + } else { output.append(", "); + } output.append(field.getName()); output.append(" = "); @@ -180,10 +208,16 @@ public class PrettyPrinter { } // Recurse - printObject(output, object, current.getSuperclass(), stop, previous, hierachyIndex); + printObject(output, object, current.getSuperclass(), stop, previous, hierachyIndex, first); } - @SuppressWarnings("rawtypes") + private static void printValue(StringBuilder output, Object value, Class stop, + Set previous, int hierachyIndex) throws IllegalAccessException { + // Handle the NULL case + printValue(output, value, value != null ? value.getClass() : null, stop, previous, hierachyIndex); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) private static void printValue(StringBuilder output, Object value, Class type, Class stop, Set previous, int hierachyIndex) throws IllegalAccessException { // Just print primitive types @@ -197,12 +231,14 @@ public class PrettyPrinter { printArray(output, value, type, stop, previous, hierachyIndex); } else if (Iterable.class.isAssignableFrom(type)) { printIterables(output, (Iterable) value, type, stop, previous, hierachyIndex); + } else if (Map.class.isAssignableFrom(type)) { + printMap(output, (Map) value, type, stop, previous, hierachyIndex); } else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) { // Don't print previous objects output.append("\"" + value + "\""); } else { output.append("{ "); - printObject(output, value, value.getClass(), stop, previous, hierachyIndex); + printObject(output, value, value.getClass(), stop, previous, hierachyIndex, true); output.append(" }"); } } From e919056f9bfcfff2a4b526fff0324e57c75c3ffa Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 17 Feb 2013 02:32:14 +0100 Subject: [PATCH 11/37] Provide a remove method in NbtCompound. Discourage getValue(). Added a missing remove method in NbtCompound. In addition, the getValue() method in NbtCompount has been depreciated. It is far better to use the put and get methods in NbtCompound instead. --- .../com/comphenix/protocol/wrappers/nbt/NbtBase.java | 3 +++ .../comphenix/protocol/wrappers/nbt/NbtCompound.java | 11 +++++++++++ .../protocol/wrappers/nbt/WrappedCompound.java | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java index 4fda61b8..87f9ffe4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java @@ -64,6 +64,9 @@ public interface NbtBase { * Is either a primitive {@link java.lang.Number wrapper}, {@link java.lang.String String}, * {@link java.util.List List} or a {@link java.util.Map Map}. *

+ * Users are encouraged to cast an NBT compound to {@link NbtCompound} and use its put and get-methods + * instead of accessing its content from getValue(). + *

* All operations that modify collections directly, such as {@link java.util.List#add(Object) List.add(Object)} or * {@link java.util.Map#clear() Map.clear()}, are considered optional. This also include members in {@link java.util.Iterator Iterator} and * {@link java.util.ListIterator ListIterator}. Operations that are not implemented throw a diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java index 5465ae29..cc4deeb3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java @@ -16,6 +16,10 @@ import java.util.Set; * @author Kristian */ public interface NbtCompound extends NbtBase>>, Iterable> { + @Override + @Deprecated() + public Map> getValue(); + /** * Determine if an entry with the given key exists or not. * @param key - the key to lookup. @@ -304,6 +308,13 @@ public interface NbtCompound extends NbtBase>>, Iterable< */ public abstract NbtCompound put(String key, Collection> list); + /** + * Remove the NBT element that is associated with the given key. + * @param key - the key of the element to remove. + * @return The removed element, or NULL if no such element was found. + */ + public abstract NbtBase remove(String key); + /** * Retrieve an iterator view of the NBT tags stored in this compound. * @return The tags stored in this compound. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java index 8d3f6fa8..217c555d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java @@ -215,6 +215,9 @@ class WrappedCompound implements NbtWrapper>>, Iterable NbtCompound put(NbtBase entry) { + if (entry == null) + throw new IllegalArgumentException("Entry cannot be NULL."); + getValue().put(entry.getName(), entry); return this; } @@ -565,6 +568,9 @@ class WrappedCompound implements NbtWrapper>>, Iterable entry) { + if (entry == null) + throw new IllegalArgumentException("Entry cannot be NULL."); + // Don't modify the original NBT NbtBase clone = entry.deepClone(); @@ -583,6 +589,11 @@ class WrappedCompound implements NbtWrapper>>, Iterable NbtBase remove(String key) { + return getValue().remove(key); + } + @Override public void write(DataOutput destination) { NbtBinarySerializer.DEFAULT.serialize(container, destination); From 61ae40b936d601cb2a8049fc7805450dabef80ef Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 18 Feb 2013 13:27:41 +0100 Subject: [PATCH 12/37] NbtCompound can now accepts arbitrary primitive, list or map objects. --- .../protocol/wrappers/nbt/MemoryElement.java | 65 +++++++++++++++++++ .../protocol/wrappers/nbt/NbtCompound.java | 18 ++++- .../wrappers/nbt/WrappedCompound.java | 21 +++++- 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/MemoryElement.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/MemoryElement.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/MemoryElement.java new file mode 100644 index 00000000..e8287954 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/MemoryElement.java @@ -0,0 +1,65 @@ +package com.comphenix.protocol.wrappers.nbt; + +class MemoryElement implements NbtBase { + private String name; + private TType value; + private NbtType type; + + public MemoryElement(String name, TType value) { + if (name == null) + throw new IllegalArgumentException("Name cannot be NULL."); + if (value == null) + throw new IllegalArgumentException("Element cannot be NULL."); + + this.name = name; + this.value = value; + this.type = NbtType.getTypeFromClass(value.getClass()); + } + + public MemoryElement(String name, TType value, NbtType type) { + if (name == null) + throw new IllegalArgumentException("Name cannot be NULL."); + if (type == null) + throw new IllegalArgumentException("Type cannot be NULL."); + + this.name = name; + this.value = value; + this.type = type; + } + + @Override + public boolean accept(NbtVisitor visitor) { + return visitor.visit(this); + } + + @Override + public NbtType getType() { + return type; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public TType getValue() { + return value; + } + + @Override + public void setValue(TType newValue) { + this.value = newValue; + } + + @Override + public NbtBase deepClone() { + // This assumes value is an immutable object + return new MemoryElement(name, value, type); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java index cc4deeb3..245a1c42 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java @@ -5,6 +5,8 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; + /** * Represents a mapping of arbitrary NBT elements and their unique names. *

@@ -52,8 +54,9 @@ public interface NbtCompound extends NbtBase>>, Iterable< * Set a entry based on its name. * @param entry - entry with a name and value. * @return This compound, for chaining. + * @throws IllegalArgumentException If entry is NULL. */ - public abstract NbtCompound put(NbtBase entry); + public abstract NbtCompound put(@Nonnull NbtBase entry); /** * Retrieve the string value of an entry identified by a given key. @@ -255,7 +258,18 @@ public interface NbtCompound extends NbtBase>>, Iterable< * @return This current compound, for chaining. */ public abstract NbtCompound put(String key, int[] value); - + + /** + * Associates a given Java primitive value, list, map or NbtBase with a certain key. + *

+ * If the value is NULL, the corresponding key is removed. + * + * @param key - the name of the new entry, + * @param value - the value of the new entry, or NULL to remove the current value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound putObject(String key, Object value); + /** * Retrieve the compound (map) value of an entry identified by a given key. * @param key - the key of the entry. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java index 217c555d..222d8340 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java @@ -152,7 +152,13 @@ class WrappedCompound implements NbtWrapper>>, Iterable> newValue) { // Write all the entries for (Map.Entry> entry : newValue.entrySet()) { - put(entry.getValue()); + Object value = entry.getValue(); + + // We don't really know + if (value instanceof NbtBase) + put(entry.getValue()); + else + putObject(entry.getKey(), entry.getValue()); } } @@ -255,6 +261,19 @@ class WrappedCompound implements NbtWrapper>>, Iterable) value); + } else { + NbtBase base = new MemoryElement(key, value); + put(base); + } + return this; + } + /** * Retrieve the byte value of an entry identified by a given key. * @param key - the key of the entry. From bf443be0d33206d92b7624699c7d1201e6dc27c5 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 18 Feb 2013 15:49:30 +0100 Subject: [PATCH 13/37] Add the ability to read arbitrary objects. Perhaps NbtBase shouldn't have implemented getValue() after all - it would have been better to have a shared base interface with getName() and getType(), and only let the primitive elements implement getValue(). Too late to change it now though. --- .../comphenix/protocol/wrappers/nbt/NbtCompound.java | 10 +++++++++- .../protocol/wrappers/nbt/WrappedCompound.java | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java index 245a1c42..226d7b61 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java @@ -262,7 +262,8 @@ public interface NbtCompound extends NbtBase>>, Iterable< /** * Associates a given Java primitive value, list, map or NbtBase with a certain key. *

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

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

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

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

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

  • kickPlayer(String)
  • * *

    - * Note that the player a player has not been assigned a name yet, and thus cannot be + * Note that a temporary player has not yet been assigned a name, and thus cannot be * uniquely identified. Use the address instead. * @param injector - the player injector used. * @param server - the current server. @@ -94,7 +93,7 @@ class TemporaryPlayerFactory { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String methodName = method.getName(); - PlayerInjector injector = ((InjectContainer) obj).getInjector(); + SocketInjector injector = ((InjectContainer) obj).getInjector(); if (injector == null) throw new IllegalStateException("Unable to find injector."); @@ -173,7 +172,7 @@ class TemporaryPlayerFactory { * @throws InvocationTargetException If the message couldn't be sent. * @throws FieldAccessException If we were unable to construct the message packet. */ - private Object sendMessage(PlayerInjector injector, String message) throws InvocationTargetException, FieldAccessException { + private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false); return null; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java index f48b01d3..73a1c5e2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java @@ -4,8 +4,6 @@ import java.io.DataInputStream; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.util.Set; -import java.util.concurrent.TimeUnit; - import org.bukkit.entity.Player; import com.comphenix.protocol.concurrency.IntegerSet; @@ -49,12 +47,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler { public void setPlayerHook(PlayerInjectHooks playerHook) { throw new UnsupportedOperationException("This is not needed in Spigot."); } - - @Override - public void scheduleDataInputRefresh(Player player) { - // Fine - } - + @Override public void addPacketHandler(int packetID) { sendingFilters.add(packetID); @@ -106,11 +99,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler { return PlayerInjectHooks.NETWORK_SERVER_OBJECT; } - @Override - public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException { - throw new UnsupportedOperationException("This is not needed in Spigot."); - } - @Override public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { throw new UnsupportedOperationException("This is not needed in Spigot."); @@ -125,4 +113,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler { public void checkListener(Set listeners) { // Yes, really } + + @Override + public void postWorldLoaded() { + // Do nothing + } } diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 2d752252..d717beb0 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -3,7 +3,6 @@ version: 2.2.1-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib -load: startup main: com.comphenix.protocol.ProtocolLibrary database: false From df4542017a09adc7979ee639ec4ede845a16b350 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 25 Feb 2013 02:00:15 +0100 Subject: [PATCH 16/37] Increment to 2.2.2-SNAPSHOT --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 9e6ff3c3..23b12cf4 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 2.2.1-SNAPSHOT + 2.2.2-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index d717beb0..464e0da5 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 2.2.1-SNAPSHOT +version: 2.2.2-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 89d2604ce25cce8854be2dad103d3d281748689c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 25 Feb 2013 14:38:53 +0100 Subject: [PATCH 17/37] We cannot support plugin reloaders (PlugMan, PluginManagers). This is because multiple plugins depend on us, and are not properly notified after ProtocolLib has been reloaded. The only possible solution is to reload every dependent plugin after ProtocolLib has been reloaded, but unfortunately, I ran into LinkageErrors when I tried it. So it's probably not possible with the current architecture to support reloaders. Instead, we'll simply print a BIG BOLD warning telling any users of these plugins that ProtocolLib cannot be reloaded except through the built in "/reload" command. --- .../comphenix/protocol/ProtocolLibrary.java | 28 ++++++++++-- .../protocol/utility/ChatExtensions.java | 45 +++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 97a65df2..0804fe51 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -42,6 +42,7 @@ import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.metrics.Updater; import com.comphenix.protocol.metrics.Updater.UpdateResult; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; +import com.comphenix.protocol.utility.ChatExtensions; /** * The main entry point for ProtocolLib. @@ -216,6 +217,21 @@ public class ProtocolLibrary extends JavaPlugin { // Don't do anything else! if (manager == null) return; + // Silly plugin reloaders! + if (protocolManager == null) { + Logger directLogging = Logger.getLogger("Minecraft"); + String[] message = new String[] { + " PROTOCOLLIB DOES NOT SUPPORT PLUGIN RELOADERS. ", + " PLEASE USE THE BUILT-IN RELOAD COMMAND. ", + }; + + // Print as severe + for (String line : ChatExtensions.toFlowerBox(message, "*", 3, 1)) { + directLogging.severe(line); + } + disablePlugin(); + return; + } // Perform logic when the world has loaded protocolManager.postWorldLoaded(); @@ -261,7 +277,7 @@ public class ProtocolLibrary extends JavaPlugin { reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc); } } - + // Used to check Minecraft version private void verifyMinecraftVersion() { try { @@ -426,9 +442,13 @@ public class ProtocolLibrary extends JavaPlugin { if (redirectHandler != null) { logger.removeHandler(redirectHandler); } - - unhookTask.close(); - protocolManager.close(); + if (protocolManager != null) + protocolManager.close(); + else + return; // Plugin reloaders! + + if (unhookTask != null) + unhookTask.close(); protocolManager = null; statistisc = null; reporter = null; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java index 92af6cca..7fc274d0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java @@ -27,6 +27,7 @@ import com.comphenix.protocol.Packets; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.reflect.FieldAccessException; +import com.google.common.base.Strings; /** * Utility methods for sending chat messages. @@ -98,4 +99,48 @@ public class ChatExtensions { } } } + + /** + * Print a flower box around a given message. + * @param message - the message to print. + * @param marginChar - the character to use as margin. + * @param marginWidth - the width (in characters) of the left and right margin. + * @param marginHeight - the height (in characters) of the top and buttom margin. + */ + public static String[] toFlowerBox(String[] message, String marginChar, int marginWidth, int marginHeight) { + String[] output = new String[message.length + marginHeight * 2]; + int width = getMaximumLength(message); + + // Margins + String topButtomMargin = Strings.repeat(marginChar, width + marginWidth * 2); + String leftRightMargin = Strings.repeat(marginChar, marginWidth); + + // Add left and right margin + for (int i = 0; i < message.length; i++) { + output[i + marginHeight] = leftRightMargin + Strings.padEnd(message[i], width, ' ') + leftRightMargin; + } + + // Insert top and bottom margin + for (int i = 0; i < marginHeight; i++) { + output[i] = topButtomMargin; + output[output.length - i - 1] = topButtomMargin; + } + return output; + } + + /** + * Retrieve the longest line lenght in a list of strings. + * @param lines - the lines. + * @return Longest line lenght. + */ + private static int getMaximumLength(String[] lines) { + int current = 0; + + // Find the longest line + for (int i = 0; i < lines.length; i++) { + if (current < lines[i].length()) + current = lines[i].length(); + } + return current; + } } From 8c2f6bddd874ed8f88e7ba3ca150f52e61799671 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 25 Feb 2013 20:28:57 +0100 Subject: [PATCH 18/37] Moved classes that inject into the server thread to a separate package --- .../player/InjectedServerConnection.java | 1 + .../injector/player/NetLoginInjector.java | 2 + .../player/NetworkObjectInjector.java | 8 ++-- .../injector/player/PlayerInjector.java | 1 + .../player/ProxyPlayerInjectionHandler.java | 21 ++++----- .../DelegatedServerSocket.java | 2 +- .../{player => server}/DelegatedSocket.java | 2 +- .../DelegatedSocketInjector.java | 2 +- .../injector/server/InjectContainer.java | 19 ++++++++ .../InjectedServerSocket.java | 14 ++++-- .../{player => server}/SocketInjector.java | 4 +- .../TemporaryPlayerFactory.java | 47 +++++++++++-------- 12 files changed, 78 insertions(+), 45 deletions(-) rename ProtocolLib/src/main/java/com/comphenix/protocol/injector/{player => server}/DelegatedServerSocket.java (98%) rename ProtocolLib/src/main/java/com/comphenix/protocol/injector/{player => server}/DelegatedSocket.java (99%) rename ProtocolLib/src/main/java/com/comphenix/protocol/injector/{player => server}/DelegatedSocketInjector.java (96%) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java rename ProtocolLib/src/main/java/com/comphenix/protocol/injector/{player => server}/InjectedServerSocket.java (98%) rename ProtocolLib/src/main/java/com/comphenix/protocol/injector/{player => server}/SocketInjector.java (95%) rename ProtocolLib/src/main/java/com/comphenix/protocol/injector/{player => server}/TemporaryPlayerFactory.java (85%) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index d502ea9c..0c259f79 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -27,6 +27,7 @@ import net.sf.cglib.proxy.Factory; import org.bukkit.Server; import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.injector.server.InjectedServerSocket; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.ObjectWriter; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index 96c2e8e8..9632be59 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -25,6 +25,8 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.injector.GamePhase; +import com.comphenix.protocol.injector.server.InjectedServerSocket; +import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index cfff267d..69acf343 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -39,7 +39,7 @@ import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; -import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; +import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; /** * Injection method that overrides the NetworkHandler itself, and its queue-method. @@ -91,10 +91,8 @@ public class NetworkObjectInjector extends PlayerInjector { if (tempPlayerFactory == null) tempPlayerFactory = new TemporaryPlayerFactory(); - // Create and associate this fake player with this network injector - Player player = tempPlayerFactory.createTemporaryPlayer(server); - ((InjectContainer) player).setInjector(this); - return player; + // Create and associate the fake player with this network injector + return tempPlayerFactory.createTemporaryPlayer(server, this); } @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 9ac43b29..5764ac9a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -37,6 +37,7 @@ import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 4c2cf262..908a1ac9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -36,7 +36,9 @@ import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; -import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; +import com.comphenix.protocol.injector.server.InjectedServerSocket; +import com.comphenix.protocol.injector.server.SocketInjector; +import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.google.common.base.Predicate; import com.google.common.collect.Maps; @@ -104,7 +106,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { public void postWorldLoaded() { // This will actually create a socket and a seperate thread ... if (serverSocket != null) { - serverSocket.cycleServerPorts(); + serverSocket.postWorldLoaded(); } } @@ -498,16 +500,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { if (injector == null) { // Try getting it from the player itself - if (player instanceof InjectContainer) { - SocketInjector socket = ((InjectContainer) player).getInjector(); - - // It may be a player injector too - if (socket instanceof PlayerInjector) - return (PlayerInjector) socket; + SocketInjector socket = TemporaryPlayerFactory.getInjectorFromPlayer(player); + + // Only accept it if it's a player injector + if (!(socket instanceof PlayerInjector)) { + socket = serverSocket.getSocketInjector(player.getAddress()); } - - SocketInjector socket = serverSocket.getSocketInjector(player.getAddress()); - + // Ensure that it is a player injector if (socket instanceof PlayerInjector) return (PlayerInjector) socket; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedServerSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedServerSocket.java similarity index 98% rename from ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedServerSocket.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedServerSocket.java index 7b39f246..88b5607c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedServerSocket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedServerSocket.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector.player; +package com.comphenix.protocol.injector.server; import java.io.IOException; import java.net.InetAddress; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocket.java similarity index 99% rename from ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocket.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocket.java index af4bf3ed..b24975c9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocket.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector.player; +package com.comphenix.protocol.injector.server; import java.io.IOException; import java.io.InputStream; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocketInjector.java similarity index 96% rename from ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocketInjector.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocketInjector.java index f8d4afc1..6185a4b9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/DelegatedSocketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocketInjector.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector.player; +package com.comphenix.protocol.injector.server; import java.lang.reflect.InvocationTargetException; import java.net.Socket; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java new file mode 100644 index 00000000..24149a35 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java @@ -0,0 +1,19 @@ +package com.comphenix.protocol.injector.server; + +/** + * Able to store a socket injector. + *

    + * A necessary hack. + * @author Kristian + */ +class InjectContainer { + private SocketInjector injector; + + public SocketInjector getInjector() { + return injector; + } + + public void setInjector(SocketInjector injector) { + this.injector = injector; + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectedServerSocket.java similarity index 98% rename from ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerSocket.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectedServerSocket.java index 7231bb2a..058a1663 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerSocket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectedServerSocket.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector.player; +package com.comphenix.protocol.injector.server; import java.io.FilterInputStream; import java.io.IOException; @@ -23,7 +23,6 @@ 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; @@ -234,12 +233,19 @@ public class InjectedServerSocket { } } + /** + * Invoked when the world has loaded. + */ + public void postWorldLoaded() { + cycleServerPorts(); + } + /** * Invoked when we need to cycle the injected server port. *

    * This uses a fairly significant hack - we connect to our own server. */ - public void cycleServerPorts() { + private void cycleServerPorts() { final ServerSocket serverSocket = (ServerSocket) injectedServerSocket.getValue(); final SocketAddress address = new InetSocketAddress("127.0.0.1", serverSocket.getLocalPort()); @@ -423,7 +429,7 @@ public class InjectedServerSocket { injectedServerSocket.revertValue(); // This is going to suck - //cycleServerPorts(); + cycleServerPorts(); } } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/SocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java similarity index 95% rename from ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/SocketInjector.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java index 9b7fc814..6407d320 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/SocketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector.player; +package com.comphenix.protocol.injector.server; import java.lang.reflect.InvocationTargetException; import java.net.Socket; @@ -11,7 +11,7 @@ import org.bukkit.entity.Player; * * @author Kristian */ -interface SocketInjector { +public interface SocketInjector { /** * Retrieve the associated socket of this player. * @return The associated socket. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java similarity index 85% rename from ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java index c8df9197..75e5b49e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java @@ -15,7 +15,7 @@ * 02111-1307 USA */ -package com.comphenix.protocol.injector.player; +package com.comphenix.protocol.injector.server; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -36,25 +36,7 @@ import com.comphenix.protocol.reflect.FieldAccessException; /** * Create fake player instances that represents pre-authenticated clients. */ -class TemporaryPlayerFactory { - /** - * Able to store a socket injector. - *

    - * A necessary hack. - * @author Kristian - */ - public static class InjectContainer { - private SocketInjector injector; - - public SocketInjector getInjector() { - return injector; - } - - public void setInjector(SocketInjector injector) { - this.injector = injector; - } - } - +public class TemporaryPlayerFactory { // Helpful constructors private final PacketConstructor chatPacket; @@ -65,6 +47,18 @@ class TemporaryPlayerFactory { chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" }); } + /** + * Retrieve the injector from a given player if it contains one. + * @param player - the player that may contain a reference to a player injector. + * @return The referenced player injector, or NULL if none can be found. + */ + public static SocketInjector getInjectorFromPlayer(Player player) { + if (player instanceof InjectContainer) { + return ((InjectContainer) player).getInjector(); + } + return null; + } + /** * Construct a temporary player that supports a subset of every player command. *

    @@ -164,6 +158,19 @@ class TemporaryPlayerFactory { return (Player) ex.create(); } + /** + * Construct a temporary player with the given associated socket injector. + * @param server - the parent server. + * @param injector - the referenced socket injector. + * @return The temporary player. + */ + public Player createTemporaryPlayer(Server server, SocketInjector injector) { + Player temporary = createTemporaryPlayer(server); + + ((InjectContainer) temporary).setInjector(injector); + return temporary; + } + /** * Send a message to the given client. * @param injector - the injector representing the client. From 9195e677ab07f67d39209a5ff3e2539ff355eb93 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 25 Feb 2013 20:32:27 +0100 Subject: [PATCH 19/37] Essentially, it's a lookup for a player's input stream. Or: InputStream -> PlayerInjector -> Player. --- .../protocol/injector/player/InjectedServerConnection.java | 6 +++--- .../protocol/injector/player/NetLoginInjector.java | 6 +++--- .../injector/player/ProxyPlayerInjectionHandler.java | 6 +++--- ...jectedServerSocket.java => InputStreamPlayerLookup.java} | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/{InjectedServerSocket.java => InputStreamPlayerLookup.java} (99%) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index 0c259f79..9b87c532 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -27,7 +27,7 @@ import net.sf.cglib.proxy.Factory; import org.bukkit.Server; import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.injector.server.InjectedServerSocket; +import com.comphenix.protocol.injector.server.InputStreamPlayerLookup; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.ObjectWriter; @@ -55,7 +55,7 @@ class InjectedServerConnection { private NetLoginInjector netLoginInjector; // Inject server connections - private InjectedServerSocket socketInjector; + private InputStreamPlayerLookup socketInjector; private Server server; private ErrorReporter reporter; @@ -64,7 +64,7 @@ class InjectedServerConnection { private Object minecraftServer = null; - public InjectedServerConnection(ErrorReporter reporter, InjectedServerSocket socketInjector, Server server, NetLoginInjector netLoginInjector) { + public InjectedServerConnection(ErrorReporter reporter, InputStreamPlayerLookup socketInjector, Server server, NetLoginInjector netLoginInjector) { this.listFields = new ArrayList(); this.replacedLists = new ArrayList>(); this.reporter = reporter; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index 9632be59..4a4b806f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -25,7 +25,7 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.server.InjectedServerSocket; +import com.comphenix.protocol.injector.server.InputStreamPlayerLookup; import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; @@ -45,12 +45,12 @@ class NetLoginInjector { private ProxyPlayerInjectionHandler injectionHandler; // Associate input streams and injectors - private InjectedServerSocket serverSocket; + private InputStreamPlayerLookup serverSocket; // The current error rerporter private ErrorReporter reporter; - public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, InjectedServerSocket serverSocket) { + public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, InputStreamPlayerLookup serverSocket) { this.reporter = reporter; this.injectionHandler = injectionHandler; this.serverSocket = serverSocket; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 908a1ac9..a713a9d0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -36,7 +36,7 @@ import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; -import com.comphenix.protocol.injector.server.InjectedServerSocket; +import com.comphenix.protocol.injector.server.InputStreamPlayerLookup; import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.google.common.base.Predicate; @@ -52,7 +52,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { private InjectedServerConnection serverInjection; // Server socket injection - private InjectedServerSocket serverSocket; + private InputStreamPlayerLookup serverSocket; // NetLogin injector private NetLoginInjector netLoginInjector; @@ -96,7 +96,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { this.invoker = invoker; this.injectionFilter = injectionFilter; this.packetListeners = packetListeners; - this.serverSocket = new InjectedServerSocket(reporter, server); + this.serverSocket = new InputStreamPlayerLookup(reporter, server); this.netLoginInjector = new NetLoginInjector(reporter, this, serverSocket); this.serverInjection = new InjectedServerConnection(reporter, serverSocket, server, netLoginInjector); serverInjection.injectList(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectedServerSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamPlayerLookup.java similarity index 99% rename from ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectedServerSocket.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamPlayerLookup.java index 058a1663..5fb38abd 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectedServerSocket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamPlayerLookup.java @@ -34,7 +34,7 @@ import com.google.common.collect.MapMaker; * * @author Kristian */ -public class InjectedServerSocket { +public class InputStreamPlayerLookup { /** * The read and connect timeout for our built-in MOTD reader. */ @@ -162,7 +162,7 @@ public class InjectedServerSocket { // Used to create fake players private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); - public InjectedServerSocket(ErrorReporter reporter, Server server) { + public InputStreamPlayerLookup(ErrorReporter reporter, Server server) { this.reporter = reporter; this.server = server; } From 2cf265f8e806b1f0f97c453b1db508154ca1de86 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 25 Feb 2013 22:12:18 +0100 Subject: [PATCH 20/37] Switch to a better InputStream -> Socket lookup for normal JVM. --- .../comphenix/protocol/ProtocolConfig.java | 18 + .../comphenix/protocol/ProtocolLibrary.java | 5 +- .../injector/PacketFilterManager.java | 3 +- .../player/InjectedServerConnection.java | 6 +- .../injector/player/NetLoginInjector.java | 10 +- .../player/PlayerInjectorBuilder.java | 13 +- .../player/ProxyPlayerInjectionHandler.java | 43 +- .../server/AbstractInputStreamLookup.java | 148 ++++++ .../injector/server/InjectContainer.java | 2 + .../server/InputStreamLookupBuilder.java | 61 +++ .../server/InputStreamPlayerLookup.java | 435 ------------------ .../server/InputStreamProxyLookup.java | 233 ++++++++++ .../server/InputStreamReflectLookup.java | 89 ++++ .../server/TemporaryPlayerFactory.java | 9 + .../server/TemporarySocketInjector.java | 108 +++++ ProtocolLib/src/main/resources/config.yml | 6 +- 16 files changed, 724 insertions(+), 465 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamPlayerLookup.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporarySocketInjector.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index ce084d0a..c8d1211f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -47,6 +47,8 @@ class ProtocolConfig { private static final String UPDATER_DELAY = "delay"; private static final String UPDATER_LAST_TIME = "last"; + private static final String ALTERNATIVE_JVM = "alternate jvm"; + // Defaults private static final long DEFAULT_UPDATER_DELAY = 43200; @@ -229,6 +231,22 @@ class ProtocolConfig { 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. * @param lastTimeSeconds - new last update time. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 0804fe51..07497651 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -136,7 +136,10 @@ public class ProtocolLibrary extends JavaPlugin { updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); 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); // Update injection hook diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index e373126d..ca58b656 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -150,7 +150,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * Only create instances of this class if protocol lib is disabled. * @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) throw new IllegalArgumentException("reporter cannot be NULL."); if (classLoader == null) @@ -200,6 +200,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok classLoader(classLoader). packetListeners(packetListeners). injectionFilter(isInjectionNecessary). + alternativeJVM(alternateJVM). buildHandler(); this.packetInjector = PacketInjectorBuilder.newBuilder(). diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index 9b87c532..4dd2fb9a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -27,7 +27,7 @@ import net.sf.cglib.proxy.Factory; import org.bukkit.Server; 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.FuzzyReflection; import com.comphenix.protocol.reflect.ObjectWriter; @@ -55,7 +55,7 @@ class InjectedServerConnection { private NetLoginInjector netLoginInjector; // Inject server connections - private InputStreamPlayerLookup socketInjector; + private AbstractInputStreamLookup socketInjector; private Server server; private ErrorReporter reporter; @@ -64,7 +64,7 @@ class InjectedServerConnection { 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(); this.replacedLists = new ArrayList>(); this.reporter = reporter; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index 4a4b806f..cb2032e6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -25,7 +25,7 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.error.ErrorReporter; 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.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; @@ -45,15 +45,15 @@ class NetLoginInjector { private ProxyPlayerInjectionHandler injectionHandler; // Associate input streams and injectors - private InputStreamPlayerLookup serverSocket; + private AbstractInputStreamLookup inputStreamLookup; // The current error rerporter private ErrorReporter reporter; - public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, InputStreamPlayerLookup serverSocket) { + public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, AbstractInputStreamLookup inputStreamLookup) { this.reporter = reporter; this.injectionHandler = injectionHandler; - this.serverSocket = serverSocket; + this.inputStreamLookup = inputStreamLookup; } /** @@ -74,7 +74,7 @@ class NetLoginInjector { // Get the underlying socket 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 if (socketInjector != null) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java index 120d5dc9..83746a94 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java @@ -37,6 +37,7 @@ public class PlayerInjectorBuilder { protected ListenerInvoker invoker; protected Set packetListeners; protected Server server; + protected boolean alternativeJVM; /** * Set the class loader to use during class generation. @@ -107,6 +108,16 @@ public class PlayerInjectorBuilder { 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. */ @@ -140,6 +151,6 @@ public class PlayerInjectorBuilder { return new ProxyPlayerInjectionHandler( classLoader, reporter, injectionFilter, - invoker, packetListeners, server); + invoker, packetListeners, server, alternativeJVM); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index a713a9d0..62da9e9b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -36,7 +36,8 @@ import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PlayerLoggedOutException; 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.TemporaryPlayerFactory; import com.google.common.base.Predicate; @@ -52,7 +53,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { private InjectedServerConnection serverInjection; // Server socket injection - private InputStreamPlayerLookup serverSocket; + private AbstractInputStreamLookup inputStreamLookup; // NetLogin injector private NetLoginInjector netLoginInjector; @@ -88,25 +89,33 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // Used to filter injection attempts private Predicate injectionFilter; - public ProxyPlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate injectionFilter, - ListenerInvoker invoker, Set packetListeners, Server server) { + public ProxyPlayerInjectionHandler( + ClassLoader classLoader, ErrorReporter reporter, Predicate injectionFilter, + ListenerInvoker invoker, Set packetListeners, Server server, boolean alternateJVM) { this.classLoader = classLoader; this.reporter = reporter; this.invoker = invoker; this.injectionFilter = injectionFilter; this.packetListeners = packetListeners; - this.serverSocket = new InputStreamPlayerLookup(reporter, server); - this.netLoginInjector = new NetLoginInjector(reporter, this, serverSocket); - this.serverInjection = new InjectedServerConnection(reporter, serverSocket, server, netLoginInjector); + + this.inputStreamLookup = InputStreamLookupBuilder.newBuilder(). + 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(); } @Override public void postWorldLoaded() { // This will actually create a socket and a seperate thread ... - if (serverSocket != null) { - serverSocket.postWorldLoaded(); + if (inputStreamLookup != null) { + inputStreamLookup.postWorldLoaded(); } } @@ -208,7 +217,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { @Override public Player getPlayerByConnection(DataInputStream inputStream) { // Wait until the connection owner has been established - SocketInjector injector = serverSocket.getSocketInjector(inputStream); + SocketInjector injector = inputStreamLookup.getSocketInjector(inputStream); if (injector != null) { return injector.getPlayer(); @@ -298,7 +307,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { Socket socket = injector.getSocket(); // 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 if (previous != null) { @@ -308,7 +317,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { injector.injectManager(); // Save injector - serverSocket.setSocketInjector(inputStream, injector); + inputStreamLookup.setSocketInjector(inputStream, injector); break; } @@ -435,7 +444,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { @Override public boolean uninjectPlayer(InetSocketAddress address) { if (!hasClosed && address != null) { - SocketInjector injector = serverSocket.getSocketInjector(address); + SocketInjector injector = inputStreamLookup.getSocketInjector(address); // Clean up if (injector != null) @@ -504,7 +513,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // Only accept it if it's a player injector if (!(socket instanceof PlayerInjector)) { - socket = serverSocket.getSocketInjector(player.getAddress()); + socket = inputStreamLookup.getSocketInjector(player.getAddress()); } // Ensure that it is a player injector @@ -597,13 +606,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { } // Remove server handler - if (serverSocket != null) - serverSocket.cleanupAll(); + if (inputStreamLookup != null) + inputStreamLookup.cleanupAll(); if (serverInjection != null) serverInjection.cleanupAll(); if (netLoginInjector != null) netLoginInjector.cleanupAll(); - serverSocket = null; + inputStreamLookup = null; serverInjection = null; netLoginInjector = null; hasClosed = true; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java new file mode 100644 index 00000000..ed2a636a --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java @@ -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 ownerSocket = new MapMaker().weakKeys().makeMap(); + protected ConcurrentMap 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(); +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java index 24149a35..11cb476b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java @@ -14,6 +14,8 @@ class InjectContainer { } public void setInjector(SocketInjector injector) { + if (injector == null) + throw new IllegalArgumentException("Injector cannot be NULL."); this.injector = injector; } } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java new file mode 100644 index 00000000..6c156059 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java @@ -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); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamPlayerLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamPlayerLookup.java deleted file mode 100644 index 5fb38abd..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamPlayerLookup.java +++ /dev/null @@ -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 syncronizedQueue = Collections.synchronizedList(new ArrayList()); - - /** - * Represents a temporary socket injector. - * @param temporaryPlayer - temporary player instance. - * @param socket - the socket we are representing. - * @param fake - whether or not this connection should be ignored. - */ - public TemporarySocketInjector(Player temporaryPlayer, Socket socket) { - this.temporaryPlayer = temporaryPlayer; - this.socket = socket; - } - - @Override - public Socket getSocket() throws IllegalAccessException { - return socket; - } - - @Override - public SocketAddress getAddress() throws IllegalAccessException { - if (socket != null) - return socket.getRemoteSocketAddress(); - return null; - } - - @Override - public void disconnect(String message) throws InvocationTargetException { - // We have no choice - disregard message too - try { - socket.close(); - } catch (IOException e) { - throw new InvocationTargetException(e); - } - } - - @Override - public void sendServerPacket(Object packet, boolean filtered) - throws InvocationTargetException { - SendPacketCommand command = new SendPacketCommand(packet, filtered); - - // Queue until we can find something better - syncronizedQueue.add(command); - } - - @Override - public Player getPlayer() { - return temporaryPlayer; - } - - @Override - public Player getUpdatedPlayer() { - return temporaryPlayer; - } - - @Override - public void transferState(SocketInjector delegate) { - // Transmit all queued packets to a different injector. - try { - synchronized(syncronizedQueue) { - for (SendPacketCommand command : syncronizedQueue) { - delegate.sendServerPacket(command.getPacket(), command.isFiltered()); - } - syncronizedQueue.clear(); - } - } catch (InvocationTargetException e) { - throw new RuntimeException("Unable to transmit packets to " + delegate + " from old injector.", e); - } - } - } - - // Used to access the inner input stream of a filtered input stream - private static Field filteredInputField; - - // Using weak keys and values ensures that we will not hold up garbage collection - private ConcurrentMap ownerSocket = new MapMaker().weakKeys().makeMap(); - private ConcurrentMap addressLookup = new MapMaker().weakValues().makeMap(); - - // Fake connections - private Set fakeConnections = Collections.newSetFromMap( - new MapMaker().weakKeys().makeMap() - ); - - // The server socket that has been injected - private VolatileField injectedServerSocket; - - // Reference to the server itself - private final Server server; - - // Error reporter - private final ErrorReporter reporter; - - // Used to create fake players - private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); - - public 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. - *

    - * 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(); - } - } -} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java new file mode 100644 index 00000000..060f0ea9 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java @@ -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 fakeConnections = Collections.newSetFromMap( + new MapMaker().weakKeys().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. + *

    + * 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(); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java new file mode 100644 index 00000000..bb8230d1 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java @@ -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); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java index 75e5b49e..0e6b4f15 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java @@ -59,6 +59,15 @@ public class TemporaryPlayerFactory { 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. *

    diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporarySocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporarySocketInjector.java new file mode 100644 index 00000000..e9f40a85 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporarySocketInjector.java @@ -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 syncronizedQueue = Collections.synchronizedList(new ArrayList()); + + /** + * Represents a temporary socket injector. + * @param temporaryPlayer - temporary player instance. + * @param socket - the socket we are representing. + * @param fake - whether or not this connection should be ignored. + */ + public TemporarySocketInjector(Player temporaryPlayer, Socket socket) { + this.temporaryPlayer = temporaryPlayer; + this.socket = socket; + } + + @Override + public Socket getSocket() throws IllegalAccessException { + return socket; + } + + @Override + public SocketAddress getAddress() throws IllegalAccessException { + if (socket != null) + return socket.getRemoteSocketAddress(); + return null; + } + + @Override + public void disconnect(String message) throws InvocationTargetException { + // We have no choice - disregard message too + try { + socket.close(); + } catch (IOException e) { + throw new InvocationTargetException(e); + } + } + + @Override + public void sendServerPacket(Object packet, boolean filtered) + throws InvocationTargetException { + SendPacketCommand command = new SendPacketCommand(packet, filtered); + + // Queue until we can find something better + syncronizedQueue.add(command); + } + + @Override + public Player getPlayer() { + return temporaryPlayer; + } + + @Override + public Player getUpdatedPlayer() { + return temporaryPlayer; + } + + @Override + public void transferState(SocketInjector delegate) { + // Transmit all queued packets to a different injector. + try { + synchronized(syncronizedQueue) { + for (SendPacketCommand command : syncronizedQueue) { + delegate.sendServerPacket(command.getPacket(), command.isFiltered()); + } + syncronizedQueue.clear(); + } + } catch (InvocationTargetException e) { + throw new RuntimeException("Unable to transmit packets to " + delegate + " from old injector.", e); + } + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml index e3c48aff..2f5a61e6 100644 --- a/ProtocolLib/src/main/resources/config.yml +++ b/ProtocolLib/src/main/resources/config.yml @@ -17,6 +17,8 @@ global: # Disable version checking for the given Minecraft version. Backup your world first! 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 - injection method: - \ No newline at end of file + injection method: \ No newline at end of file From 575248063d2394a8f03cc2dd56891acadad3faa9 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 26 Feb 2013 01:23:54 +0100 Subject: [PATCH 21/37] Attempt to correct a NPE due to a NULL socket. --- .../injector/player/NetLoginInjector.java | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index cb2032e6..db454e15 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -17,8 +17,8 @@ package com.comphenix.protocol.injector.player; -import java.lang.reflect.Method; -import java.net.Socket; +import java.lang.reflect.Field; +import java.net.SocketAddress; import java.util.concurrent.ConcurrentMap; import org.bukkit.entity.Player; @@ -27,6 +27,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.injector.server.SocketInjector; +import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; @@ -39,7 +40,8 @@ import com.google.common.collect.Maps; class NetLoginInjector { private ConcurrentMap injectedLogins = Maps.newConcurrentMap(); - private static Method getSocketMethod; + private static Field networkManagerField; + private static Field socketAddressField; // Handles every hook private ProxyPlayerInjectionHandler injectionHandler; @@ -67,14 +69,11 @@ class NetLoginInjector { if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) return inserting; - if (getSocketMethod == null) { - getSocketMethod = FuzzyReflection.fromObject(inserting). - getMethodByParameters("getSocket", Socket.class, new Class[0]); - } - + Object networkManager = getNetworkManager(inserting); + SocketAddress address = getAddress(networkManager); + // Get the underlying socket - Socket socket = (Socket) getSocketMethod.invoke(inserting); - SocketInjector socketInjector = inputStreamLookup.getSocketInjector(socket); + SocketInjector socketInjector = inputStreamLookup.getSocketInjector(address); // This is the case if we're dealing with a connection initiated by the injected server socket if (socketInjector != null) { @@ -96,6 +95,36 @@ class NetLoginInjector { } } + /** + * Retrieve the network manager from a given pending connection. + * @param inserting - the pending connection. + * @return The referenced network manager. + * @throws IllegalAccessException If we are unable to read the network manager. + */ + private Object getNetworkManager(Object inserting) throws IllegalAccessException { + if (networkManagerField == null) { + networkManagerField = FuzzyReflection.fromObject(inserting, true). + getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass()); + } + + return FieldUtils.readField(networkManagerField, inserting, true); + } + + /** + * Retrieve the socket address stored in a network manager. + * @param networkManager - the network manager. + * @return The associated socket address. + * @throws IllegalAccessException If we are unable to read the address. + */ + private SocketAddress getAddress(Object networkManager) throws IllegalAccessException { + if (socketAddressField == null) { + socketAddressField = FuzzyReflection.fromObject(networkManager, true). + getFieldByType("socketAddress", SocketAddress.class); + } + + return (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true); + } + /** * Invoked when a NetLoginHandler should be reverted. * @param inserting - the original NetLoginHandler. From 7968f1ce4f7414caaf236eac5fa57637df542aa6 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 26 Feb 2013 01:24:20 +0100 Subject: [PATCH 22/37] Incremented to 2.2.3-SNAPSHOT --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 23b12cf4..ad361142 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 464e0da5..4dac7aae 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 2.2.2-SNAPSHOT +version: 2.2.3-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 3357fd6c9c23958e2d5677b313c68d7b76b4664b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 26 Feb 2013 13:37:33 +0100 Subject: [PATCH 23/37] Fix NPE in CleanupStaticMembers. Determine if this is really necessary. No point cleanup up after yourself in a sewer. --- .../comphenix/protocol/CleanupStaticMembers.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index 57f31fe7..dd7c5bbe 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -27,6 +27,9 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; +import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; +import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.MethodUtils; @@ -36,9 +39,11 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler; import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; +import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; /** * Used to fix ClassLoader leaks that may lead to filling up the permanent generation. @@ -66,7 +71,9 @@ class CleanupStaticMembers { PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, BackgroundCompiler.class, StructureCompiler.class, ObjectWriter.class, Packets.Server.class, Packets.Client.class, - ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class + ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class, + AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class, + MinecraftReflection.class, NbtBinarySerializer.class }; String[] internalClasses = { @@ -76,14 +83,14 @@ class CleanupStaticMembers { "com.comphenix.protocol.injector.player.NetworkObjectInjector", "com.comphenix.protocol.injector.player.NetworkServerInjector", "com.comphenix.protocol.injector.player.PlayerInjector", - "com.comphenix.protocol.injector.player.TemporaryPlayerFactory", "com.comphenix.protocol.injector.EntityUtilities", "com.comphenix.protocol.injector.packet.PacketRegistry", "com.comphenix.protocol.injector.packet.PacketInjector", "com.comphenix.protocol.injector.packet.ReadPacketModifier", "com.comphenix.protocol.injector.StructureCache", "com.comphenix.protocol.reflect.compiler.BoxingHelper", - "com.comphenix.protocol.reflect.compiler.MethodDescriptor" + "com.comphenix.protocol.reflect.compiler.MethodDescriptor", + "com.comphenix.protocol.wrappers.nbt.WrappedElement", }; resetClasses(publicClasses); From 56807cbd3a6f5e7f36b9534a31c3bef79d3950ce Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 26 Feb 2013 19:14:40 +0100 Subject: [PATCH 24/37] Don't throw a NullPointerException if the injection fails. --- .../protocol/injector/player/NetLoginInjector.java | 13 ++++++++----- .../player/ProxyPlayerInjectionHandler.java | 9 ++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index db454e15..f7f669be 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -78,10 +78,13 @@ class NetLoginInjector { // This is the case if we're dealing with a connection initiated by the injected server socket if (socketInjector != null) { PlayerInjector injector = injectionHandler.injectPlayer(socketInjector.getPlayer(), inserting, GamePhase.LOGIN); - injector.updateOnLogin = true; - - // Save the login - injectedLogins.putIfAbsent(inserting, injector); + + if (injector != null) { + injector.updateOnLogin = true; + + // Save the login + injectedLogins.putIfAbsent(inserting, injector); + } } // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler @@ -90,7 +93,7 @@ class NetLoginInjector { } catch (Throwable e) { // Minecraft can't handle this, so we'll deal with it here reporter.reportDetailed(this, "Unable to hook " + - MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting); + MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting, injectionHandler); return inserting; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 62da9e9b..5a2b4c45 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -269,6 +269,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { * @return The resulting player injector, or NULL if the injection failed. */ PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) { + if (player == null) + throw new IllegalArgumentException("Player cannot be NULL."); + if (injectionPoint == null) + throw new IllegalArgumentException("injectionPoint cannot be NULL."); + if (phase == null) + throw new IllegalArgumentException("phase cannot be NULL."); + // Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method. synchronized (player) { return injectPlayerInternal(player, injectionPoint, phase); @@ -288,7 +295,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { boolean invalidInjector = injector != null ? !injector.canInject(phase) : true; // Don't inject if the class has closed - if (!hasClosed && player != null && (tempHook != getInjectorType(injector) || invalidInjector)) { + if (!hasClosed && (tempHook != getInjectorType(injector) || invalidInjector)) { while (tempHook != PlayerInjectHooks.NONE) { // Whether or not the current hook method failed completely boolean hookFailed = false; From c32d225ef307e8b9aab644f5dbf5322f0803e5b5 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 27 Feb 2013 01:09:22 +0100 Subject: [PATCH 25/37] Use socket as key instead of input stream. --- .../player/ProxyPlayerInjectionHandler.java | 16 +-- .../server/AbstractInputStreamLookup.java | 69 ++++------- .../injector/server/InjectContainer.java | 2 +- .../server/InputStreamProxyLookup.java | 62 +++++++++- .../server/InputStreamReflectLookup.java | 112 ++++++++++++++---- 5 files changed, 179 insertions(+), 82 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 5a2b4c45..043ceddf 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -284,7 +284,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // Unsafe variant of the above private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, GamePhase phase) { - PlayerInjector injector = playerInjection.get(player); PlayerInjectHooks tempHook = getPlayerHook(phase); PlayerInjectHooks permanentHook = tempHook; @@ -310,21 +309,24 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { if (injector.canInject(phase)) { injector.initialize(injectionPoint); - DataInputStream inputStream = injector.getInputStream(false); + // Get socket and socket injector Socket socket = injector.getSocket(); + SocketInjector previous = null; - // Guard against NPE here too - SocketInjector previous = socket != null ? inputStreamLookup.getSocketInjector(socket) : null; + // Due to a race condition, the main server "accept connections" thread may + // get a closed network manager with a NULL input stream, + if (socket == null) { + + } // Close any previously associated hooks before we proceed - if (previous != null) { + if (previous != null && previous instanceof PlayerInjector) { uninjectPlayer(previous.getPlayer(), true); } - injector.injectManager(); // Save injector - inputStreamLookup.setSocketInjector(inputStream, injector); + inputStreamLookup.setSocketInjector(socket, injector); break; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java index ed2a636a..b7b88d6b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java @@ -5,8 +5,6 @@ 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; @@ -14,15 +12,10 @@ 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 ownerSocket = new MapMaker().weakKeys().makeMap(); - protected ConcurrentMap addressLookup = new MapMaker().weakValues().makeMap(); // Error reporter protected final ErrorReporter reporter; @@ -82,63 +75,45 @@ public abstract class AbstractInputStreamLookup { /** * Retrieve the associated socket injector for a player. - * @param filtered - the indentifying filtered input stream. + * @param input - 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()); - } + public abstract SocketInjector getSocketInjector(Socket socket); /** - * 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. + * Retrieve a injector by its address. + * @param address - the address of the socket. + * @return The socket injector, or NULL if not found. */ - public void setSocketInjector(FilterInputStream input, SocketInjector injector) { - setSocketInjector(getInputStream(input), injector); - } + public abstract SocketInjector getSocketInjector(SocketAddress address); /** - * Associate a given input stream with the provided socket injector. - * @param input - the input stream to associate. + * Associate a given socket the provided socket injector. + * @param input - the socket 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); - } - } - + public abstract void setSocketInjector(Socket socket, SocketInjector injector); + + /** + * If a player can hold a reference to its parent injector, this method will update that reference. + * @param previous - the previous injector. + * @param current - the new injector. + */ protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) { - // Do nothing + Player player = previous.getPlayer(); + + // Default implementation + if (player instanceof InjectContainer) { + InjectContainer container = (InjectContainer) player; + container.setInjector(current); + } } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java index 11cb476b..7d3738df 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java @@ -7,7 +7,7 @@ package com.comphenix.protocol.injector.server; * @author Kristian */ class InjectContainer { - private SocketInjector injector; + private volatile SocketInjector injector; public SocketInjector getInjector() { return injector; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java index 060f0ea9..0976086a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java @@ -12,6 +12,7 @@ import java.net.SocketAddress; import java.nio.charset.Charset; import java.util.Collections; import java.util.Set; +import java.util.concurrent.ConcurrentMap; import org.bukkit.Server; import org.bukkit.entity.Player; @@ -34,6 +35,11 @@ class InputStreamProxyLookup extends AbstractInputStreamLookup { private static final int READ_TIMEOUT = 5000; private static final int CONNECT_TIMEOUT = 1000; + // Using weak keys and values ensures that we will not hold up garbage collection + protected ConcurrentMap ownerSocket = new MapMaker().weakKeys().makeMap(); + protected ConcurrentMap addressLookup = new MapMaker().weakValues().makeMap(); + protected ConcurrentMap socketLookup = new MapMaker().weakKeys().makeMap(); + // Fake connections private Set fakeConnections = Collections.newSetFromMap( new MapMaker().weakKeys().makeMap() @@ -89,7 +95,7 @@ class InputStreamProxyLookup extends AbstractInputStreamLookup { 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 @@ -99,6 +105,9 @@ class InputStreamProxyLookup extends AbstractInputStreamLookup { // Update it TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, socketInjector); + + // Socket lookup + socketLookup.put(this, input); // Associate the socket with a given input stream setSocketInjector(input, socketInjector); @@ -115,6 +124,54 @@ class InputStreamProxyLookup extends AbstractInputStreamLookup { } } + @Override + public SocketInjector getSocketInjector(Socket socket) { + InputStream stream = getStream(socket); + + if (stream != null) + return getSocketInjector(stream); + else + return null; + } + + @Override + public void setSocketInjector(Socket socket, SocketInjector injector) { + InputStream stream = getStream(socket); + + if (stream != null) { + socketLookup.put(socket, stream); + setSocketInjector(stream, injector); + } + } + + /** + * Set the referenced socket injector by input stream. + * @param stream - the input stream. + * @param injector - the injector to reference. + */ + public void setSocketInjector(InputStream stream, SocketInjector injector) { + SocketInjector previous = ownerSocket.put(stream, injector); + + // Handle overwrite + if (previous != null) { + onPreviousSocketOverwritten(previous, injector); + } + } + + private InputStream getStream(Socket socket) { + InputStream result = socketLookup.get(socket); + + // Use the socket as well + if (result == null) { + try { + result = socket.getInputStream(); + } catch (IOException e) { + throw new RuntimeException("Unable to retrieve input stream from socket " + socket, e); + } + } + return result; + } + @Override public SocketInjector getSocketInjector(InputStream input) { return ownerSocket.get(input); @@ -213,6 +270,9 @@ class InputStreamProxyLookup extends AbstractInputStreamLookup { @Override protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) { + // Don't forget this + super.onPreviousSocketOverwritten(previous, current); + if (previous instanceof DelegatedSocketInjector) { DelegatedSocketInjector delegated = (DelegatedSocketInjector) previous; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java index bb8230d1..7aeae97a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java @@ -5,6 +5,7 @@ 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; @@ -13,8 +14,14 @@ 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; class InputStreamReflectLookup extends AbstractInputStreamLookup { + // Using weak keys and values ensures that we will not hold up garbage collection + protected ConcurrentMap ownerSocket = new MapMaker().weakKeys().makeMap(); + protected ConcurrentMap addressLookup = new MapMaker().weakValues().makeMap(); + protected ConcurrentMap inputLookup = new MapMaker().weakValues().makeMap(); + // Used to create fake players private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); @@ -33,39 +40,86 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup { } @Override - public SocketInjector getSocketInjector(InputStream input) { - SocketInjector injector = ownerSocket.get(input); + public SocketInjector getSocketInjector(Socket socket) { + SocketInjector result = ownerSocket.get(socket); - 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; + if (result == null) { + Player player = tempPlayerFactory.createTemporaryPlayer(server); + SocketInjector created = new TemporarySocketInjector(player, socket); + + result = ownerSocket.putIfAbsent(socket, created); - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot find or access socket field for " + input, e); + if (result == null) { + // We won - use our created injector + TemporaryPlayerFactory.setInjectorInPlayer(player, created); + result = created; } } + return result; + } + + @Override + public SocketInjector getSocketInjector(InputStream input) { + try { + Socket socket = getSocket(input); + + // Guard against NPE + if (socket != null) + return getSocketInjector(socket); + else + return null; + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot find or access socket field for " + input, e); + } } + /** + * Use reflection to get the underlying socket from an input stream. + * @param stream - the socket stream to lookup. + * @return The underlying socket, or NULL if not found. + * @throws IllegalAccessException Unable to access socket field. + */ + private Socket getSocket(InputStream stream) throws IllegalAccessException { + // Extra check, just in case + if (stream instanceof FilterInputStream) + return getSocket(getInputStream((FilterInputStream) stream)); + + Socket result = inputLookup.get(stream); + + if (result == null) { + result = lookupSocket(stream); + + // Save it + inputLookup.put(stream, result); + } + return result; + } + + @Override + public void setSocketInjector(Socket socket, SocketInjector injector) { + if (socket == null) + throw new IllegalArgumentException("socket cannot be NULL"); + if (injector == null) + throw new IllegalArgumentException("injector cannot be NULL."); + + SocketInjector previous = ownerSocket.put(socket, injector); + + // Save the address lookup too + addressLookup.put(socket.getRemoteSocketAddress(), socket); + + // Any previous temporary players will also be associated + if (previous != null) { + // Update the reference to any previous injector + onPreviousSocketOverwritten(previous, injector); + } + } + @Override public SocketInjector getSocketInjector(SocketAddress address) { - InputStream input = addressLookup.get(address); + Socket socket = addressLookup.get(address); - if (input != null) - return getSocketInjector(input); + if (socket != null) + return getSocketInjector(socket); else return null; } @@ -75,9 +129,15 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup { // Do nothing } - private static Socket getSocket(InputStream stream) throws IllegalAccessException { + /** + * Lookup the underlying socket of a stream through reflection. + * @param stream - the socket stream. + * @return The underlying socket. + * @throws IllegalAccessException If reflection failed. + */ + private static Socket lookupSocket(InputStream stream) throws IllegalAccessException { if (stream instanceof FilterInputStream) { - return getSocket(getInputStream((FilterInputStream) stream)); + return lookupSocket(getInputStream((FilterInputStream) stream)); } else { // Just do it Field socketField = FuzzyReflection.fromObject(stream, true). From c7737ca96e424b5d0bded56ce6565bd795ac7398 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 27 Feb 2013 01:10:21 +0100 Subject: [PATCH 26/37] It's an injector container. --- .../injector/server/AbstractInputStreamLookup.java | 4 ++-- ...InjectContainer.java => InjectorContainer.java} | 2 +- .../injector/server/TemporaryPlayerFactory.java | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) rename ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/{InjectContainer.java => InjectorContainer.java} (93%) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java index b7b88d6b..eef3275d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java @@ -110,8 +110,8 @@ public abstract class AbstractInputStreamLookup { Player player = previous.getPlayer(); // Default implementation - if (player instanceof InjectContainer) { - InjectContainer container = (InjectContainer) player; + if (player instanceof InjectorContainer) { + InjectorContainer container = (InjectorContainer) player; container.setInjector(current); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectorContainer.java similarity index 93% rename from ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectorContainer.java index 7d3738df..7a642a09 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectorContainer.java @@ -6,7 +6,7 @@ package com.comphenix.protocol.injector.server; * A necessary hack. * @author Kristian */ -class InjectContainer { +class InjectorContainer { private volatile SocketInjector injector; public SocketInjector getInjector() { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java index 0e6b4f15..91d1fed6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java @@ -53,8 +53,8 @@ public class TemporaryPlayerFactory { * @return The referenced player injector, or NULL if none can be found. */ public static SocketInjector getInjectorFromPlayer(Player player) { - if (player instanceof InjectContainer) { - return ((InjectContainer) player).getInjector(); + if (player instanceof InjectorContainer) { + return ((InjectorContainer) player).getInjector(); } return null; } @@ -65,7 +65,7 @@ public class TemporaryPlayerFactory { * @param injector - the injector to store. */ public static void setInjectorInPlayer(Player player, SocketInjector injector) { - ((InjectContainer) player).setInjector(injector); + ((InjectorContainer) player).setInjector(injector); } /** @@ -96,7 +96,7 @@ public class TemporaryPlayerFactory { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String methodName = method.getName(); - SocketInjector injector = ((InjectContainer) obj).getInjector(); + SocketInjector injector = ((InjectorContainer) obj).getInjector(); if (injector == null) throw new IllegalStateException("Unable to find injector."); @@ -149,7 +149,7 @@ public class TemporaryPlayerFactory { public int accept(Method method) { // Do not override the object method or the superclass methods if (method.getDeclaringClass().equals(Object.class) || - method.getDeclaringClass().equals(InjectContainer.class)) + method.getDeclaringClass().equals(InjectorContainer.class)) return 0; else return 1; @@ -159,7 +159,7 @@ public class TemporaryPlayerFactory { // CGLib is amazing Enhancer ex = new Enhancer(); - ex.setSuperclass(InjectContainer.class); + ex.setSuperclass(InjectorContainer.class); ex.setInterfaces(new Class[] { Player.class }); ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation }); ex.setCallbackFilter(callbackFilter); @@ -176,7 +176,7 @@ public class TemporaryPlayerFactory { public Player createTemporaryPlayer(Server server, SocketInjector injector) { Player temporary = createTemporaryPlayer(server); - ((InjectContainer) temporary).setInjector(injector); + ((InjectorContainer) temporary).setInjector(injector); return temporary; } From fbfbd28bea98e61609a24099e59c871ca6dbb21d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 27 Feb 2013 01:15:06 +0100 Subject: [PATCH 27/37] We might revert this some other time, if we need to support other JVMs --- .../comphenix/protocol/ProtocolConfig.java | 20 +- .../comphenix/protocol/ProtocolLibrary.java | 2 +- .../injector/PacketFilterManager.java | 3 +- .../player/PlayerInjectorBuilder.java | 13 +- .../player/ProxyPlayerInjectionHandler.java | 4 +- .../server/DelegatedServerSocket.java | 122 -------- .../injector/server/DelegatedSocket.java | 241 -------------- .../server/DelegatedSocketInjector.java | 64 ---- .../server/InputStreamLookupBuilder.java | 18 +- .../server/InputStreamProxyLookup.java | 293 ------------------ .../server/TemporarySocketInjector.java | 108 ------- ProtocolLib/src/main/resources/config.yml | 3 - 12 files changed, 8 insertions(+), 883 deletions(-) delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedServerSocket.java delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocket.java delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocketInjector.java delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporarySocketInjector.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index c8d1211f..87e5b523 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -47,8 +47,6 @@ class ProtocolConfig { private static final String UPDATER_DELAY = "delay"; private static final String UPDATER_LAST_TIME = "last"; - private static final String ALTERNATIVE_JVM = "alternate jvm"; - // Defaults private static final long DEFAULT_UPDATER_DELAY = 43200; @@ -230,23 +228,7 @@ class ProtocolConfig { public void setBackgroundCompilerEnabled(boolean 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. * @param lastTimeSeconds - new last update time. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 07497651..271c1181 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -137,7 +137,7 @@ public class ProtocolLibrary extends JavaPlugin { unhookTask = new DelayedSingleTask(this); protocolManager = new PacketFilterManager( - getClassLoader(), getServer(), unhookTask, detailedReporter, config.isAlternateJVM()); + getClassLoader(), getServer(), unhookTask, detailedReporter); // Setup error reporter detailedReporter.addGlobalParameter("manager", protocolManager); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index ca58b656..e373126d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -150,7 +150,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * Only create instances of this class if protocol lib is disabled. * @param unhookTask */ - public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter, boolean alternateJVM) { + public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter) { if (reporter == null) throw new IllegalArgumentException("reporter cannot be NULL."); if (classLoader == null) @@ -200,7 +200,6 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok classLoader(classLoader). packetListeners(packetListeners). injectionFilter(isInjectionNecessary). - alternativeJVM(alternateJVM). buildHandler(); this.packetInjector = PacketInjectorBuilder.newBuilder(). diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java index 83746a94..120d5dc9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectorBuilder.java @@ -37,7 +37,6 @@ public class PlayerInjectorBuilder { protected ListenerInvoker invoker; protected Set packetListeners; protected Server server; - protected boolean alternativeJVM; /** * Set the class loader to use during class generation. @@ -108,16 +107,6 @@ public class PlayerInjectorBuilder { 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. */ @@ -151,6 +140,6 @@ public class PlayerInjectorBuilder { return new ProxyPlayerInjectionHandler( classLoader, reporter, injectionFilter, - invoker, packetListeners, server, alternativeJVM); + invoker, packetListeners, server); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 043ceddf..74bcaafd 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -91,7 +91,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { public ProxyPlayerInjectionHandler( ClassLoader classLoader, ErrorReporter reporter, Predicate injectionFilter, - ListenerInvoker invoker, Set packetListeners, Server server, boolean alternateJVM) { + ListenerInvoker invoker, Set packetListeners, Server server) { this.classLoader = classLoader; this.reporter = reporter; @@ -102,7 +102,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { this.inputStreamLookup = InputStreamLookupBuilder.newBuilder(). server(server). reporter(reporter). - alternativeJVM(alternateJVM). build(); // Create net login injectors and the server connection injector @@ -323,6 +322,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { if (previous != null && previous instanceof PlayerInjector) { uninjectPlayer(previous.getPlayer(), true); } + injector.injectManager(); // Save injector diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedServerSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedServerSocket.java deleted file mode 100644 index 88b5607c..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedServerSocket.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.comphenix.protocol.injector.server; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.nio.channels.ServerSocketChannel; - -class DelegatedServerSocket extends ServerSocket { - protected ServerSocket delegate; - - public DelegatedServerSocket(ServerSocket delegate) throws IOException { - super(); - this.delegate = delegate; - } - - @Override - public void close() throws IOException { - delegate.close(); - } - - @Override - public Socket accept() throws IOException { - return delegate.accept(); - } - - @Override - public void bind(SocketAddress endpoint) throws IOException { - delegate.bind(endpoint); - } - - @Override - public void bind(SocketAddress endpoint, int backlog) throws IOException { - delegate.bind(endpoint, backlog); - } - - @Override - public boolean equals(Object obj) { - return delegate.equals(obj); - } - - @Override - public ServerSocketChannel getChannel() { - return delegate.getChannel(); - } - - @Override - public InetAddress getInetAddress() { - return delegate.getInetAddress(); - } - - @Override - public int getLocalPort() { - return delegate.getLocalPort(); - } - - @Override - public SocketAddress getLocalSocketAddress() { - return delegate.getLocalSocketAddress(); - } - - @Override - public synchronized int getReceiveBufferSize() throws SocketException { - return delegate.getReceiveBufferSize(); - } - - @Override - public boolean getReuseAddress() throws SocketException { - return delegate.getReuseAddress(); - } - - @Override - public synchronized int getSoTimeout() throws IOException { - return delegate.getSoTimeout(); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public boolean isBound() { - return delegate.isBound(); - } - - @Override - public boolean isClosed() { - return delegate.isClosed(); - } - - @Override - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { - delegate.setPerformancePreferences(connectionTime, latency, bandwidth); - } - - @Override - public synchronized void setReceiveBufferSize(int size) throws SocketException { - delegate.setReceiveBufferSize(size); - } - - @Override - public void setReuseAddress(boolean on) throws SocketException { - delegate.setReuseAddress(on); - } - - @Override - public synchronized void setSoTimeout(int timeout) throws SocketException { - delegate.setSoTimeout(timeout); - } - - @Override - public String toString() { - return delegate.toString(); - } - - public ServerSocket getDelegate() { - return delegate; - } -} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocket.java deleted file mode 100644 index b24975c9..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocket.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.comphenix.protocol.injector.server; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.nio.channels.SocketChannel; - -// This is a fixed JVM class, so there's probably no need to use CGLib -class DelegatedSocket extends Socket { - protected Socket delegate; - - public DelegatedSocket(Socket delegate) { - super(); - this.delegate = delegate; - } - - @Override - public void bind(SocketAddress arg0) throws IOException { - delegate.bind(arg0); - } - - @Override - public synchronized void close() throws IOException { - delegate.close(); - } - - @Override - public void connect(SocketAddress endpoint) throws IOException { - delegate.connect(endpoint); - } - - @Override - public void connect(SocketAddress endpoint, int timeout) throws IOException { - delegate.connect(endpoint, timeout); - } - - @Override - public boolean equals(Object obj) { - return delegate.equals(obj); - } - - @Override - public SocketChannel getChannel() { - return delegate.getChannel(); - } - - @Override - public InetAddress getInetAddress() { - return delegate.getInetAddress(); - } - - @Override - public InputStream getInputStream() throws IOException { - return delegate.getInputStream(); - } - - @Override - public boolean getKeepAlive() throws SocketException { - return delegate.getKeepAlive(); - } - - @Override - public InetAddress getLocalAddress() { - return delegate.getLocalAddress(); - } - - @Override - public int getLocalPort() { - return delegate.getLocalPort(); - } - - @Override - public SocketAddress getLocalSocketAddress() { - return delegate.getLocalSocketAddress(); - } - - @Override - public boolean getOOBInline() throws SocketException { - return delegate.getOOBInline(); - } - - @Override - public OutputStream getOutputStream() throws IOException { - return delegate.getOutputStream(); - } - - @Override - public int getPort() { - return delegate.getPort(); - } - - @Override - public synchronized int getReceiveBufferSize() throws SocketException { - return delegate.getReceiveBufferSize(); - } - - @Override - public SocketAddress getRemoteSocketAddress() { - return delegate.getRemoteSocketAddress(); - } - - @Override - public boolean getReuseAddress() throws SocketException { - return delegate.getReuseAddress(); - } - - @Override - public synchronized int getSendBufferSize() throws SocketException { - return delegate.getSendBufferSize(); - } - - @Override - public int getSoLinger() throws SocketException { - return delegate.getSoLinger(); - } - - @Override - public synchronized int getSoTimeout() throws SocketException { - return delegate.getSoTimeout(); - } - - @Override - public boolean getTcpNoDelay() throws SocketException { - return delegate.getTcpNoDelay(); - } - - @Override - public int getTrafficClass() throws SocketException { - return delegate.getTrafficClass(); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public boolean isBound() { - return delegate.isBound(); - } - - @Override - public boolean isClosed() { - return delegate.isClosed(); - } - - @Override - public boolean isConnected() { - return delegate.isConnected(); - } - - @Override - public boolean isInputShutdown() { - return delegate.isInputShutdown(); - } - - @Override - public boolean isOutputShutdown() { - return delegate.isOutputShutdown(); - } - - @Override - public void sendUrgentData(int data) throws IOException { - delegate.sendUrgentData(data); - } - - @Override - public void setKeepAlive(boolean on) throws SocketException { - delegate.setKeepAlive(on); - } - - @Override - public void setOOBInline(boolean on) throws SocketException { - delegate.setOOBInline(on); - } - - @Override - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { - delegate.setPerformancePreferences(connectionTime, latency, bandwidth); - } - - @Override - public synchronized void setReceiveBufferSize(int size) throws SocketException { - delegate.setReceiveBufferSize(size); - } - - @Override - public void setReuseAddress(boolean on) throws SocketException { - delegate.setReuseAddress(on); - } - - @Override - public synchronized void setSendBufferSize(int size) throws SocketException { - - delegate.setSendBufferSize(size); - } - - @Override - public void setSoLinger(boolean on, int linger) throws SocketException { - delegate.setSoLinger(on, linger); - } - - @Override - public synchronized void setSoTimeout(int timeout) throws SocketException { - delegate.setSoTimeout(timeout); - } - - @Override - public void setTcpNoDelay(boolean on) throws SocketException { - delegate.setTcpNoDelay(on); - } - - @Override - public void setTrafficClass(int tc) throws SocketException { - delegate.setTrafficClass(tc); - } - - @Override - public void shutdownInput() throws IOException { - delegate.shutdownInput(); - } - - @Override - public void shutdownOutput() throws IOException { - delegate.shutdownOutput(); - } - - @Override - public String toString() { - return delegate.toString(); - } - - public Socket getDelegate() { - return delegate; - } -} - diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocketInjector.java deleted file mode 100644 index 6185a4b9..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/DelegatedSocketInjector.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.comphenix.protocol.injector.server; - -import java.lang.reflect.InvocationTargetException; -import java.net.Socket; -import java.net.SocketAddress; - -import org.bukkit.entity.Player; - -/** - * Represents a socket injector that delegates to a passed injector. - * @author Kristian - * - */ -class DelegatedSocketInjector implements SocketInjector { - private volatile SocketInjector delegate; - - public DelegatedSocketInjector(SocketInjector delegate) { - this.delegate = delegate; - } - - @Override - public void disconnect(String message) throws InvocationTargetException { - delegate.disconnect(message); - } - @Override - public SocketAddress getAddress() throws IllegalAccessException { - return delegate.getAddress(); - } - - @Override - public Player getPlayer() { - return delegate.getPlayer(); - } - - @Override - public Socket getSocket() throws IllegalAccessException { - return delegate.getSocket(); - } - - @Override - public Player getUpdatedPlayer() { - return delegate.getUpdatedPlayer(); - } - - @Override - public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { - delegate.sendServerPacket(packet, filtered); - } - - public SocketInjector getDelegate() { - return delegate; - } - - @Override - public void transferState(SocketInjector delegate) { - delegate.transferState(delegate); - } - - public synchronized void setDelegate(SocketInjector delegate) { - // Let the old delegate pass values to the new - this.delegate.transferState(delegate); - this.delegate = delegate; - } -} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java index 6c156059..29989e5d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java @@ -20,8 +20,7 @@ public class InputStreamLookupBuilder { private Server server; private ErrorReporter reporter; - private boolean alternativeJVM; - + /** * Set the server instance to use. * @param server - server instance. @@ -42,20 +41,7 @@ public class InputStreamLookupBuilder { 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); + return new InputStreamReflectLookup(reporter, server); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java deleted file mode 100644 index 0976086a..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamProxyLookup.java +++ /dev/null @@ -1,293 +0,0 @@ -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 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.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; - - // Using weak keys and values ensures that we will not hold up garbage collection - protected ConcurrentMap ownerSocket = new MapMaker().weakKeys().makeMap(); - protected ConcurrentMap addressLookup = new MapMaker().weakValues().makeMap(); - protected ConcurrentMap socketLookup = new MapMaker().weakKeys().makeMap(); - - // Fake connections - private Set fakeConnections = Collections.newSetFromMap( - new MapMaker().weakKeys().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); - - // Socket lookup - socketLookup.put(this, input); - - // 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(Socket socket) { - InputStream stream = getStream(socket); - - if (stream != null) - return getSocketInjector(stream); - else - return null; - } - - @Override - public void setSocketInjector(Socket socket, SocketInjector injector) { - InputStream stream = getStream(socket); - - if (stream != null) { - socketLookup.put(socket, stream); - setSocketInjector(stream, injector); - } - } - - /** - * Set the referenced socket injector by input stream. - * @param stream - the input stream. - * @param injector - the injector to reference. - */ - public void setSocketInjector(InputStream stream, SocketInjector injector) { - SocketInjector previous = ownerSocket.put(stream, injector); - - // Handle overwrite - if (previous != null) { - onPreviousSocketOverwritten(previous, injector); - } - } - - private InputStream getStream(Socket socket) { - InputStream result = socketLookup.get(socket); - - // Use the socket as well - if (result == null) { - try { - result = socket.getInputStream(); - } catch (IOException e) { - throw new RuntimeException("Unable to retrieve input stream from socket " + socket, e); - } - } - return result; - } - - @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. - *

    - * 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) { - // Don't forget this - super.onPreviousSocketOverwritten(previous, 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(); - } - } -} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporarySocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporarySocketInjector.java deleted file mode 100644 index e9f40a85..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporarySocketInjector.java +++ /dev/null @@ -1,108 +0,0 @@ -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 syncronizedQueue = Collections.synchronizedList(new ArrayList()); - - /** - * Represents a temporary socket injector. - * @param temporaryPlayer - temporary player instance. - * @param socket - the socket we are representing. - * @param fake - whether or not this connection should be ignored. - */ - public TemporarySocketInjector(Player temporaryPlayer, Socket socket) { - this.temporaryPlayer = temporaryPlayer; - this.socket = socket; - } - - @Override - public Socket getSocket() throws IllegalAccessException { - return socket; - } - - @Override - public SocketAddress getAddress() throws IllegalAccessException { - if (socket != null) - return socket.getRemoteSocketAddress(); - return null; - } - - @Override - public void disconnect(String message) throws InvocationTargetException { - // We have no choice - disregard message too - try { - socket.close(); - } catch (IOException e) { - throw new InvocationTargetException(e); - } - } - - @Override - public void sendServerPacket(Object packet, boolean filtered) - throws InvocationTargetException { - SendPacketCommand command = new SendPacketCommand(packet, filtered); - - // Queue until we can find something better - syncronizedQueue.add(command); - } - - @Override - public Player getPlayer() { - return temporaryPlayer; - } - - @Override - public Player getUpdatedPlayer() { - return temporaryPlayer; - } - - @Override - public void transferState(SocketInjector delegate) { - // Transmit all queued packets to a different injector. - try { - synchronized(syncronizedQueue) { - for (SendPacketCommand command : syncronizedQueue) { - delegate.sendServerPacket(command.getPacket(), command.isFiltered()); - } - syncronizedQueue.clear(); - } - } catch (InvocationTargetException e) { - throw new RuntimeException("Unable to transmit packets to " + delegate + " from old injector.", e); - } - } -} \ No newline at end of file diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml index 2f5a61e6..46869ad7 100644 --- a/ProtocolLib/src/main/resources/config.yml +++ b/ProtocolLib/src/main/resources/config.yml @@ -17,8 +17,5 @@ global: # Disable version checking for the given Minecraft version. Backup your world first! 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 injection method: \ No newline at end of file From d60ab3e953c004f528061befbe994e7fbd6fec02 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 28 Feb 2013 01:39:49 +0100 Subject: [PATCH 28/37] Identify player connections by socket address. It's the only thing that will not not be removed when a network manager closes, making it relatively safe to block on. --- .../protocol/concurrency/BlockingHashMap.java | 76 ++++++++--- .../injector/player/NetLoginInjector.java | 80 +++--------- .../player/NetworkServerInjector.java | 5 +- .../injector/player/PlayerInjector.java | 31 +++-- .../player/ProxyPlayerInjectionHandler.java | 45 +++---- .../server/AbstractInputStreamLookup.java | 32 +++-- .../server/InputStreamReflectLookup.java | 119 ++++++++++-------- 7 files changed, 202 insertions(+), 186 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java index 54295cdb..19d35b51 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java @@ -19,15 +19,20 @@ package com.comphenix.protocol.concurrency; import java.util.Collection; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; -import com.google.common.collect.MapMaker; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; /** * A map that supports blocking on read operations. Null keys are not supported. *

    - * Keys are stored as weak references, and will be automatically removed once they've all been dereferenced. + * Values are stored as weak references, and will be automatically removed once they've all been dereferenced. *

    * @author Kristian * @@ -35,10 +40,10 @@ import com.google.common.collect.MapMaker; * @param - type of the value. */ public class BlockingHashMap { - // Map of values + private final Cache backingCache; private final ConcurrentMap backingMap; - + // Map of locked objects private final ConcurrentMap locks; @@ -46,8 +51,24 @@ public class BlockingHashMap { * Initialize a new map. */ public BlockingHashMap() { - backingMap = new MapMaker().weakKeys().makeMap(); - locks = new MapMaker().weakKeys().makeMap(); + backingCache = CacheBuilder.newBuilder().weakValues().removalListener( + new RemovalListener() { + @Override + public void onRemoval(RemovalNotification entry) { + // Clean up locks too + locks.remove(entry.getKey()); + } + }).build( + new CacheLoader() { + @Override + public TValue load(TKey key) throws Exception { + throw new IllegalStateException("Illegal use. Access the map directly instead."); + } + }); + backingMap = backingCache.asMap(); + + // Normal concurrent hash map + locks = new ConcurrentHashMap(); } /** @@ -94,34 +115,57 @@ public class BlockingHashMap { * @throws InterruptedException If the current thread got interrupted while waiting. */ public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException { + return get(key, timeout, unit, false); + } + + /** + * Waits until a value has been associated with the given key, and then retrieves that value. + *

    + * If timeout is zero, this method will return immediately if it can't find an socket injector. + * + * @param key - the key whose associated value is to be returned + * @param timeout - the amount of time to wait until an association has been made. + * @param unit - unit of timeout. + * @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise. + * @return The value to which the specified key is mapped, or NULL if the timeout elapsed. + * @throws InterruptedException If the current thread got interrupted while waiting. + */ + public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException { if (key == null) throw new IllegalArgumentException("key cannot be NULL."); if (unit == null) throw new IllegalArgumentException("Unit cannot be NULL."); + if (timeout < 0) + throw new IllegalArgumentException("Timeout cannot be less than zero."); TValue value = backingMap.get(key); // Only lock if no value is available - if (value == null) { + if (value == null && timeout > 0) { final Object lock = getLock(key); final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout); // Don't exceed the timeout synchronized (lock) { while (value == null) { - long remainingTime = stopTimeNS - System.nanoTime(); - - if (remainingTime > 0) { - TimeUnit.NANOSECONDS.timedWait(lock, remainingTime); - value = backingMap.get(key); - } else { - // Timeout elapsed - break; + try { + long remainingTime = stopTimeNS - System.nanoTime(); + + if (remainingTime > 0) { + TimeUnit.NANOSECONDS.timedWait(lock, remainingTime); + value = backingMap.get(key); + } else { + // Timeout elapsed + break; + } + } catch (InterruptedException e) { + // This is fairly dangerous - but we might HAVE to block the thread + if (!ignoreInterrupted) + throw e; } } } } - return value; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index f7f669be..fc5052d6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -17,18 +17,14 @@ package com.comphenix.protocol.injector.player; -import java.lang.reflect.Field; -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.injector.GamePhase; -import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; -import com.comphenix.protocol.injector.server.SocketInjector; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; @@ -39,23 +35,21 @@ import com.google.common.collect.Maps; */ class NetLoginInjector { private ConcurrentMap injectedLogins = Maps.newConcurrentMap(); - - private static Field networkManagerField; - private static Field socketAddressField; - + // Handles every hook private ProxyPlayerInjectionHandler injectionHandler; + + // Create temporary players + private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory(); - // Associate input streams and injectors - private AbstractInputStreamLookup inputStreamLookup; - - // The current error rerporter + // The current error reporter private ErrorReporter reporter; + private Server server; - public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, AbstractInputStreamLookup inputStreamLookup) { + public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) { this.reporter = reporter; + this.server = server; this.injectionHandler = injectionHandler; - this.inputStreamLookup = inputStreamLookup; } /** @@ -69,22 +63,16 @@ class NetLoginInjector { if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) return inserting; - Object networkManager = getNetworkManager(inserting); - SocketAddress address = getAddress(networkManager); + Player temporary = playerFactory.createTemporaryPlayer(server); + PlayerInjector injector = injectionHandler.injectPlayer(temporary, inserting, GamePhase.LOGIN); - // Get the underlying socket - SocketInjector socketInjector = inputStreamLookup.getSocketInjector(address); - - // This is the case if we're dealing with a connection initiated by the injected server socket - if (socketInjector != null) { - PlayerInjector injector = injectionHandler.injectPlayer(socketInjector.getPlayer(), inserting, GamePhase.LOGIN); - - if (injector != null) { - injector.updateOnLogin = true; - - // Save the login - injectedLogins.putIfAbsent(inserting, injector); - } + if (injector != null) { + // Update injector as well + TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector); + injector.updateOnLogin = true; + + // Save the login + injectedLogins.putIfAbsent(inserting, injector); } // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler @@ -98,36 +86,6 @@ class NetLoginInjector { } } - /** - * Retrieve the network manager from a given pending connection. - * @param inserting - the pending connection. - * @return The referenced network manager. - * @throws IllegalAccessException If we are unable to read the network manager. - */ - private Object getNetworkManager(Object inserting) throws IllegalAccessException { - if (networkManagerField == null) { - networkManagerField = FuzzyReflection.fromObject(inserting, true). - getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass()); - } - - return FieldUtils.readField(networkManagerField, inserting, true); - } - - /** - * Retrieve the socket address stored in a network manager. - * @param networkManager - the network manager. - * @return The associated socket address. - * @throws IllegalAccessException If we are unable to read the address. - */ - private SocketAddress getAddress(Object networkManager) throws IllegalAccessException { - if (socketAddressField == null) { - socketAddressField = FuzzyReflection.fromObject(networkManager, true). - getFieldByType("socketAddress", SocketAddress.class); - } - - return (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true); - } - /** * Invoked when a NetLoginHandler should be reverted. * @param inserting - the original NetLoginHandler. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index f81be360..d9a7f447 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -180,6 +180,7 @@ class NetworkServerInjector extends PlayerInjector { return; if (!tryInjectManager()) { + Class serverHandlerClass = MinecraftReflection.getNetServerHandlerClass(); // Try to override the proxied object if (proxyServerField != null) { @@ -188,6 +189,8 @@ class NetworkServerInjector extends PlayerInjector { if (serverHandler == null) throw new RuntimeException("Cannot hook player: Inner proxy object is NULL."); + else + serverHandlerClass = serverHandler.getClass(); // Try again if (tryInjectManager()) { @@ -198,7 +201,7 @@ class NetworkServerInjector extends PlayerInjector { throw new RuntimeException( "Cannot hook player: Unable to find a valid constructor for the " - + MinecraftReflection.getNetServerHandlerClass().getName() + " object."); + + serverHandlerClass.getName() + " object."); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 5764ac9a..524bd40a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -60,6 +60,7 @@ abstract class PlayerInjector implements SocketInjector { protected static Field networkManagerField; protected static Field netHandlerField; protected static Field socketField; + protected static Field socketAddressField; private static Field inputField; private static Field entityPlayerField; @@ -87,8 +88,9 @@ abstract class PlayerInjector implements SocketInjector { protected Object serverHandler; protected Object netHandler; - // Current socket + // Current socket and address protected Socket socket; + protected SocketAddress socketAddress; // The packet manager and filters protected ListenerInvoker invoker; @@ -250,7 +252,8 @@ abstract class PlayerInjector implements SocketInjector { public Socket getSocket() throws IllegalAccessException { try { if (socketField == null) - socketField = FuzzyReflection.fromObject(networkManager, true).getFieldListByType(Socket.class).get(0); + socketField = FuzzyReflection.fromObject(networkManager, true). + getFieldListByType(Socket.class).get(0); if (socket == null) socket = (Socket) FieldUtils.readField(socketField, networkManager, true); return socket; @@ -261,19 +264,23 @@ abstract class PlayerInjector implements SocketInjector { } /** - * Retrieve the associated address of this player. - * @return The associated address. - * @throws IllegalAccessException If we're unable to read the socket field. + * Retrieve the associated remote address of a player. + * @return The associated remote address.. + * @throws IllegalAccessException If we're unable to read the socket address field. */ @Override public SocketAddress getAddress() throws IllegalAccessException { - Socket socket = getSocket(); - - // Guard against NULL - if (socket != null) - return socket.getRemoteSocketAddress(); - else - return null; + try { + if (socketAddressField == null) + socketAddressField = FuzzyReflection.fromObject(networkManager, true). + getFieldListByType(SocketAddress.class).get(0); + if (socketAddress == null) + socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true); + return socketAddress; + + } catch (IndexOutOfBoundsException e) { + throw new IllegalAccessException("Unable to read the socket address field."); + } } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 74bcaafd..14091c80 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -20,9 +20,12 @@ package com.comphenix.protocol.injector.player; import java.io.DataInputStream; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; -import java.net.Socket; +import java.net.SocketAddress; import java.util.Map; import java.util.Set; + +import net.sf.cglib.proxy.Factory; + import org.bukkit.Server; import org.bukkit.entity.Player; @@ -39,7 +42,7 @@ import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; 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.TemporaryPlayerFactory; + import com.google.common.base.Predicate; import com.google.common.collect.Maps; @@ -105,7 +108,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { build(); // Create net login injectors and the server connection injector - this.netLoginInjector = new NetLoginInjector(reporter, this, inputStreamLookup); + this.netLoginInjector = new NetLoginInjector(reporter, server, this); this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector); serverInjection.injectList(); } @@ -216,7 +219,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { @Override public Player getPlayerByConnection(DataInputStream inputStream) { // Wait until the connection owner has been established - SocketInjector injector = inputStreamLookup.getSocketInjector(inputStream); + SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream); if (injector != null) { return injector.getPlayer(); @@ -309,24 +312,18 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { injector.initialize(injectionPoint); // Get socket and socket injector - Socket socket = injector.getSocket(); - SocketInjector previous = null; - - // Due to a race condition, the main server "accept connections" thread may - // get a closed network manager with a NULL input stream, - if (socket == null) { - - } - + SocketAddress address = injector.getAddress(); + SocketInjector previous = inputStreamLookup.peekSocketInjector(address); + // Close any previously associated hooks before we proceed - if (previous != null && previous instanceof PlayerInjector) { + if (previous != null && !(player instanceof Factory)) { uninjectPlayer(previous.getPlayer(), true); } injector.injectManager(); // Save injector - inputStreamLookup.setSocketInjector(socket, injector); + inputStreamLookup.setSocketInjector(address, injector); break; } @@ -453,7 +450,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { @Override public boolean uninjectPlayer(InetSocketAddress address) { if (!hasClosed && address != null) { - SocketInjector injector = inputStreamLookup.getSocketInjector(address); + SocketInjector injector = inputStreamLookup.peekSocketInjector(address); // Clean up if (injector != null) @@ -495,7 +492,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { */ @Override public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { - PlayerInjector injector = getInjector(player); // Process the given packet, or simply give up @@ -518,16 +514,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { if (injector == null) { // Try getting it from the player itself - SocketInjector socket = TemporaryPlayerFactory.getInjectorFromPlayer(player); - - // Only accept it if it's a player injector - if (!(socket instanceof PlayerInjector)) { - socket = inputStreamLookup.getSocketInjector(player.getAddress()); - } + SocketAddress address = player.getAddress(); + // Look that up without blocking + SocketInjector result = inputStreamLookup.peekSocketInjector(address); - // Ensure that it is a player injector - if (socket instanceof PlayerInjector) - return (PlayerInjector) socket; + // Ensure that it is non-null and a player injector + if (result instanceof PlayerInjector) + return (PlayerInjector) result; else return null; } else { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java index eef3275d..86b13605 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java @@ -63,43 +63,40 @@ public abstract class AbstractInputStreamLookup { */ 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 input - the indentifying filtered input stream. * @return The socket injector we have associated with this player. */ - public abstract SocketInjector getSocketInjector(InputStream input); + public abstract SocketInjector waitSocketInjector(InputStream input); /** * Retrieve an injector by its socket. * @param socket - the socket. * @return The socket injector. */ - public abstract SocketInjector getSocketInjector(Socket socket); + public abstract SocketInjector waitSocketInjector(Socket socket); /** * Retrieve a injector by its address. * @param address - the address of the socket. * @return The socket injector, or NULL if not found. */ - public abstract SocketInjector getSocketInjector(SocketAddress address); + public abstract SocketInjector waitSocketInjector(SocketAddress address); /** - * Associate a given socket the provided socket injector. - * @param input - the socket to associate. + * Attempt to get a socket injector without blocking the thread. + * @param address - the address to lookup. + * @return The socket injector, or NULL if not found. + */ + public abstract SocketInjector peekSocketInjector(SocketAddress address); + + /** + * Associate a given socket address to the provided socket injector. + * @param input - the socket address to associate. * @param injector - the injector. */ - public abstract void setSocketInjector(Socket socket, SocketInjector injector); + public abstract void setSocketInjector(SocketAddress address, SocketInjector injector); /** * If a player can hold a reference to its parent injector, this method will update that reference. @@ -111,8 +108,7 @@ public abstract class AbstractInputStreamLookup { // Default implementation if (player instanceof InjectorContainer) { - InjectorContainer container = (InjectorContainer) player; - container.setInjector(current); + TemporaryPlayerFactory.setInjectorInPlayer(player, current); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java index 7aeae97a..58305d49 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java @@ -6,10 +6,11 @@ import java.lang.reflect.Field; import java.net.Socket; import java.net.SocketAddress; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; import org.bukkit.Server; -import org.bukkit.entity.Player; +import com.comphenix.protocol.concurrency.BlockingHashMap; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; @@ -17,18 +18,33 @@ import com.comphenix.protocol.reflect.FuzzyReflection; import com.google.common.collect.MapMaker; class InputStreamReflectLookup extends AbstractInputStreamLookup { - // Using weak keys and values ensures that we will not hold up garbage collection - protected ConcurrentMap ownerSocket = new MapMaker().weakKeys().makeMap(); - protected ConcurrentMap addressLookup = new MapMaker().weakValues().makeMap(); - protected ConcurrentMap inputLookup = new MapMaker().weakValues().makeMap(); + // The default lookup timeout + private static final long DEFAULT_TIMEOUT = 2000; // ms + + // Using weak keys and values ensures that we will not hold up garbage collection + protected BlockingHashMap addressLookup = new BlockingHashMap(); + protected ConcurrentMap inputLookup = new MapMaker().weakValues().makeMap(); + + // The timeout + private final long injectorTimeout; - // Used to create fake players - private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); - public InputStreamReflectLookup(ErrorReporter reporter, Server server) { - super(reporter, server); + this(reporter, server, DEFAULT_TIMEOUT); } + /** + * Initialize a reflect lookup with a given default injector timeout. + *

    + * This timeout defines the maximum amount of time to wait until an injector has been discovered. + * @param reporter - the error reporter. + * @param server - the current Bukkit server. + * @param injectorTimeout - the injector timeout. + */ + public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) { + super(reporter, server); + this.injectorTimeout = injectorTimeout; + } + @Override public void inject(Object container) { // Do nothing @@ -38,34 +54,45 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup { public void postWorldLoaded() { // Nothing again } - + @Override - public SocketInjector getSocketInjector(Socket socket) { - SocketInjector result = ownerSocket.get(socket); - - if (result == null) { - Player player = tempPlayerFactory.createTemporaryPlayer(server); - SocketInjector created = new TemporarySocketInjector(player, socket); - - result = ownerSocket.putIfAbsent(socket, created); - - if (result == null) { - // We won - use our created injector - TemporaryPlayerFactory.setInjectorInPlayer(player, created); - result = created; - } + public SocketInjector peekSocketInjector(SocketAddress address) { + try { + return addressLookup.get(address, 0, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // Whatever + return null; } - return result; } @Override - public SocketInjector getSocketInjector(InputStream input) { + public SocketInjector waitSocketInjector(SocketAddress address) { try { - Socket socket = getSocket(input); + // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to + // periodically wake up waiting readers and writers. We have to wait for the dedicated server thread + // to catch up, so we'll swallow these interrupts. + // + // TODO: Consider if we should raise the thread priority of the dedicated server listener thread. + return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true); + } catch (InterruptedException e) { + // This cannot be! + throw new IllegalStateException("Impossible exception occured!", e); + } + } + + @Override + public SocketInjector waitSocketInjector(Socket socket) { + return waitSocketInjector(socket.getRemoteSocketAddress()); + } + + @Override + public SocketInjector waitSocketInjector(InputStream input) { + try { + SocketAddress address = getSocketAddress(input); // Guard against NPE - if (socket != null) - return getSocketInjector(socket); + if (address != null) + return waitSocketInjector(address); else return null; } catch (IllegalAccessException e) { @@ -74,38 +101,36 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup { } /** - * Use reflection to get the underlying socket from an input stream. + * Use reflection to get the underlying socket address from an input stream. * @param stream - the socket stream to lookup. - * @return The underlying socket, or NULL if not found. + * @return The underlying socket address, or NULL if not found. * @throws IllegalAccessException Unable to access socket field. */ - private Socket getSocket(InputStream stream) throws IllegalAccessException { + private SocketAddress getSocketAddress(InputStream stream) throws IllegalAccessException { // Extra check, just in case if (stream instanceof FilterInputStream) - return getSocket(getInputStream((FilterInputStream) stream)); + return getSocketAddress(getInputStream((FilterInputStream) stream)); - Socket result = inputLookup.get(stream); + SocketAddress result = inputLookup.get(stream); if (result == null) { - result = lookupSocket(stream); + Socket socket = lookupSocket(stream); // Save it + result = socket.getRemoteSocketAddress(); inputLookup.put(stream, result); } return result; } @Override - public void setSocketInjector(Socket socket, SocketInjector injector) { - if (socket == null) - throw new IllegalArgumentException("socket cannot be NULL"); + public void setSocketInjector(SocketAddress address, SocketInjector injector) { + if (address == null) + throw new IllegalArgumentException("address cannot be NULL"); if (injector == null) throw new IllegalArgumentException("injector cannot be NULL."); - SocketInjector previous = ownerSocket.put(socket, injector); - - // Save the address lookup too - addressLookup.put(socket.getRemoteSocketAddress(), socket); + SocketInjector previous = addressLookup.put(address, injector); // Any previous temporary players will also be associated if (previous != null) { @@ -113,16 +138,6 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup { onPreviousSocketOverwritten(previous, injector); } } - - @Override - public SocketInjector getSocketInjector(SocketAddress address) { - Socket socket = addressLookup.get(address); - - if (socket != null) - return getSocketInjector(socket); - else - return null; - } @Override public void cleanupAll() { From 2e0acd3a780ead84eccedcbf9fc944996360621c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 28 Feb 2013 02:15:22 +0100 Subject: [PATCH 29/37] Increment version to 2.2.4-SNAPSHOT --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index ad361142..9f7d2635 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 2.2.3-SNAPSHOT + 2.2.4-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 4dac7aae..1cfcd9dd 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 2.2.3-SNAPSHOT +version: 2.2.4-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 8388a77f0b25d1dc74b79b5936c8430fb77a2ff1 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 28 Feb 2013 22:07:53 +0100 Subject: [PATCH 30/37] Catch ConnectExceptions - they are not serious. --- .../comphenix/protocol/metrics/Updater.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java index 3133a434..5e908ef0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java @@ -9,6 +9,7 @@ package com.comphenix.protocol.metrics; * This class provides the means to safetly and easily update a plugin, or check to see if it is updated using dev.bukkit.org */ import java.io.*; +import java.net.ConnectException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; @@ -259,10 +260,18 @@ public class Updater logger.warning("The project slug added ('" + slug + "') is invalid, and does not exist on dev.bukkit.org"); result = Updater.UpdateResult.FAIL_BADSLUG; // Bad slug! Bad! } + if (url != null) { // Obtain the results of the project's file feed - readFeed(); + try { + readFeed(); + } catch (ConnectException ex) { + // Minimal warning - it's just a temporary problem + logger.warning("Update problem: " + ex.getMessage()); + return UpdateResult.FAIL_DBO; + } + if(versionCheck(versionTitle)) { String fileLink = getFile(versionLink); @@ -545,8 +554,9 @@ public class Updater /** * Part of RSS Reader by Vogella, modified by H31IX for use with Bukkit + * @throws ConnectException Cannot connect to the server. */ - private void readFeed() + private void readFeed() throws ConnectException { try { @@ -598,15 +608,15 @@ public class Updater /** * Open the RSS feed + * @throws ConnectException If we are unable to connect to the server. */ - private InputStream read() + private InputStream read() throws ConnectException { - try - { + try { return url.openStream(); - } - catch (IOException e) - { + } catch (ConnectException e) { + throw e; + } catch (IOException e) { throw new RuntimeException(e); } } From 2985dc9cf8893582257a418c6c70649c4b58f113 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 28 Feb 2013 22:30:37 +0100 Subject: [PATCH 31/37] Be less picky over what packets to subclass. FIXES Ticket-49 --- .../protocol/injector/packet/ProxyPacketInjector.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java index 2c3e6a1b..f4224bba 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -29,6 +29,7 @@ import org.bukkit.entity.Player; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.Factory; import com.comphenix.protocol.Packets; import com.comphenix.protocol.error.ErrorReporter; @@ -132,7 +133,7 @@ class ProxyPacketInjector implements PacketInjector { throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version."); } // Check for previous injections - if (!MinecraftReflection.isMinecraftClass(old)) { + if (Factory.class.isAssignableFrom(old)) { throw new IllegalStateException("Packet " + packetID + " has already been injected."); } From d387b2d792c6fe556e1c1de4fa92b9922c0363e7 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 3 Mar 2013 14:49:20 +0100 Subject: [PATCH 32/37] Improve client packet interception by about 37%. --- .../injector/packet/ProxyPacketInjector.java | 58 +++++++++++++------ .../injector/packet/ReadPacketModifier.java | 43 +++++--------- 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java index f4224bba..391c321f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -23,13 +23,13 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - import org.bukkit.entity.Player; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Factory; +import net.sf.cglib.proxy.CallbackFilter; +import net.sf.cglib.proxy.NoOp; import com.comphenix.protocol.Packets; import com.comphenix.protocol.error.ErrorReporter; @@ -39,6 +39,8 @@ import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.MethodInfo; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; /** @@ -47,7 +49,15 @@ import com.comphenix.protocol.utility.MinecraftReflection; * @author Kristian */ class ProxyPacketInjector implements PacketInjector { - + /** + * Matches the readPacketData(DataInputStream) method in Packet. + */ + private static FuzzyMethodContract readPacket = FuzzyMethodContract.newBuilder(). + returnTypeVoid(). + parameterExactType(DataInputStream.class). + parameterCount(1). + build(); + // The "put" method that associates a packet ID with a packet class private static Method putMethod; private static Object intHashMap; @@ -61,11 +71,11 @@ class ProxyPacketInjector implements PacketInjector { // Allows us to determine the sender private PlayerInjectionHandler playerInjection; - // Allows us to look up read packet injectors - private Map readModifier; - // Class loader private ClassLoader classLoader; + + // Share callback filter + private CallbackFilter filter; public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager, PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { @@ -74,7 +84,6 @@ class ProxyPacketInjector implements PacketInjector { this.manager = manager; this.playerInjection = playerInjection; this.reporter = reporter; - this.readModifier = new ConcurrentHashMap(); initialize(); } @@ -85,11 +94,9 @@ class ProxyPacketInjector implements PacketInjector { */ @Override public void undoCancel(Integer id, Object packet) { - ReadPacketModifier modifier = readModifier.get(id); - // See if this packet has been cancelled before - if (modifier != null && modifier.hasCancelled(packet)) { - modifier.removeOverride(packet); + if (ReadPacketModifier.hasCancelled(packet)) { + ReadPacketModifier.removeOverride(packet); } } @@ -137,18 +144,34 @@ class ProxyPacketInjector implements PacketInjector { throw new IllegalStateException("Packet " + packetID + " has already been injected."); } + if (filter == null) { + filter = new CallbackFilter() { + @Override + public int accept(Method method) { + // Skip methods defined in Object + if (method.getDeclaringClass().equals(Object.class)) + return 0; + else if (readPacket.isMatch(MethodInfo.fromMethod(method), null)) + return 1; + else + return 2; + } + }; + } + // Subclass the specific packet class ex.setSuperclass(old); - ex.setCallbackType(ReadPacketModifier.class); + ex.setCallbackFilter(filter); + ex.setCallbackTypes(new Class[] { NoOp.class, ReadPacketModifier.class, ReadPacketModifier.class }); ex.setClassLoader(classLoader); Class proxy = ex.createClass(); - // Create the proxy handler - ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter); - readModifier.put(packetID, modifier); - + // Create the proxy handlers + ReadPacketModifier modifierReadPacket = new ReadPacketModifier(packetID, this, reporter, true); + ReadPacketModifier modifierRest = new ReadPacketModifier(packetID, this, reporter, false); + // Add a static reference - Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier }); + Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest }); try { // Override values @@ -184,7 +207,6 @@ class ProxyPacketInjector implements PacketInjector { putMethod.invoke(intHashMap, packetID, old); previous.remove(packetID); - readModifier.remove(packetID); registry.remove(proxy); overwritten.remove(packetID); return true; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java index 1d1e6494..67ff3da2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java @@ -19,23 +19,17 @@ package com.comphenix.protocol.injector.packet; import java.io.DataInputStream; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collections; import java.util.Map; -import java.util.WeakHashMap; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; +import com.google.common.collect.MapMaker; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; class ReadPacketModifier implements MethodInterceptor { - - @SuppressWarnings("rawtypes") - private static Class[] parameters = { DataInputStream.class }; - // A cancel marker private static final Object CANCEL_MARKER = new Object(); @@ -46,20 +40,24 @@ class ReadPacketModifier implements MethodInterceptor { // Report errors private ErrorReporter reporter; - // Whether or not a packet has been cancelled - private static Map override = Collections.synchronizedMap(new WeakHashMap()); + // If this is a read packet data method + private boolean isReadPacketDataMethod; - public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter) { + // Whether or not a packet has been cancelled + private static Map override = new MapMaker().weakKeys().makeMap(); + + public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) { this.packetID = packetID; this.packetInjector = packetInjector; this.reporter = reporter; + this.isReadPacketDataMethod = isReadPacketDataMethod; } /** * Remove any packet overrides. * @param packet - the packet to rever */ - public void removeOverride(Object packet) { + public static void removeOverride(Object packet) { override.remove(packet); } @@ -68,7 +66,7 @@ class ReadPacketModifier implements MethodInterceptor { * @param packet - the given packet. * @return Overriden object. */ - public Object getOverride(Object packet) { + public static Object getOverride(Object packet) { return override.get(packet); } @@ -77,23 +75,15 @@ class ReadPacketModifier implements MethodInterceptor { * @param packet - the packet to check. * @return TRUE if it has been cancelled, FALSE otherwise. */ - public boolean hasCancelled(Object packet) { + public static boolean hasCancelled(Object packet) { return getOverride(packet) == CANCEL_MARKER; } - + @Override public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - - Object returnValue = null; - String methodName = method.getName(); - - // We always pass these down (otherwise, we'll end up with a infinite loop) - if (methodName.equals("hashCode") || methodName.equals("equals") || methodName.equals("toString")) { - return proxy.invokeSuper(thisObj, args); - } - // Atomic retrieval Object overridenObject = override.get(thisObj); + Object returnValue = null; if (overridenObject != null) { // This packet has been cancelled @@ -111,9 +101,7 @@ class ReadPacketModifier implements MethodInterceptor { } // Is this a readPacketData method? - if (returnValue == null && - Arrays.equals(method.getParameterTypes(), parameters)) { - + if (isReadPacketDataMethod) { try { // We need this in order to get the correct player DataInputStream input = (DataInputStream) args[0]; @@ -134,10 +122,9 @@ class ReadPacketModifier implements MethodInterceptor { } } catch (Throwable e) { // Minecraft cannot handle this error - reporter.reportDetailed(this, "Cannot handle clienet packet.", e, args[0]); + reporter.reportDetailed(this, "Cannot handle client packet.", e, args[0]); } } - return returnValue; } From 6019ab177c5acc49582c4d1fd543122dd756b25e Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 4 Mar 2013 00:44:09 +0100 Subject: [PATCH 33/37] Create a dummy injector if we haven't yet injected the player. --- .../protocol/concurrency/BlockingHashMap.java | 21 +++-- .../player/NetworkServerInjector.java | 82 ++----------------- .../player/ProxyPlayerInjectionHandler.java | 46 ++++++++++- .../protocol/reflect/FuzzyReflection.java | 18 ++++ .../protocol/utility/MinecraftMethods.java | 64 +++++++++++++++ .../protocol/utility/MinecraftReflection.java | 4 +- 6 files changed, 150 insertions(+), 85 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java index 19d35b51..b5bad572 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java @@ -47,6 +47,19 @@ public class BlockingHashMap { // Map of locked objects private final ConcurrentMap locks; + /** + * Retrieve a cache loader that will always throw an exception. + * @return An invalid cache loader. + */ + public static CacheLoader newInvalidCacheLoader() { + return new CacheLoader() { + @Override + public TValue load(TKey key) throws Exception { + throw new IllegalStateException("Illegal use. Access the map directly instead."); + } + }; + } + /** * Initialize a new map. */ @@ -59,12 +72,8 @@ public class BlockingHashMap { locks.remove(entry.getKey()); } }).build( - new CacheLoader() { - @Override - public TValue load(TKey key) throws Exception { - throw new IllegalStateException("Illegal use. Access the map directly instead."); - } - }); + BlockingHashMap.newInvalidCacheLoader() + ); backingMap = backingCache.asMap(); // Normal concurrent hash map diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index d9a7f447..16218066 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -20,15 +20,7 @@ package com.comphenix.protocol.injector.player; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; -import net.sf.cglib.proxy.Callback; -import net.sf.cglib.proxy.CallbackFilter; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.Factory; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; -import net.sf.cglib.proxy.NoOp; +import net.sf.cglib.proxy.*; import org.bukkit.entity.Player; @@ -44,8 +36,8 @@ import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.ExistingGenerator; +import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftReflection; -import com.google.common.collect.Maps; /** * Represents a player hook into the NetServerHandler class. @@ -57,7 +49,6 @@ class NetworkServerInjector extends PlayerInjector { private volatile static CallbackFilter callbackFilter; private volatile static Field disconnectField; - private volatile static Method sendPacketMethod; private InjectedServerConnection serverInjection; // Determine if we're listening @@ -88,67 +79,6 @@ class NetworkServerInjector extends PlayerInjector { return sendingFilters.contains(packetID); } - @Override - public void initialize(Object injectionSource) throws IllegalAccessException { - super.initialize(injectionSource); - - // Get the send packet method! - if (hasInitialized) { - if (sendPacketMethod == null) { - try { - sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*"); - } catch (IllegalArgumentException e) { - Map netServer = getMethodList( - MinecraftReflection.getNetServerHandlerClass(), MinecraftReflection.getPacketClass()); - Map netHandler = getMethodList( - MinecraftReflection.getNetHandlerClass(), MinecraftReflection.getPacketClass()); - - // Remove every method in net handler from net server - for (String methodName : netHandler.keySet()) { - netServer.remove(methodName); - } - - // The remainder is the send packet method - if (netServer.size() == 1) { - Method[] methods = netServer.values().toArray(new Method[0]); - sendPacketMethod = methods[0]; - } else { - throw new IllegalArgumentException("Unable to find the sendPacket method in NetServerHandler/PlayerConnection."); - } - } - } - } - } - - /** - * Retrieve a method mapped list of every method with the given signature. - * @param source - class source. - * @param params - parameters. - * @return Method mapped list. - */ - private Map getMethodList(Class source, Class... params) { - return getMappedMethods( - FuzzyReflection.fromClass(source, true). - getMethodListByParameters(Void.TYPE, params) - ); - } - - /** - * Retrieve every method as a map over names. - *

    - * Note that overloaded methods will only occur once in the resulting map. - * @param methods - every method. - * @return A map over every given method. - */ - private Map getMappedMethods(List methods) { - Map map = Maps.newHashMap(); - - for (Method method : methods) { - map.put(method.getName(), method); - } - return map; - } - @Override public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue(); @@ -156,7 +86,7 @@ class NetworkServerInjector extends PlayerInjector { if (serverDelegate != null) { try { // Note that invocation target exception is a wrapper for a checked exception - sendPacketMethod.invoke(serverDelegate, packet); + MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet); } catch (IllegalArgumentException e) { throw e; @@ -229,14 +159,16 @@ class NetworkServerInjector extends PlayerInjector { }; }; Callback noOpCallback = NoOp.INSTANCE; - + // Share callback filter - that way, we avoid generating a new class for // every logged in player. if (callbackFilter == null) { + final Method sendPacket = MinecraftMethods.getSendPacketMethod(); + callbackFilter = new CallbackFilter() { @Override public int accept(Method method) { - if (method.equals(sendPacketMethod)) + if (method.equals(sendPacket)) return 0; else return 1; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 14091c80..1d95439b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -23,6 +23,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import net.sf.cglib.proxy.Factory; @@ -30,6 +31,7 @@ import org.bukkit.Server; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; +import com.comphenix.protocol.concurrency.BlockingHashMap; import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketAdapter; @@ -42,8 +44,11 @@ import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; 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.utility.MinecraftReflection; import com.google.common.base.Predicate; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.google.common.collect.Maps; /** @@ -64,6 +69,12 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // The last successful player hook private PlayerInjector lastSuccessfulHook; + // Dummy injection + private Cache dummyInjectors = + CacheBuilder.newBuilder(). + expireAfterWrite(30, TimeUnit.SECONDS). + build(BlockingHashMap.newInvalidCacheLoader()); + // Player injection private Map playerInjection = Maps.newConcurrentMap(); @@ -370,7 +381,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { return injector; } - + private void cleanupHook(PlayerInjector injector) { // Clean up as much as possible try { @@ -522,12 +533,43 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { if (result instanceof PlayerInjector) return (PlayerInjector) result; else - return null; + // Make a dummy injector them + return createDummyInjector(player); + } else { return injector; } } + /** + * Construct a simple dummy injector incase none has been constructed. + * @param player - the CraftPlayer to construct for. + * @return A dummy injector, or NULL if the given player is not a CraftPlayer. + */ + private PlayerInjector createDummyInjector(Player player) { + if (!MinecraftReflection.getCraftPlayerClass().isAssignableFrom(player.getClass())) { + // No - this is not safe + return null; + } + + try { + PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT); + dummyInjector.initializePlayer(player); + + // This probably means the player has disconnected + if (dummyInjector.getSocket() == null) { + return null; + } + + inputStreamLookup.setSocketInjector(dummyInjector.getAddress(), dummyInjector); + dummyInjectors.asMap().put(player, dummyInjector); + return dummyInjector; + + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access fields.", e); + } + } + /** * Retrieve a player injector by looking for its NetworkManager. * @param networkManager - current network manager. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java index 9da145aa..cbf8da58 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java @@ -24,11 +24,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; /** * Retrieves fields and methods by signature, not just name. @@ -422,6 +424,22 @@ public class FuzzyReflection { throw new IllegalArgumentException("Unable to find a method that matches " + matcher); } + /** + * Retrieve every method as a map over names. + *

    + * Note that overloaded methods will only occur once in the resulting map. + * @param methods - every method. + * @return A map over every given method. + */ + public Map getMappedMethods(List methods) { + Map map = Maps.newHashMap(); + + for (Method method : methods) { + map.put(method.getName(), method); + } + return map; + } + /** * Retrieve a list of every constructor that matches the given matcher. *

    diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java new file mode 100644 index 00000000..f5706fa4 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java @@ -0,0 +1,64 @@ +package com.comphenix.protocol.utility; + +import java.lang.reflect.Method; +import java.util.Map; + +import com.comphenix.protocol.reflect.FuzzyReflection; + +/** + * Static methods for accessing Minecraft methods. + * + * @author Kristian + */ +public class MinecraftMethods { + // For player connection + private volatile static Method sendPacketMethod; + + /** + * Retrieve the send packet method in PlayerConnection/NetServerHandler. + * @return The send packet method. + */ + public static Method getSendPacketMethod() { + if (sendPacketMethod == null) { + Class serverHandlerClass = MinecraftReflection.getNetServerHandlerClass(); + + try { + sendPacketMethod = FuzzyReflection.fromObject(serverHandlerClass).getMethodByName("sendPacket.*"); + } catch (IllegalArgumentException e) { + Map netServer = getMethodList( + serverHandlerClass, MinecraftReflection.getPacketClass()); + Map netHandler = getMethodList( + MinecraftReflection.getNetHandlerClass(), MinecraftReflection.getPacketClass()); + + // Remove every method in net handler from net server + for (String methodName : netHandler.keySet()) { + netServer.remove(methodName); + } + + // The remainder is the send packet method + if (netServer.size() == 1) { + Method[] methods = netServer.values().toArray(new Method[0]); + sendPacketMethod = methods[0]; + } else { + throw new IllegalArgumentException("Unable to find the sendPacket method in NetServerHandler/PlayerConnection."); + } + } + } + return sendPacketMethod; + } + + /** + * Retrieve a method mapped list of every method with the given signature. + * @param source - class source. + * @param params - parameters. + * @return Method mapped list. + */ + private static Map getMethodList(Class source, Class... params) { + FuzzyReflection reflect = FuzzyReflection.fromClass(source, true); + + return reflect.getMappedMethods( + reflect.getMethodListByParameters(Void.TYPE, params) + ); + } + +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 6f12d76b..399082d1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -85,7 +85,7 @@ public class MinecraftReflection { private static Method craftNMSMethod; private static Method craftBukkitMethod; private static boolean craftItemStackFailed; - + // net.minecraft.server private static Class itemStackArrayClass; @@ -924,7 +924,7 @@ public class MinecraftReflection { public static Class getCraftEntityClass() { return getCraftBukkitClass("entity.CraftEntity"); } - + /** * Retrieve a CraftItemStack from a given ItemStack. * @param bukkitItemStack - the Bukkit ItemStack to convert. From b1b6e9ec20b3aeb6adfebb97bea74f79e8f5d8f6 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 4 Mar 2013 13:29:46 +0100 Subject: [PATCH 34/37] In blocking hash map, don't remove locks if the value has been replaced --- .../com/comphenix/protocol/concurrency/BlockingHashMap.java | 5 ++++- .../protocol/injector/server/AbstractInputStreamLookup.java | 2 +- .../protocol/injector/server/InputStreamReflectLookup.java | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java index b5bad572..9d90b7d8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; +import com.google.common.cache.RemovalCause; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; @@ -69,7 +70,9 @@ public class BlockingHashMap { @Override public void onRemoval(RemovalNotification entry) { // Clean up locks too - locks.remove(entry.getKey()); + if (entry.getCause() != RemovalCause.REPLACED) { + locks.remove(entry.getKey()); + } } }).build( BlockingHashMap.newInvalidCacheLoader() diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java index 86b13605..2ed52ae4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java @@ -93,7 +93,7 @@ public abstract class AbstractInputStreamLookup { /** * Associate a given socket address to the provided socket injector. - * @param input - the socket address to associate. + * @param address - the socket address to associate. * @param injector - the injector. */ public abstract void setSocketInjector(SocketAddress address, SocketInjector injector); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java index 58305d49..2d971f5a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java @@ -88,7 +88,7 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup { @Override public SocketInjector waitSocketInjector(InputStream input) { try { - SocketAddress address = getSocketAddress(input); + SocketAddress address = waitSocketAddress(input); // Guard against NPE if (address != null) @@ -106,10 +106,10 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup { * @return The underlying socket address, or NULL if not found. * @throws IllegalAccessException Unable to access socket field. */ - private SocketAddress getSocketAddress(InputStream stream) throws IllegalAccessException { + private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException { // Extra check, just in case if (stream instanceof FilterInputStream) - return getSocketAddress(getInputStream((FilterInputStream) stream)); + return waitSocketAddress(getInputStream((FilterInputStream) stream)); SocketAddress result = inputLookup.get(stream); From 9f6b4b60e3a3590395a9db3811d8bbaa01821417 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 4 Mar 2013 16:54:54 +0100 Subject: [PATCH 35/37] Don't overwrite an existing player injector in net login. --- .../injector/PacketFilterManager.java | 5 +++-- .../injector/player/NetLoginInjector.java | 5 ++++- .../player/PlayerInjectionHandler.java | 20 ++++++++++++++++++- .../player/ProxyPlayerInjectionHandler.java | 20 ++++++++++++------- .../injector/spigot/DummyPlayerHandler.java | 3 ++- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index e373126d..c5b8c0ac 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -56,6 +56,7 @@ import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectorBuilder; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -620,7 +621,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok */ public void initializePlayers(Player[] players) { for (Player player : players) - playerInjection.injectPlayer(player); + playerInjection.injectPlayer(player, ConflictStrategy.OVERRIDE); } /** @@ -680,7 +681,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok private void onPlayerJoin(PlayerJoinEvent event) { try { // This call will be ignored if no listeners are registered - playerInjection.injectPlayer(event.getPlayer()); + playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE); } catch (Exception e) { reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index fc5052d6..0fc27113 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -24,6 +24,7 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.injector.GamePhase; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; @@ -64,7 +65,9 @@ class NetLoginInjector { return inserting; Player temporary = playerFactory.createTemporaryPlayer(server); - PlayerInjector injector = injectionHandler.injectPlayer(temporary, inserting, GamePhase.LOGIN); + // Note that we bail out if there's an existing player injector + PlayerInjector injector = injectionHandler.injectPlayer( + temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN); if (injector != null) { // Update injector as well diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index 976bc341..8c59c15a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -12,6 +12,23 @@ import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; public interface PlayerInjectionHandler { + /** + * How to handle a previously existing player injection. + * + * @author Kristian + */ + public enum ConflictStrategy { + /** + * Override it. + */ + OVERRIDE, + + /** + * Immediately exit. + */ + BAIL_OUT; + } + /** * Retrieves how the server packets are read. * @return Injection method for reading server packets. @@ -64,8 +81,9 @@ public interface PlayerInjectionHandler { *

    * This call will be ignored if there's no listener that can receive the given events. * @param player - player to hook. + * @param strategy - how to handle injection conflicts. */ - public abstract void injectPlayer(Player player); + public abstract void injectPlayer(Player player, ConflictStrategy strategy); /** * Invoke special routines for handling disconnect before a player is uninjected. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 1d95439b..35020261 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -253,12 +253,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { *

    * This call will be ignored if there's no listener that can receive the given events. * @param player - player to hook. + * @param strategy - how to handle previous player injections. */ @Override - public void injectPlayer(Player player) { + public void injectPlayer(Player player, ConflictStrategy strategy) { // Inject using the player instance itself if (isInjectionNecessary(GamePhase.PLAYING)) { - injectPlayer(player, player, GamePhase.PLAYING); + injectPlayer(player, player, strategy, GamePhase.PLAYING); } } @@ -281,7 +282,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { * @param phase - the current game phase. * @return The resulting player injector, or NULL if the injection failed. */ - PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) { + PlayerInjector injectPlayer(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) { if (player == null) throw new IllegalArgumentException("Player cannot be NULL."); if (injectionPoint == null) @@ -291,12 +292,12 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method. synchronized (player) { - return injectPlayerInternal(player, injectionPoint, phase); + return injectPlayerInternal(player, injectionPoint, stategy, phase); } } // Unsafe variant of the above - private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, GamePhase phase) { + private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) { PlayerInjector injector = playerInjection.get(player); PlayerInjectHooks tempHook = getPlayerHook(phase); PlayerInjectHooks permanentHook = tempHook; @@ -328,9 +329,14 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // Close any previously associated hooks before we proceed if (previous != null && !(player instanceof Factory)) { - uninjectPlayer(previous.getPlayer(), true); + switch (stategy) { + case OVERRIDE: + uninjectPlayer(previous.getPlayer(), true); + break; + case BAIL_OUT: + return null; + } } - injector.injectManager(); // Save injector diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java index 73a1c5e2..dc6f5e9d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java @@ -79,7 +79,8 @@ class DummyPlayerHandler implements PlayerInjectionHandler { } @Override - public void injectPlayer(Player player) { + public void injectPlayer(Player player, ConflictStrategy strategy) { + // We don't care about strategy injector.injectPlayer(player); } From 9a16143c896a0979a0e2547c27f9511ae3da0668 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 5 Mar 2013 16:50:59 +0100 Subject: [PATCH 36/37] Small documentation fix. --- .../protocol/concurrency/BlockingHashMap.java | 23 +++++++++++++++++++ .../server/InputStreamReflectLookup.java | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java index 9d90b7d8..78854be4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java @@ -204,6 +204,29 @@ public class BlockingHashMap { } } + /** + * If and only if a key is not present in the map will it be associated with the given value. + * @param key - the key to associate. + * @param value - the value to associate. + * @return The previous value this key has been associated with. + */ + public TValue putIfAbsent(TKey key, TValue value) { + if (value == null) + throw new IllegalArgumentException("This map doesn't support NULL values."); + + final TValue previous = backingMap.putIfAbsent(key, value); + + // No need to unlock readers if we haven't changed anything + if (previous == null) { + final Object lock = getLock(key); + + synchronized (lock) { + lock.notifyAll(); + } + } + return previous; + } + public int size() { return backingMap.size(); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java index 2d971f5a..f374e9e9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java @@ -64,7 +64,7 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup { return null; } } - + @Override public SocketInjector waitSocketInjector(SocketAddress address) { try { From 4406cdb571298742ea2d93e1a47303913bf73449 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 5 Mar 2013 16:51:08 +0100 Subject: [PATCH 37/37] Increment to version 2.3.0 --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 9f7d2635..ac7a8b99 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 2.2.4-SNAPSHOT + 2.3.0 jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 1cfcd9dd..23fb34bf 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 2.2.4-SNAPSHOT +version: 2.3.0 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib