1//! S-DD1 coprocessor, a data decompression chip with an algorithm designed for SNES tile data 2//! 3//! Used by Star Ocean and Street Fighter Alpha 2 4 5mod decompressor; 6 7use crate::common; 8use crate::common::{Rom, impl_take_set_rom}; 9use crate::sdd1::decompressor::Sdd1Decompressor; 10use bincode::{Decode, Encode}; 11use jgenesis_common::num::GetBit; 12use jgenesis_proc_macros::PartialClone; 13use std::array; 14 15#[derive(Debug, Clone, Encode, Decode)] 16struct Sdd1Mmc { 17 bank_c_base_addr: u32, 18 bank_d_base_addr: u32, 19 bank_e_base_addr: u32, 20 bank_f_base_addr: u32, 21} 22 23impl Sdd1Mmc { 24 fn new() -> Self { 25 Self { 26 bank_c_base_addr: 0x000000, 27 bank_d_base_addr: 0x100000, 28 bank_e_base_addr: 0x200000, 29 bank_f_base_addr: 0x300000, 30 } 31 } 32 33 fn map_rom_address(&self, address: u32, rom_len: u32) -> Option<u32> { 34 let bank = (address >> 16) & 0xFF; 35 let offset = address & 0xFFFF; 36 match (bank, offset) { 37 (0x00..=0x3F | 0x80..=0xBF, 0x8000..=0xFFFF) => { 38 // Banks $00-$3F and $80-$BF are mapped as if the cartridge were LoROM, except 39 // with $80-$BF explicitly mirroring $00-$3F 40 Some(common::lorom_map_rom_address(address & 0x7FFFFF, rom_len)) 41 } 42 (0xC0..=0xCF, _) => { 43 // MMC bank C 44 Some(self.bank_c_base_addr | (address & 0x0FFFFF)) 45 } 46 (0xD0..=0xDF, _) => { 47 // MMC bank D 48 Some(self.bank_d_base_addr | (address & 0x0FFFFF)) 49 } 50 (0xE0..=0xEF, _) => { 51 // MMC bank E 52 Some(self.bank_e_base_addr | (address & 0x0FFFFF)) 53 } 54 (0xF0..=0xFF, _) => { 55 // MMC bank F 56 Some(self.bank_f_base_addr | (address & 0x0FFFFF)) 57 } 58 _ => None, 59 } 60 } 61} 62 63#[derive(Debug, Clone, Encode, Decode, PartialClone)] 64pub struct Sdd1 { 65 #[partial_clone(default)] 66 rom: Rom, 67 sram: Box<[u8]>, 68 mmc: Sdd1Mmc, 69 decompressor: Sdd1Decompressor, 70 dma_enabled_1: [bool; 8], 71 dma_enabled_2: [bool; 8], 72 dma_in_progress: bool, 73} 74 75impl Sdd1 { 76 #[must_use] 77 pub fn new(rom: Box<[u8]>, sram: Box<[u8]>) -> Self { 78 Self { 79 rom: Rom(rom), 80 sram, 81 mmc: Sdd1Mmc::new(), 82 decompressor: Sdd1Decompressor::new(), 83 dma_enabled_1: [false; 8], 84 dma_enabled_2: [false; 8], 85 dma_in_progress: false, 86 } 87 } 88 89 #[inline] 90 #[must_use] 91 pub fn read(&mut self, address: u32) -> Option<u8> { 92 if self.dma_in_progress { 93 // TODO check address? 94 return Some(self.decompressor.next_byte(&self.mmc, &self.rom)); 95 } 96 97 let bank = (address >> 16) & 0xFF; 98 let offset = address & 0xFFFF; 99 match (bank, offset) { 100 (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FFF) | (0x70..=0x73, _) 101 if !self.sram.is_empty() => 102 { 103 // SRAM 104 let sram_addr = (address as usize) & (self.sram.len() - 1); 105 Some(self.sram[sram_addr]) 106 } 107 (0x00..=0x3F | 0x80..=0xBF, 0x4800..=0x4807) => { 108 // S-DD1 internal registers 109 self.read_register(offset) 110 } 111 _ => { 112 // ROM (or possibly open bus) 113 self.mmc 114 .map_rom_address(address, self.rom.len() as u32) 115 .and_then(|rom_addr| self.rom.get(rom_addr as usize).copied()) 116 } 117 } 118 } 119 120 fn read_register(&self, offset: u32) -> Option<u8> { 121 match offset { 122 0x4800 => Some( 123 (0..8).map(|i| u8::from(self.dma_enabled_1[i]) << i).reduce(|a, b| a | b).unwrap(), 124 ), 125 0x4801 => Some( 126 (0..8).map(|i| u8::from(self.dma_enabled_2[i]) << i).reduce(|a, b| a | b).unwrap(), 127 ), 128 0x4802 | 0x4803 => Some(0x00), 129 0x4804 => Some((self.mmc.bank_c_base_addr >> 20) as u8), 130 0x4805 => Some((self.mmc.bank_d_base_addr >> 20) as u8), 131 0x4806 => Some((self.mmc.bank_e_base_addr >> 20) as u8), 132 0x4807 => Some((self.mmc.bank_f_base_addr >> 20) as u8), 133 _ => None, 134 } 135 } 136 137 #[inline] 138 pub fn write(&mut self, address: u32, value: u8) { 139 let bank = (address >> 16) & 0xFF; 140 let offset = address & 0xFFFF; 141 match (bank, offset) { 142 (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FFF) | (0x70..=0x73, _) 143 if !self.sram.is_empty() => 144 { 145 // SRAM 146 let sram_addr = (address as usize) & (self.sram.len() - 1); 147 self.sram[sram_addr] = value; 148 } 149 (0x00..=0x3F | 0x80..=0xBF, 0x4800..=0x4807) => { 150 // S-DD1 internal registers 151 self.write_register(offset, value); 152 } 153 _ => {} 154 } 155 } 156 157 #[allow(clippy::match_same_arms)] 158 fn write_register(&mut self, offset: u32, value: u8) { 159 match offset { 160 0x4800 => { 161 // DMA enabled 1 162 self.dma_enabled_1 = array::from_fn(|i| value.bit(i as u8)); 163 } 164 0x4801 => { 165 // DMA enabled 2 166 self.dma_enabled_2 = array::from_fn(|i| value.bit(i as u8)); 167 } 168 0x4802 | 0x4803 => { 169 // Unknown functionality; Star Ocean writes $00 to these registers, 170 // Street Fighter Alpha 2 doesn't touch them 171 } 172 0x4804 => { 173 // Set MMC bank C 174 self.mmc.bank_c_base_addr = u32::from(value) << 20; 175 } 176 0x4805 => { 177 // Set MMC bank D 178 self.mmc.bank_d_base_addr = u32::from(value) << 20; 179 } 180 0x4806 => { 181 // Set MMC bank E 182 self.mmc.bank_e_base_addr = u32::from(value) << 20; 183 } 184 0x4807 => { 185 // Set MMC bank F 186 self.mmc.bank_f_base_addr = u32::from(value) << 20; 187 } 188 _ => {} 189 } 190 } 191 192 impl_take_set_rom!(rom); 193 194 #[inline] 195 #[must_use] 196 pub fn has_battery(&self) -> bool { 197 !self.sram.is_empty() 198 } 199 200 #[inline] 201 #[must_use] 202 pub fn sram(&self) -> Option<&[u8]> { 203 (!self.sram.is_empty()).then_some(self.sram.as_ref()) 204 } 205 206 #[inline] 207 pub fn notify_dma_start(&mut self, channel: u8, source_address: u32) { 208 if !self.dma_enabled_1[channel as usize] 209 || !self.dma_enabled_2[channel as usize] 210 || self.mmc.map_rom_address(source_address, self.rom.len() as u32).is_none() 211 { 212 self.dma_in_progress = false; 213 return; 214 } 215 216 // DMA enabled 2 is automatically cleared during/after DMA 217 self.dma_enabled_2[channel as usize] = false; 218 self.dma_in_progress = true; 219 220 self.decompressor.init(source_address, &self.mmc, &self.rom); 221 } 222 223 #[inline] 224 pub fn notify_dma_end(&mut self) { 225 self.dma_in_progress = false; 226 } 227}