diff --git a/simdnbt/README.md b/simdnbt/README.md index baf944b..aa14ea9 100644 --- a/simdnbt/README.md +++ b/simdnbt/README.md @@ -18,10 +18,9 @@ The difference is that the "borrow" variant requires you to keep a reference to ```rust,no_run use std::borrow::Cow; use std::io::Cursor; -use simdnbt::borrow::Nbt; fn example(item_bytes: &[u8]) { - let nbt = Nbt::read(&mut Cursor::new(item_bytes)) + let nbt = simdnbt::borrow::read(&mut Cursor::new(item_bytes)) .unwrap() .unwrap(); let skyblock_id: Cow = nbt diff --git a/simdnbt/benches/compare.rs b/simdnbt/benches/compare.rs index 206983c..59cf87e 100644 --- a/simdnbt/benches/compare.rs +++ b/simdnbt/benches/compare.rs @@ -27,13 +27,13 @@ fn bench_read_file(filename: &str, c: &mut Criterion) { group.bench_function("simdnbt_borrow_parse", |b| { b.iter(|| { - black_box(simdnbt::borrow::Nbt::read(&mut input_stream).unwrap()); + black_box(simdnbt::borrow::read(&mut input_stream).unwrap()); input_stream.set_position(0); }) }); group.bench_function("simdnbt_owned_parse", |b| { b.iter(|| { - black_box(simdnbt::owned::Nbt::read(&mut input_stream).unwrap()); + black_box(simdnbt::owned::read(&mut input_stream).unwrap()); input_stream.set_position(0); }) }); @@ -84,7 +84,7 @@ fn bench_read_file(filename: &str, c: &mut Criterion) { }) }); - let nbt = simdnbt::borrow::Nbt::read(&mut Cursor::new(&input)) + let nbt = simdnbt::borrow::read(&mut Cursor::new(&input)) .unwrap() .unwrap(); group.bench_function("simdnbt_borrow_write", |b| { @@ -95,7 +95,7 @@ fn bench_read_file(filename: &str, c: &mut Criterion) { }) }); - let nbt = simdnbt::owned::Nbt::read(&mut Cursor::new(&input)) + let nbt = simdnbt::owned::read(&mut Cursor::new(&input)) .unwrap() .unwrap(); group.bench_function("simdnbt_owned_write", |b| { diff --git a/simdnbt/benches/compare_realworld.rs b/simdnbt/benches/compare_realworld.rs index d93771b..bf7ef77 100644 --- a/simdnbt/benches/compare_realworld.rs +++ b/simdnbt/benches/compare_realworld.rs @@ -33,7 +33,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) { graphite_items_from_nbt(graphite_binary::nbt::decode::read(&mut &input[..]).unwrap()) .unwrap(); let simdnbt_nbt = simdnbt_items_from_nbt( - simdnbt::borrow::Nbt::read(&mut Cursor::new(input)) + simdnbt::borrow::read(&mut Cursor::new(input)) .unwrap() .unwrap(), ) @@ -63,7 +63,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) { group.bench_function("simdnbt_parse", |b| { b.iter(|| { let input = black_box(input); - let nbt = black_box(simdnbt::borrow::Nbt::read(&mut Cursor::new(input))); + let nbt = black_box(simdnbt::borrow::read(&mut Cursor::new(input))); let nbt = nbt.unwrap().unwrap(); black_box(simdnbt_items_from_nbt(nbt)); }) diff --git a/simdnbt/benches/nbt_borrow.rs b/simdnbt/benches/nbt_borrow.rs index ed506e0..0d731c8 100644 --- a/simdnbt/benches/nbt_borrow.rs +++ b/simdnbt/benches/nbt_borrow.rs @@ -27,14 +27,12 @@ fn bench_file(filename: &str, c: &mut Criterion) { group.bench_function("Decode", |b| { b.iter(|| { - black_box(simdnbt::borrow::Nbt::read(&mut input_stream).unwrap()); + black_box(simdnbt::borrow::read(&mut input_stream).unwrap()); input_stream.set_position(0); }) }); - let nbt = simdnbt::borrow::Nbt::read(&mut input_stream) - .unwrap() - .unwrap(); + let nbt = simdnbt::borrow::read(&mut input_stream).unwrap().unwrap(); group.bench_function("Get", |b| { b.iter(|| { let level = nbt.compound("abilities").unwrap(); diff --git a/simdnbt/benches/nbt_owned.rs b/simdnbt/benches/nbt_owned.rs index 3e46f5f..23790a0 100644 --- a/simdnbt/benches/nbt_owned.rs +++ b/simdnbt/benches/nbt_owned.rs @@ -27,12 +27,12 @@ fn bench_file(filename: &str, c: &mut Criterion) { group.bench_function("Decode", |b| { b.iter(|| { - black_box(simdnbt::owned::Nbt::read(&mut decoded_src_stream).unwrap()); + black_box(simdnbt::owned::read(&mut decoded_src_stream).unwrap()); decoded_src_stream.set_position(0); }) }); - let nbt = simdnbt::owned::Nbt::read(&mut decoded_src_stream) + let nbt = simdnbt::owned::read(&mut decoded_src_stream) .unwrap() .unwrap(); group.bench_function("Get", |b| { diff --git a/simdnbt/examples/hypixel.rs b/simdnbt/examples/hypixel.rs index 63a5772..2975f7a 100644 --- a/simdnbt/examples/hypixel.rs +++ b/simdnbt/examples/hypixel.rs @@ -68,7 +68,7 @@ fn main() { let input = black_box(include_bytes!("../tests/realworld.nbt")); for _ in 0..1 { - let nbt = simdnbt::borrow::Nbt::read(&mut Cursor::new(input)); + let nbt = simdnbt::borrow::read(&mut Cursor::new(input)); let nbt = black_box(nbt.unwrap().unwrap()); let data = Base::from_nbt(&nbt).unwrap(); @@ -76,7 +76,7 @@ fn main() { // roundtrip let mut new_nbt_bytes = Vec::new(); data.clone().to_nbt().write(&mut new_nbt_bytes); - let new_nbt = simdnbt::borrow::Nbt::read(&mut Cursor::new(&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(); diff --git a/simdnbt/examples/hypixel_no_derive.rs b/simdnbt/examples/hypixel_no_derive.rs index 446cbe1..35e6ea1 100644 --- a/simdnbt/examples/hypixel_no_derive.rs +++ b/simdnbt/examples/hypixel_no_derive.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, hint::black_box, io::Cursor}; -use simdnbt::borrow::{BaseNbt, Nbt}; +use simdnbt::borrow::BaseNbt; #[derive(Clone, PartialEq, Debug)] pub struct Item { @@ -103,7 +103,7 @@ fn main() { let input = black_box(include_bytes!("../tests/realworld.nbt")); for _ in 0..1 { - let nbt = Nbt::read(&mut Cursor::new(input)); + let nbt = simdnbt::borrow::read(&mut Cursor::new(input)); let nbt = black_box(nbt.unwrap().unwrap()); black_box(items_from_nbt(nbt)); } diff --git a/simdnbt/examples/read_and_write.rs b/simdnbt/examples/read_and_write.rs index 3acf837..61d8016 100644 --- a/simdnbt/examples/read_and_write.rs +++ b/simdnbt/examples/read_and_write.rs @@ -10,7 +10,7 @@ fn main() { } let input = input.as_slice(); - let nbt = simdnbt::owned::Nbt::read(&mut Cursor::new(input)) + let nbt = simdnbt::owned::read(&mut Cursor::new(input)) .unwrap() .unwrap(); diff --git a/simdnbt/src/borrow/compound.rs b/simdnbt/src/borrow/compound.rs index ed93b70..e3103f3 100644 --- a/simdnbt/src/borrow/compound.rs +++ b/simdnbt/src/borrow/compound.rs @@ -19,11 +19,18 @@ pub struct NbtCompound<'a> { } impl<'a> NbtCompound<'a> { - pub fn read(data: &mut Cursor<&'a [u8]>, alloc: &TagAllocator<'a>) -> Result { + /// # Safety + /// The given TagAllocator must be valid for the lifetime of all the tags in this NBT. + pub(crate) unsafe fn read( + data: &mut Cursor<&'a [u8]>, + alloc: &TagAllocator<'a>, + ) -> Result { Self::read_with_depth(data, alloc, 0, 0) } - pub fn read_with_depth( + /// # Safety + /// The given TagAllocator must be valid for the lifetime of all the tags in this NBT. + pub(crate) unsafe fn read_with_depth( data: &mut Cursor<&'a [u8]>, alloc: &TagAllocator<'a>, compound_depth: usize, diff --git a/simdnbt/src/borrow/list.rs b/simdnbt/src/borrow/list.rs index 788a916..f87df8c 100644 --- a/simdnbt/src/borrow/list.rs +++ b/simdnbt/src/borrow/list.rs @@ -35,7 +35,9 @@ pub enum NbtList<'a> { LongArray(&'a [RawList<'a, i64>]) = LONG_ARRAY_ID, } impl<'a> NbtList<'a> { - pub fn read( + /// # Safety + /// The given TagAllocator must be valid for the lifetime of all the tags in this NBT. + pub(crate) unsafe fn read( data: &mut Cursor<&'a [u8]>, alloc: &TagAllocator<'a>, compound_depth: usize, @@ -105,12 +107,10 @@ impl<'a> NbtList<'a> { let length = read_u32(data)?; let mut tags = alloc.get().unnamed_compound.start(list_depth); for _ in 0..length { - let tag = match NbtCompound::read_with_depth( - data, - alloc, - compound_depth + 1, - list_depth, - ) { + let tag_res = unsafe { + NbtCompound::read_with_depth(data, alloc, compound_depth + 1, list_depth) + }; + let tag = match tag_res { Ok(tag) => tag, Err(e) => { alloc.get().unnamed_compound.finish(tags, list_depth); diff --git a/simdnbt/src/borrow/mod.rs b/simdnbt/src/borrow/mod.rs index 14dc238..d844ab6 100644 --- a/simdnbt/src/borrow/mod.rs +++ b/simdnbt/src/borrow/mod.rs @@ -21,6 +21,51 @@ use crate::{ use self::tag_alloc::TagAllocator; pub use self::{compound::NbtCompound, list::NbtList}; +/// Read a normal root NBT compound. This is either empty or has a name and compound tag. +/// +/// Returns `Ok(Nbt::None)` if there is no data. +pub fn read<'a>(data: &mut Cursor<&'a [u8]>) -> Result, Error> { + Nbt::read(data) +} +/// 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 returns an [`Nbt`] instead (guaranteeing it'll be either +/// empty or a compound). +pub fn read_unnamed<'a>(data: &mut Cursor<&'a [u8]>) -> Result, Error> { + Nbt::read_unnamed(data) +} +/// Read a compound tag. This may have any number of items. +pub fn read_compound<'a>(data: &mut Cursor<&'a [u8]>) -> Result, Error> { + let tag_alloc = TagAllocator::new(); + let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?; + Ok(BaseNbtCompound { + tag, + _tag_alloc: tag_alloc, + }) +} +/// 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, Error> { + let tag_alloc = TagAllocator::new(); + let tag = unsafe { NbtTag::read(data, &tag_alloc) }?; + Ok(BaseNbtTag { + tag, + _tag_alloc: tag_alloc, + }) +} +/// 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>, 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, + })) +} + /// A complete NBT container. This contains a name and a compound tag. #[derive(Debug)] pub struct BaseNbt<'a> { @@ -30,6 +75,20 @@ pub struct BaseNbt<'a> { _tag_alloc: TagAllocator<'a>, } +/// 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>, +} + +/// A nameless NBT tag. +pub struct BaseNbtTag<'a> { + tag: NbtTag<'a>, + _tag_alloc: TagAllocator<'a>, +} + +/// Either a complete NBT container, or nothing. #[derive(Debug, PartialEq, Default)] pub enum Nbt<'a> { Some(BaseNbt<'a>), @@ -38,8 +97,8 @@ pub enum Nbt<'a> { } impl<'a> Nbt<'a> { - /// Reads NBT from the given data. Returns `Ok(None)` if there is no data. - pub fn read(data: &mut Cursor<&'a [u8]>) -> Result, Error> { + /// Reads NBT from the given data. Returns `Ok(Nbt::None)` if there is no data. + fn read(data: &mut Cursor<&'a [u8]>) -> Result, Error> { let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?; if root_type == END_ID { return Ok(Nbt::None); @@ -50,7 +109,7 @@ impl<'a> Nbt<'a> { let tag_alloc = TagAllocator::new(); let name = read_string(data)?; - let tag = NbtCompound::read_with_depth(data, &tag_alloc, 0, 0)?; + let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?; Ok(Nbt::Some(BaseNbt { name, @@ -59,6 +118,25 @@ impl<'a> Nbt<'a> { })) } + fn read_unnamed(data: &mut Cursor<&'a [u8]>) -> Result, Error> { + let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?; + if root_type == END_ID { + return Ok(Nbt::None); + } + if root_type != COMPOUND_ID { + return Err(Error::InvalidRootType(root_type)); + } + let tag_alloc = TagAllocator::new(); + + let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?; + + Ok(Nbt::Some(BaseNbt { + name: Mutf8Str::from_slice(&[]), + tag, + _tag_alloc: tag_alloc, + })) + } + pub fn write(&self, data: &mut Vec) { match self { Nbt::Some(nbt) => nbt.write(data), @@ -109,6 +187,20 @@ impl<'a> Deref for BaseNbt<'a> { &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 + } +} impl<'a> BaseNbt<'a> { pub fn write(&self, data: &mut Vec) { @@ -135,7 +227,7 @@ pub enum NbtTag<'a> { IntArray(RawList<'a, i32>), LongArray(RawList<'a, i64>), } -impl<'a> NbtTag<'a> { +impl<'a, 'b> NbtTag<'a> { /// Get the numerical ID of the tag type. #[inline] pub fn id(&self) -> u8 { @@ -155,8 +247,10 @@ impl<'a> NbtTag<'a> { } } + /// # Safety + /// The given TagAllocator must be valid for the lifetime of all the tags in this NBT. #[inline(always)] - fn read_with_type( + unsafe fn read_with_type( data: &mut Cursor<&'a [u8]>, alloc: &TagAllocator<'a>, tag_type: u8, @@ -202,12 +296,16 @@ impl<'a> NbtTag<'a> { } } - pub fn read(data: &mut Cursor<&'a [u8]>, alloc: &TagAllocator<'a>) -> Result { + /// # 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 { let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?; Self::read_with_type(data, alloc, tag_type, 0, 0) } - pub fn read_optional( + /// # Safety + /// The given TagAllocator must be valid for the lifetime of all the tags in this NBT. + unsafe fn read_optional( data: &mut Cursor<&'a [u8]>, alloc: &TagAllocator<'a>, ) -> Result, Error> { @@ -476,4 +574,24 @@ mod tests { let res = Nbt::read(&mut Cursor::new(&data)); assert_eq!(res, Err(Error::UnexpectedEof)); } + + #[test] + fn read_complexplayer_with_given_alloc() { + 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); + + let nbt = super::read_tag(&mut Cursor::new(&decoded_src_as_tag)).unwrap(); + let nbt = nbt.compound().unwrap().compound("").unwrap(); + + assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2); + assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2); + } } diff --git a/simdnbt/src/borrow/tag_alloc.rs b/simdnbt/src/borrow/tag_alloc.rs index 2d7768c..6567d59 100644 --- a/simdnbt/src/borrow/tag_alloc.rs +++ b/simdnbt/src/borrow/tag_alloc.rs @@ -29,6 +29,8 @@ 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>); impl<'a> TagAllocator<'a> { @@ -235,21 +237,6 @@ impl ContiguousTagsAllocator { self.alloc.len = self.size; } - #[inline] - pub fn extend_from_slice(&mut self, slice: &[T]) { - while self.alloc.len + slice.len() > self.alloc.cap { - self.grow(); - } - - // copy the slice - unsafe { - let end = self.alloc.ptr.as_ptr().add(self.alloc.len); - std::ptr::copy_nonoverlapping(slice.as_ptr(), end, slice.len()); - } - self.alloc.len += slice.len(); - self.size += slice.len(); - } - #[inline] pub fn push(&mut self, value: T) { // check if we need to reallocate diff --git a/simdnbt/src/mutf8.rs b/simdnbt/src/mutf8.rs index c620959..8ad665b 100644 --- a/simdnbt/src/mutf8.rs +++ b/simdnbt/src/mutf8.rs @@ -163,6 +163,10 @@ impl Borrow for Mutf8String { } impl Mutf8String { + pub fn new() -> Self { + Self { vec: Vec::new() } + } + #[inline] pub fn as_str(&self) -> &Mutf8Str { Mutf8Str::from_slice(self.vec.as_slice()) diff --git a/simdnbt/src/owned/compound.rs b/simdnbt/src/owned/compound.rs index e5166d1..883773d 100644 --- a/simdnbt/src/owned/compound.rs +++ b/simdnbt/src/owned/compound.rs @@ -28,11 +28,11 @@ impl NbtCompound { Self { values } } - pub fn read(data: &mut Cursor<&[u8]>) -> Result { + pub(crate) fn read(data: &mut Cursor<&[u8]>) -> Result { Self::read_with_depth(data, 0) } - pub fn read_with_depth_and_capacity( + pub(crate) fn read_with_depth_and_capacity( data: &mut Cursor<&[u8]>, depth: usize, capacity: usize, @@ -73,7 +73,7 @@ impl NbtCompound { Ok(Self { values }) } - pub fn read_with_depth(data: &mut Cursor<&[u8]>, depth: usize) -> Result { + pub(crate) fn read_with_depth(data: &mut Cursor<&[u8]>, depth: usize) -> Result { Self::read_with_depth_and_capacity(data, depth, 8) } diff --git a/simdnbt/src/owned/list.rs b/simdnbt/src/owned/list.rs index 02b9a27..a92e26b 100644 --- a/simdnbt/src/owned/list.rs +++ b/simdnbt/src/owned/list.rs @@ -37,7 +37,7 @@ pub enum NbtList { LongArray(Vec>) = LONG_ARRAY_ID, } impl NbtList { - pub fn read(data: &mut Cursor<&[u8]>, depth: usize) -> Result { + pub(crate) fn read(data: &mut Cursor<&[u8]>, depth: usize) -> Result { if depth > MAX_DEPTH { return Err(Error::MaxDepthExceeded); } diff --git a/simdnbt/src/owned/mod.rs b/simdnbt/src/owned/mod.rs index 42a0960..f226c2b 100644 --- a/simdnbt/src/owned/mod.rs +++ b/simdnbt/src/owned/mod.rs @@ -1,4 +1,5 @@ -//! The owned variant of NBT. This is useful if you're writing data from scratch or if you can't keep a reference to the original data. +//! The owned variant of NBT. This is useful if you're writing NBT or if you can't keep a reference +//! to the original data. mod compound; mod list; @@ -20,6 +21,36 @@ use crate::{ pub use self::{compound::NbtCompound, list::NbtList}; +/// Read a normal root NBT compound. This is either empty or has a name and compound tag. +/// +/// Returns `Ok(Nbt::None)` if there is no data. +pub fn read(data: &mut Cursor<&[u8]>) -> Result { + Nbt::read(data) +} +/// 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 returns an [`Nbt`] instead (guaranteeing it'll be either +/// empty or a compound). +pub fn read_unnamed(data: &mut Cursor<&[u8]>) -> Result { + Nbt::read_unnamed(data) +} +/// Read a compound tag. This may have any number of items. +pub fn read_compound(data: &mut Cursor<&[u8]>) -> Result { + NbtCompound::read(data) +} +/// 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 { + NbtTag::read(data) +} +/// 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(data: &mut Cursor<&[u8]>) -> Result, Error> { + Ok(NbtTag::read_optional(data)?) +} + /// A complete NBT container. This contains a name and a compound tag. #[derive(Debug, Clone, PartialEq, Default)] pub struct BaseNbt { @@ -39,8 +70,8 @@ impl Nbt { Self::Some(BaseNbt { name, tag }) } - /// Reads NBT from the given data. Returns `Ok(None)` if there is no data. - pub fn read(data: &mut Cursor<&[u8]>) -> Result { + /// Reads NBT from the given data. Returns `Ok(Nbt::None)` if there is no data. + fn read(data: &mut Cursor<&[u8]>) -> Result { let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?; if root_type == END_ID { return Ok(Nbt::None); @@ -49,12 +80,12 @@ impl Nbt { return Err(Error::InvalidRootType(root_type)); } let name = read_string(data)?.to_owned(); - let tag = NbtCompound::read_with_depth(data, 0)?; + let tag = NbtCompound::read(data)?; Ok(Nbt::Some(BaseNbt { name, tag })) } - pub fn read_unnamed(data: &mut Cursor<&[u8]>) -> Result { + fn read_unnamed(data: &mut Cursor<&[u8]>) -> Result { let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?; if root_type == END_ID { return Ok(Nbt::None); @@ -62,7 +93,7 @@ impl Nbt { if root_type != COMPOUND_ID { return Err(Error::InvalidRootType(root_type)); } - let tag = NbtCompound::read_with_depth(data, 0)?; + let tag = NbtCompound::read(data)?; Ok(Nbt::Some(BaseNbt { name: Mutf8String::from(""),