mca_parser/
data.rs

1//! Module which holds much of the data related structs that are not nbt
2
3use std::ops::Deref;
4
5use miniz_oxide::inflate;
6
7use crate::{bigendian::BigEndian, nbt, positive_mod, Result};
8
9/// A type of compression used by a chunk
10///
11/// <https://minecraft.wiki/w/Region_file_format#Payload>
12#[repr(u8)]
13#[derive(Debug, Clone, Copy, Eq, PartialEq)]
14pub enum CompressionType {
15    /// RFC1952   Unused in Practice
16    GZip = 1,
17    /// RFC1950
18    Zlib = 2,
19    ///
20    Uncompressed = 3,
21    /// Since 24w04a -- enabled in server.properties
22    LZ4 = 4,
23    /// Since 24w05a -- for third-party servers
24    Custom = 127,
25}
26
27/// The location of a chunk in the file, stored in the header
28///
29/// <https://minecraft.wiki/w/Region_file_format#Chunk_location>
30#[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/// A parsed chunk, which owns its NBT data
44///
45/// The full NBT structure can be accessed through the [`Deref`] implementation to [`nbt::ChunkNbt`]
46#[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/// Represents one chunk in a region
60#[derive(Debug, Eq, PartialEq)]
61#[repr(C)]
62pub struct Chunk {
63    /// The compression type used for the data in this chunk
64    pub compression_type: CompressionType,
65    compressed_data: [u8],
66}
67
68impl Chunk {
69    /// Allocate this [`Chunk`] into a new [`Box`] which is owned by the caller
70    pub fn boxed(&self) -> Box<Self> {
71        let mut b = vec![0u8; std::mem::size_of_val(self)];
72        // SAFETY: We need to decrease the length of the vector by one so that the fat pointer has
73        // the correct length value.  This is okay since we're shortening the len and we know that
74        // the data has been initialised.  We could use the `.truncate()` method, but we do not
75        // want to drop the last item, since we still need it.
76        unsafe { b.set_len(b.len() - 1) };
77        let b = b.into_boxed_slice();
78        // SAFETY: We have allocated enough data in the box to call it `Box<Self>`
79        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    /// Parse this chunk into a [`ParsedChunk`]
88    ///
89    /// Allocates a new [`Vec`] into which the compressed data will be uncompressed and then parses
90    /// the nbt from that [`Vec`]
91    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    /// Get the length of the compressed data within this chunk
108    pub fn len(&self) -> usize {
109        self.compressed_data.len()
110    }
111}
112
113impl ParsedChunk {
114    /// Get a chunk section (or subchunk) from the given `block_y` value which is the y value of a _block_ within
115    /// the chunk
116    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    /// Get a block from a chunk using block_{x,y,z}.  The x and z coordinates are relative to the chunk,
123    /// and the y coordinate is absolute, so (0, 0, 0) is block 0, 0 in the chunk and y=0 in the
124    /// world.
125    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 Some(nbt::BlockState {
139            //     name: "minecraft:air".into(),
140            //     properties: None,
141            // });
142            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    /// Get a block from a chunk using block_{x,y,z}.  The coordinates are absolute in the
154    /// world, so (0, 0, 0) is the block at x=0, y=0, z=0.
155    ///
156    /// Note: This is only truly valid if this chunk is the chunk which contains that block,
157    /// otherwise it's not correct.
158    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}