1use std::ops::Deref;
4
5use miniz_oxide::inflate;
6
7use crate::{bigendian::BigEndian, nbt, positive_mod, Result};
8
9#[repr(u8)]
13#[derive(Debug, Clone, Copy, Eq, PartialEq)]
14pub enum CompressionType {
15 GZip = 1,
17 Zlib = 2,
19 Uncompressed = 3,
21 LZ4 = 4,
23 Custom = 127,
25}
26
27#[derive(Debug, Clone, Copy, Eq, PartialEq)]
31#[repr(C)]
32pub(crate) struct Location {
33 pub offset: BigEndian<3>,
34 pub sector_count: u8,
35}
36
37impl Location {
38 pub const fn is_empty(&self) -> bool {
39 self.offset.as_u32() == 0 && self.sector_count == 0
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
47pub struct ParsedChunk {
48 nbt: nbt::ChunkNbt,
49}
50
51impl Deref for ParsedChunk {
52 type Target = nbt::ChunkNbt;
53
54 fn deref(&self) -> &Self::Target {
55 &self.nbt
56 }
57}
58
59#[derive(Debug, Eq, PartialEq)]
61#[repr(C)]
62pub struct Chunk {
63 pub compression_type: CompressionType,
65 compressed_data: [u8],
66}
67
68impl Chunk {
69 pub fn boxed(&self) -> Box<Self> {
71 let mut b = vec![0u8; std::mem::size_of_val(self)];
72 unsafe { b.set_len(b.len() - 1) };
77 let b = b.into_boxed_slice();
78 let mut b: Box<Self> = unsafe { std::mem::transmute(b) };
80
81 b.as_mut().compression_type = self.compression_type;
82 b.compressed_data.copy_from_slice(&self.compressed_data);
83
84 b
85 }
86
87 pub fn parse(&self) -> Result<ParsedChunk> {
92 match self.compression_type {
93 CompressionType::GZip => todo!(),
94 CompressionType::Zlib => {
95 let data = &self.compressed_data;
96 let uncompressed = inflate::decompress_to_vec_zlib(data)?;
97 Ok(ParsedChunk {
98 nbt: fastnbt::from_bytes(&uncompressed)?,
99 })
100 }
101 CompressionType::Uncompressed => todo!(),
102 CompressionType::LZ4 => todo!(),
103 CompressionType::Custom => todo!(),
104 }
105 }
106
107 pub fn len(&self) -> usize {
109 self.compressed_data.len()
110 }
111}
112
113impl ParsedChunk {
114 pub fn get_chunk_section_at(&self, block_y: i32) -> Option<&nbt::ChunkSection> {
117 let subchunk_y = (block_y / 16) as i8;
118
119 self.sections.iter().find(|s| s.y == subchunk_y)
120 }
121
122 pub fn get_block(&self, block_x: u32, block_y: i32, block_z: u32) -> Option<&nbt::BlockState> {
126 let subchunk = self.get_chunk_section_at(block_y)?;
127
128 assert!(block_x < 16);
129 assert!(block_z < 16);
130
131 let block_y: u32 = positive_mod!(block_y, 16) as u32;
132
133 let bs = subchunk.block_states.as_ref()?;
134
135 let block_states = if let Some(data) = &bs.data {
136 data
137 } else {
138 return None;
143 };
144
145 let bits = std::cmp::max((bs.palette.len() as f32).log2().ceil() as u32, 4);
146
147 let block_index = block_y * 16 * 16 + block_z * 16 + block_x;
148 let block = get_item_in_packed_slice(&block_states, block_index as usize, bits);
149
150 Some(&bs.palette[block as usize])
151 }
152
153 pub fn get_block_from_absolute_coords(
159 &self,
160 block_x: u32,
161 block_y: i32,
162 block_z: u32,
163 ) -> Option<&nbt::BlockState> {
164 self.get_block(block_x % 16, block_y, block_z % 16)
165 }
166}
167
168fn get_item_in_packed_slice(slice: &[i64], index: usize, bits: u32) -> u64 {
169 let nums_per_u64 = u64::BITS / bits;
170 assert_eq!(
171 (slice.len() as u32),
172 ((4096. / nums_per_u64 as f32).ceil() as u32)
173 );
174 let index_in_num = index as u32 % nums_per_u64;
175 let shifted_num = slice[index / nums_per_u64 as usize] as u64 >> bits * index_in_num;
176 shifted_num & (2u64.pow(bits) - 1)
177}
178
179#[test]
180fn test_get_item_in_packed_slice() {
181 let slice = &[0; 128];
182 assert_eq!(get_item_in_packed_slice(slice, 15, 2), 0);
183 let slice = &[0; 456];
184 assert_eq!(get_item_in_packed_slice(slice, 15, 7), 0);
185}