fix(libSceNpUtility): refuse null results and synchronous requests in polls and waits

Firmware 13.42's module checks a null result first (0x80550704) and
refuses a synchronous request in the lookup poll and wait with
0x80550307; the word filter keeps INVALID_ID. The debt entry records the
measurement and that IDs here do not carry the console's service bits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
louise-patraandClaude Opus 5.5 committed 2026-10-25 18:50:17 +05:30
1 parent 855e4ea6a0
commit 10f2994bb6
3 files changed
+24 -16

No files matched your search

+15 -12
View File
@@ -17,6 +17,7 @@ static constexpr int SCE_NP_COMMUNITY_ERROR_ABORTED = static_cast<int>(0x8055070
static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast<int>(0x8055070C);
static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast<int>(0x8055070E);
static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_TYPE = static_cast<int>(0x80550711);
static constexpr int SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST = static_cast<int>(0x80550307);
static constexpr int POLL_FINISHED = 0;
static constexpr int POLL_RUNNING = 1;
static constexpr std::size_t MAX_TITLE_CONTEXTS = 32;
@@ -110,25 +111,29 @@ int AbortRequest(Service& service, int reqId) {
return 0;
}
int PollAsync(Service& service, int reqId, int* result, bool asyncOnly) {
int PollAsync(Service& service, int reqId, int* result, int synchronousError) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(service.mutex);
const auto request = service.requests.find(reqId);
if (request == service.requests.end() || (asyncOnly && !request->second.async)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!request->second.async) return synchronousError;
if (!request->second.finished) return POLL_RUNNING;
if (result) *result = request->second.result;
*result = request->second.result;
return POLL_FINISHED;
}
int WaitAsync(Service& service, int reqId, int* result, bool asyncOnly) {
int WaitAsync(Service& service, int reqId, int* result, int synchronousError) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::unique_lock lock(service.mutex);
auto request = service.requests.find(reqId);
if (request == service.requests.end() || (asyncOnly && !request->second.async)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!request->second.async) return synchronousError;
service.finished.wait(lock, [&] {
request = service.requests.find(reqId);
return request == service.requests.end() || request->second.finished;
});
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (result) *result = request->second.result;
*result = request->second.result;
return 0;
}
@@ -498,7 +503,7 @@ int APS5_VABI sceNpLookupNpId(int reqId, const NpOnlineId* onlineId, void* npId,
}
int APS5_VABI sceNpLookupPollAsync(int reqId, int* result) {
return PollAsync(lookup, reqId, result, false);
return PollAsync(lookup, reqId, result, SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST);
}
int APS5_VABI sceNpLookupSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) {
@@ -506,7 +511,7 @@ int APS5_VABI sceNpLookupSetTimeout(int id, int resolveRetry, std::uint32_t reso
}
int APS5_VABI sceNpLookupWaitAsync(int reqId, int* result) {
return WaitAsync(lookup, reqId, result, false);
return WaitAsync(lookup, reqId, result, SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST);
}
int APS5_VABI sceNpWordFilterAbortRequest(int reqId) {
@@ -542,8 +547,7 @@ int APS5_VABI sceNpWordFilterDeleteTitleCtx(int titleCtxId) {
}
int APS5_VABI sceNpWordFilterPollAsync(int reqId, int* result) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
return PollAsync(wordFilter, reqId, result, true);
return PollAsync(wordFilter, reqId, result, SCE_NP_COMMUNITY_ERROR_INVALID_ID);
}
int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option) {
@@ -558,8 +562,7 @@ int APS5_VABI sceNpWordFilterSetTimeout(int id, int resolveRetry, std::uint32_t
}
int APS5_VABI sceNpWordFilterWaitAsync(int reqId, int* result) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
return WaitAsync(wordFilter, reqId, result, true);
return WaitAsync(wordFilter, reqId, result, SCE_NP_COMMUNITY_ERROR_INVALID_ID);
}
}
+8 -3
View File
@@ -16,6 +16,7 @@ constexpr int Aborted = static_cast<int>(0x80550707u);
constexpr int InsufficientArgument = static_cast<int>(0x8055070Cu);
constexpr int InvalidId = static_cast<int>(0x8055070Eu);
constexpr int InvalidType = static_cast<int>(0x80550711u);
constexpr int LookupSynchronousRequest = static_cast<int>(0x80550307u);
constexpr int PollFinished = 0;
constexpr int PollRunning = 1;
constexpr int WordFilterTitleCtxBase = 0x01000000;
@@ -172,16 +173,20 @@ void CheckLookupAsync() {
Require(sceNpLookupSetTimeout(async, 0, 1000000, 10000000, 10000000, 10000000) == 0, "sceNpLookupSetTimeout", "a request timeout was refused");
int result = 7;
Require(sceNpLookupPollAsync(async + 100, &result) == InvalidId, "sceNpLookupPollAsync", "an unknown request was taken");
Require(sceNpLookupPollAsync(async, nullptr) == PollRunning, "sceNpLookupPollAsync", "an unstarted request was not running");
Require(sceNpLookupPollAsync(async, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "a null result was taken");
Require(sceNpLookupPollAsync(async + 100, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "an unknown request was checked before a null result");
Require(sceNpLookupWaitAsync(async, nullptr) == InvalidArgument, "sceNpLookupWaitAsync", "a null result was taken");
Require(sceNpLookupPollAsync(async, &result) == PollRunning && result == 7, "sceNpLookupPollAsync", "an unstarted request was not running");
Require(WaitInThread("sceNpLookupWaitAsync", sceNpLookupWaitAsync, async, &result, StartLookup) == 0 && result == SignedOut, "sceNpLookupWaitAsync", "waiting did not end with SIGNED_OUT when the lookup started");
Require(sceNpLookupPollAsync(async, nullptr) == PollFinished, "sceNpLookupPollAsync", "a null result was refused on a finished request");
Require(sceNpLookupPollAsync(async, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "a null result was taken on a finished request");
result = 7;
Require(sceNpLookupPollAsync(async, &result) == PollFinished && result == SignedOut, "sceNpLookupPollAsync", "the request did not finish with SIGNED_OUT");
const int sync = sceNpLookupCreateRequest(context);
StartLookup(sync);
result = 7;
Require(sceNpLookupWaitAsync(sync, &result) == 0 && result == SignedOut, "sceNpLookupWaitAsync", "a finished synchronous request was not returned");
Require(sceNpLookupWaitAsync(sync, &result) == LookupSynchronousRequest && result == 7, "sceNpLookupWaitAsync", "a synchronous request was not refused with 0x80550307");
Require(sceNpLookupPollAsync(sync, &result) == LookupSynchronousRequest && result == 7, "sceNpLookupPollAsync", "a synchronous request was not refused with 0x80550307");
const int aborted = sceNpLookupCreateAsyncRequest(context, asyncParam);
Require(sceNpLookupAbortRequest(aborted + 100) == InvalidId, "sceNpLookupAbortRequest", "an unknown request was taken");
+1 -1
View File
@@ -19,4 +19,4 @@
### Functional
- [NpLookup and NpWordFilter](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - offline only, not measured on a PS5: the argument checks and their order, the limit of 32 title contexts and 32 requests, the word filter id bases `0x01000001` and `0x02000001`, the 0x400-byte comment limit and single-use requests (`INVALID_TYPE`) follow shadPS4's [lookup](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L41-L222) and [word filter](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L705-L909) calls. `sceNpLookupNpId`, `sceNpWordFilterCensorComment` and `sceNpWordFilterSanitizeComment` then finish with `SCE_NP_ERROR_SIGNED_OUT` and write no NpId or comment; shadPS4's word filter accepts every comment as a stand-in for the server, so its result is not used
- [NpLookup and NpWordFilter waits and timeouts](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - offline, not measured on a PS5: the timeouts of `sceNpLookupSetTimeout` and `sceNpWordFilterSetTimeout` are checked and then have no effect, since no request reaches a server. A wait on a request deleted meanwhile returns `INVALID_ID`. The checks and results follow shadPS4's [lookup](https://github.com/shadps4-emu/shadPS4/blob/cc4dbd45bc10049ea8a445eb0e697de268ef4723/src/core/libraries/np/np_utility/np_utility.cpp#L120-L321) and [word filter](https://github.com/shadps4-emu/shadPS4/blob/cc4dbd45bc10049ea8a445eb0e697de268ef4723/src/core/libraries/np/np_utility/np_utility.cpp#L911-L951) calls: the lookup poll and wait take a null result and a synchronous request, the word filter ones refuse both
- [NpLookup and NpWordFilter waits and timeouts](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - IDs here carry no service: on the console, measured on firmware 13.42's module in an emulator (#10640), each ID holds its service in its high bits (Lookup requests `0x2xxxxxxx` and title contexts `0x4xxxxxxx`, word filter `0x02xxxxxx` and `0x04xxxxxx`), and `SetTimeout`, `AbortRequest`, `PollAsync` and `WaitAsync` find the object by those bits whichever service's call is made; here each call finds only its own service's IDs, which keep the ranges above. The timeouts are checked as measured and then have no effect, since no request reaches a server; the console stores them. A wait on a request deleted meanwhile returns `INVALID_ID`, unmeasured. The measured checks are followed: a null result first (`0x80550704`), a synchronous request (`0x80550307` for Lookup, `INVALID_ID` for the word filter), the timeout bounds and the 0x18-byte async parameter