diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml
index e1268256..ac7a8b99 100644
--- a/ProtocolLib/pom.xml
+++ b/ProtocolLib/pom.xml
@@ -2,7 +2,7 @@
4.0.0
com.comphenix.protocol
ProtocolLib
- 2.2.0
+ 2.3.0
jar
Provides read/write access to the Minecraft protocol.
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);
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java
index ce084d0a..87e5b523 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java
@@ -228,7 +228,7 @@ class ProtocolConfig {
public void setBackgroundCompilerEnabled(boolean enabled) {
global.set(BACKGROUND_COMPILER_ENABLED, enabled);
}
-
+
/**
* 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 28c1ef9d..271c1181 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java
@@ -40,7 +40,9 @@ 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;
+import com.comphenix.protocol.utility.ChatExtensions;
/**
* The main entry point for ProtocolLib.
@@ -134,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);
+
+ // Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager);
// Update injection hook
@@ -215,6 +220,24 @@ 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();
// Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
@@ -257,7 +280,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 {
@@ -422,16 +445,22 @@ 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;
// 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
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..78854be4 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,21 @@ 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.RemovalCause;
+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,19 +41,46 @@ 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;
+ /**
+ * 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.
*/
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
+ if (entry.getCause() != RemovalCause.REPLACED) {
+ locks.remove(entry.getKey());
+ }
+ }
+ }).build(
+ BlockingHashMap.newInvalidCacheLoader()
+ );
+ backingMap = backingCache.asMap();
+
+ // Normal concurrent hash map
+ locks = new ConcurrentHashMap();
}
/**
@@ -94,34 +127,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;
}
@@ -148,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/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/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java
index eea43aae..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;
@@ -133,8 +134,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 +215,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);
}
@@ -225,6 +226,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 +283,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());
@@ -466,7 +474,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 +486,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(
@@ -612,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);
}
/**
@@ -672,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/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);
}
}
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..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,15 @@ 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;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
@@ -37,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;
/**
@@ -45,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;
@@ -59,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 {
@@ -72,7 +84,6 @@ class ProxyPacketInjector implements PacketInjector {
this.manager = manager;
this.playerInjection = playerInjection;
this.reporter = reporter;
- this.readModifier = new ConcurrentHashMap();
initialize();
}
@@ -83,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);
}
}
@@ -131,22 +140,38 @@ 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.");
}
+ 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
@@ -182,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;
@@ -211,18 +235,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 +281,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..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,24 +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.Packets;
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();
@@ -47,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);
}
@@ -69,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);
}
@@ -78,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
@@ -112,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];
@@ -132,18 +119,12 @@ 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
- reporter.reportDetailed(this, "Cannot handle clienet packet.", e, args[0]);
+ reporter.reportDetailed(this, "Cannot handle client packet.", e, args[0]);
}
}
-
return returnValue;
}
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..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,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.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
@@ -53,6 +54,9 @@ class InjectedServerConnection {
// Used to inject net handlers
private NetLoginInjector netLoginInjector;
+ // Inject server connections
+ private AbstractInputStreamLookup socketInjector;
+
private Server server;
private ErrorReporter reporter;
private boolean hasAttempted;
@@ -60,11 +64,12 @@ class InjectedServerConnection {
private Object minecraftServer = null;
- public InjectedServerConnection(ErrorReporter reporter, 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;
this.server = server;
+ this.socketInjector = socketInjector;
this.netLoginInjector = netLoginInjector;
}
@@ -126,6 +131,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 +155,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 +167,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 +182,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/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java
index f80bae7b..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,7 +24,8 @@ 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.injector.player.PlayerInjectionHandler.ConflictStrategy;
+import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
@@ -34,23 +35,22 @@ import com.google.common.collect.Maps;
* @author Kristian
*/
class NetLoginInjector {
-
private ConcurrentMap injectedLogins = Maps.newConcurrentMap();
-
+
// Handles every hook
private ProxyPlayerInjectionHandler injectionHandler;
+
+ // Create temporary players
+ private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
+
+ // The current error reporter
+ private ErrorReporter reporter;
private Server server;
- // 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, Server server, ProxyPlayerInjectionHandler injectionHandler) {
this.reporter = reporter;
- this.injectionHandler = injectionHandler;
this.server = server;
+ this.injectionHandler = injectionHandler;
}
/**
@@ -64,16 +64,19 @@ 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;
+ Player temporary = playerFactory.createTemporaryPlayer(server);
+ // Note that we bail out if there's an existing player injector
+ PlayerInjector injector = injectionHandler.injectPlayer(
+ temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
- // Associate the injector too
- InjectContainer container = (InjectContainer) fakePlayer;
- container.setInjector(injector);
-
- // 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
return inserting;
@@ -81,7 +84,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;
}
}
@@ -108,15 +111,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/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java
index 855e2390..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.
@@ -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;
@@ -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/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java
index f81be360..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;
@@ -180,6 +110,7 @@ class NetworkServerInjector extends PlayerInjector {
return;
if (!tryInjectManager()) {
+ Class> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
// Try to override the proxied object
if (proxyServerField != null) {
@@ -188,6 +119,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 +131,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.");
}
}
@@ -226,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/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java
index a1fe1b44..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
@@ -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;
@@ -14,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.
@@ -61,23 +76,14 @@ 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.
*
* 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.
@@ -149,8 +155,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 8f84cdd0..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
@@ -24,7 +24,6 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.SocketAddress;
-
import net.sf.cglib.proxy.Factory;
import org.bukkit.entity.Player;
@@ -38,13 +37,14 @@ 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;
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;
@@ -60,6 +60,7 @@ abstract class PlayerInjector {
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 {
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;
@@ -99,9 +101,6 @@ abstract class PlayerInjector {
// Handle errors
protected ErrorReporter reporter;
- // Scheduled action on the next packet event
- protected Runnable scheduledAction;
-
// Whether or not the injector has been cleaned
private boolean clean;
@@ -249,10 +248,12 @@ 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)
- 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;
@@ -263,18 +264,23 @@ abstract class PlayerInjector {
}
/**
- * 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.");
+ }
}
/**
@@ -282,6 +288,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;
@@ -450,6 +457,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;
/**
@@ -517,12 +525,7 @@ abstract class PlayerInjector {
Integer id = invoker.getPacketID(packet);
Player currentPlayer = player;
- // Hack #1: Handle a single scheduled action
- if (scheduledAction != null) {
- scheduledAction.run();
- scheduledAction = null;
- }
- // Hack #2
+ // Hack #1
if (updateOnLogin) {
if (id == Packets.Server.LOGIN) {
try {
@@ -593,17 +596,10 @@ abstract class PlayerInjector {
}
}
- /**
- * Schedule an action to occur on the next sent packet.
- * @param action - action to execute.
- */
- public void scheduleAction(Runnable action) {
- scheduledAction = action;
- }
-
/**
* Retrieve the hooked player.
*/
+ @Override
public Player getPlayer() {
return player;
}
@@ -630,6 +626,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;
@@ -637,6 +634,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 053fe0ef..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
@@ -20,12 +20,13 @@ 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 java.util.concurrent.TimeUnit;
+import net.sf.cglib.proxy.Factory;
+
import org.bukkit.Server;
import org.bukkit.entity.Player;
@@ -40,8 +41,14 @@ 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.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;
/**
@@ -50,26 +57,26 @@ 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 AbstractInputStreamLookup inputStreamLookup;
+
// NetLogin injector
private NetLoginInjector netLoginInjector;
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
- // Player injection
- private Map addressLookup = Maps.newConcurrentMap();
- private Map playerInjection = Maps.newConcurrentMap();
+ // Dummy injection
+ private Cache dummyInjectors =
+ CacheBuilder.newBuilder().
+ expireAfterWrite(30, TimeUnit.SECONDS).
+ build(BlockingHashMap.newInvalidCacheLoader());
- // Lookup player by connection
- private BlockingHashMap dataInputLookup = BlockingHashMap.create();
+ // Player injection
+ private Map playerInjection = Maps.newConcurrentMap();
// Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
@@ -96,18 +103,34 @@ 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) {
this.classLoader = classLoader;
this.reporter = reporter;
this.invoker = invoker;
this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners;
- this.netLoginInjector = new NetLoginInjector(reporter, this, server);
- this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector);
+
+ this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
+ server(server).
+ reporter(reporter).
+ build();
+
+ // Create net login injectors and the server connection injector
+ this.netLoginInjector = new NetLoginInjector(reporter, server, this);
+ 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 (inputStreamLookup != null) {
+ inputStreamLookup.postWorldLoaded();
+ }
+ }
/**
* Retrieves how the server packets are read.
@@ -202,31 +225,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 = inputStreamLookup.waitSocketInjector(inputStream);
if (injector != null) {
return injector.getPlayer();
} else {
- reporter.reportWarning(this, "Unable to find stream: " + inputStream);
return null;
}
}
@@ -245,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);
}
}
@@ -273,16 +282,22 @@ 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)
+ 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);
+ 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;
@@ -293,7 +308,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;
@@ -308,25 +323,24 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector.canInject(phase)) {
injector.initialize(injectionPoint);
- DataInputStream inputStream = injector.getInputStream(false);
+ // Get socket and socket injector
+ SocketAddress address = injector.getAddress();
+ SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
- Socket socket = injector.getSocket();
- SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
-
- // Guard against NPE here too
- PlayerInjector previous = address != null ? addressLookup.get(address) : null;
-
// Close any previously associated hooks before we proceed
- if (previous != null) {
- uninjectPlayer(previous.getPlayer(), false, true);
+ if (previous != null && !(player instanceof Factory)) {
+ switch (stategy) {
+ case OVERRIDE:
+ uninjectPlayer(previous.getPlayer(), true);
+ break;
+ case BAIL_OUT:
+ return null;
+ }
}
-
injector.injectManager();
- if (inputStream != null)
- dataInputLookup.put(inputStream, injector);
- if (address != null)
- addressLookup.put(address, injector);
+ // Save injector
+ inputStreamLookup.setSocketInjector(address, injector);
break;
}
@@ -373,7 +387,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
return injector;
}
-
+
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
@@ -404,33 +418,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
@@ -446,12 +448,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
}
}
- // Clean up
- if (removeAuxiliary) {
- // Note that the dataInputLookup will clean itself
- if (address != null)
- addressLookup.remove(address);
- }
return true;
}
}
@@ -471,11 +467,11 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) {
- PlayerInjector injector = addressLookup.get(address);
+ SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
// Clean up
if (injector != null)
- uninjectPlayer(injector.getPlayer(), false, true);
+ uninjectPlayer(injector.getPlayer(), true);
return true;
}
@@ -491,7 +487,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) {
@@ -513,7 +509,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
@@ -536,28 +531,49 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector == null) {
// Try getting it from the player itself
- if (player instanceof InjectContainer)
- return ((InjectContainer) player).getInjector();
+ SocketAddress address = player.getAddress();
+ // Look that up without blocking
+ SocketInjector result = inputStreamLookup.peekSocketInjector(address);
+
+ // Ensure that it is non-null and a player injector
+ if (result instanceof PlayerInjector)
+ return (PlayerInjector) result;
else
- return searchAddressLookup(player);
+ // Make a dummy injector them
+ return createDummyInjector(player);
+
} 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.
+ * 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 searchAddressLookup(Player player) {
- // See if we can find it anywhere
- for (PlayerInjector injector : addressLookup.values()) {
- if (player.equals(injector.getUpdatedPlayer())) {
- return injector;
- }
+ 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);
}
- return null;
}
/**
@@ -640,35 +656,18 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
}
// Remove server handler
+ if (inputStreamLookup != null)
+ inputStreamLookup.cleanupAll();
if (serverInjection != null)
serverInjection.cleanupAll();
if (netLoginInjector != null)
netLoginInjector.cleanupAll();
+ inputStreamLookup = 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 Runnable() {
- @Override
- public void run() {
- dataInputLookup.put(injector.getInputStream(false), injector);
- }
- });
- }
- }
}
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..2ed52ae4
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java
@@ -0,0 +1,119 @@
+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;
+
+public abstract class AbstractInputStreamLookup {
+ // Used to access the inner input stream of a filtered input stream
+ private static Field filteredInputField;
+
+ // 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 input - the indentifying filtered input stream.
+ * @return The socket injector we have associated with this player.
+ */
+ public abstract SocketInjector waitSocketInjector(InputStream input);
+
+ /**
+ * Retrieve an injector by its socket.
+ * @param socket - the socket.
+ * @return The socket injector.
+ */
+ 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 waitSocketInjector(SocketAddress address);
+
+ /**
+ * 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 address - the socket address to associate.
+ * @param injector - the 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.
+ * @param previous - the previous injector.
+ * @param current - the new injector.
+ */
+ protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
+ Player player = previous.getPlayer();
+
+ // Default implementation
+ if (player instanceof InjectorContainer) {
+ TemporaryPlayerFactory.setInjectorInPlayer(player, current);
+ }
+ }
+
+ /**
+ * 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/InjectorContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectorContainer.java
new file mode 100644
index 00000000..7a642a09
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InjectorContainer.java
@@ -0,0 +1,21 @@
+package com.comphenix.protocol.injector.server;
+
+/**
+ * Able to store a socket injector.
+ *
+ * A necessary hack.
+ * @author Kristian
+ */
+class InjectorContainer {
+ private volatile SocketInjector injector;
+
+ public SocketInjector getInjector() {
+ return injector;
+ }
+
+ 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..29989e5d
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java
@@ -0,0 +1,47 @@
+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;
+
+ /**
+ * 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;
+ }
+
+ public AbstractInputStreamLookup build() {
+ return new InputStreamReflectLookup(reporter, server);
+ }
+}
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..f374e9e9
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java
@@ -0,0 +1,164 @@
+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 java.util.concurrent.TimeUnit;
+
+import org.bukkit.Server;
+
+import com.comphenix.protocol.concurrency.BlockingHashMap;
+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 {
+ // 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;
+
+ public InputStreamReflectLookup(ErrorReporter reporter, Server 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
+ }
+
+ @Override
+ public void postWorldLoaded() {
+ // Nothing again
+ }
+
+ @Override
+ public SocketInjector peekSocketInjector(SocketAddress address) {
+ try {
+ return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // Whatever
+ return null;
+ }
+ }
+
+ @Override
+ public SocketInjector waitSocketInjector(SocketAddress address) {
+ try {
+ // 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 = waitSocketAddress(input);
+
+ // Guard against NPE
+ if (address != null)
+ return waitSocketInjector(address);
+ 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 address from an input stream.
+ * @param stream - the socket stream to lookup.
+ * @return The underlying socket address, or NULL if not found.
+ * @throws IllegalAccessException Unable to access socket field.
+ */
+ private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException {
+ // Extra check, just in case
+ if (stream instanceof FilterInputStream)
+ return waitSocketAddress(getInputStream((FilterInputStream) stream));
+
+ SocketAddress result = inputLookup.get(stream);
+
+ if (result == null) {
+ Socket socket = lookupSocket(stream);
+
+ // Save it
+ result = socket.getRemoteSocketAddress();
+ inputLookup.put(stream, result);
+ }
+ return result;
+ }
+
+ @Override
+ 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 = addressLookup.put(address, injector);
+
+ // Any previous temporary players will also be associated
+ if (previous != null) {
+ // Update the reference to any previous injector
+ onPreviousSocketOverwritten(previous, injector);
+ }
+ }
+
+ @Override
+ public void cleanupAll() {
+ // Do nothing
+ }
+
+ /**
+ * 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 lookupSocket(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/SocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java
new file mode 100644
index 00000000..6407d320
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java
@@ -0,0 +1,61 @@
+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 an injector that only gives access to a player's socket.
+ *
+ * @author Kristian
+ */
+public 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/server/TemporaryPlayerFactory.java
similarity index 76%
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 67336df5..91d1fed6 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,26 +36,7 @@ import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Create fake player instances that represents pre-authenticated clients.
*/
-class TemporaryPlayerFactory {
-
- /**
- * Able to store a PlayerInjector.
- *
- * A necessary hack.
- * @author Kristian
- */
- public static class InjectContainer {
- private PlayerInjector injector;
-
- public PlayerInjector getInjector() {
- return injector;
- }
-
- public void setInjector(PlayerInjector injector) {
- this.injector = injector;
- }
- }
-
+public class TemporaryPlayerFactory {
// Helpful constructors
private final PacketConstructor chatPacket;
@@ -66,6 +47,27 @@ 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 InjectorContainer) {
+ return ((InjectorContainer) player).getInjector();
+ }
+ 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) {
+ ((InjectorContainer) player).setInjector(injector);
+ }
+
/**
* Construct a temporary player that supports a subset of every player command.
*
@@ -80,7 +82,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 +96,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 = ((InjectorContainer) obj).getInjector();
if (injector == null)
throw new IllegalStateException("Unable to find injector.");
@@ -147,7 +149,7 @@ 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;
@@ -157,7 +159,7 @@ 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);
@@ -165,6 +167,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);
+
+ ((InjectorContainer) temporary).setInjector(injector);
+ return temporary;
+ }
+
/**
* Send a message to the given client.
* @param injector - the injector representing the client.
@@ -173,7 +188,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..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
@@ -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);
@@ -86,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);
}
@@ -106,11 +100,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 +114,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void checkListener(Set listeners) {
// Yes, really
}
+
+ @Override
+ public void postWorldLoaded() {
+ // Do nothing
+ }
}
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..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;
@@ -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() {
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);
}
}
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/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(" }");
}
}
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 579c069a..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
@@ -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;
@@ -94,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;
@@ -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);
- }
+ }
}
/**
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/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;
+ }
}
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 3717fe1d..399082d1 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;
@@ -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;
@@ -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");
}
}
@@ -553,7 +561,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()
);
@@ -908,7 +916,15 @@ 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.
* @param bukkitItemStack - the Bukkit ItemStack to convert.
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/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..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
@@ -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.
*
@@ -16,6 +18,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.
@@ -48,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.
@@ -251,7 +258,26 @@ 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. 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.
+ * @return This current compound, for chaining.
+ */
+ 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.
@@ -304,6 +330,13 @@ public interface NbtCompound extends NbtBase>>, Iterable<
*/
public abstract NbtCompound put(String key, Collection extends NbtBase> 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..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
@@ -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());
}
}
@@ -215,6 +221,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;
}
@@ -252,6 +261,29 @@ class WrappedCompound implements NbtWrapper>>, Iterable) value);
+ } else {
+ NbtBase> base = new MemoryElement(key, value);
+ put(base);
+ }
+ return this;
+ }
+
+ @Override
+ public Object getObject(String key) {
+ NbtBase> 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.
@@ -565,6 +597,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 +618,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);
diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml
index e3c48aff..46869ad7 100644
--- a/ProtocolLib/src/main/resources/config.yml
+++ b/ProtocolLib/src/main/resources/config.yml
@@ -18,5 +18,4 @@ global:
ignore version check:
# Override the starting injecting method
- injection method:
-
\ No newline at end of file
+ injection method:
\ No newline at end of file
diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml
index 50beb220..23fb34bf 100644
--- a/ProtocolLib/src/main/resources/plugin.yml
+++ b/ProtocolLib/src/main/resources/plugin.yml
@@ -1,9 +1,8 @@
name: ProtocolLib
-version: 2.2.0
+version: 2.3.0
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