1use bincode::{Decode, Encode}; 2use jgenesis_common::num::GetBit; 3use regex::Regex; 4use std::fmt::{Display, Formatter}; 5use std::sync::LazyLock; 6 7#[derive(Debug, Clone, Copy, PartialEq, Eq)] 8pub enum GenesisCheatCodeType { 9 GameGenie, 10 ActionReplay, 11 MemoryOverride, 12} 13 14impl GenesisCheatCodeType { 15 #[must_use] 16 pub fn guess_from(code: &str) -> Option<Self> { 17 static GAME_GENIE_REGEX: LazyLock<Regex> = LazyLock::new(|| { 18 Regex::new(r"^(?i)[A-HJ-NPR-TV-Z0-9]{4}-[A-HJ-NPR-TV-Z0-9]{4}$").unwrap() 19 }); 20 21 static PRO_ACTION_REPLAY_REGEX: LazyLock<Regex> = 22 LazyLock::new(|| Regex::new(r"^[[:xdigit:]]{5}[ -]?[[:xdigit:]]{5}$").unwrap()); 23 24 static MEMORY_OVERRIDE_REGEX: LazyLock<Regex> = 25 LazyLock::new(|| Regex::new(r"^[[:xdigit:]]{6}:[[:xdigit:]]{4}$").unwrap()); 26 27 if GAME_GENIE_REGEX.is_match(code) { 28 return Some(Self::GameGenie); 29 } 30 31 if PRO_ACTION_REPLAY_REGEX.is_match(code) { 32 return Some(Self::ActionReplay); 33 } 34 35 if MEMORY_OVERRIDE_REGEX.is_match(code) { 36 return Some(Self::MemoryOverride); 37 } 38 39 None 40 } 41 42 #[must_use] 43 pub fn decode(self, code: &str) -> Option<(u32, u16)> { 44 match self { 45 Self::GameGenie => decode_game_genie(code), 46 Self::ActionReplay => decode_action_replay(code), 47 Self::MemoryOverride => decode_memory_override(code), 48 } 49 } 50} 51 52impl Display for GenesisCheatCodeType { 53 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 54 match self { 55 Self::GameGenie => write!(f, "Game Genie"), 56 Self::ActionReplay => write!(f, "Action Replay"), 57 Self::MemoryOverride => write!(f, "Memory Override"), 58 } 59 } 60} 61 62#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] 63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 64#[cfg_attr(feature = "serde", serde(default))] 65pub struct GenesisCheat { 66 pub name: String, 67 pub enabled: bool, 68 pub codes: Vec<String>, 69} 70 71impl GenesisCheat { 72 #[must_use] 73 pub fn to_memory_override_vec(&self) -> Vec<(u32, u16)> { 74 if !self.enabled { 75 return vec![]; 76 } 77 78 self.codes 79 .iter() 80 .filter_map(|code| { 81 GenesisCheatCodeType::guess_from(code).and_then(|code_type| code_type.decode(code)) 82 }) 83 .collect() 84 } 85} 86 87impl GenesisCheat { 88 #[must_use] 89 pub fn new(name: String) -> Self { 90 Self { name, enabled: true, codes: vec![] } 91 } 92} 93 94impl Default for GenesisCheat { 95 fn default() -> Self { 96 Self::new(String::new()) 97 } 98} 99 100#[derive(Debug, Clone, PartialEq, Eq, Default, Encode, Decode)] 101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 102#[cfg_attr(feature = "serde", serde(default))] 103pub struct GenesisCheats { 104 pub cheats: Vec<GenesisCheat>, 105} 106 107impl GenesisCheats {
Convert to a [Vec] of 68000 address/value pairs
Excludes disabled cheats and any cheats that have invalid codes
121#[must_use] 122pub fn decode_game_genie(code: &str) -> Option<(u32, u16)> { 123 let code = code.as_bytes(); 124 125 // Codes should always be in ABCD-EFGH format 126 if code.len() != 9 || code[4] != b'-' { 127 return None; 128 } 129 130 // The 8 non-hyphen characters decode into a scrambled 40-bit value, 5 bits per char 131 let mut scrambled: u64 = 0; 132 133 for &c in &code[..4] { 134 scrambled = (scrambled << 5) | decode_game_genie_char(c)?; 135 } 136 137 for &c in &code[5..] { 138 scrambled = (scrambled << 5) | decode_game_genie_char(c)?; 139 } 140 141 // Scrambled bit layout: 142 // A B C D - E F G H 143 // ijklm nopIJ KLMNO PABCD EFGHd efgha bcQRS TUVWX 144 // 35 30 25 20 15 10 5 0 145 // 146 // Descrambled: 147 // Address (24-bit): ABCD EFGH IJKL MNOP QRST UVWX 148 // Value (16-bit): abcd efgh ijkl mnop 149 150 let address = 151 (scrambled.bits(16..=23) << 16) | (scrambled.bits(24..=31) << 8) | scrambled.bits(0..=7); 152 153 let value = 154 (scrambled.bits(8..=10) << 13) | (scrambled.bits(11..=15) << 8) | scrambled.bits(32..=39); 155 156 Some((address as u32, value as u16)) 157} 158 159fn decode_game_genie_char(c: u8) -> Option<u64> { 160 // Codes are encoded in base 32 using the characters A-Z and 0-9 excluding I, O, Q, and U 161 // Any other character is invalid 162 let bits = match c { 163 b'A'..=b'H' => c - b'A', 164 b'J'..=b'N' => c - b'A' - 1, 165 b'P' => c - b'A' - 2, 166 b'R'..=b'T' => c - b'A' - 3, 167 b'V'..=b'Z' => c - b'A' - 4, 168 b'0'..=b'9' => c - b'0' + 22, 169 // Support lowercase letters too, why not 170 b'a'..=b'h' => c - b'a', 171 b'j'..=b'n' => c - b'a' - 1, 172 b'p' => c - b'a' - 2, 173 b'r'..=b't' => c - b'a' - 3, 174 b'v'..=b'z' => c - b'a' - 4, 175 _ => return None, 176 }; 177 178 debug_assert!(bits < 32); 179 180 Some(bits.into()) 181} 182 183#[must_use] 184pub fn decode_action_replay(code: &str) -> Option<(u32, u16)> { 185 // Pro Action Replay codes are in format "98765-43210" or "98765 43210" 186 // With hyphen/space removed, first 6 digits are address and last 4 are value (in hex) 187 let bytes = code.as_bytes(); 188 189 if bytes.len() != 11 || !matches!(bytes[5], b'-' | b' ') { 190 return None; 191 } 192 193 let address = 194 16 * u32::from_str_radix(code.get(..5)?, 16).ok()? + (bytes[6] as char).to_digit(16)?; 195 let value = u16::from_str_radix(code.get(7..)?, 16).ok()?; 196 197 // Action Replay memory values seem to be little-endian instead of big-endian; byteswap 198 let value = value.swap_bytes(); 199 200 Some((address, value)) 201} 202 203#[must_use] 204pub fn decode_memory_override(code: &str) -> Option<(u32, u16)> { 205 // Memory override codes are in format 987654:3210 (address:value in hex) 206 if code.len() != 11 || code.as_bytes()[6] != b':' { 207 return None; 208 } 209 210 let address = u32::from_str_radix(code.get(..6)?, 16).ok()?; 211 let value = u16::from_str_radix(code.get(7..)?, 16).ok()?; 212 213 Some((address, value)) 214} 215 216#[cfg(test)] 217mod tests { 218 use super::*; 219 220 #[test] 221 fn test_decode_game_genie() { 222 assert_eq!(decode_game_genie("97JT-EVSG"), Some((0x0251E6, 0xA8FF))); 223 assert_eq!(decode_game_genie("AFJT-EVSG"), Some((0x0251E6, 0xA801))); 224 225 assert_eq!(decode_game_genie(""), None); 226 assert_eq!(decode_game_genie("AAAI-AAAA"), None); 227 } 228 229 #[test] 230 fn test_decode_pro_action_replay() { 231 assert_eq!(decode_action_replay("98765-43210"), Some((0x987654, 0x1032))); 232 assert_eq!(decode_action_replay("FFFFF-EABCD"), Some((0xFFFFFE, 0xCDAB))); 233 234 assert_eq!(decode_action_replay(""), None); 235 assert_eq!(decode_action_replay("QWERT-YUIOP"), None); 236 } 237 238 #[test] 239 fn test_decode_memory_override() { 240 assert_eq!(decode_memory_override("987654:3210"), Some((0x987654, 0x3210))); 241 assert_eq!(decode_memory_override("FFFFFE:ABCD"), Some((0xFFFFFE, 0xABCD))); 242 243 assert_eq!(decode_memory_override(""), None); 244 assert_eq!(decode_memory_override("QWERTY:UIOP"), None); 245 } 246 247 #[test] 248 fn guess_code_type() { 249 assert_eq!(GenesisCheatCodeType::guess_from(""), None); 250 assert_eq!(GenesisCheatCodeType::guess_from("ABCDEFGH"), None); 251 252 assert_eq!( 253 GenesisCheatCodeType::guess_from("ABCD-EFGH"), 254 Some(GenesisCheatCodeType::GameGenie) 255 ); 256 assert_eq!( 257 GenesisCheatCodeType::guess_from("abcd-efgh"), 258 Some(GenesisCheatCodeType::GameGenie) 259 ); 260 assert_eq!(GenesisCheatCodeType::guess_from("ABCD-EFGI"), None); 261 262 assert_eq!( 263 GenesisCheatCodeType::guess_from("02468-ACEFF"), 264 Some(GenesisCheatCodeType::ActionReplay) 265 ); 266 assert_eq!( 267 GenesisCheatCodeType::guess_from("02468-aceff"), 268 Some(GenesisCheatCodeType::ActionReplay) 269 ); 270 assert_eq!( 271 GenesisCheatCodeType::guess_from("02468 ACEFF"), 272 Some(GenesisCheatCodeType::ActionReplay) 273 ); 274 275 assert_eq!( 276 GenesisCheatCodeType::guess_from("02468A:CEFF"), 277 Some(GenesisCheatCodeType::MemoryOverride) 278 ); 279 assert_eq!( 280 GenesisCheatCodeType::guess_from("02468a:ceff"), 281 Some(GenesisCheatCodeType::MemoryOverride) 282 ); 283 } 284}