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>,
}
|