diff --git a/core/libs/prx/libSceNgs2.native/src/System.cpp b/core/libs/prx/libSceNgs2.native/src/System.cpp index 4b7acdf62c..048eb333cc 100644 --- a/core/libs/prx/libSceNgs2.native/src/System.cpp +++ b/core/libs/prx/libSceNgs2.native/src/System.cpp @@ -19,6 +19,10 @@ #include "prx/libc/include/General.hpp" #include "Ngs2Internal.hpp" +using Ngs2ParseReadHandler = std::int32_t (APS5_VABI *)(std::uintptr_t user_data, std::uint32_t offset, void* data, std::size_t size); + +extern "C" int APS5_VABI sceNgs2ParseWaveformData(const void* data, std::size_t data_size, Ngs2WaveformInfo* info); + static std::vector& Systems() { static auto* const systems = new std::vector(); return *systems; @@ -613,6 +617,62 @@ int APS5_VABI sceNgs2ReportUnregisterHandler(Ngs2Handle report_handle) { return SCE_NGS2_OK; } +int APS5_VABI sceNgs2SystemEnumHandles(uintptr_t* out_handles, uint32_t max_handles) { + constexpr int invalid_argument = static_cast(0x804A0053); + if (max_handles != 0 && out_handles == nullptr) return invalid_argument; + const Ngs2Lock lock; + uint32_t count = 0; + for (const auto* system : Systems()) { + if (out_handles != nullptr && count < max_handles) out_handles[count] = reinterpret_cast(system); + ++count; + } + return static_cast(count); +} + +int APS5_VABI sceNgs2SystemEnumRackHandles(uintptr_t system_handle, uintptr_t* out_handles, uint32_t max_handles) { + constexpr int invalid_argument = static_cast(0x804A0053); + constexpr int invalid_system_handle = static_cast(0x804A0230); + if (max_handles != 0 && out_handles == nullptr) return invalid_argument; + const Ngs2Lock lock; + const auto* system = Ngs2FindSystem(system_handle); + if (system == nullptr) return invalid_system_handle; + uint32_t count = 0; + for (const auto* rack : system->racks) { + if (out_handles != nullptr && count < max_handles) out_handles[count] = reinterpret_cast(rack); + ++count; + } + return static_cast(count); +} + +int APS5_VABI sceNgs2FftQuerySize(std::uint32_t length, std::uint32_t flags, std::uint64_t* size) { + static constexpr std::uint32_t samples[] = {64, 128, 256, 512, 1024}; + if (size == nullptr || length < 8 || length > 12) return -1; + std::uint32_t count = samples[length - 8]; + if ((flags & 2) != 0) count /= 2; + *size = static_cast(count) * 2 * 16 + 64; + return 0; +} + +int APS5_VABI sceNgs2ParseWaveformUser(std::uintptr_t handler, std::uintptr_t user_data, Ngs2WaveformInfo* out_info) { + constexpr int invalid_out_address = static_cast(0x804A0053); + constexpr int invalid_handler = static_cast(0x804A0408); + constexpr std::uint64_t max_waveform_bytes = 256ull * 1024 * 1024; + if (out_info == nullptr) return invalid_out_address; + if (handler == 0) return invalid_handler; + const auto read = reinterpret_cast(handler); + std::uint8_t header[12] = {}; + if (read(user_data, 0, header, sizeof(header)) < 0) return invalid_handler; + if (std::memcmp(header, "RIFF", 4) != 0 || std::memcmp(header + 8, "WAVE", 4) != 0) + return SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; + const std::uint32_t riff_size = static_cast(header[4]) | static_cast(header[5]) << 8 | + static_cast(header[6]) << 16 | static_cast(header[7]) << 24; + const std::uint64_t riff_end = 8ull + riff_size; + if (riff_end < 12 || riff_end > max_waveform_bytes) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; + std::vector bytes(static_cast(riff_end)); + if (read(user_data, 0, bytes.data(), bytes.size()) < 0) return invalid_handler; + return sceNgs2ParseWaveformData(bytes.data(), bytes.size(), out_info); +} + } #pragma GCC visibility pop diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp index bea3a96124..e0000ba149 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp @@ -401,6 +401,38 @@ static void CheckSetSampleRate() { Status(sceNgs2SystemDestroy(system, nullptr), SCE_NGS2_OK, "system destroy failed"); } +static void CheckFftQuerySize() { + std::uint64_t size = 0; + Require(sceNgs2FftQuerySize(8, 0, &size) == 0 && size == 2112); + Require(sceNgs2FftQuerySize(8, 2, &size) == 0 && size == 1088); + Require(sceNgs2FftQuerySize(9, 0, &size) == 0 && size == 4160); + Require(sceNgs2FftQuerySize(12, 2, &size) == 0 && size == 16448); + Require(sceNgs2FftQuerySize(7, 0, &size) == -1); + Require(sceNgs2FftQuerySize(13, 0, &size) == -1); + Require(sceNgs2FftQuerySize(8, 0, nullptr) == -1); +} + +static void CheckEnumeration() { + constexpr int invalidArgument = static_cast(0x804A0053u); + constexpr int invalidSystem = static_cast(0x804A0230u); + uintptr_t handles[8] = {}; + Require(sceNgs2SystemEnumHandles(nullptr, 0) >= 0); + Require(sceNgs2SystemEnumHandles(nullptr, 1) == invalidArgument); + const auto system = CreateSystem(); + const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_REVERB); + const int systems = sceNgs2SystemEnumHandles(handles, 8); + Require(systems >= 1); + bool found = false; + for (int i = 0; i < systems && i < 8; ++i) found = found || handles[i] == system; + Require(found); + Require(sceNgs2SystemEnumHandles(nullptr, 0) == systems); + Require(sceNgs2SystemEnumRackHandles(system, nullptr, 1) == invalidArgument); + Require(sceNgs2SystemEnumRackHandles(0x1234, handles, 8) == invalidSystem); + const int racks = sceNgs2SystemEnumRackHandles(system, handles, 8); + Require(racks == 1 && handles[0] == rack); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + int main() { try { constexpr int systemError = static_cast(0x804A8201u); @@ -441,6 +473,8 @@ int main() { CheckResetScheduler(); CheckSetGrainSamples(); CheckSetSampleRate(); + CheckFftQuerySize(); + CheckEnumeration(); CheckExportLinked(); std::cout << "NGS2 PS5 handle errors and validation precedence checked\n"; return 0; diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp index a818d0cf83..6e7e2d2694 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp @@ -241,8 +241,36 @@ static void TestFrameInfoErrors() { } } +static std::int32_t APS5_VABI ReadUserWaveform(std::uintptr_t user_data, std::uint32_t offset, void* data, std::size_t size) { + const auto& file = *reinterpret_cast*>(user_data); + if (offset > file.size() || size > file.size() - offset) return -1; + std::memcpy(data, file.data() + offset, size); + return static_cast(size); +} + +static void TestParseUser() { + const auto file = PcmFile(2, 44100, 16, 100); + Ngs2WaveformInfo expected{}; + Ngs2WaveformInfo actual{}; + Require(sceNgs2ParseWaveformData(file.data(), file.size(), &expected) == SCE_NGS2_OK); + Require(sceNgs2ParseWaveformUser(reinterpret_cast(&ReadUserWaveform), reinterpret_cast(&file), &actual) == SCE_NGS2_OK); + Require(std::memcmp(&expected, &actual, sizeof(expected)) == 0); + Require(sceNgs2ParseWaveformUser(0, 0, &actual) == static_cast(0x804A0408u)); + Require(sceNgs2ParseWaveformUser(reinterpret_cast(&ReadUserWaveform), reinterpret_cast(&file), nullptr) == + static_cast(0x804A0053u)); + auto bad = file; + bad[0] = 'X'; + Require(sceNgs2ParseWaveformUser(reinterpret_cast(&ReadUserWaveform), reinterpret_cast(&bad), &actual) == + SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT); + auto truncated = file; + truncated.resize(16); + Require(sceNgs2ParseWaveformUser(reinterpret_cast(&ReadUserWaveform), reinterpret_cast(&truncated), &actual) == + static_cast(0x804A0408u)); +} + int main() { TestParsePcm(); + TestParseUser(); TestParseUnknownFormats(); TestParseFile(); TestParseFileBank(); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp index 4c9cd946ef..e93319a57b 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp @@ -13,6 +13,10 @@ extern "C" { int APS5_VABI sceNgs2ParseWaveformData(const void*, size_t, Ngs2WaveformInfo*); int APS5_VABI sceNgs2ParseWaveformFile(const char*, uint32_t, Ngs2WaveformInfo*); +int APS5_VABI sceNgs2ParseWaveformUser(std::uintptr_t, std::uintptr_t, Ngs2WaveformInfo*); +int APS5_VABI sceNgs2FftQuerySize(std::uint32_t, std::uint32_t, std::uint64_t*); +int APS5_VABI sceNgs2SystemEnumHandles(std::uintptr_t*, std::uint32_t); +int APS5_VABI sceNgs2SystemEnumRackHandles(std::uintptr_t, std::uintptr_t*, std::uint32_t); int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat*, uint32_t, uint32_t, Ngs2WaveformBlock*); int APS5_VABI sceNgs2GetWaveformFrameInfo(const Ngs2WaveformFormat*, uint32_t*, uint32_t*, uint32_t*, uint32_t*); int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption*); diff --git a/core/libs/prx/libSceNgs2/PendingExports.cpp b/core/libs/prx/libSceNgs2/PendingExports.cpp index e1942c4923..4e1bc5c883 100644 --- a/core/libs/prx/libSceNgs2/PendingExports.cpp +++ b/core/libs/prx/libSceNgs2/PendingExports.cpp @@ -14,21 +14,5 @@ int APS5_VABI sceNgs2FftProcess() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNgs2FftQuerySize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNgs2ParseWaveformUser(std::uintptr_t handler, std::uintptr_t userData, void* outInfo) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNgs2SystemEnumHandles(std::uintptr_t* outHandles, std::uint32_t maxHandles) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNgs2SystemEnumRackHandles(std::uintptr_t systemHandle, std::uintptr_t* outHandles, std::uint32_t maxHandles) { - NotImplemented_nid_no_patch(__func__); - return 0; -} } diff --git a/core/libs/prx/libSceNgs2/include/Api.hpp b/core/libs/prx/libSceNgs2/include/Api.hpp index 3398761ed6..f37427267a 100644 --- a/core/libs/prx/libSceNgs2/include/Api.hpp +++ b/core/libs/prx/libSceNgs2/include/Api.hpp @@ -1,13 +1,14 @@ #pragma once #include "SceTypes.hpp" +#include "prx/libSceNgs2.native/include/Ngs2Types.hpp" extern "C" { int APS5_VABI sceNgs2FftInit(); int APS5_VABI sceNgs2FftProcess(); -int APS5_VABI sceNgs2FftQuerySize(); -int APS5_VABI sceNgs2ParseWaveformUser(std::uintptr_t handler, std::uintptr_t userData, void* outInfo); +int APS5_VABI sceNgs2FftQuerySize(std::uint32_t length, std::uint32_t flags, std::uint64_t* size); +int APS5_VABI sceNgs2ParseWaveformUser(std::uintptr_t handler, std::uintptr_t userData, Ngs2WaveformInfo* outInfo); int APS5_VABI sceNgs2SystemEnumHandles(std::uintptr_t* outHandles, std::uint32_t maxHandles); int APS5_VABI sceNgs2SystemEnumRackHandles(std::uintptr_t systemHandle, std::uintptr_t* outHandles, std::uint32_t maxHandles); diff --git a/docs/dev/debt/libSceNgs2.md b/docs/dev/debt/libSceNgs2.md index f25b3c941d..d587d2f82c 100644 --- a/docs/dev/debt/libSceNgs2.md +++ b/docs/dev/debt/libSceNgs2.md @@ -2,5 +2,5 @@ ### Unknown function info -- [sceNgs2FftInit, sceNgs2FftProcess, sceNgs2FftQuerySize](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) (libSceNgs2) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.c), but no credible prototypes were found in the cited sources. They are declared without parameters solely to resolve known imports and make no ABI claim. -- [sceNgs2ParseWaveformUser, sceNgs2SystemEnumHandles, sceNgs2SystemEnumRackHandles](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) (libSceNgs2) - names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.c); signatures follow [SharpProspero's Ngs2 bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs), not measured on a console. Opaque structure pointers use `void*`; these stubs throw for import resolution. +- [sceNgs2FftQuerySize](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2) - implemented from the firmware 11.00 module: lengths 8..12 select 64, 128, 256, 512 and 1024 samples, bit 1 of the flags halves the count, and the work size is `32 * count + 64`. [sceNgs2FftInit and sceNgs2FftProcess](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) still throw: the 7.3 KB AVX radix-8 kernel and its packed input/output layout are not recovered, and no dump imports these NIDs. +- [sceNgs2ParseWaveformUser, sceNgs2SystemEnumHandles, sceNgs2SystemEnumRackHandles](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2) - implemented from the firmware 11.00 module. `sceNgs2ParseWaveformUser` reads offset 0 with the caller's read handler, takes the RIFF size from the header and parses the bytes with the shared waveform parser; a null handler returns `0x804A0408` and a null output `0x804A0053`. The enumerators return the count of live systems or racks and fill up to the requested count; a null output with a nonzero count returns `0x804A0053` and an unknown system `0x804A0230`. No dump imports these NIDs.