Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 100 additions & 32 deletions core/libs/prx/libSceVoiceQoS/Export.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <limits>
#include <map>
#include <mutex>
#include <utility>
#include <vector>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"

Expand All @@ -13,66 +18,129 @@ constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_NOT_INIT = static_cast<int>(0x804E0901
constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID = static_cast<int>(0x804E0902);
constexpr int SCE_VOICE_ERROR_LIBVOICEQOS_INITIALIZED = static_cast<int>(0x804E0905);

std::mutex g_mutex;
bool g_initialized = false;
using Endpoint = std::map<std::int32_t, std::vector<std::uint8_t>>;

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<std::int32_t, Endpoint> locals;
std::map<std::int32_t, Endpoint> remotes;
std::map<std::int32_t, Connection> 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<std::int32_t>::max() ? 1 : qos.nextId + 1;
return id;
}

int CreateEndpoint(std::int32_t* id, std::map<std::int32_t, Endpoint>& 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<std::int32_t, Endpoint>& 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<std::int32_t, Endpoint>& 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<const std::uint8_t*>(value);
endpoint->second[attribute].assign(bytes, bytes + size);
return 0;
}

template<typename Body>
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() {
NotImplemented_nid_no_patch(__func__);
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() {
Expand Down
40 changes: 38 additions & 2 deletions core/libs/tests/GuestVoiceQoS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand All @@ -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);
}
2 changes: 1 addition & 1 deletion docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, VoiceQoS, WebBrowserDialog and libkernel exports imported by PPSA14632 and added without an implementation are declared without parameters
- Acm, GameLiveStreaming, Net, NpEntitlementAccess, 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

Expand Down
4 changes: 2 additions & 2 deletions docs/dev/debt/libSceVoiceQoS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 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, 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. 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

### 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
Loading