From aa9d84c639f110bad1c65270fcd1599e1a7076cb Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 17 Jul 2013 20:31:25 +0200 Subject: [PATCH] Added the ability to serialize and deserialize NbtCompounds. --- .../protocol/utility/StreamSerializer.java | 67 ++++++++++++++++++- .../protocol/wrappers/nbt/NbtFactory.java | 13 ++++ .../utility/StreamSerializerTest.java | 20 ++++++ 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java index 9cf083f3..60d898ce 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java @@ -8,7 +8,6 @@ import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Method; - import javax.annotation.Nonnull; import org.bukkit.inventory.ItemStack; @@ -16,6 +15,8 @@ import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; /** * Utility methods for reading and writing Minecraft objects to streams. @@ -27,6 +28,9 @@ public class StreamSerializer { private static Method READ_ITEM_METHOD; private static Method WRITE_ITEM_METHOD; + private static Method READ_NBT_METHOD; + private static Method WRITE_NBT_METHOD; + private static Method READ_STRING_METHOD; private static Method WRITE_STRING_METHOD; @@ -61,6 +65,31 @@ public class StreamSerializer { throw new IOException("Cannot read item stack.", e); } } + + /** + * Read or deserialize an NBT compound from a input stream. + * @param input - the target input stream. + * @return The resulting compound, or NULL. + * @throws IOException If the operation failed due to reflection or corrupt data. + */ + public NbtCompound deserializeCompound(@Nonnull DataInputStream input) throws IOException { + if (input == null) + throw new IllegalArgumentException("Input stream cannot be NULL."); + if (READ_NBT_METHOD == null) { + READ_NBT_METHOD = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(1). + parameterDerivedOf(DataInput.class). + returnDerivedOf(MinecraftReflection.getNBTBaseClass()). + build()); + } + try { + // Convert back to an NBT Compound + return NbtFactory.fromNMSCompound(READ_NBT_METHOD.invoke(null, input)); + } catch (Exception e) { + throw new IOException("Cannot read item stack.", e); + } + } /** * Deserialize a string using the standard Minecraft UTF-16 encoding. @@ -68,7 +97,7 @@ public class StreamSerializer { * Note that strings cannot exceed 32767 characters, regardless if maximum lenght. * @param input - the input stream. * @param maximumLength - the maximum lenght of the string. - * @return + * @return The deserialized string. * @throws IOException */ public String deserializeString(@Nonnull DataInputStream input, int maximumLength) throws IOException { @@ -144,6 +173,40 @@ public class StreamSerializer { } } + /** + * Write or serialize a NBT compound to the given output stream. + *

+ * Note: An NBT compound can be written to a stream even if it's NULL. + * + * @param output - the target output stream. + * @param stack - the NBT compound to be serialized, or NULL to represent nothing. + * @throws IOException If the operation fails due to reflection problems. + */ + public void serializeCompound(@Nonnull DataOutputStream output, NbtCompound compound) throws IOException { + if (output == null) + throw new IllegalArgumentException("Output stream cannot be NULL."); + + // Get the NMS version of the compound + Object handle = compound != null ? NbtFactory.fromBase(compound).getHandle() : null; + + if (WRITE_NBT_METHOD == null) { + WRITE_NBT_METHOD = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(2). + parameterDerivedOf(MinecraftReflection.getNBTBaseClass(), 0). + parameterDerivedOf(DataOutput.class, 1). + returnTypeVoid(). + build()); + WRITE_NBT_METHOD.setAccessible(true); + } + + try { + WRITE_NBT_METHOD.invoke(null, handle, output); + } catch (Exception e) { + throw new IOException("Cannot write compound " + compound, e); + } + } + /** * Deserialize a string using the standard Minecraft UTF-16 encoding. *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java index 6ae15713..0d72c602 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java @@ -22,6 +22,8 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; + import org.bukkit.inventory.ItemStack; import com.comphenix.protocol.reflect.FieldAccessException; @@ -163,6 +165,17 @@ public class NbtFactory { return partial; } + /** + * Retrieve the NBT compound from a given NMS handle. + * @param handle - the underlying net.minecraft.server object to wrap. + * @return A NBT compound wrapper + */ + public static NbtCompound fromNMSCompound(@Nonnull Object handle) { + if (handle == null) + throw new IllegalArgumentException("handle cannot be NULL."); + return (NbtCompound) NbtFactory.>>fromNMS(handle); + } + /** * Constructs a NBT tag of type string. * @param name - name of the tag. diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java index 14c1a11d..f67225e1 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java @@ -17,6 +17,8 @@ import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; @RunWith(org.powermock.modules.junit4.PowerMockRunner.class) @PrepareForTest(CraftItemFactory.class) @@ -53,4 +55,22 @@ public class StreamSerializerTest { assertEquals(initial, deserialized); } + + @Test + public void testCompound() throws IOException { + StreamSerializer serializer = new StreamSerializer(); + NbtCompound initial = NbtFactory.ofCompound("tag"); + initial.put("name", "Ole"); + initial.put("age", 20); + + // Buffer + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + serializer.serializeCompound(new DataOutputStream(buffer), initial); + + DataInputStream input = new DataInputStream( + new ByteArrayInputStream(buffer.toByteArray())); + NbtCompound deserialized = serializer.deserializeCompound(input); + + assertEquals(initial, deserialized); + } }