use super::{Candidate, ModeBufferGuard, Window, MODE_GUARD_BYTES};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Value {
Unknown,
Constant(u32),
Cdb(u32),
ByteOne,
ShiftedMode,
Mode,
Buffer,
GuardResult,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Prologue {
pub start: u32,
pub cdb_address: u32,
pub cdb_registers: u8,
pub constants: [Option<u32>; 8],
pub guard: Option<u32>,
pub rejection: Option<u32>,
pub stack_bytes: u16,
}
fn parse(w: &Window<'_>, start: u32, end: u32) -> Option<Prologue> {
if w.word(start)? != 0xb5f0 && w.literal(start).is_none() {
return None;
}
let mut values = [Value::Unknown; 8];
let mut at = start;
let mut pushed = false;
let mut stack = false;
let mut stack_bytes = 0;
let mut cdb = None;
let mut guard = None;
let mut rejection = None;
let mut compared = false;
while at < end {
let op = w.word(at)?;
if compared && op & 0xff00 != 0xd000 {
return None;
}
if op == 0xb5f0 && !pushed {
pushed = true;
} else if matches!(op, 0xb08f | 0xb091) && pushed && !stack {
stack = true;
stack_bytes = (op & 127) * 4;
} else if op & 0xf800 == 0x4800 {
if !pushed && (at != start || w.word(at.checked_add(2)?)? != 0xb5f0) {
return None;
}
let (reg, value) = w.literal(at)?;
values[reg as usize] = Value::Constant(value);
} else if op & 0xf800 == 0x7800 && pushed {
let base = ((op >> 3) & 7) as usize;
let dest = (op & 7) as usize;
let offset = (op >> 6) & 31;
let address = match values[base] {
Value::Cdb(address) | Value::Constant(address)
if (0x0200_0000..0x0201_0000).contains(&address) =>
{
address
}
_ => return None,
};
if cdb.is_some_and(|previous| previous != address) {
return None;
}
cdb = Some(address);
values[base] = Value::Cdb(address);
values[dest] = match offset {
1 => Value::ByteOne,
2 => Value::Buffer,
_ => return None,
};
} else if op & 0xf800 == 0x2000 && pushed {
values[((op >> 8) & 7) as usize] = Value::Constant(u32::from(op & 255));
} else if op & 0xf800 == 0 || op & 0xf800 == 0x0800 {
let source = values[((op >> 3) & 7) as usize];
let shift = (op >> 6) & 31;
let left = op & 0xf800 == 0;
values[(op & 7) as usize] = match (source, shift, left) {
(value, 0, true) => value,
(Value::ByteOne, 27, true) => Value::ShiftedMode,
(Value::ShiftedMode, 27, false) => Value::Mode,
(Value::Constant(value), shift, true) => Value::Constant(value << shift),
_ => return None,
};
} else if op & 0xf800 == 0xf000 && guard.is_none() {
if values[0] != Value::Mode || values[1] != Value::Buffer {
return None;
}
let second = w.word(at.checked_add(2)?)?;
if second & 0xf800 != 0xf800 {
return None;
}
let delta = (u32::from(op & 2047) << 12) | (u32::from(second & 2047) << 1);
let delta = ((delta as i32) << 9) >> 9;
let target = u32::try_from(i64::from(at) + 4 + i64::from(delta)).ok()?;
ModeBufferGuard::inspect(w.bytes(target, MODE_GUARD_BYTES)?)?;
guard = Some(target);
values[0] = Value::GuardResult;
values[2] = Value::Unknown;
at = at.checked_add(2)?;
} else if op == 0x2800 && values[0] == Value::GuardResult && !compared {
compared = true;
} else if op & 0xff00 == 0xd000 && compared && rejection.is_none() {
rejection = Some(w.conditional(at, 0xd000)?);
compared = false;
} else {
return None;
}
at = at.checked_add(2)?;
}
if at != end
|| !pushed
|| !stack
|| compared
|| guard.is_some() != rejection.is_some()
|| values[3] != Value::Mode
{
return None;
}
let cdb_address = cdb?;
let mut cdb_registers = 0;
let mut constants = [None; 8];
for (reg, value) in values.iter().enumerate() {
match value {
Value::Cdb(address) if *address == cdb_address => cdb_registers |= 1 << reg,
Value::Constant(value) => constants[reg] = Some(*value),
_ => (),
}
}
Some(Prologue {
start,
cdb_address,
cdb_registers,
constants,
guard,
rejection,
stack_bytes,
})
}
pub fn inspect_prologue(bytes: &[u8], address: u32, table: &Candidate) -> Option<Prologue> {
let w = Window {
bytes,
base: address,
};
let end = if table.helper.is_some() {
table.table
} else {
table.table.checked_sub(6)?
};
let mut result = None;
for distance in (20..=64).step_by(2) {
let Some(start) = end.checked_sub(distance) else {
continue;
};
if let Some(found) = parse(&w, start, end) {
if result.is_some() {
return None;
}
result = Some(found);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
fn v1() -> std::vec::Vec<u8> {
let mut bytes = std::vec![0xff; 0x1800];
for (address, hex) in [
(0x9ba38, "0122022804d0052805d00a280ad108e08f2907d105e0042903d0052901d0062900d1002210007047"),
(0x9ca02, "564bf0b599788fb05878c006c00efff712f80126634f002808d059781800cb06db0e042100255f4c092b00d3f9e3"),
(0x9ca30, "5b007b449b885b009f44ac0508002900ac052f001401ee02ac057104"),
(0x9ca4c, "574a93691b091b019361437adb0702d0936933439361437a9b0703d59369022423439361437a5b0702d5936919439161407a000703d590690821084390610fb0f0bd"),
(0x9cb5c, "f40c0002"),
(0x9cba4, "220c0002380c0002"),
(0x9cbac, "40200004"),
] {
for i in (0..hex.len()).step_by(2) {
bytes[address - 0x9ba00 + i / 2] = u8::from_str_radix(&hex[i..i + 2], 16).unwrap();
}
}
bytes
}
fn table() -> Candidate {
Candidate {
table: 0x9ca30,
entry: 0x9ca4c,
mode1_target: 0x9ca4c,
mode6_target: 0x9d018,
helper: None,
}
}
#[test]
fn real_v1_prologue_binds_guard_arguments_and_preloaded_constants() {
let bytes = v1();
let found = inspect_prologue(&bytes, 0x9ba00, &table()).unwrap();
assert_eq!(found.start, 0x9ca02);
assert_eq!(found.cdb_address, 0x02000cf4);
assert_eq!(found.guard, Some(0x9ba38));
assert_eq!(found.rejection, Some(0x9ca2e));
assert_eq!(found.constants[1], Some(4));
assert_eq!(found.constants[5], Some(0));
assert_eq!(found.constants[6], Some(1));
assert_ne!(found.cdb_registers & 1, 0);
for address in [0x9ca06, 0x9ca0c, 0x9ca10, 0x9ca18, 0x9ca1b, 0x9cb5f] {
let mut changed = bytes.clone();
changed[address - 0x9ba00] ^= 1;
assert!(
inspect_prologue(&changed, 0x9ba00, &table()).is_none(),
"{address:x}"
);
}
for offset in 0..MODE_GUARD_BYTES {
let mut changed = bytes.clone();
changed[0x38 + offset] ^= 1;
assert!(inspect_prologue(&changed, 0x9ba00, &table()).is_none());
}
assert!(inspect_prologue(&bytes[..0x100], 0x9ba00, &table()).is_none());
assert!(inspect_prologue(&bytes, u32::MAX - 4, &table()).is_none());
}
#[test]
fn real_v1_flag_body_binds_all_stores_branches_and_preloaded_constants() {
let bytes = v1();
let prelude = inspect_prologue(&bytes, 0x9ba00, &table()).unwrap();
let found = super::super::inspect_flags(&bytes, 0x9ba00, &table(), &prelude).unwrap();
assert_eq!(found.end, 0x9ca8e);
for address in [
0x9ca5a, 0x9ca60, 0x9ca66, 0x9ca6e, 0x9ca74, 0x9ca7a, 0x9ca80, 0x9ca88, 0x9ca8c,
] {
let mut changed = bytes.clone();
changed[address - 0x9ba00] ^= 1;
assert!(
super::super::inspect_flags(&changed, 0x9ba00, &table(), &prelude).is_none(),
"{address:x}"
);
}
let mut invented = prelude.clone();
invented.constants[6] = Some(2);
assert!(super::super::inspect_flags(&bytes, 0x9ba00, &table(), &invented).is_none());
for register in [0u16, 2] {
let mut changed = bytes.clone();
changed[0x106a..0x106c].copy_from_slice(&(0x2002 | (register << 8)).to_le_bytes());
changed[0x106c..0x106e].copy_from_slice(&(0x4303 | (register << 3)).to_le_bytes());
assert!(super::super::inspect_flags(&changed, 0x9ba00, &table(), &prelude).is_none());
}
}
}