bandai.rsannotatedbandai.rssource308 lines · 11.1 KB · raw

Code for Bandai's FCG boards (iNES mappers 16 + 153 + 159).

3mod eeprom;
5use crate::bus::cartridge::mappers::bandai::eeprom::{X24C01Chip, X24C02Chip};
6use crate::bus::cartridge::mappers::{BankSizeKb, ChrType, NametableMirroring, PpuMapResult};
7use crate::bus::cartridge::{HasBasicPpuMapping, MapperImpl};
8use bincode::{Decode, Encode};
9use jgenesis_common::num::GetBit;
10
11#[allow(clippy::upper_case_acronyms)]
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
13enum MemoryVariant {
14    None,
15    RAM,
16    X24C01,
17    X24C02,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
21enum Variant {
22    Fcg,
23    Lz93D50(MemoryVariant),
24    Unknown,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
28enum IrqCounterUpdate {
29    LowByte,
30    HighByte,
31}
32
33#[derive(Debug, Clone, Encode, Decode)]
34struct IrqCounter {
35    variant: Variant,
36    counter: u16,
37    latch: u16,
38    enabled: bool,
39}
40
41impl IrqCounter {
42    fn new(variant: Variant) -> Self {
43        Self { variant, counter: 0, latch: 0, enabled: false }
44    }
45
46    fn handle_control_write(&mut self, value: u8) {
47        self.enabled = value.bit(0);
48
49        if matches!(self.variant, Variant::Lz93D50(_) | Variant::Unknown) {
50            self.counter = self.latch;
51        }
52    }
53
54    fn update_counter(&mut self, update: IrqCounterUpdate, value: u8) {
55        let field_to_update = match self.variant {
56            Variant::Fcg => &mut self.counter,
57            Variant::Lz93D50(_) | Variant::Unknown => &mut self.latch,
58        };
59
60        *field_to_update = match update {
61            IrqCounterUpdate::LowByte => (*field_to_update & 0xFF00) | u16::from(value),
62            IrqCounterUpdate::HighByte => (*field_to_update & 0x00FF) | (u16::from(value) << 8),
63        };
64    }
65
66    fn tick_cpu(&mut self) {
67        if self.enabled {
68            self.counter = self.counter.saturating_sub(1);
69        }
70    }
71
72    fn interrupt_flag(&self) -> bool {
73        self.enabled && self.counter == 0
74    }
75}
76
77#[derive(Debug, Clone, Encode, Decode)]
78enum Eeprom {
79    X24C01(X24C01Chip),
80    X24C02(X24C02Chip),
81}
82
83#[derive(Debug, Clone, Encode, Decode)]
84pub(crate) struct BandaiFcg {
85    variant: Variant,
86    chr_type: ChrType,
87    prg_bank: u8,
88    prg_256kb_bank: u8,
89    chr_banks: [u8; 8],
90    nametable_mirroring: NametableMirroring,
91    ram_enabled: bool,
92    irq: IrqCounter,
93    eeprom: Option<Eeprom>,
94}
95
96impl BandaiFcg {
97    pub(crate) fn new(
98        mapper_number: u16,
99        sub_mapper_number: u8,
100        chr_type: ChrType,
101        prg_ram_len: u32,
102        sav_bytes: Option<&Vec<u8>>,
103    ) -> Self {
104        let variant = match (mapper_number, sub_mapper_number) {
105            (16, 4) => Variant::Fcg,
106            (16, 5) => {
107                let memory_variant =
108                    if prg_ram_len > 0 { MemoryVariant::X24C02 } else { MemoryVariant::None };
109                Variant::Lz93D50(memory_variant)
110            }
111            (16, _) => Variant::Unknown,
112            (153, _) => Variant::Lz93D50(MemoryVariant::RAM),
113            (159, _) => Variant::Lz93D50(MemoryVariant::X24C01),
114            _ => panic!("unsupported Bandai mapper number: {mapper_number}"),
115        };
116
117        let eeprom = match variant {
118            Variant::Lz93D50(MemoryVariant::X24C01) => {
119                Some(Eeprom::X24C01(X24C01Chip::new(sav_bytes)))
120            }
121            Variant::Lz93D50(MemoryVariant::X24C02) | Variant::Unknown => {
122                Some(Eeprom::X24C02(X24C02Chip::new(sav_bytes)))
123            }
124            _ => None,
125        };
126
127        log::info!("Bandai FCG variant: {variant:?}");
128
129        Self {
130            variant,
131            chr_type,
132            prg_bank: 0,
133            prg_256kb_bank: 0,
134            chr_banks: [0; 8],
135            nametable_mirroring: NametableMirroring::Vertical,
136            ram_enabled: false,
137            irq: IrqCounter::new(variant),
138            eeprom,
139        }
140    }
141}
142
143fn eeprom_read(data: bool, cpu_open_bus: u8) -> u8 {
144    (cpu_open_bus & 0xEF) | (u8::from(data) << 4)
145}
146
147impl MapperImpl<BandaiFcg> {
148    pub(crate) fn read_cpu_address(&mut self, address: u16, cpu_open_bus: u8) -> u8 {
149        match address {
150            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
151            0x4020..=0x5FFF => cpu_open_bus,
152            0x6000..=0x7FFF => match self.data.variant {
153                Variant::Fcg | Variant::Lz93D50(MemoryVariant::None) => cpu_open_bus,
154                Variant::Lz93D50(MemoryVariant::RAM) => {
155                    if self.data.ram_enabled {
156                        self.cartridge.get_prg_ram((address & 0x1FFF).into())
157                    } else {
158                        cpu_open_bus
159                    }
160                }
161                Variant::Lz93D50(MemoryVariant::X24C01 | MemoryVariant::X24C02)
162                | Variant::Unknown => match &self.data.eeprom {
163                    Some(Eeprom::X24C01(chip)) => eeprom_read(chip.handle_read(), cpu_open_bus),
164                    Some(Eeprom::X24C02(chip)) => eeprom_read(chip.handle_read(), cpu_open_bus),
165                    None => cpu_open_bus,
166                },
167            },
168            0x8000..=0xBFFF => {
169                let prg_rom_addr =
170                    BankSizeKb::Sixteen.to_absolute_address(self.data.prg_bank, address);
171                self.cartridge
172                    .get_prg_rom(prg_rom_addr | (u32::from(self.data.prg_256kb_bank) << 18))
173            }
174            0xC000..=0xFFFF => {
175                // Gross, but necessary; the one game that uses LZ93D50 w/ SRAM has two 256KB "outer"
176                // banks, and this range needs to map into the last inner bank of whichever outer bank
177                // is selected
178                let prg_rom_len = if self.data.variant == Variant::Lz93D50(MemoryVariant::RAM) {
179                    self.cartridge.prg_rom.len() / 2
180                } else {
181                    self.cartridge.prg_rom.len()
182                };
183                let prg_rom_addr =
184                    BankSizeKb::Sixteen.to_absolute_address_last_bank(prg_rom_len as u32, address);
185                self.cartridge
186                    .get_prg_rom(prg_rom_addr | (u32::from(self.data.prg_256kb_bank) << 18))
187            }
188        }
189    }
190
191    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
192        log::trace!("Wrote address={address:04X}, value={value:02X}");
193        match (self.data.variant, address) {
194            (_, 0x0000..=0x401F) => panic!("invalid CPU map address: {address:04X}"),
195            (Variant::Fcg | Variant::Unknown, 0x6000..=0x7FFF)
196            | (Variant::Lz93D50(_) | Variant::Unknown, 0x8000..=0xFFFF) => {
197                match (self.data.variant, address & 0x000F) {
198                    (Variant::Lz93D50(MemoryVariant::RAM), 0x0000..=0x0003) => {
199                        self.data.prg_256kb_bank = value & 0x01;
200                    }
201                    (
202                        Variant::Fcg
203                        | Variant::Lz93D50(
204                            MemoryVariant::None | MemoryVariant::X24C02 | MemoryVariant::X24C01,
205                        )
206                        | Variant::Unknown,
207                        0x0000..=0x0007,
208                    ) => {
209                        let chr_bank_index = address & 0x0007;
210                        self.data.chr_banks[chr_bank_index as usize] = value;
211                    }
212                    (_, 0x0008) => {
213                        self.data.prg_bank = value & 0x0F;
214                    }
215                    (_, 0x0009) => {
216                        self.data.nametable_mirroring = match value & 0x03 {
217                            0x00 => NametableMirroring::Vertical,
218                            0x01 => NametableMirroring::Horizontal,
219                            0x02 => NametableMirroring::SingleScreenBank0,
220                            0x03 => NametableMirroring::SingleScreenBank1,
221                            _ => unreachable!("value & 0x03 should be 0x00/0x01/0x02/0x03"),
222                        };
223                    }
224                    (_, 0x000A) => {
225                        self.data.irq.handle_control_write(value);
226                    }
227                    (_, 0x000B) => {
228                        self.data.irq.update_counter(IrqCounterUpdate::LowByte, value);
229                    }
230                    (_, 0x000C) => {
231                        self.data.irq.update_counter(IrqCounterUpdate::HighByte, value);
232                    }
233                    (
234                        Variant::Lz93D50(MemoryVariant::X24C01 | MemoryVariant::X24C02)
235                        | Variant::Unknown,
236                        0x000D,
237                    ) => match &mut self.data.eeprom {
238                        Some(Eeprom::X24C01(chip)) => {
239                            chip.handle_write(value);
240                        }
241                        Some(Eeprom::X24C02(chip)) => {
242                            chip.handle_write(value);
243                        }
244                        None => {}
245                    },
246                    (Variant::Lz93D50(MemoryVariant::RAM), 0x000D) => {
247                        self.data.ram_enabled = value.bit(5);
248                    }
249                    _ => {}
250                }
251            }
252            (Variant::Lz93D50(MemoryVariant::RAM), 0x6000..=0x7FFF) if self.data.ram_enabled => {
253                self.cartridge.set_prg_ram((address & 0x1FFF).into(), value);
254            }
255            _ => {}
256        }
257    }
258
259    pub(crate) fn tick_cpu(&mut self) {
260        self.data.irq.tick_cpu();
261    }
262
263    pub(crate) fn interrupt_flag(&self) -> bool {
264        self.data.irq.interrupt_flag()
265    }
266
267    pub(crate) fn get_and_clear_eeprom_dirty_bit(&mut self) -> bool {
268        self.data.eeprom.as_mut().is_some_and(|eeprom| match eeprom {
269            Eeprom::X24C01(chip) => chip.get_and_clear_dirty_bit(),
270            Eeprom::X24C02(chip) => chip.get_and_clear_dirty_bit(),
271        })
272    }
273
274    pub(crate) fn eeprom(&self) -> Option<&[u8]> {
275        self.data.eeprom.as_ref().map(|eeprom| match eeprom {
276            Eeprom::X24C01(chip) => chip.get_memory(),
277            Eeprom::X24C02(chip) => chip.get_memory(),
278        })
279    }
280
281    pub(crate) fn name(&self) -> &'static str {
282        match self.data.variant {
283            Variant::Fcg => "Bandai FCG-1 / FCG-2",
284            Variant::Lz93D50(_) => "Bandai LZ93D50",
285            Variant::Unknown => "Bandai FCG",
286        }
287    }
288}
289
290impl HasBasicPpuMapping for MapperImpl<BandaiFcg> {
291    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
292        match address {
293            0x0000..=0x1FFF => match self.data.variant {
294                Variant::Lz93D50(MemoryVariant::RAM) => PpuMapResult::ChrRAM(address.into()),
295                _ => {
296                    let chr_bank_index = address / 0x0400;
297                    let chr_bank_number = self.data.chr_banks[chr_bank_index as usize];
298                    let chr_addr = BankSizeKb::One.to_absolute_address(chr_bank_number, address);
299                    self.data.chr_type.to_map_result(chr_addr)
300                }
301            },
302            0x2000..=0x3EFF => {
303                PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address))
304            }
305            0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"),
306        }
307    }
308}