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
128
129
130
131
132
133
134
135
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>,
}
|