472 lines
15 KiB
Rust
472 lines
15 KiB
Rust
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<DataComponent, ReadingError> {
|
|
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<Box<dyn DataComponentImpl>, 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<dyn DataComponentImpl>), 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<ItemStackSerializer<'static>, 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<ItemStackSerializer<'static>, 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<ItemStack> for ItemStackSerializer<'_> {
|
|
fn from(item: ItemStack) -> Self {
|
|
ItemStackSerializer(Cow::Owned(item))
|
|
}
|
|
}
|
|
|
|
impl From<Option<ItemStack>> for ItemStackSerializer<'_> {
|
|
fn from(item: Option<ItemStack>) -> 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<VarInt>,
|
|
}
|
|
|
|
impl ItemComponentHash {
|
|
pub fn read(read: &mut impl NetworkReadExt) -> Result<Self, ReadingError> {
|
|
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<ItemStackHash>);
|
|
|
|
impl OptionalItemStackHash {
|
|
pub fn read(read: &mut impl NetworkReadExt) -> Result<Self, ReadingError> {
|
|
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<ItemStack> for ItemStackTemplateSerializer<'_> {
|
|
fn from(item: ItemStack) -> Self {
|
|
ItemStackTemplateSerializer(Cow::Owned(item))
|
|
}
|
|
}
|