From 6cb7c985bdf01bd4aa69510c66ad787032420d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 25 Sep 2026 12:16:54 -0600 Subject: [PATCH 1/4] sceHttp/sceParseUri: Fix the size query and header block, Wipeout Pure's browser works sceUriParse's size query (no parsed-URI or work area) returns 0 on a PSP, not -1, which made Wipeout Pure's embedded browser give up before connecting. And sceHttpGetAllHeader hands out the header block as received, ending with the blank line, NUL-terminated and with the NUL counted; without the blank line the browser never displayed the image the page consists of. Both from the 6.60 firmware modules (libparse_uri.prx, libhttp.prx). Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceHttp.cpp | 6 ++++-- Core/HLE/sceParseUri.cpp | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Core/HLE/sceHttp.cpp b/Core/HLE/sceHttp.cpp index 1ac71962f4..3b14cf176f 100644 --- a/Core/HLE/sceHttp.cpp +++ b/Core/HLE/sceHttp.cpp @@ -143,8 +143,10 @@ int HTTPRequest::getAllResponseHeaders(u32 headerAddrPtr, u32 headerSizePtr) { const char* const delim = "\r\n"; std::ostringstream imploded; std::copy(responseHeaders_.begin(), responseHeaders_.end(), std::ostream_iterator(imploded, delim)); - const std::string& s = httpLine_ + delim + imploded.str(); - u32 sz = (u32)s.size(); + // libhttp.prx (6.60) keeps the header block as received, up to and including the blank line + // that ends it, and sceHttpGetAllHeader hands it out NUL-terminated with the NUL counted in the size. + const std::string s = httpLine_ + delim + imploded.str() + delim; + u32 sz = (u32)s.size() + 1; auto headerAddr = PSPPointer::Create(headerAddrPtr); auto headerSize = PSPPointer::Create(headerSizePtr); diff --git a/Core/HLE/sceParseUri.cpp b/Core/HLE/sceParseUri.cpp index 8f41fb89a7..6642d90773 100644 --- a/Core/HLE/sceParseUri.cpp +++ b/Core/HLE/sceParseUri.cpp @@ -46,15 +46,18 @@ static int sceUriParse(u32 parsedUriAreaAddr, const char* url, u32 workAreaAddr, auto workAreaSz = PSPPointer::Create(workAreaSizeAddr); - // Size returner + // Size query. sceUriParse in libparse_uri.prx (6.60) only measures when either area is missing, + // and succeeds as long as it has somewhere to put the size. if (parsedUriAreaAddr == 0 || workAreaAddr == 0) { + if (workAreaSizeAddr == 0) + return hleLogError(Log::sceNet, 0x804331FE, "no work area and no size pointer"); // Based on JPCSP: The required workArea size is maximum the size of the URL + 7 times the null-byte for string termination. int sz = (int)strlen(url) + 7; if (workAreaSz.IsValid()) { *workAreaSz = sz; workAreaSz.NotifyWrite("UriParse"); } - return hleLogDebug(Log::sceNet, -1, "workAreaSize: %d, %d", sz, workAreaSize); + return hleLogDebug(Log::sceNet, 0, "workAreaSize: %d, %d", sz, workAreaSize); } auto parsedUri = PSPPointer::Create(parsedUriAreaAddr); From be30966079a81b24f14dd3f2caa14cf26e38448c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 25 Sep 2026 12:19:20 -0600 Subject: [PATCH 2/4] sceNet: Update the connection's DNS server when the infra-dns json arrives The apctl info copies the AutoDNS server when the connection gets its IP, which normally happens before netconf has downloaded infra-dns.json, so games that read the primary DNS server afterwards (to do their own lookups) got an empty string. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceNet.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index 6005446bcd..589a6bc1a3 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -378,6 +378,13 @@ bool LoadAutoDNS(std::string_view json) { net::DNSResolveFree(resolved); } } + + // The connection usually gets its IP (and copies the DNS server) before the json has arrived, + // so a game asking for the DNS server afterwards would otherwise get the empty one. + if (netApctlState == PSP_NET_APCTL_STATE_GOT_IP) { + INFO_LOG(Log::sceNet, "Updating the connection's DNS server to %s", g_infraDNSConfig.dns.c_str()); + truncate_cpy(netApctlInfo.primaryDns, sizeof(netApctlInfo.primaryDns), g_infraDNSConfig.dns); + } return true; } From e310d144c91c78404456400a164756b1402e4d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 25 Sep 2026 12:35:41 -0600 Subject: [PATCH 3/4] Headless: Don't end a --debugger-run run when the CPU stops A stop ended the run unless the CPU had been told to break at start, which is only --debugger. So under --debugger-run, pausing from the debugger (or any breakpoint) exited the process. Key it on whether the debugger is on instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- headless/Headless.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 9e6c057c0b..812dfa5203 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -328,6 +328,8 @@ struct AutoTestOptions { // sceMpeg and sceMp4 run the firmware module by default now and fall back to the HLE wherever // none is installed, which must not fail every run on such a machine. int requiredDisableHLE; + // The WebSocket debugger is on (--debugger or --debugger-run), so a stop is its to resume. + bool debugger; }; // Ends a frame of the draw context the way the app does, presenting it. Unpresented frames never @@ -482,7 +484,8 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar gpu->BeginHostFrame(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape)); } } - if (coreState == CORE_STEPPING_CPU && !coreParameter.startBreak) { + // Without a debugger nothing can resume a stop, so it ends the run. + if (coreState == CORE_STEPPING_CPU && !opt.debugger) { break; } bool debugger = false; @@ -728,6 +731,7 @@ int main(int argc, const char* argv[]) { testOptions.printEqualLines = cmdLineOptions.printEqualLines.value_or(false); testOptions.maxScreenshotError = cmdLineOptions.maxScreenshotError.value_or(0.0); testOptions.requiredDisableHLE = cmdLineOptions.disableHLE.value_or(0); + testOptions.debugger = cmdLineOptions.DebuggerPort().has_value(); bool fullLog = cmdLineOptions.enableLogging.value_or(false); const char *stateToLoad = cmdLineOptions.stateToLoad.has_value() ? cmdLineOptions.stateToLoad.value().c_str() : nullptr; From 774ed9a5a9c3abb9315dd8a013dab9ee7981aade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 25 Sep 2026 12:38:30 -0600 Subject: [PATCH 4/4] Logging cleanups in sceHttp/sceHttps --- Core/HLE/sceHttp.cpp | 106 ++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 68 deletions(-) diff --git a/Core/HLE/sceHttp.cpp b/Core/HLE/sceHttp.cpp index 3b14cf176f..cf6dda49db 100644 --- a/Core/HLE/sceHttp.cpp +++ b/Core/HLE/sceHttp.cpp @@ -310,7 +310,6 @@ void __HttpShutdown() { // id: ID of the template or connection int sceHttpSetResolveRetry(int id, int retryCount) { - WARN_LOG(Log::sceNet, "UNTESTED sceHttpSetResolveRetry(%d, %d)", id, retryCount); if (id <= 0 || id > (int)httpObjects.size()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); @@ -319,7 +318,7 @@ int sceHttpSetResolveRetry(int id, int retryCount) { return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); conn->setResolveRetry(retryCount); - return 0; + return hleLogDebug(Log::sceNet, 0); } static int sceHttpInit(int poolSize) { @@ -332,7 +331,7 @@ static int sceHttpInit(int poolSize) { // Reserve at least 1 element to prevent ::begin() from returning null when no element has been added yet httpObjects.reserve(1); httpInited = true; - return 0; + return hleLogDebug(Log::sceNet, 0); } static int sceHttpEnd() { @@ -340,35 +339,32 @@ static int sceHttpEnd() { std::lock_guard guard(httpLock); httpObjects.clear(); httpInited = false; - return 0; + return hleLogDebug(Log::sceNet, 0); } static int sceHttpInitCache(int size) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpInitCache(%d)", size); + WARN_LOG(Log::sceNet, "UNIMPL sceHttpInitCache(%d)", size); httpCacheInited = true; - return 0; + return hleLogDebug(Log::sceNet, 0); } static int sceHttpEndCache() { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpEndCache()"); + WARN_LOG(Log::sceNet, "UNIMPL sceHttpEndCache()"); httpCacheInited = false; - return 0; + return hleLogDebug(Log::sceNet, 0); } static int sceHttpEnableCache(int templateID) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpEnableCache(%d)", templateID); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // FIXME: Can be TemplateID or ConnectionID ? Megaman PoweredUp seems to use both id on sceHttpDisableCache static int sceHttpDisableCache(int templateID) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpDisableCache(%d)", templateID); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static u32 sceHttpGetProxy(u32 id, u32 activateFlagPtr, u32 modePtr, u32 proxyHostPtr, u32 len, u32 proxyPort) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpGetProxy(%d, %x, %x, %x, %d, %x)", id, activateFlagPtr, modePtr, proxyHostPtr, len, proxyPort); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) { @@ -393,7 +389,6 @@ static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) { // Games will repeatedly called sceHttpReadData until it returns (the size read into the data buffer) 0 // FIXME: sceHttpReadData seems to be blocking current thread, since hleDelayResult can make Download progressbar to moves progressively instead of instantly jump to 100% static int sceHttpReadData(int requestID, u32 dataPtr, u32 dataSize) { - WARN_LOG(Log::sceNet, "UNTESTED sceHttpReadData(%d, %x, %d)", requestID, dataPtr, dataSize); if (requestID <= 0 || requestID > (int)httpObjects.size()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); @@ -422,7 +417,6 @@ static int sceHttpReadData(int requestID, u32 dataPtr, u32 dataSize) { // FIXME: JPCSP didn't do anything other than appending the data into internal buffer, does sceHttpSendRequest can be called multiple times before using sceHttpGetStatusCode or sceHttpReadData? any game do this? static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { - WARN_LOG(Log::sceNet, "UNTESTED sceHttpSendRequest(%d, %x, %x)", requestID, dataPtr, dataSize); if (!httpInited) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_INIT, "http not initialized yet"); @@ -439,7 +433,6 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { } static int sceHttpDeleteRequest(int requestID) { - WARN_LOG(Log::sceNet, "UNTESTED sceHttpDeleteRequest(%d)", requestID); std::lock_guard guard(httpLock); if (requestID <= 0 || requestID > (int)httpObjects.size()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); @@ -448,12 +441,11 @@ static int sceHttpDeleteRequest(int requestID) { return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); httpObjects.erase(httpObjects.begin() + requestID - 1); - return 0; + return hleLogDebug(Log::sceNet, 0); } // id: ID of the template, connection or request static int sceHttpDeleteHeader(int id, const char *name) { - WARN_LOG(Log::sceNet, "UNTESTED sceHttpDeleteHeader(%d, %s)", id, safe_string(name)); if (id <= 0 || id > (int)httpObjects.size()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); @@ -462,7 +454,6 @@ static int sceHttpDeleteHeader(int id, const char *name) { } static int sceHttpDeleteConnection(int connectionID) { - WARN_LOG(Log::sceNet, "UNTESTED sceHttpDisableCache(%d)", connectionID); std::lock_guard guard(httpLock); if (connectionID <= 0 || connectionID > (int)httpObjects.size()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); @@ -471,81 +462,71 @@ static int sceHttpDeleteConnection(int connectionID) { return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); httpObjects.erase(httpObjects.begin() + connectionID - 1); - return 0; + return hleLogDebug(Log::sceNet, 0); } // id: ID of the template, connection or request static int sceHttpSetConnectTimeOut(int id, u32 timeout) { - WARN_LOG(Log::sceNet, "UNTESTED sceHttpSetConnectTimeout(%d, %d)", id, timeout); if (id <= 0 || id > (int)httpObjects.size()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); auto& conn = httpObjects[id - 1LL]; conn->setConnectTimeout(timeout); - return 0; + return hleLogDebug(Log::sceNet, 0); } // id: ID of the template, connection or request static int sceHttpSetSendTimeOut(int id, u32 timeout) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetSendTimeout(%d, %d)", id, timeout); if (id <= 0 || id > (int)httpObjects.size()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); auto& conn = httpObjects[id - 1LL]; conn->setSendTimeout(timeout); - return 0; + return hleLogDebug(Log::sceNet, 0); } static u32 sceHttpSetProxy(u32 id, u32 activateFlagPtr, u32 mode, u32 newProxyHostPtr, u32 newProxyPort) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetProxy(%d, %x, %x, %x, %d)", id, activateFlagPtr, mode, newProxyHostPtr, newProxyPort); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // id: ID of the template or connection static int sceHttpEnableCookie(int id) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpEnableCookie(%d)", id); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // id: ID of the template or connection static int sceHttpEnableKeepAlive(int id) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpEnableKeepAlive(%d)", id); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // id: ID of the template or connection static int sceHttpDisableCookie(int id) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpDisableCookie(%d)", id); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // id: ID of the template or connection static int sceHttpDisableKeepAlive(int id) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpDisableKeepAlive(%d)", id); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpsInit(int unknown1, int unknown2, int unknown3, int unknown4) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpsInit(%d, %d, %d, %x)", unknown1, unknown2, unknown3, unknown4); httpsInited = true; - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpsInitWithPath(int unknown1, int unknown2, int unknown3) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpsInitWithPath(%d, %d, %d)", unknown1, unknown2, unknown3); httpsInited = true; - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpsEnd() { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpsEnd()"); httpsInited = false; - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpsDisableOption(int id) { ERROR_LOG(Log::sceNet, "UNIMPL sceHttpsDisableOption(%d)", id); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // Parameter "method" should be one of PSPHttpMethod's listed entries @@ -622,7 +603,7 @@ static int sceHttpDeleteTemplate(int templateID) { return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); httpObjects.erase(httpObjects.begin() + templateID - 1); - return 0; + return hleLogDebug(Log::sceNet, 0); } static int sceHttpSetMallocFunction(u32 mallocFuncPtr, u32 freeFuncPtr, u32 reallocFuncPtr) { @@ -641,67 +622,56 @@ static int sceHttpSetResolveTimeOut(int id, u32 timeout) { return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); conn->setResolveTimeout(timeout); - return 0; + return hleLogDebug(Log::sceNet, 0); } //typedef int(* SceHttpsCallback) (unsigned int verifyEsrr, void *const sslCert[], int certNum, void *userArg) static int sceHttpsSetSslCallback(int id, u32 callbackFuncPtr, u32 userArgPtr) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpsSetSslCallback(%d, %x, %x)", id, callbackFuncPtr, userArgPtr); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } //typedef int(*SceHttpRedirectCallback) (int request, int statusCode, int* method, const char* location, void* userArg); static int sceHttpSetRedirectCallback(int requestID, u32 callbackFuncPtr, u32 userArgPtr) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetRedirectCallback(%d, %x, %x)", requestID, callbackFuncPtr, userArgPtr); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } //typedef int(*SceHttpAuthInfoCallback) (int request, SceHttpAuthType authType, const char* realm, char* username, char* password, int needEntity, unsigned char** entityBody, unsigned int* entitySize, int* save, void* userArg); static int sceHttpSetAuthInfoCallback(int id, u32 callbackFuncPtr, u32 userArgPtr) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetAuthInfoCallback(%d, %x, %x)", id, callbackFuncPtr, userArgPtr); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpSetAuthInfoCB(int id, u32 callbackFuncPtr) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetAuthInfoCB(%d, %x)", id, callbackFuncPtr); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // id: ID of the template or connection static int sceHttpEnableRedirect(int id) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpEnableRedirect(%d)", id); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpEnableAuth(int templateID) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpEnableAuth(%d)", templateID); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // id: ID of the template or connection static int sceHttpDisableRedirect(int id) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpDisableRedirect(%d)", id); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpDisableAuth(int templateID) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpDisableAuth(%d)", templateID); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpSaveSystemCookie() { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSaveSystemCookie()"); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpsLoadDefaultCert(int unknown1, int unknown2) { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpLoadDefaultCert(%d, %d)", unknown1, unknown2); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } static int sceHttpLoadSystemCookie() { - ERROR_LOG(Log::sceNet, "UNIMPL sceHttpLoadSystemCookie()"); - return 0; + return hleLogWarning(Log::sceNet, 0, "UNIMPL"); } // PSP Browser seems to set userAgent to 0 and later set the User-Agent header using sceHttpAddExtraHeader @@ -766,7 +736,7 @@ static int sceHttpSetRecvTimeOut(int id, u32 timeout) { return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); conn->setRecvTimeout(timeout); - return 0; + return hleLogDebug(Log::sceNet, 0); } // FIXME: Headers should includes the "HTTP/MajorVer.MinorVer StatusCode Comment" line? so PSP Browser can parse it using sceParseHttpStatusLine @@ -808,7 +778,7 @@ static int sceHttpGetContentLength(int requestID, u32 contentLengthPtr) { DEBUG_LOG(Log::sceNet, "ContentLength = %lld (in) => %lld (out)", Memory::ReadUnchecked_U64(contentLengthPtr), (u64)len); Memory::WriteUnchecked_U64((u64)len, contentLengthPtr); NotifyMemInfo(MemBlockFlags::WRITE, contentLengthPtr, 8, "HttpGetContentLength"); - return 0; + return hleLogDebug(Log::sceNet, 0); } /*