summaryrefslogtreecommitdiff
path: root/src/metadata.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/metadata.rs')
-rw-r--r--src/metadata.rs136
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>,
+}