Skip to content

Instantly share code, notes, and snippets.

@graywolf336
Last active October 26, 2024 16:41
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);
}
}
@CarrlosM
Copy link

Absolute Chad

@MorkaZ
Copy link

MorkaZ commented Feb 13, 2020

Thank you a lot. I saved a lot of time thanks to this class.

@Jasplan
Copy link

Jasplan commented Jun 8, 2020

Amazing! Got null exeptions when using the FromBase64 method but that is probably just me. Otherwise great!

@colmalle
Copy link

colmalle commented Aug 5, 2020

@efindus
Copy link

efindus commented Dec 5, 2020

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);
        }
    }
}

Copy link

ghost commented Dec 16, 2020

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);
        }
    }
}

I.... don't think that'll work. As far as I'm aware; there is no such method as serializeAsBytes or deserializeBytes

Closest I know of is deserialize and serialize

@efindus
Copy link

efindus commented Dec 16, 2020

Oh. I haven't realized that those functions are PaperMC exclusive... Well... Using them makes output string shorter. If you are using Spigot / Bukkit you can exchange them (serializeAsBytes(), and deserializeBytes()) for serialize() and deserialize(), and some variable types from byte[] to Map<String,​ Object>, and it should work as intended.

@SemmieDev
Copy link

Will it be possible to encode the inventory to a single line? I have tried removing the newlines but for some reason that causes the string to be empty. I am sure I am doing something wrong, but I have no idea what. If you want to see my code just ask for it, but I don't think it's useful.

@iFeliN3x0
Copy link

That is a great method, but i don't want to storage the inventory slots that have AIR or are null
Does Somebody know what to do?

@iGabyTM
Copy link

iGabyTM commented Mar 2, 2021

That is a great method, but i don't want to storage the inventory slots that have AIR or are null
Does Somebody know what to do?

Just filter the items

@hamza-cskn
Copy link

You're awesome.

@stroyerr
Copy link

stroyerr commented Oct 5, 2021

you are now my lord

@weaves7
Copy link

weaves7 commented Oct 27, 2021

Hi, would you mind adding the MIT or similar permissive license to this? There are many projects using it(because it is awesome) but it is unclear if the default gist license allows redistribution.

@towelcodes
Copy link

thanks, this works really well

@MarcusSlover
Copy link

MarcusSlover commented Dec 19, 2021

Hey, does anybody know if this works with Spigot 1.16.5?

@iKstruuh
Copy link

@MarcusSlover yeah, it does.

@MarcusSlover
Copy link

@iKstruuh thanks

@Ktilis
Copy link

Ktilis commented May 3, 2022

Author, thanks. Works with Purpur-1.18.2.

@streepje8
Copy link

streepje8 commented May 4, 2022

This saved me so much work trying to make my own serializer 0-0
Thanks graywolf!

@fluffeliger
Copy link

Kiss in the left and right hollow of the knee

@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