From 9a77ebb74355c841f3b959dab97a7efe9d3cb7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 10 Sep 2026 16:41:21 -0600 Subject: [PATCH] sceAudio: tighten up the rest of the savestate handling A re-read of the whole thing after the reservation bug, looking for anything else it got wrong. The SRC channel's buffer count is read straight out of the state and then used to index a two-element array, so a corrupt one is a write outside the struct. It is range-checked now, the same way the channel count above it already was. A channel holding a buffer with no samples left of it was stuck: the mixer skipped it without ever clearing the address, so it read as busy for ever and the game had no way back to sound. The API cannot produce that - a channel is never reserved for zero samples - but a savestate can claim it, so the mixer now retires such a channel instead of stepping over it. Also took the "get rid of this next time we bump" the version-2 resampler section came with, since this branch is that bump. Nothing was ever in it. Checked by loading states written by a released build for a game that uses the mixer channels and one that uses Output2, plus a round trip of the new format. Every section ends in a marker, so a conversion path that consumed the wrong number of bytes would fail the load rather than quietly corrupt what follows. Co-Authored-By: Claude Opus 5 (1M context) --- Core/HLE/__sceAudio.cpp | 26 ++++++++++++++++---------- Core/HLE/sceAudio.cpp | 7 +++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp index 80838dd54a..763703bb15 100644 --- a/Core/HLE/__sceAudio.cpp +++ b/Core/HLE/__sceAudio.cpp @@ -131,17 +131,16 @@ void __AudioDoState(PointerWrap &p) { mixFrequency = 44100; } - if (s >= 2) { - // TODO: Next time we bump, get rid of this. It's kinda useless. - auto s = p.Section("resampler", 1); - if (p.mode == p.MODE_READ) { - System_AudioClear(); - } - } else { - // Only to preserve the previous file format. Might cause a slight audio glitch on upgrades? + // Version 1 kept the whole mixed output queue here, and version 2 replaced it with an empty + // "resampler" section that never held anything. Version 3 drops that too. Either way the + // contents go in the bin: the backend is cleared and the game refills it within a block. + if (s == 2) { + auto resampler = p.Section("resampler", 1); + } else if (s < 2) { FixedSizeQueue outAudioQueue; outAudioQueue.DoState(p); - + } + if (p.mode == p.MODE_READ) { System_AudioClear(); } @@ -484,9 +483,16 @@ void __AudioSetSRCFrequency(int freq) { // Mixes one block from a mixer channel, reading straight out of the game's buffer. static bool __AudioMixChannel(AudioChannel &chan) { - if (chan.sampleAddress == 0 || chan.remainingSamples == 0) { + if (chan.sampleAddress == 0) { + // Idle, or holding the remaining count from a null-pointer output. return false; } + if (chan.remainingSamples == 0) { + // Can't happen from the API, since a channel is never reserved for zero samples, but a + // savestate could say otherwise - and left alone the channel would read as busy for + // ever, which is silence the game can't recover from. + return __AudioChannelFinished(chan); + } const u32 count = std::min(chan.remainingSamples, (u32)hwBlockSize); const bool mono = chan.format == PSP_AUDIO_FORMAT_MONO; diff --git a/Core/HLE/sceAudio.cpp b/Core/HLE/sceAudio.cpp index 17c88eb684..8c315bc079 100644 --- a/Core/HLE/sceAudio.cpp +++ b/Core/HLE/sceAudio.cpp @@ -136,6 +136,13 @@ void AudioSRCChannel::DoState(PointerWrap &p) { Do(p, rightVolume); Do(p, format); Do(p, bufferCount); + if (bufferCount < 0 || bufferCount > (int)ARRAY_SIZE(buffers)) { + // Guarded because the enqueue indexes buffers[] with this, so a negative one would be + // a write outside the struct. + ERROR_LOG(Log::sceAudio, "Savestate failure: %d buffers on the SRC channel.", bufferCount); + p.SetError(p.ERROR_FAILURE); + return; + } for (AudioPendingBuffer &buf : buffers) { Do(p, buf.address); Do(p, buf.samples);