From 84f64a9fdf464d38f0ebb07a3a525aea7cd8601b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 25 Sep 2026 18:36:57 -0600 Subject: [PATCH] Savedata: Don't free stale icon textures after a state load The old icons stayed in PPGe's decimation list with kernel addresses from before the load, and got freed out of whatever the loaded state had there. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/Dialog/SavedataParam.cpp | 26 ++++++++++++++++++++++++++ Core/Util/PPGeDraw.cpp | 6 ++++++ Core/Util/PPGeDraw.h | 2 ++ 3 files changed, 34 insertions(+) diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index f4923fc041..ef0bec2bd9 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -1958,6 +1958,32 @@ void SavedataParam::DoState(PointerWrap &p) { if (!s) return; + if (p.mode == p.MODE_READ) { + // The load replaces kernel memory, so the icons' texture addresses now belong to whatever + // the loaded state allocated there. Drop them without freeing those. + std::vector oldTextures; + for (int i = 0; saveDataList && i < saveDataListCount; i++) { + if (saveDataList[i].texture) { + oldTextures.push_back(saveDataList[i].texture); + saveDataList[i].texture = nullptr; + } + } + if (noSaveIcon) { + oldTextures.push_back(noSaveIcon->texture); + delete noSaveIcon; + noSaveIcon = nullptr; + } + // Entries may share noSaveIcon's image. + std::sort(oldTextures.begin(), oldTextures.end()); + oldTextures.erase(std::unique(oldTextures.begin(), oldTextures.end()), oldTextures.end()); + for (PPGeImage *texture : oldTextures) { + if (texture) { + texture->Forget(); + delete texture; + } + } + } + // pspParam is handled in PSPSaveDialog. Do(p, selectedSave); Do(p, saveDataListCount); diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index e0b39413ea..a9170f4b6a 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -1451,6 +1451,12 @@ void PPGeImage::Free() { } } +void PPGeImage::Forget() { + texture_ = 0; + loadFailed_ = false; + loadedTextures_.erase(std::remove(loadedTextures_.begin(), loadedTextures_.end(), this), loadedTextures_.end()); +} + void PPGeImage::DoState(PointerWrap &p) { auto s = p.Section("PPGeImage", 1, 2); if (!s) diff --git a/Core/Util/PPGeDraw.h b/Core/Util/PPGeDraw.h index ea0ef8e2fe..06f212d858 100644 --- a/Core/Util/PPGeDraw.h +++ b/Core/Util/PPGeDraw.h @@ -122,6 +122,8 @@ public: // Does not normally need to be called (except to force preloading.) bool Load(); void Free(); + // Drops the texture without freeing its memory, which a savestate load has already replaced. + void Forget(); bool IsValid(); void DoState(PointerWrap &p);