simple_anvil/
region.rs

1use nbt::Blob;
2
3use crate::{chunk::Chunk, block::Block};
4
5use std::{
6    array::TryFromSliceError,
7    cell::Cell,
8    convert::TryInto,
9    fs,
10    marker::{self, PhantomData},
11    path::Path,
12};
13
14/// Low level storage of region file contents.
15#[derive(Clone)]
16pub struct Region<'a> {
17    /// Vector containing all of the data in bytes.
18    data: Vec<u8>,
19    /// I don't remember what this was for.
20    _marker: marker::PhantomData<Cell<&'a ()>>,
21    /// The name of the file that the region was derived from.
22    pub filename: String,
23}
24
25impl<'a> Region<'a> {
26    /// Returns the header size and returns an offset for a particular chunk.
27    /// 
28    /// # Arguments
29    /// 
30    /// * `chunk_x` - The x coordinate of the particular chunk
31    /// * `chunk_z` - The z coordinate of the particular chunk
32    fn header_offset(&self, chunk_x: u32, chunk_z: u32) -> u32 {
33        return 4 * (chunk_x % 32 + chunk_z % 32 * 32);
34    }
35
36    /// Returns the location where a particular chunk is found.
37    ///
38    /// # Arguments
39    /// 
40    /// * `chunk_x` - The x coordinate of the particular chunk
41    /// * `chunk_z` - The z coordinate of the particular chunk
42    fn chunk_location(&self, chunk_x: u32, chunk_z: u32) -> (u32, u32) {
43        let b_off = self.header_offset(chunk_x, chunk_z) as usize;
44
45        let temp_range = &self.data[b_off..b_off + 3];
46        let temp: [u8; 3] = temp_range
47            .try_into()
48            .expect("Failed to convert slice into array.");
49
50        let off = from_be_3_bytes(temp);
51        let sectors = self.data[b_off as usize + 3];
52        return (off, sectors as u32);
53    }
54
55    /// Returns a Blob of all the data for a particular chunk. 
56    /// 
57    /// # Arguments
58    /// 
59    /// * `chunk_x` - The x coordinate of the particular chunk
60    /// * `chunk_z` - The z coordinate of the particular chunk
61    pub fn chunk_data(&self, chunk_x: u32, chunk_z: u32) -> Option<Box<Blob>> {
62        let off = self.chunk_location(chunk_x, chunk_z);
63        if off == (0, 0) {
64            return None;
65        }
66        let off: u32 = off.0 as u32 * 4096;
67
68        let temp: Result<[u8; 4], TryFromSliceError> =
69            self.data[off as usize..off as usize + 4].try_into();
70        let length = u32::from_be_bytes(temp.unwrap());
71        let compression = self.data[off as usize + 4];
72        if compression == 1 {
73            return None;
74        }
75        let compressed_data: Vec<u8> =
76            self.data[off as usize + 5..off as usize + 5 + length as usize - 1].into();
77        let data = Box::new(Blob::from_zlib_reader(&mut compressed_data.as_slice()).unwrap());
78        return Some(data);
79    }
80
81    /// Returns a region using a region(.mca) file
82    /// 
83    /// # Arguments
84    /// 
85    /// * `file` - The file name and relative path of the region file.
86    /// 
87    /// # Examples
88    /// 
89    /// ```rust,no_run
90    /// use simple_anvil::region::Region;
91    /// 
92    /// let region = Region::from_file("r.0.0.mca".into());
93    /// ```
94    pub fn from_file(file: String) -> Region<'a> {
95        let f = Path::new(&file);
96        return Region {
97            data: fs::read(file.clone()).unwrap(),
98            _marker: PhantomData,
99            filename: f.file_name().unwrap().to_str().unwrap().to_string(),
100        };
101    }
102
103    /// Returns a Chunk contained within the Region. A region file contains 32x32 chunks.
104    /// 
105    /// # Arguments
106    /// 
107    /// * `chunk_x` - The x coordinate of the particular chunk
108    /// * `chunk_z` - The z coordinate of the particular chunk
109    /// 
110    /// # Examples
111    /// 
112    /// ```rust,no_run
113    /// use simple_anvil::region::Region;
114    /// 
115    /// let region = Region::from_file("r.0.0.mca".into());
116    /// let chunk = region.get_chunk(11, 2).unwrap();
117    /// ```
118    pub fn get_chunk(&self, chunk_x: u32, chunk_z: u32) -> Option<Chunk> {
119        return Chunk::from_region(self, chunk_x, chunk_z);
120    }
121
122    /// Returns a Block contained within the Region. None is returned if the Chunk the Block would exist in is not fully generated.
123    /// 
124    /// # Arguments
125    /// 
126    /// * `x` - The x coordinate of the block
127    /// * `y` - The x coordinate of the block
128    /// * `z` - The x coordinate of the block
129    /// 
130    /// # Examples
131    /// 
132    /// ```rust,no_run
133    /// use simple_anvil::region::Region;
134    /// 
135    /// let region = Region::from_file("r.0.0.mca".into());
136    /// println!("{}", region.get_block(20, 56, 45).unwrap().id);
137    /// ```
138    pub fn get_block(&self, x: i32, y: i32, z: i32) -> Option<Block> {
139        let chunk = self.get_chunk((x / 32) as u32, (z / 32) as u32).unwrap();
140        return match chunk.get_status().as_str() {
141            "full" => {
142                Some(chunk.get_block(x % 32, y, z % 32))
143            },
144            _ => None,
145        }
146    }
147}
148
149/// Returns an unsigned int from three bytes. This might not be needed anymore.
150/// 
151/// # Arguments
152/// 
153/// * `bytes` - The bytes to be converted into u32
154fn from_be_3_bytes(bytes: [u8; 3]) -> u32 {
155    let mut temp: [u8; 4] = [0; 4];
156    for n in 0..bytes.len() {
157        temp[n + 1] = bytes[n];
158    }
159    return u32::from_be_bytes(temp);
160}