From bfbe90bdd106a0bb7fda04c57491bebd846e751b Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 16 Nov 2020 19:23:37 +0000 Subject: [PATCH] Add nms from other project --- .gitattributes | 6 + build.gradle | 32 +++-- bukkit/build.gradle | 13 +++ .../github/chain/inventory/Serializer.java | 110 ++++++++++++++++++ nms/build.gradle | 0 .../nms/InvalidMinecraftVersionException.java | 7 ++ .../chain/inventory/nms/InventoryNMS.java | 12 ++ .../chain/inventory/nms/MinecraftVersion.java | 19 +++ settings.gradle | 6 + v1_11_R1/build.gradle | 3 + .../inventory/nms/v1_11_R1/v1_11_R1NMS.java | 108 +++++++++++++++++ v1_14_R1/build.gradle | 3 + .../inventory/nms/v1_14_R1/v1_14_R1NMS.java | 110 ++++++++++++++++++ v1_8_R3/build.gradle | 3 + .../inventory/nms/v1_8_R3/v1_8_R3NMS.java | 104 +++++++++++++++++ 15 files changed, 519 insertions(+), 17 deletions(-) create mode 100644 .gitattributes create mode 100644 bukkit/build.gradle create mode 100644 bukkit/src/main/java/com/github/chain/inventory/Serializer.java create mode 100644 nms/build.gradle create mode 100644 nms/src/main/java/com/github/chain/inventory/nms/InvalidMinecraftVersionException.java create mode 100644 nms/src/main/java/com/github/chain/inventory/nms/InventoryNMS.java create mode 100644 nms/src/main/java/com/github/chain/inventory/nms/MinecraftVersion.java create mode 100644 v1_11_R1/build.gradle create mode 100644 v1_11_R1/src/main/java/com/github/chain/inventory/nms/v1_11_R1/v1_11_R1NMS.java create mode 100644 v1_14_R1/build.gradle create mode 100644 v1_14_R1/src/main/java/com/github/chain/inventory/nms/v1_14_R1/v1_14_R1NMS.java create mode 100644 v1_8_R3/build.gradle create mode 100644 v1_8_R3/src/main/java/com/github/chain/inventory/nms/v1_8_R3/v1_8_R3NMS.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..00a51af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/build.gradle b/build.gradle index ae3e715..7544230 100644 --- a/build.gradle +++ b/build.gradle @@ -1,21 +1,19 @@ -plugins { - id 'com.github.johnrengelman.shadow' version '6.0.0' - id 'maven' - id 'java' -} +subprojects { + apply plugin: 'java' -group 'com.github.chain-plugins' -version '1.0' + group 'com.github.chain-plugins' + version '1.0-SNAPSHOT' -repositories { - mavenCentral() - mavenLocal() - maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } - maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } -} + repositories { + mavenCentral() + mavenLocal() + maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } + maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } + } -dependencies { - annotationProcessor 'org.projectlombok:lombok:1.18.8' - compileOnly 'org.projectlombok:lombok:1.18.8' - compileOnly 'org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT' + dependencies { + annotationProcessor 'org.projectlombok:lombok:1.18.8' + compileOnly 'org.projectlombok:lombok:1.18.8' + compileOnly 'org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT' + } } \ No newline at end of file diff --git a/bukkit/build.gradle b/bukkit/build.gradle new file mode 100644 index 0000000..9729dd9 --- /dev/null +++ b/bukkit/build.gradle @@ -0,0 +1,13 @@ +repositories { + maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } + maven { url = 'https://repo.codemc.org/repository/maven-public/' } +} + +dependencies { + compile project(':nms') + compile project(':v1_8_R3') + compile project(':v1_11_R1') + compile project(':v1_14_R1') + compileOnly 'org.spigotmc:spigot-api:1.16.4-R0.1-SNAPSHOT' + compileOnly 'org.bukkit:craftbukkit:1.16.4-R0.1-SNAPSHOT' +} \ No newline at end of file diff --git a/bukkit/src/main/java/com/github/chain/inventory/Serializer.java b/bukkit/src/main/java/com/github/chain/inventory/Serializer.java new file mode 100644 index 0000000..43bbd06 --- /dev/null +++ b/bukkit/src/main/java/com/github/chain/inventory/Serializer.java @@ -0,0 +1,110 @@ +package com.github.chain.inventory; + +import com.github.chain.inventory.nms.InvalidMinecraftVersionException; +import com.github.chain.inventory.nms.InventoryNMS; +import com.github.chain.inventory.nms.MinecraftVersion; +import com.github.chain.inventory.nms.v1_11_R1.v1_11_R1NMS; +import com.github.chain.inventory.nms.v1_14_R1.v1_14_R1NMS; +import com.github.chain.inventory.nms.v1_8_R3.v1_8_R3NMS; +import lombok.NonNull; +import org.bukkit.Bukkit; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +public class Serializer { + + private static InventoryNMS nmsBridge; + private static MinecraftVersion version; + + public static String encode(@NonNull Inventory inventory) throws Exception { + checkIfProviderIsSet(); + ItemStack[] inventoryContents = inventory.getContents(); + Class nmsItemStackClazz = getItemStackClass(); + + Object nmsItemArray = Array.newInstance(nmsItemStackClazz, inventoryContents.length); + for (int i = 0; i < inventoryContents.length; i++) { + Array.set(nmsItemArray, i, toNMSItem(inventoryContents[i])); + } + + return nmsBridge.encode((Object[]) nmsItemArray); + } + + public static void decode(@NonNull String encoded, @NonNull Inventory inventory) throws Exception { + checkIfProviderIsSet(); + Object[] nmsItemStacks = nmsBridge.decode(encoded); + + ItemStack[] inventoryContents = new ItemStack[nmsItemStacks.length]; + for (int i = 0; i < nmsItemStacks.length; i++) { + inventoryContents[i] = toBukkitItem(nmsItemStacks[i]); + } + + inventory.setContents(inventoryContents); + } + + private static ItemStack toBukkitItem(Object itemStack) throws Exception { + Class nmsItemStackClazz = getItemStackClass(); + Method asBukkitCopy = getCraftItemStackClass().getMethod("asBukkitCopy", nmsItemStackClazz); + return (ItemStack) asBukkitCopy.invoke(null, itemStack); + } + + private static Object toNMSItem(ItemStack itemStack) throws Exception { + Method asNMSCopy = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class); + return asNMSCopy.invoke(null, itemStack); + } + + private static Class getCraftItemStackClass() throws ClassNotFoundException { + return Class.forName("org.bukkit.craftbukkit." + version.toString() + ".inventory.CraftItemStack"); + } + + private static Class getItemStackClass() throws ClassNotFoundException { + return Class.forName("net.minecraft.server." + version.toString() + ".ItemStack"); + } + + private static void checkIfProviderIsSet() throws InvalidMinecraftVersionException { + if (nmsBridge == null) { + String versionName = Bukkit.getServer().getClass().getPackage().getName(); + + MinecraftVersion mcVersion; + try { + mcVersion = MinecraftVersion.valueOf(versionName.substring(versionName.lastIndexOf('.') + 1)); + } catch (IllegalArgumentException e) { + throw new InvalidMinecraftVersionException("Version of Minecraft not supported."); + } + + InventoryNMS bridge; + switch (mcVersion) { + case v1_8_R3: + case v1_9_R1: + case v1_9_R2: + case v1_10_R1: + bridge = new v1_8_R3NMS(); + break; + case v1_11_R1: + case v1_12_R1: + case v1_13_R1: + case v1_13_R2: + bridge = new v1_11_R1NMS(); + break; + case v1_14_R1: + case v1_15_R1: + case v1_16_R1: + case v1_16_R2: + case v1_16_R3: + bridge = new v1_14_R1NMS(); + break; + default: + throw new InvalidMinecraftVersionException("Version of Minecraft not supported."); + } + + version = mcVersion; + nmsBridge = bridge; + + if (!nmsBridge.init(mcVersion)) { + throw new InvalidMinecraftVersionException("Version of Minecraft not supported."); + } + } + } +} diff --git a/nms/build.gradle b/nms/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/nms/src/main/java/com/github/chain/inventory/nms/InvalidMinecraftVersionException.java b/nms/src/main/java/com/github/chain/inventory/nms/InvalidMinecraftVersionException.java new file mode 100644 index 0000000..ee6795e --- /dev/null +++ b/nms/src/main/java/com/github/chain/inventory/nms/InvalidMinecraftVersionException.java @@ -0,0 +1,7 @@ +package com.github.chain.inventory.nms; + +public class InvalidMinecraftVersionException extends Exception { + public InvalidMinecraftVersionException(String errorMessage) { + super(errorMessage); + } +} diff --git a/nms/src/main/java/com/github/chain/inventory/nms/InventoryNMS.java b/nms/src/main/java/com/github/chain/inventory/nms/InventoryNMS.java new file mode 100644 index 0000000..8f6233e --- /dev/null +++ b/nms/src/main/java/com/github/chain/inventory/nms/InventoryNMS.java @@ -0,0 +1,12 @@ +package com.github.chain.inventory.nms; + +import java.lang.reflect.InvocationTargetException; + +public interface InventoryNMS { + + boolean init(MinecraftVersion version); + + String encode(Object[] craftItemStacks) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException; + + Object[] decode(String encoded) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException; +} diff --git a/nms/src/main/java/com/github/chain/inventory/nms/MinecraftVersion.java b/nms/src/main/java/com/github/chain/inventory/nms/MinecraftVersion.java new file mode 100644 index 0000000..01a26e3 --- /dev/null +++ b/nms/src/main/java/com/github/chain/inventory/nms/MinecraftVersion.java @@ -0,0 +1,19 @@ +package com.github.chain.inventory.nms; + +public enum MinecraftVersion { + + v1_8_R3, + v1_9_R1, + v1_9_R2, + v1_10_R1, + v1_11_R1, + v1_12_R1, + v1_13_R1, + v1_13_R2, + v1_14_R1, + v1_15_R1, + v1_16_R1, + v1_16_R2, + v1_16_R3 + +} diff --git a/settings.gradle b/settings.gradle index be924aa..7f562a2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,8 @@ rootProject.name = 'bukkit-inventory-serializer' +include 'bukkit' + +include 'nms' +include 'v1_8_R3' +include 'v1_11_R1' +include 'v1_14_R1' \ No newline at end of file diff --git a/v1_11_R1/build.gradle b/v1_11_R1/build.gradle new file mode 100644 index 0000000..488e268 --- /dev/null +++ b/v1_11_R1/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compileOnly project(':nms') +} diff --git a/v1_11_R1/src/main/java/com/github/chain/inventory/nms/v1_11_R1/v1_11_R1NMS.java b/v1_11_R1/src/main/java/com/github/chain/inventory/nms/v1_11_R1/v1_11_R1NMS.java new file mode 100644 index 0000000..66b5e0d --- /dev/null +++ b/v1_11_R1/src/main/java/com/github/chain/inventory/nms/v1_11_R1/v1_11_R1NMS.java @@ -0,0 +1,108 @@ +package com.github.chain.inventory.nms.v1_11_R1; + +import com.github.chain.inventory.nms.MinecraftVersion; +import com.github.chain.inventory.nms.InventoryNMS; +import lombok.extern.java.Log; + +import java.io.*; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Base64; +import java.util.logging.Level; + +@Log +public class v1_11_R1NMS implements InventoryNMS { + + private Class nbtTagListClass; + private Class nbtItemStackClass; + private Class nbtBaseClass; + private Class nbtTagCompoundClass; + private Class nbtReadLimiterClass; + + private Method writeNbt; + private Method readNbt; + + @Override + public boolean init(MinecraftVersion version) { + Class nbtToolsClass; + try { + nbtToolsClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTCompressedStreamTools"); + + nbtReadLimiterClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTReadLimiter"); + nbtTagListClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTTagList"); + nbtItemStackClass = Class.forName("net.minecraft.server." + version.toString() + ".ItemStack"); + nbtBaseClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTBase"); + nbtTagCompoundClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTTagCompound"); + } catch (ClassNotFoundException e) { + log.log(Level.SEVERE, "Unable to find classes needed for NBT. Are you sure we support this Minecraft version?", e); + return false; + } + + try { + writeNbt = nbtToolsClass.getDeclaredMethod("a", nbtBaseClass, DataOutput.class); + writeNbt.setAccessible(true); + readNbt = nbtToolsClass.getDeclaredMethod("a", DataInput.class, Integer.TYPE, nbtReadLimiterClass); + readNbt.setAccessible(true); + } catch (NoSuchMethodException e) { + log.log(Level.SEVERE, "Unable to find writeNbt or readNbt method. Are you sure we support this Minecraft version?", e); + return false; + } + + return true; + } + + @Override + public String encode(Object[] craftItemStacks) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); + + Object nbtTagList = nbtTagListClass.newInstance(); + Method nbtTagListAddMethod = nbtTagListClass.getMethod("add", nbtBaseClass); + Method itemStackSaveMethod = nbtItemStackClass.getMethod("save", nbtTagCompoundClass); + + for (int i = 0; i < craftItemStacks.length; ++i) { + Object nbtTagCompound = nbtTagCompoundClass.newInstance(); + Object itemStack = nbtItemStackClass.cast(craftItemStacks[i]); + if (itemStack != null) { + itemStackSaveMethod.invoke(itemStack, nbtTagCompound); + } + nbtTagListAddMethod.invoke(nbtTagList, nbtTagCompound); + } + + writeNbt.invoke(null, nbtTagList, dataOutputStream); + return new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())); + } + + @Override + public Object[] decode(String encoded) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(encoded)); + DataInputStream dataInputStream = new DataInputStream(byteArrayInputStream); + + Object nbtReadLimiter = nbtReadLimiterClass.getConstructor(long.class).newInstance(Long.MAX_VALUE); + Object readInvoke = readNbt.invoke(null, dataInputStream, 0, nbtReadLimiter); + + Object nbtTagList = nbtTagListClass.cast(readInvoke); + Method nbtTagListSizeMethod = nbtTagListClass.getMethod("size"); + Method nbtTagListGetMethod = nbtTagListClass.getMethod("get", int.class); + int nbtTagListSize = (int) nbtTagListSizeMethod.invoke(nbtTagList); + + Method nbtTagCompoundIsEmptyMethod = nbtTagCompoundClass.getMethod("isEmpty"); + Object items = Array.newInstance(nbtItemStackClass, nbtTagListSize); + + Constructor nbtItemStackConstructor = nbtItemStackClass.getDeclaredConstructor(nbtTagCompoundClass); + nbtItemStackConstructor.setAccessible(true); + + for (int i = 0; i < nbtTagListSize; ++i) { + Object nbtTagCompound = nbtTagListGetMethod.invoke(nbtTagList, i); + boolean isEmpty = (boolean) nbtTagCompoundIsEmptyMethod.invoke(nbtTagCompound); + if (!isEmpty) { + Array.set(items, i, nbtItemStackConstructor.newInstance(nbtTagCompound)); + } + } + + return (Object[]) items; + } +} + diff --git a/v1_14_R1/build.gradle b/v1_14_R1/build.gradle new file mode 100644 index 0000000..488e268 --- /dev/null +++ b/v1_14_R1/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compileOnly project(':nms') +} diff --git a/v1_14_R1/src/main/java/com/github/chain/inventory/nms/v1_14_R1/v1_14_R1NMS.java b/v1_14_R1/src/main/java/com/github/chain/inventory/nms/v1_14_R1/v1_14_R1NMS.java new file mode 100644 index 0000000..9083cbf --- /dev/null +++ b/v1_14_R1/src/main/java/com/github/chain/inventory/nms/v1_14_R1/v1_14_R1NMS.java @@ -0,0 +1,110 @@ +package com.github.chain.inventory.nms.v1_14_R1; + +import com.github.chain.inventory.nms.MinecraftVersion; +import com.github.chain.inventory.nms.InventoryNMS; +import lombok.extern.java.Log; + +import java.io.*; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Base64; +import java.util.logging.Level; + +@Log +public class v1_14_R1NMS implements InventoryNMS { + + private Class nbtTagListClass; + private Class nbtItemStackClass; + private Class nbtBaseClass; + private Class nbtTagCompoundClass; + private Class nbtReadLimiterClass; + + private Method writeNbt; + private Method readNbt; + + @Override + public boolean init(MinecraftVersion version) { + Class nbtToolsClass; + try { + nbtToolsClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTCompressedStreamTools"); + + nbtReadLimiterClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTReadLimiter"); + nbtTagListClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTTagList"); + nbtItemStackClass = Class.forName("net.minecraft.server." + version.toString() + ".ItemStack"); + nbtBaseClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTBase"); + nbtTagCompoundClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTTagCompound"); + } catch (ClassNotFoundException e) { + log.log(Level.SEVERE, "Unable to find classes needed for NBT. Are you sure we support this Minecraft version?", e); + return false; + } + + try { + writeNbt = nbtToolsClass.getDeclaredMethod("a", nbtBaseClass, DataOutput.class); + writeNbt.setAccessible(true); + readNbt = nbtToolsClass.getDeclaredMethod("a", DataInput.class, Integer.TYPE, nbtReadLimiterClass); + readNbt.setAccessible(true); + } catch (NoSuchMethodException e) { + log.log(Level.SEVERE, "Unable to find writeNbt or readNbt method. Are you sure we support this Minecraft version?", e); + return false; + } + + return true; + } + + @Override + public String encode(Object[] craftItemStacks) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); + + Object nbtTagList = nbtTagListClass.newInstance(); + Method nbtTagListSizeMethod = nbtTagListClass.getMethod("size"); + Method nbtTagListAddMethod = nbtTagListClass.getMethod("add", int.class, nbtBaseClass); + Method itemStackSaveMethod = nbtItemStackClass.getMethod("save", nbtTagCompoundClass); + + for (int i = 0; i < craftItemStacks.length; ++i) { + Object nbtTagCompound = nbtTagCompoundClass.newInstance(); + Object itemStack = nbtItemStackClass.cast(craftItemStacks[i]); + if (itemStack != null) { + itemStackSaveMethod.invoke(itemStack, nbtTagCompound); + } + + int size = (int) nbtTagListSizeMethod.invoke(nbtTagList); + nbtTagListAddMethod.invoke(nbtTagList, size, nbtTagCompound); + } + + writeNbt.invoke(null, nbtTagList, dataOutputStream); + return new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())); + } + + @Override + public Object[] decode(String encoded) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(encoded)); + DataInputStream dataInputStream = new DataInputStream(byteArrayInputStream); + + Object nbtReadLimiter = nbtReadLimiterClass.getConstructor(long.class).newInstance(Long.MAX_VALUE); + Object readInvoke = readNbt.invoke(null, dataInputStream, 0, nbtReadLimiter); + + Object nbtTagList = nbtTagListClass.cast(readInvoke); + Method nbtTagListSizeMethod = nbtTagListClass.getMethod("size"); + Method nbtTagListGetMethod = nbtTagListClass.getMethod("get", int.class); + int nbtTagListSize = (int) nbtTagListSizeMethod.invoke(nbtTagList); + + Method nbtTagCompoundIsEmptyMethod = nbtTagCompoundClass.getMethod("isEmpty"); + Object items = Array.newInstance(nbtItemStackClass, nbtTagListSize); + + Constructor nbtItemStackConstructor = nbtItemStackClass.getDeclaredConstructor(nbtTagCompoundClass); + nbtItemStackConstructor.setAccessible(true); + + for (int i = 0; i < nbtTagListSize; ++i) { + Object nbtTagCompound = nbtTagListGetMethod.invoke(nbtTagList, i); + boolean isEmpty = (boolean) nbtTagCompoundIsEmptyMethod.invoke(nbtTagCompound); + if (!isEmpty) { + Array.set(items, i, nbtItemStackConstructor.newInstance(nbtTagCompound)); + } + } + + return (Object[]) items; + } +} diff --git a/v1_8_R3/build.gradle b/v1_8_R3/build.gradle new file mode 100644 index 0000000..488e268 --- /dev/null +++ b/v1_8_R3/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compileOnly project(':nms') +} diff --git a/v1_8_R3/src/main/java/com/github/chain/inventory/nms/v1_8_R3/v1_8_R3NMS.java b/v1_8_R3/src/main/java/com/github/chain/inventory/nms/v1_8_R3/v1_8_R3NMS.java new file mode 100644 index 0000000..39d8009 --- /dev/null +++ b/v1_8_R3/src/main/java/com/github/chain/inventory/nms/v1_8_R3/v1_8_R3NMS.java @@ -0,0 +1,104 @@ +package com.github.chain.inventory.nms.v1_8_R3; + +import com.github.chain.inventory.nms.MinecraftVersion; +import com.github.chain.inventory.nms.InventoryNMS; +import lombok.extern.java.Log; + +import java.io.*; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Base64; +import java.util.logging.Level; + +@Log +public class v1_8_R3NMS implements InventoryNMS { + + private Class nbtTagListClass; + private Class nbtItemStackClass; + private Class nbtBaseClass; + private Class nbtTagCompoundClass; + private Class nbtReadLimiterClass; + + private Method writeNbt; + private Method readNbt; + + @Override + public boolean init(MinecraftVersion version) { + Class nbtToolsClass; + try { + nbtToolsClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTCompressedStreamTools"); + + nbtReadLimiterClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTReadLimiter"); + nbtTagListClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTTagList"); + nbtItemStackClass = Class.forName("net.minecraft.server." + version.toString() + ".ItemStack"); + nbtBaseClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTBase"); + nbtTagCompoundClass = Class.forName("net.minecraft.server." + version.toString() + ".NBTTagCompound"); + } catch (ClassNotFoundException e) { + log.log(Level.SEVERE, "Unable to find classes needed for NBT. Are you sure we support this Minecraft version?", e); + return false; + } + + try { + writeNbt = nbtToolsClass.getDeclaredMethod("a", nbtBaseClass, DataOutput.class); + writeNbt.setAccessible(true); + readNbt = nbtToolsClass.getDeclaredMethod("a", DataInput.class, Integer.TYPE, nbtReadLimiterClass); + readNbt.setAccessible(true); + } catch (NoSuchMethodException e) { + log.log(Level.SEVERE, "Unable to find writeNbt or readNbt method. Are you sure we support this Minecraft version?", e); + return false; + } + + return true; + } + + @Override + public String encode(Object[] craftItemStacks) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); + + Object nbtTagList = nbtTagListClass.newInstance(); + Method nbtTagListAddMethod = nbtTagListClass.getMethod("add", nbtBaseClass); + Method itemStackSaveMethod = nbtItemStackClass.getMethod("save", nbtTagCompoundClass); + + for (int i = 0; i < craftItemStacks.length; ++i) { + Object nbtTagCompound = nbtTagCompoundClass.newInstance(); + Object itemStack = nbtItemStackClass.cast(craftItemStacks[i]); + if (itemStack != null) { + itemStackSaveMethod.invoke(itemStack, nbtTagCompound); + } + nbtTagListAddMethod.invoke(nbtTagList, nbtTagCompound); + } + + writeNbt.invoke(null, nbtTagList, dataOutputStream); + return new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())); + } + + @Override + public Object[] decode(String encoded) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(encoded)); + DataInputStream dataInputStream = new DataInputStream(byteArrayInputStream); + + Object nbtReadLimiter = nbtReadLimiterClass.getConstructor(long.class).newInstance(Long.MAX_VALUE); + Object readInvoke = readNbt.invoke(null, dataInputStream, 0, nbtReadLimiter); + + Object nbtTagList = nbtTagListClass.cast(readInvoke); + Method nbtTagListSizeMethod = nbtTagListClass.getMethod("size"); + Method nbtTagListGetMethod = nbtTagListClass.getMethod("get", int.class); + int nbtTagListSize = (int) nbtTagListSizeMethod.invoke(nbtTagList); + + Method nbtTagCompoundIsEmptyMethod = nbtTagCompoundClass.getMethod("isEmpty"); + Method nbtItemStackCreateMethod = nbtItemStackClass.getMethod("createStack", nbtTagCompoundClass); + Object items = Array.newInstance(nbtItemStackClass, nbtTagListSize); + + for (int i = 0; i < nbtTagListSize; ++i) { + Object nbtTagCompound = nbtTagListGetMethod.invoke(nbtTagList, i); + boolean isEmpty = (boolean) nbtTagCompoundIsEmptyMethod.invoke(nbtTagCompound); + if (!isEmpty) { + Array.set(items, i, nbtItemStackCreateMethod.invoke(null, nbtTagCompound)); + } + } + + return (Object[]) items; + } +}