Create a dummy injector if we haven't yet injected the player.

This commit is contained in:
Kristian S. Stangeland 2013-03-04 00:44:09 +01:00
parent d387b2d792
commit 6019ab177c
6 changed files with 150 additions and 85 deletions

View File

@ -47,6 +47,19 @@ public class BlockingHashMap<TKey, TValue> {
// 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.
*/
@ -59,12 +72,8 @@ public class BlockingHashMap<TKey, TValue> {
locks.remove(entry.getKey());
}
}).build(
new CacheLoader<TKey, TValue>() {
@Override
public TValue load(TKey key) throws Exception {
throw new IllegalStateException("Illegal use. Access the map directly instead.");
}
});
BlockingHashMap.<TKey, TValue>newInvalidCacheLoader()
);
backingMap = backingCache.asMap();
// Normal concurrent hash map

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;
@ -229,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

@ -23,6 +23,7 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import net.sf.cglib.proxy.Factory;
@ -30,6 +31,7 @@ import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketAdapter;
@ -42,8 +44,11 @@ import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
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;
/**
@ -64,6 +69,12 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
// Dummy injection
private Cache<Player, PlayerInjector> dummyInjectors =
CacheBuilder.newBuilder().
expireAfterWrite(30, TimeUnit.SECONDS).
build(BlockingHashMap.<Player, PlayerInjector>newInvalidCacheLoader());
// Player injection
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
@ -370,7 +381,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
return injector;
}
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
@ -522,12 +533,43 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (result instanceof PlayerInjector)
return (PlayerInjector) result;
else
return null;
// Make a dummy injector them
return createDummyInjector(player);
} else {
return injector;
}
}
/**
* 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 createDummyInjector(Player player) {
if (!MinecraftReflection.getCraftPlayerClass().isAssignableFrom(player.getClass())) {
// No - this is not safe
return null;
}
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
// This probably means the player has disconnected
if (dummyInjector.getSocket() == null) {
return null;
}
inputStreamLookup.setSocketInjector(dummyInjector.getAddress(), dummyInjector);
dummyInjectors.asMap().put(player, dummyInjector);
return dummyInjector;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access fields.", e);
}
}
/**
* Retrieve a player injector by looking for its NetworkManager.
* @param networkManager - current network manager.

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

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

@ -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;
@ -924,7 +924,7 @@ public class MinecraftReflection {
public static Class<?> getCraftEntityClass() {
return getCraftBukkitClass("entity.CraftEntity");
}
/**
* Retrieve a CraftItemStack from a given ItemStack.
* @param bukkitItemStack - the Bukkit ItemStack to convert.