//! Conversion utilities for converting NBT tags to Hateno format. use hateno::{Tag, TagKind}; use nbtree_core::nbt::{Tag as NBTTag, TagId as NBTTagKind}; use uuid::Uuid; #[derive(Debug, Clone, thiserror::Error)] pub enum Error { #[error("Cannot convert NBT kind '{0:?}' to Hateno.")] CannotConvertKind(NBTTagKind), } pub type Result = core::result::Result; #[derive(Debug, Clone)] pub struct ConversionOptions { /// Whether the converter should attempt to read Int Arrays with 4 items or /// Long Arrays with 2 items as UUIDs (strictly v4). pub attempt_to_convert_uuids: bool, } impl Default for ConversionOptions { fn default() -> Self { Self { attempt_to_convert_uuids: false, } } } impl ConversionOptions { pub fn attempt_to_convert_uuids(mut self, convert: bool) -> Self { self.attempt_to_convert_uuids = convert; self } } /// Converts an NBT tag to its Hateno equivalent. /// /// This function performs a recursive conversion of NBT data structures /// to Hateno format, handling the differences between the two formats: /// /// - NBT byte/int/long arrays become Hateno `Array`/`Array`/`Array` /// - NBT lists of primitive types become Hateno homogeneous `Array`s /// - NBT lists of complex types become Hateno heterogeneous `List`s /// - NBT compounds become Hateno `Map`s with string keys pub fn convert_tag(tag: NBTTag, opts: &ConversionOptions) -> Result { match tag { NBTTag::End => Err(Error::CannotConvertKind(NBTTagKind::End)), NBTTag::Byte(byte) => Ok(Tag::I8(byte)), NBTTag::Short(short) => Ok(Tag::I16(short)), NBTTag::Int(int) => Ok(Tag::I32(int)), NBTTag::Long(long) => Ok(Tag::I64(long)), NBTTag::Float(float) => Ok(Tag::F32(float)), NBTTag::Double(double) => Ok(Tag::F64(double)), NBTTag::ByteArray(bytes) => Ok(Tag::Array( TagKind::I8, bytes.into_iter().map(Tag::I8).collect(), )), NBTTag::String(string) => Ok(Tag::String(string)), NBTTag::List(tag_kind, tags) => match tag_kind { NBTTagKind::Byte | NBTTagKind::Short | NBTTagKind::Int | NBTTagKind::Long | NBTTagKind::Float | NBTTagKind::Double => { let converted_kind = match tag_kind { NBTTagKind::Byte => TagKind::I8, NBTTagKind::Short => TagKind::I16, NBTTagKind::Int => TagKind::I32, NBTTagKind::Long => TagKind::I64, NBTTagKind::Float => TagKind::F32, NBTTagKind::Double => TagKind::F64, _ => unreachable!( "tag_kind should be one of the primitive types, got: {:?} despite already matching.", tag_kind ), }; let converted_tags: core::result::Result, _> = tags .into_iter() .map(|tag| convert_tag(tag, opts)) .collect(); Ok(Tag::Array(converted_kind, converted_tags?)) } _ => { let converted_tags: Result> = tags .into_iter() .map(|tag| convert_tag(tag, opts)) .collect(); Ok(Tag::List(converted_tags?)) } }, NBTTag::Compound(index_map) => { let converted_map: Result> = index_map .into_iter() .map(|(key, value)| { Ok((Tag::String(key), convert_tag(*value, opts)?)) }) .collect(); Ok(Tag::Map(converted_map?)) } NBTTag::IntArray(items) => { if items.len() == 4 && opts.attempt_to_convert_uuids { let bytes: [u8; 16] = [ (items[0] >> 24) as u8, (items[0] >> 16) as u8, (items[0] >> 8) as u8, items[0] as u8, (items[1] >> 24) as u8, (items[1] >> 16) as u8, (items[1] >> 8) as u8, items[1] as u8, (items[2] >> 24) as u8, (items[2] >> 16) as u8, (items[2] >> 8) as u8, items[2] as u8, (items[3] >> 24) as u8, (items[3] >> 16) as u8, (items[3] >> 8) as u8, items[3] as u8, ]; let uuid = Uuid::from_bytes(bytes); if uuid.get_version() == Some(uuid::Version::Random) { return Ok(Tag::Uuid(uuid)); } else { return Ok(Tag::Array( TagKind::I32, items.into_iter().map(Tag::I32).collect(), )); } } else { Ok(Tag::Array( TagKind::I32, items.into_iter().map(Tag::I32).collect(), )) } } NBTTag::LongArray(items) => { if items.len() == 2 && opts.attempt_to_convert_uuids { let most = items[0] as u64; // treating as raw bits let least = items[1] as u64; // ditto let uuid = Uuid::from_u64_pair(most, least); if uuid.get_version() == Some(uuid::Version::Random) { Ok(Tag::Uuid(uuid)) } else { Ok(Tag::Array( TagKind::I64, items.into_iter().map(Tag::I64).collect(), )) } } else { Ok(Tag::Array( TagKind::I64, items.into_iter().map(Tag::I64).collect(), )) } } } }