Archiviert
13
0

Added support for default values in packets.

Dieser Commit ist enthalten in:
Kristian S. Stangeland 2012-09-13 11:01:08 +02:00
Ursprung c65f6b006f
Commit da694ca5ed
7 geänderte Dateien mit 283 neuen und 65 gelöschten Zeilen

Datei anzeigen

@ -35,11 +35,12 @@ public class ProtocolLibrary extends JavaPlugin {
@Override
public void onDisable() {
protocolManager.close();
protocolManager = null;
}
/**
* Retrieves the packet protocol manager.
* @return Packet protocol manager.
* @return Packet protocol manager, or NULL if it has been disabled.
*/
public static ProtocolManager getProtocolManager() {
return protocolManager;

Datei anzeigen

@ -76,6 +76,20 @@ public interface ProtocolManager {
*/
public PacketContainer createPacket(int id);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* If set to true, the skip default option will prevent the system from assigning
* non-primitive fields in the packet to a new default instance. For instance, certain
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
* skipDefaults option is false, the List or Set will be automatically created.
*
* @param id - packet ID.
* @param skipDefaults - TRUE to skip setting default values, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(int id, boolean skipDefaults);
/**
* Retieves a set of every enabled packet.
* @return Every packet filter.

Datei anzeigen

@ -217,7 +217,23 @@ public final class PacketFilterManager implements ProtocolManager {
@Override
public PacketContainer createPacket(int id) {
return new PacketContainer(id);
return createPacket(id, false);
}
@Override
public PacketContainer createPacket(int id, boolean skipDefaults) {
PacketContainer packet = new PacketContainer(id);
// Use any default values if possible
if (!skipDefaults) {
try {
packet.getModifier().writeDefaults();
} catch (IllegalAccessException e) {
throw new RuntimeException("Security exception.", e);
}
}
return packet;
}
@Override
@ -324,6 +340,8 @@ public final class PacketFilterManager implements ProtocolManager {
if (packetInjector != null)
packetInjector.cleanupAll();
// Remove listeners
packetListeners.clear();
playerInjection.clear();
connectionLookup.clear();
hasClosed = true;

Datei anzeigen

@ -2,14 +2,19 @@ package com.comphenix.protocol.injector;
import java.io.DataInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.server.EntityPlayer;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
@ -18,16 +23,29 @@ import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.google.common.collect.Sets;
class PlayerInjector {
/**
* Marker interface that indicates a packet is fake and should not be processed.
* @author Kristian
*/
public interface FakePacket {
// Nothing
}
// Cache previously retrieved fields
private static Field serverHandlerField;
private static Field networkManagerField;
private static Field inputField;
private static Field netHandlerField;
// To add our injected array lists
private static StructureModifier<Object> networkModifier;
// And methods
private static Method queueMethod;
private static Method processMethod;
@ -36,7 +54,16 @@ class PlayerInjector {
private boolean hasInitialized;
// Reference to the player's network manager
private VolatileField networkManager;
private Object networkManager;
// Current net handler
private Object netHandler;
// Overridden fields
private List<VolatileField> overridenLists = new ArrayList<VolatileField>();
// Packets to ignore
private Set<Packet> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Packet, Boolean>());
// The packet manager and filters
private PacketFilterManager manager;
@ -45,9 +72,6 @@ class PlayerInjector {
// Previous data input
private DataInputStream cachedInput;
// Current net handler
private Object netHandler;
public PlayerInjector(Player player, PacketFilterManager manager, Set<Integer> packetFilters) throws IllegalAccessException {
this.player = player;
this.manager = manager;
@ -70,9 +94,11 @@ class PlayerInjector {
Object serverHandler = FieldUtils.readField(serverHandlerField, notchEntity);
// Next, get the network manager
if (networkManagerField == null)
if (networkManagerField == null) {
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
networkManager = new VolatileField(networkManagerField, serverHandler);
networkModifier = new StructureModifier<Object>(networkManagerField.getType(), null);
}
networkManager = FieldUtils.readField(networkManagerField, serverHandler);
// And the queue method
if (queueMethod == null)
@ -81,7 +107,7 @@ class PlayerInjector {
// And the data input stream that we'll use to identify a player
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager.getOldValue(), true).
inputField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("java\\.io\\.DataInputStream");
}
}
@ -111,7 +137,7 @@ class PlayerInjector {
// Get the handler
if (netHandler != null)
netHandler = FieldUtils.readField(netHandlerField, networkManager.getOldValue(), true);
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
return netHandler;
}
@ -152,12 +178,15 @@ class PlayerInjector {
* @param InvocationTargetException If an error occured when sending the packet.
*/
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
Object networkDelegate = filtered ? networkManager.getValue() : networkManager.getOldValue();
if (networkDelegate != null) {
if (networkManager != null) {
try {
if (!filtered) {
ignoredPackets.add(packet);
}
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkDelegate, packet);
queueMethod.invoke(networkManager, packet);
} catch (IllegalArgumentException e) {
throw e;
@ -171,47 +200,101 @@ class PlayerInjector {
}
}
@SuppressWarnings("serial")
public void injectManager() {
if (networkManager != null) {
final Class<?> networkInterface = networkManagerField.getType();
final Object networkDelegate = networkManager.getOldValue();
// Create our proxy object
Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
new Class<?>[] { networkInterface }, new InvocationHandler() {
@SuppressWarnings("rawtypes")
StructureModifier<List> list = networkModifier.withType(List.class);
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// OH OH! The queue method!
if (method.equals(queueMethod)) {
Packet packet = (Packet) args[0];
// Subclass both send queues
for (Field field : list.getFields()) {
VolatileField overwriter = new VolatileField(field, networkManager, true);
if (packet != null) {
overwriter.setValue(Collections.synchronizedList(new ArrayList<Packet>() {
@Override
public boolean add(Packet packet) {
// Check for fake packets and ignored packets
if (packet instanceof FakePacket) {
return true;
} else if (ignoredPackets.contains(packet)) {
ignoredPackets.remove(packet);
} else {
packet = handlePacketRecieved(packet);
}
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
// A NULL packet indicate cancelling
try {
if (packet != null) {
super.add(packet);
} else {
// We'll use the FakePacket marker instead of preventing the filters
sendServerPacket(createNegativePacket(packet), true);
}
// Collection.add contract
return true;
} catch (InvocationTargetException e) {
throw new RuntimeException("Reverting cancelled packet failed.", e.getTargetException());
}
}
// Delegate to our underlying class
try {
return method.invoke(networkDelegate, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
});
// Inject it, if we can.
networkManager.setValue(networkProxy);
}));
overridenLists.add(overwriter);
}
}
}
/**
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet
* whereby every int method's return value is inverted (a => -a).
*
* @param source - packet to invert.
* @return The inverted packet.
*/
private Packet createNegativePacket(Packet source) {
Enhancer ex = new Enhancer();
Class<?> type = source.getClass();
// We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets
// might cause a "disconnect.overflow" error.
//
// We do that by constructing a special packet of the same type that returns
// a negative integer for all zero-parameter integer methods. This includes the
// size() method, which is used by the queue method to count the number of
// bytes to add.
//
// Essentially, we have:
//
// public class NegativePacket extends [a packet] {
// @Override
// public int size() {
// return -super.size();
// }
// ect.
// }
ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true);
ex.setClassLoader(type.getClassLoader());
ex.setSuperclass(type);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invokeSuper(obj, args);
return -result;
} else {
return proxy.invokeSuper(obj, args);
}
}
});
return (Packet) ex.create();
}
/**
* Allows a packet to be recieved by the listeners.
* @param packet - packet to recieve.
@ -246,7 +329,7 @@ class PlayerInjector {
return cachedInput;
// Save to cache
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager.getOldValue(), true);
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
return cachedInput;
} catch (IllegalAccessException e) {
@ -256,6 +339,9 @@ class PlayerInjector {
public void cleanupAll() {
// Clean up
networkManager.revertValue();
for (VolatileField overriden : overridenLists) {
overriden.revertValue();
}
overridenLists.clear();
}
}

Datei anzeigen

@ -37,7 +37,7 @@ public class StructureCache {
// Use the vanilla class definition
if (result == null) {
result = new StructureModifier<Object>(MinecraftRegistry.getPacketClassFromID(id, true));
result = new StructureModifier<Object>(MinecraftRegistry.getPacketClassFromID(id, true), Packet.class);
structureModifiers.put(id, result);
}

Datei anzeigen

@ -4,10 +4,14 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.gson.internal.Primitives;
import net.minecraft.server.Packet;
@ -25,27 +29,40 @@ public class StructureModifier<TField> {
private Class fieldType;
private List<Field> data = new ArrayList<Field>();
// Improved default values
private Set<Field> defaultFields;
// Cache of previous types
private Map<Class, StructureModifier> subtypeCache;
public StructureModifier(Class targetType) {
this(targetType, Object.class, getFields(targetType), null, new HashMap<Class, StructureModifier>());
}
public StructureModifier(Class targetType, Class superclassExclude) {
List<Field> fields = getFields(targetType, superclassExclude);
private StructureModifier(Class targetType, Class fieldType, List<Field> data,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
this.targetType = targetType;
this.fieldType = fieldType;
this.data = data;
this.converter = converter;
this.subtypeCache = subTypeCache;
initialize(targetType, Object.class,
fields, generateDefaultFields(fields), null,
new HashMap<Class, StructureModifier>());
}
private StructureModifier(StructureModifier<TField> other, Object target) {
this(other.targetType, other.fieldType, other.data, other.converter, other.subtypeCache);
initialize(other.targetType, other.fieldType, other.data, other.defaultFields, other.converter, other.subtypeCache);
this.target = target;
}
private StructureModifier() {
// Consumers of this method should call "initialize"
}
private void initialize(Class targetType, Class fieldType,
List<Field> data, Set<Field> defaultFields,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
this.targetType = targetType;
this.fieldType = fieldType;
this.data = data;
this.defaultFields = defaultFields;
this.converter = converter;
this.subtypeCache = subTypeCache;
}
/**
* Reads the value of a field given its index.
* @param fieldIndex - index of the field.
@ -68,6 +85,20 @@ public class StructureModifier<TField> {
return (TField) result;
}
/**
* Reads the value of a field if and ONLY IF it exists.
* @param fieldIndex - index of the field.
* @return Value of the field, or NULL if it doesn't exist.
* @throws IllegalAccessException If we're unable to read the field due to a security limitation.
*/
public TField readSafely(int fieldIndex) throws IllegalAccessException {
if (fieldIndex >= 0 && fieldIndex < data.size()) {
return read(fieldIndex);
} else {
return null;
}
}
/**
* Writes the value of a field given its index.
* @param fieldIndex - index of the field.
@ -89,6 +120,20 @@ public class StructureModifier<TField> {
return this;
}
/**
* Writes the value of a given field IF and ONLY if it exists.
* @param fieldIndex - index of the potential field.
* @param value - new value of the field.
* @return This structure modifer - for chaining.
* @throws IllegalAccessException If we're unable to write to the field due to a security limitation.
*/
public StructureModifier<TField> writeSafely(int fieldIndex, TField value) throws IllegalAccessException {
if (fieldIndex >= 0 && fieldIndex < data.size()) {
write(fieldIndex, value);
}
return this;
}
/**
* Correctly modifies the value of a field.
* @param fieldIndex - index of the field to modify.
@ -110,6 +155,22 @@ public class StructureModifier<TField> {
return withType(fieldType, null);
}
/**
* Sets all non-primitive fields to a more fitting default value. See {@link DefaultInstance}.
* @return The current structure modifier - for chaining.
* @throws IllegalAccessException If we're unable to write to the fields due to a security limitation.
*/
public StructureModifier<TField> writeDefaults() throws IllegalAccessException {
// Write a default instance to every field
for (Field field : defaultFields) {
FieldUtils.writeField(field, target,
DefaultInstances.getDefault(field.getType()), true);
}
return this;
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param fieldType - the type, or supertype, of every field to modify.
@ -128,16 +189,21 @@ public class StructureModifier<TField> {
} else if (result == null) {
List<Field> filtered = new ArrayList<Field>();
Set<Field> defaults = new HashSet<Field>();
for (Field field : data) {
if (fieldType.isAssignableFrom(field.getType())) {
filtered.add(field);
if (defaultFields.contains(field))
defaults.add(field);
}
}
// Cache structure modifiers
result = new StructureModifier<T>(targetType, fieldType, filtered,
converter, new HashMap<Class, StructureModifier>());
result = new StructureModifier<T>();
result.initialize(targetType, fieldType, filtered, defaults,
converter, new HashMap<Class, StructureModifier>());
subtypeCache.put(fieldType, result);
}
@ -199,8 +265,37 @@ public class StructureModifier<TField> {
return copy;
}
/**
* Retrieves a list of the fields matching the constraints of this structure modifier.
* @return List of fields.
*/
public List<Field> getFields() {
return ImmutableList.copyOf(data);
}
// Used to generate plausible default values
private static Set<Field> generateDefaultFields(List<Field> fields) {
Set<Field> requireDefaults = new HashSet<Field>();
for (Field field : fields) {
Class<?> type = field.getType();
// First, ignore primitive fields
if (!Primitives.isPrimitive(type)) {
// Next, see if we actually can generate a default value
if (DefaultInstances.getDefault(type) != null) {
// If so, require it
requireDefaults.add(field);
}
}
}
return requireDefaults;
}
// Used to filter out irrelevant fields
private static List<Field> getFields(Class type) {
private static List<Field> getFields(Class type, Class superclassExclude) {
List<Field> result = new ArrayList<Field>();
// Retrieve every private and public field
@ -208,7 +303,10 @@ public class StructureModifier<TField> {
int mod = field.getModifiers();
// Ignore static, final and "abstract packet" fields
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && !field.getDeclaringClass().equals(Packet.class)) {
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && (
superclassExclude == null || !field.getDeclaringClass().equals(Packet.class)
)) {
result.add(field);
}
}

Datei anzeigen

@ -6,6 +6,7 @@ import net.minecraft.server.Packet103SetSlot;
import org.junit.Test;
import com.avaje.ebeaninternal.server.cluster.Packet;
import com.comphenix.protocol.reflect.StructureModifier;
public class StructureModifierTest {
@ -14,7 +15,7 @@ public class StructureModifierTest {
public void test() throws IllegalAccessException {
Packet103SetSlot move = new Packet103SetSlot();
StructureModifier<Object> modifier = new StructureModifier<Object>(Packet103SetSlot.class);
StructureModifier<Object> modifier = new StructureModifier<Object>(Packet103SetSlot.class, Packet.class);
move.a = 1;
int value = (Integer) modifier.withTarget(move).withType(int.class).read(0);