use super::{
decoding::{self, Call, Value},
programming::{self, IntegritySelection, PlainSignature, Selection},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SelectedRange {
pub selection: Selection,
pub signature: Option<PlainSignature>,
pub acceptance: Option<programming::AcceptanceTail>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BulkDecode {
pub header: Call,
pub from_zero: Call,
pub preserving_boot: Call,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApplicationAnalysis {
ranges: Vec<SelectedRange>,
integrity: Vec<IntegritySelection>,
scratch: Vec<Call>,
bulk: Vec<BulkDecode>,
conflicting_arguments: bool,
}
impl ApplicationAnalysis {
pub fn inspect(bytes: &[u8], base: u32) -> Option<Self> {
if base & 1 != 0 {
return None;
}
let end = base.checked_add(u32::try_from(bytes.len()).ok()?)?;
let mut ranges = Vec::new();
let mut integrity = Vec::new();
for address in (base..end).step_by(2) {
if let Some(selection) = programming::inspect_selection(bytes, base, address) {
let signature = programming::inspect_plain_signature(bytes, base, &selection);
ranges.push(SelectedRange {
selection,
signature,
acceptance: None,
});
}
if let Some(proof) = programming::inspect_legacy_integrity(bytes, base, address) {
integrity.push(proof);
}
}
let mut scratch: Vec<Call> = Vec::new();
let mut conflicting_arguments = false;
for range in &mut ranges {
let Some(signature) = range.signature else {
continue;
};
let mut candidates = integrity.iter().filter_map(|checks| {
if checks.address < signature.success
|| checks.address.checked_sub(signature.success)? > 1024
{
return None;
}
programming::inspect_acceptance_tail(
bytes,
base,
&range.selection,
&signature,
checks,
)
});
let first = candidates.next();
if candidates.next().is_some() {
conflicting_arguments = true;
} else {
range.acceptance = first;
}
}
for range in &ranges {
let Some(signature) = range.signature else {
continue;
};
let first = range.selection.address.saturating_sub(512).max(base);
for start in (first..range.selection.address).step_by(2) {
let Some(call) =
decoding::trace_call(bytes, base, start, [Value::Unknown; 8], |a| {
(a == Value::Constant(signature.staging_cell)).then_some(Value::Staging(0))
})
else {
continue;
};
if call.address >= range.selection.address
|| call.length != 16
|| call.source != Value::Staging(0x3000)
|| !matches!(call.destination, Value::Stack(_))
{
continue;
}
let Some(offset) = call
.target
.checked_sub(base)
.and_then(|x| usize::try_from(x).ok())
else {
continue;
};
let Some(routine) = bytes.get(offset..) else {
continue;
};
if !super::matches(routine, super::AES) {
continue;
}
if let Some(previous) = scratch.iter().find(|c| c.address == call.address) {
conflicting_arguments |= previous.target != call.target
|| previous.source != call.source
|| previous.destination != call.destination
|| previous.length != call.length;
} else {
scratch.push(call);
}
}
}
scratch.sort_by_key(|c| c.address);
let mut bulk = Vec::new();
for range in &ranges {
let trace = |start| {
decoding::trace_call(bytes, base, range.selection.end, [Value::Unknown; 8], |a| {
match a {
Value::Constant(cell) if cell == range.selection.start_cell => {
Some(Value::Constant(start))
}
Value::Constant(cell)
if cell & 3 == 0 && (0x02000000..0x02010000).contains(&cell) =>
{
Some(Value::Loaded { cell, offset: 0 })
}
_ => None,
}
})
};
let (Some(from_zero), Some(preserving_boot)) = (trace(0), trace(0x10000)) else {
continue;
};
let Value::Loaded { cell, offset: 0 } = from_zero.source else {
continue;
};
if from_zero.destination != from_zero.source
|| from_zero.length != 0x200000
|| preserving_boot.address != from_zero.address
|| preserving_boot.target != from_zero.target
|| preserving_boot.length != from_zero.length
|| preserving_boot.source
!= (Value::Loaded {
cell,
offset: 0x10000,
})
|| preserving_boot.destination != preserving_boot.source
{
continue;
}
let Some(offset) = from_zero
.target
.checked_sub(base)
.and_then(|x| usize::try_from(x).ok())
else {
continue;
};
if !bytes
.get(offset..)
.is_some_and(|routine| super::matches(routine, super::AES))
{
continue;
}
let first = range.selection.address.saturating_sub(512).max(base);
let mut header: Option<Call> = None;
let mut ambiguous = false;
for start in (first..range.selection.address).step_by(2) {
let Some(call) =
decoding::trace_call(bytes, base, start, [Value::Unknown; 8], |a| {
(a == Value::Constant(cell)).then_some(Value::Loaded { cell, offset: 0 })
})
else {
continue;
};
if call.address >= range.selection.address
|| call.target != from_zero.target
|| call.source
!= (Value::Loaded {
cell,
offset: 0x3000,
})
|| call.length != 16
|| !matches!(call.destination, Value::Stack(_))
{
continue;
}
if let Some(previous) = header {
ambiguous |= previous.address != call.address
|| previous.destination != call.destination;
} else {
header = Some(call);
}
}
if ambiguous {
conflicting_arguments = true;
} else if let Some(header) = header {
bulk.push(BulkDecode {
header,
from_zero,
preserving_boot,
});
}
}
Some(Self {
ranges,
integrity,
scratch,
bulk,
conflicting_arguments,
})
}
pub fn ranges(&self) -> &[SelectedRange] {
&self.ranges
}
pub fn integrity_selections(&self) -> &[IntegritySelection] {
&self.integrity
}
pub fn scratch_decoders(&self) -> &[Call] {
&self.scratch
}
pub fn conflicting_arguments(&self) -> bool {
self.conflicting_arguments
}
pub fn bulk_decoders(&self) -> &[BulkDecode] {
&self.bulk
}
}