From 398b1bc3be3158e4304d564403dc12fb88d3e328 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 4 Dec 2013 17:48:17 +0100 Subject: [PATCH] Add support for serializing and deserializing NBT tags in 1.7.2 --- .../protocol/utility/ClassSource.java | 16 ++ .../protocol/utility/MinecraftReflection.java | 168 ++++++++++++++---- .../wrappers/nbt/io/NbtBinarySerializer.java | 27 ++- .../protocol/events/PacketContainerTest.java | 1 - 4 files changed, 173 insertions(+), 39 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ClassSource.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ClassSource.java index a9582f72..ad0b0750 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ClassSource.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ClassSource.java @@ -1,5 +1,7 @@ package com.comphenix.protocol.utility; +import java.util.Map; + /** * Represents an abstract class loader that can only retrieve classes by their canonical name. * @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> map) { + return new ClassSource() { + @Override + public Class loadClass(String canonicalName) throws ClassNotFoundException { + return map.get(canonicalName); + } + }; + } /** * Retrieve a class by name. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index f611835a..82c608f6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -33,6 +33,8 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javassist.bytecode.CodeAttribute.RuntimeCopyException; + import javax.annotation.Nonnull; 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.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.packet.PacketRegistry; +import com.comphenix.protocol.reflect.ClassAnalyser; 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.EmptyMethodVisitor; import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; @@ -107,8 +111,9 @@ public class MinecraftReflection { private static String MINECRAFT_FULL_PACKAGE = null; private static String CRAFTBUKKIT_PACKAGE = null; - private static CachedPackage minecraftPackage; - private static CachedPackage craftbukkitPackage; + // Package private for the purpose of unit testing + static CachedPackage minecraftPackage; + static CachedPackage craftbukkitPackage; // org.bukkit.craftbukkit private static Constructor craftNMSConstructor; @@ -561,22 +566,35 @@ public class MinecraftReflection { try { return getMinecraftClass("Packet"); } catch (RuntimeException e) { + FuzzyClassContract paketContract = null; + // What kind of class we're looking for (sanity check) - FuzzyClassContract paketContract = - FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Map.class). - requireModifier(Modifier.STATIC)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Set.class). - requireModifier(Modifier.STATIC)). + if (isUsingNetty()) { + paketContract = FuzzyClassContract.newBuilder(). method(FuzzyMethodContract.newBuilder(). - parameterSuperOf(DataInputStream.class). + parameterDerivedOf(ByteBuf.class). 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 - Method selected = FuzzyReflection.fromClass(getNetHandlerClass()). + Method selected = FuzzyReflection.fromClass(getNetServerHandlerClass()). getMethod(FuzzyMethodContract.newBuilder(). parameterMatches(paketContract, 0). parameterCount(1). @@ -715,11 +733,39 @@ public class MinecraftReflection { try { return getMinecraftClass("NetServerHandler", "PlayerConnection"); } catch (RuntimeException e) { - // Use the player connection field - return setMinecraftClass("NetServerHandler", - FuzzyReflection.fromClass(getEntityPlayerClass()). - getFieldByType("playerConnection", getNetHandlerClass()).getType() - ); + try { + // Use the player connection field + return setMinecraftClass("NetServerHandler", + 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 { return getMinecraftClass("NetHandler", "Connection"); } catch (RuntimeException e) { + // Try getting the net login handler return setMinecraftClass("NetHandler", getNetLoginHandlerClass().getSuperclass()); } } @@ -948,23 +995,46 @@ public class MinecraftReflection { try { return getMinecraftClass("NBTBase"); } catch (RuntimeException e) { - FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder(). - constructor(FuzzyMethodContract.newBuilder(). - parameterExactType(String.class). - parameterCount(1)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Map.class)). - build(); + Class nbtBase = null; - 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(). - requireModifier(Modifier.STATIC). - parameterSuperOf(DataInputStream.class). - parameterCount(1). - returnTypeMatches(tagCompoundContract). - build() - ); - Class nbtBase = selected.getReturnType().getSuperclass(); + requireModifier(Modifier.STATIC). + parameterSuperOf(DataInputStream.class). + parameterCount(1). + returnTypeMatches(tagCompoundContract). + build() + ); + nbtBase = selected.getReturnType().getSuperclass(); + } // That can't be correct 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. * @param buffer - the buffer. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java index 9f2c477a..2db13f5b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java @@ -33,8 +33,9 @@ public class NbtBinarySerializer { Class base = MinecraftReflection.getNBTBaseClass(); // Use the base class - methodWrite = FuzzyReflection.fromClass(base). + methodWrite = getUtilityClass(). getMethodByParameters("writeNBT", base, DataOutput.class); + methodWrite.setAccessible(true); } try { @@ -52,19 +53,37 @@ public class NbtBinarySerializer { public NbtWrapper deserialize(DataInput source) { if (methodLoad == null) { Class base = MinecraftReflection.getNBTBaseClass(); + Class[] params = MinecraftReflection.isUsingNetty() ? + new Class[] { DataInput.class, int.class } : + new Class[] { DataInput.class }; // Use the base class - methodLoad = FuzzyReflection.fromClass(base). - getMethodByParameters("load", base, new Class[] { DataInput.class }); + methodLoad = getUtilityClass().getMethodByParameters("load", base, params); + methodLoad.setAccessible(true); } 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) { 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. * @param source - the input stream. diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 3a8125e0..ddfa422c 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -56,7 +56,6 @@ import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; // Ensure that the CraftItemFactory is mockable