Savestate: Save state that was missing from several modules

sceMpeg's AVC resource flag, whether the VSH is running, sceReg's handle
counter, sceNet's pending apctl events and product code block, and the
save dialog's copy of the original request (without which the first
Update after a load reloaded the request and lost its results).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-28 11:32:07 -06:00
1 parent 52299a5e17
commit 8663ce2a33
5 files changed
+79 -13

No files matched your search

+15 -2
View File
@@ -1373,8 +1373,9 @@ void PSPSaveDialog::DoState(PointerWrap &p) {
// value is never SAVEIO_PENDING. Without this, loading a state taken
// while a savedata operation was in flight would restart the operation
// instead of resuming from its recorded status. Version 4 keeps the
// results of a finished operation that haven't been taken yet.
auto s = p.Section("PSPSaveDialog", 1, 5);
// results of a finished operation that haven't been taken yet. Version 6 keeps originalRequest,
// or the first Update after a load would reload the request and lose results kept in request.
auto s = p.Section("PSPSaveDialog", 1, 6);
if (!s) {
return;
}
@@ -1407,6 +1408,18 @@ void PSPSaveDialog::DoState(PointerWrap &p) {
if (s == 4) {
DoStateOldPendingWrites(p);
}
if (s >= 6) {
Do(p, originalRequest);
} else if (p.mode == p.MODE_READ) {
// Best guess: what the game's request holds now, the way Update reads it.
memset(&originalRequest, 0, sizeof(originalRequest));
if (Memory::IsValidRange(requestAddr, 4)) {
const u32 size = std::min((u32)sizeof(originalRequest), Memory::ReadUnchecked_U32(requestAddr));
if (Memory::IsValidRange(requestAddr, size)) {
Memory::Memcpy(&originalRequest, requestAddr, size);
}
}
}
}
pspUtilityDialogCommon *PSPSaveDialog::GetCommonParam() {
+17 -5
View File
@@ -471,6 +471,9 @@ struct SceKernelSMOption {
static int actionAfterModule;
static std::set<SceUID> loadedModules;
// Set once we've seen a PSP_MODULE_VSH_MODE module load (i.e. we're booting the VSH rather
// than a game), and reset on the next __KernelLoadExec. See ShouldHLEModuleForLoad below.
static bool g_runningVSH = false;
// STATE END
//////////////////////////////////////////////////////////////////////////
@@ -479,7 +482,7 @@ static void __KernelModuleInit() {
}
void __KernelModuleDoState(PointerWrap &p) {
auto s = p.Section("sceKernelModule", 1, 2);
auto s = p.Section("sceKernelModule", 1, 3);
if (!s)
return;
@@ -491,6 +494,19 @@ void __KernelModuleDoState(PointerWrap &p) {
if (s >= 2) {
Do(p, loadedModules);
}
if (s >= 3) {
Do(p, g_runningVSH);
} else if (p.mode == p.MODE_READ) {
// Derive it the way the loader sets it.
g_runningVSH = false;
for (SceUID moduleId : loadedModules) {
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (module && ((module->nm.attribute & PSP_MODULE_VSH_MODE) != 0 || equals(module->nm.name, "vsh_module"))) {
g_runningVSH = true;
}
}
}
if (p.mode == p.MODE_READ) {
u32 error;
@@ -1120,10 +1136,6 @@ enum : u32 {
ELF_MAGIC = 0x464c457f,
};
// Set once we've seen a PSP_MODULE_VSH_MODE module load (i.e. we're booting the VSH rather
// than a game), and reset on the next __KernelLoadExec. See ShouldHLEModuleForLoad below.
static bool g_runningVSH = false;
// A few flash0 modules (VSH's own bridge/UI/utility libraries) should only ever be genuinely
// loaded - rather than faked via any HLE implementation we may have for them - once we know
// we're actually running the VSH. A regular game never legitimately loads these, so this only
+18 -1
View File
@@ -364,6 +364,10 @@ void __MpegInit() {
isMpegInit = false;
mpegLibVersion = 0x010A;
streamIdGen = 1;
useRingbufferPutCallbackMulti = true;
sceMpegAvcResourceAddr = 0;
sceMpegAvcResourceDataAddr = 0;
sceMpegAvcResourceFlags = 0;
actionPostPut = __KernelRegisterActionType(PostPutAction::Create);
#ifdef USE_FFMPEG
@@ -377,7 +381,7 @@ void __MpegInit() {
}
void __MpegDoState(PointerWrap &p) {
auto s = p.Section("sceMpeg", 1, 4);
auto s = p.Section("sceMpeg", 1, 5);
if (!s)
return;
@@ -394,6 +398,7 @@ void __MpegDoState(PointerWrap &p) {
useRingbufferPutCallbackMulti = false;
ringbufferPutPacketsAdded = 0;
} else {
useRingbufferPutCallbackMulti = true;
Do(p, ringbufferPutPacketsAdded);
}
if (s < 4) {
@@ -411,6 +416,18 @@ void __MpegDoState(PointerWrap &p) {
__KernelRestoreActionType(actionPostPut, PostPutAction::Create);
Do(p, g_mpegCtxs);
if (s >= 5) {
Do(p, sceMpegAvcResourceFlags);
} else {
sceMpegAvcResourceFlags = 0;
}
if (p.mode == p.MODE_READ) {
// Constant for now, see sceMpegAvcResourceInit.
const bool inited = (sceMpegAvcResourceFlags & MPEG_AVC_RESOURCE_FLAG) != 0;
sceMpegAvcResourceAddr = inited ? 0x10000000 : 0;
sceMpegAvcResourceDataAddr = inited ? sceMpegAvcResourceAddr + 8 : 0;
}
}
void __MpegShutdown() {
+14 -3
View File
@@ -29,6 +29,7 @@
#include "Common/System/OSD.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/Serialize/SerializeDeque.h"
#include "Common/Serialize/SerializeMap.h"
#include "Common/Data/Format/JSONReader.h"
#include "Common/System/System.h"
@@ -707,7 +708,7 @@ void netValidateLoopMemory() {
// This feels like a dubious proposition, mostly...
void __NetDoState(PointerWrap &p) {
auto s = p.Section("sceNet", 1, 6);
auto s = p.Section("sceNet", 1, 7);
if (!s)
return;
@@ -763,6 +764,18 @@ void __NetDoState(PointerWrap &p) {
netApctlInfoId = 0;
NetApctl_InitDefaultInfo();
}
if (s >= 7) {
// The state only moves on when an event is processed, and each queues the next, so a
// connect in progress would never finish without them.
std::lock_guard<std::recursive_mutex> apctlGuard(apctlEvtMtx);
Do(p, apctlEvents);
// Allocated from user memory, which the load just replaced.
Do(p, apctlProdCodeAddr);
} else if (p.mode == p.MODE_READ) {
std::lock_guard<std::recursive_mutex> apctlGuard(apctlEvtMtx);
apctlEvents.clear();
apctlProdCodeAddr = 0;
}
if (p.mode == p.MODE_READ) {
// Let's not change "Inited" value when Loading SaveState in the middle of multiplayer to prevent memory & port leaks
@@ -770,8 +783,6 @@ void __NetDoState(PointerWrap &p) {
netInetInited = cur_netInetInited;
g_netInited = cur_netInited;
// Discard leftover events
apctlEvents.clear();
// Discard created resolvers for now (since i'm not sure whether the information in the struct is sufficient or not, and we don't support multi-threading yet anyway)
__NetResolverShutdown();
}
+15 -2
View File
@@ -1010,9 +1010,13 @@ static const KeyValue *LookupCategory(std::string_view path, int *count) {
}
void __RegDoState(PointerWrap &p) {
auto s = p.Section("sceReg", 0, 2);
if (!s)
auto s = p.Section("sceReg", 0, 3);
if (!s) {
if (p.mode == PointerWrap::MODE_READ) {
__RegInit();
}
return;
}
Do(p, g_openRegistryMode);
Do(p, g_openCategories);
if (s >= 2) {
@@ -1021,6 +1025,15 @@ void __RegDoState(PointerWrap &p) {
// Old states didn't track this. Anything with a category open had the registry open too.
g_openRegistryCount = g_openCategories.empty() ? 0 : 1;
}
if (s >= 3) {
Do(p, g_handleGen);
} else if (p.mode == PointerWrap::MODE_READ) {
// Don't hand out a handle that's still open in the state.
g_handleGen = 1337;
if (!g_openCategories.empty()) {
g_handleGen = std::max(g_handleGen, g_openCategories.rbegin()->first + 1);
}
}
}
// Registry level (it seems only /system can exist, so kinda pointless)