#![warn(missing_docs)]
use std::{
collections::HashMap,
io::{self, Read},
ops::Deref,
path::{Path, PathBuf},
};
use bigendian::BigEndian;
use error::Error;
pub use data::*;
pub use error::Result;
mod bigendian;
pub mod data;
pub mod error;
pub mod nbt;
#[macro_use]
mod util;
#[cfg(test)]
mod test;
#[derive(Debug, Eq, PartialEq)]
#[repr(C)]
pub struct Region {
locations: [Location; 1024],
timestamps: [BigEndian<4>; 1024],
data: [u8],
}
impl Region {
pub fn from_slice(slice: &[u8]) -> Result<&Region> {
if slice.len() < 8192 {
Err(Error::MissingHeader)
} else {
let ptr = &slice[..slice.len() - 8192] as *const [u8] as *const Region;
Ok(unsafe { &*ptr })
}
}
pub const unsafe fn from_array<const N: usize>(arr: &[u8; N]) -> &'static Region {
assert!(N >= 8192);
&*(std::ptr::slice_from_raw_parts(arr as *const u8, N - 8192) as *const Region)
}
pub fn from_reader<R>(r: &mut R) -> Result<Box<Region>>
where
R: Read,
{
use std::mem::ManuallyDrop;
let mut vec = ManuallyDrop::new(Vec::new());
r.read_to_end(&mut vec)?;
if vec.len() < 8192 {
Err(Error::MissingHeader)
} else {
let slice =
unsafe { std::slice::from_raw_parts_mut(vec.as_mut_ptr(), vec.len() - 8192) };
Ok(unsafe { Box::from_raw(slice as *mut [u8] as *mut Region) })
}
}
#[inline(always)]
const fn chunk_index(x: u32, z: u32) -> usize {
assert!(x < 32);
assert!(z < 32);
z as usize * 32 + x as usize
}
pub fn validate(&self) -> Result<()> {
for x in 0..32 {
for z in 0..32 {
if let Some(chunk) = self.get_chunk(x, z)? {
chunk.parse()?;
}
}
}
Ok(())
}
pub const fn get_timestamp(&self, x: u32, z: u32) -> u32 {
self.timestamps[Self::chunk_index(x, z)].as_u32()
}
pub const fn has_chunk(&self, x: u32, z: u32) -> bool {
!self.locations[Self::chunk_index(x, z)].is_empty()
}
pub fn get_chunk(&self, chunk_x: u32, chunk_z: u32) -> Result<Option<&Chunk>> {
let loc = &self.locations[Self::chunk_index(chunk_x, chunk_z)];
let offset: u32 = loc.offset.into();
if loc.is_empty() {
return Ok(None);
}
let start = (offset - 2) as usize * 4096;
if self.data.len() < start + 4 {
return Err(Error::UnexpectedEof);
}
let len = u32::from(unsafe { *(self.data[start..][..4].as_ptr() as *const BigEndian<4>) })
as usize;
if self.data.len() < start + 4 + len {
return Err(Error::UnexpectedEof);
}
let chunk = unsafe {
&*(core::ptr::slice_from_raw_parts(self.data[start + 4..].as_ptr(), len - 1)
as *const Chunk)
};
Ok(Some(chunk))
}
pub fn get_chunk_from_block(&self, block_x: u32, block_z: u32) -> Result<Option<&Chunk>> {
self.get_chunk(block_x / 16, block_z / 16)
}
}
#[derive(Debug, Clone)]
pub struct RegionFile {
pub path: PathBuf,
}
impl RegionFile {
pub fn new<P>(path: P) -> Self
where
P: AsRef<Path>,
{
Self {
path: path.as_ref().to_path_buf(),
}
}
}
pub fn parse_directory<P>(path: P) -> io::Result<impl Iterator<Item = RegionFile>>
where
P: AsRef<Path>,
{
let path = path.as_ref();
assert!(path.is_dir());
let rd = std::fs::read_dir(path)?;
let iter = rd.filter_map(|de| {
let de = de.ok()?;
let path = de.path();
if !path.is_file() {
return None;
}
Some(RegionFile::new(path))
});
Ok(iter)
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum DimensionID {
Overworld,
Nether,
End,
Custom(i32),
}
impl DimensionID {
pub fn id(&self) -> i32 {
match self {
Self::Overworld => 0,
Self::Nether => -1,
Self::End => 1,
Self::Custom(n) => *n,
}
}
}
impl From<i32> for DimensionID {
fn from(value: i32) -> Self {
match value {
0 => Self::Overworld,
-1 => Self::Nether,
1 => Self::End,
n => Self::Custom(n),
}
}
}
#[derive(Debug)]
pub enum RegionRef<'a> {
Borrowed(&'a Region),
Owned(Box<Region>),
}
impl<'a> From<&'a Region> for RegionRef<'a> {
fn from(value: &'a Region) -> Self {
Self::Borrowed(value)
}
}
impl From<Box<Region>> for RegionRef<'_> {
fn from(value: Box<Region>) -> Self {
Self::Owned(value)
}
}
impl Deref for RegionRef<'_> {
type Target = Region;
fn deref(&self) -> &Self::Target {
match self {
RegionRef::Borrowed(r) => r,
RegionRef::Owned(r) => r,
}
}
}
pub trait RegionParser {
fn parse(&self) -> Result<RegionRef<'_>>;
fn position(&self) -> Option<(i32, i32)>;
}
impl RegionParser for RegionFile {
fn position(&self) -> Option<(i32, i32)> {
let filename = self.path.file_name()?.to_string_lossy();
let mut parts = filename.split('.');
if parts.next() != Some("r") {
return None;
}
let Some(Ok(x)) = parts.next().map(|s| s.parse()) else {
return None;
};
let Some(Ok(z)) = parts.next().map(|s| s.parse()) else {
return None;
};
if parts.next() != Some("mca") {
return None;
}
Some((x, z))
}
fn parse(&self) -> Result<RegionRef<'_>> {
let mut file = std::fs::File::open(&self.path)?;
Ok(Region::from_reader(&mut file)?.into())
}
}
pub struct Dimension<R> {
pub id: Option<DimensionID>,
regions: HashMap<(i32, i32), R>,
}
impl Dimension<RegionFile> {
pub fn from_path<P>(path: P) -> io::Result<Self>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let file = path.file_name();
let id = file
.and_then(|n| {
n.to_string_lossy()
.strip_prefix("DIM")
.and_then(|n| n.parse().ok())
})
.map(|n: i32| n.into());
Ok(Self::from_iter(id, parse_directory(path)?))
}
}
impl<R> Dimension<R>
where
R: RegionParser,
{
pub fn from_iter<I>(id: Option<DimensionID>, iter: I) -> Self
where
I: Iterator<Item = R>,
{
Self {
id,
regions: iter.map(|rf| (rf.position().unwrap(), rf)).collect(),
}
}
pub fn has_region(&self, region_x: i32, region_z: i32) -> bool {
self.regions.contains_key(&(region_x, region_z))
}
pub fn parse_region(&self, region_x: i32, region_z: i32) -> Result<RegionRef> {
self.regions[&(region_x, region_z)].parse()
}
pub fn regions(&self) -> impl Iterator<Item = &R> {
self.regions.values()
}
pub fn locations(&self) -> impl Iterator<Item = &(i32, i32)> {
self.regions.keys()
}
pub fn get_region_from_chunk(&self, chunk_x: i32, chunk_z: i32) -> Result<Option<RegionRef>> {
if self.has_region(chunk_x / 32, chunk_z / 32) {
Ok(Some(self.parse_region(chunk_x / 32, chunk_z / 32)?))
} else {
Ok(None)
}
}
pub fn get_chunk_in_world(&self, chunk_x: i32, chunk_z: i32) -> Result<Option<ParsedChunk>> {
let region = self.get_region_from_chunk(chunk_x, chunk_z);
match region {
Ok(None) => Ok(None),
Ok(Some(region)) => {
match region.get_chunk(
positive_mod!(chunk_x, 32) as u32,
positive_mod!(chunk_z, 32) as u32,
) {
Ok(Some(chunk)) => match chunk.parse() {
Ok(p) => Ok(Some(p)),
Err(e) => Err(e),
},
Ok(None) => Ok(None),
Err(e) => Err(e),
}
}
Err(e) => Err(e),
}
}
}