use phosphor::Colour; pub fn parse_metadata(bytecode: &[u8]) -> Option { MetadataParser::from_bytecode(bytecode).parse() } struct MetadataParser<'a> { bytecode: &'a [u8], } impl<'a> MetadataParser<'a> { pub fn from_bytecode(bytecode: &'a [u8]) -> Self { Self { bytecode } } pub fn parse(self) -> Option { macro_rules! array { ($len:expr, $slice:expr) => {{ let mut array = [0; $len]; for i in 0..$len { array[i] = *$slice.get(i).unwrap_or(&0); } array }}; } if self.range(0x00, 3) != &[0x41, 0x00, 0x20] { return None; } let (name, version) = split_string(self.string(self.double(0x10))); let authors = self.string(self.double(0x12)).map(|string| { string.lines().map(|line| { let (name, year) = split_string(Some(line.to_string())); Author { name: name.unwrap(), year } }).collect() }); Some( ProgramMetadata { bedrock_string: array!(7, self.range(0x03, 7)), program_memory_size: match self.double(0x0a) { 0 => 65536, double => double as usize }, working_stack_size: match self.byte(0x0c) { 0 => 256, byte => byte as usize }, return_stack_size: match self.byte(0x0d) { 0 => 256, byte => byte as usize }, required_devices: self.double(0x0e), name, version, authors, description: self.string(self.double(0x14)), path_buffer: match self.double(0x16) { 0 => None, addr => Some(addr as usize) }, small_icon: match self.double(0x18) { 0 => None, addr => Some(array!(72, self.range(addr, 72))) }, large_icon: match self.double(0x1a) { 0 => None, addr => Some(array!(512, self.range(addr, 512))) }, bg_colour: parse_colour(self.double(0x1c)), fg_colour: parse_colour(self.double(0x1e)), } ) } fn byte(&self, address: u16) -> u8 { *self.bytecode.get(address as usize).unwrap_or(&0) } fn double(&self, address: u16) -> u16 { u16::from_be_bytes([ *self.bytecode.get(address as usize).unwrap_or(&0), *self.bytecode.get(address as usize + 1).unwrap_or(&0), ]) } fn range(&self, address: u16, length: usize) -> &[u8] { let start = address as usize; let end = start + length; self.bytecode.get(start..end).unwrap_or(&[]) } fn string(&self, address: u16) -> Option { if address == 0 { return None; } let start = address as usize; let mut end = start; while let Some(byte) = self.bytecode.get(end) { match byte { 0 => break, _ => end += 1 } } let range = self.bytecode.get(start..end)?; Some( String::from_utf8_lossy(range).to_string() ) } } fn split_string(string: Option) -> (Option, Option) { if let Some(string) = string { match string.split_once(", ") { Some((left, right)) => (Some(left.to_string()), Some(right.to_string())), None => (Some(string), None), } } else { (None, None) } } fn parse_colour(double: u16) -> Option { let i = (double >> 12 ) as usize; let r = (double >> 8 & 0xf) as u8 * 17; let g = (double >> 4 & 0xf) as u8 * 17; let b = (double & 0xf) as u8 * 17; match i { 0 => Some(Colour::from_rgb(r, g, b)), _ => None, } } pub struct ProgramMetadata { pub bedrock_string: [u8; 7], pub program_memory_size: usize, pub working_stack_size: usize, pub return_stack_size: usize, pub required_devices: u16, pub name: Option, pub version: Option, pub authors: Option>, pub description: Option, pub path_buffer: Option, pub small_icon: Option<[u8; 72]>, pub large_icon: Option<[u8; 512]>, pub bg_colour: Option, pub fg_colour: Option, } pub struct Author { pub name: String, pub year: Option, }