diff --git a/core/libs/prx/libSceNpGameIntent/Export.cpp b/core/libs/prx/libSceNpGameIntent/Export.cpp index 751b4b1c5b..dfea590bc3 100644 --- a/core/libs/prx/libSceNpGameIntent/Export.cpp +++ b/core/libs/prx/libSceNpGameIntent/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libSceNpGameIntent/include/Api.hpp" +#include #include #include #include @@ -8,43 +9,57 @@ namespace { +constexpr int NpGameIntentErrorNotInitialized = -2141898750; constexpr int NpGameIntentErrorInvalidArgument = -2141898748; constexpr int NpGameIntentErrorIntentNotFound = -2141898746; constexpr int NpGameIntentErrorValueNotFound = -2141898745; constexpr int NpGameIntentUserIdInvalid = -1; +std::atomic_bool& NpGameIntentInitialized() { + static std::atomic_bool initialized{false}; + return initialized; +} + } extern "C" { int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize) { - if (intentData == nullptr || key == nullptr || valueBuf == nullptr || bufSize == 0) { - APS5_INVALID_ARG_EX; - } + if (!NpGameIntentInitialized()) { + return NpGameIntentErrorNotInitialized; + } + if (intentData == nullptr || key == nullptr || key[0] == '\0' || valueBuf == nullptr || bufSize == 0) { + return NpGameIntentErrorInvalidArgument; + } - valueBuf[0] = '\0'; - return NpGameIntentErrorValueNotFound; + valueBuf[0] = '\0'; + return NpGameIntentErrorValueNotFound; } int APS5_VABI sceNpGameIntentInitialize(const void* initParam) { - (void)initParam; - return 0; + (void)initParam; + NpGameIntentInitialized() = true; + return 0; } int APS5_VABI sceNpGameIntentReceiveIntent(NpGameIntentInfo* intentInfo) { - if (intentInfo == nullptr) { - APS5_INVALID_ARG_EX; - } + if (!NpGameIntentInitialized()) { + return NpGameIntentErrorNotInitialized; + } + if (intentInfo == nullptr || intentInfo->size != 0x4140) { + return NpGameIntentErrorInvalidArgument; + } - intentInfo->user_id = NpGameIntentUserIdInvalid; - std::memset(intentInfo->intent_type, 0, sizeof(intentInfo->intent_type)); - std::memset(&intentInfo->intent_data, 0, sizeof(intentInfo->intent_data)); + intentInfo->user_id = NpGameIntentUserIdInvalid; + std::memset(intentInfo->intent_type, 0, sizeof(intentInfo->intent_type)); + std::memset(&intentInfo->intent_data, 0, sizeof(intentInfo->intent_data)); - return NpGameIntentErrorIntentNotFound; + return NpGameIntentErrorIntentNotFound; } int APS5_VABI sceNpGameIntentTerminate(void) { - return 0; + NpGameIntentInitialized() = false; + return 0; } int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param) { diff --git a/core/libs/tests/GuestNpGameIntent.cpp b/core/libs/tests/GuestNpGameIntent.cpp index dd362cdad4..8e74607008 100644 --- a/core/libs/tests/GuestNpGameIntent.cpp +++ b/core/libs/tests/GuestNpGameIntent.cpp @@ -4,6 +4,7 @@ #include #include +#include #include static void Require(bool value, const char* what) { @@ -23,6 +24,8 @@ static bool Throws(TCall call) { } int main() { + constexpr int kNotInitialized = -2141898750; + constexpr int kInvalidArgument = -2141898748; constexpr int kIntentNotFound = -2141898746; constexpr int kValueNotFound = -2141898745; @@ -41,22 +44,75 @@ int main() { Require(info.size == sizeof(NpGameIntentInfo), "info size mismatch"); Require(info.user_id == -1, "info user_id mismatch"); + char preBuf[64]{}; + Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", preBuf, sizeof(preBuf)) == kNotInitialized, + "uninitialized get property string with null data must report not-initialized"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", preBuf, sizeof(preBuf)) == kNotInitialized, + "uninitialized get property string with valid data must report not-initialized"); + Require(sceNpGameIntentReceiveIntent(nullptr) == kNotInitialized, + "uninitialized receive intent with null pointer must report not-initialized"); + Require(sceNpGameIntentReceiveIntent(&info) == kNotInitialized, + "uninitialized receive intent with valid pointer must report not-initialized"); + Require(sceNpGameIntentInitialize(&initParam) == 0, "initialization failed"); - Require(sceNpGameIntentReceiveIntent(&info) == kIntentNotFound, "receive intent mismatch"); + Require(sceNpGameIntentReceiveIntent(nullptr) == kInvalidArgument, "null receive intent did not fail"); + Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "mismatched struct size must report invalid argument"); + info.size = 0; + Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "zero size must report invalid argument"); + info.size = 0x413f; + Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "0x413f size must report invalid argument"); + info.size = 0x4141; + Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "0x4141 size must report invalid argument"); + info.size = sizeof(NpGameIntentInfo); char buf[64]{}; - Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, sizeof(buf)) == kValueNotFound, + Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", buf, sizeof(buf)) == kInvalidArgument, + "null intent data did not fail"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, nullptr, buf, sizeof(buf)) == kInvalidArgument, + "null key did not fail"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "", buf, sizeof(buf)) == kInvalidArgument, + "empty key did not fail"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", nullptr, sizeof(buf)) == kInvalidArgument, + "null value buf did not fail"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, 0) == kInvalidArgument, + "zero buf size did not fail"); + + char canaryBuf[64]{}; + std::memset(canaryBuf, 'X', sizeof(canaryBuf)); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, 0) == kInvalidArgument, + "zero buf size canary call did not fail"); + Require(canaryBuf[0] == 'X', "buffer was modified on zero size error"); + Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", canaryBuf, sizeof(canaryBuf)) == kInvalidArgument, + "null data canary call did not fail"); + Require(canaryBuf[0] == 'X', "buffer was modified on null data error"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, nullptr, canaryBuf, sizeof(canaryBuf)) == kInvalidArgument, + "null key canary call did not fail"); + Require(canaryBuf[0] == 'X', "buffer was modified on null key error"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "", canaryBuf, sizeof(canaryBuf)) == kInvalidArgument, + "empty key canary call did not fail"); + Require(canaryBuf[0] == 'X', "buffer was modified on empty key error"); + + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, sizeof(canaryBuf)) == kValueNotFound, "get property value mismatch"); + Require(canaryBuf[0] == '\0', "valid call did not write terminator"); + + canaryBuf[0] = 'Y'; + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, 1) == kValueNotFound, + "bufSize 1 did not succeed"); + Require(canaryBuf[0] == '\0', "bufSize 1 did not write terminator"); Require(sceNpGameIntentVshInitialize() == 0, "vsh init failed"); Require(sceNpGameIntentVshTerminate() == 0, "vsh terminate failed"); Require(sceNpGameIntentTerminate() == 0, "terminate failed"); + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, sizeof(buf)) == kNotInitialized, + "post-terminate get property value must report not-initialized"); + Require(sceNpGameIntentReceiveIntent(&info) == kNotInitialized, + "post-terminate receive intent must report not-initialized"); + unsigned char appLauncherParam[0x28]{}; *reinterpret_cast(appLauncherParam) = 0x28; Require(sceNpAppLauncherInitialize(appLauncherParam) == 0, "app launcher init failed"); - Require(Throws([] { sceNpAppLauncherLaunchApp(); }), - "app launcher launch must throw until implemented"); Require(sceNpAppLauncherTerminate() == 0, "app launcher terminate failed"); *reinterpret_cast(appLauncherParam) = 0x10; Require(sceNpAppLauncherInitialize(appLauncherParam) == 0, diff --git a/docs/dev/debt/libSceNpGameIntent.md b/docs/dev/debt/libSceNpGameIntent.md index 622b40a2c2..cc0daa50c5 100644 --- a/docs/dev/debt/libSceNpGameIntent.md +++ b/docs/dev/debt/libSceNpGameIntent.md @@ -2,11 +2,12 @@ ### Silent stubs -- [sceNpGameIntentVshInitialize, sceNpGameIntentVshTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - VSH initialization and termination return 0 without performing shell operations, matching `sceNpGameIntentInitialize` and `sceNpGameIntentTerminate` -- [sceNpAppLauncherInitialize, sceNpAppLauncherTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - app-launcher lifecycle returns success without tracking launch state; `sceNpAppLauncherInitialize` accepts its init parameter without validation, following the silent-stub precedent of `sceNpGameIntentInitialize` — a title calls it early in boot, and returning success lets it continue +- [sceNpGameIntentVshInitialize, sceNpGameIntentVshTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - VSH initialization and termination return 0 without performing shell operations +- [sceNpAppLauncherInitialize, sceNpAppLauncherTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - app-launcher lifecycle returns success without tracking launch state; `sceNpAppLauncherInitialize` accepts its init parameter without validation — a title calls it early in boot, and returning success lets it continue ### Unknown function info +- [sceNpGameIntentReceiveIntent, sceNpGameIntentGetPropertyValueString](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - firmware 13.42 traces report `0x80553802` before `sceNpGameIntentInitialize` (precedence over argument validity), and `0x80553804` for invalid arguments (null pointer, empty key, or zero buffer size); `sceNpGameIntentTerminate` resetting initialized state is modeled lifecycle behavior rather than hardware-verified. `sceNpGameIntentReceiveIntent` reportedly requires `info->size == 0x4140` on firmware 13.42, while AnyPS5 `NpGameIntentInfo` is `0x560` bytes (matching fpPS4) and set by `sceNpGameIntentInfoInit`, so initialized objects are rejected by the receiver's measured size guard. Complete retail layout, output field offsets, and retail behavior of `sceNpGameIntentInfoInit` are unverified; expanding the type with unverified reserved bytes is not an evidence-backed solution. - [sceNpAppLauncherLaunchApp, sceNpGameIntentGetUdsNpComId, sceNpGameIntentLaunchApp, sceNpGameIntentLaunchApp2, sceNpGameIntentNotifyAppLaunched, sceNpSessionServiceGetJoinedPartySession, sceNpSessionServiceGetRunningGamePlayerSession and sceNpSessionServiceSendPlayerSessionInvitations](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpGameIntent.c). Call sites in a title's executable give a partial prototype: `sceNpAppLauncherLaunchApp` takes `(user_id, uri struct, context)` and returns int — throws because no launcher-service semantics are derivable. The rest are declared without parameters solely to resolve known imports and make no ABI claim. - [sceNpGameIntentInitParamInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L17) `SceNpGameIntentInitParam`; setting `size`, zeroing reserved bytes, and throwing on null pointer follow Sony SDK `*ParamInit` conventions but are unverified on retail hardware -- [sceNpGameIntentInfoInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L28) `SceNpGameIntentInfo`; setting `size`, `user_id = -1`, zeroing fields, and throwing on null pointer match `sceNpGameIntentReceiveIntent` defaults but are unverified on retail hardware +- [sceNpGameIntentInfoInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L28) `SceNpGameIntentInfo`; setting `size = 0x560`, `user_id = -1`, zeroing fields, and throwing on null pointer follow fpPS4 defaults, but reported firmware 13.42 behavior requires `0x4140` for `sceNpGameIntentReceiveIntent`, so the helper's `0x560` size does not match retail receiver defaults; the complete retail structure layout and retail behavior of `sceNpGameIntentInfoInit` remain unverified