Add scanning of chunk data in nbt format
Signed-off-by: trivernis <trivernis@protonmail.com>main
parent
701991b1ab
commit
f5ad767d22
@ -0,0 +1,120 @@
|
||||
use crate::nbt::{NBTError, NBTReader, NBTValue};
|
||||
use crate::region_file::BLOCK_SIZE;
|
||||
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
|
||||
|
||||
use flate2::bufread::ZlibDecoder;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader, Error, Read, Seek, SeekFrom};
|
||||
|
||||
type IOResult<T> = io::Result<T>;
|
||||
|
||||
const TAG_LEVEL: &str = "Level";
|
||||
const TAG_X_POS: &str = "xPos";
|
||||
const TAG_Z_POS: &str = "zPos";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Chunk {
|
||||
pub length: u32,
|
||||
pub compression_type: u8,
|
||||
nbt_raw: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn from_buf_reader(reader: &mut BufReader<File>, include_nbt: bool) -> IOResult<Self> {
|
||||
let mut length_raw = [0u8; 4];
|
||||
reader.read_exact(&mut length_raw)?;
|
||||
let length = BigEndian::read_u32(&length_raw);
|
||||
let compression_type = reader.read_u8()?;
|
||||
|
||||
let mut nbt_raw = Vec::new();
|
||||
if include_nbt {
|
||||
for _ in 0..((length - 1) as f32 / BLOCK_SIZE as f32).ceil() as u8 {
|
||||
let mut buffer = [0u8; BLOCK_SIZE];
|
||||
reader.read(&mut buffer)?;
|
||||
nbt_raw.append(&mut buffer.to_vec());
|
||||
}
|
||||
nbt_raw.truncate((length - 1) as usize);
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
reader.seek(SeekFrom::Current((length - 1) as i64))?;
|
||||
} else {
|
||||
reader.seek(SeekFrom::Current((length) as i64))?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
compression_type,
|
||||
length,
|
||||
nbt_raw,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_nbt_data(&mut self) -> Result<(), ChunkScanError> {
|
||||
if self.compression_type == 2 {
|
||||
let mut decoder = ZlibDecoder::new(&self.nbt_raw[..]);
|
||||
let mut data = Vec::new();
|
||||
decoder.read_to_end(&mut data)?;
|
||||
self.nbt_raw = data;
|
||||
}
|
||||
let mut reader = NBTReader::new(&self.nbt_raw[..]);
|
||||
let data = reader.parse()?;
|
||||
|
||||
if !data.contains_key(TAG_LEVEL) {
|
||||
Err(ChunkScanError::MissingTag(TAG_LEVEL))
|
||||
} else {
|
||||
let lvl_data = &data[TAG_LEVEL];
|
||||
|
||||
if let NBTValue::Compound(lvl_data) = lvl_data {
|
||||
if !lvl_data.contains_key(TAG_X_POS) {
|
||||
Err(ChunkScanError::MissingTag(TAG_X_POS))
|
||||
} else if !lvl_data.contains_key(TAG_Z_POS) {
|
||||
Err(ChunkScanError::MissingTag(TAG_Z_POS))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(ChunkScanError::InvalidFormat(TAG_LEVEL))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ChunkScanError {
|
||||
String(String),
|
||||
IO(io::Error),
|
||||
NBTError(NBTError),
|
||||
MissingTag(&'static str),
|
||||
InvalidFormat(&'static str),
|
||||
}
|
||||
|
||||
impl Display for ChunkScanError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::String(s) => write!(f, "{}", s),
|
||||
Self::IO(io) => write!(f, "IO Error: {}", io),
|
||||
Self::NBTError(nbt) => write!(f, "NBT Error: {}", nbt),
|
||||
Self::MissingTag(tag) => write!(f, "Missing Tag in NBT Data: {}", tag),
|
||||
Self::InvalidFormat(tag) => write!(f, "Unexpected data format for NBT Tag {}", tag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ChunkScanError {
|
||||
fn from(io_err: Error) -> Self {
|
||||
Self::IO(io_err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NBTError> for ChunkScanError {
|
||||
fn from(nbt: NBTError) -> Self {
|
||||
Self::NBTError(nbt)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ChunkScanError {
|
||||
fn from(err: String) -> Self {
|
||||
Self::String(err)
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
pub mod chunk;
|
||||
pub mod nbt;
|
||||
pub mod region_file;
|
||||
pub mod scan;
|
||||
pub mod world_folder;
|
||||
|
@ -0,0 +1,183 @@
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io::{self, Read};
|
||||
|
||||
pub struct NBTReader<R> {
|
||||
inner: Box<R>,
|
||||
}
|
||||
|
||||
type NBTResult<T> = Result<T, NBTError>;
|
||||
|
||||
impl<R> NBTReader<R>
|
||||
where
|
||||
R: io::Read,
|
||||
{
|
||||
pub fn new(inner: R) -> Self {
|
||||
Self {
|
||||
inner: Box::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the contents of the reader
|
||||
pub fn parse(&mut self) -> NBTResult<HashMap<String, NBTValue>> {
|
||||
let tag = self.inner.read_u8()?;
|
||||
|
||||
if tag != 10 {
|
||||
return Err(NBTError::MissingRootTag);
|
||||
}
|
||||
let _ = self.parse_string()?;
|
||||
|
||||
self.parse_compound()
|
||||
}
|
||||
|
||||
/// Parses a compound tag
|
||||
fn parse_compound(&mut self) -> NBTResult<HashMap<String, NBTValue>> {
|
||||
let mut root_value = HashMap::new();
|
||||
loop {
|
||||
let tag = self.inner.read_u8()?;
|
||||
if tag == 0 {
|
||||
break;
|
||||
}
|
||||
let name = self.parse_string()?;
|
||||
|
||||
let value = match tag {
|
||||
1 => NBTValue::Byte(self.inner.read_u8()?),
|
||||
2 => NBTValue::Short(self.inner.read_i16::<BigEndian>()?),
|
||||
3 => NBTValue::Int(self.inner.read_i32::<BigEndian>()?),
|
||||
4 => NBTValue::Long(self.inner.read_i64::<BigEndian>()?),
|
||||
5 => NBTValue::Float(self.inner.read_f32::<BigEndian>()?),
|
||||
6 => NBTValue::Double(self.inner.read_f64::<BigEndian>()?),
|
||||
7 => NBTValue::ByteArray(self.parse_byte_array()?),
|
||||
8 => NBTValue::String(self.parse_string()?),
|
||||
9 => NBTValue::List(self.parse_list()?),
|
||||
10 => NBTValue::Compound(self.parse_compound()?),
|
||||
11 => NBTValue::IntArray(self.parse_int_array()?),
|
||||
12 => NBTValue::LongArray(self.parse_long_array()?),
|
||||
_ => return Err(NBTError::InvalidTag(tag)),
|
||||
};
|
||||
root_value.insert(name, value);
|
||||
}
|
||||
|
||||
Ok(root_value)
|
||||
}
|
||||
|
||||
/// Parses an array of bytes
|
||||
fn parse_byte_array(&mut self) -> NBTResult<Vec<u8>> {
|
||||
let length = self.inner.read_u32::<BigEndian>()?;
|
||||
let mut buf = Vec::with_capacity(length as usize);
|
||||
self.inner.read_exact(&mut buf)?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Parses a string value
|
||||
fn parse_string(&mut self) -> NBTResult<String> {
|
||||
let length = self.inner.read_u16::<BigEndian>()?;
|
||||
if length == 0 {
|
||||
return Ok(String::new());
|
||||
}
|
||||
let mut buf = Vec::with_capacity(length as usize);
|
||||
for _ in 0..length {
|
||||
buf.push(self.inner.read_u8()?);
|
||||
}
|
||||
|
||||
String::from_utf8(buf).map_err(|_| NBTError::InvalidName)
|
||||
}
|
||||
|
||||
/// Parses a list of nbt values
|
||||
fn parse_list(&mut self) -> NBTResult<Vec<NBTValue>> {
|
||||
let tag = self.inner.read_u8()?;
|
||||
let length = self.inner.read_u32::<BigEndian>()?;
|
||||
|
||||
let parse_fn: Box<dyn Fn(&mut Self) -> NBTResult<NBTValue>> = match tag {
|
||||
0 => Box::new(|_| Ok(NBTValue::Null)),
|
||||
1 => Box::new(|nbt| Ok(NBTValue::Byte(nbt.inner.read_u8()?))),
|
||||
2 => Box::new(|nbt| Ok(NBTValue::Short(nbt.inner.read_i16::<BigEndian>()?))),
|
||||
3 => Box::new(|nbt| Ok(NBTValue::Int(nbt.inner.read_i32::<BigEndian>()?))),
|
||||
4 => Box::new(|nbt| Ok(NBTValue::Long(nbt.inner.read_i64::<BigEndian>()?))),
|
||||
5 => Box::new(|nbt| Ok(NBTValue::Float(nbt.inner.read_f32::<BigEndian>()?))),
|
||||
6 => Box::new(|nbt| Ok(NBTValue::Double(nbt.inner.read_f64::<BigEndian>()?))),
|
||||
7 => Box::new(|nbt| Ok(NBTValue::ByteArray(nbt.parse_byte_array()?))),
|
||||
8 => Box::new(|nbt| Ok(NBTValue::String(nbt.parse_string()?))),
|
||||
9 => Box::new(|nbt| Ok(NBTValue::List(nbt.parse_list()?))),
|
||||
11 => Box::new(|nbt| Ok(NBTValue::IntArray(nbt.parse_int_array()?))),
|
||||
10 => Box::new(|nbt| Ok(NBTValue::Compound(nbt.parse_compound()?))),
|
||||
12 => Box::new(|nbt| Ok(NBTValue::LongArray(nbt.parse_long_array()?))),
|
||||
_ => return Err(NBTError::InvalidTag(tag)),
|
||||
};
|
||||
let mut items = Vec::new();
|
||||
for _ in 0..length {
|
||||
items.push(parse_fn(self)?);
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
/// Parses an array of 32 bit integers
|
||||
fn parse_int_array(&mut self) -> NBTResult<Vec<i32>> {
|
||||
let length = self.inner.read_u32::<BigEndian>()?;
|
||||
let mut items = Vec::new();
|
||||
for _ in 0..length {
|
||||
items.push(self.inner.read_i32::<BigEndian>()?);
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
/// Parses an array of 64 bit integers
|
||||
fn parse_long_array(&mut self) -> NBTResult<Vec<i64>> {
|
||||
let length = self.inner.read_u32::<BigEndian>()?;
|
||||
let mut items = Vec::new();
|
||||
for _ in 0..length {
|
||||
items.push(self.inner.read_i64::<BigEndian>()?);
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum NBTValue {
|
||||
Null,
|
||||
Byte(u8),
|
||||
Short(i16),
|
||||
Int(i32),
|
||||
Long(i64),
|
||||
Float(f32),
|
||||
Double(f64),
|
||||
ByteArray(Vec<u8>),
|
||||
String(String),
|
||||
List(Vec<NBTValue>),
|
||||
Compound(HashMap<String, NBTValue>),
|
||||
IntArray(Vec<i32>),
|
||||
LongArray(Vec<i64>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NBTError {
|
||||
IO(io::Error),
|
||||
MissingRootTag,
|
||||
InvalidTag(u8),
|
||||
InvalidName,
|
||||
}
|
||||
|
||||
impl Display for NBTError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::IO(io) => write!(f, "IO Error: {}", io),
|
||||
Self::InvalidTag(tag) => write!(f, "Invalid Tag: 0x{:x}", tag),
|
||||
Self::MissingRootTag => write!(f, "Missing root tag!"),
|
||||
Self::InvalidName => write!(f, "Encountered invalid tag name"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for NBTError {}
|
||||
|
||||
impl From<io::Error> for NBTError {
|
||||
fn from(io_err: io::Error) -> Self {
|
||||
Self::IO(io_err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue