use crate::VarInt; use crate::codec::data_component::{deserialize, serialize}; use crate::ser::{NetworkReadExt, NetworkWriteExt, ReadingError, WritingError}; use pumpkin_data::data_component::DataComponent; use pumpkin_data::data_component_impl::{CustomNameImpl, DataComponentImpl}; use pumpkin_data::item::Item; use pumpkin_data::item_id_remap::{remap_item_id_for_version, remap_item_id_from_version}; use pumpkin_data::item_stack::ItemStack; use pumpkin_nbt::tag::NbtTag; use pumpkin_util::text::TextComponent; use pumpkin_util::version::JavaMinecraftVersion; use std::borrow::Cow; use std::io::Cursor; #[derive(Clone)] pub struct ItemStackSerializer<'a>(pub Cow<'a, ItemStack>); fn item_component_counts(stack: &ItemStack) -> (u8, u8) { let mut to_add = 0u8; let mut to_remove = 0u8; for (_id, data) in &stack.patch { if data.is_none() { to_remove += 1; } else { to_add += 1; } } (to_add, to_remove) } fn serialize_any_item_stack_with_id( stack: &ItemStack, item_id: u16, is_template: bool, write: &mut impl NetworkWriteExt, ) -> Result<(), WritingError> { if stack.is_empty() { write.put_var_int(&VarInt(0)) } else { let (to_add, to_remove) = item_component_counts(stack); if is_template { write.put_var_int(&VarInt::from(item_id))?; write.put_var_int(&VarInt::from(stack.item_count))?; } else { write.put_var_int(&VarInt::from(stack.item_count))?; write.put_var_int(&VarInt::from(item_id))?; } write.put_var_int(&VarInt::from(to_add))?; write.put_var_int(&VarInt::from(to_remove))?; for (id, data) in &stack.patch { if let Some(data) = data { write.put_var_int(&VarInt::from(id.to_id()))?; serialize(*id, data.as_ref(), write)?; } } for (id, data) in &stack.patch { if data.is_none() { write.put_var_int(&VarInt::from(id.to_id()))?; } } Ok(()) } } fn serialize_item_stack_with_id( stack: &ItemStack, item_id: u16, write: &mut impl NetworkWriteExt, ) -> Result<(), WritingError> { serialize_any_item_stack_with_id(stack, item_id, false, write) } fn read_component_id(read: &mut impl NetworkReadExt) -> Result { let id_val = read.get_var_int()?.0; let id_u8 = id_val .try_into() .map_err(|_| ReadingError::Message(format!("Invalid component ID: {id_val}")))?; DataComponent::try_from_id(id_u8) .ok_or_else(|| ReadingError::Message(format!("Unknown component ID: {id_val}"))) } fn decode_custom_name(component_data: &[u8]) -> Result, ReadingError> { let mut cursor = Cursor::new(component_data); let mut nbt_reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(&mut cursor); let tag = NbtTag::deserialize(&mut nbt_reader) .map_err(|err| ReadingError::Message(format!("Failed to decode CustomName NBT: {err}")))?; let name = match tag { NbtTag::String(name) => TextComponent::text(name.to_string()), NbtTag::Compound(compound) => compound .get_string("text") .map_or_else(TextComponent::empty, |name| { TextComponent::text(name.to_string()) }), _ => TextComponent::empty(), }; Ok(CustomNameImpl { name }.to_dyn()) } fn read_length_prefixed_component( read: &mut impl NetworkReadExt, ) -> Result<(DataComponent, Box), ReadingError> { let id = read_component_id(read)?; let byte_len = read.get_var_int()?.0; let byte_len: usize = byte_len .try_into() .map_err(|_| ReadingError::Message("Negative component data length".into()))?; if byte_len > crate::MAX_PACKET_DATA_SIZE { return Err(ReadingError::TooLarge("Component data too large".into())); } let component_impl = if byte_len <= 256 { let mut stack_buf = [0u8; 256]; let slice = &mut stack_buf[..byte_len]; read.read_bytes_to_buf(slice)?; if id == DataComponent::CustomName { decode_custom_name(slice)? } else { let mut cursor = Cursor::new(slice); deserialize(id, &mut cursor)? } } else { let mut component_data = vec![0u8; byte_len]; read.read_bytes_to_buf(&mut component_data)?; if id == DataComponent::CustomName { decode_custom_name(component_data.as_ref())? } else { let mut cursor = Cursor::new(component_data); deserialize(id, &mut cursor)? } }; Ok((id, component_impl)) } impl ItemStackSerializer<'_> { pub fn read( read: &mut impl NetworkReadExt, ) -> Result, ReadingError> { const MAX_COMPONENTS: i32 = 256; let item_count = read.get_var_int()?; if item_count.0 == 0 { return Ok(ItemStackSerializer(Cow::Borrowed(ItemStack::EMPTY))); } let item_id = read.get_var_int()?; let num_to_add = read.get_var_int()?.0; let num_to_remove = read.get_var_int()?.0; if num_to_add < 0 || num_to_remove < 0 { return Err(ReadingError::Message("Negative component count".into())); } let total_components = num_to_add .checked_add(num_to_remove) .ok_or_else(|| ReadingError::Message("Component count overflow".into()))?; if total_components > MAX_COMPONENTS { return Err(ReadingError::Message( "Too many components in ItemStack patch".into(), )); } let mut patch = Vec::with_capacity((num_to_add + num_to_remove) as usize); for _ in 0..num_to_add { let id_val = read.get_var_int()?.0; let id = DataComponent::try_from_id(id_val as u8) .ok_or_else(|| ReadingError::Message(format!("Unknown component ID: {id_val}")))?; let _byte_len = read.get_var_int()?; let component_impl = deserialize(id, read)?; patch.push((id, Some(component_impl))); } for _ in 0..num_to_remove { let id_val = read.get_var_int()?.0; let id = DataComponent::try_from_id(id_val as u8) .ok_or_else(|| ReadingError::Message("Unknown component ID".into()))?; patch.push((id, None)); } let item_id_u16: u16 = item_id .0 .try_into() .map_err(|_| ReadingError::Message("Invalid item id!".into()))?; Ok(ItemStackSerializer(Cow::Owned( ItemStack::new_with_component( item_count.0 as u8, Item::from_id(item_id_u16).unwrap_or(&Item::AIR), patch, ), ))) } pub fn write(&self, write: &mut impl NetworkWriteExt) -> Result<(), WritingError> { serialize_item_stack_with_id(self.0.as_ref(), self.0.item.id, write) } pub fn read_length_prefixed_optional( read: &mut impl NetworkReadExt, ) -> Result, ReadingError> { const MAX_COMPONENTS: i32 = 256; let item_count = read.get_var_int()?; if item_count.0 == 0 { return Ok(ItemStackSerializer(Cow::Borrowed(ItemStack::EMPTY))); } let item_count_u8 = item_count .0 .try_into() .map_err(|_| ReadingError::Message("Invalid item count!".into()))?; let item_id = read.get_var_int()?; let num_to_add = read.get_var_int()?.0; let num_to_remove = read.get_var_int()?.0; if num_to_add < 0 || num_to_remove < 0 { return Err(ReadingError::Message("Negative component count".into())); } let total_components = num_to_add .checked_add(num_to_remove) .ok_or_else(|| ReadingError::Message("Component count overflow".into()))?; if total_components > MAX_COMPONENTS { return Err(ReadingError::Message( "Too many components in ItemStack patch".into(), )); } let mut patch = Vec::with_capacity(total_components as usize); for _ in 0..num_to_add { let (id, component_impl) = read_length_prefixed_component(read)?; patch.push((id, Some(component_impl))); } for _ in 0..num_to_remove { patch.push((read_component_id(read)?, None)); } let item_id_u16 = item_id .0 .try_into() .map_err(|_| ReadingError::Message("Invalid item id!".into()))?; Ok(ItemStackSerializer(Cow::Owned( ItemStack::new_with_component( item_count_u8, Item::from_id(item_id_u16).unwrap_or(&Item::AIR), patch, ), ))) } pub fn write_with_version( &self, write: &mut impl NetworkWriteExt, version: &JavaMinecraftVersion, ) -> Result<(), WritingError> { let remapped_item_id = remap_item_id_for_version(self.0.item.id, *version); serialize_item_stack_with_id(self.0.as_ref(), remapped_item_id, write) } #[must_use] pub fn to_stack(self) -> ItemStack { self.0.into_owned() } #[must_use] pub fn to_stack_for_version(self, version: &JavaMinecraftVersion) -> ItemStack { let mut stack = self.0.into_owned(); if stack.is_empty() { return stack; } let remapped_item_id = remap_item_id_from_version(stack.item.id, *version); stack.item = Item::from_id(remapped_item_id).unwrap_or(&Item::AIR); stack } } impl From for ItemStackSerializer<'_> { fn from(item: ItemStack) -> Self { ItemStackSerializer(Cow::Owned(item)) } } impl From> for ItemStackSerializer<'_> { fn from(item: Option) -> Self { item.map_or_else( || ItemStackSerializer(Cow::Borrowed(ItemStack::EMPTY)), ItemStackSerializer::from, ) } } #[derive(Debug, Clone)] pub struct ItemComponentHash { pub added: Vec<(VarInt, i32)>, pub removed: Vec, } impl ItemComponentHash { pub fn read(read: &mut impl NetworkReadExt) -> Result { const MAX_COMPONENTS: i32 = 256; let added_length = read.get_var_int()?; if added_length.0 < 0 || added_length.0 > MAX_COMPONENTS { return Err(ReadingError::Message("added_length out of bounds".into())); } let mut added = Vec::with_capacity(added_length.0 as usize); for _ in 0..added_length.0 { let component_id = read.get_var_int()?; let component_value = read.get_i32()?; added.push((component_id, component_value)); } let removed_length = read.get_var_int()?; if removed_length.0 < 0 || removed_length.0 > MAX_COMPONENTS { return Err(ReadingError::Message("removed_length out of bounds".into())); } let mut removed = Vec::with_capacity(removed_length.0 as usize); for _ in 0..removed_length.0 { let component_id = read.get_var_int()?; removed.push(component_id); } Ok(Self { added, removed }) } pub fn write(&self, write: &mut impl NetworkWriteExt) -> Result<(), WritingError> { write.put_var_int(&VarInt::from(self.added.len() as i32))?; for (id, val) in &self.added { write.put_var_int(id)?; write.put_i32(*val)?; } write.put_var_int(&VarInt::from(self.removed.len() as i32))?; for id in &self.removed { write.put_var_int(id)?; } Ok(()) } } #[derive(Debug, Clone)] pub struct ItemStackHash { item_id: VarInt, count: VarInt, components: ItemComponentHash, } #[derive(Debug, Clone)] pub struct OptionalItemStackHash(pub Option); impl OptionalItemStackHash { pub fn read(read: &mut impl NetworkReadExt) -> Result { let is_some = read.get_bool()?; if is_some { let item_id = read.get_var_int()?; let count = read.get_var_int()?; let components = ItemComponentHash::read(read)?; Ok(Self(Some(ItemStackHash { item_id, count, components, }))) } else { Ok(Self(None)) } } pub fn write(&self, write: &mut impl NetworkWriteExt) -> Result<(), WritingError> { if let Some(hash) = &self.0 { write.put_bool(true)?; write.put_var_int(&hash.item_id)?; write.put_var_int(&hash.count)?; hash.components.write(write)?; } else { write.put_bool(false)?; } Ok(()) } #[must_use] pub fn hash_equals(&self, other: &ItemStack) -> bool { if let Some(hash) = &self.0 { if hash.item_id != other.item.id.into() || hash.count != other.item_count.into() { return false; } let calc = || { let mut to_add = 0u8; let mut to_remove = 0u8; for (_id, data) in &other.patch { if data.is_none() { to_remove += 1; } else { to_add += 1; } } (to_add, to_remove) }; let (to_add, to_remove) = calc(); if to_add as usize != hash.components.added.len() || to_remove as usize != hash.components.removed.len() { return false; } for (other_id, data) in &other.patch { if let Some(data) = data { let checksum = data.get_hash(); for (id, hash) in &hash.components.added { if id == &VarInt::from(other_id.to_id()) { if hash == &checksum { break; } return false; } } } else if !hash .components .removed .contains(&VarInt::from(other_id.to_id())) { return false; } } true } else { other.is_empty() } } } pub struct ItemStackTemplateSerializer<'a>(pub Cow<'a, ItemStack>); impl ItemStackTemplateSerializer<'_> { pub fn write_with_version( &self, write: &mut impl NetworkWriteExt, version: &JavaMinecraftVersion, ) -> Result<(), WritingError> { let remapped_item_id = remap_item_id_for_version(self.0.item.id, *version); serialize_any_item_stack_with_id( self.0.as_ref(), remapped_item_id, *version >= JavaMinecraftVersion::V_26_1, write, ) } pub fn write(&self, write: &mut impl NetworkWriteExt) -> Result<(), WritingError> { serialize_any_item_stack_with_id(self.0.as_ref(), self.0.item.id, true, write) } } impl From for ItemStackTemplateSerializer<'_> { fn from(item: ItemStack) -> Self { ItemStackTemplateSerializer(Cow::Owned(item)) } }