Ensure that hook method #3 receieves Packet0KeepAlive.

This commit is contained in:
Kristian S. Stangeland 2012-10-01 00:16:06 +02:00
parent 9770257a74
commit 3ad24921d9
9 changed files with 467 additions and 20 deletions

View File

@ -53,8 +53,7 @@ public class ProtocolLibrary extends JavaPlugin {
@Override
public void onLoad() {
logger = getLoggerSafely();
protocolManager = new PacketFilterManager(
getClassLoader(), getServer().getScheduler(), logger);
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), logger);
}
@Override

View File

@ -0,0 +1,189 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
/**
* Used to ensure that the 1.3 server is referencing the correct server handler.
*
* @author Kristian
*/
class InjectedServerConnection {
private static Field listenerThreadField;
private static Field minecraftServerField;
private static Method serverConnectionMethod;
private static Field listField;
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists;
private Server server;
private Logger logger;
private boolean hasAttempted;
private boolean hasSuccess;
private Object minecraftServer = null;
public InjectedServerConnection(Logger logger, Server server) {
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.logger = logger;
this.server = server;
}
public void injectList() {
// Only execute this method once
if (!hasAttempted)
hasAttempted = true;
else
return;
if (minecraftServerField == null)
minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer");
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
logger.log(Level.WARNING, "Cannot extract minecraft server from Bukkit.");
return;
}
try {
if (serverConnectionMethod == null)
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {});
// We're using Minecraft 1.3.1
injectServerConnection();
} catch (RuntimeException e) {
// Minecraft 1.2.5 or lower
injectListenerThread();
}
}
private void injectListenerThread() {
try {
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromClass(minecraftServerField.getType()).
getFieldByType(".*NetworkListenThread");
} catch (RuntimeException e) {
logger.log(Level.SEVERE, "Cannot find listener thread in MinecraftServer.");
return;
}
Object listenerThread = null;
// Attempt to get the thread
try {
listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to read the listener thread.");
return;
}
// Ok, great. Get every list field
List<Field> lists = FuzzyReflection.fromClass(listenerThreadField.getType()).getFieldListByType(List.class);
for (Field list : lists) {
injectIntoList(listenerThread, list);
}
hasSuccess = true;
}
private void injectServerConnection() {
Object serverConnection = null;
// Careful - we might fail
try {
serverConnection = serverConnectionMethod.invoke(minecraftServer);
} catch (Exception ex) {
logger.log(Level.WARNING, "Unable to retrieve server connection", ex);
return;
}
if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("serverConnection", List.class);
injectIntoList(serverConnection, listField);
hasSuccess = true;
}
@SuppressWarnings("unchecked")
private void injectIntoList(Object instance, Field field) {
VolatileField listFieldRef = new VolatileField(listField, instance, true);
List<Object> list = (List<Object>) listFieldRef.getValue();
// Careful not to inject twice
if (list instanceof ReplacedArrayList) {
replacedLists.add((ReplacedArrayList<Object>) list);
} else {
replacedLists.add(new ReplacedArrayList<Object>(list));
listFieldRef.setValue(replacedLists.get(0));
listFields.add(listFieldRef);
}
}
/**
* Replace the server handler instance kept by the "keep alive" object.
* @param oldHandler - old server handler.
* @param newHandler - new, proxied server handler.
*/
public void replaceServerHandler(Object oldHandler, Object newHandler) {
if (!hasAttempted) {
injectList();
}
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.addMapping(oldHandler, newHandler);
}
}
}
/**
* Revert to the old vanilla server handler, if it has been replaced.
* @param oldHandler - old vanilla server handler.
*/
public void revertServerHandler(Object oldHandler) {
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.removeMapping(oldHandler);
}
}
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}

View File

@ -49,7 +49,7 @@ class MinecraftRegistry {
// Initialize it, if we haven't already
if (packetToID == null) {
try {
Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("java\\.util\\.Map");
Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalAccessException e) {

View File

@ -13,8 +13,10 @@ import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
@ -28,9 +30,14 @@ import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
public class NetworkServerInjector extends PlayerInjector {
private static Method sendPacketMethod;
private StructureModifier<Object> serverHandlerModifier;
private InjectedServerConnection serverInjection;
public NetworkServerInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException {
public NetworkServerInjector(Player player, PacketFilterManager manager,
Set<Integer> sendingFilters, InjectedServerConnection serverInjection) throws IllegalAccessException {
super(player, manager, sendingFilters);
this.serverInjection = serverInjection;
}
@Override
@ -41,6 +48,8 @@ public class NetworkServerInjector extends PlayerInjector {
if (hasInitialized) {
if (sendPacketMethod == null)
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
if (serverHandlerModifier == null)
serverHandlerModifier = new StructureModifier<Object>(serverHandler.getClass(), null, false);
}
}
@ -98,12 +107,8 @@ public class NetworkServerInjector extends PlayerInjector {
}
}
// Delegate to our underlying class
try {
return method.invoke(serverHandler, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
// Call the method directly
return proxy.invokeSuper(obj, args);
}
});
@ -114,22 +119,46 @@ public class NetworkServerInjector extends PlayerInjector {
CollectionGenerator.INSTANCE);
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
serverInjection.replaceServerHandler(serverHandler, proxyObject);
// Inject it now
if (proxyObject != null) {
copyTo(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject);
} else {
throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
}
}
/**
* Copy every field in server handler A to server handler B.
* @param source - fields to copy.
* @param destination - fields to copy to.
*/
private void copyTo(Object source, Object destination) {
StructureModifier<Object> modifierSource = serverHandlerModifier.withTarget(source);
StructureModifier<Object> modifierDest = serverHandlerModifier.withTarget(destination);
// Copy every field
try {
for (int i = 0; i < modifierSource.size(); i++) {
modifierDest.write(i, modifierSource.read(i));
}
} catch (FieldAccessException e) {
throw new RuntimeException("Unable to copy fields from NetServerHandler.", e);
}
}
@Override
public void cleanupAll() {
if (serverHandlerRef != null) {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue());
serverHandlerRef.revertValue();
}
serverInjection.revertServerHandler(serverHandler);
try {
if (getNetHandler() != null) {
// Restore packet listener

View File

@ -34,6 +34,7 @@ import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Server;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -44,7 +45,6 @@ import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.ProtocolManager;
@ -92,11 +92,14 @@ public final class PacketFilterManager implements ProtocolManager {
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
// Player injection type
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
// Packet injection
private PacketInjector packetInjector;
// Server connection injection
private InjectedServerConnection serverInjection;
// Enabled packet filters
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
@ -122,7 +125,7 @@ public final class PacketFilterManager implements ProtocolManager {
/**
* Only create instances of this class if protocol lib is disabled.
*/
public PacketFilterManager(ClassLoader classLoader, BukkitScheduler scheduler, Logger logger) {
public PacketFilterManager(ClassLoader classLoader, Server server, Logger logger) {
if (logger == null)
throw new IllegalArgumentException("logger cannot be NULL.");
if (classLoader == null)
@ -133,7 +136,8 @@ public final class PacketFilterManager implements ProtocolManager {
this.classLoader = classLoader;
this.logger = logger;
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
this.asyncFilterManager = new AsyncFilterManager(logger, scheduler, this);
this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
this.serverInjection = new InjectedServerConnection(logger, server);
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
}
@ -474,7 +478,7 @@ public final class PacketFilterManager implements ProtocolManager {
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(player, this, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(player, this, sendingFilters);
return new NetworkServerInjector(player, this, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
@ -714,6 +718,8 @@ public final class PacketFilterManager implements ProtocolManager {
if (packetInjector != null)
packetInjector.cleanupAll();
// Remove server handler
serverInjection.cleanupAll();
hasClosed = true;
// Remove listeners

View File

@ -221,6 +221,7 @@ abstract class PlayerInjector {
Packet handlePacketRecieved(Packet packet) {
// Get the packet ID too
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
System.out.println(id);
// Make sure we're listening
if (id != null && sendingFilters.contains(id)) {

View File

@ -0,0 +1,136 @@
package com.comphenix.protocol.injector;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Objects;
import com.google.common.collect.BiMap;
import com.google.common.collect.ForwardingList;
import com.google.common.collect.HashBiMap;
/**
* Represents an array list that wraps another list, while automatically replacing one element with another.
* <p>
* The replaced elements can be recovered.
*
* @author Kristian
* @param <TKey> - type of the elements we're replacing.
*/
class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
private List<TKey> underlyingList;
public ReplacedArrayList(List<TKey> underlyingList) {
this.underlyingList = underlyingList;
}
@Override
public boolean add(TKey element) {
if (replaceMap.containsKey(element)) {
return super.add(replaceMap.get(element));
} else {
return super.add(element);
}
}
@Override
public void add(int index, TKey element) {
if (replaceMap.containsKey(element)) {
super.add(index, replaceMap.get(element));
} else {
super.add(index, element);
}
}
@Override
public boolean addAll(Collection<? extends TKey> collection) {
int oldSize = size();
for (TKey element : collection)
add(element);
return size() != oldSize;
}
@Override
public boolean addAll(int index, Collection<? extends TKey> elements) {
int oldSize = size();
for (TKey element : elements)
add(index++, element);
return size() != oldSize;
}
@Override
protected List<TKey> delegate() {
return underlyingList;
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
*/
public synchronized void addMapping(TKey target, TKey replacement) {
replaceMap.put(target, replacement);
// Replace existing elements
replaceAll(target, replacement);
}
/**
* Revert the given mapping.
* @param target - the instance we replaced.
*/
public synchronized void removeMapping(TKey target) {
// Make sure the mapping exist
if (replaceMap.containsKey(target)) {
TKey replacement = replaceMap.get(target);
replaceMap.remove(target);
// Revert existing elements
replaceAll(replacement, target);
}
}
/**
* Replace all instances of the given object.
* @param find - object to find.
* @param replace - object to replace it with.
*/
public synchronized void replaceAll(TKey find, TKey replace) {
for (int i = 0; i < underlyingList.size(); i++) {
if (Objects.equal(underlyingList.get(i), find))
underlyingList.set(i, replace);
}
}
/**
* Undo all replacements.
*/
public synchronized void revertAll() {
// No need to do anything else
if (replaceMap.size() < 1)
return;
BiMap<TKey, TKey> inverse = replaceMap.inverse();
for (int i = 0; i < underlyingList.size(); i++) {
TKey replaced = underlyingList.get(i);
if (inverse.containsKey(replaced)) {
underlyingList.set(i, inverse.get(replaced));
}
}
replaceMap.clear();
}
@Override
protected void finalize() throws Throwable {
revertAll();
super.finalize();
}
}

View File

@ -142,11 +142,38 @@ public class FuzzyReflection {
* @return The first method that satisfies the parameter types.
*/
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
// Find the correct method to call
List<Method> methods = getMethodListByParameters(returnType, args);
if (methods.size() > 0) {
return methods.get(0);
} else {
// That sucks
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
}
}
/**
* Retrieves a method by looking at the parameter types and return type only.
* @param name - potential name of the method. Only used by the error mechanism.
* @param returnType - regular expression matching the return type of the method to find.
* @param args - regular expressions of the matching parameter types.
* @return The first method that satisfies the parameter types.
*/
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
Pattern match = Pattern.compile(returnTypeRegex);
Pattern[] argMatch = new Pattern[argsRegex.length];
for (int i = 0; i < argsRegex.length; i++) {
argMatch[i] = Pattern.compile(argsRegex[i]);
}
// Find the correct method to call
for (Method method : getMethods()) {
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
return method;
if (match.matcher(method.getReturnType().getName()).matches()) {
if (matchParameters(argMatch, method.getParameterTypes()))
return method;
}
}
@ -154,6 +181,19 @@ public class FuzzyReflection {
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
}
private boolean matchParameters(Pattern[] parameterMatchers, Class<?>[] argTypes) {
if (parameterMatchers.length != argTypes.length)
throw new IllegalArgumentException("Arrays must have the same cardinality.");
// Check types against the regular expressions
for (int i = 0; i < argTypes.length; i++) {
if (!parameterMatchers[i].matcher(argTypes[i].getName()).matches())
return false;
}
return true;
}
/**
* Retrieves every method that has the given parameter types and return type.
* @param returnType - return type of the method to find.
@ -195,6 +235,46 @@ public class FuzzyReflection {
nameRegex + " in " + source.getName());
}
/**
* Retrieves the first field with a type equal to or more specific to the given type.
* @param name - name the field probably is given. This will only be used in the error message.
* @param type - type of the field to find.
* @return The first field with a type that is an instance of the given type.
*/
public Field getFieldByType(String name, Class<?> type) {
List<Field> fields = getFieldListByType(type);
if (fields.size() > 0) {
return fields.get(0);
} else {
// Looks like we're outdated. Too bad.
throw new RuntimeException(String.format("Unable to find a field %s with the type %s in %s",
name, type.getName(), source.getName())
);
}
}
/**
* Retrieves every field with a type equal to or more specific to the given type.
* @param type - type of the fields to find.
* @return Every field with a type that is an instance of the given type.
*/
public List<Field> getFieldListByType(Class<?> type) {
List<Field> fields = new ArrayList<Field>();
// Field with a compatible type
for (Field field : getFields()) {
// A assignable from B -> B instanceOf A
if (type.isAssignableFrom(field.getType())) {
fields.add(field);
}
}
return fields;
}
/**
* Retrieves a field by type.
* <p>

View File

@ -155,6 +155,13 @@ public class VolatileField {
}
}
/**
* Determine whether or not we'll need to revert the value.
*/
public boolean isCurrentSet() {
return currentSet;
}
private void ensureLoaded() {
// Load the value if we haven't already
if (!previousLoaded) {