mmc5.rsannotatedmmc5.rssource1018 lines · 36.7 KB · raw

Code for the MMC5 board (iNES mapper 5).

3use crate::apu;
4use crate::apu::FrameCounter;
5use crate::apu::pulse::{PulseChannel, SweepStatus};
6use crate::bus::cartridge::mappers::{BankSizeKb, CpuMapResult};
7use crate::bus::cartridge::{Cartridge, MapperImpl};
8use bincode::{Decode, Encode};
9use jgenesis_common::frontend::TimingMode;
10use jgenesis_common::num::GetBit;
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
13enum PrgBankingMode {
14    Mode0,
15    Mode1,
16    Mode2,
17    Mode3,
18}
19
20impl PrgBankingMode {
21    fn map_result(
22        bank_number: u8,
23        bank_size: BankSizeKb,
24        address: u16,
25        prg_ram_len: u32,
26    ) -> CpuMapResult {
27        let is_rom = bank_number.bit(7);
28
29        if !is_rom && prg_ram_len == 16 * 1024 && bank_size == BankSizeKb::Eight {
30            // Every MMC5 cartridge with 16KB of PRG RAM has it split across two 8KB chips
31            // Bank bit 2 is used as a chip select; other bank bits do nothing
32            let bank: u8 = bank_number.bit(2).into();
33            return CpuMapResult::PrgRAM(BankSizeKb::Eight.to_absolute_address(bank, address));
34        }
35
36        let masked_bank_number = if is_rom { bank_number & 0x7F } else { bank_number & 0x0F };
37
38        // All bank numbers are treated as 8KB banks while selectively ignoring lower bits
39        let shifted_bank_number = match bank_size {
40            BankSizeKb::Eight => masked_bank_number,
41            BankSizeKb::Sixteen => masked_bank_number >> 1,
42            BankSizeKb::ThirtyTwo => masked_bank_number >> 2,
43            _ => panic!("MMC5 mapper should only have 8KB/16KB/32KB bank size, was {bank_size:?}"),
44        };
45
46        let mapped_address = bank_size.to_absolute_address(shifted_bank_number, address);
47        if is_rom {
48            CpuMapResult::PrgROM(mapped_address)
49        } else {
50            CpuMapResult::PrgRAM(mapped_address)
51        }
52    }
53
54    fn map_prg_address(
55        self,
56        prg_bank_registers: [u8; 5],
57        address: u16,
58        prg_ram_len: u32,
59    ) -> CpuMapResult {
60        match address {
61            0x0000..=0x5FFF => panic!("invalid MMC5 PRG map address: {address:04X}"),
62            0x6000..=0x7FFF => {
63                Self::map_result(prg_bank_registers[0], BankSizeKb::Eight, address, prg_ram_len)
64            }
65            0x8000..=0xFFFF => {
66                let (bank_register, bank_size) = match self {
67                    // 1x32KB
68                    Self::Mode0 => (4, BankSizeKb::ThirtyTwo),
69                    // 2x16KB
70                    Self::Mode1 => {
71                        let bank_register = match address {
72                            0x8000..=0xBFFF => 2,
73                            0xC000..=0xFFFF => 4,
74                            0x0000..=0x7FFF => unreachable!("nested match expressions"),
75                        };
76                        (bank_register, BankSizeKb::Sixteen)
77                    }
78                    // 1x16KB + 2x8KB
79                    Self::Mode2 => match address {
80                        0x8000..=0xBFFF => (2, BankSizeKb::Sixteen),
81                        0xC000..=0xDFFF => (3, BankSizeKb::Eight),
82                        0xE000..=0xFFFF => (4, BankSizeKb::Eight),
83                        0x0000..=0x7FFF => unreachable!("nested match expressions"),
84                    },
85                    // 4x8KB
86                    Self::Mode3 => {
87                        // 0x8000..=0x9FFF to bank 1
88                        // 0xA000..=0xBFFF to bank 2
89                        // 0xC000..=0xDFFF to bank 3
90                        // 0xE000..=0xFFFF to bank 4
91                        let bank_register = (address & 0x7FFF) / 0x2000 + 1;
92                        (bank_register, BankSizeKb::Eight)
93                    }
94                };
95
96                Self::map_result(
97                    prg_bank_registers[bank_register as usize],
98                    bank_size,
99                    address,
100                    prg_ram_len,
101                )
102            }
103        }
104    }
105}
106
107#[derive(Debug, Clone, Encode, Decode)]
108struct ChrMapper {
109    bank_size: BankSizeKb,
110    bank_registers: [u8; 12],
111    double_height_sprites: bool,
112    last_register_written: usize,
113    next_access_from_ppu_data: bool,
114}
115
116impl ChrMapper {
117    fn new() -> Self {
118        Self {
119            bank_size: BankSizeKb::Eight,
120            bank_registers: [0; 12],
121            double_height_sprites: false,
122            last_register_written: 0,
123            next_access_from_ppu_data: false,
124        }
125    }
126
127    fn map_sprite_chr_address(&self, address: u16) -> u32 {
128        assert!((0x0000..=0x1FFF).contains(&address));
129
130        match self.bank_size {
131            BankSizeKb::Eight => {
132                BankSizeKb::Eight.to_absolute_address(self.bank_registers[7], address)
133            }
134            BankSizeKb::Four => {
135                // 0x0000..=0x0FFF to bank 3
136                // 0x1000..=0x1FFF to bank 7
137                let bank_register = 4 * (address / 0x1000) + 3;
138                BankSizeKb::Four
139                    .to_absolute_address(self.bank_registers[bank_register as usize], address)
140            }
141            BankSizeKb::Two => {
142                // 0x0000..=0x07FF to bank 1
143                // 0x0800..=0x0FFF to bank 3
144                // 0x1000..=0x17FF to bank 5
145                // 0x1800..=0x1FFF to bank 7
146                let bank_register = 2 * (address / 0x0800) + 1;
147                BankSizeKb::Two
148                    .to_absolute_address(self.bank_registers[bank_register as usize], address)
149            }
150            BankSizeKb::One => {
151                // 0x0000..=0x03FF to bank 0
152                // 0x0400..=0x07FF to bank 1
153                // 0x0800..=0x0BFF to bank 2
154                // 0x0C00..=0x0FFF to bank 3
155                // 0x1000..=0x13FF to bank 4
156                // 0x1400..=0x17FF to bank 5
157                // 0x1800..=0x1BFF to bank 6
158                // 0x1C00..=0x1FFF to bank 7
159                let bank_register = address / 0x0400;
160                BankSizeKb::One
161                    .to_absolute_address(self.bank_registers[bank_register as usize], address)
162            }
163            _ => panic!("MMC5 bank size should always be 1/2/4/8"),
164        }
165    }
166
167    fn map_bg_chr_address(&self, address: u16) -> u32 {
168        assert!((0x0000..=0x1FFF).contains(&address));
169
170        match self.bank_size {
171            BankSizeKb::Eight => {
172                BankSizeKb::Eight.to_absolute_address(self.bank_registers[11], address)
173            }
174            BankSizeKb::Four => {
175                BankSizeKb::Four.to_absolute_address(self.bank_registers[11], address)
176            }
177            BankSizeKb::Two => {
178                // 0x0000..=0x07FF and 0x1000..=0x17FF to bank 9
179                // 0x0800..=0x0FFF and 0x1800..=0x1FFF to bank 11
180                let bank_register = 2 * ((address & 0x0FFF) / 0x0800) + 9;
181                BankSizeKb::Two
182                    .to_absolute_address(self.bank_registers[bank_register as usize], address)
183            }
184            BankSizeKb::One => {
185                // 0x0000..=0x03FF and 0x1000..=0x13FF to bank 8
186                // 0x0400..=0x07FF and 0x1400..=0x17FF to bank 9
187                // 0x0800..=0x0BFF and 0x1800..=0x1BFF to bank 10
188                // 0x0C00..=0x0FFF and 0x1C00..=0x1FFF to bank 11
189                let bank_register = (address & 0x0FFF) / 0x0400 + 8;
190                BankSizeKb::One
191                    .to_absolute_address(self.bank_registers[bank_register as usize], address)
192            }
193            _ => panic!("MMC5 CHR bank size should always be 1/2/4/8"),
194        }
195    }
196
197    fn map_chr_address(&mut self, address: u16, tile_type: TileType) -> u32 {
198        assert!((0x0000..=0x1FFF).contains(&address));
199
200        if self.next_access_from_ppu_data {
201            self.next_access_from_ppu_data = false;
202
203            if self.last_register_written < 8 {
204                self.map_sprite_chr_address(address)
205            } else {
206                self.map_bg_chr_address(address)
207            }
208        } else if self.double_height_sprites && tile_type == TileType::Background {
209            self.map_bg_chr_address(address)
210        } else {
211            self.map_sprite_chr_address(address)
212        }
213    }
214
215    fn process_ppu_ctrl_update(&mut self, ppu_ctrl_value: u8) {
216        self.double_height_sprites = ppu_ctrl_value.bit(5);
217        log::trace!("Double height sprites update detected: {}", self.double_height_sprites);
218    }
219
220    fn process_bank_register_update(&mut self, address: u16, value: u8) {
221        assert!((0x5120..=0x512B).contains(&address));
222
223        let register_index = (address - 0x5120) as usize;
224        self.bank_registers[register_index] = value;
225        self.last_register_written = register_index;
226    }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
230enum ExtendedRamMode {
231    Nametable,
232    NametableExtendedAttributes,
233    ReadWrite,
234    ReadOnly,
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
238enum NametableMapping {
239    VramPage0,
240    VramPage1,
241    ExtendedRam,
242    FillMode,
243}
244
245impl NametableMapping {
246    fn from_bits(bits: u8) -> Self {
247        match bits {
248            0x00 => Self::VramPage0,
249            0x01 => Self::VramPage1,
250            0x02 => Self::ExtendedRam,
251            0x03 => Self::FillMode,
252            _ => panic!("invalid nametable mapping bits: {bits:02X}"),
253        }
254    }
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
258enum VerticalSplitMode {
259    Left,
260    Right,
261}
262
263#[derive(Debug, Clone, Encode, Decode)]
264struct VerticalSplit {
265    enabled: bool,
266    mode: VerticalSplitMode,
267    split_tile_index: u8,
268    y_scroll: u8,
269    chr_bank: u8,
270}
271
272impl VerticalSplit {
273    fn new() -> Self {
274        Self {
275            enabled: false,
276            mode: VerticalSplitMode::Left,
277            split_tile_index: 0,
278            y_scroll: 0,
279            chr_bank: 0,
280        }
281    }
282
283    fn inside_split(&self, scanline_counter: &ScanlineCounter) -> bool {
284        if !self.enabled {
285            return false;
286        }
287
288        match self.mode {
289            VerticalSplitMode::Left => {
290                scanline_counter.current_tile_index() < self.split_tile_index
291            }
292            VerticalSplitMode::Right => {
293                scanline_counter.current_tile_index() >= self.split_tile_index
294            }
295        }
296    }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
300enum TileType {
301    Background,
302    Sprite,
303}
304
305#[derive(Debug, Clone, Encode, Decode)]
306struct ScanlineCounter {
307    scanline: u8,
308    scanline_tile_byte_fetches: u8,
309    compare_value: u8,
310    irq_enabled: bool,
311    irq_pending: bool,
312    in_frame: bool,
313    last_nametable_address: u16,
314    same_nametable_addr_fetch_count: u8,
315    cpu_ticks_no_read: u32,
316}
317
318impl ScanlineCounter {
319    fn new() -> Self {
320        Self {
321            scanline: 0,
322            scanline_tile_byte_fetches: 0,
323            compare_value: 0,
324            irq_enabled: false,
325            irq_pending: false,
326            in_frame: false,
327            last_nametable_address: 0,
328            same_nametable_addr_fetch_count: 0,
329            cpu_ticks_no_read: 0,
330        }
331    }
332
333    // Should be called immediately before a PPU memory access; checks if the scanline has changed
334    fn pre_fetch(&mut self) {
335        if self.same_nametable_addr_fetch_count == 3 {
336            self.same_nametable_addr_fetch_count = 0;
337
338            if self.in_frame {
339                log::trace!(
340                    "Detected new scanline; nametable address = {:04X}",
341                    self.last_nametable_address
342                );
343
344                self.scanline += 1;
345                // Reset the tile byte fetch counter on every scanline in case the game disabled
346                // and re-enabled rendering mid-scanline
347                self.scanline_tile_byte_fetches = 4;
348
349                if self.scanline == 241 {
350                    log::trace!("Reached VBlank scanline, resetting state");
351                    self.scanline = 0;
352                    self.irq_pending = false;
353                    self.in_frame = false;
354                } else if self.compare_value != 0 && self.scanline == self.compare_value {
355                    log::trace!("Setting IRQ pending flag");
356                    self.irq_pending = true;
357                }
358            } else {
359                log::trace!("Detected new frame");
360                self.scanline = 0;
361                self.in_frame = true;
362            }
363        }
364    }
365
366    // This should be called *after* mapping the tile address in case the increment changes the
367    // current tile type
368    fn increment_tile_bytes_fetched(&mut self) {
369        log::trace!("Tile byte fetched, current fetches={}", self.scanline_tile_byte_fetches);
370
371        self.cpu_ticks_no_read = 0;
372
373        self.scanline_tile_byte_fetches += 1;
374
375        // 68 BG tile bytes + 8 sprite tiles * 2 pattern table bytes per tile
376        if self.scanline_tile_byte_fetches == 68 + 16 {
377            self.scanline_tile_byte_fetches = 0;
378        }
379    }
380
381    fn nametable_address_fetched(&mut self, address: u16) {
382        assert!((0x2000..=0x2FFF).contains(&address));
383
384        self.cpu_ticks_no_read = 0;
385
386        if self.last_nametable_address == address && self.same_nametable_addr_fetch_count < 3 {
387            self.same_nametable_addr_fetch_count += 1;
388        } else if self.last_nametable_address != address {
389            self.last_nametable_address = address;
390            self.same_nametable_addr_fetch_count = 1;
391        }
392    }
393
394    fn nmi_vector_fetched(&mut self) {
395        self.scanline = 0;
396        self.irq_pending = false;
397        self.in_frame = false;
398    }
399
400    fn current_tile_type(&self) -> TileType {
401        // 34 BG tiles * 2 pattern table bytes per tile
402        let tile_type = if self.scanline_tile_byte_fetches < 68 {
403            TileType::Background
404        } else {
405            TileType::Sprite
406        };
407
408        log::trace!("current tile type is {tile_type:?}");
409
410        tile_type
411    }
412
413    fn current_tile_index(&self) -> u8 {
414        self.scanline_tile_byte_fetches / 2
415    }
416
417    fn interrupt_flag(&self) -> bool {
418        self.irq_enabled && self.irq_pending
419    }
420
421    fn tick_cpu(&mut self) {
422        if self.cpu_ticks_no_read == 3 {
423            log::trace!("Went 3 CPU cycles with no PPU reads, clearing in frame flag");
424            self.in_frame = false;
425            // Set to 4 so that the counter increments correctly starting from the pre-render scanline
426            // 2 tiles * 2 bytes per tile
427            self.scanline_tile_byte_fetches = 4;
428        }
429
430        self.cpu_ticks_no_read += 1;
431    }
432}
433
434#[derive(Debug, Clone, Encode, Decode)]
435struct ExtendedAttributesState {
436    last_nametable_addr: u16,
437}
438
439impl ExtendedAttributesState {
440    fn new() -> Self {
441        Self { last_nametable_addr: 0 }
442    }
443
444    fn get_attribute_byte(&self, extended_ram: &[u8; 1024]) -> u8 {
445        let extended_attributes = extended_ram[(self.last_nametable_addr & 0x03FF) as usize];
446        let palette_index = extended_attributes >> 6;
447        (palette_index << 6) | (palette_index << 4) | (palette_index << 2) | palette_index
448    }
449
450    fn get_pattern_table_byte(
451        &self,
452        pattern_table_addr: u16,
453        extended_ram: &[u8; 1024],
454        cartridge: &Cartridge,
455    ) -> u8 {
456        let extended_attributes = extended_ram[(self.last_nametable_addr & 0x03FF) as usize];
457        let chr_4kb_bank = extended_attributes & 0x3F;
458        let chr_addr = BankSizeKb::Four.to_absolute_address(chr_4kb_bank, pattern_table_addr);
459        cartridge.get_chr_rom(chr_addr)
460    }
461}
462
463#[derive(Debug, Clone, Copy, Encode, Decode)]
464struct MultiplierUnit {
465    operand_l: u16,
466    operand_r: u16,
467}
468
469impl MultiplierUnit {
470    fn new() -> Self {
471        Self { operand_l: 0xFF, operand_r: 0xFF }
472    }
473
474    fn output(self) -> u16 {
475        self.operand_l * self.operand_r
476    }
477}
478
479#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
480enum PcmMode {
481    Read,
482    Write,
483}
484
485impl PcmMode {
486    fn bit(self) -> u8 {
487        match self {
488            Self::Read => 0x01,
489            Self::Write => 0x00,
490        }
491    }
492}
493
494#[derive(Debug, Clone, Encode, Decode)]
495struct PcmChannel {
496    output_level: u8,
497    mode: PcmMode,
498    irq_enabled: bool,
499    irq_pending: bool,
500}
501
502impl PcmChannel {
503    fn new() -> Self {
504        Self { output_level: 0, mode: PcmMode::Write, irq_enabled: false, irq_pending: false }
505    }
506
507    fn process_control_update(&mut self, value: u8) {
508        self.mode = if value.bit(0) { PcmMode::Read } else { PcmMode::Write };
509        self.irq_enabled = value.bit(7);
510
511        if !self.irq_enabled {
512            self.irq_pending = false;
513        }
514    }
515
516    fn read_control(&mut self) -> u8 {
517        let control = (u8::from(self.irq_pending) << 7) | self.mode.bit();
518        self.irq_pending = false;
519        control
520    }
521
522    fn process_cpu_read(&mut self, address: u16, value: u8) {
523        if self.mode == PcmMode::Read && (0x8000..=0xBFFF).contains(&address) {
524            if value != 0 {
525                self.output_level = value;
526            } else if self.irq_enabled {
527                self.irq_pending = true;
528            }
529        }
530    }
531
532    fn process_raw_pcm_update(&mut self, value: u8) {
533        if self.mode == PcmMode::Write {
534            if value != 0 {
535                self.output_level = value;
536            } else if self.irq_enabled {
537                self.irq_pending = true;
538            }
539        }
540    }
541}
542
543#[derive(Debug, Clone, Encode, Decode)]
544pub(crate) struct Mmc5 {
545    extended_ram: [u8; 1024],
546    extended_ram_mode: ExtendedRamMode,
547    prg_banking_mode: PrgBankingMode,
548    prg_bank_registers: [u8; 5],
549    chr_mapper: ChrMapper,
550    nametable_mappings: [NametableMapping; 4],
551    fill_mode_tile_data: u8,
552    fill_mode_attributes: u8,
553    vertical_split: VerticalSplit,
554    scanline_counter: ScanlineCounter,
555    extended_attributes_state: ExtendedAttributesState,
556    multiplier: MultiplierUnit,
557    pulse_channel_1: PulseChannel,
558    pulse_channel_2: PulseChannel,
559    pcm_channel: PcmChannel,
560    frame_counter: FrameCounter,
561    ram_writes_enabled_1: bool,
562    ram_writes_enabled_2: bool,
563    rendering_enabled: bool,
564    ppu_open_bus: u8,
565}
566
567impl Mmc5 {
568    pub(crate) fn new() -> Self {
569        Self {
570            extended_ram: [0; 1024],
571            extended_ram_mode: ExtendedRamMode::ReadOnly,
572            prg_banking_mode: PrgBankingMode::Mode3,
573            prg_bank_registers: [0xFF; 5],
574            chr_mapper: ChrMapper::new(),
575            nametable_mappings: [NametableMapping::VramPage0; 4],
576            fill_mode_tile_data: 0,
577            fill_mode_attributes: 0,
578            vertical_split: VerticalSplit::new(),
579            scanline_counter: ScanlineCounter::new(),
580            extended_attributes_state: ExtendedAttributesState::new(),
581            multiplier: MultiplierUnit::new(),
582            pulse_channel_1: PulseChannel::new_channel_1(SweepStatus::Disabled),
583            pulse_channel_2: PulseChannel::new_channel_2(SweepStatus::Disabled),
584            pcm_channel: PcmChannel::new(),
585            frame_counter: FrameCounter::new(TimingMode::Ntsc),
586            ram_writes_enabled_1: false,
587            ram_writes_enabled_2: false,
588            rendering_enabled: false,
589            ppu_open_bus: 0,
590        }
591    }
592}
593
594impl MapperImpl<Mmc5> {
595    pub(crate) fn process_ppu_ctrl_update(&mut self, value: u8) {
596        self.data.chr_mapper.process_ppu_ctrl_update(value);
597    }
598
599    pub(crate) fn process_ppu_mask_update(&mut self, value: u8) {
600        self.data.rendering_enabled = value & 0x18 != 0;
601    }
602
603    pub(crate) fn about_to_access_ppu_data(&mut self) {
604        self.data.chr_mapper.next_access_from_ppu_data = true;
605    }
606
607    fn read_internal_register(&mut self, address: u16, cpu_open_bus: u8) -> u8 {
608        match address {
609            0x5010 => self.data.pcm_channel.read_control(),
610            0x5015 => {
611                (u8::from(self.data.pulse_channel_2.length_counter() != 0) << 1)
612                    | u8::from(self.data.pulse_channel_1.length_counter() != 0)
613            }
614            0x5204 => {
615                log::trace!("Scanline IRQ status register read, clearing IRQ pending flag");
616
617                let result = (u8::from(self.data.scanline_counter.irq_pending) << 7)
618                    | (u8::from(self.data.scanline_counter.in_frame) << 6);
619                self.data.scanline_counter.irq_pending = false;
620                result
621            }
622            0x5205 => (self.data.multiplier.output() & 0x00FF) as u8,
623            0x5206 => (self.data.multiplier.output() >> 8) as u8,
624            _ => cpu_open_bus,
625        }
626    }
627
628    fn write_internal_register(&mut self, address: u16, value: u8) {
629        match address {
630            0x5000 => {
631                self.data.pulse_channel_1.process_vol_update(value);
632            }
633            0x5002 => {
634                self.data.pulse_channel_1.process_lo_update(value);
635            }
636            0x5003 => {
637                self.data.pulse_channel_1.process_hi_update(value);
638            }
639            0x5004 => {
640                self.data.pulse_channel_2.process_vol_update(value);
641            }
642            0x5006 => {
643                self.data.pulse_channel_2.process_lo_update(value);
644            }
645            0x5007 => {
646                self.data.pulse_channel_2.process_hi_update(value);
647            }
648            0x5010 => {
649                self.data.pcm_channel.process_control_update(value);
650            }
651            0x5011 => {
652                self.data.pcm_channel.process_raw_pcm_update(value);
653            }
654            0x5015 => {
655                self.data.pulse_channel_1.process_snd_chn_update(value);
656                self.data.pulse_channel_2.process_snd_chn_update(value);
657            }
658            0x5100 => {
659                self.data.prg_banking_mode = match value & 0x03 {
660                    0x00 => PrgBankingMode::Mode0,
661                    0x01 => PrgBankingMode::Mode1,
662                    0x02 => PrgBankingMode::Mode2,
663                    0x03 => PrgBankingMode::Mode3,
664                    _ => unreachable!("value & 0x03 should always be 0x00/0x01/0x02/0x03"),
665                };
666                log::trace!("PRG banking mode set to {:?}", self.data.prg_banking_mode);
667            }
668            0x5101 => {
669                self.data.chr_mapper.bank_size = match value & 0x03 {
670                    0x00 => BankSizeKb::Eight,
671                    0x01 => BankSizeKb::Four,
672                    0x02 => BankSizeKb::Two,
673                    0x03 => BankSizeKb::One,
674                    _ => unreachable!("value & 0x03 should always be 0x00/0x01/0x02/0x03"),
675                };
676                log::trace!("CHR bank size set to {:?}", self.data.chr_mapper.bank_size);
677            }
678            0x5102 => {
679                self.data.ram_writes_enabled_1 = value & 0x03 == 0x02;
680            }
681            0x5103 => {
682                self.data.ram_writes_enabled_2 = value & 0x03 == 0x01;
683            }
684            0x5104 => {
685                self.data.extended_ram_mode = match value & 0x03 {
686                    0x00 => ExtendedRamMode::Nametable,
687                    0x01 => ExtendedRamMode::NametableExtendedAttributes,
688                    0x02 => ExtendedRamMode::ReadWrite,
689                    0x03 => ExtendedRamMode::ReadOnly,
690                    _ => unreachable!("value & 0x03 should be 0x00/0x01/0x02/0x03"),
691                };
692                log::trace!("Extended RAM mode set to {:?}", self.data.extended_ram_mode);
693            }
694            0x5105 => {
695                self.data.nametable_mappings[0] = NametableMapping::from_bits(value & 0x03);
696                self.data.nametable_mappings[1] = NametableMapping::from_bits((value >> 2) & 0x03);
697                self.data.nametable_mappings[2] = NametableMapping::from_bits((value >> 4) & 0x03);
698                self.data.nametable_mappings[3] = NametableMapping::from_bits((value >> 6) & 0x03);
699                log::trace!("Nametable mappings set to {:?}", self.data.nametable_mappings);
700            }
701            0x5106 => {
702                self.data.fill_mode_tile_data = value;
703                log::trace!("Fill mode tile set to {value:02X}");
704            }
705            0x5107 => {
706                let palette_index = value & 0x03;
707                self.data.fill_mode_attributes = palette_index
708                    | (palette_index << 2)
709                    | (palette_index << 4)
710                    | (palette_index << 6);
711                log::trace!("Fill mode palette index set to {value:02X}");
712            }
713            0x5113..=0x5117 => {
714                let bank = match address {
715                    0x5113 => {
716                        // Bank 0 can only ever map to PRG RAM
717                        value & 0x7F
718                    }
719                    0x5117 => {
720                        // Bank 4 can only ever map to PRG ROM
721                        value | 0x80
722                    }
723                    _ => {
724                        // Banks 1-3 can map to PRG ROM or RAM, depending on bit 7
725                        value
726                    }
727                };
728
729                self.data.prg_bank_registers[(address - 0x5113) as usize] = bank;
730                log::trace!("PRG bank {:02X} set to {value:02X}", address - 0x5113);
731            }
732            0x5120..=0x512B => {
733                self.data.chr_mapper.process_bank_register_update(address, value);
734                log::trace!("CHR bank {:02X} set to {value:02X}", address - 0x5120);
735            }
736            0x5200 => {
737                self.data.vertical_split.enabled = value.bit(7);
738                self.data.vertical_split.mode =
739                    if value.bit(6) { VerticalSplitMode::Right } else { VerticalSplitMode::Left };
740                self.data.vertical_split.split_tile_index = value & 0x1F;
741                log::trace!(
742                    "Vertical split enabled/mode/index set: {:?}",
743                    self.data.vertical_split
744                );
745            }
746            0x5201 => {
747                self.data.vertical_split.y_scroll = value;
748                log::trace!("Vertical split Y scroll set to {value}");
749            }
750            0x5202 => {
751                self.data.vertical_split.chr_bank = value;
752                log::trace!("Vertical split CHR bank set to {value:02X}");
753            }
754            0x5203 => {
755                self.data.scanline_counter.compare_value = value;
756                log::trace!("Scanline counter compare value set to {value}");
757            }
758            0x5204 => {
759                self.data.scanline_counter.irq_enabled = value.bit(7);
760                log::trace!(
761                    "Scanline IRQ enabled set to {}",
762                    self.data.scanline_counter.irq_enabled
763                );
764            }
765            0x5205 => {
766                self.data.multiplier.operand_l = value.into();
767            }
768            0x5206 => {
769                self.data.multiplier.operand_r = value.into();
770            }
771            _ => {}
772        }
773    }
774
775    pub(crate) fn read_cpu_address(&mut self, address: u16, cpu_open_bus: u8) -> u8 {
776        if address == 0xFFFA || address == 0xFFFB {
777            self.data.scanline_counter.nmi_vector_fetched();
778        }
779
780        match address {
781            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
782            0x4020..=0x4FFF => cpu_open_bus,
783            0x5000..=0x5BFF => self.read_internal_register(address, cpu_open_bus),
784            0x5C00..=0x5FFF => match self.data.extended_ram_mode {
785                ExtendedRamMode::ReadWrite | ExtendedRamMode::ReadOnly => {
786                    self.data.extended_ram[(address - 0x5C00) as usize]
787                }
788                ExtendedRamMode::Nametable | ExtendedRamMode::NametableExtendedAttributes => {
789                    cpu_open_bus
790                }
791            },
792            0x6000..=0xFFFF => {
793                let value = self
794                    .data
795                    .prg_banking_mode
796                    .map_prg_address(
797                        self.data.prg_bank_registers,
798                        address,
799                        self.cartridge.prg_ram.len() as u32,
800                    )
801                    .read(&self.cartridge)
802                    .unwrap_or(cpu_open_bus);
803
804                self.data.pcm_channel.process_cpu_read(address, value);
805
806                value
807            }
808        }
809    }
810
811    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
812        match address {
813            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
814            0x4020..=0x4FFF => {}
815            0x5000..=0x5BFF => {
816                self.write_internal_register(address, value);
817            }
818            0x5C00..=0x5FFF => {
819                if self.data.extended_ram_mode != ExtendedRamMode::ReadOnly {
820                    self.data.extended_ram[(address - 0x5C00) as usize] = value;
821                }
822            }
823            0x6000..=0xFFFF => {
824                if self.prg_ram_writes_enabled() {
825                    self.data
826                        .prg_banking_mode
827                        .map_prg_address(
828                            self.data.prg_bank_registers,
829                            address,
830                            self.cartridge.prg_ram.len() as u32,
831                        )
832                        .write(value, &mut self.cartridge);
833                }
834            }
835        }
836    }
837
838    pub(crate) fn read_ppu_address(&mut self, address: u16, vram: &[u8; 2048]) -> u8 {
839        self.data.scanline_counter.pre_fetch();
840
841        let value = match address {
842            0x0000..=0x1FFF => {
843                let tile_type = self.data.scanline_counter.current_tile_type();
844                let pattern_table_byte = if self.data.rendering_enabled
845                    && tile_type == TileType::Background
846                    && self.data.extended_ram_mode == ExtendedRamMode::NametableExtendedAttributes
847                {
848                    self.data.extended_attributes_state.get_pattern_table_byte(
849                        address,
850                        &self.data.extended_ram,
851                        &self.cartridge,
852                    )
853                } else if self.data.rendering_enabled
854                    && tile_type == TileType::Background
855                    && self.data.vertical_split.inside_split(&self.data.scanline_counter)
856                {
857                    let fine_y_scroll = self.data.vertical_split.y_scroll & 0x07;
858                    let pattern_table_addr =
859                        (address & 0xFFF8) | ((address + u16::from(fine_y_scroll)) & 0x07);
860
861                    let chr_addr = BankSizeKb::Four
862                        .to_absolute_address(self.data.vertical_split.chr_bank, pattern_table_addr);
863                    self.cartridge.get_chr_rom(chr_addr)
864                } else {
865                    let chr_addr = self.data.chr_mapper.map_chr_address(address, tile_type);
866                    self.cartridge.get_chr_rom(chr_addr)
867                };
868
869                self.data.scanline_counter.increment_tile_bytes_fetched();
870
871                pattern_table_byte
872            }
873            0x2000..=0x3EFF => {
874                let relative_addr = address & 0x0FFF;
875                let nametable_addr = 0x2000 | relative_addr;
876
877                self.data.scanline_counter.nametable_address_fetched(nametable_addr);
878
879                let tile_type = self.data.scanline_counter.current_tile_type();
880                if tile_type == TileType::Background
881                    && self.data.extended_ram_mode == ExtendedRamMode::NametableExtendedAttributes
882                    && address & 0x03FF >= 0x03C0
883                {
884                    // In extended attributes mode, replace attribute table fetches with lookups
885                    // from extended RAM
886                    return self
887                        .data
888                        .extended_attributes_state
889                        .get_attribute_byte(&self.data.extended_ram);
890                }
891                self.data.extended_attributes_state.last_nametable_addr = address;
892
893                if self.data.vertical_split.inside_split(&self.data.scanline_counter)
894                    && matches!(
895                        self.data.extended_ram_mode,
896                        ExtendedRamMode::Nametable | ExtendedRamMode::NametableExtendedAttributes
897                    )
898                {
899                    // Ignore nametable mapping when inside the vertical split, always read from
900                    // extended RAM
901                    let scanline = self.data.scanline_counter.scanline;
902                    let y_scroll = self.data.vertical_split.y_scroll;
903                    let tile_x_index = self.data.scanline_counter.current_tile_index() & 0x1F;
904                    let tile_y_index = ((u16::from(scanline) + u16::from(y_scroll)) >> 3) % 30;
905
906                    let extended_ram_addr = if relative_addr & 0x03FF < 0x03C0 {
907                        // Nametable lookup
908                        (tile_y_index << 5) | u16::from(tile_x_index)
909                    } else {
910                        // Attribute table lookup
911                        0x03C0 + (((tile_y_index >> 2) << 3) | (u16::from(tile_x_index) >> 2))
912                    };
913
914                    return self.data.extended_ram[extended_ram_addr as usize];
915                }
916
917                let nametable_mapping =
918                    self.data.nametable_mappings[(relative_addr >> 10) as usize];
919                match nametable_mapping {
920                    NametableMapping::VramPage0 => vram[(relative_addr & 0x03FF) as usize],
921                    NametableMapping::VramPage1 => {
922                        vram[(0x0400 | (relative_addr & 0x03FF)) as usize]
923                    }
924                    NametableMapping::ExtendedRam => match self.data.extended_ram_mode {
925                        ExtendedRamMode::Nametable
926                        | ExtendedRamMode::NametableExtendedAttributes => {
927                            self.data.extended_ram[(relative_addr & 0x03FF) as usize]
928                        }
929                        ExtendedRamMode::ReadWrite | ExtendedRamMode::ReadOnly => {
930                            self.data.ppu_open_bus
931                        }
932                    },
933                    NametableMapping::FillMode => {
934                        if relative_addr & 0x03FF < 0x03C0 {
935                            // Nametable fetch
936                            self.data.fill_mode_tile_data
937                        } else {
938                            // Attribute table fetch
939                            self.data.fill_mode_attributes
940                        }
941                    }
942                }
943            }
944            0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"),
945        };
946
947        self.data.ppu_open_bus = value;
948        value
949    }
950
951    pub(crate) fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) {
952        self.data.ppu_open_bus = value;
953
954        match address {
955            0x0000..=0x1FFF => {}
956            0x2000..=0x3EFF => {
957                let relative_addr = address & 0x0FFF;
958
959                let nametable_mapping =
960                    self.data.nametable_mappings[(relative_addr >> 10) as usize];
961                match nametable_mapping {
962                    NametableMapping::VramPage0 => {
963                        vram[(relative_addr & 0x03FF) as usize] = value;
964                    }
965                    NametableMapping::VramPage1 => {
966                        vram[(0x0400 | (relative_addr & 0x03FF)) as usize] = value;
967                    }
968                    NametableMapping::ExtendedRam => {
969                        self.data.extended_ram[(relative_addr & 0x03FF) as usize] = value;
970                    }
971                    NametableMapping::FillMode => {}
972                }
973            }
974            0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"),
975        }
976    }
977
978    fn prg_ram_writes_enabled(&self) -> bool {
979        self.data.ram_writes_enabled_1 && self.data.ram_writes_enabled_2
980    }
981
982    pub(crate) fn interrupt_flag(&self) -> bool {
983        self.data.scanline_counter.interrupt_flag() || self.data.pcm_channel.irq_pending
984    }
985
986    pub(crate) fn tick_cpu(&mut self) {
987        self.data.scanline_counter.tick_cpu();
988
989        self.data.pulse_channel_1.tick_cpu();
990        self.data.pulse_channel_2.tick_cpu();
991        self.data.frame_counter.tick();
992
993        if self.data.frame_counter.generate_quarter_frame_clock() {
994            // MMC5 channels clock both length counter and envelope at 240Hz
995            self.data.pulse_channel_1.clock_quarter_frame();
996            self.data.pulse_channel_1.clock_half_frame();
997
998            self.data.pulse_channel_2.clock_quarter_frame();
999            self.data.pulse_channel_2.clock_half_frame();
1000        }
1001    }
1002
1003    pub(crate) fn sample_audio(&self, mixed_apu_sample: f64) -> f64 {
1004        let pulse1_sample = self.data.pulse_channel_1.sample();
1005        let pulse2_sample = self.data.pulse_channel_2.sample();
1006        let mmc5_pulse_mix = apu::mix_pulse_samples(pulse1_sample, pulse2_sample);
1007
1008        // Partial formula from from https://www.nesdev.org/wiki/APU_Mixer
1009        let pcm_sample = self.data.pcm_channel.output_level;
1010        let scaled_pcm_sample = if pcm_sample != 0 {
1011            159.79 / (1.0 / (f64::from(pcm_sample) / 22638.0) + 100.0)
1012        } else {
1013            0.0
1014        };
1015
1016        mixed_apu_sample - mmc5_pulse_mix - scaled_pcm_sample
1017    }
1018}