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