diff --git a/core/libs/prx/libSceNpWebApi2/Export.cpp b/core/libs/prx/libSceNpWebApi2/Export.cpp index 806accdb3bd4..76c409ce554d 100644 --- a/core/libs/prx/libSceNpWebApi2/Export.cpp +++ b/core/libs/prx/libSceNpWebApi2/Export.cpp @@ -9,7 +9,10 @@ // The PSN web API has no service behind it: handles can be created, but requests fail. static constexpr int SCE_NP_WEBAPI2_ERROR_INVALID_ARGUMENT = static_cast(0x80553402); +static constexpr int SCE_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND = static_cast(0x80553404); static constexpr int SCE_NP_WEBAPI2_ERROR_UNAVAILABLE = static_cast(0x80553406); +static constexpr int SCE_NP_WEBAPI2_ERROR_ABORTED = static_cast(0x80553409); +static constexpr int SCE_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND = static_cast(0x8055340D); static std::atomic g_nextHandle{1}; struct NpWebApi2MemoryPoolStats { @@ -19,8 +22,22 @@ struct NpWebApi2MemoryPoolStats { int32_t reserved; }; +struct NpWebApi2PushEventHandle { + int libCtxId; + bool aborted; +}; + static std::mutex g_poolsMutex; static std::unordered_map g_pools; +static std::unordered_map g_pushEventHandles; + +static int FindPushEventHandle(int libCtxId, int handleId, NpWebApi2PushEventHandle*& handle) { + if (!g_pools.contains(libCtxId)) return SCE_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND; + const auto found = g_pushEventHandles.find(handleId); + if (found == g_pushEventHandles.end() || found->second.libCtxId != libCtxId) return SCE_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND; + handle = &found->second; + return 0; +} extern "C" { @@ -104,30 +121,38 @@ int APS5_VABI sceNpWebApi2GetMemoryPoolStats(int lib_ctx_id, NpWebApi2MemoryPool } int APS5_VABI sceNpWebApi2PushEventCreateFilter(int lib_ctx_id, int handle_id, const char* np_service_name, uint32_t np_service_label, const void* filter_param, size_t filter_param_num) { - (void)lib_ctx_id; - (void)handle_id; (void)np_service_name; (void)np_service_label; (void)filter_param; (void)filter_param_num; + std::lock_guard lock(g_poolsMutex); + NpWebApi2PushEventHandle* handle = nullptr; + if (const int result = FindPushEventHandle(lib_ctx_id, handle_id, handle); result != 0) return result; + if (handle->aborted) return SCE_NP_WEBAPI2_ERROR_ABORTED; return g_nextHandle.fetch_add(1, std::memory_order_relaxed); } int APS5_VABI sceNpWebApi2PushEventCreateHandle(int lib_ctx_id) { - (void)lib_ctx_id; - return g_nextHandle.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(g_poolsMutex); + if (!g_pools.contains(lib_ctx_id)) return SCE_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND; + const int handle_id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); + g_pushEventHandles[handle_id] = {lib_ctx_id, false}; + return handle_id; } int APS5_VABI sceNpWebApi2PushEventAbortHandle(int libCtxId, int handleId) { - (void)libCtxId; - (void)handleId; - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_poolsMutex); + NpWebApi2PushEventHandle* handle = nullptr; + if (const int result = FindPushEventHandle(libCtxId, handleId, handle); result != 0) return result; + handle->aborted = true; return 0; } int APS5_VABI sceNpWebApi2PushEventDeleteHandle(int lib_ctx_id, int handle_id) { - (void)lib_ctx_id; - (void)handle_id; + std::lock_guard lock(g_poolsMutex); + NpWebApi2PushEventHandle* handle = nullptr; + if (const int result = FindPushEventHandle(lib_ctx_id, handle_id, handle); result != 0) return result; + g_pushEventHandles.erase(handle_id); return 0; } @@ -163,6 +188,7 @@ int APS5_VABI sceNpWebApi2SendRequest(int64_t request_id, const void* data, size int APS5_VABI sceNpWebApi2Terminate(int lib_ctx_id) { std::lock_guard lock(g_poolsMutex); g_pools.erase(lib_ctx_id); + std::erase_if(g_pushEventHandles, [&](const auto& handle) { return handle.second.libCtxId == lib_ctx_id; }); return 0; } diff --git a/core/libs/tests/GuestNpWebApi2.cpp b/core/libs/tests/GuestNpWebApi2.cpp index ea167e654816..13c94e07e705 100644 --- a/core/libs/tests/GuestNpWebApi2.cpp +++ b/core/libs/tests/GuestNpWebApi2.cpp @@ -15,8 +15,16 @@ extern "C" { int APS5_VABI sceNpWebApi2Initialize(int, std::size_t); int APS5_VABI sceNpWebApi2Terminate(int); int APS5_VABI sceNpWebApi2GetMemoryPoolStats(int, NpWebApi2MemoryPoolStats*); +int APS5_VABI sceNpWebApi2PushEventCreateHandle(int); +int APS5_VABI sceNpWebApi2PushEventAbortHandle(int, int); +int APS5_VABI sceNpWebApi2PushEventDeleteHandle(int, int); +int APS5_VABI sceNpWebApi2PushEventCreateFilter(int, int, const char*, std::uint32_t, const void*, std::size_t); } +static constexpr int LibContextNotFound = static_cast(0x80553404); +static constexpr int Aborted = static_cast(0x80553409); +static constexpr int HandleNotFound = static_cast(0x8055340D); + static void Require(bool value) { if (!value) std::abort(); } template @@ -29,7 +37,32 @@ static bool Throws(TCall call) { return false; } +static void TestPushEventAbortHandle() { + const int context = sceNpWebApi2Initialize(1, 0x8000); + const int other = sceNpWebApi2Initialize(1, 0x8000); + const std::uint64_t filterParam[2]{}; + const int handle = sceNpWebApi2PushEventCreateHandle(context); + const int kept = sceNpWebApi2PushEventCreateHandle(context); + Require(handle > 0 && kept > 0 && handle != kept); + Require(sceNpWebApi2PushEventCreateFilter(context, handle, nullptr, 0, filterParam, 1) > 0); + Require(sceNpWebApi2PushEventAbortHandle(context, handle) == 0); + Require(sceNpWebApi2PushEventCreateFilter(context, handle, nullptr, 0, filterParam, 1) == Aborted); + Require(sceNpWebApi2PushEventAbortHandle(context, handle) == 0); + Require(sceNpWebApi2PushEventCreateFilter(context, kept, nullptr, 0, filterParam, 1) > 0); + Require(sceNpWebApi2PushEventAbortHandle(other, handle) == HandleNotFound); + Require(sceNpWebApi2PushEventAbortHandle(context, 0x7FFFFFFF) == HandleNotFound); + Require(sceNpWebApi2PushEventAbortHandle(0x7FFFFFFF, handle) == LibContextNotFound); + Require(sceNpWebApi2PushEventCreateHandle(0x7FFFFFFF) == LibContextNotFound); + Require(sceNpWebApi2PushEventDeleteHandle(context, handle) == 0); + Require(sceNpWebApi2PushEventAbortHandle(context, handle) == HandleNotFound); + Require(sceNpWebApi2PushEventDeleteHandle(context, handle) == HandleNotFound); + Require(sceNpWebApi2Terminate(context) == 0); + Require(sceNpWebApi2PushEventAbortHandle(context, kept) == LibContextNotFound); + Require(sceNpWebApi2Terminate(other) == 0); +} + int main() { + TestPushEventAbortHandle(); const int context = sceNpWebApi2Initialize(1, 0x20000); const int other = sceNpWebApi2Initialize(1, 0x8000); Require(context > 0 && other > 0 && context != other); diff --git a/docs/dev/debt/libSceNpWebApi2.md b/docs/dev/debt/libSceNpWebApi2.md index d42b614ee263..f3f527cdd55c 100644 --- a/docs/dev/debt/libSceNpWebApi2.md +++ b/docs/dev/debt/libSceNpWebApi2.md @@ -2,5 +2,8 @@ ### Unknown function info -- [sceNpWebApi2PushEventAbortHandle](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2, NID `1OLgvahaSco`) is a throwing stub. The two signed 32-bit handle arguments and return type follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/443f022450f6857256e780daa3d5a14029a748f7/src/core/libraries/np/np_web_api2/np_web_api2.cpp) (PS4); the PS5 signature and abort behavior are unverified. PPSA01736 imports it. - [sceNpWebApi2GetMemoryPoolStats](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2) - imported by PPSA21703's Unity PSN plugin. The signature `(int libCtxId, stats*)` and the layout of the Http2 statistics above (measured on a PS5 for Http2, Ssl and Net) are assumed for this library, not measured. The pool size is the one given to `sceNpWebApi2Initialize`, and the peak and current use are 0. The error codes are unknown, so a null output or an unknown context throws + +### Functional + +- [sceNpWebApi2PushEventAbortHandle](../../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2, NID `1OLgvahaSco`) marks a push event handle as aborted, after which `sceNpWebApi2PushEventCreateFilter` on it returns `ABORTED` (`0x80553409`); callbacks already registered are left as they are. `sceNpWebApi2PushEventCreateHandle`, `sceNpWebApi2PushEventDeleteHandle` and `sceNpWebApi2PushEventCreateFilter` keep the handles per library context and `sceNpWebApi2Terminate` drops them. The signature, the order of the checks and the codes for an unknown context (`LIB_CONTEXT_NOT_FOUND`, `0x80553404`) or handle (`HANDLE_NOT_FOUND`, `0x8055340D`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/np/np_web_api2/np_web_api2_internal.cpp#L112-L132) (PS4) and are unverified on the PS5; handle timeouts and the argument checks of `CreateFilter` are not modelled. PPSA01736 imports it