use bytemuck::cast_slice;
use crate::{
error::ParseError,
types::{NbtArray, NbtCompound, NbtList, NbtString, NbtTag, NbtTagId},
};
type Result<T> = std::result::Result<T, ParseError>;
#[inline(always)]
fn split_1(mut data: &[u8]) -> Result<(u8, &[u8])> {
let &a = data
.split_off_first()
.ok_or(ParseError::UnexpectedEndOfInput)?;
Ok((a, data))
}
macro_rules! impl_splt {
($name:ident, $width:expr) => {
#[inline(always)]
fn $name(data: &[u8]) -> Result<([u8; $width], &[u8])> {
let (&a, data) = data
.split_first_chunk::<$width>()
.ok_or(ParseError::UnexpectedEndOfInput)?;
Ok((a, data))
}
};
}
impl_splt!(split_2, 2);
impl_splt!(split_4, 4);
impl_splt!(split_8, 8);
fn parse_tag_id(data: &[u8]) -> Result<(NbtTagId, &[u8])> {
let (tag_id, rest) = split_1(data)?;
let tag_id = NbtTagId::try_from(tag_id).map_err(|_| ParseError::InvalidTagId(tag_id))?;
Ok((tag_id, rest))
}
#[inline(always)]
fn parse_byte(data: &[u8]) -> Result<(i8, &[u8])> {
let (v, rest) = split_1(data)?;
Ok((v as i8, rest))
}
macro_rules! impl_parse_numeric {
($name:ident, $ty:ty, $split:ident) => {
#[inline(always)]
fn $name(data: &[u8]) -> Result<($ty, &[u8])> {
let (bytes, rest) = $split(data)?;
Ok((<$ty>::from_be_bytes(bytes), rest))
}
};
}
impl_parse_numeric!(parse_short, i16, split_2);
impl_parse_numeric!(parse_int, i32, split_4);
impl_parse_numeric!(parse_long, i64, split_8);
impl_parse_numeric!(parse_float, f32, split_4);
impl_parse_numeric!(parse_double, f64, split_8);
fn parse_byte_array(data: &[u8]) -> Result<(NbtArray<i8>, &[u8])> {
let (len, data) = parse_int(data)?;
if len < 0 {
return Err(ParseError::NegativeLength(len));
}
let (data, rest) = data
.split_at_checked(len as usize)
.ok_or(ParseError::UnexpectedEndOfInput)?;
Ok((
NbtArray {
items: cast_slice(data).into(),
},
rest,
))
}
fn parse_string(data: &[u8]) -> Result<(NbtString, &[u8])> {
let (len, data) = parse_short(data)?;
let (data, rest) = data
.split_at_checked(len as u16 as usize)
.ok_or(ParseError::UnexpectedEndOfInput)?;
let data = std::string::String::from_utf8(data.into()).map_err(|_| ParseError::InvalidUtf8)?;
Ok((NbtString { str: data }, rest))
}
fn parse_int_array(data: &[u8]) -> Result<(NbtArray<i32>, &[u8])> {
let (len, data) = parse_int(data)?;
if len < 0 {
return Err(ParseError::NegativeLength(len));
}
let (data, rest) = data
.split_at_checked(len as usize * 4)
.ok_or(ParseError::UnexpectedEndOfInput)?;
let items = data
.chunks_exact(4)
.map(|chunk| i32::from_be_bytes(chunk.try_into().unwrap()))
.collect();
Ok((NbtArray { items }, rest))
}
fn parse_long_array(data: &[u8]) -> Result<(NbtArray<i64>, &[u8])> {
let (len, data) = parse_int(data)?;
if len < 0 {
return Err(ParseError::NegativeLength(len));
}
let (data, rest) = data
.split_at_checked(len as usize * 8)
.ok_or(ParseError::UnexpectedEndOfInput)?;
let items = data
.chunks_exact(8)
.map(|chunk| i64::from_be_bytes(chunk.try_into().unwrap()))
.collect();
Ok((NbtArray { items }, rest))
}
fn parse_list(data: &[u8]) -> Result<(NbtList, &[u8])> {
let (tag_id, data) = parse_tag_id(data)?;
match tag_id {
NbtTagId::Byte => {
return parse_byte_array(data).map(|p| (NbtList::Byte(p.0), p.1));
}
NbtTagId::Int => {
return parse_int_array(data).map(|p| (NbtList::Int(p.0), p.1));
}
NbtTagId::Long => {
return parse_long_array(data).map(|p| (NbtList::Long(p.0), p.1));
}
_ => {}
}
let (len, data) = parse_int(data)?;
if len < 0 {
return Err(ParseError::NegativeLength(len));
}
macro_rules! parse {
($parser:ident, $variant:ident) => {{
let mut data = data;
let mut items = Vec::with_capacity(len as usize);
for _ in 0..len {
let (item, rest) = $parser(data)?;
items.push(item);
data = rest;
}
(
NbtList::$variant(NbtArray {
items: items.into(),
}),
data,
)
}};
}
Ok(match tag_id {
NbtTagId::End => (NbtList::End, data),
NbtTagId::Short => parse!(parse_short, Short),
NbtTagId::Float => parse!(parse_float, Float),
NbtTagId::Double => parse!(parse_double, Double),
NbtTagId::ByteArray => parse!(parse_byte_array, ByteArray),
NbtTagId::String => parse!(parse_string, String),
NbtTagId::List => parse!(parse_list, List),
NbtTagId::Compound => parse!(parse_compound, Compound),
NbtTagId::IntArray => parse!(parse_int_array, IntArray),
NbtTagId::LongArray => parse!(parse_long_array, LongArray),
NbtTagId::Byte | NbtTagId::Int | NbtTagId::Long => unreachable!(),
})
}
fn parse_payload(tag_id: NbtTagId, data: &[u8]) -> Result<(NbtTag, &[u8])> {
macro_rules! parse {
($variant:ident, $parser:expr) => {
$parser(data).map(|(v, rest)| (NbtTag::$variant(v), rest))
};
}
match tag_id {
NbtTagId::End => Ok((NbtTag::End, data)),
NbtTagId::Byte => parse!(Byte, parse_byte),
NbtTagId::Short => parse!(Short, parse_short),
NbtTagId::Int => parse!(Int, parse_int),
NbtTagId::Long => parse!(Long, parse_long),
NbtTagId::Float => parse!(Float, parse_float),
NbtTagId::Double => parse!(Double, parse_double),
NbtTagId::ByteArray => parse!(ByteArray, parse_byte_array),
NbtTagId::String => parse!(String, parse_string),
NbtTagId::List => parse!(List, parse_list),
NbtTagId::Compound => parse!(Compound, parse_compound),
NbtTagId::IntArray => parse!(IntArray, parse_int_array),
NbtTagId::LongArray => parse!(LongArray, parse_long_array),
}
}
fn parse_compound(mut data: &[u8]) -> Result<(NbtCompound, &[u8])> {
let mut compound: Vec<(NbtString, NbtTag)> = Vec::new();
loop {
let (tag_id, rest) = parse_tag_id(data)?;
if tag_id == NbtTagId::End {
return Ok((NbtCompound { data: compound }, rest));
}
let (name, rest) = parse_string(rest)?;
let (tag, rest) = parse_payload(tag_id, rest)?;
if compound
.iter()
.any(|(existing_name, _)| existing_name.eq(&name))
{
return Err(ParseError::DuplicateTagName(name));
}
compound.push((name, tag));
data = rest;
}
}
pub fn parse_nbt(data: &[u8]) -> Result<(NbtString, NbtCompound)> {
let (tag_id, data) = parse_tag_id(data)?;
if tag_id != NbtTagId::Compound {
return Err(ParseError::NotNBT);
}
let (name, data) = parse_string(data)?;
let (tag, data) = parse_compound(data)?;
if !data.is_empty() {
Err(ParseError::LeftoverData(data.len()))
} else {
Ok((name, tag))
}
}