rustynes_mappers/
m107_magic_dragon107.rs1use crate::cartridge::Mirroring;
18use crate::mapper::{Mapper, MapperCaps, MapperError};
19use alloc::{boxed::Box, vec::Vec};
20use alloc::{format, vec};
21
22const PRG_BANK_32K: usize = 0x8000;
23const CHR_BANK_8K: usize = 0x2000;
24const NAMETABLE_SIZE: usize = 0x0400;
25const NAMETABLE_SIZE_U16: u16 = 0x0400;
26
27const SAVE_STATE_VERSION: u8 = 1;
28
29const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
34 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
35 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
36 let physical = mirroring.physical_bank(table);
37 physical * NAMETABLE_SIZE + local
38}
39
40pub struct MagicDragon107 {
42 prg_rom: Box<[u8]>,
43 chr_rom: Box<[u8]>,
44 vram: Box<[u8]>,
45 prg_bank: u8,
46 chr_bank: u8,
47 mirroring: Mirroring,
48}
49
50impl MagicDragon107 {
51 pub fn new(
58 prg_rom: Box<[u8]>,
59 chr_rom: Box<[u8]>,
60 mirroring: Mirroring,
61 ) -> Result<Self, MapperError> {
62 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
63 return Err(MapperError::Invalid(format!(
64 "mapper 107 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
65 prg_rom.len()
66 )));
67 }
68 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
69 return Err(MapperError::Invalid(format!(
70 "mapper 107 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
71 chr_rom.len()
72 )));
73 }
74 Ok(Self {
75 prg_rom,
76 chr_rom,
77 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
78 prg_bank: 0,
79 chr_bank: 0,
80 mirroring,
81 })
82 }
83}
84
85impl Mapper for MagicDragon107 {
86 fn caps(&self) -> MapperCaps {
87 MapperCaps::NONE
88 }
89
90 fn cpu_read(&mut self, addr: u16) -> u8 {
91 if (0x8000..=0xFFFF).contains(&addr) {
92 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
93 let bank = (self.prg_bank as usize) % count;
94 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
95 } else {
96 0
97 }
98 }
99
100 fn cpu_write(&mut self, addr: u16, value: u8) {
101 if (0x8000..=0xFFFF).contains(&addr) {
102 self.prg_bank = value >> 1;
103 self.chr_bank = value;
104 }
105 }
106
107 fn ppu_read(&mut self, addr: u16) -> u8 {
108 let addr = addr & 0x3FFF;
109 match addr {
110 0x0000..=0x1FFF => {
111 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
112 let bank = (self.chr_bank as usize) % count;
113 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
114 }
115 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
116 _ => 0,
117 }
118 }
119
120 fn ppu_write(&mut self, addr: u16, value: u8) {
121 let addr = addr & 0x3FFF;
122 if let 0x2000..=0x3EFF = addr {
123 let off = nametable_offset(addr, self.mirroring);
124 self.vram[off] = value;
125 }
126 }
127
128 fn current_mirroring(&self) -> Mirroring {
129 self.mirroring
130 }
131
132 fn save_state(&self) -> Vec<u8> {
133 let mut out = Vec::with_capacity(3 + self.vram.len());
134 out.push(SAVE_STATE_VERSION);
135 out.push(self.prg_bank);
136 out.push(self.chr_bank);
137 out.extend_from_slice(&self.vram);
138 out
139 }
140
141 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
142 let expected = 3 + self.vram.len();
143 if data.len() != expected {
144 return Err(MapperError::WrongLength {
145 expected,
146 got: data.len(),
147 });
148 }
149 if data[0] != SAVE_STATE_VERSION {
150 return Err(MapperError::UnsupportedVersion(data[0]));
151 }
152 self.prg_bank = data[1];
153 self.chr_bank = data[2];
154 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
155 Ok(())
156 }
157}
158
159#[cfg(test)]
160#[allow(clippy::cast_possible_truncation)]
161mod tests {
162 use super::*;
163
164 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
165 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
166 for b in 0..banks {
167 v[b * PRG_BANK_32K] = b as u8;
168 }
169 v.into_boxed_slice()
170 }
171
172 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
173 let mut v = vec![0u8; banks * CHR_BANK_8K];
174 for b in 0..banks {
175 v[b * CHR_BANK_8K] = b as u8;
176 }
177 v.into_boxed_slice()
178 }
179
180 #[test]
181 fn m107_latch_selects_prg_and_chr() {
182 let mut m =
183 MagicDragon107::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
184 m.cpu_write(0x8000, 6);
186 assert_eq!(m.cpu_read(0x8000), 3);
187 assert_eq!(m.ppu_read(0x0000), 6);
188 }
189
190 #[test]
191 fn m107_save_state_round_trip() {
192 let mut m =
193 MagicDragon107::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
194 m.cpu_write(0x8000, 4); let blob = m.save_state();
196 let mut m2 =
197 MagicDragon107::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
198 m2.load_state(&blob).unwrap();
199 assert_eq!(m2.cpu_read(0x8000), 2);
200 assert_eq!(m2.ppu_read(0x0000), 4);
201 }
202}