Make it possible to specify a custom incoming packet interceptor.

This commit is contained in:
Kristian S. Stangeland 2013-02-03 20:27:47 +01:00
parent 6cf3307a3b
commit 7abfd2148e
5 changed files with 349 additions and 235 deletions

View File

@ -51,8 +51,9 @@ import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.InjectorFactory;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
@ -180,8 +181,10 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
try {
// Initialize injection mangers
this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, packetListeners, server);
this.packetInjector = new PacketInjector(classLoader, this, playerInjection, reporter);
this.playerInjection = new PlayerInjectionHandler(
classLoader, reporter, isInjectionNecessary, this, packetListeners, server);
this.packetInjector = InjectorFactory.getInstance().createProxyInjector(
classLoader, this, playerInjection, reporter);
this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
// Attempt to load the list of server and client packets

View File

@ -0,0 +1,42 @@
package com.comphenix.protocol.injector.packet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
/**
* A singleton factory for creating incoming packet injectors.
*
* @author Kristian
*/
public class InjectorFactory {
private static final InjectorFactory INSTANCE = new InjectorFactory();
private InjectorFactory() {
// No need to construct this
}
/**
* Retrieve the factory singleton.
* @return Factory singleton.
*/
public static InjectorFactory getInstance() {
return INSTANCE;
}
/**
* Create a packet injector that intercepts packets by overriding the packet registry.
* @param classLoader - current class loader.
* @param manager - packet invoker.
* @param playerInjection - to lookup Player by DataInputStream.
* @param reporter - error reporter.
* @return A packet injector with these features.
* @throws IllegalAccessException If we fail to create the injector.
*/
public PacketInjector createProxyInjector(
ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
return new ProxyPacketInjector(classLoader, manager, playerInjection, reporter);
}
}

View File

@ -1,257 +1,62 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.io.DataInputStream;
import java.lang.reflect.Field;
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 com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
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.utility.MinecraftReflection;
/**
* This class is responsible for adding or removing proxy objects that intercepts recieved packets.
* Represents a incoming packet injector.
*
* @author Kristian
*/
public class PacketInjector {
// The "put" method that associates a packet ID with a packet class
private static Method putMethod;
private static Object intHashMap;
// The packet filter manager
private ListenerInvoker manager;
// Error reporter
private ErrorReporter reporter;
// 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;
public PacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
this.classLoader = classLoader;
this.manager = manager;
this.playerInjection = playerInjection;
this.reporter = reporter;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize();
}
public interface PacketInjector {
/**
* Undo a packet cancel.
* @param id - the id of the packet.
* @param packet - packet to uncancel.
*/
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);
}
}
private void initialize() throws IllegalAccessException {
if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
// Now, get the "put" method.
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
}
}
@SuppressWarnings("rawtypes")
public boolean addPacketHandler(int packetID) {
if (hasPacketHandler(packetID))
return false;
Enhancer ex = new Enhancer();
// Unfortunately, we can't easily distinguish between these two functions:
// * Object lookup(int par1)
// * Object removeObject(int par1)
// So, we'll use the classMapToInt registry instead.
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Class old = PacketRegistry.getPacketClassFromID(packetID);
// If this packet is not known
if (old == null) {
throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version.");
}
// Check for previous injections
if (!MinecraftReflection.isMinecraftClass(old)) {
throw new IllegalStateException("Packet " + packetID + " has already been injected.");
}
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackType(ReadPacketModifier.class);
ex.setClassLoader(classLoader);
Class proxy = ex.createClass();
// Create the proxy handler
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter);
readModifier.put(packetID, modifier);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
try {
// Override values
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
putMethod.invoke(intHashMap, packetID, proxy);
return true;
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
@SuppressWarnings("rawtypes")
public boolean removePacketHandler(int packetID) {
if (!hasPacketHandler(packetID))
return false;
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
// Use the old class definition
try {
Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromID(packetID);
putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID);
readModifier.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
// Handle some problems
} catch (IllegalArgumentException e) {
return false;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
public boolean hasPacketHandler(int packetID) {
return PacketRegistry.getPreviousPackets().containsKey(packetID);
}
public Set<Integer> getPacketHandlers() {
return PacketRegistry.getPreviousPackets().keySet();
}
// Called from the ReadPacketModified monitor
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)
return packetRecieved(packet, client);
else
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;
}
}
public abstract void undoCancel(Integer id, Object packet);
/**
* Start intercepting packets with the given packet ID.
* @param packetID - the ID of the packets to start intercepting.
* @return TRUE if we didn't already intercept these packets, FALSE otherwise.
*/
public abstract boolean addPacketHandler(int packetID);
/**
* Stop intercepting packets with the given packet ID.
* @param packetID - the ID of the packets to stop intercepting.
* @return TRUE if we successfuly stopped intercepting a given packet ID, FALSE otherwise.
*/
public abstract boolean removePacketHandler(int packetID);
/**
* Determine if packets with the given packet ID is being intercepted.
* @param packetID - the packet ID to lookup.
* @return TRUE if we do, FALSE otherwise.
*/
public abstract boolean hasPacketHandler(int packetID);
/**
* Retrieve every intercepted packet ID.
* @return Every intercepted packet ID.
*/
public abstract Set<Integer> getPacketHandlers();
/**
* Let the packet listeners process the given packet.
* @param packet - a packet to process.
* @param client - the client that sent the packet.
* @return The resulting packet event.
*/
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
manager.invokePacketRecieving(event);
return event;
}
@SuppressWarnings("rawtypes")
public synchronized void cleanupAll() {
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet().toArray(new Integer[0])) {
removePacketHandler(id);
}
overwritten.clear();
previous.clear();
}
public abstract PacketEvent packetRecieved(PacketContainer packet, Player client);
/**
* Inform the current PlayerInjector that it should update the DataInputStream next.
* @param player - the player to update.
* Perform any necessary cleanup before unloading ProtocolLib.
*/
public void scheduleDataInputRefresh(Player player) {
playerInjection.scheduleDataInputRefresh(player);
}
}
public abstract void cleanupAll();
}

View File

@ -0,0 +1,264 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.io.DataInputStream;
import java.lang.reflect.Field;
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 com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
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.utility.MinecraftReflection;
/**
* This class is responsible for adding or removing proxy objects that intercepts recieved packets.
*
* @author Kristian
*/
class ProxyPacketInjector implements PacketInjector {
// The "put" method that associates a packet ID with a packet class
private static Method putMethod;
private static Object intHashMap;
// The packet filter manager
private ListenerInvoker manager;
// Error reporter
private ErrorReporter reporter;
// 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;
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
this.classLoader = classLoader;
this.manager = manager;
this.playerInjection = playerInjection;
this.reporter = reporter;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize();
}
/**
* Undo a packet cancel.
* @param id - the id of the packet.
* @param packet - packet to uncancel.
*/
@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);
}
}
private void initialize() throws IllegalAccessException {
if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
// Now, get the "put" method.
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
}
}
@Override
@SuppressWarnings("rawtypes")
public boolean addPacketHandler(int packetID) {
if (hasPacketHandler(packetID))
return false;
Enhancer ex = new Enhancer();
// Unfortunately, we can't easily distinguish between these two functions:
// * Object lookup(int par1)
// * Object removeObject(int par1)
// So, we'll use the classMapToInt registry instead.
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Class old = PacketRegistry.getPacketClassFromID(packetID);
// If this packet is not known
if (old == null) {
throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version.");
}
// Check for previous injections
if (!MinecraftReflection.isMinecraftClass(old)) {
throw new IllegalStateException("Packet " + packetID + " has already been injected.");
}
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackType(ReadPacketModifier.class);
ex.setClassLoader(classLoader);
Class proxy = ex.createClass();
// Create the proxy handler
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter);
readModifier.put(packetID, modifier);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
try {
// Override values
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
putMethod.invoke(intHashMap, packetID, proxy);
return true;
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
@Override
@SuppressWarnings("rawtypes")
public boolean removePacketHandler(int packetID) {
if (!hasPacketHandler(packetID))
return false;
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
// Use the old class definition
try {
Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromID(packetID);
putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID);
readModifier.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
// Handle some problems
} catch (IllegalArgumentException e) {
return false;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
@Override
public boolean hasPacketHandler(int packetID) {
return PacketRegistry.getPreviousPackets().containsKey(packetID);
}
@Override
public Set<Integer> getPacketHandlers() {
return PacketRegistry.getPreviousPackets().keySet();
}
// Called from the ReadPacketModified monitor
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)
return packetRecieved(packet, client);
else
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;
}
}
/**
* Let the packet listeners process the given packet.
* @param packet - a packet to process.
* @param client - the client that sent the packet.
* @return The resulting packet event.
*/
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
manager.invokePacketRecieving(event);
return event;
}
@Override
@SuppressWarnings("rawtypes")
public synchronized void cleanupAll() {
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet().toArray(new Integer[0])) {
removePacketHandler(id);
}
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

@ -41,7 +41,7 @@ class ReadPacketModifier implements MethodInterceptor {
private static final Object CANCEL_MARKER = new Object();
// Common for all packets of the same type
private PacketInjector packetInjector;
private ProxyPacketInjector packetInjector;
private int packetID;
// Report errors
@ -50,7 +50,7 @@ class ReadPacketModifier implements MethodInterceptor {
// Whether or not a packet has been cancelled
private static Map<Object, Object> override = Collections.synchronizedMap(new WeakHashMap<Object, Object>());
public ReadPacketModifier(int packetID, PacketInjector packetInjector, ErrorReporter reporter) {
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter) {
this.packetID = packetID;
this.packetInjector = packetInjector;
this.reporter = reporter;