use std::collections::HashMap;
use crate::page::PAGE_SIZE;
use crate::store::PageStore;
pub const SECTOR_SIZE: usize = 512;
pub const SECTORS_PER_PAGE: usize = PAGE_SIZE / SECTOR_SIZE;
const TRAILER_START: usize = 0xFF0;
const LEARN_PURITY: f64 = 1.0;
const PURE_AP_TYPES: [u8; 4] = [0x40, 0x43, 0x48, 0x4D];
#[inline]
fn ap_base(pn: u64, si: usize, bv: u8) -> u8 {
let p16 = (pn % 16) as u8;
let offset = p16 / 2 * 4; bv.wrapping_add(pn as u8)
.wrapping_add(si as u8)
.wrapping_sub(offset)
}
fn dominant_step(sec: &[u8]) -> u8 {
let mut hist = [0u32; 256];
for w in sec.windows(2) {
let diff = w[1].wrapping_sub(w[0]);
hist[diff as usize] += 1;
}
hist.iter()
.enumerate()
.max_by_key(|&(_, &v)| v)
.map(|(i, _)| i as u8)
.unwrap_or(0)
}
fn recover_base(sec: &[u8], step: u8) -> u8 {
let mut hist = [0u32; 256];
for (i, &b) in sec.iter().enumerate() {
let candidate = b.wrapping_sub((i as u8).wrapping_mul(step));
hist[candidate as usize] += 1;
}
hist.iter()
.enumerate()
.max_by_key(|&(_, &v)| v)
.map(|(i, _)| i as u8)
.unwrap_or(0)
}
fn sector_purity(sec: &[u8], base: u8, step: u8) -> f64 {
let hits = sec
.iter()
.enumerate()
.filter(|&(i, &b)| b == base.wrapping_add((i as u8).wrapping_mul(step)))
.count();
hits as f64 / sec.len() as f64
}
fn recover_step_peak(sec: &[u8], base: u8) -> (u8, usize) {
let mut best_step = 0u8;
let mut best_count = 0usize;
for step in 0u8..=255 {
let mut hist = [0u16; 256];
for (i, &b) in sec.iter().enumerate() {
let plain = b
.wrapping_sub(base)
.wrapping_sub((i as u8).wrapping_mul(step));
hist[plain as usize] += 1;
}
let peak = hist.iter().copied().max().unwrap_or(0) as usize;
if peak > best_count {
best_count = peak;
best_step = step;
}
}
(best_step, best_count)
}
fn apply_stream(sec: &[u8], base: u8, step: u8, out: &mut [u8]) {
for (i, (&stored, plain)) in sec.iter().zip(out.iter_mut()).enumerate() {
*plain = stored
.wrapping_sub(base)
.wrapping_sub((i as u8).wrapping_mul(step));
}
}
#[derive(Debug, Clone, Default)]
pub struct ApModel {
bv_map: HashMap<u64, u8>,
bv0: u8,
}
impl ApModel {
pub fn learn(store: &PageStore) -> Self {
let mut votes: HashMap<u64, HashMap<u8, u32>> = HashMap::new();
for page in store.pages() {
let ptype = page.trailer().page_type_raw;
if !PURE_AP_TYPES.contains(&ptype) {
continue;
}
let pn = page.index();
let bi = pn / 16;
let p16 = (pn % 16) as u8;
let bytes = page.bytes();
for si in 0..SECTORS_PER_PAGE {
let off = si * SECTOR_SIZE;
let data_end = if si + 1 == SECTORS_PER_PAGE {
TRAILER_START
} else {
off + SECTOR_SIZE
};
let sec = &bytes[off..data_end];
let step = dominant_step(sec);
let base = recover_base(sec, step);
if sector_purity(sec, base, step) < LEARN_PURITY {
continue;
}
let bv = base
.wrapping_sub(pn as u8)
.wrapping_sub(si as u8)
.wrapping_add(p16 / 2 * 4);
*votes.entry(bi).or_default().entry(bv).or_default() += 1;
}
}
let mut bv_map: HashMap<u64, u8> = HashMap::new();
for (bi, ctr) in &votes {
if let Some((&bv, _)) = ctr.iter().max_by_key(|&(_, &v)| v) {
bv_map.insert(*bi, bv);
}
}
let bv0 = bv_map
.get(&0)
.copied()
.unwrap_or_else(|| bv_map.values().copied().next().unwrap_or(0));
ApModel { bv_map, bv0 }
}
#[inline]
pub fn bv_at(&self, bi: u64) -> u8 {
self.bv_map.get(&bi).copied().unwrap_or(self.bv0)
}
pub fn deobfuscate(&self, raw: &[u8], pn: u64) -> Vec<u8> {
self.deobfuscate_with_bv(raw, pn, self.bv_at(pn / 16))
}
pub fn learned_block_count(&self) -> usize {
self.bv_map.len()
}
pub fn recover_bv_for_block(&self, store: &PageStore, bi: u64) -> u8 {
let page_start = bi * 16;
let page_end = (page_start + 16).min(store.page_count());
if page_start >= store.page_count() {
return self.bv0;
}
let sample_end = page_end.min(page_start + 3);
let mut bv_score = [0i64; 256];
for pn in page_start..sample_end {
let page = match store.page(pn) {
Ok(p) => p,
Err(_) => continue,
};
let bytes = page.bytes();
let p16 = (pn % 16) as u8;
let bias = p16 / 2 * 4;
for si in 0..7usize {
let off = si * SECTOR_SIZE;
let sec = &bytes[off..off + SECTOR_SIZE];
let offset = (pn as u8).wrapping_add(si as u8).wrapping_sub(bias);
let mut step_bv_max = [0u16; 256];
for step in 0u8..=255 {
let mut hist = [0u16; 256];
for (i, &b) in sec.iter().enumerate() {
let m = b
.wrapping_sub(offset)
.wrapping_sub((i as u8).wrapping_mul(step));
hist[m as usize] += 1;
}
for (bv, &h) in hist.iter().enumerate() {
if h > step_bv_max[bv] {
step_bv_max[bv] = h;
}
}
}
for (bv, &m) in step_bv_max.iter().enumerate() {
bv_score[bv] += m as i64;
}
}
}
bv_score
.iter()
.enumerate()
.max_by_key(|&(_, &v)| v)
.map(|(i, _)| i as u8)
.unwrap_or(self.bv0)
}
pub fn deobfuscate_with_store(&self, raw: &[u8], pn: u64, store: &PageStore) -> Vec<u8> {
let bi = pn / 16;
let bv = if self.bv_map.contains_key(&bi) {
self.bv_at(bi)
} else {
self.recover_bv_for_block(store, bi)
};
self.deobfuscate_with_bv(raw, pn, bv)
}
pub fn deobfuscate_with_bv(&self, raw: &[u8], pn: u64, bv: u8) -> Vec<u8> {
assert!(raw.len() >= PAGE_SIZE, "page buffer too small");
let mut out = vec![0u8; PAGE_SIZE];
for si in 0..SECTORS_PER_PAGE {
let off = si * SECTOR_SIZE;
let data_end = if si + 1 == SECTORS_PER_PAGE {
TRAILER_START
} else {
off + SECTOR_SIZE
};
let sec = &raw[off..data_end];
let base = ap_base(pn, si, bv);
let (step, _) = recover_step_peak(sec, base);
apply_stream(sec, base, step, &mut out[off..data_end]);
}
out[TRAILER_START..PAGE_SIZE].copy_from_slice(&raw[TRAILER_START..PAGE_SIZE]);
out
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_ap_sector(base: u8, step: u8) -> [u8; SECTOR_SIZE] {
let mut sec = [0u8; SECTOR_SIZE];
for (i, b) in sec.iter_mut().enumerate() {
*b = base.wrapping_add((i as u8).wrapping_mul(step));
}
sec
}
#[test]
fn dominant_step_pure_ap() {
let sec = make_ap_sector(0x63, 3);
assert_eq!(dominant_step(&sec), 3);
}
#[test]
fn recover_base_pure_ap() {
let sec = make_ap_sector(0x71, 7);
let step = dominant_step(&sec);
assert_eq!(step, 7);
assert_eq!(recover_base(&sec, step), 0x71);
}
#[test]
fn sector_purity_perfect() {
let sec = make_ap_sector(0x40, 11);
let step = dominant_step(&sec);
let base = recover_base(&sec, step);
assert!(sector_purity(&sec, base, step) > 0.99);
}
#[test]
fn roundtrip_zero_plaintext() {
let base: u8 = 0x55;
let step: u8 = 19;
let stored: Vec<u8> = (0..SECTOR_SIZE)
.map(|i| base.wrapping_add((i as u8).wrapping_mul(step)))
.collect();
let (recovered_step, _) = recover_step_peak(&stored, base);
let mut plain = vec![0u8; SECTOR_SIZE];
apply_stream(&stored, base, recovered_step, &mut plain);
assert!(plain.iter().all(|&b| b == 0));
}
#[test]
fn ap_base_formula() {
assert_eq!(ap_base(10, 2, 0xC0), 0xB8);
assert_eq!(ap_base(0, 0, 0x00), 0x00);
assert_eq!(ap_base(16, 3, 0x10), 0x23);
}
}