From 87ee82410ba794b4bf2479822af774ca561c83d4 Mon Sep 17 00:00:00 2001 From: lorkydey Date: Tue, 13 Oct 2026 04:05:03 +0200 Subject: [PATCH] feat(audioout2): report supported user attributes --- .../libSceAudioOut/src/AudioOut2System.cpp | 5 -- .../prx/libSceAudioOut/src/AudioOut2User.cpp | 29 +++++++++-- .../tests/AudioOut2PortLayouts.cpp | 51 +++++++++++++++++++ docs/dev/debt/libSceAudioOut.md | 2 +- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp index dff796528..955cafe1f 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp @@ -29,11 +29,6 @@ int APS5_VABI sceAudioOut2SetSystemDebugState(const AudioOut2SystemDebugStatePar return 0; } -int APS5_VABI sceAudioOut2UserGetSupportedAttributes(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAudioOut2EnableChat(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp index a139d2262..a8c42717b 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp @@ -1,23 +1,44 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -#include +#include +#include +#include +#include static constexpr int SCE_AUDIO_OUT2_ERROR_INVALID_ARGUMENT = static_cast(0x80260502); -static std::atomic g_nextUser{1}; +static std::mutex g_usersLock; +static AudioOut2UserHandle g_nextUser = 1; +static std::unordered_set g_users; extern "C" { int APS5_VABI sceAudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* handle) { (void)user_id; if (!handle) return SCE_AUDIO_OUT2_ERROR_INVALID_ARGUMENT; - *handle = g_nextUser.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(g_usersLock); + if (g_nextUser == std::numeric_limits::max()) + throw std::runtime_error("AudioOut2: user handles exhausted"); + g_users.insert(g_nextUser); + *handle = g_nextUser++; return 0; } int APS5_VABI sceAudioOut2UserDestroy(AudioOut2UserHandle handle) { - (void)handle; + std::lock_guard lock(g_usersLock); + g_users.erase(handle); + return 0; +} + +int APS5_VABI sceAudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle, uint32_t* contextAttributes, uint32_t* portAttributes) { + if (!contextAttributes || !portAttributes) + throw std::runtime_error("sceAudioOut2UserGetSupportedAttributes: null output"); + std::lock_guard lock(g_usersLock); + if (!g_users.contains(handle)) + throw std::runtime_error("sceAudioOut2UserGetSupportedAttributes: unknown user handle"); + *contextAttributes = 0; + *portAttributes = (1u << 0) | (1u << 1); return 0; } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp index bf6c33e17..1cad3a742 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortLayouts.cpp @@ -9,10 +9,15 @@ #include #include #include +#include #include +#include #include extern "C" { +int APS5_VABI sceAudioOut2UserCreate(std::uint32_t, AudioOut2UserHandle*); +int APS5_VABI sceAudioOut2UserDestroy(AudioOut2UserHandle); +int APS5_VABI sceAudioOut2UserGetSupportedAttributes(AudioOut2UserHandle, std::uint32_t*, std::uint32_t*); int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam*); int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*); int APS5_VABI sceAudioOut2ContextDestroy(AudioOut2ContextHandle); @@ -48,6 +53,51 @@ constexpr float masterGain = 0.5f; constexpr float fold = 0.70710678f; constexpr float tolerance = 1e-5f; +void TestUserAttributes() { + struct Output { + std::uint32_t before = 0x12345678u; + std::uint32_t value = 0xFFFFFFFFu; + std::uint32_t after = 0x87654321u; + } context, port; + AudioOut2UserHandle first = 0; + AudioOut2UserHandle second = 0; + Require(sceAudioOut2UserCreate(1, &first) == 0); + Require(sceAudioOut2UserCreate(2, &second) == 0 && second != first); + Require(sceAudioOut2UserGetSupportedAttributes(first, &context.value, &port.value) == 0); + Require(context.value == 0 && port.value == ((1u << attributeData) | (1u << attributeVolume))); + Require(context.before == 0x12345678u && context.after == 0x87654321u); + Require(port.before == 0x12345678u && port.after == 0x87654321u); + const auto rejects = [&](AudioOut2UserHandle handle, bool nullContext, bool nullPort) { + context.value = port.value = 0xFFFFFFFFu; + bool threw = false; + try { + sceAudioOut2UserGetSupportedAttributes(handle, nullContext ? nullptr : &context.value, nullPort ? nullptr : &port.value); + } catch (const std::runtime_error& error) { + threw = std::string(error.what()).find("sceAudioOut2UserGetSupportedAttributes") != std::string::npos; + } + Require(threw && context.value == 0xFFFFFFFFu && port.value == 0xFFFFFFFFu); + }; + rejects(first, true, false); + rejects(first, false, true); + rejects(first, true, true); + rejects(0, false, false); + rejects(std::numeric_limits::max(), false, false); + Require(sceAudioOut2UserDestroy(first) == 0); + rejects(first, false, false); + std::thread([&] { + Require(sceAudioOut2UserGetSupportedAttributes(second, &context.value, &port.value) == 0); + }).join(); + Require(context.value == 0 && port.value == 3); + AudioOut2UserHandle next = 0; + Require(sceAudioOut2UserCreate(1, &next) == 0 && next != first && next != second); + rejects(first, false, false); + Require(sceAudioOut2UserGetSupportedAttributes(next, &context.value, &port.value) == 0); + Require(context.value == 0 && port.value == 3); + Require(sceAudioOut2UserDestroy(next) == 0 && sceAudioOut2UserDestroy(second) == 0); + Require(context.before == 0x12345678u && context.after == 0x87654321u); + Require(port.before == 0x12345678u && port.after == 0x87654321u); +} + struct Layout { std::uint32_t channels; std::vector left; @@ -282,6 +332,7 @@ void TestMeasuredMainPortFormats() { } int main() { + TestUserAttributes(); SetEnvironment("SDL_AUDIODRIVER", "disk"); for (const Layout* layout : {&mono, &stereo, &surround71, &surround714}) { TestFloatLayout(*layout); diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index 893920039..2a2c811d6 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -2,7 +2,7 @@ ### Unknown function info -- PPSA23566 imports declared without parameters, signatures unknown: [sceAudioOut2UserGetSupportedAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp), [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp), [sce::Json::MemAllocator::notifyError](../../../core/libs/prx/libSceJson2/Export.cpp), [sceNpCommerceDialogOpen2](../../../core/libs/prx/libSceNpCommerce/Export.cpp), [sceProprietaryVoiceChatHelper{Initialize,Terminate,SetVoiceChatState,GetVoiceChatUsageState}](../../../core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp), [sceVoice{GetResourceInfo,ResetPort,EnableChat,DisableChat}](../../../core/libs/prx/libSceVoice/Export.cpp). Names from the shadPS4 aerolib NID list +- [sceAudioOut2UserGetSupportedAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2User.cpp) (libSceAudioOut) - the user handle and two uint32 output pointers follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp), not a console measurement. The masks describe this runtime: no context attributes and port attributes PCM (bit 0) and gain (bit 1). Null outputs and unknown or destroyed user handles throw; console validation order and error values are unknown. Users are tracked for this query, but port ownership and busy-user destruction are not implemented. The other PPSA23566 imports remain declared without parameters, with unknown signatures: [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp), [sce::Json::MemAllocator::notifyError](../../../core/libs/prx/libSceJson2/Export.cpp), [sceNpCommerceDialogOpen2](../../../core/libs/prx/libSceNpCommerce/Export.cpp), [sceProprietaryVoiceChatHelper{Initialize,Terminate,SetVoiceChatState,GetVoiceChatUsageState}](../../../core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp), [sceVoice{GetResourceInfo,ResetPort,EnableChat,DisableChat}](../../../core/libs/prx/libSceVoice/Export.cpp). Names from the shadPS4 aerolib NID list - PPSA28416 imports declared without parameters, signatures unknown: [sceAudioOut2EnableChat](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp), imported by its bundled `party-ps5-shipping.prx`. Name from the shadPS4 aerolib NID list - [sceAudioOutExClose, sceAudioOutExConfigureOutput, sceAudioOutExOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA99505, which fails to load without them; added without an implementation and declared without parameters. PPSA99505 calls them only for its HDMI bitstream output, an option that is off by default, to read the formats the TV or receiver lists, switch the HDMI output mode and open a port for IEC 61937 bursts ([Jelly5](https://github.com/02dnot/Jelly5/blob/552b866d89b2a58b27f7f7b0484dc743ce235526/app/src/jelly5_bitstream.cpp#L110-L179)); [EVO-PLAYER-PS5](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/da0c0451333bab82e79fffd2de4718e917f9dcb0/projects/evoplayer/core/src/audio/evo_pt.cpp) also calls `sceAudioOutExOpen`, `sceAudioOutExConfigureOutput` and `sceAudioOutExClose` for HDMI bitstream output. The two declare the arguments of `sceAudioOutExConfigureOutput` differently and neither is confirmed on a console. Audio reaches the host device through SDL as PCM, so there is no encoded HDMI output to switch to - [sceAudioOutExGetMonitorInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) reports a sink without audio descriptors: it zeroes the buffer and returns 0, as `sceAudioOutSysGetHdmiMonitorInfo` does, since audio reaches the host through SDL as PCM and there is no EDID to describe. The arguments `(0, 0, info, size)` and the descriptor count as a 32-bit value at +0x98, with 8-byte descriptors from +0xA0, follow [Jelly5](https://github.com/02dnot/Jelly5/blob/552b866d89b2a58b27f7f7b0484dc743ce235526/app/src/jelly5_bitstream.cpp#L62-L117) (PPSA99505), which passes a 0x400-byte buffer and read that layout from one TV; with no descriptor listed it decodes the audio itself instead of opening a bitstream port. The meaning of the first two arguments and the structure's size are unknown: nonzero first or second arguments and sizes below 0xA0, which cannot hold the count, throw, and every byte of the declared size is written as 0. A null buffer returns `0x80260004` like the other AudioOut calls; the console's code is not measured