sdd1.rsannotatedsdd1.rssource227 lines · 7.1 KB · raw

S-DD1 coprocessor, a data decompression chip with an algorithm designed for SNES tile data

Used by Star Ocean and Street Fighter Alpha 2

5mod decompressor;
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}