Skip to content

fix: seqLoadStatus OOB reads/writes for custom SAF sequences (#5706) - #6917

Open
bassdr wants to merge 1 commit into
HarbourMasters:developfrom
bassdr:fix/saf-battle-music
Open

fix: seqLoadStatus OOB reads/writes for custom SAF sequences (#5706)#6917
bassdr wants to merge 1 commit into
HarbourMasters:developfrom
bassdr:fix/saf-battle-music

Conversation

@bassdr

@bassdr bassdr commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Root cause

gAudioContext.seqLoadStatus is a malloc(sequenceMapSize) byte array — exactly sequenceMapSize entries. Custom SAF sequences are assigned IDs in the range [seqListSize, sequenceMapSize + 0xF). When a sequence player is initialized via AudioLoad_SyncInitSeqPlayer, seqPlayer->seqId is set to the resolved custom ID, which can exceed sequenceMapSize.

Three OOB paths were hit every time a custom-BGM battle music track finished:

Call site Direction Effect
AudioSeq_SequencePlayerDisableAudioLoad_SetSeqLoadStatus(seqId, 3) OOB write Corrupts heap memory after seqLoadStatus[]
AudioLoad_SyncLoadSeq: seqLoadStatus[seqId] == 1 OOB read Heap garbage may equal 1 → early NULL return → seq player never initialized
AudioSeqPlayer_UpdateAudioLoad_IsSeqLoadComplete(seqId) OOB read May return false → immediately disables the newly-started player

The write is deterministic (value 3) but the target heap address varies with allocation history, explaining the intermittent behavior: on the first encounter the heap slot is typically not 1; after subsequent audio operations it may be, silently aborting the battle music initialization.

Fix

Symmetric to the fontLoadStatus guards in PR #6916:

  • AudioLoad_IsSeqLoadComplete: return true for seqId >= sequenceMapSize. Custom SAF sequences are backed by the resource manager and are always available; they are not tracked in the async-load status table.
  • AudioLoad_SetSeqLoadStatus: skip the update for out-of-range IDs.
  • AudioLoad_SyncLoadSeq: skip the in-progress check for out-of-range IDs (cannot be "loading" if not in the table).

Test plan

  • Load a complete BGM replacement pack that includes a custom battle music track
  • Enter a dungeon, trigger enemy encounter — battle music should play
  • Back away until battle music stops; re-approach — battle music should play reliably on every subsequent encounter
  • Repeat 5-10 times without failure

Fixes #5706.
Related: #6916 (same class of bug on fontLoadStatus).

🤖 Generated with Claude Code

Build Artifacts

Custom SAF sequence IDs can exceed sequenceMapSize, causing out-of-bounds
reads/writes on the seqLoadStatus byte array (sized exactly sequenceMapSize).

This is the direct cause of intermittent battle music failure with BGM packs
(issue HarbourMasters#5706): on the second encounter, AudioLoad_SyncLoadSeq reads a garbage
value from heap memory past seqLoadStatus[] and may find 1 (loading in
progress), causing it to return NULL early — the sequence player is never
initialized and no battle music plays.

Symmetric fix to the fontLoadStatus guards in PR HarbourMasters#6916:
- AudioLoad_IsSeqLoadComplete: return true for OOB seqIds (custom SAF
  sequences are resource-manager-backed, not in the async-load status table)
- AudioLoad_SetSeqLoadStatus: skip update for seqId >= sequenceMapSize
- AudioLoad_SyncLoadSeq: skip the in-progress check for OOB seqIds

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@serprex
serprex self-requested a review July 13, 2026 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom Audio: Battle music playback issues

1 participant