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 region_file;
|
||||||
pub mod scan;
|
pub mod scan;
|
||||||
pub mod world_folder;
|
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