aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/api/0187-Add-Raw-Byte-ItemStack-Serialization.patch
blob: 681f0cf021a5e2ed27592e064400c70a30aefc8b (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
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 da997507b96908027c49dabc6daf7c787dcad95d..cb7aef53cbffc76dea9fec28445ea8aefcb29d62 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -162,5 +162,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 d758cea1d8e88937678dbfd0ac72d49b6c160fe0..fdc23ef0659f974efc6a3e8016da9104e2886a28 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -28,6 +28,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;
@@ -659,6 +660,110 @@ 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. 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 @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..4bfdc418977be8440290510e4c324c162ddb6d94 100644
--- a/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java
+++ b/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java
@@ -14,6 +14,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 org.bukkit.inventory.ItemStack#serializeAsBytes()} or
+ * {@link org.bukkit.inventory.ItemStack#serializeItemsAsBytes(java.util.Collection)}.
  */
 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..c5752ae9e5653ce103ee2ba049050bfba641858c 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 // Paper
 public class BukkitObjectOutputStream extends ObjectOutputStream {
 
     /**