From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Thu, 30 Apr 2020 16:56:31 +0200 Subject: [PATCH] Add Raw Byte ItemStack Serialization Serializes using NBT which is safer for server data migrations than bukkits format. Co-authored-by: Nassim Jahnke diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java index 688fccdbc5cf831008ef2f27db9d15b0921a7561..e4861a8be534bfeae0385f0197261fa6ec1e7bc0 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java @@ -151,5 +151,9 @@ public interface UnsafeValues { default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); } + + byte[] serializeItem(ItemStack item); + + ItemStack deserializeItem(byte[] data); // Paper end } diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java index fd3d4dd231d756d51db0155a4c3ad970c4f456ed..a9ca792de95236535f8b6779fce2875e6c3bd2a9 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable; * returns false. */ public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource { // Paper + private static final byte ARRAY_SERIALIZATION_VERSION = 1; // Paper private Material type = Material.AIR; private int amount = 0; private MaterialData data = null; @@ -650,6 +651,97 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat return Bukkit.getServer().getItemFactory().ensureServerConversions(this); } + /** + * Deserializes this itemstack from raw NBT bytes. NBT is safer for data migrations as it will + * use the built in data converter instead of bukkits dangerous serialization system. + * + * This expects that the DataVersion was stored on the root of the Compound, as saved from + * the {@link #serializeAsBytes()} API returned. + * @param bytes bytes representing an item in NBT + * @return ItemStack migrated to this version of Minecraft if needed. + */ + @NotNull + public static ItemStack deserializeBytes(@NotNull byte[] bytes) { + return org.bukkit.Bukkit.getUnsafe().deserializeItem(bytes); + } + + /** + * Serializes this itemstack to raw bytes in NBT. NBT is safer for data migrations as it will + * use the built in data converter instead of bukkits dangerous serialization system. + * @return bytes representing this item in NBT. + */ + @NotNull + public byte[] serializeAsBytes() { + return org.bukkit.Bukkit.getUnsafe().serializeItem(this); + } + + /** + * Serializes a collection of items to raw bytes in NBT. + *

+ * If you need a string representation to put into a file, you can for example use {@link java.util.Base64} encoding. + * + * @param items items to serialize + * @return bytes representing the items in NBT + * @see #serializeAsBytes() + */ + public static byte @NotNull [] serializeItemsAsBytes(java.util.@NotNull Collection items) { + try (final java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream()) { + final java.io.DataOutput output = new java.io.DataOutputStream(outputStream); + output.writeByte(ARRAY_SERIALIZATION_VERSION); + output.writeInt(items.size()); + for (final ItemStack item : items) { + if (item == null) { + // Ensure the correct order by including empty/null items + output.writeInt(0); + continue; + } + + final byte[] itemBytes = item.serializeAsBytes(); + output.writeInt(itemBytes.length); + output.write(itemBytes); + } + return outputStream.toByteArray(); + } catch (final java.io.IOException e) { + throw new RuntimeException("Error while writing itemstack", e); + } + } + + /** + * Deserializes this itemstack from raw NBT bytes. + *

+ * If you need a string representation to put into a file, you can for example use {@link java.util.Base64} encoding. + * + * @param bytes bytes representing an item in NBT + * @return ItemStack array migrated to this version of Minecraft if needed + * @see #deserializeBytes(byte[]) + */ + public static @Nullable ItemStack @NotNull [] deserializeItemsFromBytes(final byte @NotNull [] bytes) { + try (final java.io.ByteArrayInputStream inputStream = new java.io.ByteArrayInputStream(bytes)) { + final java.io.DataInputStream input = new java.io.DataInputStream(inputStream); + final byte version = input.readByte(); + if (version != ARRAY_SERIALIZATION_VERSION) { + throw new IllegalArgumentException("Unsupported version or bad data: " + version); + } + + final int count = input.readInt(); + final ItemStack[] items = new ItemStack[count]; + for (int i = 0; i < count; i++) { + final int length = input.readInt(); + if (length == 0) { + // Empty item, keep entry as null + continue; + } + + final byte[] itemBytes = new byte[length]; + input.read(itemBytes); + items[i] = ItemStack.deserializeBytes(itemBytes); + } + return items; + } catch (final java.io.IOException e) { + throw new RuntimeException("Error while reading itemstack", e); + } + } + /** * Gets the Display name as seen in the Client. * Currently the server only supports the English language. To override this, diff --git a/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java b/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java index 0f8eb97bd5e2f8b0f0cc03f7c4342aae06c4520c..def243b303b23aa42efcdbb280a29d4a877af3f8 100644 --- a/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java +++ b/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java @@ -3,8 +3,10 @@ package org.bukkit.util.io; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; +import java.util.Collection; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.inventory.ItemStack; /** * This class is designed to be used in conjunction with the {@link @@ -14,7 +16,9 @@ import org.bukkit.configuration.serialization.ConfigurationSerialization; *

* Behavior of implementations extending this class is not guaranteed across * future versions. + * @deprecated Object streams on their own are not safe. For safer and more consistent serialization of items, use {@link ItemStack#deserializeBytes(byte[])} or {@link ItemStack#deserializeItemsFromBytes(byte[])}. */ +@Deprecated // Paper public class BukkitObjectInputStream extends ObjectInputStream { /** diff --git a/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java b/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java index dd1b9ee5f57773f07924aa311823fd8d63195cb2..bd4d48105457dbc6226f84e0e8d1e22878e01ca3 100644 --- a/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java +++ b/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java @@ -4,7 +4,9 @@ import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; +import java.util.Collection; import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.inventory.ItemStack; /** * This class is designed to be used in conjunction with the {@link @@ -14,7 +16,9 @@ import org.bukkit.configuration.serialization.ConfigurationSerializable; *

* Behavior of implementations extending this class is not guaranteed across * future versions. + * @deprecated Object streams on their own are not safe. For safer and more consistent serialization of items, use {@link ItemStack#serializeAsBytes()} or {@link ItemStack#serializeItemsAsBytes(Collection)}. */ +@Deprecated // Paper public class BukkitObjectOutputStream extends ObjectOutputStream { /**