From f6118a79781c1044221ad1e26d62deff0da6383e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 27 Oct 2026 08:43:28 +0100 Subject: [PATCH 1/4] sceHttp: Stop ids shifting on delete, check request types - sceHttp ids were vector indices that shifted down on every delete, and the request calls cast any object to HTTPRequest. Deletes now leave an empty slot, and request calls check the type. A null header name or value no longer goes into a std::string. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/sceHttp.cpp | 126 ++++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/Core/HLE/sceHttp.cpp b/Core/HLE/sceHttp.cpp index a0461c8792..fa50b5eb01 100644 --- a/Core/HLE/sceHttp.cpp +++ b/Core/HLE/sceHttp.cpp @@ -36,6 +36,20 @@ static std::vector> httpObjects; + +// IDs are index + 1. Deleting leaves an empty slot, so the other IDs stay valid. +static HTTPTemplate *GetHttpObject(int id) { + if (id <= 0 || id > (int)httpObjects.size()) + return nullptr; + return httpObjects[id - 1].get(); +} + +static HTTPRequest *GetHttpRequest(int id) { + HTTPTemplate *obj = GetHttpObject(id); + if (!obj || obj->className() != name_HTTPRequest) + return nullptr; + return static_cast(obj); +} static std::mutex httpLock; bool httpInited = false; @@ -71,7 +85,7 @@ int HTTPTemplate::removeRequestHeader(const char* name) { HTTPConnection::HTTPConnection(int templateID, const char* hostString, const char* scheme, u32 port, int enableKeepalive) { // Copy base data as initial base value for this - HTTPTemplate::operator=(*httpObjects[templateID - 1LL]); + HTTPTemplate::operator=(*GetHttpObject(templateID)); // Initialize this->templateID = templateID; @@ -84,7 +98,7 @@ HTTPConnection::HTTPConnection(int templateID, const char* hostString, const cha HTTPRequest::HTTPRequest(int connectionID, int method, const char *url, u64 contentLength, net::ResolveFunc customResolver) : client(customResolver) { // Copy base data as initial base value for this // Since dynamic_cast/dynamic_pointer_cast/typeid requires RTTI to be enabled (ie. /GR instead of /GR- on msvc, enabled by default on most compilers), so we can only use static_cast here - HTTPConnection::operator=(static_cast(*httpObjects[connectionID - 1LL])); + HTTPConnection::operator=(*static_cast(GetHttpObject(connectionID))); // Initialize this->connectionID = connectionID; @@ -302,7 +316,7 @@ void __HttpShutdown() { httpCacheInited = false; for (const auto& it : httpObjects) { - if (it->className() == name_HTTPRequest) + if (it && it->className() == name_HTTPRequest) (static_cast(it.get()))->abortRequest(); } httpObjects.clear(); @@ -310,10 +324,10 @@ void __HttpShutdown() { // id: ID of the template or connection int sceHttpSetResolveRetry(int id, int retryCount) { - if (id <= 0 || id > (int)httpObjects.size()) + if (!GetHttpObject(id)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - const auto& conn = httpObjects[id - 1LL]; + HTTPTemplate *conn = GetHttpObject(id); if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); @@ -369,13 +383,15 @@ static u32 sceHttpGetProxy(u32 id, u32 activateFlagPtr, u32 modePtr, u32 proxyHo static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetStatusCode(%d, %x)", requestID, statusCodePtr); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + if (!GetHttpObject(requestID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(statusCodePtr, 4)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int status = req->getStatusCode(); @@ -389,13 +405,15 @@ 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) { - if (requestID <= 0 || requestID > (int)httpObjects.size()) + if (!GetHttpObject(requestID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(dataPtr, dataSize)) return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); @@ -420,13 +438,15 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { if (!httpInited) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_INIT, "http not initialized yet"); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + if (!GetHttpObject(requestID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (dataSize > 0 && !Memory::IsValidRange(dataPtr, dataSize)) return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); // Internally try to connect, and get response headers (at least the status code?) int retval = req->sendRequest(dataPtr, dataSize); return hleLogDebug(Log::sceNet, retval); @@ -434,53 +454,55 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { static int sceHttpDeleteRequest(int requestID) { std::lock_guard guard(httpLock); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + if (!GetHttpObject(requestID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[requestID - 1LL]->className() != name_HTTPRequest) + if (GetHttpObject(requestID)->className() != name_HTTPRequest) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - httpObjects.erase(httpObjects.begin() + requestID - 1); + httpObjects[requestID - 1].reset(); return hleLogDebug(Log::sceNet, 0); } // id: ID of the template, connection or request static int sceHttpDeleteHeader(int id, const char *name) { - if (id <= 0 || id > (int)httpObjects.size()) + HTTPTemplate *obj = GetHttpObject(id); + if (!obj) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + if (!name) + return hleLogError(Log::sceNet, -1, "invalid name"); // SCE_HTTP_ERROR_INVALID_VALUE - const auto& req = (HTTPRequest*)httpObjects[id - 1LL].get(); - return req->removeRequestHeader(name); + return obj->removeRequestHeader(name); } static int sceHttpDeleteConnection(int connectionID) { std::lock_guard guard(httpLock); - if (connectionID <= 0 || connectionID > (int)httpObjects.size()) + if (!GetHttpObject(connectionID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) + if (GetHttpObject(connectionID)->className() != name_HTTPConnection) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - httpObjects.erase(httpObjects.begin() + connectionID - 1); + httpObjects[connectionID - 1].reset(); return hleLogDebug(Log::sceNet, 0); } // id: ID of the template, connection or request static int sceHttpSetConnectTimeOut(int id, u32 timeout) { - if (id <= 0 || id > (int)httpObjects.size()) + if (!GetHttpObject(id)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - auto& conn = httpObjects[id - 1LL]; + HTTPTemplate *conn = GetHttpObject(id); conn->setConnectTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } // id: ID of the template, connection or request static int sceHttpSetSendTimeOut(int id, u32 timeout) { - if (id <= 0 || id > (int)httpObjects.size()) + if (!GetHttpObject(id)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - auto& conn = httpObjects[id - 1LL]; + HTTPTemplate *conn = GetHttpObject(id); conn->setSendTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } @@ -533,10 +555,10 @@ static int sceHttpsDisableOption(int id) { static int sceHttpCreateRequest(int connectionID, int method, const char *path, u64 contentLength) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateRequest(%d, %d, %s, %llx)", connectionID, method, safe_string(path), contentLength); std::lock_guard guard(httpLock); - if (connectionID <= 0 || connectionID > (int)httpObjects.size()) + if (!GetHttpObject(connectionID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) + if (GetHttpObject(connectionID)->className() != name_HTTPConnection) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD) @@ -551,10 +573,10 @@ static int sceHttpCreateRequest(int connectionID, int method, const char *path, static int sceHttpCreateConnection(int templateID, const char *hostString, const char *scheme, u32 port, int enableKeepalive) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateConnection(%d, %s, %s, %d, %d)", templateID, safe_string(hostString), safe_string(scheme), port, enableKeepalive); std::lock_guard guard(httpLock); - if (templateID <= 0 || templateID > (int)httpObjects.size()) + if (!GetHttpObject(templateID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) + if (GetHttpObject(templateID)->className() != name_HTTPTemplate) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); // TODO: Look up hostString in DNS here. @@ -577,32 +599,36 @@ static int sceHttpGetNetworkErrno(int request, u32 errNumPtr) { // id: ID of the template, connection or request static int sceHttpAddExtraHeader(int id, const char *name, const char *value, int unknown) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpAddExtraHeader(%d, %s, %s, %d)", id, safe_string(name), safe_string(value), unknown); - if (id <= 0 || id > (int)httpObjects.size()) + HTTPTemplate *obj = GetHttpObject(id); + if (!obj) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + if (!name || !value) + return hleLogError(Log::sceNet, -1, "invalid header"); // SCE_HTTP_ERROR_INVALID_VALUE - const auto& req = (HTTPRequest*)httpObjects[id - 1LL].get(); - return req->addRequestHeader(name, value, unknown); + return obj->addRequestHeader(name, value, unknown); } static int sceHttpAbortRequest(int requestID) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpAbortRequest(%d)", requestID); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + if (!GetHttpObject(requestID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); return req->abortRequest(); } static int sceHttpDeleteTemplate(int templateID) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpDeleteTemplate(%d)", templateID); std::lock_guard guard(httpLock); - if (templateID <= 0 || templateID > (int)httpObjects.size()) + if (!GetHttpObject(templateID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) + if (GetHttpObject(templateID)->className() != name_HTTPTemplate) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - httpObjects.erase(httpObjects.begin() + templateID - 1); + httpObjects[templateID - 1].reset(); return hleLogDebug(Log::sceNet, 0); } @@ -614,10 +640,10 @@ static int sceHttpSetMallocFunction(u32 mallocFuncPtr, u32 freeFuncPtr, u32 real // id: ID of the template or connection static int sceHttpSetResolveTimeOut(int id, u32 timeout) { ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetResolveTimeOut(%d, %d)", id, timeout); - if (id <= 0 || id > (int)httpObjects.size()) + if (!GetHttpObject(id)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - const auto& conn = httpObjects[id - 1LL]; + HTTPTemplate *conn = GetHttpObject(id); if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); @@ -689,10 +715,10 @@ static int sceHttpCreateTemplate(MIPSState *mips, const char *userAgent, int htt static int sceHttpCreateRequestWithURL(int connectionID, int method, const char *url, u64 contentLength) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateRequestWithURL(%d, %d, %s, %llx)", connectionID, method, safe_string(url), contentLength); std::lock_guard guard(httpLock); - if (connectionID <= 0 || connectionID > (int)httpObjects.size()) + if (!GetHttpObject(connectionID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) + if (GetHttpObject(connectionID)->className() != name_HTTPConnection) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD) @@ -710,10 +736,10 @@ static int sceHttpCreateRequestWithURL(int connectionID, int method, const char static int sceHttpCreateConnectionWithURL(int templateID, const char *url, int enableKeepalive) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateConnectionWithURL(%d, %s, %d)", templateID, safe_string(url), enableKeepalive); std::lock_guard guard(httpLock); - if (templateID <= 0 || templateID > (int)httpObjects.size()) + if (!GetHttpObject(templateID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) + if (GetHttpObject(templateID)->className() != name_HTTPTemplate) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); Url baseURL(url ? url: ""); @@ -728,10 +754,10 @@ static int sceHttpCreateConnectionWithURL(int templateID, const char *url, int e // id: ID of the template or connection static int sceHttpSetRecvTimeOut(int id, u32 timeout) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpSetRecvTimeOut(%d, %d)", id, timeout); - if (id <= 0 || id > (int)httpObjects.size()) + if (!GetHttpObject(id)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - const auto& conn = httpObjects[id - 1LL]; + HTTPTemplate *conn = GetHttpObject(id); if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); @@ -743,7 +769,7 @@ static int sceHttpSetRecvTimeOut(int id, u32 timeout) { // Note: Megaman PoweredUp seems to have an invalid address stored at the headerAddrPtr location, may be the game expecting us (network library) to give them a valid header address? static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizePtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetAllHeader(%d, %x, %x)", requestID, headerAddrPtr, headerSizePtr); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + if (!GetHttpObject(requestID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(headerAddrPtr, 4)) @@ -752,7 +778,9 @@ static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizeP if (!Memory::IsValidRange(headerSizePtr, 4)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int retval = req->getAllResponseHeaders(headerAddrPtr, headerSizePtr); @@ -762,13 +790,15 @@ static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizeP // FIXME: contentLength is SceULong64 but this contentLengthPtr argument should be a 32bit pointer instead of 64bit, right? static int sceHttpGetContentLength(int requestID, u32 contentLengthPtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetContentLength(%d, %x)", requestID, contentLengthPtr); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + if (!GetHttpObject(requestID)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(contentLengthPtr, 8)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int len = req->getResponseContentLength(); From adba2ed1afcf046659dc93aa8516e984fcc295ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 27 Oct 2026 00:40:15 +0100 Subject: [PATCH 2/4] NpAuth: Return the ticket param asked for, and write only what fits of a ticket sceNpAuthGetTicketParam started reading after the body section, so it copied the footer, and it concatenated the params before the one asked for instead of returning that one. Like JPCSP, it now walks the params from the start of the body and returns the value of the requested one (ints and dates converted to PSP endianness, strings up to 255 bytes). sceNpAuthGetTicket wrote its whole 248 byte ticket whatever the buffer length. It now copies what fits, and still returns the ticket's size. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/sceNp.cpp | 90 ++++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/Core/HLE/sceNp.cpp b/Core/HLE/sceNp.cpp index 4474118a97..a21cea1062 100644 --- a/Core/HLE/sceNp.cpp +++ b/Core/HLE/sceNp.cpp @@ -431,14 +431,13 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) { return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_UNKNOWN, "Missing npOnlineId"); } - int result = length; - // Built here, then copied once we know it fits. - u8 staging[512]{}; + // Built here and copied out, since the buffer can be shorter than the ticket. + u8 ticketBuf[0xF0 + sizeof(SceNpTicketHeader)]{}; SceNpTicket ticket = {}; // Dummy Login ticket returned as Login response. Dummy ticket contents were taken from https://www.psdevwiki.com/ps3/X-I-5-Ticket ticket.header.version = TICKET_VER_2_1; ticket.header.size = 0xF0; // size excluding the header - u8 *buf = staging + sizeof(ticket); + u8 *buf = ticketBuf + sizeof(ticket); int ofs = 0; ofs += writeTicketParam(buf, PARAM_TYPE_STRING_ASCII, "\x4c\x47\x56\x3b\x81\x39\x4a\x22\xd8\x6b\xc1\x57\x71\x6e\xfd\xb8\xab\x63\xcc\x51", 20); // 20 random letters, token key or SceNpSignature? ofs += writeTicketU32Param(buf + ofs, PARAM_TYPE_INT, 0x0100); // a flags? @@ -464,24 +463,23 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) { ofs += writeTicketParam(buf + ofs, PARAM_TYPE_NULL); ticket.section.type = SECTION_TYPE_BODY; ticket.section.size = ofs; - memcpy(staging, &ticket, sizeof(SceNpTicket)); + memcpy(ticketBuf, &ticket, sizeof(SceNpTicket)); SceNpTicketSection footer = { SECTION_TYPE_FOOTER, 32 }; // footer section? ie. 32-bytes on version 2.1 containing 4-chars ASCII + 20-chars ASCII memcpy(buf + ofs, &footer, sizeof(SceNpTicketSection)); ofs += sizeof(footer); ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, "\x34\xcd\x3c\xa9", 4); ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, "\x3a\x4b\x42\x66\x92\xda\x6b\x7c\xb7\x4c\xe8\xd9\x4f\x2b\x77\x15\x91\xb8\xa4\xa9", 20); // 20 random letters, token key or SceNpSignature? - // includes Language list? (36 zero bytes, staging is zeroed) - const u32 total = (u32)sizeof(ticket) + ofs + 36; - _dbg_assert_(total <= sizeof(staging)); + // includes Language list? (36 zero bytes, left by the initializer) + _dbg_assert_(sizeof(ticket) + ofs + 36 == sizeof(ticketBuf)); - // The whole ticket gets written, even if length is shorter. - if (!Memory::IsValidRange(bufferAddr, std::max(length, total))) { + // Like JPCSP's real ticket path, copy what fits. + if (!Memory::IsValidRange(bufferAddr, length)) { return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_ARGUMENT, "invalid buffer"); } Memory::Memset(bufferAddr, 0, length, "NpAuthGetTicket"); - Memory::Memcpy(bufferAddr, staging, total, "NpAuthGetTicket"); + Memory::Memcpy(bufferAddr, ticketBuf, std::min(length, (u32)sizeof(ticketBuf)), "NpAuthGetTicket"); - result = ticket.header.size + sizeof(ticket.header); // dummy ticket is 248 bytes + const int result = (int)sizeof(ticketBuf); // dummy ticket is 248 bytes return hleLogWarning(Log::sceNet, result); } @@ -538,29 +536,53 @@ int sceNpAuthGetTicketParam(u32 ticketBufPtr, int ticketLen, int paramNum, u32 b return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid ticket"); } - const SceNpTicket *ticket = (const SceNpTicket *)Memory::GetPointerUnchecked(ticketBufPtr); - const u32 ticketEnd = ticketBufPtr + ticketLen; - u32 inbuf = ticketBufPtr; - inbuf += sizeof(ticket->header); - inbuf += ticket->section.size + sizeof(ticket->section); - u32 outbuf = bufferPtr; - for (int i = 0; i < paramNum; i++) { - // Only copy params that are inside the ticket and fit in the output. - if (inbuf < ticketBufPtr || inbuf + sizeof(SceNpTicketParamData) > ticketEnd) - break; - const SceNpTicketParamData *ticketParam = (const SceNpTicketParamData *)Memory::GetPointerUnchecked(inbuf); - u32 sz = (u32)sizeof(SceNpTicketParamData) + ticketParam->length; - if (sz > ticketEnd - inbuf || sz > PARAM_BUFFER_MAX_SIZE - (outbuf - bufferPtr)) - break; - Memory::MemcpyUnchecked(outbuf, inbuf, sz); - DEBUG_LOG(Log::sceNet, "%s - Param #%d: Type = %04x, Length = %u", __FUNCTION__, i, static_cast(ticketParam->type), static_cast(ticketParam->length)); - outbuf += sz; - inbuf += sz; - if (outbuf - bufferPtr >= PARAM_BUFFER_MAX_SIZE || inbuf - ticketBufPtr >= (u32)ticketLen) - break; - } + // Like JPCSP: the params follow the header and the body section delimiter, and only the value of + // the one asked for is returned. + const u32 ticketEnd = ticketBufPtr + (u32)ticketLen; + u32 inbuf = ticketBufPtr + (u32)sizeof(SceNpTicket); + for (int i = 0; ; i++) { + if (inbuf + sizeof(SceNpTicketParamData) > ticketEnd || !Memory::IsValidRange(inbuf, sizeof(SceNpTicketParamData))) { + return hleLogWarning(Log::sceNet, 0, "Ticket ends before param %d", paramNum); + } + SceNpTicketParamData param; + Memory::Memcpy(¶m, inbuf, sizeof(param)); + const u32 valuePtr = inbuf + (u32)sizeof(param); + const u32 length = param.length; + inbuf = valuePtr + length; + if (i < paramNum) { + continue; + } - return hleLogWarning(Log::sceNet, 0); + if (inbuf > ticketEnd || !Memory::IsValidRange(valuePtr, length)) { + return hleLogWarning(Log::sceNet, 0, "Param %d runs past the ticket", paramNum); + } + DEBUG_LOG(Log::sceNet, "%s - Param #%d: Type = %04x, Length = %u", __FUNCTION__, paramNum, (u32)param.type, length); + switch ((u16)param.type) { + case PARAM_TYPE_INT: + if (length >= 4) { + u32_be value; + Memory::Memcpy(&value, valuePtr, sizeof(value)); + Memory::WriteUnchecked_U32(value, bufferPtr); + } + break; + case PARAM_TYPE_LONG: + case PARAM_TYPE_DATE: + if (length >= 8) { + u64_be value; + Memory::Memcpy(&value, valuePtr, sizeof(value)); + Memory::WriteUnchecked_U64(value, bufferPtr); + } + break; + case PARAM_TYPE_STRING: + case PARAM_TYPE_STRING_ASCII: + // At most 255 bytes, leaving the terminator from the clear above. + Memory::Memcpy(bufferPtr, valuePtr, std::min(length, PARAM_BUFFER_MAX_SIZE - 1), "NpAuthGetTicketParam"); + break; + default: + break; + } + return hleLogDebug(Log::sceNet, 0); + } } const HLEFunction sceNpAuth[] = { From 874c0908246fb859920a9e73589c4f8b8d08ace2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 4 Nov 2026 16:10:49 +0100 Subject: [PATCH 3/4] sceHttp: Number ids per kind like the PSP, and check which calls take which Templates, connections and requests each get their own numbers, with the kind in the top byte (1, 0x01000001, 0x02000001), and a new object takes the lowest free number. Resolve settings only apply to templates, the other timeouts to templates and connections, and the response calls fail with BEFORE_SEND until a send has succeeded. Deleting a connection that still has requests fails, and CreateRequest no longer rejects unknown methods. Deleting a header that isn't there returns NOT_FOUND. All as in net/http/ids. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/sceHttp.cpp | 232 +++++++++++++++++++------------------------ Core/HLE/sceHttp.h | 5 + pspautotests | 2 +- test.py | 1 + 4 files changed, 110 insertions(+), 130 deletions(-) diff --git a/Core/HLE/sceHttp.cpp b/Core/HLE/sceHttp.cpp index fa50b5eb01..ab30fe3519 100644 --- a/Core/HLE/sceHttp.cpp +++ b/Core/HLE/sceHttp.cpp @@ -35,20 +35,52 @@ #include "Common/Net/HTTPClient.h" -static std::vector> httpObjects; +// Templates, connections and requests are numbered separately, with the kind in the top byte +// (1, 0x01000001, 0x02000001). A new object takes the lowest free number (tests/net/http/ids). +enum HttpIdKind { + HTTP_ID_TEMPLATE = 0, + HTTP_ID_CONNECTION = 1, + HTTP_ID_REQUEST = 2, + HTTP_ID_KIND_COUNT, +}; + +static std::vector> httpObjects[HTTP_ID_KIND_COUNT]; -// IDs are index + 1. Deleting leaves an empty slot, so the other IDs stay valid. static HTTPTemplate *GetHttpObject(int id) { - if (id <= 0 || id > (int)httpObjects.size()) + const u32 kind = (u32)id >> 24; + const u32 number = (u32)id & 0x00FFFFFF; + if (kind >= HTTP_ID_KIND_COUNT || number == 0 || number > httpObjects[kind].size()) return nullptr; - return httpObjects[id - 1].get(); + return httpObjects[kind][number - 1].get(); +} + +static bool IsHttpId(int id, HttpIdKind kind) { + return ((u32)id >> 24) == (u32)kind && GetHttpObject(id) != nullptr; } static HTTPRequest *GetHttpRequest(int id) { - HTTPTemplate *obj = GetHttpObject(id); - if (!obj || obj->className() != name_HTTPRequest) - return nullptr; - return static_cast(obj); + return IsHttpId(id, HTTP_ID_REQUEST) ? static_cast(GetHttpObject(id)) : nullptr; +} + +static int AddHttpObject(HttpIdKind kind, std::shared_ptr obj) { + auto &objects = httpObjects[kind]; + size_t index = 0; + while (index < objects.size() && objects[index]) + index++; + if (index == objects.size()) + objects.push_back(obj); + else + objects[index] = obj; + return ((int)kind << 24) | (int)(index + 1); +} + +static void RemoveHttpObject(int id) { + httpObjects[(u32)id >> 24][((u32)id & 0x00FFFFFF) - 1].reset(); +} + +static void ClearHttpObjects() { + for (auto &objects : httpObjects) + objects.clear(); } static std::mutex httpLock; @@ -79,7 +111,8 @@ int HTTPTemplate::addRequestHeader(const char* name, const char* value, u32 mode } int HTTPTemplate::removeRequestHeader(const char* name) { - requestHeaders_.erase(name); + if (requestHeaders_.erase(name) == 0) + return SCE_HTTP_ERROR_NOT_FOUND; return 0; } @@ -117,10 +150,6 @@ HTTPRequest::~HTTPRequest() { } int HTTPRequest::getResponseContentLength() { - // FIXME: Will sceHttpGetContentLength returns an error if the request was not sent yet? - //if (progress_.progress == 0.0f) - // return SCE_HTTP_ERROR_BEFORE_SEND; - entityLength_ = -1; // FIXME: should we default to 0 instead? for (std::string& line : responseHeaders_) { if (startsWithNoCase(line, "Content-Length")) { @@ -143,17 +172,10 @@ int HTTPRequest::abortRequest() { } int HTTPRequest::getStatusCode() { - // FIXME: Will sceHttpGetStatusCode returns an error if the request was not sent yet? - //if (progress_.progress == 0.0f) - // return SCE_HTTP_ERROR_BEFORE_SEND; return responseCode_; } int HTTPRequest::getAllResponseHeaders(u32 headerAddrPtr, u32 headerSizePtr) { - // FIXME: Will sceHttpGetAllHeader returns an error if the request was not sent yet? - //if (progress_.progress == 0.0f) - // return SCE_HTTP_ERROR_BEFORE_SEND; - const char* const delim = "\r\n"; std::ostringstream imploded; std::copy(responseHeaders_.begin(), responseHeaders_.end(), std::ostream_iterator(imploded, delim)); @@ -205,9 +227,6 @@ int HTTPRequest::getAllResponseHeaders(u32 headerAddrPtr, u32 headerSizePtr) { } int HTTPRequest::readData(u32 destDataPtr, u32 size) { - // FIXME: Will sceHttpReadData returns an error if the request was not sent yet? - //if (progress_.progress == 0.0f) - // return SCE_HTTP_ERROR_BEFORE_SEND; u32 sz = std::min(size, (u32)responseContent_.size()); if (sz > 0) { Memory::MemcpyUnchecked(destDataPtr, responseContent_.c_str(), sz); @@ -303,6 +322,7 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) { return SCE_HTTP_ERROR_ABORTED; } + sent_ = true; return 0; } @@ -315,23 +335,19 @@ void __HttpShutdown() { httpsInited = false; httpCacheInited = false; - for (const auto& it : httpObjects) { - if (it && it->className() == name_HTTPRequest) + for (const auto& it : httpObjects[HTTP_ID_REQUEST]) { + if (it) (static_cast(it.get()))->abortRequest(); } - httpObjects.clear(); + ClearHttpObjects(); } -// id: ID of the template or connection +// id: ID of a template int sceHttpSetResolveRetry(int id, int retryCount) { - if (!GetHttpObject(id)) + if (!IsHttpId(id, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - HTTPTemplate *conn = GetHttpObject(id); - if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); - - conn->setResolveRetry(retryCount); + GetHttpObject(id)->setResolveRetry(retryCount); return hleLogDebug(Log::sceNet, 0); } @@ -341,9 +357,7 @@ static int sceHttpInit(MIPSState *mips, int poolSize) { return hleLogError(Log::sceNet, SCE_HTTP_ERROR_ALREADY_INITED, "http already inited"); std::lock_guard guard(httpLock); - httpObjects.clear(); - // Reserve at least 1 element to prevent ::begin() from returning null when no element has been added yet - httpObjects.reserve(1); + ClearHttpObjects(); httpInited = true; return hleLogDebug(Log::sceNet, 0); } @@ -351,7 +365,7 @@ static int sceHttpInit(MIPSState *mips, int poolSize) { static int sceHttpEnd() { WARN_LOG(Log::sceNet, "UNTESTED sceHttpEnd()"); std::lock_guard guard(httpLock); - httpObjects.clear(); + ClearHttpObjects(); httpInited = false; return hleLogDebug(Log::sceNet, 0); } @@ -383,15 +397,15 @@ static u32 sceHttpGetProxy(u32 id, u32 activateFlagPtr, u32 modePtr, u32 proxyHo static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetStatusCode(%d, %x)", requestID, statusCodePtr); - if (!GetHttpObject(requestID)) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(statusCodePtr, 4)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - HTTPRequest *req = GetHttpRequest(requestID); - if (!req) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); + if (!req->wasSent()) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int status = req->getStatusCode(); @@ -405,15 +419,15 @@ 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) { - if (!GetHttpObject(requestID)) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(dataPtr, dataSize)) return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE - HTTPRequest *req = GetHttpRequest(requestID); - if (!req) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); + if (!req->wasSent()) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); @@ -438,15 +452,12 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { if (!httpInited) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_INIT, "http not initialized yet"); - if (!GetHttpObject(requestID)) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (dataSize > 0 && !Memory::IsValidRange(dataPtr, dataSize)) return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE - - HTTPRequest *req = GetHttpRequest(requestID); - if (!req) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); // Internally try to connect, and get response headers (at least the status code?) int retval = req->sendRequest(dataPtr, dataSize); return hleLogDebug(Log::sceNet, retval); @@ -454,13 +465,10 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { static int sceHttpDeleteRequest(int requestID) { std::lock_guard guard(httpLock); - if (!GetHttpObject(requestID)) + if (!IsHttpId(requestID, HTTP_ID_REQUEST)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (GetHttpObject(requestID)->className() != name_HTTPRequest) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - httpObjects[requestID - 1].reset(); + RemoveHttpObject(requestID); return hleLogDebug(Log::sceNet, 0); } @@ -477,33 +485,33 @@ static int sceHttpDeleteHeader(int id, const char *name) { static int sceHttpDeleteConnection(int connectionID) { std::lock_guard guard(httpLock); - if (!GetHttpObject(connectionID)) + if (!IsHttpId(connectionID, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (GetHttpObject(connectionID)->className() != name_HTTPConnection) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + for (const auto &it : httpObjects[HTTP_ID_REQUEST]) { + if (it && static_cast(it.get())->getConnectionID() == connectionID) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_CONNECTION_HAS_REQUESTS, "connection has requests"); + } - httpObjects[connectionID - 1].reset(); + RemoveHttpObject(connectionID); return hleLogDebug(Log::sceNet, 0); } -// id: ID of the template, connection or request +// id: ID of the template or connection static int sceHttpSetConnectTimeOut(int id, u32 timeout) { - if (!GetHttpObject(id)) + if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - HTTPTemplate *conn = GetHttpObject(id); - conn->setConnectTimeout(timeout); + GetHttpObject(id)->setConnectTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } -// id: ID of the template, connection or request +// id: ID of the template or connection static int sceHttpSetSendTimeOut(int id, u32 timeout) { - if (!GetHttpObject(id)) + if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - HTTPTemplate *conn = GetHttpObject(id); - conn->setSendTimeout(timeout); + GetHttpObject(id)->setSendTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } @@ -555,17 +563,11 @@ static int sceHttpsDisableOption(int id) { static int sceHttpCreateRequest(int connectionID, int method, const char *path, u64 contentLength) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateRequest(%d, %d, %s, %llx)", connectionID, method, safe_string(path), contentLength); std::lock_guard guard(httpLock); - if (!GetHttpObject(connectionID)) + // Any method is accepted here. + if (!IsHttpId(connectionID, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (GetHttpObject(connectionID)->className() != name_HTTPConnection) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_UNKNOWN_METHOD, "unknown method"); - - httpObjects.emplace_back(std::make_shared(connectionID, method, path ? path : "", contentLength, &ProcessHostnameWithInfraDNS)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_REQUEST, std::make_shared(connectionID, method, path ? path : "", contentLength, &ProcessHostnameWithInfraDNS)); return hleLogDebug(Log::sceNet, retid); } @@ -573,16 +575,12 @@ static int sceHttpCreateRequest(int connectionID, int method, const char *path, static int sceHttpCreateConnection(int templateID, const char *hostString, const char *scheme, u32 port, int enableKeepalive) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateConnection(%d, %s, %s, %d, %d)", templateID, safe_string(hostString), safe_string(scheme), port, enableKeepalive); std::lock_guard guard(httpLock); - if (!GetHttpObject(templateID)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - if (GetHttpObject(templateID)->className() != name_HTTPTemplate) + if (!IsHttpId(templateID, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); // TODO: Look up hostString in DNS here. - httpObjects.emplace_back(std::make_shared(templateID, hostString ? hostString : "", scheme ? scheme : "", port, enableKeepalive)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_CONNECTION, std::make_shared(templateID, hostString ? hostString : "", scheme ? scheme : "", port, enableKeepalive)); return hleLogDebug(Log::sceNet, retid); } @@ -610,25 +608,20 @@ static int sceHttpAddExtraHeader(int id, const char *name, const char *value, in static int sceHttpAbortRequest(int requestID) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpAbortRequest(%d)", requestID); - if (!GetHttpObject(requestID)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - HTTPRequest *req = GetHttpRequest(requestID); if (!req) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); return req->abortRequest(); } static int sceHttpDeleteTemplate(int templateID) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpDeleteTemplate(%d)", templateID); std::lock_guard guard(httpLock); - if (!GetHttpObject(templateID)) + // Its connections stay usable. + if (!IsHttpId(templateID, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (GetHttpObject(templateID)->className() != name_HTTPTemplate) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - httpObjects[templateID - 1].reset(); + RemoveHttpObject(templateID); return hleLogDebug(Log::sceNet, 0); } @@ -637,17 +630,13 @@ static int sceHttpSetMallocFunction(u32 mallocFuncPtr, u32 freeFuncPtr, u32 real return 0; } -// id: ID of the template or connection +// id: ID of a template static int sceHttpSetResolveTimeOut(int id, u32 timeout) { ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetResolveTimeOut(%d, %d)", id, timeout); - if (!GetHttpObject(id)) + if (!IsHttpId(id, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - HTTPTemplate *conn = GetHttpObject(id); - if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); - conn->setResolveTimeout(timeout); + GetHttpObject(id)->setResolveTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } @@ -706,8 +695,7 @@ static int sceHttpCreateTemplate(MIPSState *mips, const char *userAgent, int htt // Reporting to find more games to be tested WARN_LOG_REPORT_ONCE(sceHttpCreateTemplate, Log::sceNet, "UNTESTED sceHttpCreateTemplate(%s, %d, %d)", safe_string(userAgent), httpVer, autoProxyConf); std::lock_guard guard(httpLock); - httpObjects.push_back(std::make_shared(userAgent? userAgent:"", httpVer, autoProxyConf)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_TEMPLATE, std::make_shared(userAgent? userAgent:"", httpVer, autoProxyConf)); return hleLogDebug(Log::sceNet, retid); } @@ -715,53 +703,39 @@ static int sceHttpCreateTemplate(MIPSState *mips, const char *userAgent, int htt static int sceHttpCreateRequestWithURL(int connectionID, int method, const char *url, u64 contentLength) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateRequestWithURL(%d, %d, %s, %llx)", connectionID, method, safe_string(url), contentLength); std::lock_guard guard(httpLock); - if (!GetHttpObject(connectionID)) + // Any method is accepted here. + if (!IsHttpId(connectionID, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (GetHttpObject(connectionID)->className() != name_HTTPConnection) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_UNKNOWN_METHOD, "unknown method"); - Url baseURL(url ? url : ""); if (!baseURL.Valid()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_URL, "invalid url"); - httpObjects.emplace_back(std::make_shared(connectionID, method, url ? url : "", contentLength, &ProcessHostnameWithInfraDNS)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_REQUEST, std::make_shared(connectionID, method, url ? url : "", contentLength, &ProcessHostnameWithInfraDNS)); return hleLogDebug(Log::sceNet, retid); } static int sceHttpCreateConnectionWithURL(int templateID, const char *url, int enableKeepalive) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateConnectionWithURL(%d, %s, %d)", templateID, safe_string(url), enableKeepalive); std::lock_guard guard(httpLock); - if (!GetHttpObject(templateID)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - if (GetHttpObject(templateID)->className() != name_HTTPTemplate) + if (!IsHttpId(templateID, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); Url baseURL(url ? url: ""); if (!baseURL.Valid()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_URL, "invalid url"); - httpObjects.emplace_back(std::make_shared(templateID, baseURL.Host().c_str(), baseURL.Protocol().c_str(), baseURL.Port(), enableKeepalive)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_CONNECTION, std::make_shared(templateID, baseURL.Host().c_str(), baseURL.Protocol().c_str(), baseURL.Port(), enableKeepalive)); return hleLogDebug(Log::sceNet, retid); } // id: ID of the template or connection static int sceHttpSetRecvTimeOut(int id, u32 timeout) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpSetRecvTimeOut(%d, %d)", id, timeout); - if (!GetHttpObject(id)) + if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - HTTPTemplate *conn = GetHttpObject(id); - if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); - - conn->setRecvTimeout(timeout); + GetHttpObject(id)->setRecvTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } @@ -769,7 +743,8 @@ static int sceHttpSetRecvTimeOut(int id, u32 timeout) { // Note: Megaman PoweredUp seems to have an invalid address stored at the headerAddrPtr location, may be the game expecting us (network library) to give them a valid header address? static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizePtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetAllHeader(%d, %x, %x)", requestID, headerAddrPtr, headerSizePtr); - if (!GetHttpObject(requestID)) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(headerAddrPtr, 4)) @@ -778,9 +753,8 @@ static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizeP if (!Memory::IsValidRange(headerSizePtr, 4)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - HTTPRequest *req = GetHttpRequest(requestID); - if (!req) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); + if (!req->wasSent()) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int retval = req->getAllResponseHeaders(headerAddrPtr, headerSizePtr); @@ -790,15 +764,15 @@ static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizeP // FIXME: contentLength is SceULong64 but this contentLengthPtr argument should be a 32bit pointer instead of 64bit, right? static int sceHttpGetContentLength(int requestID, u32 contentLengthPtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetContentLength(%d, %x)", requestID, contentLengthPtr); - if (!GetHttpObject(requestID)) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(contentLengthPtr, 8)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - HTTPRequest *req = GetHttpRequest(requestID); - if (!req) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "not a request"); + if (!req->wasSent()) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int len = req->getResponseContentLength(); diff --git a/Core/HLE/sceHttp.h b/Core/HLE/sceHttp.h index 8c532bc2cc..d661f425d2 100644 --- a/Core/HLE/sceHttp.h +++ b/Core/HLE/sceHttp.h @@ -52,6 +52,8 @@ enum SceHttpErrorCode { SCE_HTTP_ERROR_BAD_RESPONSE = 0x80431064, SCE_HTTP_ERROR_BEFORE_SEND = 0x80431065, SCE_HTTP_ERROR_AFTER_SEND = 0x80431066, + // sceHttpDeleteConnection, while the connection has requests (tests/net/http/ids). + SCE_HTTP_ERROR_CONNECTION_HAS_REQUESTS = 0x80431067, SCE_HTTP_ERROR_TIMEOUT = 0x80431068, SCE_HTTP_ERROR_UNKOWN_AUTH_TYPE = 0x80431069, SCE_HTTP_ERROR_INVALID_VERSION = 0x8043106A, @@ -258,6 +260,7 @@ private: u32 headerAddr_ = 0; u32 headerSize_ = 0; bool cancelled_ = false; + bool sent_ = false; int responseCode_ = -1; int entityLength_ = -1; @@ -275,6 +278,8 @@ public: void setInternalHeaderAddr(u32 addr) { headerAddr_ = addr; } int getConnectionID() { return connectionID; } + // Until a send succeeds, the calls that read the response fail with SCE_HTTP_ERROR_BEFORE_SEND. + bool wasSent() const { return sent_; } int getResponseRemainingContentLength() { return (int)responseContent_.size(); } int getResponseContentLength(); diff --git a/pspautotests b/pspautotests index 1a9e6c31cf..70e72391e9 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 1a9e6c31cf31411f82f8a2d659855442f6dfda11 +Subproject commit 70e72391e95b4b5bf6cd7ffd7e0259f52abf9a99 diff --git a/test.py b/test.py index 66954ee34c..1cea49f6e7 100755 --- a/test.py +++ b/test.py @@ -365,6 +365,7 @@ tests_good = [ "net/inet/polledge", "net/inet/reuse", "net/inet/errnothread", + "net/http/ids", "net/adhoc/abandon", "net/adhoc/alerts", "net/adhoc/apctlinit", From 00e03ba9b4583a13dbcc7aa892992ee98c226644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 4 Nov 2026 16:12:47 +0100 Subject: [PATCH 4/4] NpAuth: Parse tickets in GetTicketParam the way the PSP does The length (4 to 0x10000), the param number, the version (2.1 or 3.0) and the body section type are checked in that order, and param 11 is never found. Then the params are walked from the start, each one written to the buffer as it goes, until the one asked for. A param that doesn't fit, or whose type isn't the one that slot always has, fails with PARSER_FAILED and leaves the previous value in the buffer. Numbers are read at full size whatever length the param gives. All as in net/npauth/ticketparam. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/sceNp.cpp | 144 ++++++++++++++++++++++++++++++--------------- test.py | 1 + 2 files changed, 99 insertions(+), 46 deletions(-) diff --git a/Core/HLE/sceNp.cpp b/Core/HLE/sceNp.cpp index a21cea1062..21fe33bb5a 100644 --- a/Core/HLE/sceNp.cpp +++ b/Core/HLE/sceNp.cpp @@ -522,67 +522,119 @@ int sceNpAuthDestroyRequest(int requestId) return hleNoLog(0); } +// Tickets are big-endian, and params needn't be aligned. +static u16 ReadTicketU16(u32 addr) { + return ((u16)Memory::ReadUnchecked_U8(addr) << 8) | Memory::ReadUnchecked_U8(addr + 1); +} + +static u32 ReadTicketU32(u32 addr) { + return ((u32)ReadTicketU16(addr) << 16) | ReadTicketU16(addr + 2); +} + +// Writes one param's value into the 256-byte output, as GetTicketParam does for every param it +// walks past. A NULL param leaves the output alone, any other clears it first, and a type with no +// value (unknown ones) leaves it clear. Numbers are read at their full size whatever length the +// param gives. +static void WriteTicketParamValue(u32 bufferPtr, u16 type, u32 valuePtr, u32 length) { + const u32 PARAM_BUFFER_MAX_SIZE = 256; + if (type == PARAM_TYPE_NULL) { + return; + } + Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE, "NpAuthGetTicketParam"); + + u32 readSize = 0; + switch (type) { + case PARAM_TYPE_INT: readSize = 4; break; + case PARAM_TYPE_LONG: case PARAM_TYPE_DATE: readSize = 8; break; + case PARAM_TYPE_STRING: case PARAM_TYPE_STRING_ASCII: readSize = std::min(length, PARAM_BUFFER_MAX_SIZE - 1); break; + case 0x3011: readSize = 4; break; + default: return; + } + if (!Memory::IsValidRange(valuePtr, readSize)) { + return; + } + + switch (type) { + case PARAM_TYPE_INT: + Memory::WriteUnchecked_U32(ReadTicketU32(valuePtr), bufferPtr); + break; + case PARAM_TYPE_LONG: + case PARAM_TYPE_DATE: + Memory::WriteUnchecked_U64(((u64)ReadTicketU32(valuePtr) << 32) | ReadTicketU32(valuePtr + 4), bufferPtr); + break; + case 0x3011: + // A big-endian u16 followed by two bytes as they are. + Memory::WriteUnchecked_U16(ReadTicketU16(valuePtr), bufferPtr); + Memory::Memcpy(bufferPtr + 2, valuePtr + 2, 2, "NpAuthGetTicketParam"); + break; + default: + // The terminator is left by the clear. + Memory::Memcpy(bufferPtr, valuePtr, readSize, "NpAuthGetTicketParam"); + break; + } +} + +// Behaviour from tests/net/npauth/ticketparam. int sceNpAuthGetTicketParam(u32 ticketBufPtr, int ticketLen, int paramNum, u32 bufferPtr) { const u32 PARAM_BUFFER_MAX_SIZE = 256; + // The type each param has to have. Param 11 is never found. + static const u16 paramTypes[NUMBER_PARAMETERS - 1] = { + PARAM_TYPE_STRING_ASCII, PARAM_TYPE_INT, PARAM_TYPE_DATE, PARAM_TYPE_DATE, PARAM_TYPE_LONG, PARAM_TYPE_STRING, + PARAM_TYPE_STRING_ASCII, PARAM_TYPE_STRING, PARAM_TYPE_STRING_ASCII, PARAM_TYPE_INT, PARAM_TYPE_DATE, + }; + if (!Memory::IsValidRange(bufferPtr, PARAM_BUFFER_MAX_SIZE)) { return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid buffer"); } - Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE); // JPCSP: This clear is always done, even when an error is returned - if (paramNum < 0 || paramNum >= NUMBER_PARAMETERS) { - return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT); + // Cleared even when an error is returned. + Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE, "NpAuthGetTicketParam"); + if (ticketLen < 4 || ticketLen > 0x10000) { + return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_TICKET_SIZE, "invalid ticket size"); } - if (ticketLen < (int)sizeof(SceNpTicket) || !Memory::IsValidRange(ticketBufPtr, ticketLen)) { + if (paramNum < 0 || paramNum >= NUMBER_PARAMETERS) { + return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid param"); + } + if (!Memory::IsValidRange(ticketBufPtr, ticketLen)) { return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid ticket"); } - // Like JPCSP: the params follow the header and the body section delimiter, and only the value of - // the one asked for is returned. + // Major version in the top nibble, minor in the next byte: 2.1 and 3.0 are accepted. + const u32 version = ReadTicketU32(ticketBufPtr); + const u32 major = version >> 28; + const u32 minor = (version >> 16) & 0xFF; + if (!(major == 2 && minor == 1) && !(major == 3 && minor == 0)) { + return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_TICKET_VERSION, "unsupported ticket version %08x", version); + } + if (paramNum == NUMBER_PARAMETERS - 1) { + return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_TICKET_PARAM_NOT_FOUND); + } + // The header's size and the section's size aren't used. + if (ticketLen < (int)sizeof(SceNpTicket) || ReadTicketU16(ticketBufPtr + 8) != SECTION_TYPE_BODY) { + return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "no body section"); + } + + // Every param up to the one asked for is written out in turn, so a failure leaves the last + // one written in the buffer. const u32 ticketEnd = ticketBufPtr + (u32)ticketLen; u32 inbuf = ticketBufPtr + (u32)sizeof(SceNpTicket); - for (int i = 0; ; i++) { - if (inbuf + sizeof(SceNpTicketParamData) > ticketEnd || !Memory::IsValidRange(inbuf, sizeof(SceNpTicketParamData))) { - return hleLogWarning(Log::sceNet, 0, "Ticket ends before param %d", paramNum); + for (int i = 0; i <= paramNum; i++) { + if (inbuf + sizeof(SceNpTicketParamData) > ticketEnd) { + return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "ticket ends before param %d", i); + } + const u16 type = ReadTicketU16(inbuf); + const u32 length = ReadTicketU16(inbuf + 2); + const u32 valuePtr = inbuf + (u32)sizeof(SceNpTicketParamData); + if (valuePtr + length > ticketEnd) { + return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "param %d runs past the ticket", i); + } + WriteTicketParamValue(bufferPtr, type, valuePtr, length); + if (type != paramTypes[i]) { + return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "param %d has type %04x", i, type); } - SceNpTicketParamData param; - Memory::Memcpy(¶m, inbuf, sizeof(param)); - const u32 valuePtr = inbuf + (u32)sizeof(param); - const u32 length = param.length; inbuf = valuePtr + length; - if (i < paramNum) { - continue; - } - - if (inbuf > ticketEnd || !Memory::IsValidRange(valuePtr, length)) { - return hleLogWarning(Log::sceNet, 0, "Param %d runs past the ticket", paramNum); - } - DEBUG_LOG(Log::sceNet, "%s - Param #%d: Type = %04x, Length = %u", __FUNCTION__, paramNum, (u32)param.type, length); - switch ((u16)param.type) { - case PARAM_TYPE_INT: - if (length >= 4) { - u32_be value; - Memory::Memcpy(&value, valuePtr, sizeof(value)); - Memory::WriteUnchecked_U32(value, bufferPtr); - } - break; - case PARAM_TYPE_LONG: - case PARAM_TYPE_DATE: - if (length >= 8) { - u64_be value; - Memory::Memcpy(&value, valuePtr, sizeof(value)); - Memory::WriteUnchecked_U64(value, bufferPtr); - } - break; - case PARAM_TYPE_STRING: - case PARAM_TYPE_STRING_ASCII: - // At most 255 bytes, leaving the terminator from the clear above. - Memory::Memcpy(bufferPtr, valuePtr, std::min(length, PARAM_BUFFER_MAX_SIZE - 1), "NpAuthGetTicketParam"); - break; - default: - break; - } - return hleLogDebug(Log::sceNet, 0); } + return hleLogDebug(Log::sceNet, 0); } const HLEFunction sceNpAuth[] = { diff --git a/test.py b/test.py index 1cea49f6e7..98ca445ffe 100755 --- a/test.py +++ b/test.py @@ -366,6 +366,7 @@ tests_good = [ "net/inet/reuse", "net/inet/errnothread", "net/http/ids", + "net/npauth/ticketparam", "net/adhoc/abandon", "net/adhoc/alerts", "net/adhoc/apctlinit",