feat(mmc5): implement MMC5 mapper with accurate scanline IRQ and CHR banking
Some checks failed
CI / rust (push) Has been cancelled
Some checks failed
CI / rust (push) Has been cancelled
- Add ExRAM (modes 0-1) and fill-mode nametable routing via read_nametable_byte / write_nametable_byte mapper hooks - Separate sprite and BG CHR bank sets ($5120-$5127 vs $5128-$512B); BG banks are only active in 8x16 sprite mode - Use mapper.ppu_read_sprite() for sprite tile loads so they always use the sprite bank set regardless of PPU fetch phase - Replace CPU-cycle IRQ stub with scanline-based counter matching Mesen2 hardware behaviour: fire when counter == irq_scanline at dot 2 (start of scanline), irq_scanline=0 never fires - Add Mapper::notify_frame_start() called unconditionally at the PPU frame boundary; MMC5 uses it to hard-reset the scanline counter even when rendering is disabled (e.g. during room transitions), preventing stale counter values from shifting the CHR split by 8+ scanlines - Fix CHR bank calculation for modes 0-2: use << 3/2/1 shifts instead of & !7/3/1 masking to correctly convert bank numbers to 1KB indices - Correct $5204 read: bit 7 = IRQ pending (cleared on read), bit 6 = in-frame flag; IRQ line stays asserted until $5204 is read - Dispatch $4020-$5FFF CPU reads/writes to mapper cpu_read_low / cpu_write_low so MMC5 internal registers are accessible
This commit is contained in:
@@ -29,6 +29,7 @@ impl CpuBus for NativeBus {
|
||||
0x4015 => self.apu.read(addr),
|
||||
0x4016 => self.joypad_read(),
|
||||
0x4017 => self.joypad2_read(),
|
||||
0x4020..=0x5FFF => self.mapper.cpu_read_low(addr).unwrap_or(self.cpu_open_bus),
|
||||
0x6000..=0x7FFF => self.mapper.cpu_read_low(addr).unwrap_or(self.cpu_open_bus),
|
||||
0x8000..=0xFFFF => self.mapper.cpu_read(addr),
|
||||
_ => self.cpu_open_bus,
|
||||
@@ -48,6 +49,9 @@ impl CpuBus for NativeBus {
|
||||
let (ppu, mapper) = (&mut self.ppu, &mut self.mapper);
|
||||
ppu.cpu_write(reg, value, &mut **mapper);
|
||||
}
|
||||
if reg == 0 {
|
||||
self.mapper.notify_ppu_ctrl_write(value);
|
||||
}
|
||||
if reg == 0
|
||||
&& !nmi_was_enabled
|
||||
&& self.ppu.nmi_enabled()
|
||||
@@ -77,6 +81,9 @@ impl CpuBus for NativeBus {
|
||||
self.clock_cpu_cycles(513 + cpu_phase);
|
||||
}
|
||||
0x4016 => self.joypad_write(value),
|
||||
0x4020..=0x5FFF => {
|
||||
self.mapper.cpu_write_low(addr, value);
|
||||
}
|
||||
0x6000..=0x7FFF => {
|
||||
self.mapper.cpu_write_low(addr, value);
|
||||
}
|
||||
|
||||
@@ -34,12 +34,25 @@ impl NativeBus {
|
||||
self.ppu_dot = 0;
|
||||
self.frame_complete = true;
|
||||
self.odd_frame = !self.odd_frame;
|
||||
// Unconditional frame-boundary notification: mappers that maintain
|
||||
// per-frame state (e.g. MMC5 scanline IRQ counter) must reset here
|
||||
// regardless of whether rendering is currently enabled.
|
||||
self.mapper.notify_frame_start();
|
||||
}
|
||||
|
||||
let scanline = self.ppu_dot / PPU_DOTS_PER_SCANLINE;
|
||||
let dot = self.ppu_dot % PPU_DOTS_PER_SCANLINE;
|
||||
let rendering_enabled = self.ppu.rendering_enabled();
|
||||
|
||||
// Notify the mapper when PPU transitions between BG and sprite fetch phases.
|
||||
if rendering_enabled && scanline < 240 {
|
||||
if dot == 1 || dot == 321 {
|
||||
self.mapper.notify_ppu_fetch_phase(false);
|
||||
} else if dot == 257 {
|
||||
self.mapper.notify_ppu_fetch_phase(true);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mapper: &(dyn Mapper + Send) = &*self.mapper;
|
||||
self.ppu.render_dot(mapper, scanline, dot);
|
||||
@@ -62,7 +75,7 @@ impl NativeBus {
|
||||
self.mmc3_a12_prev_high = false;
|
||||
self.mmc3_a12_low_dots = self.mmc3_a12_low_dots.saturating_add(1);
|
||||
}
|
||||
} else if dot == 260 {
|
||||
} else if dot == 2 {
|
||||
self.mapper.clock_scanline();
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user