From 923d73c30599843a31664478e9ee5c1782c52d67 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 14 Oct 2026 20:10:50 +0200 Subject: [PATCH 1/2] feat(libSceVoiceQoS): implement endpoints and connections --- core/libs/prx/libSceVoiceQoS/Export.cpp | 132 ++++++++++++++++++------ core/libs/tests/GuestVoiceQoS.cpp | 40 ++++++- docs/dev/TechnicalDebt.md | 2 +- docs/dev/debt/libSceVoiceQoS.md | 4 +- 4 files changed, 141 insertions(+), 37 deletions(-) diff --git a/core/libs/prx/libSceVoiceQoS/Export.cpp b/core/libs/prx/libSceVoiceQoS/Export.cpp index e8213885a5b..3c856d1a4e5 100644 --- a/core/libs/prx/libSceVoiceQoS/Export.cpp +++ b/core/libs/prx/libSceVoiceQoS/Export.cpp @@ -1,6 +1,11 @@ #include #include +#include +#include +#include #include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -13,56 +18,120 @@ constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_NOT_INIT = static_cast(0x804E0901 constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID = static_cast(0x804E0902); constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_INITIALIZED = static_cast(0x804E0905); -std::mutex g_mutex; -bool g_initialized = false; +using Endpoint = std::map>; + +struct Connection { + std::int32_t local; + std::int32_t remote; +}; + +struct QoS { + std::mutex mutex; + bool initialized = false; + std::int32_t nextId = 1; + std::map locals; + std::map remotes; + std::map connections; +}; + +QoS& State() { + static QoS qos; + return qos; +} + +std::int32_t NewId(QoS& qos) { + const std::int32_t id = qos.nextId; + qos.nextId = qos.nextId == std::numeric_limits::max() ? 1 : qos.nextId + 1; + return id; +} + +int CreateEndpoint(std::int32_t* id, std::map& endpoints, QoS& qos) { + if (!id) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + *id = NewId(qos); + endpoints.emplace(*id, Endpoint{}); + return 0; +} + +int DeleteEndpoint(std::int32_t id, std::map& endpoints, std::int32_t Connection::*side, const QoS& qos) { + if (!endpoints.contains(id)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + for (const auto& entry : qos.connections) { + if (entry.second.*side == id) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + } + endpoints.erase(id); + return 0; +} + +int SetAttribute(std::map& endpoints, std::int32_t id, std::int32_t attribute, const void* value, std::int32_t size) { + const auto endpoint = endpoints.find(id); + if (endpoint == endpoints.end() || !value || size <= 0) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + const auto* bytes = static_cast(value); + endpoint->second[attribute].assign(bytes, bytes + size); + return 0; +} + +template +int Locked(Body&& body) { + auto& qos = State(); + std::lock_guard lock(qos.mutex); + if (!qos.initialized) return SCE_VOICE_ERROR_LIBVOICEQOS_NOT_INIT; + return body(qos); +} } extern "C" { -int APS5_VABI sceVoiceQoSInit(void* mem_block, uint32_t mem_size, int32_t app_type) { - std::lock_guard lock(g_mutex); - if (g_initialized) return SCE_VOICE_ERROR_LIBVOICEQOS_INITIALIZED; - if (!mem_block || mem_size != VOICE_QOS_MEMORY_SIZE || (app_type != VOICE_QOS_APP_TYPE_GAME && app_type != VOICE_QOS_APP_TYPE_10000000)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - g_initialized = true; +int APS5_VABI sceVoiceQoSInit(void* memBlock, uint32_t memSize, int32_t appType) { + auto& qos = State(); + std::lock_guard lock(qos.mutex); + if (qos.initialized) return SCE_VOICE_ERROR_LIBVOICEQOS_INITIALIZED; + if (!memBlock || memSize != VOICE_QOS_MEMORY_SIZE || (appType != VOICE_QOS_APP_TYPE_GAME && appType != VOICE_QOS_APP_TYPE_10000000)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + qos.initialized = true; return 0; } int APS5_VABI sceVoiceQoSEnd() { - std::lock_guard lock(g_mutex); - if (!g_initialized) return SCE_VOICE_ERROR_LIBVOICEQOS_NOT_INIT; - g_initialized = false; - return 0; + return Locked([](QoS& qos) { + qos.connections.clear(); + qos.locals.clear(); + qos.remotes.clear(); + qos.initialized = false; + return 0; + }); } -int APS5_VABI sceVoiceQoSCreateLocalEndpoint() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceQoSCreateLocalEndpoint(int32_t* localId, int32_t userId, int32_t device) { + (void)userId; + (void)device; + return Locked([&](QoS& qos) { return CreateEndpoint(localId, qos.locals, qos); }); } -int APS5_VABI sceVoiceQoSDeleteLocalEndpoint() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceQoSDeleteLocalEndpoint(int32_t localId) { + return Locked([&](QoS& qos) { return DeleteEndpoint(localId, qos.locals, &Connection::local, qos); }); } -int APS5_VABI sceVoiceQoSCreateRemoteEndpoint() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceQoSCreateRemoteEndpoint(int32_t* remoteId) { + return Locked([&](QoS& qos) { return CreateEndpoint(remoteId, qos.remotes, qos); }); } -int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint(int32_t remoteId) { + return Locked([&](QoS& qos) { return DeleteEndpoint(remoteId, qos.remotes, &Connection::remote, qos); }); } -int APS5_VABI sceVoiceQoSConnect() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceQoSConnect(int32_t* connectionId, int32_t localId, int32_t remoteId) { + return Locked([&](QoS& qos) { + if (!connectionId || !qos.locals.contains(localId) || !qos.remotes.contains(remoteId)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + for (const auto& entry : qos.connections) { + if (entry.second.local == localId && entry.second.remote == remoteId) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + } + *connectionId = NewId(qos); + qos.connections.emplace(*connectionId, Connection{localId, remoteId}); + return 0; + }); } -int APS5_VABI sceVoiceQoSDisconnect() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceQoSDisconnect(int32_t connectionId) { + return Locked([&](QoS& qos) { return qos.connections.erase(connectionId) != 0 ? 0 : SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; }); } int APS5_VABI sceVoiceQoSSetConnectionAttribute() { @@ -70,9 +139,8 @@ int APS5_VABI sceVoiceQoSSetConnectionAttribute() { return 0; } -int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute(int32_t localId, int32_t attributeId, const void* value, int32_t size) { + return Locked([&](QoS& qos) { return SetAttribute(qos.locals, localId, attributeId, value, size); }); } int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute() { diff --git a/core/libs/tests/GuestVoiceQoS.cpp b/core/libs/tests/GuestVoiceQoS.cpp index 44aa996344d..a1aa646fc04 100644 --- a/core/libs/tests/GuestVoiceQoS.cpp +++ b/core/libs/tests/GuestVoiceQoS.cpp @@ -6,6 +6,13 @@ extern "C" { int APS5_VABI sceVoiceQoSInit(void*, std::uint32_t, std::int32_t); int APS5_VABI sceVoiceQoSEnd(); +int APS5_VABI sceVoiceQoSCreateLocalEndpoint(std::int32_t*, std::int32_t, std::int32_t); +int APS5_VABI sceVoiceQoSDeleteLocalEndpoint(std::int32_t); +int APS5_VABI sceVoiceQoSCreateRemoteEndpoint(std::int32_t*); +int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint(std::int32_t); +int APS5_VABI sceVoiceQoSConnect(std::int32_t*, std::int32_t, std::int32_t); +int APS5_VABI sceVoiceQoSDisconnect(std::int32_t); +int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute(std::int32_t, std::int32_t, const void*, std::int32_t); } static void Require(bool value) { if (!value) std::abort(); } @@ -28,11 +35,40 @@ int main(int argc, char** argv) { Require(sceVoiceQoSInit(memory.data(), 0x40000, appType) == 0); Require(sceVoiceQoSInit(memory.data(), 0x40000, appType) == initialized); Require(sceVoiceQoSInit(nullptr, 0, 0) == initialized); + + std::int32_t local = -1; + std::int32_t remote = -1; + std::int32_t connection = -1; + Require(sceVoiceQoSCreateLocalEndpoint(nullptr, 1, 0) == argumentInvalid); + Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0) == 0 && local > 0); + Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == 0 && remote > 0 && remote != local); + Require(sceVoiceQoSConnect(&connection, remote, local) == argumentInvalid); + Require(sceVoiceQoSConnect(&connection, local, remote) == 0 && connection > 0); + std::int32_t duplicate = -1; + Require(sceVoiceQoSConnect(&duplicate, local, remote) == argumentInvalid); + Require(sceVoiceQoSDeleteLocalEndpoint(local) == argumentInvalid); + + const std::uint32_t level = 0x1234; + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, sizeof(level)) == 0); + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, nullptr, sizeof(level)) == argumentInvalid); + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, 0) == argumentInvalid); + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, -1) == argumentInvalid); + Require(sceVoiceQoSSetLocalEndpointAttribute(local + 100, 3, &level, sizeof(level)) == argumentInvalid); + + Require(sceVoiceQoSDisconnect(connection) == 0); + Require(sceVoiceQoSDisconnect(connection) == argumentInvalid); + Require(sceVoiceQoSDeleteLocalEndpoint(local) == 0); + Require(sceVoiceQoSDeleteLocalEndpoint(local) == argumentInvalid); + Require(sceVoiceQoSDeleteRemoteEndpoint(remote) == 0); + + Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == 0); Require(sceVoiceQoSEnd() == 0); Require(sceVoiceQoSEnd() == notInitialized); - Require(sceVoiceQoSInit(nullptr, 0x40000, appType) == argumentInvalid); + Require(sceVoiceQoSDeleteRemoteEndpoint(remote) == notInitialized); + Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0) == notInitialized); Require(sceVoiceQoSInit(memory.data(), 0x40000, appType) == 0); - Require(sceVoiceQoSInit(memory.data(), 0x40000, appType) == initialized); + Require(sceVoiceQoSDeleteRemoteEndpoint(remote) == argumentInvalid); Require(sceVoiceQoSEnd() == 0); Require(sceVoiceQoSEnd() == notInitialized); + Require(sceVoiceQoSInit(nullptr, 0x40000, appType) == argumentInvalid); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 2f721015f3f..b9bdc7aa013 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -20,7 +20,7 @@ Throughout the project, every function at every stage either **does exactly what - `sceNetCtlGetIfStat` signature and 56-byte layout come from PPSA09030's call site and SharpProspero's `SceNetCtlIfStat`; the device kind is always 0, the byte counters are the host's totals over its up non-loopback hardware interfaces (not "since cleared"), and the not-connected result is assumed to match `sceNetCtlGetState` - Font, Http2, Net, Ssl, SystemService and libkernel exports imported by PPSA12544 and added without an implementation have unknown signatures - The Share, NpTrophy2, NpEntitlementAccess and WebBrowserDialog exports imported by PPSA12544's Unity plugins and added without an implementation have unknown signatures -- Acm, GameLiveStreaming, Net, NpEntitlementAccess, NpManager, VoiceQoS, WebBrowserDialog and libkernel exports imported by PPSA14632 and added without an implementation are declared without parameters +- Acm, GameLiveStreaming, Net, NpEntitlementAccess, NpManager, VoiceQoS, WebBrowserDialog and libkernel exports imported by PPSA14632 and added without an implementation are declared without parameters; of the VoiceQoS ones only those still throwing remain so ### Functional diff --git a/docs/dev/debt/libSceVoiceQoS.md b/docs/dev/debt/libSceVoiceQoS.md index 7b3937b6a92..c37089e935b 100644 --- a/docs/dev/debt/libSceVoiceQoS.md +++ b/docs/dev/debt/libSceVoiceQoS.md @@ -3,8 +3,8 @@ ### Unknown function info - [sceVoiceQoSSetConnectionAttribute](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS, NID `KCGOxg8iX7s`) is a throwing stub declared without parameters, like the other endpoint calls of the library: PPSA04159 fails to load without it and its signature is unknown. The name hashes to the imported NID (`tools/nid_names.py`) -- [sceVoiceQoSInit](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0905` for a second initialization comes from [prosper](https://github.com/mattias800/prosper)'s reading of the module and was not measured: on a PS5 a valid call blocks inside a homebrew payload, so it never returns there. The app type and the block are not used, as no endpoint is implemented +- [sceVoiceQoSInit](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0905` for a second initialization comes from [prosper](https://github.com/mattias800/prosper)'s reading of the module and was not measured: on a PS5 a valid call blocks inside a homebrew payload, so it never returns there. The app type and the block are not used. The endpoint calls assume the PS4 `libSceVoiceQoS` signatures (`CreateLocalEndpoint(int* id, user, device)`, `DeleteLocalEndpoint(id)`, `CreateRemoteEndpoint(int* id)`, `DeleteRemoteEndpoint(id)`, `Connect(int* id, local, remote)`, `Disconnect(connection)`, `SetLocalEndpointAttribute(id, attribute, value, size)`), not confirmed on a PS5 title. Endpoints and connections are tracked (an endpoint in a connection cannot be deleted, a pair connects once); local endpoint attributes are stored as given, as their ids and defaults are unknown; other endpoint and packet calls remain throwing stubs. The not-initialized error 0x804E0901 is assumed from the 0x804E09xx numbering, and unknown ids return `ARGUMENT_INVALID`. Aliens: Fireteam Elite (PPSA03393) uses all seven implemented calls on its first load path, starting with `sceVoiceQoSCreateLocalEndpoint` when it registers the local talker; it stops with `not implemented` when any of them throws ### Functional -- [sceVoiceQoSEnd](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0901` (not initialized) before any initialization was measured on a PS5 (firmware 13.60, #1407); ending an initialized library was not, because a valid `sceVoiceQoSInit` never returns inside a homebrew payload. PPSA14632 calls it at shutdown and PPSA26344 after a nonzero `sceVoiceQoSInit`. After an initialization it returns 0 and clears the init flag, so the library can be initialized again. Whether the console also ends the libSceVoice instance that its `sceVoiceQoSInit` starts ([prosper](https://github.com/mattias800/prosper)'s reading of the module), and what it answers while endpoints exist, is unknown; no endpoint is implemented +- [sceVoiceQoSEnd](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0901` (not initialized) before any initialization was measured on a PS5 (firmware 13.60, #1407); ending an initialized library was not, because a valid `sceVoiceQoSInit` never returns inside a homebrew payload. PPSA14632 calls it at shutdown and PPSA26344 after a nonzero `sceVoiceQoSInit`. After an initialization it returns 0 and clears the init flag, so the library can be initialized again. Whether the console also ends the libSceVoice instance that its `sceVoiceQoSInit` starts ([prosper](https://github.com/mattias800/prosper)'s reading of the module), and what it answers while endpoints exist, is unknown; here it drops every endpoint and connection From b868190bc217a665551152e95ba661a37398cea0 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sat, 17 Oct 2026 00:36:18 +0200 Subject: [PATCH 2/2] fix(libSceVoiceQoS): take 4-arg local endpoint and relax unverified rejections --- core/libs/prx/libSceVoiceQoS/Export.cpp | 31 +++++++++++-------------- core/libs/tests/GuestVoiceQoS.cpp | 24 ++++++++++++------- docs/dev/debt/libSceVoiceQoS.md | 2 +- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/core/libs/prx/libSceVoiceQoS/Export.cpp b/core/libs/prx/libSceVoiceQoS/Export.cpp index 3c856d1a4e5..9813b98413d 100644 --- a/core/libs/prx/libSceVoiceQoS/Export.cpp +++ b/core/libs/prx/libSceVoiceQoS/Export.cpp @@ -52,20 +52,19 @@ int CreateEndpoint(std::int32_t* id, std::map& endpoints return 0; } -int DeleteEndpoint(std::int32_t id, std::map& endpoints, std::int32_t Connection::*side, const QoS& qos) { - if (!endpoints.contains(id)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - for (const auto& entry : qos.connections) { - if (entry.second.*side == id) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - } - endpoints.erase(id); - return 0; +int DeleteEndpoint(std::int32_t id, std::map& endpoints) { + return endpoints.erase(id) != 0 ? 0 : SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; } int SetAttribute(std::map& endpoints, std::int32_t id, std::int32_t attribute, const void* value, std::int32_t size) { const auto endpoint = endpoints.find(id); - if (endpoint == endpoints.end() || !value || size <= 0) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - const auto* bytes = static_cast(value); - endpoint->second[attribute].assign(bytes, bytes + size); + if (endpoint == endpoints.end() || !value) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; + if (size > 0) { + const auto* bytes = static_cast(value); + endpoint->second[attribute].assign(bytes, bytes + size); + } else { + endpoint->second[attribute].clear(); + } return 0; } @@ -100,14 +99,15 @@ int APS5_VABI sceVoiceQoSEnd() { }); } -int APS5_VABI sceVoiceQoSCreateLocalEndpoint(int32_t* localId, int32_t userId, int32_t device) { +int APS5_VABI sceVoiceQoSCreateLocalEndpoint(int32_t* localId, int32_t userId, int32_t deviceInId, int32_t deviceOutId) { (void)userId; - (void)device; + (void)deviceInId; + (void)deviceOutId; return Locked([&](QoS& qos) { return CreateEndpoint(localId, qos.locals, qos); }); } int APS5_VABI sceVoiceQoSDeleteLocalEndpoint(int32_t localId) { - return Locked([&](QoS& qos) { return DeleteEndpoint(localId, qos.locals, &Connection::local, qos); }); + return Locked([&](QoS& qos) { return DeleteEndpoint(localId, qos.locals); }); } int APS5_VABI sceVoiceQoSCreateRemoteEndpoint(int32_t* remoteId) { @@ -115,15 +115,12 @@ int APS5_VABI sceVoiceQoSCreateRemoteEndpoint(int32_t* remoteId) { } int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint(int32_t remoteId) { - return Locked([&](QoS& qos) { return DeleteEndpoint(remoteId, qos.remotes, &Connection::remote, qos); }); + return Locked([&](QoS& qos) { return DeleteEndpoint(remoteId, qos.remotes); }); } int APS5_VABI sceVoiceQoSConnect(int32_t* connectionId, int32_t localId, int32_t remoteId) { return Locked([&](QoS& qos) { if (!connectionId || !qos.locals.contains(localId) || !qos.remotes.contains(remoteId)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - for (const auto& entry : qos.connections) { - if (entry.second.local == localId && entry.second.remote == remoteId) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID; - } *connectionId = NewId(qos); qos.connections.emplace(*connectionId, Connection{localId, remoteId}); return 0; diff --git a/core/libs/tests/GuestVoiceQoS.cpp b/core/libs/tests/GuestVoiceQoS.cpp index a1aa646fc04..272b43a1a01 100644 --- a/core/libs/tests/GuestVoiceQoS.cpp +++ b/core/libs/tests/GuestVoiceQoS.cpp @@ -6,7 +6,7 @@ extern "C" { int APS5_VABI sceVoiceQoSInit(void*, std::uint32_t, std::int32_t); int APS5_VABI sceVoiceQoSEnd(); -int APS5_VABI sceVoiceQoSCreateLocalEndpoint(std::int32_t*, std::int32_t, std::int32_t); +int APS5_VABI sceVoiceQoSCreateLocalEndpoint(std::int32_t*, std::int32_t, std::int32_t, std::int32_t); int APS5_VABI sceVoiceQoSDeleteLocalEndpoint(std::int32_t); int APS5_VABI sceVoiceQoSCreateRemoteEndpoint(std::int32_t*); int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint(std::int32_t); @@ -39,20 +39,28 @@ int main(int argc, char** argv) { std::int32_t local = -1; std::int32_t remote = -1; std::int32_t connection = -1; - Require(sceVoiceQoSCreateLocalEndpoint(nullptr, 1, 0) == argumentInvalid); - Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0) == 0 && local > 0); + Require(sceVoiceQoSCreateLocalEndpoint(nullptr, 1, 0, 0) == argumentInvalid); + Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0, 0) == 0 && local > 0); Require(sceVoiceQoSCreateRemoteEndpoint(&remote) == 0 && remote > 0 && remote != local); Require(sceVoiceQoSConnect(&connection, remote, local) == argumentInvalid); Require(sceVoiceQoSConnect(&connection, local, remote) == 0 && connection > 0); std::int32_t duplicate = -1; - Require(sceVoiceQoSConnect(&duplicate, local, remote) == argumentInvalid); - Require(sceVoiceQoSDeleteLocalEndpoint(local) == argumentInvalid); + Require(sceVoiceQoSConnect(&duplicate, local, remote) == 0 && duplicate > 0 && duplicate != connection); + Require(sceVoiceQoSDisconnect(duplicate) == 0); + + std::int32_t otherLocal = -1; + std::int32_t otherConnection = -1; + Require(sceVoiceQoSCreateLocalEndpoint(&otherLocal, 1, 0, 0) == 0); + Require(sceVoiceQoSConnect(&otherConnection, otherLocal, remote) == 0); + Require(sceVoiceQoSDeleteLocalEndpoint(otherLocal) == 0); + Require(sceVoiceQoSDeleteLocalEndpoint(otherLocal) == argumentInvalid); + Require(sceVoiceQoSDisconnect(otherConnection) == 0); const std::uint32_t level = 0x1234; Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, sizeof(level)) == 0); Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, nullptr, sizeof(level)) == argumentInvalid); - Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, 0) == argumentInvalid); - Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, -1) == argumentInvalid); + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, 0) == 0); + Require(sceVoiceQoSSetLocalEndpointAttribute(local, 3, &level, -1) == 0); Require(sceVoiceQoSSetLocalEndpointAttribute(local + 100, 3, &level, sizeof(level)) == argumentInvalid); Require(sceVoiceQoSDisconnect(connection) == 0); @@ -65,7 +73,7 @@ int main(int argc, char** argv) { Require(sceVoiceQoSEnd() == 0); Require(sceVoiceQoSEnd() == notInitialized); Require(sceVoiceQoSDeleteRemoteEndpoint(remote) == notInitialized); - Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0) == notInitialized); + Require(sceVoiceQoSCreateLocalEndpoint(&local, 1, 0, 0) == notInitialized); Require(sceVoiceQoSInit(memory.data(), 0x40000, appType) == 0); Require(sceVoiceQoSDeleteRemoteEndpoint(remote) == argumentInvalid); Require(sceVoiceQoSEnd() == 0); diff --git a/docs/dev/debt/libSceVoiceQoS.md b/docs/dev/debt/libSceVoiceQoS.md index 4dd889ccfa2..49bb102e5ab 100644 --- a/docs/dev/debt/libSceVoiceQoS.md +++ b/docs/dev/debt/libSceVoiceQoS.md @@ -3,7 +3,7 @@ ### Unknown function info - [sceVoiceQoSSetConnectionAttribute](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS, NID `KCGOxg8iX7s`) is a throwing stub declared without parameters, like the other endpoint calls of the library: PPSA04159 fails to load without it and its signature is unknown. The name hashes to the imported NID (`tools/nid_names.py`) -- [sceVoiceQoSInit](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0905` for a second initialization comes from [prosper](https://github.com/mattias800/prosper)'s reading of the module and was not measured: on a PS5 a valid call blocks inside a homebrew payload, so it never returns there. PPSA26344's two call sites (`0xE307BF`, `0x151C45D`) pass an allocated pointer, a 0x40000-byte block and app type 0x10000000; the app type and block are not used. The endpoint calls assume the PS4 `libSceVoiceQoS` signatures (`CreateLocalEndpoint(int* id, user, device)`, `DeleteLocalEndpoint(id)`, `CreateRemoteEndpoint(int* id)`, `DeleteRemoteEndpoint(id)`, `Connect(int* id, local, remote)`, `Disconnect(connection)`, `SetLocalEndpointAttribute(id, attribute, value, size)`), not confirmed on a PS5 title. Endpoints and connections are tracked (an endpoint in a connection cannot be deleted, a pair connects once); local endpoint attributes are stored as given, as their ids and defaults are unknown; other endpoint and packet calls remain throwing stubs. The not-initialized error 0x804E0901 is assumed from the 0x804E09xx numbering, and unknown ids return `ARGUMENT_INVALID`. Aliens: Fireteam Elite (PPSA03393) uses all seven implemented calls on its first load path, starting with `sceVoiceQoSCreateLocalEndpoint` when it registers the local talker; it stops with `not implemented` when any of them throws +- [sceVoiceQoSInit](../../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0905` for a second initialization comes from [prosper](https://github.com/mattias800/prosper)'s reading of the module and was not measured: on a PS5 a valid call blocks inside a homebrew payload, so it never returns there. PPSA26344's two call sites (`0xE307BF`, `0x151C45D`) pass an allocated pointer, a 0x40000-byte block and app type 0x10000000; the app type and block are not used. `sceVoiceQoSCreateLocalEndpoint` takes four arguments (`int* id, userId, deviceInId, deviceOutId`) as declared in fpPS4 (`src/ps4_libscevoice.pas`, `ps4_sceVoiceQoSCreateLocalEndpoint`); no public reference exists for the other six signatures (`DeleteLocalEndpoint(id)`, `CreateRemoteEndpoint(int* id)`, `DeleteRemoteEndpoint(id)`, `Connect(int* id, local, remote)`, `Disconnect(connection)`, `SetLocalEndpointAttribute(id, attribute, value, size)`), which are assumed from function naming and call patterns and not confirmed on a PS5 title. Endpoints and connections are tracked by allocated id; duplicate connections and deleting an endpoint while referenced in a connection are permitted as the console is not known to reject them; local endpoint attributes are stored as given (including empty or non-positive sizes), as their ids and defaults are unknown; other endpoint and packet calls remain throwing stubs. The not-initialized error 0x804E0901 is assumed from the 0x804E09xx numbering, and unknown ids return `ARGUMENT_INVALID`. Aliens: Fireteam Elite (PPSA03393) uses all seven implemented calls on its first load path, starting with `sceVoiceQoSCreateLocalEndpoint` when it registers the local talker; it stops with `not implemented` when any of them throws ### Functional