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