use crate::TagType;
use byteorder::{BigEndian, ByteOrder};
use flate2::read::GzDecoder;
use std::fmt;
use std::io::Error as IoError;
use std::io::Read;
mod array;
mod compound;
mod internal;
mod list;
mod string;
pub use array::{IntArray, LongArray, NbtArray, NbtArrayIter};
pub use compound::{Compound, Entry};
pub(crate) use internal::{NbtParse, Reader};
pub use list::{
ByteArrayList, CompoundList, DoubleList, FloatList, IntArrayList, IntList, List, ListIter,
ListList, LongArrayList, LongList, NbtList, ShortList, StringList,
};
pub use string::NbtString;
#[derive(Debug)]
#[non_exhaustive]
pub enum ParseError {
EOF,
UnknownTag { tag: u8, offset: usize },
UnexpectedEndTag,
IncorrectStartTag { tag: TagType },
}
impl fmt::Display for ParseError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseError::EOF => write!(fmt, "Unexpected end of file"),
ParseError::UnknownTag { tag, offset } => {
write!(fmt, "Unknown tag {} at offset {:#x}", tag, offset)
}
ParseError::UnexpectedEndTag => write!(fmt, "Unexpected end tag in document"),
ParseError::IncorrectStartTag { tag } => {
write!(
fmt,
"Document starts with tag {:?}, it should only start with Compound.",
tag
)
}
}
}
}
impl std::error::Error for ParseError {}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Tag<'a> {
Byte(i8),
Short(i16),
Int(i32),
Long(i64),
Float(f32),
Double(f64),
ByteArray(&'a [u8]),
String(NbtString<'a>),
IntArray(IntArray<'a>),
LongArray(LongArray<'a>),
List(List<'a>),
Compound(Compound<'a>),
}
impl<'a> Tag<'a> {
pub(crate) fn read(tag: TagType, reader: &mut Reader<'a>) -> Result<Tag<'a>, ParseError> {
match tag {
TagType::End => Err(ParseError::UnexpectedEndTag),
TagType::Byte => Ok(Tag::Byte(reader.advance(1)?[0] as i8)),
TagType::Short => Ok(Tag::Short(BigEndian::read_i16(reader.advance(2)?))),
TagType::Int => Ok(Tag::Int(BigEndian::read_i32(reader.advance(4)?))),
TagType::Long => Ok(Tag::Long(BigEndian::read_i64(reader.advance(8)?))),
TagType::Float => Ok(Tag::Float(BigEndian::read_f32(reader.advance(4)?))),
TagType::Double => Ok(Tag::Double(BigEndian::read_f64(reader.advance(8)?))),
TagType::String => NbtString::read(reader).map(Tag::String),
TagType::List => List::read(reader).map(Tag::List),
TagType::Compound => Compound::read(reader).map(Tag::Compound),
TagType::ByteArray => read_byte_array(reader).map(Tag::ByteArray),
TagType::IntArray => IntArray::read(reader).map(Tag::IntArray),
TagType::LongArray => LongArray::read(reader).map(Tag::LongArray),
}
}
pub fn tag_type(&self) -> TagType {
match self {
Tag::Byte(_) => TagType::Byte,
Tag::Short(_) => TagType::Short,
Tag::Int(_) => TagType::Int,
Tag::Long(_) => TagType::Long,
Tag::Float(_) => TagType::Float,
Tag::Double(_) => TagType::Double,
Tag::ByteArray(_) => TagType::ByteArray,
Tag::String(_) => TagType::String,
Tag::List(_) => TagType::List,
Tag::Compound(_) => TagType::Compound,
Tag::IntArray(_) => TagType::IntArray,
Tag::LongArray(_) => TagType::LongArray,
}
}
pub fn as_string(&self) -> Option<NbtString<'a>> {
if let Tag::String(value) = self {
Some(*value)
} else {
None
}
}
pub fn as_byte_array(&self) -> Option<&[u8]> {
if let Tag::ByteArray(value) = self {
Some(value)
} else {
None
}
}
pub fn as_compound(&self) -> Option<&Compound<'a>> {
if let Tag::Compound(value) = self {
Some(value)
} else {
None
}
}
pub fn as_list(&self) -> Option<&List<'a>> {
if let Tag::List(value) = self {
Some(value)
} else {
None
}
}
pub fn to_i64(&self) -> Option<i64> {
match *self {
Tag::Byte(value) => Some(value as i64),
Tag::Short(value) => Some(value as i64),
Tag::Int(value) => Some(value as i64),
Tag::Long(value) => Some(value),
_ => None,
}
}
pub fn to_f64(&self) -> Option<f64> {
match *self {
Tag::Byte(value) => Some(value as f64),
Tag::Short(value) => Some(value as f64),
Tag::Int(value) => Some(value as f64),
Tag::Long(value) => Some(value as f64),
Tag::Float(value) => Some(value as f64),
Tag::Double(value) => Some(value),
_ => None,
}
}
pub fn to_f32(&self) -> Option<f32> {
match *self {
Tag::Byte(value) => Some(value as f32),
Tag::Short(value) => Some(value as f32),
Tag::Int(value) => Some(value as f32),
Tag::Long(value) => Some(value as f32),
Tag::Float(value) => Some(value),
Tag::Double(value) => Some(value as f32),
_ => None,
}
}
pub fn to_uuid_bytes(&self) -> Option<[u8; 16]> {
if let Tag::IntArray(array) = self {
if array.len() == 4 {
let mut buf = [0; 16];
BigEndian::write_i32(&mut buf[0..4], array.get(0).unwrap());
BigEndian::write_i32(&mut buf[4..8], array.get(1).unwrap());
BigEndian::write_i32(&mut buf[8..12], array.get(2).unwrap());
BigEndian::write_i32(&mut buf[12..16], array.get(3).unwrap());
return Some(buf);
}
}
None
}
#[cfg(feature = "uuid")]
pub fn to_uuid(&self) -> Option<uuid::Uuid> {
self.to_uuid_bytes().map(uuid::Uuid::from_bytes)
}
}
pub(crate) fn read_type(reader: &mut Reader<'_>) -> Result<TagType, ParseError> {
let offset = reader.position;
match reader.advance(1)?[0] {
0 => Ok(TagType::End),
1 => Ok(TagType::Byte),
2 => Ok(TagType::Short),
3 => Ok(TagType::Int),
4 => Ok(TagType::Long),
5 => Ok(TagType::Float),
6 => Ok(TagType::Double),
7 => Ok(TagType::ByteArray),
8 => Ok(TagType::String),
9 => Ok(TagType::List),
10 => Ok(TagType::Compound),
11 => Ok(TagType::IntArray),
12 => Ok(TagType::LongArray),
tag => Err(ParseError::UnknownTag { tag, offset }),
}
}
fn read_byte_array<'a>(reader: &mut Reader<'a>) -> Result<&'a [u8], ParseError> {
let len = BigEndian::read_u32(reader.advance(4)?);
Ok(reader.advance(len as usize)?)
}
#[derive(Clone, PartialEq)]
pub struct Document {
data: Vec<u8>,
}
impl Document {
#[doc(hidden)]
pub fn doctest_demo() -> impl Read + Clone {
use std::fs::File;
let mut file = File::open("files/hello_world.nbt").expect("File should exist");
let mut data = vec![];
file.read_to_end(&mut data).unwrap();
std::io::Cursor::new(data)
}
pub fn load<R: Read + Clone>(mut input: R) -> Result<Document, IoError> {
let mut decoder = GzDecoder::new(input.clone());
let mut data = vec![];
if decoder.header().is_some() {
decoder.read_to_end(&mut data)?;
} else {
input.read_to_end(&mut data)?;
}
Ok(Document { data })
}
pub fn parse(&self) -> Result<(NbtString, Compound), ParseError> {
let mut reader = Reader::new(&self.data);
let tag = read_type(&mut reader)?;
if tag != TagType::Compound {
return Err(ParseError::IncorrectStartTag { tag });
}
let name = NbtString::read(&mut reader)?;
let root = Compound::read(&mut reader)?;
Ok((name, root))
}
}
impl fmt::Debug for Document {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "Document({} B buffer)", self.data.len() / 1000)
}
}