const STATE_LEN: usize = 17;
const INITIAL_C1: usize = 10;
const LAST_INDEX: usize = 16;
#[must_use]
pub fn decrypt(data: &[u8], seed: u32) -> Option<Vec<u8>> {
let mut lame = LameStream::new(seed)?;
data.iter()
.map(|byte| lame.next_byte().map(|key| byte ^ key))
.collect()
}
#[derive(Debug, Clone)]
struct LameStream {
c0: usize,
c1: usize,
state: [u32; STATE_LEN],
}
impl LameStream {
fn new(seed: u32) -> Option<Self> {
let mut stream = Self {
c0: 0,
c1: INITIAL_C1,
state: [0; STATE_LEN],
};
let mut current = seed;
for state_word in &mut stream.state {
current = 1u32.wrapping_sub(current.wrapping_mul(0x53a9_b4fb));
*state_word = current;
}
for _ in 0..9 {
let _ = stream.fpusht()?;
}
Some(stream)
}
fn next_byte(&mut self) -> Option<u8> {
let _ = self.fpusht()?;
let value = self.fpusht()? * 256.0;
if value < 256.0 {
Some(value as u8)
} else {
Some(0xff)
}
}
fn fpusht(&mut self) -> Option<f64> {
let first = *self.state.get(self.c0)?;
let second = *self.state.get(self.c1)?;
let rolled = first.rotate_left(9).wrapping_add(second.rotate_left(13));
*self.state.get_mut(self.c0)? = rolled;
self.c0 = previous_index(self.c0)?;
self.c1 = previous_index(self.c1)?;
let low = u64::from(rolled << 20);
let high = u64::from((rolled >> 12) | 0x3ff0_0000);
let bits = (high << 32) | low;
Some(f64::from_bits(bits) - 1.0)
}
}
fn previous_index(index: usize) -> Option<usize> {
if index == 0 {
Some(LAST_INDEX)
} else {
index.checked_sub(1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decrypts_ea06_file_marker() -> Result<(), String> {
let decrypted = decrypt(&[0x6b, 0x43, 0xca, 0x52], 0x18ee)
.ok_or_else(|| "decryption failed".to_string())?;
if decrypted == b"FILE" {
Ok(())
} else {
Err(format!("got {decrypted:?}, expected FILE"))
}
}
#[test]
fn emits_stable_ea06_keystream_bytes() -> Result<(), String> {
let stream = decrypt(&[0; 16], 0x18ee).ok_or_else(|| "decryption failed".to_string())?;
check_eq(
stream.as_slice(),
[
0x2d, 0x0a, 0x86, 0x17, 0xb6, 0xb3, 0x71, 0xa0, 0x07, 0x10, 0x84, 0xf7, 0xe5, 0xba,
0xe7, 0x29,
]
.as_slice(),
"keystream",
)
}
fn check_eq<T>(actual: T, expected: T, context: &str) -> Result<(), String>
where
T: core::fmt::Debug + PartialEq,
{
if actual == expected {
Ok(())
} else {
Err(format!("{context}: got {actual:?}, expected {expected:?}"))
}
}
}