Fix custom music corruption past 256 sequences (#5989) (#6736)

The resolved replacement id (which can exceed 255) rode a single
per-player seqToPlay slot, written at enqueue but consumed
asynchronously on the audio thread; back-to-back starts and
priority-queue promotions clobbered it. sSeqFlags[0x6F] was also indexed
by raw id, reading out of bounds past the authentic range.

- func_800F9280 resolves the replacement and packs the full 16-bit id
  into the 0x82/0x85 play command; the handler reads opArgs & 0xFFFF.
  Audio_QueueSeqCmd no longer pre-writes the shared slot.
- SyncInitSeqPlayerInternal uses the command-carried id and bounds-checks
  it against the calloc'd sequence map (+0xF headroom for reserved-range
  skips).
- Route sSeqFlags reads through a bounded Audio_GetSeqFlags helper.
- Warn and skip gracefully past the 16-bit id limit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
David Racine
2026-06-12 23:36:25 -04:00
committed by GitHub
parent f3413bfd26
commit eb4142835e
4 changed files with 53 additions and 35 deletions
+14 -12
View File
@@ -39,15 +39,26 @@ u8 D_80133418 = 0;
void func_800F9280(u8 playerIdx, u8 seqId, u8 arg2, u16 fadeTimer) {
u8 i;
u16 dur;
u16 resolvedSeqId;
s32 pad;
if (D_80133408 == 0 || playerIdx == SEQ_PLAYER_SFX) {
// Resolve here so the full 16-bit id rides in the command (bits 0-15) rather than the shared
// seqToPlay slot. seqReplaced is set out-of-band by preview/slow load.
// See AudioEditor_GetReplacementSeq().
if (gAudioContext.seqReplaced[playerIdx]) {
resolvedSeqId = gAudioContext.seqToPlay[playerIdx];
gAudioContext.seqReplaced[playerIdx] = 0;
} else {
resolvedSeqId = AudioEditor_GetReplacementSeq(seqId);
}
arg2 &= 0x7F;
if (arg2 == 0x7F) {
dur = (fadeTimer >> 3) * 60 * gAudioContext.audioBufferParameters.updatesPerFrame;
Audio_QueueCmdS32(0x85000000 | _SHIFTL(playerIdx, 16, 8) | _SHIFTL(seqId, 8, 8), dur);
Audio_QueueCmdS32(0x85000000 | _SHIFTL(playerIdx, 16, 8) | (resolvedSeqId & 0xFFFF), dur);
} else {
Audio_QueueCmdS32(0x82000000 | _SHIFTL(playerIdx, 16, 8) | _SHIFTL(seqId, 8, 8),
Audio_QueueCmdS32(0x82000000 | _SHIFTL(playerIdx, 16, 8) | (resolvedSeqId & 0xFFFF),
(fadeTimer * (u16)gAudioContext.audioBufferParameters.updatesPerFrame) / 4);
}
@@ -372,16 +383,7 @@ extern f32 D_80130F24;
extern f32 D_80130F28;
void Audio_QueueSeqCmd(u32 cmd) {
u8 op = cmd >> 28;
if (op == 0 || op == 2 || op == 12) {
u8 seqId = cmd & 0xFF;
u8 playerIdx = GET_PLAYER_IDX(cmd);
u16 newSeqId = AudioEditor_GetReplacementSeq(seqId);
gAudioContext.seqReplaced[playerIdx] = (seqId != newSeqId);
gAudioContext.seqToPlay[playerIdx] = newSeqId;
cmd |= (seqId & 0xFF);
}
// Replacement is resolved per-command in func_800F9280().
sAudioSeqCmds[sSeqCmdWrPos++] = cmd;
}