Generate a new class for each packet type, as getID is final.

We can't override the getID method in Packet, as it is marked as final.
Instead, we'll just generate a seperate class for each packet ID that 
needs to be intercepted.

We can't inherit from the packet ID of any particular class, as it 
may get recognized and modified by the sendPacket() method. This could
be a problem if this recogition is necessary, but we'll come back to 
this later.
This commit is contained in:
Kristian S. Stangeland 2013-07-17 05:48:34 +02:00
parent 051a4eda87
commit e25ab31316
5 changed files with 179 additions and 91 deletions

View File

@ -18,6 +18,7 @@
package com.comphenix.protocol.injector;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
/**
* Represents an object that initiate the packet listeners.
@ -45,6 +46,12 @@ public interface ListenerInvoker {
*/
public abstract int getPacketID(Object packet);
/**
* Retrieve the object responsible for intercepting write packets.
* @return Object that intercepts write packets.
*/
public InterceptWritePacket getInterceptWritePacket();
/**
* Associate a given class with the given packet ID. Internal method.
* @param clazz - class to associate.

View File

@ -53,6 +53,7 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
import com.comphenix.protocol.injector.packet.PacketRegistry;
@ -134,6 +135,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Different injection types per game phase
private PlayerInjectionHandler playerInjection;
// Intercepting write packet methods
private InterceptWritePacket interceptWritePacket;
// The two listener containers
private SortedPacketListenerList recievedListeners;
private SortedPacketListenerList sendingListeners;
@ -202,6 +206,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// The plugin verifier
this.pluginVerifier = new PluginVerifier(builder.getLibrary());
// The write packet interceptor
interceptWritePacket = new InterceptWritePacket(classLoader, reporter);
// Use the correct injection type
if (builder.isNettyEnabled()) {
spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server);
@ -274,6 +281,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
return ImmutableSet.copyOf(packetListeners);
}
@Override
public InterceptWritePacket getInterceptWritePacket() {
return interceptWritePacket;
}
/**
* Warn of common programming mistakes.
* @param plugin - plugin to check.

View File

@ -2,11 +2,11 @@ package com.comphenix.protocol.injector.packet;
import java.io.DataOutput;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
@ -16,6 +16,7 @@ import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/**
* Retrieve a packet instance that has its write method intercepted.
@ -34,35 +35,41 @@ public class InterceptWritePacket {
parameterCount(1).
build();
private ClassLoader classLoader;
private ErrorReporter reporter;
private CallbackFilter filter;
private boolean writePacketIntercepted;
private ConcurrentMap<Integer, Class<?>> proxyClasses = Maps.newConcurrentMap();
private ClassLoader classLoader;
private ErrorReporter reporter;
private WritePacketModifier modifierWrite;
private WritePacketModifier modifierRest;
public InterceptWritePacket(ClassLoader classLoader, ErrorReporter reporter) {
this.classLoader = classLoader;
this.reporter = reporter;
this.reporter = reporter;
// Initialize modifiers
this.modifierWrite = new WritePacketModifier(reporter, true);
this.modifierRest = new WritePacketModifier(reporter, false);
}
/**
* Construct a new instance of the proxy object.
* @return New instance of proxy.
*/
public Object constructProxy(Object proxyObject, PacketEvent event, NetworkMarker marker) {
private Class<?> createProxyClass(int packetId) {
// Construct the proxy object
Enhancer ex = new Enhancer();
// Initialize the shared filter
// Attempt to share callback filter
if (filter == null) {
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (WRITE_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
return 1;
} else {
writePacketIntercepted = true;
return 0;
} else {
return 1;
}
}
};
@ -71,15 +78,16 @@ public class InterceptWritePacket {
// Subclass the generic packet class
ex.setSuperclass(MinecraftReflection.getPacketClass());
ex.setCallbackFilter(filter);
ex.setUseCache(false);
ex.setClassLoader(classLoader);
ex.setCallbacks(new Callback[] {
NoOp.INSTANCE,
new WritePacketModifier(reporter, proxyObject, event, marker)
});
ex.setCallbackTypes( new Class[] { WritePacketModifier.class, WritePacketModifier.class });
Class<?> proxyClass = ex.createClass();
Object proxy = ex.create();
if (proxy != null) {
// Register write modifiers too
Enhancer.registerStaticCallbacks(proxyClass, new Callback[] { modifierWrite, modifierRest });
if (proxyClass != null) {
// Check that we found the read method
if (!writePacketIntercepted) {
reporter.reportWarning(this,
@ -87,6 +95,56 @@ public class InterceptWritePacket {
messageParam(MinecraftReflection.getPacketClass()));
}
}
return proxy;
return proxyClass;
}
private Class<?> getProxyClass(int packetId) {
Class<?> stored = proxyClasses.get(packetId);
// Concurrent pattern
if (stored == null) {
final Class<?> created = createProxyClass(packetId);
stored = proxyClasses.putIfAbsent(packetId, created);
// We won!
if (stored == null) {
stored = created;
PacketRegistry.getPacketToID().put(stored, packetId);
}
}
return stored;
}
/**
* Construct a new instance of the proxy object.
* @return New instance of proxy, or NULL if we failed.
*/
public Object constructProxy(Object proxyObject, PacketEvent event, NetworkMarker marker) {
Class<?> proxyClass = null;
try {
proxyClass = getProxyClass(event.getPacketID());
Object generated = proxyClass.newInstance();
modifierWrite.register(generated, proxyObject, event, marker);
modifierRest.register(generated, proxyObject, event, marker);
return generated;
} catch (Exception e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_CONSTRUCT_WRITE_PROXY).
messageParam(proxyClass));
return null;
}
}
/**
* Invoked when the write packet proxy class should be removed.
*/
public void cleanup() {
// Remove all proxy classes from the registry
for (Class<?> stored : proxyClasses.values()) {
PacketRegistry.getPacketToID().remove(stored);
}
}
}

View File

@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.PriorityQueue;
import com.comphenix.protocol.error.ErrorReporter;
@ -29,6 +30,7 @@ import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketOutputHandler;
import com.google.common.collect.MapMaker;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
@ -36,89 +38,98 @@ import net.sf.cglib.proxy.MethodProxy;
public class WritePacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_WRITE_SERVER_PACKET = new ReportType("Cannot write server packet.");
private static class ProxyInformation {
// Marker that contains custom writers
public final Object proxyObject;
public final PacketEvent event;
public final NetworkMarker marker;
public ProxyInformation(Object proxyObject, PacketEvent event, NetworkMarker marker) {
this.proxyObject = proxyObject;
this.event = event;
this.marker = marker;
}
}
private Map<Object, ProxyInformation> proxyLookup = new MapMaker().weakKeys().makeMap();
// Report errors
private final ErrorReporter reporter;
// Marker that contains custom writers
private final Object proxyObject;
private final PacketEvent event;
private final NetworkMarker marker;
// Whether or not this represents the write method
private boolean isWriteMethod;
public WritePacketModifier(ErrorReporter reporter, Object proxyObject, PacketEvent event, NetworkMarker marker) {
this.proxyObject = proxyObject;
this.event = event;
this.marker = marker;
public WritePacketModifier(ErrorReporter reporter, boolean isWriteMethod) {
this.reporter = reporter;
this.isWriteMethod = isWriteMethod;
}
/**
* Associate the given generated instance of a class and the given parameteters.
* @param generatedClass - the generated class.
* @param proxyObject - the object to call from the generated class.
* @param event - the packet event.
* @param marker - the network marker.
*/
public void register(Object generatedClass, Object proxyObject, PacketEvent event, NetworkMarker marker) {
proxyLookup.put(generatedClass, new ProxyInformation(proxyObject, event, marker));
}
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
PriorityQueue<PacketOutputHandler> handlers = (PriorityQueue<PacketOutputHandler>) marker.getOutputHandlers();
ProxyInformation information = proxyLookup.get(thisObj);
if (information == null) {
// This is really bad - someone forgot to register the proxy
throw new RuntimeException("Cannot find proxy information for " + thisObj);
}
if (isWriteMethod) {
PriorityQueue<PacketOutputHandler> handlers = (PriorityQueue<PacketOutputHandler>)
information.marker.getOutputHandlers();
// If every output handler has been removed - ignore everything
if (!handlers.isEmpty()) {
try {
DataOutput output = (DataOutput) args[0];
// If every output handler has been removed - ignore everything
if (!handlers.isEmpty()) {
try {
DataOutput output = (DataOutput) args[0];
// First - we need the initial buffer
ByteArrayOutputStream outputBufferStream = new ByteArrayOutputStream();
proxy.invokeSuper(proxyObject, new Object[] { new DataOutputStream(outputBufferStream) });
byte[] outputBuffer = outputBufferStream.toByteArray();
// Let each handler prepare the actual output
while (!handlers.isEmpty()) {
PacketOutputHandler handler = handlers.poll();
// First - we need the initial buffer
ByteArrayOutputStream outputBufferStream = new ByteArrayOutputStream();
proxy.invoke(information.proxyObject, new Object[] { new DataOutputStream(outputBufferStream) });
byte[] outputBuffer = outputBufferStream.toByteArray();
try {
byte[] changed = handler.handle(event, outputBuffer);
// Let each handler prepare the actual output
while (!handlers.isEmpty()) {
PacketOutputHandler handler = handlers.poll();
// Don't break just because a plugin returned NULL
if (changed != null) {
outputBuffer = changed;
} else {
throw new IllegalStateException("Handler cannot return a NULL array.");
try {
byte[] changed = handler.handle(information.event, outputBuffer);
// Don't break just because a plugin returned NULL
if (changed != null) {
outputBuffer = changed;
} else {
throw new IllegalStateException("Handler cannot return a NULL array.");
}
} catch (Exception e) {
reporter.reportMinimal(handler.getPlugin(), "PacketOutputHandler.handle()", e);
}
} catch (Exception e) {
reporter.reportMinimal(handler.getPlugin(), "PacketOutputHandler.handle()", e);
}
}
// Write that output to the network stream
output.write(outputBuffer);
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_WRITE_SERVER_PACKET).callerParam(args[0]).error(e)
);
// Write that output to the network stream
output.write(outputBuffer);
return null;
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_WRITE_SERVER_PACKET).callerParam(args[0]).error(e)
);
}
}
}
// Default to the super method
return proxy.invokeSuper(proxyObject, args);
}
/**
* Retrieve the proxied Minecraft object.
* @return The proxied object.
*/
public Object getProxyObject() {
return proxyObject;
}
/**
* Retrieve the associated packet event.
* @return The packet event.
*/
public PacketEvent getEvent() {
return event;
}
/**
* Retrieve the network marker that is in use.
* @return The network marker.
*/
public NetworkMarker getMarker() {
return marker;
return proxy.invoke(information.proxyObject, args);
}
}

View File

@ -143,9 +143,7 @@ public abstract class PlayerInjector implements SocketInjector {
this.reporter = reporter;
this.player = player;
this.invoker = invoker;
// Intercept the write method
writePacketInterceptor = new InterceptWritePacket(classLoader, reporter);
this.writePacketInterceptor = invoker.getInterceptWritePacket();
}
/**
@ -523,8 +521,10 @@ public abstract class PlayerInjector implements SocketInjector {
* Remove all hooks and modifications.
*/
public final void cleanupAll() {
if (!clean)
if (!clean) {
cleanHook();
writePacketInterceptor.cleanup();
}
clean = true;
}