mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Both exports are imported by titles that link libSceAudio3d; without them the loader fails with an unresolved import before the title runs. Following the offline model, object attributes are validated but produce no audio and PortFlush drops the frames queued by sceAudio3dPortAdvance.
280 lines
14 KiB
C++
280 lines
14 KiB
C++
#include "SceTypes.hpp"
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <exception>
|
|
#include <thread>
|
|
|
|
extern "C" {
|
|
void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* parameters);
|
|
int APS5_VABI sceAudio3dInitialize(std::int64_t reserved);
|
|
int APS5_VABI sceAudio3dTerminate();
|
|
int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, std::uint32_t* id);
|
|
int APS5_VABI sceAudio3dPortClose(std::uint32_t port_id);
|
|
int APS5_VABI sceAudio3dPortSetAttribute(std::uint32_t port_id, std::uint32_t attribute_id, const void* attribute, std::size_t attribute_size);
|
|
int APS5_VABI sceAudio3dPortGetQueueLevel(std::uint32_t port_id, std::uint32_t* queue_level, std::uint32_t* queue_available);
|
|
int APS5_VABI sceAudio3dPortAdvance(std::uint32_t port_id);
|
|
int APS5_VABI sceAudio3dPortPush(std::uint32_t port_id, std::uint32_t blocking);
|
|
int APS5_VABI sceAudio3dPortFlush(std::uint32_t port_id);
|
|
int APS5_VABI sceAudio3dObjectReserve(std::uint32_t port_id, std::uint32_t* object_id);
|
|
int APS5_VABI sceAudio3dObjectUnreserve(std::uint32_t port_id, std::uint32_t object_id);
|
|
int APS5_VABI sceAudio3dObjectSetAttributes(std::uint32_t port_id, std::uint32_t object_id, std::uint64_t num_attributes, const Audio3dAttribute* attribute_array);
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr int INVALID_PORT = static_cast<int>(0x80EA0002);
|
|
constexpr int INVALID_OBJECT = static_cast<int>(0x80EA0003);
|
|
constexpr int INVALID_PARAMETER = static_cast<int>(0x80EA0004);
|
|
constexpr int OUT_OF_RESOURCES = static_cast<int>(0x80EA0006);
|
|
constexpr int NOT_READY = static_cast<int>(0x80EA0007);
|
|
constexpr int SYSTEM_USER = 0xFF;
|
|
constexpr std::uint32_t OBJECT_INVALID = 0xFFFFFFFF;
|
|
|
|
void Require(bool value, const char* message) {
|
|
if (value) return;
|
|
std::fprintf(stderr, "%s\n", message);
|
|
std::abort();
|
|
}
|
|
|
|
template <typename F>
|
|
void RequireThrows(F f, const char* message) {
|
|
bool threw = false;
|
|
try {
|
|
f();
|
|
} catch (const std::exception&) {
|
|
threw = true;
|
|
}
|
|
Require(threw, message);
|
|
}
|
|
|
|
Audio3dOpenParameters Defaults() {
|
|
Audio3dOpenParameters parameters{};
|
|
sceAudio3dGetDefaultOpenParameters(¶meters);
|
|
return parameters;
|
|
}
|
|
|
|
int Open(const Audio3dOpenParameters& parameters, std::uint32_t* id) {
|
|
return sceAudio3dPortOpen(SYSTEM_USER, ¶meters, id);
|
|
}
|
|
|
|
void RequireLevel(std::uint32_t level, std::uint32_t available, const char* message) {
|
|
std::uint32_t got_level = 99;
|
|
std::uint32_t got_available = 99;
|
|
Require(sceAudio3dPortGetQueueLevel(0, &got_level, &got_available) == 0, message);
|
|
Require(got_level == level && got_available == available, message);
|
|
}
|
|
|
|
void CheckBeforeInitialize() {
|
|
std::uint32_t id = 7;
|
|
Audio3dOpenParameters parameters = Defaults();
|
|
Require(sceAudio3dTerminate() == NOT_READY, "terminate before initialize must be NOT_READY");
|
|
Require(sceAudio3dInitialize(1) == INVALID_PARAMETER, "non-zero reserved must be rejected");
|
|
Require(Open(parameters, &id) == NOT_READY, "open before initialize must be NOT_READY");
|
|
Require(id == 7, "a failed open must not write the port id");
|
|
Require(sceAudio3dPortOpen(1, nullptr, nullptr) == NOT_READY, "NOT_READY is checked before the arguments");
|
|
std::uint32_t level = 0;
|
|
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == INVALID_PORT, "queue level needs an open port");
|
|
Require(sceAudio3dPortClose(0) == INVALID_PORT, "close needs an open port");
|
|
Require(sceAudio3dPortAdvance(0) == INVALID_PORT, "advance needs an open port");
|
|
Require(sceAudio3dPortPush(0, 0) == INVALID_PORT, "push needs an open port");
|
|
float value = 0.0f;
|
|
Require(sceAudio3dPortSetAttribute(0, 0x10001, &value, sizeof(value)) == INVALID_PORT, "set attribute needs an open port");
|
|
Require(sceAudio3dInitialize(0) == 0, "initialize");
|
|
RequireThrows([] { sceAudio3dInitialize(0); }, "repeated initialize must throw");
|
|
}
|
|
|
|
void CheckOpenParameters() {
|
|
std::uint32_t id = 7;
|
|
const Audio3dOpenParameters defaults = Defaults();
|
|
Require(sceAudio3dPortOpen(1, &defaults, &id) == INVALID_PARAMETER, "user id must be the system user");
|
|
Require(sceAudio3dPortOpen(SYSTEM_USER, nullptr, &id) == INVALID_PARAMETER, "null parameters");
|
|
Require(sceAudio3dPortOpen(SYSTEM_USER, &defaults, nullptr) == INVALID_PARAMETER, "null port id");
|
|
auto rejects = [&](auto change, const char* message) {
|
|
Audio3dOpenParameters parameters = defaults;
|
|
change(parameters);
|
|
Require(Open(parameters, &id) == INVALID_PARAMETER, message);
|
|
Require(id == 7, "a failed open must not write the port id");
|
|
};
|
|
rejects([](auto& p) { p.size_this = 0x30; }, "unknown parameter size");
|
|
rejects([](auto& p) { p.size_this = 0x21; }, "parameter size is not masked");
|
|
rejects([](auto& p) { p.rate = 1; }, "only 48 kHz");
|
|
rejects([](auto& p) { p.granularity = 0x80; }, "granularity below 256");
|
|
rejects([](auto& p) { p.granularity = 0x180; }, "granularity not a multiple of 256");
|
|
rejects([](auto& p) { p.max_objects = 0; }, "zero objects");
|
|
rejects([](auto& p) { p.queue_depth = 0; }, "zero queue depth");
|
|
rejects([](auto& p) { p.buffer_mode = 3; }, "buffer mode above 2");
|
|
rejects([](auto& p) { p.size_this = 0x28; p.num_beds = 4; }, "4 beds");
|
|
rejects([](auto& p) { p.size_this = 0x28; p.num_beds = 1; }, "1 bed");
|
|
auto throws = [&](auto change, const char* message) {
|
|
Audio3dOpenParameters parameters = defaults;
|
|
change(parameters);
|
|
RequireThrows([&] { Open(parameters, &id); }, message);
|
|
};
|
|
throws([](auto& p) { p.size_this = 0x10; }, "0x10 parameters select buffer mode 0");
|
|
throws([](auto& p) { p.size_this = 0x18; }, "0x18 parameters select buffer mode 1");
|
|
throws([](auto& p) { p.buffer_mode = 1; }, "buffer mode 1");
|
|
throws([](auto& p) { p.size_this = 0x28; p.num_beds = 3; }, "3 beds");
|
|
}
|
|
|
|
void CheckOpenClose() {
|
|
std::uint32_t id = 7;
|
|
Audio3dOpenParameters parameters = Defaults();
|
|
parameters.size_this = 0x28;
|
|
parameters.num_beds = 2;
|
|
Require(Open(parameters, &id) == 0 && id == 0, "open hands out port 0");
|
|
std::uint32_t second = 7;
|
|
Require(Open(parameters, &second) == OUT_OF_RESOURCES && second == 7, "only one port");
|
|
std::uint32_t level = 0;
|
|
Require(sceAudio3dPortGetQueueLevel(1, &level, nullptr) == INVALID_PORT, "port 1 does not exist");
|
|
Require(sceAudio3dPortClose(1) == INVALID_PORT, "close of port 1");
|
|
Require(sceAudio3dPortClose(0) == 0, "close");
|
|
Require(sceAudio3dPortClose(0) == INVALID_PORT, "double close");
|
|
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == INVALID_PORT, "closed port");
|
|
}
|
|
|
|
void CheckAttributes() {
|
|
float value = 0.5f;
|
|
int flag = 1;
|
|
Require(sceAudio3dPortSetAttribute(0, 0x10001, nullptr, 4) == INVALID_PARAMETER, "null attribute");
|
|
Require(sceAudio3dPortSetAttribute(1, 0x10001, &value, 4) == INVALID_PORT, "attribute on port 1");
|
|
Require(sceAudio3dPortSetAttribute(0, 0x10001, &value, 4) == 0, "late reverb level");
|
|
Require(sceAudio3dPortSetAttribute(0, 0x10002, &value, 4) == 0, "downmix spread radius");
|
|
Require(sceAudio3dPortSetAttribute(0, 0x10003, &flag, 4) == 0, "downmix spread height aware");
|
|
RequireThrows([&] { sceAudio3dPortSetAttribute(0, 0x10004, &value, 4); }, "unknown attribute must throw");
|
|
RequireThrows([&] { sceAudio3dPortSetAttribute(0, 0x10001, &value, 8); }, "wrong attribute size must throw");
|
|
}
|
|
|
|
void CheckObjects() {
|
|
std::uint32_t object = 7;
|
|
Require(sceAudio3dObjectReserve(0, nullptr) == INVALID_PARAMETER, "null object id");
|
|
Require(sceAudio3dObjectReserve(0, &object) == INVALID_PORT && object == OBJECT_INVALID, "reserve needs an open port");
|
|
Require(sceAudio3dObjectUnreserve(0, 1) == INVALID_PORT, "unreserve needs an open port");
|
|
Audio3dOpenParameters parameters = Defaults();
|
|
parameters.max_objects = 2;
|
|
std::uint32_t id = 7;
|
|
Require(Open(parameters, &id) == 0 && id == 0, "open");
|
|
object = 7;
|
|
Require(sceAudio3dObjectReserve(1, &object) == INVALID_PORT && object == OBJECT_INVALID, "reserve on port 1");
|
|
Require(sceAudio3dObjectReserve(0, &object) == 0 && object == 1, "first object is 1");
|
|
Require(sceAudio3dObjectReserve(0, &object) == 0 && object == 2, "second object is 2");
|
|
Require(sceAudio3dObjectReserve(0, &object) == OUT_OF_RESOURCES && object == OBJECT_INVALID, "max_objects is the limit");
|
|
Require(sceAudio3dObjectUnreserve(1, 1) == INVALID_PORT, "unreserve on port 1");
|
|
Require(sceAudio3dObjectUnreserve(0, 3) == INVALID_OBJECT, "unreserve of an unknown object");
|
|
Require(sceAudio3dObjectUnreserve(0, OBJECT_INVALID) == INVALID_OBJECT, "unreserve of the invalid object");
|
|
Require(sceAudio3dObjectUnreserve(0, 1) == 0, "unreserve");
|
|
Require(sceAudio3dObjectUnreserve(0, 1) == INVALID_OBJECT, "double unreserve");
|
|
Require(sceAudio3dObjectReserve(0, &object) == 0 && object == 3, "ids are not reused while the port is open");
|
|
Require(sceAudio3dObjectReserve(0, &object) == OUT_OF_RESOURCES, "full again");
|
|
Require(sceAudio3dPortClose(0) == 0, "close");
|
|
Require(Open(parameters, &id) == 0 && id == 0, "reopen");
|
|
Require(sceAudio3dObjectUnreserve(0, 2) == INVALID_OBJECT, "close drops the objects");
|
|
Require(sceAudio3dObjectReserve(0, &object) == 0 && object == 1, "ids restart on a new port");
|
|
Require(sceAudio3dObjectReserve(0, &object) == 0 && object == 2, "a new port has the full limit");
|
|
Require(sceAudio3dPortClose(0) == 0, "close");
|
|
}
|
|
|
|
void CheckQueue() {
|
|
using namespace std::chrono;
|
|
constexpr std::uint32_t granularity = 0x1800;
|
|
constexpr auto frame = microseconds(1000000ull * granularity / 48000);
|
|
Audio3dOpenParameters parameters = Defaults();
|
|
parameters.granularity = granularity;
|
|
std::uint32_t id = 7;
|
|
Require(Open(parameters, &id) == 0 && id == 0, "open");
|
|
CheckAttributes();
|
|
Require(sceAudio3dPortGetQueueLevel(0, nullptr, nullptr) == INVALID_PARAMETER, "both outputs null");
|
|
std::uint32_t available = 0;
|
|
Require(sceAudio3dPortGetQueueLevel(0, nullptr, &available) == 0 && available == 2, "level pointer is optional");
|
|
RequireLevel(0, 2, "empty queue");
|
|
Require(sceAudio3dPortPush(0, 1) == 0, "push of an empty queue");
|
|
RequireLevel(0, 2, "empty push queues nothing");
|
|
Require(sceAudio3dPortAdvance(0) == 0, "advance 1");
|
|
RequireLevel(1, 1, "one frame queued");
|
|
Require(sceAudio3dPortAdvance(0) == 0, "advance 2");
|
|
RequireLevel(2, 0, "queue full");
|
|
RequireThrows([] { sceAudio3dPortAdvance(0); }, "advance into a full queue must throw");
|
|
RequireThrows([] { sceAudio3dPortPush(0, 2); }, "unknown blocking mode must throw");
|
|
|
|
auto start = steady_clock::now();
|
|
Require(sceAudio3dPortPush(0, 1) == 0, "blocking push");
|
|
auto waited = steady_clock::now() - start;
|
|
Require(waited >= frame - milliseconds(2), "blocking push waits until one frame has played");
|
|
RequireLevel(1, 1, "blocking push returns with one free slot");
|
|
|
|
Require(sceAudio3dPortAdvance(0) == 0, "advance 3");
|
|
start = steady_clock::now();
|
|
Require(sceAudio3dPortPush(0, 0) == 0, "async push");
|
|
Require(steady_clock::now() - start < frame, "async push does not wait for a frame");
|
|
std::uint32_t level = 0;
|
|
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == 0 && level >= 1, "async push keeps frames queued");
|
|
std::this_thread::sleep_for(frame * 3);
|
|
RequireLevel(0, 2, "frames drain at the port's rate");
|
|
Require(sceAudio3dPortClose(0) == 0, "close");
|
|
}
|
|
|
|
void CheckFlush() {
|
|
Require(sceAudio3dPortFlush(0) == INVALID_PORT, "flush needs an open port");
|
|
Audio3dOpenParameters parameters = Defaults();
|
|
std::uint32_t id = 7;
|
|
Require(Open(parameters, &id) == 0 && id == 0, "open");
|
|
Require(sceAudio3dPortAdvance(0) == 0, "advance");
|
|
RequireLevel(1, 1, "one frame queued");
|
|
Require(sceAudio3dPortFlush(1) == INVALID_PORT, "flush of port 1");
|
|
Require(sceAudio3dPortFlush(0) == 0, "flush");
|
|
RequireLevel(0, 2, "flush drops the queued frames");
|
|
Require(sceAudio3dPortClose(0) == 0, "close");
|
|
}
|
|
|
|
void CheckObjectAttributes() {
|
|
float value = 0.5f;
|
|
Audio3dAttribute attribute{0x10001, 0, &value, sizeof(value)};
|
|
Require(sceAudio3dObjectSetAttributes(0, 1, 1, &attribute) == INVALID_PORT, "attributes need an open port");
|
|
Audio3dOpenParameters parameters = Defaults();
|
|
std::uint32_t id = 7;
|
|
Require(Open(parameters, &id) == 0 && id == 0, "open");
|
|
std::uint32_t object = 0;
|
|
Require(sceAudio3dObjectReserve(0, &object) == 0 && object == 1, "reserve");
|
|
Require(sceAudio3dObjectSetAttributes(1, 1, 1, &attribute) == INVALID_PORT, "attributes on port 1");
|
|
Require(sceAudio3dObjectSetAttributes(0, 1, 0, &attribute) == INVALID_PARAMETER, "zero attributes");
|
|
Require(sceAudio3dObjectSetAttributes(0, 1, 1, nullptr) == INVALID_PARAMETER, "null array");
|
|
Require(sceAudio3dObjectSetAttributes(0, 2, 1, &attribute) == INVALID_OBJECT, "unreserved object");
|
|
Require(sceAudio3dObjectSetAttributes(0, 1, 1, &attribute) == 0, "set one attribute");
|
|
Audio3dAttribute reset{0x20000, 0, nullptr, 0};
|
|
Require(sceAudio3dObjectSetAttributes(0, 1, 1, &reset) == 0, "reset state takes a null value");
|
|
Audio3dAttribute missing{0x10001, 0, nullptr, 0};
|
|
Require(sceAudio3dObjectSetAttributes(0, 1, 1, &missing) == INVALID_PARAMETER, "a value is required");
|
|
Require(sceAudio3dObjectUnreserve(0, 1) == 0, "unreserve");
|
|
Require(sceAudio3dPortClose(0) == 0, "close");
|
|
}
|
|
|
|
void CheckTerminate() {
|
|
std::uint32_t id = 7;
|
|
const Audio3dOpenParameters parameters = Defaults();
|
|
Require(Open(parameters, &id) == 0 && id == 0, "open before terminate");
|
|
Require(sceAudio3dTerminate() == NOT_READY, "terminate with an open port must be NOT_READY");
|
|
Require(sceAudio3dPortClose(0) == 0, "close before terminate");
|
|
Require(sceAudio3dTerminate() == 0, "terminate");
|
|
Require(sceAudio3dTerminate() == NOT_READY, "double terminate must be NOT_READY");
|
|
id = 7;
|
|
Require(Open(parameters, &id) == NOT_READY && id == 7, "open after terminate must be NOT_READY");
|
|
Require(sceAudio3dInitialize(0) == 0, "initialize after terminate");
|
|
Require(Open(parameters, &id) == 0 && id == 0, "open after reinitialize");
|
|
Require(sceAudio3dPortClose(0) == 0, "close after reinitialize");
|
|
Require(sceAudio3dTerminate() == 0, "terminate after reinitialize");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
CheckBeforeInitialize();
|
|
CheckOpenParameters();
|
|
CheckOpenClose();
|
|
CheckObjects();
|
|
CheckQueue();
|
|
CheckFlush();
|
|
CheckObjectAttributes();
|
|
CheckTerminate();
|
|
}
|