1//! SPC7110 coprocessor, a data decompression chip (optionally with an Epson RTC-4513 real-time clock chip) 2//! 3//! Used by Tengai Makyou Zero, Momotarou Dentetsu Happy, and Super Power League 4 4 5mod decompressor; 6mod registers; 7mod rtc; 8 9use crate::common::{Rom, impl_take_set_rom}; 10use crate::spc7110::decompressor::Spc7110Decompressor; 11use crate::spc7110::registers::Registers; 12use crate::spc7110::rtc::Rtc4513; 13use bincode::{Decode, Encode}; 14use jgenesis_common::frontend::SaveWriter; 15use jgenesis_common::num::{U16Ext, U24Ext}; 16use jgenesis_proc_macros::PartialClone; 17 18// All 3 SPC7110 game images have a 1MB program ROM followed by a data ROM 19const DATA_ROM_START: usize = 0x100000; 20 21// All 3 SPC7110 games have 8KB of SRAM 22const SRAM_LEN: usize = 8 * 1024; 23 24type Sram = [u8; SRAM_LEN]; 25 26#[derive(Debug, Clone, PartialClone, Encode, Decode)] 27pub struct Spc7110 { 28 #[partial_clone(default)] 29 rom: Rom, 30 sram: Box<Sram>, 31 registers: Registers, 32 decompressor: Spc7110Decompressor, 33 rtc: Option<Rtc4513>, 34} 35 36impl Spc7110 { 37 #[allow(clippy::boxed_local)] 38 #[allow(clippy::missing_panics_doc)] 39 #[must_use] 40 pub fn new<S: SaveWriter>( 41 rom: Box<[u8]>, 42 initial_sram: Box<[u8]>, 43 save_writer: &mut S, 44 ) -> Self { 45 let sram = if initial_sram.len() == SRAM_LEN { 46 initial_sram.try_into().unwrap() 47 } else { 48 vec![0; SRAM_LEN].into_boxed_slice().try_into().unwrap() 49 }; 50 51 // Chipset byte of $F9 indicates SPC7110 + RTC-4513 (only used by Tengai Makyou Zero) 52 let has_rtc = rom[0xFFD6] == 0xF9; 53 let rtc = 54 has_rtc.then(|| save_writer.load_serialized("rtc").ok().unwrap_or_else(Rtc4513::new)); 55 56 log::info!("SPC7110 has RTC-4513: {}", rtc.is_some()); 57 58 Self { 59 rom: Rom(rom), 60 sram, 61 registers: Registers::new(), 62 decompressor: Spc7110Decompressor::new(), 63 rtc, 64 } 65 } 66 67 #[inline] 68 #[must_use] 69 pub fn read(&mut self, address: u32) -> Option<u8> { 70 let bank = (address >> 16) & 0xFF; 71 let offset = address & 0xFFFF; 72 match (bank, offset) { 73 (0x00..=0x3F | 0x80..=0xBF, 0x4800..=0x4842) => { 74 // SPC7110 internal registers 75 self.read_register(address) 76 } 77 (0x00..=0x0F | 0x80..=0x8F, 0x8000..=0xFFFF) | (0xC0..=0xCF, _) => { 78 // Program ROM (1MB) 79 let rom_addr = address & 0x0FFFFF; 80 Some(self.rom[rom_addr as usize]) 81 } 82 (0x40..=0x4F, _) => { 83 // Expansion ROM (1MB) if applicable, otherwise mirror program ROM 84 // This is needed for the fan translated version of Tengai Makyou Zero which stores 85 // an additional 1MB of ROM at the end of the 7MB file and expects it to be mapped 86 // to banks $40-$4F 87 Some(if self.rom.len() >= 0x700000 { 88 self.rom[0x600000 | (address & 0xFFFFF) as usize] 89 } else { 90 self.rom[(address & 0xFFFFF) as usize] 91 }) 92 } 93 (0xD0..=0xDF, _) => { 94 // Data ROM bank D 95 let rom_addr = (address & 0x0FFFFF) | (u32::from(self.registers.rom_bank_d) << 20); 96 self.rom[DATA_ROM_START..].get(rom_addr as usize).copied() 97 } 98 (0xE0..=0xEF, _) => { 99 // Data ROM bank E 100 let rom_addr = (address & 0x0FFFFF) | (u32::from(self.registers.rom_bank_e) << 20); 101 self.rom[DATA_ROM_START..].get(rom_addr as usize).copied() 102 } 103 (0xF0..=0xFF, _) => { 104 // Data ROM bank F 105 let rom_addr = (address & 0x0FFFFF) | (u32::from(self.registers.rom_bank_f) << 20); 106 self.rom[DATA_ROM_START..].get(rom_addr as usize).copied() 107 } 108 (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FFF) => { 109 // SRAM (always 8KB) 110 self.registers.sram_enabled.then(|| { 111 let sram_addr = address & 0x1FFF; 112 self.sram[sram_addr as usize] 113 }) 114 } 115 (0x50, _) => { 116 // Alternate mapping for internal register $4800 (read next decompressed byte) 117 Some(self.decompressor.next_byte(&self.rom[DATA_ROM_START..])) 118 } 119 _ => None, 120 } 121 } 122 123 #[inline] 124 pub fn write(&mut self, address: u32, value: u8) { 125 let bank = (address >> 16) & 0xFF; 126 let offset = address & 0xFFFF; 127 match (bank, offset) { 128 (0x00..=0x3F | 0x80..=0xBF, 0x4800..=0x4842) => { 129 // SPC7110 internal registers 130 self.write_register(address, value); 131 } 132 (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FFF) if self.registers.sram_enabled => { 133 // SRAM (always 8KB) 134 let sram_addr = address & 0x1FFF; 135 self.sram[sram_addr as usize] = value; 136 } 137 _ => {} 138 } 139 } 140 141 impl_take_set_rom!(rom); 142 143 #[inline] 144 #[must_use] 145 pub fn sram(&self) -> &[u8] { 146 self.sram.as_ref() 147 } 148 149 #[inline] 150 #[must_use] 151 pub fn rtc(&self) -> Option<&Rtc4513> { 152 self.rtc.as_ref() 153 } 154 155 #[allow(clippy::match_same_arms)] 156 fn read_register(&mut self, address: u32) -> Option<u8> { 157 log::trace!("SPC7110 register read {:04X}", address & 0xFFFF); 158 159 match address & 0xFFFF { 160 0x4800 => Some(self.decompressor.next_byte(&self.rom[DATA_ROM_START..])), 161 0x4801 => Some(self.decompressor.rom_directory_base.low_byte()), 162 0x4802 => Some(self.decompressor.rom_directory_base.mid_byte()), 163 0x4803 => Some(self.decompressor.rom_directory_base.high_byte()), 164 0x4804 => Some(self.decompressor.rom_directory_index), 165 0x4805 => Some(self.decompressor.target_offset.lsb()), 166 0x4806 => Some(self.decompressor.target_offset.msb()), 167 // $4807: Unknown functionality, reportedly DMA channel? 168 0x4807 => Some(0x00), 169 // $4808: Unknown functionality, reportedly "C r/w option"? 170 0x4808 => Some(0x00), 171 0x480B => Some(self.decompressor.read_mode()), 172 0x480C => Some(self.decompressor.read_status()), 173 0x4809 => Some(self.decompressor.length_counter.lsb()), 174 0x480A => Some(self.decompressor.length_counter.msb()), 175 0x4810 => Some(self.registers.read_direct_data_rom_r4810(&self.rom[DATA_ROM_START..])), 176 0x4811 => Some(self.registers.direct_data_rom_base.low_byte()), 177 0x4812 => Some(self.registers.direct_data_rom_base.mid_byte()), 178 0x4813 => Some(self.registers.direct_data_rom_base.high_byte()), 179 0x4814 => Some(self.registers.direct_data_rom_offset.lsb()), 180 0x4815 => Some(self.registers.direct_data_rom_offset.msb()), 181 0x4816 => Some(self.registers.direct_data_rom_step.lsb()), 182 0x4817 => Some(self.registers.direct_data_rom_step.msb()), 183 // $4818: Direct data ROM mode; reading from this register has unknown functionality 184 0x4818 => Some(0x00), 185 0x481A => Some(self.registers.read_direct_data_rom_r481a(&self.rom[DATA_ROM_START..])), 186 0x4820..=0x4823 => Some(self.registers.read_dividend(address)), 187 0x4824 => Some(self.registers.math.multiplier.lsb()), 188 0x4825 => Some(self.registers.math.multiplier.msb()), 189 0x4826 => Some(self.registers.math.divisor.lsb()), 190 0x4827 => Some(self.registers.math.divisor.msb()), 191 0x4828..=0x482B => Some(self.registers.read_math_result(address)), 192 0x482C => Some(self.registers.math.remainder.lsb()), 193 0x482D => Some(self.registers.math.remainder.msb()), 194 // $482E: Reportedly either "reset" or signed/unsigned toggle; games seem to only write $00 195 0x482E => Some(0x00), 196 // $482F: Multiplication/division unit status; hardcoded to 0 (ready) 197 0x482F => Some(0x00), 198 0x4830 => Some(self.registers.read_sram_enabled()), 199 0x4831 => Some(self.registers.rom_bank_d), 200 0x4832 => Some(self.registers.rom_bank_e), 201 0x4833 => Some(self.registers.rom_bank_f), 202 0x4834 => Some(self.registers.sram_bank), 203 0x4840 => self.rtc.as_ref().map(Rtc4513::read_chip_select), 204 0x4841 => self.rtc.as_mut().map(Rtc4513::read_data_port), 205 0x4842 => self.rtc.is_some().then_some(rtc::STATUS_BYTE), 206 _ => None, 207 } 208 } 209 210 #[allow(clippy::match_same_arms)] 211 fn write_register(&mut self, address: u32, value: u8) { 212 log::trace!("SPC7110 register write {:04X} {value:02X}", address & 0xFFFF); 213 214 match address & 0xFFFF { 215 0x4801 => self.decompressor.write_rom_directory_base_low(value), 216 0x4802 => self.decompressor.write_rom_directory_base_mid(value), 217 0x4803 => self.decompressor.write_rom_directory_base_high(value), 218 0x4804 => self.decompressor.rom_directory_index = value, 219 0x4805 => self.decompressor.write_target_offset_low(value), 220 0x4806 => { 221 self.decompressor.write_target_offset_high(value, &self.rom[DATA_ROM_START..]); 222 } 223 // $4807: Unknown functionality, reportedly DMA channel? 224 0x4807 => {} 225 // $4808: Unknown functionality, reportedly "C r/w option"? 226 0x4808 => {} 227 0x4809 => self.decompressor.write_length_counter_low(value), 228 0x480A => self.decompressor.write_length_counter_high(value), 229 0x480B => self.decompressor.write_mode(value), 230 0x4811 => self.registers.write_direct_data_rom_base_low(value), 231 0x4812 => self.registers.write_direct_data_rom_base_mid(value), 232 0x4813 => self.registers.write_direct_data_rom_base_high(value), 233 0x4814 => self.registers.write_direct_data_rom_offset_low(value), 234 0x4815 => self.registers.write_direct_data_rom_offset_high(value), 235 0x4816 => self.registers.write_direct_data_rom_step_low(value), 236 0x4817 => self.registers.write_direct_data_rom_step_high(value), 237 0x4818 => self.registers.write_direct_data_rom_mode(value), 238 0x4820..=0x4823 => self.registers.write_dividend(address, value), 239 0x4824 => self.registers.write_multiplier_low(value), 240 0x4825 => self.registers.write_multiplier_high(value), 241 0x4826 => self.registers.write_divisor_low(value), 242 0x4827 => self.registers.write_divisor_high(value), 243 0x482E => self.registers.write_math_mode(value), 244 0x4830 => self.registers.write_sram_enabled(value), 245 0x4831 => self.registers.rom_bank_d = value, 246 0x4832 => self.registers.rom_bank_e = value, 247 0x4833 => self.registers.rom_bank_f = value, 248 0x4834 => self.registers.sram_bank = value, 249 0x4840 => { 250 if let Some(rtc) = &mut self.rtc { 251 rtc.write_chip_select(value); 252 } 253 } 254 0x4841 => { 255 if let Some(rtc) = &mut self.rtc { 256 rtc.write_data_port(value); 257 } 258 } 259 _ => {} 260 } 261 } 262}