diff options
Diffstat (limited to 'src/metadata.rs')
-rw-r--r-- | src/metadata.rs | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..7130517 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,136 @@ +use phosphor::Colour; + + +pub fn parse_metadata(bytecode: &[u8]) -> Option<ProgramMetadata> { + 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<ProgramMetadata> { + 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<String> { + 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<String>) -> (Option<String>, Option<String>) { + 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<Colour> { + 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<String>, + pub version: Option<String>, + pub authors: Option<Vec<Author>>, + pub description: Option<String>, + pub path_buffer: Option<usize>, + pub small_icon: Option<[u8; 72]>, + pub large_icon: Option<[u8; 512]>, + pub bg_colour: Option<Colour>, + pub fg_colour: Option<Colour>, +} + + +pub struct Author { + pub name: String, + pub year: Option<String>, +} |