Code for the unlicensed board used in Action 52 and Cheetahmen II (iNES mapper 228).
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 9enum PrgMode { 10 // Maps prg_bank to both $8000-$BFFF and $C000-$FFFF 11 Zero, 12 // Maps (prg_bank & !1) to $8000-$BFFF and (prg_bank | 1) to $C000-$FFFF 13 One, 14} 15 16impl PrgMode { 17 fn from_bit(bit: bool) -> Self { 18 if bit { Self::One } else { Self::Zero } 19 } 20} 21 22#[derive(Debug, Clone, Encode, Decode)] 23pub struct Action52 { 24 selected_chip: u8, 25 prg_bank: u8, 26 prg_bank_mask: u8, 27 prg_mode: PrgMode, 28 chr_bank: u8, 29 nametable_mirroring: NametableMirroring, 30} 31 32impl Action52 { 33 pub fn new(prg_rom_len: u32) -> Self { 34 let prg_bank_mask = 35 if prg_rom_len < 512 * 1024 { ((prg_rom_len - 1) >> 14) as u8 } else { 0xFF }; 36 37 Self::new_with_mask(prg_bank_mask) 38 } 39 40 fn new_with_mask(prg_bank_mask: u8) -> Self { 41 Self { 42 selected_chip: 0, 43 prg_bank: 0, 44 prg_bank_mask, 45 prg_mode: PrgMode::Zero, 46 chr_bank: 0, 47 nametable_mirroring: NametableMirroring::Vertical, 48 } 49 } 50} 51 52impl MapperImpl<Action52> { 53 pub fn reset(&mut self) { 54 self.data = Action52::new_with_mask(self.data.prg_bank_mask); 55 } 56 57 pub fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 58 let prg_bank = match address { 59 0x0000..=0x7FFF => return cpu_open_bus, 60 0x8000..=0xBFFF => match self.data.prg_mode { 61 PrgMode::Zero => self.data.prg_bank & !1, 62 PrgMode::One => self.data.prg_bank, 63 }, 64 0xC000..=0xFFFF => match self.data.prg_mode { 65 PrgMode::Zero => self.data.prg_bank | 1, 66 PrgMode::One => self.data.prg_bank, 67 }, 68 }; 69 70 let full_prg_bank = (prg_bank & self.data.prg_bank_mask) | (self.data.selected_chip << 5); 71 let rom_addr = BankSizeKb::Sixteen.to_absolute_address(full_prg_bank, address); 72 self.cartridge.prg_rom.get(rom_addr as usize).copied().unwrap_or(cpu_open_bus) 73 } 74 75 pub fn write_cpu_address(&mut self, address: u16, value: u8) { 76 if address < 0x8000 { 77 // Action 52 crashes after the title screen if writes to $4020-$7FFF are allowed to do 78 // anything 79 return; 80 } 81 82 self.data.chr_bank = (value & 3) | ((address & 0xF) << 2) as u8; 83 self.data.prg_mode = PrgMode::from_bit(address.bit(5)); 84 85 self.data.prg_bank = ((address >> 6) & 0x1F) as u8; 86 87 // In Action 52 (1.5MB PRG ROM), chip 3 should map to the last 512KB of ROM and chip 2 88 // should be open bus. Emulate this by swapping chips 2 and 3 on write. 89 // Only apply these writes if there is more than 512KB of PRG ROM or else Cheetahmen II will 90 // fail to boot. 91 if self.cartridge.prg_rom.len() > 512 * 1024 { 92 let selected_chip = ((address >> 11) & 3) as u8; 93 self.data.selected_chip = selected_chip ^ (selected_chip >> 1); 94 } 95 96 self.data.nametable_mirroring = if address.bit(14) { 97 NametableMirroring::Horizontal 98 } else { 99 NametableMirroring::Vertical 100 }; 101 102 log::trace!("Mapper write: {address:04X} {value:02X}"); 103 log::trace!(" Mapper state: {:?}", self.data); 104 } 105} 106 107impl HasBasicPpuMapping for MapperImpl<Action52> { 108 fn map_ppu_address(&self, address: u16) -> PpuMapResult { 109 match address { 110 0x0000..=0x1FFF => PpuMapResult::ChrROM( 111 BankSizeKb::Eight.to_absolute_address(self.data.chr_bank, address), 112 ), 113 0x2000..=0x3EFF => { 114 PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address)) 115 } 116 _ => panic!("Invalid PPU mapper address: {address:04X}"), 117 } 118 } 119}