Adding support for Spigot MCPC 1.2.5.

Very buggy indeed.
This commit is contained in:
Kristian S. Stangeland 2013-04-08 21:53:54 +02:00
parent 505226f8ad
commit 82bb7a7c43
8 changed files with 293 additions and 55 deletions

View File

@ -270,7 +270,7 @@ public class CommandFilter extends CommandBase {
} }
/* /*
* Description: Adds or removes a simple packet listener. * Description: Adds or removes a simple packet filter.
Usage: /<command> add|remove name [packet IDs] Usage: /<command> add|remove name [packet IDs]
*/ */
@Override @Override

View File

@ -340,6 +340,8 @@ class CommandPacket extends CommandBase {
supported.addAll(Packets.Client.getSupported()); supported.addAll(Packets.Client.getSupported());
else if (side.isForServer()) else if (side.isForServer())
supported.addAll(Packets.Server.getSupported()); supported.addAll(Packets.Server.getSupported());
System.out.println("Supported for " + side + ": " + supported);
return supported; return supported;
} }

View File

@ -229,7 +229,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
reporter.reportWarning(this, "Cannot load server and client packet list.", e); reporter.reportWarning(this, "Cannot load server and client packet list.", e);
} }
} catch (IllegalAccessException e) { } catch (FieldAccessException e) {
reporter.reportWarning(this, "Unable to initialize packet injector.", e); reporter.reportWarning(this, "Unable to initialize packet injector.", e);
} }
} }
@ -757,7 +757,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!MinecraftReflection.isPacketClass(packet)) if (!MinecraftReflection.isPacketClass(packet))
throw new IllegalArgumentException("The given object " + packet + " is not a packet."); throw new IllegalArgumentException("The given object " + packet + " is not a packet.");
return PacketRegistry.getPacketToID().get(packet.getClass()); Integer id = PacketRegistry.getPacketToID().get(packet.getClass());
if (id != null) {
return id;
} else {
throw new IllegalArgumentException(
"Unable to find associated packet of " + packet + ": Lookup returned NULL.");
}
} }
@Override @Override

View File

@ -8,6 +8,7 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
/** /**
@ -100,9 +101,9 @@ public class PacketInjectorBuilder {
* <p> * <p>
* Note that any non-null builder parameters must be set. * Note that any non-null builder parameters must be set.
* @return The created injector. * @return The created injector.
* @throws IllegalAccessException If anything goes wrong in terms of reflection. * @throws FieldAccessException If anything goes wrong in terms of reflection.
*/ */
public PacketInjector buildInjector() throws IllegalAccessException { public PacketInjector buildInjector() throws FieldAccessException {
initializeDefaults(); initializeDefaults();
return new ProxyPacketInjector(classLoader, invoker, playerInjection, reporter); return new ProxyPacketInjector(classLoader, invoker, playerInjection, reporter);
} }

View File

@ -25,10 +25,15 @@ import java.util.Set;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
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.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.TroveWrapper;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -39,6 +44,8 @@ import com.google.common.collect.ImmutableSet;
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class PacketRegistry { public class PacketRegistry {
private static final int MIN_SERVER_PACKETS = 5;
private static final int MIN_CLIENT_PACKETS = 5;
// Fuzzy reflection // Fuzzy reflection
private static FuzzyReflection packetRegistry; private static FuzzyReflection packetRegistry;
@ -67,6 +74,14 @@ public class PacketRegistry {
try { try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true); packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalArgumentException e) {
// Spigot 1.2.5 MCPC workaround
try {
packetToID = getSpigotWrapper();
} catch (Exception e2) {
// Very bad indeed
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
}
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
@ -76,6 +91,40 @@ public class PacketRegistry {
return packetToID; return packetToID;
} }
private static Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
// If it talks like a duck, etc.
// Perhaps it would be nice to have a proper duck typing library as well
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
method(FuzzyMethodContract.newBuilder().
nameExact("size").returnTypeExact(int.class)).
method(FuzzyMethodContract.newBuilder().
nameExact("put").parameterCount(2)).
method(FuzzyMethodContract.newBuilder().
nameExact("get").parameterCount(1)).
build();
Field packetsField = getPacketRegistry().getField(
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
Object troveMap = FieldUtils.readStaticField(packetsField, true);
// Check for stupid no_entry_values
try {
Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true);
Integer value = (Integer) FieldUtils.readField(field, troveMap, true);
if (value >= 0 && value < 256) {
// Someone forgot to set the no entry value. Let's help them.
FieldUtils.writeField(field, troveMap, -1);
}
} catch (IllegalArgumentException e) {
// Whatever
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, "Unable to correct no entry value.", e);
}
// We'll assume this a Trove map
return TroveWrapper.getDecoratedMap(troveMap);
}
/** /**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry. * Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry. * @return Reflected packet registry.
@ -109,6 +158,10 @@ public class PacketRegistry {
*/ */
public static Set<Integer> getServerPackets() throws FieldAccessException { public static Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets(); initializeSets();
// Sanity check. This is impossible!
if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
return serverPackets; return serverPackets;
} }
@ -119,6 +172,10 @@ public class PacketRegistry {
*/ */
public static Set<Integer> getClientPackets() throws FieldAccessException { public static Set<Integer> getClientPackets() throws FieldAccessException {
initializeSets(); initializeSets();
// As above
if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
return clientPackets; return clientPackets;
} }
@ -140,6 +197,14 @@ public class PacketRegistry {
serverPackets = ImmutableSet.copyOf(serverPacketsRef); serverPackets = ImmutableSet.copyOf(serverPacketsRef);
clientPackets = ImmutableSet.copyOf(clientPacketsRef); clientPackets = ImmutableSet.copyOf(clientPacketsRef);
// Check sizes
if (serverPackets.size() < MIN_SERVER_PACKETS)
ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, "Too few server packets detected: " + serverPackets.size());
if (clientPackets.size() < MIN_CLIENT_PACKETS)
ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, "Too few client packets detected: " + clientPackets.size());
} else { } else {
throw new FieldAccessException("Cannot retrieve packet client/server sets."); throw new FieldAccessException("Cannot retrieve packet client/server sets.");
} }

View File

@ -37,6 +37,7 @@ import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker; 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.FieldAccessException;
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.MethodInfo;
@ -49,6 +50,85 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian * @author Kristian
*/ */
class ProxyPacketInjector implements PacketInjector { class ProxyPacketInjector implements PacketInjector {
/**
* Represents a way to update the packet ID to class lookup table.
* @author Kristian
*/
private static interface PacketClassLookup {
public void setLookup(int packetID, Class<?> clazz);
}
private static class IntHashMapLookup implements PacketClassLookup {
// The "put" method that associates a packet ID with a packet class
private Method putMethod;
private Object intHashMap;
public IntHashMapLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
try {
putMethod.invoke(intHashMap, packetID, clazz);
} 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);
}
}
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);
}
}
}
private static class ArrayLookup implements PacketClassLookup {
private Class<?>[] array;
public ArrayLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
array[packetID] = clazz;
}
private void initialize() throws IllegalAccessException {
FuzzyReflection reflection = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass());
// Is there a Class array with 256 elements instead?
for (Field field : reflection.getFieldListByType(Class[].class)) {
Class<?>[] test = (Class<?>[]) FieldUtils.readField(field, (Object)null);
if (test.length == 256) {
array = test;
return;
}
}
throw new IllegalArgumentException(
"Unable to find an array with the type " + Class[].class +
" in " + MinecraftReflection.getPacketClass());
}
}
/** /**
* Matches the readPacketData(DataInputStream) method in Packet. * Matches the readPacketData(DataInputStream) method in Packet.
*/ */
@ -58,9 +138,7 @@ class ProxyPacketInjector implements PacketInjector {
parameterCount(1). parameterCount(1).
build(); build();
// The "put" method that associates a packet ID with a packet class private static PacketClassLookup lookup;
private static Method putMethod;
private static Object intHashMap;
// The packet filter manager // The packet filter manager
private ListenerInvoker manager; private ListenerInvoker manager;
@ -78,7 +156,7 @@ class ProxyPacketInjector implements PacketInjector {
private CallbackFilter 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 FieldAccessException {
this.classLoader = classLoader; this.classLoader = classLoader;
this.manager = manager; this.manager = manager;
@ -100,20 +178,21 @@ class ProxyPacketInjector implements PacketInjector {
} }
} }
private void initialize() throws IllegalAccessException { private void initialize() throws FieldAccessException {
if (intHashMap == null) { if (lookup == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap. try {
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true). lookup = new IntHashMapLookup();
getFieldByType(MinecraftReflection.getMinecraftObjectRegex()); } catch (Exception e1) {
try { try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); lookup = new ArrayLookup();
} catch (IllegalArgumentException e) { } catch (Exception e2) {
throw new RuntimeException("Minecraft is incompatible.", e); // Wow
throw new FieldAccessException(e1.getMessage() + ". Workaround failed too.", e2);
}
} }
// Now, get the "put" method. // Should work fine now
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
} }
} }
@ -173,21 +252,12 @@ class ProxyPacketInjector implements PacketInjector {
// Add a static reference // Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest }); Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
try {
// Override values // Override values
previous.put(packetID, old); previous.put(packetID, old);
registry.put(proxy, packetID); registry.put(proxy, packetID);
overwritten.put(packetID, proxy); overwritten.put(packetID, proxy);
putMethod.invoke(intHashMap, packetID, proxy); lookup.setLookup(packetID, proxy);
return true; 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 @Override
@ -200,25 +270,14 @@ class ProxyPacketInjector implements PacketInjector {
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets(); Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets(); Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
// Use the old class definition
try {
Class old = previous.get(packetID); Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromID(packetID); Class proxy = PacketRegistry.getPacketClassFromID(packetID);
putMethod.invoke(intHashMap, packetID, old); lookup.setLookup(packetID, old);
previous.remove(packetID); previous.remove(packetID);
registry.remove(proxy); registry.remove(proxy);
overwritten.remove(packetID); overwritten.remove(packetID);
return true; 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 @Override

View File

@ -0,0 +1,104 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
/**
* Wrap a GNU Trove Collection class with an equivalent Java Collection class.
* @author Kristian
*/
public class TroveWrapper {
private volatile static Class<?> decorators;
/**
* Retrieve a Java wrapper for the corresponding Trove map.
* @param troveMap - the trove map to wrap.
* @return The wrapped GNU Trove map.
* @throws IllegalStateException If GNU Trove cannot be found in the class map.
* @throws IllegalArgumentException If troveMap is NULL.
* @throws FieldAccessException Error in wrapper method or lack of reflection permissions.
*/
public static <TKey, TValue> Map<TKey, TValue> getDecoratedMap(@Nonnull Object troveMap) {
@SuppressWarnings("unchecked")
Map<TKey, TValue> result = (Map<TKey, TValue>) getDecorated(troveMap);
return result;
}
/**
* Retrieve a Java wrapper for the corresponding Trove set.
* @param troveSet - the trove set to wrap.
* @return The wrapped GNU Trove set.
* @throws IllegalStateException If GNU Trove cannot be found in the class map.
* @throws IllegalArgumentException If troveSet is NULL.
* @throws FieldAccessException Error in wrapper method or lack of reflection permissions.
*/
public static <TValue> Set<TValue> getDecoratedSet(@Nonnull Object troveSet) {
@SuppressWarnings("unchecked")
Set<TValue> result = (Set<TValue>) getDecorated(troveSet);
return result;
}
/**
* Retrieve a Java wrapper for the corresponding Trove list.
* @param troveList - the trove list to wrap.
* @return The wrapped GNU Trove list.
* @throws IllegalStateException If GNU Trove cannot be found in the class map.
* @throws IllegalArgumentException If troveList is NULL.
* @throws FieldAccessException Error in wrapper method or lack of reflection permissions.
*/
public static <TValue> List<TValue> getDecoratedList(@Nonnull Object troveList) {
@SuppressWarnings("unchecked")
List<TValue> result = (List<TValue>) getDecorated(troveList);
return result;
}
private static Object getDecorated(@Nonnull Object trove) {
if (trove == null)
throw new IllegalArgumentException("trove instance cannot be non-null.");
AbstractFuzzyMatcher<Class<?>> match = FuzzyMatchers.matchSuper(trove.getClass());
if (decorators == null) {
try {
// Attempt to get decorator class
decorators = TroveWrapper.class.getClassLoader().loadClass("gnu.trove.TDecorators");
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Cannot find TDecorators in Gnu Trove.", e);
}
}
// Find an appropriate wrapper method in TDecorators
for (Method method : decorators.getMethods()) {
Class<?>[] types = method.getParameterTypes();
if (types.length == 1 && match.isMatch(types[0], null)) {
try {
Object result = method.invoke(null, trove);
if (result == null)
throw new FieldAccessException("Wrapper returned NULL.");
else
return result;
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Cannot invoke wrapper method.", e);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Illegal access.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Error in invocation.", e);
}
}
}
throw new IllegalArgumentException("Cannot find decorator for " + trove + " (" + trove.getClass() + ")");
}
}