From 97b137eeb8911006d0a685177d1cfe647c058d05 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Wed, 29 Apr 2026 22:27:05 -0700 Subject: [PATCH] pi: bounds-check do_rom_read; zero-fill on out-of-range DMA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audio engine code paths in some games (Pokemon Stadium with the caller-context fragment-vaddr override active) compute wave-bank ROM offsets from corrupted SoundBank fields, causing __amDMA to issue PI DMAs from physical addresses past the cart ROM end. Previously do_rom_read computed `rom.data() + (phys - rom_base)` without checking bounds — any out-of-range physical address read host memory past the ROM buffer, almost always causing an access violation that killed the process. Bounds-check the computed offset and the size against rom.size(). On out-of-range, zero-fill the destination and log the bad DMA. The runner survives, audio gets silence/clicks instead of garbage, and the rate-limited log surfaces the bad addresses for tracing back to the corrupted wave-bank fields. This is a defensive runtime measure, not a stub. The bad DMAs are real bugs upstream (in the recompiled audio code's data flow) — this just keeps the host process alive long enough to diagnose them. Co-Authored-By: Claude Opus 4.7 (1M context) --- librecomp/src/pi.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index 6feaf71..08cd806 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -105,7 +105,32 @@ void recomp::do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr // TODO handle misaligned DMA assert((physical_addr & 0x1) == 0 && "Only PI DMA from aligned ROM addresses is currently supported"); assert((ram_address & 0x7) == 0 && "Only PI DMA to aligned RDRAM addresses is currently supported"); - uint8_t* rom_addr = rom.data() + physical_addr - recomp::rom_base; + + // Bounds check: if physical_addr is past end of ROM (audio engine + // sometimes does this when wave_list[i].base is corrupted), don't + // crash the runner. Log the bad DMA, zero-fill the destination, + // and continue. This is a runtime defensive measure to keep + // diagnostic data flowing — the underlying pointer corruption + // still needs root-cause investigation. + const uint32_t rom_off = physical_addr - recomp::rom_base; + if (rom_off >= rom.size() || rom_off + num_bytes > rom.size()) { + static int s_logged = 0; + if (s_logged < 32) { + s_logged++; + fprintf(stderr, + "[do_rom_read] OUT-OF-BOUNDS phys=0x%08X off=0x%X size=0x%zX " + "(rom_size=0x%zX) — zero-filling dst=0x%08X\n", + physical_addr, rom_off, num_bytes, rom.size(), + (uint32_t)(int32_t)ram_address); + fflush(stderr); + } + for (size_t i = 0; i < num_bytes; i++) { + MEM_B(i, ram_address) = 0; + } + return; + } + + uint8_t* rom_addr = rom.data() + rom_off; for (size_t i = 0; i < num_bytes; i++) { MEM_B(i, ram_address) = *rom_addr; rom_addr++;