Add ItemStack array serialization methods

Serializing multiple items to bytes isn't trivial, so often beginners fall into the trap of using output IO streams and Bukkit serialization instead, so these provide helpful extra methods to deal with item arrays.
This commit is contained in:
Nassim Jahnke 2024-04-04 16:52:26 +02:00
parent e709245b55
commit c43ed966bf
No known key found for this signature in database
GPG Key ID: EF6771C01F6EF02F
10 changed files with 148 additions and 22 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.
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
index 688fccdbc5cf831008ef2f27db9d15b0921a7561..e4861a8be534bfeae0385f0197261fa6ec1e7bc0 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
@ -20,10 +22,18 @@ index 688fccdbc5cf831008ef2f27db9d15b0921a7561..e4861a8be534bfeae0385f0197261fa6
// Paper end
}
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index fd3d4dd231d756d51db0155a4c3ad970c4f456ed..1f31ff5b85217a1c631f05f43c5a65839a36b26e 100644
index fd3d4dd231d756d51db0155a4c3ad970c4f456ed..a9ca792de95236535f8b6779fce2875e6c3bd2a9 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -650,6 +650,30 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable;
* returns false.</b>
*/
public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource<net.kyori.adventure.text.event.HoverEvent.ShowItem> { // 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);
}
@ -50,7 +60,123 @@ index fd3d4dd231d756d51db0155a4c3ad970c4f456ed..1f31ff5b85217a1c631f05f43c5a6583
+ public byte[] serializeAsBytes() {
+ return org.bukkit.Bukkit.getUnsafe().serializeItem(this);
+ }
+
+ /**
+ * Serializes a collection of items to raw bytes in NBT.
+ * <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) {
+ // 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.
+ * <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 @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;
* <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 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;
* <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 ItemStack#serializeAsBytes()} or {@link ItemStack#serializeItemsAsBytes(Collection)}.
*/
+@Deprecated // Paper
public class BukkitObjectOutputStream extends ObjectOutputStream {
/**

View File

@ -478,7 +478,7 @@ index 5bd252c0ae3b09fe141d131360c67bb9bfbf5422..78587d9fabe6371a23a7963917b054db
+
}
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 1f31ff5b85217a1c631f05f43c5a65839a36b26e..1532b3e1b655a9b58588c11b80824ed4cec8c66a 100644
index 01596d82ec54fdefe0acd7ea776f38c94d4d7d77..221e189503d35b59b364503d0e89dd20d1183c1f 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.Nullable;
@ -487,10 +487,10 @@ index 1f31ff5b85217a1c631f05f43c5a65839a36b26e..1532b3e1b655a9b58588c11b80824ed4
*/
-public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource<net.kyori.adventure.text.event.HoverEvent.ShowItem> { // Paper
+public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource<net.kyori.adventure.text.event.HoverEvent.ShowItem>, net.kyori.adventure.translation.Translatable { // Paper
private static final byte ARRAY_SERIALIZATION_VERSION = 1; // Paper
private Material type = Material.AIR;
private int amount = 0;
private MaterialData data = null;
@@ -617,6 +617,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -618,6 +618,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@Override
@NotNull
@ -498,7 +498,7 @@ index 1f31ff5b85217a1c631f05f43c5a65839a36b26e..1532b3e1b655a9b58588c11b80824ed4
public String getTranslationKey() {
return Bukkit.getUnsafe().getTranslationKey(this);
}
@@ -876,5 +877,16 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -944,5 +945,16 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
ItemMeta itemMeta = getItemMeta();
return itemMeta != null && itemMeta.hasItemFlag(flag);
}

View File

@ -88,10 +88,10 @@ index 72a29fff4c497a2a66e2746ad42553bcb712e20d..22db1d8645a450308fe91d0cd100c926
// Paper end
}
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 1532b3e1b655a9b58588c11b80824ed4cec8c66a..569f67ee3f89d6af4fe1420cecc74811550b6507 100644
index 221e189503d35b59b364503d0e89dd20d1183c1f..a85877288fe421c4c834db326f54543855df8441 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -888,5 +888,15 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -956,5 +956,15 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
public @NotNull String translationKey() {
return Bukkit.getUnsafe().getTranslationKey(this);
}

View File

@ -26,10 +26,10 @@ index a3810c693d3748fba818e4a835ceb77762f433b9..d9e3e4ad108a94ac6f0f5378d22d4784
* Returns the server's protocol version.
*
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 569f67ee3f89d6af4fe1420cecc74811550b6507..78ea59c8f63b5a71ba092cf1783183bb5d0a3b79 100644
index a85877288fe421c4c834db326f54543855df8441..73908f0e8d49377a4fb86c960fde10940998a325 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -898,5 +898,27 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -966,5 +966,27 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
public io.papermc.paper.inventory.ItemRarity getRarity() {
return Bukkit.getUnsafe().getItemStackRarity(this);
}

View File

@ -5,10 +5,10 @@ Subject: [PATCH] ItemStack#editMeta
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 78ea59c8f63b5a71ba092cf1783183bb5d0a3b79..12fac9cf93ae7c6b5c8daced10a4394ad29bebcb 100644
index 73908f0e8d49377a4fb86c960fde10940998a325..777a52716a7bc70c7cb3f22522dce8dcfb0cdf44 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -558,6 +558,50 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -559,6 +559,50 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
return result.ensureServerConversions(); // Paper
}

View File

@ -70,10 +70,10 @@ index 198eebd45a2efa6986a8f4b613424eb8b6d3a039..f680545b6b59bf8d2ad154b0472dda4c
+ // Paper end - enchantWithLevels API
}
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 12fac9cf93ae7c6b5c8daced10a4394ad29bebcb..f236f9bfa4d43dcbad5919abef3620fcd0696880 100644
index 777a52716a7bc70c7cb3f22522dce8dcfb0cdf44..2de0170ddd7c3c9f30cfec0596ede3386b0fb176 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -667,6 +667,24 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -668,6 +668,24 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
}
// Paper start

View File

@ -65,10 +65,10 @@ index 70953573a7bcfa682dbaeaa8e3db0c847b254fdd..041ff387a0f9e8037e2834118ef241dd
// Paper end
}
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index f236f9bfa4d43dcbad5919abef3620fcd0696880..845037909658884167136955a02b99af0040fa9c 100644
index 2de0170ddd7c3c9f30cfec0596ede3386b0fb176..0bc0fb38204acb11a48157e8371ff9328e02e8c7 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -982,5 +982,19 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -1050,5 +1050,19 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
public boolean canRepair(@NotNull ItemStack toBeRepaired) {
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.
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 845037909658884167136955a02b99af0040fa9c..c3872213acc75d913580002cf70c2abb4ec69ca0 100644
index 0bc0fb38204acb11a48157e8371ff9328e02e8c7..95dc486f5d93113cb016142a66ffd76fb81d66c3 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -996,5 +996,24 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -1064,5 +1064,24 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
public @NotNull ItemStack damage(int amount, @NotNull org.bukkit.entity.LivingEntity livingEntity) {
return livingEntity.damageItemStack(this, amount);
}

View File

@ -9,10 +9,10 @@ By removing this check we avoid unnecessarily allocating useless `ItemMeta` obje
This is a leftover from when checking for the item's durability was "free" because the durability was stored in the `ItemStack` itself, this [was changed in Minecraft 1.13](https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/commits/f8b2086d60942eb2cd7ac25a2a1408cb790c222c#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 c3872213acc75d913580002cf70c2abb4ec69ca0..7adf54c561d64e6337af8a2d86f6b574b083edb5 100644
index 95dc486f5d93113cb016142a66ffd76fb81d66c3..f44b587ca5f6fdf09020e60fedf575e479b5d948 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -295,7 +295,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -296,7 +296,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
return true;
}
Material comparisonType = (this.type.isLegacy()) ? Bukkit.getUnsafe().fromLegacy(this.getData(), true) : this.type; // This may be called from legacy item stacks, try to get the right material

View File

@ -119,10 +119,10 @@ index f2163b5238e1667a44bf623cd52bab90d9f2d88d..9a65c4f614a6c358d74491794d7b2517
+ @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
index 7adf54c561d64e6337af8a2d86f6b574b083edb5..245a730a54c4b241a9a67eccceefafd2763bd238 100644
index f44b587ca5f6fdf09020e60fedf575e479b5d948..a28a588024f3face03d9f5e7682adb95d7e971c1 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -1016,4 +1016,21 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
@@ -1084,4 +1084,21 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
return type.isAir() || amount <= 0;
}
// Paper end