agb_debug/
gwilym_encoding.rs

1use std::{slice::ChunksExact, sync::OnceLock};
2
3use thiserror::Error;
4
5const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
6
7#[derive(Debug, Error)]
8pub enum GwilymDecodeError {
9    #[error("Does not contain version")]
10    NoVersion,
11    #[error("Only version 1 is supported")]
12    WrongVersion,
13    #[error("Input must be a multiple of 3 but have {0}")]
14    LengthWrong(usize),
15}
16
17pub fn gwilym_decode(input: &str) -> Result<GwilymDecodeIter<'_>, GwilymDecodeError> {
18    GwilymDecodeIter::new(input)
19}
20
21pub struct GwilymDecodeIter<'a> {
22    chunks: ChunksExact<'a, u8>,
23}
24
25impl<'a> GwilymDecodeIter<'a> {
26    fn new(input: &'a str) -> Result<Self, GwilymDecodeError> {
27        let input = input
28            .strip_prefix("https://agbrs.dev/crash#")
29            .unwrap_or(input);
30
31        let Some((input, version)) = input.rsplit_once('v') else {
32            return Err(GwilymDecodeError::NoVersion);
33        };
34
35        if version != "1" {
36            return Err(GwilymDecodeError::WrongVersion);
37        }
38
39        if input.len() % 3 != 0 {
40            return Err(GwilymDecodeError::LengthWrong(input.len()));
41        }
42
43        Ok(Self {
44            chunks: input.as_bytes().chunks_exact(3),
45        })
46    }
47}
48
49impl Iterator for GwilymDecodeIter<'_> {
50    type Item = u32;
51
52    fn next(&mut self) -> Option<Self::Item> {
53        let chunk = self.chunks.next()?;
54
55        let value = decode_chunk(chunk);
56        if value & (1 << 16) != 0 {
57            let upper_bits = value << 16;
58            let lower_bits = self.next().unwrap_or(0) & 0xffff;
59
60            return Some(upper_bits | lower_bits);
61        }
62
63        Some(value | 0x0800_0000)
64    }
65}
66
67fn decode_chunk(chunk: &[u8]) -> u32 {
68    let a = get_value_for_char(chunk[0]);
69    let b = get_value_for_char(chunk[1]);
70    let c = get_value_for_char(chunk[2]);
71
72    (a << (16 - 5)) | (b << (16 - 10)) | c
73}
74
75fn get_value_for_char(input: u8) -> u32 {
76    static REVERSE_ALHPABET: OnceLock<[u8; 128]> = OnceLock::new();
77
78    REVERSE_ALHPABET.get_or_init(|| {
79        let mut result = [0; 128];
80        for (i, &c) in ALPHABET.iter().enumerate() {
81            result[c as usize] = i as u8;
82        }
83
84        result
85    })[input as usize] as u32
86}
87
88#[cfg(test)]
89mod test {
90    use super::*;
91    use std::fmt::Write;
92
93    #[test]
94    fn should_correctly_decode_16s() -> Result<(), GwilymDecodeError> {
95        assert_eq!(
96            &gwilym_decode("2QI65Q69306Kv1")?.collect::<Vec<_>>(),
97            &[0x0800_16d3, 0x0800_315b, 0x0800_3243, 0x0800_0195]
98        );
99
100        Ok(())
101    }
102
103    fn encode_16(input: u16) -> [u8; 3] {
104        let input = input as usize;
105        [
106            ALPHABET[input >> (16 - 5)],
107            ALPHABET[(input >> (16 - 10)) & 0b11111],
108            ALPHABET[input & 0b111111],
109        ]
110    }
111
112    fn encode_32(input: u32) -> [u8; 6] {
113        let input = input as usize;
114        let output_lower_16 = encode_16(input as u16);
115        let input_upper_16 = input >> 16;
116        [
117            ALPHABET[(input_upper_16 >> (16 - 5)) | (1 << 5)],
118            ALPHABET[(input_upper_16 >> (16 - 10)) & 0b11111],
119            ALPHABET[input_upper_16 & 0b111111],
120            output_lower_16[0],
121            output_lower_16[1],
122            output_lower_16[2],
123        ]
124    }
125
126    #[test]
127    fn should_correctly_decode_16s_and_32s() -> Result<(), Box<dyn std::error::Error>> {
128        let trace: &[u32] = &[
129            0x0300_2990,
130            0x0800_3289,
131            0x0500_2993,
132            0x3829_2910,
133            0xffff_ffff,
134            0x0000_0000,
135        ];
136
137        let mut result = String::new();
138        for &ip in trace {
139            if ip & 0xFFFF_0000 == 0x0800_0000 {
140                let encoded = encode_16(ip as u16);
141                let encoded_s = std::str::from_utf8(&encoded)?;
142                write!(&mut result, "{encoded_s}")?
143            } else {
144                let encoded = encode_32(ip);
145                let encoded_s = std::str::from_utf8(&encoded)?;
146                write!(&mut result, "{encoded_s}")?
147            }
148        }
149
150        write!(&mut result, "v1")?;
151
152        assert_eq!(&gwilym_decode(&result)?.collect::<Vec<_>>(), trace);
153
154        Ok(())
155    }
156
157    #[test]
158    fn should_strip_the_agbrsdev_prefix() -> Result<(), Box<dyn std::error::Error>> {
159        assert_eq!(
160            &gwilym_decode("https://agbrs.dev/crash#2QI65Q69306Kv1")?.collect::<Vec<_>>(),
161            &[0x0800_16d3, 0x0800_315b, 0x0800_3243, 0x0800_0195]
162        );
163
164        Ok(())
165    }
166}