From af780f6b4b657e9054604859b8635fd6e6d603a0 Mon Sep 17 00:00:00 2001 From: NichtStudioCode <51272202+NichtStudioCode@users.noreply.github.com> Date: Tue, 6 Jul 2021 18:35:11 +0200 Subject: [PATCH] Improved serialization of VirtualInventories --- .../de/studiocode/invui/util/DataUtils.java | 77 +++++++++++++++++ .../virtualinventory/VirtualInventory.java | 17 ++++ .../VirtualInventoryManager.java | 83 ++++++++++++++++--- .../v1_14_R1/util/ItemUtilsImpl.java | 75 +++++++++++++++++ .../v1_15_R1/util/ItemUtilsImpl.java | 75 +++++++++++++++++ .../v1_16_R1/util/ItemUtilsImpl.java | 75 +++++++++++++++++ .../v1_16_R2/util/ItemUtilsImpl.java | 75 +++++++++++++++++ .../v1_16_R3/util/ItemUtilsImpl.java | 75 +++++++++++++++++ .../v1_17_R1/util/ItemUtilsImpl.java | 75 +++++++++++++++++ .../api/abstraction/util/ItemUtils.java | 50 +++++++++++ .../api/version/InventoryAccess.java | 12 +++ 11 files changed, 678 insertions(+), 11 deletions(-) create mode 100644 InvUI/src/main/java/de/studiocode/invui/util/DataUtils.java create mode 100644 InventoryAccess/1_14_R1/src/main/java/de/studiocode/inventoryaccess/v1_14_R1/util/ItemUtilsImpl.java create mode 100644 InventoryAccess/1_15_R1/src/main/java/de/studiocode/inventoryaccess/v1_15_R1/util/ItemUtilsImpl.java create mode 100644 InventoryAccess/1_16_R1/src/main/java/de/studiocode/inventoryaccess/v1_16_R1/util/ItemUtilsImpl.java create mode 100644 InventoryAccess/1_16_R2/src/main/java/de/studiocode/inventoryaccess/v1_16_R2/util/ItemUtilsImpl.java create mode 100644 InventoryAccess/1_16_R3/src/main/java/de/studiocode/inventoryaccess/v1_16_R3/util/ItemUtilsImpl.java create mode 100644 InventoryAccess/1_17_R1/src/main/java/de/studiocode/inventoryaccess/v1_17_R1/util/ItemUtilsImpl.java create mode 100644 InventoryAccess/api/src/main/java/de/studiocode/inventoryaccess/api/abstraction/util/ItemUtils.java diff --git a/InvUI/src/main/java/de/studiocode/invui/util/DataUtils.java b/InvUI/src/main/java/de/studiocode/invui/util/DataUtils.java new file mode 100644 index 0000000..1080ad6 --- /dev/null +++ b/InvUI/src/main/java/de/studiocode/invui/util/DataUtils.java @@ -0,0 +1,77 @@ +package de.studiocode.invui.util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; + +public class DataUtils { + + public static byte[] toByteArray(List intList) { + byte[] array = new byte[intList.size()]; + for (int i = 0; i < intList.size(); i++) + array[i] = intList.get(i).byteValue(); + + return array; + } + + public static byte[] toByteArray(int[] intArray) { + byte[] array = new byte[intArray.length]; + for (int i = 0; i < intArray.length; i++) + array[i] = (byte) intArray[i]; + + return array; + } + + public static int[] toIntArray(byte[] byteArray) { + int[] array = new int[byteArray.length]; + for (int i = 0; i < byteArray.length; i++) + array[i] = byteArray[i]; + + return array; + } + + public static void writeIntArray(DataOutputStream dos, int[] array) throws IOException { + dos.writeInt(array.length); + for (int i : array) dos.writeInt(i); + } + + public static int[] readIntArray(DataInputStream din) throws IOException { + int size = din.readInt(); + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = din.readInt(); + } + + return array; + } + + public static void writeByteArray(DataOutputStream dos, byte[] array) throws IOException { + dos.writeInt(array.length); + dos.write(array); + } + + public static byte[] readByteArray(DataInputStream din) throws IOException { + int size = din.readInt(); + byte[] array = new byte[size]; + din.readFully(array); + return array; + } + + public static void write2DByteArray(DataOutputStream dos, byte[][] array2d) throws IOException { + dos.writeInt(array2d.length); + for (byte[] array : array2d) { + writeByteArray(dos, array); + } + } + + public static byte[][] read2DByteArray(DataInputStream din) throws IOException { + int size2d = din.readInt(); + byte[][] array2d = new byte[size2d][]; + for (int i = 0; i < size2d; i++) { + array2d[i] = readByteArray(din); + } + return array2d; + } + +} diff --git a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java index 31ec4cb..544ff57 100644 --- a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java +++ b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java @@ -13,6 +13,8 @@ import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.InputStream; +import java.io.OutputStream; import java.util.*; import java.util.function.Consumer; import java.util.stream.Stream; @@ -62,7 +64,10 @@ public class VirtualInventory implements ConfigurationSerializable { * * @param args The args which contain the data to deserialize * @return The deserialized {@link VirtualInventory} + * @deprecated Use {@link VirtualInventoryManager#serializeInventory(VirtualInventory, OutputStream)} + * and {@link VirtualInventoryManager#deserializeInventory(InputStream)} for serialization */ + @Deprecated public static VirtualInventory deserialize(@NotNull Map args) { //noinspection unchecked return new VirtualInventory( @@ -77,7 +82,10 @@ public class VirtualInventory implements ConfigurationSerializable { * Serializes this {@link VirtualInventory} to a {@link Map} * * @return A {@link Map} that contains the serialized data of this {@link VirtualInventory} + * @deprecated Use {@link VirtualInventoryManager#serializeInventory(VirtualInventory, OutputStream)} + * and {@link VirtualInventoryManager#deserializeInventory(InputStream)} for serialization */ + @Deprecated @NotNull @Override public Map serialize() { @@ -170,6 +178,15 @@ public class VirtualInventory implements ConfigurationSerializable { return size; } + /** + * Gets the array of max stack sizes for this {@link VirtualInventory}. + * + * @return The array defining the max stack sizes for this {@link VirtualInventory} + */ + public int[] getStackSizes() { + return stackSizes; + } + /** * Gets a copy of the contents of this {@link VirtualInventory}. * diff --git a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventoryManager.java b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventoryManager.java index 17e8941..0a864e5 100644 --- a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventoryManager.java +++ b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventoryManager.java @@ -1,13 +1,14 @@ package de.studiocode.invui.virtualinventory; +import de.studiocode.inventoryaccess.api.version.InventoryAccess; import de.studiocode.invui.InvUI; +import de.studiocode.invui.util.DataUtils; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -26,6 +27,8 @@ public class VirtualInventoryManager { private final Map inventories = new HashMap<>(); private VirtualInventoryManager() { + SAVE_DIR.mkdirs(); + ConfigurationSerialization.registerClass(VirtualInventory.class); InvUI.getInstance().addDisableHandler(this::serializeAll); deserializeAll(); @@ -77,12 +80,23 @@ public class VirtualInventoryManager { private void deserializeAll() { if (SAVE_DIR.exists()) { Arrays.stream(SAVE_DIR.listFiles()) - .filter(file -> file.getName().endsWith(".vi")) .forEach(file -> { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - VirtualInventory virtualInventory = config.getSerializable("vi", VirtualInventory.class); - - inventories.put(virtualInventory.getUuid(), virtualInventory); + if (file.getName().endsWith(".vi")) { + YamlConfiguration config = YamlConfiguration.loadConfiguration(file); + VirtualInventory virtualInventory = config.getSerializable("vi", VirtualInventory.class); + inventories.put(virtualInventory.getUuid(), virtualInventory); + + file.delete(); + } else if (file.getName().endsWith(".vi2")) { + try { + FileInputStream in = new FileInputStream(file); + VirtualInventory virtualInventory = deserializeInventory(in); + inventories.put(virtualInventory.getUuid(), virtualInventory); + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } }); } } @@ -91,9 +105,10 @@ public class VirtualInventoryManager { inventories.values().forEach(virtualInventory -> { try { File file = getSaveFile(virtualInventory); - YamlConfiguration config = new YamlConfiguration(); - config.set("vi", virtualInventory); - config.save(file); + FileOutputStream out = new FileOutputStream(file); + serializeInventory(virtualInventory, out); + out.flush(); + out.close(); } catch (IOException e) { e.printStackTrace(); } @@ -101,7 +116,53 @@ public class VirtualInventoryManager { } private File getSaveFile(VirtualInventory virtualInventory) { - return new File(SAVE_DIR, virtualInventory.getUuid() + ".vi"); + return new File(SAVE_DIR, virtualInventory.getUuid() + ".vi2"); + } + + public void serializeInventory(VirtualInventory vi, OutputStream out) { + try { + DataOutputStream dos = new DataOutputStream(out); + UUID uuid = vi.getUuid(); + dos.writeLong(uuid.getMostSignificantBits()); + dos.writeLong(uuid.getLeastSignificantBits()); + DataUtils.writeByteArray(dos, DataUtils.toByteArray(vi.getStackSizes())); + + byte[][] items = Arrays.stream(vi.getItems()).map(itemStack -> { + if (itemStack != null) { + return InventoryAccess.getItemUtils().serializeItemStack(itemStack, true); + } else return new byte[0]; + } + ).toArray(byte[][]::new); + + DataUtils.write2DByteArray(dos, items); + + dos.flush(); + } catch ( + IOException e) { + e.printStackTrace(); + } + + } + + public VirtualInventory deserializeInventory(InputStream in) { + try { + DataInputStream din = new DataInputStream(in); + UUID uuid = new UUID(din.readLong(), din.readLong()); + int[] stackSizes = DataUtils.toIntArray(DataUtils.readByteArray(din)); + + ItemStack[] items = Arrays.stream(DataUtils.read2DByteArray(din)).map(data -> { + if (data.length != 0) { + return InventoryAccess.getItemUtils().deserializeItemStack(data, true); + } else return null; + } + ).toArray(ItemStack[]::new); + + return new VirtualInventory(uuid, stackSizes.length, items, stackSizes); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; } } diff --git a/InventoryAccess/1_14_R1/src/main/java/de/studiocode/inventoryaccess/v1_14_R1/util/ItemUtilsImpl.java b/InventoryAccess/1_14_R1/src/main/java/de/studiocode/inventoryaccess/v1_14_R1/util/ItemUtilsImpl.java new file mode 100644 index 0000000..149b3e4 --- /dev/null +++ b/InventoryAccess/1_14_R1/src/main/java/de/studiocode/inventoryaccess/v1_14_R1/util/ItemUtilsImpl.java @@ -0,0 +1,75 @@ +package de.studiocode.inventoryaccess.v1_14_R1.util; + +import de.studiocode.inventoryaccess.api.abstraction.util.ItemUtils; +import de.studiocode.inventoryaccess.api.version.ReflectionUtils; +import net.minecraft.server.v1_14_R1.ItemStack; +import net.minecraft.server.v1_14_R1.NBTCompressedStreamTools; +import net.minecraft.server.v1_14_R1.NBTTagCompound; +import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack; + +import java.io.*; +import java.lang.reflect.Field; + +public class ItemUtilsImpl implements ItemUtils { + + private static final Field CRAFT_ITEM_STACK_HANDLE_FIELD = ReflectionUtils.getField(CraftItemStack.class, true, "handle"); + + @Override + public byte[] serializeItemStack(org.bukkit.inventory.ItemStack itemStack, boolean compressed) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serializeItemStack(itemStack, out, compressed); + return out.toByteArray(); + } + + @Override + public void serializeItemStack(org.bukkit.inventory.ItemStack itemStack, OutputStream out, boolean compressed) { + try { + ItemStack nmsStack; + + if (itemStack instanceof CraftItemStack) + nmsStack = (ItemStack) CRAFT_ITEM_STACK_HANDLE_FIELD.get(itemStack); + else nmsStack = CraftItemStack.asNMSCopy(itemStack); + + NBTTagCompound nbt = nmsStack.save(new NBTTagCompound()); + + if (compressed) { + NBTCompressedStreamTools.a(nbt, out); + } else { + DataOutputStream dataOut = new DataOutputStream(out); + NBTCompressedStreamTools.a(nbt, (DataOutput) dataOut); + } + + out.flush(); + } catch (IllegalAccessException | IOException e) { + e.printStackTrace(); + } + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(byte[] data, boolean compressed) { + ByteArrayInputStream in = new ByteArrayInputStream(data); + return deserializeItemStack(in, compressed); + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(InputStream in, boolean compressed) { + try { + NBTTagCompound nbt; + if (compressed) { + nbt = NBTCompressedStreamTools.a(in); + } else { + DataInputStream dataIn = new DataInputStream(in); + nbt = NBTCompressedStreamTools.a(dataIn); + } + + ItemStack itemStack = ItemStack.a(nbt); + + return CraftItemStack.asCraftMirror(itemStack); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + +} diff --git a/InventoryAccess/1_15_R1/src/main/java/de/studiocode/inventoryaccess/v1_15_R1/util/ItemUtilsImpl.java b/InventoryAccess/1_15_R1/src/main/java/de/studiocode/inventoryaccess/v1_15_R1/util/ItemUtilsImpl.java new file mode 100644 index 0000000..2fac3c4 --- /dev/null +++ b/InventoryAccess/1_15_R1/src/main/java/de/studiocode/inventoryaccess/v1_15_R1/util/ItemUtilsImpl.java @@ -0,0 +1,75 @@ +package de.studiocode.inventoryaccess.v1_15_R1.util; + +import de.studiocode.inventoryaccess.api.abstraction.util.ItemUtils; +import de.studiocode.inventoryaccess.api.version.ReflectionUtils; +import net.minecraft.server.v1_15_R1.ItemStack; +import net.minecraft.server.v1_15_R1.NBTCompressedStreamTools; +import net.minecraft.server.v1_15_R1.NBTTagCompound; +import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; + +import java.io.*; +import java.lang.reflect.Field; + +public class ItemUtilsImpl implements ItemUtils { + + private static final Field CRAFT_ITEM_STACK_HANDLE_FIELD = ReflectionUtils.getField(CraftItemStack.class, true, "handle"); + + @Override + public byte[] serializeItemStack(org.bukkit.inventory.ItemStack itemStack, boolean compressed) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serializeItemStack(itemStack, out, compressed); + return out.toByteArray(); + } + + @Override + public void serializeItemStack(org.bukkit.inventory.ItemStack itemStack, OutputStream out, boolean compressed) { + try { + ItemStack nmsStack; + + if (itemStack instanceof CraftItemStack) + nmsStack = (ItemStack) CRAFT_ITEM_STACK_HANDLE_FIELD.get(itemStack); + else nmsStack = CraftItemStack.asNMSCopy(itemStack); + + NBTTagCompound nbt = nmsStack.save(new NBTTagCompound()); + + if (compressed) { + NBTCompressedStreamTools.a(nbt, out); + } else { + DataOutputStream dataOut = new DataOutputStream(out); + NBTCompressedStreamTools.a(nbt, (DataOutput) dataOut); + } + + out.flush(); + } catch (IllegalAccessException | IOException e) { + e.printStackTrace(); + } + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(byte[] data, boolean compressed) { + ByteArrayInputStream in = new ByteArrayInputStream(data); + return deserializeItemStack(in, compressed); + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(InputStream in, boolean compressed) { + try { + NBTTagCompound nbt; + if (compressed) { + nbt = NBTCompressedStreamTools.a(in); + } else { + DataInputStream dataIn = new DataInputStream(in); + nbt = NBTCompressedStreamTools.a(dataIn); + } + + ItemStack itemStack = ItemStack.a(nbt); + + return CraftItemStack.asCraftMirror(itemStack); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + +} diff --git a/InventoryAccess/1_16_R1/src/main/java/de/studiocode/inventoryaccess/v1_16_R1/util/ItemUtilsImpl.java b/InventoryAccess/1_16_R1/src/main/java/de/studiocode/inventoryaccess/v1_16_R1/util/ItemUtilsImpl.java new file mode 100644 index 0000000..9fb8352 --- /dev/null +++ b/InventoryAccess/1_16_R1/src/main/java/de/studiocode/inventoryaccess/v1_16_R1/util/ItemUtilsImpl.java @@ -0,0 +1,75 @@ +package de.studiocode.inventoryaccess.v1_16_R1.util; + +import de.studiocode.inventoryaccess.api.abstraction.util.ItemUtils; +import de.studiocode.inventoryaccess.api.version.ReflectionUtils; +import net.minecraft.server.v1_16_R1.ItemStack; +import net.minecraft.server.v1_16_R1.NBTCompressedStreamTools; +import net.minecraft.server.v1_16_R1.NBTTagCompound; +import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftItemStack; + +import java.io.*; +import java.lang.reflect.Field; + +public class ItemUtilsImpl implements ItemUtils { + + private static final Field CRAFT_ITEM_STACK_HANDLE_FIELD = ReflectionUtils.getField(CraftItemStack.class, true, "handle"); + + @Override + public byte[] serializeItemStack(org.bukkit.inventory.ItemStack itemStack, boolean compressed) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serializeItemStack(itemStack, out, compressed); + return out.toByteArray(); + } + + @Override + public void serializeItemStack(org.bukkit.inventory.ItemStack itemStack, OutputStream out, boolean compressed) { + try { + ItemStack nmsStack; + + if (itemStack instanceof CraftItemStack) + nmsStack = (ItemStack) CRAFT_ITEM_STACK_HANDLE_FIELD.get(itemStack); + else nmsStack = CraftItemStack.asNMSCopy(itemStack); + + NBTTagCompound nbt = nmsStack.save(new NBTTagCompound()); + + if (compressed) { + NBTCompressedStreamTools.a(nbt, out); + } else { + DataOutputStream dataOut = new DataOutputStream(out); + NBTCompressedStreamTools.a(nbt, (DataOutput) dataOut); + } + + out.flush(); + } catch (IllegalAccessException | IOException e) { + e.printStackTrace(); + } + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(byte[] data, boolean compressed) { + ByteArrayInputStream in = new ByteArrayInputStream(data); + return deserializeItemStack(in, compressed); + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(InputStream in, boolean compressed) { + try { + NBTTagCompound nbt; + if (compressed) { + nbt = NBTCompressedStreamTools.a(in); + } else { + DataInputStream dataIn = new DataInputStream(in); + nbt = NBTCompressedStreamTools.a(dataIn); + } + + ItemStack itemStack = ItemStack.a(nbt); + + return CraftItemStack.asCraftMirror(itemStack); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + +} diff --git a/InventoryAccess/1_16_R2/src/main/java/de/studiocode/inventoryaccess/v1_16_R2/util/ItemUtilsImpl.java b/InventoryAccess/1_16_R2/src/main/java/de/studiocode/inventoryaccess/v1_16_R2/util/ItemUtilsImpl.java new file mode 100644 index 0000000..35c2976 --- /dev/null +++ b/InventoryAccess/1_16_R2/src/main/java/de/studiocode/inventoryaccess/v1_16_R2/util/ItemUtilsImpl.java @@ -0,0 +1,75 @@ +package de.studiocode.inventoryaccess.v1_16_R2.util; + +import de.studiocode.inventoryaccess.api.abstraction.util.ItemUtils; +import de.studiocode.inventoryaccess.api.version.ReflectionUtils; +import net.minecraft.server.v1_16_R2.ItemStack; +import net.minecraft.server.v1_16_R2.NBTCompressedStreamTools; +import net.minecraft.server.v1_16_R2.NBTTagCompound; +import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftItemStack; + +import java.io.*; +import java.lang.reflect.Field; + +public class ItemUtilsImpl implements ItemUtils { + + private static final Field CRAFT_ITEM_STACK_HANDLE_FIELD = ReflectionUtils.getField(CraftItemStack.class, true, "handle"); + + @Override + public byte[] serializeItemStack(org.bukkit.inventory.ItemStack itemStack, boolean compressed) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serializeItemStack(itemStack, out, compressed); + return out.toByteArray(); + } + + @Override + public void serializeItemStack(org.bukkit.inventory.ItemStack itemStack, OutputStream out, boolean compressed) { + try { + ItemStack nmsStack; + + if (itemStack instanceof CraftItemStack) + nmsStack = (ItemStack) CRAFT_ITEM_STACK_HANDLE_FIELD.get(itemStack); + else nmsStack = CraftItemStack.asNMSCopy(itemStack); + + NBTTagCompound nbt = nmsStack.save(new NBTTagCompound()); + + if (compressed) { + NBTCompressedStreamTools.a(nbt, out); + } else { + DataOutputStream dataOut = new DataOutputStream(out); + NBTCompressedStreamTools.a(nbt, (DataOutput) dataOut); + } + + out.flush(); + } catch (IllegalAccessException | IOException e) { + e.printStackTrace(); + } + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(byte[] data, boolean compressed) { + ByteArrayInputStream in = new ByteArrayInputStream(data); + return deserializeItemStack(in, compressed); + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(InputStream in, boolean compressed) { + try { + NBTTagCompound nbt; + if (compressed) { + nbt = NBTCompressedStreamTools.a(in); + } else { + DataInputStream dataIn = new DataInputStream(in); + nbt = NBTCompressedStreamTools.a((DataInput) dataIn); + } + + ItemStack itemStack = ItemStack.a(nbt); + + return CraftItemStack.asCraftMirror(itemStack); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + +} diff --git a/InventoryAccess/1_16_R3/src/main/java/de/studiocode/inventoryaccess/v1_16_R3/util/ItemUtilsImpl.java b/InventoryAccess/1_16_R3/src/main/java/de/studiocode/inventoryaccess/v1_16_R3/util/ItemUtilsImpl.java new file mode 100644 index 0000000..897c318 --- /dev/null +++ b/InventoryAccess/1_16_R3/src/main/java/de/studiocode/inventoryaccess/v1_16_R3/util/ItemUtilsImpl.java @@ -0,0 +1,75 @@ +package de.studiocode.inventoryaccess.v1_16_R3.util; + +import de.studiocode.inventoryaccess.api.abstraction.util.ItemUtils; +import de.studiocode.inventoryaccess.api.version.ReflectionUtils; +import net.minecraft.server.v1_16_R3.ItemStack; +import net.minecraft.server.v1_16_R3.NBTCompressedStreamTools; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; + +import java.io.*; +import java.lang.reflect.Field; + +public class ItemUtilsImpl implements ItemUtils { + + private static final Field CRAFT_ITEM_STACK_HANDLE_FIELD = ReflectionUtils.getField(CraftItemStack.class, true, "handle"); + + @Override + public byte[] serializeItemStack(org.bukkit.inventory.ItemStack itemStack, boolean compressed) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serializeItemStack(itemStack, out, compressed); + return out.toByteArray(); + } + + @Override + public void serializeItemStack(org.bukkit.inventory.ItemStack itemStack, OutputStream out, boolean compressed) { + try { + ItemStack nmsStack; + + if (itemStack instanceof CraftItemStack) + nmsStack = (ItemStack) CRAFT_ITEM_STACK_HANDLE_FIELD.get(itemStack); + else nmsStack = CraftItemStack.asNMSCopy(itemStack); + + NBTTagCompound nbt = nmsStack.save(new NBTTagCompound()); + + if (compressed) { + NBTCompressedStreamTools.a(nbt, out); + } else { + DataOutputStream dataOut = new DataOutputStream(out); + NBTCompressedStreamTools.a(nbt, (DataOutput) dataOut); + } + + out.flush(); + } catch (IllegalAccessException | IOException e) { + e.printStackTrace(); + } + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(byte[] data, boolean compressed) { + ByteArrayInputStream in = new ByteArrayInputStream(data); + return deserializeItemStack(in, compressed); + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(InputStream in, boolean compressed) { + try { + NBTTagCompound nbt; + if (compressed) { + nbt = NBTCompressedStreamTools.a(in); + } else { + DataInputStream dataIn = new DataInputStream(in); + nbt = NBTCompressedStreamTools.a((DataInput) dataIn); + } + + ItemStack itemStack = ItemStack.a(nbt); + + return CraftItemStack.asCraftMirror(itemStack); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + +} \ No newline at end of file diff --git a/InventoryAccess/1_17_R1/src/main/java/de/studiocode/inventoryaccess/v1_17_R1/util/ItemUtilsImpl.java b/InventoryAccess/1_17_R1/src/main/java/de/studiocode/inventoryaccess/v1_17_R1/util/ItemUtilsImpl.java new file mode 100644 index 0000000..e193356 --- /dev/null +++ b/InventoryAccess/1_17_R1/src/main/java/de/studiocode/inventoryaccess/v1_17_R1/util/ItemUtilsImpl.java @@ -0,0 +1,75 @@ +package de.studiocode.inventoryaccess.v1_17_R1.util; + +import de.studiocode.inventoryaccess.api.abstraction.util.ItemUtils; +import de.studiocode.inventoryaccess.api.version.ReflectionUtils; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; + +import java.io.*; +import java.lang.reflect.Field; + +public class ItemUtilsImpl implements ItemUtils { + + private static final Field CRAFT_ITEM_STACK_HANDLE_FIELD = ReflectionUtils.getField(CraftItemStack.class, true, "handle"); + + @Override + public byte[] serializeItemStack(org.bukkit.inventory.ItemStack itemStack, boolean compressed) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serializeItemStack(itemStack, out, compressed); + return out.toByteArray(); + } + + @Override + public void serializeItemStack(org.bukkit.inventory.ItemStack itemStack, OutputStream outputStream, boolean compressed) { + try { + ItemStack nmsStack; + + if (itemStack instanceof CraftItemStack) + nmsStack = (ItemStack) CRAFT_ITEM_STACK_HANDLE_FIELD.get(itemStack); + else nmsStack = CraftItemStack.asNMSCopy(itemStack); + + CompoundTag nbt = nmsStack.save(new CompoundTag()); + + if (compressed) { + NbtIo.writeCompressed(nbt, outputStream); + } else { + DataOutputStream dataOut = new DataOutputStream(outputStream); + NbtIo.write(nbt, dataOut); + } + + outputStream.flush(); + } catch (IllegalAccessException | IOException e) { + e.printStackTrace(); + } + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(byte[] data, boolean compressed) { + ByteArrayInputStream in = new ByteArrayInputStream(data); + return deserializeItemStack(in, compressed); + } + + @Override + public org.bukkit.inventory.ItemStack deserializeItemStack(InputStream inputStream, boolean compressed) { + try { + CompoundTag nbt; + if (compressed) { + nbt = NbtIo.readCompressed(inputStream); + } else { + DataInputStream dataIn = new DataInputStream(inputStream); + nbt = NbtIo.read(dataIn); + } + + ItemStack itemStack = ItemStack.of(nbt); + + return CraftItemStack.asCraftMirror(itemStack); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + +} \ No newline at end of file diff --git a/InventoryAccess/api/src/main/java/de/studiocode/inventoryaccess/api/abstraction/util/ItemUtils.java b/InventoryAccess/api/src/main/java/de/studiocode/inventoryaccess/api/abstraction/util/ItemUtils.java new file mode 100644 index 0000000..2ce08c6 --- /dev/null +++ b/InventoryAccess/api/src/main/java/de/studiocode/inventoryaccess/api/abstraction/util/ItemUtils.java @@ -0,0 +1,50 @@ +package de.studiocode.inventoryaccess.api.abstraction.util; + +import org.bukkit.inventory.ItemStack; + +import java.io.InputStream; +import java.io.OutputStream; + +public interface ItemUtils { + + /** + * Serializes an {@link ItemStack} to a byte[] + * + * @param itemStack The {@link ItemStack} to serialize + * @param compressed If the data should be compressed + * @return The serialized data + * @see #deserializeItemStack(byte[], boolean) + */ + byte[] serializeItemStack(ItemStack itemStack, boolean compressed); + + /** + * Serializes an {@link ItemStack} to a byte[] + * + * @param itemStack The {@link ItemStack} to serialize + * @param outputStream The {@link OutputStream} to write the serialized data to + * @param compressed If the data should be compressed + * @see #deserializeItemStack(InputStream, boolean) + */ + void serializeItemStack(ItemStack itemStack, OutputStream outputStream, boolean compressed); + + /** + * Deserializes an {@link ItemStack} from a byte[] + * + * @param data The data to deserialize + * @param compressed If the data is compressed + * @return The {@link ItemStack} + * @see #serializeItemStack(ItemStack, boolean) + */ + ItemStack deserializeItemStack(byte[] data, boolean compressed); + + /** + * Deserializes an {@link ItemStack} from a byte[] + * + * @param inputStream The {@link InputStream} to read the serialized data from + * @param compressed If the data is compressed + * @return The {@link ItemStack} + * @see #serializeItemStack(ItemStack, OutputStream, boolean) + */ + ItemStack deserializeItemStack(InputStream inputStream, boolean compressed); + +} diff --git a/InventoryAccess/api/src/main/java/de/studiocode/inventoryaccess/api/version/InventoryAccess.java b/InventoryAccess/api/src/main/java/de/studiocode/inventoryaccess/api/version/InventoryAccess.java index 974b855..ac3a6b5 100644 --- a/InventoryAccess/api/src/main/java/de/studiocode/inventoryaccess/api/version/InventoryAccess.java +++ b/InventoryAccess/api/src/main/java/de/studiocode/inventoryaccess/api/version/InventoryAccess.java @@ -2,6 +2,7 @@ package de.studiocode.inventoryaccess.api.version; import de.studiocode.inventoryaccess.api.abstraction.inventory.AnvilInventory; import de.studiocode.inventoryaccess.api.abstraction.util.InventoryUtils; +import de.studiocode.inventoryaccess.api.abstraction.util.ItemUtils; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.entity.Player; @@ -11,12 +12,14 @@ import java.util.function.Consumer; public class InventoryAccess { private static final Class INVENTORY_UTILS_CLASS = ReflectionUtils.getImplClass("util.InventoryUtilsImpl"); + private static final Class ITEM_UTILS_CLASS = ReflectionUtils.getImplClass("util.ItemUtilsImpl"); private static final Class ANVIL_INVENTORY_CLASS = ReflectionUtils.getImplClass("inventory.AnvilInventoryImpl"); private static final Constructor ANVIL_INVENTORY_CONSTRUCTOR = ReflectionUtils.getConstructor(ANVIL_INVENTORY_CLASS, Player.class, BaseComponent[].class, Consumer.class); private static final InventoryUtils INVENTORY_UTILS = ReflectionUtils.constructEmpty(INVENTORY_UTILS_CLASS); + private static final ItemUtils ITEM_UTILS = ReflectionUtils.constructEmpty(ITEM_UTILS_CLASS); /** * Gets the {@link InventoryUtils} @@ -27,6 +30,15 @@ public class InventoryAccess { return INVENTORY_UTILS; } + /** + * Gets the {@link ItemUtils} + * + * @return The {@link ItemUtils} + */ + public static ItemUtils getItemUtils() { + return ITEM_UTILS; + } + /** * Creates a new {@link AnvilInventory}. *