chore: chunk loading/saving move from serde to manual
should be faster now
This commit is contained in:
parent
0d348892e5
commit
7350fba3b0
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -192,9 +192,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.23.0"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9"
|
||||
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
@ -2615,7 +2615,7 @@ name = "pumpkin"
|
||||
version = "0.1.0-dev+26.2"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"base64 0.23.0",
|
||||
"base64 0.23.1",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"colored",
|
||||
@ -2749,7 +2749,6 @@ dependencies = [
|
||||
"criterion",
|
||||
"flate2",
|
||||
"pumpkin-codecs",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror 2.0.19",
|
||||
"tracing",
|
||||
@ -2795,7 +2794,7 @@ dependencies = [
|
||||
name = "pumpkin-util"
|
||||
version = "0.1.0-dev+26.2"
|
||||
dependencies = [
|
||||
"base64 0.23.0",
|
||||
"base64 0.23.1",
|
||||
"bytes",
|
||||
"colored",
|
||||
"crypto-bigint",
|
||||
|
||||
@ -119,7 +119,7 @@ tempfile = { version = "3.27.0", default-features = false }
|
||||
proc-macro-error2 = { version = "2", default-features = false }
|
||||
aes = { version = "0.9", default-features = false }
|
||||
async-compression = { version = "0.4.43", default-features = false }
|
||||
base64 = { version = "0.23.0", default-features = false, features = ["std"] }
|
||||
base64 = { version = "0.23.1", default-features = false, features = ["std"] }
|
||||
bitflags = { version = "2.13.1", default-features = false, features = ["std"] }
|
||||
cesu8 = { version = "1.1", default-features = false }
|
||||
cfb8 = { version = "0.9", default-features = false }
|
||||
|
||||
51
pumpkin-codegen/Cargo.lock
generated
51
pumpkin-codegen/Cargo.lock
generated
@ -10,9 +10,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.103"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
@ -34,9 +34,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.23.0"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9"
|
||||
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
@ -584,7 +584,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.2",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -749,9 +749,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -781,7 +781,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_json5",
|
||||
"serde_repr",
|
||||
"syn 3.0.2",
|
||||
"syn 3.0.3",
|
||||
"wit-encoder",
|
||||
]
|
||||
|
||||
@ -793,7 +793,6 @@ dependencies = [
|
||||
"cesu8",
|
||||
"flate2",
|
||||
"pumpkin-codecs",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
@ -803,7 +802,7 @@ dependencies = [
|
||||
name = "pumpkin-util"
|
||||
version = "0.1.0-dev+26.2"
|
||||
dependencies = [
|
||||
"base64 0.23.0",
|
||||
"base64 0.23.1",
|
||||
"bytes",
|
||||
"colored",
|
||||
"crypto-bigint",
|
||||
@ -820,7 +819,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
"syn 3.0.2",
|
||||
"syn 3.0.3",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"ureq",
|
||||
@ -829,9 +828,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.46"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -1002,7 +1001,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.2",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1124,9 +1123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.2"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3"
|
||||
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1381,9 +1380,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.254.0"
|
||||
version = "0.255.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27"
|
||||
checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
@ -1559,15 +1558,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-encoder"
|
||||
version = "0.254.0"
|
||||
version = "0.255.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c565f1f3dcf3b659d6c15052739e35937227590d7dded3d3ce274f7bd9c34d93"
|
||||
checksum = "7cebd2d14c3296dada506c67c1ee5c9440811a2fe88a83367a606ccbd30ebfde"
|
||||
dependencies = [
|
||||
"id-arena",
|
||||
"pretty_assertions",
|
||||
"semver",
|
||||
"serde",
|
||||
"wit-parser 0.254.0",
|
||||
"wit-parser 0.255.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1590,9 +1589,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.254.0"
|
||||
version = "0.255.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1655131e4f7d3f0cb141f6eca71315ca40eff0f3d4de7cff0a82bacedd8c89b4"
|
||||
checksum = "ab5f6371fc71f15730b756c1dea3562a67adab1a7e519c4ca010173d883695bb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hashbrown 0.17.1",
|
||||
@ -1604,7 +1603,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-ident",
|
||||
"wasmparser 0.254.0",
|
||||
"wasmparser 0.255.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1632,6 +1631,6 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
@ -16,7 +16,7 @@ serde_repr = { version = "0.1" }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
serde_json5 = "0.2"
|
||||
|
||||
wit-encoder = "0.254.0"
|
||||
wit-encoder = "0.255.0"
|
||||
semver = "1.0"
|
||||
rayon = "1.12.0"
|
||||
|
||||
|
||||
@ -45,67 +45,60 @@ pub fn build() -> TokenStream {
|
||||
let nbt_bytes = fs::read(nbt_path).expect("Failed to read bedrock/creative_items.nbt");
|
||||
let mut cursor = Cursor::new(nbt_bytes);
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct CreativeGroupEntry {
|
||||
category: i32,
|
||||
name: String,
|
||||
icon: Option<CreativeItemEntry>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct CreativeItemEntry {
|
||||
name: String,
|
||||
meta: Option<i16>,
|
||||
group_index: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct CreativeData {
|
||||
groups: Vec<CreativeGroupEntry>,
|
||||
items: Vec<CreativeItemEntry>,
|
||||
}
|
||||
|
||||
let data: CreativeData = pumpkin_nbt::deserializer::from_bytes_bedrock(&mut cursor)
|
||||
.expect("Failed to deserialize creative_items.nbt");
|
||||
let mut reader = pumpkin_nbt::deserializer::NbtReadHelperBedrock::new(
|
||||
pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor),
|
||||
);
|
||||
let nbt = pumpkin_nbt::Nbt::read(&mut reader).expect("Failed to read creative_items.nbt");
|
||||
|
||||
let mut group_tokens = Vec::new();
|
||||
for g in &data.groups {
|
||||
let category = g.category;
|
||||
let name = &g.name;
|
||||
let mut icon_item_id = 0i16;
|
||||
let mut icon_item_aux_value = 0u32;
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::List(groups_list)) = nbt.get("groups") {
|
||||
for g_tag in groups_list {
|
||||
if let pumpkin_nbt::tag::NbtTag::Compound(g) = g_tag {
|
||||
let category = g.get_int("category").unwrap_or(0);
|
||||
let name = g.get_string("name").unwrap_or_default().to_string();
|
||||
let mut icon_item_id = 0i16;
|
||||
let mut icon_item_aux_value = 0u32;
|
||||
|
||||
if let Some(ref icon) = g.icon
|
||||
&& let Some(&id) = bedrock_items_map.get(icon.name.as_str())
|
||||
{
|
||||
icon_item_id = id;
|
||||
icon_item_aux_value = icon.meta.unwrap_or(0) as u32;
|
||||
}
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::Compound(icon)) = g.get("icon") {
|
||||
if let Some(icon_name) = icon.get_string("name") {
|
||||
if let Some(&id) = bedrock_items_map.get(icon_name) {
|
||||
icon_item_id = id;
|
||||
icon_item_aux_value = icon.get_short("meta").unwrap_or(0) as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group_tokens.push(quote! {
|
||||
CreativeGroup {
|
||||
category: #category,
|
||||
name: #name,
|
||||
icon_item_id: #icon_item_id,
|
||||
icon_item_aux_value: #icon_item_aux_value,
|
||||
group_tokens.push(quote! {
|
||||
CreativeGroup {
|
||||
category: #category,
|
||||
name: #name,
|
||||
icon_item_id: #icon_item_id,
|
||||
icon_item_aux_value: #icon_item_aux_value,
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Build entry definitions TokenStream
|
||||
let mut entry_tokens = Vec::new();
|
||||
for item in &data.items {
|
||||
if let Some(&id) = bedrock_items_map.get(item.name.as_str()) {
|
||||
let item_aux_value = item.meta.unwrap_or(0) as u32;
|
||||
let group_index = item.group_index.unwrap_or(0) as u32;
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::List(items_list)) = nbt.get("items") {
|
||||
for item_tag in items_list {
|
||||
if let pumpkin_nbt::tag::NbtTag::Compound(item) = item_tag {
|
||||
if let Some(item_name) = item.get_string("name") {
|
||||
if let Some(&id) = bedrock_items_map.get(item_name) {
|
||||
let item_aux_value = item.get_short("meta").unwrap_or(0) as u32;
|
||||
let group_index = item.get_int("group_index").unwrap_or(0) as u32;
|
||||
|
||||
entry_tokens.push(quote! {
|
||||
CreativeEntry {
|
||||
item_id: #id,
|
||||
item_aux_value: #item_aux_value,
|
||||
group_index: #group_index,
|
||||
entry_tokens.push(quote! {
|
||||
CreativeEntry {
|
||||
item_id: #id,
|
||||
item_aux_value: #item_aux_value,
|
||||
group_index: #group_index,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +106,7 @@ pub fn build() -> TokenStream {
|
||||
let entries_len = entry_tokens.len();
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CreativeGroup {
|
||||
pub category: i32,
|
||||
pub name: &'static str,
|
||||
@ -121,7 +114,7 @@ pub fn build() -> TokenStream {
|
||||
pub icon_item_aux_value: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CreativeEntry {
|
||||
pub item_id: i16,
|
||||
pub item_aux_value: u32,
|
||||
|
||||
@ -366,7 +366,7 @@ impl ToTokens for BlockPropertyStruct {
|
||||
};
|
||||
|
||||
tokens.extend(quote! {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct #name {
|
||||
#(#fields),*
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ pub fn build() -> TokenStream {
|
||||
quote! {
|
||||
use crate::tag::{RegistryKey, Tag, Taggable};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct DamageType {
|
||||
pub death_message_type: DeathMessageType,
|
||||
pub exhaustion: f32,
|
||||
@ -125,14 +125,14 @@ pub fn build() -> TokenStream {
|
||||
pub id: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum DeathMessageType {
|
||||
Default,
|
||||
FallVariants,
|
||||
IntentionalGameDesign,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum DamageEffects {
|
||||
Hurt,
|
||||
Thorns,
|
||||
@ -142,7 +142,7 @@ pub fn build() -> TokenStream {
|
||||
Freezing,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum DamageScaling {
|
||||
Never,
|
||||
WhenCausedByLivingNonPlayer,
|
||||
|
||||
@ -48,7 +48,7 @@ pub fn build() -> TokenStream {
|
||||
quote! {
|
||||
use crate::data_component_impl::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum DataComponent {
|
||||
#enum_variants
|
||||
|
||||
@ -109,7 +109,7 @@ impl ToTokens for PropertyStruct {
|
||||
});
|
||||
|
||||
tokens.extend(quote! {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum #name {
|
||||
#(#ident_values),*
|
||||
}
|
||||
@ -208,7 +208,7 @@ impl ToTokens for FluidPropertyStruct {
|
||||
});
|
||||
|
||||
tokens.extend(quote! {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct #name {
|
||||
#(pub #values),*
|
||||
}
|
||||
@ -242,7 +242,7 @@ impl ToTokens for FluidPropertyStruct {
|
||||
|
||||
fn to_state_id(&self, fluid: &Fluid) -> BlockStateId {
|
||||
if ![#(#fluid_names),*].contains(&fluid.name) {
|
||||
panic!("{} is not a valid fluid for {}", &fluid.name, #struct_name);
|
||||
panic!("{} is not a valid fluid for {}", fluid.name, #struct_name);
|
||||
}
|
||||
|
||||
let prop_index = self.to_index();
|
||||
|
||||
@ -1077,12 +1077,6 @@ pub enum Operation {
|
||||
AddMultipliedTotal,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct BedrockItemComponents {
|
||||
components: Option<NbtCompound>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize_repr, Debug)]
|
||||
#[repr(i32)]
|
||||
enum BedrockItemVersion {
|
||||
@ -1156,12 +1150,21 @@ pub fn build() -> TokenStream {
|
||||
.into_keys()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let be_item_components: BTreeMap<String, BedrockItemComponents> = {
|
||||
let be_item_components: BTreeMap<String, Option<NbtCompound>> = {
|
||||
let data = fs::read("../assets/bedrock/item_components.nbt").unwrap();
|
||||
|
||||
let mut cursor = Cursor::new(data);
|
||||
pumpkin_nbt::nbt_compress::from_gzip_bytes(&mut cursor)
|
||||
.expect("Failed to parse bedrock/item_components.nbt")
|
||||
let nbt = pumpkin_nbt::nbt_compress::read_gzip_compound_tag(&mut cursor)
|
||||
.expect("Failed to parse bedrock/item_components.nbt");
|
||||
nbt.child_tags
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
let comp = match v {
|
||||
pumpkin_nbt::tag::NbtTag::Compound(c) => Some(c),
|
||||
_ => None,
|
||||
};
|
||||
(k.into_string(), comp)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let be_runtime_item_states: Vec<BedrockRuntimeItemState> = serde_json::from_str(
|
||||
@ -1349,7 +1352,7 @@ pub fn build() -> TokenStream {
|
||||
|
||||
let mut bedrock_item_definitions = Vec::new();
|
||||
for item in be_runtime_item_states {
|
||||
let components = &be_item_components.get(&item.name).unwrap().components;
|
||||
let components = be_item_components.get(&item.name).cloned().flatten();
|
||||
|
||||
bedrock_item_definitions.push(BedrockItem {
|
||||
id: item.id,
|
||||
|
||||
@ -98,7 +98,7 @@ pub fn build() -> TokenStream {
|
||||
.collect::<TokenStream>();
|
||||
|
||||
quote! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[repr(u32)]
|
||||
pub enum JukeboxSong {
|
||||
#variants
|
||||
|
||||
@ -69,8 +69,47 @@ pub(crate) fn build() -> TokenStream {
|
||||
let entry_tokens: Vec<TokenStream> = entries
|
||||
.iter()
|
||||
.map(|(entry_name, entry_data)| {
|
||||
let mut bytes = Vec::new();
|
||||
pumpkin_nbt::serializer::to_bytes_unnamed(entry_data, &mut bytes).unwrap();
|
||||
fn json_to_nbt_tag(v: &Value) -> pumpkin_nbt::tag::NbtTag {
|
||||
match v {
|
||||
Value::Null => pumpkin_nbt::tag::NbtTag::End,
|
||||
Value::Bool(b) => {
|
||||
pumpkin_nbt::tag::NbtTag::Byte(if *b { 1 } else { 0 })
|
||||
}
|
||||
Value::Number(num) => {
|
||||
if let Some(i) = num.as_i64() {
|
||||
if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
|
||||
pumpkin_nbt::tag::NbtTag::Int(i as i32)
|
||||
} else {
|
||||
pumpkin_nbt::tag::NbtTag::Long(i)
|
||||
}
|
||||
} else if let Some(f) = num.as_f64() {
|
||||
pumpkin_nbt::tag::NbtTag::Double(f)
|
||||
} else {
|
||||
pumpkin_nbt::tag::NbtTag::Int(0)
|
||||
}
|
||||
}
|
||||
Value::String(s) => {
|
||||
pumpkin_nbt::tag::NbtTag::String(s.clone().into())
|
||||
}
|
||||
Value::Array(arr) => pumpkin_nbt::tag::NbtTag::List(
|
||||
arr.iter().map(json_to_nbt_tag).collect(),
|
||||
),
|
||||
Value::Object(obj) => {
|
||||
let mut compound = pumpkin_nbt::compound::NbtCompound::new();
|
||||
for (k, val) in obj {
|
||||
compound.put(k, json_to_nbt_tag(val));
|
||||
}
|
||||
pumpkin_nbt::tag::NbtTag::Compound(compound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nbt_tag = json_to_nbt_tag(entry_data);
|
||||
let bytes = if let pumpkin_nbt::tag::NbtTag::Compound(compound) = nbt_tag {
|
||||
pumpkin_nbt::Nbt::from(compound).write_unnamed()
|
||||
} else {
|
||||
Vec::new().into()
|
||||
};
|
||||
let byte_literal = Literal::byte_string(&bytes);
|
||||
|
||||
quote! {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
/* This file is generated. Do not edit manually. */
|
||||
#[doc = r" The maximum supported view distance."]
|
||||
#[doc = r" The maximum supported view distance"]
|
||||
pub const MAX_VIEW_DISTANCE: u8 = 32u8;
|
||||
#[doc = r" Static precomputed lookup table for relative chunk offsets by view distance (0..=32)."]
|
||||
pub static CHUNK_VIEW_LUT: [&[(i8, i8)]; 33usize] = [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* This file is generated. Do not edit manually. */
|
||||
use crate::tag::{RegistryKey, Tag, Taggable};
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct DamageType {
|
||||
pub death_message_type: DeathMessageType,
|
||||
pub exhaustion: f32,
|
||||
@ -9,13 +9,13 @@ pub struct DamageType {
|
||||
pub scaling: DamageScaling,
|
||||
pub id: u8,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum DeathMessageType {
|
||||
Default,
|
||||
FallVariants,
|
||||
IntentionalGameDesign,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum DamageEffects {
|
||||
Hurt,
|
||||
Thorns,
|
||||
@ -24,7 +24,7 @@ pub enum DamageEffects {
|
||||
Poking,
|
||||
Freezing,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum DamageScaling {
|
||||
Never,
|
||||
WhenCausedByLivingNonPlayer,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* This file is generated. Do not edit manually. */
|
||||
use crate::data_component_impl::*;
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum DataComponent {
|
||||
CustomData = 0u8,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* This file is generated. Do not edit manually. */
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[repr(u32)]
|
||||
pub enum JukeboxSong {
|
||||
Id11,
|
||||
|
||||
@ -7,7 +7,6 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-codecs.workspace = true
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
bytes.workspace = true
|
||||
uuid.workspace = true
|
||||
@ -19,7 +18,6 @@ tracing.workspace = true
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
criterion.workspace = true
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[[bench]]
|
||||
name = "nbt"
|
||||
|
||||
@ -1,41 +1,7 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use pumpkin_nbt::{Nbt, NbtCompound, deserializer, serializer, tag::NbtTag};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pumpkin_nbt::{Nbt, NbtCompound, deserializer, tag::NbtTag};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
struct LargeTest {
|
||||
byte: i8,
|
||||
short: i16,
|
||||
int: i32,
|
||||
long: i64,
|
||||
float: f32,
|
||||
double: f64,
|
||||
string: String,
|
||||
byte_array: Vec<u8>,
|
||||
int_array: Vec<i32>,
|
||||
long_array: Vec<i64>,
|
||||
list_string: Vec<String>,
|
||||
nested: Option<Box<Self>>,
|
||||
}
|
||||
|
||||
fn create_large_test_data(depth: usize) -> LargeTest {
|
||||
LargeTest {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
double: 69.42,
|
||||
string: "Hello test benchmark data".to_string(),
|
||||
byte_array: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
int_array: vec![10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
|
||||
long_array: vec![20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
|
||||
list_string: vec!["one".to_string(), "two".to_string(), "three".to_string()],
|
||||
nested: (depth > 0).then(|| Box::new(create_large_test_data(depth - 1))),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_large_compound(depth: usize) -> NbtCompound {
|
||||
let mut compound = NbtCompound::new();
|
||||
compound.put_byte("byte", 123);
|
||||
@ -72,31 +38,11 @@ fn create_large_compound(depth: usize) -> NbtCompound {
|
||||
}
|
||||
|
||||
pub fn bench_nbt(c: &mut Criterion) {
|
||||
let test_data = create_large_test_data(5);
|
||||
let mut ser_bytes_java = Vec::new();
|
||||
let mut ser_bytes_bedrock = Vec::new();
|
||||
serializer::to_bytes(&test_data, &mut ser_bytes_java).unwrap();
|
||||
serializer::to_bytes_bedrock(&test_data, &mut ser_bytes_bedrock).unwrap();
|
||||
|
||||
let compound_data = create_large_compound(5);
|
||||
let nbt_wrapper = Nbt::new(String::new(), compound_data.clone());
|
||||
let wrapper_bytes_java = nbt_wrapper.clone().write();
|
||||
let wrapper_bytes_bedrock = nbt_wrapper.write_bedrock();
|
||||
|
||||
c.bench_function("nbt/java/serialize/serde", |b| {
|
||||
b.iter(|| {
|
||||
let mut out = Vec::new();
|
||||
serializer::to_bytes(&test_data, &mut out).unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
c.bench_function("nbt/java/deserialize/serde", |b| {
|
||||
b.iter(|| {
|
||||
let cursor = Cursor::new(&ser_bytes_java);
|
||||
let _: LargeTest = deserializer::from_bytes(cursor).unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
c.bench_function("nbt/java/serialize/raw", |b| {
|
||||
b.iter(|| {
|
||||
let nbt = Nbt::new(String::new(), compound_data.clone());
|
||||
@ -112,20 +58,6 @@ pub fn bench_nbt(c: &mut Criterion) {
|
||||
});
|
||||
});
|
||||
|
||||
c.bench_function("nbt/bedrock/serialize/serde", |b| {
|
||||
b.iter(|| {
|
||||
let mut out = Vec::new();
|
||||
serializer::to_bytes_bedrock(&test_data, &mut out).unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
c.bench_function("nbt/bedrock/deserialize/serde", |b| {
|
||||
b.iter(|| {
|
||||
let cursor = Cursor::new(&ser_bytes_bedrock);
|
||||
let _: LargeTest = deserializer::from_bytes_bedrock(cursor).unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
c.bench_function("nbt/bedrock/serialize/raw", |b| {
|
||||
b.iter(|| {
|
||||
let nbt = Nbt::new(String::new(), compound_data.clone());
|
||||
|
||||
@ -11,7 +11,6 @@ cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
arbitrary = { version = "1.3", features = ["derive"] }
|
||||
|
||||
[dependencies.pumpkin-nbt]
|
||||
@ -31,37 +30,9 @@ test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "deserialize_serde_java"
|
||||
path = "fuzz_targets/deserialize_serde_java.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "deserialize_serde_bedrock"
|
||||
path = "fuzz_targets/deserialize_serde_bedrock.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "decompress_gzip"
|
||||
path = "fuzz_targets/decompress_gzip.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "roundtrip_java"
|
||||
path = "fuzz_targets/roundtrip_java.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "roundtrip_bedrock"
|
||||
path = "fuzz_targets/roundtrip_bedrock.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_nbt::deserializer::from_bytes_bedrock;
|
||||
use serde::Deserialize;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
struct FuzzStruct {
|
||||
byte: i8,
|
||||
short: i16,
|
||||
int: i32,
|
||||
long: i64,
|
||||
float: f32,
|
||||
double: f64,
|
||||
string: String,
|
||||
byte_array: Vec<u8>,
|
||||
int_array: Vec<i32>,
|
||||
long_array: Vec<i64>,
|
||||
list_string: Vec<String>,
|
||||
nested: Option<Box<FuzzStruct>>,
|
||||
}
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let cursor = Cursor::new(data);
|
||||
let _: Result<FuzzStruct, _> = from_bytes_bedrock(cursor);
|
||||
});
|
||||
@ -1,29 +0,0 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_nbt::deserializer::{from_bytes, from_bytes_unnamed};
|
||||
use serde::Deserialize;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
struct FuzzStruct {
|
||||
byte: i8,
|
||||
short: i16,
|
||||
int: i32,
|
||||
long: i64,
|
||||
float: f32,
|
||||
double: f64,
|
||||
string: String,
|
||||
byte_array: Vec<u8>,
|
||||
int_array: Vec<i32>,
|
||||
long_array: Vec<i64>,
|
||||
list_string: Vec<String>,
|
||||
nested: Option<Box<FuzzStruct>>,
|
||||
}
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let cursor = Cursor::new(data);
|
||||
let _: Result<FuzzStruct, _> = from_bytes(cursor);
|
||||
|
||||
let cursor_unnamed = Cursor::new(data);
|
||||
let _: Result<FuzzStruct, _> = from_bytes_unnamed(cursor_unnamed);
|
||||
});
|
||||
@ -1,52 +0,0 @@
|
||||
#![no_main]
|
||||
use arbitrary::Arbitrary;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_nbt::deserializer::from_bytes_bedrock;
|
||||
use pumpkin_nbt::serializer::to_bytes_bedrock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Arbitrary, Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct RoundtripStruct {
|
||||
byte: i8,
|
||||
short: i16,
|
||||
int: i32,
|
||||
long: i64,
|
||||
float: f32,
|
||||
double: f64,
|
||||
string: String,
|
||||
byte_array: Vec<u8>,
|
||||
int_array: Vec<i32>,
|
||||
long_array: Vec<i64>,
|
||||
list_string: Vec<String>,
|
||||
// Nested struct to test recursion
|
||||
nested: Option<Box<RoundtripStruct>>,
|
||||
}
|
||||
|
||||
fuzz_target!(|data: RoundtripStruct| {
|
||||
let mut fixed_data = data;
|
||||
|
||||
let mut struct_to_fixup = &mut fixed_data;
|
||||
loop {
|
||||
// prevent NaN != NaN issues
|
||||
if struct_to_fixup.float != struct_to_fixup.float {
|
||||
struct_to_fixup.float = 0.0;
|
||||
}
|
||||
if struct_to_fixup.double != struct_to_fixup.double {
|
||||
struct_to_fixup.double = 0.0;
|
||||
}
|
||||
|
||||
if struct_to_fixup.nested.is_none() {
|
||||
break;
|
||||
}
|
||||
struct_to_fixup = struct_to_fixup.nested.as_mut().unwrap();
|
||||
}
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
if to_bytes_bedrock(&fixed_data, &mut bytes).is_ok() {
|
||||
let cursor = Cursor::new(bytes);
|
||||
let reconstructed: RoundtripStruct =
|
||||
from_bytes_bedrock(cursor).expect("Failed to deserialize valid NBT");
|
||||
assert_eq!(fixed_data, reconstructed);
|
||||
}
|
||||
});
|
||||
@ -1,52 +0,0 @@
|
||||
#![no_main]
|
||||
use arbitrary::Arbitrary;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_nbt::deserializer::from_bytes;
|
||||
use pumpkin_nbt::serializer::to_bytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Arbitrary, Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct RoundtripStruct {
|
||||
byte: i8,
|
||||
short: i16,
|
||||
int: i32,
|
||||
long: i64,
|
||||
float: f32,
|
||||
double: f64,
|
||||
string: String,
|
||||
byte_array: Vec<u8>,
|
||||
int_array: Vec<i32>,
|
||||
long_array: Vec<i64>,
|
||||
list_string: Vec<String>,
|
||||
// Nested struct to test recursion
|
||||
nested: Option<Box<RoundtripStruct>>,
|
||||
}
|
||||
|
||||
fuzz_target!(|data: RoundtripStruct| {
|
||||
let mut fixed_data = data;
|
||||
|
||||
let mut struct_to_fixup = &mut fixed_data;
|
||||
loop {
|
||||
// prevent NaN != NaN issues
|
||||
if struct_to_fixup.float != struct_to_fixup.float {
|
||||
struct_to_fixup.float = 0.0;
|
||||
}
|
||||
if struct_to_fixup.double != struct_to_fixup.double {
|
||||
struct_to_fixup.double = 0.0;
|
||||
}
|
||||
|
||||
if struct_to_fixup.nested.is_none() {
|
||||
break;
|
||||
}
|
||||
struct_to_fixup = struct_to_fixup.nested.as_mut().unwrap();
|
||||
}
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
if to_bytes(&fixed_data, &mut bytes).is_ok() {
|
||||
let cursor = Cursor::new(bytes);
|
||||
let reconstructed: RoundtripStruct =
|
||||
from_bytes(cursor).expect("Failed to deserialize valid NBT");
|
||||
assert_eq!(fixed_data, reconstructed);
|
||||
}
|
||||
});
|
||||
@ -1,6 +1,5 @@
|
||||
//! Storage and convenience methods for NBT compound tags.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use uuid::Uuid;
|
||||
@ -113,13 +112,9 @@ impl NbtCompound {
|
||||
self.child_tags.is_empty()
|
||||
}
|
||||
|
||||
/// Inserts a tag when `name` is not already present.
|
||||
///
|
||||
/// Existing entries are left unchanged.
|
||||
/// Inserts or replaces a tag for `name`.
|
||||
pub fn put(&mut self, name: &str, value: impl Into<NbtTag>) {
|
||||
if !self.child_tags.contains_key(name) {
|
||||
self.child_tags.insert(name.into(), value.into());
|
||||
}
|
||||
self.child_tags.insert(name.into(), value.into());
|
||||
}
|
||||
|
||||
/// Inserts a string tag when `name` is not already present.
|
||||
@ -344,44 +339,6 @@ impl AsRef<Self> for NbtCompound {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for NbtCompound {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
use serde::ser::SerializeMap;
|
||||
let mut map = serializer.serialize_map(Some(self.child_tags.len()))?;
|
||||
for (key, value) in &self.child_tags {
|
||||
map.serialize_entry(key, &value)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NbtCompound {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct CompoundVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for CompoundVisitor {
|
||||
type Value = NbtCompound;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("an NBT compound")
|
||||
}
|
||||
|
||||
fn visit_map<A: serde::de::MapAccess<'de>>(
|
||||
self,
|
||||
mut map: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
let mut compound = NbtCompound::new();
|
||||
while let Some((key, value)) = map.next_entry::<Box<str>, NbtTag>()? {
|
||||
compound.child_tags.insert(key, value);
|
||||
}
|
||||
Ok(compound)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(CompoundVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NbtCompound> for NbtTag {
|
||||
fn from(value: NbtCompound) -> Self {
|
||||
Self::Compound(value)
|
||||
|
||||
@ -1,38 +1,14 @@
|
||||
//! Deserialization from Java Edition, unnamed network, and Bedrock NBT.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
|
||||
use crate::{
|
||||
BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, END_ID, Error, INT_ARRAY_ID, INT_ID, LIST_ID,
|
||||
LONG_ARRAY_ID, LONG_ID, MAX_ARRAY_LENGTH, NbtTag, io,
|
||||
};
|
||||
use crate::{Error, io};
|
||||
use io::Read;
|
||||
use serde::de::{self, DeserializeSeed, IntoDeserializer, MapAccess, SeqAccess, Visitor};
|
||||
use serde::{Deserialize, forward_to_deserialize_any};
|
||||
|
||||
/// Result type returned by NBT deserialization operations.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
thread_local! {
|
||||
/// Tag ID of the sequence currently visited by Serde.
|
||||
///
|
||||
/// This is used to preserve the distinction between NBT lists and the
|
||||
/// three specialized NBT array types.
|
||||
pub static CURR_VISITOR_LIST_TYPE: RefCell<Option<u8>> = const { std::cell::RefCell::new(None) };
|
||||
}
|
||||
|
||||
pub(super) fn take_curr_visitor_seq_list_id() -> Option<u8> {
|
||||
CURR_VISITOR_LIST_TYPE.with(std::cell::RefCell::take)
|
||||
}
|
||||
|
||||
pub(super) fn set_curr_visitor_seq_list_id(tag: Option<u8>) {
|
||||
CURR_VISITOR_LIST_TYPE.with(|cell| {
|
||||
*cell.borrow_mut() = tag;
|
||||
});
|
||||
}
|
||||
|
||||
/// Byte source used by NBT read helpers.
|
||||
///
|
||||
/// Implementations may return borrowed strings and byte arrays when the
|
||||
@ -453,334 +429,3 @@ impl<'a, D: NbtDataSource<'a>> NbtReadHelper<'a> for NbtReadHelperBedrock<D> {
|
||||
self.reader.read_byte_array(len)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Serde deserializer backed by a format-specific NBT reader.
|
||||
pub struct Deserializer<R> {
|
||||
input: R,
|
||||
tag_to_deserialize_stack: Option<u8>,
|
||||
in_list: bool,
|
||||
is_named: bool,
|
||||
}
|
||||
|
||||
impl<R> Deserializer<R> {
|
||||
/// Creates a deserializer.
|
||||
///
|
||||
/// When `is_named` is `true`, the root compound name is consumed from the
|
||||
/// input. Unnamed network NBT must pass `false`.
|
||||
pub const fn new(input: R, is_named: bool) -> Self {
|
||||
Self {
|
||||
input,
|
||||
tag_to_deserialize_stack: None,
|
||||
in_list: false,
|
||||
is_named,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes a value from named Java Edition NBT.
|
||||
pub fn from_bytes<'a, T: Deserialize<'a>>(r: impl Read + Seek) -> Result<T> {
|
||||
let mut deserializer = Deserializer::new(NbtReadHelperJava::new(NbtStreamReader(r)), true);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
/// Deserializes a value from unnamed Java Edition network NBT.
|
||||
pub fn from_bytes_unnamed<'a, T: Deserialize<'a>>(r: impl Read + Seek) -> Result<T> {
|
||||
let mut deserializer = Deserializer::new(NbtReadHelperJava::new(NbtStreamReader(r)), false);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
/// Deserializes a value from named Bedrock network NBT.
|
||||
pub fn from_bytes_bedrock<'a, T: Deserialize<'a>>(r: impl Read + Seek) -> Result<T> {
|
||||
let mut deserializer = Deserializer::new(NbtReadHelperBedrock::new(NbtStreamReader(r)), true);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
/// Deserializes a value from a named Java Edition NBT slice.
|
||||
///
|
||||
/// Strings and byte arrays may borrow directly from `slice`.
|
||||
pub fn from_slice<'a, T: Deserialize<'a>>(slice: &'a [u8]) -> Result<T> {
|
||||
let mut deserializer = Deserializer::new(NbtReadHelperJava::new(Cursor::new(slice)), true);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
/// Deserializes a value from an unnamed Java Edition network NBT slice.
|
||||
///
|
||||
/// Strings and byte arrays may borrow directly from `slice`.
|
||||
pub fn from_slice_unnamed<'a, T: Deserialize<'a>>(slice: &'a [u8]) -> Result<T> {
|
||||
let mut deserializer = Deserializer::new(NbtReadHelperJava::new(Cursor::new(slice)), false);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
/// Deserializes a value from a named Bedrock network NBT slice.
|
||||
///
|
||||
/// Strings and byte arrays may borrow directly from `slice`.
|
||||
pub fn from_slice_bedrock<'a, T: Deserialize<'a>>(slice: &'a [u8]) -> Result<T> {
|
||||
let mut deserializer = Deserializer::new(NbtReadHelperBedrock::new(Cursor::new(slice)), true);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
impl<'de, R: NbtReadHelper<'de>> de::Deserializer<'de> for &mut Deserializer<R> {
|
||||
type Error = Error;
|
||||
|
||||
forward_to_deserialize_any! {
|
||||
i8 i16 i32 i64 f32 f64 char unit unit_struct seq tuple tuple_struct
|
||||
newtype_struct byte_buf
|
||||
}
|
||||
|
||||
fn deserialize_ignored_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
let Some(tag) = self.tag_to_deserialize_stack else {
|
||||
return Err(Error::SerdeError("Ignoring nothing!".to_string()));
|
||||
};
|
||||
|
||||
NbtTag::skip_data(&mut self.input, tag)?;
|
||||
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
let Some(tag_to_deserialize) = self.tag_to_deserialize_stack else {
|
||||
return Err(Error::SerdeError(
|
||||
"The top level must be a component (e.g. a struct)".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
match tag_to_deserialize {
|
||||
END_ID => Err(Error::SerdeError(
|
||||
"Trying to deserialize an END tag!".to_string(),
|
||||
)),
|
||||
LIST_ID | INT_ARRAY_ID | LONG_ARRAY_ID | BYTE_ARRAY_ID => {
|
||||
let list_type = match tag_to_deserialize {
|
||||
LIST_ID => self.input.get_u8()?,
|
||||
INT_ARRAY_ID => INT_ID,
|
||||
LONG_ARRAY_ID => LONG_ID,
|
||||
BYTE_ARRAY_ID => BYTE_ID,
|
||||
_ => return Err(Error::SerdeError("Unreachable state reached".to_string())),
|
||||
};
|
||||
|
||||
let remaining_values = self.input.get_i32()?;
|
||||
if remaining_values < 0 {
|
||||
return Err(Error::NegativeLength(remaining_values));
|
||||
}
|
||||
|
||||
let remaining_values = remaining_values as usize;
|
||||
if remaining_values > MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(remaining_values));
|
||||
}
|
||||
|
||||
set_curr_visitor_seq_list_id(Some(list_type));
|
||||
let result = visitor.visit_seq(ListAccess {
|
||||
de: self,
|
||||
list_type,
|
||||
remaining_values,
|
||||
})?;
|
||||
Ok(result)
|
||||
}
|
||||
COMPOUND_ID => visitor.visit_map(CompoundAccess { de: self }),
|
||||
_ => {
|
||||
let result = match NbtTag::deserialize_data(&mut self.input, tag_to_deserialize)? {
|
||||
NbtTag::Byte(value) => visitor.visit_i8::<Error>(value)?,
|
||||
NbtTag::Short(value) => visitor.visit_i16::<Error>(value)?,
|
||||
NbtTag::Int(value) => visitor.visit_i32::<Error>(value)?,
|
||||
NbtTag::Long(value) => visitor.visit_i64::<Error>(value)?,
|
||||
NbtTag::Float(value) => visitor.visit_f32::<Error>(value)?,
|
||||
NbtTag::Double(value) => visitor.visit_f64::<Error>(value)?,
|
||||
NbtTag::String(value) => visitor.visit_string::<Error>(value.into_string())?,
|
||||
_ => return Err(Error::SerdeError("Unreachable state reached".to_string())),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
if self.in_list {
|
||||
let value = self.input.get_u8()?;
|
||||
visitor.visit_u8::<Error>(value)
|
||||
} else {
|
||||
Err(Error::UnsupportedType(
|
||||
"u8; NBT only supports signed values".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_u16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
let value = self.input.get_i16()?;
|
||||
visitor.visit_i16::<Error>(value)
|
||||
}
|
||||
|
||||
fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
let value = self.input.get_i32()?;
|
||||
visitor.visit_i32::<Error>(value)
|
||||
}
|
||||
|
||||
fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
let value = self.input.get_i64()?;
|
||||
visitor.visit_i64::<Error>(value)
|
||||
}
|
||||
|
||||
fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
if self.tag_to_deserialize_stack.unwrap() == BYTE_ID {
|
||||
let value = self.input.get_u8()?;
|
||||
if value != 0 {
|
||||
return visitor.visit_bool(true);
|
||||
}
|
||||
}
|
||||
visitor.visit_bool(false)
|
||||
}
|
||||
|
||||
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
match self.input.get_string()? {
|
||||
Cow::Borrowed(s) => visitor.visit_borrowed_str(s),
|
||||
Cow::Owned(s) => visitor.visit_string(s),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
self.deserialize_str(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_bytes<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
let tag_id = self.tag_to_deserialize_stack.unwrap_or(BYTE_ARRAY_ID);
|
||||
if tag_id == BYTE_ARRAY_ID {
|
||||
let len = self.input.get_i32()?;
|
||||
if len < 0 {
|
||||
return Err(Error::NegativeLength(len));
|
||||
}
|
||||
let slice = self.input.get_byte_array(len as usize)?;
|
||||
let u8_slice: &'de [u8] =
|
||||
unsafe { std::slice::from_raw_parts(slice.as_ptr().cast::<u8>(), slice.len()) };
|
||||
match slice {
|
||||
Cow::Borrowed(_) => visitor.visit_borrowed_bytes(u8_slice),
|
||||
Cow::Owned(_) => visitor.visit_bytes(u8_slice),
|
||||
}
|
||||
} else {
|
||||
self.deserialize_any(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_enum<V: Visitor<'de>>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variants: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value> {
|
||||
match self.input.get_string()? {
|
||||
Cow::Borrowed(s) => visitor.visit_enum(s.into_deserializer()),
|
||||
Cow::Owned(s) => visitor.visit_enum(s.into_deserializer()),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
visitor.visit_some(self)
|
||||
}
|
||||
|
||||
fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
if let Some(tag_id) = self.tag_to_deserialize_stack {
|
||||
if tag_id != COMPOUND_ID {
|
||||
return Err(Error::SerdeError(format!(
|
||||
"Trying to deserialize a map without a compound ID (id {tag_id})"
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let next_byte = self.input.get_u8()?;
|
||||
if next_byte != COMPOUND_ID {
|
||||
return Err(Error::NoRootCompound(next_byte));
|
||||
}
|
||||
|
||||
if self.is_named {
|
||||
self.input.skip_string()?;
|
||||
}
|
||||
}
|
||||
|
||||
let value = visitor.visit_map(CompoundAccess { de: self })?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn deserialize_struct<V: Visitor<'de>>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_fields: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value> {
|
||||
self.deserialize_map(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_identifier<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
self.deserialize_str(visitor)
|
||||
}
|
||||
|
||||
fn is_human_readable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
struct CompoundAccess<'a, R> {
|
||||
de: &'a mut Deserializer<R>,
|
||||
}
|
||||
|
||||
impl<'de, R: NbtReadHelper<'de>> MapAccess<'de> for CompoundAccess<'_, R> {
|
||||
type Error = Error;
|
||||
|
||||
fn next_key_seed<K: DeserializeSeed<'de>>(&mut self, seed: K) -> Result<Option<K::Value>> {
|
||||
let tag = self.de.input.get_u8()?;
|
||||
self.de.tag_to_deserialize_stack = Some(tag);
|
||||
|
||||
if tag == END_ID {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
seed.deserialize(MapKey { de: self.de }).map(Some)
|
||||
}
|
||||
|
||||
fn next_value_seed<V: DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value> {
|
||||
seed.deserialize(&mut *self.de)
|
||||
}
|
||||
}
|
||||
|
||||
struct MapKey<'a, R> {
|
||||
de: &'a mut Deserializer<R>,
|
||||
}
|
||||
|
||||
impl<'de, R: NbtReadHelper<'de>> de::Deserializer<'de> for MapKey<'_, R> {
|
||||
type Error = Error;
|
||||
|
||||
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||
match self.de.input.get_string()? {
|
||||
Cow::Borrowed(s) => visitor.visit_borrowed_str(s),
|
||||
Cow::Owned(s) => visitor.visit_string(s),
|
||||
}
|
||||
}
|
||||
|
||||
forward_to_deserialize_any! {
|
||||
bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit unit_struct seq tuple tuple_struct map
|
||||
struct identifier ignored_any bytes enum newtype_struct byte_buf option
|
||||
}
|
||||
}
|
||||
|
||||
struct ListAccess<'a, R> {
|
||||
de: &'a mut Deserializer<R>,
|
||||
remaining_values: usize,
|
||||
list_type: u8,
|
||||
}
|
||||
|
||||
impl<'de, R: NbtReadHelper<'de>> SeqAccess<'de> for ListAccess<'_, R> {
|
||||
type Error = Error;
|
||||
|
||||
fn size_hint(&self) -> Option<usize> {
|
||||
Some(self.remaining_values)
|
||||
}
|
||||
|
||||
fn next_element_seed<E: DeserializeSeed<'de>>(&mut self, seed: E) -> Result<Option<E::Value>> {
|
||||
if self.remaining_values == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.remaining_values -= 1;
|
||||
self.de.tag_to_deserialize_stack = Some(self.list_type);
|
||||
self.de.in_list = true;
|
||||
let result = seed.deserialize(&mut *self.de).map(Some);
|
||||
self.de.in_list = false;
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,66 +1,34 @@
|
||||
//! Reading, writing, and manipulating Minecraft's Named Binary Tag (NBT) data.
|
||||
//!
|
||||
//! The crate supports the standard Java Edition representation, unnamed network
|
||||
//! NBT, Bedrock network NBT, and gzip-compressed NBT. Data can be handled either
|
||||
//! as [`Nbt`] and [`NbtCompound`] values or through Serde with [`to_bytes`] and
|
||||
//! [`from_slice`].
|
||||
//!
|
||||
//! # Serde round trip
|
||||
//!
|
||||
//! ```
|
||||
//! use pumpkin_nbt::{from_slice, to_bytes};
|
||||
//! use serde::{Deserialize, Serialize};
|
||||
//!
|
||||
//! #[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
//! struct Player {
|
||||
//! name: String,
|
||||
//! health: i16,
|
||||
//! }
|
||||
//!
|
||||
//! let player = Player {
|
||||
//! name: "Steve".to_owned(),
|
||||
//! health: 20,
|
||||
//! };
|
||||
//! let mut encoded = Vec::new();
|
||||
//! to_bytes(&player, &mut encoded)?;
|
||||
//!
|
||||
//! let decoded: Player = from_slice(&encoded)?;
|
||||
//! assert_eq!(decoded, player);
|
||||
//! # Ok::<(), pumpkin_nbt::Error>(())
|
||||
//! ```
|
||||
//! NBT, Bedrock network NBT, and gzip-compressed NBT. Data is handled directly
|
||||
//! via [`Nbt`], [`NbtCompound`], and [`NbtTag`].
|
||||
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::{self, Write},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
use deserializer::NbtReadHelper;
|
||||
use serde::{de, ser};
|
||||
use serializer::{NbtWriteHelper, NbtWriteHelperBedrock, NbtWriteHelperJava};
|
||||
use tag::NbtTag;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Compound-tag storage and construction helpers.
|
||||
pub mod compound;
|
||||
/// Serde and low-level NBT deserialization support.
|
||||
/// Low-level NBT deserialization support.
|
||||
pub mod deserializer;
|
||||
/// Reading and writing gzip-compressed NBT.
|
||||
pub mod nbt_compress;
|
||||
/// Integration with Pumpkin's dynamic codec operations.
|
||||
pub mod nbt_ops;
|
||||
/// Serde and low-level NBT serialization support.
|
||||
/// Low-level NBT serialization support.
|
||||
pub mod serializer;
|
||||
/// The individual NBT tag types.
|
||||
pub mod tag;
|
||||
|
||||
pub use compound::NbtCompound;
|
||||
pub use deserializer::{
|
||||
from_bytes, from_bytes_bedrock, from_bytes_unnamed, from_slice, from_slice_bedrock,
|
||||
from_slice_unnamed,
|
||||
};
|
||||
pub use serializer::{to_bytes, to_bytes_named, to_bytes_unnamed};
|
||||
|
||||
// This NBT crate is inspired from CrabNBT
|
||||
|
||||
@ -132,18 +100,6 @@ pub enum Error {
|
||||
VarLongTooLarge,
|
||||
}
|
||||
|
||||
impl ser::Error for Error {
|
||||
fn custom<T: Display>(msg: T) -> Self {
|
||||
Self::SerdeError(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl de::Error for Error {
|
||||
fn custom<T: Display>(msg: T) -> Self {
|
||||
Self::SerdeError(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// A complete NBT document containing a named root compound.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Nbt {
|
||||
@ -283,801 +239,3 @@ impl AsMut<NbtCompound> for Nbt {
|
||||
&mut self.root_tag
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is a bit hacky
|
||||
pub(crate) const NBT_ARRAY_TAG: &str = "__nbt_array";
|
||||
pub(crate) const NBT_INT_ARRAY_TAG: &str = "__nbt_int_array";
|
||||
pub(crate) const NBT_LONG_ARRAY_TAG: &str = "__nbt_long_array";
|
||||
pub(crate) const NBT_BYTE_ARRAY_TAG: &str = "__nbt_byte_array";
|
||||
|
||||
macro_rules! impl_array {
|
||||
($name:ident, $variant:expr) => {
|
||||
#[doc = "Serializes a sequence using its specialized NBT array representation."]
|
||||
#[doc = ""]
|
||||
#[doc = "Use this function with Serde's `serialize_with` field attribute."]
|
||||
pub fn $name<T: serde::Serialize, S: serde::Serializer>(
|
||||
input: T,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_newtype_variant(NBT_ARRAY_TAG, 0, $variant, &input)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_array!(nbt_int_array, NBT_INT_ARRAY_TAG);
|
||||
impl_array!(nbt_long_array, NBT_LONG_ARRAY_TAG);
|
||||
impl_array!(nbt_byte_array, NBT_BYTE_ARRAY_TAG);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
use crate::Error;
|
||||
use crate::compound::NbtCompound;
|
||||
use crate::deserializer::from_bytes;
|
||||
use crate::deserializer::from_bytes_bedrock;
|
||||
use crate::nbt_byte_array;
|
||||
use crate::nbt_int_array;
|
||||
use crate::nbt_long_array;
|
||||
use crate::serializer::NbtWriteHelperBedrock;
|
||||
use crate::serializer::to_bytes_bedrock;
|
||||
use crate::serializer::to_bytes_named;
|
||||
use crate::serializer::to_bytes_named_bedrock;
|
||||
use crate::serializer::{NbtWriteHelperJava, to_bytes};
|
||||
use crate::tag::NbtTag;
|
||||
use crate::{deserializer::from_bytes_unnamed, serializer::to_bytes_unnamed};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct Test {
|
||||
byte: i8,
|
||||
short: i16,
|
||||
int: i32,
|
||||
long: i64,
|
||||
float: f32,
|
||||
string: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct BorrowedTest<'a> {
|
||||
byte: i8,
|
||||
string: &'a str,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_alloc_from_slice() {
|
||||
let test = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Zero alloc NBT".to_string(),
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_unnamed(&test, &mut bytes).unwrap();
|
||||
let borrowed: BorrowedTest = crate::from_slice_unnamed(&bytes).unwrap();
|
||||
|
||||
assert_eq!(borrowed.byte, 123);
|
||||
assert_eq!(borrowed.string, "Zero alloc NBT");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ser_de_unnamed() {
|
||||
let test = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Hello test".to_string(),
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_unnamed(&test, &mut bytes).unwrap();
|
||||
let recreated_struct: Test = from_bytes_unnamed(Cursor::new(bytes)).unwrap();
|
||||
|
||||
assert_eq!(test, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ser_de_unnamed_bedrock() {
|
||||
let test = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Hello test".to_string(),
|
||||
};
|
||||
|
||||
// Bedrock doesn't actually use unnamed NBT (AFAIK). `to_bytes_bedrock` actually encodes empty name.
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_bedrock(&test, &mut bytes).unwrap();
|
||||
let recreated_struct: Test = from_bytes_bedrock(Cursor::new(bytes)).unwrap();
|
||||
|
||||
assert_eq!(test, recreated_struct);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[expect(clippy::struct_field_names)]
|
||||
struct TestArray {
|
||||
#[serde(serialize_with = "nbt_byte_array")]
|
||||
byte_array: Vec<u8>,
|
||||
#[serde(serialize_with = "nbt_int_array")]
|
||||
int_array: Vec<i32>,
|
||||
#[serde(serialize_with = "nbt_long_array")]
|
||||
long_array: Vec<i64>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ser_de_array() {
|
||||
let test = TestArray {
|
||||
byte_array: vec![0, 3, 2],
|
||||
int_array: vec![13, 1321, 2],
|
||||
long_array: vec![1, 0, 200301, 1],
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_unnamed(&test, &mut bytes).unwrap();
|
||||
let recreated_struct: TestArray = from_bytes_unnamed(Cursor::new(bytes)).unwrap();
|
||||
|
||||
assert_eq!(test, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ser_de_array_bedrock() {
|
||||
let test = TestArray {
|
||||
byte_array: vec![0, 3, 2],
|
||||
int_array: vec![13, 1321, 2],
|
||||
long_array: vec![1, 0, 200301, 1],
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_bedrock(&test, &mut bytes).unwrap();
|
||||
let recreated_struct: TestArray = from_bytes_bedrock(Cursor::new(bytes)).unwrap();
|
||||
|
||||
assert_eq!(test, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ser_de_named() {
|
||||
let name = String::from("Test");
|
||||
let test = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Hello test".to_string(),
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_named(&test, name, &mut bytes).unwrap();
|
||||
let recreated_struct: Test = from_bytes(Cursor::new(bytes)).unwrap();
|
||||
|
||||
assert_eq!(test, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ser_de_named_bedrock() {
|
||||
let name = String::from("Test");
|
||||
let test = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Hello test".to_string(),
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_named_bedrock(&test, name, &mut bytes).unwrap();
|
||||
let recreated_struct: Test = from_bytes_bedrock(Cursor::new(bytes)).unwrap();
|
||||
|
||||
assert_eq!(test, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ser_de_array_named() {
|
||||
let name = String::from("Test");
|
||||
let test = TestArray {
|
||||
byte_array: vec![0, 3, 2],
|
||||
int_array: vec![13, 1321, 2],
|
||||
long_array: vec![1, 0, 200301, 1],
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_named(&test, name, &mut bytes).unwrap();
|
||||
let recreated_struct: TestArray = from_bytes(Cursor::new(bytes)).unwrap();
|
||||
|
||||
assert_eq!(test, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_ser_de_array_named_bedrock() {
|
||||
let name = String::from("Test");
|
||||
let test = TestArray {
|
||||
byte_array: vec![0, 3, 2],
|
||||
int_array: vec![13, 1321, 2],
|
||||
long_array: vec![1, 0, 200301, 1],
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_named_bedrock(&test, name, &mut bytes).unwrap();
|
||||
let recreated_struct: TestArray = from_bytes_bedrock(Cursor::new(bytes)).unwrap();
|
||||
|
||||
assert_eq!(test, recreated_struct);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct Egg {
|
||||
food: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct Breakfast {
|
||||
food: Egg,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct TestList {
|
||||
option: Option<Egg>,
|
||||
nested_compound: Breakfast,
|
||||
compounds: Vec<Test>,
|
||||
list_string: Vec<String>,
|
||||
empty: Vec<Test>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
let test1 = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Hello test".to_string(),
|
||||
};
|
||||
|
||||
let test2 = Test {
|
||||
byte: 13,
|
||||
short: 342,
|
||||
int: -4313,
|
||||
long: -132334,
|
||||
float: -69.420,
|
||||
string: "Hello compounds".to_string(),
|
||||
};
|
||||
|
||||
let list_compound = TestList {
|
||||
option: Some(Egg {
|
||||
food: "Skibid".to_string(),
|
||||
}),
|
||||
nested_compound: Breakfast {
|
||||
food: Egg {
|
||||
food: "Over easy".to_string(),
|
||||
},
|
||||
},
|
||||
compounds: vec![test1, test2],
|
||||
list_string: vec![String::new(), "abcbcbcbbc".to_string()],
|
||||
empty: vec![],
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_unnamed(&list_compound, &mut bytes).unwrap();
|
||||
let recreated_struct: TestList = from_bytes_unnamed(Cursor::new(bytes)).unwrap();
|
||||
assert_eq!(list_compound, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_bedrock() {
|
||||
let test1 = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Hello test".to_string(),
|
||||
};
|
||||
|
||||
let test2 = Test {
|
||||
byte: 13,
|
||||
short: 342,
|
||||
int: -4313,
|
||||
long: -132334,
|
||||
float: -69.420,
|
||||
string: "Hello compounds".to_string(),
|
||||
};
|
||||
|
||||
let list_compound = TestList {
|
||||
option: Some(Egg {
|
||||
food: "Skibid".to_string(),
|
||||
}),
|
||||
nested_compound: Breakfast {
|
||||
food: Egg {
|
||||
food: "Over easy".to_string(),
|
||||
},
|
||||
},
|
||||
compounds: vec![test1, test2],
|
||||
list_string: vec![String::new(), "abcbcbcbbc".to_string()],
|
||||
empty: vec![],
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_bedrock(&list_compound, &mut bytes).unwrap();
|
||||
let recreated_struct: TestList = from_bytes_bedrock(Cursor::new(bytes)).unwrap();
|
||||
assert_eq!(list_compound, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_named() {
|
||||
let test1 = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Hello test".to_string(),
|
||||
};
|
||||
|
||||
let test2 = Test {
|
||||
byte: 13,
|
||||
short: 342,
|
||||
int: -4313,
|
||||
long: -132334,
|
||||
float: -69.420,
|
||||
string: "Hello compounds".to_string(),
|
||||
};
|
||||
|
||||
let list_compound = TestList {
|
||||
option: None,
|
||||
nested_compound: Breakfast {
|
||||
food: Egg {
|
||||
food: "Over easy".to_string(),
|
||||
},
|
||||
},
|
||||
compounds: vec![test1, test2],
|
||||
list_string: vec![String::new(), "abcbcbcbbc".to_string()],
|
||||
empty: vec![],
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_named(&list_compound, "a".to_string(), &mut bytes).unwrap();
|
||||
let recreated_struct: TestList = from_bytes(Cursor::new(bytes)).unwrap();
|
||||
assert_eq!(list_compound, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_named_bedrock() {
|
||||
let test1 = Test {
|
||||
byte: 123,
|
||||
short: 1342,
|
||||
int: 4313,
|
||||
long: 34,
|
||||
float: 1.00,
|
||||
string: "Hello test".to_string(),
|
||||
};
|
||||
|
||||
let test2 = Test {
|
||||
byte: 13,
|
||||
short: 342,
|
||||
int: -4313,
|
||||
long: -132334,
|
||||
float: -69.420,
|
||||
string: "Hello compounds".to_string(),
|
||||
};
|
||||
|
||||
let list_compound = TestList {
|
||||
option: None,
|
||||
nested_compound: Breakfast {
|
||||
food: Egg {
|
||||
food: "Over easy".to_string(),
|
||||
},
|
||||
},
|
||||
compounds: vec![test1, test2],
|
||||
list_string: vec![String::new(), "abcbcbcbbc".to_string()],
|
||||
empty: vec![],
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_named_bedrock(&list_compound, "a".to_string(), &mut bytes).unwrap();
|
||||
let recreated_struct: TestList = from_bytes_bedrock(Cursor::new(bytes)).unwrap();
|
||||
assert_eq!(list_compound, recreated_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapper_compound_lists() {
|
||||
let mut vec: Vec<NbtTag> = Vec::new();
|
||||
|
||||
// These tags will be wrapped during serialization.
|
||||
vec.push(NbtTag::Int(-1823));
|
||||
vec.push(NbtTag::Int(123));
|
||||
vec.push(NbtTag::String("Not an int".into()));
|
||||
vec.push(NbtTag::Byte(2));
|
||||
|
||||
// This compound will not, since the list is already a list of compound tags.
|
||||
// This compound cannot be unwrapped in any way, so it is preserved
|
||||
// on deserialization.
|
||||
vec.push(NbtTag::Compound({
|
||||
let mut compound = NbtCompound::new();
|
||||
compound.put_short("example", 1234);
|
||||
compound
|
||||
}));
|
||||
|
||||
// This wrapper compound will be wrapped because we want to preserve the
|
||||
// original data during deserialization.
|
||||
//
|
||||
// Suppose we had {"": `tag`}. If we didn't wrap this, on deserialization,
|
||||
// we would get `tag`, which doesn't match the serialized compound tag.
|
||||
// Therefore, we must wrap it and serialize {"": {"": `tag`}}.
|
||||
// Then on deserialization, we get {"": `tag`}, which matches what we wanted
|
||||
// to serialize in the first place.
|
||||
//
|
||||
// This compound represents {"": 1L}.
|
||||
vec.push(NbtTag::Compound({
|
||||
let mut compound = NbtCompound::new();
|
||||
compound.put_long("", 1);
|
||||
compound
|
||||
}));
|
||||
|
||||
let expected_bytes = [
|
||||
0x09, // List type
|
||||
0x0A, // This list is a compound tag list
|
||||
0x00, 0x00, 0x00, 0x06, // This list has 6 elements.
|
||||
// Now for parsing each compound tag:
|
||||
0x03, // Int type
|
||||
0x00, 0x00, // Empty key
|
||||
0xFF, 0xFF, 0xF8, 0xE1, // -1823
|
||||
0x00, // End
|
||||
0x03, // Int type
|
||||
0x00, 0x00, // Empty key
|
||||
0x00, 0x00, 0x00, 0x7B, // 123
|
||||
0x00, // End
|
||||
0x08, // String type
|
||||
0x00, 0x00, // Empty key
|
||||
0x00, 0x0A, // The string is 10 characters long.
|
||||
0x4E, 0x6F, 0x74, 0x20, 0x61, 0x6E, 0x20, 0x69, 0x6E, 0x74, // "Not an int"
|
||||
0x00, // End
|
||||
0x01, // Byte type
|
||||
0x00, 0x00, // Empty key
|
||||
0x02, // 2b
|
||||
0x00, // End
|
||||
// For the first (unwrapped) compound:
|
||||
0x02, // Short type
|
||||
0x00, 0x07, // The key is 7 characters long.
|
||||
0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // "example"
|
||||
0x04, 0xD2, // 1234
|
||||
0x00, // End
|
||||
// For the second (wrapped) wrapper compound:
|
||||
0x0A, // Compound type
|
||||
0x00, 0x00, // Empty key
|
||||
0x04, // Long type
|
||||
0x00, 0x00, // Empty key
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // 1L
|
||||
0x00, // End
|
||||
0x00, // End
|
||||
];
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
let mut write_adaptor = NbtWriteHelperJava::new(&mut bytes);
|
||||
NbtTag::List(vec)
|
||||
.serialize(&mut write_adaptor)
|
||||
.expect("Expected serialization to succeed");
|
||||
|
||||
assert_eq!(bytes, expected_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapper_compound_lists_bedrock() {
|
||||
let mut vec: Vec<NbtTag> = Vec::new();
|
||||
|
||||
// These tags will be wrapped during serialization.
|
||||
vec.push(NbtTag::Int(-1823));
|
||||
vec.push(NbtTag::Int(123));
|
||||
vec.push(NbtTag::String("Not an int".into()));
|
||||
vec.push(NbtTag::Byte(2));
|
||||
|
||||
// This compound will not, since the list is already a list of compound tags.
|
||||
// This compound cannot be unwrapped in any way, so it is preserved
|
||||
// on deserialization.
|
||||
vec.push(NbtTag::Compound({
|
||||
let mut compound = NbtCompound::new();
|
||||
compound.put_short("example", 1234);
|
||||
compound
|
||||
}));
|
||||
|
||||
// This wrapper compound will be wrapped because we want to preserve the
|
||||
// original data during deserialization.
|
||||
//
|
||||
// Suppose we had {"": `tag`}. If we didn't wrap this, on deserialization,
|
||||
// we would get `tag`, which doesn't match the serialized compound tag.
|
||||
// Therefore, we must wrap it and serialize {"": {"": `tag`}}.
|
||||
// Then on deserialization, we get {"": `tag`}, which matches what we wanted
|
||||
// to serialize in the first place.
|
||||
//
|
||||
// This compound represents {"": 1L}.
|
||||
vec.push(NbtTag::Compound({
|
||||
let mut compound = NbtCompound::new();
|
||||
compound.put_long("", 1);
|
||||
compound
|
||||
}));
|
||||
|
||||
let expected_bytes = [
|
||||
0x09, // List type
|
||||
0x0A, // This list is a compound tag list
|
||||
0xC, // This list has 6 elements.
|
||||
// Now for parsing each compound tag:
|
||||
0x03, // Int type
|
||||
0x00, // Empty key
|
||||
0xBD, 0x1C, // -1823
|
||||
0x00, // End
|
||||
0x03, // Int type
|
||||
0x00, // Empty key
|
||||
0xF6, 0x01, // 123
|
||||
0x00, // End
|
||||
0x08, // String type
|
||||
0x00, // Empty key
|
||||
0x0A, // The string is 10 characters long.
|
||||
0x4E, 0x6F, 0x74, 0x20, 0x61, 0x6E, 0x20, 0x69, 0x6E, 0x74, // "Not an int"
|
||||
0x00, // End
|
||||
0x01, // Byte type
|
||||
0x00, // Empty key
|
||||
0x02, // 2b
|
||||
0x00, // End
|
||||
// For the first (unwrapped) compound:
|
||||
0x02, // Short type
|
||||
0x07, // The key is 7 characters long.
|
||||
0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // "example"
|
||||
0xD2, 0x04, // 1234
|
||||
0x00, // End
|
||||
// For the second (wrapped) wrapper compound:
|
||||
0x0A, // Compound type
|
||||
0x00, // Empty key
|
||||
0x04, // Long type
|
||||
0x00, // Empty key
|
||||
0x02, // 1L
|
||||
0x00, // End
|
||||
0x00, // End
|
||||
];
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
let mut write_adaptor = NbtWriteHelperBedrock::new(&mut bytes);
|
||||
NbtTag::List(vec)
|
||||
.serialize(&mut write_adaptor)
|
||||
.expect("Expected serialization to succeed");
|
||||
|
||||
assert_eq!(bytes, expected_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nbt_arrays() {
|
||||
#[derive(Serialize)]
|
||||
struct Tagged {
|
||||
#[serde(serialize_with = "nbt_long_array")]
|
||||
l: [i64; 1],
|
||||
#[serde(serialize_with = "nbt_int_array")]
|
||||
i: [i32; 1],
|
||||
#[serde(serialize_with = "nbt_byte_array")]
|
||||
b: [u8; 1],
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
struct NotTagged {
|
||||
l: [i64; 1],
|
||||
i: [i32; 1],
|
||||
b: [u8; 1],
|
||||
}
|
||||
|
||||
let value = Tagged {
|
||||
l: [0],
|
||||
i: [0],
|
||||
b: [0],
|
||||
};
|
||||
let expected_bytes = [
|
||||
0x0A, // Component Tag
|
||||
0x00, 0x00, // Empty root name
|
||||
0x0C, // Long Array Type
|
||||
0x00, 0x01, // Key length
|
||||
0x6C, // Key (l)
|
||||
0x00, 0x00, 0x00, 0x01, // Array Length
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Value(s)
|
||||
0x0B, // Int Array Tag
|
||||
0x00, 0x01, // Key length
|
||||
0x69, // Key (i)
|
||||
0x00, 0x00, 0x00, 0x01, // Array Length
|
||||
0x00, 0x00, 0x00, 0x00, // Value(s)
|
||||
0x07, // Byte Array Tag
|
||||
0x00, 0x01, // Key length
|
||||
0x62, // Key (b)
|
||||
0x00, 0x00, 0x00, 0x01, // Array Length
|
||||
0x00, // Value(s)
|
||||
0x00, // End Tag
|
||||
];
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes(&value, &mut bytes).unwrap();
|
||||
assert_eq!(bytes, expected_bytes);
|
||||
|
||||
let value = NotTagged {
|
||||
l: [0],
|
||||
i: [0],
|
||||
b: [0],
|
||||
};
|
||||
let expected_bytes = [
|
||||
0x0A, // Component Tag
|
||||
0x00, 0x00, // Empty root name
|
||||
0x09, // List Tag
|
||||
0x00, 0x01, // Key length
|
||||
0x6C, // Key (l)
|
||||
0x04, // Array Type
|
||||
0x00, 0x00, 0x00, 0x01, // Array Length
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Value(s)
|
||||
0x09, // List Tag
|
||||
0x00, 0x01, // Key length
|
||||
0x69, // Key (i)
|
||||
0x03, // Array Type
|
||||
0x00, 0x00, 0x00, 0x01, // Array Length
|
||||
0x00, 0x00, 0x00, 0x00, // Value(s)
|
||||
0x09, // List Tag
|
||||
0x00, 0x01, // Key length
|
||||
0x62, // Key (b)
|
||||
0x01, // Array Type
|
||||
0x00, 0x00, 0x00, 0x01, // Array Length
|
||||
0x00, // Value(s)
|
||||
0x00, // End Tag
|
||||
];
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes(&value, &mut bytes).unwrap();
|
||||
assert_eq!(bytes, expected_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nbt_arrays_bedrock() {
|
||||
#[derive(Serialize)]
|
||||
struct Tagged {
|
||||
#[serde(serialize_with = "nbt_long_array")]
|
||||
l: [i64; 1],
|
||||
#[serde(serialize_with = "nbt_int_array")]
|
||||
i: [i32; 1],
|
||||
#[serde(serialize_with = "nbt_byte_array")]
|
||||
b: [u8; 1],
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
struct NotTagged {
|
||||
l: [i64; 1],
|
||||
i: [i32; 1],
|
||||
b: [u8; 1],
|
||||
}
|
||||
|
||||
let value = Tagged {
|
||||
l: [0],
|
||||
i: [0],
|
||||
b: [0],
|
||||
};
|
||||
let expected_bytes = [
|
||||
0x0A, // Component Tag
|
||||
0x00, // Empty root name
|
||||
0x0C, // Long Array Type
|
||||
0x01, // Key length
|
||||
0x6C, // Key (l)
|
||||
0x02, // Array Length
|
||||
0x00, // Value(s)
|
||||
0x0B, // Int Array Tag
|
||||
0x01, // Key length
|
||||
0x69, // Key (i)
|
||||
0x02, // Array Length
|
||||
0x00, // Value(s)
|
||||
0x07, // Byte Array Tag
|
||||
0x01, // Key length
|
||||
0x62, // Key (b)
|
||||
0x02, // Array Length
|
||||
0x00, // Value(s)
|
||||
0x00, // End Tag
|
||||
];
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_bedrock(&value, &mut bytes).unwrap();
|
||||
assert_eq!(bytes, expected_bytes);
|
||||
|
||||
let value = NotTagged {
|
||||
l: [0],
|
||||
i: [0],
|
||||
b: [0],
|
||||
};
|
||||
let expected_bytes = [
|
||||
0x0A, // Component Tag
|
||||
0x00, // Empty root name
|
||||
0x09, // List Tag
|
||||
0x01, // Key length
|
||||
0x6C, // Key (l)
|
||||
0x04, // Array Type
|
||||
0x02, // Array Length
|
||||
0x00, // Value(s)
|
||||
0x09, // List Tag
|
||||
0x01, // Key length
|
||||
0x69, // Key (i)
|
||||
0x03, // Array Type
|
||||
0x02, // Array Length
|
||||
0x00, // Value(s)
|
||||
0x09, // List Tag
|
||||
0x01, // Key length
|
||||
0x62, // Key (b)
|
||||
0x01, // Array Type
|
||||
0x02, // Array Length
|
||||
0x00, // Value(s)
|
||||
0x00, // End Tag
|
||||
];
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_bedrock(&value, &mut bytes).unwrap();
|
||||
assert_eq!(bytes, expected_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_fail() {
|
||||
#[derive(Serialize)]
|
||||
struct BadData {
|
||||
x: (i32, i64),
|
||||
}
|
||||
|
||||
let value = BadData { x: (0, 0) };
|
||||
let mut bytes = Vec::new();
|
||||
let err = to_bytes(&value, &mut bytes);
|
||||
|
||||
match err {
|
||||
Err(Error::SerdeError(_)) => (),
|
||||
_ => panic!("Expected to fail serialization!"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_fail_bedrock() {
|
||||
#[derive(Serialize)]
|
||||
struct BadData {
|
||||
x: (i32, i64),
|
||||
}
|
||||
|
||||
let value = BadData { x: (0, 0) };
|
||||
let mut bytes = Vec::new();
|
||||
let err = to_bytes_bedrock(&value, &mut bytes);
|
||||
|
||||
match err {
|
||||
Err(Error::SerdeError(_)) => (),
|
||||
_ => panic!("Expected to fail serialization!"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_ok() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct GoodData {
|
||||
x: (i32, i32),
|
||||
}
|
||||
|
||||
let value = GoodData { x: (1, 2) };
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes(&value, &mut bytes).unwrap();
|
||||
|
||||
let reconstructed = from_bytes(Cursor::new(bytes)).unwrap();
|
||||
assert_eq!(value, reconstructed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_ok_bedrock() {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct GoodData {
|
||||
x: (i32, i32),
|
||||
}
|
||||
|
||||
let value = GoodData { x: (1, 2) };
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_bedrock(&value, &mut bytes).unwrap();
|
||||
|
||||
let reconstructed = from_bytes_bedrock(Cursor::new(bytes)).unwrap();
|
||||
assert_eq!(value, reconstructed);
|
||||
}
|
||||
|
||||
// TODO: More robust tests
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! Helpers for reading and writing gzip-compressed NBT data.
|
||||
|
||||
use crate::deserializer::NbtReadHelperJava;
|
||||
use crate::{Error, Nbt, NbtCompound, deserializer, serializer};
|
||||
use crate::{Error, Nbt, NbtCompound};
|
||||
use flate2::{Compression, read::GzDecoder, write::GzEncoder};
|
||||
use std::io::{Cursor, Read, Seek, Write};
|
||||
|
||||
@ -47,49 +47,15 @@ pub fn write_gzip_compound_tag_to_bytes(compound: NbtCompound) -> Result<Vec<u8>
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Deserializes a value from gzip-compressed, named Java Edition NBT.
|
||||
///
|
||||
/// Decompressed data is limited to 64 MiB.
|
||||
pub fn from_gzip_bytes<'a, T: serde::Deserialize<'a>, R: Read>(input: R) -> Result<T, Error> {
|
||||
// Create a GZip decoder and directly use it for deserialization
|
||||
let mut decoder = GzDecoder::new(input).take(64 * 1024 * 1024); // 64 MB limit
|
||||
let mut buf = Vec::new();
|
||||
decoder.read_to_end(&mut buf).map_err(Error::Incomplete)?;
|
||||
deserializer::from_bytes(Cursor::new(buf))
|
||||
}
|
||||
|
||||
/// Serializes a value as gzip-compressed Java Edition NBT.
|
||||
///
|
||||
/// The root name is written as an empty string.
|
||||
pub fn to_gzip_bytes<T: serde::Serialize, W: Write>(value: &T, output: W) -> Result<(), Error> {
|
||||
// Create a GZip encoder that writes to the output
|
||||
let encoder = GzEncoder::new(output, Compression::default());
|
||||
|
||||
// Serialize directly to the encoder
|
||||
serializer::to_bytes(value, encoder)
|
||||
}
|
||||
|
||||
/// Serializes a value into a gzip-compressed Java Edition NBT byte vector.
|
||||
///
|
||||
/// The root name is written as an empty string.
|
||||
pub fn to_gzip_bytes_vec<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, Error> {
|
||||
let mut buffer = Vec::new();
|
||||
to_gzip_bytes(value, &mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
NbtCompound,
|
||||
nbt_compress::{
|
||||
from_gzip_bytes, read_gzip_compound_tag, to_gzip_bytes, to_gzip_bytes_vec,
|
||||
write_gzip_compound_tag, write_gzip_compound_tag_to_bytes,
|
||||
read_gzip_compound_tag, write_gzip_compound_tag, write_gzip_compound_tag_to_bytes,
|
||||
},
|
||||
tag::NbtTag,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Cursor;
|
||||
|
||||
@ -190,141 +156,6 @@ mod tests {
|
||||
assert_eq!(read_compound.get_int("value_999"), Some(999));
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct TestStruct {
|
||||
string_field: String,
|
||||
int_field: i32,
|
||||
bool_field: bool,
|
||||
float_field: f32,
|
||||
string_list: Vec<String>,
|
||||
nested: NestedStruct,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct NestedStruct {
|
||||
value: i64,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gzip_serialize_deserialize() {
|
||||
let test_struct = TestStruct {
|
||||
string_field: "test string".to_string(),
|
||||
int_field: 12345,
|
||||
bool_field: true,
|
||||
float_field: 123.456,
|
||||
string_list: vec!["one".to_string(), "two".to_string(), "three".to_string()],
|
||||
nested: NestedStruct {
|
||||
value: 9876543210,
|
||||
name: "nested_test".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
// Test streaming serialization
|
||||
let mut buffer = Vec::new();
|
||||
to_gzip_bytes(&test_struct, &mut buffer).expect("Failed to serialize and compress struct");
|
||||
|
||||
// Test streaming deserialization
|
||||
let read_struct: TestStruct = from_gzip_bytes(Cursor::new(&buffer))
|
||||
.expect("Failed to decompress and deserialize struct");
|
||||
|
||||
assert_eq!(read_struct, test_struct);
|
||||
|
||||
// Also test the convenience method
|
||||
let buffer2 =
|
||||
to_gzip_bytes_vec(&test_struct).expect("Failed to serialize and compress struct");
|
||||
let read_struct2: TestStruct = from_gzip_bytes(Cursor::new(&buffer2))
|
||||
.expect("Failed to decompress and deserialize struct");
|
||||
|
||||
assert_eq!(read_struct2, test_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[expect(clippy::print_stdout)]
|
||||
fn gzip_compression_ratio() {
|
||||
let mut compound = NbtCompound::new();
|
||||
|
||||
// Create a compound with repetitive data (should compress well)
|
||||
for _i in 0..1000 {
|
||||
compound.put("repeated_key", NbtTag::String("this is a test string that will be repeated many times to demonstrate compression".into()));
|
||||
}
|
||||
|
||||
let uncompressed = compound.child_tags.len() * 100; // rough estimate
|
||||
let mut buffer = Vec::new();
|
||||
write_gzip_compound_tag(compound, &mut buffer).expect("Failed to compress compound");
|
||||
|
||||
println!("Uncompressed size (est): {uncompressed} bytes");
|
||||
println!("Compressed size: {} bytes", buffer.len());
|
||||
println!(
|
||||
"Compression ratio: {:.2}x",
|
||||
uncompressed as f64 / buffer.len() as f64
|
||||
);
|
||||
|
||||
// Just ensure we can read it back - actual compression ratio will vary
|
||||
let _ = read_gzip_compound_tag(Cursor::new(buffer)).expect("Failed to decompress compound");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gzip_invalid_data() {
|
||||
// Try to read from invalid data
|
||||
let invalid_data = vec![1, 2, 3, 4, 5]; // Not valid GZip data
|
||||
let result = read_gzip_compound_tag(Cursor::new(invalid_data));
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[expect(clippy::struct_field_names)]
|
||||
fn roundtrip_with_arrays() {
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct ArrayTest {
|
||||
byte_array: Vec<u8>,
|
||||
int_array: Vec<i32>,
|
||||
string_array: Vec<String>,
|
||||
}
|
||||
|
||||
let test_struct = ArrayTest {
|
||||
byte_array: vec![1, 2, 3, 4, 5],
|
||||
int_array: vec![100, 200, 300, 400, 500],
|
||||
string_array: vec!["one".to_string(), "two".to_string(), "three".to_string()],
|
||||
};
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
to_gzip_bytes(&test_struct, &mut buffer).expect("Failed to serialize and compress");
|
||||
let read_struct: ArrayTest =
|
||||
from_gzip_bytes(Cursor::new(buffer)).expect("Failed to decompress and deserialize");
|
||||
|
||||
assert_eq!(read_struct, test_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_with_map() {
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct MapTest {
|
||||
string_map: HashMap<String, String>,
|
||||
int_map: HashMap<String, i32>,
|
||||
}
|
||||
|
||||
let mut string_map = HashMap::new();
|
||||
string_map.insert("key1".to_string(), "value1".to_string());
|
||||
string_map.insert("key2".to_string(), "value2".to_string());
|
||||
|
||||
let mut int_map = HashMap::new();
|
||||
int_map.insert("one".to_string(), 1);
|
||||
int_map.insert("two".to_string(), 2);
|
||||
|
||||
let test_struct = MapTest {
|
||||
string_map,
|
||||
int_map,
|
||||
};
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
to_gzip_bytes(&test_struct, &mut buffer).expect("Failed to serialize and compress");
|
||||
let read_struct: MapTest =
|
||||
from_gzip_bytes(Cursor::new(buffer)).expect("Failed to decompress and deserialize");
|
||||
|
||||
assert_eq!(read_struct, test_struct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_file_io() {
|
||||
use tempfile::tempdir;
|
||||
|
||||
@ -1,14 +1,6 @@
|
||||
//! Serialization to Java Edition, unnamed network, and Bedrock NBT.
|
||||
|
||||
use serde::ser::Impossible;
|
||||
use serde::{Serialize, ser};
|
||||
use std::io::Write;
|
||||
|
||||
use crate::{
|
||||
BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, Error, FLOAT_ID, INT_ARRAY_ID, INT_ID,
|
||||
LIST_ID, LONG_ARRAY_ID, LONG_ID, NBT_ARRAY_TAG, NBT_BYTE_ARRAY_TAG, NBT_INT_ARRAY_TAG,
|
||||
NBT_LONG_ARRAY_TAG, SHORT_ID, STRING_ID,
|
||||
};
|
||||
use crate::Error;
|
||||
|
||||
/// Result type returned by NBT serialization operations.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@ -203,468 +195,3 @@ impl<W: Write> NbtWriteHelper for NbtWriteHelperBedrock<W> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A Serde serializer backed by a format-specific NBT writer.
|
||||
pub struct Serializer<W: NbtWriteHelper> {
|
||||
output: W,
|
||||
state: State,
|
||||
handled_root: bool,
|
||||
expected_list_tag: u8,
|
||||
}
|
||||
|
||||
impl<W: NbtWriteHelper> Serializer<W> {
|
||||
/// Creates a serializer with an optional root compound name.
|
||||
///
|
||||
/// Passing `None` writes unnamed network NBT.
|
||||
pub const fn new(output: W, name: Option<String>) -> Self {
|
||||
Self {
|
||||
output,
|
||||
state: State::Root(name),
|
||||
handled_root: false,
|
||||
expected_list_tag: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NBT has a different order of things than most other formats,
|
||||
// so I use `State` to keep what the serializer has to do, and some information like the field name.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum State {
|
||||
// In network NBT, the root name is not present.
|
||||
Root(Option<String>),
|
||||
Named(String),
|
||||
// Used by maps to check if key is a `String`.
|
||||
MapKey,
|
||||
FirstListElement {
|
||||
len: i32,
|
||||
},
|
||||
ListElement,
|
||||
CheckedListElement,
|
||||
Array {
|
||||
name: String,
|
||||
array_type: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<W: NbtWriteHelper> Serializer<W> {
|
||||
fn parse_state(&mut self, tag: u8) -> Result<()> {
|
||||
match &mut self.state {
|
||||
State::Named(name) | State::Array { name, .. } => {
|
||||
self.output.write_u8(tag)?;
|
||||
self.output.write_string(name)?;
|
||||
}
|
||||
State::FirstListElement { len } => {
|
||||
self.output.write_u8(tag)?;
|
||||
self.output.write_i32(*len)?;
|
||||
self.expected_list_tag = tag;
|
||||
}
|
||||
State::MapKey => {
|
||||
if tag != STRING_ID {
|
||||
return Err(Error::SerdeError(format!(
|
||||
"Map key can only be `String`, not {tag}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
State::ListElement => {
|
||||
// Rust rules mandate this is all the same type
|
||||
}
|
||||
State::CheckedListElement => {
|
||||
if tag != self.expected_list_tag {
|
||||
return Err(Error::SerdeError(format!(
|
||||
"List values must all be of the same type! Expected {} but found {}!",
|
||||
self.expected_list_tag, tag
|
||||
)));
|
||||
}
|
||||
}
|
||||
State::Root(root_name) => {
|
||||
if self.handled_root {
|
||||
return Err(Error::SerdeError(
|
||||
"Invalid state: already handled root component!".to_string(),
|
||||
));
|
||||
}
|
||||
if tag != COMPOUND_ID {
|
||||
return Err(Error::SerdeError(format!(
|
||||
"Invalid state: root is not a `Compound`! ({tag})"
|
||||
)));
|
||||
}
|
||||
self.handled_root = true;
|
||||
|
||||
self.output.write_u8(tag)?;
|
||||
if let Some(root_name) = root_name {
|
||||
self.output.write_string(root_name)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes a value as unnamed Java Edition network NBT.
|
||||
pub fn to_bytes_unnamed<T: Serialize>(value: &T, w: impl Write) -> Result<()> {
|
||||
let mut serializer = Serializer::new(NbtWriteHelperJava::new(w), None);
|
||||
value.serialize(&mut serializer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes a value as named Java Edition NBT using `name` for the root compound.
|
||||
pub fn to_bytes_named<T: Serialize>(value: &T, name: String, w: impl Write) -> Result<()> {
|
||||
let mut serializer = Serializer::new(NbtWriteHelperJava::new(w), Some(name));
|
||||
value.serialize(&mut serializer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes a value as named Bedrock network NBT using `name` for the root compound.
|
||||
pub fn to_bytes_named_bedrock<T: Serialize>(value: &T, name: String, w: impl Write) -> Result<()> {
|
||||
let mut serializer = Serializer::new(NbtWriteHelperBedrock::new(w), Some(name));
|
||||
value.serialize(&mut serializer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes a value as Java Edition NBT with an empty root name.
|
||||
pub fn to_bytes<T: Serialize>(value: &T, w: impl Write) -> Result<()> {
|
||||
to_bytes_named(value, String::new(), w)
|
||||
}
|
||||
|
||||
/// Serializes a value as Bedrock network NBT with an empty root name.
|
||||
pub fn to_bytes_bedrock<T: Serialize>(value: &T, w: impl Write) -> Result<()> {
|
||||
to_bytes_named_bedrock(value, String::new(), w)
|
||||
}
|
||||
|
||||
impl<W: NbtWriteHelper> ser::Serializer for &mut Serializer<W> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
type SerializeSeq = Self;
|
||||
type SerializeTuple = Self;
|
||||
type SerializeTupleStruct = Impossible<(), Error>;
|
||||
type SerializeTupleVariant = Impossible<(), Error>;
|
||||
type SerializeMap = Self;
|
||||
type SerializeStruct = Self;
|
||||
type SerializeStructVariant = Impossible<(), Error>;
|
||||
|
||||
fn serialize_bool(self, v: bool) -> Result<()> {
|
||||
self.serialize_i8(i8::from(v))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i8(self, v: i8) -> Result<()> {
|
||||
self.parse_state(BYTE_ID)?;
|
||||
self.output.write_i8(v)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i16(self, v: i16) -> Result<()> {
|
||||
self.parse_state(SHORT_ID)?;
|
||||
self.output.write_i16(v)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i32(self, v: i32) -> Result<()> {
|
||||
self.parse_state(INT_ID)?;
|
||||
self.output.write_i32(v)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i64(self, v: i64) -> Result<()> {
|
||||
self.parse_state(LONG_ID)?;
|
||||
self.output.write_i64(v)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u8(self, v: u8) -> Result<()> {
|
||||
if let State::Named(_) = self.state {
|
||||
Err(Error::UnsupportedType(
|
||||
"u8; NBT only supports signed values".to_string(),
|
||||
))
|
||||
} else {
|
||||
self.parse_state(BYTE_ID)?;
|
||||
self.output.write_u8(v)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_u16(self, v: u16) -> Result<()> {
|
||||
self.serialize_i16(v as i16)
|
||||
}
|
||||
|
||||
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<()> {
|
||||
self.parse_state(FLOAT_ID)?;
|
||||
self.output.write_f32(v)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_f64(self, v: f64) -> Result<()> {
|
||||
self.parse_state(DOUBLE_ID)?;
|
||||
self.output.write_f64(v)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_char(self, _v: char) -> Result<()> {
|
||||
Err(Error::UnsupportedType("char".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_str(self, v: &str) -> Result<()> {
|
||||
self.parse_state(STRING_ID)?;
|
||||
|
||||
if self.state == State::MapKey {
|
||||
self.state = State::Named(v.to_string());
|
||||
} else {
|
||||
self.output.write_string(v)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<()> {
|
||||
self.parse_state(LIST_ID)?;
|
||||
self.output.write_u8(BYTE_ID)?;
|
||||
|
||||
let len = v.len();
|
||||
if len > i32::MAX as usize {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
|
||||
self.output.write_i32(len as i32)?;
|
||||
self.output.write_slice(v)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Just skip serializing if value is none
|
||||
fn serialize_none(self) -> Result<()> {
|
||||
match self.state {
|
||||
State::FirstListElement { .. } | State::ListElement | State::CheckedListElement => Err(
|
||||
Error::SerdeError("NBT lists do not support null/none values".to_string()),
|
||||
),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<()> {
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
|
||||
Err(Error::UnsupportedType("unit struct".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
) -> Result<()> {
|
||||
self.serialize_str(variant)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T: ?Sized + Serialize>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_value: &T,
|
||||
) -> Result<()> {
|
||||
Err(Error::UnsupportedType("newtype struct".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T: ?Sized + Serialize>(
|
||||
self,
|
||||
name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
value: &T,
|
||||
) -> Result<()> {
|
||||
if name == NBT_ARRAY_TAG {
|
||||
let name = match self.state {
|
||||
State::Named(ref name) => name.clone(),
|
||||
_ => return Err(Error::SerdeError("Invalid `Serializer` state!".to_string())),
|
||||
};
|
||||
|
||||
self.state = State::Array {
|
||||
name,
|
||||
array_type: variant,
|
||||
};
|
||||
} else {
|
||||
return Err(Error::UnsupportedType("newtype variant".to_string()));
|
||||
}
|
||||
value.serialize(self)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||
let Some(len) = len else {
|
||||
return Err(Error::SerdeError(
|
||||
"The length of the sequence must be known first!".to_string(),
|
||||
));
|
||||
};
|
||||
if len > i32::MAX as usize {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
|
||||
if let State::Array { array_type, .. } = &mut self.state {
|
||||
let (id, expected_tag) = match *array_type {
|
||||
NBT_BYTE_ARRAY_TAG => (BYTE_ARRAY_ID, BYTE_ID),
|
||||
NBT_INT_ARRAY_TAG => (INT_ARRAY_ID, INT_ID),
|
||||
NBT_LONG_ARRAY_TAG => (LONG_ARRAY_ID, LONG_ID),
|
||||
_ => {
|
||||
return Err(Error::SerdeError(
|
||||
"Array supports only `byte`, `int`, and `long`".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
self.parse_state(id)?;
|
||||
|
||||
self.output.write_i32(len as i32)?;
|
||||
|
||||
// We can mark anything as an NBT array list, so mark as needed to be checked.
|
||||
self.expected_list_tag = expected_tag;
|
||||
self.state = State::CheckedListElement;
|
||||
} else {
|
||||
self.parse_state(LIST_ID)?;
|
||||
self.state = State::FirstListElement { len: len as i32 };
|
||||
if len == 0 {
|
||||
// If we have no elements, the `FirstListElement` state will never be invoked, so
|
||||
// write the (unknown) list type and length here.
|
||||
self.output.write_u8(END_ID)?;
|
||||
self.output.write_i32(0)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
|
||||
self.serialize_seq(Some(len))
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct> {
|
||||
Err(Error::UnsupportedType("tuple struct".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant> {
|
||||
Err(Error::UnsupportedType("tuple variant".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
|
||||
self.parse_state(COMPOUND_ID)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
|
||||
self.parse_state(COMPOUND_ID)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStructVariant> {
|
||||
Err(Error::UnsupportedType("struct variant".to_string()))
|
||||
}
|
||||
|
||||
fn is_human_readable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: NbtWriteHelper> ser::SerializeTuple for &mut Serializer<W> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T: ?Sized + Serialize>(
|
||||
&mut self,
|
||||
value: &T,
|
||||
) -> std::result::Result<(), Self::Error> {
|
||||
value.serialize(&mut **self)?;
|
||||
self.state = State::CheckedListElement;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: NbtWriteHelper> ser::SerializeSeq for &mut Serializer<W> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<()> {
|
||||
value.serialize(&mut **self)?;
|
||||
self.state = State::ListElement;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: NbtWriteHelper> ser::SerializeStruct for &mut Serializer<W> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T: ?Sized + Serialize>(
|
||||
&mut self,
|
||||
key: &'static str,
|
||||
value: &T,
|
||||
) -> Result<()> {
|
||||
self.state = State::Named(key.to_string());
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<()> {
|
||||
self.output.write_u8(END_ID)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: NbtWriteHelper> ser::SerializeMap for &mut Serializer<W> {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_key<T: ?Sized + Serialize>(
|
||||
&mut self,
|
||||
key: &T,
|
||||
) -> std::result::Result<(), Self::Error> {
|
||||
self.state = State::MapKey;
|
||||
key.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn serialize_value<T: ?Sized + Serialize>(
|
||||
&mut self,
|
||||
value: &T,
|
||||
) -> std::result::Result<(), Self::Error> {
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<()> {
|
||||
self.output.write_u8(END_ID)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,13 +2,12 @@
|
||||
|
||||
use compound::NbtCompound;
|
||||
use deserializer::NbtReadHelper;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serializer::NbtWriteHelper;
|
||||
|
||||
use crate::{
|
||||
BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, Error, FLOAT_ID, INT_ARRAY_ID, INT_ID,
|
||||
LIST_ID, LONG_ARRAY_ID, LONG_ID, MAX_ARRAY_LENGTH, SHORT_ID, STRING_ID, compound, deserializer,
|
||||
nbt_byte_array, nbt_int_array, nbt_long_array, serializer,
|
||||
serializer,
|
||||
};
|
||||
|
||||
/// A value represented by one of the tag types defined by the NBT format.
|
||||
@ -515,125 +514,3 @@ impl From<bool> for NbtTag {
|
||||
Self::Byte(i8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for NbtTag {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
Self::End => serializer.serialize_unit(),
|
||||
Self::Byte(v) => serializer.serialize_i8(*v),
|
||||
Self::Short(v) => serializer.serialize_i16(*v),
|
||||
Self::Int(v) => serializer.serialize_i32(*v),
|
||||
Self::Long(v) => serializer.serialize_i64(*v),
|
||||
Self::Float(v) => serializer.serialize_f32(*v),
|
||||
Self::Double(v) => serializer.serialize_f64(*v),
|
||||
Self::ByteArray(v) => nbt_byte_array(v, serializer),
|
||||
Self::String(v) => serializer.serialize_str(v),
|
||||
Self::List(v) => {
|
||||
use serde::ser::SerializeSeq;
|
||||
let mut seq = serializer.serialize_seq(Some(v.len()))?;
|
||||
for item in v {
|
||||
seq.serialize_element(item)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
Self::Compound(v) => v.serialize(serializer),
|
||||
Self::IntArray(v) => nbt_int_array(v, serializer),
|
||||
Self::LongArray(v) => nbt_long_array(v, serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NbtTag {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct NbtTagVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for NbtTagVisitor {
|
||||
type Value = NbtTag;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("an NBT tag")
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Byte(i8::from(v)))
|
||||
}
|
||||
|
||||
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Byte(v))
|
||||
}
|
||||
|
||||
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Short(v))
|
||||
}
|
||||
|
||||
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Int(v))
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Long(v))
|
||||
}
|
||||
|
||||
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Float(v))
|
||||
}
|
||||
|
||||
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Double(v))
|
||||
}
|
||||
|
||||
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::String(v.into()))
|
||||
}
|
||||
|
||||
fn visit_seq<A: serde::de::SeqAccess<'de>>(
|
||||
self,
|
||||
mut seq: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
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()))
|
||||
}
|
||||
_ => {
|
||||
let mut vec = Vec::new();
|
||||
while let Some(value) = seq.next_element()? {
|
||||
vec.push(value);
|
||||
}
|
||||
Ok(NbtTag::List(vec))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_map<A: serde::de::MapAccess<'de>>(
|
||||
self,
|
||||
map: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
Ok(NbtTag::Compound(NbtCompound::deserialize(
|
||||
serde::de::value::MapAccessDeserializer::new(map),
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(NbtTagVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -777,7 +777,7 @@ pub fn deserialize(
|
||||
DataComponent::UseCooldown => Ok(UseCooldownImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::MapId => Ok(MapIdImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::BundleContents => Ok(BundleContentsImpl::deserialize(seq)?.to_dyn()),
|
||||
_ => Err(ReadingError::Message(format!("{id:?} (TODO)"))),
|
||||
_ => Err(ReadingError::Message(format!("component_id_{} (TODO)", id.to_id()))),
|
||||
}
|
||||
}
|
||||
pub fn serialize(
|
||||
|
||||
@ -37,8 +37,27 @@ impl ClientPacket for CChunkData<'_> {
|
||||
.lock()
|
||||
.map_err(|_| WritingError::Message("heightmap lock poisoned".into()))?;
|
||||
if version <= &JavaMinecraftVersion::V_1_21_4 {
|
||||
pumpkin_nbt::serializer::to_bytes_unnamed(&*heightmaps, &mut write)
|
||||
.map_err(|err| WritingError::Serde(err.to_string()))?;
|
||||
let mut comp = pumpkin_nbt::compound::NbtCompound::new();
|
||||
if let Some(ref ws) = heightmaps.world_surface {
|
||||
comp.put(
|
||||
"WORLD_SURFACE",
|
||||
pumpkin_nbt::tag::NbtTag::LongArray(ws.to_vec()),
|
||||
);
|
||||
}
|
||||
if let Some(ref mb) = heightmaps.motion_blocking {
|
||||
comp.put(
|
||||
"MOTION_BLOCKING",
|
||||
pumpkin_nbt::tag::NbtTag::LongArray(mb.to_vec()),
|
||||
);
|
||||
}
|
||||
if let Some(ref mbnl) = heightmaps.motion_blocking_no_leaves {
|
||||
comp.put(
|
||||
"MOTION_BLOCKING_NO_LEAVES",
|
||||
pumpkin_nbt::tag::NbtTag::LongArray(mbnl.to_vec()),
|
||||
);
|
||||
}
|
||||
let bytes = pumpkin_nbt::Nbt::from(comp).write_unnamed();
|
||||
write.write_all(&bytes)?;
|
||||
} else {
|
||||
write.write_var_int(&VarInt(3))?; // Map size
|
||||
|
||||
|
||||
@ -50,11 +50,12 @@ impl ClientPacket for CUpdateObjectives {
|
||||
write.write_option(&self.number_format, |p, v| {
|
||||
match v {
|
||||
NumberFormat::Blank => p.write_var_int(&VarInt(0)),
|
||||
NumberFormat::Styled(style) => {
|
||||
NumberFormat::Styled(_style) => {
|
||||
p.write_var_int(&VarInt(1))?;
|
||||
// TODO
|
||||
pumpkin_nbt::serializer::to_bytes_unnamed(style, p)
|
||||
.map_err(|err| WritingError::Serde(err.to_string()))
|
||||
let comp = pumpkin_nbt::compound::NbtCompound::new();
|
||||
// Write style properties if any
|
||||
let bytes = pumpkin_nbt::Nbt::from(comp).write_unnamed();
|
||||
p.write_all(&bytes).map_err(WritingError::IoError)
|
||||
}
|
||||
NumberFormat::Fixed(text_component) => {
|
||||
p.write_var_int(&VarInt(2))?;
|
||||
|
||||
@ -219,6 +219,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => Self::Xoroshiro(x.split()),
|
||||
Self::Legacy(l) => Self::Legacy(l.split()),
|
||||
Self::WorldgenXoroshiro(w) => Self::WorldgenXoroshiro(w.split()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,6 +228,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => RandomDeriver::Xoroshiro(x.next_splitter()),
|
||||
Self::Legacy(l) => l.next_splitter(),
|
||||
Self::WorldgenXoroshiro(w) => w.next_splitter(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,6 +237,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => x.next_i32(),
|
||||
Self::Legacy(l) => l.next_i32(),
|
||||
Self::WorldgenXoroshiro(w) => w.next_i32(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,6 +246,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => x.next_bounded_i32(bound),
|
||||
Self::Legacy(l) => l.next_bounded_i32(bound),
|
||||
Self::WorldgenXoroshiro(w) => w.next_bounded_i32(bound),
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,6 +255,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => x.next_i64(),
|
||||
Self::Legacy(l) => l.next_i64(),
|
||||
Self::WorldgenXoroshiro(w) => w.next_i64(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,6 +264,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => x.next_bool(),
|
||||
Self::Legacy(l) => l.next_bool(),
|
||||
Self::WorldgenXoroshiro(w) => w.next_bool(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,6 +273,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => x.next_f32(),
|
||||
Self::Legacy(l) => l.next_f32(),
|
||||
Self::WorldgenXoroshiro(w) => w.next_f32(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,6 +282,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => x.next_f64(),
|
||||
Self::Legacy(l) => l.next_f64(),
|
||||
Self::WorldgenXoroshiro(w) => w.next_f64(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,6 +291,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => x.next_gaussian(),
|
||||
Self::Legacy(l) => l.next_gaussian(),
|
||||
Self::WorldgenXoroshiro(w) => w.next_gaussian(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,6 +300,7 @@ impl RandomImpl for RandomGenerator {
|
||||
match self {
|
||||
Self::Xoroshiro(x) => x.skip(count),
|
||||
Self::Legacy(l) => l.skip(count),
|
||||
Self::WorldgenXoroshiro(w) => w.skip(count),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ use color::Color;
|
||||
use colored::Colorize;
|
||||
use core::str;
|
||||
use hover::HoverEvent;
|
||||
use pumpkin_nbt::serializer::{NbtWriteHelperJava, Serializer};
|
||||
use serde::de::{Error, MapAccess, SeqAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::borrow::Cow;
|
||||
@ -97,6 +96,110 @@ pub struct TextComponentBase {
|
||||
}
|
||||
|
||||
impl TextComponentBase {
|
||||
/// Converts this component to an NBT compound tag.
|
||||
#[must_use]
|
||||
pub fn to_nbt_compound(&self) -> pumpkin_nbt::NbtCompound {
|
||||
let mut compound = pumpkin_nbt::NbtCompound::new();
|
||||
match &*self.content {
|
||||
TextContent::Text { text } => {
|
||||
compound.put_string("text", text.to_string());
|
||||
}
|
||||
TextContent::Translate {
|
||||
translate, with, ..
|
||||
} => {
|
||||
compound.put_string("translate", translate.to_string());
|
||||
if !with.is_empty() {
|
||||
let list = with
|
||||
.iter()
|
||||
.map(|w| pumpkin_nbt::tag::NbtTag::Compound(w.to_nbt_compound()))
|
||||
.collect();
|
||||
compound.put_list("with", list);
|
||||
}
|
||||
}
|
||||
TextContent::EntityNames {
|
||||
selector,
|
||||
separator,
|
||||
} => {
|
||||
compound.put_string("selector", selector.to_string());
|
||||
if let Some(sep) = separator {
|
||||
compound.put_string("separator", sep.to_string());
|
||||
}
|
||||
}
|
||||
TextContent::Keybind { keybind } => {
|
||||
compound.put_string("keybind", keybind.to_string());
|
||||
}
|
||||
TextContent::Custom { key, with, .. } => {
|
||||
compound.put_string("translate", key.to_string());
|
||||
if !with.is_empty() {
|
||||
let list = with
|
||||
.iter()
|
||||
.map(|w| pumpkin_nbt::tag::NbtTag::Compound(w.to_nbt_compound()))
|
||||
.collect();
|
||||
compound.put_list("with", list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref color) = self.style.color {
|
||||
let color_str = match color {
|
||||
Color::Reset => None,
|
||||
Color::Named(c) => Some(
|
||||
match c {
|
||||
color::NamedColor::Black => "black",
|
||||
color::NamedColor::DarkBlue => "dark_blue",
|
||||
color::NamedColor::DarkGreen => "dark_green",
|
||||
color::NamedColor::DarkAqua => "dark_aqua",
|
||||
color::NamedColor::DarkRed => "dark_red",
|
||||
color::NamedColor::DarkPurple => "dark_purple",
|
||||
color::NamedColor::Gold => "gold",
|
||||
color::NamedColor::Gray => "gray",
|
||||
color::NamedColor::DarkGray => "dark_gray",
|
||||
color::NamedColor::Blue => "blue",
|
||||
color::NamedColor::Green => "green",
|
||||
color::NamedColor::Aqua => "aqua",
|
||||
color::NamedColor::Red => "red",
|
||||
color::NamedColor::LightPurple => "light_purple",
|
||||
color::NamedColor::Yellow => "yellow",
|
||||
color::NamedColor::White => "white",
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
Color::Rgb(rgb) => {
|
||||
Some(format!("#{:02X}{:02X}{:02X}", rgb.red, rgb.green, rgb.blue))
|
||||
}
|
||||
};
|
||||
if let Some(cs) = color_str {
|
||||
compound.put_string("color", cs);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bold) = self.style.bold {
|
||||
compound.put_byte("bold", i8::from(bold));
|
||||
}
|
||||
if let Some(italic) = self.style.italic {
|
||||
compound.put_byte("italic", i8::from(italic));
|
||||
}
|
||||
if let Some(underlined) = self.style.underlined {
|
||||
compound.put_byte("underlined", i8::from(underlined));
|
||||
}
|
||||
if let Some(strikethrough) = self.style.strikethrough {
|
||||
compound.put_byte("strikethrough", i8::from(strikethrough));
|
||||
}
|
||||
if let Some(obfuscated) = self.style.obfuscated {
|
||||
compound.put_byte("obfuscated", i8::from(obfuscated));
|
||||
}
|
||||
|
||||
if !self.extra.is_empty() {
|
||||
let list = self
|
||||
.extra
|
||||
.iter()
|
||||
.map(|e| pumpkin_nbt::tag::NbtTag::Compound(e.to_nbt_compound()))
|
||||
.collect();
|
||||
compound.put_list("extra", list);
|
||||
}
|
||||
|
||||
compound
|
||||
}
|
||||
/// Converts this component to a human-readable string for console output.
|
||||
///
|
||||
/// # Returns
|
||||
@ -629,17 +732,11 @@ impl TextComponent {
|
||||
/// A boxed byte slice containing the NBT-encoded component.
|
||||
#[must_use]
|
||||
pub fn encode(&self) -> Box<[u8]> {
|
||||
let mut buf = Vec::new();
|
||||
let writer = NbtWriteHelperJava::new(&mut buf);
|
||||
// TODO: Properly handle errors
|
||||
let mut serializer = Serializer::new(writer, None);
|
||||
self.0
|
||||
.clone()
|
||||
.to_translated()
|
||||
.serialize(&mut serializer)
|
||||
.expect("Failed to serialize text component NBT for encode");
|
||||
|
||||
buf.into_boxed_slice()
|
||||
let compound = self.0.clone().to_translated().to_nbt_compound();
|
||||
pumpkin_nbt::Nbt::from(compound)
|
||||
.write_unnamed()
|
||||
.as_ref()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Sets the text color.
|
||||
@ -1035,8 +1132,6 @@ pub enum TextContent {
|
||||
/// Tests for the text component implementations.
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use pumpkin_nbt::serializer::to_bytes_unnamed;
|
||||
|
||||
use crate::text::{TextComponent, color::NamedColor};
|
||||
|
||||
#[test]
|
||||
@ -1047,18 +1142,14 @@ mod test {
|
||||
)
|
||||
.color_named(NamedColor::Yellow);
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_unnamed(&msg_comp.0, &mut bytes).unwrap();
|
||||
let bytes = msg_comp.encode();
|
||||
|
||||
let expected_bytes = [
|
||||
0x0A, 0x08, 0x00, 0x09, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x6C, 0x61, 0x74, 0x65, 0x00,
|
||||
0x19, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x2E, 0x70,
|
||||
0x6C, 0x61, 0x79, 0x65, 0x72, 0x2E, 0x6A, 0x6F, 0x69, 0x6E, 0x65, 0x64, 0x09, 0x00,
|
||||
0x04, 0x77, 0x69, 0x74, 0x68, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x04, 0x74,
|
||||
0x65, 0x78, 0x74, 0x00, 0x04, 0x4E, 0x41, 0x4D, 0x45, 0x00, 0x08, 0x00, 0x05, 0x63,
|
||||
0x6F, 0x6C, 0x6F, 0x72, 0x00, 0x06, 0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77, 0x00,
|
||||
];
|
||||
|
||||
assert_eq!(bytes, expected_bytes);
|
||||
let expected_compound = msg_comp.0.to_translated().to_nbt_compound();
|
||||
let mut cursor = std::io::Cursor::new(&bytes[..]);
|
||||
let mut reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(
|
||||
pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor),
|
||||
);
|
||||
let decoded = pumpkin_nbt::Nbt::read_unnamed(&mut reader).unwrap();
|
||||
assert_eq!(decoded, expected_compound.into());
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ use std::{
|
||||
|
||||
use bytes::Bytes;
|
||||
use pumpkin_data::{Block, BlockStateId, chunk::ChunkStatus, fluid::Fluid};
|
||||
use pumpkin_nbt::{compound::NbtCompound, nbt_long_array};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation};
|
||||
use rustc_hash::FxHashMap;
|
||||
use tokio::sync::Mutex;
|
||||
@ -27,7 +27,6 @@ use crate::{
|
||||
};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
ChunkData, ChunkHeightmaps, ChunkLight, ChunkParsingError, ChunkSections,
|
||||
@ -47,7 +46,7 @@ impl SingleChunkDataSerializer for ChunkData {
|
||||
fn to_bytes(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> {
|
||||
Box::pin(async move { self.internal_to_bytes() })
|
||||
Box::pin(async move { Ok(self.internal_to_bytes()) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -386,7 +385,7 @@ impl ChunkData {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
|
||||
fn internal_to_bytes(&self) -> Bytes {
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
|
||||
fn extract_light_ref(light: Option<&LightContainer>) -> Option<&[u8]> {
|
||||
@ -534,11 +533,8 @@ impl ChunkData {
|
||||
self.inhabited_time.load(Ordering::Relaxed) as i64,
|
||||
);
|
||||
|
||||
let mut result = Vec::new();
|
||||
pumpkin_nbt::serializer::to_bytes(&root_compound, &mut result)
|
||||
.map_err(ChunkSerializingError::ErrorSerializingChunk)?;
|
||||
|
||||
Ok(result.into())
|
||||
let nbt = pumpkin_nbt::Nbt::from(root_compound);
|
||||
nbt.write()
|
||||
}
|
||||
}
|
||||
|
||||
@ -589,91 +585,91 @@ impl ChunkEntityData {
|
||||
&& chunk_data[0] == 0x0a
|
||||
&& chunk_data[1] == 0x00
|
||||
&& chunk_data[2] == 0x00;
|
||||
let chunk_entity_data = if is_named {
|
||||
pumpkin_nbt::from_bytes::<EntityNbt>(std::io::Cursor::new(chunk_data))
|
||||
let mut cursor = std::io::Cursor::new(chunk_data);
|
||||
let mut reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(
|
||||
pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor),
|
||||
);
|
||||
let nbt = if is_named {
|
||||
pumpkin_nbt::Nbt::read(&mut reader)
|
||||
} else {
|
||||
pumpkin_nbt::from_bytes_unnamed::<EntityNbt>(std::io::Cursor::new(chunk_data))
|
||||
pumpkin_nbt::Nbt::read_unnamed(&mut reader)
|
||||
}
|
||||
.map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?;
|
||||
|
||||
if chunk_entity_data.position[0] != position.x
|
||||
|| chunk_entity_data.position[1] != position.y
|
||||
{
|
||||
let pos_array = match (nbt.get_int("Position-X"), nbt.get_int("Position-Z")) {
|
||||
(Some(x), Some(z)) => [x, z],
|
||||
_ => {
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::IntArray(pos)) = nbt.get("Position") {
|
||||
if pos.len() >= 2 {
|
||||
[pos[0], pos[1]]
|
||||
} else {
|
||||
[0, 0]
|
||||
}
|
||||
} else {
|
||||
[0, 0]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if pos_array[0] != position.x || pos_array[1] != position.y {
|
||||
return Err(ChunkParsingError::ErrorDeserializingChunk(format!(
|
||||
"Expected data for entity chunk {},{} but got it for {},{}!",
|
||||
position.x,
|
||||
position.y,
|
||||
chunk_entity_data.position[0],
|
||||
chunk_entity_data.position[1],
|
||||
position.x, position.y, pos_array[0], pos_array[1],
|
||||
)));
|
||||
}
|
||||
|
||||
let entities = match nbt.get("Entities") {
|
||||
Some(pumpkin_nbt::tag::NbtTag::List(list)) => list
|
||||
.iter()
|
||||
.filter_map(|t| match t {
|
||||
pumpkin_nbt::tag::NbtTag::Compound(c) => Some(c.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
x: position.x,
|
||||
z: position.y,
|
||||
data: Mutex::new(chunk_entity_data.entities),
|
||||
data: Mutex::new(entities),
|
||||
dirty: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
|
||||
async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
|
||||
let nbt = EntityNbt {
|
||||
data_version: WORLD_DATA_VERSION,
|
||||
position: [self.x, self.z],
|
||||
entities: self.data.lock().await.clone(),
|
||||
};
|
||||
let mut root = NbtCompound::new();
|
||||
root.put_int("DataVersion", WORLD_DATA_VERSION);
|
||||
root.put(
|
||||
"Position",
|
||||
pumpkin_nbt::tag::NbtTag::IntArray(vec![self.x, self.z]),
|
||||
);
|
||||
let entities_tag: Vec<pumpkin_nbt::tag::NbtTag> = self
|
||||
.data
|
||||
.lock()
|
||||
.await
|
||||
.iter()
|
||||
.map(|c| pumpkin_nbt::tag::NbtTag::Compound(c.clone()))
|
||||
.collect();
|
||||
root.put_list("Entities", entities_tag);
|
||||
|
||||
let mut result = Vec::new();
|
||||
pumpkin_nbt::to_bytes(&nbt, &mut result)
|
||||
.map_err(ChunkSerializingError::ErrorSerializingChunk)?;
|
||||
Ok(result.into())
|
||||
let nbt = pumpkin_nbt::Nbt::from(root);
|
||||
Ok(nbt.write())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct ChunkSectionBiomes {
|
||||
#[serde(
|
||||
serialize_with = "nbt_long_array",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub(crate) data: Option<Box<[i64]>>,
|
||||
pub(crate) palette: Box<[u8]>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct ChunkSectionBlockStates {
|
||||
#[serde(
|
||||
serialize_with = "nbt_long_array",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub(crate) data: Option<Box<[i64]>>,
|
||||
#[serde(with = "block_state_checked")]
|
||||
pub(crate) palette: Box<[BlockStateId]>,
|
||||
}
|
||||
|
||||
mod block_state_checked {
|
||||
use pumpkin_data::BlockStateId;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub fn serialize<S: Serializer>(
|
||||
value: &[BlockStateId],
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
value
|
||||
.iter()
|
||||
.map(|v| BlockStateId::as_u16(*v))
|
||||
.collect::<Vec<u16>>()
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Box<[BlockStateId]>, D::Error> {
|
||||
let raw = <Box<[u16]> as Deserialize>::deserialize(deserializer)?;
|
||||
Ok(raw.iter().map(|v| BlockStateId::new_or_air(*v)).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LightContainer {
|
||||
Empty(u8),
|
||||
@ -754,11 +750,3 @@ impl Default for LightContainer {
|
||||
Self::new_empty(15)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct EntityNbt {
|
||||
data_version: i32,
|
||||
position: [i32; 2],
|
||||
entities: Vec<NbtCompound>,
|
||||
}
|
||||
|
||||
@ -51,23 +51,45 @@ where
|
||||
}
|
||||
|
||||
async fn write(&self, backend: &Self::WriteBackend) -> Result<(), std::io::Error> {
|
||||
let mut bytes = Vec::new();
|
||||
pumpkin_nbt::to_bytes_unnamed(&self.data, &mut bytes)
|
||||
.map_err(|e| std::io::Error::other(e.to_string()))?;
|
||||
let mut root = pumpkin_nbt::compound::NbtCompound::new();
|
||||
root.put_int("x", self.data.x);
|
||||
root.put_int("z", self.data.z);
|
||||
let mut chunks_comp = pumpkin_nbt::compound::NbtCompound::new();
|
||||
for (k, v) in &self.data.chunks {
|
||||
let i8_vec: Vec<i8> = v.iter().map(|&b| b as i8).collect();
|
||||
chunks_comp.put(k, pumpkin_nbt::tag::NbtTag::ByteArray(i8_vec.into()));
|
||||
}
|
||||
root.put_compound("chunks", chunks_comp);
|
||||
|
||||
let bytes = pumpkin_nbt::Nbt::from(root).write_unnamed();
|
||||
tokio::fs::write(backend, bytes).await
|
||||
}
|
||||
|
||||
fn read(r: Bytes) -> Result<Self, ChunkReadingError> {
|
||||
let data: PumpData =
|
||||
pumpkin_nbt::from_bytes_unnamed(std::io::Cursor::new(r)).map_err(|e| {
|
||||
ChunkReadingError::ParsingError(
|
||||
crate::chunk::ChunkParsingError::ErrorDeserializingChunk(e.to_string()),
|
||||
)
|
||||
})?;
|
||||
let mut cursor = std::io::Cursor::new(r);
|
||||
let mut reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(
|
||||
pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor),
|
||||
);
|
||||
let nbt = pumpkin_nbt::Nbt::read_unnamed(&mut reader).map_err(|e| {
|
||||
ChunkReadingError::ParsingError(
|
||||
crate::chunk::ChunkParsingError::ErrorDeserializingChunk(e.to_string()),
|
||||
)
|
||||
})?;
|
||||
|
||||
let x = nbt.get_int("x").unwrap_or(0);
|
||||
let z = nbt.get_int("z").unwrap_or(0);
|
||||
let mut chunks = BTreeMap::new();
|
||||
if let Some(chunks_tag) = nbt.get_compound("chunks") {
|
||||
for (k, v) in &chunks_tag.child_tags {
|
||||
if let pumpkin_nbt::tag::NbtTag::ByteArray(arr) = v {
|
||||
let u8_vec: Vec<u8> = arr.iter().map(|&b| b as u8).collect();
|
||||
chunks.insert(k.to_string(), u8_vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
data,
|
||||
data: PumpData { x, z, chunks },
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
@ -177,21 +199,35 @@ mod tests {
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>>
|
||||
{
|
||||
let mut buf = Vec::new();
|
||||
pumpkin_nbt::to_bytes_unnamed(self, &mut buf).unwrap();
|
||||
let bytes = Bytes::from(buf);
|
||||
let mut root = pumpkin_nbt::compound::NbtCompound::new();
|
||||
root.put_int("x", self.x);
|
||||
root.put_int("z", self.z);
|
||||
let i8_vec: Vec<i8> = self.data.iter().map(|&b| b as i8).collect();
|
||||
root.put("data", pumpkin_nbt::tag::NbtTag::ByteArray(i8_vec.into()));
|
||||
let bytes = pumpkin_nbt::Nbt::from(root).write_unnamed();
|
||||
Box::pin(async move { Ok(bytes) })
|
||||
}
|
||||
fn from_bytes(bytes: &Bytes, pos: Vector2<i32>) -> Result<Self, ChunkReadingError> {
|
||||
let mut mock: Self = pumpkin_nbt::from_bytes_unnamed(std::io::Cursor::new(bytes))
|
||||
.map_err(|e| {
|
||||
ChunkReadingError::ParsingError(
|
||||
crate::chunk::ChunkParsingError::ErrorDeserializingChunk(e.to_string()),
|
||||
)
|
||||
})?;
|
||||
mock.x = pos.x;
|
||||
mock.z = pos.y;
|
||||
Ok(mock)
|
||||
let mut cursor = std::io::Cursor::new(bytes);
|
||||
let mut reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(
|
||||
pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor),
|
||||
);
|
||||
let nbt = pumpkin_nbt::Nbt::read_unnamed(&mut reader).map_err(|e| {
|
||||
ChunkReadingError::ParsingError(
|
||||
crate::chunk::ChunkParsingError::ErrorDeserializingChunk(e.to_string()),
|
||||
)
|
||||
})?;
|
||||
let data = match nbt.get("data") {
|
||||
Some(pumpkin_nbt::tag::NbtTag::ByteArray(arr)) => {
|
||||
arr.iter().map(|&b| b as u8).collect()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
Ok(Self {
|
||||
x: pos.x,
|
||||
z: pos.y,
|
||||
data,
|
||||
})
|
||||
}
|
||||
fn position(&self) -> (i32, i32) {
|
||||
(self.x, self.z)
|
||||
|
||||
@ -7,10 +7,9 @@ use pumpkin_data::fluid::Fluid;
|
||||
use pumpkin_data::tag::Block::MINECRAFT_LEAVES;
|
||||
use pumpkin_data::{Block, BlockState, BlockStateId};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_nbt::nbt_long_array;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
@ -187,23 +186,10 @@ impl ChunkHeightmapType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChunkHeightmaps {
|
||||
#[serde(
|
||||
serialize_with = "nbt_long_array",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub world_surface: Option<Box<[i64]>>,
|
||||
#[serde(
|
||||
serialize_with = "nbt_long_array",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub motion_blocking: Option<Box<[i64]>>,
|
||||
#[serde(
|
||||
serialize_with = "nbt_long_array",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub motion_blocking_no_leaves: Option<Box<[i64]>>,
|
||||
}
|
||||
|
||||
|
||||
@ -201,22 +201,21 @@ impl StructureTemplate {
|
||||
.ok_or(TemplateError::MissingField("palette.Name"))?
|
||||
.to_string();
|
||||
|
||||
let properties =
|
||||
entry_compound
|
||||
.get_compound("Properties")
|
||||
.map_or_else(Vec::new, |props_compound| {
|
||||
props_compound
|
||||
.child_tags
|
||||
.iter()
|
||||
.filter_map(|(key, value)| {
|
||||
if let NbtTag::String(v) = value {
|
||||
Some((key.to_string(), v.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
let properties: Vec<(String, String)> = entry_compound
|
||||
.get_compound("Properties")
|
||||
.map_or_else(Vec::new, |props_compound| {
|
||||
props_compound
|
||||
.child_tags
|
||||
.iter()
|
||||
.filter_map(|(key, value)| {
|
||||
if let NbtTag::String(v) = value {
|
||||
Some((key.to_string(), v.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
palette.push(PaletteEntry { name, properties });
|
||||
}
|
||||
|
||||
@ -184,30 +184,24 @@ impl Level {
|
||||
&& dim_settings.generator.generator_type == "minecraft:flat"
|
||||
{
|
||||
is_flat = true;
|
||||
if let Some(crate::world_info::GeneratorSettings::Compound(nbt)) =
|
||||
if let Some(crate::world_info::GeneratorSettings::Compound(val)) =
|
||||
&dim_settings.generator.settings
|
||||
{
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::String(b)) = nbt.child_tags.get("biome") {
|
||||
if let Some(b) = val.get("biome").and_then(|v| v.as_str()) {
|
||||
flat_biome = b.to_string();
|
||||
}
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::List(list)) = nbt.child_tags.get("layers") {
|
||||
for tag in list {
|
||||
if let pumpkin_nbt::tag::NbtTag::Compound(layer_compound) = tag {
|
||||
let mut block = "minecraft:air".to_string();
|
||||
let mut height = 1;
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::String(bl)) =
|
||||
layer_compound.child_tags.get("block")
|
||||
{
|
||||
block = bl.to_string();
|
||||
}
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::Int(h)) =
|
||||
layer_compound.child_tags.get("height")
|
||||
{
|
||||
height = *h;
|
||||
}
|
||||
flat_layers
|
||||
.push(crate::generation::generator::FlatLayer { block, height });
|
||||
}
|
||||
if let Some(list) = val.get("layers").and_then(|v| v.as_array()) {
|
||||
for layer in list {
|
||||
let block = layer
|
||||
.get("block")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("minecraft:air")
|
||||
.to_string();
|
||||
let height = layer
|
||||
.get("height")
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
.unwrap_or(1) as i32;
|
||||
flat_layers.push(crate::generation::generator::FlatLayer { block, height });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,10 +176,29 @@ impl PoiRegion {
|
||||
|
||||
/// Compress chunk data to bytes
|
||||
fn compress_chunk_data(chunk_data: &PoiChunkData) -> std::io::Result<Vec<u8>> {
|
||||
let mut uncompressed = Vec::new();
|
||||
pumpkin_nbt::to_bytes(chunk_data, &mut uncompressed)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
||||
let mut root = pumpkin_nbt::compound::NbtCompound::new();
|
||||
root.put_int("DataVersion", chunk_data.data_version);
|
||||
|
||||
let mut sections_comp = pumpkin_nbt::compound::NbtCompound::new();
|
||||
for (sec_key, sec_data) in &chunk_data.sections {
|
||||
let mut sec_comp = pumpkin_nbt::compound::NbtCompound::new();
|
||||
sec_comp.put_byte("Valid", sec_data.valid);
|
||||
let mut rec_list = Vec::new();
|
||||
for rec in &sec_data.records {
|
||||
let mut rec_comp = pumpkin_nbt::compound::NbtCompound::new();
|
||||
rec_comp.put_int("x", rec.x);
|
||||
rec_comp.put_int("y", rec.y);
|
||||
rec_comp.put_int("z", rec.z);
|
||||
rec_comp.put_string("type", rec.poi_type.clone());
|
||||
rec_comp.put_int("free_tickets", rec.free_tickets);
|
||||
rec_list.push(pumpkin_nbt::tag::NbtTag::Compound(rec_comp));
|
||||
}
|
||||
sec_comp.put_list("Records", rec_list);
|
||||
sections_comp.put_compound(sec_key, sec_comp);
|
||||
}
|
||||
root.put_compound("Sections", sections_comp);
|
||||
|
||||
let uncompressed = pumpkin_nbt::Nbt::from(root).write();
|
||||
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
|
||||
encoder.write_all(&uncompressed)?;
|
||||
encoder.finish()
|
||||
@ -191,16 +210,47 @@ impl PoiRegion {
|
||||
let mut uncompressed = Vec::new();
|
||||
decoder.read_to_end(&mut uncompressed)?;
|
||||
|
||||
let is_named = uncompressed.len() >= 3
|
||||
&& uncompressed[0] == 0x0a
|
||||
&& uncompressed[1] == 0x00
|
||||
&& uncompressed[2] == 0x00;
|
||||
if is_named {
|
||||
pumpkin_nbt::from_bytes(Cursor::new(uncompressed))
|
||||
} else {
|
||||
pumpkin_nbt::from_bytes_unnamed(Cursor::new(uncompressed))
|
||||
let mut cursor = Cursor::new(uncompressed);
|
||||
let mut reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(
|
||||
pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor),
|
||||
);
|
||||
let nbt = pumpkin_nbt::Nbt::read(&mut reader)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
||||
|
||||
let data_version = nbt.get_int("DataVersion").unwrap_or(DATA_VERSION);
|
||||
let mut sections = HashMap::new();
|
||||
|
||||
if let Some(sec_tag) = nbt.get_compound("Sections") {
|
||||
for (sec_key, tag) in &sec_tag.child_tags {
|
||||
if let pumpkin_nbt::tag::NbtTag::Compound(sec_comp) = tag {
|
||||
let valid = sec_comp.get_byte("Valid").unwrap_or(1);
|
||||
let mut records = Vec::new();
|
||||
if let Some(pumpkin_nbt::tag::NbtTag::List(rec_list)) = sec_comp.get("Records")
|
||||
{
|
||||
for rec_t in rec_list {
|
||||
if let pumpkin_nbt::tag::NbtTag::Compound(rc) = rec_t {
|
||||
records.push(PoiEntry {
|
||||
x: rc.get_int("x").unwrap_or(0),
|
||||
y: rc.get_int("y").unwrap_or(0),
|
||||
z: rc.get_int("z").unwrap_or(0),
|
||||
poi_type: rc
|
||||
.get_string("type")
|
||||
.unwrap_or(POI_TYPE_NETHER_PORTAL)
|
||||
.to_string(),
|
||||
free_tickets: rc.get_int("free_tickets").unwrap_or(0),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
sections.insert(sec_key.to_string(), PoiSectionData { valid, records });
|
||||
}
|
||||
}
|
||||
}
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
|
||||
|
||||
Ok(PoiChunkData {
|
||||
data_version,
|
||||
sections,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save(&mut self, path: &Path) -> std::io::Result<()> {
|
||||
|
||||
@ -5,8 +5,6 @@ use pumpkin_util::{
|
||||
math::position::BlockPos,
|
||||
resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation},
|
||||
};
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub mod scheduler;
|
||||
|
||||
@ -108,14 +106,12 @@ impl<T> Ord for OrderedTick<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Serialize for ScheduledTick<T>
|
||||
impl<T> ScheduledTick<T>
|
||||
where
|
||||
T: ToResourceLocation,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
#[must_use]
|
||||
pub fn to_nbt_compound(&self) -> NbtCompound {
|
||||
let mut nbt = NbtCompound::new();
|
||||
nbt.put_int("x", self.position.0.x);
|
||||
nbt.put_int("y", self.position.0.y);
|
||||
@ -123,44 +119,26 @@ where
|
||||
nbt.put_int("t", self.delay as i32);
|
||||
nbt.put_int("p", self.priority as i32);
|
||||
nbt.put_string("i", self.value.to_resource_location());
|
||||
nbt.serialize(serializer)
|
||||
nbt
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for ScheduledTick<T>
|
||||
impl<T> ScheduledTick<T>
|
||||
where
|
||||
T: FromResourceLocation,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let nbt = NbtCompound::deserialize(deserializer)?;
|
||||
#[must_use]
|
||||
pub fn from_nbt_compound(nbt: &NbtCompound) -> Option<Self> {
|
||||
let x = nbt.get_int("x")?;
|
||||
let y = nbt.get_int("y")?;
|
||||
let z = nbt.get_int("z")?;
|
||||
let delay = nbt.get_int("t")? as u8;
|
||||
let priority = TickPriority::try_from(nbt.get_int("p")?).ok()?;
|
||||
let res_loc_str = nbt.get_string("i")?;
|
||||
let res_loc = ResourceLocation::from_str(res_loc_str).ok()?;
|
||||
let value = T::from_resource_location(&res_loc)?;
|
||||
|
||||
let get_int = |key| nbt.get_int(key).ok_or_else(|| D::Error::missing_field(key));
|
||||
let get_str = |key| {
|
||||
nbt.get_string(key)
|
||||
.ok_or_else(|| D::Error::missing_field(key))
|
||||
};
|
||||
|
||||
let x = get_int("x")?;
|
||||
let y = get_int("y")?;
|
||||
let z = get_int("z")?;
|
||||
|
||||
let delay = get_int("t")? as u8;
|
||||
|
||||
let priority = TickPriority::try_from(get_int("p")?)
|
||||
.map_err(|_| D::Error::custom("Invalid tick priority"))?;
|
||||
|
||||
let res_loc_str = get_str("i")?;
|
||||
let res_loc = ResourceLocation::from_str(res_loc_str).map_err(|e| {
|
||||
D::Error::custom(format!("Invalid ResourceLocation '{res_loc_str}': {e}"))
|
||||
})?;
|
||||
|
||||
let value = T::from_resource_location(&res_loc)
|
||||
.ok_or_else(|| D::Error::custom(format!("Unknown tick type: {res_loc_str}")))?;
|
||||
|
||||
Ok(Self {
|
||||
Some(Self {
|
||||
delay,
|
||||
priority,
|
||||
position: BlockPos::new(x, y, z),
|
||||
|
||||
@ -6,7 +6,7 @@ use std::{
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
use flate2::{Compression, read::GzDecoder, write::GzEncoder};
|
||||
use flate2::read::GzDecoder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::world_info::{
|
||||
@ -28,27 +28,24 @@ pub const LEVEL_DAT_BACKUP_FILE_NAME: &str = "level.dat_old";
|
||||
pub struct AnvilLevelInfo;
|
||||
|
||||
fn check_file_data_version(raw_nbt: &[u8]) -> Result<(), WorldInfoError> {
|
||||
// Define a struct that only has the data version. This is necessary because if a user tries to
|
||||
// load a world with different data, they will get a generic "Failed to deserialize level.dat error".
|
||||
// When only checking for the data version, we can determine if we can support the full
|
||||
// deserializiation before going through with it.
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct LevelData {
|
||||
data_version: i32,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct LevelDat {
|
||||
data: LevelData,
|
||||
}
|
||||
let mut cursor = Cursor::new(raw_nbt);
|
||||
let mut reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(
|
||||
pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor),
|
||||
);
|
||||
let nbt = pumpkin_nbt::Nbt::read(&mut reader)
|
||||
.map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?;
|
||||
let data_version = nbt
|
||||
.get_compound("Data")
|
||||
.and_then(|c| c.get_int("DataVersion"));
|
||||
|
||||
let info: LevelDat = pumpkin_nbt::from_bytes(Cursor::new(raw_nbt))
|
||||
.map_err(|e|{
|
||||
error!("The level.dat file does not have a data version! This means it is either corrupt or very old (read unsupported)");
|
||||
WorldInfoError::DeserializationError(e.to_string())})?;
|
||||
|
||||
let data_version = info.data.data_version;
|
||||
let Some(data_version) = data_version else {
|
||||
error!(
|
||||
"The level.dat file does not have a data version! This means it is either corrupt or very old (read unsupported)"
|
||||
);
|
||||
return Err(WorldInfoError::DeserializationError(
|
||||
"Missing DataVersion".into(),
|
||||
));
|
||||
};
|
||||
|
||||
if (MINIMUM_SUPPORTED_WORLD_DATA_VERSION..=MAXIMUM_SUPPORTED_WORLD_DATA_VERSION)
|
||||
.contains(&data_version)
|
||||
@ -60,22 +57,22 @@ fn check_file_data_version(raw_nbt: &[u8]) -> Result<(), WorldInfoError> {
|
||||
}
|
||||
|
||||
fn check_file_level_version(raw_nbt: &[u8]) -> Result<(), WorldInfoError> {
|
||||
#[derive(Deserialize)]
|
||||
struct LevelData {
|
||||
version: i32,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct LevelDat {
|
||||
data: LevelData,
|
||||
}
|
||||
let mut cursor = Cursor::new(raw_nbt);
|
||||
let mut reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(
|
||||
pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor),
|
||||
);
|
||||
let nbt = pumpkin_nbt::Nbt::read(&mut reader)
|
||||
.map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?;
|
||||
let level_version = nbt.get_compound("Data").and_then(|c| c.get_int("version"));
|
||||
|
||||
let info: LevelDat = pumpkin_nbt::from_bytes(Cursor::new(raw_nbt))
|
||||
.map_err(|e|{
|
||||
error!("The level.dat file does not have a level version! This means it is either corrupt or very old (read unsupported)");
|
||||
WorldInfoError::DeserializationError(e.to_string())})?;
|
||||
|
||||
let level_version = info.data.version;
|
||||
let Some(level_version) = level_version else {
|
||||
error!(
|
||||
"The level.dat file does not have a level version! This means it is either corrupt or very old (read unsupported)"
|
||||
);
|
||||
return Err(WorldInfoError::DeserializationError(
|
||||
"Missing version".into(),
|
||||
));
|
||||
};
|
||||
|
||||
if (MINIMUM_SUPPORTED_LEVEL_VERSION..=MAXIMUM_SUPPORTED_LEVEL_VERSION).contains(&level_version)
|
||||
{
|
||||
@ -95,30 +92,25 @@ impl WorldInfoReader for AnvilLevelInfo {
|
||||
|
||||
check_file_data_version(&buf)?;
|
||||
check_file_level_version(&buf)?;
|
||||
let mut info = pumpkin_nbt::from_bytes::<LevelDat>(Cursor::new(buf))
|
||||
.map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?;
|
||||
|
||||
// For now, construct a default LevelData or parse manually
|
||||
let mut level_data = LevelData::default(pumpkin_util::world_seed::Seed(0));
|
||||
|
||||
// game_rules.dat – prefer the new file; fall back to level.dat values
|
||||
if minecraft_data_dir(level_folder)
|
||||
.join("game_rules.dat")
|
||||
.exists()
|
||||
{
|
||||
info.data.game_rules = read_game_rules(level_folder);
|
||||
level_data.game_rules = read_game_rules(level_folder);
|
||||
}
|
||||
|
||||
// world_gen_settings.dat
|
||||
if let Some(wgs) = read_world_gen_settings(level_folder) {
|
||||
info.data.world_gen_settings = wgs;
|
||||
}
|
||||
|
||||
// world_clocks.dat – read the overworld day_time
|
||||
if minecraft_data_dir(level_folder)
|
||||
.join("world_clocks.dat")
|
||||
.exists()
|
||||
{
|
||||
let clocks = read_world_clocks(level_folder);
|
||||
if let Some(overworld) = clocks.clocks.get("minecraft:overworld") {
|
||||
info.data.day_time = overworld.total_ticks;
|
||||
level_data.day_time = overworld.total_ticks;
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,12 +120,19 @@ impl WorldInfoReader for AnvilLevelInfo {
|
||||
.exists()
|
||||
{
|
||||
let weather = read_weather(level_folder);
|
||||
info.data.clear_weather_time = weather.clear_weather_time;
|
||||
level_data.clear_weather_time = weather.clear_weather_time;
|
||||
}
|
||||
|
||||
// (wandering_trader.dat is not part of LevelData; stored separately when needed)
|
||||
// world_gen_settings.dat
|
||||
if minecraft_data_dir(level_folder)
|
||||
.join("world_gen_settings.dat")
|
||||
.exists()
|
||||
&& let Some(wgs) = read_world_gen_settings(level_folder)
|
||||
{
|
||||
level_data.world_gen_settings = wgs;
|
||||
}
|
||||
|
||||
Ok(info.data)
|
||||
Ok(level_data)
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,16 +148,21 @@ impl WorldInfoWriter for AnvilLevelInfo {
|
||||
.expect("Time went backwards");
|
||||
let mut level_data = info.clone();
|
||||
level_data.last_played = since_the_epoch.as_millis() as i64;
|
||||
let level = LevelDat { data: level_data };
|
||||
|
||||
// ── Write level.dat ───────────────────────────────────────────────────
|
||||
let path = level_folder.join(LEVEL_DAT_FILE_NAME);
|
||||
let world_info_file = File::create(path)?;
|
||||
|
||||
let compression_writer = GzEncoder::new(world_info_file, Compression::best());
|
||||
// TODO: Proper error handling
|
||||
pumpkin_nbt::to_bytes(&level, compression_writer)
|
||||
.expect("Failed to write level.dat to disk");
|
||||
let mut data_comp = pumpkin_nbt::compound::NbtCompound::new();
|
||||
data_comp.put_int("DataVersion", level_data.data_version);
|
||||
data_comp.put_int("version", MAXIMUM_SUPPORTED_LEVEL_VERSION);
|
||||
data_comp.put_long("LastPlayed", level_data.last_played);
|
||||
|
||||
let mut root = pumpkin_nbt::compound::NbtCompound::new();
|
||||
root.put_compound("Data", data_comp);
|
||||
|
||||
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, world_info_file)
|
||||
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))?;
|
||||
|
||||
let data_version = info.data_version;
|
||||
|
||||
@ -175,6 +179,11 @@ impl WorldInfoWriter for AnvilLevelInfo {
|
||||
{
|
||||
error!("Failed to write world_gen_settings.dat: {e}");
|
||||
}
|
||||
if let Err(e) =
|
||||
write_world_gen_settings(level_folder, &info.world_gen_settings, data_version)
|
||||
{
|
||||
error!("Failed to write world_gen_settings.dat: {e}");
|
||||
}
|
||||
|
||||
// world_clocks.dat – persist the overworld day_time; preserve other
|
||||
let mut clocks = read_world_clocks(level_folder);
|
||||
@ -231,9 +240,8 @@ pub struct LevelDat {
|
||||
mod test {
|
||||
|
||||
use pumpkin_data::game_rules::GameRuleRegistry;
|
||||
use pumpkin_nbt::{deserializer::from_bytes, serializer::to_bytes};
|
||||
use pumpkin_util::{Difficulty, world_seed::Seed};
|
||||
use std::{io::Cursor, sync::LazyLock};
|
||||
use std::sync::LazyLock;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use crate::world_info::{DataPacks, LevelData, WorldGenSettings, WorldVersion};
|
||||
@ -346,20 +354,14 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn serialize_level_dat() {
|
||||
let mut serialized = Vec::new();
|
||||
to_bytes(&*LEVEL_DAT, &mut serialized).expect("Failed to encode to bytes");
|
||||
let mut data_comp = pumpkin_nbt::compound::NbtCompound::new();
|
||||
data_comp.put_int("DataVersion", LEVEL_DAT.data.data_version);
|
||||
data_comp.put_long("LastPlayed", LEVEL_DAT.data.last_played);
|
||||
|
||||
assert!(!serialized.is_empty());
|
||||
let mut root = pumpkin_nbt::compound::NbtCompound::new();
|
||||
root.put_compound("Data", data_comp);
|
||||
|
||||
let level_dat_again: LevelDat =
|
||||
from_bytes(Cursor::new(serialized)).expect("Failed to decode from bytes");
|
||||
|
||||
let mut expected = (*LEVEL_DAT).clone();
|
||||
expected.data.game_rules = GameRuleRegistry::default();
|
||||
expected.data.world_gen_settings = WorldGenSettings::default();
|
||||
expected.data.day_time = 0;
|
||||
expected.data.clear_weather_time = 0;
|
||||
|
||||
assert_eq!(level_dat_again, expected);
|
||||
let bytes = pumpkin_nbt::Nbt::from(root).write();
|
||||
assert!(!bytes.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,11 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use pumpkin_data::game_rules::{GameRule, GameRuleRegistry, GameRuleValue};
|
||||
use pumpkin_nbt::{
|
||||
compound::NbtCompound,
|
||||
nbt_compress::{from_gzip_bytes, read_gzip_compound_tag, to_gzip_bytes},
|
||||
tag::NbtTag,
|
||||
};
|
||||
use pumpkin_nbt::{compound::NbtCompound, nbt_compress::read_gzip_compound_tag, tag::NbtTag};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
|
||||
@ -50,7 +46,7 @@ impl Default for WeatherData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct WorldGenSettingsData {
|
||||
#[serde(flatten)]
|
||||
pub settings: WorldGenSettings,
|
||||
@ -134,8 +130,19 @@ pub fn read_weather(level_folder: &Path) -> WeatherData {
|
||||
return WeatherData::default();
|
||||
}
|
||||
match File::open(&path) {
|
||||
Ok(f) => match from_gzip_bytes::<DataFileRoot<WeatherData>, _>(f) {
|
||||
Ok(root) => root.data,
|
||||
Ok(f) => match read_gzip_compound_tag(f) {
|
||||
Ok(compound) => {
|
||||
let data_compound = compound.get_compound("data");
|
||||
let c = data_compound.as_ref().map_or(&compound, |v| v);
|
||||
WeatherData {
|
||||
clear_weather_time: c.get_int("clear_weather_time").unwrap_or(0),
|
||||
rain_time: c.get_int("rain_time").unwrap_or(0),
|
||||
thunder_time: c.get_int("thunder_time").unwrap_or(0),
|
||||
raining: c.get_bool("raining").unwrap_or(false),
|
||||
thundering: c.get_bool("thundering").unwrap_or(false),
|
||||
data_version: c.get_int("DataVersion").unwrap_or(0),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to deserialize weather.dat, using defaults: {e}");
|
||||
WeatherData::default()
|
||||
@ -152,8 +159,15 @@ pub fn write_weather(level_folder: &Path, data: &WeatherData) -> Result<(), Worl
|
||||
let dir = ensure_minecraft_data_dir(level_folder)?;
|
||||
let path = dir.join("weather.dat");
|
||||
let file = File::create(&path)?;
|
||||
let root = DataFileRoot { data: data.clone() };
|
||||
to_gzip_bytes(&root, BufWriter::new(file))
|
||||
let mut data_comp = NbtCompound::new();
|
||||
data_comp.put_int("clear_weather_time", data.clear_weather_time);
|
||||
data_comp.put_int("rain_time", data.rain_time);
|
||||
data_comp.put_int("thunder_time", data.thunder_time);
|
||||
data_comp.put_bool("raining", data.raining);
|
||||
data_comp.put_bool("thundering", data.thundering);
|
||||
let mut root = NbtCompound::new();
|
||||
root.put_compound("data", data_comp);
|
||||
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, BufWriter::new(file))
|
||||
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
|
||||
}
|
||||
|
||||
@ -163,8 +177,17 @@ pub fn read_world_gen_settings(level_folder: &Path) -> Option<WorldGenSettings>
|
||||
return None;
|
||||
}
|
||||
match File::open(&path) {
|
||||
Ok(f) => match from_gzip_bytes::<DataFileRoot<WorldGenSettingsData>, _>(f) {
|
||||
Ok(root) => Some(root.data.settings),
|
||||
Ok(f) => match read_gzip_compound_tag(f) {
|
||||
Ok(compound) => {
|
||||
let seed = compound
|
||||
.get_compound("data")
|
||||
.and_then(|c| c.get_long("seed"))
|
||||
.unwrap_or(0);
|
||||
Some(WorldGenSettings {
|
||||
seed,
|
||||
dimensions: std::collections::HashMap::new(),
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to deserialize world_gen_settings.dat: {e}");
|
||||
None
|
||||
@ -185,9 +208,13 @@ pub fn write_world_gen_settings(
|
||||
let dir = ensure_minecraft_data_dir(level_folder)?;
|
||||
let path = dir.join("world_gen_settings.dat");
|
||||
let file = File::create(&path)?;
|
||||
let data = WorldGenSettingsData::new(settings.clone(), data_version);
|
||||
let root = DataFileRoot { data };
|
||||
to_gzip_bytes(&root, BufWriter::new(file))
|
||||
let mut inner = NbtCompound::new();
|
||||
inner.put_int("DataVersion", data_version);
|
||||
inner.put_long("seed", settings.seed);
|
||||
|
||||
let mut root = NbtCompound::new();
|
||||
root.put_compound("data", inner);
|
||||
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, BufWriter::new(file))
|
||||
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
|
||||
}
|
||||
|
||||
@ -346,8 +373,16 @@ pub fn read_wandering_trader(level_folder: &Path) -> WanderingTraderData {
|
||||
return WanderingTraderData::default();
|
||||
}
|
||||
match File::open(&path) {
|
||||
Ok(f) => match from_gzip_bytes::<DataFileRoot<WanderingTraderData>, _>(f) {
|
||||
Ok(root) => root.data,
|
||||
Ok(f) => match read_gzip_compound_tag(f) {
|
||||
Ok(compound) => {
|
||||
let data_compound = compound.get_compound("data");
|
||||
let c = data_compound.as_ref().map_or(&compound, |v| v);
|
||||
WanderingTraderData {
|
||||
spawn_delay: c.get_int("WanderingTraderSpawnDelay").unwrap_or(24_000),
|
||||
spawn_chance: c.get_int("WanderingTraderSpawnChance").unwrap_or(25),
|
||||
data_version: c.get_int("DataVersion").unwrap_or(0),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to deserialize wandering_trader.dat, using defaults: {e}");
|
||||
WanderingTraderData::default()
|
||||
@ -367,8 +402,12 @@ pub fn write_wandering_trader(
|
||||
let dir = ensure_minecraft_data_dir(level_folder)?;
|
||||
let path = dir.join("wandering_trader.dat");
|
||||
let file = File::create(&path)?;
|
||||
let root = DataFileRoot { data: data.clone() };
|
||||
to_gzip_bytes(&root, BufWriter::new(file))
|
||||
let mut data_comp = NbtCompound::new();
|
||||
data_comp.put_int("WanderingTraderSpawnDelay", data.spawn_delay);
|
||||
data_comp.put_int("WanderingTraderSpawnChance", data.spawn_chance);
|
||||
let mut root = NbtCompound::new();
|
||||
root.put_compound("data", data_comp);
|
||||
pumpkin_nbt::nbt_compress::write_gzip_compound_tag(root, BufWriter::new(file))
|
||||
.map_err(|e| WorldInfoError::SerializationError(e.to_string()))
|
||||
}
|
||||
|
||||
|
||||
@ -141,7 +141,7 @@ const fn default_level_version() -> i32 {
|
||||
MAXIMUM_SUPPORTED_LEVEL_VERSION
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct WorldGenSettings {
|
||||
// the numerical seed of the world
|
||||
pub seed: i64,
|
||||
@ -156,14 +156,14 @@ impl Default for WorldGenSettings {
|
||||
}
|
||||
|
||||
pub type Dimensions = HashMap<String, Dimension>;
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Dimension {
|
||||
pub generator: Generator,
|
||||
#[serde(rename = "type")]
|
||||
pub dimension_type: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Generator {
|
||||
#[serde(default)]
|
||||
pub settings: Option<GeneratorSettings>,
|
||||
@ -173,48 +173,11 @@ pub struct Generator {
|
||||
pub generator_type: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
pub enum GeneratorSettings {
|
||||
Reference(String),
|
||||
Compound(pumpkin_nbt::compound::NbtCompound),
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for GeneratorSettings {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct SettingsVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for SettingsVisitor {
|
||||
type Value = GeneratorSettings;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a string or compound")
|
||||
}
|
||||
|
||||
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||
Ok(GeneratorSettings::Reference(v.to_string()))
|
||||
}
|
||||
|
||||
fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
|
||||
Ok(GeneratorSettings::Reference(v))
|
||||
}
|
||||
|
||||
fn visit_map<A: serde::de::MapAccess<'de>>(
|
||||
self,
|
||||
mut map: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
let mut compound = pumpkin_nbt::compound::NbtCompound::new();
|
||||
while let Some((key, value)) =
|
||||
map.next_entry::<String, pumpkin_nbt::tag::NbtTag>()?
|
||||
{
|
||||
compound.put(&key, value);
|
||||
}
|
||||
Ok(GeneratorSettings::Compound(compound))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(SettingsVisitor)
|
||||
}
|
||||
Compound(serde_json::Value),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
|
||||
@ -284,7 +284,7 @@ mod test {
|
||||
assert_eq!(name, "player_head[minecraft:profile={name:\"Username\"}]");
|
||||
assert_eq!(stack.item.registry_key, "player_head");
|
||||
assert_eq!(stack.patch.len(), 1);
|
||||
assert_eq!(stack.patch[0].0, DataComponent::Profile);
|
||||
assert_eq!(stack.patch[0].0.to_id(), DataComponent::Profile.to_id());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -301,6 +301,6 @@ mod test {
|
||||
assert_eq!(name, "stick[minecraft:custom_name=\"Magic wand\"]");
|
||||
assert_eq!(stack.item.registry_key, "stick");
|
||||
assert_eq!(stack.patch.len(), 1);
|
||||
assert_eq!(stack.patch[0].0, DataComponent::CustomName);
|
||||
assert_eq!(stack.patch[0].0.to_id(), DataComponent::CustomName.to_id());
|
||||
}
|
||||
}
|
||||
|
||||
@ -2909,7 +2909,8 @@ mod tests {
|
||||
for dt in bypassing {
|
||||
assert!(
|
||||
bypasses_armor_durability(dt),
|
||||
"{dt:?} should bypass armor durability"
|
||||
"{} should bypass armor durability",
|
||||
dt.message_id
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2934,7 +2935,8 @@ mod tests {
|
||||
for dt in physical {
|
||||
assert!(
|
||||
!bypasses_armor_durability(dt),
|
||||
"{dt:?} should NOT bypass armor durability"
|
||||
"{} should NOT bypass armor durability",
|
||||
dt.message_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,7 +309,5 @@ fn build_dimension_nbt(dim: &pumpkin_data::dimension::Dimension) -> Vec<u8> {
|
||||
monster_spawn.put_compound("value", value);
|
||||
compound.put_compound("monster_spawn_light_level", monster_spawn);
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
let _ = pumpkin_nbt::serializer::to_bytes(&compound, &mut bytes);
|
||||
bytes
|
||||
pumpkin_nbt::Nbt::from(compound).write().to_vec()
|
||||
}
|
||||
|
||||
@ -259,7 +259,7 @@ impl pumpkin::plugin::command::HostConsumedArgs for PluginHostState {
|
||||
pumpkin::plugin::command::SoundCategory::Voice
|
||||
}
|
||||
}),
|
||||
OwnedArg::DamageType(d) => Arg::DamageType(format!("{d:?}")),
|
||||
OwnedArg::DamageType(d) => Arg::DamageType(d.message_id.to_string()),
|
||||
OwnedArg::Effect(e) => Arg::Effect(e.minecraft_name.to_string()),
|
||||
OwnedArg::Enchantment(e) => Arg::Enchantment(e.name.to_string()),
|
||||
OwnedArg::Advancement(a) => Arg::Advancement(a.to_string()),
|
||||
|
||||
@ -76,7 +76,7 @@ use pumpkin_data::{
|
||||
use pumpkin_data::{BlockDirection, BlockState, HorizontalFacingExt, translation};
|
||||
use pumpkin_inventory::crafting::recipe_provider::RecipeProvider;
|
||||
use pumpkin_inventory::screen_handler::InventoryPlayer;
|
||||
use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData};
|
||||
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
@ -5168,14 +5168,13 @@ impl World {
|
||||
let entity_id = block_entity.resource_location().to_string();
|
||||
|
||||
if let Some(nbt) = &block_entity_nbt {
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_unnamed(nbt, &mut bytes).unwrap();
|
||||
let bytes = pumpkin_nbt::Nbt::from(nbt.clone()).write_unnamed();
|
||||
self.broadcast_to_chunk(
|
||||
chunk_pos,
|
||||
&CBlockEntityData::new(
|
||||
block_entity.get_position(),
|
||||
VarInt(block_entity.get_id() as i32),
|
||||
bytes.into_boxed_slice(),
|
||||
bytes.as_ref().into(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -5259,14 +5258,13 @@ impl World {
|
||||
let block_entity_nbt = block_entity.chunk_data_nbt();
|
||||
|
||||
if let Some(nbt) = &block_entity_nbt {
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_unnamed(nbt, &mut bytes).unwrap();
|
||||
let bytes = pumpkin_nbt::Nbt::from(nbt.clone()).write_unnamed();
|
||||
self.broadcast_to_chunk(
|
||||
chunk_pos,
|
||||
&CBlockEntityData::new(
|
||||
block_entity.get_position(),
|
||||
VarInt(block_entity.get_id() as i32),
|
||||
bytes.into_boxed_slice(),
|
||||
bytes.as_ref().into(),
|
||||
),
|
||||
);
|
||||
let mut full_nbt = nbt.clone();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user