1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 23:44:40 +00:00
simdnbt/src/owned/list.rs
2023-09-21 22:51:39 -05:00

270 lines
9.7 KiB
Rust

use std::io::Cursor;
use byteorder::ReadBytesExt;
use crate::{
common::{
read_i8_array, read_int_array, read_long_array, read_string, read_u8_array,
read_with_u32_length, slice_i8_into_u8, slice_into_u8_big_endian, unchecked_extend,
unchecked_push, write_string, write_u32, write_with_u32_length, BYTE_ARRAY_ID, BYTE_ID,
COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID,
LONG_ID, SHORT_ID, STRING_ID,
},
mutf8::Mutf8String,
swap_endianness::swap_endianness,
Error,
};
use super::{compound::NbtCompound, read_u32, MAX_DEPTH};
/// A list of NBT tags of a single type.
#[repr(u8)]
#[derive(Debug, Default, Clone, PartialEq)]
pub enum ListTag {
#[default]
Empty = END_ID,
Byte(Vec<i8>) = BYTE_ID,
Short(Vec<i16>) = SHORT_ID,
Int(Vec<i32>) = INT_ID,
Long(Vec<i64>) = LONG_ID,
Float(Vec<f32>) = FLOAT_ID,
Double(Vec<f64>) = DOUBLE_ID,
ByteArray(Vec<Vec<u8>>) = BYTE_ARRAY_ID,
String(Vec<Mutf8String>) = STRING_ID,
List(Vec<ListTag>) = LIST_ID,
Compound(Vec<NbtCompound>) = COMPOUND_ID,
IntArray(Vec<Vec<i32>>) = INT_ARRAY_ID,
LongArray(Vec<Vec<i64>>) = LONG_ARRAY_ID,
}
impl ListTag {
pub fn read(data: &mut Cursor<&[u8]>, depth: usize) -> Result<Self, Error> {
if depth > MAX_DEPTH {
return Err(Error::MaxDepthExceeded);
}
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
Ok(match tag_type {
END_ID => {
data.set_position(data.position() + 4);
ListTag::Empty
}
BYTE_ID => ListTag::Byte(read_i8_array(data)?.to_owned()),
SHORT_ID => ListTag::Short(swap_endianness(read_with_u32_length(data, 2)?)),
INT_ID => ListTag::Int(swap_endianness(read_with_u32_length(data, 4)?)),
LONG_ID => ListTag::Long(swap_endianness(read_with_u32_length(data, 8)?)),
FLOAT_ID => ListTag::Float(swap_endianness(read_with_u32_length(data, 4)?)),
DOUBLE_ID => ListTag::Double(swap_endianness(read_with_u32_length(data, 8)?)),
BYTE_ARRAY_ID => ListTag::ByteArray({
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut arrays = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
arrays.push(read_u8_array(data)?.to_vec())
}
arrays
}),
STRING_ID => ListTag::String({
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut strings = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
strings.push(read_string(data)?.to_owned())
}
strings
}),
LIST_ID => ListTag::List({
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut lists = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
lists.push(ListTag::read(data, depth + 1)?)
}
lists
}),
COMPOUND_ID => ListTag::Compound({
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut compounds = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
compounds.push(NbtCompound::read_with_depth(data, depth + 1)?)
}
compounds
}),
INT_ARRAY_ID => ListTag::IntArray({
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut arrays = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
arrays.push(read_int_array(data)?.to_vec())
}
arrays
}),
LONG_ARRAY_ID => ListTag::LongArray({
let length = read_u32(data)?;
// arbitrary number to prevent big allocations
let mut arrays = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
arrays.push(read_long_array(data)?.to_vec())
}
arrays
}),
_ => return Err(Error::UnknownTagId(tag_type)),
})
}
pub fn write(&self, data: &mut Vec<u8>) {
// fast path for compound since it's very common to have lists of compounds
if let ListTag::Compound(compounds) = self {
data.reserve(5);
// SAFETY: we just reserved 5 bytes
unsafe {
unchecked_push(data, COMPOUND_ID);
unchecked_extend(data, &(compounds.len() as u32).to_be_bytes());
}
for compound in compounds {
compound.write(data);
}
return;
}
data.push(self.id());
match self {
ListTag::Empty => {
data.extend(&0u32.to_be_bytes());
}
ListTag::Byte(bytes) => {
write_with_u32_length(data, 1, slice_i8_into_u8(bytes));
}
ListTag::Short(shorts) => {
write_with_u32_length(data, 2, &slice_into_u8_big_endian(shorts));
}
ListTag::Int(ints) => {
write_with_u32_length(data, 4, &slice_into_u8_big_endian(ints));
}
ListTag::Long(longs) => {
write_with_u32_length(data, 8, &slice_into_u8_big_endian(longs));
}
ListTag::Float(floats) => {
write_with_u32_length(data, 4, &slice_into_u8_big_endian(floats));
}
ListTag::Double(doubles) => {
write_with_u32_length(data, 8, &slice_into_u8_big_endian(doubles));
}
ListTag::ByteArray(byte_arrays) => {
write_u32(data, byte_arrays.len() as u32);
for array in byte_arrays {
write_with_u32_length(data, 1, array);
}
}
ListTag::String(strings) => {
write_u32(data, strings.len() as u32);
for string in strings {
write_string(data, string);
}
}
ListTag::List(lists) => {
write_u32(data, lists.len() as u32);
for list in lists {
list.write(data);
}
}
ListTag::Compound(_) => {
unreachable!("fast path for compound should have been taken")
}
ListTag::IntArray(int_arrays) => {
write_u32(data, int_arrays.len() as u32);
for array in int_arrays {
write_with_u32_length(data, 4, &slice_into_u8_big_endian(array));
}
}
ListTag::LongArray(long_arrays) => {
write_u32(data, long_arrays.len() as u32);
for array in long_arrays {
write_with_u32_length(data, 8, &slice_into_u8_big_endian(array));
}
}
}
}
/// Get the numerical ID of the tag type.
#[inline]
pub fn id(&self) -> u8 {
// SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)`
// `union` between `repr(C)` structs, each of which has the `u8`
// discriminant as its first field, so we can read the discriminant
// without offsetting the pointer.
unsafe { *<*const _>::from(self).cast::<u8>() }
}
pub fn bytes(&self) -> Option<&[i8]> {
match self {
ListTag::Byte(bytes) => Some(bytes),
_ => None,
}
}
pub fn shorts(&self) -> Option<Vec<i16>> {
match self {
ListTag::Short(shorts) => Some(shorts.to_vec()),
_ => None,
}
}
pub fn ints(&self) -> Option<Vec<i32>> {
match self {
ListTag::Int(ints) => Some(ints.to_vec()),
_ => None,
}
}
pub fn longs(&self) -> Option<Vec<i64>> {
match self {
ListTag::Long(longs) => Some(longs.to_vec()),
_ => None,
}
}
pub fn floats(&self) -> Option<Vec<f32>> {
match self {
ListTag::Float(floats) => Some(floats.to_vec()),
_ => None,
}
}
pub fn doubles(&self) -> Option<Vec<f64>> {
match self {
ListTag::Double(doubles) => Some(doubles.to_vec()),
_ => None,
}
}
pub fn byte_arrays(&self) -> Option<&[Vec<u8>]> {
match self {
ListTag::ByteArray(byte_arrays) => Some(byte_arrays),
_ => None,
}
}
pub fn strings(&self) -> Option<&[Mutf8String]> {
match self {
ListTag::String(strings) => Some(strings),
_ => None,
}
}
pub fn lists(&self) -> Option<&[ListTag]> {
match self {
ListTag::List(lists) => Some(lists),
_ => None,
}
}
pub fn compounds(&self) -> Option<&[NbtCompound]> {
match self {
ListTag::Compound(compounds) => Some(compounds),
_ => None,
}
}
pub fn int_arrays(&self) -> Option<&[Vec<i32>]> {
match self {
ListTag::IntArray(int_arrays) => Some(int_arrays),
_ => None,
}
}
pub fn long_arrays(&self) -> Option<&[Vec<i64>]> {
match self {
ListTag::LongArray(long_arrays) => Some(long_arrays),
_ => None,
}
}
}