Archiviert
13
0

Add support for serializing and deserializing NBT tags in 1.7.2

Dieser Commit ist enthalten in:
Kristian S. Stangeland 2013-12-04 17:48:17 +01:00
Ursprung 65e665aa59
Commit 398b1bc3be
4 geänderte Dateien mit 173 neuen und 39 gelöschten Zeilen

Datei anzeigen

@ -1,5 +1,7 @@
package com.comphenix.protocol.utility; package com.comphenix.protocol.utility;
import java.util.Map;
/** /**
* Represents an abstract class loader that can only retrieve classes by their canonical name. * Represents an abstract class loader that can only retrieve classes by their canonical name.
* @author Kristian * @author Kristian
@ -26,6 +28,20 @@ abstract class ClassSource {
} }
}; };
} }
/**
* Construct a class source from a mapping of canonical names and the corresponding classes.
* @param map - map of class names and classes.
* @return The class source.
*/
public static ClassSource fromMap(final Map<String, Class<?>> map) {
return new ClassSource() {
@Override
public Class<?> loadClass(String canonicalName) throws ClassNotFoundException {
return map.get(canonicalName);
}
};
}
/** /**
* Retrieve a class by name. * Retrieve a class by name.

Datei anzeigen

@ -33,6 +33,8 @@ import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javassist.bytecode.CodeAttribute.RuntimeCopyException;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import net.minecraft.util.io.netty.buffer.ByteBuf; import net.minecraft.util.io.netty.buffer.ByteBuf;
@ -51,7 +53,9 @@ import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.ClassAnalyser;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod;
import com.comphenix.protocol.reflect.compiler.EmptyClassVisitor; import com.comphenix.protocol.reflect.compiler.EmptyClassVisitor;
import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor; import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
@ -107,8 +111,9 @@ public class MinecraftReflection {
private static String MINECRAFT_FULL_PACKAGE = null; private static String MINECRAFT_FULL_PACKAGE = null;
private static String CRAFTBUKKIT_PACKAGE = null; private static String CRAFTBUKKIT_PACKAGE = null;
private static CachedPackage minecraftPackage; // Package private for the purpose of unit testing
private static CachedPackage craftbukkitPackage; static CachedPackage minecraftPackage;
static CachedPackage craftbukkitPackage;
// org.bukkit.craftbukkit // org.bukkit.craftbukkit
private static Constructor<?> craftNMSConstructor; private static Constructor<?> craftNMSConstructor;
@ -561,22 +566,35 @@ public class MinecraftReflection {
try { try {
return getMinecraftClass("Packet"); return getMinecraftClass("Packet");
} catch (RuntimeException e) { } catch (RuntimeException e) {
FuzzyClassContract paketContract = null;
// What kind of class we're looking for (sanity check) // What kind of class we're looking for (sanity check)
FuzzyClassContract paketContract = if (isUsingNetty()) {
FuzzyClassContract.newBuilder(). paketContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Map.class).
requireModifier(Modifier.STATIC)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Set.class).
requireModifier(Modifier.STATIC)).
method(FuzzyMethodContract.newBuilder(). method(FuzzyMethodContract.newBuilder().
parameterSuperOf(DataInputStream.class). parameterDerivedOf(ByteBuf.class).
returnTypeVoid()). returnTypeVoid()).
build(); method(FuzzyMethodContract.newBuilder().
parameterDerivedOf(ByteBuf.class, 0).
parameterExactType(byte[].class, 1).
returnTypeVoid()).
build();
} else {
paketContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Map.class).
requireModifier(Modifier.STATIC)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Set.class).
requireModifier(Modifier.STATIC)).
method(FuzzyMethodContract.newBuilder().
parameterSuperOf(DataInputStream.class).
returnTypeVoid()).
build();
}
// Select a method with one Minecraft object parameter // Select a method with one Minecraft object parameter
Method selected = FuzzyReflection.fromClass(getNetHandlerClass()). Method selected = FuzzyReflection.fromClass(getNetServerHandlerClass()).
getMethod(FuzzyMethodContract.newBuilder(). getMethod(FuzzyMethodContract.newBuilder().
parameterMatches(paketContract, 0). parameterMatches(paketContract, 0).
parameterCount(1). parameterCount(1).
@ -715,11 +733,39 @@ public class MinecraftReflection {
try { try {
return getMinecraftClass("NetServerHandler", "PlayerConnection"); return getMinecraftClass("NetServerHandler", "PlayerConnection");
} catch (RuntimeException e) { } catch (RuntimeException e) {
// Use the player connection field try {
return setMinecraftClass("NetServerHandler", // Use the player connection field
FuzzyReflection.fromClass(getEntityPlayerClass()). return setMinecraftClass("NetServerHandler",
getFieldByType("playerConnection", getNetHandlerClass()).getType() FuzzyReflection.fromClass(getEntityPlayerClass()).
); getFieldByType("playerConnection", getNetHandlerClass()).getType()
);
} catch (RuntimeException e1) {
// Okay, this must be on 1.7.2
Class<?> playerClass = getEntityPlayerClass();
FuzzyClassContract playerConnection = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().typeExact(playerClass).build()).
constructor(FuzzyMethodContract.newBuilder().
parameterCount(3).
parameterSuperOf(getMinecraftServerClass(), 0).
parameterSuperOf(getEntityPlayerClass(), 2).
build()
).
method(FuzzyMethodContract.newBuilder().
parameterCount(1).
parameterExactType(String.class).
build()
).
build();
// If not, use duck typing
Class<?> fieldType = FuzzyReflection.fromClass(getEntityPlayerClass(), true).getField(
FuzzyFieldContract.newBuilder().typeMatches(playerConnection).build()
).getType();
return setMinecraftClass("NetServerHandler", fieldType);
}
} }
} }
@ -751,6 +797,7 @@ public class MinecraftReflection {
try { try {
return getMinecraftClass("NetHandler", "Connection"); return getMinecraftClass("NetHandler", "Connection");
} catch (RuntimeException e) { } catch (RuntimeException e) {
// Try getting the net login handler
return setMinecraftClass("NetHandler", getNetLoginHandlerClass().getSuperclass()); return setMinecraftClass("NetHandler", getNetLoginHandlerClass().getSuperclass());
} }
} }
@ -948,23 +995,46 @@ public class MinecraftReflection {
try { try {
return getMinecraftClass("NBTBase"); return getMinecraftClass("NBTBase");
} catch (RuntimeException e) { } catch (RuntimeException e) {
FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder(). Class<?> nbtBase = null;
constructor(FuzzyMethodContract.newBuilder().
parameterExactType(String.class).
parameterCount(1)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Map.class)).
build();
Method selected = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). if (isUsingNetty()) {
FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Map.class)).
method(FuzzyMethodContract.newBuilder().
parameterDerivedOf(DataOutput.class).
parameterCount(1)).
build();
Method selected = FuzzyReflection.fromClass(getPacketDataSerializerClass()).
getMethod(FuzzyMethodContract.newBuilder().
banModifier(Modifier.STATIC).
parameterCount(1).
parameterMatches(tagCompoundContract).
returnTypeVoid().
build()
);
nbtBase = selected.getParameterTypes()[0].getSuperclass();
} else {
FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder().
constructor(FuzzyMethodContract.newBuilder().
parameterExactType(String.class).
parameterCount(1)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Map.class)).
build();
Method selected = FuzzyReflection.fromClass(getPacketClass()).
getMethod(FuzzyMethodContract.newBuilder(). getMethod(FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC). requireModifier(Modifier.STATIC).
parameterSuperOf(DataInputStream.class). parameterSuperOf(DataInputStream.class).
parameterCount(1). parameterCount(1).
returnTypeMatches(tagCompoundContract). returnTypeMatches(tagCompoundContract).
build() build()
); );
Class<?> nbtBase = selected.getReturnType().getSuperclass(); nbtBase = selected.getReturnType().getSuperclass();
}
// That can't be correct // That can't be correct
if (nbtBase == null || nbtBase.equals(Object.class)) { if (nbtBase == null || nbtBase.equals(Object.class)) {
@ -1201,6 +1271,36 @@ public class MinecraftReflection {
} }
} }
/**
* Retrieve the NBTCompressedStreamTools class.
* @return The NBTCompressedStreamTools class.
*/
public static Class<?> getNbtCompressedStreamToolsClass() {
try {
return getMinecraftClass("NBTCompressedStreamTools");
} catch (RuntimeException e) {
Class<?> packetSerializer = getPacketDataSerializerClass();
// Get the write NBT compound method
Method writeNbt = FuzzyReflection.fromClass(packetSerializer).
getMethodByParameters("writeNbt", getNBTCompoundClass());
try {
// Now -- we inspect all the method calls within that method, and use the first foreign Minecraft class
for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(writeNbt)) {
Class<?> owner = MinecraftReflection.class.getClassLoader().loadClass(method.getOwnerClass().replace('/', '.'));
if (!packetSerializer.equals(owner) && isMinecraftClass(owner)) {
return setMinecraftClass("NBTCompressedStreamTools", owner);
}
}
} catch (Exception e1) {
throw new RuntimeException("Unable to analyse class.", e1);
}
throw new IllegalArgumentException("Unable to find NBTCompressedStreamTools.");
}
}
/** /**
* Retrieve an instance of the packet data serializer wrapper. * Retrieve an instance of the packet data serializer wrapper.
* @param buffer - the buffer. * @param buffer - the buffer.

Datei anzeigen

@ -33,8 +33,9 @@ public class NbtBinarySerializer {
Class<?> base = MinecraftReflection.getNBTBaseClass(); Class<?> base = MinecraftReflection.getNBTBaseClass();
// Use the base class // Use the base class
methodWrite = FuzzyReflection.fromClass(base). methodWrite = getUtilityClass().
getMethodByParameters("writeNBT", base, DataOutput.class); getMethodByParameters("writeNBT", base, DataOutput.class);
methodWrite.setAccessible(true);
} }
try { try {
@ -52,19 +53,37 @@ public class NbtBinarySerializer {
public <TType> NbtWrapper<TType> deserialize(DataInput source) { public <TType> NbtWrapper<TType> deserialize(DataInput source) {
if (methodLoad == null) { if (methodLoad == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass(); Class<?> base = MinecraftReflection.getNBTBaseClass();
Class<?>[] params = MinecraftReflection.isUsingNetty() ?
new Class<?>[] { DataInput.class, int.class } :
new Class<?>[] { DataInput.class };
// Use the base class // Use the base class
methodLoad = FuzzyReflection.fromClass(base). methodLoad = getUtilityClass().getMethodByParameters("load", base, params);
getMethodByParameters("load", base, new Class<?>[] { DataInput.class }); methodLoad.setAccessible(true);
} }
try { try {
return NbtFactory.fromNMS(methodLoad.invoke(null, source), null); Object result = null;
// Invoke the correct utility method
if (MinecraftReflection.isUsingNetty())
result = methodLoad.invoke(null, source, 0);
else
result = methodLoad.invoke(null, source);
return NbtFactory.fromNMS(result, null);
} catch (Exception e) { } catch (Exception e) {
throw new FieldAccessException("Unable to read NBT from " + source, e); throw new FieldAccessException("Unable to read NBT from " + source, e);
} }
} }
private FuzzyReflection getUtilityClass() {
if (MinecraftReflection.isUsingNetty()) {
return FuzzyReflection.fromClass(MinecraftReflection.getNbtCompressedStreamToolsClass(), true);
} else {
return FuzzyReflection.fromClass(MinecraftReflection.getNBTBaseClass(), true);
}
}
/** /**
* Load an NBT compound from a stream. * Load an NBT compound from a stream.
* @param source - the input stream. * @param source - the input stream.

Datei anzeigen

@ -56,7 +56,6 @@ import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
// Ensure that the CraftItemFactory is mockable // Ensure that the CraftItemFactory is mockable