From af9cbb8e8aeb5c5cae59779ccc4ae767d8eee7ba Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Tue, 24 Feb 2026 08:00:17 +0100 Subject: [PATCH 01/13] Fix some logic bugs in sceHttpUriParse (#4067) --- src/core/libraries/network/http.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp index 8bc9b51f0..4d6886908 100644 --- a/src/core/libraries/network/http.cpp +++ b/src/core/libraries/network/http.cpp @@ -935,18 +935,24 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v pathLength++; } - // Ensure the path starts with '/' - if (pathLength > 0 && pathStart[0] != '/') { + if (pathLength > 0) { // Prepend '/' to the path requiredSize += pathLength + 2; // Include '/' and null terminator if (pool && prepare < requiredSize) { - LOG_ERROR(Lib_Http, "out of memory"); + LOG_ERROR(Lib_Http, "out of memory, provided size: {}, required size: {}", + prepare, requiredSize); return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; } if (out && pool) { out->path = (char*)pool + (requiredSize - pathLength - 2); + out->username = (char*)pool + (requiredSize - pathLength - 3); + out->password = (char*)pool + (requiredSize - pathLength - 3); + out->hostname = (char*)pool + (requiredSize - pathLength - 3); + out->query = (char*)pool + (requiredSize - pathLength - 3); + out->fragment = (char*)pool + (requiredSize - pathLength - 3); + out->username[0] = '\0'; out->path[0] = '/'; // Add leading '/' memcpy(out->path + 1, pathStart, pathLength); out->path[pathLength + 1] = '\0'; @@ -969,6 +975,19 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v // Move past the path offset += pathLength; + } else { + // Parse the path (everything after the slashes) + char* pathStart = (char*)srcUri + offset; + u64 pathLength = 0; + while (pathStart[pathLength] && pathStart[pathLength] != '?' && + pathStart[pathLength] != '#') { + pathLength++; + } + + if (pathLength > 0) { + requiredSize += pathLength + 3; // Add '/' and null terminator, and the dummy + // null character for the other fields + } } } From e1ecd8e98c12128c6dca9044f6688dcec1b9cfc2 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Tue, 24 Feb 2026 20:35:05 +0300 Subject: [PATCH 02/13] threads: initialize tls on thread creation (take 2) (#4070) --- src/core/libraries/avplayer/avplayer_impl.cpp | 16 +++--- .../libraries/avplayer/avplayer_state.cpp | 2 +- src/core/libraries/ime/ime.cpp | 8 +-- src/core/libraries/ime/ime_dialog_ui.cpp | 5 +- src/core/libraries/kernel/threads.cpp | 11 ++++ src/core/libraries/kernel/threads.h | 3 ++ .../libraries/kernel/threads/exception.cpp | 2 +- src/core/libraries/kernel/threads/pthread.cpp | 11 +++- .../libraries/kernel/threads/pthread_spec.cpp | 2 +- src/core/libraries/network/net_ctl_obj.cpp | 4 +- src/core/libraries/ngs2/ngs2.cpp | 4 +- .../libraries/usbd/emulated/dimensions.cpp | 53 ++++++++++++++----- src/core/libraries/usbd/emulated/dimensions.h | 2 + src/core/linker.cpp | 10 ++-- src/core/module.cpp | 2 +- src/core/tls.cpp | 2 +- src/core/tls.h | 25 +-------- 17 files changed, 94 insertions(+), 68 deletions(-) diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index 138747da4..db32862ad 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -12,28 +12,28 @@ void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(allocate, ptr, alignment, size); + return allocate(ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(deallocate, ptr, memory); + return deallocate(ptr, memory); } void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(allocate, ptr, alignment, size); + return allocate(ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(deallocate, ptr, memory); + return deallocate(ptr, memory); } int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { @@ -42,7 +42,7 @@ int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { const auto open = self->m_init_data_original.file_replacement.open; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(open, ptr, filename); + return open(ptr, filename); } int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { @@ -51,7 +51,7 @@ int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { const auto close = self->m_init_data_original.file_replacement.close; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(close, ptr); + return close(ptr); } int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) { @@ -60,7 +60,7 @@ int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position const auto read_offset = self->m_init_data_original.file_replacement.read_offset; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(read_offset, ptr, buffer, position, length); + return read_offset(ptr, buffer, position, length); } u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { @@ -69,7 +69,7 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { const auto size = self->m_init_data_original.file_replacement.size; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(size, ptr); + return size(ptr); } AvPlayerInitData AvPlayer::StubInitData(const AvPlayerInitData& data) { diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index e1b11840e..dbaa36d18 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -92,7 +92,7 @@ void AvPlayerState::DefaultEventCallback(void* opaque, AvPlayerEvents event_id, const auto callback = self->m_event_replacement.event_callback; const auto ptr = self->m_event_replacement.object_ptr; if (callback != nullptr) { - Core::ExecuteGuest(callback, ptr, event_id, 0, event_data); + callback(ptr, event_id, 0, event_data); } } diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index 258cc61e1..96ae446fa 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -99,16 +99,16 @@ public: if (m_ime_mode) { OrbisImeParam param = m_param.ime; if (use_param_handler) { - Core::ExecuteGuest(param.handler, param.arg, event); + param.handler(param.arg, event); } else { - Core::ExecuteGuest(handler, param.arg, event); + handler(param.arg, event); } } else { OrbisImeKeyboardParam param = m_param.key; if (use_param_handler) { - Core::ExecuteGuest(param.handler, param.arg, event); + param.handler(param.arg, event); } else { - Core::ExecuteGuest(handler, param.arg, event); + handler(param.arg, event); } } } diff --git a/src/core/libraries/ime/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp index 4a95c60c9..9611e7c49 100644 --- a/src/core/libraries/ime/ime_dialog_ui.cpp +++ b/src/core/libraries/ime/ime_dialog_ui.cpp @@ -131,8 +131,7 @@ bool ImeDialogState::CallTextFilter() { return false; } - int ret = - Core::ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length); + int ret = text_filter(out_text, &out_text_length, src_text, src_text_length); if (ret != 0) { return false; @@ -153,7 +152,7 @@ bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* return true; } - int ret = Core::ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr); + int ret = keyboard_filter(src_keycode, out_keycode, out_status, nullptr); return ret == 0; } diff --git a/src/core/libraries/kernel/threads.cpp b/src/core/libraries/kernel/threads.cpp index 082a52b67..083dc8ee1 100644 --- a/src/core/libraries/kernel/threads.cpp +++ b/src/core/libraries/kernel/threads.cpp @@ -7,6 +7,17 @@ namespace Libraries::Kernel { +void PS4_SYSV_ABI ClearStack() { + void* const stackaddr_attr = Libraries::Kernel::g_curthread->attr.stackaddr_attr; + void* volatile sp; + asm("mov %%rsp, %0" : "=rm"(sp)); + // leave a safety net of 64 bytes for memset + const size_t size = ((uintptr_t)sp - (uintptr_t)stackaddr_attr) - 64; + void* volatile buf = alloca(size); + memset(buf, 0, size); + sp = nullptr; +} + void RegisterThreads(Core::Loader::SymbolsResolver* sym) { RegisterMutex(sym); RegisterCond(sym); diff --git a/src/core/libraries/kernel/threads.h b/src/core/libraries/kernel/threads.h index 42ab0b13f..81535352c 100644 --- a/src/core/libraries/kernel/threads.h +++ b/src/core/libraries/kernel/threads.h @@ -27,6 +27,7 @@ int PS4_SYSV_ABI posix_pthread_create(PthreadT* thread, const PthreadAttrT* attr PthreadEntryFunc start_routine, void* arg); int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return); +int PS4_SYSV_ABI posix_pthread_detach(PthreadT pthread); int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr); int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type); @@ -40,6 +41,8 @@ int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex); void RegisterThreads(Core::Loader::SymbolsResolver* sym); +void PS4_SYSV_ABI ClearStack(); + class Thread { public: explicit Thread() = default; diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 0b27f2bd8..cf36da0cc 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -317,7 +317,7 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { u64 res = NtQueueApcThreadEx(reinterpret_cast(thread->native_thr.GetHandle()), option, ExceptionHandler, (void*)thread->name.c_str(), - (void*)native_signum, nullptr); + (void*)(s64)native_signum, nullptr); ASSERT(res == 0); #endif return ORBIS_OK; diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp index 20bd20f4b..da9e1600f 100644 --- a/src/core/libraries/kernel/threads/pthread.cpp +++ b/src/core/libraries/kernel/threads/pthread.cpp @@ -199,10 +199,17 @@ static void RunThread(void* arg) { g_curthread = curthread; Common::SetCurrentThreadName(curthread->name.c_str()); DebugState.AddCurrentThreadToGuestList(); + Core::InitializeTLS(); + + curthread->native_thr.Initialize(); + + // Clear the stack before running the guest thread + if (False(g_curthread->attr.flags & PthreadAttrFlags::StackUser)) { + ClearStack(); + } /* Run the current thread's start routine with argument: */ - curthread->native_thr.Initialize(); - void* ret = Core::ExecuteGuest(curthread->start_routine, curthread->arg); + void* ret = curthread->start_routine(curthread->arg); /* Remove thread from tracking */ DebugState.RemoveCurrentThreadFromGuestList(); diff --git a/src/core/libraries/kernel/threads/pthread_spec.cpp b/src/core/libraries/kernel/threads/pthread_spec.cpp index 094866a5a..38032f174 100644 --- a/src/core/libraries/kernel/threads/pthread_spec.cpp +++ b/src/core/libraries/kernel/threads/pthread_spec.cpp @@ -84,7 +84,7 @@ void _thread_cleanupspecific() { * destructor: */ lk.unlock(); - Core::ExecuteGuest(destructor, data); + destructor(data); lk.lock(); } } diff --git a/src/core/libraries/network/net_ctl_obj.cpp b/src/core/libraries/network/net_ctl_obj.cpp index a295477b6..a4081cd11 100644 --- a/src/core/libraries/network/net_ctl_obj.cpp +++ b/src/core/libraries/network/net_ctl_obj.cpp @@ -50,7 +50,7 @@ void NetCtlInternal::CheckCallback() { : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : callbacks) { if (func != nullptr) { - Core::ExecuteGuest(func, event, arg); + func(event, arg); } } } @@ -61,7 +61,7 @@ void NetCtlInternal::CheckNpToolkitCallback() { : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : nptool_callbacks) { if (func != nullptr) { - Core::ExecuteGuest(func, event, arg); + func(event, arg); } } } diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index 2f785f9a0..97d19c352 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -160,13 +160,13 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* o result = SystemSetup(option, &bufferInfo, 0, 0); if (result >= 0) { uintptr_t sysUserData = allocator->userData; - result = Core::ExecuteGuest(hostAlloc, &bufferInfo); + result = hostAlloc(&bufferInfo); if (result >= 0) { OrbisNgs2Handle* handleCopy = outHandle; result = SystemSetup(option, &bufferInfo, hostFree, handleCopy); if (result < 0) { if (hostFree) { - Core::ExecuteGuest(hostFree, &bufferInfo); + hostFree(&bufferInfo); } } } diff --git a/src/core/libraries/usbd/emulated/dimensions.cpp b/src/core/libraries/usbd/emulated/dimensions.cpp index 272f2f649..452968840 100644 --- a/src/core/libraries/usbd/emulated/dimensions.cpp +++ b/src/core/libraries/usbd/emulated/dimensions.cpp @@ -3,6 +3,9 @@ #include "dimensions.h" +#include "core/libraries/kernel/threads.h" +#include "core/tls.h" + #include #include @@ -619,22 +622,46 @@ libusb_transfer_status DimensionsBackend::HandleAsyncTransfer(libusb_transfer* t return LIBUSB_TRANSFER_COMPLETED; } +struct WriteThreadArgs { + DimensionsBackend* self; + libusb_transfer* transfer; +}; + +void* PS4_SYSV_ABI DimensionsBackend::WriteThread(void* arg) { + auto* args = reinterpret_cast(arg); + + auto* self = args->self; + auto* transfer = args->transfer; + + self->HandleAsyncTransfer(transfer); + + const u8 flags = transfer->flags; + transfer->status = LIBUSB_TRANSFER_COMPLETED; + transfer->actual_length = transfer->length; + if (transfer->callback) { + transfer->callback(transfer); + } + if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) { + libusb_free_transfer(transfer); + } + delete args; + return nullptr; +} + s32 DimensionsBackend::SubmitTransfer(libusb_transfer* transfer) { if (transfer->endpoint == 0x01) { - std::thread write_thread([this, transfer] { - HandleAsyncTransfer(transfer); + using namespace Libraries::Kernel; + + PthreadAttrT attr{}; + posix_pthread_attr_init(&attr); + PthreadT thread{}; + auto* args = new WriteThreadArgs(); + args->self = this; + args->transfer = transfer; + posix_pthread_create(&thread, &attr, HOST_CALL(DimensionsBackend::WriteThread), args); + posix_pthread_attr_destroy(&attr); + posix_pthread_detach(thread); - const u8 flags = transfer->flags; - transfer->status = LIBUSB_TRANSFER_COMPLETED; - transfer->actual_length = transfer->length; - if (transfer->callback) { - transfer->callback(transfer); - } - if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) { - libusb_free_transfer(transfer); - } - }); - write_thread.detach(); return LIBUSB_SUCCESS; } diff --git a/src/core/libraries/usbd/emulated/dimensions.h b/src/core/libraries/usbd/emulated/dimensions.h index d9573b5f4..bc409f7c3 100644 --- a/src/core/libraries/usbd/emulated/dimensions.h +++ b/src/core/libraries/usbd/emulated/dimensions.h @@ -103,6 +103,8 @@ protected: std::queue> m_queries; private: + static void* PS4_SYSV_ABI WriteThread(void* arg); + std::shared_ptr m_dimensions_toypad = std::make_shared(); std::array m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00}; diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 7a0653e9f..0b80ecacc 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -135,7 +135,8 @@ void Linker::Execute(const std::vector& args) { } } params.entry_addr = module->GetEntryAddress(); - ExecuteGuest(RunMainEntry, ¶ms); + Libraries::Kernel::ClearStack(); + RunMainEntry(¶ms); }); } @@ -379,8 +380,7 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) { if (!addr) { // Module was just loaded by above code. Allocate TLS block for it. const u32 init_image_size = module->tls.init_image_size; - u8* dest = reinterpret_cast( - Core::ExecuteGuest(heap_api->heap_malloc, module->tls.image_size)); + u8* dest = reinterpret_cast(heap_api->heap_malloc(module->tls.image_size)); const u8* src = reinterpret_cast(module->tls.image_virtual_addr); std::memcpy(dest, src, init_image_size); std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size); @@ -412,7 +412,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) { ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread"); } else { if (heap_api) { - addr_out = Core::ExecuteGuest(heap_api->heap_malloc, total_tls_size); + addr_out = heap_api->heap_malloc(total_tls_size); } else { addr_out = std::malloc(total_tls_size); } @@ -422,7 +422,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) { void Linker::FreeTlsForNonPrimaryThread(void* pointer) { if (heap_api) { - Core::ExecuteGuest(heap_api->heap_free, pointer); + heap_api->heap_free(pointer); } else { std::free(pointer); } diff --git a/src/core/module.cpp b/src/core/module.cpp index 127e74293..7e9d74a09 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -97,7 +97,7 @@ Module::~Module() = default; s32 Module::Start(u64 args, const void* argp, void* param) { LOG_INFO(Core_Linker, "Module started : {}", name); const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress(); - return ExecuteGuest(reinterpret_cast(addr), args, argp, param); + return reinterpret_cast(addr)(args, argp, param); } void Module::LoadModuleToMemory(u32& max_tls_index) { diff --git a/src/core/tls.cpp b/src/core/tls.cpp index 57ed20f38..8b926cb39 100644 --- a/src/core/tls.cpp +++ b/src/core/tls.cpp @@ -198,7 +198,7 @@ Tcb* GetTcbBase() { thread_local std::once_flag init_tls_flag; -void EnsureThreadInitialized() { +void InitializeTLS() { std::call_once(init_tls_flag, [] { SetTcbBase(Libraries::Kernel::g_curthread->tcb); }); } diff --git a/src/core/tls.h b/src/core/tls.h index 27de518ea..2d94488f7 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -43,30 +43,7 @@ void SetTcbBase(void* image_address); Tcb* GetTcbBase(); /// Makes sure TLS is initialized for the thread before entering guest. -void EnsureThreadInitialized(); - -template -#ifdef __clang__ -__attribute__((optnone)) -#else -__attribute__((optimize("O0"))) -#endif -void ClearStack() { - volatile void* buf = alloca(size); - memset(const_cast(buf), 0, size); - buf = nullptr; -} - -template -ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { - EnsureThreadInitialized(); - // clear stack to avoid trash from EnsureThreadInitialized - auto* tcb = GetTcbBase(); - if (tcb != nullptr && tcb->tcb_fiber == nullptr) { - ClearStack<12_KB>(); - } - return func(std::forward(args)...); -} +void InitializeTLS(); template struct HostCallWrapperImpl; From f91b8410fd5b605dbd055e12b281b904a02d4d17 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 26 Feb 2026 15:31:50 +0200 Subject: [PATCH 03/13] SDL Audio3d (#4077) * implemented dummy audio3d for sdl * removed sdl::free --- src/core/libraries/audio3d/audio3d.cpp | 599 +++++++++++++++++++++---- src/core/libraries/audio3d/audio3d.h | 51 ++- 2 files changed, 549 insertions(+), 101 deletions(-) diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp index 3f5fdcf78..989679107 100644 --- a/src/core/libraries/audio3d/audio3d.cpp +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include +#include #include #include "common/assert.h" @@ -20,12 +21,21 @@ static constexpr u32 AUDIO3D_SAMPLE_RATE = 48000; static constexpr AudioOut::OrbisAudioOutParamFormat AUDIO3D_OUTPUT_FORMAT = AudioOut::OrbisAudioOutParamFormat::S16Stereo; static constexpr u32 AUDIO3D_OUTPUT_NUM_CHANNELS = 2; -static constexpr u32 AUDIO3D_OUTPUT_BUFFER_FRAMES = 0x100; static std::unique_ptr state; s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) { LOG_INFO(Lib_Audio3d, "called, handle = {}", handle); + + // Remove from any port that was tracking this handle. + if (state) { + for (auto& [port_id, port] : state->ports) { + std::scoped_lock lock{port.mutex}; + auto& handles = port.audioout_handles; + handles.erase(std::remove(handles.begin(), handles.end(), handle), handles.end()); + } + } + return AudioOut::sceAudioOutClose(handle); } @@ -42,13 +52,21 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen( return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } + std::scoped_lock lock{state->ports[port_id].mutex}; if (len != state->ports[port_id].parameters.granularity) { LOG_ERROR(Lib_Audio3d, "len != state->ports[port_id].parameters.granularity"); return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - return sceAudioOutOpen(user_id, static_cast(type), index, len, - freq, param); + const s32 handle = sceAudioOutOpen(user_id, static_cast(type), + index, len, freq, param); + if (handle < 0) { + return handle; + } + + // Track this handle in the port so sceAudio3dPortFlush can use it for sync. + state->ports[port_id].audioout_handles.push_back(handle); + return handle; } s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(const s32 handle, void* ptr) { @@ -79,34 +97,31 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* p return AudioOut::sceAudioOutOutputs(param, num); } -static s32 PortQueueAudio(Port& port, const OrbisAudio3dPcm& pcm, const u32 num_channels) { - // Audio3d output is configured for stereo signed 16-bit PCM. Convert the data to match. - const SDL_AudioSpec src_spec = { - .format = pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? SDL_AUDIO_S16LE - : SDL_AUDIO_F32LE, - .channels = static_cast(num_channels), - .freq = AUDIO3D_SAMPLE_RATE, - }; - constexpr SDL_AudioSpec dst_spec = { - .format = SDL_AUDIO_S16LE, - .channels = AUDIO3D_OUTPUT_NUM_CHANNELS, - .freq = AUDIO3D_SAMPLE_RATE, - }; - const auto src_size = pcm.num_samples * - (pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? 2 : 4) * - num_channels; +static s32 ConvertAndEnqueue(std::deque& queue, const OrbisAudio3dPcm& pcm, + const u32 num_channels, const u32 granularity) { + if (!pcm.sample_buffer || !pcm.num_samples) { + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } - u8* dst_data; - int dst_len; - if (!SDL_ConvertAudioSamples(&src_spec, static_cast(pcm.sample_buffer), - static_cast(src_size), &dst_spec, &dst_data, &dst_len)) { - LOG_ERROR(Lib_Audio3d, "SDL_ConvertAudioSamples failed: {}", SDL_GetError()); + const u32 bytes_per_sample = + (pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) ? sizeof(s16) : sizeof(float); + + // Always allocate exactly granularity samples (zeroed = silence for padding). + const u32 dst_bytes = granularity * num_channels * bytes_per_sample; + u8* copy = static_cast(std::calloc(1, dst_bytes)); + if (!copy) { return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY; } - port.queue.emplace_back(AudioData{ - .sample_buffer = dst_data, - .num_samples = pcm.num_samples, + // Copy min(provided, granularity) samples — extra are dropped, shortage stays zero. + const u32 samples_to_copy = std::min(pcm.num_samples, granularity); + std::memcpy(copy, pcm.sample_buffer, samples_to_copy * num_channels * bytes_per_sample); + + queue.emplace_back(AudioData{ + .sample_buffer = copy, + .num_samples = granularity, + .num_channels = num_channels, + .format = pcm.format, }); return ORBIS_OK; } @@ -145,8 +160,8 @@ s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32 return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - if (num_channels != 2 && num_channels != 8) { - LOG_ERROR(Lib_Audio3d, "num_channels != 2 && num_channels != 8"); + if (num_channels != 2 && num_channels != 6 && num_channels != 8) { + LOG_ERROR(Lib_Audio3d, "num_channels must be 2, 6, or 8"); return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } @@ -167,13 +182,14 @@ s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32 } } - return PortQueueAudio(state->ports[port_id], - OrbisAudio3dPcm{ - .format = format, - .sample_buffer = buffer, - .num_samples = num_samples, - }, - num_channels); + std::scoped_lock lock{state->ports[port_id].mutex}; + return ConvertAndEnqueue(state->ports[port_id].bed_queue, + OrbisAudio3dPcm{ + .format = format, + .sample_buffer = buffer, + .num_samples = num_samples, + }, + num_channels, state->ports[port_id].parameters.granularity); } s32 PS4_SYSV_ABI sceAudio3dCreateSpeakerArray() { @@ -237,15 +253,6 @@ s32 PS4_SYSV_ABI sceAudio3dInitialize(const s64 reserved) { return init_ret; } - AudioOut::OrbisAudioOutParamExtendedInformation ext_info{}; - ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT); - state->audio_out_handle = - AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0, - AUDIO3D_OUTPUT_BUFFER_FRAMES, AUDIO3D_SAMPLE_RATE, ext_info); - if (state->audio_out_handle < 0) { - return state->audio_out_handle; - } - return ORBIS_OK; } @@ -254,18 +261,84 @@ s32 PS4_SYSV_ABI sceAudio3dObjectReserve(const OrbisAudio3dPortId port_id, LOG_INFO(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id, static_cast(object_id)); - if (!state->ports.contains(port_id)) { - LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); - return ORBIS_AUDIO3D_ERROR_INVALID_PORT; - } - if (!object_id) { LOG_ERROR(Lib_Audio3d, "!object_id"); return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - static int last_id = 0; - *object_id = ++last_id; + *object_id = ORBIS_AUDIO3D_OBJECT_INVALID; + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + + // Enforce the max_objects limit set at PortOpen time. + if (port.objects.size() >= port.parameters.max_objects) { + LOG_ERROR(Lib_Audio3d, "port has no available objects (max_objects = {})", + port.parameters.max_objects); + return ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES; + } + + // counter lives in the Port so it resets when the port is closed and reopened, + do { + ++port.next_object_id; + } while (port.next_object_id == 0 || port.next_object_id == ORBIS_AUDIO3D_OBJECT_INVALID || + port.objects.contains(port.next_object_id)); + + *object_id = port.next_object_id; + port.objects.emplace(*object_id, ObjectState{}); + LOG_INFO(Lib_Audio3d, "reserved object_id = {}", *object_id); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dObjectSetAttribute(const OrbisAudio3dPortId port_id, + const OrbisAudio3dObjectId object_id, + const OrbisAudio3dAttributeId attribute_id, + const void* attribute, const u64 attribute_size) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, object_id = {}, attribute_id = {:#x}, size = {}", + port_id, object_id, static_cast(attribute_id), attribute_size); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + if (!port.objects.contains(object_id)) { + LOG_DEBUG(Lib_Audio3d, "object_id {} not reserved (race with Unreserve?), no-op", + object_id); + return ORBIS_OK; + } + + if (!attribute_size && + attribute_id != OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) { + LOG_ERROR(Lib_Audio3d, "!attribute_size for non-reset attribute"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + auto& obj = port.objects[object_id]; + + // RESET_STATE clears all attributes and queued PCM; it takes no value. + if (attribute_id == OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) { + for (auto& data : obj.pcm_queue) { + std::free(data.sample_buffer); + } + obj.pcm_queue.clear(); + obj.persistent_attributes.clear(); + LOG_DEBUG(Lib_Audio3d, "RESET_STATE for object {}", object_id); + return ORBIS_OK; + } + + // we don't handle any attributes yet, but store them in the ObjectState so they're available + // when we do + const auto* src = static_cast(attribute); + obj.persistent_attributes[static_cast(attribute_id)].assign(src, src + attribute_size); return ORBIS_OK; } @@ -283,32 +356,95 @@ s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(const OrbisAudio3dPortId port_id, return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } - auto& port = state->ports[port_id]; + if (!num_attributes || !attribute_array) { + LOG_ERROR(Lib_Audio3d, "!num_attributes || !attribute_array"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + if (!port.objects.contains(object_id)) { + LOG_DEBUG(Lib_Audio3d, "object_id {} not reserved", object_id); + return ORBIS_OK; + } + + auto& obj = port.objects[object_id]; + + for (u64 i = 0; i < num_attributes; i++) { + if (attribute_array[i].attribute_id == + OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) { + for (auto& data : obj.pcm_queue) { + std::free(data.sample_buffer); + } + obj.pcm_queue.clear(); + obj.persistent_attributes.clear(); + LOG_DEBUG(Lib_Audio3d, "RESET_STATE for object {}", object_id); + break; // Only one reset is needed even if listed multiple times. + } + } + + // apply all other attributes. for (u64 i = 0; i < num_attributes; i++) { const auto& attribute = attribute_array[i]; switch (attribute.attribute_id) { + case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE: + break; // Already applied in first pass above. case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_PCM: { + if (attribute.value_size < sizeof(OrbisAudio3dPcm)) { + LOG_ERROR(Lib_Audio3d, "PCM attribute value_size too small"); + continue; + } const auto pcm = static_cast(attribute.value); - // Object audio has 1 channel. - if (const auto ret = PortQueueAudio(port, *pcm, 1); ret != ORBIS_OK) { + // Object audio is always mono (1 channel). + if (const auto ret = + ConvertAndEnqueue(obj.pcm_queue, *pcm, 1, port.parameters.granularity); + ret != ORBIS_OK) { return ret; } break; } - default: - LOG_ERROR(Lib_Audio3d, "Unsupported attribute ID: {:#x}", - static_cast(attribute.attribute_id)); + default: { + // store the other attributes in the ObjectState so they're available when we implement + // them + if (attribute.value && attribute.value_size > 0) { + const auto* src = static_cast(attribute.value); + obj.persistent_attributes[static_cast(attribute.attribute_id)].assign( + src, src + attribute.value_size); + } + LOG_DEBUG(Lib_Audio3d, "Stored attribute {:#x} for object {}", + static_cast(attribute.attribute_id), object_id); break; } + } } return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve() { - LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(const OrbisAudio3dPortId port_id, + const OrbisAudio3dObjectId object_id) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id, object_id); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + + if (!port.objects.contains(object_id)) { + LOG_ERROR(Lib_Audio3d, "object_id not reserved"); + return ORBIS_AUDIO3D_ERROR_INVALID_OBJECT; + } + + // Free any queued PCM audio for this object. + for (auto& data : port.objects[object_id].pcm_queue) { + std::free(data.sample_buffer); + } + + port.objects.erase(object_id); return ORBIS_OK; } @@ -320,32 +456,164 @@ s32 PS4_SYSV_ABI sceAudio3dPortAdvance(const OrbisAudio3dPortId port_id) { return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } - if (state->ports[port_id].parameters.buffer_mode == - OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) { + auto& port = state->ports[port_id]; + + if (port.parameters.buffer_mode == OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) { LOG_ERROR(Lib_Audio3d, "port doesn't have advance capability"); return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED; } - auto& port = state->ports[port_id]; - if (port.current_buffer.has_value()) { - // Free existing buffer before replacing. - SDL_free(port.current_buffer->sample_buffer); + if (port.mixed_queue.size() >= port.parameters.queue_depth) { + LOG_WARNING(Lib_Audio3d, "mixed queue full (depth={}), dropping advance", + port.parameters.queue_depth); + return ORBIS_AUDIO3D_ERROR_NOT_READY; } - if (!port.queue.empty()) { - port.current_buffer = port.queue.front(); - port.queue.pop_front(); - } else { - // Nothing to advance to. - LOG_DEBUG(Lib_Audio3d, "Port advance with no buffer queued"); - port.current_buffer = std::nullopt; + const u32 granularity = port.parameters.granularity; + const u32 out_samples = granularity * AUDIO3D_OUTPUT_NUM_CHANNELS; + + // ---- FLOAT MIX BUFFER ---- + float* mix_float = static_cast(std::calloc(out_samples, sizeof(float))); + + if (!mix_float) + return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY; + + auto mix_in = [&](std::deque& queue, const float gain) { + if (queue.empty()) + return; + + // default gain is 0.0 — objects with no GAIN set are silent. + if (gain == 0.0f) { + AudioData data = queue.front(); + queue.pop_front(); + std::free(data.sample_buffer); + return; + } + + AudioData data = queue.front(); + queue.pop_front(); + + const u32 frames = std::min(granularity, data.num_samples); + const u32 channels = data.num_channels; + + if (data.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) { + const s16* src = reinterpret_cast(data.sample_buffer); + + for (u32 i = 0; i < frames; i++) { + float left = 0.0f; + float right = 0.0f; + + if (channels == 1) { + float v = src[i] / 32768.0f; + left = v; + right = v; + } else { + left = src[i * channels + 0] / 32768.0f; + right = src[i * channels + 1] / 32768.0f; + } + + mix_float[i * 2 + 0] += left * gain; + mix_float[i * 2 + 1] += right * gain; + } + } else { // FLOAT input + const float* src = reinterpret_cast(data.sample_buffer); + + for (u32 i = 0; i < frames; i++) { + float left = 0.0f; + float right = 0.0f; + + if (channels == 1) { + left = src[i]; + right = src[i]; + } else { + left = src[i * channels + 0]; + right = src[i * channels + 1]; + } + + mix_float[i * 2 + 0] += left * gain; + mix_float[i * 2 + 1] += right * gain; + } + } + + std::free(data.sample_buffer); + }; + + // Bed is mixed at full gain (1.0) + mix_in(port.bed_queue, 1.0f); + + // Mix all object PCM queues, applying each object's GAIN persistent attribute. + for (auto& [obj_id, obj] : port.objects) { + float gain = 0.0f; + const auto gain_key = + static_cast(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_GAIN); + if (obj.persistent_attributes.contains(gain_key)) { + const auto& blob = obj.persistent_attributes.at(gain_key); + if (blob.size() >= sizeof(float)) { + std::memcpy(&gain, blob.data(), sizeof(float)); + } + } + mix_in(obj.pcm_queue, gain); } + s16* mix_s16 = static_cast(std::malloc(out_samples * sizeof(s16))); + + if (!mix_s16) { + std::free(mix_float); + return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY; + } + + for (u32 i = 0; i < out_samples; i++) { + float v = std::clamp(mix_float[i], -1.0f, 1.0f); + mix_s16[i] = static_cast(v * 32767.0f); + } + + std::free(mix_float); + + port.mixed_queue.push_back(AudioData{.sample_buffer = reinterpret_cast(mix_s16), + .num_samples = granularity, + .num_channels = AUDIO3D_OUTPUT_NUM_CHANNELS, + .format = OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16}); + return ORBIS_OK; } +s32 PS4_SYSV_ABI sceAudio3dPortClose(const OrbisAudio3dPortId port_id) { + LOG_INFO(Lib_Audio3d, "called, port_id = {}", port_id); -s32 PS4_SYSV_ABI sceAudio3dPortClose() { - LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + { + std::scoped_lock lock{port.mutex}; + + if (port.audio_out_handle >= 0) { + AudioOut::sceAudioOutClose(port.audio_out_handle); + port.audio_out_handle = -1; + } + + for (const s32 handle : port.audioout_handles) { + AudioOut::sceAudioOutClose(handle); + } + port.audioout_handles.clear(); + + for (auto& data : port.mixed_queue) { + std::free(data.sample_buffer); + } + + for (auto& data : port.bed_queue) { + std::free(data.sample_buffer); + } + + for (auto& [obj_id, obj] : port.objects) { + for (auto& data : obj.pcm_queue) { + std::free(data.sample_buffer); + } + } + } + + state->ports.erase(port_id); return ORBIS_OK; } @@ -359,8 +627,65 @@ s32 PS4_SYSV_ABI sceAudio3dPortDestroy() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudio3dPortFlush() { - LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceAudio3dPortFlush(const OrbisAudio3dPortId port_id) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}", port_id); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + + if (!port.audioout_handles.empty()) { + for (const s32 handle : port.audioout_handles) { + const s32 ret = AudioOut::sceAudioOutOutput(handle, nullptr); + if (ret < 0) { + return ret; + } + } + return ORBIS_OK; + } + + if (port.mixed_queue.empty()) { + // Only mix if there's actually something to mix. + if (!port.bed_queue.empty() || + std::any_of(port.objects.begin(), port.objects.end(), + [](const auto& kv) { return !kv.second.pcm_queue.empty(); })) { + const s32 ret = sceAudio3dPortAdvance(port_id); + if (ret != ORBIS_OK && ret != ORBIS_AUDIO3D_ERROR_NOT_READY) { + return ret; + } + } + } + + if (port.mixed_queue.empty()) { + return ORBIS_OK; + } + + if (port.audio_out_handle < 0) { + AudioOut::OrbisAudioOutParamExtendedInformation ext_info{}; + ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT); + port.audio_out_handle = + AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0, + port.parameters.granularity, AUDIO3D_SAMPLE_RATE, ext_info); + if (port.audio_out_handle < 0) { + return port.audio_out_handle; + } + } + + // Drain all queued mixed frames, blocking on each until consumed. + while (!port.mixed_queue.empty()) { + AudioData frame = port.mixed_queue.front(); + port.mixed_queue.pop_front(); + const s32 ret = AudioOut::sceAudioOutOutput(port.audio_out_handle, frame.sample_buffer); + std::free(frame.sample_buffer); + if (ret < 0) { + return ret; + } + } + return ORBIS_OK; } @@ -398,15 +723,17 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(const OrbisAudio3dPortId port_id, u return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - const auto port = state->ports[port_id]; - const size_t size = port.queue.size(); + const auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + const size_t size = port.mixed_queue.size(); if (queue_level) { - *queue_level = size; + *queue_level = static_cast(size); } if (queue_available) { - *queue_available = port.parameters.queue_depth - size; + const u32 depth = port.parameters.queue_depth; + *queue_available = (size < depth) ? static_cast(depth - size) : 0u; } return ORBIS_OK; @@ -446,7 +773,10 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServi } *port_id = id; - std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this); + auto& port = state->ports[id]; + std::memcpy( + &port.parameters, parameters, + std::min(parameters->size_this, static_cast(sizeof(OrbisAudio3dOpenParameters)))); return ORBIS_OK; } @@ -461,24 +791,96 @@ s32 PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id, return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } - const auto& port = state->ports[port_id]; + auto& port = state->ports[port_id]; + if (port.parameters.buffer_mode != OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH) { LOG_ERROR(Lib_Audio3d, "port doesn't have push capability"); return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED; } - if (!port.current_buffer.has_value()) { - // Nothing to push. - LOG_DEBUG(Lib_Audio3d, "Port push with no buffer ready"); + const u32 depth = port.parameters.queue_depth; + + if (port.audio_out_handle < 0) { + AudioOut::OrbisAudioOutParamExtendedInformation ext_info{}; + ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT); + + port.audio_out_handle = + AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0, + port.parameters.granularity, AUDIO3D_SAMPLE_RATE, ext_info); + + if (port.audio_out_handle < 0) + return port.audio_out_handle; + } + + // Function that submits exactly one frame (if available) + auto submit_one_frame = [&](bool& submitted) -> s32 { + AudioData frame; + { + std::scoped_lock lock{port.mutex}; + + if (port.mixed_queue.empty()) { + submitted = false; + return ORBIS_OK; + } + + frame = port.mixed_queue.front(); + port.mixed_queue.pop_front(); + } + + const s32 ret = AudioOut::sceAudioOutOutput(port.audio_out_handle, frame.sample_buffer); + + std::free(frame.sample_buffer); + + if (ret < 0) + return ret; + + submitted = true; + return ORBIS_OK; + }; + + // if not full, return immediately + { + std::scoped_lock lock{port.mutex}; + if (port.mixed_queue.size() < depth) { + return ORBIS_OK; + } + } + + // Submit one frame to free space + bool submitted = false; + s32 ret = submit_one_frame(submitted); + if (ret < 0) + return ret; + + if (!submitted) + return ORBIS_OK; + + // ASYNC: free exactly one slot and return + if (blocking == OrbisAudio3dBlocking::ORBIS_AUDIO3D_BLOCKING_ASYNC) { return ORBIS_OK; } - // TODO: Implement asynchronous blocking mode. - const auto& [sample_buffer, num_samples] = port.current_buffer.value(); - return AudioOut::sceAudioOutOutput(state->audio_out_handle, sample_buffer); -} + // SYNC: ensure at least one slot is free + // (drain until size < depth) + while (true) { + { + std::scoped_lock lock{port.mutex}; + if (port.mixed_queue.size() < depth) + break; + } + bool drained = false; + ret = submit_one_frame(drained); + if (ret < 0) + return ret; + + if (!drained) + break; + } + + return ORBIS_OK; +} s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug() { LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); return ORBIS_OK; @@ -532,9 +934,15 @@ s32 PS4_SYSV_ABI sceAudio3dTerminate() { return ORBIS_AUDIO3D_ERROR_NOT_READY; } - AudioOut::sceAudioOutOutput(state->audio_out_handle, nullptr); - AudioOut::sceAudioOutClose(state->audio_out_handle); - state.release(); + std::vector port_ids; + for (const auto& [id, _] : state->ports) { + port_ids.push_back(id); + } + for (const auto id : port_ids) { + sceAudio3dPortClose(id); + } + + state.reset(); return ORBIS_OK; } @@ -557,6 +965,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceAudio3dGetSpeakerArrayMixCoefficients2); LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dInitialize); LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectReserve); + LIB_FUNCTION("V1FBFpNIAzk", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectSetAttribute); LIB_FUNCTION("4uyHN9q4ZeU", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectSetAttributes); LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectUnreserve); LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortAdvance); diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h index ae20e39a8..0db7fa83b 100644 --- a/src/core/libraries/audio3d/audio3d.h +++ b/src/core/libraries/audio3d/audio3d.h @@ -3,7 +3,10 @@ #pragma once +#include +#include #include +#include #include #include "common/types.h" @@ -15,6 +18,8 @@ class SymbolsResolver; namespace Libraries::Audio3d { +constexpr int ORBIS_AUDIO3D_OBJECT_INVALID = 0xFFFFFFFF; + enum class OrbisAudio3dRate : u32 { ORBIS_AUDIO3D_RATE_48000 = 0, }; @@ -60,10 +65,21 @@ struct OrbisAudio3dPcm { enum class OrbisAudio3dAttributeId : u32 { ORBIS_AUDIO3D_ATTRIBUTE_PCM = 1, + ORBIS_AUDIO3D_ATTRIBUTE_POSITION = 2, + ORBIS_AUDIO3D_ATTRIBUTE_GAIN = 3, + ORBIS_AUDIO3D_ATTRIBUTE_SPREAD = 4, + ORBIS_AUDIO3D_ATTRIBUTE_PRIORITY = 5, + ORBIS_AUDIO3D_ATTRIBUTE_PASSTHROUGH = 6, + ORBIS_AUDIO3D_ATTRIBUTE_AMBISONICS = 7, + ORBIS_AUDIO3D_ATTRIBUTE_APPLICATION_SPECIFIC = 8, + ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE = 9, + ORBIS_AUDIO3D_ATTRIBUTE_RESTRICTED = 10, + ORBIS_AUDIO3D_ATTRIBUTE_OUTPUT_ROUTE = 11, }; using OrbisAudio3dPortId = u32; using OrbisAudio3dObjectId = u32; +using OrbisAudio3dAmbisonics = u32; struct OrbisAudio3dAttribute { OrbisAudio3dAttributeId attribute_id; @@ -75,17 +91,35 @@ struct OrbisAudio3dAttribute { struct AudioData { u8* sample_buffer; u32 num_samples; + u32 num_channels{1}; // channels in sample_buffer + OrbisAudio3dFormat format{ + OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16}; // format of sample_buffer +}; + +struct ObjectState { + std::deque pcm_queue; + std::unordered_map> persistent_attributes; }; struct Port { + mutable std::recursive_mutex mutex; OrbisAudio3dOpenParameters parameters{}; - std::deque queue; // Only stores PCM buffers for now - std::optional current_buffer{}; + // Opened lazily on the first sceAudio3dPortPush call. + s32 audio_out_handle{-1}; + // Handles explicitly opened by the game via sceAudio3dAudioOutOpen. + std::vector audioout_handles; + // Reserved objects and their state. + std::unordered_map objects; + // increasing counter for generating unique object IDs within this port. + OrbisAudio3dObjectId next_object_id{0}; + // Bed audio queue + std::deque bed_queue; + // Mixed stereo frames ready to be consumed by sceAudio3dPortPush. + std::deque mixed_queue; }; struct Audio3dState { std::unordered_map ports; - s32 audio_out_handle; }; s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle); @@ -109,15 +143,20 @@ s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2(); s32 PS4_SYSV_ABI sceAudio3dInitialize(s64 reserved); s32 PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId port_id, OrbisAudio3dObjectId* object_id); +s32 PS4_SYSV_ABI sceAudio3dObjectSetAttribute(OrbisAudio3dPortId port_id, + OrbisAudio3dObjectId object_id, + OrbisAudio3dAttributeId attribute_id, + const void* attribute, u64 attribute_size); s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId port_id, OrbisAudio3dObjectId object_id, u64 num_attributes, const OrbisAudio3dAttribute* attribute_array); -s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(); +s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId port_id, + OrbisAudio3dObjectId object_id); s32 PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId port_id); -s32 PS4_SYSV_ABI sceAudio3dPortClose(); +s32 PS4_SYSV_ABI sceAudio3dPortClose(OrbisAudio3dPortId port_id); s32 PS4_SYSV_ABI sceAudio3dPortCreate(); s32 PS4_SYSV_ABI sceAudio3dPortDestroy(); -s32 PS4_SYSV_ABI sceAudio3dPortFlush(); +s32 PS4_SYSV_ABI sceAudio3dPortFlush(OrbisAudio3dPortId port_id); s32 PS4_SYSV_ABI sceAudio3dPortFreeState(); s32 PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported(); s32 PS4_SYSV_ABI sceAudio3dPortGetList(); From 19d2027105679d751328357d7c85cd83a84c9758 Mon Sep 17 00:00:00 2001 From: evill33t Date: Thu, 26 Feb 2026 16:02:14 +0100 Subject: [PATCH 04/13] skipped guest/host marker parsing/calls when disabled (#4078) * skipped guest/host marker parsing/calls when disabled * clang-format --------- Co-authored-by: Ronny Stiftel --- src/video_core/amdgpu/liverpool.cpp | 174 ++++++++++++++++++---------- 1 file changed, 116 insertions(+), 58 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index b2a4d7a61..f3b366e49 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -229,6 +229,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(dcb.data()); while (!dcb.empty()) { @@ -267,27 +269,27 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanheader.count.Value() * 2; - const std::string_view label{reinterpret_cast(&nop->data_block[1]), - marker_sz}; - if (rasterizer) { + if (guest_markers_enabled) { + const auto marker_sz = nop->header.count.Value() * 2; + const std::string_view label{ + reinterpret_cast(&nop->data_block[1]), marker_sz}; rasterizer->ScopeMarkerBegin(label, true); } break; } case PM4CmdNop::PayloadType::DebugColorMarkerPush: { - const auto marker_sz = nop->header.count.Value() * 2; - const std::string_view label{reinterpret_cast(&nop->data_block[1]), - marker_sz}; - const u32 color = *reinterpret_cast( - reinterpret_cast(&nop->data_block[1]) + marker_sz); - if (rasterizer) { + if (guest_markers_enabled) { + const auto marker_sz = nop->header.count.Value() * 2; + const std::string_view label{ + reinterpret_cast(&nop->data_block[1]), marker_sz}; + const u32 color = *reinterpret_cast( + reinterpret_cast(&nop->data_block[1]) + marker_sz); rasterizer->ScopedMarkerInsertColor(label, color, true); } break; } case PM4CmdNop::PayloadType::DebugMarkerPop: { - if (rasterizer) { + if (guest_markers_enabled) { rasterizer->ScopeMarkerEnd(true); } break; @@ -427,9 +429,13 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndex2", cmd_address)); - rasterizer->Draw(true); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndex2", cmd_address)); + rasterizer->Draw(true); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->Draw(true); + } } break; } @@ -444,10 +450,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DrawIndexOffset2", cmd_address)); - rasterizer->Draw(true, draw_index_off->index_offset); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexOffset2", cmd_address)); + rasterizer->Draw(true, draw_index_off->index_offset); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->Draw(true, draw_index_off->index_offset); + } } break; } @@ -460,9 +470,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndexAuto", cmd_address)); - rasterizer->Draw(false); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexAuto", cmd_address)); + rasterizer->Draw(false); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->Draw(false); + } } break; } @@ -475,9 +490,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndirect", cmd_address)); - rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndirect", cmd_address)); + rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0); + } } break; } @@ -491,10 +511,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DrawIndexIndirect", cmd_address)); - rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexIndirect", cmd_address)); + rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0); + } } break; } @@ -507,12 +531,18 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DrawIndexIndirectMulti", cmd_address)); - rasterizer->DrawIndirect(true, indirect_args_addr, offset, - draw_index_indirect->stride, - draw_index_indirect->count, 0); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexIndirectMulti", cmd_address)); + rasterizer->DrawIndirect(true, indirect_args_addr, offset, + draw_index_indirect->stride, + draw_index_indirect->count, 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(true, indirect_args_addr, offset, + draw_index_indirect->stride, + draw_index_indirect->count, 0); + } } break; } @@ -525,15 +555,24 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DrawIndexIndirectCountMulti", cmd_address)); - rasterizer->DrawIndirect(true, indirect_args_addr, offset, - draw_index_indirect->stride, - draw_index_indirect->count, - draw_index_indirect->count_indirect_enable.Value() - ? draw_index_indirect->count_addr - : 0); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexIndirectCountMulti", cmd_address)); + rasterizer->DrawIndirect(true, indirect_args_addr, offset, + draw_index_indirect->stride, + draw_index_indirect->count, + draw_index_indirect->count_indirect_enable.Value() + ? draw_index_indirect->count_addr + : 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(true, indirect_args_addr, offset, + draw_index_indirect->stride, + draw_index_indirect->count, + draw_index_indirect->count_indirect_enable.Value() + ? draw_index_indirect->count_addr + : 0); + } } break; } @@ -550,9 +589,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DispatchDirect", cmd_address)); - rasterizer->DispatchDirect(); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DispatchDirect", cmd_address)); + rasterizer->DispatchDirect(); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DispatchDirect(); + } } break; } @@ -568,10 +612,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DispatchIndirect", cmd_address)); - rasterizer->DispatchIndirect(indirect_args_addr, offset, size); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DispatchIndirect", cmd_address)); + rasterizer->DispatchIndirect(indirect_args_addr, offset, size); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DispatchIndirect(indirect_args_addr, offset, size); + } } break; } @@ -829,6 +877,7 @@ template Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { FIBER_ENTER(acb_task_name[vqid]); auto& queue = asc_queues[{vqid}]; + const bool host_markers_enabled = rasterizer && Config::getVkHostMarkersEnabled(); struct IndirectPatch { const PM4Header* header; @@ -881,6 +930,7 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } const PM4ItOpcode opcode = header->type3.opcode; + const auto* it_body = reinterpret_cast(header) + 1; switch (opcode) { case PM4ItOpcode::Nop: { @@ -998,10 +1048,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } if (rasterizer && (cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); - rasterizer->ScopeMarkerBegin( - fmt::format("asc[{}]:{}:DispatchDirect", vqid, cmd_address)); - rasterizer->DispatchDirect(); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("asc[{}]:{}:DispatchDirect", vqid, cmd_address)); + rasterizer->DispatchDirect(); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DispatchDirect(); + } } break; } @@ -1017,10 +1071,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } if (rasterizer && (cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); - rasterizer->ScopeMarkerBegin( - fmt::format("asc[{}]:{}:DispatchIndirect", vqid, cmd_address)); - rasterizer->DispatchIndirect(ib_address, 0, size); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("asc[{}]:{}:DispatchIndirect", vqid, cmd_address)); + rasterizer->DispatchIndirect(ib_address, 0, size); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DispatchIndirect(ib_address, 0, size); + } } break; } From 8bb29695edbe9b944052d7a2464d49860e00e8b2 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:50:41 -0600 Subject: [PATCH 05/13] Lib.Net: Proper resolver errors when isConnectedToNetwork is disabled (#4081) * Force resolver errors when not connected to network Error values are based on real hardware testing. sceNetResolverGetError is based on libSceNet decompilation. * Update net_resolver.h --- src/core/libraries/network/net.cpp | 28 +++++++++++++++++++-- src/core/libraries/network/net_resolver.cpp | 10 ++++++-- src/core/libraries/network/net_resolver.h | 3 ++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp index 102447952..ca75ad394 100644 --- a/src/core/libraries/network/net.cpp +++ b/src/core/libraries/network/net.cpp @@ -887,6 +887,10 @@ int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, } file->resolver->Resolve(); + if (file->resolver->resolution_error != ORBIS_OK) { + // Resolution failed, shouldn't appear. + continue; + } const auto it = std::ranges::find_if(epoll->events, [&](auto& el) { return el.first == rid; }); @@ -1402,8 +1406,21 @@ int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid) { } int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status) { - LOG_ERROR(Lib_Net, "(STUBBED) called rid = {}", resolverid); - *status = 0; + if (!status) { + LOG_ERROR(Lib_Net, "status == nullptr"); + *sceNetErrnoLoc() = ORBIS_NET_EINVAL; + return ORBIS_NET_ERROR_EINVAL; + } + + auto file = FDTable::Instance()->GetResolver(resolverid); + if (!file) { + LOG_ERROR(Lib_Net, "invalid resolverid {}", resolverid); + *sceNetErrnoLoc() = ORBIS_NET_EBADF; + return ORBIS_NET_ERROR_EBADF; + } + + *status = file->resolver->resolution_error; + LOG_INFO(Lib_Net, "called rid = {}, error = {:#x}", resolverid, static_cast(*status)); return ORBIS_OK; } @@ -1425,10 +1442,17 @@ int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* host auto file = FDTable::Instance()->GetResolver(resolverid); if (!file) { + LOG_ERROR(Lib_Net, "invalid resolverid {}", resolverid); *sceNetErrnoLoc() = ORBIS_NET_EBADF; return ORBIS_NET_ERROR_EBADF; } + if (!Config::getIsConnectedToNetwork()) { + *sceNetErrnoLoc() = ORBIS_NET_RESOLVER_ENODNS; + file->resolver->resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS; + return ORBIS_NET_ERROR_RESOLVER_ENODNS; + } + if ((flags & ORBIS_NET_RESOLVER_ASYNC) != 0) { return file->resolver->ResolveAsync(hostname, addr, timeout, retry, flags); } diff --git a/src/core/libraries/network/net_resolver.cpp b/src/core/libraries/network/net_resolver.cpp index 6571176df..a334cd8a4 100644 --- a/src/core/libraries/network/net_resolver.cpp +++ b/src/core/libraries/network/net_resolver.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "common/config.h" #include "common/singleton.h" #include "common/types.h" #include "core/libraries/error_codes.h" @@ -26,11 +27,16 @@ int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeo } void Resolver::Resolve() { + if (!Config::getIsConnectedToNetwork()) { + resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS; + return; + } + if (async_resolution) { auto* netinfo = Common::Singleton::Instance(); auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr); - - resolution_error = ret; + // Resolver errors are stored as ORBIS_NET_ERROR values. + resolution_error = -ret | ORBIS_NET_ERROR_BASE; } else { LOG_ERROR(Lib_Net, "async resolution has not been set-up"); } diff --git a/src/core/libraries/network/net_resolver.h b/src/core/libraries/network/net_resolver.h index 34d7dc591..4c5c2ece8 100644 --- a/src/core/libraries/network/net_resolver.h +++ b/src/core/libraries/network/net_resolver.h @@ -18,6 +18,8 @@ public: int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags); void Resolve(); + int resolution_error = ORBIS_OK; + private: struct AsyncResolution { const char* hostname; @@ -31,7 +33,6 @@ private: int poolid; int flags; std::optional async_resolution{}; - int resolution_error = ORBIS_OK; std::mutex m_mutex; }; From aae10ecdf76fd75630fd086693e82eb378001763 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:32:32 -0600 Subject: [PATCH 06/13] Lib.GnmDriver: Implement sceGnmDrawIndirectMulti (#4083) --- src/core/libraries/gnmdriver/gnmdriver.cpp | 28 ++++++++++++++++++---- src/core/libraries/gnmdriver/gnmdriver.h | 4 +++- src/video_core/amdgpu/liverpool.cpp | 22 +++++++++++++++++ src/video_core/amdgpu/pm4_cmds.h | 18 ++++++++++++++ 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 326dc2418..1993d8cd7 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -627,10 +627,30 @@ int PS4_SYSV_ABI sceGnmDrawIndirectCountMulti() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmDrawIndirectMulti() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); - UNREACHABLE(); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceGnmDrawIndirectMulti(u32* cmdbuf, u32 size, u32 data_offset, u32 max_count, + u32 shader_stage, u32 vertex_sgpr_offset, + u32 instance_sgpr_offset, u32 flags) { + LOG_TRACE(Lib_GnmDriver, "called"); + + if (cmdbuf && size == 11 && shader_stage < ShaderStages::Max && vertex_sgpr_offset < 0x10 && + instance_sgpr_offset < 0x10) { + const auto predicate = flags & 1 ? PM4Predicate::PredEnable : PM4Predicate::PredDisable; + cmdbuf = WriteHeader( + cmdbuf, 4, PM4ShaderType::ShaderGraphics, predicate); + + const auto sgpr_offset = indirect_sgpr_offsets[shader_stage]; + cmdbuf[0] = data_offset; + cmdbuf[1] = vertex_sgpr_offset == 0 ? 0 : (vertex_sgpr_offset & 0xffffu) + sgpr_offset; + cmdbuf[2] = instance_sgpr_offset == 0 ? 0 : (instance_sgpr_offset & 0xffffu) + sgpr_offset; + cmdbuf[3] = max_count; + cmdbuf[4] = sizeof(DrawIndirectArgs); + cmdbuf[5] = sceKernelIsNeoMode() ? flags & 0xe0000000u | 2u : 2u; // auto index + + cmdbuf += 6; + WriteTrailingNop<3>(cmdbuf); + return ORBIS_OK; + } + return -1; } u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState(u32* cmdbuf, u32 size) { diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 9f5fde628..4ece58ebd 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -60,7 +60,9 @@ s32 PS4_SYSV_ABI sceGnmDrawIndexOffset(u32* cmdbuf, u32 size, u32 index_offset, s32 PS4_SYSV_ABI sceGnmDrawIndirect(u32* cmdbuf, u32 size, u32 data_offset, u32 shader_stage, u32 vertex_sgpr_offset, u32 instance_sgpr_offset, u32 flags); int PS4_SYSV_ABI sceGnmDrawIndirectCountMulti(); -int PS4_SYSV_ABI sceGnmDrawIndirectMulti(); +s32 PS4_SYSV_ABI sceGnmDrawIndirectMulti(u32* cmdbuf, u32 size, u32 data_offset, u32 max_count, + u32 shader_stage, u32 vertex_sgpr_offset, + u32 instance_sgpr_offset, u32 flags); u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState(u32* cmdbuf, u32 size); u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState175(u32* cmdbuf, u32 size); u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState200(u32* cmdbuf, u32 size); diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index f3b366e49..c4f1d6695 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -501,6 +501,28 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); + const auto offset = draw_indirect->data_offset; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } + if (rasterizer) { + const auto cmd_address = reinterpret_cast(header); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndirectMulti", cmd_address)); + rasterizer->DrawIndirect(false, indirect_args_addr, offset, + draw_indirect->stride, draw_indirect->count, 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(false, indirect_args_addr, offset, + draw_indirect->stride, draw_indirect->count, 0); + } + } + break; + } case PM4ItOpcode::DrawIndexIndirect: { const auto* draw_index_indirect = reinterpret_cast(header); diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 17511d0a2..abf58ad89 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -1051,6 +1051,24 @@ struct PM4CmdDrawIndirect { u32 draw_initiator; ///< Draw Initiator Register }; +struct PM4CmdDrawIndirectMulti { + PM4Type3Header header; ///< header + u32 data_offset; ///< Byte aligned offset where the required data structure starts + union { + u32 dw2; + BitField<0, 16, u32> base_vtx_loc; ///< Offset where the CP will write the + ///< BaseVertexLocation it fetched from memory + }; + union { + u32 dw3; + BitField<0, 16, u32> start_inst_loc; ///< Offset where the CP will write the + ///< StartInstanceLocation it fetched from memory + }; + u32 count; ///< Count of data structures to loop through before going to next packet + u32 stride; ///< Stride in memory from one data structure to the next + u32 draw_initiator; ///< Draw Initiator Register +}; + struct DrawIndexedIndirectArgs { u32 index_count_per_instance; u32 instance_count; From 49c2a4999b1e1878985876bcd37e221ca1e4e5d5 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:09:01 -0600 Subject: [PATCH 07/13] Lib.Net: Misc fixes (#4082) * Fix Windows-specific incorrect error in PosixSocket::Connect * Hide typical non-blocking errors * Also hide EWOULDBLOCK from recvfrom * Fix the resolver fix --- src/core/libraries/network/net_resolver.cpp | 7 ++++-- src/core/libraries/network/posix_sockets.cpp | 9 ++++++++ src/core/libraries/network/sys_net.cpp | 23 +++++++++++++++----- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/core/libraries/network/net_resolver.cpp b/src/core/libraries/network/net_resolver.cpp index a334cd8a4..7eb4c4001 100644 --- a/src/core/libraries/network/net_resolver.cpp +++ b/src/core/libraries/network/net_resolver.cpp @@ -35,8 +35,11 @@ void Resolver::Resolve() { if (async_resolution) { auto* netinfo = Common::Singleton::Instance(); auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr); - // Resolver errors are stored as ORBIS_NET_ERROR values. - resolution_error = -ret | ORBIS_NET_ERROR_BASE; + resolution_error = ret; + if (ret != ORBIS_OK) { + // Resolver errors are stored as ORBIS_NET_ERROR values. + resolution_error = -ret | ORBIS_NET_ERROR_BASE; + } } else { LOG_ERROR(Lib_Net, "async resolution has not been set-up"); } diff --git a/src/core/libraries/network/posix_sockets.cpp b/src/core/libraries/network/posix_sockets.cpp index f2adccf50..164d85896 100644 --- a/src/core/libraries/network/posix_sockets.cpp +++ b/src/core/libraries/network/posix_sockets.cpp @@ -430,6 +430,15 @@ int PosixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) { sockaddr addr2; convertOrbisNetSockaddrToPosix(addr, &addr2); int result = ::connect(sock, &addr2, sizeof(sockaddr_in)); +#ifdef _WIN32 + // Winsock returns EWOULDBLOCK where real hardware returns EINPROGRESS + // Step in here on errors to address this. + if (result == -1) { + if (WSAGetLastError() == WSAEWOULDBLOCK) { + WSASetLastError(WSAEINPROGRESS); + } + } +#endif LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result, result == -1 ? Common::GetLastErrorMsg() : "none"); return ConvertReturnErrorCode(result); diff --git a/src/core/libraries/network/sys_net.cpp b/src/core/libraries/network/sys_net.cpp index 76107d323..a0fae3a58 100644 --- a/src/core/libraries/network/sys_net.cpp +++ b/src/core/libraries/network/sys_net.cpp @@ -28,8 +28,11 @@ int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 add if (returncode >= 0) { return returncode; } - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, - (u32)*Libraries::Kernel::__Error()); + u32 error = *Libraries::Kernel::__Error(); + // Don't log EINPROGRESS or EISCONN, these are normal to see from non-blocking communication. + if (error != ORBIS_NET_EINPROGRESS && error != ORBIS_NET_EISCONN) { + LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error); + } return -1; } @@ -59,8 +62,13 @@ int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name); auto new_sock = file->socket->Accept(addr, paddrlen); if (!new_sock) { - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code creating new socket for accepting: {}", - s, file->m_guest_name, (u32)*Libraries::Kernel::__Error()); + u32 error = *Libraries::Kernel::__Error(); + // Don't log EWOULDBLOCK, this is normal to see from non-blocking communication. + if (error != ORBIS_NET_EWOULDBLOCK) { + LOG_ERROR(Lib_Net, + "s = {} ({}) returned error code creating new socket for accepting: {}", s, + file->m_guest_name, error); + } return -1; } auto fd = FDTable::Instance()->CreateHandle(); @@ -396,8 +404,11 @@ s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, Orbis if (returncode >= 0) { return returncode; } - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, - (u32)*Libraries::Kernel::__Error()); + // Don't log EWOULDBLOCK, this is normal to see from non-blocking communication. + u32 error = *Libraries::Kernel::__Error(); + if (error != ORBIS_NET_EWOULDBLOCK) { + LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error); + } return -1; } From 6a8c50c3a27148521ca0c162e9f98ebe62dca428 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Sat, 28 Feb 2026 23:17:11 +0800 Subject: [PATCH 08/13] Low readbacks mode (#4085) --- src/common/config.cpp | 16 ++++++++-------- src/common/config.h | 10 ++++++++-- src/emulator.cpp | 2 +- src/video_core/buffer_cache/memory_tracker.h | 4 +++- src/video_core/buffer_cache/region_manager.h | 4 ++-- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index fb1181d62..79d3f799f 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -172,7 +172,7 @@ static ConfigEntry internalScreenWidth(1280); static ConfigEntry internalScreenHeight(720); static ConfigEntry isNullGpu(false); static ConfigEntry shouldCopyGPUBuffers(false); -static ConfigEntry readbacksEnabled(false); +static ConfigEntry readbacksMode(GpuReadbacksMode::Disabled); static ConfigEntry readbackLinearImagesEnabled(false); static ConfigEntry directMemoryAccessEnabled(false); static ConfigEntry shouldDumpShaders(false); @@ -440,8 +440,8 @@ bool copyGPUCmdBuffers() { return shouldCopyGPUBuffers.get(); } -bool readbacks() { - return readbacksEnabled.get(); +int getReadbacksMode() { + return readbacksMode.get(); } bool readbackLinearImages() { @@ -591,8 +591,8 @@ void setCopyGPUCmdBuffers(bool enable, bool is_game_specific) { shouldCopyGPUBuffers.set(enable, is_game_specific); } -void setReadbacks(bool enable, bool is_game_specific) { - readbacksEnabled.set(enable, is_game_specific); +void setReadbacksMode(int mode, bool is_game_specific) { + readbacksMode.set(mode, is_game_specific); } void setReadbackLinearImages(bool enable, bool is_game_specific) { @@ -943,7 +943,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { internalScreenHeight.setFromToml(gpu, "internalScreenHeight", is_game_specific); isNullGpu.setFromToml(gpu, "nullGpu", is_game_specific); shouldCopyGPUBuffers.setFromToml(gpu, "copyGPUBuffers", is_game_specific); - readbacksEnabled.setFromToml(gpu, "readbacks", is_game_specific); + readbacksMode.setFromToml(gpu, "readbacksMode", is_game_specific); readbackLinearImagesEnabled.setFromToml(gpu, "readbackLinearImages", is_game_specific); directMemoryAccessEnabled.setFromToml(gpu, "directMemoryAccess", is_game_specific); shouldDumpShaders.setFromToml(gpu, "dumpShaders", is_game_specific); @@ -1119,7 +1119,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { windowHeight.setTomlValue(data, "GPU", "screenHeight", is_game_specific); isNullGpu.setTomlValue(data, "GPU", "nullGpu", is_game_specific); shouldCopyGPUBuffers.setTomlValue(data, "GPU", "copyGPUBuffers", is_game_specific); - readbacksEnabled.setTomlValue(data, "GPU", "readbacks", is_game_specific); + readbacksMode.setTomlValue(data, "GPU", "readbacksMode", is_game_specific); readbackLinearImagesEnabled.setTomlValue(data, "GPU", "readbackLinearImages", is_game_specific); shouldDumpShaders.setTomlValue(data, "GPU", "dumpShaders", is_game_specific); vblankFrequency.setTomlValue(data, "GPU", "vblankFrequency", is_game_specific); @@ -1218,7 +1218,7 @@ void setDefaultValues(bool is_game_specific) { // Entries with game-specific settings that are in the game-specific setings GUI but not in // the global settings GUI if (is_game_specific) { - readbacksEnabled.set(false, is_game_specific); + readbacksMode.set(GpuReadbacksMode::Disabled, is_game_specific); readbackLinearImagesEnabled.set(false, is_game_specific); isNeo.set(false, is_game_specific); isDevKit.set(false, is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index eb2b91f52..7a351d424 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -23,6 +23,12 @@ struct GameInstallDir { enum HideCursorState : int { Never, Idle, Always }; +enum GpuReadbacksMode : int { + Disabled, + Low, + High, +}; + void load(const std::filesystem::path& path, bool is_game_specific = false); void save(const std::filesystem::path& path, bool is_game_specific = false); void resetGameSpecificValue(std::string entry); @@ -63,8 +69,8 @@ bool nullGpu(); void setNullGpu(bool enable, bool is_game_specific = false); bool copyGPUCmdBuffers(); void setCopyGPUCmdBuffers(bool enable, bool is_game_specific = false); -bool readbacks(); -void setReadbacks(bool enable, bool is_game_specific = false); +int getReadbacksMode(); +void setReadbacksMode(int mode, bool is_game_specific = false); bool readbackLinearImages(); void setReadbackLinearImages(bool enable, bool is_game_specific = false); bool directMemoryAccess(); diff --git a/src/emulator.cpp b/src/emulator.cpp index 27def3565..18d3024dc 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -245,7 +245,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork()); LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); - LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks()); + LOG_INFO(Config, "GPU readbacksMode: {}", Config::getReadbacksMode()); LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages()); LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); diff --git a/src/video_core/buffer_cache/memory_tracker.h b/src/video_core/buffer_cache/memory_tracker.h index ec0878c3b..2ec86de35 100644 --- a/src/video_core/buffer_cache/memory_tracker.h +++ b/src/video_core/buffer_cache/memory_tracker.h @@ -5,8 +5,10 @@ #include #include +#include #include #include + #include "common/debug.h" #include "common/types.h" #include "video_core/buffer_cache/region_manager.h" @@ -71,7 +73,7 @@ public: // modified. If we need to flush the flush function is going to perform CPU // state change. std::scoped_lock lk{manager->lock}; - if (Config::readbacks() && + if (Config::getReadbacksMode() != Config::GpuReadbacksMode::Disabled && manager->template IsRegionModified(offset, size)) { return true; } diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h index 608b16fb3..f9da020d1 100644 --- a/src/video_core/buffer_cache/region_manager.h +++ b/src/video_core/buffer_cache/region_manager.h @@ -95,7 +95,7 @@ public: } if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::readbacks()) { + } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::High) { UpdateProtection(); } } @@ -126,7 +126,7 @@ public: bits.UnsetRange(start_page, end_page); if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::readbacks()) { + } else if (Config::getReadbacksMode() != Config::GpuReadbacksMode::Disabled) { UpdateProtection(); } } From dbf23a66afaa3040fb8abcebb2c2d9f8873a8c61 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 28 Feb 2026 23:30:19 -0600 Subject: [PATCH 09/13] fix (#4088) --- src/core/libraries/save_data/savedata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index a5199c297..1edb3d40b 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -330,7 +330,7 @@ static bool match(std::string_view str, std::string_view pattern) { auto pat_it = pattern.begin(); while (str_it != str.end() && pat_it != pattern.end()) { if (*pat_it == '%') { // 0 or more wildcard - for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { + for (auto str_wild_it = str_it; str_wild_it < str.end(); ++str_wild_it) { if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { return true; } From 636efaf2b550d3762037ad081197f0f3dc1490bb Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 1 Mar 2026 20:49:55 +0200 Subject: [PATCH 10/13] changed readbacks mode to Relaxed,Precised (#4091) --- src/common/config.h | 4 ++-- src/video_core/buffer_cache/region_manager.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/config.h b/src/common/config.h index 7a351d424..0fa241c6c 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -25,8 +25,8 @@ enum HideCursorState : int { Never, Idle, Always }; enum GpuReadbacksMode : int { Disabled, - Low, - High, + Relaxed, + Precised, }; void load(const std::filesystem::path& path, bool is_game_specific = false); diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h index f9da020d1..3467b6791 100644 --- a/src/video_core/buffer_cache/region_manager.h +++ b/src/video_core/buffer_cache/region_manager.h @@ -95,7 +95,7 @@ public: } if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::High) { + } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precised) { UpdateProtection(); } } From e5d7dc4090c00ffe566a3086d4033e74409e7f4a Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 1 Mar 2026 21:02:21 +0200 Subject: [PATCH 11/13] the uber fix (#4092) --- src/common/config.h | 2 +- src/video_core/buffer_cache/region_manager.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/config.h b/src/common/config.h index 0fa241c6c..b341030e0 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -26,7 +26,7 @@ enum HideCursorState : int { Never, Idle, Always }; enum GpuReadbacksMode : int { Disabled, Relaxed, - Precised, + Precise, }; void load(const std::filesystem::path& path, bool is_game_specific = false); diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h index 3467b6791..ecf9406af 100644 --- a/src/video_core/buffer_cache/region_manager.h +++ b/src/video_core/buffer_cache/region_manager.h @@ -95,7 +95,7 @@ public: } if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precised) { + } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precise) { UpdateProtection(); } } From fc949a74497a29a4eda5ad0b4b7a78ada4a22ba1 Mon Sep 17 00:00:00 2001 From: Niram7777 Date: Mon, 2 Mar 2026 18:32:28 +0000 Subject: [PATCH 12/13] CI wget linuxdeploy retries (#4093) --- .github/linux-appimage-sdl.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/linux-appimage-sdl.sh b/.github/linux-appimage-sdl.sh index 7961f5312..d85aa6c4c 100755 --- a/.github/linux-appimage-sdl.sh +++ b/.github/linux-appimage-sdl.sh @@ -8,8 +8,8 @@ if [[ -z $GITHUB_WORKSPACE ]]; then fi # Prepare Tools for building the AppImage -wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage -wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh +wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh chmod a+x linuxdeploy-x86_64.AppImage chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh From 1f5430e4c276dfac0286e25d4fa6470121b8910e Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:35:58 -0600 Subject: [PATCH 13/13] More targeted fix (#4096) Cyberpunk's issue seems to actually come from the incrementing in the loop. It wasn't clear while debugging, but the problem is that the pattern the game supplies causes match to fail when str_wild_it hits the end, and then tries iterating past end due to the loop condition. Our pattern matching code seems broken for the case Cyberpunk triggers, but since I'm not aware of the intricacies of how real hardware behaves, best to just revert the loop condition change and instead break the loop before the broken iteration. --- src/core/libraries/save_data/savedata.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 1edb3d40b..48b086457 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -330,9 +330,12 @@ static bool match(std::string_view str, std::string_view pattern) { auto pat_it = pattern.begin(); while (str_it != str.end() && pat_it != pattern.end()) { if (*pat_it == '%') { // 0 or more wildcard - for (auto str_wild_it = str_it; str_wild_it < str.end(); ++str_wild_it) { + for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { return true; + } else if (str_wild_it == str.end()) { + // Avoid incrementing str_wild_it past str.end(). + break; } } return false;