1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
//! Module which holds much of the data related structs that are not nbt
use std::ops::Deref;
use miniz_oxide::inflate;
use crate::{bigendian::BigEndian, nbt, positive_mod, Result};
/// A type of compression used by a chunk
///
/// <https://minecraft.wiki/w/Region_file_format#Payload>
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum CompressionType {
/// RFC1952 Unused in Practice
GZip = 1,
/// RFC1950
Zlib = 2,
///
Uncompressed = 3,
/// Since 24w04a -- enabled in server.properties
LZ4 = 4,
/// Since 24w05a -- for third-party servers
Custom = 127,
}
/// The location of a chunk in the file, stored in the header
///
/// <https://minecraft.wiki/w/Region_file_format#Chunk_location>
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Location {
pub offset: BigEndian<3>,
pub sector_count: u8,
}
impl Location {
pub const fn is_empty(&self) -> bool {
self.offset.as_u32() == 0 && self.sector_count == 0
}
}
/// A parsed chunk, which owns its NBT data
///
/// The full NBT structure can be accessed through the [`Deref`] implementation to [`nbt::ChunkNbt`]
#[derive(Debug, Clone, PartialEq)]
pub struct ParsedChunk {
nbt: nbt::ChunkNbt,
}
impl Deref for ParsedChunk {
type Target = nbt::ChunkNbt;
fn deref(&self) -> &Self::Target {
&self.nbt
}
}
/// Represents one chunk in a region
#[derive(Debug, Eq, PartialEq)]
#[repr(C)]
pub struct Chunk {
/// The compression type used for the data in this chunk
pub compression_type: CompressionType,
compressed_data: [u8],
}
impl Chunk {
/// Allocate this [`Chunk`] into a new [`Box`] which is owned by the caller
pub fn boxed(&self) -> Box<Self> {
let mut b = vec![0u8; std::mem::size_of_val(self)];
// SAFETY: We need to decrease the length of the vector by one so that the fat pointer has
// the correct length value. This is okay since we're shortening the len and we know that
// the data has been initialised. We could use the `.truncate()` method, but we do not
// want to drop the last item, since we still need it.
unsafe { b.set_len(b.len() - 1) };
let b = b.into_boxed_slice();
// SAFETY: We have allocated enough data in the box to call it `Box<Self>`
let mut b: Box<Self> = unsafe { std::mem::transmute(b) };
b.as_mut().compression_type = self.compression_type;
b.compressed_data.copy_from_slice(&self.compressed_data);
b
}
/// Parse this chunk into a [`ParsedChunk`]
///
/// Allocates a new [`Vec`] into which the compressed data will be uncompressed and then parses
/// the nbt from that [`Vec`]
pub fn parse(&self) -> Result<ParsedChunk> {
match self.compression_type {
CompressionType::GZip => todo!(),
CompressionType::Zlib => {
let data = &self.compressed_data;
let uncompressed = inflate::decompress_to_vec_zlib(data)?;
Ok(ParsedChunk {
nbt: fastnbt::from_bytes(&uncompressed)?,
})
}
CompressionType::Uncompressed => todo!(),
CompressionType::LZ4 => todo!(),
CompressionType::Custom => todo!(),
}
}
/// Get the length of the compressed data within this chunk
pub fn len(&self) -> usize {
self.compressed_data.len()
}
}
impl ParsedChunk {
/// Get a chunk section (or subchunk) from the given `block_y` value which is the y value of a _block_ within
/// the chunk
pub fn get_chunk_section_at(&self, block_y: i32) -> Option<&nbt::ChunkSection> {
let subchunk_y = (block_y / 16) as i8;
self.sections.iter().find(|s| s.y == subchunk_y)
}
/// Get a block from a chunk using block_{x,y,z}. The x and z coordinates are relative to the chunk,
/// and the y coordinate is absolute, so (0, 0, 0) is block 0, 0 in the chunk and y=0 in the
/// world.
pub fn get_block(&self, block_x: u32, block_y: i32, block_z: u32) -> Option<nbt::BlockState> {
let subchunk = self.get_chunk_section_at(block_y)?;
assert!(block_x < 16);
assert!(block_z < 16);
let block_y: u32 = positive_mod!(block_y, 16) as u32;
let bs = subchunk.clone().block_states?;
let block_states: Vec<_> = if let Some(data) = bs.data {
data.iter().map(|n| *n as u64).collect()
} else {
return Some(nbt::BlockState {
name: "minecraft:air".into(),
properties: None,
});
};
let bits = std::cmp::max((bs.palette.len() as f32).log2().ceil() as u32, 4);
let block_index = block_y * 16 * 16 + block_z * 16 + block_x;
let block = get_item_in_packed_slice(&block_states, block_index as usize, bits);
Some(bs.palette[block as usize].clone())
}
/// Get a block from a chunk using block_{x,y,z}. The coordinates are absolute in the
/// world, so (0, 0, 0) is the block at x=0, y=0, z=0.
///
/// Note: This is only truly valid if this chunk is the chunk which contains that block,
/// otherwise it's not correct.
pub fn get_block_from_absolute_coords(
&self,
block_x: u32,
block_y: i32,
block_z: u32,
) -> Option<nbt::BlockState> {
self.get_block(block_x % 16, block_y, block_z % 16)
}
}
fn get_item_in_packed_slice(slice: &[u64], index: usize, bits: u32) -> u64 {
let nums_per_u64 = u64::BITS / bits;
assert_eq!(
(slice.len() as u32),
((4096. / nums_per_u64 as f32).ceil() as u32)
);
let index_in_num = index as u32 % nums_per_u64;
let shifted_num = slice[index / nums_per_u64 as usize] >> bits * index_in_num;
shifted_num & (2u64.pow(bits) - 1)
}
#[test]
fn test_get_item_in_packed_slice() {
let slice = &[0; 128];
assert_eq!(get_item_in_packed_slice(slice, 15, 2), 0);
let slice = &[0; 456];
assert_eq!(get_item_in_packed_slice(slice, 15, 7), 0);
}