1use anyhow::{bail, Result};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum AngouStepKind {
18 ExeKey16,
19 BaseCode,
20 GameCode,
21}
22
23#[derive(Debug, Clone)]
24pub struct AngouStep {
25 pub kind: AngouStepKind,
26 pub key: Vec<u8>,
27}
28
29impl AngouStep {
30 pub fn new(kind: AngouStepKind, key: Vec<u8>) -> Result<Self> {
31 if key.is_empty() {
32 bail!("angou: empty key for step {kind:?}");
33 }
34 Ok(Self { kind, key })
35 }
36}
37
38#[derive(Debug, Clone, Default)]
40pub struct AngouChain {
41 pub steps: Vec<AngouStep>,
42}
43
44impl AngouChain {
45 pub fn apply_in_place(&self, buf: &mut [u8]) {
46 for step in &self.steps {
47 xor_cycle_in_place(buf, &step.key);
48 }
49 }
50
51 pub fn describe(&self) -> Vec<(AngouStepKind, usize)> {
52 self.steps.iter().map(|s| (s.kind, s.key.len())).collect()
53 }
54}
55
56pub fn xor_cycle_in_place(buf: &mut [u8], key: &[u8]) {
57 if key.is_empty() {
58 return;
59 }
60 for (i, b) in buf.iter_mut().enumerate() {
61 *b ^= key[i % key.len()];
62 }
63}
64
65pub fn parse_hex_bytes(s: &str) -> Result<Vec<u8>> {
69 let mut hex = String::with_capacity(s.len());
70 for ch in s.chars() {
71 if ch == 'x' || ch == 'X' {
72 }
74 if ch.is_ascii_hexdigit() {
75 hex.push(ch);
76 }
77 }
78
79 if hex.len() % 2 != 0 {
80 bail!("hex string has odd length");
81 }
82 let mut out = Vec::with_capacity(hex.len() / 2);
83 let bytes = hex.as_bytes();
84 for i in (0..bytes.len()).step_by(2) {
85 let hi = from_hex_digit(bytes[i])?;
86 let lo = from_hex_digit(bytes[i + 1])?;
87 out.push((hi << 4) | lo);
88 }
89 Ok(out)
90}
91
92fn from_hex_digit(c: u8) -> Result<u8> {
93 match c {
94 b'0'..=b'9' => Ok(c - b'0'),
95 b'a'..=b'f' => Ok(c - b'a' + 10),
96 b'A'..=b'F' => Ok(c - b'A' + 10),
97 _ => bail!("invalid hex digit"),
98 }
99}