From ed737dd75f78325cff98dce579e73e97bfd4feab Mon Sep 17 00:00:00 2001 From: Hendrik Lind Date: Thu, 27 Nov 2025 17:36:02 +0100 Subject: [PATCH] Properly implement serialization / deserialization for NBT Arrays (#1213) * fix: use int array instead of list for chunk entities * just get the int array and don't fall back for the list * update deserialization to use proper NbtTag * dix deserialization of nbt arrays --------- Co-authored-by: Alexander Medvedev --- pumpkin-nbt/src/deserializer.rs | 18 ++++++++ pumpkin-nbt/src/tag.rs | 66 +++++++++++++++------------ pumpkin-world/src/chunk/format/mod.rs | 23 +++++----- 3 files changed, 68 insertions(+), 39 deletions(-) diff --git a/pumpkin-nbt/src/deserializer.rs b/pumpkin-nbt/src/deserializer.rs index 4c4a93d4e..bacdf1019 100644 --- a/pumpkin-nbt/src/deserializer.rs +++ b/pumpkin-nbt/src/deserializer.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::io::{Seek, SeekFrom}; use crate::*; @@ -7,6 +8,20 @@ use serde::{Deserialize, forward_to_deserialize_any}; pub type Result = std::result::Result; +thread_local! { + pub static CURR_VISITOR_LIST_TYPE: RefCell> = const { std::cell::RefCell::new(None) }; +} + +pub(super) fn take_curr_visitor_seq_list_id() -> Option { + CURR_VISITOR_LIST_TYPE.with(|cell| cell.take()) +} + +pub(super) fn set_curr_visitor_seq_list_id(tag: Option) { + CURR_VISITOR_LIST_TYPE.with(|cell| { + *cell.borrow_mut() = tag; + }); +} + #[derive(Debug)] pub struct NbtReadHelper { reader: R, @@ -135,6 +150,9 @@ impl<'de, R: Read + Seek> de::Deserializer<'de> for &mut Deserializer { return Err(Error::NegativeLength(remaining_values)); } + //TODO this is a bit hacky but I couldn't think of a better way + // This flag gets auto cleared in visit_seq + set_curr_visitor_seq_list_id(Some(list_type)); let result = visitor.visit_seq(ListAccess { de: self, list_type, diff --git a/pumpkin-nbt/src/tag.rs b/pumpkin-nbt/src/tag.rs index d1e4b9e8b..64e038e3c 100644 --- a/pumpkin-nbt/src/tag.rs +++ b/pumpkin-nbt/src/tag.rs @@ -393,14 +393,7 @@ impl Serialize for NbtTag { NbtTag::Long(v) => serializer.serialize_i64(*v), NbtTag::Float(v) => serializer.serialize_f32(*v), NbtTag::Double(v) => serializer.serialize_f64(*v), - NbtTag::ByteArray(v) => { - use serde::ser::SerializeSeq; - let mut seq = serializer.serialize_seq(Some(v.len()))?; - for byte in v.iter() { - seq.serialize_element(byte)?; - } - seq.end() - } + NbtTag::ByteArray(v) => nbt_byte_array(v, serializer), NbtTag::String(v) => serializer.serialize_str(v), NbtTag::List(v) => { use serde::ser::SerializeSeq; @@ -411,22 +404,8 @@ impl Serialize for NbtTag { seq.end() } NbtTag::Compound(v) => v.serialize(serializer), - NbtTag::IntArray(v) => { - use serde::ser::SerializeSeq; - let mut seq = serializer.serialize_seq(Some(v.len()))?; - for int in v.iter() { - seq.serialize_element(int)?; - } - seq.end() - } - NbtTag::LongArray(v) => { - use serde::ser::SerializeSeq; - let mut seq = serializer.serialize_seq(Some(v.len()))?; - for long in v.iter() { - seq.serialize_element(long)?; - } - seq.end() - } + NbtTag::IntArray(v) => nbt_int_array(v, serializer), + NbtTag::LongArray(v) => nbt_long_array(v, serializer), } } } @@ -478,11 +457,42 @@ impl<'de> Deserialize<'de> for NbtTag { self, mut seq: A, ) -> Result { - let mut vec = Vec::new(); - while let Some(value) = seq.next_element()? { - vec.push(value); + let curr = deserializer::take_curr_visitor_seq_list_id().unwrap_or(LIST_ID); + + match curr { + INT_ARRAY_ID => { + let mut vec = Vec::new(); + while let Some(value) = seq.next_element()? { + vec.push(value); + } + Ok(NbtTag::IntArray(vec)) + } + LONG_ARRAY_ID => { + let mut vec = Vec::new(); + while let Some(value) = seq.next_element()? { + vec.push(value); + } + Ok(NbtTag::LongArray(vec)) + } + BYTE_ARRAY_ID => { + let mut vec = Vec::new(); + while let Some(value) = seq.next_element()? { + vec.push(value); + } + Ok(NbtTag::ByteArray(vec.into_boxed_slice())) + } + LIST_ID => { + let mut vec = Vec::new(); + while let Some(value) = seq.next_element()? { + vec.push(value); + } + Ok(NbtTag::List(vec)) + } + _ => Err(serde::de::Error::custom(format!( + "Invalid list type id: {}", + curr + ))), } - Ok(NbtTag::List(vec)) } fn visit_map>( diff --git a/pumpkin-world/src/chunk/format/mod.rs b/pumpkin-world/src/chunk/format/mod.rs index 5c14b425e..c271a84c9 100644 --- a/pumpkin-world/src/chunk/format/mod.rs +++ b/pumpkin-world/src/chunk/format/mod.rs @@ -286,21 +286,22 @@ impl ChunkEntityData { } let mut map = HashMap::new(); for entity_nbt in chunk_entity_data.entities { - // TODO: This is wrong, we should use an int array, but our NBT lib for some reason does not work with int arrays and - // Just gives me a list when putting in a int array - let uuid = match entity_nbt.get_list("UUID") { - Some(uuid) => uuid, + let uuid = match entity_nbt.get_int_array("UUID") { + Some(uuid) => Uuid::from_u128( + (uuid[0] as u128) << 96 + | (uuid[1] as u128) << 64 + | (uuid[2] as u128) << 32 + | (uuid[3] as u128), + ), None => { - log::warn!("TODO: use int arrays for UUID"); + println!( + "Entity in chunk {},{} is missing UUID: {:?}", + position.x, position.y, entity_nbt + ); continue; } }; - let uuid = Uuid::from_u128( - (uuid.first().unwrap().extract_int().unwrap() as u128) << 96 - | (uuid.get(1).unwrap().extract_int().unwrap() as u128) << 64 - | (uuid.get(2).unwrap().extract_int().unwrap() as u128) << 32 - | (uuid.get(3).unwrap().extract_int().unwrap() as u128), - ); + map.insert(uuid, entity_nbt); }