Merge branch 'master' into gh-pages

This commit is contained in:
Kristian S. Stangeland 2013-03-05 16:59:29 +01:00
commit ef334aff50
41 changed files with 1460 additions and 498 deletions

View File

@ -7,12 +7,6 @@
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/> <classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes> <attributes>
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>

View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId> <groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId> <artifactId>ProtocolLib</artifactId>
<version>2.2.0</version> <version>2.3.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<description>Provides read/write access to the Minecraft protocol.</description> <description>Provides read/write access to the Minecraft protocol.</description>

View File

@ -27,6 +27,9 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodUtils; import com.comphenix.protocol.reflect.MethodUtils;
@ -36,9 +39,11 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
/** /**
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation. * Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
@ -66,7 +71,9 @@ class CleanupStaticMembers {
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
BackgroundCompiler.class, StructureCompiler.class, BackgroundCompiler.class, StructureCompiler.class,
ObjectWriter.class, Packets.Server.class, Packets.Client.class, ObjectWriter.class, Packets.Server.class, Packets.Client.class,
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
MinecraftReflection.class, NbtBinarySerializer.class
}; };
String[] internalClasses = { String[] internalClasses = {
@ -76,14 +83,14 @@ class CleanupStaticMembers {
"com.comphenix.protocol.injector.player.NetworkObjectInjector", "com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.player.NetworkServerInjector", "com.comphenix.protocol.injector.player.NetworkServerInjector",
"com.comphenix.protocol.injector.player.PlayerInjector", "com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.injector.player.TemporaryPlayerFactory",
"com.comphenix.protocol.injector.EntityUtilities", "com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.injector.packet.PacketRegistry", "com.comphenix.protocol.injector.packet.PacketRegistry",
"com.comphenix.protocol.injector.packet.PacketInjector", "com.comphenix.protocol.injector.packet.PacketInjector",
"com.comphenix.protocol.injector.packet.ReadPacketModifier", "com.comphenix.protocol.injector.packet.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache", "com.comphenix.protocol.injector.StructureCache",
"com.comphenix.protocol.reflect.compiler.BoxingHelper", "com.comphenix.protocol.reflect.compiler.BoxingHelper",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor" "com.comphenix.protocol.reflect.compiler.MethodDescriptor",
"com.comphenix.protocol.wrappers.nbt.WrappedElement",
}; };
resetClasses(publicClasses); resetClasses(publicClasses);

View File

@ -228,7 +228,7 @@ class ProtocolConfig {
public void setBackgroundCompilerEnabled(boolean enabled) { public void setBackgroundCompilerEnabled(boolean enabled) {
global.set(BACKGROUND_COMPILER_ENABLED, enabled); global.set(BACKGROUND_COMPILER_ENABLED, enabled);
} }
/** /**
* Set the last time we updated, in seconds since 1970.01.01 00:00. * Set the last time we updated, in seconds since 1970.01.01 00:00.
* @param lastTimeSeconds - new last update time. * @param lastTimeSeconds - new last update time.

View File

@ -40,7 +40,9 @@ import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.metrics.Updater; import com.comphenix.protocol.metrics.Updater;
import com.comphenix.protocol.metrics.Updater.UpdateResult;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.utility.ChatExtensions;
/** /**
* The main entry point for ProtocolLib. * The main entry point for ProtocolLib.
@ -134,7 +136,10 @@ public class ProtocolLibrary extends JavaPlugin {
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
unhookTask = new DelayedSingleTask(this); unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, detailedReporter); protocolManager = new PacketFilterManager(
getClassLoader(), getServer(), unhookTask, detailedReporter);
// Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager); detailedReporter.addGlobalParameter("manager", protocolManager);
// Update injection hook // Update injection hook
@ -215,6 +220,24 @@ public class ProtocolLibrary extends JavaPlugin {
// Don't do anything else! // Don't do anything else!
if (manager == null) if (manager == null)
return; return;
// Silly plugin reloaders!
if (protocolManager == null) {
Logger directLogging = Logger.getLogger("Minecraft");
String[] message = new String[] {
" PROTOCOLLIB DOES NOT SUPPORT PLUGIN RELOADERS. ",
" PLEASE USE THE BUILT-IN RELOAD COMMAND. ",
};
// Print as severe
for (String line : ChatExtensions.toFlowerBox(message, "*", 3, 1)) {
directLogging.severe(line);
}
disablePlugin();
return;
}
// Perform logic when the world has loaded
protocolManager.postWorldLoaded();
// Initialize background compiler // Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) { if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
@ -257,7 +280,7 @@ public class ProtocolLibrary extends JavaPlugin {
reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc); reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc);
} }
} }
// Used to check Minecraft version // Used to check Minecraft version
private void verifyMinecraftVersion() { private void verifyMinecraftVersion() {
try { try {
@ -422,16 +445,22 @@ public class ProtocolLibrary extends JavaPlugin {
if (redirectHandler != null) { if (redirectHandler != null) {
logger.removeHandler(redirectHandler); logger.removeHandler(redirectHandler);
} }
if (protocolManager != null)
unhookTask.close(); protocolManager.close();
protocolManager.close(); else
return; // Plugin reloaders!
if (unhookTask != null)
unhookTask.close();
protocolManager = null; protocolManager = null;
statistisc = null; statistisc = null;
reporter = null; reporter = null;
// Leaky ClassLoader begone! // Leaky ClassLoader begone!
CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter); if (updater == null || updater.getResult() != UpdateResult.SUCCESS) {
cleanup.resetAll(); CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter);
cleanup.resetAll();
}
} }
// Get the Bukkit logger first, before we try to create our own // Get the Bukkit logger first, before we try to create our own

View File

@ -19,15 +19,21 @@ package com.comphenix.protocol.concurrency;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.google.common.collect.MapMaker; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
/** /**
* A map that supports blocking on read operations. Null keys are not supported. * A map that supports blocking on read operations. Null keys are not supported.
* <p> * <p>
* Keys are stored as weak references, and will be automatically removed once they've all been dereferenced. * Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
* <p> * <p>
* @author Kristian * @author Kristian
* *
@ -35,19 +41,46 @@ import com.google.common.collect.MapMaker;
* @param <TValue> - type of the value. * @param <TValue> - type of the value.
*/ */
public class BlockingHashMap<TKey, TValue> { public class BlockingHashMap<TKey, TValue> {
// Map of values // Map of values
private final Cache<TKey, TValue> backingCache;
private final ConcurrentMap<TKey, TValue> backingMap; private final ConcurrentMap<TKey, TValue> backingMap;
// Map of locked objects // Map of locked objects
private final ConcurrentMap<TKey, Object> locks; private final ConcurrentMap<TKey, Object> locks;
/**
* Retrieve a cache loader that will always throw an exception.
* @return An invalid cache loader.
*/
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
return new CacheLoader<TKey, TValue>() {
@Override
public TValue load(TKey key) throws Exception {
throw new IllegalStateException("Illegal use. Access the map directly instead.");
}
};
}
/** /**
* Initialize a new map. * Initialize a new map.
*/ */
public BlockingHashMap() { public BlockingHashMap() {
backingMap = new MapMaker().weakKeys().makeMap(); backingCache = CacheBuilder.newBuilder().weakValues().removalListener(
locks = new MapMaker().weakKeys().makeMap(); new RemovalListener<TKey, TValue>() {
@Override
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
// Clean up locks too
if (entry.getCause() != RemovalCause.REPLACED) {
locks.remove(entry.getKey());
}
}
}).build(
BlockingHashMap.<TKey, TValue>newInvalidCacheLoader()
);
backingMap = backingCache.asMap();
// Normal concurrent hash map
locks = new ConcurrentHashMap<TKey, Object>();
} }
/** /**
@ -94,34 +127,57 @@ public class BlockingHashMap<TKey, TValue> {
* @throws InterruptedException If the current thread got interrupted while waiting. * @throws InterruptedException If the current thread got interrupted while waiting.
*/ */
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException { public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
return get(key, timeout, unit, false);
}
/**
* Waits until a value has been associated with the given key, and then retrieves that value.
* <p>
* If timeout is zero, this method will return immediately if it can't find an socket injector.
*
* @param key - the key whose associated value is to be returned
* @param timeout - the amount of time to wait until an association has been made.
* @param unit - unit of timeout.
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
* @throws InterruptedException If the current thread got interrupted while waiting.
*/
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
if (key == null) if (key == null)
throw new IllegalArgumentException("key cannot be NULL."); throw new IllegalArgumentException("key cannot be NULL.");
if (unit == null) if (unit == null)
throw new IllegalArgumentException("Unit cannot be NULL."); throw new IllegalArgumentException("Unit cannot be NULL.");
if (timeout < 0)
throw new IllegalArgumentException("Timeout cannot be less than zero.");
TValue value = backingMap.get(key); TValue value = backingMap.get(key);
// Only lock if no value is available // Only lock if no value is available
if (value == null) { if (value == null && timeout > 0) {
final Object lock = getLock(key); final Object lock = getLock(key);
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout); final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
// Don't exceed the timeout // Don't exceed the timeout
synchronized (lock) { synchronized (lock) {
while (value == null) { while (value == null) {
long remainingTime = stopTimeNS - System.nanoTime(); try {
long remainingTime = stopTimeNS - System.nanoTime();
if (remainingTime > 0) {
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime); if (remainingTime > 0) {
value = backingMap.get(key); TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
} else { value = backingMap.get(key);
// Timeout elapsed } else {
break; // Timeout elapsed
break;
}
} catch (InterruptedException e) {
// This is fairly dangerous - but we might HAVE to block the thread
if (!ignoreInterrupted)
throw e;
} }
} }
} }
} }
return value; return value;
} }
@ -148,6 +204,29 @@ public class BlockingHashMap<TKey, TValue> {
} }
} }
/**
* If and only if a key is not present in the map will it be associated with the given value.
* @param key - the key to associate.
* @param value - the value to associate.
* @return The previous value this key has been associated with.
*/
public TValue putIfAbsent(TKey key, TValue value) {
if (value == null)
throw new IllegalArgumentException("This map doesn't support NULL values.");
final TValue previous = backingMap.putIfAbsent(key, value);
// No need to unlock readers if we haven't changed anything
if (previous == null) {
final Object lock = getLock(key);
synchronized (lock) {
lock.notifyAll();
}
}
return previous;
}
public int size() { public int size() {
return backingMap.size(); return backingMap.size();
} }

View File

@ -96,6 +96,9 @@ public class PacketContainer implements Serializable {
andThen(new Function<BuilderParameters, Cloner>() { andThen(new Function<BuilderParameters, Cloner>() {
@Override @Override
public Cloner apply(@Nullable BuilderParameters param) { public Cloner apply(@Nullable BuilderParameters param) {
if (param == null)
throw new IllegalArgumentException("Cannot be NULL.");
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{ return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
// Use a default writer with no concept of cloning // Use a default writer with no concept of cloning
writer = new ObjectWriter(); writer = new ObjectWriter();

View File

@ -56,6 +56,7 @@ import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.player.PlayerInjectorBuilder; import com.comphenix.protocol.injector.player.PlayerInjectorBuilder;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
@ -133,8 +134,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
private AsyncFilterManager asyncFilterManager; private AsyncFilterManager asyncFilterManager;
// Valid server and client packets // Valid server and client packets
private Set<Integer> serverPackets; private boolean knowsServerPackets;
private Set<Integer> clientPackets; private boolean knowsClientPackets;
// Ensure that we're not performing too may injections // Ensure that we're not performing too may injections
private AtomicInteger phaseLoginCount = new AtomicInteger(0); private AtomicInteger phaseLoginCount = new AtomicInteger(0);
@ -214,8 +215,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Attempt to load the list of server and client packets // Attempt to load the list of server and client packets
try { try {
this.serverPackets = PacketRegistry.getServerPackets(); knowsServerPackets = PacketRegistry.getServerPackets() != null;
this.clientPackets = PacketRegistry.getClientPackets(); knowsClientPackets = PacketRegistry.getClientPackets() != null;
} catch (FieldAccessException e) { } catch (FieldAccessException e) {
reporter.reportWarning(this, "Cannot load server and client packet list.", e); reporter.reportWarning(this, "Cannot load server and client packet list.", e);
} }
@ -225,6 +226,13 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
} }
} }
/**
* Initiate logic that is performed after the world has loaded.
*/
public void postWorldLoaded() {
playerInjection.postWorldLoaded();
}
@Override @Override
public AsynchronousManager getAsynchronousManager() { public AsynchronousManager getAsynchronousManager() {
return asyncFilterManager; return asyncFilterManager;
@ -275,15 +283,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Make sure this is possible // Make sure this is possible
playerInjection.checkListener(listener); playerInjection.checkListener(listener);
} }
if (hasSending)
incrementPhases(sending.getGamePhase());
// Handle receivers after senders
if (hasReceiving) { if (hasReceiving) {
verifyWhitelist(listener, receiving); verifyWhitelist(listener, receiving);
recievedListeners.addListener(listener, receiving); recievedListeners.addListener(listener, receiving);
enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
} }
// Increment phases too
if (hasSending)
incrementPhases(sending.getGamePhase());
if (hasReceiving) if (hasReceiving)
incrementPhases(receiving.getGamePhase()); incrementPhases(receiving.getGamePhase());
@ -466,7 +474,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
for (int packetID : packets) { for (int packetID : packets) {
// Only register server packets that are actually supported by Minecraft // Only register server packets that are actually supported by Minecraft
if (side.isForServer()) { if (side.isForServer()) {
if (serverPackets != null && serverPackets.contains(packetID)) // Note that we may update the packet list here
if (!knowsServerPackets || PacketRegistry.getServerPackets().contains(packetID))
playerInjection.addPacketHandler(packetID); playerInjection.addPacketHandler(packetID);
else else
reporter.reportWarning(this, String.format( reporter.reportWarning(this, String.format(
@ -477,7 +486,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// As above, only for client packets // As above, only for client packets
if (side.isForClient() && packetInjector != null) { if (side.isForClient() && packetInjector != null) {
if (clientPackets != null && clientPackets.contains(packetID)) if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID))
packetInjector.addPacketHandler(packetID); packetInjector.addPacketHandler(packetID);
else else
reporter.reportWarning(this, String.format( reporter.reportWarning(this, String.format(
@ -612,7 +621,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
*/ */
public void initializePlayers(Player[] players) { public void initializePlayers(Player[] players) {
for (Player player : players) for (Player player : players)
playerInjection.injectPlayer(player); playerInjection.injectPlayer(player, ConflictStrategy.OVERRIDE);
} }
/** /**
@ -672,7 +681,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
private void onPlayerJoin(PlayerJoinEvent event) { private void onPlayerJoin(PlayerJoinEvent event) {
try { try {
// This call will be ignored if no listeners are registered // This call will be ignored if no listeners are registered
playerInjection.injectPlayer(event.getPlayer()); playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE);
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event); reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
} }

View File

@ -47,8 +47,12 @@ public class PacketRegistry {
private static Map<Class, Integer> packetToID; private static Map<Class, Integer> packetToID;
// Whether or not certain packets are sent by the client or the server // Whether or not certain packets are sent by the client or the server
private static Set<Integer> serverPackets; private static ImmutableSet<Integer> serverPackets;
private static Set<Integer> clientPackets; private static ImmutableSet<Integer> clientPackets;
// The underlying sets
private static Set<Integer> serverPacketsRef;
private static Set<Integer> clientPacketsRef;
// New proxy values // New proxy values
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>(); private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
@ -120,21 +124,21 @@ public class PacketRegistry {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static void initializeSets() throws FieldAccessException { private static void initializeSets() throws FieldAccessException {
if (serverPackets == null || clientPackets == null) { if (serverPacketsRef == null || clientPacketsRef == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class); List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try { try {
if (sets.size() > 1) { if (sets.size() > 1) {
serverPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true); serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true); clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Impossible // Impossible
if (serverPackets == null || clientPackets == null) if (serverPacketsRef == null || clientPacketsRef == null)
throw new FieldAccessException("Packet sets are in an illegal state."); throw new FieldAccessException("Packet sets are in an illegal state.");
// NEVER allow callers to modify the underlying sets // NEVER allow callers to modify the underlying sets
serverPackets = ImmutableSet.copyOf(serverPackets); serverPackets = ImmutableSet.copyOf(serverPacketsRef);
clientPackets = ImmutableSet.copyOf(clientPackets); clientPackets = ImmutableSet.copyOf(clientPacketsRef);
} else { } else {
throw new FieldAccessException("Cannot retrieve packet client/server sets."); throw new FieldAccessException("Cannot retrieve packet client/server sets.");
@ -143,6 +147,13 @@ public class PacketRegistry {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access field.", e); throw new FieldAccessException("Cannot access field.", e);
} }
} else {
// Copy over again if it has changed
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
} }
} }

View File

@ -23,13 +23,15 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.NoOp;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
@ -37,6 +39,8 @@ import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
/** /**
@ -45,7 +49,15 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian * @author Kristian
*/ */
class ProxyPacketInjector implements PacketInjector { class ProxyPacketInjector implements PacketInjector {
/**
* Matches the readPacketData(DataInputStream) method in Packet.
*/
private static FuzzyMethodContract readPacket = FuzzyMethodContract.newBuilder().
returnTypeVoid().
parameterExactType(DataInputStream.class).
parameterCount(1).
build();
// The "put" method that associates a packet ID with a packet class // The "put" method that associates a packet ID with a packet class
private static Method putMethod; private static Method putMethod;
private static Object intHashMap; private static Object intHashMap;
@ -59,11 +71,11 @@ class ProxyPacketInjector implements PacketInjector {
// Allows us to determine the sender // Allows us to determine the sender
private PlayerInjectionHandler playerInjection; private PlayerInjectionHandler playerInjection;
// Allows us to look up read packet injectors
private Map<Integer, ReadPacketModifier> readModifier;
// Class loader // Class loader
private ClassLoader classLoader; private ClassLoader classLoader;
// Share callback filter
private CallbackFilter filter;
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager, public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
@ -72,7 +84,6 @@ class ProxyPacketInjector implements PacketInjector {
this.manager = manager; this.manager = manager;
this.playerInjection = playerInjection; this.playerInjection = playerInjection;
this.reporter = reporter; this.reporter = reporter;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize(); initialize();
} }
@ -83,11 +94,9 @@ class ProxyPacketInjector implements PacketInjector {
*/ */
@Override @Override
public void undoCancel(Integer id, Object packet) { public void undoCancel(Integer id, Object packet) {
ReadPacketModifier modifier = readModifier.get(id);
// See if this packet has been cancelled before // See if this packet has been cancelled before
if (modifier != null && modifier.hasCancelled(packet)) { if (ReadPacketModifier.hasCancelled(packet)) {
modifier.removeOverride(packet); ReadPacketModifier.removeOverride(packet);
} }
} }
@ -131,22 +140,38 @@ class ProxyPacketInjector implements PacketInjector {
throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version."); throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version.");
} }
// Check for previous injections // Check for previous injections
if (!MinecraftReflection.isMinecraftClass(old)) { if (Factory.class.isAssignableFrom(old)) {
throw new IllegalStateException("Packet " + packetID + " has already been injected."); throw new IllegalStateException("Packet " + packetID + " has already been injected.");
} }
if (filter == null) {
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (method.getDeclaringClass().equals(Object.class))
return 0;
else if (readPacket.isMatch(MethodInfo.fromMethod(method), null))
return 1;
else
return 2;
}
};
}
// Subclass the specific packet class // Subclass the specific packet class
ex.setSuperclass(old); ex.setSuperclass(old);
ex.setCallbackType(ReadPacketModifier.class); ex.setCallbackFilter(filter);
ex.setCallbackTypes(new Class<?>[] { NoOp.class, ReadPacketModifier.class, ReadPacketModifier.class });
ex.setClassLoader(classLoader); ex.setClassLoader(classLoader);
Class proxy = ex.createClass(); Class proxy = ex.createClass();
// Create the proxy handler // Create the proxy handlers
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter); ReadPacketModifier modifierReadPacket = new ReadPacketModifier(packetID, this, reporter, true);
readModifier.put(packetID, modifier); ReadPacketModifier modifierRest = new ReadPacketModifier(packetID, this, reporter, false);
// Add a static reference // Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier }); Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
try { try {
// Override values // Override values
@ -182,7 +207,6 @@ class ProxyPacketInjector implements PacketInjector {
putMethod.invoke(intHashMap, packetID, old); putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID); previous.remove(packetID);
readModifier.remove(packetID);
registry.remove(proxy); registry.remove(proxy);
overwritten.remove(packetID); overwritten.remove(packetID);
return true; return true;
@ -211,18 +235,22 @@ class ProxyPacketInjector implements PacketInjector {
public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
try { try {
Player client = playerInjection.getPlayerByConnection(input); Player client = playerInjection.getPlayerByConnection(input);
// Never invoke a event if we don't know where it's from // Never invoke a event if we don't know where it's from
if (client != null) if (client != null) {
return packetRecieved(packet, client); return packetRecieved(packet, client);
else } else {
// Hack #2 - Caused by our server socket injector
if (packet.getID() != Packets.Client.GET_INFO)
System.out.println("[ProtocolLib] Unknown origin " + input + " for packet " + packet.getID());
return null; return null;
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
// We will ignore this - it occurs when a player disconnects // We will ignore this - it occurs when a player disconnects
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input); //reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
return null; return null;
} }
} }
/** /**
@ -253,12 +281,4 @@ class ProxyPacketInjector implements PacketInjector {
overwritten.clear(); overwritten.clear();
previous.clear(); previous.clear();
} }
/**
* Inform the current PlayerInjector that it should update the DataInputStream next.
* @param player - the player to update.
*/
public void scheduleDataInputRefresh(Player player) {
playerInjection.scheduleDataInputRefresh(player);
}
} }

View File

@ -19,24 +19,17 @@ package com.comphenix.protocol.injector.packet;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.MapMaker;
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.MethodProxy;
class ReadPacketModifier implements MethodInterceptor { class ReadPacketModifier implements MethodInterceptor {
@SuppressWarnings("rawtypes")
private static Class[] parameters = { DataInputStream.class };
// A cancel marker // A cancel marker
private static final Object CANCEL_MARKER = new Object(); private static final Object CANCEL_MARKER = new Object();
@ -47,20 +40,24 @@ class ReadPacketModifier implements MethodInterceptor {
// Report errors // Report errors
private ErrorReporter reporter; private ErrorReporter reporter;
// Whether or not a packet has been cancelled // If this is a read packet data method
private static Map<Object, Object> override = Collections.synchronizedMap(new WeakHashMap<Object, Object>()); private boolean isReadPacketDataMethod;
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter) { // Whether or not a packet has been cancelled
private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap();
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
this.packetID = packetID; this.packetID = packetID;
this.packetInjector = packetInjector; this.packetInjector = packetInjector;
this.reporter = reporter; this.reporter = reporter;
this.isReadPacketDataMethod = isReadPacketDataMethod;
} }
/** /**
* Remove any packet overrides. * Remove any packet overrides.
* @param packet - the packet to rever * @param packet - the packet to rever
*/ */
public void removeOverride(Object packet) { public static void removeOverride(Object packet) {
override.remove(packet); override.remove(packet);
} }
@ -69,7 +66,7 @@ class ReadPacketModifier implements MethodInterceptor {
* @param packet - the given packet. * @param packet - the given packet.
* @return Overriden object. * @return Overriden object.
*/ */
public Object getOverride(Object packet) { public static Object getOverride(Object packet) {
return override.get(packet); return override.get(packet);
} }
@ -78,23 +75,15 @@ class ReadPacketModifier implements MethodInterceptor {
* @param packet - the packet to check. * @param packet - the packet to check.
* @return TRUE if it has been cancelled, FALSE otherwise. * @return TRUE if it has been cancelled, FALSE otherwise.
*/ */
public boolean hasCancelled(Object packet) { public static boolean hasCancelled(Object packet) {
return getOverride(packet) == CANCEL_MARKER; return getOverride(packet) == CANCEL_MARKER;
} }
@Override @Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object returnValue = null;
String methodName = method.getName();
// We always pass these down (otherwise, we'll end up with a infinite loop)
if (methodName.equals("hashCode") || methodName.equals("equals") || methodName.equals("toString")) {
return proxy.invokeSuper(thisObj, args);
}
// Atomic retrieval // Atomic retrieval
Object overridenObject = override.get(thisObj); Object overridenObject = override.get(thisObj);
Object returnValue = null;
if (overridenObject != null) { if (overridenObject != null) {
// This packet has been cancelled // This packet has been cancelled
@ -112,9 +101,7 @@ class ReadPacketModifier implements MethodInterceptor {
} }
// Is this a readPacketData method? // Is this a readPacketData method?
if (returnValue == null && if (isReadPacketDataMethod) {
Arrays.equals(method.getParameterTypes(), parameters)) {
try { try {
// We need this in order to get the correct player // We need this in order to get the correct player
DataInputStream input = (DataInputStream) args[0]; DataInputStream input = (DataInputStream) args[0];
@ -132,18 +119,12 @@ class ReadPacketModifier implements MethodInterceptor {
} else if (!objectEquals(thisObj, result)) { } else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result); override.put(thisObj, result);
} }
// Update DataInputStream next time
if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) {
packetInjector.scheduleDataInputRefresh(event.getPlayer());
}
} }
} catch (Throwable e) { } catch (Throwable e) {
// Minecraft cannot handle this error // Minecraft cannot handle this error
reporter.reportDetailed(this, "Cannot handle clienet packet.", e, args[0]); reporter.reportDetailed(this, "Cannot handle client packet.", e, args[0]);
} }
} }
return returnValue; return returnValue;
} }

View File

@ -27,6 +27,7 @@ import net.sf.cglib.proxy.Factory;
import org.bukkit.Server; import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.ObjectWriter;
@ -53,6 +54,9 @@ class InjectedServerConnection {
// Used to inject net handlers // Used to inject net handlers
private NetLoginInjector netLoginInjector; private NetLoginInjector netLoginInjector;
// Inject server connections
private AbstractInputStreamLookup socketInjector;
private Server server; private Server server;
private ErrorReporter reporter; private ErrorReporter reporter;
private boolean hasAttempted; private boolean hasAttempted;
@ -60,11 +64,12 @@ class InjectedServerConnection {
private Object minecraftServer = null; private Object minecraftServer = null;
public InjectedServerConnection(ErrorReporter reporter, Server server, NetLoginInjector netLoginInjector) { public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>(); this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>(); this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.reporter = reporter; this.reporter = reporter;
this.server = server; this.server = server;
this.socketInjector = socketInjector;
this.netLoginInjector = netLoginInjector; this.netLoginInjector = netLoginInjector;
} }
@ -126,6 +131,9 @@ class InjectedServerConnection {
return; return;
} }
// Inject the server socket too
injectServerSocket(listenerThread);
// Just inject every list field we can get // Just inject every list field we can get
injectEveryListField(listenerThread, 1); injectEveryListField(listenerThread, 1);
hasSuccess = true; hasSuccess = true;
@ -147,7 +155,8 @@ class InjectedServerConnection {
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("netServerHandlerList", List.class); getFieldByType("netServerHandlerList", List.class);
if (dedicatedThreadField == null) { if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(Thread.class); List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
getFieldListByType(Thread.class);
// Verify the field count // Verify the field count
if (matches.size() != 1) if (matches.size() != 1)
@ -158,8 +167,13 @@ class InjectedServerConnection {
// Next, try to get the dedicated thread // Next, try to get the dedicated thread
try { try {
if (dedicatedThreadField != null) if (dedicatedThreadField != null) {
injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1); Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
// Inject server socket and NetServerHandlers.
injectServerSocket(dedicatedThread);
injectEveryListField(dedicatedThread, 1);
}
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e); reporter.reportWarning(this, "Unable to retrieve net handler thread.", e);
} }
@ -168,6 +182,10 @@ class InjectedServerConnection {
hasSuccess = true; hasSuccess = true;
} }
private void injectServerSocket(Object container) {
socketInjector.inject(container);
}
/** /**
* Automatically inject into every List-compatible public or private field of the given object. * Automatically inject into every List-compatible public or private field of the given object.
* @param container - container object with the fields to inject. * @param container - container object with the fields to inject.

View File

@ -24,7 +24,8 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -34,23 +35,22 @@ import com.google.common.collect.Maps;
* @author Kristian * @author Kristian
*/ */
class NetLoginInjector { class NetLoginInjector {
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap(); private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook // Handles every hook
private ProxyPlayerInjectionHandler injectionHandler; private ProxyPlayerInjectionHandler injectionHandler;
// Create temporary players
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
// The current error reporter
private ErrorReporter reporter;
private Server server; private Server server;
// The current error rerporter public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
private ErrorReporter reporter;
// Used to create fake players
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, Server server) {
this.reporter = reporter; this.reporter = reporter;
this.injectionHandler = injectionHandler;
this.server = server; this.server = server;
this.injectionHandler = injectionHandler;
} }
/** /**
@ -64,16 +64,19 @@ class NetLoginInjector {
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting; return inserting;
Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server); Player temporary = playerFactory.createTemporaryPlayer(server);
PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN); // Note that we bail out if there's an existing player injector
injector.updateOnLogin = true; PlayerInjector injector = injectionHandler.injectPlayer(
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
// Associate the injector too if (injector != null) {
InjectContainer container = (InjectContainer) fakePlayer; // Update injector as well
container.setInjector(injector); TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
injector.updateOnLogin = true;
// Save the login
injectedLogins.putIfAbsent(inserting, injector); // Save the login
injectedLogins.putIfAbsent(inserting, injector);
}
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting; return inserting;
@ -81,7 +84,7 @@ class NetLoginInjector {
} catch (Throwable e) { } catch (Throwable e) {
// Minecraft can't handle this, so we'll deal with it here // Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this, "Unable to hook " + reporter.reportDetailed(this, "Unable to hook " +
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting); MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting, injectionHandler);
return inserting; return inserting;
} }
} }
@ -108,15 +111,13 @@ class NetLoginInjector {
// Hack to clean up other references // Hack to clean up other references
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager()); newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
injectionHandler.uninjectPlayer(player);
// Update NetworkManager // Update NetworkManager
if (newInjector == null) { if (newInjector != null) {
injectionHandler.uninjectPlayer(player); if (injected instanceof NetworkObjectInjector) {
} else {
injectionHandler.uninjectPlayer(player, false);
if (injected instanceof NetworkObjectInjector)
newInjector.setNetworkManager(injected.getNetworkManager(), true); newInjector.setNetworkManager(injected.getNetworkManager(), true);
}
} }
} catch (Throwable e) { } catch (Throwable e) {

View File

@ -39,7 +39,7 @@ import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
/** /**
* Injection method that overrides the NetworkHandler itself, and its queue-method. * Injection method that overrides the NetworkHandler itself, and its queue-method.
@ -54,7 +54,7 @@ public class NetworkObjectInjector extends PlayerInjector {
private ClassLoader classLoader; private ClassLoader classLoader;
// Shared callback filter - avoid creating a new class every time // Shared callback filter - avoid creating a new class every time
private static CallbackFilter callbackFilter; private volatile static CallbackFilter callbackFilter;
// Temporary player factory // Temporary player factory
private static volatile TemporaryPlayerFactory tempPlayerFactory; private static volatile TemporaryPlayerFactory tempPlayerFactory;
@ -91,10 +91,8 @@ public class NetworkObjectInjector extends PlayerInjector {
if (tempPlayerFactory == null) if (tempPlayerFactory == null)
tempPlayerFactory = new TemporaryPlayerFactory(); tempPlayerFactory = new TemporaryPlayerFactory();
// Create and associate this fake player with this network injector // Create and associate the fake player with this network injector
Player player = tempPlayerFactory.createTemporaryPlayer(server); return tempPlayerFactory.createTemporaryPlayer(server, this);
((InjectContainer) player).setInjector(this);
return player;
} }
@Override @Override

View File

@ -20,15 +20,7 @@ package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import net.sf.cglib.proxy.*;
import java.util.Map;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -44,8 +36,8 @@ import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator; import com.comphenix.protocol.reflect.instances.ExistingGenerator;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/** /**
* Represents a player hook into the NetServerHandler class. * Represents a player hook into the NetServerHandler class.
@ -57,7 +49,6 @@ class NetworkServerInjector extends PlayerInjector {
private volatile static CallbackFilter callbackFilter; private volatile static CallbackFilter callbackFilter;
private volatile static Field disconnectField; private volatile static Field disconnectField;
private volatile static Method sendPacketMethod;
private InjectedServerConnection serverInjection; private InjectedServerConnection serverInjection;
// Determine if we're listening // Determine if we're listening
@ -88,67 +79,6 @@ class NetworkServerInjector extends PlayerInjector {
return sendingFilters.contains(packetID); return sendingFilters.contains(packetID);
} }
@Override
public void initialize(Object injectionSource) throws IllegalAccessException {
super.initialize(injectionSource);
// Get the send packet method!
if (hasInitialized) {
if (sendPacketMethod == null) {
try {
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
} catch (IllegalArgumentException e) {
Map<String, Method> netServer = getMethodList(
MinecraftReflection.getNetServerHandlerClass(), MinecraftReflection.getPacketClass());
Map<String, Method> netHandler = getMethodList(
MinecraftReflection.getNetHandlerClass(), MinecraftReflection.getPacketClass());
// Remove every method in net handler from net server
for (String methodName : netHandler.keySet()) {
netServer.remove(methodName);
}
// The remainder is the send packet method
if (netServer.size() == 1) {
Method[] methods = netServer.values().toArray(new Method[0]);
sendPacketMethod = methods[0];
} else {
throw new IllegalArgumentException("Unable to find the sendPacket method in NetServerHandler/PlayerConnection.");
}
}
}
}
}
/**
* Retrieve a method mapped list of every method with the given signature.
* @param source - class source.
* @param params - parameters.
* @return Method mapped list.
*/
private Map<String, Method> getMethodList(Class<?> source, Class<?>... params) {
return getMappedMethods(
FuzzyReflection.fromClass(source, true).
getMethodListByParameters(Void.TYPE, params)
);
}
/**
* Retrieve every method as a map over names.
* <p>
* Note that overloaded methods will only occur once in the resulting map.
* @param methods - every method.
* @return A map over every given method.
*/
private Map<String, Method> getMappedMethods(List<Method> methods) {
Map<String, Method> map = Maps.newHashMap();
for (Method method : methods) {
map.put(method.getName(), method);
}
return map;
}
@Override @Override
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue(); Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
@ -156,7 +86,7 @@ class NetworkServerInjector extends PlayerInjector {
if (serverDelegate != null) { if (serverDelegate != null) {
try { try {
// Note that invocation target exception is a wrapper for a checked exception // Note that invocation target exception is a wrapper for a checked exception
sendPacketMethod.invoke(serverDelegate, packet); MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw e; throw e;
@ -180,6 +110,7 @@ class NetworkServerInjector extends PlayerInjector {
return; return;
if (!tryInjectManager()) { if (!tryInjectManager()) {
Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
// Try to override the proxied object // Try to override the proxied object
if (proxyServerField != null) { if (proxyServerField != null) {
@ -188,6 +119,8 @@ class NetworkServerInjector extends PlayerInjector {
if (serverHandler == null) if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL."); throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
else
serverHandlerClass = serverHandler.getClass();
// Try again // Try again
if (tryInjectManager()) { if (tryInjectManager()) {
@ -198,7 +131,7 @@ class NetworkServerInjector extends PlayerInjector {
throw new RuntimeException( throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the " "Cannot hook player: Unable to find a valid constructor for the "
+ MinecraftReflection.getNetServerHandlerClass().getName() + " object."); + serverHandlerClass.getName() + " object.");
} }
} }
@ -226,14 +159,16 @@ class NetworkServerInjector extends PlayerInjector {
}; };
}; };
Callback noOpCallback = NoOp.INSTANCE; Callback noOpCallback = NoOp.INSTANCE;
// Share callback filter - that way, we avoid generating a new class for // Share callback filter - that way, we avoid generating a new class for
// every logged in player. // every logged in player.
if (callbackFilter == null) { if (callbackFilter == null) {
final Method sendPacket = MinecraftMethods.getSendPacketMethod();
callbackFilter = new CallbackFilter() { callbackFilter = new CallbackFilter() {
@Override @Override
public int accept(Method method) { public int accept(Method method) {
if (method.equals(sendPacketMethod)) if (method.equals(sendPacket))
return 0; return 0;
else else
return 1; return 1;

View File

@ -4,8 +4,6 @@ import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
@ -14,6 +12,23 @@ import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
public interface PlayerInjectionHandler { public interface PlayerInjectionHandler {
/**
* How to handle a previously existing player injection.
*
* @author Kristian
*/
public enum ConflictStrategy {
/**
* Override it.
*/
OVERRIDE,
/**
* Immediately exit.
*/
BAIL_OUT;
}
/** /**
* Retrieves how the server packets are read. * Retrieves how the server packets are read.
* @return Injection method for reading server packets. * @return Injection method for reading server packets.
@ -61,23 +76,14 @@ public interface PlayerInjectionHandler {
public abstract Player getPlayerByConnection(DataInputStream inputStream) public abstract Player getPlayerByConnection(DataInputStream inputStream)
throws InterruptedException; throws InterruptedException;
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @param playerTimeout - the amount of time to wait for a result.
* @param unit - unit of playerTimeout.
* @return The player.
* @throws InterruptedException If the thread was interrupted during the wait.
*/
public abstract Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException;
/** /**
* Initialize a player hook, allowing us to read server packets. * Initialize a player hook, allowing us to read server packets.
* <p> * <p>
* This call will be ignored if there's no listener that can receive the given events. * This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook. * @param player - player to hook.
* @param strategy - how to handle injection conflicts.
*/ */
public abstract void injectPlayer(Player player); public abstract void injectPlayer(Player player, ConflictStrategy strategy);
/** /**
* Invoke special routines for handling disconnect before a player is uninjected. * Invoke special routines for handling disconnect before a player is uninjected.
@ -149,8 +155,7 @@ public interface PlayerInjectionHandler {
public abstract void close(); public abstract void close();
/** /**
* Inform the current PlayerInjector that it should update the DataInputStream next. * Perform any action that must be delayed until the world(s) has loaded.
* @param player - the player to update.
*/ */
public abstract void scheduleDataInputRefresh(Player player); public abstract void postWorldLoaded();
} }

View File

@ -24,7 +24,6 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -38,13 +37,14 @@ import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
abstract class PlayerInjector { abstract class PlayerInjector implements SocketInjector {
// Net login handler stuff // Net login handler stuff
private static Field netLoginNetworkField; private static Field netLoginNetworkField;
@ -60,6 +60,7 @@ abstract class PlayerInjector {
protected static Field networkManagerField; protected static Field networkManagerField;
protected static Field netHandlerField; protected static Field netHandlerField;
protected static Field socketField; protected static Field socketField;
protected static Field socketAddressField;
private static Field inputField; private static Field inputField;
private static Field entityPlayerField; private static Field entityPlayerField;
@ -87,8 +88,9 @@ abstract class PlayerInjector {
protected Object serverHandler; protected Object serverHandler;
protected Object netHandler; protected Object netHandler;
// Current socket // Current socket and address
protected Socket socket; protected Socket socket;
protected SocketAddress socketAddress;
// The packet manager and filters // The packet manager and filters
protected ListenerInvoker invoker; protected ListenerInvoker invoker;
@ -99,9 +101,6 @@ abstract class PlayerInjector {
// Handle errors // Handle errors
protected ErrorReporter reporter; protected ErrorReporter reporter;
// Scheduled action on the next packet event
protected Runnable scheduledAction;
// Whether or not the injector has been cleaned // Whether or not the injector has been cleaned
private boolean clean; private boolean clean;
@ -249,10 +248,12 @@ abstract class PlayerInjector {
* @return The associated socket. * @return The associated socket.
* @throws IllegalAccessException If we're unable to read the socket field. * @throws IllegalAccessException If we're unable to read the socket field.
*/ */
@Override
public Socket getSocket() throws IllegalAccessException { public Socket getSocket() throws IllegalAccessException {
try { try {
if (socketField == null) if (socketField == null)
socketField = FuzzyReflection.fromObject(networkManager, true).getFieldListByType(Socket.class).get(0); socketField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(Socket.class).get(0);
if (socket == null) if (socket == null)
socket = (Socket) FieldUtils.readField(socketField, networkManager, true); socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
return socket; return socket;
@ -263,18 +264,23 @@ abstract class PlayerInjector {
} }
/** /**
* Retrieve the associated address of this player. * Retrieve the associated remote address of a player.
* @return The associated address. * @return The associated remote address..
* @throws IllegalAccessException If we're unable to read the socket field. * @throws IllegalAccessException If we're unable to read the socket address field.
*/ */
@Override
public SocketAddress getAddress() throws IllegalAccessException { public SocketAddress getAddress() throws IllegalAccessException {
Socket socket = getSocket(); try {
if (socketAddressField == null)
// Guard against NULL socketAddressField = FuzzyReflection.fromObject(networkManager, true).
if (socket != null) getFieldListByType(SocketAddress.class).get(0);
return socket.getRemoteSocketAddress(); if (socketAddress == null)
else socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
return null; return socketAddress;
} catch (IndexOutOfBoundsException e) {
throw new IllegalAccessException("Unable to read the socket address field.");
}
} }
/** /**
@ -282,6 +288,7 @@ abstract class PlayerInjector {
* @param message - the message to display. * @param message - the message to display.
* @throws InvocationTargetException If disconnection failed. * @throws InvocationTargetException If disconnection failed.
*/ */
@Override
public void disconnect(String message) throws InvocationTargetException { public void disconnect(String message) throws InvocationTargetException {
// Get a non-null handler // Get a non-null handler
boolean usingNetServer = serverHandler != null; boolean usingNetServer = serverHandler != null;
@ -450,6 +457,7 @@ abstract class PlayerInjector {
* @param filtered - whether or not the packet will be filtered by our listeners. * @param filtered - whether or not the packet will be filtered by our listeners.
* @param InvocationTargetException If an error occured when sending the packet. * @param InvocationTargetException If an error occured when sending the packet.
*/ */
@Override
public abstract void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException; public abstract void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException;
/** /**
@ -517,12 +525,7 @@ abstract class PlayerInjector {
Integer id = invoker.getPacketID(packet); Integer id = invoker.getPacketID(packet);
Player currentPlayer = player; Player currentPlayer = player;
// Hack #1: Handle a single scheduled action // Hack #1
if (scheduledAction != null) {
scheduledAction.run();
scheduledAction = null;
}
// Hack #2
if (updateOnLogin) { if (updateOnLogin) {
if (id == Packets.Server.LOGIN) { if (id == Packets.Server.LOGIN) {
try { try {
@ -593,17 +596,10 @@ abstract class PlayerInjector {
} }
} }
/**
* Schedule an action to occur on the next sent packet.
* @param action - action to execute.
*/
public void scheduleAction(Runnable action) {
scheduledAction = action;
}
/** /**
* Retrieve the hooked player. * Retrieve the hooked player.
*/ */
@Override
public Player getPlayer() { public Player getPlayer() {
return player; return player;
} }
@ -630,6 +626,7 @@ abstract class PlayerInjector {
* Retrieve the hooked player object OR the more up-to-date player instance. * Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance. * @return The hooked player, or a more up-to-date instance.
*/ */
@Override
public Player getUpdatedPlayer() { public Player getUpdatedPlayer() {
if (updatedPlayer != null) if (updatedPlayer != null)
return updatedPlayer; return updatedPlayer;
@ -637,6 +634,11 @@ abstract class PlayerInjector {
return player; return player;
} }
@Override
public void transferState(SocketInjector delegate) {
// Do nothing
}
/** /**
* Set the real Bukkit player that we will use. * Set the real Bukkit player that we will use.
* @param updatedPlayer - the real Bukkit player. * @param updatedPlayer - the real Bukkit player.

View File

@ -20,12 +20,13 @@ package com.comphenix.protocol.injector.player;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -40,8 +41,14 @@ import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
/** /**
@ -50,26 +57,26 @@ import com.google.common.collect.Maps;
* @author Kristian * @author Kristian
*/ */
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
/**
* The maximum number of milliseconds to wait until a player can be looked up by connection.
*/
private static final long TIMEOUT_PLAYER_LOOKUP = 2000; // ms
// Server connection injection // Server connection injection
private InjectedServerConnection serverInjection; private InjectedServerConnection serverInjection;
// Server socket injection
private AbstractInputStreamLookup inputStreamLookup;
// NetLogin injector // NetLogin injector
private NetLoginInjector netLoginInjector; private NetLoginInjector netLoginInjector;
// The last successful player hook // The last successful player hook
private PlayerInjector lastSuccessfulHook; private PlayerInjector lastSuccessfulHook;
// Player injection // Dummy injection
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap(); private Cache<Player, PlayerInjector> dummyInjectors =
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap(); CacheBuilder.newBuilder().
expireAfterWrite(30, TimeUnit.SECONDS).
build(BlockingHashMap.<Player, PlayerInjector>newInvalidCacheLoader());
// Lookup player by connection // Player injection
private BlockingHashMap<DataInputStream, PlayerInjector> dataInputLookup = BlockingHashMap.create(); private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
// Player injection types // Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
@ -96,18 +103,34 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// Used to filter injection attempts // Used to filter injection attempts
private Predicate<GamePhase> injectionFilter; private Predicate<GamePhase> injectionFilter;
public ProxyPlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter, public ProxyPlayerInjectionHandler(
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) { ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
this.classLoader = classLoader; this.classLoader = classLoader;
this.reporter = reporter; this.reporter = reporter;
this.invoker = invoker; this.invoker = invoker;
this.injectionFilter = injectionFilter; this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners; this.packetListeners = packetListeners;
this.netLoginInjector = new NetLoginInjector(reporter, this, server);
this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector); this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
server(server).
reporter(reporter).
build();
// Create net login injectors and the server connection injector
this.netLoginInjector = new NetLoginInjector(reporter, server, this);
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
serverInjection.injectList(); serverInjection.injectList();
} }
@Override
public void postWorldLoaded() {
// This will actually create a socket and a seperate thread ...
if (inputStreamLookup != null) {
inputStreamLookup.postWorldLoaded();
}
}
/** /**
* Retrieves how the server packets are read. * Retrieves how the server packets are read.
@ -202,31 +225,16 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
/** /**
* Retrieve a player by its DataInput connection. * Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection. * @param inputStream - the associated DataInput connection.
* @return The player. * @return The player we found.
* @throws InterruptedException If the thread was interrupted during the wait.
*/ */
@Override @Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { public Player getPlayerByConnection(DataInputStream inputStream) {
return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS);
}
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @param playerTimeout - the amount of time to wait for a result.
* @param unit - unit of playerTimeout.
* @return The player.
* @throws InterruptedException If the thread was interrupted during the wait.
*/
@Override
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
// Wait until the connection owner has been established // Wait until the connection owner has been established
PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit); SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream);
if (injector != null) { if (injector != null) {
return injector.getPlayer(); return injector.getPlayer();
} else { } else {
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
return null; return null;
} }
} }
@ -245,12 +253,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
* <p> * <p>
* This call will be ignored if there's no listener that can receive the given events. * This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook. * @param player - player to hook.
* @param strategy - how to handle previous player injections.
*/ */
@Override @Override
public void injectPlayer(Player player) { public void injectPlayer(Player player, ConflictStrategy strategy) {
// Inject using the player instance itself // Inject using the player instance itself
if (isInjectionNecessary(GamePhase.PLAYING)) { if (isInjectionNecessary(GamePhase.PLAYING)) {
injectPlayer(player, player, GamePhase.PLAYING); injectPlayer(player, player, strategy, GamePhase.PLAYING);
} }
} }
@ -273,16 +282,22 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
* @param phase - the current game phase. * @param phase - the current game phase.
* @return The resulting player injector, or NULL if the injection failed. * @return The resulting player injector, or NULL if the injection failed.
*/ */
PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) { PlayerInjector injectPlayer(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
if (player == null)
throw new IllegalArgumentException("Player cannot be NULL.");
if (injectionPoint == null)
throw new IllegalArgumentException("injectionPoint cannot be NULL.");
if (phase == null)
throw new IllegalArgumentException("phase cannot be NULL.");
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method. // Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
synchronized (player) { synchronized (player) {
return injectPlayerInternal(player, injectionPoint, phase); return injectPlayerInternal(player, injectionPoint, stategy, phase);
} }
} }
// Unsafe variant of the above // Unsafe variant of the above
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, GamePhase phase) { private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
PlayerInjector injector = playerInjection.get(player); PlayerInjector injector = playerInjection.get(player);
PlayerInjectHooks tempHook = getPlayerHook(phase); PlayerInjectHooks tempHook = getPlayerHook(phase);
PlayerInjectHooks permanentHook = tempHook; PlayerInjectHooks permanentHook = tempHook;
@ -293,7 +308,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true; boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
// Don't inject if the class has closed // Don't inject if the class has closed
if (!hasClosed && player != null && (tempHook != getInjectorType(injector) || invalidInjector)) { if (!hasClosed && (tempHook != getInjectorType(injector) || invalidInjector)) {
while (tempHook != PlayerInjectHooks.NONE) { while (tempHook != PlayerInjectHooks.NONE) {
// Whether or not the current hook method failed completely // Whether or not the current hook method failed completely
boolean hookFailed = false; boolean hookFailed = false;
@ -308,25 +323,24 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector.canInject(phase)) { if (injector.canInject(phase)) {
injector.initialize(injectionPoint); injector.initialize(injectionPoint);
DataInputStream inputStream = injector.getInputStream(false); // Get socket and socket injector
SocketAddress address = injector.getAddress();
SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
Socket socket = injector.getSocket();
SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
// Guard against NPE here too
PlayerInjector previous = address != null ? addressLookup.get(address) : null;
// Close any previously associated hooks before we proceed // Close any previously associated hooks before we proceed
if (previous != null) { if (previous != null && !(player instanceof Factory)) {
uninjectPlayer(previous.getPlayer(), false, true); switch (stategy) {
case OVERRIDE:
uninjectPlayer(previous.getPlayer(), true);
break;
case BAIL_OUT:
return null;
}
} }
injector.injectManager(); injector.injectManager();
if (inputStream != null) // Save injector
dataInputLookup.put(inputStream, injector); inputStreamLookup.setSocketInjector(address, injector);
if (address != null)
addressLookup.put(address, injector);
break; break;
} }
@ -373,7 +387,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
return injector; return injector;
} }
private void cleanupHook(PlayerInjector injector) { private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible // Clean up as much as possible
try { try {
@ -404,33 +418,21 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
*/ */
@Override @Override
public boolean uninjectPlayer(Player player) { public boolean uninjectPlayer(Player player) {
return uninjectPlayer(player, true, false); return uninjectPlayer(player, false);
} }
/** /**
* Unregisters the given player. * Unregisters the given player.
* @param player - player to unregister. * @param player - player to unregister.
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
return uninjectPlayer(player, removeAuxiliary, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
* @param prepareNextHook - whether or not we need to fix any lingering hooks. * @param prepareNextHook - whether or not we need to fix any lingering hooks.
* @return TRUE if a player has been uninjected, FALSE otherwise. * @return TRUE if a player has been uninjected, FALSE otherwise.
*/ */
private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) { private boolean uninjectPlayer(Player player, boolean prepareNextHook) {
if (!hasClosed && player != null) { if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player); PlayerInjector injector = playerInjection.remove(player);
if (injector != null) { if (injector != null) {
InetSocketAddress address = player.getAddress();
injector.cleanupAll(); injector.cleanupAll();
// Remove the "hooked" network manager in our instance as well // Remove the "hooked" network manager in our instance as well
@ -446,12 +448,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} }
} }
// Clean up
if (removeAuxiliary) {
// Note that the dataInputLookup will clean itself
if (address != null)
addressLookup.remove(address);
}
return true; return true;
} }
} }
@ -471,11 +467,11 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
@Override @Override
public boolean uninjectPlayer(InetSocketAddress address) { public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) { if (!hasClosed && address != null) {
PlayerInjector injector = addressLookup.get(address); SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
// Clean up // Clean up
if (injector != null) if (injector != null)
uninjectPlayer(injector.getPlayer(), false, true); uninjectPlayer(injector.getPlayer(), true);
return true; return true;
} }
@ -491,7 +487,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
*/ */
@Override @Override
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
PlayerInjector injector = getInjector(reciever); SocketInjector injector = getInjector(reciever);
// Send the packet, or drop it completely // Send the packet, or drop it completely
if (injector != null) { if (injector != null) {
@ -513,7 +509,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
*/ */
@Override @Override
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player); PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up // Process the given packet, or simply give up
@ -536,28 +531,49 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector == null) { if (injector == null) {
// Try getting it from the player itself // Try getting it from the player itself
if (player instanceof InjectContainer) SocketAddress address = player.getAddress();
return ((InjectContainer) player).getInjector(); // Look that up without blocking
SocketInjector result = inputStreamLookup.peekSocketInjector(address);
// Ensure that it is non-null and a player injector
if (result instanceof PlayerInjector)
return (PlayerInjector) result;
else else
return searchAddressLookup(player); // Make a dummy injector them
return createDummyInjector(player);
} else { } else {
return injector; return injector;
} }
} }
/** /**
* Find an injector by looking through the address map. * Construct a simple dummy injector incase none has been constructed.
* @param player - player to find. * @param player - the CraftPlayer to construct for.
* @return The injector, or NULL if not found. * @return A dummy injector, or NULL if the given player is not a CraftPlayer.
*/ */
private PlayerInjector searchAddressLookup(Player player) { private PlayerInjector createDummyInjector(Player player) {
// See if we can find it anywhere if (!MinecraftReflection.getCraftPlayerClass().isAssignableFrom(player.getClass())) {
for (PlayerInjector injector : addressLookup.values()) { // No - this is not safe
if (player.equals(injector.getUpdatedPlayer())) { return null;
return injector; }
}
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
// This probably means the player has disconnected
if (dummyInjector.getSocket() == null) {
return null;
}
inputStreamLookup.setSocketInjector(dummyInjector.getAddress(), dummyInjector);
dummyInjectors.asMap().put(player, dummyInjector);
return dummyInjector;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access fields.", e);
} }
return null;
} }
/** /**
@ -640,35 +656,18 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} }
// Remove server handler // Remove server handler
if (inputStreamLookup != null)
inputStreamLookup.cleanupAll();
if (serverInjection != null) if (serverInjection != null)
serverInjection.cleanupAll(); serverInjection.cleanupAll();
if (netLoginInjector != null) if (netLoginInjector != null)
netLoginInjector.cleanupAll(); netLoginInjector.cleanupAll();
inputStreamLookup = null;
serverInjection = null; serverInjection = null;
netLoginInjector = null; netLoginInjector = null;
hasClosed = true; hasClosed = true;
playerInjection.clear(); playerInjection.clear();
addressLookup.clear();
invoker = null; invoker = null;
} }
/**
* Inform the current PlayerInjector that it should update the DataInputStream next.
* @param player - the player to update.
*/
@Override
public void scheduleDataInputRefresh(Player player) {
final PlayerInjector injector = getInjector(player);
// Update the DataInputStream
if (injector != null) {
injector.scheduleAction(new Runnable() {
@Override
public void run() {
dataInputLookup.put(injector.getInputStream(false), injector);
}
});
}
}
} }

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -15,7 +15,7 @@
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.server;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -36,26 +36,7 @@ import com.comphenix.protocol.reflect.FieldAccessException;
/** /**
* Create fake player instances that represents pre-authenticated clients. * Create fake player instances that represents pre-authenticated clients.
*/ */
class TemporaryPlayerFactory { public class TemporaryPlayerFactory {
/**
* Able to store a PlayerInjector.
* <p>
* A necessary hack.
* @author Kristian
*/
public static class InjectContainer {
private PlayerInjector injector;
public PlayerInjector getInjector() {
return injector;
}
public void setInjector(PlayerInjector injector) {
this.injector = injector;
}
}
// Helpful constructors // Helpful constructors
private final PacketConstructor chatPacket; private final PacketConstructor chatPacket;
@ -66,6 +47,27 @@ class TemporaryPlayerFactory {
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" }); chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
} }
/**
* Retrieve the injector from a given player if it contains one.
* @param player - the player that may contain a reference to a player injector.
* @return The referenced player injector, or NULL if none can be found.
*/
public static SocketInjector getInjectorFromPlayer(Player player) {
if (player instanceof InjectorContainer) {
return ((InjectorContainer) player).getInjector();
}
return null;
}
/**
* Set the player injector, if possible.
* @param player - the player to update.
* @param injector - the injector to store.
*/
public static void setInjectorInPlayer(Player player, SocketInjector injector) {
((InjectorContainer) player).setInjector(injector);
}
/** /**
* Construct a temporary player that supports a subset of every player command. * Construct a temporary player that supports a subset of every player command.
* <p> * <p>
@ -80,7 +82,7 @@ class TemporaryPlayerFactory {
* <li>kickPlayer(String)</li> * <li>kickPlayer(String)</li>
* </ul> * </ul>
* <p> * <p>
* Note that the player a player has not been assigned a name yet, and thus cannot be * Note that a temporary player has not yet been assigned a name, and thus cannot be
* uniquely identified. Use the address instead. * uniquely identified. Use the address instead.
* @param injector - the player injector used. * @param injector - the player injector used.
* @param server - the current server. * @param server - the current server.
@ -94,7 +96,7 @@ class TemporaryPlayerFactory {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName(); String methodName = method.getName();
PlayerInjector injector = ((InjectContainer) obj).getInjector(); SocketInjector injector = ((InjectorContainer) obj).getInjector();
if (injector == null) if (injector == null)
throw new IllegalStateException("Unable to find injector."); throw new IllegalStateException("Unable to find injector.");
@ -147,7 +149,7 @@ class TemporaryPlayerFactory {
public int accept(Method method) { public int accept(Method method) {
// Do not override the object method or the superclass methods // Do not override the object method or the superclass methods
if (method.getDeclaringClass().equals(Object.class) || if (method.getDeclaringClass().equals(Object.class) ||
method.getDeclaringClass().equals(InjectContainer.class)) method.getDeclaringClass().equals(InjectorContainer.class))
return 0; return 0;
else else
return 1; return 1;
@ -157,7 +159,7 @@ class TemporaryPlayerFactory {
// CGLib is amazing // CGLib is amazing
Enhancer ex = new Enhancer(); Enhancer ex = new Enhancer();
ex.setSuperclass(InjectContainer.class); ex.setSuperclass(InjectorContainer.class);
ex.setInterfaces(new Class[] { Player.class }); ex.setInterfaces(new Class[] { Player.class });
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation }); ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
ex.setCallbackFilter(callbackFilter); ex.setCallbackFilter(callbackFilter);
@ -165,6 +167,19 @@ class TemporaryPlayerFactory {
return (Player) ex.create(); return (Player) ex.create();
} }
/**
* Construct a temporary player with the given associated socket injector.
* @param server - the parent server.
* @param injector - the referenced socket injector.
* @return The temporary player.
*/
public Player createTemporaryPlayer(Server server, SocketInjector injector) {
Player temporary = createTemporaryPlayer(server);
((InjectorContainer) temporary).setInjector(injector);
return temporary;
}
/** /**
* Send a message to the given client. * Send a message to the given client.
* @param injector - the injector representing the client. * @param injector - the injector representing the client.
@ -173,7 +188,7 @@ class TemporaryPlayerFactory {
* @throws InvocationTargetException If the message couldn't be sent. * @throws InvocationTargetException If the message couldn't be sent.
* @throws FieldAccessException If we were unable to construct the message packet. * @throws FieldAccessException If we were unable to construct the message packet.
*/ */
private Object sendMessage(PlayerInjector injector, String message) throws InvocationTargetException, FieldAccessException { private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false); injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
return null; return null;
} }

View File

@ -4,8 +4,6 @@ import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
@ -49,12 +47,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void setPlayerHook(PlayerInjectHooks playerHook) { public void setPlayerHook(PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot."); throw new UnsupportedOperationException("This is not needed in Spigot.");
} }
@Override
public void scheduleDataInputRefresh(Player player) {
// Fine
}
@Override @Override
public void addPacketHandler(int packetID) { public void addPacketHandler(int packetID) {
sendingFilters.add(packetID); sendingFilters.add(packetID);
@ -86,7 +79,8 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
} }
@Override @Override
public void injectPlayer(Player player) { public void injectPlayer(Player player, ConflictStrategy strategy) {
// We don't care about strategy
injector.injectPlayer(player); injector.injectPlayer(player);
} }
@ -106,11 +100,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT; return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
} }
@Override
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override @Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot."); throw new UnsupportedOperationException("This is not needed in Spigot.");
@ -125,4 +114,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void checkListener(Set<PacketListener> listeners) { public void checkListener(Set<PacketListener> listeners) {
// Yes, really // Yes, really
} }
@Override
public void postWorldLoaded() {
// Do nothing
}
} }

View File

@ -349,7 +349,7 @@ public class SpigotPacketInjector implements SpigotPacketListener {
public Object packetQueued(Object networkManager, Object connection, Object packet) { public Object packetQueued(Object networkManager, Object connection, Object packet) {
Integer id = invoker.getPacketID(packet); Integer id = invoker.getPacketID(packet);
if (id != null & queuedFilters.contains(id)) { if (id != null && queuedFilters.contains(id)) {
// Check for ignored packets // Check for ignored packets
if (ignoredPackets.remove(packet)) { if (ignoredPackets.remove(packet)) {
return packet; return packet;
@ -427,7 +427,7 @@ public class SpigotPacketInjector implements SpigotPacketListener {
void uninjectPlayer(Player player) { void uninjectPlayer(Player player) {
final NetworkObjectInjector injector = getInjector(player); final NetworkObjectInjector injector = getInjector(player);
if (player != null) { if (player != null && injector != null) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@ -9,6 +9,7 @@ package com.comphenix.protocol.metrics;
* This class provides the means to safetly and easily update a plugin, or check to see if it is updated using dev.bukkit.org * This class provides the means to safetly and easily update a plugin, or check to see if it is updated using dev.bukkit.org
*/ */
import java.io.*; import java.io.*;
import java.net.ConnectException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
@ -259,10 +260,18 @@ public class Updater
logger.warning("The project slug added ('" + slug + "') is invalid, and does not exist on dev.bukkit.org"); logger.warning("The project slug added ('" + slug + "') is invalid, and does not exist on dev.bukkit.org");
result = Updater.UpdateResult.FAIL_BADSLUG; // Bad slug! Bad! result = Updater.UpdateResult.FAIL_BADSLUG; // Bad slug! Bad!
} }
if (url != null) if (url != null)
{ {
// Obtain the results of the project's file feed // Obtain the results of the project's file feed
readFeed(); try {
readFeed();
} catch (ConnectException ex) {
// Minimal warning - it's just a temporary problem
logger.warning("Update problem: " + ex.getMessage());
return UpdateResult.FAIL_DBO;
}
if(versionCheck(versionTitle)) if(versionCheck(versionTitle))
{ {
String fileLink = getFile(versionLink); String fileLink = getFile(versionLink);
@ -545,8 +554,9 @@ public class Updater
/** /**
* Part of RSS Reader by Vogella, modified by H31IX for use with Bukkit * Part of RSS Reader by Vogella, modified by H31IX for use with Bukkit
* @throws ConnectException Cannot connect to the server.
*/ */
private void readFeed() private void readFeed() throws ConnectException
{ {
try try
{ {
@ -598,15 +608,15 @@ public class Updater
/** /**
* Open the RSS feed * Open the RSS feed
* @throws ConnectException If we are unable to connect to the server.
*/ */
private InputStream read() private InputStream read() throws ConnectException
{ {
try try {
{
return url.openStream(); return url.openStream();
} } catch (ConnectException e) {
catch (IOException e) throw e;
{ } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }

View File

@ -24,11 +24,13 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/** /**
* Retrieves fields and methods by signature, not just name. * Retrieves fields and methods by signature, not just name.
@ -422,6 +424,22 @@ public class FuzzyReflection {
throw new IllegalArgumentException("Unable to find a method that matches " + matcher); throw new IllegalArgumentException("Unable to find a method that matches " + matcher);
} }
/**
* Retrieve every method as a map over names.
* <p>
* Note that overloaded methods will only occur once in the resulting map.
* @param methods - every method.
* @return A map over every given method.
*/
public Map<String, Method> getMappedMethods(List<Method> methods) {
Map<String, Method> map = Maps.newHashMap();
for (Method method : methods) {
map.put(method.getName(), method);
}
return map;
}
/** /**
* Retrieve a list of every constructor that matches the given matcher. * Retrieve a list of every constructor that matches the given matcher.
* <p> * <p>

View File

@ -21,6 +21,8 @@ import java.lang.reflect.Array;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import com.google.common.primitives.Primitives; import com.google.common.primitives.Primitives;
@ -80,7 +82,7 @@ public class PrettyPrinter {
// Start and stop // Start and stop
output.append("{ "); output.append("{ ");
printObject(output, object, start, stop, previous, hierachyDepth); printObject(output, object, start, stop, previous, hierachyDepth, true);
output.append(" }"); output.append(" }");
return output.toString(); return output.toString();
@ -99,15 +101,42 @@ public class PrettyPrinter {
else else
output.append(", "); output.append(", ");
// Handle exceptions // Print value
if (value != null) printValue(output, value, stop, previous, hierachyIndex - 1);
printValue(output, value, value.getClass(), stop, previous, hierachyIndex - 1);
else
output.append("NULL");
} }
output.append(")"); output.append(")");
} }
/**
* Print the content of a maps entries.
* @param output - the output string builder.
* @param map - the map to print.
* @param current - the type of this map.
* @param stop - the class that indicates we should stop printing.
* @param previous - previous objects printed.
* @param hierachyIndex - hierachy index.
* @throws IllegalAccessException If any reflection went wrong.
*/
private static void printMap(StringBuilder output, Map<Object, Object> map, Class<?> current, Class<?> stop,
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
boolean first = true;
output.append("[");
for (Entry<Object, Object> entry : map.entrySet()) {
if (first)
first = false;
else
output.append(", ");
printValue(output, entry.getKey(), stop, previous, hierachyIndex - 1);
output.append(": ");
printValue(output, entry.getValue(), stop, previous, hierachyIndex - 1);
}
output.append("]");
}
private static void printArray(StringBuilder output, Object array, Class<?> current, Class<?> stop, private static void printArray(StringBuilder output, Object array, Class<?> current, Class<?> stop,
Set<Object> previous, int hierachyIndex) throws IllegalAccessException { Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
@ -142,10 +171,8 @@ public class PrettyPrinter {
// Internal recursion method // Internal recursion method
private static void printObject(StringBuilder output, Object object, Class<?> current, Class<?> stop, private static void printObject(StringBuilder output, Object object, Class<?> current, Class<?> stop,
Set<Object> previous, int hierachyIndex) throws IllegalAccessException { Set<Object> previous, int hierachyIndex, boolean first) throws IllegalAccessException {
// Trickery
boolean first = true;
// See if we're supposed to skip this class // See if we're supposed to skip this class
if (current == Object.class || (stop != null && current.equals(stop))) { if (current == Object.class || (stop != null && current.equals(stop))) {
return; return;
@ -168,10 +195,11 @@ public class PrettyPrinter {
Class<?> type = field.getType(); Class<?> type = field.getType();
Object value = FieldUtils.readField(field, object, true); Object value = FieldUtils.readField(field, object, true);
if (first) if (first) {
first = false; first = false;
else } else {
output.append(", "); output.append(", ");
}
output.append(field.getName()); output.append(field.getName());
output.append(" = "); output.append(" = ");
@ -180,10 +208,16 @@ public class PrettyPrinter {
} }
// Recurse // Recurse
printObject(output, object, current.getSuperclass(), stop, previous, hierachyIndex); printObject(output, object, current.getSuperclass(), stop, previous, hierachyIndex, first);
} }
@SuppressWarnings("rawtypes") private static void printValue(StringBuilder output, Object value, Class<?> stop,
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
// Handle the NULL case
printValue(output, value, value != null ? value.getClass() : null, stop, previous, hierachyIndex);
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static void printValue(StringBuilder output, Object value, Class<?> type, private static void printValue(StringBuilder output, Object value, Class<?> type,
Class<?> stop, Set<Object> previous, int hierachyIndex) throws IllegalAccessException { Class<?> stop, Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
// Just print primitive types // Just print primitive types
@ -197,12 +231,14 @@ public class PrettyPrinter {
printArray(output, value, type, stop, previous, hierachyIndex); printArray(output, value, type, stop, previous, hierachyIndex);
} else if (Iterable.class.isAssignableFrom(type)) { } else if (Iterable.class.isAssignableFrom(type)) {
printIterables(output, (Iterable) value, type, stop, previous, hierachyIndex); printIterables(output, (Iterable) value, type, stop, previous, hierachyIndex);
} else if (Map.class.isAssignableFrom(type)) {
printMap(output, (Map<Object, Object>) value, type, stop, previous, hierachyIndex);
} else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) { } else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) {
// Don't print previous objects // Don't print previous objects
output.append("\"" + value + "\""); output.append("\"" + value + "\"");
} else { } else {
output.append("{ "); output.append("{ ");
printObject(output, value, value.getClass(), stop, previous, hierachyIndex); printObject(output, value, value.getClass(), stop, previous, hierachyIndex, true);
output.append(" }"); output.append(" }");
} }
} }

View File

@ -17,6 +17,10 @@
package com.comphenix.protocol.reflect.compiler; package com.comphenix.protocol.reflect.compiler;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -29,6 +33,9 @@ import javax.annotation.Nullable;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
/** /**
@ -48,15 +55,26 @@ public class BackgroundCompiler {
// How long to wait for a shutdown // How long to wait for a shutdown
public static final int SHUTDOWN_DELAY_MS = 2000; public static final int SHUTDOWN_DELAY_MS = 2000;
/**
* The default fraction of perm gen space after which the background compiler will be disabled.
*/
public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65;
// The single background compiler we're using // The single background compiler we're using
private static BackgroundCompiler backgroundCompiler; private static BackgroundCompiler backgroundCompiler;
// Classes we're currently compiling
private Map<StructureKey, List<CompileListener<?>>> listeners = Maps.newHashMap();
private Object listenerLock = new Object();
private StructureCompiler compiler; private StructureCompiler compiler;
private boolean enabled; private boolean enabled;
private boolean shuttingDown; private boolean shuttingDown;
private ExecutorService executor; private ExecutorService executor;
private ErrorReporter reporter; private ErrorReporter reporter;
private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN;
/** /**
* Retrieves the current background compiler. * Retrieves the current background compiler.
@ -139,26 +157,61 @@ public class BackgroundCompiler {
* @param uncompiled - structure modifier to compile. * @param uncompiled - structure modifier to compile.
* @param listener - listener responsible for responding to the compilation. * @param listener - listener responsible for responding to the compilation.
*/ */
@SuppressWarnings({"rawtypes", "unchecked"})
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) { public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
// Only schedule if we're enabled // Only schedule if we're enabled
if (enabled && !shuttingDown) { if (enabled && !shuttingDown) {
// Check perm gen
if (getPermGenUsage() > disablePermGenFraction)
return;
// Don't try to schedule anything // Don't try to schedule anything
if (executor == null || executor.isShutdown()) if (executor == null || executor.isShutdown())
return; return;
// Use to look up structure modifiers
final StructureKey key = new StructureKey(uncompiled);
// Allow others to listen in too
synchronized (listenerLock) {
List list = listeners.get(key);
if (!listeners.containsKey(key)) {
listeners.put(key, (List) Lists.newArrayList(listener));
} else {
// We're currently compiling
list.add(listener);
return;
}
}
// Create the worker that will compile our modifier // Create the worker that will compile our modifier
Callable<?> worker = new Callable<Object>() { Callable<?> worker = new Callable<Object>() {
@Override @Override
public Object call() throws Exception { public Object call() throws Exception {
StructureModifier<TKey> modifier = uncompiled; StructureModifier<TKey> modifier = uncompiled;
List list = null;
// Do our compilation // Do our compilation
try { try {
modifier = compiler.compile(modifier); modifier = compiler.compile(modifier);
listener.onCompiled(modifier);
synchronized (listenerLock) {
list = listeners.get(key);
}
// Only execute the listeners if there is a list
if (list != null) {
for (Object compileListener : list) {
((CompileListener<TKey>) compileListener).onCompiled(modifier);
}
// Remove it when we're done
synchronized (listenerLock) {
list = listeners.remove(key);
}
}
} catch (Throwable e) { } catch (Throwable e) {
// Disable future compilations! // Disable future compilations!
setEnabled(false); setEnabled(false);
@ -205,6 +258,41 @@ public class BackgroundCompiler {
} }
} }
/**
* Add a compile listener if we are still waiting for the structure modifier to be compiled.
* @param uncompiled - the structure modifier that may get compiled.
* @param listener - the listener to invoke in that case.
*/
@SuppressWarnings("unchecked")
public <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
synchronized (listenerLock) {
StructureKey key = new StructureKey(uncompiled);
@SuppressWarnings("rawtypes")
List list = listeners.get(key);
if (list != null) {
list.add(listener);
}
}
}
/**
* Retrieve the current usage of the Perm Gen space in percentage.
* @return Usage of the perm gen space.
*/
private double getPermGenUsage() {
for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) {
if (item.getName().contains("Perm Gen")) {
MemoryUsage usage = item.getUsage();
return usage.getUsed() / (double) usage.getCommitted();
}
}
// Unknown
return 0;
}
/** /**
* Clean up after ourselves using the default timeout. * Clean up after ourselves using the default timeout.
*/ */
@ -246,6 +334,22 @@ public class BackgroundCompiler {
this.enabled = enabled; this.enabled = enabled;
} }
/**
* Retrieve the fraction of perm gen space used after which the background compiler will be disabled.
* @return The fraction after which the background compiler is disabled.
*/
public double getDisablePermGenFraction() {
return disablePermGenFraction;
}
/**
* Set the fraction of perm gen space used after which the background compiler will be disabled.
* @param fraction - the maximum use of perm gen space.
*/
public void setDisablePermGenFraction(double fraction) {
this.disablePermGenFraction = fraction;
}
/** /**
* Retrieve the current structure compiler. * Retrieve the current structure compiler.
* @return Current structure compiler. * @return Current structure compiler.

View File

@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.primitives.Primitives; import com.google.common.primitives.Primitives;
@ -94,7 +95,7 @@ public final class StructureCompiler {
// Used to store generated classes of different types // Used to store generated classes of different types
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private static class StructureKey { static class StructureKey {
private Class targetType; private Class targetType;
private Class fieldType; private Class fieldType;
@ -206,6 +207,11 @@ public final class StructureCompiler {
return (StructureModifier<TField>) compiledClass.getConstructor( return (StructureModifier<TField>) compiledClass.getConstructor(
StructureModifier.class, StructureCompiler.class). StructureModifier.class, StructureCompiler.class).
newInstance(source, this); newInstance(source, this);
} catch (OutOfMemoryError e) {
// Print the number of generated classes by the current instances
ProtocolLibrary.getErrorReporter().reportWarning(
this, "May have generated too many classes (count: " + compiledCache.size() + ")");
throw e;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new IllegalStateException("Used invalid parameters in instance creation", e); throw new IllegalStateException("Used invalid parameters in instance creation", e);
} catch (SecurityException e) { } catch (SecurityException e) {
@ -218,7 +224,7 @@ public final class StructureCompiler {
throw new RuntimeException("Error occured while instancing generated class.", e); throw new RuntimeException("Error occured while instancing generated class.", e);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot happen.", e); throw new IllegalStateException("Cannot happen.", e);
} }
} }
/** /**

View File

@ -58,7 +58,7 @@ class CachedPackage {
if (result == null) { if (result == null) {
// Look up the class dynamically // Look up the class dynamically
result = CachedPackage.class.getClassLoader(). result = CachedPackage.class.getClassLoader().
loadClass(packageName + "." + className); loadClass(combine(packageName, className));
cache.put(className, result); cache.put(className, result);
} }
@ -68,4 +68,16 @@ class CachedPackage {
throw new RuntimeException("Cannot find class " + className, e); throw new RuntimeException("Cannot find class " + className, e);
} }
} }
/**
* Correctly combine a package name and the child class we're looking for.
* @param packageName - name of the package, or an empty string for the default package.
* @param className - the class name.
* @return We full class path.
*/
private String combine(String packageName, String className) {
if (packageName.length() == 0)
return className;
return packageName + "." + className;
}
} }

View File

@ -27,6 +27,7 @@ import com.comphenix.protocol.Packets;
import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.base.Strings;
/** /**
* Utility methods for sending chat messages. * Utility methods for sending chat messages.
@ -98,4 +99,48 @@ public class ChatExtensions {
} }
} }
} }
/**
* Print a flower box around a given message.
* @param message - the message to print.
* @param marginChar - the character to use as margin.
* @param marginWidth - the width (in characters) of the left and right margin.
* @param marginHeight - the height (in characters) of the top and buttom margin.
*/
public static String[] toFlowerBox(String[] message, String marginChar, int marginWidth, int marginHeight) {
String[] output = new String[message.length + marginHeight * 2];
int width = getMaximumLength(message);
// Margins
String topButtomMargin = Strings.repeat(marginChar, width + marginWidth * 2);
String leftRightMargin = Strings.repeat(marginChar, marginWidth);
// Add left and right margin
for (int i = 0; i < message.length; i++) {
output[i + marginHeight] = leftRightMargin + Strings.padEnd(message[i], width, ' ') + leftRightMargin;
}
// Insert top and bottom margin
for (int i = 0; i < marginHeight; i++) {
output[i] = topButtomMargin;
output[output.length - i - 1] = topButtomMargin;
}
return output;
}
/**
* Retrieve the longest line lenght in a list of strings.
* @param lines - the lines.
* @return Longest line lenght.
*/
private static int getMaximumLength(String[] lines) {
int current = 0;
// Find the longest line
for (int i = 0; i < lines.length; i++) {
if (current < lines[i].length())
current = lines[i].length();
}
return current;
}
} }

View File

@ -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)
);
}
}

View File

@ -73,7 +73,7 @@ public class MinecraftReflection {
private static String MINECRAFT_FULL_PACKAGE = null; private static String MINECRAFT_FULL_PACKAGE = null;
private static String CRAFTBUKKIT_PACKAGE = null; private static String CRAFTBUKKIT_PACKAGE = null;
private static CachedPackage minecraftPackage; private static CachedPackage minecraftPackage;
private static CachedPackage craftbukkitPackage; private static CachedPackage craftbukkitPackage;
@ -85,7 +85,7 @@ public class MinecraftReflection {
private static Method craftNMSMethod; private static Method craftNMSMethod;
private static Method craftBukkitMethod; private static Method craftBukkitMethod;
private static boolean craftItemStackFailed; private static boolean craftItemStackFailed;
// net.minecraft.server // net.minecraft.server
private static Class<?> itemStackArrayClass; private static Class<?> itemStackArrayClass;
@ -125,15 +125,15 @@ public class MinecraftReflection {
// This server should have a "getHandle" method that we can use // This server should have a "getHandle" method that we can use
if (craftServer != null) { if (craftServer != null) {
try { try {
Class<?> craftClass = craftServer.getClass();
Method getHandle = craftClass.getMethod("getHandle");
Class<?> returnType = getHandle.getReturnType();
String returnName = returnType.getCanonicalName();
// The return type will tell us the full package, regardless of formating // The return type will tell us the full package, regardless of formating
Class<?> craftClass = craftServer.getClass();
CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName()); CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName());
MINECRAFT_FULL_PACKAGE = getPackage(returnName);
// Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package
Class<?> craftEntity = getCraftEntityClass();
Method getHandle = craftEntity.getMethod("getHandle");
MINECRAFT_FULL_PACKAGE = getPackage(getHandle.getReturnType().getCanonicalName());
// Pretty important invariant // Pretty important invariant
if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) { if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) {
@ -165,7 +165,7 @@ public class MinecraftReflection {
throw new IllegalStateException("Could not find Bukkit. Is it running?"); throw new IllegalStateException("Could not find Bukkit. Is it running?");
} }
} }
/** /**
* Used during debugging and testing. * Used during debugging and testing.
* @param minecraftPackage - the current Minecraft package. * @param minecraftPackage - the current Minecraft package.
@ -185,7 +185,8 @@ public class MinecraftReflection {
*/ */
public static String getCraftBukkitPackage() { public static String getCraftBukkitPackage() {
// Ensure it has been initialized // Ensure it has been initialized
getMinecraftPackage(); if (CRAFTBUKKIT_PACKAGE == null)
getMinecraftPackage();
return CRAFTBUKKIT_PACKAGE; return CRAFTBUKKIT_PACKAGE;
} }
@ -490,22 +491,29 @@ public class MinecraftReflection {
public static Class<?> getMinecraftServerClass() { public static Class<?> getMinecraftServerClass() {
try { try {
return getMinecraftClass("MinecraftServer"); return getMinecraftClass("MinecraftServer");
} catch (RuntimeException e) { } catch (RuntimeException e) {
// Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) useFallbackServer();
Constructor<?> selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")). return getMinecraftClass("MinecraftServer");
getConstructor(FuzzyMethodContract.newBuilder().
parameterMatches(getMinecraftObjectMatcher(), 0).
parameterCount(2).
build()
);
Class<?>[] params = selected.getParameterTypes();
// Jackpot - two classes at the same time!
setMinecraftClass("MinecraftServer", params[0]);
setMinecraftClass("ServerConfigurationManager", params[1]);
return params[0];
} }
} }
/**
* Fallback method that can determine the MinecraftServer and the ServerConfigurationManager.
*/
private static void useFallbackServer() {
// Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY)
Constructor<?> selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")).
getConstructor(FuzzyMethodContract.newBuilder().
parameterMatches(getMinecraftObjectMatcher(), 0).
parameterCount(2).
build()
);
Class<?>[] params = selected.getParameterTypes();
// Jackpot - two classes at the same time!
setMinecraftClass("MinecraftServer", params[0]);
setMinecraftClass("ServerConfigurationManager", params[1]);
}
/** /**
* Retrieve the player list class (or ServerConfigurationManager), * Retrieve the player list class (or ServerConfigurationManager),
@ -516,7 +524,7 @@ public class MinecraftReflection {
return getMinecraftClass("ServerConfigurationManager", "PlayerList"); return getMinecraftClass("ServerConfigurationManager", "PlayerList");
} catch (RuntimeException e) { } catch (RuntimeException e) {
// Try again // Try again
getMinecraftServerClass(); useFallbackServer();
return getMinecraftClass("ServerConfigurationManager"); return getMinecraftClass("ServerConfigurationManager");
} }
} }
@ -553,7 +561,7 @@ public class MinecraftReflection {
return getMinecraftClass("NetServerHandler", "PlayerConnection"); return getMinecraftClass("NetServerHandler", "PlayerConnection");
} catch (RuntimeException e) { } catch (RuntimeException e) {
// Use the player connection field // Use the player connection field
return setMinecraftClass("NetLoginHandler", return setMinecraftClass("NetServerHandler",
FuzzyReflection.fromClass(getEntityPlayerClass()). FuzzyReflection.fromClass(getEntityPlayerClass()).
getFieldByType("playerConnection", getNetHandlerClass()).getType() getFieldByType("playerConnection", getNetHandlerClass()).getType()
); );
@ -908,7 +916,15 @@ public class MinecraftReflection {
public static Class<?> getCraftPlayerClass() { public static Class<?> getCraftPlayerClass() {
return getCraftBukkitClass("entity.CraftPlayer"); return getCraftBukkitClass("entity.CraftPlayer");
} }
/**
* Retrieve the CraftEntity class.
* @return CraftEntity class.
*/
public static Class<?> getCraftEntityClass() {
return getCraftBukkitClass("entity.CraftEntity");
}
/** /**
* Retrieve a CraftItemStack from a given ItemStack. * Retrieve a CraftItemStack from a given ItemStack.
* @param bukkitItemStack - the Bukkit ItemStack to convert. * @param bukkitItemStack - the Bukkit ItemStack to convert.

View File

@ -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);
}
}

View File

@ -64,6 +64,9 @@ public interface NbtBase<TType> {
* Is either a primitive {@link java.lang.Number wrapper}, {@link java.lang.String String}, * Is either a primitive {@link java.lang.Number wrapper}, {@link java.lang.String String},
* {@link java.util.List List} or a {@link java.util.Map Map}. * {@link java.util.List List} or a {@link java.util.Map Map}.
* <p> * <p>
* Users are encouraged to cast an NBT compound to {@link NbtCompound} and use its put and get-methods
* instead of accessing its content from getValue().
* <p>
* All operations that modify collections directly, such as {@link java.util.List#add(Object) List.add(Object)} or * All operations that modify collections directly, such as {@link java.util.List#add(Object) List.add(Object)} or
* {@link java.util.Map#clear() Map.clear()}, are considered optional. This also include members in {@link java.util.Iterator Iterator} and * {@link java.util.Map#clear() Map.clear()}, are considered optional. This also include members in {@link java.util.Iterator Iterator} and
* {@link java.util.ListIterator ListIterator}. Operations that are not implemented throw a * {@link java.util.ListIterator ListIterator}. Operations that are not implemented throw a

View File

@ -5,6 +5,8 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.annotation.Nonnull;
/** /**
* Represents a mapping of arbitrary NBT elements and their unique names. * Represents a mapping of arbitrary NBT elements and their unique names.
* <p> * <p>
@ -16,6 +18,10 @@ import java.util.Set;
* @author Kristian * @author Kristian
*/ */
public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<NbtBase<?>> { public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<NbtBase<?>> {
@Override
@Deprecated()
public Map<String, NbtBase<?>> getValue();
/** /**
* Determine if an entry with the given key exists or not. * Determine if an entry with the given key exists or not.
* @param key - the key to lookup. * @param key - the key to lookup.
@ -48,8 +54,9 @@ public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<
* Set a entry based on its name. * Set a entry based on its name.
* @param entry - entry with a name and value. * @param entry - entry with a name and value.
* @return This compound, for chaining. * @return This compound, for chaining.
* @throws IllegalArgumentException If entry is NULL.
*/ */
public abstract <T> NbtCompound put(NbtBase<T> entry); public abstract <T> NbtCompound put(@Nonnull NbtBase<T> entry);
/** /**
* Retrieve the string value of an entry identified by a given key. * Retrieve the string value of an entry identified by a given key.
@ -251,7 +258,26 @@ public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<
* @return This current compound, for chaining. * @return This current compound, for chaining.
*/ */
public abstract NbtCompound put(String key, int[] value); public abstract NbtCompound put(String key, int[] value);
/**
* Associates a given Java primitive value, list, map or NbtBase<?> with a certain key.
* <p>
* If the value is NULL, the corresponding key is removed. Any Map or List will be converted
* to a corresponding NbtCompound or NbtList.
*
* @param key - the name of the new entry,
* @param value - the value of the new entry, or NULL to remove the current value.
* @return This current compound, for chaining.
*/
public abstract NbtCompound putObject(String key, Object value);
/**
* Retrieve the primitive object, NbtList or NbtCompound associated with the given key.
* @param key - the key of the object to find.
* @return The object with this key, or NULL if we couldn't find anything.
*/
public abstract Object getObject(String key);
/** /**
* Retrieve the compound (map) value of an entry identified by a given key. * Retrieve the compound (map) value of an entry identified by a given key.
* @param key - the key of the entry. * @param key - the key of the entry.
@ -304,6 +330,13 @@ public interface NbtCompound extends NbtBase<Map<String, NbtBase<?>>>, Iterable<
*/ */
public abstract <T> NbtCompound put(String key, Collection<? extends NbtBase<T>> list); public abstract <T> NbtCompound put(String key, Collection<? extends NbtBase<T>> list);
/**
* Remove the NBT element that is associated with the given key.
* @param key - the key of the element to remove.
* @return The removed element, or NULL if no such element was found.
*/
public abstract <T> NbtBase<?> remove(String key);
/** /**
* Retrieve an iterator view of the NBT tags stored in this compound. * Retrieve an iterator view of the NBT tags stored in this compound.
* @return The tags stored in this compound. * @return The tags stored in this compound.

View File

@ -152,7 +152,13 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
public void setValue(Map<String, NbtBase<?>> newValue) { public void setValue(Map<String, NbtBase<?>> newValue) {
// Write all the entries // Write all the entries
for (Map.Entry<String, NbtBase<?>> entry : newValue.entrySet()) { for (Map.Entry<String, NbtBase<?>> entry : newValue.entrySet()) {
put(entry.getValue()); Object value = entry.getValue();
// We don't really know
if (value instanceof NbtBase)
put(entry.getValue());
else
putObject(entry.getKey(), entry.getValue());
} }
} }
@ -215,6 +221,9 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
*/ */
@Override @Override
public <T> NbtCompound put(NbtBase<T> entry) { public <T> NbtCompound put(NbtBase<T> entry) {
if (entry == null)
throw new IllegalArgumentException("Entry cannot be NULL.");
getValue().put(entry.getName(), entry); getValue().put(entry.getName(), entry);
return this; return this;
} }
@ -252,6 +261,29 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
return this; return this;
} }
@Override
public NbtCompound putObject(String key, Object value) {
if (value == null) {
remove(key);
} else if (value instanceof NbtBase) {
put(key, (NbtBase<?>) value);
} else {
NbtBase<?> base = new MemoryElement<Object>(key, value);
put(base);
}
return this;
}
@Override
public Object getObject(String key) {
NbtBase<?> base = getValue(key);
if (base != null && base.getType() != NbtType.TAG_LIST && base.getType() != NbtType.TAG_COMPOUND)
return base.getValue();
else
return base;
}
/** /**
* Retrieve the byte value of an entry identified by a given key. * Retrieve the byte value of an entry identified by a given key.
* @param key - the key of the entry. * @param key - the key of the entry.
@ -565,6 +597,9 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
@Override @Override
public NbtCompound put(String key, NbtBase<?> entry) { public NbtCompound put(String key, NbtBase<?> entry) {
if (entry == null)
throw new IllegalArgumentException("Entry cannot be NULL.");
// Don't modify the original NBT // Don't modify the original NBT
NbtBase<?> clone = entry.deepClone(); NbtBase<?> clone = entry.deepClone();
@ -583,6 +618,11 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
return put(WrappedList.fromList(key, list)); return put(WrappedList.fromList(key, list));
} }
@Override
public <T> NbtBase<?> remove(String key) {
return getValue().remove(key);
}
@Override @Override
public void write(DataOutput destination) { public void write(DataOutput destination) {
NbtBinarySerializer.DEFAULT.serialize(container, destination); NbtBinarySerializer.DEFAULT.serialize(container, destination);

View File

@ -18,5 +18,4 @@ global:
ignore version check: ignore version check:
# Override the starting injecting method # Override the starting injecting method
injection method: injection method:

View File

@ -1,9 +1,8 @@
name: ProtocolLib name: ProtocolLib
version: 2.2.0 version: 2.3.0
description: Provides read/write access to the Minecraft protocol. description: Provides read/write access to the Minecraft protocol.
author: Comphenix author: Comphenix
website: http://www.comphenix.net/ProtocolLib website: http://www.comphenix.net/ProtocolLib
load: startup
main: com.comphenix.protocol.ProtocolLibrary main: com.comphenix.protocol.ProtocolLibrary
database: false database: false