use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct Code0Header {
pub above_a5: u32,
pub below_a5: u32,
pub jump_table_size: u32,
pub jump_table_offset: u32,
}
impl Code0Header {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 16 {
return None;
}
Some(Self {
above_a5: u32::from_be_bytes([data[0], data[1], data[2], data[3]]),
below_a5: u32::from_be_bytes([data[4], data[5], data[6], data[7]]),
jump_table_size: u32::from_be_bytes([data[8], data[9], data[10], data[11]]),
jump_table_offset: u32::from_be_bytes([data[12], data[13], data[14], data[15]]),
})
}
pub fn num_entries(&self) -> usize {
(self.jump_table_size / 8) as usize
}
}
#[derive(Debug, Clone)]
pub struct JumpTableEntry {
pub offset: u16,
pub segment: i16,
pub loaded: bool,
pub address: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodeSegmentHeader {
MpwFar,
Near { table_offset: u16, entry_count: u16 },
ThinkFar {
has_relocations: bool,
first_entry_index: u16,
entry_count: u16,
},
}
impl CodeSegmentHeader {
const THINK_RELOC_FLAG: u16 = 0x8000;
const THINK_FAR_FLAG: u16 = 0x4000;
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
let first = u16::from_be_bytes([data[0], data[1]]);
let second = u16::from_be_bytes([data[2], data[3]]);
Some(Self::from_words(first, second))
}
pub fn from_words(first: u16, second: u16) -> Self {
if first == 0xFFFF {
Self::MpwFar
} else if (second & Self::THINK_FAR_FLAG) != 0 {
Self::ThinkFar {
has_relocations: (first & Self::THINK_RELOC_FLAG) != 0,
first_entry_index: first & !Self::THINK_RELOC_FLAG,
entry_count: second & 0x3FFF,
}
} else {
Self::Near {
table_offset: first,
entry_count: second,
}
}
}
pub fn code_header_size(self) -> u32 {
match self {
Self::MpwFar => 40,
Self::Near { .. } | Self::ThinkFar { .. } => 4,
}
}
pub fn jump_table_start_offset(self) -> Option<u32> {
match self {
Self::MpwFar => None,
Self::Near { table_offset, .. } => Some(table_offset as u32),
Self::ThinkFar {
first_entry_index, ..
} => Some(first_entry_index as u32 * 8),
}
}
pub fn jump_table_entry_count(self) -> Option<u32> {
match self {
Self::MpwFar => None,
Self::Near { entry_count, .. } | Self::ThinkFar { entry_count, .. } => {
Some(entry_count as u32)
}
}
}
}
#[cfg(test)]
mod tests {
use super::CodeSegmentHeader;
#[test]
fn parses_think_far_header_entry_index_and_count_flags() {
let header = CodeSegmentHeader::from_words(0x8051, 0x4085);
assert_eq!(
header,
CodeSegmentHeader::ThinkFar {
has_relocations: true,
first_entry_index: 0x0051,
entry_count: 0x0085,
}
);
assert_eq!(header.code_header_size(), 4);
assert_eq!(header.jump_table_start_offset(), Some(0x0051 * 8));
assert_eq!(header.jump_table_entry_count(), Some(0x0085));
}
#[test]
fn parses_near_and_mpw_far_segment_headers() {
let near = CodeSegmentHeader::from_words(0x0018, 0x0002);
assert_eq!(
near,
CodeSegmentHeader::Near {
table_offset: 0x0018,
entry_count: 2,
}
);
assert_eq!(near.code_header_size(), 4);
assert_eq!(near.jump_table_start_offset(), Some(0x0018));
assert_eq!(near.jump_table_entry_count(), Some(2));
let mpw_far = CodeSegmentHeader::from_words(0xFFFF, 0x0000);
assert_eq!(mpw_far, CodeSegmentHeader::MpwFar);
assert_eq!(mpw_far.code_header_size(), 40);
assert_eq!(mpw_far.jump_table_start_offset(), None);
assert_eq!(mpw_far.jump_table_entry_count(), None);
}
}
#[derive(Default)]
pub struct LoadedApp {
pub code0_header: Code0Header,
pub a5_base: u32,
pub jump_table: Vec<JumpTableEntry>,
pub segment_bases: HashMap<i16, u32>,
pub initial_sp: u32,
}
impl LoadedApp {
pub fn entry_point(&self, a5_base: u32) -> u32 {
a5_base + self.code0_header.jump_table_offset + 2
}
}