also add contributors to /pumpkin command Co-Authored-By: Fyor <90567841+fyordev@users.noreply.github.com> Co-Authored-By: MartV0 <34748938+martv0@users.noreply.github.com> Co-Authored-By: FabseGP <fabsegp@fabseman.space>
525 lines
14 KiB
Rust
525 lines
14 KiB
Rust
use std::{
|
|
fmt::Display,
|
|
io::{self, Read, Seek, Write},
|
|
ops::Deref,
|
|
};
|
|
|
|
use bytes::Bytes;
|
|
use compound::NbtCompound;
|
|
use deserializer::NbtReadHelper;
|
|
use serde::{de, ser};
|
|
use serializer::WriteAdaptor;
|
|
use tag::NbtTag;
|
|
use thiserror::Error;
|
|
|
|
pub mod compound;
|
|
pub mod deserializer;
|
|
pub mod nbt_compress;
|
|
pub mod serializer;
|
|
pub mod tag;
|
|
|
|
pub use deserializer::{from_bytes, from_bytes_unnamed};
|
|
pub use serializer::{to_bytes, to_bytes_named, to_bytes_unnamed};
|
|
|
|
// This NBT crate is inspired from CrabNBT
|
|
|
|
pub const END_ID: u8 = 0x00;
|
|
pub const BYTE_ID: u8 = 0x01;
|
|
pub const SHORT_ID: u8 = 0x02;
|
|
pub const INT_ID: u8 = 0x03;
|
|
pub const LONG_ID: u8 = 0x04;
|
|
pub const FLOAT_ID: u8 = 0x05;
|
|
pub const DOUBLE_ID: u8 = 0x06;
|
|
pub const BYTE_ARRAY_ID: u8 = 0x07;
|
|
pub const STRING_ID: u8 = 0x08;
|
|
pub const LIST_ID: u8 = 0x09;
|
|
pub const COMPOUND_ID: u8 = 0x0A;
|
|
pub const INT_ARRAY_ID: u8 = 0x0B;
|
|
pub const LONG_ARRAY_ID: u8 = 0x0C;
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum Error {
|
|
#[error("The root tag of the NBT file is not a compound tag. Received tag id: {0}")]
|
|
NoRootCompound(u8),
|
|
#[error("Encountered an unknown NBT tag id: {0}.")]
|
|
UnknownTagId(u8),
|
|
#[error("Failed to Cesu 8 Decode")]
|
|
Cesu8DecodingError,
|
|
#[error("Serde error: {0}")]
|
|
SerdeError(String),
|
|
#[error("NBT doesn't support this type: {0}")]
|
|
UnsupportedType(String),
|
|
#[error("NBT reading was cut short: {0}")]
|
|
Incomplete(io::Error),
|
|
#[error("Negative list length: {0}")]
|
|
NegativeLength(i32),
|
|
#[error("Length too large: {0}")]
|
|
LargeLength(usize),
|
|
}
|
|
|
|
impl ser::Error for Error {
|
|
fn custom<T: Display>(msg: T) -> Self {
|
|
Self::SerdeError(msg.to_string())
|
|
}
|
|
}
|
|
|
|
impl de::Error for Error {
|
|
fn custom<T: Display>(msg: T) -> Self {
|
|
Self::SerdeError(msg.to_string())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
|
|
pub struct Nbt {
|
|
pub name: String,
|
|
pub root_tag: NbtCompound,
|
|
}
|
|
|
|
impl Nbt {
|
|
#[must_use]
|
|
pub fn new(name: String, tag: NbtCompound) -> Self {
|
|
Self {
|
|
name,
|
|
root_tag: tag,
|
|
}
|
|
}
|
|
|
|
pub fn read<R: Read + Seek>(reader: &mut NbtReadHelper<R>) -> Result<Self, Error> {
|
|
let tag_type_id = reader.get_u8_be()?;
|
|
|
|
if tag_type_id != COMPOUND_ID {
|
|
return Err(Error::NoRootCompound(tag_type_id));
|
|
}
|
|
|
|
Ok(Self {
|
|
name: get_nbt_string(reader)?,
|
|
root_tag: NbtCompound::deserialize_content(reader)?,
|
|
})
|
|
}
|
|
|
|
/// Reads an NBT tag that doesn't contain the name of the root `Compound`.
|
|
pub fn read_unnamed<R: Read + Seek>(reader: &mut NbtReadHelper<R>) -> Result<Self, Error> {
|
|
let tag_type_id = reader.get_u8_be()?;
|
|
|
|
if tag_type_id != COMPOUND_ID {
|
|
return Err(Error::NoRootCompound(tag_type_id));
|
|
}
|
|
|
|
Ok(Self {
|
|
name: String::new(),
|
|
root_tag: NbtCompound::deserialize_content(reader)?,
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn write(self) -> Bytes {
|
|
let mut bytes = Vec::new();
|
|
let mut writer = WriteAdaptor::new(&mut bytes);
|
|
writer.write_u8_be(COMPOUND_ID).unwrap();
|
|
NbtTag::String(self.name)
|
|
.serialize_data(&mut writer)
|
|
.unwrap();
|
|
self.root_tag.serialize_content(&mut writer).unwrap();
|
|
|
|
bytes.into()
|
|
}
|
|
|
|
pub fn write_to_writer<W: Write>(self, mut writer: W) -> Result<(), io::Error> {
|
|
writer.write_all(&self.write())?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Writes an NBT tag without a root `Compound` name.
|
|
#[must_use]
|
|
pub fn write_unnamed(self) -> Bytes {
|
|
let mut bytes = Vec::new();
|
|
let mut writer = WriteAdaptor::new(&mut bytes);
|
|
|
|
writer.write_u8_be(COMPOUND_ID).unwrap();
|
|
self.root_tag.serialize_content(&mut writer).unwrap();
|
|
|
|
bytes.into()
|
|
}
|
|
|
|
pub fn write_unnamed_to_writer<W: Write>(self, mut writer: W) -> Result<(), io::Error> {
|
|
writer.write_all(&self.write_unnamed())?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Deref for Nbt {
|
|
type Target = NbtCompound;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.root_tag
|
|
}
|
|
}
|
|
|
|
impl From<NbtCompound> for Nbt {
|
|
fn from(value: NbtCompound) -> Self {
|
|
Self::new(String::new(), value)
|
|
}
|
|
}
|
|
|
|
impl<T> AsRef<T> for Nbt
|
|
where
|
|
T: ?Sized,
|
|
<Self as Deref>::Target: AsRef<T>,
|
|
{
|
|
fn as_ref(&self) -> &T {
|
|
self.deref().as_ref()
|
|
}
|
|
}
|
|
|
|
impl AsMut<NbtCompound> for Nbt {
|
|
fn as_mut(&mut self) -> &mut NbtCompound {
|
|
&mut self.root_tag
|
|
}
|
|
}
|
|
|
|
pub fn get_nbt_string<R: Read + Seek>(bytes: &mut NbtReadHelper<R>) -> Result<String, Error> {
|
|
let len = bytes.get_u16_be()? as usize;
|
|
let string_bytes = bytes.read_boxed_slice(len)?;
|
|
let string = cesu8::from_java_cesu8(&string_bytes).map_err(|_| Error::Cesu8DecodingError)?;
|
|
Ok(string.into_owned())
|
|
}
|
|
|
|
// TODO: This is a bit hacky
|
|
pub(crate) const NBT_ARRAY_TAG: &str = "__nbt_array";
|
|
pub(crate) const NBT_INT_ARRAY_TAG: &str = "__nbt_int_array";
|
|
pub(crate) const NBT_LONG_ARRAY_TAG: &str = "__nbt_long_array";
|
|
pub(crate) const NBT_BYTE_ARRAY_TAG: &str = "__nbt_byte_array";
|
|
|
|
macro_rules! impl_array {
|
|
($name:ident, $variant:expr) => {
|
|
pub fn $name<T: serde::Serialize, S: serde::Serializer>(
|
|
input: T,
|
|
serializer: S,
|
|
) -> Result<S::Ok, S::Error> {
|
|
serializer.serialize_newtype_variant(NBT_ARRAY_TAG, 0, $variant, &input)
|
|
}
|
|
};
|
|
}
|
|
|
|
impl_array!(nbt_int_array, NBT_INT_ARRAY_TAG);
|
|
impl_array!(nbt_long_array, NBT_LONG_ARRAY_TAG);
|
|
impl_array!(nbt_byte_array, NBT_BYTE_ARRAY_TAG);
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
|
|
use std::io::Cursor;
|
|
|
|
use crate::Error;
|
|
use crate::deserializer::from_bytes;
|
|
use crate::nbt_byte_array;
|
|
use crate::nbt_int_array;
|
|
use crate::nbt_long_array;
|
|
use crate::serializer::to_bytes;
|
|
use crate::serializer::to_bytes_named;
|
|
use crate::{deserializer::from_bytes_unnamed, serializer::to_bytes_unnamed};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
struct Test {
|
|
byte: i8,
|
|
short: i16,
|
|
int: i32,
|
|
long: i64,
|
|
float: f32,
|
|
string: String,
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_ser_de_unnamed() {
|
|
let test = Test {
|
|
byte: 123,
|
|
short: 1342,
|
|
int: 4313,
|
|
long: 34,
|
|
float: 1.00,
|
|
string: "Hello test".to_string(),
|
|
};
|
|
|
|
let mut bytes = Vec::new();
|
|
to_bytes_unnamed(&test, &mut bytes).unwrap();
|
|
let recreated_struct: Test = from_bytes_unnamed(Cursor::new(bytes)).unwrap();
|
|
|
|
assert_eq!(test, recreated_struct);
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
#[expect(clippy::struct_field_names)]
|
|
struct TestArray {
|
|
#[serde(serialize_with = "nbt_byte_array")]
|
|
byte_array: Vec<u8>,
|
|
#[serde(serialize_with = "nbt_int_array")]
|
|
int_array: Vec<i32>,
|
|
#[serde(serialize_with = "nbt_long_array")]
|
|
long_array: Vec<i64>,
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_ser_de_array() {
|
|
let test = TestArray {
|
|
byte_array: vec![0, 3, 2],
|
|
int_array: vec![13, 1321, 2],
|
|
long_array: vec![1, 0, 200301, 1],
|
|
};
|
|
|
|
let mut bytes = Vec::new();
|
|
to_bytes_unnamed(&test, &mut bytes).unwrap();
|
|
let recreated_struct: TestArray = from_bytes_unnamed(Cursor::new(bytes)).unwrap();
|
|
|
|
assert_eq!(test, recreated_struct);
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_ser_de_named() {
|
|
let name = String::from("Test");
|
|
let test = Test {
|
|
byte: 123,
|
|
short: 1342,
|
|
int: 4313,
|
|
long: 34,
|
|
float: 1.00,
|
|
string: "Hello test".to_string(),
|
|
};
|
|
|
|
let mut bytes = Vec::new();
|
|
to_bytes_named(&test, name, &mut bytes).unwrap();
|
|
let recreated_struct: Test = from_bytes(Cursor::new(bytes)).unwrap();
|
|
|
|
assert_eq!(test, recreated_struct);
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_ser_de_array_named() {
|
|
let name = String::from("Test");
|
|
let test = TestArray {
|
|
byte_array: vec![0, 3, 2],
|
|
int_array: vec![13, 1321, 2],
|
|
long_array: vec![1, 0, 200301, 1],
|
|
};
|
|
|
|
let mut bytes = Vec::new();
|
|
to_bytes_named(&test, name, &mut bytes).unwrap();
|
|
let recreated_struct: TestArray = from_bytes(Cursor::new(bytes)).unwrap();
|
|
|
|
assert_eq!(test, recreated_struct);
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
struct Egg {
|
|
food: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
struct Breakfast {
|
|
food: Egg,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
struct TestList {
|
|
option: Option<Egg>,
|
|
nested_compound: Breakfast,
|
|
compounds: Vec<Test>,
|
|
list_string: Vec<String>,
|
|
empty: Vec<Test>,
|
|
}
|
|
|
|
#[test]
|
|
fn test_list() {
|
|
let test1 = Test {
|
|
byte: 123,
|
|
short: 1342,
|
|
int: 4313,
|
|
long: 34,
|
|
float: 1.00,
|
|
string: "Hello test".to_string(),
|
|
};
|
|
|
|
let test2 = Test {
|
|
byte: 13,
|
|
short: 342,
|
|
int: -4313,
|
|
long: -132334,
|
|
float: -69.420,
|
|
string: "Hello compounds".to_string(),
|
|
};
|
|
|
|
let list_compound = TestList {
|
|
option: Some(Egg {
|
|
food: "Skibid".to_string(),
|
|
}),
|
|
nested_compound: Breakfast {
|
|
food: Egg {
|
|
food: "Over easy".to_string(),
|
|
},
|
|
},
|
|
compounds: vec![test1, test2],
|
|
list_string: vec![String::new(), "abcbcbcbbc".to_string()],
|
|
empty: vec![],
|
|
};
|
|
|
|
let mut bytes = Vec::new();
|
|
to_bytes_unnamed(&list_compound, &mut bytes).unwrap();
|
|
let recreated_struct: TestList = from_bytes_unnamed(Cursor::new(bytes)).unwrap();
|
|
assert_eq!(list_compound, recreated_struct);
|
|
}
|
|
|
|
#[test]
|
|
fn test_list_named() {
|
|
let test1 = Test {
|
|
byte: 123,
|
|
short: 1342,
|
|
int: 4313,
|
|
long: 34,
|
|
float: 1.00,
|
|
string: "Hello test".to_string(),
|
|
};
|
|
|
|
let test2 = Test {
|
|
byte: 13,
|
|
short: 342,
|
|
int: -4313,
|
|
long: -132334,
|
|
float: -69.420,
|
|
string: "Hello compounds".to_string(),
|
|
};
|
|
|
|
let list_compound = TestList {
|
|
option: None,
|
|
nested_compound: Breakfast {
|
|
food: Egg {
|
|
food: "Over easy".to_string(),
|
|
},
|
|
},
|
|
compounds: vec![test1, test2],
|
|
list_string: vec![String::new(), "abcbcbcbbc".to_string()],
|
|
empty: vec![],
|
|
};
|
|
|
|
let mut bytes = Vec::new();
|
|
to_bytes_named(&list_compound, "a".to_string(), &mut bytes).unwrap();
|
|
let recreated_struct: TestList = from_bytes(Cursor::new(bytes)).unwrap();
|
|
assert_eq!(list_compound, recreated_struct);
|
|
}
|
|
|
|
#[test]
|
|
fn test_nbt_arrays() {
|
|
#[derive(Serialize)]
|
|
struct Tagged {
|
|
#[serde(serialize_with = "nbt_long_array")]
|
|
l: [i64; 1],
|
|
#[serde(serialize_with = "nbt_int_array")]
|
|
i: [i32; 1],
|
|
#[serde(serialize_with = "nbt_byte_array")]
|
|
b: [u8; 1],
|
|
}
|
|
#[derive(Serialize)]
|
|
struct NotTagged {
|
|
l: [i64; 1],
|
|
i: [i32; 1],
|
|
b: [u8; 1],
|
|
}
|
|
|
|
let value = Tagged {
|
|
l: [0],
|
|
i: [0],
|
|
b: [0],
|
|
};
|
|
let expected_bytes = [
|
|
0x0A, // Component Tag
|
|
0x00, 0x00, // Empty root name
|
|
0x0C, // Long Array Type
|
|
0x00, 0x01, // Key length
|
|
0x6C, // Key (l)
|
|
0x00, 0x00, 0x00, 0x01, // Array Length
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Value(s)
|
|
0x0B, // Int Array Tag
|
|
0x00, 0x01, // Key length
|
|
0x69, // Key (i)
|
|
0x00, 0x00, 0x00, 0x01, // Array Length
|
|
0x00, 0x00, 0x00, 0x00, // Value(s)
|
|
0x07, // Byte Array Tag
|
|
0x00, 0x01, // Key length
|
|
0x62, // Key (b)
|
|
0x00, 0x00, 0x00, 0x01, // Array Length
|
|
0x00, // Value(s)
|
|
0x00, // End Tag
|
|
];
|
|
|
|
let mut bytes = Vec::new();
|
|
to_bytes(&value, &mut bytes).unwrap();
|
|
assert_eq!(bytes, expected_bytes);
|
|
|
|
let value = NotTagged {
|
|
l: [0],
|
|
i: [0],
|
|
b: [0],
|
|
};
|
|
let expected_bytes = [
|
|
0x0A, // Component Tag
|
|
0x00, 0x00, // Empty root name
|
|
0x09, // List Tag
|
|
0x00, 0x01, // Key length
|
|
0x6C, // Key (l)
|
|
0x04, // Array Type
|
|
0x00, 0x00, 0x00, 0x01, // Array Length
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Value(s)
|
|
0x09, // List Tag
|
|
0x00, 0x01, // Key length
|
|
0x69, // Key (i)
|
|
0x03, // Array Type
|
|
0x00, 0x00, 0x00, 0x01, // Array Length
|
|
0x00, 0x00, 0x00, 0x00, // Value(s)
|
|
0x09, // List Tag
|
|
0x00, 0x01, // Key length
|
|
0x62, // Key (b)
|
|
0x01, // Array Type
|
|
0x00, 0x00, 0x00, 0x01, // Array Length
|
|
0x00, // Value(s)
|
|
0x00, // End Tag
|
|
];
|
|
|
|
let mut bytes = Vec::new();
|
|
to_bytes(&value, &mut bytes).unwrap();
|
|
assert_eq!(bytes, expected_bytes);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tuple_fail() {
|
|
#[derive(Serialize)]
|
|
struct BadData {
|
|
x: (i32, i64),
|
|
}
|
|
|
|
let value = BadData { x: (0, 0) };
|
|
let mut bytes = Vec::new();
|
|
let err = to_bytes(&value, &mut bytes);
|
|
|
|
match err {
|
|
Err(Error::SerdeError(_)) => (),
|
|
_ => panic!("Expected to fail serialization!"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_tuple_ok() {
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
struct GoodData {
|
|
x: (i32, i32),
|
|
}
|
|
|
|
let value = GoodData { x: (1, 2) };
|
|
let mut bytes = Vec::new();
|
|
to_bytes(&value, &mut bytes).unwrap();
|
|
|
|
let reconstructed = from_bytes(Cursor::new(bytes)).unwrap();
|
|
assert_eq!(value, reconstructed);
|
|
}
|
|
|
|
// TODO: More robust tests
|
|
}
|