compact binary serialization format with built-in compression
at trunk 6.0 kB view raw
1//! Conversion utilities for converting NBT tags to Hateno format. 2use hateno::{Tag, TagKind}; 3use nbtree_core::nbt::{Tag as NBTTag, TagId as NBTTagKind}; 4use uuid::Uuid; 5 6#[derive(Debug, Clone, thiserror::Error)] 7pub enum Error { 8 #[error("Cannot convert NBT kind '{0:?}' to Hateno.")] 9 CannotConvertKind(NBTTagKind), 10} 11 12pub type Result<T> = core::result::Result<T, Error>; 13 14#[derive(Debug, Clone)] 15pub struct ConversionOptions { 16 /// Whether the converter should attempt to read Int Arrays with 4 items or 17 /// Long Arrays with 2 items as UUIDs (strictly v4). 18 pub attempt_to_convert_uuids: bool, 19} 20 21impl Default for ConversionOptions { 22 fn default() -> Self { 23 Self { 24 attempt_to_convert_uuids: false, 25 } 26 } 27} 28 29impl ConversionOptions { 30 pub fn attempt_to_convert_uuids(mut self, convert: bool) -> Self { 31 self.attempt_to_convert_uuids = convert; 32 self 33 } 34} 35 36/// Converts an NBT tag to its Hateno equivalent. 37/// 38/// This function performs a recursive conversion of NBT data structures 39/// to Hateno format, handling the differences between the two formats: 40/// 41/// - NBT byte/int/long arrays become Hateno `Array<i8>`/`Array<i32>`/`Array<i64>` 42/// - NBT lists of primitive types become Hateno homogeneous `Array`s 43/// - NBT lists of complex types become Hateno heterogeneous `List`s 44/// - NBT compounds become Hateno `Map`s with string keys 45pub fn convert_tag(tag: NBTTag, opts: &ConversionOptions) -> Result<Tag> { 46 match tag { 47 NBTTag::End => Err(Error::CannotConvertKind(NBTTagKind::End)), 48 NBTTag::Byte(byte) => Ok(Tag::I8(byte)), 49 NBTTag::Short(short) => Ok(Tag::I16(short)), 50 NBTTag::Int(int) => Ok(Tag::I32(int)), 51 NBTTag::Long(long) => Ok(Tag::I64(long)), 52 NBTTag::Float(float) => Ok(Tag::F32(float)), 53 NBTTag::Double(double) => Ok(Tag::F64(double)), 54 NBTTag::ByteArray(bytes) => Ok(Tag::Array( 55 TagKind::I8, 56 bytes.into_iter().map(Tag::I8).collect(), 57 )), 58 NBTTag::String(string) => Ok(Tag::String(string)), 59 NBTTag::List(tag_kind, tags) => match tag_kind { 60 NBTTagKind::Byte 61 | NBTTagKind::Short 62 | NBTTagKind::Int 63 | NBTTagKind::Long 64 | NBTTagKind::Float 65 | NBTTagKind::Double => { 66 let converted_kind = match tag_kind { 67 NBTTagKind::Byte => TagKind::I8, 68 NBTTagKind::Short => TagKind::I16, 69 NBTTagKind::Int => TagKind::I32, 70 NBTTagKind::Long => TagKind::I64, 71 NBTTagKind::Float => TagKind::F32, 72 NBTTagKind::Double => TagKind::F64, 73 _ => unreachable!( 74 "tag_kind should be one of the primitive types, got: {:?} despite already matching.", 75 tag_kind 76 ), 77 }; 78 79 let converted_tags: core::result::Result<Vec<Tag>, _> = tags 80 .into_iter() 81 .map(|tag| convert_tag(tag, opts)) 82 .collect(); 83 84 Ok(Tag::Array(converted_kind, converted_tags?)) 85 } 86 _ => { 87 let converted_tags: Result<Vec<Tag>> = tags 88 .into_iter() 89 .map(|tag| convert_tag(tag, opts)) 90 .collect(); 91 Ok(Tag::List(converted_tags?)) 92 } 93 }, 94 NBTTag::Compound(index_map) => { 95 let converted_map: Result<Vec<(Tag, Tag)>> = index_map 96 .into_iter() 97 .map(|(key, value)| { 98 Ok((Tag::String(key), convert_tag(*value, opts)?)) 99 }) 100 .collect(); 101 Ok(Tag::Map(converted_map?)) 102 } 103 NBTTag::IntArray(items) => { 104 if items.len() == 4 && opts.attempt_to_convert_uuids { 105 let bytes: [u8; 16] = [ 106 (items[0] >> 24) as u8, 107 (items[0] >> 16) as u8, 108 (items[0] >> 8) as u8, 109 items[0] as u8, 110 (items[1] >> 24) as u8, 111 (items[1] >> 16) as u8, 112 (items[1] >> 8) as u8, 113 items[1] as u8, 114 (items[2] >> 24) as u8, 115 (items[2] >> 16) as u8, 116 (items[2] >> 8) as u8, 117 items[2] as u8, 118 (items[3] >> 24) as u8, 119 (items[3] >> 16) as u8, 120 (items[3] >> 8) as u8, 121 items[3] as u8, 122 ]; 123 124 let uuid = Uuid::from_bytes(bytes); 125 126 if uuid.get_version() == Some(uuid::Version::Random) { 127 return Ok(Tag::Uuid(uuid)); 128 } else { 129 return Ok(Tag::Array( 130 TagKind::I32, 131 items.into_iter().map(Tag::I32).collect(), 132 )); 133 } 134 } else { 135 Ok(Tag::Array( 136 TagKind::I32, 137 items.into_iter().map(Tag::I32).collect(), 138 )) 139 } 140 } 141 NBTTag::LongArray(items) => { 142 if items.len() == 2 && opts.attempt_to_convert_uuids { 143 let most = items[0] as u64; // treating as raw bits 144 let least = items[1] as u64; // ditto 145 let uuid = Uuid::from_u64_pair(most, least); 146 147 if uuid.get_version() == Some(uuid::Version::Random) { 148 Ok(Tag::Uuid(uuid)) 149 } else { 150 Ok(Tag::Array( 151 TagKind::I64, 152 items.into_iter().map(Tag::I64).collect(), 153 )) 154 } 155 } else { 156 Ok(Tag::Array( 157 TagKind::I64, 158 items.into_iter().map(Tag::I64).collect(), 159 )) 160 } 161 } 162 } 163}