mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-09-24 17:11:00 +00:00
697 lines
25 KiB
Rust
697 lines
25 KiB
Rust
use super::nbt;
|
|
const SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE: i16 = 8;
|
|
const SAVE_FILE_VERSION_CHUNK_INHABITED_TIME: i16 = 9;
|
|
const COMPRESSED_CHUNK_SECTION_HEIGHT: usize = 128;
|
|
const BLOCKS_PER_SECTION: usize = COMPRESSED_CHUNK_SECTION_HEIGHT * 16 * 16;
|
|
const NIBBLES_PER_SECTION: usize = BLOCKS_PER_SECTION / 2;
|
|
const FULL_CHUNK_BLOCKS: usize = 256 * 16 * 16;
|
|
const FULL_CHUNK_NIBBLES: usize = FULL_CHUNK_BLOCKS / 2;
|
|
const INDEX_TYPE_MASK: u16 = 0x0003;
|
|
const INDEX_TYPE_1BIT: u16 = 0x0000;
|
|
const INDEX_TYPE_2BIT: u16 = 0x0001;
|
|
const INDEX_TYPE_4BIT: u16 = 0x0002;
|
|
const INDEX_TYPE_0_OR_8BIT: u16 = 0x0003;
|
|
const INDEX_TYPE_0BIT_FLAG: u16 = 0x0004;
|
|
const SPARSE_ALL_ZERO_INDEX: u8 = 128;
|
|
const SPARSE_ALL_FIFTEEN_INDEX: u8 = 129;
|
|
fn read_be_i16(data: &[u8], off: usize) -> i16 {
|
|
i16::from_be_bytes([data[off], data[off + 1]])
|
|
}
|
|
|
|
fn read_be_i32(data: &[u8], off: usize) -> i32 {
|
|
i32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]])
|
|
}
|
|
|
|
fn read_be_i64(data: &[u8], off: usize) -> i64 {
|
|
i64::from_be_bytes([
|
|
data[off], data[off + 1], data[off + 2], data[off + 3], data[off + 4], data[off + 5],
|
|
data[off + 6], data[off + 7],
|
|
])
|
|
}
|
|
/*
|
|
//neo: unused
|
|
fn write_be_i16(out: &mut Vec<u8>, v: i16) {
|
|
out.extend_from_slice(&v.to_be_bytes());
|
|
}
|
|
|
|
fn write_be_i32(out: &mut Vec<u8>, v: i32) {
|
|
out.extend_from_slice(&v.to_be_bytes());
|
|
}
|
|
|
|
fn write_be_i64(out: &mut Vec<u8>, v: i64) {
|
|
out.extend_from_slice(&v.to_be_bytes());
|
|
}*/
|
|
|
|
fn get_compressed_tile_index(block: usize, tile: usize) -> usize {
|
|
let mut index = ((block & 0x180) << 6) | ((block & 0x060) << 4) | ((block & 0x01F) << 2);
|
|
index |= ((tile & 0x30) << 7) | ((tile & 0x0C) << 5) | (tile & 0x03);
|
|
index
|
|
}
|
|
/*
|
|
//neo: unused
|
|
fn get_nibble_value(nibble_data: &[u8], xz: usize, y: usize) -> u8 {
|
|
let pos = (xz << 7) | y;
|
|
let slot = pos >> 1;
|
|
let part = pos & 1;
|
|
if slot >= nibble_data.len() {
|
|
return 0;
|
|
}
|
|
let b = nibble_data[slot];
|
|
if part == 0 {
|
|
b & 0x0F
|
|
} else {
|
|
(b >> 4) & 0x0F
|
|
}
|
|
}*/
|
|
|
|
fn set_nibble_value(nibble_data: &mut [u8], xz: usize, y: usize, value: u8) {
|
|
let pos = (xz << 7) | y;
|
|
let slot = pos >> 1;
|
|
let part = pos & 1;
|
|
if slot >= nibble_data.len() {
|
|
return;
|
|
}
|
|
let val = value & 0x0F;
|
|
if part == 0 {
|
|
nibble_data[slot] = (nibble_data[slot] & 0xF0) | val;
|
|
} else {
|
|
nibble_data[slot] = (nibble_data[slot] & 0x0F) | (val << 4);
|
|
}
|
|
}
|
|
|
|
/*
|
|
//neo: unused
|
|
fn ensure_length(input: &[u8], expected_len: usize) -> Vec<u8> {
|
|
if input.len() == expected_len {
|
|
return input.to_vec();
|
|
}
|
|
let mut v = vec![0u8; expected_len];
|
|
let copy_len = input.len().min(expected_len);
|
|
v[..copy_len].copy_from_slice(&input[..copy_len]);
|
|
v
|
|
}
|
|
|
|
fn extract_lower_block_section(full_blocks: &[u8]) -> Vec<u8> {
|
|
if full_blocks.len() == BLOCKS_PER_SECTION {
|
|
return full_blocks.to_vec();
|
|
}
|
|
let mut lower = vec![0u8; BLOCKS_PER_SECTION];
|
|
for xz in 0..256 {
|
|
let src_start = xz * 256;
|
|
let dst_start = xz * COMPRESSED_CHUNK_SECTION_HEIGHT;
|
|
let copy_len = COMPRESSED_CHUNK_SECTION_HEIGHT.min(full_blocks.len() - src_start.min(full_blocks.len()));
|
|
if src_start + copy_len <= full_blocks.len() && dst_start + copy_len <= BLOCKS_PER_SECTION {
|
|
lower[dst_start..dst_start + copy_len]
|
|
.copy_from_slice(&full_blocks[src_start..src_start + copy_len]);
|
|
}
|
|
}
|
|
lower
|
|
}
|
|
|
|
fn extract_upper_block_section(full_blocks: &[u8]) -> Vec<u8> {
|
|
if full_blocks.len() < FULL_CHUNK_BLOCKS {
|
|
return vec![0u8; BLOCKS_PER_SECTION];
|
|
}
|
|
let mut upper = vec![0u8; BLOCKS_PER_SECTION];
|
|
for xz in 0..256 {
|
|
let src_start = xz * 256 + COMPRESSED_CHUNK_SECTION_HEIGHT;
|
|
let dst_start = xz * COMPRESSED_CHUNK_SECTION_HEIGHT;
|
|
upper[dst_start..dst_start + COMPRESSED_CHUNK_SECTION_HEIGHT].copy_from_slice(&full_blocks[src_start..src_start + COMPRESSED_CHUNK_SECTION_HEIGHT]);
|
|
}
|
|
upper
|
|
}*/
|
|
|
|
fn extract_lower_nibble_section(full_nibbles: &[u8]) -> Vec<u8> {
|
|
let nibble_height = COMPRESSED_CHUNK_SECTION_HEIGHT / 2;
|
|
if full_nibbles.len() == NIBBLES_PER_SECTION {
|
|
return full_nibbles.to_vec();
|
|
}
|
|
let mut lower = vec![0u8; NIBBLES_PER_SECTION];
|
|
for xz in 0..256 {
|
|
let src_start = xz * nibble_height * 2;
|
|
let dst_start = xz * nibble_height;
|
|
let copy_len = nibble_height.min(
|
|
full_nibbles.len().saturating_sub(src_start),
|
|
);
|
|
if copy_len > 0 {
|
|
lower[dst_start..dst_start + copy_len].copy_from_slice(&full_nibbles[src_start..src_start + copy_len]);
|
|
}
|
|
}
|
|
lower
|
|
}
|
|
/*
|
|
//neo: unused
|
|
fn extract_upper_nibble_section(full_nibbles: &[u8]) -> Vec<u8> {
|
|
let nibble_height = COMPRESSED_CHUNK_SECTION_HEIGHT / 2;
|
|
if full_nibbles.len() < FULL_CHUNK_NIBBLES {
|
|
return vec![0u8; NIBBLES_PER_SECTION];
|
|
}
|
|
let mut upper = vec![0u8; NIBBLES_PER_SECTION];
|
|
for xz in 0..256 {
|
|
let src_start = xz * nibble_height * 2 + nibble_height;
|
|
let dst_start = xz * nibble_height;
|
|
upper[dst_start..dst_start + nibble_height]
|
|
.copy_from_slice(&full_nibbles[src_start..src_start + nibble_height]);
|
|
}
|
|
upper
|
|
}*/
|
|
|
|
fn combine_block_sections(lower: &[u8], upper: &[u8]) -> Vec<u8> {
|
|
if lower.len() == FULL_CHUNK_BLOCKS {
|
|
return lower.to_vec();
|
|
}
|
|
let mut combined = vec![0u8; FULL_CHUNK_BLOCKS];
|
|
for xz in 0..256 {
|
|
let lower_start = xz * COMPRESSED_CHUNK_SECTION_HEIGHT;
|
|
let out_start = xz * 256;
|
|
let copy_len = COMPRESSED_CHUNK_SECTION_HEIGHT.min(lower.len().saturating_sub(lower_start));
|
|
if copy_len > 0 {
|
|
combined[out_start..out_start + copy_len].copy_from_slice(&lower[lower_start..lower_start + copy_len]);
|
|
}
|
|
let upper_start = xz * COMPRESSED_CHUNK_SECTION_HEIGHT;
|
|
let out_upper = out_start + COMPRESSED_CHUNK_SECTION_HEIGHT;
|
|
let copy_len2 = COMPRESSED_CHUNK_SECTION_HEIGHT.min(upper.len().saturating_sub(upper_start));
|
|
if copy_len2 > 0 {
|
|
combined[out_upper..out_upper + copy_len2]
|
|
.copy_from_slice(&upper[upper_start..upper_start + copy_len2]);
|
|
}
|
|
}
|
|
combined
|
|
}
|
|
|
|
fn combine_nibble_sections(lower: &[u8], upper: &[u8]) -> Vec<u8> {
|
|
let nibble_height = COMPRESSED_CHUNK_SECTION_HEIGHT / 2;
|
|
if lower.len() == FULL_CHUNK_NIBBLES {
|
|
return lower.to_vec();
|
|
}
|
|
let mut combined = vec![0u8; FULL_CHUNK_NIBBLES];
|
|
for xz in 0..256 {
|
|
let lower_start = xz * nibble_height;
|
|
let out_start = xz * nibble_height * 2;
|
|
let copy_len = nibble_height.min(lower.len().saturating_sub(lower_start));
|
|
if copy_len > 0 {
|
|
combined[out_start..out_start + copy_len]
|
|
.copy_from_slice(&lower[lower_start..lower_start + copy_len]);
|
|
}
|
|
let upper_start = xz * nibble_height;
|
|
let out_upper = out_start + nibble_height;
|
|
let copy_len2 = nibble_height.min(upper.len().saturating_sub(upper_start));
|
|
if copy_len2 > 0 {
|
|
combined[out_upper..out_upper + copy_len2]
|
|
.copy_from_slice(&upper[upper_start..upper_start + copy_len2]);
|
|
}
|
|
}
|
|
combined
|
|
}
|
|
|
|
pub fn encode_legacy_nbt(level: &nbt::NbtCompound) -> Vec<u8> {
|
|
let mut root = nbt::NbtCompound::new("");
|
|
root.insert("Level", nbt::NbtValue::Compound(level.clone()));
|
|
nbt::write_nbt(&root)
|
|
}
|
|
|
|
/*
|
|
//neo: unused
|
|
fn write_compressed_tile_storage(out: &mut Vec<u8>, blocks: &[u8]) {
|
|
let normalized = ensure_length(blocks, BLOCKS_PER_SECTION);
|
|
let mut blob = vec![0u8; 1024 + BLOCKS_PER_SECTION];
|
|
let mut data_offset: usize = 0;
|
|
for block in 0..512 {
|
|
let index = (INDEX_TYPE_0_OR_8BIT | ((data_offset as u16) << 1)) as u16;
|
|
blob[block * 2..block * 2 + 2].copy_from_slice(&index.to_le_bytes());
|
|
for tile in 0..64 {
|
|
blob[1024 + data_offset + tile] = normalized[get_compressed_tile_index(block, tile)];
|
|
}
|
|
data_offset += 64;
|
|
}
|
|
|
|
write_be_i32(out, blob.len() as i32);
|
|
out.extend_from_slice(&blob);
|
|
}
|
|
|
|
fn write_empty_compressed_tile_storage(out: &mut Vec<u8>) {
|
|
let mut blob = vec![0u8; 1024];
|
|
for block in 0..512 {
|
|
let index = (INDEX_TYPE_0_OR_8BIT | INDEX_TYPE_0BIT_FLAG) as u16;
|
|
blob[block * 2..block * 2 + 2].copy_from_slice(&index.to_le_bytes());
|
|
}
|
|
write_be_i32(out, blob.len() as i32);
|
|
out.extend_from_slice(&blob);
|
|
}
|
|
|
|
fn write_sparse_nibble_storage(out: &mut Vec<u8>, nibble_data: &[u8], supports_all_fifteen: bool) {
|
|
let normalized = ensure_length(nibble_data, NIBBLES_PER_SECTION);
|
|
let mut plane_indices = [0u8; 128];
|
|
let mut planes: Vec<Vec<u8>> = Vec::new();
|
|
for y in 0..128 {
|
|
let mut all_zero = true;
|
|
let mut all_fifteen = supports_all_fifteen;
|
|
let mut plane = vec![0u8; 128];
|
|
let mut plane_cursor = 0;
|
|
for xz in 0..128 {
|
|
let first = get_nibble_value(&normalized, xz * 2, y);
|
|
let second = get_nibble_value(&normalized, xz * 2 + 1, y);
|
|
|
|
if first != 0 || second != 0 {
|
|
all_zero = false;
|
|
}
|
|
if supports_all_fifteen && (first != 15 || second != 15) {
|
|
all_fifteen = false;
|
|
}
|
|
|
|
plane[plane_cursor] = first | (second << 4);
|
|
plane_cursor += 1;
|
|
}
|
|
|
|
if all_zero {
|
|
plane_indices[y] = SPARSE_ALL_ZERO_INDEX;
|
|
continue;
|
|
}
|
|
if supports_all_fifteen && all_fifteen {
|
|
plane_indices[y] = SPARSE_ALL_FIFTEEN_INDEX;
|
|
continue;
|
|
}
|
|
|
|
plane_indices[y] = planes.len() as u8;
|
|
planes.push(plane);
|
|
}
|
|
|
|
write_be_i32(out, planes.len() as i32);
|
|
out.extend_from_slice(&plane_indices);
|
|
for plane in &planes {
|
|
out.extend_from_slice(plane);
|
|
}
|
|
}
|
|
|
|
fn write_empty_sparse_nibble_storage(out: &mut Vec<u8>, supports_all_fifteen: bool, fill_with_fifteen: bool) {
|
|
write_be_i32(out, 0);
|
|
let fill = if supports_all_fifteen && fill_with_fifteen {
|
|
SPARSE_ALL_FIFTEEN_INDEX
|
|
} else {
|
|
SPARSE_ALL_ZERO_INDEX
|
|
};
|
|
let plane_indices = [fill; 128];
|
|
out.extend_from_slice(&plane_indices);
|
|
}
|
|
|
|
pub fn encode_compressed_storage(level: &nbt::NbtCompound) -> Vec<u8> {
|
|
let blocks = level.byte_array("Blocks").unwrap_or(&[]);
|
|
let data = level.byte_array("Data").unwrap_or(&[]);
|
|
let sky_light_raw = level.byte_array("SkyLight").unwrap_or(&[]);
|
|
let block_light_raw = level.byte_array("BlockLight").unwrap_or(&[]);
|
|
let height_map = level.byte_array("HeightMap").unwrap_or(&[]);
|
|
let biomes = level.byte_array("Biomes").unwrap_or(&[]);
|
|
let default_sky = vec![0xFF; NIBBLES_PER_SECTION];
|
|
let default_light = vec![0u8; NIBBLES_PER_SECTION];
|
|
let sky_light = if sky_light_raw.is_empty() { default_sky.as_slice() } else { sky_light_raw };
|
|
let block_light = if block_light_raw.is_empty() { default_light.as_slice() } else { block_light_raw };
|
|
let mut out = Vec::new();
|
|
write_be_i16(&mut out, SAVE_FILE_VERSION_CHUNK_INHABITED_TIME);
|
|
write_be_i32(&mut out, level.int("xPos").unwrap_or(0));
|
|
write_be_i32(&mut out, level.int("zPos").unwrap_or(0));
|
|
write_be_i64(&mut out, level.long("LastUpdate").unwrap_or(0));
|
|
write_be_i64(&mut out, level.long("InhabitedTime").unwrap_or(0));
|
|
write_compressed_tile_storage(&mut out, &extract_lower_block_section(blocks));
|
|
write_compressed_tile_storage(&mut out, &extract_upper_block_section(blocks));
|
|
write_sparse_nibble_storage(&mut out, &extract_lower_nibble_section(data), false);
|
|
write_sparse_nibble_storage(&mut out, &extract_upper_nibble_section(data), false);
|
|
write_sparse_nibble_storage(&mut out, &extract_lower_nibble_section(sky_light), true);
|
|
write_sparse_nibble_storage(&mut out, &extract_upper_nibble_section(sky_light), true);
|
|
write_sparse_nibble_storage(&mut out, &extract_lower_nibble_section(block_light), true);
|
|
write_sparse_nibble_storage(&mut out, &extract_upper_nibble_section(block_light), true);
|
|
let hm = ensure_length(height_map, 256);
|
|
out.extend_from_slice(&hm[..256]);
|
|
write_be_i16(&mut out, level.short("TerrainPopulatedFlags").unwrap_or(0));
|
|
let bio = ensure_length(biomes, 256);
|
|
out.extend_from_slice(&bio[..256]);
|
|
let dynamic_root = nbt::NbtCompound::new("");
|
|
let dynamic_bytes = nbt::write_nbt(&dynamic_root);
|
|
out.extend_from_slice(&dynamic_bytes);
|
|
out
|
|
}*/
|
|
|
|
fn is_compressed_chunk_storage(data: &[u8]) -> bool {
|
|
if data.len() < 2 + 4 + 4 + 8 {
|
|
return false;
|
|
}
|
|
let version = read_be_i16(data, 0);
|
|
version == SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE || version == SAVE_FILE_VERSION_CHUNK_INHABITED_TIME
|
|
}
|
|
/*
|
|
//neo: unused
|
|
pub fn try_read_chunk_coordinates(
|
|
data: &[u8],
|
|
) -> Option<(i32, i32, bool)> {
|
|
if let Some((cx, cz, wrapped)) = try_read_legacy_level_coords(data) {
|
|
return Some((cx, cz, wrapped));
|
|
}
|
|
|
|
if is_compressed_chunk_storage(data) {
|
|
let cx = read_be_i32(data, 2);
|
|
let cz = read_be_i32(data, 6);
|
|
return Some((cx, cz, false));
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn try_read_legacy_level_coords(data: &[u8]) -> Option<(i32, i32, bool)> {
|
|
if data.is_empty() || data[0] != 10 {
|
|
return None;
|
|
}
|
|
let compound = nbt::read_nbt(data).ok()?;
|
|
let (level, has_wrapper) = if let Some(nbt::NbtValue::Compound(c)) = compound.get("Level") {
|
|
(c, true)
|
|
} else {
|
|
(&compound, false)
|
|
};
|
|
let cx = level.int("xPos")?;
|
|
let cz = level.int("zPos")?;
|
|
Some((cx, cz, has_wrapper))
|
|
}
|
|
|
|
pub fn force_chunk_coordinates(data: &[u8], expected_x: i32, expected_z: i32) -> Vec<u8> {
|
|
if data.is_empty() {
|
|
return Vec::new();
|
|
}
|
|
|
|
if let Ok((mut level, _)) = read_legacy_level(data) {
|
|
level.insert("xPos", nbt::NbtValue::Int(expected_x));
|
|
level.insert("zPos", nbt::NbtValue::Int(expected_z));
|
|
return encode_legacy_nbt(&level);
|
|
}
|
|
|
|
if is_compressed_chunk_storage(data) {
|
|
let mut patched = data.to_vec();
|
|
patched[2..6].copy_from_slice(&expected_x.to_be_bytes());
|
|
patched[6..10].copy_from_slice(&expected_z.to_be_bytes());
|
|
return patched;
|
|
}
|
|
|
|
Vec::new()
|
|
}
|
|
*/
|
|
fn read_compressed_tile_storage(data: &[u8], offset: &mut usize) -> Result<Vec<u8>, String> {
|
|
let allocated_size = read_be_i32(data, *offset) as usize;
|
|
*offset += 4;
|
|
if allocated_size < 1024 || *offset + allocated_size > data.len() {
|
|
return Err("Invalid CompressedTileStorage payload.".into());
|
|
}
|
|
|
|
let blob = &data[*offset..*offset + allocated_size];
|
|
*offset += allocated_size;
|
|
let data_region = &blob[1024..];
|
|
let mut blocks = vec![0u8; BLOCKS_PER_SECTION];
|
|
for block in 0..512 {
|
|
let block_index = u16::from_le_bytes([blob[block * 2], blob[block * 2 + 1]]);
|
|
let index_type = block_index & INDEX_TYPE_MASK;
|
|
if index_type == INDEX_TYPE_0_OR_8BIT {
|
|
if (block_index & INDEX_TYPE_0BIT_FLAG) != 0 {
|
|
let value = ((block_index >> 8) & 0xFF) as u8;
|
|
for tile in 0..64 {
|
|
blocks[get_compressed_tile_index(block, tile)] = value;
|
|
}
|
|
} else {
|
|
let data_offset = ((block_index >> 1) & 0x7FFE) as usize;
|
|
if data_offset + 64 > data_region.len() {
|
|
return Err("Invalid 8-bit CompressedTileStorage offset.".into());
|
|
}
|
|
for tile in 0..64 {
|
|
blocks[get_compressed_tile_index(block, tile)] = data_region[data_offset + tile];
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
let bits_per_tile = match index_type {
|
|
INDEX_TYPE_1BIT => 1,
|
|
INDEX_TYPE_2BIT => 2,
|
|
INDEX_TYPE_4BIT => 4,
|
|
_ => return Err("Unsupported CompressedTileStorage index type.".into()),
|
|
};
|
|
|
|
let tile_type_count = 1usize << bits_per_tile;
|
|
let tile_type_mask = (tile_type_count - 1) as u8;
|
|
let index_shift = 3 - index_type as usize;
|
|
let index_mask_bits = (7 >> index_type) as usize;
|
|
let index_mask_bytes = (62 >> index_shift) as usize;
|
|
let packed_data_size = (8usize << index_type) as usize;
|
|
let data_offset_packed = ((block_index >> 1) & 0x7FFE) as usize;
|
|
if data_offset_packed + tile_type_count + packed_data_size > data_region.len() {
|
|
return Err("Invalid packed CompressedTileStorage offset.".into());
|
|
}
|
|
|
|
let tile_types = &data_region[data_offset_packed..data_offset_packed + tile_type_count];
|
|
let packed = &data_region[data_offset_packed + tile_type_count..data_offset_packed + tile_type_count + packed_data_size];
|
|
for tile in 0..64 {
|
|
let idx = (tile >> index_shift) & index_mask_bytes;
|
|
let bit = (tile & index_mask_bits) * bits_per_tile;
|
|
let palette_index = (packed[idx] >> bit) & tile_type_mask;
|
|
blocks[get_compressed_tile_index(block, tile)] = tile_types[palette_index as usize];
|
|
}
|
|
}
|
|
|
|
Ok(blocks)
|
|
}
|
|
/*
|
|
//neo: unused
|
|
fn skip_compressed_tile_storage(data: &[u8], offset: &mut usize) -> Result<(), String> {
|
|
let allocated_size = read_be_i32(data, *offset) as usize;
|
|
*offset += 4;
|
|
if *offset + allocated_size > data.len() {
|
|
return Err("Invalid CompressedTileStorage payload.".into());
|
|
}
|
|
*offset += allocated_size;
|
|
Ok(())
|
|
}
|
|
*/
|
|
|
|
fn read_sparse_nibble_storage(data: &[u8], offset: &mut usize, supports_all_fifteen: bool) -> Result<Vec<u8>, String> {
|
|
let count = read_be_i32(data, *offset) as usize;
|
|
*offset += 4;
|
|
let storage_bytes = 128 + count * 128;
|
|
if count > data.len() || *offset + storage_bytes > data.len() {
|
|
return Err("Invalid SparseStorage payload.".into());
|
|
}
|
|
|
|
let blob = &data[*offset..*offset + storage_bytes];
|
|
*offset += storage_bytes;
|
|
let plane_indices = &blob[..128];
|
|
let plane_data = &blob[128..];
|
|
let mut nibble_data = vec![0u8; NIBBLES_PER_SECTION];
|
|
for y in 0..128 {
|
|
let plane_index = plane_indices[y];
|
|
if plane_index == SPARSE_ALL_ZERO_INDEX {
|
|
continue;
|
|
}
|
|
if supports_all_fifteen && plane_index == SPARSE_ALL_FIFTEEN_INDEX {
|
|
for xz in 0..256 {
|
|
set_nibble_value(&mut nibble_data, xz, y, 15);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
let plane_offset = plane_index as usize * 128;
|
|
if plane_offset + 128 > plane_data.len() {
|
|
return Err("Invalid sparse plane index.".into());
|
|
}
|
|
|
|
let plane = &plane_data[plane_offset..plane_offset + 128];
|
|
for xz in 0..128 {
|
|
let packed = plane[xz];
|
|
set_nibble_value(&mut nibble_data, xz * 2, y, packed & 0x0F);
|
|
set_nibble_value(&mut nibble_data, xz * 2 + 1, y, (packed >> 4) & 0x0F);
|
|
}
|
|
}
|
|
|
|
Ok(nibble_data)
|
|
}
|
|
/*
|
|
//neo: unused
|
|
fn skip_sparse_nibble_storage(data: &[u8], offset: &mut usize) -> Result<(), String> {
|
|
let count = read_be_i32(data, *offset) as usize;
|
|
*offset += 4;
|
|
let storage_bytes = 128 + count * 128;
|
|
if count > data.len() || *offset + storage_bytes > data.len() {
|
|
return Err("Invalid SparseStorage payload.".into());
|
|
}
|
|
*offset += storage_bytes;
|
|
Ok(())
|
|
}
|
|
*/
|
|
fn read_sized_bytes(data: &[u8], offset: &mut usize, length: usize) -> Vec<u8> {
|
|
let end = (*offset + length).min(data.len());
|
|
let result = data[*offset..end].to_vec();
|
|
*offset += length;
|
|
result
|
|
}
|
|
|
|
fn read_legacy_level(data: &[u8]) -> Result<(nbt::NbtCompound, bool), String> {
|
|
if data.is_empty() || data[0] != 10 {
|
|
return Err("Not NBT compound".into());
|
|
}
|
|
let file = nbt::read_nbt(data)?;
|
|
let has_level = file.get("Level").is_some();
|
|
let level = if let Some(nbt::NbtValue::Compound(c)) = file.get("Level") {
|
|
let mut cloned = c.clone();
|
|
cloned.name = "Level".to_string();
|
|
cloned
|
|
} else {
|
|
let mut cloned = file.clone();
|
|
cloned.name = "Level".to_string();
|
|
cloned
|
|
};
|
|
Ok((level, has_level))
|
|
}
|
|
|
|
pub fn try_decode_to_legacy_nbt(data: &[u8]) -> Option<Vec<u8>> {
|
|
if let Ok((mut level, _)) = read_legacy_level(data) {
|
|
level.name = "Level".to_string();
|
|
return Some(encode_legacy_nbt(&level));
|
|
}
|
|
|
|
if !is_compressed_chunk_storage(data) {
|
|
return None;
|
|
}
|
|
|
|
decode_compressed_chunk_to_legacy_root(data).ok().map(|root| nbt::write_nbt(&root))
|
|
}
|
|
|
|
fn decode_compressed_chunk_to_legacy_root(data: &[u8]) -> Result<nbt::NbtCompound, String> {
|
|
let mut offset = 0;
|
|
let version = read_be_i16(data, offset);
|
|
offset = 2;
|
|
if version != SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE
|
|
&& version != SAVE_FILE_VERSION_CHUNK_INHABITED_TIME
|
|
{
|
|
return Err(format!("Unsupported compressed chunk version: {}", version));
|
|
}
|
|
|
|
let chunk_x = read_be_i32(data, offset);
|
|
offset += 4;
|
|
let chunk_z = read_be_i32(data, offset);
|
|
offset += 4;
|
|
let last_update = read_be_i64(data, offset);
|
|
offset += 8;
|
|
let inhabited_time = if version >= SAVE_FILE_VERSION_CHUNK_INHABITED_TIME {
|
|
let v = read_be_i64(data, offset);
|
|
offset += 8;
|
|
v
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let lower_blocks = read_compressed_tile_storage(data, &mut offset)?;
|
|
let upper_blocks = read_compressed_tile_storage(data, &mut offset)?;
|
|
let lower_data = read_sparse_nibble_storage(data, &mut offset, false)?;
|
|
let upper_data = read_sparse_nibble_storage(data, &mut offset, false)?;
|
|
let lower_sky = read_sparse_nibble_storage(data, &mut offset, true)?;
|
|
let upper_sky = read_sparse_nibble_storage(data, &mut offset, true)?;
|
|
let lower_block_light = read_sparse_nibble_storage(data, &mut offset, true)?;
|
|
let upper_block_light = read_sparse_nibble_storage(data, &mut offset, true)?;
|
|
let height_map = read_sized_bytes(data, &mut offset, 256);
|
|
let terrain_populated_flags = read_be_i16(data, offset);
|
|
offset += 2;
|
|
let biomes = read_sized_bytes(data, &mut offset, 256);
|
|
let dynamic_root = if offset < data.len() {
|
|
nbt::read_nbt(&data[offset..]).unwrap_or_default()
|
|
} else {
|
|
nbt::NbtCompound::default()
|
|
};
|
|
|
|
let mut level = nbt::NbtCompound::new("Level");
|
|
level.insert("xPos", nbt::NbtValue::Int(chunk_x));
|
|
level.insert("zPos", nbt::NbtValue::Int(chunk_z));
|
|
level.insert("LastUpdate", nbt::NbtValue::Long(last_update));
|
|
level.insert("InhabitedTime", nbt::NbtValue::Long(inhabited_time));
|
|
level.insert(
|
|
"Blocks",
|
|
nbt::NbtValue::ByteArray(combine_block_sections(&lower_blocks, &upper_blocks)),
|
|
);
|
|
level.insert(
|
|
"Data",
|
|
nbt::NbtValue::ByteArray(combine_nibble_sections(&lower_data, &upper_data)),
|
|
);
|
|
level.insert(
|
|
"SkyLight",
|
|
nbt::NbtValue::ByteArray(combine_nibble_sections(&lower_sky, &upper_sky)),
|
|
);
|
|
level.insert(
|
|
"BlockLight",
|
|
nbt::NbtValue::ByteArray(combine_nibble_sections(
|
|
&lower_block_light,
|
|
&upper_block_light,
|
|
)),
|
|
);
|
|
level.insert("HeightMap", nbt::NbtValue::ByteArray(height_map));
|
|
level.insert(
|
|
"TerrainPopulatedFlags",
|
|
nbt::NbtValue::Short(terrain_populated_flags),
|
|
);
|
|
level.insert("Biomes", nbt::NbtValue::ByteArray(biomes));
|
|
let entities = dynamic_root
|
|
.list("Entities")
|
|
.map(|l| nbt::NbtValue::List(l.to_vec()))
|
|
.unwrap_or_else(|| nbt::NbtValue::List(Vec::new()));
|
|
level.insert("Entities", entities);
|
|
let tile_entities = dynamic_root
|
|
.list("TileEntities")
|
|
.map(|l| nbt::NbtValue::List(l.to_vec()))
|
|
.unwrap_or_else(|| nbt::NbtValue::List(Vec::new()));
|
|
level.insert("TileEntities", tile_entities);
|
|
if let Some(tile_ticks) = dynamic_root.get("TileTicks") {
|
|
level.insert("TileTicks", tile_ticks.clone());
|
|
}
|
|
|
|
let mut root = nbt::NbtCompound::new("");
|
|
root.insert("Level", nbt::NbtValue::Compound(level));
|
|
Ok(root)
|
|
}
|
|
/*
|
|
//neo: unused
|
|
pub fn try_get_compressed_chunk_nbt_offset(data: &[u8]) -> Option<usize> {
|
|
if !is_compressed_chunk_storage(data) {
|
|
return None;
|
|
}
|
|
|
|
let mut offset = 0;
|
|
let version = read_be_i16(data, offset);
|
|
offset = 2;
|
|
if version != SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE
|
|
&& version != SAVE_FILE_VERSION_CHUNK_INHABITED_TIME
|
|
{
|
|
return None;
|
|
}
|
|
|
|
offset += 8;
|
|
if version >= SAVE_FILE_VERSION_CHUNK_INHABITED_TIME {
|
|
offset += 8;
|
|
}
|
|
|
|
let r1 = skip_compressed_tile_storage(data, &mut offset);
|
|
if r1.is_err() { return None; }
|
|
let r2 = skip_compressed_tile_storage(data, &mut offset);
|
|
if r2.is_err() { return None; }
|
|
let r3 = skip_sparse_nibble_storage(data, &mut offset);
|
|
if r3.is_err() { return None; }
|
|
let r4 = skip_sparse_nibble_storage(data, &mut offset);
|
|
if r4.is_err() { return None; }
|
|
let r5 = skip_sparse_nibble_storage(data, &mut offset);
|
|
if r5.is_err() { return None; }
|
|
let r6 = skip_sparse_nibble_storage(data, &mut offset);
|
|
if r6.is_err() { return None; }
|
|
let r7 = skip_sparse_nibble_storage(data, &mut offset);
|
|
if r7.is_err() { return None; }
|
|
let r8 = skip_sparse_nibble_storage(data, &mut offset);
|
|
if r8.is_err() { return None; }
|
|
offset += 256;
|
|
offset += 2;
|
|
offset += 256;
|
|
if offset > data.len() {
|
|
return None;
|
|
}
|
|
|
|
Some(offset)
|
|
}
|
|
*/
|