From 0207c6d04a37a0ba9b93d8cdacac4c6015e25847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 17 Sep 2026 12:51:52 -0600 Subject: [PATCH] Clear the codec context maps per boot, and replace functions in headless too sceMpeg and sceAudiocodec only cleared their context maps on shutdown, while sceMpegbase and sceVideocodec clear theirs on init. Both maps are keyed on an address the game chooses, and getMpegCtx reads its key straight out of game memory, so anything left behind can be handed to the next game we run in the same session. Clear them on init as well. sceVideocodec's init cleared its map without deleting the decoders in it; use ClearContexts for that. Headless never loads a config file, so every g_Config field it doesn't set keeps the zero-initialized value rather than the ConfigSetting default. bFuncReplacements is one of those, so headless was the only build running games without function replacements - which is why a crash in Jak and Daxter's memcpy_jak wouldn't reproduce there. Co-Authored-By: Claude Opus 5 (1M context) --- Core/HLE/sceAudiocodec.cpp | 3 +++ Core/HLE/sceMpeg.cpp | 16 +++++++++++----- Core/HLE/sceVideocodec.cpp | 5 +++-- headless/Headless.cpp | 4 ++++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Core/HLE/sceAudiocodec.cpp b/Core/HLE/sceAudiocodec.cpp index 29b0449b34..cb00a38b5a 100644 --- a/Core/HLE/sceAudiocodec.cpp +++ b/Core/HLE/sceAudiocodec.cpp @@ -210,6 +210,9 @@ static void clearDecoders() { } void __AudioCodecInit() { + // findDecoder keys on a game-chosen context address, so don't leave decoders from a previous + // game around for the next one to find. + clearDecoders(); oldStateLoaded = false; } diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 99c295a757..70e4c065a0 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -348,8 +348,18 @@ private: s32 remainingPackets_ = 0; }; +static void ClearMpegContexts() { + for (const auto &[_, ctx] : g_mpegCtxs) { + delete ctx; + } + g_mpegCtxs.clear(); +} + void __MpegInit() { __MpegBaseInit(); + // getMpegCtx keys on a handle read out of game memory, so don't leave contexts from a previous + // game around for the next one to find. + ClearMpegContexts(); isMpegInit = false; mpegLibVersion = 0x010A; streamIdGen = 1; @@ -403,11 +413,7 @@ void __MpegDoState(PointerWrap &p) { } void __MpegShutdown() { - std::map::iterator it, end; - for (it = g_mpegCtxs.begin(), end = g_mpegCtxs.end(); it != end; ++it) { - delete it->second; - } - g_mpegCtxs.clear(); + ClearMpegContexts(); } void __MpegLoadModule(int version,u32 crc) { diff --git a/Core/HLE/sceVideocodec.cpp b/Core/HLE/sceVideocodec.cpp index b9489ce120..6e74aefeec 100644 --- a/Core/HLE/sceVideocodec.cpp +++ b/Core/HLE/sceVideocodec.cpp @@ -162,8 +162,9 @@ static void ClearContexts(bool freeMemory) { } void __VideocodecInit() { - // Nothing to free: a boot starts with a fresh allocator. - g_videocodecCtxs.clear(); + // The decoders have to be deleted; the ME blocks they hold don't need freeing individually, + // since the allocator is emptied right below. + ClearContexts(false); g_meRam.clear(); g_meRam.shrink_to_fit(); g_meAlloc.Shutdown(); diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 4450558d1d..1f58b1f87e 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -785,6 +785,10 @@ int main(int argc, const char* argv[]) { g_Config.iInternalResolution = cmdLineOptions.resolutionScale.value_or(1); g_Config.bEnableLogging = (fullLog || outputDebugStringLog); g_Config.bVertexDecoderJit = true; + // Headless never loads a config file, so anything not set here keeps the zero-initialized + // value rather than the ConfigSetting default. This one defaults to true in the app, and + // leaving it false made headless run games differently from every other build. + g_Config.bFuncReplacements = true; g_Config.bSoftwareRendering = cmdLineOptions.softwareRendering.value_or(false); g_Config.bSoftwareRenderingJit = true; g_Config.iSplineBezierQuality = 2;