aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-15 00:37:04 +0200
committergbc dev <gbc@localhost>2026-07-15 00:37:04 +0200
commit12e67588053160d2f9599a09ef932f9b37b1b873 (patch)
treef1db2e3e8fb585bc76610995d2437aeb6ee9e51a /src
parentcontrol: socket-based debug channel + gbctl CLI driver (diff)
downloadsl0pboy-12e67588053160d2f9599a09ef932f9b37b1b873.tar.gz
sl0pboy-12e67588053160d2f9599a09ef932f9b37b1b873.tar.xz
sl0pboy-12e67588053160d2f9599a09ef932f9b37b1b873.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/gb.c16
-rw-r--r--src/gb.h5
2 files changed, 19 insertions, 2 deletions
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;