Merge branch 'master' into gh-pages
Dieser Commit ist enthalten in:
Commit
ef334aff50
@ -7,12 +7,6 @@
|
|||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/>
|
<classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/>
|
||||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="optional" value="true"/>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.comphenix.protocol</groupId>
|
<groupId>com.comphenix.protocol</groupId>
|
||||||
<artifactId>ProtocolLib</artifactId>
|
<artifactId>ProtocolLib</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.3.0</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||||
|
|
||||||
|
@ -27,6 +27,9 @@ import com.comphenix.protocol.error.ErrorReporter;
|
|||||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
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.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.MethodUtils;
|
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.CollectionGenerator;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
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.
|
* 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,
|
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
|
||||||
BackgroundCompiler.class, StructureCompiler.class,
|
BackgroundCompiler.class, StructureCompiler.class,
|
||||||
ObjectWriter.class, Packets.Server.class, Packets.Client.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 = {
|
String[] internalClasses = {
|
||||||
@ -76,14 +83,14 @@ class CleanupStaticMembers {
|
|||||||
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
|
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
|
||||||
"com.comphenix.protocol.injector.player.NetworkServerInjector",
|
"com.comphenix.protocol.injector.player.NetworkServerInjector",
|
||||||
"com.comphenix.protocol.injector.player.PlayerInjector",
|
"com.comphenix.protocol.injector.player.PlayerInjector",
|
||||||
"com.comphenix.protocol.injector.player.TemporaryPlayerFactory",
|
|
||||||
"com.comphenix.protocol.injector.EntityUtilities",
|
"com.comphenix.protocol.injector.EntityUtilities",
|
||||||
"com.comphenix.protocol.injector.packet.PacketRegistry",
|
"com.comphenix.protocol.injector.packet.PacketRegistry",
|
||||||
"com.comphenix.protocol.injector.packet.PacketInjector",
|
"com.comphenix.protocol.injector.packet.PacketInjector",
|
||||||
"com.comphenix.protocol.injector.packet.ReadPacketModifier",
|
"com.comphenix.protocol.injector.packet.ReadPacketModifier",
|
||||||
"com.comphenix.protocol.injector.StructureCache",
|
"com.comphenix.protocol.injector.StructureCache",
|
||||||
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
|
"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);
|
resetClasses(publicClasses);
|
||||||
|
@ -228,7 +228,7 @@ class ProtocolConfig {
|
|||||||
public void setBackgroundCompilerEnabled(boolean enabled) {
|
public void setBackgroundCompilerEnabled(boolean enabled) {
|
||||||
global.set(BACKGROUND_COMPILER_ENABLED, enabled);
|
global.set(BACKGROUND_COMPILER_ENABLED, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
||||||
* @param lastTimeSeconds - new last update time.
|
* @param lastTimeSeconds - new last update time.
|
||||||
|
@ -40,7 +40,9 @@ import com.comphenix.protocol.injector.PacketFilterManager;
|
|||||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||||
import com.comphenix.protocol.metrics.Statistics;
|
import com.comphenix.protocol.metrics.Statistics;
|
||||||
import com.comphenix.protocol.metrics.Updater;
|
import com.comphenix.protocol.metrics.Updater;
|
||||||
|
import com.comphenix.protocol.metrics.Updater.UpdateResult;
|
||||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||||
|
import com.comphenix.protocol.utility.ChatExtensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main entry point for ProtocolLib.
|
* The main entry point for ProtocolLib.
|
||||||
@ -134,7 +136,10 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
||||||
|
|
||||||
unhookTask = new DelayedSingleTask(this);
|
unhookTask = new DelayedSingleTask(this);
|
||||||
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, detailedReporter);
|
protocolManager = new PacketFilterManager(
|
||||||
|
getClassLoader(), getServer(), unhookTask, detailedReporter);
|
||||||
|
|
||||||
|
// Setup error reporter
|
||||||
detailedReporter.addGlobalParameter("manager", protocolManager);
|
detailedReporter.addGlobalParameter("manager", protocolManager);
|
||||||
|
|
||||||
// Update injection hook
|
// Update injection hook
|
||||||
@ -215,6 +220,24 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
// Don't do anything else!
|
// Don't do anything else!
|
||||||
if (manager == null)
|
if (manager == null)
|
||||||
return;
|
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
|
// Initialize background compiler
|
||||||
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
|
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);
|
reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to check Minecraft version
|
// Used to check Minecraft version
|
||||||
private void verifyMinecraftVersion() {
|
private void verifyMinecraftVersion() {
|
||||||
try {
|
try {
|
||||||
@ -422,16 +445,22 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
if (redirectHandler != null) {
|
if (redirectHandler != null) {
|
||||||
logger.removeHandler(redirectHandler);
|
logger.removeHandler(redirectHandler);
|
||||||
}
|
}
|
||||||
|
if (protocolManager != null)
|
||||||
unhookTask.close();
|
protocolManager.close();
|
||||||
protocolManager.close();
|
else
|
||||||
|
return; // Plugin reloaders!
|
||||||
|
|
||||||
|
if (unhookTask != null)
|
||||||
|
unhookTask.close();
|
||||||
protocolManager = null;
|
protocolManager = null;
|
||||||
statistisc = null;
|
statistisc = null;
|
||||||
reporter = null;
|
reporter = null;
|
||||||
|
|
||||||
// Leaky ClassLoader begone!
|
// Leaky ClassLoader begone!
|
||||||
CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter);
|
if (updater == null || updater.getResult() != UpdateResult.SUCCESS) {
|
||||||
cleanup.resetAll();
|
CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter);
|
||||||
|
cleanup.resetAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the Bukkit logger first, before we try to create our own
|
// Get the Bukkit logger first, before we try to create our own
|
||||||
|
@ -19,15 +19,21 @@ package com.comphenix.protocol.concurrency;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.
|
* A map that supports blocking on read operations. Null keys are not supported.
|
||||||
* <p>
|
* <p>
|
||||||
* 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.
|
||||||
* <p>
|
* <p>
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*
|
*
|
||||||
@ -35,19 +41,46 @@ import com.google.common.collect.MapMaker;
|
|||||||
* @param <TValue> - type of the value.
|
* @param <TValue> - type of the value.
|
||||||
*/
|
*/
|
||||||
public class BlockingHashMap<TKey, TValue> {
|
public class BlockingHashMap<TKey, TValue> {
|
||||||
|
|
||||||
// Map of values
|
// Map of values
|
||||||
|
private final Cache<TKey, TValue> backingCache;
|
||||||
private final ConcurrentMap<TKey, TValue> backingMap;
|
private final ConcurrentMap<TKey, TValue> backingMap;
|
||||||
|
|
||||||
// Map of locked objects
|
// Map of locked objects
|
||||||
private final ConcurrentMap<TKey, Object> locks;
|
private final ConcurrentMap<TKey, Object> locks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a cache loader that will always throw an exception.
|
||||||
|
* @return An invalid cache loader.
|
||||||
|
*/
|
||||||
|
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
|
||||||
|
return new CacheLoader<TKey, TValue>() {
|
||||||
|
@Override
|
||||||
|
public TValue load(TKey key) throws Exception {
|
||||||
|
throw new IllegalStateException("Illegal use. Access the map directly instead.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new map.
|
* Initialize a new map.
|
||||||
*/
|
*/
|
||||||
public BlockingHashMap() {
|
public BlockingHashMap() {
|
||||||
backingMap = new MapMaker().weakKeys().makeMap();
|
backingCache = CacheBuilder.newBuilder().weakValues().removalListener(
|
||||||
locks = new MapMaker().weakKeys().makeMap();
|
new RemovalListener<TKey, TValue>() {
|
||||||
|
@Override
|
||||||
|
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
|
||||||
|
// Clean up locks too
|
||||||
|
if (entry.getCause() != RemovalCause.REPLACED) {
|
||||||
|
locks.remove(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).build(
|
||||||
|
BlockingHashMap.<TKey, TValue>newInvalidCacheLoader()
|
||||||
|
);
|
||||||
|
backingMap = backingCache.asMap();
|
||||||
|
|
||||||
|
// Normal concurrent hash map
|
||||||
|
locks = new ConcurrentHashMap<TKey, Object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,34 +127,57 @@ public class BlockingHashMap<TKey, TValue> {
|
|||||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||||
*/
|
*/
|
||||||
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
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.
|
||||||
|
* <p>
|
||||||
|
* 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)
|
if (key == null)
|
||||||
throw new IllegalArgumentException("key cannot be NULL.");
|
throw new IllegalArgumentException("key cannot be NULL.");
|
||||||
if (unit == null)
|
if (unit == null)
|
||||||
throw new IllegalArgumentException("Unit cannot be 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);
|
TValue value = backingMap.get(key);
|
||||||
|
|
||||||
// Only lock if no value is available
|
// Only lock if no value is available
|
||||||
if (value == null) {
|
if (value == null && timeout > 0) {
|
||||||
final Object lock = getLock(key);
|
final Object lock = getLock(key);
|
||||||
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
||||||
|
|
||||||
// Don't exceed the timeout
|
// Don't exceed the timeout
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
while (value == null) {
|
while (value == null) {
|
||||||
long remainingTime = stopTimeNS - System.nanoTime();
|
try {
|
||||||
|
long remainingTime = stopTimeNS - System.nanoTime();
|
||||||
if (remainingTime > 0) {
|
|
||||||
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
if (remainingTime > 0) {
|
||||||
value = backingMap.get(key);
|
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
||||||
} else {
|
value = backingMap.get(key);
|
||||||
// Timeout elapsed
|
} else {
|
||||||
break;
|
// Timeout elapsed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// This is fairly dangerous - but we might HAVE to block the thread
|
||||||
|
if (!ignoreInterrupted)
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +204,29 @@ public class BlockingHashMap<TKey, TValue> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
public int size() {
|
||||||
return backingMap.size();
|
return backingMap.size();
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,9 @@ public class PacketContainer implements Serializable {
|
|||||||
andThen(new Function<BuilderParameters, Cloner>() {
|
andThen(new Function<BuilderParameters, Cloner>() {
|
||||||
@Override
|
@Override
|
||||||
public Cloner apply(@Nullable BuilderParameters param) {
|
public Cloner apply(@Nullable BuilderParameters param) {
|
||||||
|
if (param == null)
|
||||||
|
throw new IllegalArgumentException("Cannot be NULL.");
|
||||||
|
|
||||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||||
// Use a default writer with no concept of cloning
|
// Use a default writer with no concept of cloning
|
||||||
writer = new ObjectWriter();
|
writer = new ObjectWriter();
|
||||||
|
@ -56,6 +56,7 @@ import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
|
|||||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||||
import com.comphenix.protocol.injector.player.PlayerInjectorBuilder;
|
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.injector.spigot.SpigotPacketInjector;
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
@ -133,8 +134,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
private AsyncFilterManager asyncFilterManager;
|
private AsyncFilterManager asyncFilterManager;
|
||||||
|
|
||||||
// Valid server and client packets
|
// Valid server and client packets
|
||||||
private Set<Integer> serverPackets;
|
private boolean knowsServerPackets;
|
||||||
private Set<Integer> clientPackets;
|
private boolean knowsClientPackets;
|
||||||
|
|
||||||
// Ensure that we're not performing too may injections
|
// Ensure that we're not performing too may injections
|
||||||
private AtomicInteger phaseLoginCount = new AtomicInteger(0);
|
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
|
// Attempt to load the list of server and client packets
|
||||||
try {
|
try {
|
||||||
this.serverPackets = PacketRegistry.getServerPackets();
|
knowsServerPackets = PacketRegistry.getServerPackets() != null;
|
||||||
this.clientPackets = PacketRegistry.getClientPackets();
|
knowsClientPackets = PacketRegistry.getClientPackets() != null;
|
||||||
} catch (FieldAccessException e) {
|
} catch (FieldAccessException e) {
|
||||||
reporter.reportWarning(this, "Cannot load server and client packet list.", 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
|
@Override
|
||||||
public AsynchronousManager getAsynchronousManager() {
|
public AsynchronousManager getAsynchronousManager() {
|
||||||
return asyncFilterManager;
|
return asyncFilterManager;
|
||||||
@ -275,15 +283,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
// Make sure this is possible
|
// Make sure this is possible
|
||||||
playerInjection.checkListener(listener);
|
playerInjection.checkListener(listener);
|
||||||
}
|
}
|
||||||
|
if (hasSending)
|
||||||
|
incrementPhases(sending.getGamePhase());
|
||||||
|
|
||||||
|
// Handle receivers after senders
|
||||||
if (hasReceiving) {
|
if (hasReceiving) {
|
||||||
verifyWhitelist(listener, receiving);
|
verifyWhitelist(listener, receiving);
|
||||||
recievedListeners.addListener(listener, receiving);
|
recievedListeners.addListener(listener, receiving);
|
||||||
enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
|
enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment phases too
|
|
||||||
if (hasSending)
|
|
||||||
incrementPhases(sending.getGamePhase());
|
|
||||||
if (hasReceiving)
|
if (hasReceiving)
|
||||||
incrementPhases(receiving.getGamePhase());
|
incrementPhases(receiving.getGamePhase());
|
||||||
|
|
||||||
@ -466,7 +474,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
for (int packetID : packets) {
|
for (int packetID : packets) {
|
||||||
// Only register server packets that are actually supported by Minecraft
|
// Only register server packets that are actually supported by Minecraft
|
||||||
if (side.isForServer()) {
|
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);
|
playerInjection.addPacketHandler(packetID);
|
||||||
else
|
else
|
||||||
reporter.reportWarning(this, String.format(
|
reporter.reportWarning(this, String.format(
|
||||||
@ -477,7 +486,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
|
|
||||||
// As above, only for client packets
|
// As above, only for client packets
|
||||||
if (side.isForClient() && packetInjector != null) {
|
if (side.isForClient() && packetInjector != null) {
|
||||||
if (clientPackets != null && clientPackets.contains(packetID))
|
if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID))
|
||||||
packetInjector.addPacketHandler(packetID);
|
packetInjector.addPacketHandler(packetID);
|
||||||
else
|
else
|
||||||
reporter.reportWarning(this, String.format(
|
reporter.reportWarning(this, String.format(
|
||||||
@ -612,7 +621,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
*/
|
*/
|
||||||
public void initializePlayers(Player[] players) {
|
public void initializePlayers(Player[] players) {
|
||||||
for (Player 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) {
|
private void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
try {
|
try {
|
||||||
// This call will be ignored if no listeners are registered
|
// This call will be ignored if no listeners are registered
|
||||||
playerInjection.injectPlayer(event.getPlayer());
|
playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
|
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,12 @@ public class PacketRegistry {
|
|||||||
private static Map<Class, Integer> packetToID;
|
private static Map<Class, Integer> packetToID;
|
||||||
|
|
||||||
// Whether or not certain packets are sent by the client or the server
|
// Whether or not certain packets are sent by the client or the server
|
||||||
private static Set<Integer> serverPackets;
|
private static ImmutableSet<Integer> serverPackets;
|
||||||
private static Set<Integer> clientPackets;
|
private static ImmutableSet<Integer> clientPackets;
|
||||||
|
|
||||||
|
// The underlying sets
|
||||||
|
private static Set<Integer> serverPacketsRef;
|
||||||
|
private static Set<Integer> clientPacketsRef;
|
||||||
|
|
||||||
// New proxy values
|
// New proxy values
|
||||||
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
|
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
|
||||||
@ -120,21 +124,21 @@ public class PacketRegistry {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static void initializeSets() throws FieldAccessException {
|
private static void initializeSets() throws FieldAccessException {
|
||||||
if (serverPackets == null || clientPackets == null) {
|
if (serverPacketsRef == null || clientPacketsRef == null) {
|
||||||
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
|
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (sets.size() > 1) {
|
if (sets.size() > 1) {
|
||||||
serverPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
|
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
|
||||||
clientPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
|
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
|
||||||
|
|
||||||
// Impossible
|
// Impossible
|
||||||
if (serverPackets == null || clientPackets == null)
|
if (serverPacketsRef == null || clientPacketsRef == null)
|
||||||
throw new FieldAccessException("Packet sets are in an illegal state.");
|
throw new FieldAccessException("Packet sets are in an illegal state.");
|
||||||
|
|
||||||
// NEVER allow callers to modify the underlying sets
|
// NEVER allow callers to modify the underlying sets
|
||||||
serverPackets = ImmutableSet.copyOf(serverPackets);
|
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
|
||||||
clientPackets = ImmutableSet.copyOf(clientPackets);
|
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
|
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
|
||||||
@ -143,6 +147,13 @@ public class PacketRegistry {
|
|||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new FieldAccessException("Cannot access field.", 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,13 +23,15 @@ import java.lang.reflect.InvocationTargetException;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import net.sf.cglib.proxy.Callback;
|
import net.sf.cglib.proxy.Callback;
|
||||||
import net.sf.cglib.proxy.Enhancer;
|
import net.sf.cglib.proxy.Enhancer;
|
||||||
|
import 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.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
@ -37,6 +39,8 @@ import com.comphenix.protocol.injector.ListenerInvoker;
|
|||||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.MethodInfo;
|
||||||
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +49,15 @@ import com.comphenix.protocol.utility.MinecraftReflection;
|
|||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
class ProxyPacketInjector implements PacketInjector {
|
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
|
// The "put" method that associates a packet ID with a packet class
|
||||||
private static Method putMethod;
|
private static Method putMethod;
|
||||||
private static Object intHashMap;
|
private static Object intHashMap;
|
||||||
@ -59,11 +71,11 @@ class ProxyPacketInjector implements PacketInjector {
|
|||||||
// Allows us to determine the sender
|
// Allows us to determine the sender
|
||||||
private PlayerInjectionHandler playerInjection;
|
private PlayerInjectionHandler playerInjection;
|
||||||
|
|
||||||
// Allows us to look up read packet injectors
|
|
||||||
private Map<Integer, ReadPacketModifier> readModifier;
|
|
||||||
|
|
||||||
// Class loader
|
// Class loader
|
||||||
private ClassLoader classLoader;
|
private ClassLoader classLoader;
|
||||||
|
|
||||||
|
// Share callback filter
|
||||||
|
private CallbackFilter filter;
|
||||||
|
|
||||||
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
|
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
|
||||||
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
|
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
|
||||||
@ -72,7 +84,6 @@ class ProxyPacketInjector implements PacketInjector {
|
|||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.playerInjection = playerInjection;
|
this.playerInjection = playerInjection;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
|
|
||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,11 +94,9 @@ class ProxyPacketInjector implements PacketInjector {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void undoCancel(Integer id, Object packet) {
|
public void undoCancel(Integer id, Object packet) {
|
||||||
ReadPacketModifier modifier = readModifier.get(id);
|
|
||||||
|
|
||||||
// See if this packet has been cancelled before
|
// See if this packet has been cancelled before
|
||||||
if (modifier != null && modifier.hasCancelled(packet)) {
|
if (ReadPacketModifier.hasCancelled(packet)) {
|
||||||
modifier.removeOverride(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.");
|
throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version.");
|
||||||
}
|
}
|
||||||
// Check for previous injections
|
// Check for previous injections
|
||||||
if (!MinecraftReflection.isMinecraftClass(old)) {
|
if (Factory.class.isAssignableFrom(old)) {
|
||||||
throw new IllegalStateException("Packet " + packetID + " has already been injected.");
|
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
|
// Subclass the specific packet class
|
||||||
ex.setSuperclass(old);
|
ex.setSuperclass(old);
|
||||||
ex.setCallbackType(ReadPacketModifier.class);
|
ex.setCallbackFilter(filter);
|
||||||
|
ex.setCallbackTypes(new Class<?>[] { NoOp.class, ReadPacketModifier.class, ReadPacketModifier.class });
|
||||||
ex.setClassLoader(classLoader);
|
ex.setClassLoader(classLoader);
|
||||||
Class proxy = ex.createClass();
|
Class proxy = ex.createClass();
|
||||||
|
|
||||||
// Create the proxy handler
|
// Create the proxy handlers
|
||||||
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter);
|
ReadPacketModifier modifierReadPacket = new ReadPacketModifier(packetID, this, reporter, true);
|
||||||
readModifier.put(packetID, modifier);
|
ReadPacketModifier modifierRest = new ReadPacketModifier(packetID, this, reporter, false);
|
||||||
|
|
||||||
// Add a static reference
|
// Add a static reference
|
||||||
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
|
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Override values
|
// Override values
|
||||||
@ -182,7 +207,6 @@ class ProxyPacketInjector implements PacketInjector {
|
|||||||
|
|
||||||
putMethod.invoke(intHashMap, packetID, old);
|
putMethod.invoke(intHashMap, packetID, old);
|
||||||
previous.remove(packetID);
|
previous.remove(packetID);
|
||||||
readModifier.remove(packetID);
|
|
||||||
registry.remove(proxy);
|
registry.remove(proxy);
|
||||||
overwritten.remove(packetID);
|
overwritten.remove(packetID);
|
||||||
return true;
|
return true;
|
||||||
@ -211,18 +235,22 @@ class ProxyPacketInjector implements PacketInjector {
|
|||||||
public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
|
public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
|
||||||
try {
|
try {
|
||||||
Player client = playerInjection.getPlayerByConnection(input);
|
Player client = playerInjection.getPlayerByConnection(input);
|
||||||
|
|
||||||
// Never invoke a event if we don't know where it's from
|
// Never invoke a event if we don't know where it's from
|
||||||
if (client != null)
|
if (client != null) {
|
||||||
return packetRecieved(packet, client);
|
return packetRecieved(packet, client);
|
||||||
else
|
} else {
|
||||||
|
// Hack #2 - Caused by our server socket injector
|
||||||
|
if (packet.getID() != Packets.Client.GET_INFO)
|
||||||
|
System.out.println("[ProtocolLib] Unknown origin " + input + " for packet " + packet.getID());
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// We will ignore this - it occurs when a player disconnects
|
// We will ignore this - it occurs when a player disconnects
|
||||||
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
|
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -253,12 +281,4 @@ class ProxyPacketInjector implements PacketInjector {
|
|||||||
overwritten.clear();
|
overwritten.clear();
|
||||||
previous.clear();
|
previous.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform the current PlayerInjector that it should update the DataInputStream next.
|
|
||||||
* @param player - the player to update.
|
|
||||||
*/
|
|
||||||
public void scheduleDataInputRefresh(Player player) {
|
|
||||||
playerInjection.scheduleDataInputRefresh(player);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,24 +19,17 @@ package com.comphenix.protocol.injector.packet;
|
|||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
import com.comphenix.protocol.Packets;
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
import net.sf.cglib.proxy.MethodInterceptor;
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
import net.sf.cglib.proxy.MethodProxy;
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
|
|
||||||
class ReadPacketModifier implements MethodInterceptor {
|
class ReadPacketModifier implements MethodInterceptor {
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private static Class[] parameters = { DataInputStream.class };
|
|
||||||
|
|
||||||
// A cancel marker
|
// A cancel marker
|
||||||
private static final Object CANCEL_MARKER = new Object();
|
private static final Object CANCEL_MARKER = new Object();
|
||||||
|
|
||||||
@ -47,20 +40,24 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
// Report errors
|
// Report errors
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
|
|
||||||
// Whether or not a packet has been cancelled
|
// If this is a read packet data method
|
||||||
private static Map<Object, Object> override = Collections.synchronizedMap(new WeakHashMap<Object, Object>());
|
private boolean isReadPacketDataMethod;
|
||||||
|
|
||||||
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter) {
|
// Whether or not a packet has been cancelled
|
||||||
|
private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap();
|
||||||
|
|
||||||
|
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
|
||||||
this.packetID = packetID;
|
this.packetID = packetID;
|
||||||
this.packetInjector = packetInjector;
|
this.packetInjector = packetInjector;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
|
this.isReadPacketDataMethod = isReadPacketDataMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove any packet overrides.
|
* Remove any packet overrides.
|
||||||
* @param packet - the packet to rever
|
* @param packet - the packet to rever
|
||||||
*/
|
*/
|
||||||
public void removeOverride(Object packet) {
|
public static void removeOverride(Object packet) {
|
||||||
override.remove(packet);
|
override.remove(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +66,7 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
* @param packet - the given packet.
|
* @param packet - the given packet.
|
||||||
* @return Overriden object.
|
* @return Overriden object.
|
||||||
*/
|
*/
|
||||||
public Object getOverride(Object packet) {
|
public static Object getOverride(Object packet) {
|
||||||
return override.get(packet);
|
return override.get(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,23 +75,15 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
* @param packet - the packet to check.
|
* @param packet - the packet to check.
|
||||||
* @return TRUE if it has been cancelled, FALSE otherwise.
|
* @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;
|
return getOverride(packet) == CANCEL_MARKER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
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
|
// Atomic retrieval
|
||||||
Object overridenObject = override.get(thisObj);
|
Object overridenObject = override.get(thisObj);
|
||||||
|
Object returnValue = null;
|
||||||
|
|
||||||
if (overridenObject != null) {
|
if (overridenObject != null) {
|
||||||
// This packet has been cancelled
|
// This packet has been cancelled
|
||||||
@ -112,9 +101,7 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Is this a readPacketData method?
|
// Is this a readPacketData method?
|
||||||
if (returnValue == null &&
|
if (isReadPacketDataMethod) {
|
||||||
Arrays.equals(method.getParameterTypes(), parameters)) {
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// We need this in order to get the correct player
|
// We need this in order to get the correct player
|
||||||
DataInputStream input = (DataInputStream) args[0];
|
DataInputStream input = (DataInputStream) args[0];
|
||||||
@ -132,18 +119,12 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
} else if (!objectEquals(thisObj, result)) {
|
} else if (!objectEquals(thisObj, result)) {
|
||||||
override.put(thisObj, result);
|
override.put(thisObj, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update DataInputStream next time
|
|
||||||
if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) {
|
|
||||||
packetInjector.scheduleDataInputRefresh(event.getPlayer());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// Minecraft cannot handle this error
|
// Minecraft cannot handle this error
|
||||||
reporter.reportDetailed(this, "Cannot handle clienet packet.", e, args[0]);
|
reporter.reportDetailed(this, "Cannot handle client packet.", e, args[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import net.sf.cglib.proxy.Factory;
|
|||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||||
@ -53,6 +54,9 @@ class InjectedServerConnection {
|
|||||||
// Used to inject net handlers
|
// Used to inject net handlers
|
||||||
private NetLoginInjector netLoginInjector;
|
private NetLoginInjector netLoginInjector;
|
||||||
|
|
||||||
|
// Inject server connections
|
||||||
|
private AbstractInputStreamLookup socketInjector;
|
||||||
|
|
||||||
private Server server;
|
private Server server;
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
private boolean hasAttempted;
|
private boolean hasAttempted;
|
||||||
@ -60,11 +64,12 @@ class InjectedServerConnection {
|
|||||||
|
|
||||||
private Object minecraftServer = null;
|
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<VolatileField>();
|
this.listFields = new ArrayList<VolatileField>();
|
||||||
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
this.socketInjector = socketInjector;
|
||||||
this.netLoginInjector = netLoginInjector;
|
this.netLoginInjector = netLoginInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +131,9 @@ class InjectedServerConnection {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject the server socket too
|
||||||
|
injectServerSocket(listenerThread);
|
||||||
|
|
||||||
// Just inject every list field we can get
|
// Just inject every list field we can get
|
||||||
injectEveryListField(listenerThread, 1);
|
injectEveryListField(listenerThread, 1);
|
||||||
hasSuccess = true;
|
hasSuccess = true;
|
||||||
@ -147,7 +155,8 @@ class InjectedServerConnection {
|
|||||||
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
|
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
|
||||||
getFieldByType("netServerHandlerList", List.class);
|
getFieldByType("netServerHandlerList", List.class);
|
||||||
if (dedicatedThreadField == null) {
|
if (dedicatedThreadField == null) {
|
||||||
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(Thread.class);
|
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
|
||||||
|
getFieldListByType(Thread.class);
|
||||||
|
|
||||||
// Verify the field count
|
// Verify the field count
|
||||||
if (matches.size() != 1)
|
if (matches.size() != 1)
|
||||||
@ -158,8 +167,13 @@ class InjectedServerConnection {
|
|||||||
|
|
||||||
// Next, try to get the dedicated thread
|
// Next, try to get the dedicated thread
|
||||||
try {
|
try {
|
||||||
if (dedicatedThreadField != null)
|
if (dedicatedThreadField != null) {
|
||||||
injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1);
|
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
|
||||||
|
|
||||||
|
// Inject server socket and NetServerHandlers.
|
||||||
|
injectServerSocket(dedicatedThread);
|
||||||
|
injectEveryListField(dedicatedThread, 1);
|
||||||
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e);
|
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e);
|
||||||
}
|
}
|
||||||
@ -168,6 +182,10 @@ class InjectedServerConnection {
|
|||||||
hasSuccess = true;
|
hasSuccess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void injectServerSocket(Object container) {
|
||||||
|
socketInjector.inject(container);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically inject into every List-compatible public or private field of the given object.
|
* Automatically inject into every List-compatible public or private field of the given object.
|
||||||
* @param container - container object with the fields to inject.
|
* @param container - container object with the fields to inject.
|
||||||
|
@ -24,7 +24,8 @@ import org.bukkit.entity.Player;
|
|||||||
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.injector.GamePhase;
|
import com.comphenix.protocol.injector.GamePhase;
|
||||||
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
|
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
|
||||||
|
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
@ -34,23 +35,22 @@ import com.google.common.collect.Maps;
|
|||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
class NetLoginInjector {
|
class NetLoginInjector {
|
||||||
|
|
||||||
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
||||||
|
|
||||||
// Handles every hook
|
// Handles every hook
|
||||||
private ProxyPlayerInjectionHandler injectionHandler;
|
private ProxyPlayerInjectionHandler injectionHandler;
|
||||||
|
|
||||||
|
// Create temporary players
|
||||||
|
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
||||||
|
|
||||||
|
// The current error reporter
|
||||||
|
private ErrorReporter reporter;
|
||||||
private Server server;
|
private Server server;
|
||||||
|
|
||||||
// The current error rerporter
|
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
|
||||||
private ErrorReporter reporter;
|
|
||||||
|
|
||||||
// Used to create fake players
|
|
||||||
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
|
||||||
|
|
||||||
public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, Server server) {
|
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.injectionHandler = injectionHandler;
|
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
this.injectionHandler = injectionHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,16 +64,19 @@ class NetLoginInjector {
|
|||||||
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
||||||
return inserting;
|
return inserting;
|
||||||
|
|
||||||
Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server);
|
Player temporary = playerFactory.createTemporaryPlayer(server);
|
||||||
PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN);
|
// Note that we bail out if there's an existing player injector
|
||||||
injector.updateOnLogin = true;
|
PlayerInjector injector = injectionHandler.injectPlayer(
|
||||||
|
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
|
||||||
|
|
||||||
// Associate the injector too
|
if (injector != null) {
|
||||||
InjectContainer container = (InjectContainer) fakePlayer;
|
// Update injector as well
|
||||||
container.setInjector(injector);
|
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
|
||||||
|
injector.updateOnLogin = true;
|
||||||
// Save the login
|
|
||||||
injectedLogins.putIfAbsent(inserting, injector);
|
// Save the login
|
||||||
|
injectedLogins.putIfAbsent(inserting, injector);
|
||||||
|
}
|
||||||
|
|
||||||
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
|
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
|
||||||
return inserting;
|
return inserting;
|
||||||
@ -81,7 +84,7 @@ class NetLoginInjector {
|
|||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// Minecraft can't handle this, so we'll deal with it here
|
// Minecraft can't handle this, so we'll deal with it here
|
||||||
reporter.reportDetailed(this, "Unable to hook " +
|
reporter.reportDetailed(this, "Unable to hook " +
|
||||||
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting);
|
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting, injectionHandler);
|
||||||
return inserting;
|
return inserting;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,15 +111,13 @@ class NetLoginInjector {
|
|||||||
|
|
||||||
// Hack to clean up other references
|
// Hack to clean up other references
|
||||||
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
|
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
|
||||||
|
injectionHandler.uninjectPlayer(player);
|
||||||
|
|
||||||
// Update NetworkManager
|
// Update NetworkManager
|
||||||
if (newInjector == null) {
|
if (newInjector != null) {
|
||||||
injectionHandler.uninjectPlayer(player);
|
if (injected instanceof NetworkObjectInjector) {
|
||||||
} else {
|
|
||||||
injectionHandler.uninjectPlayer(player, false);
|
|
||||||
|
|
||||||
if (injected instanceof NetworkObjectInjector)
|
|
||||||
newInjector.setNetworkManager(injected.getNetworkManager(), true);
|
newInjector.setNetworkManager(injected.getNetworkManager(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -39,7 +39,7 @@ import com.comphenix.protocol.events.PacketListener;
|
|||||||
import com.comphenix.protocol.injector.GamePhase;
|
import com.comphenix.protocol.injector.GamePhase;
|
||||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
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.
|
* Injection method that overrides the NetworkHandler itself, and its queue-method.
|
||||||
@ -54,7 +54,7 @@ public class NetworkObjectInjector extends PlayerInjector {
|
|||||||
private ClassLoader classLoader;
|
private ClassLoader classLoader;
|
||||||
|
|
||||||
// Shared callback filter - avoid creating a new class every time
|
// Shared callback filter - avoid creating a new class every time
|
||||||
private static CallbackFilter callbackFilter;
|
private volatile static CallbackFilter callbackFilter;
|
||||||
|
|
||||||
// Temporary player factory
|
// Temporary player factory
|
||||||
private static volatile TemporaryPlayerFactory tempPlayerFactory;
|
private static volatile TemporaryPlayerFactory tempPlayerFactory;
|
||||||
@ -91,10 +91,8 @@ public class NetworkObjectInjector extends PlayerInjector {
|
|||||||
if (tempPlayerFactory == null)
|
if (tempPlayerFactory == null)
|
||||||
tempPlayerFactory = new TemporaryPlayerFactory();
|
tempPlayerFactory = new TemporaryPlayerFactory();
|
||||||
|
|
||||||
// Create and associate this fake player with this network injector
|
// Create and associate the fake player with this network injector
|
||||||
Player player = tempPlayerFactory.createTemporaryPlayer(server);
|
return tempPlayerFactory.createTemporaryPlayer(server, this);
|
||||||
((InjectContainer) player).setInjector(this);
|
|
||||||
return player;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -20,15 +20,7 @@ package com.comphenix.protocol.injector.player;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import net.sf.cglib.proxy.*;
|
||||||
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 org.bukkit.entity.Player;
|
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.VolatileField;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a player hook into the NetServerHandler class.
|
* Represents a player hook into the NetServerHandler class.
|
||||||
@ -57,7 +49,6 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
private volatile static CallbackFilter callbackFilter;
|
private volatile static CallbackFilter callbackFilter;
|
||||||
|
|
||||||
private volatile static Field disconnectField;
|
private volatile static Field disconnectField;
|
||||||
private volatile static Method sendPacketMethod;
|
|
||||||
private InjectedServerConnection serverInjection;
|
private InjectedServerConnection serverInjection;
|
||||||
|
|
||||||
// Determine if we're listening
|
// Determine if we're listening
|
||||||
@ -88,67 +79,6 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
return sendingFilters.contains(packetID);
|
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<String, Method> netServer = getMethodList(
|
|
||||||
MinecraftReflection.getNetServerHandlerClass(), MinecraftReflection.getPacketClass());
|
|
||||||
Map<String, Method> 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<String, Method> getMethodList(Class<?> source, Class<?>... params) {
|
|
||||||
return getMappedMethods(
|
|
||||||
FuzzyReflection.fromClass(source, true).
|
|
||||||
getMethodListByParameters(Void.TYPE, params)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve every method as a map over names.
|
|
||||||
* <p>
|
|
||||||
* 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<String, Method> getMappedMethods(List<Method> methods) {
|
|
||||||
Map<String, Method> map = Maps.newHashMap();
|
|
||||||
|
|
||||||
for (Method method : methods) {
|
|
||||||
map.put(method.getName(), method);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
|
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
|
||||||
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
|
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
|
||||||
@ -156,7 +86,7 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
if (serverDelegate != null) {
|
if (serverDelegate != null) {
|
||||||
try {
|
try {
|
||||||
// Note that invocation target exception is a wrapper for a checked exception
|
// Note that invocation target exception is a wrapper for a checked exception
|
||||||
sendPacketMethod.invoke(serverDelegate, packet);
|
MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw e;
|
throw e;
|
||||||
@ -180,6 +110,7 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!tryInjectManager()) {
|
if (!tryInjectManager()) {
|
||||||
|
Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
|
||||||
|
|
||||||
// Try to override the proxied object
|
// Try to override the proxied object
|
||||||
if (proxyServerField != null) {
|
if (proxyServerField != null) {
|
||||||
@ -188,6 +119,8 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
|
|
||||||
if (serverHandler == null)
|
if (serverHandler == null)
|
||||||
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
|
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
|
||||||
|
else
|
||||||
|
serverHandlerClass = serverHandler.getClass();
|
||||||
|
|
||||||
// Try again
|
// Try again
|
||||||
if (tryInjectManager()) {
|
if (tryInjectManager()) {
|
||||||
@ -198,7 +131,7 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
|
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Cannot hook player: Unable to find a valid constructor for the "
|
"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;
|
Callback noOpCallback = NoOp.INSTANCE;
|
||||||
|
|
||||||
// Share callback filter - that way, we avoid generating a new class for
|
// Share callback filter - that way, we avoid generating a new class for
|
||||||
// every logged in player.
|
// every logged in player.
|
||||||
if (callbackFilter == null) {
|
if (callbackFilter == null) {
|
||||||
|
final Method sendPacket = MinecraftMethods.getSendPacketMethod();
|
||||||
|
|
||||||
callbackFilter = new CallbackFilter() {
|
callbackFilter = new CallbackFilter() {
|
||||||
@Override
|
@Override
|
||||||
public int accept(Method method) {
|
public int accept(Method method) {
|
||||||
if (method.equals(sendPacketMethod))
|
if (method.equals(sendPacket))
|
||||||
return 0;
|
return 0;
|
||||||
else
|
else
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -4,8 +4,6 @@ import java.io.DataInputStream;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
@ -14,6 +12,23 @@ import com.comphenix.protocol.injector.GamePhase;
|
|||||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||||
|
|
||||||
public interface PlayerInjectionHandler {
|
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.
|
* Retrieves how the server packets are read.
|
||||||
* @return Injection method for reading server packets.
|
* @return Injection method for reading server packets.
|
||||||
@ -61,23 +76,14 @@ public interface PlayerInjectionHandler {
|
|||||||
public abstract Player getPlayerByConnection(DataInputStream inputStream)
|
public abstract Player getPlayerByConnection(DataInputStream inputStream)
|
||||||
throws InterruptedException;
|
throws InterruptedException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a player by its DataInput connection.
|
|
||||||
* @param inputStream - the associated DataInput connection.
|
|
||||||
* @param playerTimeout - the amount of time to wait for a result.
|
|
||||||
* @param unit - unit of playerTimeout.
|
|
||||||
* @return The player.
|
|
||||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
|
||||||
*/
|
|
||||||
public abstract Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a player hook, allowing us to read server packets.
|
* Initialize a player hook, allowing us to read server packets.
|
||||||
* <p>
|
* <p>
|
||||||
* This call will be ignored if there's no listener that can receive the given events.
|
* This call will be ignored if there's no listener that can receive the given events.
|
||||||
* @param player - player to hook.
|
* @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.
|
* Invoke special routines for handling disconnect before a player is uninjected.
|
||||||
@ -149,8 +155,7 @@ public interface PlayerInjectionHandler {
|
|||||||
public abstract void close();
|
public abstract void close();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inform the current PlayerInjector that it should update the DataInputStream next.
|
* Perform any action that must be delayed until the world(s) has loaded.
|
||||||
* @param player - the player to update.
|
|
||||||
*/
|
*/
|
||||||
public abstract void scheduleDataInputRefresh(Player player);
|
public abstract void postWorldLoaded();
|
||||||
}
|
}
|
@ -24,7 +24,6 @@ import java.lang.reflect.InvocationTargetException;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
import net.sf.cglib.proxy.Factory;
|
import net.sf.cglib.proxy.Factory;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
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.GamePhase;
|
||||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
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.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
import com.comphenix.protocol.reflect.VolatileField;
|
import com.comphenix.protocol.reflect.VolatileField;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
|
||||||
abstract class PlayerInjector {
|
abstract class PlayerInjector implements SocketInjector {
|
||||||
|
|
||||||
// Net login handler stuff
|
// Net login handler stuff
|
||||||
private static Field netLoginNetworkField;
|
private static Field netLoginNetworkField;
|
||||||
@ -60,6 +60,7 @@ abstract class PlayerInjector {
|
|||||||
protected static Field networkManagerField;
|
protected static Field networkManagerField;
|
||||||
protected static Field netHandlerField;
|
protected static Field netHandlerField;
|
||||||
protected static Field socketField;
|
protected static Field socketField;
|
||||||
|
protected static Field socketAddressField;
|
||||||
|
|
||||||
private static Field inputField;
|
private static Field inputField;
|
||||||
private static Field entityPlayerField;
|
private static Field entityPlayerField;
|
||||||
@ -87,8 +88,9 @@ abstract class PlayerInjector {
|
|||||||
protected Object serverHandler;
|
protected Object serverHandler;
|
||||||
protected Object netHandler;
|
protected Object netHandler;
|
||||||
|
|
||||||
// Current socket
|
// Current socket and address
|
||||||
protected Socket socket;
|
protected Socket socket;
|
||||||
|
protected SocketAddress socketAddress;
|
||||||
|
|
||||||
// The packet manager and filters
|
// The packet manager and filters
|
||||||
protected ListenerInvoker invoker;
|
protected ListenerInvoker invoker;
|
||||||
@ -99,9 +101,6 @@ abstract class PlayerInjector {
|
|||||||
// Handle errors
|
// Handle errors
|
||||||
protected ErrorReporter reporter;
|
protected ErrorReporter reporter;
|
||||||
|
|
||||||
// Scheduled action on the next packet event
|
|
||||||
protected Runnable scheduledAction;
|
|
||||||
|
|
||||||
// Whether or not the injector has been cleaned
|
// Whether or not the injector has been cleaned
|
||||||
private boolean clean;
|
private boolean clean;
|
||||||
|
|
||||||
@ -249,10 +248,12 @@ abstract class PlayerInjector {
|
|||||||
* @return The associated socket.
|
* @return The associated socket.
|
||||||
* @throws IllegalAccessException If we're unable to read the socket field.
|
* @throws IllegalAccessException If we're unable to read the socket field.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public Socket getSocket() throws IllegalAccessException {
|
public Socket getSocket() throws IllegalAccessException {
|
||||||
try {
|
try {
|
||||||
if (socketField == null)
|
if (socketField == null)
|
||||||
socketField = FuzzyReflection.fromObject(networkManager, true).getFieldListByType(Socket.class).get(0);
|
socketField = FuzzyReflection.fromObject(networkManager, true).
|
||||||
|
getFieldListByType(Socket.class).get(0);
|
||||||
if (socket == null)
|
if (socket == null)
|
||||||
socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
|
socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
|
||||||
return socket;
|
return socket;
|
||||||
@ -263,18 +264,23 @@ abstract class PlayerInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the associated address of this player.
|
* Retrieve the associated remote address of a player.
|
||||||
* @return The associated address.
|
* @return The associated remote address..
|
||||||
* @throws IllegalAccessException If we're unable to read the socket field.
|
* @throws IllegalAccessException If we're unable to read the socket address field.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public SocketAddress getAddress() throws IllegalAccessException {
|
public SocketAddress getAddress() throws IllegalAccessException {
|
||||||
Socket socket = getSocket();
|
try {
|
||||||
|
if (socketAddressField == null)
|
||||||
// Guard against NULL
|
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
|
||||||
if (socket != null)
|
getFieldListByType(SocketAddress.class).get(0);
|
||||||
return socket.getRemoteSocketAddress();
|
if (socketAddress == null)
|
||||||
else
|
socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
|
||||||
return null;
|
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.
|
* @param message - the message to display.
|
||||||
* @throws InvocationTargetException If disconnection failed.
|
* @throws InvocationTargetException If disconnection failed.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void disconnect(String message) throws InvocationTargetException {
|
public void disconnect(String message) throws InvocationTargetException {
|
||||||
// Get a non-null handler
|
// Get a non-null handler
|
||||||
boolean usingNetServer = serverHandler != null;
|
boolean usingNetServer = serverHandler != null;
|
||||||
@ -450,6 +457,7 @@ abstract class PlayerInjector {
|
|||||||
* @param filtered - whether or not the packet will be filtered by our listeners.
|
* @param filtered - whether or not the packet will be filtered by our listeners.
|
||||||
* @param InvocationTargetException If an error occured when sending the packet.
|
* @param InvocationTargetException If an error occured when sending the packet.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public abstract void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException;
|
public abstract void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -517,12 +525,7 @@ abstract class PlayerInjector {
|
|||||||
Integer id = invoker.getPacketID(packet);
|
Integer id = invoker.getPacketID(packet);
|
||||||
Player currentPlayer = player;
|
Player currentPlayer = player;
|
||||||
|
|
||||||
// Hack #1: Handle a single scheduled action
|
// Hack #1
|
||||||
if (scheduledAction != null) {
|
|
||||||
scheduledAction.run();
|
|
||||||
scheduledAction = null;
|
|
||||||
}
|
|
||||||
// Hack #2
|
|
||||||
if (updateOnLogin) {
|
if (updateOnLogin) {
|
||||||
if (id == Packets.Server.LOGIN) {
|
if (id == Packets.Server.LOGIN) {
|
||||||
try {
|
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.
|
* Retrieve the hooked player.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public Player getPlayer() {
|
public Player getPlayer() {
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
@ -630,6 +626,7 @@ abstract class PlayerInjector {
|
|||||||
* Retrieve the hooked player object OR the more up-to-date player instance.
|
* Retrieve the hooked player object OR the more up-to-date player instance.
|
||||||
* @return The hooked player, or a more up-to-date instance.
|
* @return The hooked player, or a more up-to-date instance.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public Player getUpdatedPlayer() {
|
public Player getUpdatedPlayer() {
|
||||||
if (updatedPlayer != null)
|
if (updatedPlayer != null)
|
||||||
return updatedPlayer;
|
return updatedPlayer;
|
||||||
@ -637,6 +634,11 @@ abstract class PlayerInjector {
|
|||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transferState(SocketInjector delegate) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the real Bukkit player that we will use.
|
* Set the real Bukkit player that we will use.
|
||||||
* @param updatedPlayer - the real Bukkit player.
|
* @param updatedPlayer - the real Bukkit player.
|
||||||
|
@ -20,12 +20,13 @@ package com.comphenix.protocol.injector.player;
|
|||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import net.sf.cglib.proxy.Factory;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
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.ListenerInvoker;
|
||||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||||
import com.comphenix.protocol.injector.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.base.Predicate;
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,26 +57,26 @@ import com.google.common.collect.Maps;
|
|||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||||
/**
|
|
||||||
* The maximum number of milliseconds to wait until a player can be looked up by connection.
|
|
||||||
*/
|
|
||||||
private static final long TIMEOUT_PLAYER_LOOKUP = 2000; // ms
|
|
||||||
|
|
||||||
// Server connection injection
|
// Server connection injection
|
||||||
private InjectedServerConnection serverInjection;
|
private InjectedServerConnection serverInjection;
|
||||||
|
|
||||||
|
// Server socket injection
|
||||||
|
private AbstractInputStreamLookup inputStreamLookup;
|
||||||
|
|
||||||
// NetLogin injector
|
// NetLogin injector
|
||||||
private NetLoginInjector netLoginInjector;
|
private NetLoginInjector netLoginInjector;
|
||||||
|
|
||||||
// The last successful player hook
|
// The last successful player hook
|
||||||
private PlayerInjector lastSuccessfulHook;
|
private PlayerInjector lastSuccessfulHook;
|
||||||
|
|
||||||
// Player injection
|
// Dummy injection
|
||||||
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
|
private Cache<Player, PlayerInjector> dummyInjectors =
|
||||||
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
|
CacheBuilder.newBuilder().
|
||||||
|
expireAfterWrite(30, TimeUnit.SECONDS).
|
||||||
|
build(BlockingHashMap.<Player, PlayerInjector>newInvalidCacheLoader());
|
||||||
|
|
||||||
// Lookup player by connection
|
// Player injection
|
||||||
private BlockingHashMap<DataInputStream, PlayerInjector> dataInputLookup = BlockingHashMap.create();
|
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
|
||||||
|
|
||||||
// Player injection types
|
// Player injection types
|
||||||
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||||
@ -96,18 +103,34 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
// Used to filter injection attempts
|
// Used to filter injection attempts
|
||||||
private Predicate<GamePhase> injectionFilter;
|
private Predicate<GamePhase> injectionFilter;
|
||||||
|
|
||||||
public ProxyPlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
|
public ProxyPlayerInjectionHandler(
|
||||||
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
|
ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
|
||||||
|
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
|
||||||
|
|
||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.invoker = invoker;
|
this.invoker = invoker;
|
||||||
this.injectionFilter = injectionFilter;
|
this.injectionFilter = injectionFilter;
|
||||||
this.packetListeners = packetListeners;
|
this.packetListeners = packetListeners;
|
||||||
this.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();
|
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.
|
* Retrieves how the server packets are read.
|
||||||
@ -202,31 +225,16 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
/**
|
/**
|
||||||
* Retrieve a player by its DataInput connection.
|
* Retrieve a player by its DataInput connection.
|
||||||
* @param inputStream - the associated DataInput connection.
|
* @param inputStream - the associated DataInput connection.
|
||||||
* @return The player.
|
* @return The player we found.
|
||||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
|
public Player getPlayerByConnection(DataInputStream inputStream) {
|
||||||
return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a player by its DataInput connection.
|
|
||||||
* @param inputStream - the associated DataInput connection.
|
|
||||||
* @param playerTimeout - the amount of time to wait for a result.
|
|
||||||
* @param unit - unit of playerTimeout.
|
|
||||||
* @return The player.
|
|
||||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
|
|
||||||
// Wait until the connection owner has been established
|
// Wait until the connection owner has been established
|
||||||
PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit);
|
SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream);
|
||||||
|
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
return injector.getPlayer();
|
return injector.getPlayer();
|
||||||
} else {
|
} else {
|
||||||
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,12 +253,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
* <p>
|
* <p>
|
||||||
* This call will be ignored if there's no listener that can receive the given events.
|
* This call will be ignored if there's no listener that can receive the given events.
|
||||||
* @param player - player to hook.
|
* @param player - player to hook.
|
||||||
|
* @param strategy - how to handle previous player injections.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void injectPlayer(Player player) {
|
public void injectPlayer(Player player, ConflictStrategy strategy) {
|
||||||
// Inject using the player instance itself
|
// Inject using the player instance itself
|
||||||
if (isInjectionNecessary(GamePhase.PLAYING)) {
|
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.
|
* @param phase - the current game phase.
|
||||||
* @return The resulting player injector, or NULL if the injection failed.
|
* @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.
|
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
|
||||||
synchronized (player) {
|
synchronized (player) {
|
||||||
return injectPlayerInternal(player, injectionPoint, phase);
|
return injectPlayerInternal(player, injectionPoint, stategy, phase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsafe variant of the above
|
// 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);
|
PlayerInjector injector = playerInjection.get(player);
|
||||||
PlayerInjectHooks tempHook = getPlayerHook(phase);
|
PlayerInjectHooks tempHook = getPlayerHook(phase);
|
||||||
PlayerInjectHooks permanentHook = tempHook;
|
PlayerInjectHooks permanentHook = tempHook;
|
||||||
@ -293,7 +308,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
|
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
|
||||||
|
|
||||||
// Don't inject if the class has closed
|
// 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) {
|
while (tempHook != PlayerInjectHooks.NONE) {
|
||||||
// Whether or not the current hook method failed completely
|
// Whether or not the current hook method failed completely
|
||||||
boolean hookFailed = false;
|
boolean hookFailed = false;
|
||||||
@ -308,25 +323,24 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
if (injector.canInject(phase)) {
|
if (injector.canInject(phase)) {
|
||||||
injector.initialize(injectionPoint);
|
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
|
// Close any previously associated hooks before we proceed
|
||||||
if (previous != null) {
|
if (previous != null && !(player instanceof Factory)) {
|
||||||
uninjectPlayer(previous.getPlayer(), false, true);
|
switch (stategy) {
|
||||||
|
case OVERRIDE:
|
||||||
|
uninjectPlayer(previous.getPlayer(), true);
|
||||||
|
break;
|
||||||
|
case BAIL_OUT:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
injector.injectManager();
|
injector.injectManager();
|
||||||
|
|
||||||
if (inputStream != null)
|
// Save injector
|
||||||
dataInputLookup.put(inputStream, injector);
|
inputStreamLookup.setSocketInjector(address, injector);
|
||||||
if (address != null)
|
|
||||||
addressLookup.put(address, injector);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +387,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
|
|
||||||
return injector;
|
return injector;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanupHook(PlayerInjector injector) {
|
private void cleanupHook(PlayerInjector injector) {
|
||||||
// Clean up as much as possible
|
// Clean up as much as possible
|
||||||
try {
|
try {
|
||||||
@ -404,33 +418,21 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean uninjectPlayer(Player player) {
|
public boolean uninjectPlayer(Player player) {
|
||||||
return uninjectPlayer(player, true, false);
|
return uninjectPlayer(player, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregisters the given player.
|
* Unregisters the given player.
|
||||||
* @param player - player to unregister.
|
* @param player - player to unregister.
|
||||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
|
||||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
|
||||||
*/
|
|
||||||
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
|
|
||||||
return uninjectPlayer(player, removeAuxiliary, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters the given player.
|
|
||||||
* @param player - player to unregister.
|
|
||||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
|
||||||
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
|
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
|
||||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) {
|
private boolean uninjectPlayer(Player player, boolean prepareNextHook) {
|
||||||
if (!hasClosed && player != null) {
|
if (!hasClosed && player != null) {
|
||||||
|
|
||||||
PlayerInjector injector = playerInjection.remove(player);
|
PlayerInjector injector = playerInjection.remove(player);
|
||||||
|
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
InetSocketAddress address = player.getAddress();
|
|
||||||
injector.cleanupAll();
|
injector.cleanupAll();
|
||||||
|
|
||||||
// Remove the "hooked" network manager in our instance as well
|
// Remove the "hooked" network manager in our instance as well
|
||||||
@ -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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -471,11 +467,11 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
@Override
|
@Override
|
||||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||||
if (!hasClosed && address != null) {
|
if (!hasClosed && address != null) {
|
||||||
PlayerInjector injector = addressLookup.get(address);
|
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
if (injector != null)
|
if (injector != null)
|
||||||
uninjectPlayer(injector.getPlayer(), false, true);
|
uninjectPlayer(injector.getPlayer(), true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,7 +487,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
||||||
PlayerInjector injector = getInjector(reciever);
|
SocketInjector injector = getInjector(reciever);
|
||||||
|
|
||||||
// Send the packet, or drop it completely
|
// Send the packet, or drop it completely
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
@ -513,7 +509,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||||
|
|
||||||
PlayerInjector injector = getInjector(player);
|
PlayerInjector injector = getInjector(player);
|
||||||
|
|
||||||
// Process the given packet, or simply give up
|
// Process the given packet, or simply give up
|
||||||
@ -536,28 +531,49 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
|
|
||||||
if (injector == null) {
|
if (injector == null) {
|
||||||
// Try getting it from the player itself
|
// Try getting it from the player itself
|
||||||
if (player instanceof InjectContainer)
|
SocketAddress address = player.getAddress();
|
||||||
return ((InjectContainer) player).getInjector();
|
// 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
|
else
|
||||||
return searchAddressLookup(player);
|
// Make a dummy injector them
|
||||||
|
return createDummyInjector(player);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return injector;
|
return injector;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an injector by looking through the address map.
|
* Construct a simple dummy injector incase none has been constructed.
|
||||||
* @param player - player to find.
|
* @param player - the CraftPlayer to construct for.
|
||||||
* @return The injector, or NULL if not found.
|
* @return A dummy injector, or NULL if the given player is not a CraftPlayer.
|
||||||
*/
|
*/
|
||||||
private PlayerInjector searchAddressLookup(Player player) {
|
private PlayerInjector createDummyInjector(Player player) {
|
||||||
// See if we can find it anywhere
|
if (!MinecraftReflection.getCraftPlayerClass().isAssignableFrom(player.getClass())) {
|
||||||
for (PlayerInjector injector : addressLookup.values()) {
|
// No - this is not safe
|
||||||
if (player.equals(injector.getUpdatedPlayer())) {
|
return null;
|
||||||
return injector;
|
}
|
||||||
}
|
|
||||||
|
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
|
// Remove server handler
|
||||||
|
if (inputStreamLookup != null)
|
||||||
|
inputStreamLookup.cleanupAll();
|
||||||
if (serverInjection != null)
|
if (serverInjection != null)
|
||||||
serverInjection.cleanupAll();
|
serverInjection.cleanupAll();
|
||||||
if (netLoginInjector != null)
|
if (netLoginInjector != null)
|
||||||
netLoginInjector.cleanupAll();
|
netLoginInjector.cleanupAll();
|
||||||
|
inputStreamLookup = null;
|
||||||
serverInjection = null;
|
serverInjection = null;
|
||||||
netLoginInjector = null;
|
netLoginInjector = null;
|
||||||
hasClosed = true;
|
hasClosed = true;
|
||||||
|
|
||||||
playerInjection.clear();
|
playerInjection.clear();
|
||||||
addressLookup.clear();
|
|
||||||
invoker = null;
|
invoker = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform the current PlayerInjector that it should update the DataInputStream next.
|
|
||||||
* @param player - the player to update.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void scheduleDataInputRefresh(Player player) {
|
|
||||||
final PlayerInjector injector = getInjector(player);
|
|
||||||
|
|
||||||
// Update the DataInputStream
|
|
||||||
if (injector != null) {
|
|
||||||
injector.scheduleAction(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
dataInputLookup.put(injector.getInputStream(false), injector);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Able to store a socket injector.
|
||||||
|
* <p>
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
|
||||||
|
protected ConcurrentMap<InputStream, SocketAddress> 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.
|
||||||
|
* <p>
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
* 02111-1307 USA
|
* 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.comphenix.protocol.injector.player;
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@ -36,26 +36,7 @@ import com.comphenix.protocol.reflect.FieldAccessException;
|
|||||||
/**
|
/**
|
||||||
* Create fake player instances that represents pre-authenticated clients.
|
* Create fake player instances that represents pre-authenticated clients.
|
||||||
*/
|
*/
|
||||||
class TemporaryPlayerFactory {
|
public class TemporaryPlayerFactory {
|
||||||
|
|
||||||
/**
|
|
||||||
* Able to store a PlayerInjector.
|
|
||||||
* <p>
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpful constructors
|
// Helpful constructors
|
||||||
private final PacketConstructor chatPacket;
|
private final PacketConstructor chatPacket;
|
||||||
|
|
||||||
@ -66,6 +47,27 @@ class TemporaryPlayerFactory {
|
|||||||
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
|
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.
|
* Construct a temporary player that supports a subset of every player command.
|
||||||
* <p>
|
* <p>
|
||||||
@ -80,7 +82,7 @@ class TemporaryPlayerFactory {
|
|||||||
* <li>kickPlayer(String)</li>
|
* <li>kickPlayer(String)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Note that the player a player has not been assigned a name yet, and thus cannot be
|
* Note that a temporary player has not yet been assigned a name, and thus cannot be
|
||||||
* uniquely identified. Use the address instead.
|
* uniquely identified. Use the address instead.
|
||||||
* @param injector - the player injector used.
|
* @param injector - the player injector used.
|
||||||
* @param server - the current server.
|
* @param server - the current server.
|
||||||
@ -94,7 +96,7 @@ class TemporaryPlayerFactory {
|
|||||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
|
|
||||||
String methodName = method.getName();
|
String methodName = method.getName();
|
||||||
PlayerInjector injector = ((InjectContainer) obj).getInjector();
|
SocketInjector injector = ((InjectorContainer) obj).getInjector();
|
||||||
|
|
||||||
if (injector == null)
|
if (injector == null)
|
||||||
throw new IllegalStateException("Unable to find injector.");
|
throw new IllegalStateException("Unable to find injector.");
|
||||||
@ -147,7 +149,7 @@ class TemporaryPlayerFactory {
|
|||||||
public int accept(Method method) {
|
public int accept(Method method) {
|
||||||
// Do not override the object method or the superclass methods
|
// Do not override the object method or the superclass methods
|
||||||
if (method.getDeclaringClass().equals(Object.class) ||
|
if (method.getDeclaringClass().equals(Object.class) ||
|
||||||
method.getDeclaringClass().equals(InjectContainer.class))
|
method.getDeclaringClass().equals(InjectorContainer.class))
|
||||||
return 0;
|
return 0;
|
||||||
else
|
else
|
||||||
return 1;
|
return 1;
|
||||||
@ -157,7 +159,7 @@ class TemporaryPlayerFactory {
|
|||||||
|
|
||||||
// CGLib is amazing
|
// CGLib is amazing
|
||||||
Enhancer ex = new Enhancer();
|
Enhancer ex = new Enhancer();
|
||||||
ex.setSuperclass(InjectContainer.class);
|
ex.setSuperclass(InjectorContainer.class);
|
||||||
ex.setInterfaces(new Class[] { Player.class });
|
ex.setInterfaces(new Class[] { Player.class });
|
||||||
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
|
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
|
||||||
ex.setCallbackFilter(callbackFilter);
|
ex.setCallbackFilter(callbackFilter);
|
||||||
@ -165,6 +167,19 @@ class TemporaryPlayerFactory {
|
|||||||
return (Player) ex.create();
|
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.
|
* Send a message to the given client.
|
||||||
* @param injector - the injector representing the client.
|
* @param injector - the injector representing the client.
|
||||||
@ -173,7 +188,7 @@ class TemporaryPlayerFactory {
|
|||||||
* @throws InvocationTargetException If the message couldn't be sent.
|
* @throws InvocationTargetException If the message couldn't be sent.
|
||||||
* @throws FieldAccessException If we were unable to construct the message packet.
|
* @throws FieldAccessException If we were unable to construct the message packet.
|
||||||
*/
|
*/
|
||||||
private Object sendMessage(PlayerInjector injector, String message) throws InvocationTargetException, FieldAccessException {
|
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
|
||||||
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
|
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
@ -4,8 +4,6 @@ import java.io.DataInputStream;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.comphenix.protocol.concurrency.IntegerSet;
|
import com.comphenix.protocol.concurrency.IntegerSet;
|
||||||
@ -49,12 +47,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
|
|||||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||||
throw new UnsupportedOperationException("This is not needed in Spigot.");
|
throw new UnsupportedOperationException("This is not needed in Spigot.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scheduleDataInputRefresh(Player player) {
|
|
||||||
// Fine
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addPacketHandler(int packetID) {
|
public void addPacketHandler(int packetID) {
|
||||||
sendingFilters.add(packetID);
|
sendingFilters.add(packetID);
|
||||||
@ -86,7 +79,8 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectPlayer(Player player) {
|
public void injectPlayer(Player player, ConflictStrategy strategy) {
|
||||||
|
// We don't care about strategy
|
||||||
injector.injectPlayer(player);
|
injector.injectPlayer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,11 +100,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
|
|||||||
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
|
|
||||||
throw new UnsupportedOperationException("This is not needed in Spigot.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
|
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
|
||||||
throw new UnsupportedOperationException("This is not needed in Spigot.");
|
throw new UnsupportedOperationException("This is not needed in Spigot.");
|
||||||
@ -125,4 +114,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
|
|||||||
public void checkListener(Set<PacketListener> listeners) {
|
public void checkListener(Set<PacketListener> listeners) {
|
||||||
// Yes, really
|
// Yes, really
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postWorldLoaded() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,7 +349,7 @@ public class SpigotPacketInjector implements SpigotPacketListener {
|
|||||||
public Object packetQueued(Object networkManager, Object connection, Object packet) {
|
public Object packetQueued(Object networkManager, Object connection, Object packet) {
|
||||||
Integer id = invoker.getPacketID(packet);
|
Integer id = invoker.getPacketID(packet);
|
||||||
|
|
||||||
if (id != null & queuedFilters.contains(id)) {
|
if (id != null && queuedFilters.contains(id)) {
|
||||||
// Check for ignored packets
|
// Check for ignored packets
|
||||||
if (ignoredPackets.remove(packet)) {
|
if (ignoredPackets.remove(packet)) {
|
||||||
return packet;
|
return packet;
|
||||||
@ -427,7 +427,7 @@ public class SpigotPacketInjector implements SpigotPacketListener {
|
|||||||
void uninjectPlayer(Player player) {
|
void uninjectPlayer(Player player) {
|
||||||
final NetworkObjectInjector injector = getInjector(player);
|
final NetworkObjectInjector injector = getInjector(player);
|
||||||
|
|
||||||
if (player != null) {
|
if (player != null && injector != null) {
|
||||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
|
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -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
|
* 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.io.*;
|
||||||
|
import java.net.ConnectException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
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");
|
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!
|
result = Updater.UpdateResult.FAIL_BADSLUG; // Bad slug! Bad!
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url != null)
|
if (url != null)
|
||||||
{
|
{
|
||||||
// Obtain the results of the project's file feed
|
// 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))
|
if(versionCheck(versionTitle))
|
||||||
{
|
{
|
||||||
String fileLink = getFile(versionLink);
|
String fileLink = getFile(versionLink);
|
||||||
@ -545,8 +554,9 @@ public class Updater
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of RSS Reader by Vogella, modified by H31IX for use with Bukkit
|
* 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
|
try
|
||||||
{
|
{
|
||||||
@ -598,15 +608,15 @@ public class Updater
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the RSS feed
|
* 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();
|
return url.openStream();
|
||||||
}
|
} catch (ConnectException e) {
|
||||||
catch (IOException e)
|
throw e;
|
||||||
{
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
|
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves fields and methods by signature, not just name.
|
* 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);
|
throw new IllegalArgumentException("Unable to find a method that matches " + matcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every method as a map over names.
|
||||||
|
* <p>
|
||||||
|
* 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<String, Method> getMappedMethods(List<Method> methods) {
|
||||||
|
Map<String, Method> 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.
|
* Retrieve a list of every constructor that matches the given matcher.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -21,6 +21,8 @@ import java.lang.reflect.Array;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.google.common.primitives.Primitives;
|
import com.google.common.primitives.Primitives;
|
||||||
@ -80,7 +82,7 @@ public class PrettyPrinter {
|
|||||||
|
|
||||||
// Start and stop
|
// Start and stop
|
||||||
output.append("{ ");
|
output.append("{ ");
|
||||||
printObject(output, object, start, stop, previous, hierachyDepth);
|
printObject(output, object, start, stop, previous, hierachyDepth, true);
|
||||||
output.append(" }");
|
output.append(" }");
|
||||||
|
|
||||||
return output.toString();
|
return output.toString();
|
||||||
@ -99,15 +101,42 @@ public class PrettyPrinter {
|
|||||||
else
|
else
|
||||||
output.append(", ");
|
output.append(", ");
|
||||||
|
|
||||||
// Handle exceptions
|
// Print value
|
||||||
if (value != null)
|
printValue(output, value, stop, previous, hierachyIndex - 1);
|
||||||
printValue(output, value, value.getClass(), stop, previous, hierachyIndex - 1);
|
|
||||||
else
|
|
||||||
output.append("NULL");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output.append(")");
|
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<Object, Object> map, Class<?> current, Class<?> stop,
|
||||||
|
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
|
output.append("[");
|
||||||
|
|
||||||
|
for (Entry<Object, Object> 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,
|
private static void printArray(StringBuilder output, Object array, Class<?> current, Class<?> stop,
|
||||||
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
|
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
|
||||||
@ -142,10 +171,8 @@ public class PrettyPrinter {
|
|||||||
|
|
||||||
// Internal recursion method
|
// Internal recursion method
|
||||||
private static void printObject(StringBuilder output, Object object, Class<?> current, Class<?> stop,
|
private static void printObject(StringBuilder output, Object object, Class<?> current, Class<?> stop,
|
||||||
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
|
Set<Object> previous, int hierachyIndex, boolean first) throws IllegalAccessException {
|
||||||
// Trickery
|
|
||||||
boolean first = true;
|
|
||||||
|
|
||||||
// See if we're supposed to skip this class
|
// See if we're supposed to skip this class
|
||||||
if (current == Object.class || (stop != null && current.equals(stop))) {
|
if (current == Object.class || (stop != null && current.equals(stop))) {
|
||||||
return;
|
return;
|
||||||
@ -168,10 +195,11 @@ public class PrettyPrinter {
|
|||||||
Class<?> type = field.getType();
|
Class<?> type = field.getType();
|
||||||
Object value = FieldUtils.readField(field, object, true);
|
Object value = FieldUtils.readField(field, object, true);
|
||||||
|
|
||||||
if (first)
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
else
|
} else {
|
||||||
output.append(", ");
|
output.append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
output.append(field.getName());
|
output.append(field.getName());
|
||||||
output.append(" = ");
|
output.append(" = ");
|
||||||
@ -180,10 +208,16 @@ public class PrettyPrinter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recurse
|
// 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<Object> 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,
|
private static void printValue(StringBuilder output, Object value, Class<?> type,
|
||||||
Class<?> stop, Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
|
Class<?> stop, Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
|
||||||
// Just print primitive types
|
// Just print primitive types
|
||||||
@ -197,12 +231,14 @@ public class PrettyPrinter {
|
|||||||
printArray(output, value, type, stop, previous, hierachyIndex);
|
printArray(output, value, type, stop, previous, hierachyIndex);
|
||||||
} else if (Iterable.class.isAssignableFrom(type)) {
|
} else if (Iterable.class.isAssignableFrom(type)) {
|
||||||
printIterables(output, (Iterable) value, type, stop, previous, hierachyIndex);
|
printIterables(output, (Iterable) value, type, stop, previous, hierachyIndex);
|
||||||
|
} else if (Map.class.isAssignableFrom(type)) {
|
||||||
|
printMap(output, (Map<Object, Object>) value, type, stop, previous, hierachyIndex);
|
||||||
} else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) {
|
} else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) {
|
||||||
// Don't print previous objects
|
// Don't print previous objects
|
||||||
output.append("\"" + value + "\"");
|
output.append("\"" + value + "\"");
|
||||||
} else {
|
} else {
|
||||||
output.append("{ ");
|
output.append("{ ");
|
||||||
printObject(output, value, value.getClass(), stop, previous, hierachyIndex);
|
printObject(output, value, value.getClass(), stop, previous, hierachyIndex, true);
|
||||||
output.append(" }");
|
output.append(" }");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol.reflect.compiler;
|
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.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@ -29,6 +33,9 @@ import javax.annotation.Nullable;
|
|||||||
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
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;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,15 +55,26 @@ public class BackgroundCompiler {
|
|||||||
// How long to wait for a shutdown
|
// How long to wait for a shutdown
|
||||||
public static final int SHUTDOWN_DELAY_MS = 2000;
|
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
|
// The single background compiler we're using
|
||||||
private static BackgroundCompiler backgroundCompiler;
|
private static BackgroundCompiler backgroundCompiler;
|
||||||
|
|
||||||
|
// Classes we're currently compiling
|
||||||
|
private Map<StructureKey, List<CompileListener<?>>> listeners = Maps.newHashMap();
|
||||||
|
private Object listenerLock = new Object();
|
||||||
|
|
||||||
private StructureCompiler compiler;
|
private StructureCompiler compiler;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean shuttingDown;
|
private boolean shuttingDown;
|
||||||
|
|
||||||
private ExecutorService executor;
|
private ExecutorService executor;
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
|
|
||||||
|
private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the current background compiler.
|
* Retrieves the current background compiler.
|
||||||
@ -139,26 +157,61 @@ public class BackgroundCompiler {
|
|||||||
* @param uncompiled - structure modifier to compile.
|
* @param uncompiled - structure modifier to compile.
|
||||||
* @param listener - listener responsible for responding to the compilation.
|
* @param listener - listener responsible for responding to the compilation.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
||||||
|
|
||||||
// Only schedule if we're enabled
|
// Only schedule if we're enabled
|
||||||
if (enabled && !shuttingDown) {
|
if (enabled && !shuttingDown) {
|
||||||
|
// Check perm gen
|
||||||
|
if (getPermGenUsage() > disablePermGenFraction)
|
||||||
|
return;
|
||||||
|
|
||||||
// Don't try to schedule anything
|
// Don't try to schedule anything
|
||||||
if (executor == null || executor.isShutdown())
|
if (executor == null || executor.isShutdown())
|
||||||
return;
|
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
|
// Create the worker that will compile our modifier
|
||||||
Callable<?> worker = new Callable<Object>() {
|
Callable<?> worker = new Callable<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
StructureModifier<TKey> modifier = uncompiled;
|
StructureModifier<TKey> modifier = uncompiled;
|
||||||
|
List list = null;
|
||||||
|
|
||||||
// Do our compilation
|
// Do our compilation
|
||||||
try {
|
try {
|
||||||
modifier = compiler.compile(modifier);
|
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<TKey>) compileListener).onCompiled(modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove it when we're done
|
||||||
|
synchronized (listenerLock) {
|
||||||
|
list = listeners.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// Disable future compilations!
|
// Disable future compilations!
|
||||||
setEnabled(false);
|
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 <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> 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.
|
* Clean up after ourselves using the default timeout.
|
||||||
*/
|
*/
|
||||||
@ -246,6 +334,22 @@ public class BackgroundCompiler {
|
|||||||
this.enabled = enabled;
|
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.
|
* Retrieve the current structure compiler.
|
||||||
* @return Current structure compiler.
|
* @return Current structure compiler.
|
||||||
|
@ -25,6 +25,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.primitives.Primitives;
|
import com.google.common.primitives.Primitives;
|
||||||
@ -94,7 +95,7 @@ public final class StructureCompiler {
|
|||||||
|
|
||||||
// Used to store generated classes of different types
|
// Used to store generated classes of different types
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
private static class StructureKey {
|
static class StructureKey {
|
||||||
private Class targetType;
|
private Class targetType;
|
||||||
private Class fieldType;
|
private Class fieldType;
|
||||||
|
|
||||||
@ -206,6 +207,11 @@ public final class StructureCompiler {
|
|||||||
return (StructureModifier<TField>) compiledClass.getConstructor(
|
return (StructureModifier<TField>) compiledClass.getConstructor(
|
||||||
StructureModifier.class, StructureCompiler.class).
|
StructureModifier.class, StructureCompiler.class).
|
||||||
newInstance(source, this);
|
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) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new IllegalStateException("Used invalid parameters in instance creation", e);
|
throw new IllegalStateException("Used invalid parameters in instance creation", e);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
@ -218,7 +224,7 @@ public final class StructureCompiler {
|
|||||||
throw new RuntimeException("Error occured while instancing generated class.", e);
|
throw new RuntimeException("Error occured while instancing generated class.", e);
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
throw new IllegalStateException("Cannot happen.", e);
|
throw new IllegalStateException("Cannot happen.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,7 +58,7 @@ class CachedPackage {
|
|||||||
if (result == null) {
|
if (result == null) {
|
||||||
// Look up the class dynamically
|
// Look up the class dynamically
|
||||||
result = CachedPackage.class.getClassLoader().
|
result = CachedPackage.class.getClassLoader().
|
||||||
loadClass(packageName + "." + className);
|
loadClass(combine(packageName, className));
|
||||||
cache.put(className, result);
|
cache.put(className, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,4 +68,16 @@ class CachedPackage {
|
|||||||
throw new RuntimeException("Cannot find class " + className, e);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import com.comphenix.protocol.Packets;
|
|||||||
import com.comphenix.protocol.ProtocolManager;
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
import com.comphenix.protocol.injector.PacketConstructor;
|
import com.comphenix.protocol.injector.PacketConstructor;
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility methods for sending chat messages.
|
* 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<String, Method> netServer = getMethodList(
|
||||||
|
serverHandlerClass, MinecraftReflection.getPacketClass());
|
||||||
|
Map<String, Method> 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<String, Method> getMethodList(Class<?> source, Class<?>... params) {
|
||||||
|
FuzzyReflection reflect = FuzzyReflection.fromClass(source, true);
|
||||||
|
|
||||||
|
return reflect.getMappedMethods(
|
||||||
|
reflect.getMethodListByParameters(Void.TYPE, params)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -73,7 +73,7 @@ public class MinecraftReflection {
|
|||||||
|
|
||||||
private static String MINECRAFT_FULL_PACKAGE = null;
|
private static String MINECRAFT_FULL_PACKAGE = null;
|
||||||
private static String CRAFTBUKKIT_PACKAGE = null;
|
private static String CRAFTBUKKIT_PACKAGE = null;
|
||||||
|
|
||||||
private static CachedPackage minecraftPackage;
|
private static CachedPackage minecraftPackage;
|
||||||
private static CachedPackage craftbukkitPackage;
|
private static CachedPackage craftbukkitPackage;
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ public class MinecraftReflection {
|
|||||||
private static Method craftNMSMethod;
|
private static Method craftNMSMethod;
|
||||||
private static Method craftBukkitMethod;
|
private static Method craftBukkitMethod;
|
||||||
private static boolean craftItemStackFailed;
|
private static boolean craftItemStackFailed;
|
||||||
|
|
||||||
// net.minecraft.server
|
// net.minecraft.server
|
||||||
private static Class<?> itemStackArrayClass;
|
private static Class<?> itemStackArrayClass;
|
||||||
|
|
||||||
@ -125,15 +125,15 @@ public class MinecraftReflection {
|
|||||||
// This server should have a "getHandle" method that we can use
|
// This server should have a "getHandle" method that we can use
|
||||||
if (craftServer != null) {
|
if (craftServer != null) {
|
||||||
try {
|
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
|
// The return type will tell us the full package, regardless of formating
|
||||||
|
Class<?> craftClass = craftServer.getClass();
|
||||||
CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName());
|
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
|
// Pretty important invariant
|
||||||
if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) {
|
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?");
|
throw new IllegalStateException("Could not find Bukkit. Is it running?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used during debugging and testing.
|
* Used during debugging and testing.
|
||||||
* @param minecraftPackage - the current Minecraft package.
|
* @param minecraftPackage - the current Minecraft package.
|
||||||
@ -185,7 +185,8 @@ public class MinecraftReflection {
|
|||||||
*/
|
*/
|
||||||
public static String getCraftBukkitPackage() {
|
public static String getCraftBukkitPackage() {
|
||||||
// Ensure it has been initialized
|
// Ensure it has been initialized
|
||||||
getMinecraftPackage();
|
if (CRAFTBUKKIT_PACKAGE == null)
|
||||||
|
getMinecraftPackage();
|
||||||
return CRAFTBUKKIT_PACKAGE;
|
return CRAFTBUKKIT_PACKAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,22 +491,29 @@ public class MinecraftReflection {
|
|||||||
public static Class<?> getMinecraftServerClass() {
|
public static Class<?> getMinecraftServerClass() {
|
||||||
try {
|
try {
|
||||||
return getMinecraftClass("MinecraftServer");
|
return getMinecraftClass("MinecraftServer");
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
// Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY)
|
useFallbackServer();
|
||||||
Constructor<?> selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")).
|
return getMinecraftClass("MinecraftServer");
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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),
|
* Retrieve the player list class (or ServerConfigurationManager),
|
||||||
@ -516,7 +524,7 @@ public class MinecraftReflection {
|
|||||||
return getMinecraftClass("ServerConfigurationManager", "PlayerList");
|
return getMinecraftClass("ServerConfigurationManager", "PlayerList");
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
// Try again
|
// Try again
|
||||||
getMinecraftServerClass();
|
useFallbackServer();
|
||||||
return getMinecraftClass("ServerConfigurationManager");
|
return getMinecraftClass("ServerConfigurationManager");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -553,7 +561,7 @@ public class MinecraftReflection {
|
|||||||
return getMinecraftClass("NetServerHandler", "PlayerConnection");
|
return getMinecraftClass("NetServerHandler", "PlayerConnection");
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
// Use the player connection field
|
// Use the player connection field
|
||||||
return setMinecraftClass("NetLoginHandler",
|
return setMinecraftClass("NetServerHandler",
|
||||||
FuzzyReflection.fromClass(getEntityPlayerClass()).
|
FuzzyReflection.fromClass(getEntityPlayerClass()).
|
||||||
getFieldByType("playerConnection", getNetHandlerClass()).getType()
|
getFieldByType("playerConnection", getNetHandlerClass()).getType()
|
||||||
);
|
);
|
||||||
@ -908,7 +916,15 @@ public class MinecraftReflection {
|
|||||||
public static Class<?> getCraftPlayerClass() {
|
public static Class<?> getCraftPlayerClass() {
|
||||||
return getCraftBukkitClass("entity.CraftPlayer");
|
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.
|
* Retrieve a CraftItemStack from a given ItemStack.
|
||||||
* @param bukkitItemStack - the Bukkit ItemStack to convert.
|
* @param bukkitItemStack - the Bukkit ItemStack to convert.
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.comphenix.protocol.wrappers.nbt;
|
||||||
|
|
||||||
|
class MemoryElement<TType> implements NbtBase<TType> {
|
||||||
|
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<TType> deepClone() {
|
||||||
|
// This assumes value is an immutable object
|
||||||
|
return new MemoryElement<TType>(name, value, type);
|
||||||
|
}
|
||||||
|
}
|
@ -64,6 +64,9 @@ public interface NbtBase<TType> {
|
|||||||
* Is either a primitive {@link java.lang.Number wrapper}, {@link java.lang.String String},
|
* 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}.
|
* {@link java.util.List List} or a {@link java.util.Map Map}.
|
||||||
* <p>
|
* <p>
|
||||||
|
* 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().
|
||||||
|
* <p>
|
||||||
* All operations that modify collections directly, such as {@link java.util.List#add(Object) List.add(Object)} or
|
* 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.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
|
* {@link java.util.ListIterator ListIterator}. Operations that are not implemented throw a
|
||||||
|
@ -5,6 +5,8 @@ import java.util.Iterator;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a mapping of arbitrary NBT elements and their unique names.
|
* Represents a mapping of arbitrary NBT elements and their unique names.
|
||||||
* <p>
|
* <p>
|
||||||
@ -16,6 +18,10 @@ import java.util.Set;
|
|||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<NbtBase<?>> {
|
public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<NbtBase<?>> {
|
||||||
|
@Override
|
||||||
|
@Deprecated()
|
||||||
|
public Map<String, NbtBase<?>> getValue();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if an entry with the given key exists or not.
|
* Determine if an entry with the given key exists or not.
|
||||||
* @param key - the key to lookup.
|
* @param key - the key to lookup.
|
||||||
@ -48,8 +54,9 @@ public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<
|
|||||||
* Set a entry based on its name.
|
* Set a entry based on its name.
|
||||||
* @param entry - entry with a name and value.
|
* @param entry - entry with a name and value.
|
||||||
* @return This compound, for chaining.
|
* @return This compound, for chaining.
|
||||||
|
* @throws IllegalArgumentException If entry is NULL.
|
||||||
*/
|
*/
|
||||||
public abstract <T> NbtCompound put(NbtBase<T> entry);
|
public abstract <T> NbtCompound put(@Nonnull NbtBase<T> entry);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the string value of an entry identified by a given key.
|
* Retrieve the string value of an entry identified by a given key.
|
||||||
@ -251,7 +258,26 @@ public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<
|
|||||||
* @return This current compound, for chaining.
|
* @return This current compound, for chaining.
|
||||||
*/
|
*/
|
||||||
public abstract NbtCompound put(String key, int[] value);
|
public abstract NbtCompound put(String key, int[] value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associates a given Java primitive value, list, map or NbtBase<?> with a certain key.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
* Retrieve the compound (map) value of an entry identified by a given key.
|
||||||
* @param key - the key of the entry.
|
* @param key - the key of the entry.
|
||||||
@ -304,6 +330,13 @@ public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<
|
|||||||
*/
|
*/
|
||||||
public abstract <T> NbtCompound put(String key, Collection<? extends NbtBase<T>> list);
|
public abstract <T> NbtCompound put(String key, Collection<? extends NbtBase<T>> 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 <T> NbtBase<?> remove(String key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an iterator view of the NBT tags stored in this compound.
|
* Retrieve an iterator view of the NBT tags stored in this compound.
|
||||||
* @return The tags stored in this compound.
|
* @return The tags stored in this compound.
|
||||||
|
@ -152,7 +152,13 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
public void setValue(Map<String, NbtBase<?>> newValue) {
|
public void setValue(Map<String, NbtBase<?>> newValue) {
|
||||||
// Write all the entries
|
// Write all the entries
|
||||||
for (Map.Entry<String, NbtBase<?>> entry : newValue.entrySet()) {
|
for (Map.Entry<String, NbtBase<?>> 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<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public <T> NbtCompound put(NbtBase<T> entry) {
|
public <T> NbtCompound put(NbtBase<T> entry) {
|
||||||
|
if (entry == null)
|
||||||
|
throw new IllegalArgumentException("Entry cannot be NULL.");
|
||||||
|
|
||||||
getValue().put(entry.getName(), entry);
|
getValue().put(entry.getName(), entry);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -252,6 +261,29 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NbtCompound putObject(String key, Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
remove(key);
|
||||||
|
} else if (value instanceof NbtBase) {
|
||||||
|
put(key, (NbtBase<?>) value);
|
||||||
|
} else {
|
||||||
|
NbtBase<?> base = new MemoryElement<Object>(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.
|
* Retrieve the byte value of an entry identified by a given key.
|
||||||
* @param key - the key of the entry.
|
* @param key - the key of the entry.
|
||||||
@ -565,6 +597,9 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NbtCompound put(String key, NbtBase<?> entry) {
|
public NbtCompound put(String key, NbtBase<?> entry) {
|
||||||
|
if (entry == null)
|
||||||
|
throw new IllegalArgumentException("Entry cannot be NULL.");
|
||||||
|
|
||||||
// Don't modify the original NBT
|
// Don't modify the original NBT
|
||||||
NbtBase<?> clone = entry.deepClone();
|
NbtBase<?> clone = entry.deepClone();
|
||||||
|
|
||||||
@ -583,6 +618,11 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
|||||||
return put(WrappedList.fromList(key, list));
|
return put(WrappedList.fromList(key, list));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> NbtBase<?> remove(String key) {
|
||||||
|
return getValue().remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(DataOutput destination) {
|
public void write(DataOutput destination) {
|
||||||
NbtBinarySerializer.DEFAULT.serialize(container, destination);
|
NbtBinarySerializer.DEFAULT.serialize(container, destination);
|
||||||
|
@ -18,5 +18,4 @@ global:
|
|||||||
ignore version check:
|
ignore version check:
|
||||||
|
|
||||||
# Override the starting injecting method
|
# Override the starting injecting method
|
||||||
injection method:
|
injection method:
|
||||||
|
|
@ -1,9 +1,8 @@
|
|||||||
name: ProtocolLib
|
name: ProtocolLib
|
||||||
version: 2.2.0
|
version: 2.3.0
|
||||||
description: Provides read/write access to the Minecraft protocol.
|
description: Provides read/write access to the Minecraft protocol.
|
||||||
author: Comphenix
|
author: Comphenix
|
||||||
website: http://www.comphenix.net/ProtocolLib
|
website: http://www.comphenix.net/ProtocolLib
|
||||||
load: startup
|
|
||||||
main: com.comphenix.protocol.ProtocolLibrary
|
main: com.comphenix.protocol.ProtocolLibrary
|
||||||
database: false
|
database: false
|
||||||
|
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren