diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2025-08-10 18:55:23 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-10 18:55:23 -0500 |
| commit | 7120842f9d2c659a2f12d8922299c2a761bc5582 (patch) | |
| tree | 0d7976ceec82d914e4c75f23adcdd5839f9960a4 /azalea-core/src | |
| parent | 3b659833c1ad4cca89b4cd553193edcb6d223163 (diff) | |
| download | azalea-drasl-7120842f9d2c659a2f12d8922299c2a761bc5582.tar.xz | |
Send correct data component checksums (#234)
* start implementing data component crc32 hashes
* start doing serde impls for checksums
* make more components hashable
* make all data components serializable
* support recursive components
* fix simdnbt dep
* update changelog
* clippy
Diffstat (limited to 'azalea-core/src')
| -rw-r--r-- | azalea-core/src/checksum.rs | 823 | ||||
| -rw-r--r-- | azalea-core/src/codec_utils.rs | 83 | ||||
| -rw-r--r-- | azalea-core/src/direction.rs | 8 | ||||
| -rw-r--r-- | azalea-core/src/filterable.rs | 3 | ||||
| -rw-r--r-- | azalea-core/src/hit_result.rs | 19 | ||||
| -rw-r--r-- | azalea-core/src/lib.rs | 2 | ||||
| -rw-r--r-- | azalea-core/src/position.rs | 39 | ||||
| -rw-r--r-- | azalea-core/src/resource_location.rs | 3 | ||||
| -rw-r--r-- | azalea-core/src/sound.rs | 3 |
9 files changed, 957 insertions, 26 deletions
diff --git a/azalea-core/src/checksum.rs b/azalea-core/src/checksum.rs new file mode 100644 index 00000000..8265906f --- /dev/null +++ b/azalea-core/src/checksum.rs @@ -0,0 +1,823 @@ +use std::{cmp::Ordering, fmt, hash::Hasher}; + +use azalea_buf::AzBuf; +use crc32c::Crc32cHasher; +use serde::{Serialize, ser}; +use thiserror::Error; +use tracing::error; + +use crate::{registry_holder::RegistryHolder, resource_location::ResourceLocation}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, AzBuf)] +pub struct Checksum(pub u32); + +pub struct ChecksumSerializer<'a, 'r> { + hasher: &'a mut Crc32cHasher, + registries: &'r RegistryHolder, +} +impl<'a, 'r> ChecksumSerializer<'a, 'r> { + pub fn checksum(&mut self) -> Checksum { + Checksum(self.hasher.finish() as u32) + } +} + +impl<'a, 'r> ser::Serializer for ChecksumSerializer<'a, 'r> { + type Ok = (); + + // The error type when some error occurs during serialization. + type Error = ChecksumError; + + type SerializeSeq = ChecksumListSerializer<'a, 'r>; + type SerializeTuple = ChecksumListSerializer<'a, 'r>; + type SerializeTupleStruct = ChecksumListSerializer<'a, 'r>; + type SerializeTupleVariant = ChecksumMapSerializer<'a, 'r>; + type SerializeMap = ChecksumMapSerializer<'a, 'r>; + type SerializeStruct = ChecksumMapSerializer<'a, 'r>; + type SerializeStructVariant = ChecksumMapSerializer<'a, 'r>; + + fn serialize_bool(self, v: bool) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(13); + self.hasher.write(&[v as u8]); + Ok(()) + } + + fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> { + assert!(self.hasher.finish() == 0); + Ok(ChecksumMapSerializer { + hasher: self.hasher, + registries: self.registries, + entries: Vec::new(), + }) + } + fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> { + assert!(self.hasher.finish() == 0); + self.serialize_map(Some(len)) + } + + fn serialize_i8(self, v: i8) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(6); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_i16(self, v: i16) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(7); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_i32(self, v: i32) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(8); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_i64(self, v: i64) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(9); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_i8(v as i8) + } + + fn serialize_u16(self, v: u16) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_i16(v as i16) + } + + fn serialize_u32(self, v: u32) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_i32(v as i32) + } + + fn serialize_u64(self, v: u64) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_i64(v as i64) + } + + fn serialize_f32(self, v: f32) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(10); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_f64(self, v: f64) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(11); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_char(self, v: char) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_u32(v as u32) + } + + fn serialize_str(self, v: &str) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(12); + let utf16 = v.encode_utf16().collect::<Vec<_>>(); + self.hasher.write(&(utf16.len() as u32).to_le_bytes()); + for c in utf16 { + self.hasher.write(&c.to_le_bytes()); + } + Ok(()) + } + + fn serialize_bytes(self, v: &[u8]) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(14); + self.hasher.write(v); + self.hasher.write_u8(15); + Ok(()) + } + + fn serialize_none(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + println!("serialize none"); + self.hasher.write_u8(1); + Ok(()) + } + + fn serialize_some<T>(self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + // check if t + + value.serialize(self)?; + Ok(()) + } + + fn serialize_unit(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + assert!(self.hasher.finish() == 0); + update_hasher_for_map(self.hasher, &[]); + Ok(()) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result<()> { + self.serialize_str(variant) + } + + fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant<T>( + self, + name: &'static str, + variant_index: u32, + _variant: &'static str, + value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + // we can't have custom handlers with serde's traits, so we use this silly hack + // to make serializing data-driven registries work + if name.starts_with("minecraft:") { + let value = self + .registries + .map + .get(&ResourceLocation::from(name)) + .and_then(|r| r.get_index(variant_index as usize)) + .map(|r| r.0.to_string()) + .unwrap_or_default(); + self.serialize_str(&value)?; + return Ok(()); + } + + value.serialize(ChecksumSerializer { + hasher: self.hasher, + registries: self.registries, + }) + } + + fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> { + assert!(self.hasher.finish() == 0); + println!("serialize seq with len: {:?}", len); + Ok(ChecksumListSerializer { + hasher: self.hasher, + registries: self.registries, + values: Vec::with_capacity(len.unwrap_or_default()), + list_kind: ListKind::Normal, + }) + } + + fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> { + assert!(self.hasher.finish() == 0); + Ok(ChecksumListSerializer { + hasher: self.hasher, + registries: self.registries, + values: Vec::with_capacity(len), + list_kind: ListKind::Normal, + }) + } + + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result<Self::SerializeTupleStruct> { + assert!(self.hasher.finish() == 0); + let list_kind = if name == "azalea:int_array" { + self.hasher.write_u8(16); + ListKind::Int + } else if name == "azalea:long_array" { + self.hasher.write_u8(18); + ListKind::Long + } else { + ListKind::Normal + }; + Ok(ChecksumListSerializer { + hasher: self.hasher, + registries: self.registries, + values: Vec::with_capacity(len), + list_kind, + }) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result<Self::SerializeTupleVariant> { + assert!(self.hasher.finish() == 0); + Ok(ChecksumMapSerializer { + hasher: self.hasher, + registries: self.registries, + entries: Vec::with_capacity(len), + }) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result<Self::SerializeStructVariant> { + Ok(ChecksumMapSerializer { + hasher: self.hasher, + registries: self.registries, + entries: Vec::with_capacity(len), + }) + } +} + +pub struct ChecksumListSerializer<'a, 'r> { + hasher: &'a mut Crc32cHasher, + registries: &'r RegistryHolder, + values: Vec<Checksum>, + /// If you set this to not be the default, you should also update the hasher + /// before creating the list serializer. + list_kind: ListKind, +} +impl<'a, 'r> ser::SerializeSeq for ChecksumListSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_element<T>(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if self.list_kind == ListKind::Normal { + // elements are hashed individually + self.values.push(get_checksum(value, self.registries)?); + } else { + value.serialize(IntOrLongArrayChecksumSerializer { + hasher: self.hasher, + })?; + } + + Ok(()) + } + + fn end(self) -> Result<()> { + match self.list_kind { + ListKind::Normal => { + assert!(self.hasher.finish() == 0); + update_hasher_for_list(self.hasher, &self.values); + } + ListKind::Int => { + self.hasher.write_u8(17); + } + ListKind::Long => { + self.hasher.write_u8(19); + } + } + + Ok(()) + } +} +/// Minecraft sometimes serializes u8/i32/i64 lists differently, so we have to +/// keep track of that when serializing the arrays. +/// +/// Byte arrays aren't included here as they're handled with `serialize_bytes`. +#[derive(Default, PartialEq, Eq)] +enum ListKind { + #[default] + Normal, + Int, + Long, +} + +impl<'a, 'r> ser::SerializeTuple for ChecksumListSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_element<T>(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result<()> { + ser::SerializeSeq::end(self) + } +} +impl<'a, 'r> ser::SerializeTupleStruct for ChecksumListSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_field<T>(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result<()> { + ser::SerializeSeq::end(self) + } +} + +pub struct ChecksumMapSerializer<'a, 'r> { + // this is only written to at the end + hasher: &'a mut Crc32cHasher, + registries: &'r RegistryHolder, + // we have to keep track of the elements like this because they're sorted at the end + entries: Vec<(Checksum, Checksum)>, +} +impl<'a, 'r> ser::SerializeMap for ChecksumMapSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_key<T>(&mut self, key: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + // this 0 is a placeholder + self.entries + .push((get_checksum(key, self.registries)?, Checksum(0))); + Ok(()) + } + + // It doesn't make a difference whether the colon is printed at the end of + // `serialize_key` or at the beginning of `serialize_value`. In this case + // the code is a bit simpler having it here. + fn serialize_value<T>(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + // placeholder gets replaced here + self.entries + .last_mut() + .expect("entry should've already been added") + .1 = get_checksum(value, self.registries)?; + Ok(()) + } + + fn end(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + update_hasher_for_map(self.hasher, &self.entries); + Ok(()) + } +} +impl<'a, 'r> ser::SerializeTupleVariant for ChecksumMapSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_field<T>(&mut self, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + // TODO + error!("tuple variants are not supported when serializing checksums"); + Ok(()) + } + + fn end(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + Ok(()) + } +} +impl<'a, 'r> ser::SerializeStruct for ChecksumMapSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.entries.push(( + get_checksum(key, self.registries)?, + get_checksum(value, self.registries)?, + )); + Ok(()) + } + + fn end(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + update_hasher_for_map(self.hasher, &self.entries); + Ok(()) + } +} +impl<'a, 'r> ser::SerializeStructVariant for ChecksumMapSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.entries.push(( + get_checksum(key, self.registries)?, + get_checksum(value, self.registries)?, + )); + Ok(()) + } + + fn end(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + update_hasher_for_map(self.hasher, &self.entries); + Ok(()) + } +} + +/// A hasher that can only serialize i32 and i64. +struct IntOrLongArrayChecksumSerializer<'a> { + hasher: &'a mut Crc32cHasher, +} +impl<'a> ser::Serializer for IntOrLongArrayChecksumSerializer<'a> { + type Ok = (); + type Error = ChecksumError; + // unused + type SerializeSeq = ChecksumListSerializer<'a, 'a>; + type SerializeTuple = ChecksumListSerializer<'a, 'a>; + type SerializeTupleStruct = ChecksumListSerializer<'a, 'a>; + type SerializeTupleVariant = ChecksumMapSerializer<'a, 'a>; + type SerializeMap = ChecksumMapSerializer<'a, 'a>; + type SerializeStruct = ChecksumMapSerializer<'a, 'a>; + type SerializeStructVariant = ChecksumMapSerializer<'a, 'a>; + + fn serialize_bool(self, _v: bool) -> Result<()> { + unimplemented!() + } + fn serialize_i8(self, _v: i8) -> Result<()> { + unimplemented!() + } + fn serialize_i16(self, _v: i16) -> Result<()> { + unimplemented!() + } + fn serialize_i32(self, v: i32) -> Result<()> { + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + fn serialize_i64(self, v: i64) -> Result<()> { + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + fn serialize_u8(self, _v: u8) -> Result<()> { + unimplemented!() + } + fn serialize_u16(self, _v: u16) -> Result<()> { + unimplemented!() + } + fn serialize_u32(self, v: u32) -> Result<()> { + self.serialize_i32(v as i32) + } + fn serialize_u64(self, v: u64) -> Result<()> { + self.serialize_i64(v as i64) + } + fn serialize_f32(self, _v: f32) -> Result<()> { + unimplemented!() + } + fn serialize_f64(self, _v: f64) -> Result<()> { + unimplemented!() + } + fn serialize_char(self, _v: char) -> Result<()> { + unimplemented!() + } + fn serialize_str(self, _v: &str) -> Result<()> { + unimplemented!() + } + fn serialize_bytes(self, _v: &[u8]) -> Result<()> { + unimplemented!() + } + fn serialize_none(self) -> Result<()> { + unimplemented!() + } + fn serialize_some<T>(self, _v: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + unimplemented!() + } + fn serialize_unit(self) -> Result<()> { + unimplemented!() + } + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + unimplemented!() + } + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> Result<()> { + unimplemented!() + } + fn serialize_newtype_struct<T>(self, _name: &'static str, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + unimplemented!() + } + fn serialize_newtype_variant<T>( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + unimplemented!() + } + fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> { + unimplemented!() + } + fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> { + unimplemented!() + } + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result<Self::SerializeTupleStruct> { + unimplemented!() + } + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result<Self::SerializeTupleVariant> { + unimplemented!() + } + fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> { + unimplemented!() + } + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> { + unimplemented!() + } + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result<Self::SerializeStructVariant> { + unimplemented!() + } +} + +#[derive(Error, Debug)] +#[error("Checksum serialization error")] +pub struct ChecksumError; +impl ser::Error for ChecksumError { + fn custom<T>(msg: T) -> Self + where + T: fmt::Display, + { + eprintln!("Serialization error: {msg}"); + ChecksumError + } +} +type Result<T> = std::result::Result<T, ChecksumError>; + +pub fn get_checksum<T: Serialize + ?Sized>( + value: &T, + registries: &RegistryHolder, +) -> Result<Checksum> { + let mut hasher = Crc32cHasher::default(); + value.serialize(ChecksumSerializer { + hasher: &mut hasher, + registries, + })?; + Ok(Checksum(hasher.finish() as u32)) +} + +fn update_hasher_for_list(h: &mut Crc32cHasher, values: &[Checksum]) { + h.write_u8(4); + for v in values { + h.write(&v.0.to_le_bytes()); + } + h.write_u8(5); +} +fn update_hasher_for_map(h: &mut Crc32cHasher, entries: &[(Checksum, Checksum)]) { + println!("getting checksum for map with {} entries", entries.len()); + h.write_u8(2); + let mut entries = entries.to_vec(); + entries.sort_by(|a, b| match a.0.cmp(&b.0) { + Ordering::Equal => a.1.cmp(&b.1), + other => other, + }); + for (k, v) in entries { + h.write(&k.0.to_le_bytes()); + h.write(&v.0.to_le_bytes()); + } + h.write_u8(3); +} + +// impl AzaleaChecksum for i8 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(6); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for i16 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(7); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for i32 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(8); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for i64 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(9); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for f32 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(10); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for f64 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(11); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for &str { +// fn azalea_checksum(&self) -> HashCode { +// println!("doing checksum for str: {self:?}"); +// let mut h = Crc32cHasher::default(); +// h.write_u8(12); +// h.write(&(self.len() as u32).to_le_bytes()); +// h.write(&self.as_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for String { +// fn azalea_checksum(&self) -> HashCode { +// println!("doing checksum for String: {self:?}"); +// let mut h = Crc32cHasher::default(); +// h.write_u8(12); + +// let utf16 = self.encode_utf16().collect::<Vec<_>>(); +// h.write(&(utf16.len() as u32).to_le_bytes()); +// for c in utf16 { +// h.write(&c.to_le_bytes()); +// } + +// println!("doing checksum for string: {self:?}"); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for bool { +// fn azalea_checksum(&self) -> HashCode { +// println!("doing checksum for bool: {self:?}"); +// let mut h = Crc32cHasher::default(); +// h.write_u8(13); +// h.write_u8(*self as u8); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<u8> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(14); +// h.write(self); +// h.write_u8(15); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<i8> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(14); +// for item in self { +// h.write(&[*item as u8]); +// } +// h.write_u8(15); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<u32> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(16); +// for item in self { +// h.write(&item.to_le_bytes()); +// } +// h.write_u8(17); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<i32> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(16); +// for item in self { +// h.write(&item.to_le_bytes()); +// } +// h.write_u8(17); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<u64> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(18); +// for item in self { +// h.write(&item.to_le_bytes()); +// } +// h.write_u8(19); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<i64> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(18); +// for item in self { +// h.write(&item.to_le_bytes()); +// } +// h.write_u8(19); + +// HashCode(h.finish() as u32) +// } +// } diff --git a/azalea-core/src/codec_utils.rs b/azalea-core/src/codec_utils.rs new file mode 100644 index 00000000..0014f86d --- /dev/null +++ b/azalea-core/src/codec_utils.rs @@ -0,0 +1,83 @@ +//! Some functions that are useful to have when implementing +//! `Serialize`/`Deserialize`, which Azalea uses to imitate Minecraft codecs. + +use azalea_buf::SerializableUuid; +use serde::{Serialize, Serializer, ser::SerializeTupleStruct}; +use uuid::Uuid; + +/// Intended to be used for skipping serialization if the value is the default. +/// +/// ```no_run +/// #[serde(skip_serializing_if = "is_default")] +/// ``` +pub fn is_default<T: Default + PartialEq>(t: &T) -> bool { + *t == Default::default() +} + +/// Intended to be used for skipping serialization if the value is `true`. +/// +/// ```no_run +/// #[serde(skip_serializing_if = "is_true")] +/// ``` +pub fn is_true(t: &bool) -> bool { + *t +} + +/// If the array has a single item, don't serialize as an array +/// +/// ```no_run +/// #[serde(serialize_with = "flatten_array")] +/// ``` +pub fn flatten_array<S: Serializer, T: Serialize>(x: &Vec<T>, s: S) -> Result<S::Ok, S::Error> { + if x.len() == 1 { + x[0].serialize(s) + } else { + x.serialize(s) + } +} + +/// Minecraft writes UUIDs as an IntArray<4> +pub fn uuid<'a, S: Serializer>( + uuid: impl Into<&'a Option<Uuid>>, + serializer: S, +) -> Result<S::Ok, S::Error> { + if let Some(uuid) = uuid.into() { + let arr: [u32; 4] = uuid.to_int_array(); + let arr: [i32; 4] = [arr[0] as i32, arr[1] as i32, arr[2] as i32, arr[3] as i32]; + IntArray(arr).serialize(serializer) + } else { + serializer.serialize_unit() + } +} + +/// An internal type that makes the i32 array be serialized differently. +/// +/// Azalea currently only uses this when writing checksums, but Minecraft also +/// uses this internally when converting types to NBT. +pub struct IntArray<const N: usize>(pub [i32; N]); +/// An internal type that makes the i64 array be serialized differently. +/// +/// Azalea currently only uses this when writing checksums, but Minecraft also +/// uses this internally when converting types to NBT. +pub struct LongArray<const N: usize>(pub [i64; N]); + +impl<const N: usize> Serialize for IntArray<N> { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + // see checksum::serialize_tuple_struct + let mut seq = serializer.serialize_tuple_struct("azalea:int_array", N)?; + for &item in &self.0 { + seq.serialize_field(&item)?; + } + seq.end() + } +} +impl<const N: usize> Serialize for LongArray<N> { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + // see checksum::serialize_tuple_struct + let mut seq = serializer.serialize_tuple_struct("azalea:long_array", N)?; + for &item in &self.0 { + seq.serialize_field(&item)?; + } + seq.end() + } +} diff --git a/azalea-core/src/direction.rs b/azalea-core/src/direction.rs index 9f51ceaf..c794c79a 100644 --- a/azalea-core/src/direction.rs +++ b/azalea-core/src/direction.rs @@ -2,8 +2,9 @@ use azalea_buf::AzBuf; use crate::position::{BlockPos, Vec3}; -#[derive(Clone, Copy, Debug, AzBuf, Default, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive( + Clone, Copy, Debug, AzBuf, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, +)] pub enum Direction { #[default] Down = 0, @@ -90,8 +91,7 @@ impl Direction { /// /// Note that azalea_block has a similar enum named `FacingCardinal` that is /// used for block states. -#[derive(Clone, Copy, Debug, AzBuf, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Copy, Debug, AzBuf, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] pub enum CardinalDirection { North, South, diff --git a/azalea-core/src/filterable.rs b/azalea-core/src/filterable.rs index 1c432b28..893ebc3e 100644 --- a/azalea-core/src/filterable.rs +++ b/azalea-core/src/filterable.rs @@ -1,10 +1,13 @@ use std::io::{self, Cursor, Write}; use azalea_buf::{AzaleaRead, AzaleaReadLimited, AzaleaReadVar, AzaleaWrite}; +use serde::Serialize; /// Used for written books. +#[derive(Serialize)] pub struct Filterable<T> { pub raw: T, + #[serde(skip_serializing_if = "Option::is_none")] pub filtered: Option<T>, } diff --git a/azalea-core/src/hit_result.rs b/azalea-core/src/hit_result.rs index fe759d43..96ff32c9 100644 --- a/azalea-core/src/hit_result.rs +++ b/azalea-core/src/hit_result.rs @@ -1,5 +1,3 @@ -use bevy_ecs::entity::Entity; - use crate::{ direction::Direction, position::{BlockPos, Vec3}, @@ -8,12 +6,14 @@ use crate::{ /// The block or entity that our player is looking at and can interact with. /// /// If there's nothing, it'll be a [`BlockHitResult`] with `miss` set to true. +#[cfg(feature = "bevy_ecs")] #[derive(Debug, Clone, PartialEq)] pub enum HitResult { Block(BlockHitResult), Entity(EntityHitResult), } +#[cfg(feature = "bevy_ecs")] impl HitResult { pub fn miss(&self) -> bool { match self { @@ -86,18 +86,21 @@ impl BlockHitResult { Self { block_pos, ..*self } } } +#[cfg(feature = "bevy_ecs")] +impl From<BlockHitResult> for HitResult { + fn from(value: BlockHitResult) -> Self { + HitResult::Block(value) + } +} +#[cfg(feature = "bevy_ecs")] #[derive(Debug, Clone, PartialEq)] pub struct EntityHitResult { pub location: Vec3, - pub entity: Entity, + pub entity: bevy_ecs::entity::Entity, } -impl From<BlockHitResult> for HitResult { - fn from(value: BlockHitResult) -> Self { - HitResult::Block(value) - } -} +#[cfg(feature = "bevy_ecs")] impl From<EntityHitResult> for HitResult { fn from(value: EntityHitResult) -> Self { HitResult::Entity(value) diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs index 9f6e9386..9fdf4b6c 100644 --- a/azalea-core/src/lib.rs +++ b/azalea-core/src/lib.rs @@ -4,6 +4,8 @@ pub mod aabb; pub mod bitset; +pub mod checksum; +pub mod codec_utils; pub mod color; pub mod cursor3d; pub mod data_registry; diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index dd6a37e0..8ba381a5 100644 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -13,8 +13,12 @@ use std::{ }; use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError}; +use serde::{Serialize, Serializer}; +use simdnbt::Deserialize; -use crate::{direction::Direction, math, resource_location::ResourceLocation}; +use crate::{ + codec_utils::IntArray, direction::Direction, math, resource_location::ResourceLocation, +}; macro_rules! vec3_impl { ($name:ident, $type:ty) => { @@ -295,8 +299,7 @@ macro_rules! vec3_impl { /// Used to represent an exact position in the world where an entity could be. /// /// For blocks, [`BlockPos`] is used instead. -#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf, serde::Deserialize, serde::Serialize)] pub struct Vec3 { pub x: f64, pub y: f64, @@ -362,7 +365,6 @@ impl Vec3 { /// /// For entities (if the coordinates are floating-point), use [`Vec3`] instead. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct BlockPos { pub x: i32, pub y: i32, @@ -425,6 +427,24 @@ impl BlockPos { (self - other).length() } } +impl serde::Serialize for BlockPos { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + // makes sure it gets serialized correctly for the checksum + IntArray([self.x, self.y, self.z]).serialize(serializer) + } +} +impl<'de> serde::Deserialize<'de> for BlockPos { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let [x, y, z] = <[i32; 3]>::deserialize(deserializer)?; + Ok(BlockPos { x, y, z }) + } +} /// Similar to [`BlockPos`] but it's serialized as 3 varints instead of one /// 64-bit integer, so it can represent a bigger range of numbers. @@ -661,10 +681,10 @@ impl From<ChunkSectionBlockPos> for u16 { impl nohash_hasher::IsEnabled for ChunkSectionBlockPos {} /// A block pos with an attached world -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize)] pub struct GlobalPos { // this is actually a ResourceKey in Minecraft, but i don't think it matters? - pub world: ResourceLocation, + pub dimension: ResourceLocation, pub pos: BlockPos, } @@ -819,8 +839,7 @@ impl fmt::Display for Vec3 { } /// A 2D vector. -#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf, Deserialize, Serialize)] pub struct Vec2 { pub x: f32, pub y: f32, @@ -900,7 +919,7 @@ impl AzaleaRead for BlockPos { impl AzaleaRead for GlobalPos { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { Ok(GlobalPos { - world: ResourceLocation::azalea_read(buf)?, + dimension: ResourceLocation::azalea_read(buf)?, pos: BlockPos::azalea_read(buf)?, }) } @@ -929,7 +948,7 @@ impl AzaleaWrite for BlockPos { impl AzaleaWrite for GlobalPos { fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { - ResourceLocation::azalea_write(&self.world, buf)?; + ResourceLocation::azalea_write(&self.dimension, buf)?; BlockPos::azalea_write(&self.pos, buf)?; Ok(()) diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs index c6e39150..1591f678 100644 --- a/azalea-core/src/resource_location.rs +++ b/azalea-core/src/resource_location.rs @@ -7,7 +7,6 @@ use std::{ }; use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; -#[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use simdnbt::{FromNbtTag, ToNbtTag, owned::NbtTag}; @@ -77,7 +76,6 @@ impl AzaleaWrite for ResourceLocation { } } -#[cfg(feature = "serde")] impl Serialize for ResourceLocation { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where @@ -87,7 +85,6 @@ impl Serialize for ResourceLocation { } } -#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for ResourceLocation { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where diff --git a/azalea-core/src/sound.rs b/azalea-core/src/sound.rs index f12205e0..3f7b86c3 100644 --- a/azalea-core/src/sound.rs +++ b/azalea-core/src/sound.rs @@ -1,8 +1,9 @@ use azalea_buf::AzBuf; +use serde::Serialize; use crate::resource_location::ResourceLocation; -#[derive(Clone, Debug, PartialEq, AzBuf)] +#[derive(Clone, Debug, PartialEq, AzBuf, Serialize)] pub struct CustomSound { pub location: ResourceLocation, pub fixed_range: Option<f32>, |
