From a893c2caa277096106845304a8fa3c8354b41478 Mon Sep 17 00:00:00 2001 From: AlxcBTW <187763517+AlxcBTW@users.noreply.github.com> Date: Thu, 22 Oct 2026 00:46:24 +0200 Subject: [PATCH 1/3] feat(libSceGameLiveStreaming): answer offline setting and feedback calls locally --- .../prx/libSceGameLiveStreaming/Export.cpp | 115 +++++++++++++----- .../libSceGameLiveStreaming/include/Api.hpp | 53 +++++--- docs/dev/debt/libSceGameLiveStreaming.md | 20 +-- 3 files changed, 128 insertions(+), 60 deletions(-) diff --git a/core/libs/prx/libSceGameLiveStreaming/Export.cpp b/core/libs/prx/libSceGameLiveStreaming/Export.cpp index d9e1f9a227..ec6278d1e3 100644 --- a/core/libs/prx/libSceGameLiveStreaming/Export.cpp +++ b/core/libs/prx/libSceGameLiveStreaming/Export.cpp @@ -15,6 +15,27 @@ static constexpr int ErrorInvalidArgument = static_cast(0x80A00002); static constexpr int ErrorAlreadyInitialized = static_cast(0x80A00003); static constexpr int ErrorNotInitialized = static_cast(0x80A00004); static constexpr std::size_t ProgramInfoSize = 0x134C; +static constexpr std::size_t MaxMetadataLength = 240; +static constexpr std::size_t MaxPresetDescriptionLength = 720; +static constexpr std::size_t MaxInvitationSessionIdLength = 45; +static constexpr std::size_t MaxSocialMessageLength = 360; +static constexpr std::size_t MaxSpoilerTags = 16; +static constexpr std::size_t MaxPresetCommands = 5; + +static bool ValidFeedbackType(int type) { + return type >= 1 && type <= 3; +} + +static bool ValidString(const char* text, std::size_t maxLength) { + return text != nullptr && std::memchr(text, 0, maxLength + 1) != nullptr; +} + +static bool AllZero(const std::uint8_t* bytes, std::size_t size) { + for (std::size_t i = 0; i < size; i++) { + if (bytes[i] != 0) return false; + } + return true; +} static_assert(sizeof(GameLiveStreamingStatus2) == 72); @@ -87,13 +108,15 @@ int APS5_VABI sceGameLiveStreamingScreenSetMode(void) { return 0; } -int APS5_VABI sceGameLiveStreamingPermitLiveStreaming(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingPermitLiveStreaming(bool permit) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; return 0; } -int APS5_VABI sceGameLiveStreamingSetMaxBitrate(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingSetMaxBitrate(std::uint32_t bitrate) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; return 0; } @@ -102,68 +125,98 @@ int APS5_VABI sceGameLiveStreamingGetCurrentStatus(GameLiveStreamingStatus* stat return 0; } -int APS5_VABI sceGameLiveStreamingEnableSocialFeedback(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingEnableSocialFeedback(bool enable) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; return 0; } -int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessagesCount(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessagesCount(int type, std::uint32_t* count) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (count == nullptr || !ValidFeedbackType(type)) return ErrorInvalidArgument; + *count = 0; return 0; } -int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessages(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessages(int type, size_t capacity, GameLiveStreamingFeedbackMessage* messages, size_t* acquired) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (messages == nullptr || acquired == nullptr || !ValidFeedbackType(type)) return ErrorInvalidArgument; + *acquired = 0; return 0; } -int APS5_VABI sceGameLiveStreamingPermitServerSideRecording(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingPermitServerSideRecording(bool permit) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; return 0; } -int APS5_VABI sceGameLiveStreamingSetMetadata(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingSetMetadata(const char* metadata) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (!ValidString(metadata, MaxMetadataLength)) return ErrorInvalidArgument; return 0; } -int APS5_VABI sceGameLiveStreamingSetSpoilerTag(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingSetSpoilerTag(size_t count, const GameLiveStreamingSpoilerTag* tags) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (tags == nullptr || count > MaxSpoilerTags) return ErrorInvalidArgument; + for (size_t i = 0; i < count; i++) { + if (!AllZero(tags[i].reserved, sizeof(tags[i].reserved))) return ErrorInvalidArgument; + } return 0; } int APS5_VABI sceGameLiveStreamingClearSpoilerTag(void) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; return 0; } -int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommands(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommands(size_t count, const GameLiveStreamingPresetCommand* commands) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (commands == nullptr || count > MaxPresetCommands) return ErrorInvalidArgument; + for (size_t i = 0; i < count; i++) { + if (!AllZero(commands[i].reserved, sizeof(commands[i].reserved))) return ErrorInvalidArgument; + } return 0; } int APS5_VABI sceGameLiveStreamingClearPresetSocialFeedbackCommands(void) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; return 0; } -int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription(const char* description) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (!ValidString(description, MaxPresetDescriptionLength)) return ErrorInvalidArgument; return 0; } -int APS5_VABI sceGameLiveStreamingClearSocialFeedbackMessages(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingClearSocialFeedbackMessages(int type) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (!ValidFeedbackType(type)) return ErrorInvalidArgument; return 0; } -int APS5_VABI sceGameLiveStreamingPostSocialMessage(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingPostSocialMessage(const char* message) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (!ValidString(message, MaxSocialMessageLength)) return ErrorInvalidArgument; return 0; } -int APS5_VABI sceGameLiveStreamingSetCameraFrameSetting(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingSetCameraFrameSetting(const GameLiveStreamingCameraFrameSetting* setting) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (setting == nullptr || !AllZero(setting->reserved, sizeof(setting->reserved))) return ErrorInvalidArgument; return 0; } @@ -177,8 +230,10 @@ int APS5_VABI sceGameLiveStreamingSetDefaultServiceProviderPermission(void) { return 0; } -int APS5_VABI sceGameLiveStreamingSetInvitationSessionId(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingSetInvitationSessionId(const char* sessionId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return ErrorNotInitialized; + if (!ValidString(sessionId, MaxInvitationSessionIdLength)) return ErrorInvalidArgument; return 0; } diff --git a/core/libs/prx/libSceGameLiveStreaming/include/Api.hpp b/core/libs/prx/libSceGameLiveStreaming/include/Api.hpp index 28b744e492..9f69bbc0fc 100644 --- a/core/libs/prx/libSceGameLiveStreaming/include/Api.hpp +++ b/core/libs/prx/libSceGameLiveStreaming/include/Api.hpp @@ -6,6 +6,31 @@ struct GameLiveStreamingProgramInfo; +struct GameLiveStreamingSpoilerTag { + char text[193]; + std::uint8_t reserved[31]; +}; + +static_assert(offsetof(GameLiveStreamingSpoilerTag, reserved) == 193); +static_assert(sizeof(GameLiveStreamingSpoilerTag) == 224); + +struct GameLiveStreamingPresetCommand { + std::uint8_t body[197]; + std::uint8_t reserved[31]; +}; + +static_assert(offsetof(GameLiveStreamingPresetCommand, reserved) == 197); +static_assert(sizeof(GameLiveStreamingPresetCommand) == 228); + +struct GameLiveStreamingCameraFrameSetting { + std::uint8_t head[9]; + std::uint8_t reserved[31]; +}; + +static_assert(offsetof(GameLiveStreamingCameraFrameSetting, reserved) == 9); + +struct GameLiveStreamingFeedbackMessage; + struct GameLiveStreamingStatus { std::uint8_t opaque00[12]; std::uint8_t reserved[60]; @@ -38,25 +63,25 @@ int APS5_VABI sceGameLiveStreamingScreenCloseSeparateMode(void); int APS5_VABI sceGameLiveStreamingScreenConfigureSeparateMode(void); int APS5_VABI sceGameLiveStreamingScreenOpenSeparateMode(void); int APS5_VABI sceGameLiveStreamingScreenSetMode(void); -int APS5_VABI sceGameLiveStreamingPermitLiveStreaming(void); -int APS5_VABI sceGameLiveStreamingSetMaxBitrate(void); +int APS5_VABI sceGameLiveStreamingPermitLiveStreaming(bool permit); +int APS5_VABI sceGameLiveStreamingSetMaxBitrate(std::uint32_t bitrate); int APS5_VABI sceGameLiveStreamingGetCurrentStatus(GameLiveStreamingStatus* status); -int APS5_VABI sceGameLiveStreamingEnableSocialFeedback(void); -int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessagesCount(void); -int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessages(void); -int APS5_VABI sceGameLiveStreamingPermitServerSideRecording(void); -int APS5_VABI sceGameLiveStreamingSetMetadata(void); -int APS5_VABI sceGameLiveStreamingSetSpoilerTag(void); +int APS5_VABI sceGameLiveStreamingEnableSocialFeedback(bool enable); +int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessagesCount(int type, std::uint32_t* count); +int APS5_VABI sceGameLiveStreamingGetSocialFeedbackMessages(int type, size_t capacity, GameLiveStreamingFeedbackMessage* messages, size_t* acquired); +int APS5_VABI sceGameLiveStreamingPermitServerSideRecording(bool permit); +int APS5_VABI sceGameLiveStreamingSetMetadata(const char* metadata); +int APS5_VABI sceGameLiveStreamingSetSpoilerTag(size_t count, const GameLiveStreamingSpoilerTag* tags); int APS5_VABI sceGameLiveStreamingClearSpoilerTag(void); -int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommands(void); +int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommands(size_t count, const GameLiveStreamingPresetCommand* commands); int APS5_VABI sceGameLiveStreamingClearPresetSocialFeedbackCommands(void); -int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription(void); -int APS5_VABI sceGameLiveStreamingClearSocialFeedbackMessages(void); -int APS5_VABI sceGameLiveStreamingPostSocialMessage(void); -int APS5_VABI sceGameLiveStreamingSetCameraFrameSetting(void); +int APS5_VABI sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription(const char* description); +int APS5_VABI sceGameLiveStreamingClearSocialFeedbackMessages(int type); +int APS5_VABI sceGameLiveStreamingPostSocialMessage(const char* message); +int APS5_VABI sceGameLiveStreamingSetCameraFrameSetting(const GameLiveStreamingCameraFrameSetting* setting); int APS5_VABI sceGameLiveStreamingSetServiceProviderPermission(void); int APS5_VABI sceGameLiveStreamingSetDefaultServiceProviderPermission(void); -int APS5_VABI sceGameLiveStreamingSetInvitationSessionId(void); +int APS5_VABI sceGameLiveStreamingSetInvitationSessionId(const char* sessionId); int APS5_VABI sceGameLiveStreamingStartGenerateStandbyScreenResource(void); int APS5_VABI sceGameLiveStreamingStopGenerateStandbyScreenResource(void); int APS5_VABI sceGameLiveStreamingRegisterCallback(void); diff --git a/docs/dev/debt/libSceGameLiveStreaming.md b/docs/dev/debt/libSceGameLiveStreaming.md index c0cc7bcb4a..b0aacd6a98 100644 --- a/docs/dev/debt/libSceGameLiveStreaming.md +++ b/docs/dev/debt/libSceGameLiveStreaming.md @@ -7,6 +7,10 @@ - [sceGameLiveStreamingGetCurrentStatus2](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - the supplied module confirms the 72-byte layout and inactive status (user -1, remaining bytes zero). Live service counters, active-user changes and broadcast-resolution conversion are unimplemented. In the 25 instructions after their one call, PPSA01494, PPSA30839 and PPSA31162 read `isOnAir` (the byte at 4) and `spectatorCounts` (the word at 8), so those two are the fields these titles act on; here they stay false and 0. - [sceGameLiveStreamingGetProgramInfo](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - the PS5 13.60 module clears the 0x134C-byte program info, asks the system service for its first 0xFD4 bytes and returns `0x80A00002` for a null pointer and `0x80A00004` before initialization. With nothing broadcast here the info stays zero, which assumes the service reports an empty program when nothing is on air; the field layout is unknown. Two titles (PPSA03001 at 0x1ad86b4, PPSA07426 at 0xe89594) disassembled for this entry pass the address of a stack buffer with no store into it between the start of the block and the call; for a result other than 0 they do not read it, for 0 they copy three text fields at offsets 0, 0x802 and 0xC03, and they do not tell `0x80A00002` from `0x80A00004`. The buffer size is not shown. +### Silent stubs + +- [sceGameLiveStreamingPermitLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - this and fifteen related setting, feedback and message calls (PermitServerSideRecording, SetMaxBitrate, EnableSocialFeedback, SetMetadata, Set/ClearSpoilerTag, Set/ClearPresetSocialFeedbackCommands, ...Description, SetCameraFrameSetting, SetInvitationSessionId, Get/ClearSocialFeedbackMessages, GetSocialFeedbackMessagesCount, PostSocialMessage) validate their arguments locally and store nothing, as no broadcast is ever on air. Order: not initialized (`0x80A00004`), then `0x80A00002` for a null pointer (including a null array with count 0 for SetSpoilerTag and a null `messages` with capacity 0), a feedback type outside 1-3, a string longer than 240 (SetMetadata), 720 (preset description), 45 (invitation session id) or 360 (PostSocialMessage) characters, more than 16 spoiler tags or 5 preset commands, and a non-zero reserved tail (31 bytes at offset 193 of the 224-byte spoiler tag, at offset 197 of the 228-byte preset command, at offset 9 of the camera setting). These checks and layouts come from a report on this pull request that ran each function of the firmware 13.42 `libSceGameLiveStreaming.sprx` under an emulator with the service call stubbed; the order between the argument checks, the camera setting size and the preset and message field layouts beyond the reserved tails are not measured. What the service returns after validation is not measured on a PS5: the calls return 0 and queries report zero messages. + ### Unknown function info - [sceGameLiveStreamingScreenInitialize](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature @@ -16,25 +20,9 @@ - [sceGameLiveStreamingScreenConfigureSeparateMode](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingScreenOpenSeparateMode](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingScreenSetMode](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingPermitLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingSetMaxBitrate](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingGetCurrentStatus](../../../core/libs/prx/libSceGameLiveStreaming/include/Api.hpp) - still throws; the meaning, division and SDK types of the first 12 bytes remain unknown. In the supplied `libSceGameLiveStreaming.sprx`, export `CoPMx369EqM` at 0x1810 takes one output pointer, clears 72 bytes, obtains service command 9's status, copies its first 12 bytes and clears bytes 0x0C..0x47. `GameLiveStreamingStatus` exposes this byte storage without assuming the `GetCurrentStatus2` field layout or SDK alignment. The decompiler's extra SIMD parameter is not an input: the assembly initializes the register before use. -- [sceGameLiveStreamingEnableSocialFeedback](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingGetSocialFeedbackMessagesCount](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingGetSocialFeedbackMessages](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingPermitServerSideRecording](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingSetMetadata](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingSetSpoilerTag](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingClearSpoilerTag](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingSetPresetSocialFeedbackCommands](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingClearPresetSocialFeedbackCommands](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingClearSocialFeedbackMessages](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingPostSocialMessage](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingSetCameraFrameSetting](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingSetServiceProviderPermission](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingSetDefaultServiceProviderPermission](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature -- [sceGameLiveStreamingSetInvitationSessionId](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingStartGenerateStandbyScreenResource](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingStopGenerateStandbyScreenResource](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature - [sceGameLiveStreamingRegisterCallback](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - unknown signature From ab45baa0ab7bd8864bc02528bac21c94d3f9bb7b Mon Sep 17 00:00:00 2001 From: AlxcBTW <187763517+AlxcBTW@users.noreply.github.com> Date: Thu, 22 Oct 2026 00:49:15 +0200 Subject: [PATCH 2/3] test(libSceGameLiveStreaming): cover lifecycle order and null arguments of offline calls --- core/libs/tests/GuestGameLiveStreaming.cpp | 165 +++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/core/libs/tests/GuestGameLiveStreaming.cpp b/core/libs/tests/GuestGameLiveStreaming.cpp index 7d641c4e23..cb973dfdac 100644 --- a/core/libs/tests/GuestGameLiveStreaming.cpp +++ b/core/libs/tests/GuestGameLiveStreaming.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include static_assert(sizeof(GameLiveStreamingStatus2) == 72); @@ -26,6 +27,163 @@ bool EnableThrowsNotInitialized(bool isEnableLiveStreaming) { return false; } +constexpr int kInvalidArgument = static_cast(0x80A00002); + +GameLiveStreamingFeedbackMessage* AsMessages(std::array& buffer) { + return reinterpret_cast(buffer.data()); +} + +void CheckLifecycleFirst() { + constexpr int notInitialized = static_cast(0x80A00004); + GameLiveStreamingSpoilerTag tag{}; + GameLiveStreamingPresetCommand preset{}; + GameLiveStreamingCameraFrameSetting camera{}; + std::array messages{}; + std::uint32_t count = 0x55; + std::size_t acquired = 0x55; + Require(sceGameLiveStreamingPermitLiveStreaming(true) == notInitialized); + Require(sceGameLiveStreamingPermitServerSideRecording(false) == notInitialized); + Require(sceGameLiveStreamingSetMaxBitrate(6000) == notInitialized); + Require(sceGameLiveStreamingEnableSocialFeedback(true) == notInitialized); + Require(sceGameLiveStreamingSetMetadata(nullptr) == notInitialized); + Require(sceGameLiveStreamingSetMetadata("metadata") == notInitialized); + Require(sceGameLiveStreamingSetSpoilerTag(0, nullptr) == notInitialized); + Require(sceGameLiveStreamingSetSpoilerTag(1, &tag) == notInitialized); + Require(sceGameLiveStreamingClearSpoilerTag() == notInitialized); + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(0, nullptr) == notInitialized); + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(1, &preset) == notInitialized); + Require(sceGameLiveStreamingClearPresetSocialFeedbackCommands() == notInitialized); + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription(nullptr) == notInitialized); + Require(sceGameLiveStreamingSetCameraFrameSetting(nullptr) == notInitialized); + Require(sceGameLiveStreamingSetCameraFrameSetting(&camera) == notInitialized); + Require(sceGameLiveStreamingSetInvitationSessionId(nullptr) == notInitialized); + Require(sceGameLiveStreamingGetSocialFeedbackMessagesCount(0, nullptr) == notInitialized); + Require(sceGameLiveStreamingGetSocialFeedbackMessagesCount(1, &count) == notInitialized); + Require(count == 0x55); + Require(sceGameLiveStreamingGetSocialFeedbackMessages(0, 1, nullptr, nullptr) == notInitialized); + Require(sceGameLiveStreamingGetSocialFeedbackMessages(1, 1, AsMessages(messages), &acquired) == notInitialized); + Require(acquired == 0x55); + Require(sceGameLiveStreamingClearSocialFeedbackMessages(0) == notInitialized); + Require(sceGameLiveStreamingClearSocialFeedbackMessages(1) == notInitialized); + Require(sceGameLiveStreamingPostSocialMessage(nullptr) == notInitialized); + Require(sceGameLiveStreamingPostSocialMessage("message") == notInitialized); +} + +void CheckString(int (APS5_VABI *call)(const char*), std::size_t limit) { + Require(call(nullptr) == kInvalidArgument); + Require(call("") == 0); + Require(call(std::string(limit, 'a').c_str()) == 0); + Require(call(std::string(limit + 1, 'a').c_str()) == kInvalidArgument); + Require(call(std::string(limit + 100, 'a').c_str()) == kInvalidArgument); +} + +void CheckStrings() { + CheckString(sceGameLiveStreamingSetMetadata, 240); + CheckString(sceGameLiveStreamingSetPresetSocialFeedbackCommandsDescription, 720); + CheckString(sceGameLiveStreamingSetInvitationSessionId, 45); + CheckString(sceGameLiveStreamingPostSocialMessage, 360); +} + +void CheckFeedbackTypes() { + std::array messages{}; + for (const int type : {-1, 0, 4, 5, 0x7fffffff}) { + std::uint32_t count = 0xdeadbeef; + Require(sceGameLiveStreamingGetSocialFeedbackMessagesCount(type, &count) == kInvalidArgument); + Require(count == 0xdeadbeef); + std::size_t acquired = 0xdeadbeef; + Require(sceGameLiveStreamingGetSocialFeedbackMessages(type, 1, AsMessages(messages), &acquired) == kInvalidArgument); + Require(acquired == 0xdeadbeef); + Require(sceGameLiveStreamingClearSocialFeedbackMessages(type) == kInvalidArgument); + } + for (const int type : {1, 2, 3}) { + std::uint32_t count = 0xdeadbeef; + Require(sceGameLiveStreamingGetSocialFeedbackMessagesCount(type, &count) == 0); + Require(count == 0); + messages.fill(0xaa); + std::size_t acquired = 0xdeadbeef; + Require(sceGameLiveStreamingGetSocialFeedbackMessages(type, 1, AsMessages(messages), &acquired) == 0); + Require(acquired == 0); + for (const std::uint8_t byte : messages) Require(byte == 0xaa); + acquired = 0xdeadbeef; + Require(sceGameLiveStreamingGetSocialFeedbackMessages(type, 0, AsMessages(messages), &acquired) == 0); + Require(acquired == 0); + Require(sceGameLiveStreamingClearSocialFeedbackMessages(type) == 0); + } + std::size_t acquired = 0xdeadbeef; + Require(sceGameLiveStreamingGetSocialFeedbackMessages(1, 0, nullptr, &acquired) == kInvalidArgument); + Require(sceGameLiveStreamingGetSocialFeedbackMessages(1, 1, nullptr, &acquired) == kInvalidArgument); + Require(sceGameLiveStreamingGetSocialFeedbackMessages(1, 1, AsMessages(messages), nullptr) == kInvalidArgument); + Require(acquired == 0xdeadbeef); + Require(sceGameLiveStreamingGetSocialFeedbackMessagesCount(1, nullptr) == kInvalidArgument); +} + +void CheckArrays() { + GameLiveStreamingSpoilerTag tags[17]{}; + Require(sceGameLiveStreamingSetSpoilerTag(0, nullptr) == kInvalidArgument); + Require(sceGameLiveStreamingSetSpoilerTag(1, nullptr) == kInvalidArgument); + Require(sceGameLiveStreamingSetSpoilerTag(0, tags) == 0); + Require(sceGameLiveStreamingSetSpoilerTag(1, tags) == 0); + Require(sceGameLiveStreamingSetSpoilerTag(16, tags) == 0); + Require(sceGameLiveStreamingSetSpoilerTag(17, tags) == kInvalidArgument); + Require(sceGameLiveStreamingSetSpoilerTag(0x1000, tags) == kInvalidArgument); + for (const std::size_t index : {0u, 15u}) { + for (const std::size_t byte : {0u, 15u, 30u}) { + tags[index].reserved[byte] = 1; + Require(sceGameLiveStreamingSetSpoilerTag(16, tags) == kInvalidArgument); + Require(sceGameLiveStreamingSetSpoilerTag(index, tags) == 0); + tags[index].reserved[byte] = 0; + } + } + tags[16].reserved[0] = 1; + Require(sceGameLiveStreamingSetSpoilerTag(16, tags) == 0); + tags[0].text[192] = 'x'; + Require(sceGameLiveStreamingSetSpoilerTag(1, tags) == 0); + Require(sceGameLiveStreamingClearSpoilerTag() == 0); + Require(sceGameLiveStreamingClearSpoilerTag() == 0); + + GameLiveStreamingPresetCommand presets[6]{}; + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(0, nullptr) == kInvalidArgument); + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(1, nullptr) == kInvalidArgument); + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(0, presets) == 0); + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(5, presets) == 0); + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(6, presets) == kInvalidArgument); + for (const std::size_t index : {0u, 4u}) { + for (const std::size_t byte : {0u, 30u}) { + presets[index].reserved[byte] = 1; + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(5, presets) == kInvalidArgument); + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(index, presets) == 0); + presets[index].reserved[byte] = 0; + } + } + presets[5].reserved[0] = 1; + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(5, presets) == 0); + presets[0].body[196] = 1; + Require(sceGameLiveStreamingSetPresetSocialFeedbackCommands(5, presets) == 0); + Require(sceGameLiveStreamingClearPresetSocialFeedbackCommands() == 0); + Require(sceGameLiveStreamingClearPresetSocialFeedbackCommands() == 0); + + GameLiveStreamingCameraFrameSetting camera{}; + Require(sceGameLiveStreamingSetCameraFrameSetting(nullptr) == kInvalidArgument); + Require(sceGameLiveStreamingSetCameraFrameSetting(&camera) == 0); + camera.head[8] = 1; + Require(sceGameLiveStreamingSetCameraFrameSetting(&camera) == 0); + camera.head[8] = 0; + for (const std::size_t byte : {0u, 15u, 30u}) { + camera.reserved[byte] = 1; + Require(sceGameLiveStreamingSetCameraFrameSetting(&camera) == kInvalidArgument); + camera.reserved[byte] = 0; + } +} + +void CheckScalars() { + for (const bool value : {true, false}) { + Require(sceGameLiveStreamingPermitLiveStreaming(value) == 0); + Require(sceGameLiveStreamingPermitServerSideRecording(value) == 0); + Require(sceGameLiveStreamingEnableSocialFeedback(value) == 0); + } + for (const std::uint32_t bitrate : {0u, 6000u, 0xffffffffu}) Require(sceGameLiveStreamingSetMaxBitrate(bitrate) == 0); +} + } int main() { @@ -35,6 +193,7 @@ int main() { Require(sceGameLiveStreamingTerminate() == notInitialized); Require(sceGameLiveStreamingGetCurrentStatus2(nullptr) == notInitialized); Require(EnableThrowsNotInitialized(true)); + CheckLifecycleFirst(); for (const std::size_t size : {0u, 1u, 0x3fffu, 0x4001u, 0x100000u}) Require(sceGameLiveStreamingInitialize(size) == invalidArgument); Require(sceGameLiveStreamingInitialize(0x4000) == 0); Require(sceGameLiveStreamingEnableLiveStreaming(true) == 0); @@ -66,8 +225,14 @@ int main() { Require(programInfo[i] == (i < 0x134C ? 0 : 0x7f)); } + CheckScalars(); + CheckStrings(); + CheckFeedbackTypes(); + CheckArrays(); + Require(sceGameLiveStreamingEnableLiveStreaming(false) == 0); Require(sceGameLiveStreamingTerminate() == 0); + CheckLifecycleFirst(); Require(EnableThrowsNotInitialized(false)); std::memset(&status, 0x7f, sizeof(status)); Require(sceGameLiveStreamingGetCurrentStatus2(&status) == notInitialized); From bd19a7e5612cd7988d7e33247951f861c1a46968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0?= Date: Wed, 28 Oct 2026 13:45:33 +0100 Subject: [PATCH 3/3] docs(libSceGameLiveStreaming): shorten silent stub entry --- docs/dev/debt/libSceGameLiveStreaming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/libSceGameLiveStreaming.md b/docs/dev/debt/libSceGameLiveStreaming.md index b0aacd6a98..c025ffb571 100644 --- a/docs/dev/debt/libSceGameLiveStreaming.md +++ b/docs/dev/debt/libSceGameLiveStreaming.md @@ -9,7 +9,7 @@ ### Silent stubs -- [sceGameLiveStreamingPermitLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - this and fifteen related setting, feedback and message calls (PermitServerSideRecording, SetMaxBitrate, EnableSocialFeedback, SetMetadata, Set/ClearSpoilerTag, Set/ClearPresetSocialFeedbackCommands, ...Description, SetCameraFrameSetting, SetInvitationSessionId, Get/ClearSocialFeedbackMessages, GetSocialFeedbackMessagesCount, PostSocialMessage) validate their arguments locally and store nothing, as no broadcast is ever on air. Order: not initialized (`0x80A00004`), then `0x80A00002` for a null pointer (including a null array with count 0 for SetSpoilerTag and a null `messages` with capacity 0), a feedback type outside 1-3, a string longer than 240 (SetMetadata), 720 (preset description), 45 (invitation session id) or 360 (PostSocialMessage) characters, more than 16 spoiler tags or 5 preset commands, and a non-zero reserved tail (31 bytes at offset 193 of the 224-byte spoiler tag, at offset 197 of the 228-byte preset command, at offset 9 of the camera setting). These checks and layouts come from a report on this pull request that ran each function of the firmware 13.42 `libSceGameLiveStreaming.sprx` under an emulator with the service call stubbed; the order between the argument checks, the camera setting size and the preset and message field layouts beyond the reserved tails are not measured. What the service returns after validation is not measured on a PS5: the calls return 0 and queries report zero messages. +- [sceGameLiveStreamingPermitLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - this and fifteen related setting, feedback and message calls (see `Api.hpp`) validate their arguments and store nothing, as no broadcast is ever on air. The checks (not initialized, null pointers, feedback type 1-3, string lengths 240/720/45/360, at most 16 spoiler tags and 5 preset commands, zero reserved tails in the 224-byte spoiler tag, 228-byte preset command and camera setting) come from a report on #9524 that ran each function of the firmware 13.42 module with the service call stubbed. The order between argument checks, the camera setting size and the other struct fields are not measured, nor what the service returns after validation: the calls return 0 and queries report zero messages. ### Unknown function info