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}