mirror of
https://github.com/Momoko-Ayase/Senbei.git
synced 2026-09-19 03:57:59 -04:00
refactor: align platform crate boundaries
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
// Bytecode interpreter for the custom-decryptor stages. Those stages are tiny
|
||||
// instruction programs embedded in the decrypted buffer; we compile each
|
||||
// program down to a Vec<Op> and interpret it.
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Op {
|
||||
Add(u8),
|
||||
Sub(u8),
|
||||
Xor(u8),
|
||||
Rol(u32),
|
||||
Ror(u32),
|
||||
Inc,
|
||||
Dec,
|
||||
}
|
||||
|
||||
pub fn apply(ops: &[Op], mut x: u8) -> u8 {
|
||||
for &op in ops {
|
||||
x = match op {
|
||||
Op::Add(n) => x.wrapping_add(n),
|
||||
Op::Sub(n) => x.wrapping_sub(n),
|
||||
Op::Xor(n) => x ^ n,
|
||||
Op::Rol(n) => x.rotate_left(n & 7),
|
||||
Op::Ror(n) => x.rotate_right(n & 7),
|
||||
Op::Inc => x.wrapping_add(1),
|
||||
Op::Dec => x.wrapping_sub(1),
|
||||
};
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
/// A precomputed 256-entry byte→byte translation table for a fixed op list.
|
||||
///
|
||||
/// `apply` is a pure function of a single byte, but the hot decrypt paths run it
|
||||
/// over multi-megabyte regions. Building the full table once and translating
|
||||
/// each byte with a single lookup turns an O(region × ops) walk into O(region) —
|
||||
/// a large constant-factor win on those paths.
|
||||
pub struct OpsLut {
|
||||
t: [u8; 256],
|
||||
}
|
||||
|
||||
impl OpsLut {
|
||||
pub fn new(ops: &[Op]) -> Self {
|
||||
let mut t = [0u8; 256];
|
||||
let mut i = 0;
|
||||
while i < 256 {
|
||||
t[i] = apply(ops, i as u8);
|
||||
i += 1;
|
||||
}
|
||||
Self { t }
|
||||
}
|
||||
|
||||
/// Translate `d[off .. off + n]` in place through the table.
|
||||
#[inline]
|
||||
pub fn map_region(&self, d: &mut [u8], off: usize, n: usize) {
|
||||
for b in &mut d[off..off + n] {
|
||||
*b = self.t[*b as usize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(data: &[u8], offset: u32) -> Option<Vec<Op>> {
|
||||
// Bounds-checked cursor: a corrupt `data_offset` (bad decrypt_data6 / the
|
||||
// alignment fallback) must yield `None`, not an out-of-bounds panic — the
|
||||
// panic path would surface as a misleading `UnpackError::InternalPanic` instead
|
||||
// of the precise `BytecodeGenerationFailed`, and any future caller without a
|
||||
// `catch_unwind` wrapper would abort outright.
|
||||
let mut pos = offset as usize;
|
||||
let mut next = move || {
|
||||
let b = data.get(pos).copied()?;
|
||||
pos += 1;
|
||||
Some(b)
|
||||
};
|
||||
let mut ops = Vec::new();
|
||||
loop {
|
||||
match next()? {
|
||||
4 => ops.push(Op::Add(next()?)),
|
||||
44 => ops.push(Op::Sub(next()?)),
|
||||
52 => ops.push(Op::Xor(next()?)),
|
||||
144 => {} // nop
|
||||
192 => {
|
||||
let mb = next()?;
|
||||
let rm = mb & 7;
|
||||
let reg = (mb >> 3) & 7;
|
||||
let mod_ = (mb >> 6) & 3;
|
||||
if mod_ != 3 || rm != 0 {
|
||||
return None;
|
||||
}
|
||||
let imm = next()? as u32;
|
||||
match reg {
|
||||
0 => ops.push(Op::Rol(imm)),
|
||||
1 => ops.push(Op::Ror(imm)),
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
254 => {
|
||||
let mb = next()?;
|
||||
let rm = mb & 7;
|
||||
let reg = (mb >> 3) & 7;
|
||||
let mod_ = (mb >> 6) & 3;
|
||||
if mod_ != 3 || rm != 0 {
|
||||
return None;
|
||||
}
|
||||
match reg {
|
||||
0 => ops.push(Op::Inc),
|
||||
1 => ops.push(Op::Dec),
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
195 => return Some(ops),
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user