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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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; From 14450d330f450ce68368819711d423a5f7f456f3 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 3 Mar 2026 08:52:12 +0200 Subject: [PATCH 14/40] CopyImage stencil fixes (#4095) * stencil fixes hope it fixes driveclub * revert image copy to the one that had driveclub worked * reverted texture cache change * some more fixes and reverts * added logging for overlap again --- src/video_core/texture_cache/image.cpp | 142 +++++++----------- src/video_core/texture_cache/image_info.h | 2 +- .../texture_cache/texture_cache.cpp | 8 - 3 files changed, 56 insertions(+), 96 deletions(-) diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 418641bc3..972f028d4 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -461,33 +461,36 @@ static std::pair SanitizeCopyLayers(const ImageInfo& src_info, const I void Image::CopyImage(Image& src_image) { const auto& src_info = src_image.info; + const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); - // Check format compatibility + // Format mismatch warning (safe but useful) if (src_info.pixel_format != info.pixel_format) { LOG_DEBUG(Render_Vulkan, - "Copy between different formats: src={}, dst={}. Color may be incorrect.", + "Copy between different formats: src={}, dst={}. " + "Result may be undefined.", vk::to_string(src_info.pixel_format), vk::to_string(info.pixel_format)); } - const u32 width = src_info.size.width; - const u32 height = src_info.size.height; + const u32 base_width = src_info.size.width; + const u32 base_height = src_info.size.height; const u32 base_depth = info.type == AmdGpu::ImageType::Color3D ? info.size.depth : src_info.size.depth; - auto [test_src_layers, test_dst_layers] = SanitizeCopyLayers(src_info, info, base_depth); - - ASSERT(test_src_layers == test_dst_layers || num_mips == 1 || - (ConvertImageType(src_info.type) != ConvertImageType(info.type) && - (test_src_layers == 1 || test_dst_layers == 1))); - + // Match sample count before copying SetBackingSamples(info.num_samples, false); src_image.SetBackingSamples(src_info.num_samples); - boost::container::small_vector image_copies; + boost::container::small_vector regions; + + const vk::ImageAspectFlags src_aspect = + src_image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil; + + const vk::ImageAspectFlags dst_aspect = aspect_mask & ~vk::ImageAspectFlagBits::eStencil; const bool src_is_2d = ConvertImageType(src_info.type) == vk::ImageType::e2D; const bool src_is_3d = ConvertImageType(src_info.type) == vk::ImageType::e3D; + const bool dst_is_2d = ConvertImageType(info.type) == vk::ImageType::e2D; const bool dst_is_3d = ConvertImageType(info.type) == vk::ImageType::e3D; @@ -495,103 +498,68 @@ void Image::CopyImage(Image& src_image) { const bool is_3d_to_2d = src_is_3d && dst_is_2d; const bool is_same_type = !is_2d_to_3d && !is_3d_to_2d; - // Determine aspect mask - exclude stencil - vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor; - - // For depth/stencil images, only copy the depth aspect (skip stencil) - if (src_image.aspect_mask & vk::ImageAspectFlagBits::eDepth) { - aspect = vk::ImageAspectFlagBits::eDepth; - } - for (u32 mip = 0; mip < num_mips; ++mip) { - const auto mip_w = std::max(width >> mip, 1u); - const auto mip_h = std::max(height >> mip, 1u); - const auto mip_d = std::max(base_depth >> mip, 1u); + const u32 mip_w = std::max(base_width >> mip, 1u); + const u32 mip_h = std::max(base_height >> mip, 1u); + const u32 mip_d = std::max(base_depth >> mip, 1u); auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d); + vk::ImageCopy region{}; + + region.srcSubresource.aspectMask = src_aspect; + region.srcSubresource.mipLevel = mip; + region.srcSubresource.baseArrayLayer = 0; + + region.dstSubresource.aspectMask = dst_aspect; + region.dstSubresource.mipLevel = mip; + region.dstSubresource.baseArrayLayer = 0; + if (is_same_type) { - u32 copy_layers = std::min(src_layers, dst_layers); - - if (src_is_3d) - src_layers = 1; - if (dst_is_3d) - dst_layers = 1; - - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = copy_layers, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = copy_layers, - }, - .extent = vk::Extent3D(mip_w, mip_h, mip_d), - }; - image_copies.push_back(copy_region); + // 2D->2D OR 3D->3D + if (src_is_3d) { + // 3D images must use layerCount=1 + region.srcSubresource.layerCount = 1; + region.dstSubresource.layerCount = 1; + region.extent = vk::Extent3D(mip_w, mip_h, mip_d); + } else { + // Array images + const u32 copy_layers = std::min(src_layers, dst_layers); + region.srcSubresource.layerCount = copy_layers; + region.dstSubresource.layerCount = copy_layers; + region.extent = vk::Extent3D(mip_w, mip_h, 1); + } } else if (is_2d_to_3d) { - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = src_layers, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .extent = vk::Extent3D(mip_w, mip_h, src_layers), - }; - image_copies.push_back(copy_region); + // 2D array -> 3D volume + region.srcSubresource.layerCount = src_layers; + region.dstSubresource.layerCount = 1; + region.extent = vk::Extent3D(mip_w, mip_h, src_layers); } else if (is_3d_to_2d) { - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = dst_layers, - }, - .extent = vk::Extent3D(mip_w, mip_h, dst_layers), - }; - image_copies.push_back(copy_region); + // 3D volume -> 2D array + region.srcSubresource.layerCount = 1; + region.dstSubresource.layerCount = dst_layers; + region.extent = vk::Extent3D(mip_w, mip_h, dst_layers); } + + regions.push_back(region); } scheduler->EndRendering(); - // Remove the pipeline stage flags - they don't belong here src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); - if (!image_copies.empty()) { - cmdbuf.copyImage(src_image.GetImage(), vk::ImageLayout::eTransferSrcOptimal, GetImage(), - vk::ImageLayout::eTransferDstOptimal, image_copies); + if (!regions.empty()) { + cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(), + backing->state.layout, regions); } - // Remove pipeline stage flags here too - src_image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, - {}); - - Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}); + Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } - void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) { const auto& src_info = src_image.info; const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index 0da9c8bfb..543e144d2 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -48,7 +48,7 @@ struct ImageInfo { } Extent2D BlockDim() const { const auto dim = props.is_block ? 2 : 0; - return Extent2D{size.width >> dim, size.height >> dim}; + return Extent2D{pitch >> dim, size.height >> dim}; } s32 MipOf(const ImageInfo& info) const; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index a8d846cfc..8163902cc 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -297,14 +297,6 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag if (image_info.guest_address == cache_image.info.guest_address) { const u32 lhs_block_size = image_info.num_bits * image_info.num_samples; const u32 rhs_block_size = cache_image.info.num_bits * cache_image.info.num_samples; - - if (image_info.pitch != cache_image.info.pitch) { - if (safe_to_delete) { - FreeImage(cache_image_id); - } - return {merged_image_id, -1, -1}; - } - if (image_info.BlockDim() != cache_image.info.BlockDim() || lhs_block_size != rhs_block_size) { // Very likely this kind of overlap is caused by allocation from a pool. From 89e74828e63680e211f36bd5ec600f2bf4be7552 Mon Sep 17 00:00:00 2001 From: Pavel <68122101+red-prig@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:44:37 +0300 Subject: [PATCH 15/40] fixup r128 (#4100) --- src/shader_recompiler/frontend/translate/vector_memory.cpp | 1 + src/shader_recompiler/resource.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index e0c64ff4a..72286b29c 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -458,6 +458,7 @@ void Translator::IMAGE_STORE(bool has_mip, const GcnInst& inst) { IR::TextureInstInfo info{}; info.has_lod.Assign(has_mip); info.is_array.Assign(mimg.da); + info.is_r128.Assign(mimg.r128); boost::container::static_vector comps; for (u32 i = 0; i < 4; i++) { diff --git a/src/shader_recompiler/resource.h b/src/shader_recompiler/resource.h index 5d9965105..5ae3179f6 100644 --- a/src/shader_recompiler/resource.h +++ b/src/shader_recompiler/resource.h @@ -86,6 +86,7 @@ struct ImageResource { } else { const auto raw = info.template ReadUdSharp(sharp_idx); std::memcpy(&image, &raw, sizeof(raw)); + image.pitch = image.width; } if (!image.Valid()) { LOG_DEBUG(Render_Vulkan, "Encountered invalid image sharp"); From 50d5ce9a8392bce170efca9bb7232c25bb4a8f0d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 7 Mar 2026 08:55:17 +0200 Subject: [PATCH 16/40] fixed an issue in sceKernelRemoveExceptionHandler (#4086) * fixed an issue in sceKernelRemoveExceptionHandler * fixed comment --- src/core/libraries/kernel/threads/exception.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index cf36da0cc..247c387fe 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -276,7 +276,10 @@ int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) { return ORBIS_KERNEL_ERROR_EINVAL; } int const native_signum = OrbisToNativeSignal(signum); - ASSERT_MSG(Handlers[native_signum], "Invalid parameters"); + if (!Handlers[native_signum]) { + LOG_WARNING(Lib_Kernel, "removing non-installed handler for signum {}", signum); + return ORBIS_KERNEL_ERROR_EINVAL; + } Handlers[native_signum] = nullptr; #ifndef _WIN64 if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) { From 014b11e9da8765b281b331a1edff15d12c365d7f Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 7 Mar 2026 01:15:32 -0600 Subject: [PATCH 17/40] Return an empty certificate (#4104) This is enough to get past initialization checks in Rise of the Tomb Raider. --- src/core/libraries/network/ssl2.cpp | 26 +++++++++++++------------ src/core/libraries/network/ssl2.h | 12 ++++++++++++ src/core/libraries/network/ssl2_error.h | 8 ++++++++ 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 src/core/libraries/network/ssl2_error.h diff --git a/src/core/libraries/network/ssl2.cpp b/src/core/libraries/network/ssl2.cpp index 682095801..0b408d094 100644 --- a/src/core/libraries/network/ssl2.cpp +++ b/src/core/libraries/network/ssl2.cpp @@ -5,6 +5,7 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/network/ssl2.h" +#include "core/libraries/network/ssl2_error.h" namespace Libraries::Ssl2 { @@ -108,8 +109,13 @@ int PS4_SYSV_ABI sceSslEnableVerifyOption() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSslFreeCaCerts() { - LOG_ERROR(Lib_Ssl2, "(STUBBED) called"); +int PS4_SYSV_ABI sceSslFreeCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) { + LOG_ERROR(Lib_Ssl2, "(DUMMY) called"); + if (certs == nullptr) { + return ORBIS_SSL_ERROR_INVALID_ARGUMENT; + } + delete (certs->certs); + // delete (certs->pool); return ORBIS_OK; } @@ -128,17 +134,13 @@ int PS4_SYSV_ABI sceSslGetAlpnSelected() { return ORBIS_OK; } -struct OrbisSslCaCerts { - void* certs; - u64 num; - void* pool; -}; - -int PS4_SYSV_ABI sceSslGetCaCerts(int sslCtxId, OrbisSslCaCerts* certs) { - // check if it is same as libSceSsl +int PS4_SYSV_ABI sceSslGetCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) { LOG_ERROR(Lib_Ssl2, "(DUMMY) called"); - certs->certs = nullptr; - certs->num = 0; + if (certs == nullptr) { + return ORBIS_SSL_ERROR_INVALID_ARGUMENT; + } + certs->certs = new OrbisSslData{nullptr, 0}; + certs->num = 1; certs->pool = nullptr; return ORBIS_OK; } diff --git a/src/core/libraries/network/ssl2.h b/src/core/libraries/network/ssl2.h index 754dda40c..18fb205d3 100644 --- a/src/core/libraries/network/ssl2.h +++ b/src/core/libraries/network/ssl2.h @@ -10,5 +10,17 @@ class SymbolsResolver; } namespace Libraries::Ssl2 { + +struct OrbisSslData { + char* ptr; + u64 size; +}; + +struct OrbisSslCaCerts { + OrbisSslData* certs; + u64 num; + void* pool; +}; + void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Ssl2 \ No newline at end of file diff --git a/src/core/libraries/network/ssl2_error.h b/src/core/libraries/network/ssl2_error.h new file mode 100644 index 000000000..03bf94256 --- /dev/null +++ b/src/core/libraries/network/ssl2_error.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/libraries/error_codes.h" + +constexpr int ORBIS_SSL_ERROR_INVALID_ARGUMENT = 0x8095F007; \ No newline at end of file From cc6af03adf6208974b0afcd7684358dd83fcfc8b Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:48:03 -0500 Subject: [PATCH 18/40] Lib.SysModule: Proper HLE implementation (#4102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace sysmodule enums with table Dumped this from the 12.52 module, using a script I created. * Better documentation * Separate from system libraries That system folder is going to be getting quite large if I left all the sysmodule stuff in there. * More arrays from library * Found another preload list Ghidra really hates decompiling libSceSysmodule, so I didn't notice this one at first. Also documented specific versions tied to each preload list. * Start work on implementation * Some basic implementations * Initial stub for module loading Just enough to see if the logic functions appropriately. * Clang * sceSysmoduleLoadModule Now I need to get sceSysmodulePreloadModuleForLibkernel done so that we don't have bugs from not loading internal LLEs. * sceSysmoduleLoadModuleInternal * sceSysmodulePreloadModuleForLibkernel I have successfully broken quite literally everything. I shall debug this tomorrow. * Slight fix * Maybe fix? * Change log Enjoy the log spam 😄 * Increased defined stub count Now that libc and libSceLibcInternal loads later, all the auto stubs are getting consumed by it. * sceSysmoduleUnloadModule stub Also a couple fixes. Sysmodule does pass argc and argv to game modules, but only after loading them once to check binaries. Shouldn't matter for the most part. * Clang * Less stubs 2 thousand is seemingly enough. * sceSysmoduleLoadModuleInternalWithArg Doesn't hurt to have, since Apex Legends calls it. * Oops * Oops 2 * Rename isModuleLoaded to getModuleHandle Review comment * Remove debug game module loads These cases only trigger when specific sceRegMgr key values are set, and for our purposes, we can treat that case as false. * Allow preloading to fail For kalaposfos * Clang --- CMakeLists.txt | 9 +- src/common/elf_info.h | 3 + src/core/aerolib/stubs.cpp | 5 +- src/core/libraries/kernel/orbis_error.h | 2 + src/core/libraries/libs.cpp | 2 +- src/core/libraries/sysmodule/sysmodule.cpp | 204 ++++++ src/core/libraries/sysmodule/sysmodule.h | 38 + .../libraries/sysmodule/sysmodule_error.h | 10 + .../sysmodule/sysmodule_internal.cpp | 440 +++++++++++ .../libraries/sysmodule/sysmodule_internal.h | 20 + .../libraries/sysmodule/sysmodule_table.h | 684 ++++++++++++++++++ src/core/libraries/system/sysmodule.cpp | 169 ----- src/core/libraries/system/sysmodule.h | 194 ----- src/core/libraries/system/system_error.h | 8 - src/core/linker.cpp | 8 +- src/emulator.cpp | 67 -- 16 files changed, 1416 insertions(+), 447 deletions(-) create mode 100644 src/core/libraries/sysmodule/sysmodule.cpp create mode 100644 src/core/libraries/sysmodule/sysmodule.h create mode 100644 src/core/libraries/sysmodule/sysmodule_error.h create mode 100644 src/core/libraries/sysmodule/sysmodule_internal.cpp create mode 100644 src/core/libraries/sysmodule/sysmodule_internal.h create mode 100644 src/core/libraries/sysmodule/sysmodule_table.h delete mode 100644 src/core/libraries/system/sysmodule.cpp delete mode 100644 src/core/libraries/system/sysmodule.h delete mode 100644 src/core/libraries/system/system_error.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b07dcea87..dd02a6378 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,9 +414,12 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/save_data/dialog/savedatadialog.h src/core/libraries/save_data/dialog/savedatadialog_ui.cpp src/core/libraries/save_data/dialog/savedatadialog_ui.h - src/core/libraries/system/sysmodule.cpp - src/core/libraries/system/sysmodule.h - src/core/libraries/system/system_error.h + src/core/libraries/sysmodule/sysmodule.cpp + src/core/libraries/sysmodule/sysmodule.h + src/core/libraries/sysmodule/sysmodule_internal.cpp + src/core/libraries/sysmodule/sysmodule_internal.h + src/core/libraries/sysmodule/sysmodule_error.h + src/core/libraries/sysmodule/sysmodule_table.h src/core/libraries/system/systemservice.cpp src/core/libraries/system/systemservice.h src/core/libraries/system/systemservice_error.h diff --git a/src/common/elf_info.h b/src/common/elf_info.h index 0f2311cb0..b84f36ecb 100644 --- a/src/common/elf_info.h +++ b/src/common/elf_info.h @@ -88,7 +88,10 @@ public: static constexpr u32 FW_50 = 0x5000000; static constexpr u32 FW_55 = 0x5500000; static constexpr u32 FW_60 = 0x6000000; + static constexpr u32 FW_70 = 0x7000000; + static constexpr u32 FW_75 = 0x7500000; static constexpr u32 FW_80 = 0x8000000; + static constexpr u32 FW_115 = 0x11500000; static ElfInfo& Instance() { return *Singleton::Instance(); diff --git a/src/core/aerolib/stubs.cpp b/src/core/aerolib/stubs.cpp index 2634fc46a..7023144d7 100644 --- a/src/core/aerolib/stubs.cpp +++ b/src/core/aerolib/stubs.cpp @@ -19,7 +19,7 @@ namespace Core::AeroLib { // and to longer compile / CI times // // Must match STUBS_LIST define -constexpr u32 MAX_STUBS = 1024; +constexpr u32 MAX_STUBS = 2048; u64 UnresolvedStub() { LOG_ERROR(Core, "Returning zero to {}", __builtin_return_address(0)); @@ -61,8 +61,9 @@ static u32 UsedStubEntries; #define XREP_256(x) XREP_128(x) XREP_128(x + 128) #define XREP_512(x) XREP_256(x) XREP_256(x + 256) #define XREP_1024(x) XREP_512(x) XREP_512(x + 512) +#define XREP_2048(x) XREP_1024(x) XREP_1024(x + 1024) -#define STUBS_LIST XREP_1024(0) +#define STUBS_LIST XREP_2048(0) static u64 (*stub_handlers[MAX_STUBS])() = {STUBS_LIST}; diff --git a/src/core/libraries/kernel/orbis_error.h b/src/core/libraries/kernel/orbis_error.h index d19b3f3f1..6ebff0ba3 100644 --- a/src/core/libraries/kernel/orbis_error.h +++ b/src/core/libraries/kernel/orbis_error.h @@ -106,3 +106,5 @@ constexpr int ORBIS_KERNEL_ERROR_ECAPMODE = 0x8002005E; constexpr int ORBIS_KERNEL_ERROR_ENOBLK = 0x8002005F; constexpr int ORBIS_KERNEL_ERROR_EICV = 0x80020060; constexpr int ORBIS_KERNEL_ERROR_ENOPLAYGOENT = 0x80020061; +constexpr int ORBIS_KERNEL_ERROR_ESDKVERSION = 0x80020063; +constexpr int ORBIS_KERNEL_ERROR_ESTART = 0x80020064; \ No newline at end of file diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index eebb991dc..ac35c4b63 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -57,10 +57,10 @@ #include "core/libraries/screenshot/screenshot.h" #include "core/libraries/share_play/shareplay.h" #include "core/libraries/signin_dialog/signindialog.h" +#include "core/libraries/sysmodule/sysmodule.h" #include "core/libraries/system/commondialog.h" #include "core/libraries/system/msgdialog.h" #include "core/libraries/system/posix.h" -#include "core/libraries/system/sysmodule.h" #include "core/libraries/system/systemservice.h" #include "core/libraries/system/userservice.h" #include "core/libraries/system_gesture/system_gesture.h" diff --git a/src/core/libraries/sysmodule/sysmodule.cpp b/src/core/libraries/sysmodule/sysmodule.cpp new file mode 100644 index 000000000..1ad9075e7 --- /dev/null +++ b/src/core/libraries/sysmodule/sysmodule.cpp @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/elf_info.h" +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/process.h" +#include "core/libraries/libs.h" +#include "core/libraries/sysmodule/sysmodule.h" +#include "core/libraries/sysmodule/sysmodule_error.h" +#include "core/libraries/sysmodule/sysmodule_internal.h" +#include "core/linker.h" + +namespace Libraries::SysModule { + +static std::mutex g_mutex{}; + +s32 PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(OrbisSysModuleInternal id, s32* handle) { + LOG_INFO(Lib_SysModule, "called"); + if ((id & 0x7fffffff) == 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + std::scoped_lock lk{g_mutex}; + return getModuleHandle(id, handle); +} + +s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, + Kernel::OrbisModuleInfoForUnwind* info) { + LOG_TRACE(Lib_SysModule, "sceSysmoduleGetModuleInfoForUnwind called"); + s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, info); + if (res != ORBIS_OK) { + return res; + } + + if (shouldHideName(info->name.data())) { + std::ranges::fill(info->name, '\0'); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule() { + LOG_ERROR(Lib_SysModule, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded() { + LOG_ERROR(Lib_SysModule, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id) { + if (id == 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + std::scoped_lock lk{g_mutex}; + return getModuleHandle(id, nullptr); +} + +s32 PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id) { + if ((id & 0x7fffffff) == 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + std::scoped_lock lk{g_mutex}; + return getModuleHandle(id, nullptr); +} + +s32 PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id) { + LOG_INFO(Lib_SysModule, "called, id = {:#x}", id); + s32 result = validateModuleId(id); + if (result < ORBIS_OK) { + return result; + } + + // Only locks for internal loadModule call. + { + std::scoped_lock lk{g_mutex}; + result = loadModule(id, 0, nullptr, nullptr); + } + + if (result == ORBIS_KERNEL_ERROR_ESTART) { + s32 sdk_ver = 0; + result = Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver); + if (sdk_ver < Common::ElfInfo::FW_115 || result != ORBIS_OK) { + return ORBIS_KERNEL_ERROR_EINVAL; + } else { + return ORBIS_KERNEL_ERROR_ESTART; + } + } + + // The real library has some weird workaround for CUSA01478 and CUSA01495 here. + // Unless this is proven necessary, I don't plan to handle this. + return result; +} + +s32 PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal() { + LOG_ERROR(Lib_SysModule, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternal(OrbisSysModuleInternal id) { + LOG_INFO(Lib_SysModule, "called, id = {:#x}", id); + s32 result = validateModuleId(id); + if (result < ORBIS_OK) { + return result; + } + + // This specific module ID is loaded unlocked. + if (id == 0x80000039) { + return loadModule(id, 0, nullptr, nullptr); + } + std::scoped_lock lk{g_mutex}; + return loadModule(id, 0, nullptr, nullptr); +} + +s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg(OrbisSysModuleInternal id, s32 argc, + const void* argv, u64 unk, s32* res_out) { + LOG_INFO(Lib_SysModule, "called, id = {:#x}", id); + s32 result = validateModuleId(id); + if (result < ORBIS_OK) { + return result; + } + + if (unk != 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + std::scoped_lock lk{g_mutex}; + return loadModule(id, argc, argv, res_out); +} + +s32 PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel() { + LOG_ERROR(Lib_SysModule, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel() { + LOG_DEBUG(Lib_SysModule, "called"); + return preloadModulesForLibkernel(); +} + +s32 PS4_SYSV_ABI sceSysmoduleUnloadModule(OrbisSysModule id) { + LOG_ERROR(Lib_SysModule, "(STUBBED) called, id = {:#x}", id); + if (id == 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + std::scoped_lock lk{g_mutex}; + return unloadModule(id, 0, nullptr, nullptr, false); +} + +s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal() { + LOG_ERROR(Lib_SysModule, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal() { + LOG_ERROR(Lib_SysModule, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg() { + LOG_ERROR(Lib_SysModule, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("D8cuU4d72xM", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleGetModuleHandleInternal); + LIB_FUNCTION("4fU5yvOkVG4", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleGetModuleInfoForUnwind); + LIB_FUNCTION("ctfO7dQ7geg", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleIsCalledFromSysModule); + LIB_FUNCTION("no6T3EfiS3E", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleIsCameraPreloaded); + LIB_FUNCTION("fMP5NHUOaMk", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleIsLoaded); + LIB_FUNCTION("ynFKQ5bfGks", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleIsLoadedInternal); + LIB_FUNCTION("g8cM39EUZ6o", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleLoadModule); + LIB_FUNCTION("CU8m+Qs+HN4", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleLoadModuleByNameInternal); + LIB_FUNCTION("39iV5E1HoCk", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleLoadModuleInternal); + LIB_FUNCTION("hHrGoGoNf+s", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleLoadModuleInternalWithArg); + LIB_FUNCTION("lZ6RvVl0vo0", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleMapLibcForLibkernel); + LIB_FUNCTION("DOO+zuW1lrE", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmodulePreloadModuleForLibkernel); + LIB_FUNCTION("eR2bZFAAU0Q", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleUnloadModule); + LIB_FUNCTION("vpTHmA6Knvg", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleUnloadModuleByNameInternal); + LIB_FUNCTION("vXZhrtJxkGc", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleUnloadModuleInternal); + LIB_FUNCTION("aKa6YfBKZs4", "libSceSysmodule", 1, "libSceSysmodule", + sceSysmoduleUnloadModuleInternalWithArg); +}; + +} // namespace Libraries::SysModule diff --git a/src/core/libraries/sysmodule/sysmodule.h b/src/core/libraries/sysmodule/sysmodule.h new file mode 100644 index 000000000..17ac3188f --- /dev/null +++ b/src/core/libraries/sysmodule/sysmodule.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/kernel/process.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::SysModule { + +using OrbisSysModule = u16; +using OrbisSysModuleInternal = u32; + +s32 PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(OrbisSysModuleInternal id, s32* handle); +s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, + Kernel::OrbisModuleInfoForUnwind* info); +s32 PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule(); +s32 PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded(); +s32 PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id); +s32 PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id); +s32 PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id); +s32 PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal(); +s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternal(OrbisSysModuleInternal id); +s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg(OrbisSysModuleInternal id, s32 argc, + const void* argv, u64 unk, s32* res_out); +s32 PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel(); +s32 PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel(); +s32 PS4_SYSV_ABI sceSysmoduleUnloadModule(OrbisSysModule id); +s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal(); +s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal(); +s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg(); + +void RegisterLib(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::SysModule diff --git a/src/core/libraries/sysmodule/sysmodule_error.h b/src/core/libraries/sysmodule/sysmodule_error.h new file mode 100644 index 000000000..aee14b9df --- /dev/null +++ b/src/core/libraries/sysmodule/sysmodule_error.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +constexpr s32 ORBIS_SYSMODULE_INVALID_ID = 0x805A1000; +constexpr s32 ORBIS_SYSMODULE_NOT_LOADED = 0x805A1001; +constexpr s32 ORBIS_SYSMODULE_LOCK_FAILED = 0x805A10FF; \ No newline at end of file diff --git a/src/core/libraries/sysmodule/sysmodule_internal.cpp b/src/core/libraries/sysmodule/sysmodule_internal.cpp new file mode 100644 index 000000000..55acded94 --- /dev/null +++ b/src/core/libraries/sysmodule/sysmodule_internal.cpp @@ -0,0 +1,440 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/config.h" +#include "common/elf_info.h" +#include "common/logging/log.h" +#include "core/file_sys/fs.h" +#include "core/libraries/disc_map/disc_map.h" +#include "core/libraries/font/font.h" +#include "core/libraries/font/fontft.h" +#include "core/libraries/jpeg/jpegenc.h" +#include "core/libraries/kernel/kernel.h" +#include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/libc_internal/libc_internal.h" +#include "core/libraries/libpng/pngenc.h" +#include "core/libraries/libs.h" +#include "core/libraries/ngs2/ngs2.h" +#include "core/libraries/rtc/rtc.h" +#include "core/libraries/sysmodule/sysmodule_error.h" +#include "core/libraries/sysmodule/sysmodule_internal.h" +#include "core/libraries/sysmodule/sysmodule_table.h" +#include "core/linker.h" +#include "emulator.h" + +namespace Libraries::SysModule { + +s32 getModuleHandle(s32 id, s32* handle) { + if (id == 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + for (OrbisSysmoduleModuleInternal mod : g_modules_array) { + if (mod.id != id) { + continue; + } + if (mod.is_loaded < 1) { + return ORBIS_SYSMODULE_NOT_LOADED; + } + if (handle != nullptr) { + *handle = mod.handle; + } + return ORBIS_OK; + } + return ORBIS_SYSMODULE_INVALID_ID; +} + +bool shouldHideName(const char* module_name) { + for (u64 i = 0; i < g_num_modules; i++) { + OrbisSysmoduleModuleInternal mod = g_modules_array[i]; + if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsGame) == 0) { + continue; + } + u64 name_length = std::strlen(mod.name); + char name_copy[0x100]; + std::strncpy(name_copy, mod.name, sizeof(name_copy)); + // Module table stores names without extensions, so check with .prx appended to the name. + std::strncpy(&name_copy[name_length], ".prx", 4); + s32 result = std::strncmp(module_name, name_copy, sizeof(name_copy)); + if (result == 0) { + return true; + } + + // libSceFios2 and libc are checked as both sprx or prx modules. + if (i == 3) { + result = std::strncmp(module_name, "libSceFios2.sprx", sizeof(name_copy)); + } else if (i == 4) { + result = std::strncmp(module_name, "libc.sprx", sizeof(name_copy)); + } + + if (result == 0) { + return true; + } + } + return false; +} + +bool isDebugModule(s32 id) { + for (OrbisSysmoduleModuleInternal mod : g_modules_array) { + if (mod.id == id && (mod.flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0) { + return true; + } + } + return false; +} + +bool validateModuleId(s32 id) { + if ((id & 0x7fffffff) == 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + s32 sdk_ver = 0; + ASSERT_MSG(!Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver), + "Failed to retrieve compiled SDK version"); + + // libSceGameCustomDialog isn't loadable on SDK >= 7.50 + if (id == 0xb8 && sdk_ver >= Common::ElfInfo::FW_75) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + // libSceNpSnsFacebookDialog isn't loadable on SDK >= 7.00 + if (id == 0xb0 && sdk_ver >= Common::ElfInfo::FW_70) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + // libSceJson isn't loadable on SDK >= 3.00 + if (id == 0x80 && sdk_ver >= Common::ElfInfo::FW_30) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + // Cannot load debug modules on retail hardware. + if (isDebugModule(id) && !Config::isDevKitConsole()) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + return ORBIS_OK; +} + +s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out) { + auto* mnt = Common::Singleton::Instance(); + auto* linker = Common::Singleton::Instance(); + auto* game_info = Common::Singleton::Instance(); + + // If the module is already loaded, increment is_loaded and return ORBIS_OK. + OrbisSysmoduleModuleInternal& mod = g_modules_array[index]; + if (mod.is_loaded > 0) { + mod.is_loaded++; + return ORBIS_OK; + } + + s32 start_result = 0; + // Most of the logic the actual module has here is to get the correct location of this module. + // Since we only care about a small subset of LLEs, we can simplify this logic. + if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsGame) != 0) { + std::string guest_path = std::string("/app0/sce_module/").append(mod.name); + guest_path.append(".prx"); + const auto& host_path = mnt->GetHostPath(guest_path); + + // For convenience, load through linker directly instead of loading through libkernel calls. + s32 result = linker->LoadAndStartModule(host_path, argc, argv, &start_result); + // If the module is missing, the library prints a very helpful message for developers. + // We'll just log an error. + if (result < 0) { + LOG_ERROR(Lib_SysModule, "Failed to load game library {}", guest_path); + return result; + } else { + // On success, the library validates module params and the module SDK version. + // We don't store the information this uses, so skip the proper checks. + mod.handle = result; + mod.is_loaded++; + } + } else { + // This is not a game library. We'll need to perform some checks, + // but we don't need to perform the path resolution logic the actual library has. + std::string mod_name = std::string(mod.name); + + // libSceGnmDriver case + if (index == 0xd && Config::isDevKitConsole()) { + // There are some other checks involved here that I am not familiar with. + // Since we're not exactly running libSceGnmDriver LLE, this shouldn't matter too much. + mod_name.append("_padebug"); + } + + // libSceSsl2 case + if (index == 0x27 && false /*needs legacy ssl*/) { + // Replaces module name with libSceSsl (index 0x15) + mod_name.clear(); + mod_name.append(g_modules_array[0x15].name); + } + + // libSceVrTracker case + if (index == 0xb3 && Config::isDevKitConsole()) { + mod_name.append("_debug"); + } + + if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeo) == 0 && + (mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeoMode) != 0 && + Kernel::sceKernelIsNeoMode() == 1) { + // PS4 Pro running in enhanced mode + mod_name.append("ForNeoMode"); + } else if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeo) != 0 && + Config::isNeoModeConsole()) { + // PS4 Pro running in base mode + mod_name.append("ForNeo"); + } + + // Append .sprx extension. + mod_name.append(".sprx"); + + // Now we need to check if the requested library is allowed to LLE. + // First, we allow all modules from game-specific sys_modules + const auto& sys_module_path = Config::getSysModulesPath(); + const auto& game_specific_module_path = + sys_module_path / game_info->GameSerial() / mod_name; + if (std::filesystem::exists(game_specific_module_path)) { + // The requested module is present in the game-specific sys_modules, load it. + LOG_INFO(Loader, "Loading {} from game serial file {}", mod_name, + game_info->GameSerial()); + s32 handle = + linker->LoadAndStartModule(game_specific_module_path, argc, argv, &start_result); + ASSERT_MSG(handle >= 0, "Failed to load module {}", mod_name); + mod.handle = handle; + mod.is_loaded++; + if (res_out != nullptr) { + *res_out = start_result; + } + return ORBIS_OK; + } + + // We need to check a few things here. + // First, check if this is a module we allow LLE for. + static s32 stub_handle = 100; + constexpr auto ModulesToLoad = std::to_array( + {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib}, + {"libSceUlt.sprx", nullptr}, + {"libSceRtc.sprx", &Libraries::Rtc::RegisterLib}, + {"libSceJpegDec.sprx", nullptr}, + {"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib}, + {"libScePngEnc.sprx", &Libraries::PngEnc::RegisterLib}, + {"libSceJson.sprx", nullptr}, + {"libSceJson2.sprx", nullptr}, + {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib}, + {"libSceCesCs.sprx", nullptr}, + {"libSceAudiodec.sprx", nullptr}, + {"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont}, + {"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt}, + {"libSceFreeTypeOt.sprx", nullptr}}); + + // Iterate through the allowed array + const auto it = std::ranges::find_if( + ModulesToLoad, [&](Core::SysModules module) { return mod_name == module.module_name; }); + if (it == ModulesToLoad.end()) { + // Not an allowed LLE, stub success without warning. + mod.is_loaded++; + // Some internal checks rely on a handle, stub a valid one. + mod.handle = stub_handle++; + if (res_out != nullptr) { + *res_out = ORBIS_OK; + } + return ORBIS_OK; + } + + // Allowed module, check if it exists + const auto& module_path = sys_module_path / mod_name; + if (std::filesystem::exists(module_path)) { + LOG_INFO(Loader, "Loading {}", mod_name); + s32 handle = linker->LoadAndStartModule(module_path, argc, argv, &start_result); + ASSERT_MSG(handle >= 0, "Failed to load module {}", mod_name); + mod.handle = handle; + } else { + // Allowed LLE that isn't present, log message + auto& [name, init_func] = *it; + if (init_func) { + LOG_INFO(Loader, "Can't Load {} switching to HLE", mod_name); + init_func(&linker->GetHLESymbols()); + } else { + LOG_INFO(Loader, "No HLE available for {} module", mod_name); + } + mod.handle = stub_handle++; + } + + // Mark module as loaded. + mod.is_loaded++; + } + + // Only successful loads will reach here + if (res_out != nullptr) { + *res_out = start_result; + } + + return ORBIS_OK; +} + +s32 loadModule(s32 id, s32 argc, const void* argv, s32* res_out) { + // Retrieve the module to load from the table + OrbisSysmoduleModuleInternal requested_module{}; + for (OrbisSysmoduleModuleInternal mod : g_modules_array) { + if (mod.id == id) { + requested_module = mod; + break; + } + } + if (requested_module.id != id || requested_module.id == 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + // Every module has a pointer to an array of indexes to modules that need loading. + if (requested_module.to_load == nullptr) { + // Seems like ORBIS_SYSMODULE_LOCK_FAILED is a generic internal error. + return ORBIS_SYSMODULE_LOCK_FAILED; + } + + LOG_INFO(Lib_SysModule, "Loading {}", requested_module.name); + + // Loop through every module that requires loading, in reverse order + for (s64 i = requested_module.num_to_load - 1; i >= 0; i--) { + // Modules flagged as debug modules only load for devkits + u32 mod_index = requested_module.to_load[i]; + if ((!Config::isDevKitConsole() && + g_modules_array[mod_index].flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0) { + continue; + } + + // Arguments and result should only be applied to the requested module + // Dependencies don't receive these values. + s32 result = 0; + if (i != 0) { + result = loadModuleInternal(mod_index, 0, nullptr, nullptr); + } else { + result = loadModuleInternal(mod_index, argc, argv, res_out); + } + + // If loading any module fails, abort there. + if (result != ORBIS_OK) { + return result; + } + } + return ORBIS_OK; +} + +s32 unloadModule(s32 id, s32 argc, const void* argv, s32* res_out, bool is_internal) { + OrbisSysmoduleModuleInternal mod{}; + for (s32 i = 0; i < g_modules_array.size(); i++) { + mod = g_modules_array[i]; + if (mod.id != id) { + continue; + } + + // Skips checking libSceDiscMap + if (i == 0x22) { + continue; + } + + // If the module is loaded once, and is part of the second preload list, + // then return OK and do nothing. + for (s32 index : g_preload_list_2) { + if (index == i && mod.is_loaded == 1) { + return ORBIS_OK; + } + } + + // Found the correct module. + break; + } + + // If we failed to locate the module, return invalid id. + if (mod.id != id || mod.id == 0) { + return ORBIS_SYSMODULE_INVALID_ID; + } + + // If the module has no dependencies, then return an internal error. + if (mod.num_to_load == 0 || mod.to_load == nullptr) { + return ORBIS_SYSMODULE_LOCK_FAILED; + } + + // Unload the module and it's dependencies + for (s64 i = 0; i < mod.num_to_load; i++) { + OrbisSysmoduleModuleInternal dep_mod = g_modules_array[mod.to_load[i]]; + // If this is a debug module and we're not emulating a devkit, skip it. + if ((dep_mod.flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0 && + !Config::isDevKitConsole()) { + continue; + } + + // If the module to unload is marked as unloaded, then return not loaded + if (dep_mod.is_loaded == 0) { + return ORBIS_SYSMODULE_NOT_LOADED; + } + + // By this point, all necessary checks are performed, decrement the load count. + dep_mod.is_loaded--; + + // Normally, this is where the real library would actually unload the module, + // through a call to sceKernelStopUnloadModule. + // As we don't implement module unloading, this behavior is skipped. + + // Stub success during requested module unload. + if (i == 0 && res_out != nullptr) { + *res_out = ORBIS_OK; + } + } + return ORBIS_OK; +} + +s32 preloadModulesForLibkernel() { + // For now, default to loading g_preload_list_3. + // As far as I can tell, g_preload_list_1 seems to be some sort of list with libs + // that games don't typically use, and g_preload_list_2 is just a reorganized version of 3. + s32 sdk_ver = 0; + ASSERT_MSG(Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver) == 0, + "Failed to get compiled SDK version"); + for (u32 module_index : g_preload_list_3) { + // As per usual, these are arrays of indexes for g_modules_array + // libSceDbg, libScePerf, libSceMat, and libSceRazorCpu_debug. + // These are skipped unless this console is a devkit. + if ((module_index == 0x12 || module_index == 0x1e || module_index == 0x24 || + module_index == 0x26) && + !Config::isDevKitConsole()) { + continue; + } + + // libSceDiscMap case, skipped on newer SDK versions. + if (module_index == 0x22 && sdk_ver >= Common::ElfInfo::FW_20) { + continue; + } + + // libSceDbgAssist is skipped on non-testkit consoles. + // For now, stub check to non-devkit. + if (module_index == 0x23 && !Config::isDevKitConsole()) { + continue; + } + + // libSceRazorCpu, skipped for old non-devkit consoles. + if (module_index == 0x25 && sdk_ver < Common::ElfInfo::FW_45 && + !Config::isDevKitConsole()) { + continue; + } + + // libSceHttp2, skipped for SDK versions below 7.00. + if (module_index == 0x28 && sdk_ver < Common::ElfInfo::FW_70) { + continue; + } + + // libSceNpWebApi2 and libSceNpGameIntent, skipped for SDK versions below 7.50 + if ((module_index == 0x29 || module_index == 0x2a) && sdk_ver < Common::ElfInfo::FW_75) { + continue; + } + + // Load the actual module + s32 result = loadModuleInternal(module_index, 0, nullptr, nullptr); + if (result != ORBIS_OK) { + // On real hardware, module preloading must succeed or the game will abort. + // To enable users to test homebrew easier, we'll log a critical error instead. + LOG_CRITICAL(Lib_SysModule, "Failed to preload {}, expect crashes", + g_modules_array[module_index].name); + } + } + return ORBIS_OK; +} + +} // namespace Libraries::SysModule diff --git a/src/core/libraries/sysmodule/sysmodule_internal.h b/src/core/libraries/sysmodule/sysmodule_internal.h new file mode 100644 index 000000000..8f88f85ea --- /dev/null +++ b/src/core/libraries/sysmodule/sysmodule_internal.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/kernel/process.h" + +namespace Libraries::SysModule { + +s32 getModuleHandle(s32 id, s32* handle); +bool shouldHideName(const char* module_name); +bool isDebugModule(s32 id); +bool validateModuleId(s32 id); +s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out); +s32 loadModule(s32 id, s32 argc, const void* argv, s32* res_out); +s32 unloadModule(s32 id, s32 argc, const void* argv, s32* res_out, bool is_internal); +s32 preloadModulesForLibkernel(); + +} // namespace Libraries::SysModule \ No newline at end of file diff --git a/src/core/libraries/sysmodule/sysmodule_table.h b/src/core/libraries/sysmodule/sysmodule_table.h new file mode 100644 index 000000000..bd27a8aae --- /dev/null +++ b/src/core/libraries/sysmodule/sysmodule_table.h @@ -0,0 +1,684 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Libraries::SysModule { + +/** + * libSceSysmodule hardcodes an array of valuable data about loading each PS4 module. + * This header stores the contents of this array, as dumped from 12.52's libSceSysmodule, + * and altered to fit within my simplified internal module struct. + */ + +// This is an internal struct. Doesn't match the real one exactly. +struct OrbisSysmoduleModuleInternal { + u32 id; // User requested ID + s32 handle; // Handle of the module, once loaded + s32 is_loaded; // 0 by default, set to 1 once loaded. + s32 flags; // Miscellaneous details about the module + const char* name; // Name of the actual SPRX/PRX library + const u16* to_load; // Pointer to an array of modules to load + s32 num_to_load; // Number of indicies in the array of modules +}; + +// This enum contains helpful identifiers for some bits used in the flags of a module. +enum OrbisSysmoduleModuleInternalFlags : s32 { + IsCommon = 1, // Module is located in /system/common/lib + IsPriv = 2, // Module is located in /system/priv/lib + IsGame = 4, // Module is located in /app0/sce_module + IsDebug = 8, // Module should only be loaded on devkit/testkit consoles + IsNeo = 0x200, // Module should only be loaded on PS4 Pro consoles + IsNeoMode = 0x400, // Module should only be loaded for PS4 Pro running in enhanced mode + IsCommonEx = 0x1000, // Module is located in /system_ex/common_ex/lib + IsPrivEx = 0x2000, // Module is located in /system_ex/priv_ex/lib +}; + +// Array of module indexes to load in sceSysmodulePreloadModuleForLibkernel. +// The library has three versions of this array +u32 g_preload_list_1[36] = {0x24, 3, 4, 5, 6, 7, 8, 9, 0x25, 0xb, 0xc, 0xd, + 0xe, 0xf, 0x10, 0x11, 0x1f, 0x12, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17, + 0x2a, 0x18, 0x29, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x26, 0x1e, 0x20, 0x21}; +u32 g_preload_list_2[38] = {1, 2, 0x24, 0x22, 3, 4, 5, 6, 7, 8, + 9, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x1f, 0x12, + 0x23, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17, 0x2a, 0x18, 0x29, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x25, 0x26, 0x1e}; +u32 g_preload_list_3[38] = {1, 2, 0x24, 0x22, 3, 4, 5, 6, 7, 8, + 9, 0x25, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x1f, + 0x12, 0x23, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17, 0x2a, 0x18, + 0x29, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x26, 0x1e}; + +// Arrays of modules to load for each module. +// The stored values are valid indices to modules in g_modules_array. +u16 g_libSceNet_modules[1] = {5}; +u16 g_libSceIpmi_modules[1] = {6}; +u16 g_libSceMbus_modules[2] = {7, 6}; +u16 g_libSceRegMgr_modules[1] = {8}; +u16 g_libSceRtc_modules[1] = {9}; +u16 g_libSceAvSetting_modules[3] = {11, 7, 6}; +u16 g_libSceVideoOut_modules[3] = {12, 11, 7}; +u16 g_libSceGnmDriver_modules[4] = {13, 12, 8, 37}; +u16 g_libSceAudioOut_modules[4] = {14, 11, 7, 6}; +u16 g_libSceAudioIn_modules[4] = {15, 14, 7, 6}; +u16 g_libSceAjm_modules[1] = {16}; +u16 g_libScePad_modules[2] = {17, 7}; +u16 g_libSceDbg_debug_modules[1] = {18}; +u16 g_libSceNetCtl_modules[2] = {19, 6}; +u16 g_libSceHttp_modules[5] = {20, 39, 9, 19, 5}; +u16 g_libSceSsl_modules[3] = {21, 9, 5}; +u16 g_libSceNpCommon_modules[8] = {22, 20, 39, 19, 9, 8, 6, 5}; +u16 g_libSceNpManager_modules[7] = {23, 22, 20, 39, 19, 9, 5}; +u16 g_libSceNpWebApi_modules[7] = {24, 23, 22, 20, 39, 9, 5}; +u16 g_libSceSaveData_modules[4] = {25, 27, 9, 6}; +u16 g_libSceSystemService_modules[3] = {26, 8, 6}; +u16 g_libSceUserService_modules[2] = {27, 6}; +u16 g_libSceCommonDialog_modules[1] = {28}; +u16 g_libSceSysUtil_modules[2] = {29, 8}; +u16 g_libScePerf_debug_modules[3] = {30, 38, 37}; +u16 g_libSceCamera_modules[2] = {31, 7}; +u16 g_libSceDiscMap_modules[1] = {34}; +u16 g_libSceDbgAssist_modules[1] = {35}; +u16 g_libSceMat_debug_modules[1] = {36}; +u16 g_libSceRazorCpu_modules[1] = {37}; +u16 g_libSceRazorCpu_debug_debug_modules[2] = {38, 37}; +u16 g_libSceSsl2_modules[3] = {39, 9, 5}; +u16 g_libSceHttp2_modules[13] = {40, 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5}; +u16 g_libSceNpWebApi2_modules[39] = {41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9, + 8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9, + 19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5}; +u16 g_libSceNpGameIntent_modules[10] = {42, 22, 20, 39, 19, 9, 8, 6, 5, 6}; +u16 g_libSceFiber_modules[5] = {49, 114, 30, 38, 37}; +u16 g_libSceUlt_modules[6] = {50, 49, 114, 30, 38, 37}; +u16 g_libSceNgs2_modules[2] = {51, 16}; +u16 g_libSceXml_modules[1] = {52}; +u16 g_libSceNpUtility_modules[5] = {53, 22, 20, 19, 5}; +u16 g_libSceVoice_modules[4] = {54, 16, 15, 14}; +u16 g_libSceNpMatching2_modules[7] = {55, 23, 22, 20, 39, 19, 5}; +u16 g_libSceNpScoreRanking_modules[3] = {56, 23, 22}; +u16 g_libSceRudp_modules[1] = {57}; +u16 g_libSceNpTus_modules[3] = {58, 23, 22}; +u16 g_libSceFace_modules[1] = {59}; +u16 g_libSceSmart_modules[1] = {60}; +u16 g_libSceJson_modules[1] = {61}; +u16 g_libSceGameLiveStreaming_modules[2] = {62, 6}; +u16 g_libSceCompanionUtil_modules[3] = {63, 7, 6}; +u16 g_libScePlayGo_modules[1] = {64}; +u16 g_libSceFont_modules[1] = {65}; +u16 g_libSceVideoRecording_modules[2] = {66, 82}; +u16 g_libSceAudiodec_modules[2] = {67, 16}; +u16 g_libSceJpegDec_modules[1] = {68}; +u16 g_libSceJpegEnc_modules[1] = {69}; +u16 g_libScePngDec_modules[1] = {70}; +u16 g_libScePngEnc_modules[1] = {71}; +u16 g_libSceVideodec_modules[3] = {72, 80, 161}; +u16 g_libSceMove_modules[1] = {73}; +u16 g_libScePadTracker_modules[2] = {75, 17}; +u16 g_libSceDepth_modules[2] = {76, 31}; +u16 g_libSceHand_modules[1] = {77}; +u16 g_libSceIme_modules[2] = {78, 6}; +u16 g_libSceImeDialog_modules[2] = {79, 6}; +u16 g_libSceVdecCore_modules[1] = {80}; +u16 g_libSceNpParty_modules[2] = {81, 6}; +u16 g_libSceAvcap_modules[2] = {82, 6}; +u16 g_libSceFontFt_modules[1] = {83}; +u16 g_libSceFreeTypeOt_modules[1] = {84}; +u16 g_libSceFreeTypeOl_modules[1] = {85}; +u16 g_libSceFreeTypeOptOl_modules[1] = {86}; +u16 g_libSceScreenShot_modules[3] = {87, 29, 6}; +u16 g_libSceNpAuth_modules[3] = {88, 22, 23}; +u16 g_libSceVoiceQos_modules[5] = {89, 54, 16, 15, 14}; +u16 g_libSceSysCore_modules[2] = {90, 6}; +u16 g_libSceM4aacEnc_modules[2] = {91, 16}; +u16 g_libSceAudiodecCpu_modules[1] = {92}; +u16 g_libSceCdlgUtilServer_modules[2] = {93, 26}; +u16 g_libSceSulpha_debug_modules[1] = {94}; +u16 g_libSceSaveDataDialog_modules[4] = {95, 9, 28, 26}; +u16 g_libSceInvitationDialog_modules[1] = {96}; +u16 g_libSceKeyboard_debug_modules[1] = {97}; +u16 g_libSceKeyboard_modules[1] = {98}; +u16 g_libSceMsgDialog_modules[1] = {99}; +u16 g_libSceAvPlayer_modules[1] = {100}; +u16 g_libSceContentExport_modules[1] = {101}; +u16 g_libSceVisionManager_modules[1] = {102}; +u16 g_libSceAc3Enc_modules[2] = {103, 16}; +u16 g_libSceAppInstUtil_modules[1] = {104}; +u16 g_libSceVencCore_modules[1] = {105}; +u16 g_libSceAudio3d_modules[1] = {106}; +u16 g_libSceNpCommerce_modules[1] = {107}; +u16 g_libSceHidControl_modules[1] = {108}; +u16 g_libSceMouse_modules[1] = {109}; +u16 g_libSceCompanionHttpd_modules[1] = {110}; +u16 g_libSceWebBrowserDialog_modules[1] = {111}; +u16 g_libSceErrorDialog_modules[1] = {112}; +u16 g_libSceNpTrophy_modules[1] = {113}; +u16 g_ulobjmgr_modules[1] = {114}; +u16 g_libSceVideoCoreInterface_modules[1] = {115}; +u16 g_libSceVideoCoreServerInterface_modules[1] = {116}; +u16 g_libSceNpSns_modules[1] = {117}; +u16 g_libSceNpSnsFacebookDialog_modules[2] = {118, 117}; +u16 g_libSceMoveTracker_modules[1] = {119}; +u16 g_libSceNpProfileDialog_modules[1] = {120}; +u16 g_libSceNpFriendListDialog_modules[1] = {121}; +u16 g_libSceAppContent_modules[1] = {122}; +u16 g_libSceMarlin_modules[1] = {123}; +u16 g_libSceDtsEnc_modules[2] = {124, 16}; +u16 g_libSceNpSignaling_modules[1] = {125}; +u16 g_libSceRemoteplay_modules[1] = {126}; +u16 g_libSceUsbd_modules[1] = {127}; +u16 g_libSceGameCustomDataDialog_modules[1] = {128}; +u16 g_libSceNpEulaDialog_modules[1] = {129}; +u16 g_libSceRandom_modules[1] = {130}; +u16 g_libSceDipsw_modules[1] = {131}; +u16 g_libSceS3DConversion_modules[1] = {132}; +u16 g_libSceOttvCapture_debug_modules[1] = {133}; +u16 g_libSceBgft_modules[1] = {134}; +u16 g_libSceAudiodecCpuDdp_modules[1] = {135}; +u16 g_libSceAudiodecCpuM4aac_modules[1] = {136}; +u16 g_libSceAudiodecCpuDts_modules[1] = {137}; +u16 g_libSceAudiodecCpuDtsHdLbr_modules[1] = {138}; +u16 g_libSceAudiodecCpuDtsHdMa_modules[1] = {139}; +u16 g_libSceAudiodecCpuLpcm_modules[1] = {140}; +u16 g_libSceBemp2sys_modules[1] = {141}; +u16 g_libSceBeisobmf_modules[1] = {142}; +u16 g_libScePlayReady_modules[1] = {143}; +u16 g_libSceVideoNativeExtEssential_modules[1] = {144}; +u16 g_libSceZlib_modules[1] = {145}; +u16 g_libSceIduUtil_modules[1] = {146}; +u16 g_libScePsm_modules[1] = {147}; +u16 g_libSceDtcpIp_modules[1] = {148}; +u16 g_libSceKbEmulate_modules[1] = {149}; +u16 g_libSceAppChecker_modules[1] = {150}; +u16 g_libSceNpGriefReport_modules[1] = {151}; +u16 g_libSceContentSearch_modules[1] = {152}; +u16 g_libSceShareUtility_modules[1] = {153}; +u16 g_libSceWeb_modules[6] = {154, 155, 147, 192, 27, 6}; +u16 g_libSceWebKit2_modules[30] = {155, 266, 90, 6, 8, 255, 192, 116, 266, 90, 6, 8, 12, 11, 7, + 17, 7, 26, 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9}; +u16 g_libSceDeci4h_debug_modules[1] = {156}; +u16 g_libSceHeadTracker_modules[1] = {157}; +u16 g_libSceGameUpdate_modules[2] = {158, 6}; +u16 g_libSceAutoMounterClient_modules[2] = {159, 6}; +u16 g_libSceSystemGesture_modules[1] = {160}; +u16 g_libSceVdecSavc_modules[1] = {161}; +u16 g_libSceVdecSavc2_modules[1] = {162}; +u16 g_libSceVideodec2_modules[3] = {163, 80, 162}; +u16 g_libSceVdecwrap_modules[2] = {164, 80}; +u16 g_libSceVshctl_modules[1] = {165}; +u16 g_libSceAt9Enc_modules[1] = {166}; +u16 g_libSceConvertKeycode_modules[1] = {167}; +u16 g_libSceGpuException_modules[1] = {168}; +u16 g_libSceSharePlay_modules[1] = {169}; +u16 g_libSceAudiodReport_modules[1] = {170}; +u16 g_libSceSulphaDrv_modules[1] = {171}; +u16 g_libSceHmd_modules[1] = {172}; +u16 g_libSceUsbStorage_modules[2] = {173, 6}; +u16 g_libSceVdecShevc_modules[1] = {174}; +u16 g_libSceUsbStorageDialog_modules[1] = {175}; +u16 g_libSceFaceTracker_modules[2] = {176, 59}; +u16 g_libSceHandTracker_modules[1] = {177}; +u16 g_libSceNpSnsYouTubeDialog_modules[2] = {178, 117}; +u16 g_libSceVrTracker_modules[6] = {179, 6, 172, 31, 17, 73}; +u16 g_libSceProfileCacheExternal_modules[2] = {180, 6}; +u16 g_libSceBackupRestoreUtil_modules[1] = {181}; +u16 g_libSceMusicPlayerService_modules[2] = {182, 183}; +u16 g_libSceMusicCoreServerClientJsEx_modules[1] = {183}; +u16 g_libSceSpSysCallWrapper_modules[3] = {184, 19, 6}; +u16 g_libScePs2EmuMenuDialog_modules[1] = {185}; +u16 g_libSceNpSnsDailyMotionDialog_modules[1] = {186}; +u16 g_libSceAudiodecCpuHevag_modules[1] = {187}; +u16 g_libSceLoginDialog_modules[2] = {188, 6}; +u16 g_libSceLoginService_modules[2] = {189, 6}; +u16 g_libSceSigninDialog_modules[2] = {190, 6}; +u16 g_libSceVdecsw_modules[3] = {191, 80, 162}; +u16 g_libSceOrbisCompat_modules[24] = {192, 116, 266, 90, 6, 8, 12, 11, 7, 17, 7, 26, + 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9}; +u16 g_libSceCoreIPC_modules[1] = {193}; +u16 g_libSceCustomMusicCore_modules[12] = {194, 29, 8, 27, 6, 14, 11, 7, 6, 11, 7, 6}; +u16 g_libSceJson2_modules[1] = {195}; +u16 g_libSceAudioLatencyEstimation_modules[1] = {196}; +u16 g_libSceWkFontConfig_modules[1] = {197}; +u16 g_libSceVorbisDec_modules[3] = {198, 67, 16}; +u16 g_libSceTtsCoreEnUs_modules[1] = {199}; +u16 g_libSceTtsCoreJp_modules[1] = {200}; +u16 g_libSceOpusCeltEnc_modules[2] = {201, 16}; +u16 g_libSceOpusCeltDec_modules[2] = {202, 16}; +u16 g_libSceLoginMgrServer_modules[1] = {203}; +u16 g_libSceHmdSetupDialog_modules[1] = {204}; +u16 g_libSceVideoOutSecondary_modules[6] = {205, 82, 6, 12, 11, 7}; +u16 g_libSceContentDelete_modules[1] = {206}; +u16 g_libSceImeBackend_modules[1] = {207}; +u16 g_libSceNetCtlApDialog_modules[1] = {208}; +u16 g_libSceGnmResourceRegistration_modules[1] = {209}; +u16 g_libScePlayGoDialog_modules[1] = {210}; +u16 g_libSceSocialScreen_modules[7] = {211, 205, 82, 6, 12, 11, 7}; +u16 g_libSceEditMp4_modules[1] = {212}; +u16 g_libScePsmKitSystem_modules[1] = {221}; +u16 g_libSceTextToSpeech_modules[1] = {222}; +u16 g_libSceNpToolkit_modules[1] = {223}; +u16 g_libSceCustomMusicService_modules[2] = {224, 183}; +u16 g_libSceClSysCallWrapper_modules[11] = {225, 20, 39, 9, 19, 5, 39, 9, 5, 67, 16}; +u16 g_libSceScm_modules[1] = {226}; +u16 g_libSceSystemLogger_modules[2] = {227, 6}; +u16 g_libSceBluetoothHid_modules[1] = {228}; +u16 g_libSceAvPlayerStreaming_modules[1] = {229}; +u16 g_libSceAudiodecCpuAlac_modules[1] = {230}; +u16 g_libSceVideoDecoderArbitration_modules[1] = {231}; +u16 g_libSceVrServiceDialog_modules[1] = {232}; +u16 g_libSceJobManager_modules[2] = {233, 114}; +u16 g_libSceAudiodecCpuFlac_modules[1] = {234}; +u16 g_libSceSrcUtl_modules[2] = {235, 16}; +u16 g_libSceS3da_modules[1] = {236}; +u16 g_libSceDseehx_modules[1] = {237}; +u16 g_libSceShareFactoryUtil_modules[1] = {238}; +u16 g_libSceDataTransfer_modules[1] = {239}; +u16 g_libSceSocialScreenDialog_modules[1] = {240}; +u16 g_libSceAbstractStorage_modules[1] = {241}; +u16 g_libSceImageUtil_modules[1] = {242}; +u16 g_libSceMetadataReaderWriter_modules[1] = {243}; +u16 g_libSceJpegParser_modules[1] = {244}; +u16 g_libSceGvMp4Parser_modules[1] = {245}; +u16 g_libScePngParser_modules[1] = {246}; +u16 g_libSceGifParser_modules[1] = {247}; +u16 g_libSceNpSnsDialog_modules[2] = {248, 117}; +u16 g_libSceAbstractLocal_modules[1] = {249}; +u16 g_libSceAbstractFacebook_modules[1] = {250}; +u16 g_libSceAbstractYoutube_modules[1] = {251}; +u16 g_libSceAbstractTwitter_modules[1] = {252}; +u16 g_libSceAbstractDailymotion_modules[1] = {253}; +u16 g_libSceNpToolkit2_modules[1] = {254}; +u16 g_libScePrecompiledShaders_modules[1] = {255}; +u16 g_libSceDiscId_modules[1] = {256}; +u16 g_libSceLibreSsl_modules[2] = {257, 130}; +u16 g_libSceFsInternalForVsh_modules[1] = {258}; +u16 g_libSceNpUniversalDataSystem_modules[1] = {259}; +u16 g_libSceDolbyVision_modules[1] = {260}; +u16 g_libSceOpusSilkEnc_modules[2] = {261, 16}; +u16 g_libSceOpusDec_modules[2] = {262, 16}; +u16 g_libSceWebKit2Secure_modules[34] = {263, 265, 26, 8, 6, 266, 90, 6, 8, 255, 192, 116, + 266, 90, 6, 8, 12, 11, 7, 17, 7, 26, 8, 6, + 257, 130, 39, 9, 5, 19, 6, 5, 8, 9}; +u16 g_libSceJscCompiler_modules[1] = {264}; +u16 g_libSceJitBridge_modules[4] = {265, 26, 8, 6}; +u16 g_libScePigletv2VSH_modules[4] = {266, 90, 6, 8}; +u16 g_libSceJitBridge_common_ex_modules[4] = {267, 26, 8, 6}; +u16 g_libSceJscCompiler_common_ex_modules[1] = {268}; +u16 g_libSceOrbisCompat_common_ex_modules[24] = {269, 116, 266, 90, 6, 8, 12, 11, 7, 17, 7, 26, + 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9}; +u16 g_libSceWeb_common_ex_modules[6] = {270, 271, 147, 269, 27, 6}; +u16 g_libSceWebKit2_common_ex_modules[30] = {271, 266, 90, 6, 8, 273, 269, 116, 266, 90, + 6, 8, 12, 11, 7, 17, 7, 26, 8, 6, + 257, 130, 39, 9, 5, 19, 6, 5, 8, 9}; +u16 g_libSceWebKit2Secure_common_ex_modules[34] = { + 272, 267, 26, 8, 6, 266, 90, 6, 8, 273, 269, 116, 266, 90, 6, 8, 12, + 11, 7, 17, 7, 26, 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9}; +u16 g_libScePrecompiledShaders_common_ex_modules[1] = {273}; +u16 g_libSceGic_modules[1] = {274}; +u16 g_libSceRnpsAppMgr_modules[1] = {275}; +u16 g_libSceAsyncStorageInternal_modules[1] = {276}; +u16 g_libSceHttpCache_modules[1] = {277}; +u16 g_libScePlayReady2_modules[1] = {278}; +u16 g_libSceHdrScopes_debug_modules[1] = {279}; +u16 g_libSceNKWeb_modules[1] = {280}; +u16 g_libSceNKWebKit_modules[2] = {281, 282}; +u16 g_libSceNKWebKitRequirements_modules[1] = {282}; +u16 g_libSceVnaInternal_modules[1] = {283}; +u16 g_libSceVnaWebsocket_modules[1] = {284}; +u16 g_libSceCesCs_modules[1] = {285}; +u16 g_libSceComposite_modules[1] = {286}; +u16 g_libSceCompositeExt_modules[1] = {287}; +u16 g_libSceHubAppUtil_modules[1] = {288}; +u16 g_libScePosixForWebKit_modules[1] = {289}; +u16 g_libSceNpPartner001_modules[1] = {290}; +u16 g_libSceNpSessionSignaling_modules[75] = { + 291, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9, + 5, 9, 19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 23, 22, + 20, 39, 19, 9, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 39, 9, 5, 19, 6, 5, 9}; +u16 g_libScePlayerInvitationDialog_modules[1] = {292}; +u16 g_libSceNpCppWebApi_modules[42] = {293, 195, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, + 9, 8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9, + 19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5, 9}; +u16 g_libSceNpEntitlementAccess_modules[1] = {294}; +u16 g_libSceNpRemotePlaySessionSignaling_modules[76] = { + 295, 291, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 40, + 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 20, 39, 9, 19, 5, 39, 9, + 5, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 23, 22, 20, 39, 19, 9, 5, 40, + 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 39, 9, 5, 19, 6, 5, 9}; +u16 g_libSceLibreSsl3_modules[2] = {296, 130}; +u16 g_libcurl_modules[2] = {297, 289}; +u16 g_libicu_modules[2] = {298, 289}; +u16 g_libcairo_modules[9] = {299, 300, 301, 302, 303, 289, 298, 289, 289}; +u16 g_libfontconfig_modules[1] = {300}; +u16 g_libfreetype_modules[1] = {301}; +u16 g_libharfbuzz_modules[1] = {302}; +u16 g_libpng16_modules[2] = {303, 289}; +u16 g_libSceFontGs_modules[1] = {304}; +u16 g_libSceGLSlimClientVSH_modules[1] = {305}; +u16 g_libSceGLSlimServerVSH_modules[1] = {306}; +u16 g_libSceFontGsm_modules[1] = {307}; +u16 g_libSceNpPartnerSubscription_modules[1] = {308}; +u16 g_libSceNpAuthAuthorizedAppDialog_modules[1] = {309}; + +// This is the actual array of modules. +constexpr u64 g_num_modules = 310; +std::array g_modules_array = std::to_array< + OrbisSysmoduleModuleInternal>( + {{0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 1, "libkernel", nullptr, 0}, + {0x0, -1, 0, 1, "libSceLibcInternal", nullptr, 0}, + {0x0, -1, 0, 4, "libSceFios2", nullptr, 0}, + {0x0, -1, 0, 4, "libc", nullptr, 0}, + {0x8000001c, -1, 0, 1, "libSceNet", g_libSceNet_modules, 1}, + {0x8000001d, -1, 0, 1, "libSceIpmi", g_libSceIpmi_modules, 1}, + {0x8000001e, -1, 0, 1, "libSceMbus", g_libSceMbus_modules, 2}, + {0x8000001f, -1, 0, 1, "libSceRegMgr", g_libSceRegMgr_modules, 1}, + {0x80000020, -1, 0, 1, "libSceRtc", g_libSceRtc_modules, 1}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x80000021, -1, 0, 1, "libSceAvSetting", g_libSceAvSetting_modules, 3}, + {0x80000022, -1, 0, 1, "libSceVideoOut", g_libSceVideoOut_modules, 3}, + {0x80000052, -1, 0, 1025, "libSceGnmDriver", g_libSceGnmDriver_modules, 4}, + {0x80000001, -1, 0, 1, "libSceAudioOut", g_libSceAudioOut_modules, 4}, + {0x80000002, -1, 0, 1, "libSceAudioIn", g_libSceAudioIn_modules, 4}, + {0x80000023, -1, 0, 1, "libSceAjm", g_libSceAjm_modules, 1}, + {0x80000024, -1, 0, 1, "libScePad", g_libScePad_modules, 2}, + {0x80000025, -1, 0, 9, "libSceDbg", g_libSceDbg_debug_modules, 1}, + {0x80000009, -1, 0, 1, "libSceNetCtl", g_libSceNetCtl_modules, 2}, + {0x8000000a, -1, 0, 1, "libSceHttp", g_libSceHttp_modules, 5}, + {0x0, -1, 0, 1, "libSceSsl", g_libSceSsl_modules, 3}, + {0x8000000c, -1, 0, 1, "libSceNpCommon", g_libSceNpCommon_modules, 8}, + {0x8000000d, -1, 0, 1, "libSceNpManager", g_libSceNpManager_modules, 7}, + {0x8000000e, -1, 0, 1, "libSceNpWebApi", g_libSceNpWebApi_modules, 7}, + {0x8000000f, -1, 0, 1, "libSceSaveData", g_libSceSaveData_modules, 4}, + {0x80000010, -1, 0, 1, "libSceSystemService", g_libSceSystemService_modules, 3}, + {0x80000011, -1, 0, 1, "libSceUserService", g_libSceUserService_modules, 2}, + {0x80000018, -1, 0, 1, "libSceCommonDialog", g_libSceCommonDialog_modules, 1}, + {0x80000026, -1, 0, 1, "libSceSysUtil", g_libSceSysUtil_modules, 2}, + {0x80000019, -1, 0, 9, "libScePerf", g_libScePerf_debug_modules, 3}, + {0x8000001a, -1, 0, 1, "libSceCamera", g_libSceCamera_modules, 2}, + {0x0, -1, 0, 1, "libSceWebKit2ForVideoService", nullptr, 0}, + {0x0, -1, 0, 1, "libSceOrbisCompatForVideoService", nullptr, 0}, + {0xd7, -1, 0, 1, "libSceDiscMap", g_libSceDiscMap_modules, 1}, + {0x8000003d, -1, 0, 129, "libSceDbgAssist", g_libSceDbgAssist_modules, 1}, + {0x80000048, -1, 0, 9, "libSceMat", g_libSceMat_debug_modules, 1}, + {0x0, -1, 0, 1, "libSceRazorCpu", g_libSceRazorCpu_modules, 1}, + {0x80000075, -1, 0, 9, "libSceRazorCpu_debug", g_libSceRazorCpu_debug_debug_modules, 2}, + {0x8000000b, -1, 0, 1, "libSceSsl2", g_libSceSsl2_modules, 3}, + {0x8000008c, -1, 0, 1, "libSceHttp2", g_libSceHttp2_modules, 13}, + {0x8000008f, -1, 0, 1, "libSceNpWebApi2", g_libSceNpWebApi2_modules, 39}, + {0x8000008d, -1, 0, 1, "libSceNpGameIntent", g_libSceNpGameIntent_modules, 10}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x6, -1, 0, 1, "libSceFiber", g_libSceFiber_modules, 5}, + {0x7, -1, 0, 1, "libSceUlt", g_libSceUlt_modules, 6}, + {0xb, -1, 0, 1, "libSceNgs2", g_libSceNgs2_modules, 2}, + {0x17, -1, 0, 1, "libSceXml", g_libSceXml_modules, 1}, + {0x19, -1, 0, 1, "libSceNpUtility", g_libSceNpUtility_modules, 5}, + {0x1a, -1, 0, 1, "libSceVoice", g_libSceVoice_modules, 4}, + {0x1c, -1, 0, 1, "libSceNpMatching2", g_libSceNpMatching2_modules, 7}, + {0x1e, -1, 0, 1, "libSceNpScoreRanking", g_libSceNpScoreRanking_modules, 3}, + {0x21, -1, 0, 1, "libSceRudp", g_libSceRudp_modules, 1}, + {0x2c, -1, 0, 1, "libSceNpTus", g_libSceNpTus_modules, 3}, + {0x38, -1, 0, 4, "libSceFace", g_libSceFace_modules, 1}, + {0x39, -1, 0, 4, "libSceSmart", g_libSceSmart_modules, 1}, + {0x80, -1, 0, 1, "libSceJson", g_libSceJson_modules, 1}, + {0x81, -1, 0, 1, "libSceGameLiveStreaming", g_libSceGameLiveStreaming_modules, 2}, + {0x82, -1, 0, 1, "libSceCompanionUtil", g_libSceCompanionUtil_modules, 3}, + {0x83, -1, 0, 1, "libScePlayGo", g_libScePlayGo_modules, 1}, + {0x84, -1, 0, 1, "libSceFont", g_libSceFont_modules, 1}, + {0x85, -1, 0, 1, "libSceVideoRecording", g_libSceVideoRecording_modules, 2}, + {0x88, -1, 0, 1, "libSceAudiodec", g_libSceAudiodec_modules, 2}, + {0x8a, -1, 0, 1, "libSceJpegDec", g_libSceJpegDec_modules, 1}, + {0x8b, -1, 0, 1, "libSceJpegEnc", g_libSceJpegEnc_modules, 1}, + {0x8c, -1, 0, 1, "libScePngDec", g_libScePngDec_modules, 1}, + {0x8d, -1, 0, 1, "libScePngEnc", g_libScePngEnc_modules, 1}, + {0x8e, -1, 0, 2049, "libSceVideodec", g_libSceVideodec_modules, 3}, + {0x8f, -1, 0, 1, "libSceMove", g_libSceMove_modules, 1}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x91, -1, 0, 1, "libScePadTracker", g_libScePadTracker_modules, 2}, + {0x92, -1, 0, 1, "libSceDepth", g_libSceDepth_modules, 2}, + {0x93, -1, 0, 4, "libSceHand", g_libSceHand_modules, 1}, + {0x95, -1, 0, 1, "libSceIme", g_libSceIme_modules, 2}, + {0x96, -1, 0, 1, "libSceImeDialog", g_libSceImeDialog_modules, 2}, + {0x80000015, -1, 0, 1, "libSceVdecCore", g_libSceVdecCore_modules, 1}, + {0x97, -1, 0, 1, "libSceNpParty", g_libSceNpParty_modules, 2}, + {0x80000003, -1, 0, 1, "libSceAvcap", g_libSceAvcap_modules, 2}, + {0x98, -1, 0, 1, "libSceFontFt", g_libSceFontFt_modules, 1}, + {0x99, -1, 0, 1, "libSceFreeTypeOt", g_libSceFreeTypeOt_modules, 1}, + {0x9a, -1, 0, 1, "libSceFreeTypeOl", g_libSceFreeTypeOl_modules, 1}, + {0x9b, -1, 0, 1, "libSceFreeTypeOptOl", g_libSceFreeTypeOptOl_modules, 1}, + {0x9c, -1, 0, 1, "libSceScreenShot", g_libSceScreenShot_modules, 3}, + {0x9d, -1, 0, 1, "libSceNpAuth", g_libSceNpAuth_modules, 3}, + {0x1b, -1, 0, 1, "libSceVoiceQos", g_libSceVoiceQos_modules, 5}, + {0x80000004, -1, 0, 1, "libSceSysCore", g_libSceSysCore_modules, 2}, + {0xbc, -1, 0, 1, "libSceM4aacEnc", g_libSceM4aacEnc_modules, 2}, + {0xbd, -1, 0, 1, "libSceAudiodecCpu", g_libSceAudiodecCpu_modules, 1}, + {0x80000007, -1, 0, 1, "libSceCdlgUtilServer", g_libSceCdlgUtilServer_modules, 2}, + {0x9f, -1, 0, 9, "libSceSulpha", g_libSceSulpha_debug_modules, 1}, + {0xa0, -1, 0, 1, "libSceSaveDataDialog", g_libSceSaveDataDialog_modules, 4}, + {0xa2, -1, 0, 1, "libSceInvitationDialog", g_libSceInvitationDialog_modules, 1}, + {0xa3, -1, 0, 2057, "libSceKeyboard", g_libSceKeyboard_debug_modules, 1}, + {0x106, -1, 0, 2049, "libSceKeyboard", g_libSceKeyboard_modules, 1}, + {0xa4, -1, 0, 1, "libSceMsgDialog", g_libSceMsgDialog_modules, 1}, + {0xa5, -1, 0, 1, "libSceAvPlayer", g_libSceAvPlayer_modules, 1}, + {0xa6, -1, 0, 1, "libSceContentExport", g_libSceContentExport_modules, 1}, + {0x80000012, -1, 0, 2, "libSceVisionManager", g_libSceVisionManager_modules, 1}, + {0x80000013, -1, 0, 2, "libSceAc3Enc", g_libSceAc3Enc_modules, 2}, + {0x80000014, -1, 0, 1, "libSceAppInstUtil", g_libSceAppInstUtil_modules, 1}, + {0x80000016, -1, 0, 514, "libSceVencCore", g_libSceVencCore_modules, 1}, + {0xa7, -1, 0, 1, "libSceAudio3d", g_libSceAudio3d_modules, 1}, + {0xa8, -1, 0, 1, "libSceNpCommerce", g_libSceNpCommerce_modules, 1}, + {0x80000017, -1, 0, 1, "libSceHidControl", g_libSceHidControl_modules, 1}, + {0xa9, -1, 0, 1, "libSceMouse", g_libSceMouse_modules, 1}, + {0xaa, -1, 0, 1, "libSceCompanionHttpd", g_libSceCompanionHttpd_modules, 1}, + {0xab, -1, 0, 1, "libSceWebBrowserDialog", g_libSceWebBrowserDialog_modules, 1}, + {0xac, -1, 0, 1, "libSceErrorDialog", g_libSceErrorDialog_modules, 1}, + {0xad, -1, 0, 1, "libSceNpTrophy", g_libSceNpTrophy_modules, 1}, + {0x0, -1, 0, 1, "ulobjmgr", g_ulobjmgr_modules, 1}, + {0xae, -1, 0, 1, "libSceVideoCoreInterface", g_libSceVideoCoreInterface_modules, 1}, + {0xaf, -1, 0, 1, "libSceVideoCoreServerInterface", g_libSceVideoCoreServerInterface_modules, + 1}, + {0x8000001b, -1, 0, 1, "libSceNpSns", g_libSceNpSns_modules, 1}, + {0xb0, -1, 0, 1, "libSceNpSnsFacebookDialog", g_libSceNpSnsFacebookDialog_modules, 2}, + {0xb1, -1, 0, 1, "libSceMoveTracker", g_libSceMoveTracker_modules, 1}, + {0xb2, -1, 0, 1, "libSceNpProfileDialog", g_libSceNpProfileDialog_modules, 1}, + {0xb3, -1, 0, 1, "libSceNpFriendListDialog", g_libSceNpFriendListDialog_modules, 1}, + {0xb4, -1, 0, 1, "libSceAppContent", g_libSceAppContent_modules, 1}, + {0x80000027, -1, 0, 2, "libSceMarlin", g_libSceMarlin_modules, 1}, + {0x80000028, -1, 0, 2, "libSceDtsEnc", g_libSceDtsEnc_modules, 2}, + {0xb5, -1, 0, 1, "libSceNpSignaling", g_libSceNpSignaling_modules, 1}, + {0xb6, -1, 0, 1, "libSceRemoteplay", g_libSceRemoteplay_modules, 1}, + {0xb7, -1, 0, 1, "libSceUsbd", g_libSceUsbd_modules, 1}, + {0xb8, -1, 0, 1, "libSceGameCustomDataDialog", g_libSceGameCustomDataDialog_modules, 1}, + {0xb9, -1, 0, 1, "libSceNpEulaDialog", g_libSceNpEulaDialog_modules, 1}, + {0xba, -1, 0, 1, "libSceRandom", g_libSceRandom_modules, 1}, + {0x80000029, -1, 0, 2, "libSceDipsw", g_libSceDipsw_modules, 1}, + {0x86, -1, 0, 4, "libSceS3DConversion", g_libSceS3DConversion_modules, 1}, + {0x8000003e, -1, 0, 9, "libSceOttvCapture", g_libSceOttvCapture_debug_modules, 1}, + {0x8000002a, -1, 0, 1, "libSceBgft", g_libSceBgft_modules, 1}, + {0xbe, -1, 0, 1, "libSceAudiodecCpuDdp", g_libSceAudiodecCpuDdp_modules, 1}, + {0xc0, -1, 0, 1, "libSceAudiodecCpuM4aac", g_libSceAudiodecCpuM4aac_modules, 1}, + {0x8000002b, -1, 0, 2, "libSceAudiodecCpuDts", g_libSceAudiodecCpuDts_modules, 1}, + {0xc9, -1, 0, 1, "libSceAudiodecCpuDtsHdLbr", g_libSceAudiodecCpuDtsHdLbr_modules, 1}, + {0x8000002d, -1, 0, 2, "libSceAudiodecCpuDtsHdMa", g_libSceAudiodecCpuDtsHdMa_modules, 1}, + {0x8000002e, -1, 0, 2, "libSceAudiodecCpuLpcm", g_libSceAudiodecCpuLpcm_modules, 1}, + {0xc1, -1, 0, 1, "libSceBemp2sys", g_libSceBemp2sys_modules, 1}, + {0xc2, -1, 0, 1, "libSceBeisobmf", g_libSceBeisobmf_modules, 1}, + {0xc3, -1, 0, 1, "libScePlayReady", g_libScePlayReady_modules, 1}, + {0xc4, -1, 0, 1, "libSceVideoNativeExtEssential", g_libSceVideoNativeExtEssential_modules, 1}, + {0xc5, -1, 0, 1, "libSceZlib", g_libSceZlib_modules, 1}, + {0x8000002f, -1, 0, 1, "libSceIduUtil", g_libSceIduUtil_modules, 1}, + {0x80000030, -1, 0, 1, "libScePsm", g_libScePsm_modules, 1}, + {0xc6, -1, 0, 1, "libSceDtcpIp", g_libSceDtcpIp_modules, 1}, + {0x80000031, -1, 0, 1, "libSceKbEmulate", g_libSceKbEmulate_modules, 1}, + {0x80000032, -1, 0, 2, "libSceAppChecker", g_libSceAppChecker_modules, 1}, + {0x80000033, -1, 0, 1, "libSceNpGriefReport", g_libSceNpGriefReport_modules, 1}, + {0xc7, -1, 0, 1, "libSceContentSearch", g_libSceContentSearch_modules, 1}, + {0xc8, -1, 0, 1, "libSceShareUtility", g_libSceShareUtility_modules, 1}, + {0x80000034, -1, 0, 1, "libSceWeb", g_libSceWeb_modules, 6}, + {0x8000006a, -1, 0, 1, "libSceWebKit2", g_libSceWebKit2_modules, 30}, + {0xca, -1, 0, 9, "libSceDeci4h", g_libSceDeci4h_debug_modules, 1}, + {0xcb, -1, 0, 4, "libSceHeadTracker", g_libSceHeadTracker_modules, 1}, + {0xcc, -1, 0, 1, "libSceGameUpdate", g_libSceGameUpdate_modules, 2}, + {0xcd, -1, 0, 1, "libSceAutoMounterClient", g_libSceAutoMounterClient_modules, 2}, + {0xce, -1, 0, 1, "libSceSystemGesture", g_libSceSystemGesture_modules, 1}, + {0x80000035, -1, 0, 1, "libSceVdecSavc", g_libSceVdecSavc_modules, 1}, + {0x80000036, -1, 0, 1, "libSceVdecSavc2", g_libSceVdecSavc2_modules, 1}, + {0xcf, -1, 0, 2049, "libSceVideodec2", g_libSceVideodec2_modules, 3}, + {0xd0, -1, 0, 1, "libSceVdecwrap", g_libSceVdecwrap_modules, 2}, + {0x80000037, -1, 0, 1, "libSceVshctl", g_libSceVshctl_modules, 1}, + {0xd1, -1, 0, 1, "libSceAt9Enc", g_libSceAt9Enc_modules, 1}, + {0xd2, -1, 0, 1, "libSceConvertKeycode", g_libSceConvertKeycode_modules, 1}, + {0x80000039, -1, 0, 1, "libSceGpuException", g_libSceGpuException_modules, 1}, + {0xd3, -1, 0, 1, "libSceSharePlay", g_libSceSharePlay_modules, 1}, + {0x8000003a, -1, 0, 2, "libSceAudiodReport", g_libSceAudiodReport_modules, 1}, + {0x8000003b, -1, 0, 2, "libSceSulphaDrv", g_libSceSulphaDrv_modules, 1}, + {0xd4, -1, 0, 1, "libSceHmd", g_libSceHmd_modules, 1}, + {0xd5, -1, 0, 1, "libSceUsbStorage", g_libSceUsbStorage_modules, 2}, + {0x8000003c, -1, 0, 1, "libSceVdecShevc", g_libSceVdecShevc_modules, 1}, + {0xd6, -1, 0, 1, "libSceUsbStorageDialog", g_libSceUsbStorageDialog_modules, 1}, + {0xd8, -1, 0, 4, "libSceFaceTracker", g_libSceFaceTracker_modules, 2}, + {0xd9, -1, 0, 4, "libSceHandTracker", g_libSceHandTracker_modules, 1}, + {0xda, -1, 0, 1, "libSceNpSnsYouTubeDialog", g_libSceNpSnsYouTubeDialog_modules, 2}, + {0xed, -1, 0, 1, "libSceVrTracker", g_libSceVrTracker_modules, 6}, + {0xdc, -1, 0, 1, "libSceProfileCacheExternal", g_libSceProfileCacheExternal_modules, 2}, + {0x8000003f, -1, 0, 1, "libSceBackupRestoreUtil", g_libSceBackupRestoreUtil_modules, 1}, + {0xdd, -1, 0, 1, "libSceMusicPlayerService", g_libSceMusicPlayerService_modules, 2}, + {0x0, -1, 0, 1, "libSceMusicCoreServerClientJsEx", g_libSceMusicCoreServerClientJsEx_modules, + 1}, + {0xde, -1, 0, 1, "libSceSpSysCallWrapper", g_libSceSpSysCallWrapper_modules, 3}, + {0xdf, -1, 0, 1, "libScePs2EmuMenuDialog", g_libScePs2EmuMenuDialog_modules, 1}, + {0xe0, -1, 0, 1, "libSceNpSnsDailyMotionDialog", g_libSceNpSnsDailyMotionDialog_modules, 1}, + {0xe1, -1, 0, 1, "libSceAudiodecCpuHevag", g_libSceAudiodecCpuHevag_modules, 1}, + {0xe2, -1, 0, 1, "libSceLoginDialog", g_libSceLoginDialog_modules, 2}, + {0xe3, -1, 0, 1, "libSceLoginService", g_libSceLoginService_modules, 2}, + {0xe4, -1, 0, 1, "libSceSigninDialog", g_libSceSigninDialog_modules, 2}, + {0xe5, -1, 0, 1, "libSceVdecsw", g_libSceVdecsw_modules, 3}, + {0x8000006d, -1, 0, 1, "libSceOrbisCompat", g_libSceOrbisCompat_modules, 24}, + {0x0, -1, 0, 1, "libSceCoreIPC", g_libSceCoreIPC_modules, 1}, + {0xe6, -1, 0, 1, "libSceCustomMusicCore", g_libSceCustomMusicCore_modules, 12}, + {0xe7, -1, 0, 1, "libSceJson2", g_libSceJson2_modules, 1}, + {0xe8, -1, 0, 4, "libSceAudioLatencyEstimation", g_libSceAudioLatencyEstimation_modules, 1}, + {0xe9, -1, 0, 1, "libSceWkFontConfig", g_libSceWkFontConfig_modules, 1}, + {0xea, -1, 0, 2, "libSceVorbisDec", g_libSceVorbisDec_modules, 3}, + {0x80000041, -1, 0, 1, "libSceTtsCoreEnUs", g_libSceTtsCoreEnUs_modules, 1}, + {0x80000042, -1, 0, 1, "libSceTtsCoreJp", g_libSceTtsCoreJp_modules, 1}, + {0x80000043, -1, 0, 1, "libSceOpusCeltEnc", g_libSceOpusCeltEnc_modules, 2}, + {0x80000044, -1, 0, 1, "libSceOpusCeltDec", g_libSceOpusCeltDec_modules, 2}, + {0x80000045, -1, 0, 2, "libSceLoginMgrServer", g_libSceLoginMgrServer_modules, 1}, + {0xeb, -1, 0, 1, "libSceHmdSetupDialog", g_libSceHmdSetupDialog_modules, 1}, + {0x80000046, -1, 0, 1, "libSceVideoOutSecondary", g_libSceVideoOutSecondary_modules, 6}, + {0xee, -1, 0, 1, "libSceContentDelete", g_libSceContentDelete_modules, 1}, + {0xef, -1, 0, 1, "libSceImeBackend", g_libSceImeBackend_modules, 1}, + {0xf0, -1, 0, 1, "libSceNetCtlApDialog", g_libSceNetCtlApDialog_modules, 1}, + {0x80000047, -1, 0, 1, "libSceGnmResourceRegistration", + g_libSceGnmResourceRegistration_modules, 1}, + {0xf1, -1, 0, 1, "libScePlayGoDialog", g_libScePlayGoDialog_modules, 1}, + {0xf2, -1, 0, 1, "libSceSocialScreen", g_libSceSocialScreen_modules, 7}, + {0xf3, -1, 0, 1, "libSceEditMp4", g_libSceEditMp4_modules, 1}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0x0, -1, 0, 0, nullptr, nullptr, 0}, + {0xf5, -1, 0, 1, "libScePsmKitSystem", g_libScePsmKitSystem_modules, 1}, + {0xf6, -1, 0, 1, "libSceTextToSpeech", g_libSceTextToSpeech_modules, 1}, + {0xf7, -1, 0, 2052, "libSceNpToolkit", g_libSceNpToolkit_modules, 1}, + {0xf8, -1, 0, 1, "libSceCustomMusicService", g_libSceCustomMusicService_modules, 2}, + {0xf9, -1, 0, 1, "libSceClSysCallWrapper", g_libSceClSysCallWrapper_modules, 11}, + {0x80000049, -1, 0, 1, "libSceScm", g_libSceScm_modules, 1}, + {0xfa, -1, 0, 1, "libSceSystemLogger", g_libSceSystemLogger_modules, 2}, + {0xfb, -1, 0, 1, "libSceBluetoothHid", g_libSceBluetoothHid_modules, 1}, + {0x80000050, -1, 0, 1, "libSceAvPlayerStreaming", g_libSceAvPlayerStreaming_modules, 1}, + {0x80000051, -1, 0, 2, "libSceAudiodecCpuAlac", g_libSceAudiodecCpuAlac_modules, 1}, + {0xfc, -1, 0, 1, "libSceVideoDecoderArbitration", g_libSceVideoDecoderArbitration_modules, 1}, + {0xfd, -1, 0, 1, "libSceVrServiceDialog", g_libSceVrServiceDialog_modules, 1}, + {0xfe, -1, 0, 4, "libSceJobManager", g_libSceJobManager_modules, 2}, + {0x80000053, -1, 0, 2, "libSceAudiodecCpuFlac", g_libSceAudiodecCpuFlac_modules, 1}, + {0x103, -1, 0, 1, "libSceSrcUtl", g_libSceSrcUtl_modules, 2}, + {0x80000055, -1, 0, 2, "libSceS3da", g_libSceS3da_modules, 1}, + {0x80000056, -1, 0, 2, "libSceDseehx", g_libSceDseehx_modules, 1}, + {0xff, -1, 0, 1, "libSceShareFactoryUtil", g_libSceShareFactoryUtil_modules, 1}, + {0x80000057, -1, 0, 1, "libSceDataTransfer", g_libSceDataTransfer_modules, 1}, + {0x100, -1, 0, 1, "libSceSocialScreenDialog", g_libSceSocialScreenDialog_modules, 1}, + {0x80000058, -1, 0, 1, "libSceAbstractStorage", g_libSceAbstractStorage_modules, 1}, + {0x80000059, -1, 0, 1, "libSceImageUtil", g_libSceImageUtil_modules, 1}, + {0x8000005a, -1, 0, 1, "libSceMetadataReaderWriter", g_libSceMetadataReaderWriter_modules, 1}, + {0x8000005b, -1, 0, 1, "libSceJpegParser", g_libSceJpegParser_modules, 1}, + {0x8000005c, -1, 0, 1, "libSceGvMp4Parser", g_libSceGvMp4Parser_modules, 1}, + {0x8000005d, -1, 0, 1, "libScePngParser", g_libScePngParser_modules, 1}, + {0x8000005e, -1, 0, 1, "libSceGifParser", g_libSceGifParser_modules, 1}, + {0x101, -1, 0, 1, "libSceNpSnsDialog", g_libSceNpSnsDialog_modules, 2}, + {0x8000005f, -1, 0, 1, "libSceAbstractLocal", g_libSceAbstractLocal_modules, 1}, + {0x80000060, -1, 0, 1, "libSceAbstractFacebook", g_libSceAbstractFacebook_modules, 1}, + {0x80000061, -1, 0, 1, "libSceAbstractYoutube", g_libSceAbstractYoutube_modules, 1}, + {0x80000062, -1, 0, 1, "libSceAbstractTwitter", g_libSceAbstractTwitter_modules, 1}, + {0x80000063, -1, 0, 1, "libSceAbstractDailymotion", g_libSceAbstractDailymotion_modules, 1}, + {0x102, -1, 0, 2052, "libSceNpToolkit2", g_libSceNpToolkit2_modules, 1}, + {0x80000064, -1, 0, 1, "libScePrecompiledShaders", g_libScePrecompiledShaders_modules, 1}, + {0x104, -1, 0, 1, "libSceDiscId", g_libSceDiscId_modules, 1}, + {0x80000065, -1, 0, 1, "libSceLibreSsl", g_libSceLibreSsl_modules, 2}, + {0x80000066, -1, 0, 2, "libSceFsInternalForVsh", g_libSceFsInternalForVsh_modules, 1}, + {0x105, -1, 0, 1, "libSceNpUniversalDataSystem", g_libSceNpUniversalDataSystem_modules, 1}, + {0x80000067, -1, 0, 1, "libSceDolbyVision", g_libSceDolbyVision_modules, 1}, + {0x80000068, -1, 0, 1, "libSceOpusSilkEnc", g_libSceOpusSilkEnc_modules, 2}, + {0x80000069, -1, 0, 1, "libSceOpusDec", g_libSceOpusDec_modules, 2}, + {0x8000006b, -1, 0, 1, "libSceWebKit2Secure", g_libSceWebKit2Secure_modules, 34}, + {0x8000006c, -1, 0, 1, "libSceJscCompiler", g_libSceJscCompiler_modules, 1}, + {0x8000006e, -1, 0, 1, "libSceJitBridge", g_libSceJitBridge_modules, 4}, + {0x0, -1, 0, 1, "libScePigletv2VSH", g_libScePigletv2VSH_modules, 4}, + {0x8000006f, -1, 0, 4096, "libSceJitBridge", g_libSceJitBridge_common_ex_modules, 4}, + {0x80000070, -1, 0, 4096, "libSceJscCompiler", g_libSceJscCompiler_common_ex_modules, 1}, + {0x80000071, -1, 0, 4096, "libSceOrbisCompat", g_libSceOrbisCompat_common_ex_modules, 24}, + {0x80000072, -1, 0, 4096, "libSceWeb", g_libSceWeb_common_ex_modules, 6}, + {0x80000073, -1, 0, 4096, "libSceWebKit2", g_libSceWebKit2_common_ex_modules, 30}, + {0x80000074, -1, 0, 4096, "libSceWebKit2Secure", g_libSceWebKit2Secure_common_ex_modules, 34}, + {0x0, -1, 0, 4096, "libScePrecompiledShaders", g_libScePrecompiledShaders_common_ex_modules, + 1}, + {0x107, -1, 0, 1, "libSceGic", g_libSceGic_modules, 1}, + {0x80000076, -1, 0, 1, "libSceRnpsAppMgr", g_libSceRnpsAppMgr_modules, 1}, + {0x80000077, -1, 0, 1, "libSceAsyncStorageInternal", g_libSceAsyncStorageInternal_modules, 1}, + {0x80000078, -1, 0, 1, "libSceHttpCache", g_libSceHttpCache_modules, 1}, + {0x108, -1, 0, 1, "libScePlayReady2", g_libScePlayReady2_modules, 1}, + {0x109, -1, 0, 9, "libSceHdrScopes", g_libSceHdrScopes_debug_modules, 1}, + {0x80000079, -1, 0, 1, "libSceNKWeb", g_libSceNKWeb_modules, 1}, + {0x8000007a, -1, 0, 1, "libSceNKWebKit", g_libSceNKWebKit_modules, 2}, + {0x0, -1, 0, 1, "libSceNKWebKitRequirements", g_libSceNKWebKitRequirements_modules, 1}, + {0x8000007c, -1, 0, 1, "libSceVnaInternal", g_libSceVnaInternal_modules, 1}, + {0x8000007d, -1, 0, 1, "libSceVnaWebsocket", g_libSceVnaWebsocket_modules, 1}, + {0x10c, -1, 0, 1, "libSceCesCs", g_libSceCesCs_modules, 1}, + {0x8000008a, -1, 0, 2, "libSceComposite", g_libSceComposite_modules, 1}, + {0x8000008b, -1, 0, 1, "libSceCompositeExt", g_libSceCompositeExt_modules, 1}, + {0x116, -1, 0, 1, "libSceHubAppUtil", g_libSceHubAppUtil_modules, 1}, + {0x80000098, -1, 0, 1, "libScePosixForWebKit", g_libScePosixForWebKit_modules, 1}, + {0x11a, -1, 0, 1, "libSceNpPartner001", g_libSceNpPartner001_modules, 1}, + {0x112, -1, 0, 1, "libSceNpSessionSignaling", g_libSceNpSessionSignaling_modules, 75}, + {0x10d, -1, 0, 1, "libScePlayerInvitationDialog", g_libScePlayerInvitationDialog_modules, 1}, + {0x115, -1, 0, 4, "libSceNpCppWebApi", g_libSceNpCppWebApi_modules, 42}, + {0x113, -1, 0, 1, "libSceNpEntitlementAccess", g_libSceNpEntitlementAccess_modules, 1}, + {0x8000009a, -1, 0, 2, "libSceNpRemotePlaySessionSignaling", + g_libSceNpRemotePlaySessionSignaling_modules, 76}, + {0x800000b8, -1, 0, 1, "libSceLibreSsl3", g_libSceLibreSsl3_modules, 2}, + {0x800000b1, -1, 0, 1, "libcurl", g_libcurl_modules, 2}, + {0x800000aa, -1, 0, 1, "libicu", g_libicu_modules, 2}, + {0x800000ac, -1, 0, 1, "libcairo", g_libcairo_modules, 9}, + {0x0, -1, 0, 1, "libfontconfig", g_libfontconfig_modules, 1}, + {0x0, -1, 0, 1, "libfreetype", g_libfreetype_modules, 1}, + {0x0, -1, 0, 1, "libharfbuzz", g_libharfbuzz_modules, 1}, + {0x800000ab, -1, 0, 1, "libpng16", g_libpng16_modules, 2}, + {0x12f, -1, 0, 1, "libSceFontGs", g_libSceFontGs_modules, 1}, + {0x800000c0, -1, 0, 1, "libSceGLSlimClientVSH", g_libSceGLSlimClientVSH_modules, 1}, + {0x800000c1, -1, 0, 1, "libSceGLSlimServerVSH", g_libSceGLSlimServerVSH_modules, 1}, + {0x135, -1, 0, 4, "libSceFontGsm", g_libSceFontGsm_modules, 1}, + {0x138, -1, 0, 1, "libSceNpPartnerSubscription", g_libSceNpPartnerSubscription_modules, 1}, + {0x139, -1, 0, 1, "libSceNpAuthAuthorizedAppDialog", g_libSceNpAuthAuthorizedAppDialog_modules, + 1}}); + +} // namespace Libraries::SysModule diff --git a/src/core/libraries/system/sysmodule.cpp b/src/core/libraries/system/sysmodule.cpp deleted file mode 100644 index 50d030065..000000000 --- a/src/core/libraries/system/sysmodule.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#define MAGIC_ENUM_RANGE_MIN 0 -#define MAGIC_ENUM_RANGE_MAX 300 -#include - -#include "common/logging/log.h" -#include "core/libraries/error_codes.h" -#include "core/libraries/kernel/process.h" -#include "core/libraries/libs.h" -#include "core/libraries/system/sysmodule.h" -#include "core/libraries/system/system_error.h" - -namespace Libraries::SysModule { - -int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, - Kernel::OrbisModuleInfoForUnwind* info) { - LOG_TRACE(Lib_SysModule, "sceSysmoduleGetModuleInfoForUnwind(addr=0x{:X}, flags=0x{:X})", addr, - flags); - - s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, info); - if (res != 0) { - return res; - } - - static constexpr std::array modules_to_hide = { - "libc.prx", - "libc.sprx", - "libSceAudioLatencyEstimation.prx", - "libSceFace.prx", - "libSceFaceTracker.prx", - "libSceFios2.prx", - "libSceFios2.sprx", - "libSceFontGsm.prx", - "libSceHand.prx", - "libSceHandTracker.prx", - "libSceHeadTracker.prx", - "libSceJobManager.prx", - "libSceNpCppWebApi.prx", - "libSceNpToolkit.prx", - "libSceNpToolkit2.prx", - "libSceS3DConversion.prx", - "libSceSmart.prx", - }; - - const std::string_view module_name = info->name.data(); - if (std::ranges::find(modules_to_hide, module_name) != modules_to_hide.end()) { - std::ranges::fill(info->name, '\0'); - } - return res; -} - -int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id) { - LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {}", magic_enum::enum_name(id)); - if (static_cast(id) == 0) { - LOG_ERROR(Lib_SysModule, "Invalid sysmodule ID: {:#x}", static_cast(id)); - return ORBIS_SYSMODULE_INVALID_ID; - } - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id) { - LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {:#x}", static_cast(id)); - if ((static_cast(id) & 0x7FFFFFFF) == 0) { - LOG_ERROR(Lib_SysModule, "Invalid internal sysmodule ID: {:#x}", static_cast(id)); - return ORBIS_SYSMODULE_INVALID_ID; - } - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id) { - LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {}", magic_enum::enum_name(id)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleLoadModuleInternal() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleUnloadModule() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg() { - LOG_ERROR(Lib_SysModule, "(STUBBED) called"); - return ORBIS_OK; -} - -void RegisterLib(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("D8cuU4d72xM", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleGetModuleHandleInternal); - LIB_FUNCTION("4fU5yvOkVG4", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleGetModuleInfoForUnwind); - LIB_FUNCTION("ctfO7dQ7geg", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleIsCalledFromSysModule); - LIB_FUNCTION("no6T3EfiS3E", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleIsCameraPreloaded); - LIB_FUNCTION("fMP5NHUOaMk", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleIsLoaded); - LIB_FUNCTION("ynFKQ5bfGks", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleIsLoadedInternal); - LIB_FUNCTION("g8cM39EUZ6o", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleLoadModule); - LIB_FUNCTION("CU8m+Qs+HN4", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleLoadModuleByNameInternal); - LIB_FUNCTION("39iV5E1HoCk", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleLoadModuleInternal); - LIB_FUNCTION("hHrGoGoNf+s", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleLoadModuleInternalWithArg); - LIB_FUNCTION("lZ6RvVl0vo0", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleMapLibcForLibkernel); - LIB_FUNCTION("DOO+zuW1lrE", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmodulePreloadModuleForLibkernel); - LIB_FUNCTION("eR2bZFAAU0Q", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleUnloadModule); - LIB_FUNCTION("vpTHmA6Knvg", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleUnloadModuleByNameInternal); - LIB_FUNCTION("vXZhrtJxkGc", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleUnloadModuleInternal); - LIB_FUNCTION("aKa6YfBKZs4", "libSceSysmodule", 1, "libSceSysmodule", - sceSysmoduleUnloadModuleInternalWithArg); -}; - -} // namespace Libraries::SysModule diff --git a/src/core/libraries/system/sysmodule.h b/src/core/libraries/system/sysmodule.h deleted file mode 100644 index 3f1328e4b..000000000 --- a/src/core/libraries/system/sysmodule.h +++ /dev/null @@ -1,194 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" -#include "core/libraries/kernel/process.h" - -namespace Core::Loader { -class SymbolsResolver; -} - -namespace Libraries::SysModule { - -enum class OrbisSysModule : u16 { - ORBIS_SYSMODULE_INVALID = 0x0000, - ORBIS_SYSMODULE_FIBER = 0x0006, // libSceFiber.sprx - ORBIS_SYSMODULE_ULT = 0x0007, // libSceUlt.sprx - ORBIS_SYSMODULE_NGS2 = 0x000B, // libSceNgs2.sprx - ORBIS_SYSMODULE_XML = 0x0017, // libSceXml.sprx - ORBIS_SYSMODULE_NP_UTILITY = 0x0019, // libSceNpUtility.sprx - ORBIS_SYSMODULE_VOICE = 0x001A, // libSceVoice.sprx - ORBIS_SYSMODULE_VOICEQOS = 0x001B, // libSceVoiceQos.sprx - ORBIS_SYSMODULE_NP_MATCHING2 = 0x001C, // libSceNpMatching2.sprx - ORBIS_SYSMODULE_NP_SCORE_RANKING = 0x001E, // libSceNpScoreRanking.sprx - ORBIS_SYSMODULE_RUDP = 0x0021, // libSceRudp.sprx - ORBIS_SYSMODULE_NP_TUS = 0x002C, // libSceNpTus.sprx - ORBIS_SYSMODULE_FACE = 0x0038, // libSceFace.sprx - ORBIS_SYSMODULE_SMART = 0x0039, // libSceSmart.sprx - ORBIS_SYSMODULE_JSON = 0x0080, // libSceJson.sprx - ORBIS_SYSMODULE_GAME_LIVE_STREAMING = 0x0081, // libSceGameLiveStreaming.sprx - ORBIS_SYSMODULE_COMPANION_UTIL = 0x0082, // libSceCompanionUtil.sprx - ORBIS_SYSMODULE_PLAYGO = 0x0083, // libScePlayGo.sprx - ORBIS_SYSMODULE_FONT = 0x0084, // libSceFont.sprx - ORBIS_SYSMODULE_VIDEO_RECORDING = 0x0085, // libSceVideoRecording.sprx - ORBIS_SYSMODULE_S3DCONVERSION = 0x0086, // libSceS3DConversion - ORBIS_SYSMODULE_AUDIODEC = 0x0088, // libSceAudiodec.sprx - ORBIS_SYSMODULE_JPEG_DEC = 0x008A, // libSceJpegDec.sprx - ORBIS_SYSMODULE_JPEG_ENC = 0x008B, // libSceJpegEnc.sprx - ORBIS_SYSMODULE_PNG_DEC = 0x008C, // libScePngDec.sprx - ORBIS_SYSMODULE_PNG_ENC = 0x008D, // libScePngEnc.sprx - ORBIS_SYSMODULE_VIDEODEC = 0x008E, // libSceVideodec.sprx - ORBIS_SYSMODULE_MOVE = 0x008F, // libSceMove.sprx - ORBIS_SYSMODULE_PAD_TRACKER = 0x0091, // libScePadTracker.sprx - ORBIS_SYSMODULE_DEPTH = 0x0092, // libSceDepth.sprx - ORBIS_SYSMODULE_HAND = 0x0093, // libSceHand.sprx - ORBIS_SYSMODULE_LIBIME = 0x0095, // libSceIme.sprx - ORBIS_SYSMODULE_IME_DIALOG = 0x0096, // libSceImeDialog.sprx - ORBIS_SYSMODULE_NP_PARTY = 0x0097, // libSceNpParty.sprx - ORBIS_SYSMODULE_FONT_FT = 0x0098, // libSceFontFt.sprx - ORBIS_SYSMODULE_FREETYPE_OT = 0x0099, // libSceFreeTypeOt.sprx - ORBIS_SYSMODULE_FREETYPE_OL = 0x009A, // libSceFreeTypeOl.sprx - ORBIS_SYSMODULE_FREETYPE_OPT_OL = 0x009B, // libSceFreeTypeOptOl.sprx - ORBIS_SYSMODULE_SCREEN_SHOT = 0x009C, // libSceScreenShot.sprx - ORBIS_SYSMODULE_NP_AUTH = 0x009D, // libSceNpAuth.sprx - ORBIS_SYSMODULE_SULPHA = 0x009F, - ORBIS_SYSMODULE_SAVE_DATA_DIALOG = 0x00A0, // libSceSaveDataDialog.sprx - ORBIS_SYSMODULE_INVITATION_DIALOG = 0x00A2, // libSceInvitationDialog.sprx - ORBIS_SYSMODULE_DEBUG_KEYBOARD = 0x00A3, - ORBIS_SYSMODULE_MESSAGE_DIALOG = 0x00A4, // libSceMsgDialog.sprx - ORBIS_SYSMODULE_AV_PLAYER = 0x00A5, // libSceAvPlayer.sprx - ORBIS_SYSMODULE_CONTENT_EXPORT = 0x00A6, // libSceContentExport.sprx - ORBIS_SYSMODULE_AUDIO_3D = 0x00A7, // libSceAudio3d.sprx - ORBIS_SYSMODULE_NP_COMMERCE = 0x00A8, // libSceNpCommerce.sprx - ORBIS_SYSMODULE_MOUSE = 0x00A9, // libSceMouse.sprx - ORBIS_SYSMODULE_COMPANION_HTTPD = 0x00AA, // libSceCompanionHttpd.sprx - ORBIS_SYSMODULE_WEB_BROWSER_DIALOG = 0x00AB, // libSceWebBrowserDialog.sprx - ORBIS_SYSMODULE_ERROR_DIALOG = 0x00AC, // libSceErrorDialog.sprx - ORBIS_SYSMODULE_NP_TROPHY = 0x00AD, // libSceNpTrophy.sprx - ORBIS_SYSMODULE_VIDEO_CORE_IF = 0x00AE, // libSceVideoCoreInterface.sprx - ORBIS_SYSMODULE_VIDEO_CORE_SERVER_IF = 0x00AF, // libSceVideoCoreServerInterface.sprx - ORBIS_SYSMODULE_NP_SNS_FACEBOOK = 0x00B0, // libSceNpSnsFacebookDialog.sprx - ORBIS_SYSMODULE_MOVE_TRACKER = 0x00B1, // libSceMoveTracker.sprx - ORBIS_SYSMODULE_NP_PROFILE_DIALOG = 0x00B2, // libSceNpProfileDialog.sprx - ORBIS_SYSMODULE_NP_FRIEND_LIST_DIALOG = 0x00B3, // libSceNpFriendListDialog.sprx - ORBIS_SYSMODULE_APP_CONTENT = 0x00B4, // libSceAppContent.sprx - ORBIS_SYSMODULE_NP_SIGNALING = 0x00B5, // libSceNpSignaling.sprx - ORBIS_SYSMODULE_REMOTE_PLAY = 0x00B6, // libSceRemoteplay.sprx - ORBIS_SYSMODULE_USBD = 0x00B7, // libSceUsbd.sprx - ORBIS_SYSMODULE_GAME_CUSTOM_DATA_DIALOG = 0x00B8, // libSceGameCustomDataDialog.sprx - ORBIS_SYSMODULE_NP_EULA_DIALOG = 0x00B9, // libSceNpEulaDialog.sprx - ORBIS_SYSMODULE_RANDOM = 0x00BA, // libSceRandom.sprx - ORBIS_SYSMODULE_RESERVED2 = 0x00BB, - ORBIS_SYSMODULE_M4AAC_ENC = 0x00BC, // libSceM4aacEnc.sprx - ORBIS_SYSMODULE_AUDIODEC_CPU = 0x00BD, // libSceAudiodecCpu.sprx - ORBIS_SYSMODULE_AUDIODEC_CPU_DDP = 0x00BE, // libSceAudiodecCpuDdp.sprx - ORBIS_SYSMODULE_AUDIODEC_CPU_M4AAC = 0x00C0, // libSceAudiodecCpuM4aac.sprx - ORBIS_SYSMODULE_BEMP2_SYS = 0x00C1, // libSceBemp2sys.sprx - ORBIS_SYSMODULE_BEISOBMF = 0x00C2, // libSceBeisobmf.sprx - ORBIS_SYSMODULE_PLAY_READY = 0x00C3, // libScePlayReady.sprx - ORBIS_SYSMODULE_VIDEO_NATIVE_EXT_ESSENTIAL = 0x00C4, // libSceVideoNativeExtEssential.sprx - ORBIS_SYSMODULE_ZLIB = 0x00C5, // libSceZlib.sprx - ORBIS_SYSMODULE_DTCP_IP = 0x00C6, // libSceDtcpIp.sprx - ORBIS_SYSMODULE_CONTENT_SEARCH = 0x00C7, // libSceContentSearch.sprx - ORBIS_SYSMODULE_SHARE_UTILITY = 0x00C8, // libSceShareUtility.sprx - ORBIS_SYSMODULE_AUDIODEC_CPU_DTS_HD_LBR = 0x00C9, // libSceAudiodecCpuDtsHdLbr.sprx - ORBIS_SYSMODULE_DECI4H = 0x00CA, - ORBIS_SYSMODULE_HEAD_TRACKER = 0x00CB, // libSceHeadTracker.sprx - ORBIS_SYSMODULE_GAME_UPDATE = 0x00CC, // libSceGameUpdate.sprx - ORBIS_SYSMODULE_AUTO_MOUNTER_CLIENT = 0x00CD, // libSceAutoMounterClient.sprx - ORBIS_SYSMODULE_SYSTEM_GESTURE = 0x00CE, // libSceSystemGesture.sprx - ORBIS_SYSMODULE_VIDEODEC2 = 0x00CF, // libSceVideodec2.sprx - ORBIS_SYSMODULE_VDECWRAP = 0x00D0, // libSceVdecwrap.sprx - ORBIS_SYSMODULE_AT9_ENC = 0x00D1, // libSceAt9Enc.sprx - ORBIS_SYSMODULE_CONVERT_KEYCODE = 0x00D2, // libSceConvertKeycode.sprx - ORBIS_SYSMODULE_SHARE_PLAY = 0x00D3, // libSceSharePlay.sprx - ORBIS_SYSMODULE_HMD = 0x00D4, // libSceHmd.sprx - ORBIS_SYSMODULE_USB_STORAGE = 0x00D5, // libSceUsbStorage.sprx - ORBIS_SYSMODULE_USB_STORAGE_DIALOG = 0x00D6, // libSceUsbStorageDialog.sprx - ORBIS_SYSMODULE_DISC_MAP = 0x00D7, // libSceDiscMap.sprx - ORBIS_SYSMODULE_FACE_TRACKER = 0x00D8, // libSceFaceTracker.sprx - ORBIS_SYSMODULE_HAND_TRACKER = 0x00D9, // libSceHandTracker.sprx - ORBIS_SYSMODULE_NP_SNS_YOUTUBE_DIALOG = 0x00DA, // libSceNpSnsYouTubeDialog.sprx - ORBIS_SYSMODULE_PROFILE_CACHE_EXTERNAL = 0x00DC, // libSceProfileCacheExternal.sprx - ORBIS_SYSMODULE_MUSIC_PLAYER_SERVICE = 0x00DD, // libSceMusicPlayerService.sprx - ORBIS_SYSMODULE_SP_SYS_CALL_WRAPPER = 0x00DE, // libSceSpSysCallWrapper.sprx - ORBIS_SYSMODULE_PS2_EMU_MENU_DIALOG = 0x00DF, // libScePs2EmuMenuDialog.sprx - ORBIS_SYSMODULE_NP_SNS_DAILYMOTION_DIALOG = 0x00E0, // libSceNpSnsDailyMotionDialog.sprx - ORBIS_SYSMODULE_AUDIODEC_CPU_HEVAG = 0x00E1, // libSceAudiodecCpuHevag.sprx - ORBIS_SYSMODULE_LOGIN_DIALOG = 0x00E2, // libSceLoginDialog.sprx - ORBIS_SYSMODULE_LOGIN_SERVICE = 0x00E3, // libSceLoginService.sprx - ORBIS_SYSMODULE_SIGNIN_DIALOG = 0x00E4, // libSceSigninDialog.sprx - ORBIS_SYSMODULE_VDECSW = 0x00E5, // libSceVdecsw.sprx - ORBIS_SYSMODULE_CUSTOM_MUSIC_CORE = 0x00E6, // libSceCustomMusicCore.sprx - ORBIS_SYSMODULE_JSON2 = 0x00E7, // libSceJson2.sprx - ORBIS_SYSMODULE_AUDIO_LATENCY_ESTIMATION = 0x00E8, // libSceAudioLatencyEstimation.sprx - ORBIS_SYSMODULE_WK_FONT_CONFIG = 0x00E9, // libSceWkFontConfig.sprx - ORBIS_SYSMODULE_VORBIS_DEC = 0x00EA, // libSceVorbisDec.sprx - ORBIS_SYSMODULE_HMD_SETUP_DIALOG = 0x00EB, // libSceHmdSetupDialog.sprx - ORBIS_SYSMODULE_RESERVED28 = 0x00EC, - ORBIS_SYSMODULE_VR_TRACKER = 0x00ED, // libSceVrTracker.sprx - ORBIS_SYSMODULE_CONTENT_DELETE = 0x00EE, // libSceContentDelete.sprx - ORBIS_SYSMODULE_IME_BACKEND = 0x00EF, // libSceImeBackend.sprx - ORBIS_SYSMODULE_NET_CTL_AP_DIALOG = 0x00F0, // libSceNetCtlApDialog.sprx - ORBIS_SYSMODULE_PLAYGO_DIALOG = 0x00F1, // libScePlayGoDialog.sprx - ORBIS_SYSMODULE_SOCIAL_SCREEN = 0x00F2, // libSceSocialScreen.sprx - ORBIS_SYSMODULE_EDIT_MP4 = 0x00F3, // libSceEditMp4.sprx - ORBIS_SYSMODULE_PSM_KIT_SYSTEM = 0x00F5, // libScePsmKitSystem.sprx - ORBIS_SYSMODULE_TEXT_TO_SPEECH = 0x00F6, // libSceTextToSpeech.sprx - ORBIS_SYSMODULE_NP_TOOLKIT = 0x00F7, // libSceNpToolkit.sprx - ORBIS_SYSMODULE_CUSTOM_MUSIC_SERVICE = 0x00F8, // libSceCustomMusicService.sprx - ORBIS_SYSMODULE_CL_SYS_CALL_WRAPPER = 0x00F9, // libSceClSysCallWrapper.sprx - ORBIS_SYSMODULE_SYSTEM_LOGGER = 0x00FA, // libSceSystemLogger.sprx - ORBIS_SYSMODULE_BLUETOOTH_HID = 0x00FB, // libSceBluetoothHid.sprx - ORBIS_SYSMODULE_VIDEO_DECODER_ARBITRATION = 0x00FC, // libSceVideoDecoderArbitration.sprx - ORBIS_SYSMODULE_VR_SERVICE_DIALOG = 0x00FD, // libSceVrServiceDialog.sprx - ORBIS_SYSMODULE_JOB_MANAGER = 0x00FE, // libSceJobManager.sprx - ORBIS_SYSMODULE_SHARE_FACTORY_UTIL = 0x00FF, // libSceShareFactoryUtil.sprx - ORBIS_SYSMODULE_SOCIAL_SCREEN_DIALOG = 0x0100, // libSceSocialScreenDialog.sprx - ORBIS_SYSMODULE_NP_SNS_DIALOG = 0x0101, // libSceNpSnsDialog.sprx - ORBIS_SYSMODULE_NP_TOOLKIT2 = 0x0102, // libSceNpToolkit2.sprx - ORBIS_SYSMODULE_SRC_UTL = 0x0103, // libSceSrcUtl.sprx - ORBIS_SYSMODULE_DISC_ID = 0x0104, // libSceDiscId.sprx - ORBIS_SYSMODULE_NP_UNIVERSAL_DATA_SYSTEM = 0x0105, // libSceNpUniversalDataSystem.sprx - ORBIS_SYSMODULE_KEYBOARD = 0x0106, // libSceKeyboard.sprx - ORBIS_SYSMODULE_GIC = 0x0107, // libSceGic.sprx - ORBIS_SYSMODULE_PLAY_READY2 = 0x0108, // libScePlayReady2.sprx - ORBIS_SYSMODULE_CES_CS = 0x010c, // libSceCesCs.sprx - ORBIS_SYSMODULE_PLAYER_INVITATION_DIALOG = 0x010d, // libScePlayerInvitationDialog.sprx - ORBIS_SYSMODULE_NP_SESSION_SIGNALING = 0x0112, // libSceNpSessionSignaling.sprx - ORBIS_SYSMODULE_NP_ENTITLEMENT_ACCESS = 0x0113, // libSceNpEntitlementAccess.sprx - ORBIS_SYSMODULE_NP_CPP_WEB_API = 0x0115, // libSceNpCppWebApi.sprx - ORBIS_SYSMODULE_HUB_APP_UTIL = 0x0116, // libSceHubAppUtil.sprx - ORBIS_SYSMODULE_NP_PARTNER001 = 0x011a, // libSceNpPartner001.sprx - ORBIS_SYSMODULE_FONT_GS = 0x012f, // libSceFontGs.sprx - ORBIS_SYSMODULE_FONT_GSM = 0x0135, // libSceFontGsm.sprx - ORBIS_SYSMODULE_NP_PARTNER_SUBSCRIPTION = 0x0138, // libSceNpPartnerSubscription.sprx - ORBIS_SYSMODULE_NP_AUTH_AUTHORIZED_APP_DIALOG = 0x0139, // libSceNpAuthAuthorizedAppDialog.sprx -}; - -enum class OrbisSysModuleInternal : u32 { - ORBIS_SYSMODULE_INTERNAL_RAZOR_CPU = 0x80000019, // libSceRazorCpu.sprx -}; - -int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(); -s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags, - Kernel::OrbisModuleInfoForUnwind* info); -int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule(); -int PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded(); -int PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id); -int PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id); -int PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id); -int PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal(); -int PS4_SYSV_ABI sceSysmoduleLoadModuleInternal(); -int PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg(); -int PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel(); -int PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel(); -int PS4_SYSV_ABI sceSysmoduleUnloadModule(); -int PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal(); -int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal(); -int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg(); - -void RegisterLib(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::SysModule diff --git a/src/core/libraries/system/system_error.h b/src/core/libraries/system/system_error.h deleted file mode 100644 index 615e4cd5f..000000000 --- a/src/core/libraries/system/system_error.h +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -constexpr int ORBIS_SYSMODULE_INVALID_ID = 0x805A1000; -constexpr int ORBIS_SYSMODULE_NOT_LOADED = 0x805A1001; -constexpr int ORBIS_SYSMODULE_LOCK_FAILED = 0x805A10FF; \ No newline at end of file diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 0b80ecacc..d0d682043 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -16,6 +16,7 @@ #include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/threads.h" +#include "core/libraries/sysmodule/sysmodule.h" #include "core/linker.h" #include "core/memory.h" #include "core/tls.h" @@ -110,7 +111,8 @@ void Linker::Execute(const std::vector& args) { ipc.WaitForStart(); } - LoadSharedLibraries(); + // Have libSceSysmodule preload our libraries. + Libraries::SysModule::sceSysmodulePreloadModuleForLibkernel(); // Simulate libSceGnmDriver initialization, which maps a chunk of direct memory. // Some games fail without accurately emulating this behavior. @@ -350,8 +352,8 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul return_info->virtual_address = AeroLib::GetStub(sr.name.c_str()); return_info->name = "Unknown !!!"; } - LOG_ERROR(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name, - return_info->name, library->name, module->name); + LOG_WARNING(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name, + return_info->name, library->name, module->name); return false; } diff --git a/src/emulator.cpp b/src/emulator.cpp index 18d3024dc..cc862f8ab 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -32,17 +32,9 @@ #include "core/file_format/psf.h" #include "core/file_format/trp.h" #include "core/file_sys/fs.h" -#include "core/libraries/disc_map/disc_map.h" -#include "core/libraries/font/font.h" -#include "core/libraries/font/fontft.h" -#include "core/libraries/jpeg/jpegenc.h" #include "core/libraries/kernel/kernel.h" -#include "core/libraries/libc_internal/libc_internal.h" -#include "core/libraries/libpng/pngenc.h" #include "core/libraries/libs.h" -#include "core/libraries/ngs2/ngs2.h" #include "core/libraries/np/np_trophy.h" -#include "core/libraries/rtc/rtc.h" #include "core/libraries/save_data/save_backup.h" #include "core/linker.h" #include "core/memory.h" @@ -405,17 +397,6 @@ void Emulator::Run(std::filesystem::path file, std::vector args, std::quick_exit(0); } - // check if we have system modules to load - LoadSystemModules(game_info.game_serial); - - // Load all prx from game's sce_module folder - mnt->IterateDirectory("/app0/sce_module", [this](const auto& path, const auto is_file) { - if (is_file) { - LOG_INFO(Loader, "Loading {}", fmt::UTF(path.u8string())); - linker->LoadModule(path); - } - }); - #ifdef ENABLE_DISCORD_RPC // Discord RPC if (Config::getEnableDiscordRPC()) { @@ -556,54 +537,6 @@ void Emulator::Restart(std::filesystem::path eboot_path, std::quick_exit(0); } -void Emulator::LoadSystemModules(const std::string& game_serial) { - constexpr auto ModulesToLoad = std::to_array( - {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib}, - {"libSceUlt.sprx", nullptr}, - {"libSceRtc.sprx", &Libraries::Rtc::RegisterLib}, - {"libSceJpegDec.sprx", nullptr}, - {"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib}, - {"libScePngEnc.sprx", &Libraries::PngEnc::RegisterLib}, - {"libSceJson.sprx", nullptr}, - {"libSceJson2.sprx", nullptr}, - {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib}, - {"libSceCesCs.sprx", nullptr}, - {"libSceAudiodec.sprx", nullptr}, - {"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont}, - {"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt}, - {"libSceFreeTypeOt.sprx", nullptr}}); - - std::vector found_modules; - const auto& sys_module_path = Config::getSysModulesPath(); - for (const auto& entry : std::filesystem::directory_iterator(sys_module_path)) { - found_modules.push_back(entry.path()); - } - for (const auto& [module_name, init_func] : ModulesToLoad) { - const auto it = std::ranges::find_if( - found_modules, [&](const auto& path) { return path.filename() == module_name; }); - if (it != found_modules.end()) { - LOG_INFO(Loader, "Loading {}", it->string()); - if (linker->LoadModule(*it) != -1) { - continue; - } - } - if (init_func) { - LOG_INFO(Loader, "Can't Load {} switching to HLE", module_name); - init_func(&linker->GetHLESymbols()); - } else { - LOG_INFO(Loader, "No HLE available for {} module", module_name); - } - } - if (!game_serial.empty() && std::filesystem::exists(sys_module_path / game_serial)) { - for (const auto& entry : - std::filesystem::directory_iterator(sys_module_path / game_serial)) { - LOG_INFO(Loader, "Loading {} from game serial file {}", entry.path().string(), - game_serial); - linker->LoadModule(entry.path()); - } - } -} - void Emulator::UpdatePlayTime(const std::string& serial) { const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const auto filePath = (user_dir / "play_time.txt").string(); From 0579569f139bd1da3d356d529a41b044e0f1eb77 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:20:14 +0100 Subject: [PATCH 19/40] Improve signal emulation (#4108) * improve signal emulation * make the sce function use the new posix ones * ifdefing away the issues * fix me being very tired yesterday night * let macOS handle SIGRT signals with the native sigaction call instead of an early error return * windows still has no clue what the fuck is going on * the loathsome clang-formatter * fix oact * return the guest handler, not the host one * Clear any existing signal mask for game threads. * don't rely on implementation specific things * Fix Windows support and sceKernelRaiseException bug * Review suggestions @kalaposfos13 suggested I push these. --------- Co-authored-by: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> --- src/core/cpu_patches.cpp | 13 +- .../libraries/kernel/threads/exception.cpp | 236 +++++++++++++----- src/core/libraries/kernel/threads/exception.h | 82 +++++- src/core/libraries/kernel/threads/pthread.cpp | 1 + src/core/linker.cpp | 9 + src/core/signals.cpp | 6 +- src/core/signals.h | 4 +- 7 files changed, 277 insertions(+), 74 deletions(-) diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index e303417c3..f6a4f7620 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -788,14 +788,11 @@ static bool PatchesIllegalInstructionHandler(void* context) { ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; const auto status = Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address); - if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_UD2) - [[unlikely]] { - UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast(code_address)); - } - UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}", - reinterpret_cast(code_address), - ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic) - : "Failed to decode"); + LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}", + reinterpret_cast(code_address), + ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic) + : "Failed to decode"); + return false; } } diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 247c387fe..e2fd032f5 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -2,7 +2,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/posix_error.h" #include "core/libraries/kernel/threads/exception.h" #include "core/libraries/kernel/threads/pthread.h" #include "core/libraries/libs.h" @@ -13,23 +15,24 @@ #else #include #endif +#include namespace Libraries::Kernel { #ifdef _WIN32 // Windows doesn't have native versions of these, and we don't need to use them either. -static s32 NativeToOrbisSignal(s32 s) { +s32 NativeToOrbisSignal(s32 s) { return s; } -static s32 OrbisToNativeSignal(s32 s) { +s32 OrbisToNativeSignal(s32 s) { return s; } #else -static s32 NativeToOrbisSignal(s32 s) { +s32 NativeToOrbisSignal(s32 s) { switch (s) { case SIGHUP: return POSIX_SIGHUP; @@ -89,12 +92,21 @@ static s32 NativeToOrbisSignal(s32 s) { return POSIX_SIGUSR1; case SIGUSR2: return POSIX_SIGUSR2; + case _SIGEMT: + return POSIX_SIGEMT; + case _SIGINFO: + return POSIX_SIGINFO; + case 0: + return 128; default: + if (s > 0 && s < 128) { + return s; + } UNREACHABLE_MSG("Unknown signal {}", s); } } -static s32 OrbisToNativeSignal(s32 s) { +s32 OrbisToNativeSignal(s32 s) { switch (s) { case POSIX_SIGHUP: return SIGHUP; @@ -108,6 +120,8 @@ static s32 OrbisToNativeSignal(s32 s) { return SIGTRAP; case POSIX_SIGABRT: return SIGABRT; + case POSIX_SIGEMT: + return _SIGEMT; case POSIX_SIGFPE: return SIGFPE; case POSIX_SIGKILL: @@ -150,22 +164,33 @@ static s32 OrbisToNativeSignal(s32 s) { return SIGPROF; case POSIX_SIGWINCH: return SIGWINCH; + case POSIX_SIGINFO: + return _SIGINFO; case POSIX_SIGUSR1: return SIGUSR1; case POSIX_SIGUSR2: return SIGUSR2; + case 128: + return 0; default: + if (s > 0 && s < 128) { + return s; + } UNREACHABLE_MSG("Unknown signal {}", s); } } #endif -std::array Handlers{}; +#ifdef __APPLE__ +#define sigisemptyset(x) (*(x) == 0) +#endif + +std::array Handlers{}; #ifndef _WIN64 void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context) { - const auto handler = Handlers[native_signum]; + const auto handler = Handlers[NativeToOrbisSignal(native_signum)]; if (handler) { auto ctx = Ucontext{}; #ifdef __APPLE__ @@ -214,6 +239,8 @@ void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context ctx.uc_mcontext.mc_addr = reinterpret_cast(inf->si_addr); #endif handler(NativeToOrbisSignal(native_signum), &ctx); + } else { + UNREACHABLE_MSG("Unhandled exception"); } } #else @@ -221,7 +248,7 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) { const char* thrName = (char*)arg1; int native_signum = reinterpret_cast(arg2); LOG_INFO(Lib_Kernel, "Exception raised successfully on thread '{}'", thrName); - const auto handler = Handlers[native_signum]; + const auto handler = Handlers[NativeToOrbisSignal(native_signum)]; if (handler) { auto ctx = Ucontext{}; ctx.uc_mcontext.mc_r8 = context->R8; @@ -243,76 +270,105 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) { ctx.uc_mcontext.mc_fs = context->SegFs; ctx.uc_mcontext.mc_gs = context->SegGs; handler(NativeToOrbisSignal(native_signum), &ctx); + } else { + UNREACHABLE_MSG("Unhandled exception"); } } #endif -int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, SceKernelExceptionHandler handler) { - if (signum > POSIX_SIGUSR2) { - return ORBIS_KERNEL_ERROR_EINVAL; +s32 PS4_SYSV_ABI posix_sigemptyset(Sigset* s) { + s->bits[0] = 0; + s->bits[1] = 0; + return 0; +} + +bool PS4_SYSV_ABI posix_sigisemptyset(Sigset* s) { + return s->bits[0] == 0 && s->bits[1] == 0; +} + +s32 PS4_SYSV_ABI posix_sigaction(s32 sig, Sigaction* act, Sigaction* oact) { + if (sig < 1 || sig > 128 || sig == POSIX_SIGTHR || sig == POSIX_SIGKILL || + sig == POSIX_SIGSTOP) { + *__Error() = POSIX_EINVAL; + return ORBIS_FAIL; + } +#ifdef _WIN32 + LOG_ERROR(Lib_Kernel, "(STUBBED) called, sig: {}", sig); + Handlers[sig] = reinterpret_cast( + act ? act->__sigaction_handler.sigaction : nullptr); +#else + s32 native_sig = OrbisToNativeSignal(sig); + if (native_sig == SIGVTALRM) { + LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE-reserved signal {}!", sig); + *__Error() = POSIX_EINVAL; + return ORBIS_FAIL; + } +#ifndef __APPLE__ + if (native_sig >= __SIGRTMIN && native_sig < SIGRTMIN) { + LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE libc-reserved signal {}!", sig); + *__Error() = POSIX_EINVAL; + return ORBIS_FAIL; + } +#else + if (native_sig > SIGUSR2) { + LOG_ERROR(Lib_Kernel, + "Guest is attempting to use SIGRT signals, which aren't available on this " + "platform (signal: {})!", + sig); } - LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum); - int const native_signum = OrbisToNativeSignal(signum); -#ifdef __APPLE__ - ASSERT_MSG(native_signum != SIGVTALRM, "SIGVTALRM is HLE-reserved on macOS!"); #endif - ASSERT_MSG(!Handlers[native_signum], "Invalid parameters"); - Handlers[native_signum] = handler; -#ifndef _WIN64 - if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) { + LOG_INFO(Lib_Kernel, "called, sig: {}, native sig: {}", sig, native_sig); + struct sigaction native_act{}; + if (act) { + native_act.sa_flags = act->sa_flags; // todo check compatibility, on Linux it seems fine + native_act.sa_sigaction = + reinterpret_cast(SigactionHandler); + if (!posix_sigisemptyset(&act->sa_mask)) { + LOG_ERROR(Lib_Kernel, "Unhandled sa_mask: {:x}", act->sa_mask.bits[0]); + } + } + auto const prev_handler = Handlers[sig]; + Handlers[sig] = reinterpret_cast( + act ? act->__sigaction_handler.sigaction : nullptr); + + if (native_sig == SIGSEGV || native_sig == SIGBUS || native_sig == SIGILL) { return ORBIS_OK; // These are handled in Core::SignalHandler } - struct sigaction act = {}; - act.sa_flags = SA_SIGINFO | SA_RESTART; - act.sa_sigaction = reinterpret_cast(SigactionHandler); - sigemptyset(&act.sa_mask); - sigaction(native_signum, &act, nullptr); -#endif - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) { - if (signum > POSIX_SIGUSR2) { - return ORBIS_KERNEL_ERROR_EINVAL; + if (native_sig > 127) { + LOG_WARNING(Lib_Kernel, "We can't install a handler for native signal {}!", native_sig); + return ORBIS_OK; } - int const native_signum = OrbisToNativeSignal(signum); - if (!Handlers[native_signum]) { - LOG_WARNING(Lib_Kernel, "removing non-installed handler for signum {}", signum); - return ORBIS_KERNEL_ERROR_EINVAL; + struct sigaction native_oact{}; + s32 ret = sigaction(native_sig, act ? &native_act : nullptr, oact ? &native_oact : nullptr); + if (oact) { + oact->sa_flags = native_oact.sa_flags; + oact->__sigaction_handler.sigaction = + reinterpret_cast__sigaction_handler.sigaction)>(prev_handler); + if (!sigisemptyset(&native_oact.sa_mask)) { + LOG_ERROR(Lib_Kernel, "Unhandled sa_mask"); + } } - Handlers[native_signum] = nullptr; -#ifndef _WIN64 - if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) { - struct sigaction action{}; - action.sa_sigaction = Core::SignalHandler; - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - sigemptyset(&action.sa_mask); - - ASSERT_MSG(sigaction(native_signum, &action, nullptr) == 0, - "Failed to reinstate original signal handler for signal {}", native_signum); - } else { - struct sigaction act = {}; - act.sa_flags = SA_SIGINFO | SA_RESTART; - act.sa_sigaction = nullptr; - sigemptyset(&act.sa_mask); - sigaction(native_signum, &act, nullptr); + if (ret < 0) { + LOG_ERROR(Lib_Kernel, "sigaction failed: {}", strerror(errno)); + *__Error() = ErrnoToSceKernelError(errno); + return ORBIS_FAIL; } #endif return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { - if (signum != POSIX_SIGUSR1) { - return ORBIS_KERNEL_ERROR_EINVAL; +s32 PS4_SYSV_ABI posix_pthread_kill(PthreadT thread, s32 sig) { + if (sig < 1 || sig > 128) { // off-by-one error? + return POSIX_EINVAL; } - LOG_WARNING(Lib_Kernel, "Raising exception on thread '{}'", thread->name); - int const native_signum = OrbisToNativeSignal(signum); + LOG_WARNING(Lib_Kernel, "Raising signal {} on thread '{}'", sig, thread->name); + int const native_signum = OrbisToNativeSignal(sig); #ifndef _WIN64 const auto pthr = reinterpret_cast(thread->native_thr.GetHandle()); const auto ret = pthread_kill(pthr, native_signum); if (ret != 0) { LOG_ERROR(Kernel, "Failed to send exception signal to thread '{}': {}", thread->name, - strerror(ret)); + strerror(errno)); } #else USER_APC_OPTION option; @@ -326,6 +382,67 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { return ORBIS_OK; } +// libkernel has a check in sceKernelInstallExceptionHandler and sceKernelRemoveExceptionHandler for +// validating if the application requested a handler for an allowed signal or not. However, that is +// just a wrapper for sigaction, which itself does not have any such restrictions, and therefore +// this check is ridiculously trivial to go around. This, however, means that we need to support all +// 127 - 3 possible signals, even if realistically, only homebrew will use most of them. +static std::unordered_set orbis_allowed_signals{ + POSIX_SIGHUP, POSIX_SIGILL, POSIX_SIGFPE, POSIX_SIGBUS, POSIX_SIGSEGV, POSIX_SIGUSR1, +}; + +int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, OrbisKernelExceptionHandler handler) { + if (!orbis_allowed_signals.contains(signum)) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + if (Handlers[signum] != nullptr) { + return ORBIS_KERNEL_ERROR_EAGAIN; + } + LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum); + Sigaction act = {}; + act.sa_flags = POSIX_SA_SIGINFO | POSIX_SA_RESTART; + act.__sigaction_handler.sigaction = + reinterpret_cast(handler); + posix_sigemptyset(&act.sa_mask); + s32 ret = posix_sigaction(signum, &act, nullptr); + if (ret < 0) { + LOG_ERROR(Lib_Kernel, "Failed to add handler for signal {}: {}", signum, + strerror(*__Error())); + return ErrnoToSceKernelError(*__Error()); + } + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) { + if (!orbis_allowed_signals.contains(signum)) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + int const native_signum = OrbisToNativeSignal(signum); + Handlers[signum] = nullptr; + Sigaction act = {}; + act.sa_flags = POSIX_SA_SIGINFO; + act.__sigaction_handler.sigaction = nullptr; + posix_sigemptyset(&act.sa_mask); + s32 ret = posix_sigaction(signum, &act, nullptr); + if (ret < 0) { + LOG_ERROR(Lib_Kernel, "Failed to remove handler for signal {}: {}", signum, + strerror(*__Error())); + return ErrnoToSceKernelError(*__Error()); + } + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { + if (signum != POSIX_SIGUSR1) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + s32 ret = posix_pthread_kill(thread, signum); + if (ret < 0) { + return ErrnoToSceKernelError(ret); + } + return ret; +} + s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) { if (unk != 0) { return ORBIS_KERNEL_ERROR_EINVAL; @@ -352,6 +469,13 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) { sceKernelDebugRaiseExceptionOnReleaseMode); LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler); LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler); + + LIB_FUNCTION("KiJEPEWRyUY", "libkernel", 1, "libkernel", posix_sigaction); + LIB_FUNCTION("+F7C-hdk7+E", "libkernel", 1, "libkernel", posix_sigemptyset); + LIB_FUNCTION("yH-uQW3LbX0", "libkernel", 1, "libkernel", posix_pthread_kill); + LIB_FUNCTION("KiJEPEWRyUY", "libScePosix", 1, "libkernel", posix_sigaction); + LIB_FUNCTION("+F7C-hdk7+E", "libScePosix", 1, "libkernel", posix_sigemptyset); + LIB_FUNCTION("yH-uQW3LbX0", "libScePosix", 1, "libkernel", posix_pthread_kill); } } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/threads/exception.h b/src/core/libraries/kernel/threads/exception.h index 42c92ab2e..c07242c1d 100644 --- a/src/core/libraries/kernel/threads/exception.h +++ b/src/core/libraries/kernel/threads/exception.h @@ -11,7 +11,7 @@ class SymbolsResolver; namespace Libraries::Kernel { -using SceKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*); +using OrbisKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*); constexpr s32 POSIX_SIGHUP = 1; constexpr s32 POSIX_SIGINT = 2; @@ -47,6 +47,23 @@ constexpr s32 POSIX_SIGUSR2 = 31; constexpr s32 POSIX_SIGTHR = 32; constexpr s32 POSIX_SIGLIBRT = 33; +#ifdef __linux__ +constexpr s32 _SIGEMT = 128; +constexpr s32 _SIGINFO = 129; +#elif !defined(_WIN32) +constexpr s32 _SIGEMT = SIGEMT; +constexpr s32 _SIGINFO = SIGINFO; +#endif + +constexpr s32 POSIX_SA_NOCLDSTOP = 1; +constexpr s32 POSIX_SA_NOCLDWAIT = 2; +constexpr s32 POSIX_SA_SIGINFO = 4; +constexpr s32 POSIX_SA_ONSTACK = 0x08000000; +constexpr s32 POSIX_SA_RESTART = 0x10000000; +constexpr s32 POSIX_SA_NODEFER = 0x40000000; +constexpr s32 POSIX_SA_RESETHAND = 0x80000000; +constexpr s32 POSIX_SA_RESTORER = 0x04000000; + struct Mcontext { u64 mc_onstack; u64 mc_rdi; @@ -101,17 +118,74 @@ struct Sigset { u64 bits[2]; }; +union Sigval { + /* Members as suggested by Annex C of POSIX 1003.1b. */ + int sival_int; + void* sival_ptr; + /* 6.0 compatibility */ + int sigval_int; + void* sigval_ptr; +}; + +struct Siginfo { + int _si_signo; /* signal number */ + int _si_errno; /* errno association */ + /* + * Cause of signal, one of the SI_ macros or signal-specific + * values, i.e. one of the FPE_... values for SIGFPE. This + * value is equivalent to the second argument to an old-style + * FreeBSD signal handler. + */ + int _si_code; /* signal code */ + s32 _si_pid; /* sending process */ + u32 _si_uid; /* sender's ruid */ + int _si_status; /* exit value */ + void* _si_addr; /* faulting instruction */ + union Sigval _si_value; /* signal value */ + union { + struct { + int _trapno; /* machine specific trap code */ + } _fault; + struct { + int _timerid; + int _overrun; + } _timer; + struct { + int _mqd; + } _mesgq; + struct { + long _band; /* band event for SIGPOLL */ + } _poll; /* was this ever used ? */ + struct { + long __spare1__; + int __spare2__[7]; + } __spare__; + } _reason; +}; + +struct Sigaction { + union { + void (*handler)(int); + void (*sigaction)(int, struct Siginfo*, void*); + } __sigaction_handler; + int sa_flags; + Sigset sa_mask; +}; + struct Ucontext { struct Sigset uc_sigmask; int field1_0x10[12]; - struct Mcontext uc_mcontext; - struct Ucontext* uc_link; - struct ExStack uc_stack; + Mcontext uc_mcontext; + Ucontext* uc_link; + ExStack uc_stack; int uc_flags; int __spare[4]; int field7_0x4f4[3]; }; +s32 NativeToOrbisSignal(s32 s); +s32 OrbisToNativeSignal(s32 s); + void RegisterException(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp index da9e1600f..f97451154 100644 --- a/src/core/libraries/kernel/threads/pthread.cpp +++ b/src/core/libraries/kernel/threads/pthread.cpp @@ -665,6 +665,7 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("Z4QosVuAsA0", "libkernel", 1, "libkernel", posix_pthread_once); LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", posix_pthread_self); LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", posix_pthread_create); + LIB_FUNCTION("Jmi+9w9u0E4", "libkernel", 1, "libkernel", posix_pthread_create_name_np); LIB_FUNCTION("lZzFeSxPl08", "libkernel", 1, "libkernel", posix_pthread_setcancelstate); LIB_FUNCTION("CBNtXOoef-E", "libkernel", 1, "libkernel", posix_sched_get_priority_max); LIB_FUNCTION("m0iS6jNsXds", "libkernel", 1, "libkernel", posix_sched_get_priority_min); diff --git a/src/core/linker.cpp b/src/core/linker.cpp index d0d682043..6640c7204 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -22,6 +22,10 @@ #include "core/tls.h" #include "ipc/ipc.h" +#ifndef _WIN32 +#include +#endif + namespace Core { static PS4_SYSV_ABI void ProgramExitFunc() { @@ -107,6 +111,11 @@ void Linker::Execute(const std::vector& args) { main_thread.Run([this, module, &args](std::stop_token) { Common::SetCurrentThreadName("Game:Main"); +#ifndef _WIN32 // Clear any existing signal mask for game threads. + sigset_t emptyset; + sigemptyset(&emptyset); + pthread_sigmask(SIG_SETMASK, &emptyset, nullptr); +#endif if (auto& ipc = IPC::Instance()) { ipc.WaitForStart(); } diff --git a/src/core/signals.cpp b/src/core/signals.cpp index 70b431d39..f9b45bab7 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.cpp @@ -21,7 +21,7 @@ #ifndef _WIN32 namespace Libraries::Kernel { void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context); -extern std::array Handlers; +extern std::array Handlers; } // namespace Libraries::Kernel #endif @@ -86,7 +86,7 @@ void SignalHandler(int sig, siginfo_t* info, void* raw_context) { if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) { // If the guest has installed a custom signal handler, and the access violation didn't // come from HLE memory tracking, pass the signal on - if (Libraries::Kernel::Handlers[sig]) { + if (Libraries::Kernel::Handlers[Libraries::Kernel::NativeToOrbisSignal(sig)]) { Libraries::Kernel::SigactionHandler(sig, info, reinterpret_cast(raw_context)); return; @@ -99,7 +99,7 @@ void SignalHandler(int sig, siginfo_t* info, void* raw_context) { } case SIGILL: if (!signals->DispatchIllegalInstruction(raw_context)) { - if (Libraries::Kernel::Handlers[sig]) { + if (Libraries::Kernel::Handlers[Libraries::Kernel::NativeToOrbisSignal(sig)]) { Libraries::Kernel::SigactionHandler(sig, info, reinterpret_cast(raw_context)); return; diff --git a/src/core/signals.h b/src/core/signals.h index 90801debb..011383693 100644 --- a/src/core/signals.h +++ b/src/core/signals.h @@ -10,10 +10,8 @@ #ifdef _WIN32 #define SIGSLEEP -1 -#elif defined(__APPLE__) -#define SIGSLEEP SIGVTALRM #else -#define SIGSLEEP SIGRTMAX +#define SIGSLEEP SIGVTALRM #endif namespace Core { From e16ba06ab0aa10492d719c3a11f46aeaf73ff5c1 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Mon, 9 Mar 2026 17:33:58 +0200 Subject: [PATCH 20/40] shader_recompiler: Support 32 thread sharing mode (#4110) --- .../frontend/translate/data_share.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 634486fc4..b1aca83d3 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -277,12 +277,21 @@ void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { const u8 offset0 = inst.control.ds.offset0; const u8 offset1 = inst.control.ds.offset1; const IR::U32 src{GetSrc(inst.src[0])}; - // ASSERT(offset1 & 0x80); const IR::U32 lane_id = ir.LaneId(); - const IR::U32 id_in_group = ir.BitwiseAnd(lane_id, ir.Imm32(0b11)); - const IR::U32 base = ir.ShiftLeftLogical(id_in_group, ir.Imm32(1)); - const IR::U32 index = ir.BitFieldExtract(ir.Imm32(offset0), base, ir.Imm32(2)); - SetDst(inst.dst[0], ir.QuadShuffle(src, index)); + if (offset1 & 0x80) { + const IR::U32 id_in_group = ir.BitwiseAnd(lane_id, ir.Imm32(0b11)); + const IR::U32 base = ir.ShiftLeftLogical(id_in_group, ir.Imm32(1)); + const IR::U32 index = ir.BitFieldExtract(ir.Imm32(offset0), base, ir.Imm32(2)); + SetDst(inst.dst[0], ir.QuadShuffle(src, index)); + } else { + const u8 and_mask = (offset0 & 0x1f) | (~u8{0} << 5); + const u8 or_mask = (offset0 >> 5) | ((offset1 & 0x3) << 3); + const u8 xor_mask = offset1 >> 2; + const IR::U32 index = ir.BitwiseXor( + ir.BitwiseOr(ir.BitwiseAnd(lane_id, ir.Imm32(and_mask)), ir.Imm32(or_mask)), + ir.Imm32(xor_mask)); + SetDst(inst.dst[0], ir.ReadLane(src, index)); + } } void Translator::DS_APPEND(const GcnInst& inst) { From df6bb8562e79971beb966789e4cbbb2b35fcc9d9 Mon Sep 17 00:00:00 2001 From: TheTurtle Date: Mon, 9 Mar 2026 17:46:51 +0200 Subject: [PATCH 21/40] renderer_vulkan: Force subgroup size to 64 when possible (#4111) --- src/video_core/renderer_vulkan/vk_compute_pipeline.cpp | 4 ++++ src/video_core/renderer_vulkan/vk_instance.cpp | 7 +++++-- src/video_core/renderer_vulkan/vk_instance.h | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 35eda86da..eecd416d1 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -21,7 +21,11 @@ ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler, info = &info_; const auto debug_str = GetDebugString(); + const vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo subgroup_size_ci = { + .requiredSubgroupSize = 64, + }; const vk::PipelineShaderStageCreateInfo shader_ci = { + .pNext = instance.IsSubgroupSize64Supported() ? &subgroup_size_ci : nullptr, .stage = vk::ShaderStageFlagBits::eCompute, .module = module, .pName = "main", diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 44aa79d98..6898df97a 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -220,9 +220,11 @@ bool Instance::CreateDevice() { const vk::StructureChain properties_chain = physical_device.getProperties2< vk::PhysicalDeviceProperties2, vk::PhysicalDeviceVulkan11Properties, - vk::PhysicalDeviceVulkan12Properties, vk::PhysicalDevicePushDescriptorPropertiesKHR>(); + vk::PhysicalDeviceVulkan12Properties, vk::PhysicalDeviceVulkan13Properties, + vk::PhysicalDevicePushDescriptorPropertiesKHR>(); vk11_props = properties_chain.get(); vk12_props = properties_chain.get(); + vk13_props = properties_chain.get(); push_descriptor_props = properties_chain.get(); LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", vk11_props.subgroupSize); @@ -367,7 +369,7 @@ bool Instance::CreateDevice() { feature_chain.get(); const auto vk11_features = feature_chain.get(); vk12_features = feature_chain.get(); - const auto vk13_features = feature_chain.get(); + vk13_features = feature_chain.get(); vk::StructureChain device_chain = { vk::DeviceCreateInfo{ .queueCreateInfoCount = 1u, @@ -429,6 +431,7 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceVulkan13Features{ .robustImageAccess = vk13_features.robustImageAccess, .shaderDemoteToHelperInvocation = vk13_features.shaderDemoteToHelperInvocation, + .subgroupSizeControl = vk13_features.subgroupSizeControl, .synchronization2 = vk13_features.synchronization2, .dynamicRendering = vk13_features.dynamicRendering, .maintenance4 = vk13_features.maintenance4, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 8975669bb..7a8a906d5 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -233,6 +233,11 @@ public: return vk12_features.shaderSharedInt64Atomics; } + /// Returns true if the subgroup size can be set to match guest subgroup size + bool IsSubgroupSize64Supported() const { + return vk13_features.subgroupSizeControl && vk13_props.maxSubgroupSize >= 64; + } + /// Returns true when VK_KHR_workgroup_memory_explicit_layout is supported. bool IsWorkgroupMemoryExplicitLayoutSupported() const { return workgroup_memory_explicit_layout && @@ -455,9 +460,11 @@ private: vk::PhysicalDeviceMemoryProperties memory_properties; vk::PhysicalDeviceVulkan11Properties vk11_props; vk::PhysicalDeviceVulkan12Properties vk12_props; + vk::PhysicalDeviceVulkan13Properties vk13_props; vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; vk::PhysicalDeviceFeatures features; vk::PhysicalDeviceVulkan12Features vk12_features; + vk::PhysicalDeviceVulkan13Features vk13_features; vk::PhysicalDevicePortabilitySubsetFeaturesKHR portability_features; vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT dynamic_state_3_features; vk::PhysicalDeviceRobustness2FeaturesEXT robustness2_features; From 85476e55ea70201c470eb618193864844bead976 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:02:22 -0500 Subject: [PATCH 22/40] Recompiler: Implement IMAGE_ATOMIC_CMPSWAP (#4109) * To implement ImageAtomicCmpSwap ...but it doesn't work, so here it shall stay. * a fix * Clang * Add to MayHaveSideEffects I missed this while digging through IR code. --- .../backend/spirv/emit_spirv_atomic.cpp | 15 +++++++++++++++ .../backend/spirv/emit_spirv_instructions.h | 2 ++ src/shader_recompiler/frontend/format.cpp | 4 ++-- .../frontend/translate/vector_memory.cpp | 6 ++++++ src/shader_recompiler/ir/ir_emitter.cpp | 5 +++++ src/shader_recompiler/ir/ir_emitter.h | 3 +++ src/shader_recompiler/ir/microinstruction.cpp | 1 + src/shader_recompiler/ir/opcodes.inc | 1 + .../ir/passes/resource_tracking_pass.cpp | 1 + 9 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 6dd1637dd..549e27ae0 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -140,6 +140,15 @@ Id ImageAtomicF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id va const auto [scope, semantics]{AtomicArgs(ctx)}; return (ctx.*atomic_func)(ctx.F32[1], pointer, scope, semantics, value); } + +Id ImageAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value, + Id cmp_value, + Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) { + const auto& texture = ctx.images[handle & 0xFFFF]; + const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, texture.id, coords, ctx.ConstU32(0U))}; + const auto [scope, semantics]{AtomicArgs(ctx)}; + return (ctx.*atomic_func)(ctx.F32[1], pointer, scope, semantics, semantics, value, cmp_value); +} } // Anonymous namespace Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value) { @@ -420,6 +429,12 @@ Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id co return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicExchange); } +Id EmitImageAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value, + Id cmp_value) { + return ImageAtomicU32CmpSwap(ctx, inst, handle, coords, value, cmp_value, + &Sirit::Module::OpAtomicCompareExchange); +} + Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) { const auto& buffer = ctx.buffers[binding]; const auto [id, pointer_type] = buffer.Alias(PointerType::U32); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 80968eaf0..69fa36eaa 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -456,6 +456,8 @@ Id EmitImageAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id EmitImageAtomicOr32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); Id EmitImageAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); +Id EmitImageAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, + Id cmp_value); Id EmitCubeFaceIndex(EmitContext& ctx, IR::Inst* inst, Id cube_coords); Id EmitLaneId(EmitContext& ctx); Id EmitWarpId(EmitContext& ctx); diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index d26873396..4a90fe358 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -3430,8 +3430,8 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Uint32}, // 16 = IMAGE_ATOMIC_CMPSWAP - {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Uint32}, // 17 = IMAGE_ATOMIC_ADD {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Uint32}, diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 72286b29c..0d9e8f220 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -137,6 +137,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { // Image atomic operations case Opcode::IMAGE_ATOMIC_SWAP: return IMAGE_ATOMIC(AtomicOp::Swap, inst); + case Opcode::IMAGE_ATOMIC_CMPSWAP: + return IMAGE_ATOMIC(AtomicOp::CmpSwap, inst); case Opcode::IMAGE_ATOMIC_ADD: return IMAGE_ATOMIC(AtomicOp::Add, inst); case Opcode::IMAGE_ATOMIC_SMIN: @@ -520,6 +522,10 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) { switch (op) { case AtomicOp::Swap: return ir.ImageAtomicExchange(handle, body, value, {}); + case AtomicOp::CmpSwap: { + const IR::Value cmp_val = ir.GetVectorReg(val_reg + 1); + return ir.ImageAtomicCmpSwap(handle, body, value, cmp_val, info); + } case AtomicOp::Add: return ir.ImageAtomicIAdd(handle, body, value, info); case AtomicOp::Smin: diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 1e77dc677..c681c3120 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -2055,6 +2055,11 @@ Value IREmitter::ImageAtomicExchange(const Value& handle, const Value& coords, c return Inst(Opcode::ImageAtomicExchange32, Flags{info}, handle, coords, value); } +Value IREmitter::ImageAtomicCmpSwap(const Value& handle, const Value& coords, const Value& value, + const Value& cmp_value, TextureInstInfo info) { + return Inst(Opcode::ImageAtomicCmpSwap32, Flags{info}, handle, coords, value, cmp_value); +} + Value IREmitter::ImageSampleRaw(const Value& image_handle, const Value& sampler_handle, const Value& address1, const Value& address2, const Value& address3, const Value& address4, TextureInstInfo info) { diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 6f20d5780..adc8f5fb1 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -360,6 +360,9 @@ public: TextureInstInfo info); [[nodiscard]] Value ImageAtomicExchange(const Value& handle, const Value& coords, const Value& value, TextureInstInfo info); + [[nodiscard]] Value ImageAtomicCmpSwap(const Value& handle, const Value& coords, + const Value& value, const Value& cmp_value, + TextureInstInfo info); [[nodiscard]] Value ImageSampleRaw(const Value& image_handle, const Value& sampler_handle, const Value& address1, const Value& address2, diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index 40ce69df8..cd0131770 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -123,6 +123,7 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::ImageAtomicOr32: case Opcode::ImageAtomicXor32: case Opcode::ImageAtomicExchange32: + case Opcode::ImageAtomicCmpSwap32: case Opcode::DebugPrint: case Opcode::EmitVertex: case Opcode::EmitPrimitive: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 4875375bc..6304a96fa 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -436,6 +436,7 @@ OPCODE(ImageAtomicAnd32, U32, Opaq OPCODE(ImageAtomicOr32, U32, Opaque, Opaque, U32, ) OPCODE(ImageAtomicXor32, U32, Opaque, Opaque, U32, ) OPCODE(ImageAtomicExchange32, U32, Opaque, Opaque, U32, ) +OPCODE(ImageAtomicCmpSwap32, U32, Opaque, Opaque, U32, U32, ) // Cube operations - optional, usable if profile.supports_native_cube_calc OPCODE(CubeFaceIndex, F32, F32x3, ) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 93129ac0e..4c41e94e9 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -214,6 +214,7 @@ bool IsImageAtomicInstruction(const IR::Inst& inst) { case IR::Opcode::ImageAtomicOr32: case IR::Opcode::ImageAtomicXor32: case IR::Opcode::ImageAtomicExchange32: + case IR::Opcode::ImageAtomicCmpSwap32: return true; default: return false; From 3ee06a91df6975851660ae497bff7a79d61a9f96 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:25:45 -0500 Subject: [PATCH 23/40] Relocate imports on HLE loads (#4113) Now that dynamic HLE loads happen after the eboot loads, HLEs for most "preload" modules wouldn't detect if you didn't have libSceRtc dumped. This was because, while we stored all the new symbols from the HLE lib, we weren't relocating loaded modules to use these symbols. --- src/core/libraries/sysmodule/sysmodule_internal.cpp | 4 ++++ src/core/linker.h | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/sysmodule/sysmodule_internal.cpp b/src/core/libraries/sysmodule/sysmodule_internal.cpp index 55acded94..def410e25 100644 --- a/src/core/libraries/sysmodule/sysmodule_internal.cpp +++ b/src/core/libraries/sysmodule/sysmodule_internal.cpp @@ -252,6 +252,10 @@ s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out) { if (init_func) { LOG_INFO(Loader, "Can't Load {} switching to HLE", mod_name); init_func(&linker->GetHLESymbols()); + + // When loading HLEs, we need to relocate imports + // This ensures later module loads can see our HLE functions. + linker->RelocateAllImports(); } else { LOG_INFO(Loader, "No HLE available for {} module", mod_name); } diff --git a/src/core/linker.h b/src/core/linker.h index 8ffcd9d45..3cb59d9ee 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -125,11 +125,10 @@ public: } } - void LoadSharedLibraries() { + void RelocateAllImports() { + std::scoped_lock lk{mutex}; for (auto& module : m_modules) { - if (module->IsSharedLib()) { - module->Start(0, nullptr, nullptr); - } + Relocate(module.get()); } } From ac3786f533024f04c77b5d295a14bca05f737751 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:25:22 -0500 Subject: [PATCH 24/40] Fix return type for ImageAtomicU32CmpSwap (#4115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Somedays I wonder how I miss these details. But hey, least there are two other people who also missed this 😅 --- src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 549e27ae0..1055bf081 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -147,7 +147,7 @@ Id ImageAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords const auto& texture = ctx.images[handle & 0xFFFF]; const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, texture.id, coords, ctx.ConstU32(0U))}; const auto [scope, semantics]{AtomicArgs(ctx)}; - return (ctx.*atomic_func)(ctx.F32[1], pointer, scope, semantics, semantics, value, cmp_value); + return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, semantics, value, cmp_value); } } // Anonymous namespace From c8b3d63e7ebb48ac16a287b78032708cf3c9e222 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 12 Mar 2026 01:53:12 -0500 Subject: [PATCH 25/40] Core: Fix game arguments (#4118) * Fix game arguments. Tested with Crash Team Racing Nitro Fueled * Fix the fix This callback runs unconditionally, so only perform erase if we actually place anything in gameArgs --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index d3799e2ec..3370901a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,6 +99,7 @@ int main(int argc, char* argv[]) { const auto& extras = app.remaining(); if (!extras.empty()) { gameArgs = extras; + gameArgs.erase(gameArgs.begin()); } }); From 67ffd0334b8428f395563c89fbdaa5f6233ea1e7 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:15:04 +0100 Subject: [PATCH 26/40] Properly fix game flag handling (#4119) * fix the fix for the fix * fine there's no debug info then because of Ubuntu things --- src/main.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 3370901a8..d2804ee62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,7 +99,6 @@ int main(int argc, char* argv[]) { const auto& extras = app.remaining(); if (!extras.empty()) { gameArgs = extras; - gameArgs.erase(gameArgs.begin()); } }); @@ -143,6 +142,14 @@ int main(int argc, char* argv[]) { return 1; } } + if (!gameArgs.empty()) { + if (gameArgs.front() == "--") { + gameArgs.erase(gameArgs.begin()); + } else { + std::cerr << "Error: unhandled flags\n"; + return 1; + } + } // ---- Apply flags ---- if (patchFile) From f336096b12cf853b8eb8137cb67646fee7cfde17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B0=D0=BD=D1=8C=D0=BA=D0=B0=20=D0=A7=D0=B5=D1=82?= =?UTF-8?q?=D0=B2=D1=91=D1=80=D1=82=D1=8B=D0=B9?= Date: Fri, 13 Mar 2026 17:38:22 +0700 Subject: [PATCH 27/40] PSF file format: close file after encode() (#4122) --- src/core/file_format/psf.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/file_format/psf.cpp b/src/core/file_format/psf.cpp index e647059f0..c5be7410a 100644 --- a/src/core/file_format/psf.cpp +++ b/src/core/file_format/psf.cpp @@ -113,6 +113,7 @@ bool PSF::Encode(const std::filesystem::path& filepath) const { LOG_ERROR(Core, "Failed to write PSF file. Written {} Expected {}", written, psf_buffer.size()); } + file.Close(); return written == psf_buffer.size(); } From 844cfe51850e97bd3b3aee5ad99a390394b356f3 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:12:26 -0500 Subject: [PATCH 28/40] Lib.Ssl2: Stub data for sceSslGetCaCerts (#4127) * Test * More robust logic for storing and freeing dummy data Anything heap allocated is invalidated when the function returns. Use malloc to allocate the string instead, and make sure to free those allocations in sceSslFreeCaCerts. --- src/core/libraries/network/ssl2.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/network/ssl2.cpp b/src/core/libraries/network/ssl2.cpp index 0b408d094..3a7fd71e5 100644 --- a/src/core/libraries/network/ssl2.cpp +++ b/src/core/libraries/network/ssl2.cpp @@ -114,7 +114,13 @@ int PS4_SYSV_ABI sceSslFreeCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) { if (certs == nullptr) { return ORBIS_SSL_ERROR_INVALID_ARGUMENT; } - delete (certs->certs); + if (certs->certs != nullptr) { + for (s32 data = 0; data < certs->num; data++) { + free(certs->certs[data].ptr); + } + delete (certs->certs); + } + // delete (certs->pool); return ORBIS_OK; } @@ -139,7 +145,12 @@ int PS4_SYSV_ABI sceSslGetCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) { if (certs == nullptr) { return ORBIS_SSL_ERROR_INVALID_ARGUMENT; } - certs->certs = new OrbisSslData{nullptr, 0}; + // Allocate a buffer to store dummy data in. + const char* dummy_data = "dummy"; + u64 dummy_length = strlen(dummy_data) + 1; + char* data = static_cast(malloc(dummy_length)); + strncpy(data, dummy_data, dummy_length); + certs->certs = new OrbisSslData{data, dummy_length}; certs->num = 1; certs->pool = nullptr; return ORBIS_OK; From 30ff9cf05045124d47843512568fdd7cfbbbff3d Mon Sep 17 00:00:00 2001 From: shinra-electric <50119606+shinra-electric@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:15:29 +0000 Subject: [PATCH 29/40] CI: Update actions/cache due to Node 20 deprecation (#4128) * Upload-artifact v4 --> v6 * Download-artifact v5 --> v8 * Checkout v5 --> v6 * cache v4 --> v5 --- .github/workflows/build.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ffe7c22fb..b54698e3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,14 +26,14 @@ jobs: runs-on: ubuntu-24.04 continue-on-error: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: fsfe/reuse-action@v5 clang-format: runs-on: ubuntu-24.04 continue-on-error: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install @@ -54,7 +54,7 @@ jobs: shorthash: ${{ steps.vars.outputs.shorthash }} fullhash: ${{ steps.vars.outputs.fullhash }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Get date and git hash id: vars run: | @@ -69,12 +69,12 @@ jobs: runs-on: windows-2025 needs: get-info steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: recursive - name: Cache CMake Configuration - uses: actions/cache@v4 + uses: actions/cache@v5 env: cache-name: ${{ runner.os }}-sdl-ninja-cache-cmake-configuration with: @@ -99,7 +99,7 @@ jobs: run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $env:NUMBER_OF_PROCESSORS - name: Upload Windows SDL artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: shadps4-win64-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} path: ${{github.workspace}}/build/shadPS4.exe @@ -108,7 +108,7 @@ jobs: runs-on: macos-15 needs: get-info steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: recursive @@ -118,7 +118,7 @@ jobs: xcode-version: latest - name: Cache CMake Configuration - uses: actions/cache@v4 + uses: actions/cache@v5 env: cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration with: @@ -150,7 +150,7 @@ jobs: mv ${{github.workspace}}/build/shadps4 upload mv ${{github.workspace}}/build/MoltenVK_icd.json upload mv ${{github.workspace}}/build/libMoltenVK.dylib upload - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} path: upload/ @@ -159,7 +159,7 @@ jobs: runs-on: ubuntu-24.04 needs: get-info steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: recursive @@ -172,7 +172,7 @@ jobs: run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev - name: Cache CMake Configuration - uses: actions/cache@v4 + uses: actions/cache@v5 env: cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration with: @@ -200,7 +200,7 @@ jobs: run: | ls -la ${{ github.workspace }}/build/shadps4 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: shadps4-ubuntu64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} path: ${{ github.workspace }}/build/shadps4 @@ -211,7 +211,7 @@ jobs: - name: Package and Upload Linux SDL artifact run: | tar cf shadps4-linux-sdl.tar.gz -C ${{github.workspace}}/build shadps4 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: shadps4-linux-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} path: Shadps4-sdl.AppImage @@ -220,7 +220,7 @@ jobs: runs-on: ubuntu-24.04 needs: get-info steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: recursive @@ -228,7 +228,7 @@ jobs: run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev - name: Cache CMake Configuration - uses: actions/cache@v4 + uses: actions/cache@v5 env: cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-configuration with: @@ -258,7 +258,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download all artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: path: ./artifacts From 4d62930075925f5ff7c1d0d9db015e2b0b588397 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 17 Mar 2026 09:22:22 +0200 Subject: [PATCH 30/40] tagged 0.15.0 release --- CMakeLists.txt | 8 ++++---- dist/net.shadps4.shadPS4.metainfo.xml | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd02a6378..b75592a4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,13 +202,13 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") -set(EMULATOR_VERSION_MINOR "14") -set(EMULATOR_VERSION_PATCH "1") +set(EMULATOR_VERSION_MINOR "15") +set(EMULATOR_VERSION_PATCH "0") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") -set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP") -set(APP_IS_RELEASE false) +set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") +set(APP_IS_RELEASE true) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) message("-- end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}, link: ${GIT_REMOTE_URL}") diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml index 210ca1c5e..8a7fa852b 100644 --- a/dist/net.shadps4.shadPS4.metainfo.xml +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -38,7 +38,10 @@ Game - + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.15.0 + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.14.0 From 3a3ef5b05f3dc5276857e3e0bcadb03f3f8c89f5 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 17 Mar 2026 10:26:34 +0200 Subject: [PATCH 31/40] started 0.15.1 WIP --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b75592a4c..ee6f37802 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,12 +203,12 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") set(EMULATOR_VERSION_MINOR "15") -set(EMULATOR_VERSION_PATCH "0") +set(EMULATOR_VERSION_PATCH "1") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") -set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") -set(APP_IS_RELEASE true) +set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP") +set(APP_IS_RELEASE false) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) message("-- end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}, link: ${GIT_REMOTE_URL}") From e6b743032dc7f48ba3e839cc4a726b5c2cf3d705 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:58:31 +0100 Subject: [PATCH 32/40] Don't print unresolved libc and libSceFios2 stubs (#4137) --- src/core/linker.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 6640c7204..3f410e926 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -361,8 +361,10 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul return_info->virtual_address = AeroLib::GetStub(sr.name.c_str()); return_info->name = "Unknown !!!"; } - LOG_WARNING(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name, - return_info->name, library->name, module->name); + if (library->name != "libc" && library->name != "libSceFios2") { + LOG_WARNING(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name, + return_info->name, library->name, module->name); + } return false; } From 88c34372402ed32f5102c2504604800df8d0139f Mon Sep 17 00:00:00 2001 From: shinra-electric <50119606+shinra-electric@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:36:59 +0000 Subject: [PATCH 33/40] Bump ccache-action (#4138) --- .github/workflows/build.yml | 92 ++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b54698e3a..3d77c5800 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: env: COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} run: ./.ci/clang-format.sh - + get-info: runs-on: ubuntu-24.04 outputs: @@ -78,14 +78,14 @@ jobs: env: cache-name: ${{ runner.os }}-sdl-ninja-cache-cmake-configuration with: - path: | + path: | ${{github.workspace}}/build key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} restore-keys: | ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.19 + uses: hendrikmuhs/ccache-action@v1.2.21 env: cache-name: ${{ runner.os }}-sdl-cache-cmake-build with: @@ -119,17 +119,17 @@ jobs: - name: Cache CMake Configuration uses: actions/cache@v5 - env: + env: cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration - with: - path: | - ${{github.workspace}}/build - key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - restore-keys: | - ${{ env.cache-name }}- + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.19 + uses: hendrikmuhs/ccache-action@v1.2.21 env: cache-name: ${{runner.os}}-sdl-cache-cmake-build with: @@ -173,17 +173,17 @@ jobs: - name: Cache CMake Configuration uses: actions/cache@v5 - env: + env: cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration - with: - path: | - ${{github.workspace}}/build - key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - restore-keys: | - ${{ env.cache-name }}- + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.19 + uses: hendrikmuhs/ccache-action@v1.2.21 env: cache-name: ${{ runner.os }}-sdl-cache-cmake-build with: @@ -195,11 +195,11 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) - - - name: Package and Upload Linux(ubuntu64) SDL artifact + + - name: Package and Upload Linux(ubuntu64) SDL artifact run: | ls -la ${{ github.workspace }}/build/shadps4 - + - uses: actions/upload-artifact@v6 with: name: shadps4-ubuntu64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} @@ -207,7 +207,7 @@ jobs: - name: Run AppImage packaging script run: ./.github/linux-appimage-sdl.sh - + - name: Package and Upload Linux SDL artifact run: | tar cf shadps4-linux-sdl.tar.gz -C ${{github.workspace}}/build shadps4 @@ -229,17 +229,17 @@ jobs: - name: Cache CMake Configuration uses: actions/cache@v5 - env: + env: cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-configuration - with: - path: | - ${{github.workspace}}/build - key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - restore-keys: | - ${{ env.cache-name }}- + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- - name: Cache CMake Build - uses: hendrikmuhs/ccache-action@v1.2.19 + uses: hendrikmuhs/ccache-action@v1.2.21 env: cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build with: @@ -266,7 +266,7 @@ jobs: run: | chmod -R a+x ./artifacts/shadps4-linux-sdl-* chmod -R a+x ./artifacts/shadps4-macos-sdl-* - + - name: Compress individual directories (without parent directory) run: | cd ./artifacts @@ -277,7 +277,7 @@ jobs: (cd "$dir_name" && zip -r "../${dir_name}.zip" .) fi done - + - name: Get latest release information id: get_latest_release env: @@ -351,52 +351,52 @@ jobs: upload_url="https://uploads.github.com/repos/$REPO/releases/$release_id/assets?name=$filename" curl -X POST -H "Authorization: token $GITHUB_TOKEN" -H "Content-Type: application/octet-stream" --data-binary @"$file" "$upload_url" done - + - name: Get current pre-release information env: GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }} run: | api_url="https://api.github.com/repos/${{ github.repository }}/releases" - + # Get all releases (sorted by date) releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url") - + # Capture the most recent pre-release (assuming the first one is the latest) current_release=$(echo "$releases" | jq -c '.[] | select(.prerelease == true) | .published_at' | sort -r | head -n 1) - + # Remove extra quotes from captured date current_release=$(echo $current_release | tr -d '"') - + # Export the current published_at to be available for the next step echo "CURRENT_PUBLISHED_AT=$current_release" >> $GITHUB_ENV - + - name: Delete old pre-releases and tags env: GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }} run: | api_url="https://api.github.com/repos/${{ github.repository }}/releases" - + # Get current pre-releases releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url") - + # Remove extra quotes from captured date CURRENT_PUBLISHED_AT=$(echo $CURRENT_PUBLISHED_AT | tr -d '"') - + # Convert CURRENT_PUBLISHED_AT para timestamp Unix current_published_ts=$(date -d "$CURRENT_PUBLISHED_AT" +%s) - + # Identify pre-releases echo "$releases" | jq -c '.[] | select(.prerelease == true)' | while read -r release; do release_date=$(echo "$release" | jq -r '.published_at') release_id=$(echo "$release" | jq -r '.id') release_tag=$(echo "$release" | jq -r '.tag_name') - + # Remove extra quotes from captured date release_date=$(echo $release_date | tr -d '"') - + # Convert release_date para timestamp Unix release_date_ts=$(date -d "$release_date" +%s) - + # Compare timestamps and delete old pre-releases if [[ "$release_date_ts" -lt "$current_published_ts" ]]; then echo "Deleting old pre-release: $release_id from $release_date with tag: $release_tag" From 1bb152d9769a454652e2658b2e35c429c96127ee Mon Sep 17 00:00:00 2001 From: baggins183 Date: Tue, 17 Mar 2026 12:47:19 -0700 Subject: [PATCH 34/40] IMAGE_STORE_MIP fallback (#4075) * fallback for IMAGE_STORE_MIP when not natively supported * Lod should be treated as absolute, independent of sharp's base_level (judging by other implemented instructions) * fix descriptor set layouts * dumb error * force fallback for testing * treat Lod as relative to base_level * optimization when lod index is constant --- .../backend/spirv/emit_spirv_image.cpp | 26 +++++-- .../backend/spirv/spirv_emit_context.cpp | 16 ++++- .../backend/spirv/spirv_emit_context.h | 2 +- src/shader_recompiler/ir/passes/ir_passes.h | 2 +- .../ir/passes/resource_tracking_pass.cpp | 59 +++++++++++++-- src/shader_recompiler/recompiler.cpp | 2 +- src/shader_recompiler/resource.h | 11 +++ src/shader_recompiler/specialization.h | 5 +- .../renderer_vulkan/vk_compute_pipeline.cpp | 6 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 6 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 2 +- .../renderer_vulkan/vk_rasterizer.cpp | 71 ++++++++++++++----- 12 files changed, 168 insertions(+), 40 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index e2a969b61..0b05dcef4 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -220,20 +220,33 @@ Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms) { const auto& texture = ctx.images[handle & 0xFFFF]; - const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id color_type = texture.data_types->Get(4); ImageOperands operands; operands.Add(spv::ImageOperandsMask::Sample, ms); Id texel; if (!texture.is_storage) { + const Id image = ctx.OpLoad(texture.image_type, texture.id); operands.Add(spv::ImageOperandsMask::Lod, lod); texel = ctx.OpImageFetch(color_type, image, coords, operands.mask, operands.operands); } else { + Id image_ptr = texture.id; if (ctx.profile.supports_image_load_store_lod) { operands.Add(spv::ImageOperandsMask::Lod, lod); } else if (Sirit::ValidId(lod)) { - LOG_WARNING(Render, "Image read with LOD not supported by driver"); +#if 1 + // It's confusing what interactions will cause this code path so leave it as + // unreachable until a case is found. + // Normally IMAGE_LOAD_MIP should translate -> OpImageFetch + UNREACHABLE_MSG("Unsupported ImageRead with Lod"); +#else + LOG_WARNING(Render, "Fallback for ImageRead with LOD"); + ASSERT(texture.mip_fallback_mode == MipStorageFallbackMode::DynamicIndex); + const Id single_image_ptr_type = + ctx.TypePointer(spv::StorageClass::UniformConstant, texture.image_type); + image_ptr = ctx.OpAccessChain(single_image_ptr_type, image_ptr, std::array{lod}); +#endif } + const Id image = ctx.OpLoad(texture.image_type, image_ptr); texel = ctx.OpImageRead(color_type, image, coords, operands.mask, operands.operands); } return texture.is_integer ? ctx.OpBitcast(ctx.F32[4], texel) : texel; @@ -242,15 +255,20 @@ Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms, Id color) { const auto& texture = ctx.images[handle & 0xFFFF]; - const Id image = ctx.OpLoad(texture.image_type, texture.id); + Id image_ptr = texture.id; const Id color_type = texture.data_types->Get(4); ImageOperands operands; operands.Add(spv::ImageOperandsMask::Sample, ms); if (ctx.profile.supports_image_load_store_lod) { operands.Add(spv::ImageOperandsMask::Lod, lod); } else if (Sirit::ValidId(lod)) { - LOG_WARNING(Render, "Image write with LOD not supported by driver"); + LOG_WARNING(Render, "Fallback for ImageWrite with LOD"); + ASSERT(texture.mip_fallback_mode == MipStorageFallbackMode::DynamicIndex); + const Id single_image_ptr_type = + ctx.TypePointer(spv::StorageClass::UniformConstant, texture.image_type); + image_ptr = ctx.OpAccessChain(single_image_ptr_type, image_ptr, std::array{lod}); } + const Id image = ctx.OpLoad(texture.image_type, image_ptr); const Id texel = texture.is_integer ? ctx.OpBitcast(color_type, color) : color; ctx.OpImageWrite(image, coords, texel, operands.mask, operands.operands); } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 261155ab5..c0e469964 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -961,23 +961,33 @@ void EmitContext::DefineImagesAndSamplers() { const auto nfmt = sharp.GetNumberFmt(); const bool is_integer = AmdGpu::IsInteger(nfmt); const bool is_storage = image_desc.is_written; + const MipStorageFallbackMode mip_fallback_mode = image_desc.mip_fallback_mode; const VectorIds& data_types = GetAttributeType(*this, nfmt); const Id sampled_type = data_types[1]; const Id image_type{ImageType(*this, image_desc, sampled_type)}; - const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; + + const u32 num_bindings = image_desc.NumBindings(info); + Id pointee_type = image_type; + if (mip_fallback_mode == MipStorageFallbackMode::DynamicIndex) { + pointee_type = TypeArray(pointee_type, ConstU32(num_bindings)); + } + + const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, pointee_type)}; const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; - Decorate(id, spv::Decoration::Binding, binding.unified++); + Decorate(id, spv::Decoration::Binding, binding.unified); + binding.unified += num_bindings; Decorate(id, spv::Decoration::DescriptorSet, 0U); + // TODO better naming for resources (flattened sharp_idx is not informative) Name(id, fmt::format("{}_{}{}", stage, "img", image_desc.sharp_idx)); images.push_back({ .data_types = &data_types, .id = id, .sampled_type = is_storage ? sampled_type : TypeSampledImage(image_type), - .pointer_type = pointer_type, .image_type = image_type, .view_type = sharp.GetViewType(image_desc.is_array), .is_integer = is_integer, .is_storage = is_storage, + .mip_fallback_mode = mip_fallback_mode, }); interfaces.push_back(id); } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 9bb2b7d7a..a9c6f0968 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -293,11 +293,11 @@ public: const VectorIds* data_types; Id id; Id sampled_type; - Id pointer_type; Id image_type; AmdGpu::ImageType view_type; bool is_integer = false; bool is_storage = false; + MipStorageFallbackMode mip_fallback_mode{}; }; enum class PointerType : u32 { diff --git a/src/shader_recompiler/ir/passes/ir_passes.h b/src/shader_recompiler/ir/passes/ir_passes.h index f103b6736..1b14a1c6b 100644 --- a/src/shader_recompiler/ir/passes/ir_passes.h +++ b/src/shader_recompiler/ir/passes/ir_passes.h @@ -19,7 +19,7 @@ void DeadCodeEliminationPass(IR::Program& program); void ConstantPropagationPass(IR::BlockList& program); void FlattenExtendedUserdataPass(IR::Program& program); void ReadLaneEliminationPass(IR::Program& program); -void ResourceTrackingPass(IR::Program& program); +void ResourceTrackingPass(IR::Program& program, const Profile& profile); void CollectShaderInfoPass(IR::Program& program, const Profile& profile); void LowerBufferFormatToRaw(IR::Program& program); void LowerFp64ToFp32(IR::Program& program); diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 4c41e94e9..3b7888ab3 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -9,6 +9,7 @@ #include "shader_recompiler/ir/operand_helper.h" #include "shader_recompiler/ir/program.h" #include "shader_recompiler/ir/reinterpret.h" +#include "shader_recompiler/profile.h" #include "video_core/amdgpu/resource.h" namespace Shader::Optimization { @@ -255,7 +256,9 @@ public: u32 Add(const ImageResource& desc) { const u32 index{Add(image_resources, desc, [&desc](const auto& existing) { - return desc.sharp_idx == existing.sharp_idx && desc.is_array == existing.is_array; + return desc.sharp_idx == existing.sharp_idx && desc.is_array == existing.is_array && + desc.mip_fallback_mode == existing.mip_fallback_mode && + desc.constant_mip_index == existing.constant_mip_index; })}; auto& image = image_resources[index]; image.is_atomic |= desc.is_atomic; @@ -529,14 +532,21 @@ void PatchBufferSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& inst.SetArg(0, ir.Imm32(buffer_binding)); } -void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { +void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors, + const Profile& profile) { // Read image sharp. const auto inst_info = inst.Flags(); const IR::Inst* image_handle = inst.Arg(0).InstRecursive(); const auto tsharp = TrackSharp(image_handle, block, inst_info.pc); const bool is_atomic = IsImageAtomicInstruction(inst); const bool is_written = inst.GetOpcode() == IR::Opcode::ImageWrite || is_atomic; - const ImageResource image_res = { + const bool is_storage = + inst.GetOpcode() == IR::Opcode::ImageRead || inst.GetOpcode() == IR::Opcode::ImageWrite; + // ImageRead with !is_written gets emitted as OpImageFetch with LOD operand, doesn't + // need fallback (TODO is this 100% true?) + const bool needs_mip_storage_fallback = + inst_info.has_lod && is_written && !profile.supports_image_load_store_lod; + ImageResource image_res = { .sharp_idx = tsharp, .is_depth = bool(inst_info.is_depth), .is_atomic = is_atomic, @@ -544,9 +554,42 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& .is_written = is_written, .is_r128 = bool(inst_info.is_r128), }; + auto image = image_res.GetSharp(info); ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); + if (needs_mip_storage_fallback) { + // If the mip level to IMAGE_(LOAD/STORE)_MIP is a constant, set up ImageResource + // so that we will only bind a single level. + // If index is dynamic, we will bind levels as an array + const auto view_type = image.GetViewType(image_res.is_array); + + IR::Inst* body = inst.Arg(1).InstRecursive(); + const auto lod_arg = [&] -> IR::Value { + switch (view_type) { + case AmdGpu::ImageType::Color1D: // x, [lod] + return body->Arg(1); + case AmdGpu::ImageType::Color1DArray: // x, slice, [lod] + case AmdGpu::ImageType::Color2D: // x, y, [lod] + return body->Arg(2); + case AmdGpu::ImageType::Color2DArray: // x, y, slice, [lod] + case AmdGpu::ImageType::Color3D: // x, y, z, [lod] + return body->Arg(3); + case AmdGpu::ImageType::Color2DMsaa: + case AmdGpu::ImageType::Color2DMsaaArray: + default: + UNREACHABLE_MSG("Invalid image type {}", view_type); + } + }(); + + if (lod_arg.IsImmediate()) { + image_res.mip_fallback_mode = MipStorageFallbackMode::ConstantIndex; + image_res.constant_mip_index = lod_arg.U32(); + } else { + image_res.mip_fallback_mode = MipStorageFallbackMode::DynamicIndex; + } + } + // Patch image instruction if image is FMask. if (AmdGpu::IsFmask(image.GetDataFmt())) { ASSERT_MSG(!is_written, "FMask storage instructions are not supported"); @@ -1080,7 +1123,11 @@ void PatchImageArgs(IR::Block& block, IR::Inst& inst, Info& info) { const auto has_ms = view_type == AmdGpu::ImageType::Color2DMsaa || view_type == AmdGpu::ImageType::Color2DMsaaArray; ASSERT(!inst_info.has_lod || !has_ms); - const auto lod = inst_info.has_lod ? IR::U32{arg} : IR::U32{}; + // If we are binding a single mip level as fallback, drop the argument + const auto lod = + (inst_info.has_lod && image_res.mip_fallback_mode != MipStorageFallbackMode::ConstantIndex) + ? IR::U32{arg} + : IR::U32{}; const auto ms = has_ms ? IR::U32{arg} : IR::U32{}; const auto is_storage = image_res.is_written; @@ -1111,7 +1158,7 @@ void PatchImageArgs(IR::Block& block, IR::Inst& inst, Info& info) { } } -void ResourceTrackingPass(IR::Program& program) { +void ResourceTrackingPass(IR::Program& program, const Profile& profile) { // Iterate resource instructions and patch them after finding the sharp. auto& info = program.info; @@ -1122,7 +1169,7 @@ void ResourceTrackingPass(IR::Program& program) { if (IsBufferInstruction(inst)) { PatchBufferSharp(*block, inst, info, descriptors); } else if (IsImageInstruction(inst)) { - PatchImageSharp(*block, inst, info, descriptors); + PatchImageSharp(*block, inst, info, descriptors, profile); } } } diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index f4fa45afc..d6efa2890 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -80,7 +80,7 @@ IR::Program TranslateProgram(const std::span& code, Pools& pools, Inf Shader::Optimization::RingAccessElimination(program, runtime_info); Shader::Optimization::ReadLaneEliminationPass(program); Shader::Optimization::FlattenExtendedUserdataPass(program); - Shader::Optimization::ResourceTrackingPass(program); + Shader::Optimization::ResourceTrackingPass(program, profile); Shader::Optimization::LowerBufferFormatToRaw(program); Shader::Optimization::SharedMemorySimplifyPass(program, profile); Shader::Optimization::SharedMemoryToStoragePass(program, runtime_info, profile); diff --git a/src/shader_recompiler/resource.h b/src/shader_recompiler/resource.h index 5ae3179f6..82a861e2a 100644 --- a/src/shader_recompiler/resource.h +++ b/src/shader_recompiler/resource.h @@ -71,6 +71,8 @@ struct BufferResource { }; using BufferResourceList = boost::container::static_vector; +enum class MipStorageFallbackMode : u32 { None, DynamicIndex, ConstantIndex }; + struct ImageResource { u32 sharp_idx; bool is_depth{}; @@ -78,6 +80,8 @@ struct ImageResource { bool is_array{}; bool is_written{}; bool is_r128{}; + MipStorageFallbackMode mip_fallback_mode{}; + u32 constant_mip_index{}; constexpr AmdGpu::Image GetSharp(const auto& info) const noexcept { AmdGpu::Image image{}; @@ -102,6 +106,13 @@ struct ImageResource { } return image; } + + u32 NumBindings(const auto& info) const { + const AmdGpu::Image tsharp = GetSharp(info); + return (mip_fallback_mode == MipStorageFallbackMode::DynamicIndex) + ? (tsharp.last_level - tsharp.base_level + 1) + : 1; + } }; using ImageResourceList = boost::container::static_vector; diff --git a/src/shader_recompiler/specialization.h b/src/shader_recompiler/specialization.h index 4f6bb44bf..fa14583af 100644 --- a/src/shader_recompiler/specialization.h +++ b/src/shader_recompiler/specialization.h @@ -52,6 +52,8 @@ struct ImageSpecialization { bool is_srgb = false; AmdGpu::CompMapping dst_select{}; AmdGpu::NumberConversion num_conversion{}; + // FIXME any pipeline cache changes needed? + u32 num_bindings = 0; bool operator==(const ImageSpecialization&) const = default; }; @@ -133,7 +135,7 @@ struct StageSpecialization { } }); ForEachSharp(binding, images, info->images, - [](auto& spec, const auto& desc, AmdGpu::Image sharp) { + [&](auto& spec, const auto& desc, AmdGpu::Image sharp) { spec.type = sharp.GetViewType(desc.is_array); spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt()); spec.is_storage = desc.is_written; @@ -144,6 +146,7 @@ struct StageSpecialization { spec.is_srgb = sharp.GetNumberFmt() == AmdGpu::NumberFormat::Srgb; } spec.num_conversion = sharp.GetNumberConversion(); + spec.num_bindings = desc.NumBindings(*info); }); ForEachSharp(binding, fmasks, info->fmasks, [](auto& spec, const auto& desc, AmdGpu::Image sharp) { diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index eecd416d1..ba0a3afa2 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -48,13 +48,15 @@ ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler, }); } for (const auto& image : info->images) { + const u32 num_bindings = image.NumBindings(*info); bindings.push_back({ - .binding = binding++, + .binding = binding, .descriptorType = image.is_written ? vk::DescriptorType::eStorageImage : vk::DescriptorType::eSampledImage, - .descriptorCount = 1, + .descriptorCount = num_bindings, .stageFlags = vk::ShaderStageFlagBits::eCompute, }); + binding += num_bindings; } for (const auto& sampler : info->samplers) { bindings.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 242c9b6f2..bc9ef571b 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -457,13 +457,15 @@ void GraphicsPipeline::BuildDescSetLayout(bool preloading) { }); } for (const auto& image : stage->images) { + const u32 num_bindings = image.NumBindings(*stage); bindings.push_back({ - .binding = binding++, + .binding = binding, .descriptorType = image.is_written ? vk::DescriptorType::eStorageImage : vk::DescriptorType::eSampledImage, - .descriptorCount = 1, + .descriptorCount = num_bindings, .stageFlags = stage_bit, }); + binding += num_bindings; } for (const auto& sampler : stage->samplers) { bindings.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 1b0af1d17..fdf6b3f2d 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -246,7 +246,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, .support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32), .support_fp32_round_to_zero = bool(vk12_props.shaderRoundingModeRTZFloat32), .support_legacy_vertex_attributes = instance_.IsLegacyVertexAttributesSupported(), - .supports_image_load_store_lod = instance_.IsImageLoadStoreLodSupported(), + .supports_image_load_store_lod = /*instance_.IsImageLoadStoreLodSupported()*/ false, // TEST .supports_native_cube_calc = instance_.IsAmdGcnShaderSupported(), .supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(), // TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed. diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 737c9feed..80af19372 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -662,6 +662,13 @@ void Rasterizer::BindBuffers(const Shader::Info& stage, Shader::Backend::Binding void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindings& binding) { image_bindings.clear(); + const u32 first_image_idx = image_infos.size(); + // For loading/storing to explicit mip levels, when no native instruction support, bind an array + // of descriptors consecutively, 1 for each mip level. The shader can index this with LOD + // operand. + // This array holds the size of each consecutive array with the number of bindings consumed. + // This is currently always 1 for anything other than mip fallback arrays. + boost::container::small_vector image_descriptor_array_sizes; for (const auto& image_desc : stage.images) { const auto tsharp = image_desc.GetSharp(stage); @@ -671,25 +678,43 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin if (tsharp.GetDataFmt() == AmdGpu::DataFormat::FormatInvalid) { image_bindings.emplace_back(std::piecewise_construct, std::tuple{}, std::tuple{}); + image_descriptor_array_sizes.push_back(1); continue; } - auto& [image_id, desc] = image_bindings.emplace_back(std::piecewise_construct, std::tuple{}, - std::tuple{tsharp, image_desc}); - image_id = texture_cache.FindImage(desc); - auto* image = &texture_cache.GetImage(image_id); - if (image->depth_id) { - // If this image has an associated depth image, it's a stencil attachment. - // Redirect the access to the actual depth-stencil buffer. - image_id = image->depth_id; - image = &texture_cache.GetImage(image_id); + const Shader::MipStorageFallbackMode mip_fallback_mode = image_desc.mip_fallback_mode; + const u32 num_bindings = image_desc.NumBindings(stage); + + for (auto i = 0; i < num_bindings; i++) { + auto& [image_id, desc] = image_bindings.emplace_back( + std::piecewise_construct, std::tuple{}, std::tuple{tsharp, image_desc}); + + if (mip_fallback_mode == Shader::MipStorageFallbackMode::ConstantIndex) { + ASSERT(num_bindings == 1); + desc.view_info.range.base.level += image_desc.constant_mip_index; + desc.view_info.range.extent.levels = 1; + } else if (mip_fallback_mode == Shader::MipStorageFallbackMode::DynamicIndex) { + desc.view_info.range.base.level += i; + desc.view_info.range.extent.levels = 1; + } + + image_id = texture_cache.FindImage(desc); + auto* image = &texture_cache.GetImage(image_id); + if (image->depth_id) { + // If this image has an associated depth image, it's a stencil attachment. + // Redirect the access to the actual depth-stencil buffer. + image_id = image->depth_id; + image = &texture_cache.GetImage(image_id); + } + if (image->binding.is_bound) { + // The image is already bound. In case if it is about to be used as storage we + // need to force general layout on it. + image->binding.force_general |= image_desc.is_written; + } + image->binding.is_bound = 1u; } - if (image->binding.is_bound) { - // The image is already bound. In case if it is about to be used as storage we need - // to force general layout on it. - image->binding.force_general |= image_desc.is_written; - } - image->binding.is_bound = 1u; + + image_descriptor_array_sizes.push_back(num_bindings); } // Second pass to re-bind images that were updated after binding @@ -749,16 +774,26 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, image.backing->state.layout); } + } + u32 image_info_idx = first_image_idx; + u32 image_binding_idx = 0; + for (u32 array_size : image_descriptor_array_sizes) { + const auto& [_, desc] = image_bindings[image_binding_idx]; + const bool is_storage = desc.type == VideoCore::TextureCache::BindingType::Storage; set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding.unified++, + .dstBinding = binding.unified, .dstArrayElement = 0, - .descriptorCount = 1, + .descriptorCount = array_size, .descriptorType = is_storage ? vk::DescriptorType::eStorageImage : vk::DescriptorType::eSampledImage, - .pImageInfo = &image_infos.back(), + .pImageInfo = &image_infos[image_info_idx], }); + + image_info_idx += array_size; + image_binding_idx += array_size; + binding.unified += array_size; } for (const auto& sampler : stage.samplers) { From 6e843d0c4ba977d584d04698d20cf2570131af00 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 17 Mar 2026 22:18:26 +0200 Subject: [PATCH 35/40] feeling dangerous , let's re-enable lod where supported --- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index fdf6b3f2d..1b0af1d17 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -246,7 +246,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, .support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32), .support_fp32_round_to_zero = bool(vk12_props.shaderRoundingModeRTZFloat32), .support_legacy_vertex_attributes = instance_.IsLegacyVertexAttributesSupported(), - .supports_image_load_store_lod = /*instance_.IsImageLoadStoreLodSupported()*/ false, // TEST + .supports_image_load_store_lod = instance_.IsImageLoadStoreLodSupported(), .supports_native_cube_calc = instance_.IsAmdGcnShaderSupported(), .supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(), // TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed. From 9a3e7b097c9d0153143a5f4ce3fbb245275a7f16 Mon Sep 17 00:00:00 2001 From: rosenkolev1 <50500415+rosenkolev1@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:40:37 +0200 Subject: [PATCH 36/40] Make thread TidCounter atomic (#4133) --- src/core/libraries/kernel/threads/pthread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp index f97451154..3742db5cf 100644 --- a/src/core/libraries/kernel/threads/pthread.cpp +++ b/src/core/libraries/kernel/threads/pthread.cpp @@ -242,7 +242,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt new_thread->attr.sched_policy = curthread->attr.sched_policy; } - static int TidCounter = 1; + static std::atomic TidCounter = 1; new_thread->tid = ++TidCounter; if (new_thread->attr.stackaddr_attr == nullptr) { From 2ca342970a8c5f0a03ae026cb9004d6a49794b75 Mon Sep 17 00:00:00 2001 From: Kravickas Date: Wed, 18 Mar 2026 09:05:20 +0100 Subject: [PATCH 37/40] MIP fixes (#4141) * int32-modifiers GCN VOP3 abs/neg modifier bits always operate on the sign bit (bit 31) regardless of instruction type. For integer operands this means: abs = clear bit 31 (x & 0x7FFFFFFF) neg = toggle bit 31 (x ^ 0x80000000) * int64-modifiers Previously GetSrc64 completely ignored input modifiers for integer operands. Now unpacks to two U32s, modifies the high dword's bit 31 (= bit 63 of the 64-bit value), and repacks. * V_MUL_LEGACY_F32 GCN V_MUL_LEGACY_F32: if either source is zero, result is +0.0 regardless of the other operand (even NaN or Inf). Standard IEEE multiply produces NaN for 0*Inf. The fix adds a zero-check select before the multiply. --- .../frontend/translate/translate.cpp | 21 +++++++++++++++++-- .../frontend/translate/translate.h | 1 + .../frontend/translate/vector_alu.cpp | 15 ++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index 3aa70e2ec..611070a86 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -352,10 +352,10 @@ T Translator::GetSrc(const InstOperand& operand) { } } else { if (operand.input_modifier.abs) { - value = ir.IAbs(value); + value = ir.BitwiseAnd(value, ir.Imm32(0x7FFFFFFFu)); } if (operand.input_modifier.neg) { - value = ir.INeg(value); + value = ir.BitwiseXor(value, ir.Imm32(0x80000000u)); } } return value; @@ -453,6 +453,23 @@ T Translator::GetSrc64(const InstOperand& operand) { if (operand.input_modifier.neg) { value = ir.FPNeg(value); } + } else { + // GCN VOP3 abs/neg modifier bits operate on the sign bit (bit 63 for + // 64-bit values). Unpack, modify the high dword's bit 31, repack. + if (operand.input_modifier.abs) { + const auto unpacked = ir.UnpackUint2x32(value); + const auto lo = IR::U32{ir.CompositeExtract(unpacked, 0)}; + const auto hi = IR::U32{ir.CompositeExtract(unpacked, 1)}; + const auto hi_abs = ir.BitwiseAnd(hi, ir.Imm32(0x7FFFFFFFu)); + value = ir.PackUint2x32(ir.CompositeConstruct(lo, hi_abs)); + } + if (operand.input_modifier.neg) { + const auto unpacked = ir.UnpackUint2x32(value); + const auto lo = IR::U32{ir.CompositeExtract(unpacked, 0)}; + const auto hi = IR::U32{ir.CompositeExtract(unpacked, 1)}; + const auto hi_neg = ir.BitwiseXor(hi, ir.Imm32(0x80000000u)); + value = ir.PackUint2x32(ir.CompositeConstruct(lo, hi_neg)); + } } return value; } diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 08b0192f5..5ee75e336 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -153,6 +153,7 @@ public: void V_SUB_F32(const GcnInst& inst); void V_SUBREV_F32(const GcnInst& inst); void V_MUL_F32(const GcnInst& inst); + void V_MUL_LEGACY_F32(const GcnInst& inst); void V_MUL_I32_I24(const GcnInst& inst, bool is_signed); void V_MIN_F32(const GcnInst& inst, bool is_legacy = false); void V_MAX_F32(const GcnInst& inst, bool is_legacy = false); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 08a0f6527..23236b702 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -25,7 +25,7 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { case Opcode::V_MAC_LEGACY_F32: return V_MAC_F32(inst); case Opcode::V_MUL_LEGACY_F32: - return V_MUL_F32(inst); + return V_MUL_LEGACY_F32(inst); case Opcode::V_MUL_F32: return V_MUL_F32(inst); case Opcode::V_MUL_I32_I24: @@ -493,6 +493,19 @@ void Translator::V_MUL_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); } +void Translator::V_MUL_LEGACY_F32(const GcnInst& inst) { + // GCN V_MUL_LEGACY_F32: if either source is zero, the result is +0.0 + // regardless of the other operand (even if NaN or Inf). + // Standard IEEE multiply would produce NaN for 0 * Inf. + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 zero{ir.Imm32(0.0f)}; + const IR::U1 src0_zero{ir.FPEqual(src0, zero)}; + const IR::U1 src1_zero{ir.FPEqual(src1, zero)}; + const IR::U1 either_zero{ir.LogicalOr(src0_zero, src1_zero)}; + SetDst(inst.dst[0], IR::F32{ir.Select(either_zero, zero, ir.FPMul(src0, src1))}); +} + void Translator::V_MUL_I32_I24(const GcnInst& inst, bool is_signed) { const IR::U32 src0{ ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), is_signed)}; From ec1719e4d35947931538771942a5264e7db5e34d Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:58:00 +0100 Subject: [PATCH 38/40] update current firmware version (#4144) --- src/core/libraries/kernel/kernel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/kernel.h b/src/core/libraries/kernel/kernel.h index ce6446129..315af6b06 100644 --- a/src/core/libraries/kernel/kernel.h +++ b/src/core/libraries/kernel/kernel.h @@ -37,7 +37,7 @@ struct OrbisWrapperImpl { #define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl::wrap) -#define CURRENT_FIRMWARE_VERSION 0x13020011 +#define CURRENT_FIRMWARE_VERSION 0x13500011 s32* PS4_SYSV_ABI __Error(); From 78411c4b8a21e55078f8a55ff2fb69636cf63c1d Mon Sep 17 00:00:00 2001 From: Kravickas Date: Wed, 18 Mar 2026 22:09:19 +0100 Subject: [PATCH 39/40] Write after write sync hazard (#4142) * WAW barrier * clang --- src/video_core/texture_cache/image.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 972f028d4..fa28d0c65 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -239,7 +239,13 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout dst_layout, vk::AccessFlags2 ASSERT(subres_idx < subresource_states.size()); auto& state = subresource_states[subres_idx]; - if (state.layout != dst_layout || state.access_mask != dst_mask) { + if (state.layout != dst_layout || state.access_mask != dst_mask || + static_cast(dst_mask & + (vk::AccessFlagBits2::eTransferWrite | + vk::AccessFlagBits2::eShaderWrite | + vk::AccessFlagBits2::eColorAttachmentWrite | + vk::AccessFlagBits2::eDepthStencilAttachmentWrite | + vk::AccessFlagBits2::eMemoryWrite))) { barriers.emplace_back(vk::ImageMemoryBarrier2{ .srcStageMask = state.pl_stage, .srcAccessMask = state.access_mask, @@ -269,7 +275,12 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout dst_layout, vk::AccessFlags2 subresource_states.clear(); } } else { // Full resource transition - if (last_state.layout == dst_layout && last_state.access_mask == dst_mask) { + constexpr auto write_flags = + vk::AccessFlagBits2::eTransferWrite | vk::AccessFlagBits2::eShaderWrite | + vk::AccessFlagBits2::eColorAttachmentWrite | + vk::AccessFlagBits2::eDepthStencilAttachmentWrite | vk::AccessFlagBits2::eMemoryWrite; + const bool is_write = static_cast(dst_mask & write_flags); + if (last_state.layout == dst_layout && last_state.access_mask == dst_mask && !is_write) { return {}; } From f245cf76a7663a5dc434c0a45a0cf3fd4ce3c5f8 Mon Sep 17 00:00:00 2001 From: Kravickas Date: Wed, 18 Mar 2026 22:52:15 +0100 Subject: [PATCH 40/40] waw hotfix (#4146) --- src/video_core/texture_cache/image.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index fa28d0c65..ffa5168c5 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -647,6 +647,10 @@ void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) cmdbuf.copyBufferToImage(buffer, GetImage(), vk::ImageLayout::eTransferDstOptimal, buffer_copies); + + // Match CopyImage: transition to general so shaders can sample the result. + Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } void Image::CopyMip(Image& src_image, u32 mip, u32 slice) {