[SPIGOT-7857] BlockStateMeta: Item not the same (isSimilar fails) after deserialization cycle Created: 03/Aug/24  Updated: 25/Dec/24  Resolved: 07/Aug/24

Status: Resolved
Project: Spigot
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Minor
Reporter: blablubbabc Assignee: Unassigned
Resolution: Fixed Votes: 0
Labels: BlockStateMeta, item, serialization

Version:  This server is running CraftBukkit version dev-Spigot-34bd42b-0cc6acb (MC: 1.21) (Implementing API version 1.21-R0.1-SNAPSHOT)
Guidelines Read: Yes

 Description   

Example involving a campfire item:

public class SerializationTest implements CommandExecutor {
    public SerializationTest() {
        Test.INSTANCE.getCommand("test").setExecutor(this);
    }
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        try {
            ItemStack item = createItemStack();
            YamlConfiguration config = new YamlConfiguration();
            config.set("item", item);
            String serialized = config.saveToString();
            sender.sendMessage("Serialized: " + serialized);
            config.loadFromString(serialized);
            ItemStack deserialized = config.getItemStack("item");
            sender.sendMessage("Deserialized: " + config.saveToString());
            sender.sendMessage("Deserialized equals original? " + item.isSimilar(deserialized));
        } catch (Exception e) {
            sender.sendMessage("Command execution failed: " + e.getMessage());
            Test.INSTANCE.getLogger().log(Level.SEVERE, "Command execution failed!", e);
        }
        return true;
    }
    private static ItemStack createItemStack() {
        ItemStack itemStack = new ItemStack(Material.CAMPFIRE);
        BlockDataMeta itemMeta = (BlockDataMeta) itemStack.getItemMeta();
        Campfire blockData = (Campfire) Material.CAMPFIRE.createBlockData();
        blockData.setLit(false);
        itemMeta.setBlockData(blockData);
        itemStack.setItemMeta(itemMeta);
        return itemStack;
    }
}

Output:

[17:51:46] [Server thread/INFO]: Serialized: item:
[17:51:46] [Server thread/INFO]:   ==: org.bukkit.inventory.ItemStack
[17:51:46] [Server thread/INFO]:   v: 3953
[17:51:46] [Server thread/INFO]:   type: CAMPFIRE
[17:51:46] [Server thread/INFO]:   meta:
[17:51:46] [Server thread/INFO]:     ==: ItemMeta
[17:51:46] [Server thread/INFO]:     meta-type: TILE_ENTITY
[17:51:46] [Server thread/INFO]:     BlockStateTag:
[17:51:46] [Server thread/INFO]:       waterlogged: 'false'
[17:51:46] [Server thread/INFO]:       signal_fire: 'false'
[17:51:46] [Server thread/INFO]:       lit: 'false'
[17:51:46] [Server thread/INFO]:       facing: north
[17:51:46] [Server thread/INFO]:     blockMaterial: CAMPFIRE
[17:51:46] [Server thread/INFO]:
[17:51:46] [Server thread/INFO]: Deserialized: item:
[17:51:46] [Server thread/INFO]:   ==: org.bukkit.inventory.ItemStack
[17:51:46] [Server thread/INFO]:   v: 3953
[17:51:46] [Server thread/INFO]:   type: CAMPFIRE
[17:51:46] [Server thread/INFO]:   meta:
[17:51:46] [Server thread/INFO]:     ==: ItemMeta
[17:51:46] [Server thread/INFO]:     meta-type: TILE_ENTITY
[17:51:46] [Server thread/INFO]:     BlockStateTag:
[17:51:46] [Server thread/INFO]:       waterlogged: ''
[17:51:46] [Server thread/INFO]:       signal_fire: ''
[17:51:46] [Server thread/INFO]:       lit: ''
[17:51:46] [Server thread/INFO]:       facing: north
[17:51:46] [Server thread/INFO]:     internal: H4sIAAAAAAAA/+NiYOBi4HPKyU/Ods0rySypDElMZ2ZgrGAAAiBdCaI5GVg9S1JzixmgglUgmpuBxzk/PzszLz0kMzcVJMfCgAa4GQRhSvJLEnNwquNgYMpMYRDKzcxLTS5KTCuxSk7MLUjLLEplYAAAe+3XY58AAAA=
[17:51:46] [Server thread/INFO]:     blockMaterial: CAMPFIRE
[17:51:46] [Server thread/INFO]:
[17:51:46] [Server thread/INFO]: Deserialized equals original? false

The decoded "internal" data is as follows:

{"BlockEntityTag":{"x":0,"y":0,"Items":[],"z":0,"CookingTimes":[I;0,0,0,0],"CookingTotalTimes":[I;0,0,0,0],"id":"minecraft:campfire"}}


 Comments   
Comment by blablubbabc [ 06/Aug/24 ]

Okay, seems like this (deserializing boolean block state data) didn't properly work before 1.20.5 already (see comment in: https://hub.spigotmc.org/jira/browse/SPIGOT-6257).
Edit: Actually, it worked since ~1.20.2 with the serialization as SNBT, and broke again in 1.20.5 because the Map is again deserialized as NBTTagCompound like in pre 1.20.2.

I will prepare a PR and ignore this issue.

Comment by md_5 [ 06/Aug/24 ]

Unfortunately I don't have a better idea

Comment by blablubbabc [ 05/Aug/24 ]

Something like this works:

 

Object blockData = SerializableMeta.getObject(Object.class, map, BLOCK_DATA.BUKKIT, true);
if (blockData != null) {
    Map<String, String> mapBlockData = new HashMap<>();
 
    if (blockData instanceof Map) {
        for (Entry<String, Object> entry : ((Map<String, Object>) blockData).entrySet()) {
            mapBlockData.put(entry.getKey(), entry.getValue().toString());
        }
    } else {
        // Legacy pre 1.20.5
        NBTTagCompound nbtBlockData = (NBTTagCompound) CraftNBTTagConfigSerializer.deserialize(blockData);
        for (String key : nbtBlockData.getAllKeys()) {
            // TODO What if the values did not parse as string?
            mapBlockData.put(key, nbtBlockData.getString(key));
    }
}
 
this.blockData = mapBlockData;

But I am not sure if the legacy data is handled correctly. E.g. if legacy data ByteTag was serialized, it is deserialized as ByteTag, but there is no way to turn this into 'true'/'false' (only "1b"/"0b") Strings, right (except by hard-coding this conversion)?

Comment by blablubbabc [ 05/Aug/24 ]

My guess is that the 'false' string is parsed by boolean by the MojangParser, which results in empty strings to be returned for "getString(key)" during deserialization (https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/browse/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java#524)

Likely an issue since the update for 1.20.5

Comment by blablubbabc [ 05/Aug/24 ]

The additional "internal" tag is gone

[23:24:02] [Server thread/INFO]: Serialized: item:
[23:24:02] [Server thread/INFO]:   ==: org.bukkit.inventory.ItemStack
[23:24:02] [Server thread/INFO]:   v: 3953
[23:24:02] [Server thread/INFO]:   type: CAMPFIRE
[23:24:02] [Server thread/INFO]:   meta:
[23:24:02] [Server thread/INFO]:     ==: ItemMeta
[23:24:02] [Server thread/INFO]:     meta-type: TILE_ENTITY
[23:24:02] [Server thread/INFO]:     BlockStateTag:
[23:24:02] [Server thread/INFO]:       waterlogged: 'false'
[23:24:02] [Server thread/INFO]:       signal_fire: 'false'
[23:24:02] [Server thread/INFO]:       lit: 'false'
[23:24:02] [Server thread/INFO]:       facing: north
[23:24:02] [Server thread/INFO]:     blockMaterial: CAMPFIRE
[23:24:02] [Server thread/INFO]:
[23:24:02] [Server thread/INFO]: Deserialized: item:
[23:24:02] [Server thread/INFO]:   ==: org.bukkit.inventory.ItemStack
[23:24:02] [Server thread/INFO]:   v: 3953
[23:24:02] [Server thread/INFO]:   type: CAMPFIRE
[23:24:02] [Server thread/INFO]:   meta:
[23:24:02] [Server thread/INFO]:     ==: ItemMeta
[23:24:02] [Server thread/INFO]:     meta-type: TILE_ENTITY
[23:24:02] [Server thread/INFO]:     BlockStateTag:
[23:24:02] [Server thread/INFO]:       waterlogged: ''
[23:24:02] [Server thread/INFO]:       signal_fire: ''
[23:24:02] [Server thread/INFO]:       lit: ''
[23:24:02] [Server thread/INFO]:       facing: north
[23:24:02] [Server thread/INFO]:     blockMaterial: CAMPFIRE
[23:24:02] [Server thread/INFO]:
[23:24:02] [Server thread/INFO]: Deserialized equals original? false

But there still seems to be some issue with the block states and the comparison between the serialized and deserialized item.

Comment by md_5 [ 05/Aug/24 ]

I think this is the fix, can you please test?

diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java
index ca5820792..42b812b03 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java
@@ -110,8 +110,10 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta
         } else {
             material = Material.AIR;
         }
-        blockEntityTag = getBlockState(material, internalTag);
-        internalTag = null;
+        if (internalTag != null) {
+            blockEntityTag = getBlockState(material, internalTag);
+            internalTag = null;
+        }
     }
 
     @Override
Comment by md_5 [ 05/Aug/24 ]

> Also noticed (might be related): https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/browse/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java#135 deserializeInternal writes to the "internalTag" field. It should probably write to "blockEntityTag".

That's intentional due to initialisation order issues

Comment by blablubbabc [ 03/Aug/24 ]

Also noticed (might be related): https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/browse/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java#135 deserializeInternal writes to the "internalTag" field. It should probably write to "blockEntityTag".

Generated at Sat Apr 05 10:22:58 UTC 2025 using Jira 10.3.3#10030003-sha1:d220e3fefc8dfc6d47f522d3b9a20c1455e12b7b.