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}