From e933285d8243fef0d4dc0117d17a84bb22980670 Mon Sep 17 00:00:00 2001 From: Matheus Lima da Silva Date: Sat, 10 Oct 2026 12:10:54 -0300 Subject: [PATCH] feat(libSceProprietaryVoiceChatHelperPrx): implement the voice chat helper lifecycle Initialize and Terminate track whether the library is initialized, and SetVoiceChatState returns success without recording anything: the host has no system voice chat for a title's own voice chat to announce itself to, and the state argument is unknown. GetVoiceChatUsageState keeps throwing. With the state argument unreadable there is no recorded usage state, and whether the console returns the state or writes it through an output pointer, and how it encodes it, is unknown. Misuse throws rather than guessing an error code, as the library's error codes are unknown. Co-Authored-By: Claude Opus 5 --- .../CMakeLists.txt | 15 +++++ .../Export.cpp | 59 +++++++++++++++---- .../tests/GuestProprietaryVoiceChatHelper.cpp | 53 +++++++++++++++++ .../libSceProprietaryVoiceChatHelperPrx.md | 5 ++ 4 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 core/libs/prx/libSceProprietaryVoiceChatHelperPrx/CMakeLists.txt create mode 100644 core/libs/tests/GuestProprietaryVoiceChatHelper.cpp create mode 100644 docs/dev/debt/libSceProprietaryVoiceChatHelperPrx.md diff --git a/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/CMakeLists.txt b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/CMakeLists.txt new file mode 100644 index 000000000..4feb3ddf2 --- /dev/null +++ b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceProprietaryVoiceChatHelperPrx SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceProprietaryVoiceChatHelperPrx PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceProprietaryVoiceChatHelperPrx) + +if(BUILD_TESTING) + add_test_executable(guest_proprietary_voice_chat_helper_tests ${LIBS_INCLUDE_DIR}/tests/GuestProprietaryVoiceChatHelper.cpp) + target_include_directories(guest_proprietary_voice_chat_helper_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_proprietary_voice_chat_helper_tests PRIVATE libSceProprietaryVoiceChatHelperPrx libc) + configure_windows_unwind(guest_proprietary_voice_chat_helper_tests) + add_test(NAME guest_proprietary_voice_chat_helper COMMAND guest_proprietary_voice_chat_helper_tests) + if(WIN32) + set_tests_properties(guest_proprietary_voice_chat_helper PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp index 882e6e92c..315ce70e7 100644 --- a/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp +++ b/core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp @@ -1,23 +1,60 @@ +#include +#include +#include + #include "prx/libc/include/General.hpp" +namespace { + +struct Helper { + std::mutex mutex; + bool initialized = false; +}; + +Helper& State() { + static Helper helper; + return helper; +} + +[[noreturn]] void Fail(const char* function, const std::string& why) { + throw std::logic_error(std::string(function) + ": " + why); +} + +void RequireInitialized(const Helper& helper, const char* function) { + if (!helper.initialized) Fail(function, "library not initialized"); +} + +} // namespace + extern "C" { -int APS5_VABI sceProprietaryVoiceChatHelperGetVoiceChatUsageState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceProprietaryVoiceChatHelperInitialize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceProprietaryVoiceChatHelperSetVoiceChatState(void) { - NotImplemented_nid_no_patch(__func__); + auto& helper = State(); + std::lock_guard lock(helper.mutex); + if (helper.initialized) Fail(__func__, "already initialized"); + helper.initialized = true; return 0; } int APS5_VABI sceProprietaryVoiceChatHelperTerminate(void) { + auto& helper = State(); + std::lock_guard lock(helper.mutex); + RequireInitialized(helper, __func__); + helper.initialized = false; + return 0; +} + +int APS5_VABI sceProprietaryVoiceChatHelperSetVoiceChatState(void) { + auto& helper = State(); + std::lock_guard lock(helper.mutex); + RequireInitialized(helper, __func__); + return 0; +} + +int APS5_VABI sceProprietaryVoiceChatHelperGetVoiceChatUsageState(void) { + auto& helper = State(); + std::lock_guard lock(helper.mutex); + RequireInitialized(helper, __func__); NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/tests/GuestProprietaryVoiceChatHelper.cpp b/core/libs/tests/GuestProprietaryVoiceChatHelper.cpp new file mode 100644 index 000000000..e5a76a3bb --- /dev/null +++ b/core/libs/tests/GuestProprietaryVoiceChatHelper.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +extern "C" { +int APS5_VABI sceProprietaryVoiceChatHelperInitialize(); +int APS5_VABI sceProprietaryVoiceChatHelperTerminate(); +int APS5_VABI sceProprietaryVoiceChatHelperSetVoiceChatState(); +int APS5_VABI sceProprietaryVoiceChatHelperGetVoiceChatUsageState(); +} + +static void Require(bool value, const char* what) { + if (value) return; + std::printf("ProprietaryVoiceChatHelper: %s\n", what); + std::abort(); +} + +template +static bool Throws(TCall call) { + try { + call(); + } catch (const TError&) { + return true; + } + return false; +} + +int main() { + Require(Throws([] { sceProprietaryVoiceChatHelperSetVoiceChatState(); }), + "setting the state before initialization did not throw"); + Require(Throws([] { sceProprietaryVoiceChatHelperGetVoiceChatUsageState(); }), + "reading the usage state before initialization did not throw"); + Require(Throws([] { sceProprietaryVoiceChatHelperTerminate(); }), + "terminating before initialization did not throw"); + + Require(sceProprietaryVoiceChatHelperInitialize() == 0, "initialization failed"); + Require(Throws([] { sceProprietaryVoiceChatHelperInitialize(); }), + "initializing twice did not throw"); + Require(sceProprietaryVoiceChatHelperSetVoiceChatState() == 0, "setting the state failed"); + Require(Throws([] { sceProprietaryVoiceChatHelperGetVoiceChatUsageState(); }), + "reading the unknown usage state did not throw"); + Require(sceProprietaryVoiceChatHelperTerminate() == 0, "termination failed"); + + Require(Throws([] { sceProprietaryVoiceChatHelperSetVoiceChatState(); }), + "setting the state after termination did not throw"); + Require(sceProprietaryVoiceChatHelperInitialize() == 0, "initialization after termination failed"); + Require(sceProprietaryVoiceChatHelperTerminate() == 0, "termination after reinitialization failed"); + + std::puts("ProprietaryVoiceChatHelper tests passed"); + return 0; +} diff --git a/docs/dev/debt/libSceProprietaryVoiceChatHelperPrx.md b/docs/dev/debt/libSceProprietaryVoiceChatHelperPrx.md new file mode 100644 index 000000000..027e2ab7d --- /dev/null +++ b/docs/dev/debt/libSceProprietaryVoiceChatHelperPrx.md @@ -0,0 +1,5 @@ +# libSceProprietaryVoiceChatHelperPrx technical debt + +### Silent stubs + +- [libSceProprietaryVoiceChatHelperPrx](../../../core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp) implements only the no-system-voice-chat path: the host has no system voice chat for a title's own voice chat to announce itself to, so `sceProprietaryVoiceChatHelperInitialize` and `sceProprietaryVoiceChatHelperTerminate` only track whether the library is initialized, and `sceProprietaryVoiceChatHelperSetVoiceChatState` returns success and does nothing: there is nothing to notify, and its state argument is unknown, so no state is recorded. `sceProprietaryVoiceChatHelperGetVoiceChatUsageState` throws, because nothing it could report is known: with the state argument unreadable there is no recorded usage state, and whether the console returns the state or writes it through an output pointer, and how it encodes it, is unknown. Initializing twice, and any call before `sceProprietaryVoiceChatHelperInitialize` or after `sceProprietaryVoiceChatHelperTerminate`, throws, as the console's error codes for this library are unknown. Whether the console checks initialization order at all is unverified