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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -4,8 +4,6 @@ import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet;
@ -49,12 +47,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void setPlayerHook(PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void scheduleDataInputRefresh(Player player) {
// Fine
}
@Override
public void addPacketHandler(int packetID) {
sendingFilters.add(packetID);
@ -86,7 +79,8 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
}
@Override
public void injectPlayer(Player player) {
public void injectPlayer(Player player, ConflictStrategy strategy) {
// We don't care about strategy
injector.injectPlayer(player);
}
@ -106,11 +100,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
@Override
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot.");
@ -125,4 +114,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void checkListener(Set<PacketListener> listeners) {
// 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) {
Integer id = invoker.getPacketID(packet);
if (id != null & queuedFilters.contains(id)) {
if (id != null && queuedFilters.contains(id)) {
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
@ -427,7 +427,7 @@ public class SpigotPacketInjector implements SpigotPacketListener {
void uninjectPlayer(Player player) {
final NetworkObjectInjector injector = getInjector(player);
if (player != null) {
if (player != null && injector != null) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
@Override
public void run() {

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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},
* {@link java.util.List List} or a {@link java.util.Map Map}.
* <p>
* Users are encouraged to cast an NBT compound to {@link NbtCompound} and use its put and get-methods
* instead of accessing its content from getValue().
* <p>
* All operations that modify collections directly, such as {@link java.util.List#add(Object) List.add(Object)} or
* {@link java.util.Map#clear() Map.clear()}, are considered optional. This also include members in {@link java.util.Iterator Iterator} and
* {@link java.util.ListIterator ListIterator}. Operations that are not implemented throw a

View File

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

View File

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

View File

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

View File

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