From f4580723582f338db5104e67bab3a79a7f038f48 Mon Sep 17 00:00:00 2001 From: kralverde Date: Thu, 13 Feb 2025 23:24:38 -1000 Subject: [PATCH] replace everything with pumpkin_nbt --- pumpkin-nbt/src/deserializer.rs | 44 ++++++++++------ pumpkin-nbt/src/lib.rs | 2 + pumpkin-nbt/src/serializer.rs | 36 ++++++-------- pumpkin-registry/src/lib.rs | 1 + pumpkin-world/Cargo.toml | 3 +- pumpkin-world/src/chunk/anvil.rs | 8 +-- pumpkin-world/src/chunk/mod.rs | 24 ++++----- pumpkin-world/src/world_info/anvil.rs | 72 +++++++++++++++++++-------- 8 files changed, 117 insertions(+), 73 deletions(-) diff --git a/pumpkin-nbt/src/deserializer.rs b/pumpkin-nbt/src/deserializer.rs index 7b0afcc54..f2879cdf2 100644 --- a/pumpkin-nbt/src/deserializer.rs +++ b/pumpkin-nbt/src/deserializer.rs @@ -1,6 +1,6 @@ use crate::*; use io::Read; -use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor}; +use serde::de::{self, DeserializeSeed, IntoDeserializer, MapAccess, SeqAccess, Visitor}; use serde::{forward_to_deserialize_any, Deserialize}; pub type Result = std::result::Result; @@ -54,15 +54,6 @@ impl ReadAdaptor { Ok(u16::from_be_bytes(buf)) } - pub fn get_u32_be(&mut self) -> Result { - let mut buf = [0u8; 4]; - self.reader - .read_exact(&mut buf) - .map_err(Error::Incomplete)?; - - Ok(u32::from_be_bytes(buf)) - } - pub fn get_i32_be(&mut self) -> Result { let mut buf = [0u8; 4]; self.reader @@ -151,7 +142,7 @@ impl<'de, R: Read> de::Deserializer<'de> for &mut Deserializer { forward_to_deserialize_any! { i8 i16 i32 i64 f32 f64 char str string unit unit_struct seq tuple tuple_struct - ignored_any bytes enum newtype_struct byte_buf option + ignored_any bytes newtype_struct byte_buf } fn deserialize_any(self, visitor: V) -> Result @@ -169,7 +160,11 @@ impl<'de, R: Read> de::Deserializer<'de> for &mut Deserializer { }; if let Some(list_type) = list_type { - let remaining_values = self.input.get_u32_be()?; + let remaining_values = self.input.get_i32_be()?; + if remaining_values < 0 { + return Err(Error::NegativeLength(remaining_values)); + } + return visitor.visit_seq(ListAccess { de: self, list_type, @@ -254,6 +249,27 @@ impl<'de, R: Read> de::Deserializer<'de> for &mut Deserializer { } } + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + let variant = get_nbt_string(&mut self.input)?; + visitor.visit_enum(variant.into_deserializer()) + } + + fn deserialize_option(self, visitor: V) -> std::result::Result + where + V: Visitor<'de>, + { + // None is not encoded, so no need for it + visitor.visit_some(self) + } + fn deserialize_map(self, visitor: V) -> Result where V: Visitor<'de>, @@ -330,7 +346,7 @@ impl<'de, R: Read> MapAccess<'de> for CompoundAccess<'_, R> { struct ListAccess<'a, R: Read> { de: &'a mut Deserializer, - remaining_values: u32, + remaining_values: i32, list_type: u8, } @@ -341,7 +357,7 @@ impl<'de, R: Read> SeqAccess<'de> for ListAccess<'_, R> { where E: DeserializeSeed<'de>, { - if self.remaining_values == 0 { + if self.remaining_values <= 0 { return Ok(None); } diff --git a/pumpkin-nbt/src/lib.rs b/pumpkin-nbt/src/lib.rs index 6a8c19a85..9ad89268b 100644 --- a/pumpkin-nbt/src/lib.rs +++ b/pumpkin-nbt/src/lib.rs @@ -348,6 +348,8 @@ mod test { let mut serialized = Vec::new(); to_bytes(&*LEVEL_DAT, &mut serialized).expect("Failed to encode to bytes"); + assert!(!serialized.is_empty()); + let level_dat_again: LevelDat = from_bytes(&serialized[..]).expect("Failed to decode from bytes"); diff --git a/pumpkin-nbt/src/serializer.rs b/pumpkin-nbt/src/serializer.rs index 2eb7b5903..99fc4e477 100644 --- a/pumpkin-nbt/src/serializer.rs +++ b/pumpkin-nbt/src/serializer.rs @@ -263,12 +263,12 @@ impl ser::Serializer for &mut Serializer { fn serialize_str(self, v: &str) -> Result<()> { self.parse_state(STRING_ID)?; + + NbtTag::String(v.to_string()).serialize_data(&mut self.output)?; + if self.state == State::MapKey { self.state = State::Named(v.to_string()); - return Ok(()); } - - NbtTag::String(v.to_string()).serialize_data(&mut self.output)?; Ok(()) } @@ -355,10 +355,13 @@ impl ser::Serializer for &mut Serializer { } fn serialize_seq(self, len: Option) -> Result { - if len.is_none() { + let Some(len) = len else { return Err(Error::SerdeError( "Length of the sequence must be known first!".to_string(), )); + }; + if len > i32::MAX as usize { + return Err(Error::LargeLength(len)); } match &mut self.state { @@ -375,19 +378,18 @@ impl ser::Serializer for &mut Serializer { }; self.parse_state(id)?; - let len = len.unwrap(); - if len > i32::MAX as usize { - return Err(Error::LargeLength(len)); - } - self.output.write_i32_be(len as i32)?; self.state = State::ListElement; } _ => { self.parse_state(LIST_ID)?; - self.state = State::FirstListElement { - len: len.unwrap() as i32, - }; + self.state = State::FirstListElement { len: len as i32 }; + if len == 0 { + // If we have no elements, FirstListElement state will never be invoked; so + // write the list type and length here. + self.output.write_u8_be(END_ID)?; + self.output.write_i32_be(0)?; + } } } @@ -428,20 +430,14 @@ impl ser::Serializer for &mut Serializer { } fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { - self.output.write_u8_be(COMPOUND_ID)?; - match &mut self.state { State::Root(root_name) => { + self.output.write_u8_be(COMPOUND_ID)?; if let Some(root_name) = root_name { NbtTag::String(root_name.clone()).serialize_data(&mut self.output)?; } } - State::Named(string) => { - NbtTag::String(string.clone()).serialize_data(&mut self.output)?; - } - _ => { - unimplemented!() - } + _ => self.parse_state(COMPOUND_ID)?, } Ok(self) diff --git a/pumpkin-registry/src/lib.rs b/pumpkin-registry/src/lib.rs index 67b575201..9b6e9e035 100644 --- a/pumpkin-registry/src/lib.rs +++ b/pumpkin-registry/src/lib.rs @@ -104,6 +104,7 @@ impl Registry { .iter() .map(|(name, nbt)| RegistryEntry::from_nbt(name, nbt)) .collect(); + let chat_type = Registry { registry_id: Identifier::vanilla("chat_type"), registry_entries, diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 7ab39505e..387a60459 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -38,8 +38,6 @@ indexmap = "2.7" enum_dispatch = "0.3" -fastnbt = { git = "https://github.com/owengage/fastnbt.git" } - noise = "0.9" rand = "0.8" @@ -49,6 +47,7 @@ serde_json5 = { git = "https://github.com/kralverde/serde_json5.git" } derive-getters = "0.5.0" [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } +temp-dir = "0.1.14" [[bench]] name = "chunk_noise_populate" diff --git a/pumpkin-world/src/chunk/anvil.rs b/pumpkin-world/src/chunk/anvil.rs index 43c853c70..ceb56069e 100644 --- a/pumpkin-world/src/chunk/anvil.rs +++ b/pumpkin-world/src/chunk/anvil.rs @@ -1,8 +1,8 @@ use bytes::*; -use fastnbt::LongArray; use flate2::bufread::{GzDecoder, GzEncoder, ZlibDecoder, ZlibEncoder}; use indexmap::IndexMap; use pumpkin_config::ADVANCED_CONFIG; +use pumpkin_nbt::serializer::to_bytes; use pumpkin_util::math::ceil_log2; use std::time::{SystemTime, UNIX_EPOCH}; use std::{ @@ -425,7 +425,7 @@ impl AnvilChunkFormat { sections.push(ChunkSection { y: i as i8 - 4, block_states: Some(ChunkSectionBlockStates { - data: Some(LongArray::new(section_longs)), + data: Some(section_longs.into_boxed_slice()), palette: palette .into_iter() .map(|entry| PaletteEntry { @@ -446,7 +446,9 @@ impl AnvilChunkFormat { sections, }; - fastnbt::to_bytes(&nbt).map_err(ChunkSerializingError::ErrorSerializingChunk) + let mut result = Vec::new(); + to_bytes(&nbt, &mut result).map_err(ChunkSerializingError::ErrorSerializingChunk)?; + Ok(result) } /// Returns the next free writable sector diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 7c2b1abbe..12267e891 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -2,8 +2,8 @@ use dashmap::{ mapref::one::{Ref, RefMut}, DashMap, }; -use fastnbt::LongArray; use pumpkin_data::chunk::ChunkStatus; +use pumpkin_nbt::deserializer::from_bytes; use pumpkin_util::math::{ceil_log2, vector2::Vector2}; use serde::{Deserialize, Serialize}; use std::{ @@ -168,9 +168,9 @@ struct PaletteEntry { #[serde(rename_all = "UPPERCASE")] pub struct ChunkHeightmaps { // #[serde(with = "LongArray")] - motion_blocking: LongArray, + motion_blocking: Box<[i64]>, // #[serde(with = "LongArray")] - world_surface: LongArray, + world_surface: Box<[i64]>, } #[derive(Serialize, Deserialize, Debug)] @@ -183,7 +183,7 @@ struct ChunkSection { #[derive(Serialize, Deserialize, Debug, Clone)] struct ChunkSectionBlockStates { // #[serde(with = "LongArray")] - data: Option, + data: Option>, palette: Vec, } @@ -234,8 +234,8 @@ impl Default for ChunkHeightmaps { fn default() -> Self { Self { // 0 packed into an i64 7 times. - motion_blocking: LongArray::new(vec![0; 37]), - world_surface: LongArray::new(vec![0; 37]), + motion_blocking: vec![0; 37].into_boxed_slice(), + world_surface: vec![0; 37].into_boxed_slice(), } } } @@ -402,15 +402,15 @@ impl ChunkData { chunk_data: &[u8], position: Vector2, ) -> Result { - if fastnbt::from_bytes::(chunk_data) - .map_err(|_| ChunkParsingError::FailedReadStatus)? + if from_bytes::(chunk_data) + .map_err(ChunkParsingError::FailedReadStatus)? .status != ChunkStatus::Full { return Err(ChunkParsingError::ChunkNotGenerated); } - let chunk_data = fastnbt::from_bytes::(chunk_data) + let chunk_data = from_bytes::(chunk_data) .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?; if chunk_data.x_pos != position.x || chunk_data.z_pos != position.z { @@ -502,8 +502,8 @@ impl ChunkData { #[derive(Error, Debug)] pub enum ChunkParsingError { - #[error("Failed reading chunk status")] - FailedReadStatus, + #[error("Failed reading chunk status {0}")] + FailedReadStatus(pumpkin_nbt::Error), #[error("The chunk isn't generated yet")] ChunkNotGenerated, #[error("Error deserializing chunk: {0}")] @@ -517,5 +517,5 @@ fn convert_index(index: ChunkRelativeBlockCoordinates) -> usize { #[derive(Error, Debug)] pub enum ChunkSerializingError { #[error("Error serializing chunk: {0}")] - ErrorSerializingChunk(fastnbt::error::Error), + ErrorSerializingChunk(pumpkin_nbt::Error), } diff --git a/pumpkin-world/src/world_info/anvil.rs b/pumpkin-world/src/world_info/anvil.rs index 3dfbb897d..0040b6161 100644 --- a/pumpkin-world/src/world_info/anvil.rs +++ b/pumpkin-world/src/world_info/anvil.rs @@ -1,10 +1,10 @@ use std::{ fs::OpenOptions, - io::{Read, Write}, time::{SystemTime, UNIX_EPOCH}, }; use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +use pumpkin_nbt::{deserializer::from_bytes, serializer::to_bytes}; use serde::{Deserialize, Serialize}; use crate::level::LevelFolder; @@ -19,17 +19,9 @@ impl WorldInfoReader for AnvilLevelInfo { fn read_world_info(&self, level_folder: &LevelFolder) -> Result { let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME); - let mut world_info_file = OpenOptions::new().read(true).open(path)?; - - let mut buffer = Vec::new(); - world_info_file.read_to_end(&mut buffer)?; - - // try to decompress using GZip - let mut decoder = GzDecoder::new(&buffer[..]); - let mut decompressed_data = Vec::new(); - decoder.read_to_end(&mut decompressed_data)?; - - let info = fastnbt::from_bytes::(&decompressed_data) + let world_info_file = OpenOptions::new().read(true).open(path)?; + let compression_reader = GzDecoder::new(world_info_file); + let info = from_bytes::(compression_reader) .map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?; // todo check version @@ -52,23 +44,27 @@ impl WorldInfoWriter for AnvilLevelInfo { level_data.last_played = since_the_epoch.as_millis() as i64; let level = LevelDat { data: level_data }; - // convert it into nbt - let mut nbt = Vec::new(); - pumpkin_nbt::serializer::to_bytes_unnamed(&level, &mut nbt).unwrap(); - // now compress using GZip, TODO: im not sure about the to_vec, but writer is not implemented for BytesMut, see https://github.com/tokio-rs/bytes/pull/478 - let mut encoder = GzEncoder::new(nbt.to_vec(), Compression::best()); - let compressed_data = Vec::new(); - encoder.write_all(&compressed_data)?; - // open file let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME); - let mut world_info_file = OpenOptions::new() + let world_info_file = OpenOptions::new() .truncate(true) .create(true) .write(true) .open(path)?; + // write compressed data into file - world_info_file.write_all(&compressed_data).unwrap(); + let compression_writer = GzEncoder::new(world_info_file, Compression::best()); + // TODO: Proper error handling + to_bytes(&level, compression_writer).unwrap(); + + /* + let mut raw_nbt = Vec::new(); + to_bytes(&level, &mut raw_nbt).unwrap(); + println!("{:02X?}", &raw_nbt); + + let mut encoder = GzEncoder::new(world_info_file, Compression::best()); + encoder.write_all(&raw_nbt).unwrap(); + */ Ok(()) } @@ -80,3 +76,35 @@ pub struct LevelDat { #[serde(rename = "Data")] pub data: LevelData, } + +#[cfg(test)] +mod test { + + use temp_dir::TempDir; + + use crate::{level::LevelFolder, world_info::LevelData}; + + use super::{AnvilLevelInfo, WorldInfoReader, WorldInfoWriter}; + + #[test] + fn test_perserve_level_dat_seed() { + let seed = 1337; + + let mut data = LevelData::default(); + data.world_gen_settings.seed = seed; + + let temp_dir = TempDir::new().unwrap(); + let level_folder = LevelFolder { + root_folder: temp_dir.path().to_path_buf(), + region_folder: temp_dir.path().join("region"), + }; + + AnvilLevelInfo + .write_world_info(data, &level_folder) + .unwrap(); + + let data = AnvilLevelInfo.read_world_info(&level_folder).unwrap(); + + assert_eq!(data.world_gen_settings.seed, seed); + } +}