1use serde::{Deserialize, Serialize};
4use std::{collections::HashMap, sync::OnceLock};
5use thiserror::Error;
6
7static GENIE_MAP: OnceLock<HashMap<char, u8>> = OnceLock::new();
8
9pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Error, Debug)]
12#[error("invalid genie code {code:?}. {kind}")]
13pub struct Error {
14 code: String,
15 kind: ErrorKind,
16}
17
18impl Error {
19 fn new(code: impl Into<String>, kind: ErrorKind) -> Self {
20 Self {
21 code: code.into(),
22 kind,
23 }
24 }
25
26 pub const fn kind(&self) -> ErrorKind {
27 self.kind
28 }
29}
30
31#[derive(Error, Debug, Copy, Clone)]
32#[must_use]
33pub enum ErrorKind {
34 #[error("length must be 6 or 8 characters. found `{0}`")]
35 InvalidLength(usize),
36 #[error("invalid character: `{0}`")]
37 InvalidCharacter(char),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct GenieCode {
43 code: String,
44 addr: u16,
45 data: u8,
46 compare: Option<u8>,
47}
48
49impl GenieCode {
50 pub fn new(code: String) -> Result<Self> {
56 let hex = Self::parse(&code)?;
57 Ok(Self::from_raw(code, hex))
58 }
59
60 pub fn from_raw(code: String, hex: Vec<u8>) -> Self {
63 let addr = 0x8000
64 + (((u16::from(hex[3]) & 7) << 12)
65 | ((u16::from(hex[5]) & 7) << 8)
66 | ((u16::from(hex[4]) & 8) << 8)
67 | ((u16::from(hex[2]) & 7) << 4)
68 | ((u16::from(hex[1]) & 8) << 4)
69 | (u16::from(hex[4]) & 7)
70 | (u16::from(hex[3]) & 8));
71 let data = if hex.len() == 6 {
72 ((hex[1] & 7) << 4) | ((hex[0] & 8) << 4) | (hex[0] & 7) | (hex[5] & 8)
73 } else {
74 ((hex[1] & 7) << 4) | ((hex[0] & 8) << 4) | (hex[0] & 7) | (hex[7] & 8)
75 };
76 let compare = if hex.len() == 8 {
77 Some(((hex[7] & 7) << 4) | ((hex[6] & 8) << 4) | (hex[6] & 7) | (hex[5] & 8))
78 } else {
79 None
80 };
81 Self {
82 code: code.to_ascii_uppercase(),
83 addr,
84 data,
85 compare,
86 }
87 }
88
89 fn generate_genie_map() -> HashMap<char, u8> {
90 HashMap::from([
92 ('A', 0x0),
93 ('P', 0x1),
94 ('Z', 0x2),
95 ('L', 0x3),
96 ('G', 0x4),
97 ('I', 0x5),
98 ('T', 0x6),
99 ('Y', 0x7),
100 ('E', 0x8),
101 ('O', 0x9),
102 ('X', 0xA),
103 ('U', 0xB),
104 ('K', 0xC),
105 ('S', 0xD),
106 ('V', 0xE),
107 ('N', 0xF),
108 ])
109 }
110
111 pub fn parse(code: &str) -> Result<Vec<u8>> {
112 if code.len() != 6 && code.len() != 8 {
113 return Err(Error::new(code, ErrorKind::InvalidLength(code.len())));
114 }
115 let mut hex: Vec<u8> = Vec::with_capacity(code.len());
116 for s in code.chars() {
117 if let Some(h) = GENIE_MAP
118 .get_or_init(Self::generate_genie_map)
119 .get(&s.to_ascii_uppercase())
120 {
121 hex.push(*h);
122 } else {
123 return Err(Error::new(code, ErrorKind::InvalidCharacter(s)));
124 }
125 }
126 Ok(hex)
127 }
128
129 #[must_use]
130 #[allow(clippy::missing_const_for_fn)] pub fn code(&self) -> &str {
132 &self.code
133 }
134
135 #[must_use]
136 pub const fn addr(&self) -> u16 {
137 self.addr
138 }
139
140 #[must_use]
141 pub const fn read(&self, val: u8) -> u8 {
142 if let Some(compare) = self.compare {
143 if val == compare { self.data } else { val }
144 } else {
145 self.data
146 }
147 }
148}
149
150impl std::fmt::Display for GenieCode {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 write!(f, "{}", &self.code)
153 }
154}