Skip to content

Instantly share code, notes, and snippets.

@graywolf336
Last active April 30, 2024 02:00
Show Gist options
  • Save graywolf336/8153678 to your computer and use it in GitHub Desktop.
Save graywolf336/8153678 to your computer and use it in GitHub Desktop.
Serialize and deserialize the player's inventory, including armor and content.
/**
* Converts the player inventory to a String array of Base64 strings. First string is the content and second string is the armor.
*
* @param playerInventory to turn into an array of strings.
* @return Array of strings: [ main content, armor content ]
* @throws IllegalStateException
*/
public static String[] playerInventoryToBase64(PlayerInventory playerInventory) throws IllegalStateException {
//get the main content part, this doesn't return the armor
String content = toBase64(playerInventory);
String armor = itemStackArrayToBase64(playerInventory.getArmorContents());
return new String[] { content, armor };
}
/**
*
* A method to serialize an {@link ItemStack} array to Base64 String.
*
* <p />
*
* Based off of {@link #toBase64(Inventory)}.
*
* @param items to turn into a Base64 String.
* @return Base64 string of the items.
* @throws IllegalStateException
*/
public static String itemStackArrayToBase64(ItemStack[] items) throws IllegalStateException {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
// Write the size of the inventory
dataOutput.writeInt(items.length);
// Save every element in the list
for (int i = 0; i < items.length; i++) {
dataOutput.writeObject(items[i]);
}
// Serialize that array
dataOutput.close();
return Base64Coder.encodeLines(outputStream.toByteArray());
} catch (Exception e) {
throw new IllegalStateException("Unable to save item stacks.", e);
}
}
/**
* A method to serialize an inventory to Base64 string.
*
* <p />
*
* Special thanks to Comphenix in the Bukkit forums or also known
* as aadnk on GitHub.
*
* <a href="https://gist.github.com/aadnk/8138186">Original Source</a>
*
* @param inventory to serialize
* @return Base64 string of the provided inventory
* @throws IllegalStateException
*/
public static String toBase64(Inventory inventory) throws IllegalStateException {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
// Write the size of the inventory
dataOutput.writeInt(inventory.getSize());
// Save every element in the list
for (int i = 0; i < inventory.getSize(); i++) {
dataOutput.writeObject(inventory.getItem(i));
}
// Serialize that array
dataOutput.close();
return Base64Coder.encodeLines(outputStream.toByteArray());
} catch (Exception e) {
throw new IllegalStateException("Unable to save item stacks.", e);
}
}
/**
*
* A method to get an {@link Inventory} from an encoded, Base64, string.
*
* <p />
*
* Special thanks to Comphenix in the Bukkit forums or also known
* as aadnk on GitHub.
*
* <a href="https://gist.github.com/aadnk/8138186">Original Source</a>
*
* @param data Base64 string of data containing an inventory.
* @return Inventory created from the Base64 string.
* @throws IOException
*/
public static Inventory fromBase64(String data) throws IOException {
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data));
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
Inventory inventory = Bukkit.getServer().createInventory(null, dataInput.readInt());
// Read the serialized inventory
for (int i = 0; i < inventory.getSize(); i++) {
inventory.setItem(i, (ItemStack) dataInput.readObject());
}
dataInput.close();
return inventory;
} catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", e);
}
}
/**
* Gets an array of ItemStacks from Base64 string.
*
* <p />
*
* Base off of {@link #fromBase64(String)}.
*
* @param data Base64 string to convert to ItemStack array.
* @return ItemStack array created from the Base64 string.
* @throws IOException
*/
public static ItemStack[] itemStackArrayFromBase64(String data) throws IOException {
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data));
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
ItemStack[] items = new ItemStack[dataInput.readInt()];
// Read the serialized inventory
for (int i = 0; i < items.length; i++) {
items[i] = (ItemStack) dataInput.readObject();
}
dataInput.close();
return items;
} catch (ClassNotFoundException e) {
throw new IOException("Unable to decode class type.", e);
}
}
@rinpr
Copy link

rinpr commented Mar 20, 2023

is it works on spigot 1.19.3?

@rinpr
Copy link

rinpr commented Mar 26, 2023

nvm i tested its still work on 1.19.3 with a spigot api

@MrClean1337
Copy link

Still works on 1.19.4 Spigot/Paper.

In addition to that, here for those who wants to encode / decode single itemstacks:

    /**
     * Gets one {@link ItemStack} from Base64 string.
     *
     * @param data Base64 string to convert to {@link ItemStack}.
     * @return {@link ItemStack} created from the Base64 string.
     * @throws IOException
     */
    public static ItemStack itemStackFromBase64(String data) throws IOException {
        try {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decode(data));
            BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
            ItemStack item;

            // Read the serialized inventory
            item = (ItemStack) dataInput.readObject();

            dataInput.close();
            return item;
        } catch (ClassNotFoundException e) {
            throw new IOException("Unable to decode class type.", e);
        }
    }

    /**
     * A method to serialize one {@link ItemStack} to Base64 String.
     *
     * @param item to turn into a Base64 String.
     * @return Base64 string of the item.
     * @throws IllegalStateException
     */
    public static String itemStackToBase64(ItemStack item) throws IllegalStateException {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);

            // Save every element
            dataOutput.writeObject(item);

            // Serialize that array
            dataOutput.close();
            return new String(Base64Coder.encode(outputStream.toByteArray()));
        } catch (Exception e) {
            throw new IllegalStateException("Unable to save item stacks.", e);
        }
    }

@Badbird5907
Copy link

Fixed for 1.16 (may work in other versions, but I haven't tested):

import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;


public class SerializeInventory {
    /**
     * Converts the player inventory to a Base64 encoded string.
     *
     * @param playerInventory to turn into an array of strings.
     * @return string with serialized Inventory
     * @throws IllegalStateException
     */
    public static String playerInventoryToBase64(PlayerInventory playerInventory) throws IllegalStateException {
        // This contains contents, armor and offhand (contents are indexes 0 - 35, armor 36 - 39, offhand - 40)
        return itemStackArrayToBase64(playerInventory.getContents());
    }

    /**
     *
     * A method to serialize an {@link ItemStack} array to Base64 String.
     *
     * @param items to turn into a Base64 String.
     * @return Base64 string of the items.
     * @throws IllegalStateException
     */
    public static String itemStackArrayToBase64(ItemStack[] items) throws IllegalStateException {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);

            dataOutput.writeInt(items.length);

            for (ItemStack item : items) {
                if (item != null) {
                    dataOutput.writeObject(item.serializeAsBytes());
                } else {
                    dataOutput.writeObject(null);
                }
            }

            dataOutput.close();
            return Base64Coder.encodeLines(outputStream.toByteArray());
        } catch (Exception e) {
            throw new IllegalStateException("Unable to save item stacks.", e);
        }
    }

    /**
     * Gets an array of ItemStacks from Base64 string.
     *
     * @param data Base64 string to convert to ItemStack array.
     * @return ItemStack array created from the Base64 string.
     * @throws IOException 
     */
    public static ItemStack[] itemStackArrayFromBase64(String data) throws IOException {
        try {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data));
            BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
            ItemStack[] items = new ItemStack[dataInput.readInt()];

            for (int Index = 0; Index < items.length; Index++) {
                byte[] stack = (byte[]) dataInput.readObject();

                if (stack != null) {
                    items[Index] = ItemStack.deserializeBytes(stack);
                } else {
                    items[Index] = null;
                }
            }

            dataInput.close();
            return items;
        } catch (ClassNotFoundException e) {
            throw new IOException("Unable to decode class type.", e);
        }
    }
}

Thanks!

@enncy
Copy link

enncy commented Jul 15, 2023

Still works on 1.19.4 Spigot/Paper.

In addition to that, here for those who wants to encode / decode single itemstacks:

    /**
     * Gets one {@link ItemStack} from Base64 string.
     *
     * @param data Base64 string to convert to {@link ItemStack}.
     * @return {@link ItemStack} created from the Base64 string.
     * @throws IOException
     */
    public static ItemStack itemStackFromBase64(String data) throws IOException {
        try {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decode(data));
            BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
            ItemStack item;

            // Read the serialized inventory
            item = (ItemStack) dataInput.readObject();

            dataInput.close();
            return item;
        } catch (ClassNotFoundException e) {
            throw new IOException("Unable to decode class type.", e);
        }
    }

    /**
     * A method to serialize one {@link ItemStack} to Base64 String.
     *
     * @param item to turn into a Base64 String.
     * @return Base64 string of the item.
     * @throws IllegalStateException
     */
    public static String itemStackToBase64(ItemStack item) throws IllegalStateException {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);

            // Save every element
            dataOutput.writeObject(item);

            // Serialize that array
            dataOutput.close();
            return new String(Base64Coder.encode(outputStream.toByteArray()));
        } catch (Exception e) {
            throw new IllegalStateException("Unable to save item stacks.", e);
        }
    }

And you, my friend, are a true hero

@Sithey
Copy link

Sithey commented Oct 19, 2023

hello, i have this problem on 1.20.2
java.lang.IllegalArgumentException: Length of Base64 encoded input string is not a multiple of 4.

@finchstarling
Copy link

finchstarling commented Nov 9, 2023

.

@MySoulcutting
Copy link

Thank you

@kennytv
Copy link

kennytv commented Apr 4, 2024

Using ObjectInputStream is highly discouraged and should be avoided whenever possible due to major security risks. This will also have (continuously) worse(ning) performance compared to https://jd.papermc.io/paper/1.20/org/bukkit/inventory/ItemStack.html#serializeAsBytes() and https://jd.papermc.io/paper/1.20/org/bukkit/inventory/ItemStack.html#deserializeBytes(byte[]), e.g.:

    public static String serialize(ItemStack[] items) {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            DataOutput output = new DataOutputStream(outputStream);
            output.writeInt(items.length);

            for (ItemStack item : items) {
                if (item == null) {
                    // Ensure the correct order by including empty/null items
                    // Simply remove the write line if you don't want this
                    output.writeInt(0);
                    continue;
                }

                byte[] bytes = item.serializeAsBytes();
                output.writeInt(bytes.length);
                output.write(bytes);
            }
            return Base64Coder.encodeLines(outputStream.toByteArray()); // Base64 encoding is not strictly needed
        } catch (IOException e) {
            throw new RuntimeException("Error while writing itemstack", e);
        }
    }

    public static ItemStack[] deserialize(String encodedItems) {
        byte[] bytes = Base64Coder.decodeLines(encodedItems); // Base64 encoding is not strictly needed
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            DataInputStream input = new DataInputStream(inputStream);
            int count = input.readInt();
            ItemStack[] items = new ItemStack[count];
            for (int i = 0; i < count; i++) {
                int length = input.readInt();
                if (length == 0) {
                    // Empty item, keep entry as null
                    continue;
                }

                byte[] itemBytes = new byte[length];
                input.read(itemBytes);
                items[i] = ItemStack.deserializeBytes(itemBytes);
            }
            return items;
        } catch (IOException e) {
            throw new RuntimeException("Error while reading itemstack", e);
        }
    }

Or if you for some reason really need to support those few % of lost souls still using Spigot on even semi recent version, using internals is still better than the object streams and Spigot item serialization (though the linked example will still perform worse than Paper API)

@anjoismysign
Copy link

License?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment