aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/api/0182-Add-Raw-Byte-ItemStack-Serialization.patch
blob: 1d4c5dfaea570560badfc32e2f815213f54057b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mariell Hoversholm <proximyst@proximyst.com>
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 <nassim@njahnke.dev>

diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
index 8b5e4756d1c80e55be166dbe1faf57799f9cc03b..66d29e7f4187309d4a55202d1bda4b94e42211b0 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -164,5 +164,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 e9c29fc1db686b80bc2477d78ec2b361b8600b9e..af09398e0864d338da530495bfd577db8adbe65a 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -661,6 +661,117 @@ 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);
+    }
+
+    /**
+     * 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 null items as {@link #empty()}.
+     * <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 null items as {@link #empty()}.
+     * <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.
      * 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 {
 
     /**