#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BedrockKey {
Chunk {
x: i32,
z: i32,
dim: i32,
tag: u8,
subchunk: Option<i8>,
},
Actor(Vec<u8>),
ActorDigp { x: i32, z: i32 },
Named(String),
Binary(Vec<u8>),
}
const ACTOR_PREFIX: &[u8] = b"actorprefix";
const DIGP_PREFIX: &[u8] = b"digp";
impl BedrockKey {
pub fn parse(key: &[u8]) -> Self {
if key.starts_with(ACTOR_PREFIX) {
return BedrockKey::Actor(key[ACTOR_PREFIX.len()..].to_vec());
}
if key.starts_with(DIGP_PREFIX) && key.len() == 12 {
let x = i32::from_le_bytes(key[4..8].try_into().unwrap());
let z = i32::from_le_bytes(key[8..12].try_into().unwrap());
return BedrockKey::ActorDigp { x, z };
}
if let Some(ck) = parse_chunk_key(key) {
return ck;
}
if let Ok(s) = std::str::from_utf8(key)
&& s.bytes().all(|b| (0x20..0x7F).contains(&b))
{
return BedrockKey::Named(s.to_owned());
}
BedrockKey::Binary(key.to_vec())
}
pub fn to_raw_bytes(&self) -> Vec<u8> {
match self {
BedrockKey::Chunk {
x,
z,
dim,
tag,
subchunk,
} => {
let mut out = Vec::with_capacity(14);
out.extend_from_slice(&x.to_le_bytes());
out.extend_from_slice(&z.to_le_bytes());
if *dim != 0 {
out.extend_from_slice(&dim.to_le_bytes());
}
out.push(*tag);
if let Some(y) = subchunk {
out.push(*y as u8);
}
out
}
BedrockKey::Actor(id) => {
let mut out = ACTOR_PREFIX.to_vec();
out.extend_from_slice(id);
out
}
BedrockKey::ActorDigp { x, z } => {
let mut out = DIGP_PREFIX.to_vec();
out.extend_from_slice(&x.to_le_bytes());
out.extend_from_slice(&z.to_le_bytes());
out
}
BedrockKey::Named(s) => s.as_bytes().to_vec(),
BedrockKey::Binary(b) => b.clone(),
}
}
}
fn parse_chunk_key(key: &[u8]) -> Option<BedrockKey> {
let len = key.len();
if len == 9 || len == 10 {
let x = i32::from_le_bytes(key[0..4].try_into().ok()?);
let z = i32::from_le_bytes(key[4..8].try_into().ok()?);
let tag = key[8];
if !is_chunk_tag(tag) {
return None;
}
let subchunk = if len == 10 {
if tag != 47 {
return None;
}
Some(key[9] as i8)
} else {
None
};
return Some(BedrockKey::Chunk {
x,
z,
dim: 0,
tag,
subchunk,
});
}
if len == 13 || len == 14 {
let x = i32::from_le_bytes(key[0..4].try_into().ok()?);
let z = i32::from_le_bytes(key[4..8].try_into().ok()?);
let dim = i32::from_le_bytes(key[8..12].try_into().ok()?);
let tag = key[12];
if !is_chunk_tag(tag) {
return None;
}
let subchunk = if len == 14 {
if tag != 47 {
return None;
}
Some(key[13] as i8)
} else {
None
};
return Some(BedrockKey::Chunk {
x,
z,
dim,
tag,
subchunk,
});
}
None
}
fn is_chunk_tag(tag: u8) -> bool {
(43..=65).contains(&tag) || tag == 118
}
pub fn dim_dir(dim: i32) -> String {
match dim {
0 => "overworld".to_string(),
1 => "nether".to_string(),
2 => "the_end".to_string(),
n => format!("dim_{n}"),
}
}
pub fn is_scalar_tag(tag: u8) -> bool {
matches!(tag, 44 | 54 | 60 | 65)
}
pub fn is_nbt_tag(tag: u8) -> bool {
matches!(tag, 49..=51)
}
pub fn tag_name(tag: u8) -> &'static str {
match tag {
43 => "data3d",
44 => "version",
45 => "data2d",
46 => "data2d_legacy",
47 => "sub",
48 => "terrain_legacy",
49 => "block_entities",
50 => "entities",
51 => "pending_ticks",
52 => "block_extra_data",
53 => "biome_state",
54 => "finalized",
55 => "conversion_data",
56 => "border_blocks",
57 => "hardcoded_spawners",
58 => "random_ticks",
59 => "checksums",
60 => "generation_seed",
61 => "pre_caves_blending",
62 => "blending_biome_height",
63 => "metadata_hash",
64 => "blending_data",
65 => "actor_digest_version",
118 => "chunk_metadata_dict",
_ => "unknown",
}
}