use crate::device; use crate::ui; #[derive(serde::Serialize, serde::Deserialize, Copy, Clone)] pub struct DecodedCheat { code_type: u8, address: u32, data: u16, } #[derive(serde::Serialize, serde::Deserialize)] pub struct Cheats { pub cheats: Vec>, pub boot: bool, pub enabled: bool, } #[derive(serde::Serialize, serde::Deserialize, Clone)] pub struct CheatData { pub note: String, pub data: Vec, pub options: Option>, } pub type Cheat = std::collections::BTreeMap>; pub fn init( device: &mut device::Device, cheat_settings: std::collections::HashMap>, ) { let cheats = serde_json::from_slice::(include_bytes!("../data/cheats.json")) .unwrap() .get(&ui::storage::get_game_crc(&device.cart.rom)) .cloned() .unwrap_or_default(); let re = regex::Regex::new(r"\?+").unwrap(); for cheat_setting in cheat_settings.iter() { if let Some(cheat_data) = cheats.get(cheat_setting.0) { let mut option_value = None; if let Some(option) = cheat_setting.1 { if let Some(found_option_value) = cheat_data .options .as_ref() .cloned() .unwrap_or_default() .get(option) { option_value = Some(found_option_value.to_string()); } else { eprintln!("Cheat option: {option} not found"); continue; } } println!( "Cheat: {} Option: {}", cheat_setting.0, cheat_setting.1.clone().unwrap_or("none".to_string()) ); let mut decoded_cheat: Vec = vec![]; for code in cheat_data.data.iter() { let mut result = code.clone(); if let Some(option_value) = option_value.as_ref() { result = re.replace_all(code, option_value).into_owned(); } let mut split = result.split_whitespace(); let first_part = u32::from_str_radix(split.next().unwrap(), 16).unwrap(); if let Ok(data) = u16::from_str_radix(split.next().unwrap(), 16) { decoded_cheat.push(DecodedCheat { code_type: (first_part >> 24) as u8, address: first_part & 0x00FFFFFF, data, }) } else { eprintln!("Could not parse data for: {}", cheat_setting.0); }; } if !decoded_cheat.is_empty() { device.cheats.cheats.push(decoded_cheat); } } else { eprintln!("Could not find cheat: {}", cheat_setting.0); } } if !device.cheats.cheats.is_empty() { device.cheats.enabled = true; ui::video::onscreen_message("Cheats enabled", ui::video::MESSAGE_LENGTH_MESSAGE_SHORT); } } fn write_byte(device: &mut device::Device, cheat_line: &DecodedCheat) { *device .rdram .mem .get_mut(cheat_line.address as usize ^ device.byte_swap) .unwrap_or(&mut 0) = cheat_line.data as u8; } fn write_half(device: &mut device::Device, cheat_line: &DecodedCheat) { *device .rdram .mem .get_mut(cheat_line.address as usize ^ device.byte_swap) .unwrap_or(&mut 0) = (cheat_line.data >> 8) as u8; *device .rdram .mem .get_mut((cheat_line.address + 1) as usize ^ device.byte_swap) .unwrap_or(&mut 0) = cheat_line.data as u8; } fn equal_byte(device: &mut device::Device, cheat_line: &DecodedCheat) -> bool { let current_value = *device .rdram .mem .get(cheat_line.address as usize ^ device.byte_swap) .unwrap_or(&0); current_value == (cheat_line.data as u8) } fn equal_half(device: &mut device::Device, cheat_line: &DecodedCheat) -> bool { let current_value1 = *device .rdram .mem .get(cheat_line.address as usize ^ device.byte_swap) .unwrap_or(&0); let current_value2 = *device .rdram .mem .get((cheat_line.address + 1) as usize ^ device.byte_swap) .unwrap_or(&0); current_value1 == (cheat_line.data >> 8) as u8 && current_value2 == cheat_line.data as u8 } pub fn execute_cheats(device: &mut device::Device, cheats: Vec>) { for cheat in cheats.iter() { let mut valid = true; let mut cheat_iter = cheat.iter().peekable(); while let Some(cheat_line) = cheat_iter.next() { match cheat_line.code_type { 0x50 => { if valid { let mut expanded_cheat: Vec = vec![]; let compressed_cheat = *cheat_iter.peek().unwrap(); let count = (cheat_line.address & 0xFF00) >> 8; let offset = cheat_line.address & 0xFF; for i in 0..count { let line = DecodedCheat { code_type: compressed_cheat.code_type, address: compressed_cheat.address + (i * offset), data: compressed_cheat.data + (i as i16 * cheat_line.data as i16) as u16, }; expanded_cheat.push(line); } execute_cheats(device, [expanded_cheat].to_vec()); } valid = false; // skip the next line } 0x80 | 0xA0 => { if valid { write_byte(device, cheat_line); } valid = true; } 0x81 | 0xA1 => { if valid { write_half(device, cheat_line); } valid = true; } 0xF0 => { if device.cheats.boot && valid { write_byte(device, cheat_line); valid = true; } } 0xF1 => { if device.cheats.boot && valid { write_half(device, cheat_line); valid = true; } } 0xD0 => { valid = equal_byte(device, cheat_line); } 0xD1 => { valid = equal_half(device, cheat_line); } 0xD2 => { valid = !equal_byte(device, cheat_line); } 0xD3 => { valid = !equal_half(device, cheat_line); } 0x88 | 0x89 | 0xD8 | 0xD9 | 0xDA | 0xDB => { /* GS button not emulated */ } _ => panic!("Unknown cheat code type: {:X}", cheat_line.code_type), } } } device.cheats.boot = false; }