summaryrefslogtreecommitdiff
path: root/src/metadata.rs
blob: 7692434cc3ace287c322573fbaa9a846c8a37a9a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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_name_version(self.string(self.double(0x10)));
        let authors = self.string(self.double(0x12)).map(|string| {
            string.lines().map(|line| line.to_string()).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_name_version(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<String>>,
    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>,
}