From 12e67588053160d2f9599a09ef932f9b37b1b873 Mon Sep 17 00:00:00 2001 From: gbc dev Date: Wed, 15 Jul 2026 00:37:04 +0200 Subject: apu: register-level emulation of 0xFF10-0xFF3F (fixes music fades) The APU was a black hole: reads returned 0xFF, writes were dropped. That hangs any game that fades music out by decrementing the NR50/rAUDVOL volume register until it reads 0 (Pokemon's StopMusic spin-wait) - the write never stuck, the read never hit 0, infinite loop. Store the 48 sound registers and return them with the standard hardware read-back OR-masks (NR50 mask 0x00 = exact readback). No synthesis, just correct register behavior. --- src/gb.c | 16 ++++++++++++++-- src/gb.h | 5 +++++ 2 files changed, 19 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/gb.c b/src/gb.c index 9ed2521..afdcb89 100644 --- a/src/gb.c +++ b/src/gb.c @@ -64,7 +64,19 @@ u8 gb_read(GB *gb, u16 addr) { if (addr == 0xFF02) return gb->sc | 0x7E; if (addr >= 0xFF04 && addr <= 0xFF07) return timer_read(gb, addr); if (addr == 0xFF0F) return gb->iff | 0xE0; - if (addr >= 0xFF10 && addr <= 0xFF3F) return 0xFF; // APU (stub) + if (addr >= 0xFF10 && addr <= 0xFF3F) { + // Register-level APU: return stored value OR'd with the bits that read + // back as 1 on real hardware (write-only / unused bits). NR50 (0xFF24) + // has mask 0x00, so it reads back exactly what was written, which is + // what the game's volume fade-out relies on. + static const u8 apu_or[0x30] = { + 0x80,0x3F,0x00,0xFF,0xBF, 0xFF, 0x3F,0x00,0xFF,0xBF, + 0x7F,0xFF,0x9F,0xFF,0xBF, 0xFF, 0xFF,0x00,0x00,0xBF, + 0x00,0x00,0x70, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 /* FF30-FF3F wave RAM: exact */ + }; + return gb->apu[addr - 0xFF10] | apu_or[addr - 0xFF10]; + } if (addr >= 0xFF40 && addr <= 0xFF4B) { if (addr == 0xFF46) return gb->dma; return ppu_read_reg(gb, addr); @@ -114,7 +126,7 @@ void gb_write(GB *gb, u16 addr, u8 val) { } if (addr >= 0xFF04 && addr <= 0xFF07) { timer_write(gb, addr, val); return; } if (addr == 0xFF0F) { gb->iff = val & 0x1F; return; } - if (addr >= 0xFF10 && addr <= 0xFF3F) return; // APU stub + if (addr >= 0xFF10 && addr <= 0xFF3F) { gb->apu[addr - 0xFF10] = val; return; } if (addr >= 0xFF40 && addr <= 0xFF4B) { if (addr == 0xFF46) { oam_dma(gb, val); return; } ppu_write_reg(gb, addr, val); diff --git a/src/gb.h b/src/gb.h index 806daba..7439615 100644 --- a/src/gb.h +++ b/src/gb.h @@ -94,6 +94,11 @@ struct GB { // serial u8 sb, sc; + // APU registers 0xFF10-0xFF3F. We don't synthesize sound, but games read + // these back (e.g. the music fade-out decrements NR50/rAUDVOL until it + // reads 0), so they must behave as real storage, not a 0xFF black hole. + u8 apu[0x30]; + // DMA u8 dma; -- cgit v1.3.1-sl0p