use crate::utils::take; use crate::{DeserializeErr, DeserializeResult, Deserialized}; use alloc::{string::{String, ToString}, borrow::ToOwned, fmt, vec::Vec, vec, format}; #[cfg(all(test, feature = "std"))] use crate::protocol::TestRandom; use crate::byte_order::{ProtoByteOrder, ByteOrder}; #[derive(Clone, Debug, PartialEq)] pub struct NamedTag { pub name: String, pub payload: Tag, } impl NamedTag { pub fn root_compound_tag_from_bytes(data: &[u8]) -> DeserializeResult { read_nbt_data(data) } pub fn is_end(&self) -> bool { match self.payload { Tag::End => true, _ => false, } } } #[cfg(all(test, feature = "std"))] impl TestRandom for NamedTag { fn test_gen_random() -> Self { Self { name: "".to_owned(), payload: Tag::Compound(vec![Self { name: String::test_gen_random(), payload: Tag::test_gen_random(), }]), } } } impl fmt::Display for NamedTag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!( "TAG_{}('{}'): ", self.payload.tag_type_name(), self.name ))?; self.payload.write_contents(f) } } #[derive(Clone, Debug, PartialEq)] pub enum Tag { Byte(i8), Short(i16), Int(i32), Long(i64), Float(f32), Double(f64), ByteArray(Vec), String(String), List(Vec), Compound(Vec), IntArray(Vec), LongArray(Vec), End, } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("TAG_{}: ", self.tag_type_name()))?; self.write_contents(f) } } impl Tag { pub fn with_name(self, name: &str) -> NamedTag { NamedTag { name: name.into(), payload: self, } } pub fn tag_type_name(&self) -> &str { match self { Tag::Byte(_) => "Byte", Tag::Short(_) => "Short", Tag::Int(_) => "Int", Tag::Long(_) => "Long", Tag::Float(_) => "Float", Tag::Double(_) => "Double", Tag::ByteArray(_) => "Byte_Array", Tag::String(_) => "String", Tag::List(_) => "List", Tag::Compound(_) => "Compound", Tag::IntArray(_) => "Int_Array", Tag::LongArray(_) => "Long_Array", Tag::End => "END", } } fn write_contents(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Tag::Byte(v) => f.write_fmt(format_args!("{}", *v)), Tag::Short(v) => f.write_fmt(format_args!("{}", *v)), Tag::Int(v) => f.write_fmt(format_args!("{}", *v)), Tag::Long(v) => f.write_fmt(format_args!("{}L", *v)), Tag::Float(v) => f.write_fmt(format_args!("{}", *v)), Tag::Double(v) => f.write_fmt(format_args!("{}", *v)), Tag::ByteArray(v) => f.write_fmt(format_args!("[{} bytes]", v.len())), Tag::String(v) => f.write_fmt(format_args!("\"{}\"", v)), Tag::List(v) => { let out = write_contents(v); f.write_str(out.as_str()) } Tag::Compound(v) => { let out = write_contents(v); f.write_str(out.as_str()) } Tag::IntArray(v) => f.write_fmt(format_args!("[{} ints]", v.len())), Tag::LongArray(v) => f.write_fmt(format_args!("[{} longs]", v.len())), Tag::End => f.write_str("END"), } } } #[cfg(all(test, feature = "std"))] impl TestRandom for Tag { fn test_gen_random() -> Self { let random_idx = rand::random::() % 8; match random_idx { 0 => Tag::Byte(i8::test_gen_random()), 1 => Tag::Short(i16::test_gen_random()), 2 => Tag::Int(i32::test_gen_random()), 3 => Tag::Long(i64::test_gen_random()), 4 => Tag::Float(f32::test_gen_random()), 5 => Tag::Double(f64::test_gen_random()), 6 => Tag::String(String::test_gen_random()), 7 => Tag::List({ let count = rand::random::() % 256; let mut out = Vec::with_capacity(count); let random_idx = rand::random::() % 6; for _ in 0..count { out.push(match random_idx { 0 => Tag::Byte(i8::test_gen_random()), 1 => Tag::Short(i16::test_gen_random()), 2 => Tag::Int(i32::test_gen_random()), 3 => Tag::Long(i64::test_gen_random()), 4 => Tag::Float(f32::test_gen_random()), 5 => Tag::Double(f64::test_gen_random()), 6 => Tag::String(String::test_gen_random()), other => panic!("impossible {}", other), }); } out }), 8 => Tag::Compound({ let count = rand::random::() % 256; let mut out = Vec::with_capacity(count); for _ in 0..count { out.push(NamedTag::test_gen_random()); } out }), other => panic!("impossible {}", other), } } } fn write_contents(contents: &Vec) -> String where F: fmt::Display, { format!( "{} entries\n{{\n{}\n}}", contents.len(), contents .iter() .flat_map(move |elem| elem .to_string() .split("\n") .map(String::from) .collect::>()) .map(move |line| " ".to_owned() + line.as_str()) .collect::>() .join("\n") ) } // deserialization first // reads from the root level fn read_nbt_data(data: &[u8]) -> DeserializeResult { let Deserialized { value: tag_type_id, data: _ } = ProtoByteOrder::read_ubyte(data)?; match tag_type_id { 0x0A => read_named_tag(data), other => Err(DeserializeErr::NbtInvalidStartTag(other)), } } // reads any named tag: read id -> read name -> read tag with id -> name tag with name pub fn read_named_tag(data: &[u8]) -> DeserializeResult { let Deserialized { value: tag_type_id, data } = ProtoByteOrder::read_ubyte(data)?; if tag_type_id == 0x00 { // tag end Deserialized::ok(Tag::End.with_name(""), data) } else { let Deserialized { value: name, data } = read_string(data)?; Ok(read_tag(tag_type_id, data)?.map(move |payload| NamedTag { name, payload })) } } // reads any tag (given it's id) pub fn read_tag(tag_type_id: u8, data: &[u8]) -> DeserializeResult { match tag_type_id { 0x00 => Deserialized::ok(Tag::End, data), 0x01 => read_tag_byte(data), 0x02 => read_tag_short(data), 0x03 => read_tag_int(data), 0x04 => read_tag_long(data), 0x05 => read_tag_float(data), 0x06 => read_tag_double(data), 0x07 => read_tag_byte_array(data), 0x08 => read_tag_string(data), 0x09 => read_tag_list(data), 0x0A => read_tag_compound(data), 0x0B => read_tag_int_array(data), 0x0C => read_tag_long_array(data), other => Err(DeserializeErr::NbtUnknownTagType(other)), } } fn read_tag_byte(data: &[u8]) -> DeserializeResult { Ok(ProtoByteOrder::read_byte(data)?.map(Tag::Byte)) } fn read_tag_short(data: &[u8]) -> DeserializeResult { Ok(ProtoByteOrder::read_short(data)?.map(Tag::Short)) } fn read_tag_int(data: &[u8]) -> DeserializeResult { Ok(ProtoByteOrder::read_int(data)?.map(Tag::Int)) } fn read_tag_long(data: &[u8]) -> DeserializeResult { Ok(ProtoByteOrder::read_long(data)?.map(Tag::Long)) } fn read_tag_float(data: &[u8]) -> DeserializeResult { Ok(ProtoByteOrder::read_float(data)?.map(Tag::Float)) } fn read_tag_double(data: &[u8]) -> DeserializeResult { Ok(ProtoByteOrder::read_double(data)?.map(Tag::Double)) } fn read_tag_byte_array(data: &[u8]) -> DeserializeResult { Ok(ProtoByteOrder::read_int(data)? .and_then(move |size, rest| take(size as usize, rest))? .map(move |arr| Tag::ByteArray(Vec::from(arr)))) } fn read_tag_string(data: &[u8]) -> DeserializeResult { Ok(read_string(data)?.map(move |str| Tag::String(str))) } fn read_tag_list(data: &[u8]) -> DeserializeResult { let Deserialized { value: contents_tag_type_id, data } = ProtoByteOrder::read_ubyte(data)?; let Deserialized { value: list_length, data } = ProtoByteOrder::read_int(data)?; if list_length == 0 { Deserialized::ok(Tag::List(vec![]), data) } else { let mut out_vec = Vec::with_capacity(list_length as usize); let mut remaining_data = data; for _ in 0..list_length { let Deserialized { value: element, data: rest } = read_tag(contents_tag_type_id, &remaining_data)?; out_vec.push(element); remaining_data = rest; } Deserialized::ok(Tag::List(out_vec), remaining_data) } } fn read_tag_compound(data: &[u8]) -> DeserializeResult { let mut out = Vec::new(); let mut remaining_data = data; loop { let Deserialized { value: elem, data: rest, } = read_named_tag(remaining_data)?; remaining_data = rest; if elem.is_end() { break; } out.push(elem); } Deserialized::ok(Tag::Compound(out), remaining_data) } fn read_tag_int_array(data: &[u8]) -> DeserializeResult { read_array_tag(data, ProtoByteOrder::read_int, Tag::IntArray) } fn read_tag_long_array(data: &[u8]) -> DeserializeResult { read_array_tag(data, ProtoByteOrder::read_long, Tag::LongArray) } fn read_array_tag<'a, R, F, M>( data: &'a [u8], parser: F, finalizer: M, ) -> DeserializeResult<'a, Tag> where F: Fn(&'a [u8]) -> DeserializeResult<'a, R>, M: Fn(Vec) -> Tag, { let Deserialized { value: count, data } = ProtoByteOrder::read_int(data)?.map(move |v| v as i32); if count < 0 { Err(DeserializeErr::NbtBadLength(count as isize)) } else { let mut out = Vec::with_capacity(count as usize); let mut data_remaining = data; for _ in 0..count { let Deserialized { value: elem, data: rest, } = parser(data_remaining)?; data_remaining = rest; out.push(elem); } Deserialized::ok(finalizer(out), data_remaining) } } fn read_string(data: &[u8]) -> DeserializeResult { ProtoByteOrder::read_short(data)? .and_then(move |length, data| take(length as usize, data))? .try_map(move |bytes| { String::from_utf8(Vec::from(bytes)) .map_err(move |err| DeserializeErr::BadStringEncoding(err)) }) } // serialize impl NamedTag { pub fn bytes(&self) -> Vec { let type_id = self.payload.id(); if type_id == 0x00 { vec![0x00] } else { let payload_bytes = self.payload.bytes(); let name_len = self.name.len(); let name_len_bytes = ProtoByteOrder::write_ushort(name_len as u16); let mut out = Vec::with_capacity(1 + name_len_bytes.len() + name_len + payload_bytes.len()); out.push(type_id); out.extend_from_slice(&name_len_bytes); out.extend(self.name.bytes()); out.extend(payload_bytes); out } } } impl Tag { pub fn id(&self) -> u8 { match self { Tag::Byte(_) => 0x01, Tag::Short(_) => 0x02, Tag::Int(_) => 0x03, Tag::Long(_) => 0x04, Tag::Float(_) => 0x05, Tag::Double(_) => 0x06, Tag::ByteArray(_) => 0x07, Tag::String(_) => 0x08, Tag::List(_) => 0x09, Tag::Compound(_) => 0x0A, Tag::IntArray(_) => 0x0B, Tag::LongArray(_) => 0x0C, Tag::End => 0x00, } } pub fn bytes(&self) -> Vec { match self { Tag::Byte(b) => vec![*b as u8], Tag::Short(v) => Vec::from(ProtoByteOrder::write_short(*v)), Tag::Int(v) => Vec::from(ProtoByteOrder::write_int(*v)), Tag::Long(v) => Vec::from(ProtoByteOrder::write_long(*v)), Tag::Float(v) => Vec::from(ProtoByteOrder::write_float(*v)), Tag::Double(v) => Vec::from(ProtoByteOrder::write_double(*v)), Tag::ByteArray(v) => { let n = v.len(); let mut out = Vec::with_capacity(n + 4); let size_bytes = ProtoByteOrder::write_uint(n as u32); out.extend_from_slice(&size_bytes); out.extend(v); out } Tag::String(v) => { let n = v.len(); let mut out = Vec::with_capacity(n + 2); let size_bytes = ProtoByteOrder::write_ushort(n as u16); out.extend_from_slice(&size_bytes); out.extend(v.bytes()); out } Tag::List(v) => { let count = v.len(); let elem_id = { if count == 0 { 0x00 } else { let mut id = None; for elem in v { let elem_id = elem.id(); if let Some(old_id) = id.replace(elem_id) { if old_id != elem_id { panic!( "list contains tags of different types, cannot serialize" ); } } } id.expect("there must be some elements in the list") } }; let mut out = Vec::new(); out.push(elem_id); let count_bytes = ProtoByteOrder::write_uint(count as u32); out.extend_from_slice(&count_bytes); out.extend(v.iter().flat_map(move |elem| elem.bytes().into_iter())); out } Tag::Compound(v) => { let mut out = Vec::new(); for elem in v { out.extend(elem.bytes()); } out.extend(Tag::End.with_name("").bytes()); out } Tag::IntArray(v) => { let n = v.len(); let mut out = Vec::with_capacity(4 + (4 * n)); let n_bytes = ProtoByteOrder::write_uint(n as u32); out.extend_from_slice(&n_bytes); for value in v { let bytes = ProtoByteOrder::write_int(*value); out.extend_from_slice(&bytes); } out } Tag::LongArray(v) => { let n = v.len(); let mut out = Vec::with_capacity(4 + (8 * n)); let n_bytes = ProtoByteOrder::write_uint(n as u32); out.extend_from_slice(&n_bytes); for value in v { let bytes = ProtoByteOrder::write_long(*value); out.extend_from_slice(&bytes); } out } Tag::End => Vec::default(), } } } // test #[cfg(test)] mod tests { use super::*; #[cfg(feature = "std")] #[test] fn test_read_bignbt_example() { let actual = read_bigtest(); let expected = Tag::Compound(vec!( Tag::Long(9223372036854775807).with_name("longTest"), Tag::Short(32767).with_name("shortTest"), Tag::String("HELLO WORLD THIS IS A TEST STRING ÅÄÖ!".into()).with_name("stringTest"), Tag::Float(0.49823147).with_name("floatTest"), Tag::Int(2147483647).with_name("intTest"), Tag::Compound(vec!( Tag::Compound(vec!( Tag::String("Hampus".into()).with_name("name"), Tag::Float(0.75).with_name("value"), )).with_name("ham"), Tag::Compound(vec!( Tag::String("Eggbert".into()).with_name("name"), Tag::Float(0.5).with_name("value"), )).with_name("egg") )).with_name("nested compound test"), Tag::List(vec!( Tag::Long(11), Tag::Long(12), Tag::Long(13), Tag::Long(14), Tag::Long(15), )).with_name("listTest (long)"), Tag::List(vec!( Tag::Compound(vec!( Tag::String("Compound tag #0".into()).with_name("name"), Tag::Long(1264099775885).with_name("created-on"), )), Tag::Compound(vec!( Tag::String("Compound tag #1".into()).with_name("name"), Tag::Long(1264099775885).with_name("created-on"), )) )).with_name("listTest (compound)"), Tag::Byte(127).with_name("byteTest"), Tag::ByteArray(bigtest_generate_byte_array()).with_name("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))"), Tag::Double(0.4931287132182315).with_name("doubleTest") )).with_name("Level"); assert_eq!(actual, expected); } #[cfg(feature = "std")] #[test] fn test_serialize_bigtest() { let (unzipped, result) = read_bigtest_with_bytes(); let serialized = result.bytes(); assert_eq!(unzipped, serialized); let Deserialized { value: unserialized, data: _, } = NamedTag::root_compound_tag_from_bytes(serialized.as_slice()) .expect("deserialize serialized nbt"); assert_eq!(unserialized, result); } #[test] fn test_int_array() { let original = Tag::Compound(vec![Tag::IntArray(vec![ 1, 2, -5, 123127, -12373, 0, 0, 4, 2, ]) .with_name("test ints")]) .with_name("test"); let bytes = original.bytes(); let Deserialized { value: unserialized, data: _, } = NamedTag::root_compound_tag_from_bytes(bytes.as_slice()) .expect("deserialize int array"); assert_eq!(original, unserialized); } #[test] fn test_long_array() { let original = Tag::Compound(vec![Tag::LongArray(vec![ 1, 2, -5, 123127999999, -1237399999, 0, 0, 4, 2, ]) .with_name("test ints")]) .with_name("test"); let bytes = original.bytes(); let Deserialized { value: unserialized, data: _, } = NamedTag::root_compound_tag_from_bytes(bytes.as_slice()) .expect("deserialize int array"); assert_eq!(original, unserialized); } #[cfg(feature = "std")] #[test] fn test_display() { println!("{}", read_bigtest()); } #[cfg(feature = "std")] #[test] fn test_debug() { println!("{:?}", read_bigtest()); } #[cfg(feature = "std")] fn read_bigtest_with_bytes() -> (Vec, NamedTag) { let unzipped = read_compressed_file("src/testdata/bigtest.nbt").expect("read nbt data"); let Deserialized { value: result, data: rest, } = NamedTag::root_compound_tag_from_bytes(unzipped.as_slice()).expect("deserialize nbt"); assert_eq!(rest.len(), 0); (unzipped, result) } #[cfg(feature = "std")] fn read_bigtest() -> NamedTag { let (_, result) = read_bigtest_with_bytes(); result } #[cfg(feature = "std")] fn bigtest_generate_byte_array() -> Vec { const COUNT: usize = 1000; let mut out = Vec::with_capacity(COUNT); for i in 0..COUNT { out.push((((i * i * 255) + (i * 7)) % 100) as u8); } out } #[cfg(feature = "std")] fn read_compressed_file(at: &str) -> std::io::Result> { use flate2::read::GzDecoder; use std::fs::File; use std::io::Read; let file = File::open(at)?; let mut gz = GzDecoder::new(file); let mut out = Vec::new(); gz.read_to_end(&mut out)?; Ok(out) } }