diff options
| author | gbc dev <gbc@localhost> | 2026-07-15 00:37:04 +0200 |
|---|---|---|
| committer | gbc dev <gbc@localhost> | 2026-07-15 00:37:04 +0200 |
| commit | 12e67588053160d2f9599a09ef932f9b37b1b873 (patch) | |
| tree | f1db2e3e8fb585bc76610995d2437aeb6ee9e51a /src | |
| parent | control: socket-based debug channel + gbctl CLI driver (diff) | |
| download | sl0pboy-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.c | 16 | ||||
| -rw-r--r-- | src/gb.h | 5 |
2 files changed, 19 insertions, 2 deletions
@@ -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); @@ -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; |
