mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-20 17:59:08 +02:00
SaveState: Fixes from a review of the network modules' savestates
- sceNet kept the current session's "inited" flags across a load, while sceNetAdhoc takes them from the state. A state saved after sceNetApctlInit loaded into a fresh boot had no apctl, and an earlier state loaded mid-session found apctl already inited with no thread to deliver its events. They now come from the state, and sceNetInet's host sockets are closed on load instead of leaking ports. - Adhoc matching contexts aren't saved, but survived a load while their sockets were deleted under their host threads. They're deleted first now, leaving their fake PSP threads (ids from before the load) alone. - sceHttp had no state at all. Its inited flags are saved now, and its objects, which hold host connections, are dropped on load. - The achievements data size from the file was skipped unchecked, which can wrap the pointer on 32-bit, and a MemSlabMap slab count could size a huge allocation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
30cab94840
commit
e3bb5f1ada
10 files changed
+80
-20
No files matched your search
@@ -209,8 +209,10 @@ void MemSlabMap::DoState(PointerWrap &p) {
|
||||
Slab *old = first_;
|
||||
Slab *oldBulk = bulkStorage_;
|
||||
Do(p, count);
|
||||
// Every slab takes up at least one byte.
|
||||
if (count < 1 || !p.CheckRead(count)) {
|
||||
// Every slab takes up at least 30 bytes in the file (even the oldest version: its section
|
||||
// version, fields and tag), so a count from a broken file can't size a huge allocation: each
|
||||
// takes over 150 in memory.
|
||||
if (count < 1 || !p.CheckRead((size_t)count * 30)) {
|
||||
p.SetError(p.ERROR_FAILURE);
|
||||
count = 1;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,16 @@ SOCKET SocketManager::GetHostSocketFromInetSocket(int sock) {
|
||||
return inetSockets_[sock].sock;
|
||||
}
|
||||
|
||||
void SocketManager::CloseAll(SocketState state) {
|
||||
for (auto &sock : inetSockets_) {
|
||||
if (sock.state == state) {
|
||||
closesocket(sock.sock);
|
||||
sock.state = SocketState::Unused;
|
||||
sock.sock = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SocketManager::CloseAll() {
|
||||
for (auto &sock : inetSockets_) {
|
||||
if (sock.state != SocketState::Unused) {
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
SOCKET GetHostSocketFromInetSocket(int sock);
|
||||
bool Close(InetSocket *inetSocket);
|
||||
void CloseAll();
|
||||
// Only the sockets in this state (sceNetInet's, or proAdhoc's).
|
||||
void CloseAll(SocketState state);
|
||||
|
||||
// For debugger
|
||||
const InetSocket *Sockets() {
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "Core/HLE/sceHttp.h"
|
||||
#include "Core/HLE/sceNet.h"
|
||||
#include "Core/Debugger/MemBlockInfo.h"
|
||||
#include "Common/Serialize/Serializer.h"
|
||||
#include "Common/Serialize/SerializeFuncs.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/LogReporting.h"
|
||||
#include "Common/Net/URL.h"
|
||||
@@ -308,6 +310,29 @@ void __HttpShutdown() {
|
||||
httpObjects.clear();
|
||||
}
|
||||
|
||||
void __HttpDoState(PointerWrap &p) {
|
||||
// Older states don't have it.
|
||||
auto s = p.Section("sceHttp", 0, 1);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
// The game remembers whether it initialized the library.
|
||||
Do(p, httpInited);
|
||||
Do(p, httpsInited);
|
||||
Do(p, httpCacheInited);
|
||||
if (p.mode == PointerWrap::MODE_READ) {
|
||||
// Templates, connections and requests hold host connections, which can't be restored. The
|
||||
// game's ids for them now fail as invalid, rather than reach objects from before the load.
|
||||
std::lock_guard<std::mutex> guard(httpLock);
|
||||
for (const auto &it : httpObjects) {
|
||||
if (it->className() == name_HTTPRequest)
|
||||
(static_cast<HTTPRequest *>(it.get()))->abortRequest();
|
||||
}
|
||||
httpObjects.clear();
|
||||
httpObjects.reserve(1);
|
||||
}
|
||||
}
|
||||
|
||||
// id: ID of the template or connection
|
||||
int sceHttpSetResolveRetry(int id, int retryCount) {
|
||||
if (id <= 0 || id > (int)httpObjects.size())
|
||||
|
||||
@@ -287,6 +287,7 @@ public:
|
||||
|
||||
|
||||
void __HttpInit();
|
||||
void __HttpDoState(PointerWrap &p);
|
||||
void __HttpShutdown();
|
||||
|
||||
void Register_sceHttp();
|
||||
@@ -322,6 +322,7 @@ void __KernelDoState(PointerWrap &p)
|
||||
__DmacDoState(p);
|
||||
__UsbCamDoState(p);
|
||||
__NpDrmDoState(p);
|
||||
__HttpDoState(p);
|
||||
|
||||
// IMPORTANT! Add new sections last!
|
||||
}
|
||||
|
||||
+7
-8
@@ -56,6 +56,7 @@
|
||||
#include "Core/HLE/sceNp.h"
|
||||
#include "Core/HLE/sceNp2.h"
|
||||
#include "Core/HLE/sceNetInet.h"
|
||||
#include "Core/HLE/SocketManager.h"
|
||||
#include "Core/HLE/sceNetResolver.h"
|
||||
#include "Core/HLE/NetAdhocCommon.h"
|
||||
|
||||
@@ -712,10 +713,6 @@ void __NetDoState(PointerWrap &p) {
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
auto cur_netInited = g_netInited;
|
||||
auto cur_netInetInited = netInetInited;
|
||||
auto cur_netApctlInited = g_netApctlInited;
|
||||
|
||||
Do(p, g_netInited);
|
||||
Do(p, netInetInited);
|
||||
Do(p, g_netApctlInited);
|
||||
@@ -778,10 +775,12 @@ void __NetDoState(PointerWrap &p) {
|
||||
}
|
||||
|
||||
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
|
||||
g_netApctlInited = cur_netApctlInited;
|
||||
netInetInited = cur_netInetInited;
|
||||
g_netInited = cur_netInited;
|
||||
// The game remembers which libraries it initialized, so the "Inited" flags come from the state
|
||||
// (sceNetAdhoc does the same). Keeping the current ones left a state saved after
|
||||
// sceNetApctlInit, loaded into a fresh boot, without apctl, and an earlier state loaded
|
||||
// mid-session with it already inited but no apctl thread. Host sockets can't be restored:
|
||||
// close them, rather than leak ports the loaded game may bind again.
|
||||
g_socketManager.CloseAll(SocketState::UsedNetInet);
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -333,6 +333,7 @@ void discardMatchingEvents();
|
||||
void DoNetAdhocMatchingInited(PointerWrap &p);
|
||||
void DoNetAdhocMatchingThreads(PointerWrap &p);
|
||||
void ZeroNetAdhocMatchingThreads();
|
||||
void NetAdhocMatching_DeleteAllBeforeLoad();
|
||||
|
||||
bool __NetAdhocConnected() {
|
||||
return netAdhocInited && netAdhocctlInited && (adhocctlState == ADHOCCTL_STATE_CONNECTED || adhocctlState == ADHOCCTL_STATE_GAMEMODE);
|
||||
@@ -1868,6 +1869,12 @@ void __NetAdhocDoState(PointerWrap &p) {
|
||||
auto cur_netAdhocInited = netAdhocInited;
|
||||
auto cur_netAdhocctlInited = netAdhocctlInited;
|
||||
|
||||
if (p.mode == p.MODE_READ) {
|
||||
// Matching contexts aren't saved, and their host threads use sockets deleted below. Before
|
||||
// the matching thread ids are loaded over.
|
||||
NetAdhocMatching_DeleteAllBeforeLoad();
|
||||
}
|
||||
|
||||
Do(p, netAdhocInited);
|
||||
Do(p, netAdhocctlInited);
|
||||
DoNetAdhocMatchingInited(p);
|
||||
|
||||
@@ -1708,24 +1708,33 @@ int sceNetAdhocMatchingInit(u32 memsize) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void DeleteAllMatchingContexts() {
|
||||
SceNetAdhocMatchingContext *next = NULL;
|
||||
SceNetAdhocMatchingContext *context = contexts;
|
||||
while (context != NULL) {
|
||||
next = context->next;
|
||||
NetAdhocMatching_Delete(context->id);
|
||||
context = next;
|
||||
}
|
||||
contexts = NULL;
|
||||
}
|
||||
|
||||
int NetAdhocMatching_Term() {
|
||||
if (netAdhocMatchingInited) {
|
||||
// Delete all Matching contexts
|
||||
SceNetAdhocMatchingContext* next = NULL;
|
||||
SceNetAdhocMatchingContext* context = contexts;
|
||||
while (context != NULL) {
|
||||
next = context->next;
|
||||
//if (context->running) NetAdhocMatching_Stop(context->id);
|
||||
NetAdhocMatching_Delete(context->id);
|
||||
context = next;
|
||||
}
|
||||
contexts = NULL;
|
||||
DeleteAllMatchingContexts();
|
||||
matchingThreads.clear();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NetAdhocMatching_DeleteAllBeforeLoad() {
|
||||
// Their fake PSP threads' ids are from the kernel before the load, and the kernel is the loaded
|
||||
// one now: zeroed, stopping a context leaves the threads alone, and only the host side goes.
|
||||
ZeroNetAdhocMatchingThreads();
|
||||
DeleteAllMatchingContexts();
|
||||
}
|
||||
|
||||
int sceNetAdhocMatchingTerm() {
|
||||
WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingTerm() at %08x", currentMIPS->pc);
|
||||
// Should we cleanup all created matching contexts first? just in case there are games that doesn't delete them before calling this
|
||||
|
||||
@@ -998,6 +998,10 @@ void DoState(PointerWrap &p) {
|
||||
Do(p, data_size);
|
||||
if (p.mode == PointerWrap::MODE_READ) {
|
||||
WARN_LOG(Log::Achievements, "Save state contained achievement data, but achievements are not active. Ignore.");
|
||||
// The size is straight from the file: skipping it unchecked can wrap the pointer on 32-bit.
|
||||
if (!p.CheckRead(data_size)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
p.SkipBytes(data_size);
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user