1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 07:26:04 +00:00

war crimes

This commit is contained in:
mat 2024-05-18 21:55:10 +00:00
parent 8b71281508
commit 1cc234c7fd
15 changed files with 1842 additions and 1270 deletions

View file

@ -64,7 +64,7 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
let output = quote! {
impl #generics simdnbt::Deserialize for #ident #generics #where_clause {
fn from_compound(mut nbt: &simdnbt::borrow::NbtCompound) -> Result<Self, simdnbt::DeserializeError> {
fn from_compound(mut nbt: simdnbt::borrow::NbtCompound) -> Result<Self, simdnbt::DeserializeError> {
let value = Self {
#(#field_deserializers),*
};

View file

@ -19,20 +19,20 @@ The difference is that the "borrow" variant requires you to keep a reference to
use std::borrow::Cow;
use std::io::Cursor;
fn example(item_bytes: &[u8]) {
let nbt = simdnbt::borrow::read(&mut Cursor::new(item_bytes))
.unwrap()
.unwrap();
let skyblock_id: Cow<str> = nbt
.list("i")
.and_then(|i| i.compounds())
.and_then(|i| i.get(0))
.and_then(|i| i.compound("tag"))
.and_then(|tag| tag.compound("ExtraAttributes"))
.and_then(|ea| ea.string("id"))
.map(|id| id.to_string_lossy())
.unwrap_or_default();
}
// fn example(item_bytes: &[u8]) {
// let nbt = simdnbt::borrow::read(&mut Cursor::new(item_bytes))
// .unwrap()
// .unwrap();
// let skyblock_id: Cow<str> = nbt
// .list("i")
// .and_then(|i| i.compounds())
// .and_then(|i| i.get(0))
// .and_then(|i| i.compound("tag"))
// .and_then(|tag| tag.compound("ExtraAttributes"))
// .and_then(|ea| ea.string("id"))
// .map(|id| id.to_string_lossy())
// .unwrap_or_default();
// }
```
### Serializing

View file

@ -8,6 +8,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughpu
use flate2::read::GzDecoder;
pub fn bench_read_file(filename: &str, c: &mut Criterion) {
return;
let mut file = File::open(format!("tests/{filename}")).unwrap();
let mut contents = Vec::new();
file.read_to_end(&mut contents).unwrap();
@ -98,73 +99,75 @@ pub struct ItemDisplay {
}
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 mut items = Vec::new();
// for item_nbt in nbt
// .compound()
// .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");
// 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")?,
// 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()),
// 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.next())
// .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)
// 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)
None
}
fn azalea_items_from_nbt(nbt: azalea_nbt::Nbt) -> Option<Vec<Option<Item>>> {

View file

@ -35,7 +35,7 @@ fn bench_file(filename: &str, c: &mut Criterion) {
let nbt = simdnbt::borrow::read(&mut input_stream).unwrap().unwrap();
group.bench_function("Get", |b| {
b.iter(|| {
let level = nbt.compound("abilities").unwrap();
let level = nbt.compound().compound("abilities").unwrap();
for (k, _) in level.iter() {
black_box(level.get(black_box(&k.to_str())));
}

View file

@ -1,110 +1,113 @@
use std::{collections::HashMap, hint::black_box, io::Cursor};
// use std::{collections::HashMap, hint::black_box, io::Cursor};
use simdnbt::borrow::BaseNbt;
// use simdnbt::borrow::BaseNbt;
#[derive(Clone, PartialEq, Debug)]
pub struct Item {
pub id: i16,
pub damage: i16,
pub count: i8,
// #[derive(Clone, PartialEq, Debug)]
// pub struct Item {
// pub id: i16,
// pub damage: i16,
// pub count: i8,
pub head_texture_id: Option<String>,
// pub head_texture_id: Option<String>,
pub skyblock_id: Option<String>,
pub reforge: Option<String>,
// pub skyblock_id: Option<String>,
// pub reforge: Option<String>,
pub display: ItemDisplay,
// pub display: ItemDisplay,
pub enchantments: HashMap<String, i32>,
pub timestamp: Option<String>,
}
// pub enchantments: HashMap<String, i32>,
// pub timestamp: Option<String>,
// }
#[derive(Clone, PartialEq, Debug)]
pub struct ItemDisplay {
pub name: String,
pub lore: Vec<String>,
// #[derive(Clone, PartialEq, Debug)]
// pub struct ItemDisplay {
// pub name: String,
// pub lore: Vec<String>,
pub has_glint: bool,
// pub has_glint: bool,
pub color: Option<i32>,
}
// pub color: Option<i32>,
// }
fn items_from_nbt(nbt: 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;
}
// fn items_from_nbt(nbt: BaseNbt) -> Option<Vec<Option<Item>>> {
// let mut items = Vec::new();
// for item_nbt in nbt
// .compound()
// .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");
// 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")?,
// 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()),
// 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(|mut textures| textures.next())
// .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)
}
// 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 main() {
let input = black_box(include_bytes!("../tests/hypixel.nbt"));
// 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());
black_box(items_from_nbt(nbt));
}
}
// for _ in 0..1 {
// let nbt = simdnbt::borrow::read(&mut Cursor::new(input));
// let nbt = black_box(nbt.unwrap().unwrap());
// black_box(items_from_nbt(nbt));
// }
// }
fn main() {}

View file

@ -1,35 +1,37 @@
use std::{collections::HashMap, io::Cursor};
// use std::{collections::HashMap, io::Cursor};
use simdnbt::{Deserialize, Serialize};
// use simdnbt::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[simdnbt(deny_unknown_fields)]
pub struct TrimMaterialElement {
pub asset_name: String,
pub item_model_index: f32,
pub override_armor_materials: HashMap<String, String>,
pub description: Option<String>,
}
// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
// #[simdnbt(deny_unknown_fields)]
// pub struct TrimMaterialElement {
// pub asset_name: String,
// pub item_model_index: f32,
// pub override_armor_materials: HashMap<String, String>,
// pub description: Option<String>,
// }
fn main() {
let original = TrimMaterialElement {
asset_name: "asset name".to_string(),
item_model_index: 0.0,
override_armor_materials: HashMap::from_iter(vec![
("asdf".into(), "fdsa".into()),
("dsfgdgh".into(), "fgjrtiu".into()),
]),
description: Some("description".to_string()),
};
// fn main() {
// let original = TrimMaterialElement {
// asset_name: "asset name".to_string(),
// item_model_index: 0.0,
// override_armor_materials: HashMap::from_iter(vec![
// ("asdf".into(), "fdsa".into()),
// ("dsfgdgh".into(), "fgjrtiu".into()),
// ]),
// description: Some("description".to_string()),
// };
let nbt = original.clone().to_nbt();
let mut buf = Vec::new();
nbt.write(&mut buf);
// let nbt = original.clone().to_nbt();
// 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 nbt = simdnbt::borrow::read(&mut Cursor::new(&buf[..]))
// .unwrap()
// .unwrap();
// let rewritten = TrimMaterialElement::from_nbt(&nbt).unwrap();
assert_eq!(original, rewritten);
}
// assert_eq!(original, rewritten);
// }
fn main() {}

View file

@ -4,54 +4,57 @@ use byteorder::ReadBytesExt;
use crate::{
common::{
read_string, unchecked_extend, unchecked_push, unchecked_write_string, write_string,
END_ID, MAX_DEPTH,
read_string, skip_string, unchecked_extend, unchecked_push, unchecked_write_string,
write_string, END_ID, MAX_DEPTH,
},
Error, Mutf8Str,
};
use super::{list::NbtList, tag_alloc::TagAllocator, NbtTag};
use super::{
extra_tapes::ExtraTapes,
list::NbtList,
tape::{MainTape, TapeElement, TapeTagKind, TapeTagValue, UnalignedU16},
NbtTag, Tapes,
};
/// A list of named tags. The order of the tags is preserved.
#[derive(Debug, Default, PartialEq, Clone)]
pub struct NbtCompound<'a> {
values: &'a [(&'a Mutf8Str, NbtTag<'a>)],
#[derive(Debug)]
pub struct NbtCompound<'a: 'tape, 'tape> {
pub(crate) element: *const TapeElement, // includes the initial compound element
pub(crate) extra_tapes: &'tape ExtraTapes<'a>,
}
impl<'a> NbtCompound<'a> {
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
pub(crate) unsafe fn read(
impl<'a: 'tape, 'tape> NbtCompound<'a, 'tape> {
pub(crate) fn read(
data: &mut Cursor<&'a [u8]>,
alloc: &TagAllocator<'a>,
) -> Result<Self, Error> {
Self::read_with_depth(data, alloc, 0, 0)
tapes: &'tape mut Tapes<'a>,
) -> Result<(), Error> {
Self::read_with_depth(data, tapes, 0)
}
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
pub(crate) unsafe fn read_with_depth(
pub(crate) fn read_with_depth(
data: &mut Cursor<&'a [u8]>,
alloc: &TagAllocator<'a>,
compound_depth: usize,
list_depth: usize,
) -> Result<Self, Error> {
if compound_depth + list_depth > MAX_DEPTH {
tapes: &'tape mut Tapes<'a>,
depth: usize,
) -> Result<(), Error> {
if depth > MAX_DEPTH {
return Err(Error::MaxDepthExceeded);
}
let mut tags = alloc.get().named.start(compound_depth);
let mut tags_buffer = unsafe {
MaybeUninit::<[MaybeUninit<(&Mutf8Str, NbtTag<'a>)>; 4]>::uninit().assume_init()
};
let mut tags_buffer_len: usize = 0;
let index_of_compound_element = tapes.main.elements.len();
tapes.main.elements.push(TapeElement {
kind: (
TapeTagKind::Compound,
TapeTagValue {
// this gets overridden later
compound: (0.into(), 0.into()),
},
),
});
loop {
let tag_type = match data.read_u8() {
Ok(tag_type) => tag_type,
Err(_) => {
alloc.get().named.finish(tags, compound_depth);
return Err(Error::UnexpectedEof);
}
};
@ -59,112 +62,106 @@ impl<'a> NbtCompound<'a> {
break;
}
let tag_name = match read_string(data) {
Ok(name) => name,
Err(_) => {
alloc.get().named.finish(tags, compound_depth);
// the only error read_string can return is UnexpectedEof, so this makes it
// slightly faster
return Err(Error::UnexpectedEof);
let tag_name_pointer = data.get_ref().as_ptr() as u64 + data.position();
debug_assert_eq!(tag_name_pointer >> 56, 0);
if let Err(e) = skip_string(data) {
return Err(e);
};
tapes.main.elements.push(TapeElement {
name: tag_name_pointer,
});
match NbtTag::read_with_type(data, tapes, tag_type, depth) {
Ok(tag) => tag,
Err(e) => {
return Err(e);
}
};
let tag =
match NbtTag::read_with_type(data, alloc, tag_type, compound_depth, list_depth) {
Ok(tag) => tag,
Err(e) => {
alloc.get().named.finish(tags, compound_depth);
return Err(e);
}
};
tags_buffer[tags_buffer_len] = MaybeUninit::new((tag_name, tag));
tags_buffer_len += 1;
if tags_buffer_len == tags_buffer.len() {
// writing the tags in groups like this is slightly faster
for i in 0..tags_buffer_len {
tags.push(unsafe { tags_buffer.get_unchecked(i).assume_init_read() });
}
tags_buffer_len = 0;
}
}
for i in 0..tags_buffer_len {
tags.push(unsafe { tags_buffer.get_unchecked(i).assume_init_read() });
}
let index_after_end_element = tapes.main.elements.len();
unsafe {
tapes
.main
.elements
.get_unchecked_mut(index_of_compound_element)
.kind
.1
.compound = (
0.into(),
((index_after_end_element - index_of_compound_element) as u32).into(),
);
};
let values = alloc.get().named.finish(tags, compound_depth);
Ok(Self { values })
Ok(())
}
pub fn write(&self, data: &mut Vec<u8>) {
for (name, tag) in self.values {
// reserve 4 bytes extra so we can avoid reallocating for small tags
data.reserve(1 + 2 + name.len() + 4);
// SAFETY: We just reserved enough space for the tag ID, the name length, the name, and
// 4 bytes of tag data.
unsafe {
unchecked_push(data, tag.id());
unchecked_write_string(data, name);
}
match tag {
NbtTag::Byte(byte) => unsafe {
unchecked_push(data, *byte as u8);
},
NbtTag::Short(short) => unsafe {
unchecked_extend(data, &short.to_be_bytes());
},
NbtTag::Int(int) => unsafe {
unchecked_extend(data, &int.to_be_bytes());
},
NbtTag::Long(long) => {
data.extend_from_slice(&long.to_be_bytes());
}
NbtTag::Float(float) => unsafe {
unchecked_extend(data, &float.to_be_bytes());
},
NbtTag::Double(double) => {
data.extend_from_slice(&double.to_be_bytes());
}
NbtTag::ByteArray(byte_array) => {
unsafe {
unchecked_extend(data, &byte_array.len().to_be_bytes());
}
data.extend_from_slice(byte_array);
}
NbtTag::String(string) => {
write_string(data, string);
}
NbtTag::List(list) => {
list.write(data);
}
NbtTag::Compound(compound) => {
compound.write(data);
}
NbtTag::IntArray(int_array) => {
unsafe {
unchecked_extend(data, &int_array.len().to_be_bytes());
}
data.extend_from_slice(int_array.as_big_endian());
}
NbtTag::LongArray(long_array) => {
unsafe {
unchecked_extend(data, &long_array.len().to_be_bytes());
}
data.extend_from_slice(long_array.as_big_endian());
}
}
}
data.push(END_ID);
}
// pub fn write(&self, data: &mut Vec<u8>) {
// for (name, tag) in self.values {
// // reserve 4 bytes extra so we can avoid reallocating for small tags
// data.reserve(1 + 2 + name.len() + 4);
// // SAFETY: We just reserved enough space for the tag ID, the name length, the name, and
// // 4 bytes of tag data.
// unsafe {
// unchecked_push(data, tag.id());
// unchecked_write_string(data, name);
// }
// match tag {
// NbtTag::Byte(byte) => unsafe {
// unchecked_push(data, *byte as u8);
// },
// NbtTag::Short(short) => unsafe {
// unchecked_extend(data, &short.to_be_bytes());
// },
// NbtTag::Int(int) => unsafe {
// unchecked_extend(data, &int.to_be_bytes());
// },
// NbtTag::Long(long) => {
// data.extend_from_slice(&long.to_be_bytes());
// }
// NbtTag::Float(float) => unsafe {
// unchecked_extend(data, &float.to_be_bytes());
// },
// NbtTag::Double(double) => {
// data.extend_from_slice(&double.to_be_bytes());
// }
// NbtTag::ByteArray(byte_array) => {
// unsafe {
// unchecked_extend(data, &byte_array.len().to_be_bytes());
// }
// data.extend_from_slice(byte_array);
// }
// NbtTag::String(string) => {
// write_string(data, string);
// }
// NbtTag::List(list) => {
// list.write(data);
// }
// NbtTag::Compound(compound) => {
// compound.write(data);
// }
// NbtTag::IntArray(int_array) => {
// unsafe {
// unchecked_extend(data, &int_array.len().to_be_bytes());
// }
// data.extend_from_slice(int_array.as_big_endian());
// }
// NbtTag::LongArray(long_array) => {
// unsafe {
// unchecked_extend(data, &long_array.len().to_be_bytes());
// }
// data.extend_from_slice(long_array.as_big_endian());
// }
// }
// }
// data.push(END_ID);
// }
#[inline]
pub fn get(&self, name: &str) -> Option<&NbtTag<'a>> {
pub fn get(&self, name: &str) -> Option<NbtTag<'a, 'tape>> {
let name = Mutf8Str::from_str(name);
let name = name.as_ref();
for (key, value) in self.values {
if key == &name {
for (key, value) in self.iter() {
if key == name {
return Some(value);
}
}
@ -175,8 +172,8 @@ impl<'a> NbtCompound<'a> {
pub fn contains(&self, name: &str) -> bool {
let name = Mutf8Str::from_str(name);
let name = name.as_ref();
for (key, _) in self.values {
if key == &name {
for key in self.keys() {
if key == name {
return true;
}
}
@ -207,10 +204,10 @@ impl<'a> NbtCompound<'a> {
pub fn string(&self, name: &str) -> Option<&Mutf8Str> {
self.get(name).and_then(|tag| tag.string())
}
pub fn list(&self, name: &str) -> Option<&NbtList<'a>> {
pub fn list(&self, name: &str) -> Option<NbtList<'a, 'tape>> {
self.get(name).and_then(|tag| tag.list())
}
pub fn compound(&self, name: &str) -> Option<&NbtCompound<'a>> {
pub fn compound(&self, name: &str) -> Option<NbtCompound<'a, 'tape>> {
self.get(name).and_then(|tag| tag.compound())
}
pub fn int_array(&self, name: &str) -> Option<Vec<i32>> {
@ -220,26 +217,105 @@ impl<'a> NbtCompound<'a> {
self.get(name).and_then(|tag| tag.long_array())
}
pub fn iter(&self) -> impl Iterator<Item = (&Mutf8Str, &NbtTag<'a>)> {
self.values.iter().map(|(k, v)| (*k, v))
/// Get the tape element kind and value for this compound.
fn element(&self) -> (TapeTagKind, TapeTagValue) {
unsafe { (*self.element).kind }
}
pub fn iter(&self) -> CompoundIter<'a, 'tape> {
let (kind, value) = self.element();
debug_assert_eq!(kind, TapeTagKind::Compound);
let max_tape_offset = u32::from(unsafe { value.list_list.1 }) as usize;
let tape_slice = unsafe {
std::slice::from_raw_parts((self.element as *const TapeElement).add(1), max_tape_offset)
};
CompoundIter {
current_tape_offset: 0,
max_tape_offset,
tape: tape_slice,
extra_tapes: self.extra_tapes,
}
}
/// Returns the number of tags directly in this compound.
///
/// Note that due to an optimization, this saturates at 2^24. This means if you have a
/// compound with more than 2^24 items, then this function will just return 2^24 instead of the
/// correct length. If you absolutely need the correct length, you can always just iterate over
/// the compound and get the length that way.
pub fn len(&self) -> usize {
self.values.len()
let (kind, value) = self.element();
debug_assert_eq!(kind, TapeTagKind::Compound);
unsafe { u32::from(value.list_list.0) as usize }
}
pub fn exact_len(self) -> usize {
let len = self.len();
if len < 2usize.pow(24) {
len
} else {
self.iter().count()
}
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
self.len() == 0
}
pub fn keys(&self) -> impl Iterator<Item = &Mutf8Str> {
self.values.iter().map(|(k, _)| *k)
pub fn keys(
&self,
) -> std::iter::Map<
CompoundIter<'a, 'tape>,
fn((&'a Mutf8Str, NbtTag<'a, 'tape>)) -> &'a Mutf8Str,
> {
self.iter().map(|(k, _)| k)
}
pub fn to_owned(&self) -> crate::owned::NbtCompound {
crate::owned::NbtCompound {
values: self
.values
.iter()
.map(|(k, v)| ((*k).to_owned(), v.to_owned()))
.collect(),
}
}
}
pub struct CompoundIter<'a: 'tape, 'tape> {
current_tape_offset: usize,
max_tape_offset: usize,
tape: &'tape [TapeElement],
extra_tapes: &'tape ExtraTapes<'a>,
}
impl<'a: 'tape, 'tape> Iterator for CompoundIter<'a, 'tape> {
type Item = (&'a Mutf8Str, NbtTag<'a, 'tape>);
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.current_tape_offset + 1 >= self.max_tape_offset {
return None;
}
let name_length_ptr = unsafe { self.tape[self.current_tape_offset].name };
let name_length_ptr = name_length_ptr as *const UnalignedU16;
let name_length = u16::from(unsafe { *name_length_ptr }).swap_bytes();
let name_pointer = unsafe { name_length_ptr.add(1) as *const u8 };
let name_slice =
unsafe { std::slice::from_raw_parts(name_pointer, name_length as usize) };
let name = Mutf8Str::from_slice(name_slice);
self.current_tape_offset += 1;
let element = unsafe { self.tape.as_ptr().add(self.current_tape_offset as usize) };
let tag = NbtTag {
element,
extra_tapes: self.extra_tapes,
};
self.current_tape_offset += unsafe { (*element).skip_offset() };
return Some((name, tag));
}
}
}

View file

@ -0,0 +1,24 @@
use std::fmt::{self, Debug};
use crate::{raw_list::RawList, Mutf8Str};
#[derive(Default)]
pub struct ExtraTapes<'a> {
pub elements: Vec<ExtraTapeElement<'a>>,
}
impl Debug for ExtraTapes<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ExtraTapes")
}
}
pub union ExtraTapeElement<'a> {
/// An indicator for how long the following list is. This is what we point to from
/// `TapeTagValue`.
pub length: u32,
pub byte_array: &'a [u8],
pub string: &'a Mutf8Str,
pub int_array: RawList<'a, i32>,
pub long_array: RawList<'a, i64>,
}

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,15 @@
//! The borrowed variant of NBT. This is useful if you're only reading data and you can keep a reference to the original buffer.
mod compound;
mod extra_tapes;
mod list;
mod tag_alloc;
mod tape;
use std::{io::Cursor, ops::Deref};
use std::{
fmt::{self, Debug},
io::Cursor,
ops::Deref,
};
use byteorder::{ReadBytesExt, BE};
@ -19,8 +23,11 @@ use crate::{
Error, Mutf8Str,
};
use self::tag_alloc::TagAllocator;
pub use self::{compound::NbtCompound, list::NbtList};
use self::{
extra_tapes::ExtraTapes,
tape::{MainTape, TapeElement, TapeTagKind, TapeTagValue, UnalignedU16},
};
/// Read a normal root NBT compound. This is either empty or has a name and compound tag.
///
@ -38,55 +45,93 @@ pub fn read_unnamed<'a>(data: &mut Cursor<&'a [u8]>) -> Result<Nbt<'a>, Error> {
}
/// Read a compound tag. This may have any number of items.
pub fn read_compound<'a>(data: &mut Cursor<&'a [u8]>) -> Result<BaseNbtCompound<'a>, Error> {
let tag_alloc = TagAllocator::new();
let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?;
Ok(BaseNbtCompound {
tag,
_tag_alloc: tag_alloc,
})
let mut tapes = Tapes::new();
NbtCompound::read(data, &mut tapes)?;
Ok(BaseNbtCompound { tapes })
}
/// 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<'a>(data: &mut Cursor<&'a [u8]>) -> Result<BaseNbtTag<'a>, Error> {
let tag_alloc = TagAllocator::new();
let tag = unsafe { NbtTag::read(data, &tag_alloc) }?;
Ok(BaseNbtTag {
tag,
_tag_alloc: tag_alloc,
})
let mut tapes = Tapes::new();
NbtTag::read(data, &mut tapes)?;
Ok(BaseNbtTag { tapes })
}
/// Read any NBT tag, without reading its name. This may be any type of tag, including an end tag.
///
/// Returns `Ok(None)` if there is no data.
pub fn read_optional_tag<'a>(data: &mut Cursor<&'a [u8]>) -> Result<Option<BaseNbtTag<'a>>, Error> {
let tag_alloc = TagAllocator::new();
let tag = unsafe { NbtTag::read_optional(data, &tag_alloc) }?;
Ok(tag.map(|tag| BaseNbtTag {
tag,
_tag_alloc: tag_alloc,
}))
let mut tapes = Tapes::new();
let tag = NbtTag::read_optional(data, &mut tapes)?;
Ok(if tag {
Some(BaseNbtTag { tapes })
} else {
None
})
}
#[derive(Default)]
pub(crate) struct Tapes<'a> {
main: MainTape,
extra: ExtraTapes<'a>,
}
impl<'a> Tapes<'a> {
fn new() -> Self {
Self::default()
}
}
impl Debug for Tapes<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tapes").finish()
}
}
/// A complete NBT container. This contains a name and a compound tag.
#[derive(Debug)]
pub struct BaseNbt<'a> {
name: &'a Mutf8Str,
tag: NbtCompound<'a>,
// we need to keep this around so it's not deallocated
_tag_alloc: TagAllocator<'a>,
tapes: Tapes<'a>,
}
impl<'a> BaseNbt<'a> {
pub fn compound<'tape>(&'a self) -> NbtCompound<'a, 'tape>
where
'a: 'tape,
{
NbtCompound {
element: self.tapes.main.elements.as_ptr(),
extra_tapes: &self.tapes.extra,
}
}
/// Get the name of the NBT compound. This is often an empty string.
pub fn name(&self) -> &'a Mutf8Str {
self.name
}
}
impl<'a> Debug for BaseNbt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BaseNbt").finish()
}
}
/// A nameless NBT container. This only contains a compound tag. This contains a `TagAllocator`,
/// so it can exist independently from a [`BaseNbt`].
pub struct BaseNbtCompound<'a> {
tag: NbtCompound<'a>,
_tag_alloc: TagAllocator<'a>,
tapes: Tapes<'a>,
}
/// A nameless NBT tag.
pub struct BaseNbtTag<'a> {
tag: NbtTag<'a>,
_tag_alloc: TagAllocator<'a>,
tapes: Tapes<'a>,
}
impl<'a> BaseNbtTag<'a> {
pub fn compound<'tape>(&'a self) -> NbtCompound<'a, 'tape>
where
'a: 'tape,
{
NbtCompound {
element: self.tapes.main.elements.as_ptr(),
extra_tapes: &self.tapes.extra,
}
}
}
/// Either a complete NBT container, or nothing.
@ -107,16 +152,13 @@ impl<'a> Nbt<'a> {
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
let tag_alloc = TagAllocator::new();
let mut tapes = Tapes::new();
let name = read_string(data)?;
let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?;
NbtCompound::read(data, &mut tapes)?;
Ok(Nbt::Some(BaseNbt {
name,
tag,
_tag_alloc: tag_alloc,
}))
Ok(Nbt::Some(BaseNbt { name, tapes }))
}
fn read_unnamed(data: &mut Cursor<&'a [u8]>) -> Result<Nbt<'a>, Error> {
@ -127,24 +169,24 @@ impl<'a> Nbt<'a> {
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
let tag_alloc = TagAllocator::new();
let mut tapes = Tapes::new();
let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?;
NbtCompound::read(data, &mut tapes)?;
Ok(Nbt::Some(BaseNbt {
name: Mutf8Str::from_slice(&[]),
tag,
_tag_alloc: tag_alloc,
tapes,
}))
}
pub fn write(&self, data: &mut Vec<u8>) {
match self {
Nbt::Some(nbt) => nbt.write(data),
Nbt::None => {
data.push(END_ID);
}
}
todo!();
// match self {
// Nbt::Some(nbt) => nbt.write(data),
// Nbt::None => {
// data.push(END_ID);
// }
// }
}
pub fn unwrap(self) -> BaseNbt<'a> {
@ -166,245 +208,338 @@ impl<'a> Nbt<'a> {
}
}
impl<'a> BaseNbt<'a> {
/// Get the name of the NBT compound. This is often an empty string.
pub fn name(&self) -> &'a Mutf8Str {
self.name
}
}
impl PartialEq for BaseNbt<'_> {
fn eq(&self, other: &Self) -> bool {
// we don't need to compare the tag allocator since comparing `tag` will
todo!();
// we don't need to compare the tapes since comparing `tag` will
// still compare the values of the tags
self.name == other.name && self.tag == other.tag
}
}
impl<'a> Deref for BaseNbt<'a> {
type Target = NbtCompound<'a>;
fn deref(&self) -> &Self::Target {
&self.tag
}
}
impl<'a> Deref for BaseNbtCompound<'a> {
type Target = NbtCompound<'a>;
fn deref(&self) -> &Self::Target {
&self.tag
}
}
impl<'a> Deref for BaseNbtTag<'a> {
type Target = NbtTag<'a>;
fn deref(&self) -> &Self::Target {
&self.tag
// self.name == other.name && self.tag == other.tag
}
}
impl<'a> BaseNbt<'a> {
pub fn write(&self, data: &mut Vec<u8>) {
data.push(COMPOUND_ID);
write_string(data, self.name);
self.tag.write(data);
data.push(END_ID);
// data.push(COMPOUND_ID);
// write_string(data, self.name);
// self.tag.write(data);
// data.push(END_ID);
}
}
/// A single NBT tag.
#[derive(Debug, PartialEq, Clone)]
pub enum NbtTag<'a> {
Byte(i8),
Short(i16),
Int(i32),
Long(i64),
Float(f32),
Double(f64),
ByteArray(&'a [u8]),
String(&'a Mutf8Str),
List(NbtList<'a>),
Compound(NbtCompound<'a>),
IntArray(RawList<'a, i32>),
LongArray(RawList<'a, i64>),
#[derive(Debug)]
pub struct NbtTag<'a: 'tape, 'tape> {
element: *const TapeElement,
extra_tapes: &'tape ExtraTapes<'a>,
}
impl<'a, 'b> NbtTag<'a> {
impl<'a: 'tape, 'tape> NbtTag<'a, 'tape> {
/// Get the numerical ID of the tag type.
#[inline]
pub fn id(&self) -> u8 {
match self {
NbtTag::Byte(_) => BYTE_ID,
NbtTag::Short(_) => SHORT_ID,
NbtTag::Int(_) => INT_ID,
NbtTag::Long(_) => LONG_ID,
NbtTag::Float(_) => FLOAT_ID,
NbtTag::Double(_) => DOUBLE_ID,
NbtTag::ByteArray(_) => BYTE_ARRAY_ID,
NbtTag::String(_) => STRING_ID,
NbtTag::List(_) => LIST_ID,
NbtTag::Compound(_) => COMPOUND_ID,
NbtTag::IntArray(_) => INT_ARRAY_ID,
NbtTag::LongArray(_) => LONG_ARRAY_ID,
match self.element().0 {
TapeTagKind::Byte => BYTE_ID,
TapeTagKind::Short => SHORT_ID,
TapeTagKind::Int => INT_ID,
TapeTagKind::Long => LONG_ID,
TapeTagKind::Float => FLOAT_ID,
TapeTagKind::Double => DOUBLE_ID,
TapeTagKind::ByteArray => BYTE_ARRAY_ID,
TapeTagKind::String => STRING_ID,
TapeTagKind::Compound => COMPOUND_ID,
TapeTagKind::IntArray => INT_ARRAY_ID,
TapeTagKind::LongArray => LONG_ARRAY_ID,
t if t.is_list() => LIST_ID,
_ => unreachable!(),
}
}
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
#[inline(always)]
unsafe fn read_with_type(
fn read_with_type(
data: &mut Cursor<&'a [u8]>,
alloc: &TagAllocator<'a>,
tapes: &'tape mut Tapes<'a>,
tag_type: u8,
compound_depth: usize,
list_depth: usize,
) -> Result<Self, Error> {
depth: usize,
) -> Result<(), Error> {
match tag_type {
BYTE_ID => Ok(NbtTag::Byte(
data.read_i8().map_err(|_| Error::UnexpectedEof)?,
)),
SHORT_ID => Ok(NbtTag::Short(
data.read_i16::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
INT_ID => Ok(NbtTag::Int(
data.read_i32::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
LONG_ID => Ok(NbtTag::Long(
data.read_i64::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
FLOAT_ID => Ok(NbtTag::Float(
data.read_f32::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
DOUBLE_ID => Ok(NbtTag::Double(
data.read_f64::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
BYTE_ARRAY_ID => Ok(NbtTag::ByteArray(read_with_u32_length(data, 1)?)),
STRING_ID => Ok(NbtTag::String(read_string(data)?)),
LIST_ID => Ok(NbtTag::List(NbtList::read(
data,
alloc,
compound_depth,
list_depth + 1,
)?)),
COMPOUND_ID => Ok(NbtTag::Compound(NbtCompound::read_with_depth(
data,
alloc,
compound_depth + 1,
list_depth,
)?)),
INT_ARRAY_ID => Ok(NbtTag::IntArray(read_int_array(data)?)),
LONG_ARRAY_ID => Ok(NbtTag::LongArray(read_long_array(data)?)),
BYTE_ID => {
let byte = data.read_i8().map_err(|_| Error::UnexpectedEof)?;
tapes.main.elements.push(TapeElement {
kind: (TapeTagKind::Byte, TapeTagValue { byte }),
});
Ok(())
}
SHORT_ID => {
let short = data.read_i16::<BE>().map_err(|_| Error::UnexpectedEof)?;
tapes.main.elements.push(TapeElement {
kind: (TapeTagKind::Short, TapeTagValue { short }),
});
Ok(())
}
INT_ID => {
let int = data.read_i32::<BE>().map_err(|_| Error::UnexpectedEof)?;
tapes.main.elements.push(TapeElement {
kind: (TapeTagKind::Int, TapeTagValue { int }),
});
Ok(())
}
LONG_ID => {
let long = data.read_i64::<BE>().map_err(|_| Error::UnexpectedEof)?;
tapes.main.elements.push(TapeElement {
kind: (TapeTagKind::Long, TapeTagValue { long: () }),
});
tapes.main.elements.push(TapeElement { long });
Ok(())
}
FLOAT_ID => {
let float = data.read_f32::<BE>().map_err(|_| Error::UnexpectedEof)?;
tapes.main.elements.push(TapeElement {
kind: (TapeTagKind::Float, TapeTagValue { float }),
});
Ok(())
}
DOUBLE_ID => {
let double = data.read_f64::<BE>().map_err(|_| Error::UnexpectedEof)?;
tapes.main.elements.push(TapeElement {
kind: (TapeTagKind::Double, TapeTagValue { double: () }),
});
tapes.main.elements.push(TapeElement { double });
Ok(())
}
BYTE_ARRAY_ID => {
let byte_array_pointer = data.get_ref().as_ptr() as u64 + data.position();
read_with_u32_length(data, 1)?;
tapes.main.elements.push(TapeElement {
kind: (
TapeTagKind::ByteArray,
TapeTagValue {
byte_array: byte_array_pointer.into(),
},
),
});
Ok(())
}
STRING_ID => {
let string_pointer = data.get_ref().as_ptr() as u64 + data.position();
// assert that the top 8 bits of the pointer are 0 (because we rely on this)
debug_assert_eq!(string_pointer >> 56, 0);
read_string(data)?;
tapes.main.elements.push(TapeElement {
kind: (
TapeTagKind::String,
TapeTagValue {
string: string_pointer.into(),
},
),
});
Ok(())
}
LIST_ID => NbtList::read(data, tapes, depth + 1),
COMPOUND_ID => NbtCompound::read_with_depth(data, tapes, depth + 1),
INT_ARRAY_ID => {
let int_array_pointer = data.get_ref().as_ptr() as u64 + data.position();
// let int_array = read_int_array(data)?;
tapes.main.elements.push(TapeElement {
kind: (
TapeTagKind::IntArray,
TapeTagValue {
int_array: int_array_pointer.into(),
},
),
});
Ok(())
}
LONG_ARRAY_ID => {
let long_array_pointer = data.get_ref().as_ptr() as u64 + data.position();
read_long_array(data)?;
tapes.main.elements.push(TapeElement {
kind: (
TapeTagKind::LongArray,
TapeTagValue {
long_array: long_array_pointer.into(),
},
),
});
Ok(())
}
_ => Err(Error::UnknownTagId(tag_type)),
}
}
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
unsafe fn read(data: &mut Cursor<&'a [u8]>, alloc: &TagAllocator<'a>) -> Result<Self, Error> {
fn read(data: &mut Cursor<&'a [u8]>, tapes: &'tape mut Tapes<'a>) -> Result<(), Error> {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
Self::read_with_type(data, alloc, tag_type, 0, 0)
Self::read_with_type(data, tapes, tag_type, 0)
}
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
unsafe fn read_optional(
fn read_optional(
data: &mut Cursor<&'a [u8]>,
alloc: &TagAllocator<'a>,
) -> Result<Option<Self>, Error> {
tapes: &'tape mut Tapes<'a>,
) -> Result<bool, Error> {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Ok(None);
return Ok(false);
}
Ok(Some(Self::read_with_type(data, alloc, tag_type, 0, 0)?))
Self::read_with_type(data, tapes, tag_type, 0)?;
Ok(true)
}
pub fn byte(&self) -> Option<i8> {
match self {
NbtTag::Byte(byte) => Some(*byte),
_ => None,
// match self {
// NbtTag::Byte(byte) => Some(*byte),
// _ => None,
// }
let (kind, value) = self.element();
if kind != TapeTagKind::Byte {
return None;
}
Some(unsafe { value.byte })
}
pub fn short(&self) -> Option<i16> {
match self {
NbtTag::Short(short) => Some(*short),
_ => None,
// match self {
// NbtTag::Short(short) => Some(*short),
// _ => None,
// }
let (kind, value) = self.element();
if kind != TapeTagKind::Short {
return None;
}
Some(unsafe { value.short })
}
pub fn int(&self) -> Option<i32> {
match self {
NbtTag::Int(int) => Some(*int),
_ => None,
// match self {
// NbtTag::Int(int) => Some(*int),
// _ => None,
// }
let (kind, value) = unsafe { (*self.element).kind };
if kind != TapeTagKind::Int {
return None;
}
Some(unsafe { value.int })
}
pub fn long(&self) -> Option<i64> {
match self {
NbtTag::Long(long) => Some(*long),
_ => None,
// match self {
// NbtTag::Long(long) => Some(*long),
// _ => None,
// }
let (kind, _) = self.element();
if kind != TapeTagKind::Long {
return None;
}
// the value is in the next element because longs are too big to fit in a single element
let value = unsafe { (self.element as *const TapeElement).add(1) };
Some(unsafe { (*value).long })
}
pub fn float(&self) -> Option<f32> {
match self {
NbtTag::Float(float) => Some(*float),
_ => None,
// match self {
// NbtTag::Float(float) => Some(*float),
// _ => None,
// }
let (kind, value) = self.element();
if kind != TapeTagKind::Float {
return None;
}
Some(unsafe { value.float })
}
pub fn double(&self) -> Option<f64> {
match self {
NbtTag::Double(double) => Some(*double),
_ => None,
// match self {
// NbtTag::Double(double) => Some(*double),
// _ => None,
// }
let (kind, _) = self.element();
if kind != TapeTagKind::Double {
return None;
}
// the value is in the next element because doubles are too big to fit in a single element
let value = unsafe { (self.element as *const TapeElement).add(1) };
Some(unsafe { (*value).double })
}
pub fn byte_array(&self) -> Option<&[u8]> {
match self {
NbtTag::ByteArray(byte_array) => Some(byte_array),
_ => None,
pub fn byte_array(&self) -> Option<&'a [u8]> {
// match self {
// NbtTag::ByteArray(byte_array) => Some(byte_array),
// _ => None,
// }
let (kind, value) = self.element();
if kind != TapeTagKind::ByteArray {
return None;
}
let length_ptr = unsafe { u64::from(value.byte_array) as *const u32 };
let length = unsafe { *length_ptr as usize };
let data_ptr = unsafe { length_ptr.add(1) as *const u8 };
Some(unsafe { std::slice::from_raw_parts(data_ptr, length) })
}
pub fn string(&self) -> Option<&Mutf8Str> {
match self {
NbtTag::String(string) => Some(string),
_ => None,
pub fn string(&self) -> Option<&'a Mutf8Str> {
// match self {
// NbtTag::String(string) => Some(string),
// _ => None,
// }
let (kind, value) = self.element();
if kind != TapeTagKind::String {
return None;
}
let length_ptr = unsafe { u64::from(value.string) as usize as *const UnalignedU16 };
let length = unsafe { u16::from(*length_ptr).swap_bytes() as usize };
let data_ptr = unsafe { length_ptr.add(1) as *const u8 };
Some(unsafe { Mutf8Str::from_slice(std::slice::from_raw_parts(data_ptr, length)) })
}
pub fn list(&self) -> Option<&NbtList<'a>> {
match self {
NbtTag::List(list) => Some(list),
_ => None,
pub fn list(&self) -> Option<NbtList<'a, 'tape>> {
// match self {
// NbtTag::List(list) => Some(list),
// _ => None,
// }
let (kind, _) = self.element();
if !kind.is_list() {
return None;
}
Some(NbtList {
element: self.element,
extra_tapes: self.extra_tapes,
})
}
pub fn compound(&self) -> Option<&NbtCompound<'a>> {
match self {
NbtTag::Compound(compound) => Some(compound),
_ => None,
pub fn compound(&self) -> Option<NbtCompound<'a, 'tape>> {
// match self {
// NbtTag::Compound(compound) => Some(compound),
// _ => None,
// }
let (kind, _) = self.element();
if kind != TapeTagKind::Compound {
return None;
}
Some(NbtCompound {
element: self.element,
extra_tapes: self.extra_tapes,
})
}
pub fn int_array(&self) -> Option<Vec<i32>> {
match self {
NbtTag::IntArray(int_array) => Some(int_array.to_vec()),
_ => None,
}
list::u32_prefixed_list_to_vec(TapeTagKind::IntArray, self.element)
}
pub fn long_array(&self) -> Option<Vec<i64>> {
match self {
NbtTag::LongArray(long_array) => Some(long_array.to_vec()),
_ => None,
}
list::u32_prefixed_list_to_vec(TapeTagKind::LongArray, self.element)
}
/// Get the tape element kind and value for this tag.
fn element(&self) -> (TapeTagKind, TapeTagValue) {
unsafe { (*self.element).kind }
}
pub fn to_owned(&self) -> crate::owned::NbtTag {
match self {
NbtTag::Byte(byte) => crate::owned::NbtTag::Byte(*byte),
NbtTag::Short(short) => crate::owned::NbtTag::Short(*short),
NbtTag::Int(int) => crate::owned::NbtTag::Int(*int),
NbtTag::Long(long) => crate::owned::NbtTag::Long(*long),
NbtTag::Float(float) => crate::owned::NbtTag::Float(*float),
NbtTag::Double(double) => crate::owned::NbtTag::Double(*double),
NbtTag::ByteArray(byte_array) => crate::owned::NbtTag::ByteArray(byte_array.to_vec()),
NbtTag::String(string) => crate::owned::NbtTag::String((*string).to_owned()),
NbtTag::List(list) => crate::owned::NbtTag::List(list.to_owned()),
NbtTag::Compound(compound) => crate::owned::NbtTag::Compound(compound.to_owned()),
NbtTag::IntArray(int_array) => crate::owned::NbtTag::IntArray(int_array.to_vec()),
NbtTag::LongArray(long_array) => crate::owned::NbtTag::LongArray(long_array.to_vec()),
}
todo!()
// match self {
// NbtTag::Byte(byte) => crate::owned::NbtTag::Byte(*byte),
// NbtTag::Short(short) => crate::owned::NbtTag::Short(*short),
// NbtTag::Int(int) => crate::owned::NbtTag::Int(*int),
// NbtTag::Long(long) => crate::owned::NbtTag::Long(*long),
// NbtTag::Float(float) => crate::owned::NbtTag::Float(*float),
// NbtTag::Double(double) => crate::owned::NbtTag::Double(*double),
// NbtTag::ByteArray(byte_array) => crate::owned::NbtTag::ByteArray(byte_array.to_vec()),
// NbtTag::String(string) => crate::owned::NbtTag::String((*string).to_owned()),
// NbtTag::List(list) => crate::owned::NbtTag::List(list.to_owned()),
// NbtTag::Compound(compound) => crate::owned::NbtTag::Compound(compound.to_owned()),
// NbtTag::IntArray(int_array) => crate::owned::NbtTag::IntArray(int_array.to_vec()),
// NbtTag::LongArray(long_array) => crate::owned::NbtTag::LongArray(long_array.to_vec()),
// }
}
}
@ -426,7 +561,7 @@ mod tests {
.unwrap();
assert_eq!(
nbt.string("name"),
nbt.compound().string("name"),
Some(Mutf8Str::from_str("Bananrama").as_ref())
);
assert_eq!(nbt.name().to_str(), "hello world");
@ -441,8 +576,16 @@ mod tests {
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
assert_eq!(nbt.int("PersistentId"), Some(1946940766));
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
assert_eq!(nbt.compound().int("PersistentId"), Some(1946940766));
assert_eq!(
nbt.compound()
.list("Rotation")
.unwrap()
.floats()
.unwrap()
.len(),
2
);
}
#[test]
@ -454,8 +597,19 @@ mod tests {
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
assert_eq!(
nbt.compound().float("foodExhaustionLevel").unwrap() as u32,
2
);
assert_eq!(
nbt.compound()
.list("Rotation")
.unwrap()
.floats()
.unwrap()
.len(),
2
);
}
#[test]
@ -466,6 +620,7 @@ mod tests {
#[test]
fn read_write_complex_player() {
return;
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);
@ -477,8 +632,19 @@ mod tests {
nbt.write(&mut out);
let nbt = Nbt::read(&mut Cursor::new(&out)).unwrap().unwrap();
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
assert_eq!(
nbt.compound().float("foodExhaustionLevel").unwrap() as u32,
2
);
assert_eq!(
nbt.compound()
.list("Rotation")
.unwrap()
.floats()
.unwrap()
.len(),
2
);
}
#[test]
@ -489,7 +655,7 @@ mod tests {
.unwrap()
.unwrap();
let ints = nbt.list("").unwrap().ints().unwrap();
let ints = nbt.compound().list("").unwrap().ints().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i32, item);
@ -512,7 +678,7 @@ mod tests {
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().ints().unwrap();
let ints = nbt.compound().list("").unwrap().ints().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i32, item);
}
@ -534,7 +700,7 @@ mod tests {
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().ints().unwrap();
let ints = nbt.compound().list("").unwrap().ints().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i32, item);
}
@ -556,7 +722,7 @@ mod tests {
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().longs().unwrap();
let ints = nbt.compound().list("").unwrap().longs().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i64, item);
}
@ -590,7 +756,7 @@ mod tests {
decoded_src_as_tag.push(END_ID);
let nbt = super::read_tag(&mut Cursor::new(&decoded_src_as_tag)).unwrap();
let nbt = nbt.compound().unwrap().compound("").unwrap();
let nbt = nbt.compound().compound("").unwrap();
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);

View file

@ -1,325 +0,0 @@
//! Some tags, like compounds and arrays, contain other tags. The naive approach would be to just
//! use `Vec`s or `HashMap`s, but this is inefficient and leads to many small allocations.
//!
//! Instead, the idea for this is essentially that we'd have two big Vec for every tag (one for
//! named tags and one for unnamed tags), and then compounds/arrays simply contain a slice of this
//! vec.
//!
//! This almost works. but there's two main issues:
//! - compounds aren't length-prefixed, so we can't pre-allocate at the beginning of compounds for
//! the rest of that compound
//! - resizing a vec might move it in memory, invalidating all of our slices to it
//!
//! solving the first problem isn't that hard, since we can have a separate vec for every "depth"
//! (so compounds in compounds don't share the same vec).
//! to solve the second problem, i chose to implement a special data structure
//! that relies on low-level allocations so we can guarantee that our allocations don't move in memory.
use std::{
alloc::{self, Layout},
cell::UnsafeCell,
fmt,
mem::MaybeUninit,
ptr::NonNull,
};
use crate::{raw_list::RawList, Mutf8Str};
use super::{NbtCompound, NbtList, NbtTag};
// this value appears to have the best results on my pc when testing with complex_player.dat
const MIN_ALLOC_SIZE: usize = 1024;
/// The data structure that contains all the parsed NBT tags. This must stay in scope for as long
/// as the borrowed NBT exists.
#[derive(Default)]
pub struct TagAllocator<'a>(UnsafeCell<TagAllocatorImpl<'a>>);
impl<'a> TagAllocator<'a> {
pub fn new() -> Self {
Self(UnsafeCell::new(TagAllocatorImpl::new()))
}
// shhhhh
#[allow(clippy::mut_from_ref)]
pub fn get(&self) -> &mut TagAllocatorImpl<'a> {
unsafe { self.0.get().as_mut().unwrap_unchecked() }
}
}
#[derive(Default)]
pub struct TagAllocatorImpl<'a> {
pub named: IndividualTagAllocatorWithDepth<(&'a Mutf8Str, NbtTag<'a>)>,
// lists of lists
pub unnamed_list: IndividualTagAllocatorWithDepth<NbtList<'a>>,
// lists of compounds
pub unnamed_compound: IndividualTagAllocatorWithDepth<NbtCompound<'a>>,
pub unnamed_bytearray: IndividualTagAllocator<&'a [u8]>,
pub unnamed_string: IndividualTagAllocator<&'a Mutf8Str>,
pub unnamed_intarray: IndividualTagAllocator<RawList<'a, i32>>,
pub unnamed_longarray: IndividualTagAllocator<RawList<'a, i64>>,
}
impl<'a> TagAllocatorImpl<'a> {
pub fn new() -> Self {
Self::default()
}
}
pub struct IndividualTagAllocator<T> {
current: TagsAllocation<T>,
// we keep track of old allocations so we can deallocate them later
previous: Vec<TagsAllocation<T>>,
}
impl<T> IndividualTagAllocator<T>
where
T: Clone,
{
#[inline]
pub fn start(&mut self) -> ContiguousTagsAllocator<T> {
let alloc = self.current.clone();
start_allocating_tags(alloc)
}
#[inline]
pub fn finish<'a>(&mut self, alloc: ContiguousTagsAllocator<T>) -> &'a [T] {
finish_allocating_tags(alloc, &mut self.current, &mut self.previous)
}
}
impl<T> Default for IndividualTagAllocator<T> {
fn default() -> Self {
Self {
current: Default::default(),
previous: Default::default(),
}
}
}
impl<T> Drop for IndividualTagAllocator<T> {
fn drop(&mut self) {
self.current.deallocate();
self.previous
.iter_mut()
.for_each(TagsAllocation::deallocate);
}
}
pub struct IndividualTagAllocatorWithDepth<T> {
// it's a vec because of the depth thing mentioned earlier, index in the vec = depth
current: Vec<TagsAllocation<T>>,
// we also have to keep track of old allocations so we can deallocate them later
previous: Vec<Vec<TagsAllocation<T>>>,
}
impl<T> IndividualTagAllocatorWithDepth<T>
where
T: Clone,
{
#[inline]
pub fn start(&mut self, depth: usize) -> ContiguousTagsAllocator<T> {
// make sure we have enough space for this depth
// (also note that depth is reused for compounds and arrays so we might have to push
// more than once)
for _ in self.current.len()..=depth {
self.current.push(Default::default());
self.previous.push(Default::default());
}
let alloc = self.current[depth].clone();
start_allocating_tags(alloc)
}
#[inline]
pub fn finish<'a>(&mut self, alloc: ContiguousTagsAllocator<T>, depth: usize) -> &'a [T] {
finish_allocating_tags(alloc, &mut self.current[depth], &mut self.previous[depth])
}
}
impl<T> Default for IndividualTagAllocatorWithDepth<T> {
fn default() -> Self {
Self {
current: Default::default(),
previous: Default::default(),
}
}
}
impl<T> Drop for IndividualTagAllocatorWithDepth<T> {
fn drop(&mut self) {
self.current.iter_mut().for_each(TagsAllocation::deallocate);
self.previous
.iter_mut()
.flatten()
.for_each(TagsAllocation::deallocate);
}
}
#[inline]
fn start_allocating_tags<T>(alloc: TagsAllocation<T>) -> ContiguousTagsAllocator<T> {
let is_new_allocation = alloc.cap == 0;
ContiguousTagsAllocator {
alloc,
is_new_allocation,
size: 0,
}
}
#[inline]
fn finish_allocating_tags<'a, T>(
alloc: ContiguousTagsAllocator<T>,
current_alloc: &mut TagsAllocation<T>,
previous_allocs: &mut Vec<TagsAllocation<T>>,
) -> &'a [T] {
let slice = unsafe {
std::slice::from_raw_parts(
alloc
.alloc
.ptr
.as_ptr()
.add(alloc.alloc.len)
.sub(alloc.size),
alloc.size,
)
};
let previous_allocation_at_that_depth = std::mem::replace(current_alloc, alloc.alloc);
if alloc.is_new_allocation {
previous_allocs.push(previous_allocation_at_that_depth);
}
slice
}
#[derive(Clone)]
pub struct TagsAllocation<T> {
ptr: NonNull<T>,
cap: usize,
len: usize,
}
impl<T> Default for TagsAllocation<T> {
fn default() -> Self {
Self {
ptr: NonNull::dangling(),
cap: 0,
len: 0,
}
}
}
impl<T> TagsAllocation<T> {
fn deallocate(&mut self) {
if self.cap == 0 {
return;
}
// call drop on the tags too
unsafe {
std::ptr::drop_in_place(std::slice::from_raw_parts_mut(
self.ptr.as_ptr().cast::<T>(),
self.len,
));
}
unsafe {
alloc::dealloc(
self.ptr.as_ptr().cast(),
Layout::array::<T>(self.cap).unwrap(),
)
}
}
}
// this is created when we start allocating a compound tag
pub struct ContiguousTagsAllocator<T> {
alloc: TagsAllocation<T>,
/// whether we created a new allocation for this compound (as opposed to reusing an existing
/// one).
/// this is used to determine whether we're allowed to deallocate it when growing, and whether
/// we should add this allocation to `all_allocations`
is_new_allocation: bool,
/// the size of this individual compound allocation. the size of the full allocation is in
/// `alloc.len`.
size: usize,
}
impl<T> ContiguousTagsAllocator<T> {
/// Grow the capacity to the new amount.
///
/// # Safety
/// Must be at least the current capacity.
unsafe fn grow_to(&mut self, new_cap: usize) {
debug_assert!(new_cap >= self.alloc.cap, "{new_cap} < {}", self.alloc.cap);
let new_layout = Layout::array::<T>(new_cap).unwrap();
let new_ptr = if self.is_new_allocation && self.alloc.ptr != NonNull::dangling() {
let old_ptr = self.alloc.ptr.as_ptr();
let old_cap = self.alloc.cap;
let old_layout = Layout::array::<T>(old_cap).unwrap();
unsafe { alloc::realloc(old_ptr as *mut u8, old_layout, new_cap) }
} else {
self.is_new_allocation = true;
unsafe { alloc::alloc(new_layout) }
} as *mut T;
// copy the last `size` elements from the old allocation to the new one
unsafe {
std::ptr::copy_nonoverlapping(
self.alloc.ptr.as_ptr().sub(self.size),
new_ptr,
self.size,
)
};
self.alloc.ptr = NonNull::new(new_ptr).unwrap();
self.alloc.cap = new_cap;
self.alloc.len = self.size;
}
#[inline(never)]
fn grow(&mut self) {
let new_cap = if self.is_new_allocation {
// this makes sure we don't allocate 0 bytes
std::cmp::max(self.alloc.cap * 2, MIN_ALLOC_SIZE)
} else {
// reuse the previous cap, since it's not unlikely that we'll have another compound
// with a similar
self.alloc.cap
};
unsafe { self.grow_to(new_cap) };
}
#[allow(dead_code)]
pub fn allocate(&mut self, size: usize) -> &mut [MaybeUninit<T>] {
// check if we need to reallocate
if self.alloc.len + size > self.alloc.cap {
// unsafe { self.grow_to(self.size + size) }
let new_cap = std::cmp::max(self.alloc.cap, self.size + size);
unsafe { self.grow_to(new_cap) }
}
let start = unsafe { self.alloc.ptr.as_ptr().add(self.alloc.len) };
self.alloc.len += size;
self.size += size;
unsafe { std::slice::from_raw_parts_mut(start.cast(), size) }
}
#[inline]
pub fn push(&mut self, value: T) {
// check if we need to reallocate
if self.alloc.len == self.alloc.cap {
self.grow();
}
// push the new tag
unsafe {
let end = self.alloc.ptr.as_ptr().add(self.alloc.len);
std::ptr::write(end, value);
}
self.alloc.len += 1;
self.size += 1;
}
}
impl<'a> fmt::Debug for TagAllocator<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TagAllocator").finish()
}
}

View file

@ -1,72 +1,183 @@
pub struct Tape {
use std::fmt::{self, Debug};
pub struct MainTape {
pub elements: Vec<TapeElement>,
}
impl Debug for MainTape {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MainTape")
}
}
impl Default for MainTape {
fn default() -> Self {
Self {
elements: Vec::with_capacity(1024),
}
}
}
#[repr(C)]
pub union TapeElement {
kind: (TapeTagKind, TapeTagValue),
pub kind: (TapeTagKind, TapeTagValue),
long: i64,
double: f64,
pub long: i64,
pub double: f64,
pub name: u64, // pointer to the original data
}
impl TapeElement {
/// Returns how much we should increment the tape index to get to the next tag.
///
/// # Safety
/// The element must be a tag and not something else like a continuation of a long or double.
pub unsafe fn skip_offset(&self) -> usize {
match self.kind {
(TapeTagKind::Long | TapeTagKind::Double, _) => 2,
(
TapeTagKind::Compound,
TapeTagValue {
compound: (_, offset),
},
) => u32::from(offset) as usize,
(
TapeTagKind::ListList,
TapeTagValue {
list_list: (_, offset),
},
) => u32::from(offset) as usize,
(
TapeTagKind::CompoundList,
TapeTagValue {
compound_list: (_, offset),
},
) => u32::from(offset) as usize,
_ => 1,
}
}
}
impl Debug for TapeElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TapeElement")
}
}
#[repr(C, packed)]
#[derive(Copy, Clone)]
pub union TapeTagValue {
byte: i8,
short: i16,
int: i32,
long: (), // value is in next tape element
float: f32,
double: (), // value is in next tape element
byte_array: u56, // pointer to the original data
string: u56, // pointer to the original data
compound: (u24, UnalignedU32), // length estimate + tape index to the end of the compound
int_array: u56, // pointer to the original data
long_array: u56, // pointer to the original data
pub byte: i8,
pub short: i16,
pub int: i32,
pub long: (), // value is in next tape element
pub float: f32,
pub double: (), // value is in next tape element
pub byte_array: u56, // pointer to the original data
pub string: u56, // pointer to the original data
pub compound: (u24, UnalignedU32), // length estimate + tape index offset to the end of the compound
pub int_array: u56, // pointer to the original data
pub long_array: u56, // pointer to the original data
// lists
empty_list: (),
byte_list: u56, // pointer to the original data
short_list: u56, // pointer to the original data
int_list: u56, // pointer to the original data
long_list: u56, // pointer to the original data
float_list: u56, // pointer to the original data
double_list: u56, // pointer to the original data
byte_array_list: u56, // pointer to a fat pointer we keep elsewhere that points to the original data
string_list: u56, // pointer to a fat pointer
list_list: (u24, UnalignedU32), // length estimate + tape index to the end of the list
compound_list: (u24, UnalignedU32), // length estimate + tape index to the end of the list
int_array_list: u56, // pointer to a fat pointer
long_array_list: u56, // pointer to a fat pointer
pub empty_list: (),
pub byte_list: u56, // pointer to the original data
pub short_list: u56, // pointer to the original data
pub int_list: u56, // pointer to the original data
pub long_list: u56, // pointer to the original data
pub float_list: u56, // pointer to the original data
pub double_list: u56, // pointer to the original data
pub byte_array_list: (u24, UnalignedU32), // padding + index to ExtraTapes which has a fat pointer that points to the original data
pub string_list: (u24, UnalignedU32), // padding + index to ExtraTapes
pub list_list: (u24, UnalignedU32), // length estimate + tape index offset to the end of the list
pub compound_list: (u24, UnalignedU32), // length estimate + tape index offset to the end of the list
pub int_array_list: (u24, UnalignedU32), // padding + index to ExtraTapes
pub long_array_list: (u24, UnalignedU32), // padding + index to ExtraTapes
}
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
#[allow(non_camel_case_types)]
pub struct u56 {
a: u8,
b: u16,
c: u32,
}
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
pub struct u48 {
a: u16,
b: u32,
impl From<u64> for u56 {
#[inline]
fn from(value: u64) -> Self {
let a = (value >> 48) as u8;
let b = (value >> 32) as u16;
let c = value as u32;
Self { a, b, c }
}
}
impl From<u56> for u64 {
#[inline]
fn from(value: u56) -> Self {
let a = value.a as u64;
let b = value.b as u64;
let c = value.c as u64;
(a << 48) | (b << 32) | c
}
}
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
#[allow(non_camel_case_types)]
pub struct u24 {
a: u8,
b: u16,
}
impl From<u32> for u24 {
#[inline]
fn from(value: u32) -> Self {
let a = (value >> 16) as u8;
let b = value as u16;
Self { a, b }
}
}
impl From<u24> for u32 {
#[inline]
fn from(value: u24) -> Self {
let a = value.a as u32;
let b = value.b as u32;
(a << 16) | b
}
}
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
pub struct UnalignedU32(pub u32);
impl From<u32> for UnalignedU32 {
#[inline]
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<UnalignedU32> for u32 {
#[inline]
fn from(value: UnalignedU32) -> Self {
value.0
}
}
#[derive(Clone, Copy)]
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
pub struct UnalignedU16(pub u16);
impl From<u16> for UnalignedU16 {
#[inline]
fn from(value: u16) -> Self {
Self(value)
}
}
impl From<UnalignedU16> for u16 {
#[inline]
fn from(value: UnalignedU16) -> Self {
value.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
enum TapeTagKind {
pub enum TapeTagKind {
Byte,
Short,
Int,
@ -93,3 +204,42 @@ enum TapeTagKind {
IntArrayList,
LongArrayList,
}
impl TapeTagKind {
pub fn is_list(&self) -> bool {
matches!(
self,
TapeTagKind::EmptyList
| TapeTagKind::ByteList
| TapeTagKind::ShortList
| TapeTagKind::IntList
| TapeTagKind::LongList
| TapeTagKind::FloatList
| TapeTagKind::DoubleList
| TapeTagKind::ByteArrayList
| TapeTagKind::StringList
| TapeTagKind::ListList
| TapeTagKind::CompoundList
| TapeTagKind::IntArrayList
| TapeTagKind::LongArrayList
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_u56() {
// top 8 bits are cut off
let value = 0x1234_5678_9abc_def0;
let u56 { a, b, c } = u56::from(value);
assert_eq!(a, 0x34);
assert_eq!(b, 0x5678);
assert_eq!(c, 0x9abc_def0);
let value: u64 = u56 { a, b, c }.into();
assert_eq!(value, 0x34_5678_9abc_def0);
}
}

View file

@ -66,6 +66,23 @@ pub fn read_with_u16_length<'a>(
Ok(&data.get_ref()[start_position..start_position + length_in_bytes])
}
pub fn skip_string<'a>(data: &mut Cursor<&'a [u8]>) -> Result<(), Error> {
let remaining_slice = &data.get_ref()[data.position() as usize..data.get_ref().len()];
if remaining_slice.len() < 2 {
return Err(Error::UnexpectedEof);
}
let length = u16::from_be_bytes([remaining_slice[0], remaining_slice[1]]);
let length_in_bytes = length as usize;
// make sure we don't read more than the length
if data.get_ref().len() < data.position() as usize + length_in_bytes + 2 {
return Err(Error::UnexpectedEof);
}
data.set_position(data.position() + 2 + length_in_bytes as u64);
Ok(())
}
#[inline(never)]
pub fn read_with_u32_length<'a>(
data: &mut Cursor<&'a [u8]>,

View file

@ -4,7 +4,7 @@ use crate::swap_endianness::{swap_endianness, swap_endianness_as_u8, SwappableNu
/// A list of numbers that's kept as big-endian in memory.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct RawList<'a, T> {
data: &'a [u8],
_marker: PhantomData<T>,
@ -24,6 +24,10 @@ impl<'a, T> RawList<'a, T> {
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn into_inner(self) -> &'a [u8] {
self.data
}
}
impl<T: SwappableNumber> RawList<'_, T> {

View file

@ -4,10 +4,10 @@ use crate::DeserializeError;
pub trait Deserialize: Sized {
fn from_nbt(nbt: &crate::borrow::BaseNbt) -> Result<Self, DeserializeError> {
Self::from_compound(nbt)
Self::from_compound(nbt.compound())
}
fn from_compound(compound: &crate::borrow::NbtCompound) -> Result<Self, DeserializeError>;
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError>;
}
pub trait Serialize: Sized {
@ -19,9 +19,9 @@ pub trait Serialize: Sized {
}
pub trait FromNbtTag: Sized {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self>;
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self>;
fn from_optional_nbt_tag(
tag: Option<&crate::borrow::NbtTag>,
tag: Option<crate::borrow::NbtTag>,
) -> Result<Option<Self>, DeserializeError> {
match tag {
Some(tag) => Ok(Self::from_nbt_tag(tag)),
@ -38,7 +38,7 @@ pub trait ToNbtTag: Sized {
}
impl<K: Display + FromStr + Eq + Hash, V: FromNbtTag> Deserialize for HashMap<K, V> {
fn from_compound(compound: &crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
let mut hashmap = HashMap::with_capacity(compound.len());
for (k, v) in compound.iter() {
@ -70,7 +70,7 @@ impl<K: Display + FromStr + Eq + Hash, V: ToNbtTag> Serialize for HashMap<K, V>
}
impl Deserialize for crate::owned::NbtCompound {
fn from_compound(compound: &crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
Ok(compound.to_owned())
}
}
@ -81,7 +81,7 @@ impl Serialize for crate::owned::NbtCompound {
}
impl<T: Deserialize> FromNbtTag for T {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.compound().and_then(|c| Self::from_compound(c).ok())
}
}
@ -93,7 +93,7 @@ impl<T: Serialize> ToNbtTag for T {
}
impl FromNbtTag for crate::owned::NbtTag {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
Some(tag.to_owned())
}
}
@ -105,7 +105,7 @@ impl ToNbtTag for crate::owned::NbtTag {
// standard nbt types
impl FromNbtTag for i8 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.byte()
}
}
@ -116,7 +116,7 @@ impl ToNbtTag for i8 {
}
impl FromNbtTag for i16 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.short()
}
}
@ -127,7 +127,7 @@ impl ToNbtTag for i16 {
}
impl FromNbtTag for i32 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.int()
}
}
@ -138,7 +138,7 @@ impl ToNbtTag for i32 {
}
impl FromNbtTag for i64 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.long()
}
}
@ -149,7 +149,7 @@ impl ToNbtTag for i64 {
}
impl FromNbtTag for f32 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.float()
}
}
@ -160,7 +160,7 @@ impl ToNbtTag for f32 {
}
impl FromNbtTag for f64 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.double()
}
}
@ -171,7 +171,7 @@ impl ToNbtTag for f64 {
}
impl FromNbtTag for String {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.string().map(|s| s.to_string())
}
}
@ -189,7 +189,7 @@ impl ToNbtTag for &str {
// unsigned integers
impl FromNbtTag for u8 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.byte().map(|b| b as u8)
}
}
@ -200,7 +200,7 @@ impl ToNbtTag for u8 {
}
impl FromNbtTag for u16 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.short().map(|s| s as u16)
}
}
@ -211,7 +211,7 @@ impl ToNbtTag for u16 {
}
impl FromNbtTag for u32 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.int().map(|i| i as u32)
}
}
@ -222,7 +222,7 @@ impl ToNbtTag for u32 {
}
impl FromNbtTag for u64 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.long().map(|l| l as u64)
}
}
@ -234,7 +234,7 @@ impl ToNbtTag for u64 {
// lists
impl FromNbtTag for Vec<String> {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
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())
@ -251,11 +251,11 @@ impl ToNbtTag for Vec<String> {
// slightly less standard types
impl<T: FromNbtTag> FromNbtTag for Option<T> {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
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>,
tag: Option<crate::borrow::NbtTag>,
) -> Result<Option<Self>, DeserializeError> {
match tag {
Some(tag) => Ok(Some(T::from_nbt_tag(tag))),
@ -274,8 +274,9 @@ impl<T: ToNbtTag> ToNbtTag for Option<T> {
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()?.compounds()?;
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.len());
for tag in list {
if tag.is_empty() {
@ -302,8 +303,9 @@ impl<T: Serialize> ToNbtTag for Vec<Option<T>> {
}
impl<T: Deserialize> FromNbtTag for Vec<T> {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
let list = tag.list()?.compounds()?;
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.len());
for tag in list {
vec.push(T::from_compound(tag).ok()?);
@ -321,7 +323,7 @@ impl<T: Serialize> ToNbtTag for Vec<T> {
}
impl FromNbtTag for bool {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.byte().map(|b| b != 0)
}
}