mirror of
https://github.com/azalea-rs/simdnbt.git
synced 2025-08-02 07:26:04 +00:00
generate specialized code for #[derive(Deserialize)]
This commit is contained in:
parent
6314ae195e
commit
e3bf977ba4
18 changed files with 1348 additions and 633 deletions
|
@ -2,7 +2,7 @@ mod attrs;
|
|||
|
||||
use attrs::{parse_field_attrs, parse_unit_attrs};
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
use syn::{parse_macro_input, DeriveInput, Type};
|
||||
|
||||
#[proc_macro_derive(Deserialize, attributes(simdnbt))]
|
||||
pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
|
@ -10,7 +10,18 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||
|
||||
let ident = input.ident;
|
||||
|
||||
let mut field_deserializers = Vec::<proc_macro2::TokenStream>::new();
|
||||
struct FieldData {
|
||||
rust_type: Type,
|
||||
field_name: syn::Ident,
|
||||
nbt_name: String,
|
||||
debug_name: String,
|
||||
is_flatten: bool,
|
||||
}
|
||||
|
||||
let mut field_datas = Vec::<FieldData>::new();
|
||||
|
||||
// let mut field_deserializers = Vec::<proc_macro2::TokenStream>::new();
|
||||
// let mut fields
|
||||
|
||||
match input.data {
|
||||
syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields {
|
||||
|
@ -20,24 +31,20 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||
|
||||
let mut field_attrs = parse_field_attrs(&field.attrs);
|
||||
|
||||
let field_name = field_attrs
|
||||
let nbt_name = field_attrs
|
||||
.rename
|
||||
.take()
|
||||
.unwrap_or_else(|| struct_field_name.to_string());
|
||||
|
||||
if field_attrs.flatten {
|
||||
field_deserializers.push(quote! {
|
||||
#struct_field_name: simdnbt::Deserialize::from_compound(nbt)?,
|
||||
})
|
||||
} else {
|
||||
let debug_ident = format!("{ident}::{struct_field_name}");
|
||||
let debug_ident = format!("{ident}::{struct_field_name}");
|
||||
|
||||
field_deserializers.push(quote! {
|
||||
#struct_field_name: simdnbt::FromNbtTag::from_optional_nbt_tag(
|
||||
nbt.get(#field_name)
|
||||
)?.ok_or(simdnbt::DeserializeError::MismatchedFieldType(#debug_ident.to_owned()))?
|
||||
});
|
||||
}
|
||||
field_datas.push(FieldData {
|
||||
rust_type: field.ty,
|
||||
field_name: struct_field_name,
|
||||
nbt_name: nbt_name.clone(),
|
||||
debug_name: debug_ident,
|
||||
is_flatten: field_attrs.flatten,
|
||||
});
|
||||
}
|
||||
}
|
||||
syn::Fields::Unnamed(_) => todo!(),
|
||||
|
@ -48,28 +55,183 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||
}
|
||||
|
||||
let generics = input.generics;
|
||||
let data_lifetime =
|
||||
generics
|
||||
.lifetimes()
|
||||
.next()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| syn::LifetimeParam {
|
||||
attrs: Vec::new(),
|
||||
lifetime: syn::Lifetime::new("'_", proc_macro2::Span::call_site()),
|
||||
colon_token: None,
|
||||
bounds: syn::punctuated::Punctuated::new(),
|
||||
});
|
||||
|
||||
let where_clause = &generics.where_clause;
|
||||
|
||||
let struct_attrs = attrs::parse_struct_attrs(&input.attrs);
|
||||
let deny_unknown_fields = struct_attrs.deny_unknown_fields;
|
||||
|
||||
let extra_checks = if struct_attrs.deny_unknown_fields {
|
||||
quote! {
|
||||
if !nbt.is_empty() {
|
||||
return Err(simdnbt::DeserializeError::UnknownField(nbt.keys().next().unwrap().to_string()));
|
||||
// let extra_checks = if struct_attrs.deny_unknown_fields {
|
||||
// quote! {
|
||||
// if !nbt.is_empty() {
|
||||
// return
|
||||
// Err(simdnbt::DeserializeError::UnknownField(nbt.keys().next().unwrap().
|
||||
// to_string())); }
|
||||
// }
|
||||
// } else {
|
||||
// quote! {}
|
||||
// };
|
||||
|
||||
// Option<String>,
|
||||
// Option<Vec<String>>,
|
||||
// Option<Option<i32>>,
|
||||
// simdnbt::owned::NbtCompound,
|
||||
let mut partial_type_inner = quote! {};
|
||||
for field_data in &field_datas {
|
||||
let field_type = &field_data.rust_type;
|
||||
partial_type_inner.extend(if field_data.is_flatten {
|
||||
quote! { <#field_type as simdnbt::Deserialize<'PARTIAL>>::Partial<'PARTIAL>, }
|
||||
} else {
|
||||
quote! { Option<#field_type>, }
|
||||
});
|
||||
}
|
||||
|
||||
// if tag_name == "Name" {
|
||||
// "ItemDisplay::name"
|
||||
// } else if tag_name == "Lore" {
|
||||
// "ItemDisplay::lore"
|
||||
// } else if tag_name == "color" {
|
||||
// "ItemDisplay::color"
|
||||
// } else
|
||||
let mut name_to_field_if_statements = quote! {};
|
||||
for field_data in &field_datas {
|
||||
let nbt_name = &field_data.nbt_name;
|
||||
let debug_name = &field_data.debug_name;
|
||||
name_to_field_if_statements.extend(quote! {
|
||||
if tag_name == #nbt_name {
|
||||
#debug_name
|
||||
} else
|
||||
});
|
||||
}
|
||||
|
||||
// name: partial
|
||||
// .0
|
||||
// .ok_or(simdnbt::DeserializeError::MissingField("ItemDisplay::name"))?,
|
||||
// lore: partial
|
||||
// .1
|
||||
// .ok_or(simdnbt::DeserializeError::MissingField("ItemDisplay::lore"))?,
|
||||
// color: partial.2.ok_or(simdnbt::DeserializeError::MissingField(
|
||||
// "ItemDisplay::color",
|
||||
// ))?,
|
||||
// _extra: partial.3,
|
||||
let mut construct_fields = quote! {};
|
||||
for (i, field_data) in field_datas.iter().enumerate() {
|
||||
let field_name = &field_data.field_name;
|
||||
let debug_name = &field_data.debug_name;
|
||||
let rust_type = &field_data.rust_type;
|
||||
let index = proc_macro2::Literal::usize_unsuffixed(i);
|
||||
construct_fields.extend(if field_data.is_flatten {
|
||||
quote! {
|
||||
#field_name: simdnbt::Deserialize::from_partial(partial.#index)?,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
} else {
|
||||
quote! {
|
||||
#field_name: <#rust_type>::try_flatten_with_option(partial.#index).ok_or(simdnbt::DeserializeError::MissingField(#debug_name))?,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// if <String>::type_matches(tag_type) && tag_name == "Name" {
|
||||
// partial.0 = Some(simdnbt::Deserialize::read_value_direct(data)?);
|
||||
// } else if <Vec<String>>::type_matches(tag_type) && tag_name == "Lore" {
|
||||
// partial.1 = Some(simdnbt::Deserialize::read_value_direct(data)?);
|
||||
// } else if <i32>::type_matches(tag_type) && tag_name == "color" {
|
||||
// partial.2 = Some(simdnbt::Deserialize::read_value_direct(data)?);
|
||||
// } else if <simdnbt::owned::NbtCompound>::update_partial(
|
||||
// &mut partial.3,
|
||||
// tag_name,
|
||||
// tag_type,
|
||||
// data,
|
||||
// )? {
|
||||
// } else
|
||||
let mut update_partial_inner = quote! {};
|
||||
for (i, field_data) in field_datas.iter().enumerate() {
|
||||
let field_type = &field_data.rust_type;
|
||||
let index = proc_macro2::Literal::usize_unsuffixed(i);
|
||||
let nbt_name = &field_data.nbt_name;
|
||||
update_partial_inner.extend(if field_data.is_flatten {
|
||||
quote! {
|
||||
if <#field_type>::update_partial(&mut partial.#index, tag_name, tag_type, data)? {
|
||||
} else
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if <#field_type>::type_matches(tag_type) && tag_name == #nbt_name {
|
||||
partial.#index = Some(simdnbt::Deserialize::read_value_direct_with_explicit_type(data, tag_type)?);
|
||||
} else
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let output = quote! {
|
||||
impl #generics simdnbt::Deserialize for #ident #generics #where_clause {
|
||||
fn from_compound(mut nbt: simdnbt::borrow::NbtCompound) -> Result<Self, simdnbt::DeserializeError> {
|
||||
let value = Self {
|
||||
#(#field_deserializers),*
|
||||
};
|
||||
#extra_checks
|
||||
Ok(value)
|
||||
impl #generics simdnbt::Deserialize<#data_lifetime> for #ident #generics #where_clause {
|
||||
const NBT_TYPE_ID: u8 = simdnbt::common::COMPOUND_ID;
|
||||
type Partial<'PARTIAL> = (#partial_type_inner);
|
||||
|
||||
#[inline]
|
||||
fn read_value_direct(data: &mut simdnbt::reader::Reader<#data_lifetime>) -> Result<Self, simdnbt::DeserializeError> {
|
||||
let mut partial = Self::Partial::default();
|
||||
|
||||
loop {
|
||||
let tag_type = data.read_u8()?;
|
||||
if tag_type == simdnbt::common::END_ID {
|
||||
break;
|
||||
}
|
||||
let tag_name = simdnbt::common::read_string(data)?;
|
||||
|
||||
let matched = Self::update_partial(&mut partial, tag_name, tag_type, data)?;
|
||||
|
||||
if !matched {
|
||||
let field_name = #name_to_field_if_statements {
|
||||
if #deny_unknown_fields {
|
||||
return Err(simdnbt::DeserializeError::UnknownField(
|
||||
tag_name.to_str().into(),
|
||||
));
|
||||
}
|
||||
|
||||
// skip the field
|
||||
simdnbt::validate::internal_read_tag(data, tag_type)?;
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
return Err(simdnbt::DeserializeError::MismatchedFieldType(field_name));
|
||||
}
|
||||
}
|
||||
|
||||
Self::from_partial(partial)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_partial(
|
||||
partial: &mut Self::Partial<'_>,
|
||||
tag_name: &simdnbt::Mutf8Str,
|
||||
tag_type: u8,
|
||||
data: &mut simdnbt::reader::Reader<#data_lifetime>,
|
||||
) -> Result<bool, simdnbt::DeserializeError> {
|
||||
#update_partial_inner {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_partial(partial: Self::Partial<'_>) -> Result<Self, simdnbt::DeserializeError> {
|
||||
Ok(Self {
|
||||
#construct_fields
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -53,7 +53,7 @@ name = "compare"
|
|||
|
||||
[[bench]]
|
||||
harness = false
|
||||
name = "compare_hypixel"
|
||||
name = "compare_derive"
|
||||
|
||||
[[bench]]
|
||||
harness = false
|
||||
|
|
127
simdnbt/benches/compare_derive.rs
Normal file
127
simdnbt/benches/compare_derive.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{Cursor, Read},
|
||||
};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
|
||||
use flate2::read::GzDecoder;
|
||||
use simdnbt::{Deserialize, Mutf8Str};
|
||||
|
||||
fn bench_read_file(filename: &str, c: &mut Criterion) {
|
||||
let mut file = File::open(format!("tests/{filename}")).unwrap();
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents).unwrap();
|
||||
let mut src = &contents[..];
|
||||
|
||||
// decode the original src so most of the time isn't spent on unzipping
|
||||
let mut src_decoder = GzDecoder::new(&mut src);
|
||||
let mut input = Vec::new();
|
||||
if src_decoder.read_to_end(&mut input).is_err() {
|
||||
// oh probably wasn't gzipped then
|
||||
input = contents;
|
||||
}
|
||||
|
||||
let mut input_stream = Cursor::new(&input[..]);
|
||||
|
||||
let mut group = c.benchmark_group(format!("compare_derive/{filename}"));
|
||||
group.throughput(Throughput::Bytes(input.len() as u64));
|
||||
|
||||
group.bench_function("simdnbt_owned_parse", |b| {
|
||||
b.iter(|| {
|
||||
black_box(simdnbt::owned::read(&mut input_stream).unwrap());
|
||||
input_stream.set_position(0);
|
||||
})
|
||||
});
|
||||
group.bench_function("simdnbt_borrow_parse", |b| {
|
||||
b.iter(|| {
|
||||
black_box(simdnbt::borrow::read(&mut input_stream).unwrap());
|
||||
input_stream.set_position(0);
|
||||
})
|
||||
});
|
||||
group.bench_function("simdnbt_validate_parse", |b| {
|
||||
b.iter(|| {
|
||||
simdnbt::validate::read(&mut input_stream).unwrap();
|
||||
input_stream.set_position(0);
|
||||
})
|
||||
});
|
||||
group.bench_function("simdnbt_derive_parse", |b| {
|
||||
b.iter(|| {
|
||||
black_box(Base::read(&mut input_stream).unwrap());
|
||||
input_stream.set_position(0);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
fn bench(c: &mut Criterion) {
|
||||
bench_read_file("hypixel.nbt", c);
|
||||
}
|
||||
|
||||
criterion_group!(compare, bench);
|
||||
criterion_main!(compare);
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Item<'a> {
|
||||
pub id: i16,
|
||||
#[simdnbt(rename = "Damage")]
|
||||
pub damage: Option<i16>,
|
||||
#[simdnbt(rename = "Count")]
|
||||
pub count: i8,
|
||||
|
||||
pub tag: ItemTag<'a>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct ItemTag<'a> {
|
||||
#[simdnbt(rename = "SkullOwner")]
|
||||
pub skull_owner: Option<SkullOwner<'a>>,
|
||||
#[simdnbt(rename = "ExtraAttributes")]
|
||||
pub extra_attributes: Option<ExtraAttributes<'a>>,
|
||||
pub display: Option<ItemDisplay<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct ExtraAttributes<'a> {
|
||||
pub id: Option<&'a Mutf8Str>,
|
||||
pub modifier: Option<&'a Mutf8Str>,
|
||||
|
||||
// pub ench: Option<simdnbt::owned::NbtCompound>,
|
||||
// pub enchantments: Option<HashMap<&'a Mutf8Str, i32>>,
|
||||
pub enchantments: Option<Vec<(&'a Mutf8Str, i32)>>,
|
||||
pub timestamp: Option<&'a Mutf8Str>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct SkullOwner<'a> {
|
||||
#[simdnbt(rename = "Properties")]
|
||||
pub properties: Properties<'a>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Properties<'a> {
|
||||
pub textures: Vec<Texture<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Texture<'a> {
|
||||
#[simdnbt(rename = "Value")]
|
||||
pub value: &'a Mutf8Str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct ItemDisplay<'a> {
|
||||
#[simdnbt(rename = "Name")]
|
||||
pub name: &'a Mutf8Str,
|
||||
#[simdnbt(rename = "Lore")]
|
||||
pub lore: Vec<&'a Mutf8Str>,
|
||||
|
||||
pub color: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Base<'a> {
|
||||
#[simdnbt(rename = "i")]
|
||||
pub items: Vec<Option<Item<'a>>>,
|
||||
}
|
|
@ -1,362 +0,0 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
io::{Cursor, Read},
|
||||
};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
|
||||
use flate2::read::GzDecoder;
|
||||
|
||||
pub fn bench_read_file(filename: &str, c: &mut Criterion) {
|
||||
let mut file = File::open(format!("tests/{filename}")).unwrap();
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents).unwrap();
|
||||
let mut src = &contents[..];
|
||||
|
||||
// decode the original src so most of the time isn't spent on unzipping
|
||||
let mut decoded_src_decoder = GzDecoder::new(&mut src);
|
||||
let mut input = Vec::new();
|
||||
if decoded_src_decoder.read_to_end(&mut input).is_err() {
|
||||
// oh probably wasn't gzipped then
|
||||
input = contents;
|
||||
}
|
||||
let input = input.as_slice();
|
||||
|
||||
let mut group = c.benchmark_group(format!("compare_hypixel/{filename}"));
|
||||
group.throughput(Throughput::Bytes(input.len() as u64));
|
||||
|
||||
{
|
||||
// compare to make sure they decode equally
|
||||
let azalea_nbt =
|
||||
azalea_items_from_nbt(azalea_nbt::Nbt::read(&mut Cursor::new(input)).unwrap()).unwrap();
|
||||
let graphite_nbt =
|
||||
graphite_items_from_nbt(graphite_binary::nbt::decode::read(&mut &input[..]).unwrap())
|
||||
.unwrap();
|
||||
let simdnbt_nbt = simdnbt_items_from_nbt(
|
||||
simdnbt::borrow::read(&mut Cursor::new(input))
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(azalea_nbt, graphite_nbt);
|
||||
assert_eq!(azalea_nbt, simdnbt_nbt);
|
||||
}
|
||||
|
||||
group.bench_function("azalea_parse", |b| {
|
||||
b.iter(|| {
|
||||
let input = black_box(input);
|
||||
let nbt = black_box(azalea_nbt::Nbt::read(&mut Cursor::new(input)).unwrap());
|
||||
black_box(azalea_items_from_nbt(nbt));
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("graphite_parse", |b| {
|
||||
b.iter(|| {
|
||||
let input = black_box(input);
|
||||
let nbt = black_box(graphite_binary::nbt::decode::read(&mut &input[..]).unwrap());
|
||||
// black_box(nbt);
|
||||
black_box(graphite_items_from_nbt(nbt));
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("simdnbt_parse", |b| {
|
||||
b.iter(|| {
|
||||
let input = black_box(input);
|
||||
let nbt = black_box(simdnbt::borrow::read(&mut Cursor::new(input)));
|
||||
let nbt = nbt.unwrap().unwrap();
|
||||
black_box(simdnbt_items_from_nbt(nbt));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct Item {
|
||||
pub id: i16,
|
||||
pub damage: i16,
|
||||
pub count: i8,
|
||||
|
||||
pub head_texture_id: Option<String>,
|
||||
|
||||
pub skyblock_id: Option<String>,
|
||||
pub reforge: Option<String>,
|
||||
|
||||
pub display: ItemDisplay,
|
||||
|
||||
pub enchantments: HashMap<String, i32>,
|
||||
pub timestamp: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct ItemDisplay {
|
||||
pub name: String,
|
||||
pub lore: Vec<String>,
|
||||
|
||||
pub has_glint: bool,
|
||||
|
||||
pub color: Option<i32>,
|
||||
}
|
||||
|
||||
fn simdnbt_items_from_nbt(nbt: simdnbt::borrow::BaseNbt) -> Option<Vec<Option<Item>>> {
|
||||
let mut items = Vec::new();
|
||||
for item_nbt in nbt
|
||||
.list("i")
|
||||
.and_then(|list| list.compounds())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// check if "id" is present, if not, skip
|
||||
if !item_nbt.contains("id") {
|
||||
// this just means the item isn't present
|
||||
items.push(None);
|
||||
continue;
|
||||
}
|
||||
|
||||
let item_tag = item_nbt.compound("tag")?;
|
||||
let item_extra_attributes = item_tag.compound("ExtraAttributes");
|
||||
let item_display = item_tag.compound("display");
|
||||
|
||||
items.push(Some(Item {
|
||||
id: item_nbt.short("id")?,
|
||||
damage: item_nbt.short("Damage")?,
|
||||
count: item_nbt.byte("Count")?,
|
||||
|
||||
head_texture_id: item_tag
|
||||
.compound("SkullOwner")
|
||||
.and_then(|skull_owner| skull_owner.compound("Properties"))
|
||||
.and_then(|properties| properties.list("textures"))
|
||||
.and_then(|textures| textures.compounds())
|
||||
.and_then(|textures| textures.first())
|
||||
.and_then(|texture| texture.string("Value"))
|
||||
// the real program does some base64+json decoding here but that's unnecessary for
|
||||
// the benchmark
|
||||
.map(|value| value.to_string()),
|
||||
skyblock_id: item_extra_attributes
|
||||
.and_then(|e| e.string("id"))
|
||||
.map(|id| id.to_string()),
|
||||
reforge: item_extra_attributes
|
||||
.and_then(|e| e.string("modifier"))
|
||||
.map(|id| id.to_string()),
|
||||
|
||||
display: ItemDisplay {
|
||||
name: item_display
|
||||
.and_then(|d| d.string("Name"))
|
||||
.map(|n| n.to_string())
|
||||
.unwrap_or_default(),
|
||||
lore: item_display
|
||||
.and_then(|d| d.list("Lore"))
|
||||
.and_then(|l| l.strings())
|
||||
.map(|l| l.iter().map(|s| s.to_string()).collect())
|
||||
.unwrap_or_default(),
|
||||
color: item_display.and_then(|d| d.int("color")),
|
||||
has_glint: item_extra_attributes
|
||||
.map(|e| e.contains("ench"))
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
enchantments: item_extra_attributes
|
||||
.and_then(|e| e.compound("enchantments"))
|
||||
.map(|e| {
|
||||
e.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.int().unwrap_or_default()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
timestamp: item_extra_attributes
|
||||
.and_then(|e| e.string("timestamp"))
|
||||
.map(|t| t.to_string()),
|
||||
}));
|
||||
}
|
||||
Some(items)
|
||||
}
|
||||
|
||||
fn azalea_items_from_nbt(nbt: azalea_nbt::Nbt) -> Option<Vec<Option<Item>>> {
|
||||
let mut items = Vec::new();
|
||||
let azalea_nbt::NbtList::Compound(item_compound_list) = nbt
|
||||
.as_compound()
|
||||
.and_then(|c| c.get(""))
|
||||
.and_then(|c| c.as_compound())
|
||||
.and_then(|c| c.get("i"))
|
||||
.and_then(|i| i.as_list())?
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
for item_nbt in item_compound_list {
|
||||
// check if "id" is present, if not, skip
|
||||
let Some(id) = item_nbt.get("id") else {
|
||||
// this just means the item isn't present
|
||||
items.push(None);
|
||||
continue;
|
||||
};
|
||||
|
||||
// let item_tag = item_nbt.compound("tag")?;
|
||||
// let item_extra_attributes = item_tag.compound("ExtraAttributes");
|
||||
// let item_display = item_tag.compound("display");
|
||||
let item_tag = item_nbt.get("tag")?.as_compound()?;
|
||||
let item_extra_attributes = item_tag
|
||||
.get("ExtraAttributes")
|
||||
.and_then(|e| e.as_compound());
|
||||
let item_display = item_tag.get("display").and_then(|d| d.as_compound());
|
||||
|
||||
items.push(Some(Item {
|
||||
id: *id.as_short()?,
|
||||
damage: *item_nbt.get("Damage")?.as_short()?,
|
||||
count: *item_nbt.get("Count")?.as_byte()?,
|
||||
head_texture_id: item_tag
|
||||
.get("SkullOwner")
|
||||
.and_then(|skull_owner| skull_owner.as_compound())
|
||||
.and_then(|skull_owner| {
|
||||
skull_owner
|
||||
.get("Properties")
|
||||
.and_then(|properties| properties.as_compound())
|
||||
})
|
||||
.and_then(|properties| {
|
||||
properties
|
||||
.get("textures")
|
||||
.and_then(|textures| textures.as_list())
|
||||
})
|
||||
.and_then(|textures| {
|
||||
if let azalea_nbt::NbtList::Compound(textures) = textures {
|
||||
textures
|
||||
.first()
|
||||
.and_then(|texture| texture.get("Value"))
|
||||
.and_then(|value| value.as_string().cloned())
|
||||
.map(|string| string.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
skyblock_id: item_extra_attributes
|
||||
.and_then(|e| e.get("id"))
|
||||
.and_then(|id| id.as_string().cloned())
|
||||
.map(|string| string.to_string()),
|
||||
reforge: item_extra_attributes
|
||||
.and_then(|e| e.get("modifier"))
|
||||
.and_then(|id| id.as_string().cloned())
|
||||
.map(|string| string.to_string()),
|
||||
display: ItemDisplay {
|
||||
name: item_display
|
||||
.and_then(|d| d.get("Name"))
|
||||
.and_then(|n| n.as_string().cloned())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
lore: item_display
|
||||
.and_then(|d| d.get("Lore"))
|
||||
.and_then(|l| l.as_list())
|
||||
.and_then(|l| {
|
||||
if let azalea_nbt::NbtList::String(l) = l {
|
||||
Some(l)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|l| l.iter().map(|s| s.to_string()).collect())
|
||||
.unwrap_or_default(),
|
||||
color: item_display
|
||||
.and_then(|d| d.get("color"))
|
||||
.and_then(|c| c.as_int())
|
||||
.copied(),
|
||||
has_glint: item_extra_attributes
|
||||
.map(|e| e.get("ench").is_some())
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
enchantments: item_extra_attributes
|
||||
.and_then(|e| e.get("enchantments"))
|
||||
.and_then(|e| e.as_compound())
|
||||
.map(|e| {
|
||||
e.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.as_int().copied().unwrap_or_default()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
timestamp: item_extra_attributes
|
||||
.and_then(|e| e.get("timestamp"))
|
||||
.and_then(|t| t.as_string().cloned())
|
||||
.map(|string| string.to_string()),
|
||||
}));
|
||||
}
|
||||
Some(items)
|
||||
}
|
||||
|
||||
fn graphite_items_from_nbt(nbt: graphite_binary::nbt::NBT) -> Option<Vec<Option<Item>>> {
|
||||
let mut items = Vec::new();
|
||||
for item_nbt in nbt.find_root("i").and_then(|i| nbt.iter(i))? {
|
||||
// check if "id" is present, if not, skip
|
||||
let Some(id) = nbt.find(item_nbt, "id") else {
|
||||
// this just means the item isn't present
|
||||
items.push(None);
|
||||
continue;
|
||||
};
|
||||
|
||||
let item_tag = nbt.find(item_nbt, "tag")?;
|
||||
let item_extra_attributes = nbt.find(item_tag, "ExtraAttributes");
|
||||
let item_display = nbt.find(item_tag, "display");
|
||||
|
||||
items.push(Some(Item {
|
||||
id: id.as_short()?,
|
||||
damage: nbt.find(item_nbt, "Damage")?.as_short()?,
|
||||
count: nbt.find(item_nbt, "Count")?.as_byte()?,
|
||||
|
||||
head_texture_id: nbt
|
||||
.find(item_tag, "SkullOwner")
|
||||
.and_then(|skull_owner| nbt.find(skull_owner, "Properties"))
|
||||
.and_then(|properties| nbt.find(properties, "textures"))
|
||||
.and_then(|textures| nbt.iter(textures)?.next())
|
||||
.and_then(|texture| nbt.find(texture, "Value"))
|
||||
// the real program does some base64+json decoding here but that's unnecessary for
|
||||
// the benchmark
|
||||
.and_then(|value| value.as_string().cloned()),
|
||||
skyblock_id: item_extra_attributes
|
||||
.and_then(|e| nbt.find(e, "id"))
|
||||
.and_then(|id| id.as_string().cloned()),
|
||||
reforge: item_extra_attributes
|
||||
.and_then(|e| nbt.find(e, "modifier"))
|
||||
.and_then(|id| id.as_string().cloned()),
|
||||
|
||||
display: ItemDisplay {
|
||||
name: item_display
|
||||
.and_then(|d| nbt.find(d, "Name"))
|
||||
.and_then(|n| n.as_string().cloned())
|
||||
.unwrap_or_default(),
|
||||
lore: item_display
|
||||
.and_then(|d| nbt.find(d, "Lore"))
|
||||
.and_then(|l| nbt.iter(l))
|
||||
.map(|l| l.filter_map(|s| s.as_string().cloned()).collect())
|
||||
.unwrap_or_default(),
|
||||
color: item_display
|
||||
.and_then(|d| nbt.find(d, "color"))
|
||||
.and_then(|c| c.as_int()),
|
||||
has_glint: item_extra_attributes
|
||||
.map(|e| nbt.find(e, "ench").is_some())
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
enchantments: item_extra_attributes
|
||||
.and_then(|e| nbt.find(e, "enchantments"))
|
||||
.and_then(|e| nbt.iter(e))
|
||||
.map(|e| {
|
||||
e.map(|n| {
|
||||
(
|
||||
nbt.find(n, "key")
|
||||
.and_then(|k| k.as_string())
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
nbt.find(n, "value")
|
||||
.and_then(|v| v.as_int())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
timestamp: item_extra_attributes
|
||||
.and_then(|e| nbt.find(e, "timestamp"))
|
||||
.and_then(|t| t.as_string().cloned()),
|
||||
}));
|
||||
}
|
||||
Some(items)
|
||||
}
|
||||
|
||||
fn bench(c: &mut Criterion) {
|
||||
bench_read_file("hypixel.nbt", c);
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench);
|
||||
criterion_main!(benches);
|
|
@ -1,51 +1,52 @@
|
|||
use std::{collections::HashMap, hint::black_box, io::Cursor};
|
||||
use std::{borrow::Cow, collections::HashMap, hint::black_box, io::Cursor};
|
||||
|
||||
use simdnbt::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Item {
|
||||
pub struct Item<'a> {
|
||||
pub id: i16,
|
||||
#[simdnbt(rename = "Damage")]
|
||||
pub damage: i16,
|
||||
pub damage: Option<i16>,
|
||||
#[simdnbt(rename = "Count")]
|
||||
pub count: i8,
|
||||
|
||||
pub tag: ItemTag,
|
||||
pub tag: ItemTag<'a>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct ItemTag {
|
||||
pub struct ItemTag<'a> {
|
||||
#[simdnbt(rename = "SkullOwner")]
|
||||
pub skull_owner: Option<SkullOwner>,
|
||||
pub skull_owner: Option<SkullOwner<'a>>,
|
||||
#[simdnbt(rename = "ExtraAttributes")]
|
||||
pub extra_attributes: ExtraAttributes,
|
||||
pub display: ItemDisplay,
|
||||
pub extra_attributes: Option<ExtraAttributes<'a>>,
|
||||
pub display: Option<ItemDisplay>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct ExtraAttributes {
|
||||
pub id: Option<String>,
|
||||
pub modifier: Option<String>,
|
||||
pub struct ExtraAttributes<'a> {
|
||||
pub id: Option<Cow<'a, str>>,
|
||||
pub modifier: Option<Cow<'a, str>>,
|
||||
|
||||
pub ench: Option<simdnbt::owned::NbtCompound>,
|
||||
pub enchantments: Option<HashMap<String, i32>>,
|
||||
pub timestamp: Option<String>,
|
||||
pub timestamp: Option<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct SkullOwner {
|
||||
pub properties: Properties,
|
||||
pub struct SkullOwner<'a> {
|
||||
#[simdnbt(rename = "Properties")]
|
||||
pub properties: Properties<'a>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Properties {
|
||||
pub textures: Vec<Texture>,
|
||||
pub struct Properties<'a> {
|
||||
pub textures: Vec<Texture<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Texture {
|
||||
pub struct Texture<'a> {
|
||||
#[simdnbt(rename = "Value")]
|
||||
pub value: String,
|
||||
pub value: Cow<'a, str>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
|
@ -59,29 +60,22 @@ pub struct ItemDisplay {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Base {
|
||||
pub struct Base<'a> {
|
||||
#[simdnbt(rename = "i")]
|
||||
pub items: Vec<Option<Item>>,
|
||||
pub items: Vec<Option<Item<'a>>>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = black_box(include_bytes!("../tests/hypixel.nbt"));
|
||||
|
||||
for _ in 0..1 {
|
||||
let nbt = simdnbt::borrow::read(&mut Cursor::new(input));
|
||||
let nbt = black_box(nbt.unwrap().unwrap());
|
||||
|
||||
let data = Base::from_nbt(&nbt).unwrap();
|
||||
let (_, data) = Base::read(&mut Cursor::new(input)).unwrap();
|
||||
|
||||
// roundtrip
|
||||
let mut new_nbt_bytes = Vec::new();
|
||||
data.clone().to_nbt().write(&mut new_nbt_bytes);
|
||||
let new_nbt = simdnbt::borrow::read(&mut Cursor::new(&new_nbt_bytes[..]))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let new_data = Base::from_nbt(&new_nbt).unwrap();
|
||||
assert_eq!(data, new_data);
|
||||
|
||||
// println!("data: {:?}", data.items);
|
||||
let (_, new_data) = Base::read(&mut Cursor::new(&new_nbt_bytes)).unwrap();
|
||||
assert_eq!(data, new_data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,7 @@ fn main() {
|
|||
let mut buf = Vec::new();
|
||||
nbt.write(&mut buf);
|
||||
|
||||
let nbt = simdnbt::borrow::read(&mut Cursor::new(&buf[..]))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let rewritten = TrimMaterialElement::from_nbt(&nbt).unwrap();
|
||||
let (_, rewritten) = TrimMaterialElement::read(&mut Cursor::new(&buf)).unwrap();
|
||||
|
||||
assert_eq!(original, rewritten);
|
||||
}
|
||||
|
|
|
@ -566,7 +566,7 @@ impl<'a, 'tape> NbtListList<'a, 'tape> {
|
|||
self.iter.clone().last()
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.approx_len() == 0
|
||||
}
|
||||
}
|
||||
|
@ -622,7 +622,7 @@ impl<'a: 'tape, 'tape> NbtListListIter<'a, 'tape> {
|
|||
self.approx_length
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.approx_len() == 0
|
||||
}
|
||||
}
|
||||
|
@ -698,7 +698,7 @@ impl<'a, 'tape> NbtCompoundList<'a, 'tape> {
|
|||
self.iter.clone().last()
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.approx_len() == 0
|
||||
}
|
||||
}
|
||||
|
@ -753,7 +753,7 @@ impl<'a: 'tape, 'tape> NbtCompoundListIter<'a, 'tape> {
|
|||
self.approx_length
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.approx_len() == 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![doc(hidden)]
|
||||
|
||||
use std::{mem, slice};
|
||||
|
||||
use crate::{
|
||||
|
|
|
@ -18,7 +18,9 @@ pub enum Error {
|
|||
|
||||
// these two structs exist to optimize errors, since Error is an entire 2 bytes
|
||||
// which are often unnecessary
|
||||
pub(crate) struct UnexpectedEofError;
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct UnexpectedEofError;
|
||||
impl From<UnexpectedEofError> for Error {
|
||||
fn from(_: UnexpectedEofError) -> Self {
|
||||
Error::UnexpectedEof
|
||||
|
@ -78,10 +80,30 @@ impl Debug for NonRootError {
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DeserializeError {
|
||||
#[error("Missing field")]
|
||||
MissingField,
|
||||
#[error(transparent)]
|
||||
Nbt(#[from] Error),
|
||||
|
||||
#[error("Missing field {0}")]
|
||||
MissingField(&'static str),
|
||||
#[error("Mismatched type for {0}")]
|
||||
MismatchedFieldType(String),
|
||||
MismatchedFieldType(&'static str),
|
||||
#[error("Unexpected list type ID {0}")]
|
||||
MismatchedListType(u8),
|
||||
#[error("Unknown field {0:?}")]
|
||||
UnknownField(String),
|
||||
UnknownField(Box<str>),
|
||||
#[error("Nbt is empty")]
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl From<UnexpectedEofError> for DeserializeError {
|
||||
#[inline]
|
||||
fn from(_: UnexpectedEofError) -> Self {
|
||||
DeserializeError::Nbt(Error::UnexpectedEof)
|
||||
}
|
||||
}
|
||||
impl From<NonRootError> for DeserializeError {
|
||||
#[inline]
|
||||
fn from(e: NonRootError) -> Self {
|
||||
DeserializeError::Nbt(Error::from(e))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,14 +133,9 @@ impl<T, A: Allocator> FastVec<T, A> {
|
|||
self.ptr.cast().as_ptr()
|
||||
}
|
||||
|
||||
pub fn to_vec(self) -> Vec<T> {
|
||||
let vec = unsafe {
|
||||
Vec::from_raw_parts(
|
||||
self.ptr.cast().as_ptr() as *mut T,
|
||||
self.len(),
|
||||
self.capacity(),
|
||||
)
|
||||
};
|
||||
pub fn into_vec(self) -> Vec<T> {
|
||||
let vec =
|
||||
unsafe { Vec::from_raw_parts(self.ptr.cast().as_ptr(), self.len(), self.capacity()) };
|
||||
// the allocation was moved so don't drop it
|
||||
mem::forget(self);
|
||||
vec
|
||||
|
@ -193,7 +188,7 @@ impl<T, A: Allocator> FastVec<T, A> {
|
|||
cur: ptr.add(len),
|
||||
end,
|
||||
ptr: ptr.cast(),
|
||||
alloc: alloc,
|
||||
alloc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +251,7 @@ impl<T> From<Vec<T>> for FastVec<T> {
|
|||
}
|
||||
impl<T> From<FastVec<T>> for Vec<T> {
|
||||
fn from(fastvec: FastVec<T>) -> Self {
|
||||
fastvec.to_vec()
|
||||
fastvec.into_vec()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,7 +274,7 @@ impl<T> Drop for FastVecFromVec<'_, T> {
|
|||
fn drop(&mut self) {
|
||||
// we intentionally don't drop the fastvec since the allocation is moved into
|
||||
// the vec
|
||||
*self.original = unsafe { ManuallyDrop::take(&mut self.fastvec).to_vec() };
|
||||
*self.original = unsafe { ManuallyDrop::take(&mut self.fastvec).into_vec() };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,20 +9,21 @@
|
|||
compile_error!("simdnbt only supports 64-bit platforms");
|
||||
|
||||
pub mod borrow;
|
||||
mod common;
|
||||
pub mod common;
|
||||
mod error;
|
||||
mod fastvec;
|
||||
mod mutf8;
|
||||
pub mod owned;
|
||||
pub mod raw_list;
|
||||
mod reader;
|
||||
pub mod reader;
|
||||
pub mod swap_endianness;
|
||||
mod traits;
|
||||
pub mod validate;
|
||||
|
||||
pub use error::{DeserializeError, Error};
|
||||
pub use mutf8::Mutf8Str;
|
||||
pub use simdnbt_derive::*;
|
||||
pub use traits::{Deserialize, FromNbtTag, Serialize, ToNbtTag};
|
||||
pub use traits::{Deserialize, Serialize, ToNbtTag};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -11,7 +11,7 @@ use simd_cesu8::mutf8;
|
|||
|
||||
/// A MUTF-8 string slice. This is how strings are represented internally in
|
||||
/// NBT.
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
pub struct Mutf8Str {
|
||||
pub(crate) slice: [u8],
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ impl Mutf8Str {
|
|||
/// Convert a slice of bytes into a Mutf8Str. This is safe because it's only
|
||||
/// checked to be valid MUTF-8 while being converted to UTF-8.
|
||||
#[inline]
|
||||
pub fn from_slice(slice: &[u8]) -> &Mutf8Str {
|
||||
pub const fn from_slice(slice: &[u8]) -> &Mutf8Str {
|
||||
// SAFETY: &[u8] and &Mutf8Str are the same layout.
|
||||
unsafe { mem::transmute::<&[u8], &Mutf8Str>(slice) }
|
||||
}
|
||||
|
@ -257,6 +257,16 @@ impl From<&Mutf8Str> for String {
|
|||
s.to_str().into_owned()
|
||||
}
|
||||
}
|
||||
impl PartialEq<str> for Mutf8Str {
|
||||
/// A fast but somewhat incorrect way to compare MUTF-8 strings to UTF-8.
|
||||
///
|
||||
/// This just compares the underlying bytes, so if the MUTF-8 encodes
|
||||
/// differently then it'll return the wrong answer.
|
||||
#[inline]
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_bytes() == other.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -272,7 +272,7 @@ impl NbtTag {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_with_type(
|
||||
pub(crate) fn read_with_type(
|
||||
data: &mut Reader<'_>,
|
||||
tag_type: u8,
|
||||
depth: usize,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![doc(hidden)]
|
||||
|
||||
use std::{
|
||||
io::Cursor,
|
||||
marker::PhantomData,
|
||||
|
@ -7,6 +9,8 @@ use std::{
|
|||
|
||||
use crate::error::UnexpectedEofError;
|
||||
|
||||
/// An internal type for efficiently reading bytes, and is sometimes faster than
|
||||
/// `Cursor<&[u8]>`.
|
||||
pub struct Reader<'a> {
|
||||
pub cur: *const u8,
|
||||
/// pointer to after the last byte (so remaining=end-cur)
|
||||
|
@ -71,6 +75,15 @@ impl<'a> Reader<'a> {
|
|||
self.read_u8().map(|x| x as i8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn peek_u8(&self) -> Result<u8, UnexpectedEofError> {
|
||||
let addr = self.cur;
|
||||
if addr >= self.end {
|
||||
return Err(UnexpectedEofError);
|
||||
}
|
||||
Ok(unsafe { addr.read_unaligned() })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_u16(&mut self) -> Result<u16, UnexpectedEofError> {
|
||||
self.read_type::<u16>().map(u16::to_be)
|
||||
|
|
|
@ -1,340 +1,499 @@
|
|||
use std::{collections::HashMap, fmt::Display, hash::Hash, str::FromStr};
|
||||
use std::{any, borrow::Cow, collections::HashMap, hash::Hash, io::Cursor};
|
||||
|
||||
use crate::DeserializeError;
|
||||
use byteorder::ReadBytesExt;
|
||||
|
||||
pub trait Deserialize: Sized {
|
||||
fn from_nbt(nbt: &crate::borrow::BaseNbt) -> Result<Self, DeserializeError> {
|
||||
Self::from_compound(nbt.as_compound())
|
||||
use crate::{
|
||||
common::{self, read_string},
|
||||
mutf8::Mutf8String,
|
||||
owned,
|
||||
reader::{Reader, ReaderFromCursor},
|
||||
DeserializeError, Error, Mutf8Str,
|
||||
};
|
||||
|
||||
pub trait Deserialize<'a>: Sized {
|
||||
/// The NBT type that we expect the data to be. If this is set to END_ID,
|
||||
/// then any type is allowed.
|
||||
const NBT_TYPE_ID: u8;
|
||||
type Partial<'b>;
|
||||
|
||||
fn read(data: &mut Cursor<&'a [u8]>) -> Result<(&'a Mutf8Str, Self), DeserializeError> {
|
||||
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
|
||||
if root_type != common::COMPOUND_ID {
|
||||
return Err(Error::InvalidRootType(root_type).into());
|
||||
}
|
||||
|
||||
let mut data = ReaderFromCursor::new(data);
|
||||
let name = read_string(&mut data)?;
|
||||
|
||||
Ok((
|
||||
name,
|
||||
Self::read_value_direct_with_explicit_type(&mut data, root_type)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError>;
|
||||
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError>;
|
||||
|
||||
#[inline]
|
||||
fn read_value_direct_with_explicit_type(
|
||||
data: &mut Reader<'a>,
|
||||
type_id: u8,
|
||||
) -> Result<Self, DeserializeError> {
|
||||
debug_assert_eq!(type_id, Self::NBT_TYPE_ID);
|
||||
|
||||
Self::read_value_direct(data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn type_matches(type_id: u8) -> bool {
|
||||
Self::NBT_TYPE_ID == common::END_ID || type_id == Self::NBT_TYPE_ID
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_partial(
|
||||
_partial: &mut Self::Partial<'_>,
|
||||
_name: &Mutf8Str,
|
||||
_tag_type: u8,
|
||||
_data: &mut Reader<'a>,
|
||||
) -> Result<bool, DeserializeError> {
|
||||
panic!("{} cannot be flattened", any::type_name::<Self>())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_partial(_partial: Self::Partial<'_>) -> Result<Self, DeserializeError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_flatten_with_option(other: Option<Self>) -> Option<Self> {
|
||||
// can't unflatten by default
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Serialize: Sized {
|
||||
fn to_nbt(self) -> crate::owned::BaseNbt {
|
||||
crate::owned::BaseNbt::new("", self.to_compound())
|
||||
fn to_nbt(self) -> owned::BaseNbt {
|
||||
owned::BaseNbt::new("", self.to_compound())
|
||||
}
|
||||
|
||||
fn to_compound(self) -> crate::owned::NbtCompound;
|
||||
}
|
||||
|
||||
pub trait FromNbtTag: Sized {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self>;
|
||||
fn from_optional_nbt_tag(
|
||||
tag: Option<crate::borrow::NbtTag>,
|
||||
) -> Result<Option<Self>, DeserializeError> {
|
||||
match tag {
|
||||
Some(tag) => Ok(Self::from_nbt_tag(tag)),
|
||||
None => Err(DeserializeError::MissingField),
|
||||
}
|
||||
}
|
||||
fn to_compound(self) -> owned::NbtCompound;
|
||||
}
|
||||
|
||||
pub trait ToNbtTag: Sized {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag;
|
||||
fn to_optional_nbt_tag(self) -> Option<crate::owned::NbtTag> {
|
||||
fn to_nbt_tag(self) -> owned::NbtTag;
|
||||
fn to_optional_nbt_tag(self) -> Option<owned::NbtTag> {
|
||||
Some(self.to_nbt_tag())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Display + FromStr + Eq + Hash, V: FromNbtTag> Deserialize for HashMap<K, V> {
|
||||
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
|
||||
let mut hashmap = HashMap::new();
|
||||
impl<'a, K: From<&'a Mutf8Str> + Eq + Hash, V: Deserialize<'a>> Deserialize<'a> for HashMap<K, V> {
|
||||
const NBT_TYPE_ID: u8 = common::COMPOUND_ID;
|
||||
type Partial<'b> = HashMap<K, V>;
|
||||
|
||||
for (k, v) in compound.iter() {
|
||||
let k_str = k.to_str();
|
||||
let k_parsed = k_str
|
||||
.parse()
|
||||
.map_err(|_| DeserializeError::MismatchedFieldType("key".to_owned()))?;
|
||||
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
let v_parsed = V::from_nbt_tag(v).ok_or_else(|| {
|
||||
DeserializeError::MismatchedFieldType(format!("value for key {k_str}"))
|
||||
})?;
|
||||
loop {
|
||||
let tag_type = data.read_u8()?;
|
||||
if tag_type == common::END_ID {
|
||||
break;
|
||||
}
|
||||
let name = read_string(data)?;
|
||||
|
||||
hashmap.insert(k_parsed, v_parsed);
|
||||
if !V::type_matches(tag_type) {
|
||||
return Err(DeserializeError::MismatchedFieldType("HashMap"));
|
||||
}
|
||||
let value = V::read_value_direct_with_explicit_type(data, tag_type)?;
|
||||
|
||||
map.insert(name.into(), value);
|
||||
}
|
||||
|
||||
Ok(hashmap)
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
impl<K: Display + FromStr + Eq + Hash, V: ToNbtTag> Serialize for HashMap<K, V> {
|
||||
fn to_compound(self) -> crate::owned::NbtCompound {
|
||||
let mut compound = crate::owned::NbtCompound::new();
|
||||
impl<'a, K: From<&'a Mutf8Str> + Eq + Hash, V: Deserialize<'a>> Deserialize<'a> for Vec<(K, V)> {
|
||||
const NBT_TYPE_ID: u8 = common::COMPOUND_ID;
|
||||
type Partial<'b> = HashMap<K, V>;
|
||||
|
||||
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
|
||||
let mut map = Vec::new();
|
||||
|
||||
loop {
|
||||
let tag_type = data.read_u8()?;
|
||||
if tag_type == common::END_ID {
|
||||
break;
|
||||
}
|
||||
let name = read_string(data)?;
|
||||
|
||||
if !V::type_matches(tag_type) {
|
||||
return Err(DeserializeError::MismatchedFieldType("HashMap"));
|
||||
}
|
||||
let value = V::read_value_direct_with_explicit_type(data, tag_type)?;
|
||||
|
||||
map.push((name.into(), value));
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
impl<K: Into<Mutf8String> + Eq + Hash, V: ToNbtTag> Serialize for HashMap<K, V> {
|
||||
fn to_compound(self) -> owned::NbtCompound {
|
||||
let mut compound = owned::NbtCompound::new();
|
||||
|
||||
for (k, v) in self {
|
||||
compound.insert(k.to_string(), v.to_nbt_tag());
|
||||
compound.insert(k, v.to_nbt_tag());
|
||||
}
|
||||
|
||||
compound
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for crate::owned::NbtCompound {
|
||||
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
|
||||
Ok(compound.to_owned())
|
||||
}
|
||||
}
|
||||
impl Serialize for crate::owned::NbtCompound {
|
||||
fn to_compound(self) -> crate::owned::NbtCompound {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl Deserialize<'_> for owned::NbtCompound {
|
||||
const NBT_TYPE_ID: u8 = common::COMPOUND_ID;
|
||||
|
||||
impl<T: Deserialize> FromNbtTag for T {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.compound().and_then(|c| Self::from_compound(c).ok())
|
||||
type Partial<'b> = owned::NbtCompound;
|
||||
|
||||
fn read_value_direct(data: &mut Reader<'_>) -> Result<Self, DeserializeError> {
|
||||
owned::NbtCompound::read(data).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn update_partial(
|
||||
partial: &mut Self::Partial<'_>,
|
||||
name: &Mutf8Str,
|
||||
tag_type: u8,
|
||||
data: &mut Reader<'_>,
|
||||
) -> Result<bool, DeserializeError> {
|
||||
let tag = owned::NbtTag::read_with_type(data, tag_type, 0).map_err(Error::from)?;
|
||||
partial.insert(name.to_owned(), tag);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn from_partial(partial: Self::Partial<'_>) -> Result<Self, DeserializeError> {
|
||||
Ok(partial)
|
||||
}
|
||||
}
|
||||
impl Serialize for owned::NbtCompound {
|
||||
fn to_compound(self) -> owned::NbtCompound {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> ToNbtTag for T {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Compound(self.to_compound())
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Compound(self.to_compound())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for crate::owned::NbtTag {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
Some(tag.to_owned())
|
||||
impl Deserialize<'_> for owned::NbtTag {
|
||||
const NBT_TYPE_ID: u8 = common::END_ID;
|
||||
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(_data: &mut Reader<'_>) -> Result<Self, DeserializeError> {
|
||||
unimplemented!("can't deserialize an NbtTag without the type being known")
|
||||
}
|
||||
|
||||
fn read_value_direct_with_explicit_type(
|
||||
data: &mut Reader<'_>,
|
||||
type_id: u8,
|
||||
) -> Result<Self, DeserializeError> {
|
||||
let tag = owned::NbtTag::read_with_type(data, type_id, 0).map_err(Error::from)?;
|
||||
Ok(tag)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for crate::owned::NbtTag {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
impl ToNbtTag for owned::NbtTag {
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// standard nbt types
|
||||
impl FromNbtTag for i8 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.byte()
|
||||
// // standard nbt types
|
||||
impl Deserialize<'_> for i8 {
|
||||
const NBT_TYPE_ID: u8 = common::BYTE_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_i8().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for i8 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Byte(self)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Byte(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for i16 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.short()
|
||||
impl Deserialize<'_> for i16 {
|
||||
const NBT_TYPE_ID: u8 = common::SHORT_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_i16().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for i16 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Short(self)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Short(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for i32 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.int()
|
||||
impl Deserialize<'_> for i32 {
|
||||
const NBT_TYPE_ID: u8 = common::INT_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_i32().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for i32 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Int(self)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Int(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for i64 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.long()
|
||||
impl Deserialize<'_> for i64 {
|
||||
const NBT_TYPE_ID: u8 = common::LONG_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_i64().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for i64 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Long(self)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Long(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for f32 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.float()
|
||||
impl Deserialize<'_> for f32 {
|
||||
const NBT_TYPE_ID: u8 = common::FLOAT_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_f32().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for f32 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Float(self)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Float(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for f64 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.double()
|
||||
impl Deserialize<'_> for f64 {
|
||||
const NBT_TYPE_ID: u8 = common::DOUBLE_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_f64().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for f64 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Double(self)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Double(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for String {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.string().map(|s| s.to_string())
|
||||
impl Deserialize<'_> for String {
|
||||
const NBT_TYPE_ID: u8 = common::STRING_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
let str = common::read_string(data)?;
|
||||
Ok(str.to_string())
|
||||
}
|
||||
}
|
||||
impl<'a> Deserialize<'a> for &'a Mutf8Str {
|
||||
const NBT_TYPE_ID: u8 = common::STRING_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
|
||||
common::read_string(data).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl<'a> Deserialize<'a> for Cow<'a, str> {
|
||||
const NBT_TYPE_ID: u8 = common::STRING_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
|
||||
common::read_string(data)
|
||||
.map_err(Into::into)
|
||||
.map(|s| s.to_str())
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for Mutf8String {
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::String(self)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for &Mutf8Str {
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::String(self.to_owned())
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for Cow<'_, str> {
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::String(self.into_owned().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNbtTag for String {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::String(self.into())
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::String(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNbtTag for &str {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::String(self.into())
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::String(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
// unsigned integers
|
||||
impl FromNbtTag for u8 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.byte().map(|b| b as u8)
|
||||
impl Deserialize<'_> for u8 {
|
||||
const NBT_TYPE_ID: u8 = common::BYTE_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_u8().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for u8 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Byte(self as i8)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Byte(self as i8)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for u16 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.short().map(|s| s as u16)
|
||||
impl Deserialize<'_> for u16 {
|
||||
const NBT_TYPE_ID: u8 = common::SHORT_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_u16().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for u16 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Short(self as i16)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Short(self as i16)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for u32 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.int().map(|i| i as u32)
|
||||
impl Deserialize<'_> for u32 {
|
||||
const NBT_TYPE_ID: u8 = common::INT_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_u32().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for u32 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Int(self as i32)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Int(self as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for u64 {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.long().map(|l| l as u64)
|
||||
impl Deserialize<'_> for u64 {
|
||||
const NBT_TYPE_ID: u8 = common::LONG_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
|
||||
data.read_u64().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for u64 {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Long(self as i64)
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Long(self as i64)
|
||||
}
|
||||
}
|
||||
|
||||
// lists
|
||||
impl FromNbtTag for Vec<String> {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.list().and_then(|l| {
|
||||
l.strings()
|
||||
.map(|s| s.iter().map(|s| s.to_string()).collect())
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for Vec<String> {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::List(crate::owned::NbtList::String(
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::List(owned::NbtList::String(
|
||||
self.into_iter().map(|s| s.into()).collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// slightly less standard types
|
||||
impl<T: FromNbtTag> FromNbtTag for Option<T> {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
Some(T::from_nbt_tag(tag))
|
||||
}
|
||||
fn from_optional_nbt_tag(
|
||||
tag: Option<crate::borrow::NbtTag>,
|
||||
) -> Result<Option<Self>, DeserializeError> {
|
||||
match tag {
|
||||
Some(tag) => Ok(Some(T::from_nbt_tag(tag))),
|
||||
None => Ok(Some(None)),
|
||||
impl<'a, T: Deserialize<'a>> Deserialize<'a> for Option<T> {
|
||||
const NBT_TYPE_ID: u8 = T::NBT_TYPE_ID;
|
||||
type Partial<'b> = Option<T>;
|
||||
|
||||
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
|
||||
// empty compounds also count as None
|
||||
if Self::NBT_TYPE_ID == common::COMPOUND_ID {
|
||||
let next_tag_type = data.peek_u8()?;
|
||||
if next_tag_type == common::END_ID {
|
||||
data.skip(1)?;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(T::read_value_direct(data)?))
|
||||
}
|
||||
|
||||
fn try_flatten_with_option(other: Option<Self>) -> Option<Self> {
|
||||
Some(other.flatten())
|
||||
}
|
||||
}
|
||||
impl<T: ToNbtTag> ToNbtTag for Option<T> {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
panic!("Called to_nbt_tag on Option<T>. Use to_optional_nbt_tag instead.")
|
||||
}
|
||||
fn to_optional_nbt_tag(self) -> Option<crate::owned::NbtTag> {
|
||||
fn to_optional_nbt_tag(self) -> Option<owned::NbtTag> {
|
||||
self.map(|t| t.to_nbt_tag())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Deserialize> FromNbtTag for Vec<Option<T>> {
|
||||
/// A list of compounds where `None` is an empty compound
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
let list = tag.list()?;
|
||||
let list = list.compounds()?;
|
||||
let mut vec = Vec::with_capacity(list.approx_len() as usize);
|
||||
for tag in list {
|
||||
if tag.is_empty() {
|
||||
vec.push(None);
|
||||
} else {
|
||||
vec.push(Some(T::from_compound(tag).ok()?));
|
||||
}
|
||||
}
|
||||
|
||||
Some(vec)
|
||||
}
|
||||
}
|
||||
impl<T: Serialize> ToNbtTag for Vec<Option<T>> {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::List(crate::owned::NbtList::Compound(
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::List(owned::NbtList::Compound(
|
||||
self.into_iter()
|
||||
.map(|t| match t {
|
||||
Some(t) => t.to_compound(),
|
||||
None => crate::owned::NbtCompound::new(),
|
||||
None => owned::NbtCompound::new(),
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Deserialize> FromNbtTag for Vec<T> {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
let list = tag.list()?;
|
||||
let list = list.compounds()?;
|
||||
let mut vec = Vec::with_capacity(list.approx_len() as usize);
|
||||
for tag in list {
|
||||
vec.push(T::from_compound(tag).ok()?);
|
||||
impl<'a, T: Deserialize<'a>> Deserialize<'a> for Vec<T> {
|
||||
const NBT_TYPE_ID: u8 = common::LIST_ID;
|
||||
type Partial<'b> = ();
|
||||
|
||||
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
|
||||
let tag_type = data.read_u8()?;
|
||||
let list_length = data.read_i32()?;
|
||||
if tag_type == common::END_ID || list_length <= 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
if !T::type_matches(tag_type) {
|
||||
return Err(DeserializeError::MismatchedListType(tag_type));
|
||||
}
|
||||
|
||||
Some(vec)
|
||||
let mut vec = Vec::with_capacity(list_length.min(128) as usize);
|
||||
for _ in 0..list_length {
|
||||
vec.push(T::read_value_direct_with_explicit_type(data, tag_type)?);
|
||||
}
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
impl<T: Serialize> ToNbtTag for Vec<T> {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::List(crate::owned::NbtList::Compound(
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::List(owned::NbtList::Compound(
|
||||
self.into_iter().map(|t| t.to_compound()).collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNbtTag for bool {
|
||||
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
|
||||
tag.byte().map(|b| b != 0)
|
||||
}
|
||||
}
|
||||
impl ToNbtTag for bool {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::Byte(if self { 1 } else { 0 })
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::Byte(if self { 1 } else { 0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNbtTag for crate::owned::NbtList {
|
||||
fn to_nbt_tag(self) -> crate::owned::NbtTag {
|
||||
crate::owned::NbtTag::List(self)
|
||||
impl ToNbtTag for owned::NbtList {
|
||||
fn to_nbt_tag(self) -> owned::NbtTag {
|
||||
owned::NbtTag::List(self)
|
||||
}
|
||||
}
|
||||
|
|
192
simdnbt/src/validate/compound.rs
Normal file
192
simdnbt/src/validate/compound.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
use std::mem::MaybeUninit;
|
||||
|
||||
use super::list::NbtList;
|
||||
use crate::{
|
||||
common::{
|
||||
read_int_array, read_long_array, read_string, read_with_u32_length, BYTE_ARRAY_ID, BYTE_ID,
|
||||
COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID,
|
||||
LONG_ID, MAX_DEPTH, SHORT_ID, STRING_ID,
|
||||
},
|
||||
error::NonRootError,
|
||||
reader::Reader,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NbtCompound;
|
||||
|
||||
impl NbtCompound {
|
||||
pub(crate) fn read(
|
||||
// compounds have no header so nothing to read
|
||||
_data: &mut Reader,
|
||||
stack: &mut ParsingStack,
|
||||
) -> Result<(), NonRootError> {
|
||||
stack.push(ParsingStackElementKind::Compound)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum ParsingStackElementKind {
|
||||
Compound,
|
||||
ListOfCompounds,
|
||||
ListOfLists,
|
||||
}
|
||||
|
||||
pub struct ParsingStack {
|
||||
stack: [MaybeUninit<ParsingStackElementKind>; MAX_DEPTH],
|
||||
remaining_elements_in_lists: [u32; MAX_DEPTH],
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
impl ParsingStack {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stack: unsafe { MaybeUninit::uninit().assume_init() },
|
||||
remaining_elements_in_lists: [0; MAX_DEPTH],
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push(&mut self, state: ParsingStackElementKind) -> Result<(), NonRootError> {
|
||||
unsafe { self.stack.get_unchecked_mut(self.depth).write(state) };
|
||||
self.depth += 1;
|
||||
|
||||
if self.depth >= MAX_DEPTH {
|
||||
Err(NonRootError::max_depth_exceeded())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_list_length(&mut self, length: u32) {
|
||||
unsafe {
|
||||
*self
|
||||
.remaining_elements_in_lists
|
||||
.get_unchecked_mut(self.depth - 1) = length;
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn decrement_list_length(&mut self) {
|
||||
unsafe {
|
||||
*self
|
||||
.remaining_elements_in_lists
|
||||
.get_unchecked_mut(self.depth - 1) -= 1;
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remaining_elements_in_list(&self) -> u32 {
|
||||
unsafe {
|
||||
*self
|
||||
.remaining_elements_in_lists
|
||||
.get_unchecked(self.depth - 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pop(&mut self) -> ParsingStackElementKind {
|
||||
self.depth -= 1;
|
||||
unsafe { self.stack.get_unchecked(self.depth).assume_init() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn peek(&self) -> ParsingStackElementKind {
|
||||
unsafe { self.stack.get_unchecked(self.depth - 1).assume_init() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.depth == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_tag<'a>(
|
||||
data: &mut Reader<'a>,
|
||||
stack: &mut ParsingStack,
|
||||
tag_type: u8,
|
||||
) -> Result<(), NonRootError> {
|
||||
match tag_type {
|
||||
BYTE_ID => {
|
||||
let _ = data.read_i8()?;
|
||||
}
|
||||
SHORT_ID => {
|
||||
let _ = data.read_i16()?;
|
||||
}
|
||||
INT_ID => {
|
||||
let _ = data.read_i32()?;
|
||||
}
|
||||
LONG_ID => {
|
||||
data.skip(8)?;
|
||||
}
|
||||
FLOAT_ID => {
|
||||
let _ = data.read_f32()?;
|
||||
}
|
||||
DOUBLE_ID => {
|
||||
data.skip(8)?;
|
||||
}
|
||||
BYTE_ARRAY_ID => {
|
||||
read_with_u32_length(data, 1)?;
|
||||
}
|
||||
STRING_ID => {
|
||||
read_string(data)?;
|
||||
}
|
||||
COMPOUND_ID => return NbtCompound::read(data, stack),
|
||||
LIST_ID => return NbtList::read(data, stack),
|
||||
INT_ARRAY_ID => {
|
||||
read_int_array(data)?;
|
||||
}
|
||||
LONG_ARRAY_ID => {
|
||||
read_long_array(data)?;
|
||||
}
|
||||
_ => return Err(NonRootError::unknown_tag_id(tag_type)),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn read_tag_in_compound<'a>(
|
||||
data: &mut Reader<'a>,
|
||||
stack: &mut ParsingStack,
|
||||
) -> Result<(), NonRootError> {
|
||||
let tag_type = data.read_u8()?;
|
||||
if tag_type == END_ID {
|
||||
handle_compound_end(stack);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tag_name_ptr = data.cur;
|
||||
debug_assert_eq!(tag_name_ptr as u64 >> 56, 0);
|
||||
|
||||
// read the tag name in a more efficient way than just calling read_string
|
||||
|
||||
let mut cur_addr = tag_name_ptr as usize;
|
||||
let end_addr = data.end_addr();
|
||||
cur_addr += 2;
|
||||
if cur_addr > end_addr {
|
||||
return Err(NonRootError::unexpected_eof());
|
||||
}
|
||||
// this actually results in an extra instruction since it sets the data.cur
|
||||
// unnecessarily, but for some reason it's faster anyways
|
||||
let length = unsafe { data.read_type_unchecked::<u16>() }.to_be();
|
||||
let length_in_bytes: usize = length as usize;
|
||||
cur_addr += length_in_bytes;
|
||||
if cur_addr > end_addr {
|
||||
return Err(NonRootError::unexpected_eof());
|
||||
}
|
||||
data.cur = cur_addr as *const u8;
|
||||
|
||||
// finished reading the tag name
|
||||
|
||||
read_tag(data, stack, tag_type)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn handle_compound_end(stack: &mut ParsingStack) {
|
||||
stack.pop();
|
||||
}
|
115
simdnbt/src/validate/list.rs
Normal file
115
simdnbt/src/validate/list.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
use super::{
|
||||
compound::{ParsingStack, ParsingStackElementKind},
|
||||
NbtCompound,
|
||||
};
|
||||
use crate::{
|
||||
common::{
|
||||
read_i8_array, read_int_array, read_long_array, read_string, read_u8_array,
|
||||
read_with_u32_length, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID,
|
||||
INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID, SHORT_ID, STRING_ID,
|
||||
},
|
||||
error::NonRootError,
|
||||
reader::Reader,
|
||||
};
|
||||
|
||||
/// A list of NBT tags of a single type.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NbtList;
|
||||
impl NbtList {
|
||||
pub(crate) fn read(data: &mut Reader, stack: &mut ParsingStack) -> Result<(), NonRootError> {
|
||||
let tag_type = data.read_u8()?;
|
||||
|
||||
match tag_type {
|
||||
END_ID => {
|
||||
// the length is unused for this type of lists
|
||||
data.skip(4)?;
|
||||
}
|
||||
BYTE_ID => {
|
||||
let _ = read_i8_array(data)?;
|
||||
}
|
||||
SHORT_ID => {
|
||||
read_with_u32_length(data, 2)?;
|
||||
}
|
||||
INT_ID => {
|
||||
read_with_u32_length(data, 4)?;
|
||||
}
|
||||
LONG_ID => {
|
||||
read_with_u32_length(data, 8)?;
|
||||
}
|
||||
FLOAT_ID => {
|
||||
read_with_u32_length(data, 4)?;
|
||||
}
|
||||
DOUBLE_ID => {
|
||||
read_with_u32_length(data, 8)?;
|
||||
}
|
||||
BYTE_ARRAY_ID => {
|
||||
let length = data.read_u32()?;
|
||||
for _ in 0..length {
|
||||
let _ = read_u8_array(data)?;
|
||||
}
|
||||
}
|
||||
STRING_ID => {
|
||||
let length = data.read_u32()?;
|
||||
for _ in 0..length {
|
||||
let _ = read_string(data)?;
|
||||
}
|
||||
}
|
||||
LIST_ID => {
|
||||
let length = data.read_u32()?;
|
||||
// length estimate + tape index offset to the end of the list
|
||||
|
||||
stack.push(ParsingStackElementKind::ListOfLists)?;
|
||||
stack.set_list_length(length);
|
||||
}
|
||||
COMPOUND_ID => {
|
||||
let length = data.read_u32()?;
|
||||
|
||||
stack.push(ParsingStackElementKind::ListOfCompounds)?;
|
||||
stack.set_list_length(length);
|
||||
}
|
||||
INT_ARRAY_ID => {
|
||||
let length = data.read_u32()?;
|
||||
for _ in 0..length {
|
||||
let _ = read_int_array(data)?;
|
||||
}
|
||||
}
|
||||
LONG_ARRAY_ID => {
|
||||
let length = data.read_u32()?;
|
||||
for _ in 0..length {
|
||||
let _ = read_long_array(data)?;
|
||||
}
|
||||
}
|
||||
_ => return Err(NonRootError::unknown_tag_id(tag_type)),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_list_in_list<'a>(
|
||||
data: &mut Reader<'a>,
|
||||
stack: &mut ParsingStack,
|
||||
) -> Result<(), NonRootError> {
|
||||
let remaining = stack.remaining_elements_in_list();
|
||||
if remaining == 0 {
|
||||
stack.pop();
|
||||
return Ok(());
|
||||
}
|
||||
stack.decrement_list_length();
|
||||
NbtList::read(data, stack)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn read_compound_in_list<'a>(
|
||||
data: &mut Reader<'a>,
|
||||
stack: &mut ParsingStack,
|
||||
) -> Result<(), NonRootError> {
|
||||
let remaining = stack.remaining_elements_in_list();
|
||||
if remaining == 0 {
|
||||
stack.pop();
|
||||
return Ok(());
|
||||
}
|
||||
stack.decrement_list_length();
|
||||
NbtCompound::read(data, stack)
|
||||
}
|
288
simdnbt/src/validate/mod.rs
Normal file
288
simdnbt/src/validate/mod.rs
Normal file
|
@ -0,0 +1,288 @@
|
|||
//! Validate NBT without actually reading it.
|
||||
//!
|
||||
//! This is used by `simdnbt-derive` for skipping unused fields.
|
||||
|
||||
mod compound;
|
||||
mod list;
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
use byteorder::ReadBytesExt;
|
||||
use compound::ParsingStackElementKind;
|
||||
|
||||
pub use self::{compound::NbtCompound, list::NbtList};
|
||||
use self::{
|
||||
compound::{read_tag_in_compound, ParsingStack},
|
||||
list::{read_compound_in_list, read_list_in_list},
|
||||
};
|
||||
use crate::{
|
||||
common::{read_string, COMPOUND_ID, END_ID},
|
||||
reader::{Reader, ReaderFromCursor},
|
||||
Error,
|
||||
};
|
||||
|
||||
/// Read a normal root NBT compound. This is either empty or has a name and
|
||||
/// compound tag.
|
||||
pub fn read(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
|
||||
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
|
||||
if root_type == END_ID {
|
||||
return Ok(());
|
||||
}
|
||||
if root_type != COMPOUND_ID {
|
||||
return Err(Error::InvalidRootType(root_type));
|
||||
}
|
||||
// our Reader type is faster than Cursor
|
||||
let mut data = ReaderFromCursor::new(data);
|
||||
read_string(&mut data)?;
|
||||
|
||||
let mut stack = ParsingStack::new();
|
||||
stack.push(ParsingStackElementKind::Compound)?;
|
||||
|
||||
read_with_stack(&mut data, &mut stack)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Read a root NBT compound, but without reading the name. This is used in
|
||||
/// Minecraft when reading NBT over the network.
|
||||
///
|
||||
/// This is similar to [`read_tag`], but only allows the data to be empty or a
|
||||
/// compound.
|
||||
pub fn read_unnamed(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
|
||||
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
|
||||
if root_type == END_ID {
|
||||
return Ok(());
|
||||
}
|
||||
if root_type != COMPOUND_ID {
|
||||
return Err(Error::InvalidRootType(root_type));
|
||||
}
|
||||
read_compound(data)?;
|
||||
Ok(())
|
||||
}
|
||||
/// Read a compound tag. This may have any number of items.
|
||||
pub fn read_compound(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
|
||||
let mut stack = ParsingStack::new();
|
||||
let mut data = ReaderFromCursor::new(data);
|
||||
stack.push(ParsingStackElementKind::Compound)?;
|
||||
read_with_stack(&mut data, &mut stack)?;
|
||||
Ok(())
|
||||
}
|
||||
/// Read an NBT tag, without reading its name. This may be any type of tag
|
||||
/// except for an end tag. If you need to be able to handle end tags, use
|
||||
/// [`read_optional_tag`].
|
||||
pub fn read_tag(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
|
||||
let mut stack = ParsingStack::new();
|
||||
|
||||
let mut data = ReaderFromCursor::new(data);
|
||||
|
||||
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
|
||||
if tag_type == END_ID {
|
||||
return Err(Error::InvalidRootType(0));
|
||||
}
|
||||
compound::read_tag(&mut data, &mut stack, tag_type)?;
|
||||
read_with_stack(&mut data, &mut stack)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn internal_read_tag(data: &mut Reader, tag_type: u8) -> Result<(), Error> {
|
||||
let mut stack = ParsingStack::new();
|
||||
compound::read_tag(data, &mut stack, tag_type)?;
|
||||
read_with_stack(data, &mut stack)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read any NBT tag, without reading its name. This may be any type of tag,
|
||||
/// including an end tag.
|
||||
pub fn read_optional_tag(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
|
||||
let mut stack = ParsingStack::new();
|
||||
|
||||
let mut data = ReaderFromCursor::new(data);
|
||||
|
||||
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
|
||||
if tag_type == END_ID {
|
||||
return Ok(());
|
||||
}
|
||||
compound::read_tag(&mut data, &mut stack, tag_type)?;
|
||||
read_with_stack(&mut data, &mut stack)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_with_stack<'a>(data: &mut Reader<'a>, stack: &mut ParsingStack) -> Result<(), Error> {
|
||||
while !stack.is_empty() {
|
||||
let top = stack.peek();
|
||||
match top {
|
||||
ParsingStackElementKind::Compound => read_tag_in_compound(data, stack)?,
|
||||
ParsingStackElementKind::ListOfCompounds => read_compound_in_list(data, stack)?,
|
||||
ParsingStackElementKind::ListOfLists => read_list_in_list(data, stack)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Read;
|
||||
|
||||
use byteorder::{WriteBytesExt, BE};
|
||||
use flate2::read::GzDecoder;
|
||||
|
||||
use super::*;
|
||||
use crate::common::{INT_ID, LIST_ID, LONG_ID};
|
||||
|
||||
#[test]
|
||||
fn hello_world() {
|
||||
super::read(&mut Cursor::new(include_bytes!(
|
||||
"../../tests/hello_world.nbt"
|
||||
)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_player() {
|
||||
let src = include_bytes!("../../tests/simple_player.dat").to_vec();
|
||||
let mut src_slice = src.as_slice();
|
||||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
super::read(&mut Cursor::new(&decoded_src)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_complex_player() {
|
||||
let src = include_bytes!("../../tests/complex_player.dat").to_vec();
|
||||
let mut src_slice = src.as_slice();
|
||||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
super::read(&mut Cursor::new(&decoded_src)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_hypixel() {
|
||||
let src = include_bytes!("../../tests/hypixel.nbt").to_vec();
|
||||
super::read(&mut Cursor::new(&src[..])).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inttest_1023() {
|
||||
super::read(&mut Cursor::new(include_bytes!(
|
||||
"../../tests/inttest1023.nbt"
|
||||
)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inttest_1024() {
|
||||
let mut data = Vec::new();
|
||||
data.write_u8(COMPOUND_ID).unwrap();
|
||||
data.write_u16::<BE>(0).unwrap();
|
||||
data.write_u8(LIST_ID).unwrap();
|
||||
data.write_u16::<BE>(0).unwrap();
|
||||
data.write_u8(INT_ID).unwrap();
|
||||
data.write_i32::<BE>(1024).unwrap();
|
||||
for i in 0..1024 {
|
||||
data.write_i32::<BE>(i).unwrap();
|
||||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
super::read(&mut Cursor::new(&data)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inttest_1021() {
|
||||
let mut data = Vec::new();
|
||||
data.write_u8(COMPOUND_ID).unwrap();
|
||||
data.write_u16::<BE>(0).unwrap();
|
||||
data.write_u8(LIST_ID).unwrap();
|
||||
data.write_u16::<BE>(0).unwrap();
|
||||
data.write_u8(INT_ID).unwrap();
|
||||
data.write_i32::<BE>(1021).unwrap();
|
||||
for i in 0..1021 {
|
||||
data.write_i32::<BE>(i).unwrap();
|
||||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
super::read(&mut Cursor::new(&data)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn longtest_1023() {
|
||||
let mut data = Vec::new();
|
||||
data.write_u8(COMPOUND_ID).unwrap();
|
||||
data.write_u16::<BE>(0).unwrap();
|
||||
data.write_u8(LIST_ID).unwrap();
|
||||
data.write_u16::<BE>(0).unwrap();
|
||||
data.write_u8(LONG_ID).unwrap();
|
||||
data.write_i32::<BE>(1023).unwrap();
|
||||
for i in 0..1023 {
|
||||
data.write_i64::<BE>(i).unwrap();
|
||||
}
|
||||
data.write_u8(END_ID).unwrap();
|
||||
|
||||
super::read(&mut Cursor::new(&data)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compound_eof() {
|
||||
let mut data = Vec::new();
|
||||
data.write_u8(COMPOUND_ID).unwrap(); // root type
|
||||
data.write_u16::<BE>(0).unwrap(); // root name length
|
||||
data.write_u8(COMPOUND_ID).unwrap(); // first element type
|
||||
data.write_u16::<BE>(0).unwrap(); // first element name length
|
||||
// eof
|
||||
|
||||
let res = super::read(&mut Cursor::new(&data));
|
||||
assert_eq!(res, Err(Error::UnexpectedEof));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_complex_player_as_tag() {
|
||||
let src = include_bytes!("../../tests/complex_player.dat").to_vec();
|
||||
let mut src_slice = src.as_slice();
|
||||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
|
||||
let mut decoded_src_as_tag = Vec::new();
|
||||
decoded_src_as_tag.push(COMPOUND_ID);
|
||||
decoded_src_as_tag.extend_from_slice(&decoded_src);
|
||||
decoded_src_as_tag.push(END_ID);
|
||||
|
||||
super::read_tag(&mut Cursor::new(&decoded_src_as_tag)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_array() {
|
||||
// found from fuzzing
|
||||
let data = [10, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0];
|
||||
super::read(&mut Cursor::new(&data)).unwrap();
|
||||
}
|
||||
#[test]
|
||||
fn list_of_empty_lists() {
|
||||
// found from fuzzing
|
||||
// BaseNbt { name: m"", tag: NbtTag::NbtCompound { m"":
|
||||
// NbtTag::List(List::List([List::Empty])) } }
|
||||
let data = [10, 0, 0, 9, 0, 0, 9, 0, 0, 0, 1, 0, 9, 0, 0, 0, 0];
|
||||
super::read(&mut Cursor::new(&data)).unwrap();
|
||||
}
|
||||
#[test]
|
||||
fn list_of_byte_arrays() {
|
||||
// BaseNbt { name: m"", tag: NbtCompound { values: [(m"",
|
||||
// List(List([List::ByteArray([])])))] } }
|
||||
let data = [10, 0, 0, 9, 0, 0, 9, 0, 0, 0, 1, 7, 0, 0, 0, 0, 0];
|
||||
super::read(&mut Cursor::new(&data)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compound_len() {
|
||||
let src = include_bytes!("../../tests/complex_player.dat").to_vec();
|
||||
let mut src_slice = src.as_slice();
|
||||
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
|
||||
let mut decoded_src = Vec::new();
|
||||
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
|
||||
super::read(&mut Cursor::new(&decoded_src)).unwrap();
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue