Add ItemStack array serialization methods (#10387)

This commit is contained in:
Nassim Jahnke 2024-09-22 21:02:06 +02:00 committed by GitHub
parent 1cb2bf466f
commit 81d94483a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 139 additions and 18 deletions

View file

@ -5,6 +5,8 @@ Subject: [PATCH] Add Raw Byte ItemStack Serialization
Serializes using NBT which is safer for server data migrations than bukkits format. Serializes using NBT which is safer for server data migrations than bukkits format.
Co-authored-by: Nassim Jahnke <nassim@njahnke.dev>
diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
index 6e67fdb091a006d2d13bc2d93db4d55348af4c8f..e41d5d9b810c8816cd0d1eba5fd1ea56252fb0df 100644 index 6e67fdb091a006d2d13bc2d93db4d55348af4c8f..e41d5d9b810c8816cd0d1eba5fd1ea56252fb0df 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java --- a/src/main/java/org/bukkit/UnsafeValues.java
@ -20,10 +22,10 @@ index 6e67fdb091a006d2d13bc2d93db4d55348af4c8f..e41d5d9b810c8816cd0d1eba5fd1ea56
// Paper end // Paper end
} }
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index e9c29fc1db686b80bc2477d78ec2b361b8600b9e..4b4c364dad70126dee17aabca2c3da9f148dd6d0 100644 index e9c29fc1db686b80bc2477d78ec2b361b8600b9e..dda3a421953761dfca5f2f6498905c43fb6fe37d 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java --- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -661,6 +661,30 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -661,6 +661,117 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
return Bukkit.getServer().getItemFactory().ensureServerConversions(this); return Bukkit.getServer().getItemFactory().ensureServerConversions(this);
} }
@ -50,7 +52,126 @@ index e9c29fc1db686b80bc2477d78ec2b361b8600b9e..4b4c364dad70126dee17aabca2c3da9f
+ public byte[] serializeAsBytes() { + public byte[] serializeAsBytes() {
+ return org.bukkit.Bukkit.getUnsafe().serializeItem(this); + return org.bukkit.Bukkit.getUnsafe().serializeItem(this);
+ } + }
+
+ /**
+ * The current version byte of the item array format used in {@link #serializeItemsAsBytes(java.util.Collection)}
+ * and {@link #deserializeItemsFromBytes(byte[])} respectively.
+ */
+ private static final byte ARRAY_SERIALIZATION_VERSION = 1;
+
+ /**
+ * Serializes a collection of items to raw bytes in NBT. Serializes empty items as null.
+ * <p>
+ * 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<ItemStack> 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 || item.isEmpty()) {
+ // 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);
+ }
+ }
+
+ /**
+ * Serializes a collection of items to raw bytes in NBT. Serializes empty items as null.
+ * <p>
+ * 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(@Nullable ItemStack @NotNull [] items) {
+ return serializeItemsAsBytes(java.util.Arrays.asList(items));
+ }
+
+ /**
+ * Deserializes this itemstack from raw NBT bytes.
+ * <p>
+ * 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 @NotNull 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 empty
+ items[i] = ItemStack.empty();
+ 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. * Gets the Display name as seen in the Client.
* Currently the server only supports the English language. To override this, * 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..6c074ff2dcfc279657037013b9b54890d7c8a533 100644
--- a/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java
+++ b/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java
@@ -14,7 +14,11 @@ import org.bukkit.configuration.serialization.ConfigurationSerialization;
* <p>
* 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 org.bukkit.inventory.ItemStack#serializeAsBytes()} or
+ * {@link org.bukkit.inventory.ItemStack#serializeItemsAsBytes(java.util.Collection)}.
*/
+@Deprecated(since = "1.21") // 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..4e24e7c73271a579db2c4309951693dfb2fecceb 100644
--- a/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java
+++ b/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java
@@ -14,7 +14,11 @@ import org.bukkit.configuration.serialization.ConfigurationSerializable;
* <p>
* 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 org.bukkit.inventory.ItemStack#serializeAsBytes()} or
+ * {@link org.bukkit.inventory.ItemStack#serializeItemsAsBytes(java.util.Collection)}.
*/
+@Deprecated(since = "1.21") // Paper
public class BukkitObjectOutputStream extends ObjectOutputStream {
/**

View file

@ -526,7 +526,7 @@ index 5bd252c0ae3b09fe141d131360c67bb9bfbf5422..78587d9fabe6371a23a7963917b054db
+ +
} }
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 4b4c364dad70126dee17aabca2c3da9f148dd6d0..ef36a696f2f926d70c5d93ce08f75fa9a749e32f 100644 index 5707d038bdc41448bca7d6ebf4ef5b378809f5a8..69e7cbe49ff2388dea0214f6cb26f36f29ec1453 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java --- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -27,7 +27,7 @@ import org.jetbrains.annotations.Nullable; @@ -27,7 +27,7 @@ import org.jetbrains.annotations.Nullable;
@ -546,7 +546,7 @@ index 4b4c364dad70126dee17aabca2c3da9f148dd6d0..ef36a696f2f926d70c5d93ce08f75fa9
public String getTranslationKey() { public String getTranslationKey() {
return Bukkit.getUnsafe().getTranslationKey(this); return Bukkit.getUnsafe().getTranslationKey(this);
} }
@@ -895,5 +896,16 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -982,5 +983,16 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
ItemMeta itemMeta = getItemMeta(); ItemMeta itemMeta = getItemMeta();
return itemMeta != null && itemMeta.hasItemFlag(flag); return itemMeta != null && itemMeta.hasItemFlag(flag);
} }

View file

@ -110,10 +110,10 @@ index e7931f73f10fe35ebd5fe4a04b036d53bb117ebd..cbce835ed6d44e5b8c9aaae4e36a77f8
+ // Paper end + // Paper end
} }
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index ef36a696f2f926d70c5d93ce08f75fa9a749e32f..fb1d7ff51f267225ac76c29a56d67fb443d3a92f 100644 index 69e7cbe49ff2388dea0214f6cb26f36f29ec1453..d2af613c56010f3b0dd0d3ff7b438193127353d0 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java --- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -907,5 +907,17 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -994,5 +994,17 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
public @NotNull String translationKey() { public @NotNull String translationKey() {
return Bukkit.getUnsafe().getTranslationKey(this); return Bukkit.getUnsafe().getTranslationKey(this);
} }

View file

@ -25,10 +25,10 @@ index 51473ffbec65a2344449daa8ff5cf535b0b60520..07669aad6d9910174fbc8fdf3cdd5421
// Paper end // Paper end
} }
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index fb1d7ff51f267225ac76c29a56d67fb443d3a92f..b38154b45935ec45154e89277a8c2b1b9e46522d 100644 index d2af613c56010f3b0dd0d3ff7b438193127353d0..5b84aec2897b35da3e1bee8ac73fba5c83717d5d 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java --- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -919,5 +919,27 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -1006,5 +1006,27 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
public io.papermc.paper.inventory.ItemRarity getRarity() { public io.papermc.paper.inventory.ItemRarity getRarity() {
return io.papermc.paper.inventory.ItemRarity.valueOf(this.getItemMeta().getRarity().name()); return io.papermc.paper.inventory.ItemRarity.valueOf(this.getItemMeta().getRarity().name());
} }

View file

@ -66,10 +66,10 @@ index 86c5ceddc722d28261f8a6d8368400fe2731aaf0..9f3e2903c955f2a5d1b25825c49188df
+ // Paper end - ItemStack damage API + // Paper end - ItemStack damage API
} }
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index ab74890e9b6a13b76756f884d6d176bb45470191..659191a226cae406a14c67cc0992f7026f6188e6 100644 index a20f3a0894b700c235da1b8e1481062014054585..5c02a5fe37ea09502ca6c93d637a8ef5e4392ad4 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java --- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -1003,5 +1003,19 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -1090,5 +1090,19 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
public boolean canRepair(@NotNull ItemStack toBeRepaired) { public boolean canRepair(@NotNull ItemStack toBeRepaired) {
return Bukkit.getUnsafe().isValidRepairItemStack(toBeRepaired, this); return Bukkit.getUnsafe().isValidRepairItemStack(toBeRepaired, this);
} }

View file

@ -6,10 +6,10 @@ Subject: [PATCH] Allow proper checking of empty item stacks
This adds a method to check if an item stack is empty or not. This mirrors vanilla's implementation of the same method. This adds a method to check if an item stack is empty or not. This mirrors vanilla's implementation of the same method.
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 659191a226cae406a14c67cc0992f7026f6188e6..5b918d510b9c8a6f8c6d146e90e1d0ef4a204b5a 100644 index 5c02a5fe37ea09502ca6c93d637a8ef5e4392ad4..8ded653f2b0549ebbe1a84d50ff0f3c85ddd07b7 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java --- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -1017,5 +1017,24 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -1104,5 +1104,24 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
public @NotNull ItemStack damage(int amount, @NotNull org.bukkit.entity.LivingEntity livingEntity) { public @NotNull ItemStack damage(int amount, @NotNull org.bukkit.entity.LivingEntity livingEntity) {
return livingEntity.damageItemStack(this, amount); return livingEntity.damageItemStack(this, amount);
} }

View file

@ -119,10 +119,10 @@ index 141d5a964cc299284aecd4d34d57008a32f94247..31217b38e769f97801fa1afefeb223d1
+ @NotNull java.util.List<net.kyori.adventure.text.Component> computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines + @NotNull java.util.List<net.kyori.adventure.text.Component> computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines
} }
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 13d035ace9fbe93c3754595ac6cadbfbe30062a5..718070359c644de65c8fc2b34ad39913525d18c6 100644 index 43b8823d4af93febbd60d1b16b406a665373bacd..268baface3b58fba4f7c8a4d3f1b370fa1d26cee 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java --- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -1037,4 +1037,21 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -1124,4 +1124,21 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
return type.isAir() || amount <= 0; return type.isAir() || amount <= 0;
} }
// Paper end // Paper end

View file

@ -16,7 +16,7 @@ index 9bdba60fa96edbc4be5dcf54a815579db887048b..330e3013eda204aa9b33d5e1c3104e0b
+ ItemStack createEmptyStack(); // Paper - proxy ItemStack + ItemStack createEmptyStack(); // Paper - proxy ItemStack
} }
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 1d2ffdf88daa9186993c69c5ab2b96520b41920b..4fe68a22576933676271f841a5af0ee45b90216f 100644 index e80261e0e66e6d3969513593dbbf3d890158676c..9464c27fe536fc5765d945631215cbe90fd5fa47 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java --- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -28,10 +28,38 @@ import org.jetbrains.annotations.Nullable; @@ -28,10 +28,38 @@ import org.jetbrains.annotations.Nullable;
@ -412,7 +412,7 @@ index 1d2ffdf88daa9186993c69c5ab2b96520b41920b..4fe68a22576933676271f841a5af0ee4
@Override @Override
@NotNull @NotNull
@@ -807,11 +747,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -894,11 +834,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
} }
public int getMaxItemUseDuration(@NotNull final org.bukkit.entity.LivingEntity entity) { public int getMaxItemUseDuration(@NotNull final org.bukkit.entity.LivingEntity entity) {
@ -425,7 +425,7 @@ index 1d2ffdf88daa9186993c69c5ab2b96520b41920b..4fe68a22576933676271f841a5af0ee4
} }
/** /**
@@ -1061,7 +997,8 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -1148,7 +1084,8 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
*/ */
@NotNull @NotNull
public static ItemStack empty() { public static ItemStack empty() {
@ -435,7 +435,7 @@ index 1d2ffdf88daa9186993c69c5ab2b96520b41920b..4fe68a22576933676271f841a5af0ee4
} }
/** /**
@@ -1069,7 +1006,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat @@ -1156,7 +1093,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
* it is either air or the stack has a size of 0. * it is either air or the stack has a size of 0.
*/ */
public boolean isEmpty() { public boolean isEmpty() {