mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-30 22:53:26 +01:00
Experimental fix for issue #7 on Github.
Attempt to detect the correct "get" method in a IntHashMap.
This commit is contained in:
parent
3923e05178
commit
be9bbc924e
@ -21,7 +21,6 @@ import java.lang.reflect.Constructor;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -35,8 +34,8 @@ import org.bukkit.entity.Player;
|
|||||||
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.FuzzyMethodContract;
|
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,7 +50,6 @@ class EntityUtilities {
|
|||||||
private static Field trackedPlayersField;
|
private static Field trackedPlayersField;
|
||||||
private static Field trackerField;
|
private static Field trackerField;
|
||||||
|
|
||||||
private static Method hashGetMethod;
|
|
||||||
private static Method scanPlayersMethod;
|
private static Method scanPlayersMethod;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -180,7 +178,7 @@ class EntityUtilities {
|
|||||||
|
|
||||||
if (trackedEntitiesField == null) {
|
if (trackedEntitiesField == null) {
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
Set<Class> ignoredTypes = new HashSet<Class>();
|
Set<Class> ignoredTypes = new HashSet<Class>();
|
||||||
|
|
||||||
// Well, this is more difficult. But we're looking for a Minecraft object that is not
|
// Well, this is more difficult. But we're looking for a Minecraft object that is not
|
||||||
// created by the constructor(s).
|
// created by the constructor(s).
|
||||||
@ -197,42 +195,14 @@ class EntityUtilities {
|
|||||||
|
|
||||||
// Read the entity hashmap
|
// Read the entity hashmap
|
||||||
Object trackedEntities = null;
|
Object trackedEntities = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
|
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
|
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getting the "get" method is pretty hard, but first - try to just get it by name
|
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
|
||||||
if (hashGetMethod == null) {
|
|
||||||
|
|
||||||
Class<?> type = trackedEntities.getClass();
|
|
||||||
|
|
||||||
try {
|
|
||||||
hashGetMethod = type.getMethod("get", int.class);
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
// Then it's probably the lowest named method that takes an int-parameter and returns a object
|
|
||||||
hashGetMethod = FuzzyReflection.fromClass(type).getMethod(
|
|
||||||
FuzzyMethodContract.newBuilder().banModifier(Modifier.STATIC).
|
|
||||||
parameterCount(1).
|
|
||||||
parameterExactType(int.class).
|
|
||||||
returnTypeExact(Object.class).
|
|
||||||
build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap exceptions
|
|
||||||
try {
|
|
||||||
return hashGetMethod.invoke(trackedEntities, entityID);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
throw new RuntimeException("Exception occurred in Minecraft.", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,7 +20,6 @@ package com.comphenix.protocol.injector.packet;
|
|||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -48,6 +47,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
|
|||||||
import com.comphenix.protocol.reflect.MethodInfo;
|
import com.comphenix.protocol.reflect.MethodInfo;
|
||||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is responsible for adding or removing proxy objects that intercepts recieved packets.
|
* This class is responsible for adding or removing proxy objects that intercepts recieved packets.
|
||||||
@ -67,9 +67,7 @@ class ProxyPacketInjector implements PacketInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class IntHashMapLookup implements PacketClassLookup {
|
private static class IntHashMapLookup implements PacketClassLookup {
|
||||||
// The "put" method that associates a packet ID with a packet class
|
private WrappedIntHashMap intHashMap;
|
||||||
private Method putMethod;
|
|
||||||
private Object intHashMap;
|
|
||||||
|
|
||||||
public IntHashMapLookup() throws IllegalAccessException {
|
public IntHashMapLookup() throws IllegalAccessException {
|
||||||
initialize();
|
initialize();
|
||||||
@ -77,32 +75,21 @@ class ProxyPacketInjector implements PacketInjector {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLookup(int packetID, Class<?> clazz) {
|
public void setLookup(int packetID, Class<?> clazz) {
|
||||||
try {
|
intHashMap.put(packetID, clazz);
|
||||||
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 {
|
private void initialize() throws IllegalAccessException {
|
||||||
if (intHashMap == null) {
|
if (intHashMap == null) {
|
||||||
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
|
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
|
||||||
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
|
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
|
||||||
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
|
getFieldByType("packetIdMap", MinecraftReflection.getIntHashMapClass());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
|
intHashMap = WrappedIntHashMap.fromHandle(
|
||||||
|
FieldUtils.readField(intHashMapField, (Object) null, true));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new RuntimeException("Minecraft is incompatible.", e);
|
throw new RuntimeException("Minecraft is incompatible.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, get the "put" method.
|
|
||||||
putMethod = FuzzyReflection.fromObject(intHashMap).
|
|
||||||
getMethodByParameters("put", int.class, Object.class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.comphenix.protocol.reflect.fuzzy;
|
package com.comphenix.protocol.reflect.fuzzy;
|
||||||
|
|
||||||
import java.lang.reflect.Member;
|
import java.lang.reflect.Member;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -46,6 +47,14 @@ public abstract class AbstractFuzzyMember<T extends Member> extends AbstractFuzz
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require that every matching member is public.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder<T> requirePublic() {
|
||||||
|
return requireModifier(Modifier.PUBLIC);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a given bit-field of modifers that will skip or ignore members.
|
* Add a given bit-field of modifers that will skip or ignore members.
|
||||||
* @param modifier - bit-field of modifiers to skip or ignore.
|
* @param modifier - bit-field of modifiers to skip or ignore.
|
||||||
|
@ -34,6 +34,12 @@ public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder requirePublic() {
|
||||||
|
super.requirePublic();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder nameRegex(String regex) {
|
public Builder nameRegex(String regex) {
|
||||||
super.nameRegex(regex);
|
super.nameRegex(regex);
|
||||||
|
@ -4,6 +4,9 @@ import java.lang.reflect.Member;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,14 +15,56 @@ import com.google.common.collect.Sets;
|
|||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public class FuzzyMatchers {
|
public class FuzzyMatchers {
|
||||||
|
// Constant matchers
|
||||||
|
private static AbstractFuzzyMatcher<Class<?>> MATCH_ALL = new AbstractFuzzyMatcher<Class<?>>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatch(Class<?> value, Object parent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int calculateRoundNumber() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private FuzzyMatchers() {
|
private FuzzyMatchers() {
|
||||||
// Don't make this constructable
|
// Don't make this constructable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a class matcher that matches an array with a given component matcher.
|
||||||
|
* @param componentMatcher - the component matcher.
|
||||||
|
* @return A new array matcher.
|
||||||
|
*/
|
||||||
|
public static AbstractFuzzyMatcher<Class<?>> matchArray(@Nonnull final AbstractFuzzyMatcher<Class<?>> componentMatcher) {
|
||||||
|
Preconditions.checkNotNull(componentMatcher, "componentMatcher cannot be NULL.");
|
||||||
|
return new AbstractFuzzyMatcher<Class<?>>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatch(Class<?> value, Object parent) {
|
||||||
|
return value.isArray() && componentMatcher.isMatch(value.getComponentType(), parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int calculateRoundNumber() {
|
||||||
|
// We're just above object
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a fuzzy matcher that will match any class.
|
||||||
|
* @return A class matcher.
|
||||||
|
*/
|
||||||
|
public static AbstractFuzzyMatcher<Class<?>> matchAll() {
|
||||||
|
return MATCH_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a class matcher that matches types exactly.
|
* Construct a class matcher that matches types exactly.
|
||||||
* @param matcher - the matching class.
|
* @param matcher - the matching class.
|
||||||
* @return A new class mathcher.
|
* @return A new class matcher.
|
||||||
*/
|
*/
|
||||||
public static AbstractFuzzyMatcher<Class<?>> matchExact(Class<?> matcher) {
|
public static AbstractFuzzyMatcher<Class<?>> matchExact(Class<?> matcher) {
|
||||||
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_EXACT);
|
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_EXACT);
|
||||||
@ -28,7 +73,7 @@ public class FuzzyMatchers {
|
|||||||
/**
|
/**
|
||||||
* Construct a class matcher that matches any of the given classes exactly.
|
* Construct a class matcher that matches any of the given classes exactly.
|
||||||
* @param classes - list of classes to match.
|
* @param classes - list of classes to match.
|
||||||
* @return A new class mathcher.
|
* @return A new class matcher.
|
||||||
*/
|
*/
|
||||||
public static AbstractFuzzyMatcher<Class<?>> matchAnyOf(Class<?>... classes) {
|
public static AbstractFuzzyMatcher<Class<?>> matchAnyOf(Class<?>... classes) {
|
||||||
return matchAnyOf(Sets.newHashSet(classes));
|
return matchAnyOf(Sets.newHashSet(classes));
|
||||||
@ -37,7 +82,7 @@ public class FuzzyMatchers {
|
|||||||
/**
|
/**
|
||||||
* Construct a class matcher that matches any of the given classes exactly.
|
* Construct a class matcher that matches any of the given classes exactly.
|
||||||
* @param classes - set of classes to match.
|
* @param classes - set of classes to match.
|
||||||
* @return A new class mathcher.
|
* @return A new class matcher.
|
||||||
*/
|
*/
|
||||||
public static AbstractFuzzyMatcher<Class<?>> matchAnyOf(Set<Class<?>> classes) {
|
public static AbstractFuzzyMatcher<Class<?>> matchAnyOf(Set<Class<?>> classes) {
|
||||||
return new ClassSetMatcher(classes);
|
return new ClassSetMatcher(classes);
|
||||||
@ -46,7 +91,7 @@ public class FuzzyMatchers {
|
|||||||
/**
|
/**
|
||||||
* Construct a class matcher that matches super types of the given class.
|
* Construct a class matcher that matches super types of the given class.
|
||||||
* @param matcher - the matching type must be a super class of this type.
|
* @param matcher - the matching type must be a super class of this type.
|
||||||
* @return A new class mathcher.
|
* @return A new class matcher.
|
||||||
*/
|
*/
|
||||||
public static AbstractFuzzyMatcher<Class<?>> matchSuper(Class<?> matcher) {
|
public static AbstractFuzzyMatcher<Class<?>> matchSuper(Class<?> matcher) {
|
||||||
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_SUPER);
|
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_SUPER);
|
||||||
@ -55,7 +100,7 @@ public class FuzzyMatchers {
|
|||||||
/**
|
/**
|
||||||
* Construct a class matcher that matches derived types of the given class.
|
* Construct a class matcher that matches derived types of the given class.
|
||||||
* @param matcher - the matching type must be a derived class of this type.
|
* @param matcher - the matching type must be a derived class of this type.
|
||||||
* @return A new class mathcher.
|
* @return A new class matcher.
|
||||||
*/
|
*/
|
||||||
public static AbstractFuzzyMatcher<Class<?>> matchDerived(Class<?> matcher) {
|
public static AbstractFuzzyMatcher<Class<?>> matchDerived(Class<?> matcher) {
|
||||||
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_DERIVED);
|
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_DERIVED);
|
||||||
|
@ -98,6 +98,12 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder requirePublic() {
|
||||||
|
super.requirePublic();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder banModifier(int modifier) {
|
public Builder banModifier(int modifier) {
|
||||||
super.banModifier(modifier);
|
super.banModifier(modifier);
|
||||||
|
@ -419,6 +419,15 @@ public class MinecraftReflection {
|
|||||||
return getDataWatcherClass().isAssignableFrom(obj.getClass());
|
return getDataWatcherClass().isAssignableFrom(obj.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given object is an IntHashMap object.
|
||||||
|
* @param obj - the given object.
|
||||||
|
* @return TRUE if it is, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isIntHashMap(Object obj) {
|
||||||
|
return getIntHashMapClass().isAssignableFrom(obj.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the given object is a CraftItemStack instancey.
|
* Determine if the given object is a CraftItemStack instancey.
|
||||||
* @param obj - the given object.
|
* @param obj - the given object.
|
||||||
@ -1010,6 +1019,45 @@ public class MinecraftReflection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the IntHashMap class.
|
||||||
|
* @return IntHashMap class.
|
||||||
|
*/
|
||||||
|
public static Class<?> getIntHashMapClass() {
|
||||||
|
try {
|
||||||
|
return getMinecraftClass("IntHashMap");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
final Class<?> parent = getEntityTrackerClass();
|
||||||
|
|
||||||
|
// Expected structure of a IntHashMap
|
||||||
|
final FuzzyClassContract intHashContract = FuzzyClassContract.newBuilder().
|
||||||
|
// add(int key, Object value)
|
||||||
|
method(FuzzyMethodContract.newBuilder().
|
||||||
|
parameterCount(2).
|
||||||
|
parameterExactType(int.class, 0).
|
||||||
|
parameterExactType(Object.class, 1).requirePublic()
|
||||||
|
).
|
||||||
|
// Object get(int key)
|
||||||
|
method(FuzzyMethodContract.newBuilder().
|
||||||
|
parameterCount(1).
|
||||||
|
parameterExactType(int.class).
|
||||||
|
returnTypeExact(Object.class).requirePublic()
|
||||||
|
).
|
||||||
|
// Finally, there should be an array of some kind
|
||||||
|
field(FuzzyFieldContract.newBuilder().
|
||||||
|
typeMatches(FuzzyMatchers.matchArray(FuzzyMatchers.matchAll()))
|
||||||
|
).
|
||||||
|
build();
|
||||||
|
|
||||||
|
final AbstractFuzzyMatcher<Field> intHashField = FuzzyFieldContract.newBuilder().
|
||||||
|
typeMatches(getMinecraftObjectMatcher().and(intHashContract)).
|
||||||
|
build();
|
||||||
|
|
||||||
|
// Use the type of the first field that matches
|
||||||
|
return setMinecraftClass("IntHashMap", FuzzyReflection.fromClass(parent).getField(intHashField).getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the attribute modifier class.
|
* Retrieve the attribute modifier class.
|
||||||
* @return Attribute modifier class.
|
* @return Attribute modifier class.
|
||||||
@ -1295,6 +1343,4 @@ public class MinecraftReflection {
|
|||||||
public static String getNetLoginHandlerName() {
|
public static String getNetLoginHandlerName() {
|
||||||
return getNetLoginHandlerClass().getSimpleName();
|
return getNetLoginHandlerClass().getSimpleName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,176 @@
|
|||||||
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a wrapper for the internal IntHashMap in Minecraft.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class WrappedIntHashMap {
|
||||||
|
private static final Class<?> INT_HASH_MAP = MinecraftReflection.getIntHashMapClass();
|
||||||
|
|
||||||
|
private static Method PUT_METHOD;
|
||||||
|
private static Method GET_METHOD;
|
||||||
|
|
||||||
|
private Object handle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an IntHashMap wrapper around an instance.
|
||||||
|
* @param handle - the NMS instance.
|
||||||
|
*/
|
||||||
|
private WrappedIntHashMap(Object handle) {
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new IntHashMap.
|
||||||
|
* @return A new IntHashMap.
|
||||||
|
*/
|
||||||
|
public static WrappedIntHashMap newMap() {
|
||||||
|
try {
|
||||||
|
return new WrappedIntHashMap(INT_HASH_MAP.newInstance());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unable to construct IntHashMap.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a wrapper around a given NMS IntHashMap.
|
||||||
|
* @param handle - the NMS IntHashMap.
|
||||||
|
* @return The created wrapped.
|
||||||
|
* @throws IllegalArgumentException If the handle is not an IntHasMap.
|
||||||
|
*/
|
||||||
|
public static WrappedIntHashMap fromHandle(@Nonnull Object handle) {
|
||||||
|
Preconditions.checkNotNull(handle, "handle cannot be NULL");
|
||||||
|
Preconditions.checkState(MinecraftReflection.isIntHashMap(handle),
|
||||||
|
"handle is a " + handle.getClass() + ", not an IntHashMap.");
|
||||||
|
|
||||||
|
return new WrappedIntHashMap(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associates a specified key with the given value in the integer map.
|
||||||
|
* <p>
|
||||||
|
* If the key has already been associated with a value, then it will be replaced by the new value.
|
||||||
|
* @param key - the key to insert.
|
||||||
|
* @param value - the value to insert. Cannot be NULL.
|
||||||
|
* @throws RuntimeException If the reflection machinery failed.
|
||||||
|
*/
|
||||||
|
public void put(int key, Object value) {
|
||||||
|
Preconditions.checkNotNull(value, "value cannot be NULL.");
|
||||||
|
|
||||||
|
initializePutMethod();
|
||||||
|
putInternal(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a value must be inserted into the underlying map, regardless of preconditions.
|
||||||
|
* @param key - the key.
|
||||||
|
* @param value - the value to insert.
|
||||||
|
*/
|
||||||
|
private void putInternal(int key, Object value) {
|
||||||
|
try {
|
||||||
|
PUT_METHOD.invoke(handle, key, value);
|
||||||
|
} 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 " + PUT_METHOD + " for" + handle, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the value associated with a specific key, or NULL if not found.
|
||||||
|
* @param key - the integer key.
|
||||||
|
* @return The associated value, or NULL.
|
||||||
|
*/
|
||||||
|
public Object get(int key) {
|
||||||
|
initializeGetMethod();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return GET_METHOD.invoke(handle, key);
|
||||||
|
} 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("Unable to invoke " + GET_METHOD + " on " + handle, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a mapping of a key to a value if it is present.
|
||||||
|
* @param key - the key of the mapping to remove.
|
||||||
|
* @return TRUE if a key-value pair was removed, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean remove(int key) {
|
||||||
|
Object prev = get(key);
|
||||||
|
|
||||||
|
if (prev != null) {
|
||||||
|
putInternal(key, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializePutMethod() {
|
||||||
|
if (PUT_METHOD == null) {
|
||||||
|
// Fairly straight forward
|
||||||
|
PUT_METHOD = FuzzyReflection.fromClass(INT_HASH_MAP).getMethod(
|
||||||
|
FuzzyMethodContract.newBuilder().
|
||||||
|
banModifier(Modifier.STATIC).
|
||||||
|
parameterCount(2).
|
||||||
|
parameterExactType(int.class).
|
||||||
|
parameterExactType(Object.class).
|
||||||
|
build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeGetMethod() {
|
||||||
|
if (GET_METHOD == null) {
|
||||||
|
WrappedIntHashMap temp = WrappedIntHashMap.newMap();
|
||||||
|
String expected = "hello";
|
||||||
|
|
||||||
|
// Initialize a value
|
||||||
|
temp.put(1, expected);
|
||||||
|
|
||||||
|
// Determine which method to trust
|
||||||
|
for (Method method : FuzzyReflection.fromClass(INT_HASH_MAP).
|
||||||
|
getMethodListByParameters(Object.class, new Class<?>[] { int.class })) {
|
||||||
|
|
||||||
|
// Skip static methods
|
||||||
|
if (Modifier.isStatic(method.getModifiers()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// See if we found the method we are looking for
|
||||||
|
if (expected.equals(method.invoke(temp.getHandle(), 1))) {
|
||||||
|
GET_METHOD = method;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Suppress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Unable to find appropriate GET_METHOD for IntHashMap.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the underlying IntHashMap object.
|
||||||
|
* @return The underlying object.
|
||||||
|
*/
|
||||||
|
public Object getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,8 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import net.minecraft.server.v1_6_R2.IntHashMap;
|
||||||
|
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
|
import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
@ -28,6 +30,11 @@ public class StreamSerializerTest {
|
|||||||
BukkitInitialization.initializeItemMeta();
|
BukkitInitialization.initializeItemMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMinecraftReflection() {
|
||||||
|
assertEquals(IntHashMap.class, MinecraftReflection.getIntHashMapClass());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSerializer() throws IOException {
|
public void testSerializer() throws IOException {
|
||||||
ItemStack before = new ItemStack(Material.GOLD_AXE);
|
ItemStack before = new ItemStack(Material.GOLD_AXE);
|
||||||
|
Loading…
Reference in New Issue
Block a user