1use std::collections::HashMap;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ApplicationSizeResource {
13 pub flags: u16,
14 pub preferred_size: u32,
15 pub minimum_size: u32,
16}
17
18impl ApplicationSizeResource {
19 pub fn parse(data: &[u8]) -> Option<Self> {
20 if data.len() < 10 {
21 return None;
22 }
23 Some(Self {
24 flags: u16::from_be_bytes([data[0], data[1]]),
25 preferred_size: u32::from_be_bytes([data[2], data[3], data[4], data[5]]),
26 minimum_size: u32::from_be_bytes([data[6], data[7], data[8], data[9]]),
27 })
28 }
29
30 pub fn preferred_partition_size(self) -> Option<u32> {
31 if self.preferred_size >= self.minimum_size && self.preferred_size >= 128 * 1024 {
32 Some(self.preferred_size)
33 } else {
34 None
35 }
36 }
37}
38
39#[derive(Debug, Clone, Default)]
44pub struct Code0Header {
45 pub above_a5: u32,
48 pub below_a5: u32,
50 pub jump_table_size: u32,
52 pub jump_table_offset: u32,
54}
55
56impl Code0Header {
57 pub fn parse(data: &[u8]) -> Option<Self> {
61 if data.len() < 16 {
62 return None;
63 }
64 Some(Self {
65 above_a5: u32::from_be_bytes([data[0], data[1], data[2], data[3]]),
66 below_a5: u32::from_be_bytes([data[4], data[5], data[6], data[7]]),
67 jump_table_size: u32::from_be_bytes([data[8], data[9], data[10], data[11]]),
68 jump_table_offset: u32::from_be_bytes([data[12], data[13], data[14], data[15]]),
69 })
70 }
71
72 pub fn num_entries(&self) -> usize {
74 (self.jump_table_size / 8) as usize
75 }
76}
77
78#[derive(Debug, Clone)]
82pub struct JumpTableEntry {
83 pub offset: u16,
85 pub segment: i16,
87 pub loaded: bool,
89 pub address: u32,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum CodeSegmentHeader {
102 MpwFar,
104 Near { table_offset: u16, entry_count: u16 },
107 ThinkFar {
109 has_relocations: bool,
110 first_entry_index: u16,
111 entry_count: u16,
112 },
113}
114
115impl CodeSegmentHeader {
116 const THINK_RELOC_FLAG: u16 = 0x8000;
117 const THINK_FAR_FLAG: u16 = 0x4000;
118
119 pub fn parse(data: &[u8]) -> Option<Self> {
120 if data.len() < 4 {
121 return None;
122 }
123
124 let first = u16::from_be_bytes([data[0], data[1]]);
125 let second = u16::from_be_bytes([data[2], data[3]]);
126 Some(Self::from_words(first, second))
127 }
128
129 pub fn from_words(first: u16, second: u16) -> Self {
130 if first == 0xFFFF {
131 Self::MpwFar
132 } else if (second & Self::THINK_FAR_FLAG) != 0 {
133 Self::ThinkFar {
134 has_relocations: (first & Self::THINK_RELOC_FLAG) != 0,
135 first_entry_index: first & !Self::THINK_RELOC_FLAG,
136 entry_count: second & 0x3FFF,
137 }
138 } else {
139 Self::Near {
140 table_offset: first,
141 entry_count: second,
142 }
143 }
144 }
145
146 pub fn code_header_size(self) -> u32 {
147 match self {
148 Self::MpwFar => 40,
149 Self::Near { .. } | Self::ThinkFar { .. } => 4,
150 }
151 }
152
153 pub fn jump_table_start_offset(self) -> Option<u32> {
154 match self {
155 Self::MpwFar => None,
156 Self::Near { table_offset, .. } => Some(table_offset as u32),
157 Self::ThinkFar {
158 first_entry_index, ..
159 } => Some(first_entry_index as u32 * 8),
160 }
161 }
162
163 pub fn jump_table_entry_count(self) -> Option<u32> {
164 match self {
165 Self::MpwFar => None,
166 Self::Near { entry_count, .. } | Self::ThinkFar { entry_count, .. } => {
167 Some(entry_count as u32)
168 }
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::{ApplicationSizeResource, CodeSegmentHeader};
176
177 #[test]
178 fn parses_application_size_resource_flags_and_partition_sizes() {
179 let bytes = [0x51, 0x80, 0x00, 0x30, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00];
180 let size = ApplicationSizeResource::parse(&bytes).expect("SIZE resource should parse");
181
182 assert_eq!(size.flags, 0x5180);
183 assert_eq!(size.preferred_size, 0x0030_0000);
184 assert_eq!(size.minimum_size, 0x0020_0000);
185 assert_eq!(size.preferred_partition_size(), Some(0x0030_0000));
186 }
187
188 #[test]
189 fn rejects_truncated_or_inverted_application_size_resources() {
190 assert!(ApplicationSizeResource::parse(&[0; 9]).is_none());
191
192 let inverted = ApplicationSizeResource {
193 flags: 0,
194 preferred_size: 0x0010_0000,
195 minimum_size: 0x0020_0000,
196 };
197 assert_eq!(inverted.preferred_partition_size(), None);
198 }
199
200 #[test]
201 fn parses_think_far_header_entry_index_and_count_flags() {
202 let header = CodeSegmentHeader::from_words(0x8051, 0x4085);
203
204 assert_eq!(
205 header,
206 CodeSegmentHeader::ThinkFar {
207 has_relocations: true,
208 first_entry_index: 0x0051,
209 entry_count: 0x0085,
210 }
211 );
212 assert_eq!(header.code_header_size(), 4);
213 assert_eq!(header.jump_table_start_offset(), Some(0x0051 * 8));
214 assert_eq!(header.jump_table_entry_count(), Some(0x0085));
215 }
216
217 #[test]
218 fn parses_near_and_mpw_far_segment_headers() {
219 let near = CodeSegmentHeader::from_words(0x0018, 0x0002);
220 assert_eq!(
221 near,
222 CodeSegmentHeader::Near {
223 table_offset: 0x0018,
224 entry_count: 2,
225 }
226 );
227 assert_eq!(near.code_header_size(), 4);
228 assert_eq!(near.jump_table_start_offset(), Some(0x0018));
229 assert_eq!(near.jump_table_entry_count(), Some(2));
230
231 let mpw_far = CodeSegmentHeader::from_words(0xFFFF, 0x0000);
232 assert_eq!(mpw_far, CodeSegmentHeader::MpwFar);
233 assert_eq!(mpw_far.code_header_size(), 40);
234 assert_eq!(mpw_far.jump_table_start_offset(), None);
235 assert_eq!(mpw_far.jump_table_entry_count(), None);
236 }
237}
238
239#[derive(Default)]
248pub struct LoadedApp {
249 pub code0_header: Code0Header,
251 pub a5_base: u32,
254 pub jump_table: Vec<JumpTableEntry>,
256 pub segment_bases: HashMap<i16, u32>,
259 pub initial_sp: u32,
262 pub size_resource: Option<ApplicationSizeResource>,
264}
265
266impl LoadedApp {
267 pub fn entry_point(&self, a5_base: u32) -> u32 {
268 a5_base + self.code0_header.jump_table_offset + 2
269 }
270}