Improve client packet interception by about 37%.

This commit is contained in:
Kristian S. Stangeland 2013-03-03 14:49:20 +01:00
parent 2985dc9cf8
commit d387b2d792
2 changed files with 55 additions and 46 deletions

View File

@ -23,13 +23,13 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.NoOp;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
@ -39,6 +39,8 @@ import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
/** /**
@ -47,6 +49,14 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian * @author Kristian
*/ */
class ProxyPacketInjector implements PacketInjector { class ProxyPacketInjector implements PacketInjector {
/**
* Matches the readPacketData(DataInputStream) method in Packet.
*/
private static FuzzyMethodContract readPacket = FuzzyMethodContract.newBuilder().
returnTypeVoid().
parameterExactType(DataInputStream.class).
parameterCount(1).
build();
// The "put" method that associates a packet ID with a packet class // The "put" method that associates a packet ID with a packet class
private static Method putMethod; private static Method putMethod;
@ -61,12 +71,12 @@ class ProxyPacketInjector implements PacketInjector {
// Allows us to determine the sender // Allows us to determine the sender
private PlayerInjectionHandler playerInjection; private PlayerInjectionHandler playerInjection;
// Allows us to look up read packet injectors
private Map<Integer, ReadPacketModifier> readModifier;
// Class loader // Class loader
private ClassLoader classLoader; private ClassLoader classLoader;
// Share callback filter
private CallbackFilter filter;
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager, public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
@ -74,7 +84,6 @@ class ProxyPacketInjector implements PacketInjector {
this.manager = manager; this.manager = manager;
this.playerInjection = playerInjection; this.playerInjection = playerInjection;
this.reporter = reporter; this.reporter = reporter;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize(); initialize();
} }
@ -85,11 +94,9 @@ class ProxyPacketInjector implements PacketInjector {
*/ */
@Override @Override
public void undoCancel(Integer id, Object packet) { public void undoCancel(Integer id, Object packet) {
ReadPacketModifier modifier = readModifier.get(id);
// See if this packet has been cancelled before // See if this packet has been cancelled before
if (modifier != null && modifier.hasCancelled(packet)) { if (ReadPacketModifier.hasCancelled(packet)) {
modifier.removeOverride(packet); ReadPacketModifier.removeOverride(packet);
} }
} }
@ -137,18 +144,34 @@ class ProxyPacketInjector implements PacketInjector {
throw new IllegalStateException("Packet " + packetID + " has already been injected."); throw new IllegalStateException("Packet " + packetID + " has already been injected.");
} }
if (filter == null) {
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (method.getDeclaringClass().equals(Object.class))
return 0;
else if (readPacket.isMatch(MethodInfo.fromMethod(method), null))
return 1;
else
return 2;
}
};
}
// Subclass the specific packet class // Subclass the specific packet class
ex.setSuperclass(old); ex.setSuperclass(old);
ex.setCallbackType(ReadPacketModifier.class); ex.setCallbackFilter(filter);
ex.setCallbackTypes(new Class<?>[] { NoOp.class, ReadPacketModifier.class, ReadPacketModifier.class });
ex.setClassLoader(classLoader); ex.setClassLoader(classLoader);
Class proxy = ex.createClass(); Class proxy = ex.createClass();
// Create the proxy handler // Create the proxy handlers
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter); ReadPacketModifier modifierReadPacket = new ReadPacketModifier(packetID, this, reporter, true);
readModifier.put(packetID, modifier); ReadPacketModifier modifierRest = new ReadPacketModifier(packetID, this, reporter, false);
// Add a static reference // Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier }); Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
try { try {
// Override values // Override values
@ -184,7 +207,6 @@ class ProxyPacketInjector implements PacketInjector {
putMethod.invoke(intHashMap, packetID, old); putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID); previous.remove(packetID);
readModifier.remove(packetID);
registry.remove(proxy); registry.remove(proxy);
overwritten.remove(packetID); overwritten.remove(packetID);
return true; return true;

View File

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