From fd8aefb9f89f6506da82e49faf9bf9d735d4848c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 27 Oct 2026 00:43:09 +0100 Subject: [PATCH] NpAuth: Fix GetTicketParam, and GetTicket's buffer and service ID sizes 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 248 byte ticket whatever the buffer length. It's now built on the host and copied out up to length. It also copied 24 bytes from a possibly shorter service ID. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceNp.cpp | 92 +++++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/Core/HLE/sceNp.cpp b/Core/HLE/sceNp.cpp index fc174bc697..dd3a8eb6a3 100644 --- a/Core/HLE/sceNp.cpp +++ b/Core/HLE/sceNp.cpp @@ -426,13 +426,13 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) { return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_UNKNOWN, "Missing npOnlineId"); } - int result = length; - Memory::Memset(bufferAddr, 0, length, "NpAuthGetTicket"); + // 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 = Memory::GetPointerWriteOrException(bufferAddr + 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? @@ -445,7 +445,10 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) { ofs += writeTicketStringParam(buf + ofs, PARAM_TYPE_STRING, npOnlineId.c_str(), 32); // username (pre-cut to 16 chars) ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, npCountryCode, 4); // SceNpCountryCode ? ie. "fr" + 00 02 ofs += writeTicketStringParam(buf + ofs, PARAM_TYPE_STRING, npRegionCode, 4); // 2-char code? related to country/lang code? ie. "c9" + 00 00 - ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, npServiceId.c_str(), 24); + // Copies 24 bytes, so pad the ID out to that. + std::string serviceId = npServiceId; + serviceId.resize(24, '\0'); + ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, serviceId.c_str(), 24); int status = 0; if (npParentalControl == PARENTAL_CONTROL_ENABLED) { status |= STATUS_ACCOUNT_PARENTAL_CONTROL_ENABLED; @@ -456,16 +459,22 @@ 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; - Memory::Memcpy(bufferAddr, &ticket, sizeof(SceNpTicket), "NpAuthGetTicket"); + 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 - Memory::Memcpy(bufferAddr + sizeof(ticket) + ofs, &footer, sizeof(SceNpTicketSection), "NpAuthGetTicket"); + 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? - Memory::Memset(bufferAddr + sizeof(ticket) + ofs, 0, 36); + // includes Language list? (36 zero bytes, left by the initializer) + _dbg_assert_(sizeof(ticket) + ofs + 36 == sizeof(ticketBuf)); - result = ticket.header.size + sizeof(ticket.header); // dummy ticket is 248 bytes + // The dummy ticket is 248 bytes. Like JPCSP's real ticket path, copy what fits. + const u32 result = std::min(length, (u32)sizeof(ticketBuf)); + 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, ticketBuf, result, "NpAuthGetTicket"); return hleLogWarning(Log::sceNet, result); } @@ -511,28 +520,61 @@ int sceNpAuthDestroyRequest(int requestId) int sceNpAuthGetTicketParam(u32 ticketBufPtr, int ticketLen, int paramNum, u32 bufferPtr) { const u32 PARAM_BUFFER_MAX_SIZE = 256; + if (!Memory::IsValidRange(bufferPtr, PARAM_BUFFER_MAX_SIZE)) { + return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "bad 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); } - SceNpTicket* ticket = (SceNpTicket*)Memory::GetPointerOrException(ticketBufPtr); - u32 inbuf = ticketBufPtr; - inbuf += sizeof(ticket->header); - inbuf += ticket->section.size + sizeof(ticket->section); - u32 outbuf = bufferPtr; - for (int i = 0; i < paramNum; i++) { - SceNpTicketParamData* ticketParam = (SceNpTicketParamData*)Memory::GetPointerOrException(inbuf); - u32 sz = (u32)sizeof(SceNpTicketParamData) + ticketParam->length; - Memory::Memcpy(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)std::max(ticketLen, 0); + 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[] = {