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