Files
AnyPS5/core/libs/prx/libSceNgs2.native/src/System.cpp
T
gookie a2bb991171 fix(libSceNgs2.native): keep the system list and its mutex alive through static teardown
sceNgs2SystemRender and the other entry points look up their handles in a
function-local std::vector guarded by a function-local std::recursive_mutex.
Both were destroyed with the other statics when the process exits, while a
title's audio thread can still be rendering: std::exit runs the destructors
on the exiting thread and other threads keep running until the process
ends. The freed vector then no longer holds the system, so rendering
returns SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE, and a lookup that walks the
racks reads freed memory and crashes.

Both are now allocated once and never destroyed, as other driver state that
must outlive static teardown already is.

PPSA21402 (Balatro) printed "Error: sceNgs2SystemRender() 804A0230" and
segfaulted in Ngs2FindVoice (from sceNgs2VoiceGetState) when it was closed.

ngs2_render now renders from an atexit handler registered before the first
NGS2 call, which runs after the library's statics are destroyed; without
this change it fails on that render.
2026-10-07 14:00:37 +02:00

257 lines
10 KiB
C++

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <memory>
#include <mutex>
#include <new>
#include <stdexcept>
#include <string>
#include <vector>
#include "prx/libc/include/General.hpp"
#include "Ngs2Internal.hpp"
static std::vector<Ngs2System*>& Systems() {
static auto* const systems = new std::vector<Ngs2System*>();
return *systems;
}
std::string Ngs2Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%x", value);
return text;
}
std::recursive_mutex& Ngs2Mutex() {
static auto* const mutex = new std::recursive_mutex();
return *mutex;
}
Ngs2System* Ngs2FindSystem(Ngs2Handle handle) {
auto& systems = Systems();
const auto found = std::find(systems.begin(), systems.end(), reinterpret_cast<Ngs2System*>(handle));
return found == systems.end() ? nullptr : *found;
}
Ngs2Rack* Ngs2FindRack(Ngs2Handle handle) {
for (auto* system : Systems()) {
for (auto* rack : system->racks) {
if (reinterpret_cast<Ngs2Handle>(rack) == handle) return rack;
}
}
return nullptr;
}
Ngs2Voice* Ngs2FindVoice(Ngs2Handle handle) {
for (auto* system : Systems()) {
for (auto* rack : system->racks) {
for (auto& voice : rack->voices) {
if (reinterpret_cast<Ngs2Handle>(&voice) == handle) return &voice;
}
}
}
return nullptr;
}
void* Ngs2Place(const Ngs2ContextBufferInfo* bufferInfo, std::size_t size, std::size_t alignment) {
if (bufferInfo == nullptr || bufferInfo->host_buffer == nullptr || bufferInfo->host_buffer_size < size) {
throw std::invalid_argument("NGS2: the context buffer is missing or smaller than the queried size");
}
if (reinterpret_cast<std::uintptr_t>(bufferInfo->host_buffer) % alignment != 0) {
throw std::invalid_argument("NGS2: the context buffer is not aligned to " + std::to_string(alignment) + " bytes");
}
return bufferInfo->host_buffer;
}
int Ngs2ReleaseBuffer(const Ngs2BufferAllocator& allocator, Ngs2ContextBufferInfo bufferInfo, Ngs2ContextBufferInfo* outBufferInfo) {
if (outBufferInfo != nullptr) *outBufferInfo = allocator.free_handler == nullptr ? bufferInfo : Ngs2ContextBufferInfo{};
return allocator.free_handler == nullptr ? SCE_NGS2_OK : allocator.free_handler(&bufferInfo);
}
static Ngs2ContextBufferInfo Allocate(const Ngs2BufferAllocator* allocator, std::size_t size) {
if (allocator == nullptr || allocator->alloc_handler == nullptr || allocator->free_handler == nullptr) APS5_INVALID_ARG_EX;
Ngs2ContextBufferInfo bufferInfo{};
bufferInfo.host_buffer_size = size;
bufferInfo.user_data = allocator->user_data;
const int result = allocator->alloc_handler(&bufferInfo);
if (result != SCE_NGS2_OK) throw std::runtime_error("NGS2: the allocator handler failed with " + std::to_string(result));
return bufferInfo;
}
static Ngs2SystemOption DefaultSystemOption() {
Ngs2SystemOption option{};
option.size = sizeof(Ngs2SystemOption);
option.max_grain_samples = 512;
option.num_grain_samples = 256;
option.sample_rate = 48000;
return option;
}
static Ngs2SystemOption CheckedSystemOption(const Ngs2SystemOption* option) {
if (option == nullptr) return DefaultSystemOption();
if (option->size != sizeof(Ngs2SystemOption)) throw std::invalid_argument("NGS2: unexpected system option size " + std::to_string(option->size));
if (option->flags != 0) throw std::runtime_error("NGS2: system option flags are not implemented");
if (option->sample_rate == 0 || option->max_grain_samples < MIN_GRAIN_SAMPLES || option->num_grain_samples == 0 ||
option->num_grain_samples > option->max_grain_samples) {
throw std::invalid_argument("NGS2: invalid system grain or sample rate");
}
return *option;
}
static int CreateSystem(const Ngs2SystemOption& option, const Ngs2ContextBufferInfo& bufferInfo, const Ngs2BufferAllocator& allocator, Ngs2Handle* handle) {
static std::uint32_t nextUid = 1;
auto* system = new (Ngs2Place(&bufferInfo, sizeof(Ngs2System), alignof(Ngs2System))) Ngs2System{};
system->option = option;
system->bufferInfo = bufferInfo;
system->allocator = allocator;
system->uid = nextUid++;
Systems().push_back(system);
*handle = reinterpret_cast<Ngs2Handle>(system);
return SCE_NGS2_OK;
}
#pragma GCC visibility push(default)
extern "C" {
int APS5_VABI sceNgs2SystemQueryBufferSize(const Ngs2SystemOption* option, Ngs2ContextBufferInfo* buffer_info) {
if (buffer_info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
CheckedSystemOption(option);
*buffer_info = {};
buffer_info->host_buffer_size = sizeof(Ngs2System);
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemCreate(const Ngs2SystemOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) {
if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
const auto checked = CheckedSystemOption(option);
if (buffer_info == nullptr) APS5_INVALID_ARG_EX;
std::lock_guard lock(Ngs2Mutex());
return CreateSystem(checked, *buffer_info, {}, handle);
}
int APS5_VABI sceNgs2SystemCreateWithAllocator(const Ngs2SystemOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) {
if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
const auto checked = CheckedSystemOption(option);
const auto bufferInfo = Allocate(allocator, sizeof(Ngs2System));
std::lock_guard lock(Ngs2Mutex());
return CreateSystem(checked, bufferInfo, *allocator, handle);
}
int APS5_VABI sceNgs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferInfo* buffer_info) {
std::lock_guard lock(Ngs2Mutex());
if (buffer_info != nullptr) *buffer_info = {};
auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
while (!system->racks.empty()) {
const int result = Ngs2DestroyRack(*system->racks.back(), nullptr);
if (result != SCE_NGS2_OK) return result;
}
std::erase(Systems(), system);
const auto bufferInfo = system->bufferInfo;
const auto allocator = system->allocator;
std::destroy_at(system);
return Ngs2ReleaseBuffer(allocator, bufferInfo, buffer_info);
}
int APS5_VABI sceNgs2SystemGetInfo(uintptr_t system_handle, Ngs2SystemInfo* info, size_t info_size) {
if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
if (info_size != sizeof(Ngs2SystemInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE;
std::lock_guard lock(Ngs2Mutex());
const auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
*info = {};
std::memcpy(info->name, system->option.name, sizeof(info->name));
info->system_handle = system_handle;
info->buffer_info = system->bufferInfo;
info->uid = system->uid;
info->min_grain_samples = MIN_GRAIN_SAMPLES;
info->max_grain_samples = system->option.max_grain_samples;
info->state_flags = 1;
info->rack_count = static_cast<std::uint32_t>(system->racks.size());
info->render_count = system->renderCount;
info->sample_rate = system->option.sample_rate;
info->num_grain_samples = system->option.num_grain_samples;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num_samples) {
std::lock_guard lock(Ngs2Mutex());
auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
if (num_samples == 0 || num_samples > system->option.max_grain_samples) APS5_INVALID_ARG_EX;
system->option.num_grain_samples = num_samples;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemSetSampleRate(uintptr_t system_handle, uint32_t sample_rate) {
std::lock_guard lock(Ngs2Mutex());
auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
if (sample_rate == 0) APS5_INVALID_ARG_EX;
for (const auto* rack : system->racks) {
for (const auto& voice : rack->voices) {
for (const auto& filter : voice.filters) {
if (filter.enabled && sample_rate != system->option.sample_rate) {
throw std::runtime_error("NGS2: changing the sample rate under an enabled sampler filter is not implemented");
}
}
}
}
system->option.sample_rate = sample_rate;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemSetUserData(uintptr_t system_handle, uintptr_t user_data) {
std::lock_guard lock(Ngs2Mutex());
auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
system->userData = user_data;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemGetUserData(uintptr_t system_handle, uintptr_t* user_data) {
std::lock_guard lock(Ngs2Mutex());
const auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
if (user_data == nullptr) APS5_INVALID_ARG_EX;
*user_data = system->userData;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemLock(uintptr_t system_handle) {
Ngs2Mutex().lock();
if (Ngs2FindSystem(system_handle) != nullptr) return SCE_NGS2_OK;
Ngs2Mutex().unlock();
return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
}
int APS5_VABI sceNgs2SystemUnlock(uintptr_t system_handle) {
std::lock_guard lock(Ngs2Mutex());
if (Ngs2FindSystem(system_handle) == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
Ngs2Mutex().unlock();
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info, uint32_t num_buffer_info) {
std::lock_guard lock(Ngs2Mutex());
auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
if (buffer_info == nullptr || num_buffer_info == 0) APS5_INVALID_ARG_EX;
Ngs2RenderSystem(*system, buffer_info, num_buffer_info);
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption* option) {
if (option == nullptr) APS5_INVALID_ARG_EX;
*option = DefaultSystemOption();
return SCE_NGS2_OK;
}
}
#pragma GCC visibility pop