From 233192fa95c9f08f984328899d73462bdbb17c8d Mon Sep 17 00:00:00 2001 From: TheThunderTurner <64212185+thethunderturner@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:22:21 +0100 Subject: [PATCH 01/14] LIbrary: libSceRudp (#3903) * libSceRudp * use newer glslang * remove module start/stop * register lib, revert glslang upgrade * 2026 * remove default funcions --- CMakeLists.txt | 2 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/libs.cpp | 2 + src/core/libraries/rudp/rudp.cpp | 218 +++++++++++++++++++++++++++++++ src/core/libraries/rudp/rudp.h | 50 +++++++ 6 files changed, 274 insertions(+) create mode 100644 src/core/libraries/rudp/rudp.cpp create mode 100644 src/core/libraries/rudp/rudp.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 99aca5268..e3ac31f17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -424,6 +424,8 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/rtc/rtc.cpp src/core/libraries/rtc/rtc.h src/core/libraries/rtc/rtc_error.h + src/core/libraries/rudp/rudp.cpp + src/core/libraries/rudp/rudp.h src/core/libraries/disc_map/disc_map.cpp src/core/libraries/disc_map/disc_map.h src/core/libraries/disc_map/disc_map_codes.h diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index fd8386aff..da683d9d1 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -115,6 +115,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, LibCInternal) \ SUB(Lib, AppContent) \ SUB(Lib, Rtc) \ + SUB(Lib, Rudp) \ SUB(Lib, DiscMap) \ SUB(Lib, Png) \ SUB(Lib, Jpeg) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 82db477ed..513ca4535 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -82,6 +82,7 @@ enum class Class : u8 { Lib_LibCInternal, ///< The LibCInternal implementation. Lib_AppContent, ///< The LibSceAppContent implementation. Lib_Rtc, ///< The LibSceRtc implementation. + Lib_Rudp, ///< The LibSceRudp implementation. Lib_DiscMap, ///< The LibSceDiscMap implementation. Lib_Png, ///< The LibScePng implementation. Lib_Jpeg, ///< The LibSceJpeg implementation. diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 1f7ecb75e..4ac8532db 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -48,6 +48,7 @@ #include "core/libraries/razor_cpu/razor_cpu.h" #include "core/libraries/remote_play/remoteplay.h" #include "core/libraries/rtc/rtc.h" +#include "core/libraries/rudp/rudp.h" #include "core/libraries/save_data/dialog/savedatadialog.h" #include "core/libraries/save_data/savedata.h" #include "core/libraries/screenshot/screenshot.h" @@ -141,6 +142,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::CompanionUtil::RegisterLib(sym); Libraries::Voice::RegisterLib(sym); Libraries::Rtc::RegisterLib(sym); + Libraries::Rudp::RegisterLib(sym); Libraries::VrTracker::RegisterLib(sym); // Loading libSceSsl is locked behind a title workaround that currently applies to nothing. diff --git a/src/core/libraries/rudp/rudp.cpp b/src/core/libraries/rudp/rudp.cpp new file mode 100644 index 000000000..2dfb66f64 --- /dev/null +++ b/src/core/libraries/rudp/rudp.cpp @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/libraries/rudp/rudp.h" + +namespace Libraries::Rudp { + +s32 PS4_SYSV_ABI sceRudpAccept() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpActivate() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpBind() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpCreateContext() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpEnableInternalIOThread() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpEnableInternalIOThread2() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpEnd() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpFlush() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetContextStatus() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetLocalInfo() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetMaxSegmentSize() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetNumberOfPacketsToRead() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetOption() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetRemoteInfo() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetSizeReadable() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetSizeWritable() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetStatus() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpInit() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpInitiate() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpListen() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpNetFlush() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpNetReceived() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollCancel() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollControl() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollCreate() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollDestroy() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollWait() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpProcessEvents() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpRead() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpSetEventHandler() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpSetMaxSegmentSize() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpSetOption() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpTerminate() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpWrite() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("uQiK7fjU6y8", "libSceRudp", 1, "libSceRudp", sceRudpAccept); + LIB_FUNCTION("J-6d0WTjzMc", "libSceRudp", 1, "libSceRudp", sceRudpActivate); + LIB_FUNCTION("l4SLBpKUDK4", "libSceRudp", 1, "libSceRudp", sceRudpBind); + LIB_FUNCTION("CAbbX6BuQZ0", "libSceRudp", 1, "libSceRudp", sceRudpCreateContext); + LIB_FUNCTION("6PBNpsgyaxw", "libSceRudp", 1, "libSceRudp", sceRudpEnableInternalIOThread); + LIB_FUNCTION("fJ51weR1WAI", "libSceRudp", 1, "libSceRudp", sceRudpEnableInternalIOThread2); + LIB_FUNCTION("3hBvwqEwqj8", "libSceRudp", 1, "libSceRudp", sceRudpEnd); + LIB_FUNCTION("Ms0cLK8sTtE", "libSceRudp", 1, "libSceRudp", sceRudpFlush); + LIB_FUNCTION("wIJsiqY+BMk", "libSceRudp", 1, "libSceRudp", sceRudpGetContextStatus); + LIB_FUNCTION("2G7-vVz9SIg", "libSceRudp", 1, "libSceRudp", sceRudpGetLocalInfo); + LIB_FUNCTION("vfrL8gPlm2Y", "libSceRudp", 1, "libSceRudp", sceRudpGetMaxSegmentSize); + LIB_FUNCTION("Px0miD2LuW0", "libSceRudp", 1, "libSceRudp", sceRudpGetNumberOfPacketsToRead); + LIB_FUNCTION("mCQIhSmCP6o", "libSceRudp", 1, "libSceRudp", sceRudpGetOption); + LIB_FUNCTION("Qignjmfgha0", "libSceRudp", 1, "libSceRudp", sceRudpGetRemoteInfo); + LIB_FUNCTION("sAZqO2+5Qqo", "libSceRudp", 1, "libSceRudp", sceRudpGetSizeReadable); + LIB_FUNCTION("fRc1ahQppR4", "libSceRudp", 1, "libSceRudp", sceRudpGetSizeWritable); + LIB_FUNCTION("i3STzxuwPx0", "libSceRudp", 1, "libSceRudp", sceRudpGetStatus); + LIB_FUNCTION("amuBfI-AQc4", "libSceRudp", 1, "libSceRudp", sceRudpInit); + LIB_FUNCTION("szEVu+edXV4", "libSceRudp", 1, "libSceRudp", sceRudpInitiate); + LIB_FUNCTION("tYVWcWDnctE", "libSceRudp", 1, "libSceRudp", sceRudpListen); + LIB_FUNCTION("+BJ9svDmjYs", "libSceRudp", 1, "libSceRudp", sceRudpNetFlush); + LIB_FUNCTION("vPzJldDSxXc", "libSceRudp", 1, "libSceRudp", sceRudpNetReceived); + LIB_FUNCTION("yzeXuww-UWg", "libSceRudp", 1, "libSceRudp", sceRudpPollCancel); + LIB_FUNCTION("haMpc7TFx0A", "libSceRudp", 1, "libSceRudp", sceRudpPollControl); + LIB_FUNCTION("MVbmLASjn5M", "libSceRudp", 1, "libSceRudp", sceRudpPollCreate); + LIB_FUNCTION("LjwbHpEeW0A", "libSceRudp", 1, "libSceRudp", sceRudpPollDestroy); + LIB_FUNCTION("M6ggviwXpLs", "libSceRudp", 1, "libSceRudp", sceRudpPollWait); + LIB_FUNCTION("9U9m1YH0ScQ", "libSceRudp", 1, "libSceRudp", sceRudpProcessEvents); + LIB_FUNCTION("rZqWV3eXgOA", "libSceRudp", 1, "libSceRudp", sceRudpRead); + LIB_FUNCTION("SUEVes8gvmw", "libSceRudp", 1, "libSceRudp", sceRudpSetEventHandler); + LIB_FUNCTION("beAsSTVWVPQ", "libSceRudp", 1, "libSceRudp", sceRudpSetMaxSegmentSize); + LIB_FUNCTION("0yzYdZf0IwE", "libSceRudp", 1, "libSceRudp", sceRudpSetOption); + LIB_FUNCTION("OMYRTU0uc4w", "libSceRudp", 1, "libSceRudp", sceRudpTerminate); + LIB_FUNCTION("KaPL3fbTLCA", "libSceRudp", 1, "libSceRudp", sceRudpWrite); +}; + +} // namespace Libraries::Rudp \ No newline at end of file diff --git a/src/core/libraries/rudp/rudp.h b/src/core/libraries/rudp/rudp.h new file mode 100644 index 000000000..22d2576a2 --- /dev/null +++ b/src/core/libraries/rudp/rudp.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Rudp { + +s32 PS4_SYSV_ABI sceRudpAccept(); +s32 PS4_SYSV_ABI sceRudpActivate(); +s32 PS4_SYSV_ABI sceRudpBind(); +s32 PS4_SYSV_ABI sceRudpCreateContext(); +s32 PS4_SYSV_ABI sceRudpEnableInternalIOThread(); +s32 PS4_SYSV_ABI sceRudpEnableInternalIOThread2(); +s32 PS4_SYSV_ABI sceRudpEnd(); +s32 PS4_SYSV_ABI sceRudpFlush(); +s32 PS4_SYSV_ABI sceRudpGetContextStatus(); +s32 PS4_SYSV_ABI sceRudpGetLocalInfo(); +s32 PS4_SYSV_ABI sceRudpGetMaxSegmentSize(); +s32 PS4_SYSV_ABI sceRudpGetNumberOfPacketsToRead(); +s32 PS4_SYSV_ABI sceRudpGetOption(); +s32 PS4_SYSV_ABI sceRudpGetRemoteInfo(); +s32 PS4_SYSV_ABI sceRudpGetSizeReadable(); +s32 PS4_SYSV_ABI sceRudpGetSizeWritable(); +s32 PS4_SYSV_ABI sceRudpGetStatus(); +s32 PS4_SYSV_ABI sceRudpInit(); +s32 PS4_SYSV_ABI sceRudpInitiate(); +s32 PS4_SYSV_ABI sceRudpListen(); +s32 PS4_SYSV_ABI sceRudpNetFlush(); +s32 PS4_SYSV_ABI sceRudpNetReceived(); +s32 PS4_SYSV_ABI sceRudpPollCancel(); +s32 PS4_SYSV_ABI sceRudpPollControl(); +s32 PS4_SYSV_ABI sceRudpPollCreate(); +s32 PS4_SYSV_ABI sceRudpPollDestroy(); +s32 PS4_SYSV_ABI sceRudpPollWait(); +s32 PS4_SYSV_ABI sceRudpProcessEvents(); +s32 PS4_SYSV_ABI sceRudpRead(); +s32 PS4_SYSV_ABI sceRudpSetEventHandler(); +s32 PS4_SYSV_ABI sceRudpSetMaxSegmentSize(); +s32 PS4_SYSV_ABI sceRudpSetOption(); +s32 PS4_SYSV_ABI sceRudpTerminate(); +s32 PS4_SYSV_ABI sceRudpWrite(); + +void RegisterLib(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Rudp \ No newline at end of file From f7a473b3917dc5eb0fad0a8289d56e1ccde90b87 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Fri, 9 Jan 2026 18:29:09 +0300 Subject: [PATCH 02/14] read compiled SDK version from eboot (#3905) --- src/core/libraries/kernel/process.cpp | 8 +++++--- src/core/linker.cpp | 4 ++-- src/emulator.cpp | 29 +++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index e88446e02..4ea7fa062 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -36,9 +36,11 @@ s32 PS4_SYSV_ABI sceKernelGetMainSocId() { } s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) { - s32 version = Common::ElfInfo::Instance().CompiledSdkVer(); - *ver = version; - return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; + if (!ver) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + *ver = Common::ElfInfo::Instance().CompiledSdkVer(); + return ORBIS_OK; } s32 PS4_SYSV_ABI sceKernelGetCpumode() { diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 8d7f9207e..97d766a38 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -180,8 +180,8 @@ s32 Linker::LoadAndStartModule(const std::filesystem::path& path, u64 args, cons } // Retrieve and verify proc param according to libkernel. - u64* param = module->GetProcParam(); - ASSERT_MSG(!param || param[0] >= 0x18, "Invalid module param size: {}", param[0]); + auto* param = module->GetProcParam(); + ASSERT_MSG(!param || param->size >= 0x18, "Invalid module param size: {}", param->size); s32 ret = module->Start(args, argp, param); if (pRes) { *pRes = ret; diff --git a/src/emulator.cpp b/src/emulator.cpp index 6f199649f..4947beeb8 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -74,6 +74,25 @@ Emulator::Emulator() { Emulator::~Emulator() {} +s32 ReadCompiledSdkVersion(const std::filesystem::path& file) { + Core::Loader::Elf elf; + elf.Open(file); + if (!elf.IsElfFile()) { + return 0; + } + const auto elf_pheader = elf.GetProgramHeader(); + auto i_procparam = std::find_if(elf_pheader.begin(), elf_pheader.end(), [](const auto& entry) { + return entry.p_type == PT_SCE_PROCPARAM; + }); + + if (i_procparam != elf_pheader.end()) { + Core::OrbisProcParam param{}; + elf.LoadSegment(u64(¶m), i_procparam->p_offset, i_procparam->p_filesz); + return param.sdk_version; + } + return 0; +} + void Emulator::Run(std::filesystem::path file, std::vector args, std::optional p_game_folder) { Common::SetCurrentThreadName("Main Thread"); @@ -162,6 +181,9 @@ void Emulator::Run(std::filesystem::path file, std::vector args, } } + auto guest_eboot_path = "/app0/" + eboot_name.generic_string(); + const auto eboot_path = mnt->GetHostPath(guest_eboot_path); + auto& game_info = Common::ElfInfo::Instance(); game_info.initialized = true; game_info.game_serial = id; @@ -169,7 +191,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, game_info.app_ver = app_version; game_info.firmware_ver = fw_version & 0xFFF00000; game_info.raw_firmware_ver = fw_version; - game_info.sdk_ver = sdk_version; + game_info.sdk_ver = ReadCompiledSdkVersion(eboot_path); game_info.psf_attributes = psf_attributes; const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png"); @@ -241,7 +263,8 @@ void Emulator::Run(std::filesystem::path file, std::vector args, if (param_sfo_exists) { LOG_INFO(Loader, "Game id: {} Title: {}", id, title); LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version); - LOG_INFO(Loader, "Compiled SDK version: {:#x}", sdk_version); + LOG_INFO(Loader, "param.sfo SDK version: {:#x}", sdk_version); + LOG_INFO(Loader, "eboot SDK version: {:#x}", game_info.sdk_ver); LOG_INFO(Loader, "PSVR Supported: {}", (bool)psf_attributes.support_ps_vr.Value()); LOG_INFO(Loader, "PSVR Required: {}", (bool)psf_attributes.require_ps_vr.Value()); } @@ -339,8 +362,6 @@ void Emulator::Run(std::filesystem::path file, std::vector args, Libraries::InitHLELibs(&linker->GetHLESymbols()); // Load the module with the linker - auto guest_eboot_path = "/app0/" + eboot_name.generic_string(); - const auto eboot_path = mnt->GetHostPath(guest_eboot_path); if (linker->LoadModule(eboot_path) == -1) { LOG_CRITICAL(Loader, "Failed to load game's eboot.bin: {}", Common::FS::PathToUTF8String(std::filesystem::absolute(eboot_path))); From be99cb4d5bfd386eeb0df0af033c99f8ef5deb71 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 9 Jan 2026 19:42:05 +0200 Subject: [PATCH 03/14] Emulator state (#3906) * moved getShowFpsCounter to emu state * more state variables * fps counter is back * removed fpscolor --- CMakeLists.txt | 2 ++ src/common/config.cpp | 25 ----------------------- src/common/config.h | 3 --- src/common/memory_patcher.cpp | 5 +++-- src/core/devtools/layer.cpp | 15 ++++++-------- src/core/emulator_state.cpp | 37 +++++++++++++++++++++++++++++++++++ src/core/emulator_state.h | 29 +++++++++++++++++++++++++++ src/core/ipc/ipc.cpp | 5 +++-- src/main.cpp | 7 +++++-- 9 files changed, 85 insertions(+), 43 deletions(-) create mode 100644 src/core/emulator_state.cpp create mode 100644 src/core/emulator_state.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e3ac31f17..ef5e935a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -840,6 +840,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/thread.h src/core/tls.cpp src/core/tls.h + src/core/emulator_state.cpp + src/core/emulator_state.h ) if (ARCHITECTURE STREQUAL "x86_64") diff --git a/src/common/config.cpp b/src/common/config.cpp index 1af326af7..eac463d0a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -198,7 +198,6 @@ static ConfigEntry pipelineCacheArchive(false); static ConfigEntry isDebugDump(false); static ConfigEntry isShaderDebug(false); static ConfigEntry isSeparateLogFilesEnabled(false); -static ConfigEntry isFpsColor(true); static ConfigEntry showFpsCounter(false); static ConfigEntry logEnabled(true); @@ -223,16 +222,6 @@ static string config_version = Common::g_scm_rev; // These entries aren't stored in the config static bool overrideControllerColor = false; static int controllerCustomColorRGB[3] = {0, 0, 255}; -static bool isGameRunning = false; -static bool load_auto_patches = true; - -bool getGameRunning() { - return isGameRunning; -} - -void setGameRunning(bool running) { - isGameRunning = running; -} std::filesystem::path getSysModulesPath() { if (sys_modules_path.empty()) { @@ -463,10 +452,6 @@ bool isPipelineCacheArchived() { return pipelineCacheArchive.get(); } -bool fpsColor() { - return isFpsColor.get(); -} - bool getShowFpsCounter() { return showFpsCounter.get(); } @@ -855,13 +840,6 @@ void setUsbDeviceBackend(int value, bool is_game_specific) { usbDeviceBackend.set(value, is_game_specific); } -bool getLoadAutoPatches() { - return load_auto_patches; -} -void setLoadAutoPatches(bool enable) { - load_auto_patches = enable; -} - void load(const std::filesystem::path& path, bool is_game_specific) { // If the configuration file does not exist, create it and return, unless it is game specific std::error_code error; @@ -977,7 +955,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { isDebugDump.setFromToml(debug, "DebugDump", is_game_specific); isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific); isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific); - isFpsColor.setFromToml(debug, "FPSColor", is_game_specific); showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific); logEnabled.setFromToml(debug, "logEnabled", is_game_specific); current_version = toml::find_or(debug, "ConfigVersion", current_version); @@ -1197,7 +1174,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value; data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value; data["GPU"]["patchShaders"] = shouldPatchShaders.base_value; - data["Debug"]["FPSColor"] = isFpsColor.base_value; data["Debug"]["showFpsCounter"] = showFpsCounter.base_value; } @@ -1306,7 +1282,6 @@ void setDefaultValues(bool is_game_specific) { internalScreenHeight.base_value = 720; // Debug - isFpsColor.base_value = true; showFpsCounter.base_value = false; } } diff --git a/src/common/config.h b/src/common/config.h index 2bd65b783..2a95e6cf0 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -125,7 +125,6 @@ int getSpecialPadClass(); bool getPSNSignedIn(); void setPSNSignedIn(bool sign, bool is_game_specific = false); bool patchShaders(); // no set -bool fpsColor(); // no set bool getShowFpsCounter(); void setShowFpsCounter(bool enable, bool is_game_specific = false); bool isNeoModeConsole(); @@ -154,8 +153,6 @@ void setConnectedToNetwork(bool enable, bool is_game_specific = false); void setUserName(const std::string& name, bool is_game_specific = false); std::filesystem::path getSysModulesPath(); void setSysModulesPath(const std::filesystem::path& path); -bool getLoadAutoPatches(); -void setLoadAutoPatches(bool enable); enum UsbBackendType : int { Real, SkylandersPortal, InfinityBase, DimensionsToypad }; int getUsbDeviceBackend(); diff --git a/src/common/memory_patcher.cpp b/src/common/memory_patcher.cpp index ad737dab4..a7c020246 100644 --- a/src/common/memory_patcher.cpp +++ b/src/common/memory_patcher.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -12,6 +12,7 @@ #include "common/elf_info.h" #include "common/logging/log.h" #include "common/path_util.h" +#include "core/emulator_state.h" #include "core/file_format/psf.h" #include "memory_patcher.h" @@ -192,7 +193,7 @@ void OnGameLoaded() { } else { ApplyPatchesFromXML(file_path); } - } else if (Config::getLoadAutoPatches()) { + } else if (EmulatorState::GetInstance()->IsAutoPatchesLoadEnabled()) { for (auto const& repo : std::filesystem::directory_iterator(patch_dir)) { if (!repo.is_directory()) { continue; diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index cfa950568..928040fec 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "layer.h" @@ -11,6 +11,7 @@ #include "common/singleton.h" #include "common/types.h" #include "core/debug_state.h" +#include "core/emulator_state.h" #include "imgui/imgui_std.h" #include "imgui_internal.h" #include "options.h" @@ -273,14 +274,10 @@ void L::DrawAdvanced() { void L::DrawSimple() { const float frameRate = DebugState.Framerate; - if (Config::fpsColor()) { - if (frameRate < 10) { - PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red - } else if (frameRate >= 10 && frameRate < 20) { - PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange - } else { - PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White - } + if (frameRate < 10) { + PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red + } else if (frameRate >= 10 && frameRate < 20) { + PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange } else { PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White } diff --git a/src/core/emulator_state.cpp b/src/core/emulator_state.cpp new file mode 100644 index 000000000..1f02043a3 --- /dev/null +++ b/src/core/emulator_state.cpp @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "emulator_state.h" + +std::shared_ptr EmulatorState::s_instance = nullptr; +std::mutex EmulatorState::s_mutex; + +EmulatorState::EmulatorState() {} + +EmulatorState::~EmulatorState() {} + +std::shared_ptr EmulatorState::GetInstance() { + std::lock_guard lock(s_mutex); + if (!s_instance) + s_instance = std::make_shared(); + return s_instance; +} + +void EmulatorState::SetInstance(std::shared_ptr instance) { + std::lock_guard lock(s_mutex); + s_instance = instance; +} + +bool EmulatorState::IsGameRunning() const { + return m_running; +} +void EmulatorState::SetGameRunning(bool running) { + m_running = running; +} + +bool EmulatorState::IsAutoPatchesLoadEnabled() const { + return m_load_patches_auto; +} +void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) { + m_load_patches_auto = enable; +} diff --git a/src/core/emulator_state.h b/src/core/emulator_state.h new file mode 100644 index 000000000..c12af5401 --- /dev/null +++ b/src/core/emulator_state.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +class EmulatorState { +public: + EmulatorState(); + ~EmulatorState(); + + static std::shared_ptr GetInstance(); + static void SetInstance(std::shared_ptr instance); + + bool IsGameRunning() const; + void SetGameRunning(bool running); + bool IsAutoPatchesLoadEnabled() const; + void SetAutoPatchesLoadEnabled(bool enable); + +private: + static std::shared_ptr s_instance; + static std::mutex s_mutex; + + // state variables + bool m_running = false; + bool m_load_patches_auto = true; +}; \ No newline at end of file diff --git a/src/core/ipc/ipc.cpp b/src/core/ipc/ipc.cpp index 08cf3bbb2..ea7cd38b4 100644 --- a/src/core/ipc/ipc.cpp +++ b/src/core/ipc/ipc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "ipc.h" @@ -14,6 +14,7 @@ #include "common/types.h" #include "core/debug_state.h" #include "core/debugger.h" +#include "core/emulator_state.h" #include "core/libraries/audio/audioout.h" #include "input/input_handler.h" #include "sdl_window.h" @@ -71,7 +72,7 @@ void IPC::Init() { return; } - Config::setLoadAutoPatches(false); + EmulatorState::GetInstance()->SetAutoPatchesLoadEnabled(false); input_thread = std::jthread([this] { Common::SetCurrentThreadName("IPC Read thread"); diff --git a/src/main.cpp b/src/main.cpp index e19e8a938..aa3f4de45 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -8,6 +8,7 @@ #include "system_error" #include "unordered_map" +#include #include #include "common/config.h" #include "common/logging/backend.h" @@ -27,7 +28,9 @@ int main(int argc, char* argv[]) { SetConsoleOutputCP(CP_UTF8); #endif IPC::Instance().Init(); - + // Init emulator state + std::shared_ptr m_emu_state = std::make_shared(); + EmulatorState::SetInstance(m_emu_state); // Load configurations const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(user_dir / "config.toml"); From f42a566cefbb085fcdb5219fcf238c55c01f8bc5 Mon Sep 17 00:00:00 2001 From: marecl Date: Fri, 9 Jan 2026 23:35:43 +0100 Subject: [PATCH 04/14] Dirents backport (#3876) * lseek for directories behaves correctly when final index is smaller than 0 (EINVAL) Backported and improved dirents from QFS Normal directory dirents update on change * PFS moves pointer to end when last dirent is returned * Correct entry type in PFS directory --- .../file_sys/directories/base_directory.cpp | 32 +++ .../file_sys/directories/base_directory.h | 24 +- .../file_sys/directories/normal_directory.cpp | 221 ++++++++-------- .../file_sys/directories/normal_directory.h | 18 +- .../file_sys/directories/pfs_directory.cpp | 236 ++++++------------ src/core/file_sys/directories/pfs_directory.h | 16 +- 6 files changed, 260 insertions(+), 287 deletions(-) diff --git a/src/core/file_sys/directories/base_directory.cpp b/src/core/file_sys/directories/base_directory.cpp index c709da6a2..75f67577c 100644 --- a/src/core/file_sys/directories/base_directory.cpp +++ b/src/core/file_sys/directories/base_directory.cpp @@ -5,6 +5,7 @@ #include "common/singleton.h" #include "core/file_sys/directories/base_directory.h" #include "core/file_sys/fs.h" +#include "core/libraries/kernel/orbis_error.h" namespace Core::Directories { @@ -12,4 +13,35 @@ BaseDirectory::BaseDirectory() = default; BaseDirectory::~BaseDirectory() = default; +s64 BaseDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) { + s64 bytes_read = 0; + for (s32 i = 0; i < iovcnt; i++) { + const s64 result = read(iov[i].iov_base, iov[i].iov_len); + if (result < 0) { + return result; + } + bytes_read += result; + } + return bytes_read; +} + +s64 BaseDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) { + const u64 old_file_pointer = file_offset; + file_offset = offset; + const s64 bytes_read = readv(iov, iovcnt); + file_offset = old_file_pointer; + return bytes_read; +} + +s64 BaseDirectory::lseek(s64 offset, s32 whence) { + + s64 file_offset_new = ((0 == whence) * offset) + ((1 == whence) * (file_offset + offset)) + + ((2 == whence) * (directory_size + offset)); + if (file_offset_new < 0) + return ORBIS_KERNEL_ERROR_EINVAL; + + file_offset = file_offset_new; + return file_offset; +} + } // namespace Core::Directories \ No newline at end of file diff --git a/src/core/file_sys/directories/base_directory.h b/src/core/file_sys/directories/base_directory.h index b412865a2..832b8ac40 100644 --- a/src/core/file_sys/directories/base_directory.h +++ b/src/core/file_sys/directories/base_directory.h @@ -19,6 +19,17 @@ struct OrbisKernelDirent; namespace Core::Directories { class BaseDirectory { +protected: + static inline u32 fileno_pool{10}; + + static u32 next_fileno() { + return ++fileno_pool; + } + + s64 file_offset = 0; + u64 directory_size = 0; + std::vector dirent_cache_bin{}; + public: explicit BaseDirectory(); @@ -28,13 +39,8 @@ public: return ORBIS_KERNEL_ERROR_EBADF; } - virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) { - return ORBIS_KERNEL_ERROR_EBADF; - } - - virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) { - return ORBIS_KERNEL_ERROR_EBADF; - } + virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt); + virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset); virtual s64 write(const void* buf, u64 nbytes) { return ORBIS_KERNEL_ERROR_EBADF; @@ -48,9 +54,7 @@ public: return ORBIS_KERNEL_ERROR_EBADF; } - virtual s64 lseek(s64 offset, s32 whence) { - return ORBIS_KERNEL_ERROR_EBADF; - } + virtual s64 lseek(s64 offset, s32 whence); virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) { return ORBIS_KERNEL_ERROR_EBADF; diff --git a/src/core/file_sys/directories/normal_directory.cpp b/src/core/file_sys/directories/normal_directory.cpp index a7d76074a..3ed7c9492 100644 --- a/src/core/file_sys/directories/normal_directory.cpp +++ b/src/core/file_sys/directories/normal_directory.cpp @@ -15,111 +15,30 @@ std::shared_ptr NormalDirectory::Create(std::string_view guest_di std::make_shared(guest_directory)); } -NormalDirectory::NormalDirectory(std::string_view guest_directory) { - auto* mnt = Common::Singleton::Instance(); - - static s32 fileno = 0; - mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) { - auto& dirent = dirents.emplace_back(); - dirent.d_fileno = ++fileno; - dirent.d_type = (ent_is_file ? 8 : 4); - strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1); - dirent.d_namlen = ent_path.filename().string().size(); - - // Calculate the appropriate length for this dirent. - // Account for the null terminator in d_name too. - dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) + - sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) + - (dirent.d_namlen + 1), - 4); - - directory_size += dirent.d_reclen; - }); - - // The last entry of a normal directory should have d_reclen covering the remaining data. - // Since the dirents of a folder are constant by this point, we can modify the last dirent - // before creating the emulated file buffer. - const u64 filler_count = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT) - directory_size; - dirents[dirents.size() - 1].d_reclen += filler_count; - - // Reading from standard directories seems to be based around file pointer logic. - // Keep an internal buffer representing the raw contents of this file descriptor, - // then emulate the various read functions with that. - directory_size = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT); - data_buffer.reserve(directory_size); - memset(data_buffer.data(), 0, directory_size); - - u8* current_dirent = data_buffer.data(); - for (const NormalDirectoryDirent& dirent : dirents) { - NormalDirectoryDirent* dirent_to_write = - reinterpret_cast(current_dirent); - dirent_to_write->d_fileno = dirent.d_fileno; - - // Using size d_namlen + 1 to account for null terminator. - strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1); - dirent_to_write->d_namlen = dirent.d_namlen; - dirent_to_write->d_reclen = dirent.d_reclen; - dirent_to_write->d_type = dirent.d_type; - - current_dirent += dirent.d_reclen; - } +NormalDirectory::NormalDirectory(std::string_view guest_directory) + : guest_directory(guest_directory) { + RebuildDirents(); } s64 NormalDirectory::read(void* buf, u64 nbytes) { - // Nothing left to read. - if (file_offset >= directory_size) { - return ORBIS_OK; - } + RebuildDirents(); - const s64 remaining_data = directory_size - file_offset; - const s64 bytes = nbytes > remaining_data ? remaining_data : nbytes; + // data is contiguous. read goes like any regular file would: start at offset, read n bytes + // output is always aligned up to 512 bytes with 0s + // offset - classic. however at the end of read any unused (exceeding dirent buffer size) buffer + // space will be left untouched + // reclen always sums up to end of current alignment - std::memcpy(buf, data_buffer.data() + file_offset, bytes); + s64 bytes_available = this->dirent_cache_bin.size() - file_offset; + if (bytes_available <= 0) + return 0; + bytes_available = std::min(bytes_available, static_cast(nbytes)); - file_offset += bytes; - return bytes; -} + // data + memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available); -s64 NormalDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) { - s64 bytes_read = 0; - for (s32 i = 0; i < iovcnt; i++) { - const s64 result = read(iov[i].iov_base, iov[i].iov_len); - if (result < 0) { - return result; - } - bytes_read += result; - } - return bytes_read; -} - -s64 NormalDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, - s64 offset) { - const u64 old_file_pointer = file_offset; - file_offset = offset; - const s64 bytes_read = readv(iov, iovcnt); - file_offset = old_file_pointer; - return bytes_read; -} - -s64 NormalDirectory::lseek(s64 offset, s32 whence) { - switch (whence) { - case 0: { - file_offset = offset; - break; - } - case 1: { - file_offset += offset; - break; - } - case 2: { - file_offset = directory_size + offset; - break; - } - default: { - UNREACHABLE_MSG("lseek with unknown whence {}", whence); - } - } - return file_offset; + file_offset += bytes_available; + return bytes_available; } s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) { @@ -131,10 +50,110 @@ s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) { } s64 NormalDirectory::getdents(void* buf, u64 nbytes, s64* basep) { - if (basep != nullptr) { + RebuildDirents(); + + if (basep) *basep = file_offset; + + // same as others, we just don't need a variable + if (file_offset >= directory_size) + return 0; + + s64 bytes_written = 0; + s64 working_offset = file_offset; + s64 dirent_buffer_offset = 0; + s64 aligned_count = Common::AlignDown(nbytes, 512); + + const u8* dirent_buffer = this->dirent_cache_bin.data(); + while (dirent_buffer_offset < this->dirent_cache_bin.size()) { + const u8* normal_dirent_ptr = dirent_buffer + dirent_buffer_offset; + const NormalDirectoryDirent* normal_dirent = + reinterpret_cast(normal_dirent_ptr); + auto d_reclen = normal_dirent->d_reclen; + + // bad, incomplete or OOB entry + if (normal_dirent->d_namlen == 0) + break; + + if (working_offset >= d_reclen) { + dirent_buffer_offset += d_reclen; + working_offset -= d_reclen; + continue; + } + + if ((bytes_written + d_reclen) > aligned_count) + // dirents are aligned to the last full one + break; + + memcpy(static_cast(buf) + bytes_written, normal_dirent_ptr + working_offset, + d_reclen - working_offset); + bytes_written += d_reclen - working_offset; + dirent_buffer_offset += d_reclen; + working_offset = 0; } - // read behaves identically to getdents for normal directories. - return read(buf, nbytes); + + file_offset += bytes_written; + return bytes_written; } + +void NormalDirectory::RebuildDirents() { + // regenerate only when target wants to read contents again + // no reason for testing - read is always raw and dirents get processed on the go + if (previous_file_offset == file_offset) + return; + previous_file_offset = file_offset; + + constexpr u32 dirent_meta_size = + sizeof(NormalDirectoryDirent::d_fileno) + sizeof(NormalDirectoryDirent::d_type) + + sizeof(NormalDirectoryDirent::d_namlen) + sizeof(NormalDirectoryDirent::d_reclen); + + u64 next_ceiling = 0; + u64 dirent_offset = 0; + u64 last_reclen_offset = 4; + dirent_cache_bin.clear(); + dirent_cache_bin.reserve(512); + + auto* mnt = Common::Singleton::Instance(); + + mnt->IterateDirectory( + guest_directory, [this, &next_ceiling, &dirent_offset, &last_reclen_offset]( + const std::filesystem::path& ent_path, const bool ent_is_file) { + NormalDirectoryDirent tmp{}; + std::string leaf(ent_path.filename().string()); + + // prepare dirent + tmp.d_fileno = BaseDirectory::next_fileno(); + tmp.d_namlen = leaf.size(); + strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1); + tmp.d_type = (ent_is_file ? 0100000 : 0040000) >> 12; + tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 4); + + // next element may break 512 byte alignment + if (tmp.d_reclen + dirent_offset > next_ceiling) { + // align previous dirent's size to the current ceiling + *reinterpret_cast(static_cast(dirent_cache_bin.data()) + + last_reclen_offset) += next_ceiling - dirent_offset; + // set writing pointer to the aligned start position (current ceiling) + dirent_offset = next_ceiling; + // move the ceiling up and zero-out the buffer + next_ceiling += 512; + dirent_cache_bin.resize(next_ceiling); + std::fill(dirent_cache_bin.begin() + dirent_offset, + dirent_cache_bin.begin() + next_ceiling, 0); + } + + // current dirent's reclen position + last_reclen_offset = dirent_offset + 4; + memcpy(dirent_cache_bin.data() + dirent_offset, &tmp, tmp.d_reclen); + dirent_offset += tmp.d_reclen; + }); + + // last reclen, as before + *reinterpret_cast(static_cast(dirent_cache_bin.data()) + last_reclen_offset) += + next_ceiling - dirent_offset; + + // i have no idea if this is the case, but lseek returns size aligned to 512 + directory_size = next_ceiling; +} + } // namespace Core::Directories \ No newline at end of file diff --git a/src/core/file_sys/directories/normal_directory.h b/src/core/file_sys/directories/normal_directory.h index 70e52f581..4fc84cd2a 100644 --- a/src/core/file_sys/directories/normal_directory.h +++ b/src/core/file_sys/directories/normal_directory.h @@ -19,27 +19,23 @@ public: ~NormalDirectory() override = default; virtual s64 read(void* buf, u64 nbytes) override; - virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override; - virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, - s64 offset) override; - virtual s64 lseek(s64 offset, s32 whence) override; virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override; virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override; private: - static constexpr s32 MAX_LENGTH = 255; - static constexpr s64 DIRECTORY_ALIGNMENT = 0x200; +#pragma pack(push, 1) struct NormalDirectoryDirent { u32 d_fileno; u16 d_reclen; u8 d_type; u8 d_namlen; - char d_name[MAX_LENGTH + 1]; + char d_name[256]; }; +#pragma pack(pop) - u64 directory_size = 0; - s64 file_offset = 0; - std::vector data_buffer; - std::vector dirents; + std::string_view guest_directory{}; + s64 previous_file_offset = -1; + + void RebuildDirents(void); }; } // namespace Core::Directories diff --git a/src/core/file_sys/directories/pfs_directory.cpp b/src/core/file_sys/directories/pfs_directory.cpp index fbd97c019..38ceaf345 100644 --- a/src/core/file_sys/directories/pfs_directory.cpp +++ b/src/core/file_sys/directories/pfs_directory.cpp @@ -15,77 +15,49 @@ std::shared_ptr PfsDirectory::Create(std::string_view guest_direc } PfsDirectory::PfsDirectory(std::string_view guest_directory) { + constexpr u32 dirent_meta_size = + sizeof(PfsDirectoryDirent::d_fileno) + sizeof(PfsDirectoryDirent::d_type) + + sizeof(PfsDirectoryDirent::d_namlen) + sizeof(PfsDirectoryDirent::d_reclen); + + dirent_cache_bin.reserve(512); + auto* mnt = Common::Singleton::Instance(); - static s32 fileno = 0; - mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) { - auto& dirent = dirents.emplace_back(); - dirent.d_fileno = ++fileno; - dirent.d_type = (ent_is_file ? 8 : 4); - strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1); - dirent.d_namlen = ent_path.filename().string().size(); + mnt->IterateDirectory( + guest_directory, [this](const std::filesystem::path& ent_path, const bool ent_is_file) { + PfsDirectoryDirent tmp{}; + std::string leaf(ent_path.filename().string()); - // Calculate the appropriate length for this dirent. - // Account for the null terminator in d_name too. - dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) + - sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) + - (dirent.d_namlen + 1), - 8); + tmp.d_fileno = BaseDirectory::next_fileno(); + tmp.d_namlen = leaf.size(); + strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1); + tmp.d_type = ent_is_file ? 2 : 4; + tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 8); + auto dirent_ptr = reinterpret_cast(&tmp); - // To handle some obscure dirents_index behavior, - // keep track of the "actual" length of this directory. - directory_content_size += dirent.d_reclen; - }); + dirent_cache_bin.insert(dirent_cache_bin.end(), dirent_ptr, dirent_ptr + tmp.d_reclen); + }); - directory_size = Common::AlignUp(directory_content_size, DIRECTORY_ALIGNMENT); + directory_size = Common::AlignUp(dirent_cache_bin.size(), 0x10000); } s64 PfsDirectory::read(void* buf, u64 nbytes) { - if (dirents_index >= dirents.size()) { - if (dirents_index < directory_content_size) { - // We need to find the appropriate dirents_index to start from. - s64 data_to_skip = dirents_index; - u64 corrected_index = 0; - while (data_to_skip > 0) { - const auto dirent = dirents[corrected_index++]; - data_to_skip -= dirent.d_reclen; - } - dirents_index = corrected_index; - } else { - // Nothing left to read. - return ORBIS_OK; - } + s64 bytes_available = this->dirent_cache_bin.size() - file_offset; + if (bytes_available <= 0) + return 0; + + bytes_available = std::min(bytes_available, static_cast(nbytes)); + memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available); + + s64 to_fill = + (std::min(directory_size, static_cast(nbytes))) - bytes_available - file_offset; + if (to_fill < 0) { + LOG_ERROR(Kernel_Fs, "Dirent may have leaked {} bytes", -to_fill); + return -to_fill + bytes_available; } - - s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes; - // read on PfsDirectories will always return the maximum possible value. - const u64 bytes_written = bytes_remaining; - memset(buf, 0, bytes_remaining); - - char* current_dirent = static_cast(buf); - PfsDirectoryDirent dirent = dirents[dirents_index]; - while (bytes_remaining > dirent.d_reclen) { - PfsDirectoryDirent* dirent_to_write = reinterpret_cast(current_dirent); - dirent_to_write->d_fileno = dirent.d_fileno; - - // Using size d_namlen + 1 to account for null terminator. - strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1); - dirent_to_write->d_namlen = dirent.d_namlen; - dirent_to_write->d_reclen = dirent.d_reclen; - dirent_to_write->d_type = dirent.d_type; - - current_dirent += dirent.d_reclen; - bytes_remaining -= dirent.d_reclen; - - if (dirents_index == dirents.size() - 1) { - // Currently at the last dirent, so break out of the loop. - dirents_index++; - break; - } - dirent = dirents[++dirents_index]; - } - - return bytes_written; + memset(static_cast(buf) + bytes_available, 0, to_fill); + file_offset += to_fill + bytes_available; + return to_fill + bytes_available; } s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) { @@ -101,62 +73,13 @@ s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovc } s64 PfsDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) { - const u64 old_dirent_index = dirents_index; - dirents_index = 0; - s64 data_to_skip = offset; - // If offset is part-way through one dirent, that dirent is skipped. - while (data_to_skip > 0) { - const auto dirent = dirents[dirents_index++]; - data_to_skip -= dirent.d_reclen; - if (dirents_index == dirents.size()) { - // We've reached the end of the dirents, nothing more can be skipped. - break; - } - } - + const u64 old_file_pointer = file_offset; + file_offset = offset; const s64 bytes_read = readv(iov, iovcnt); - dirents_index = old_dirent_index; + file_offset = old_file_pointer; return bytes_read; } -s64 PfsDirectory::lseek(s64 offset, s32 whence) { - switch (whence) { - // Seek start - case 0: { - dirents_index = 0; - } - case 1: { - // There aren't any dirents left to pass through. - if (dirents_index >= dirents.size()) { - dirents_index = dirents_index + offset; - break; - } - s64 data_to_skip = offset; - while (data_to_skip > 0) { - const auto dirent = dirents[dirents_index++]; - data_to_skip -= dirent.d_reclen; - if (dirents_index == dirents.size()) { - // We've passed through all file dirents. - // Set dirents_index to directory_size + remaining_offset instead. - dirents_index = directory_content_size + data_to_skip; - break; - } - } - break; - } - case 2: { - // Seems like real hardware gives up on tracking dirents_index if you go this route. - dirents_index = directory_size + offset; - break; - } - default: { - UNREACHABLE_MSG("lseek with unknown whence {}", whence); - } - } - - return dirents_index; -} - s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) { stat->st_mode = 0000777u | 0040000u; stat->st_size = directory_size; @@ -166,55 +89,58 @@ s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) { } s64 PfsDirectory::getdents(void* buf, u64 nbytes, s64* basep) { - // basep is set at the start of the function. - if (basep != nullptr) { - *basep = dirents_index; - } + if (basep) + *basep = file_offset; - if (dirents_index >= dirents.size()) { - if (dirents_index < directory_content_size) { - // We need to find the appropriate dirents_index to start from. - s64 data_to_skip = dirents_index; - u64 corrected_index = 0; - while (data_to_skip > 0) { - const auto dirent = dirents[corrected_index++]; - data_to_skip -= dirent.d_reclen; - } - dirents_index = corrected_index; - } else { - // Nothing left to read. - return ORBIS_OK; - } - } - - s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes; - memset(buf, 0, bytes_remaining); + // same as others, we just don't need a variable + if (file_offset >= directory_size) + return 0; u64 bytes_written = 0; - char* current_dirent = static_cast(buf); - // getdents has to convert pfs dirents to normal dirents - PfsDirectoryDirent dirent = dirents[dirents_index]; - while (bytes_remaining > dirent.d_reclen) { - NormalDirectoryDirent* dirent_to_write = - reinterpret_cast(current_dirent); - dirent_to_write->d_fileno = dirent.d_fileno; - strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1); - dirent_to_write->d_namlen = dirent.d_namlen; - dirent_to_write->d_reclen = dirent.d_reclen; - dirent_to_write->d_type = dirent.d_type; + u64 starting_offset = 0; + u64 buffer_position = 0; + while (buffer_position < this->dirent_cache_bin.size()) { + const PfsDirectoryDirent* pfs_dirent = + reinterpret_cast(this->dirent_cache_bin.data() + buffer_position); - current_dirent += dirent.d_reclen; - bytes_remaining -= dirent.d_reclen; - bytes_written += dirent.d_reclen; - - if (dirents_index == dirents.size() - 1) { - // Currently at the last dirent, so set dirents_index appropriately and break. - dirents_index = directory_size; + // bad, incomplete or OOB entry + if (pfs_dirent->d_namlen == 0) break; + + if (starting_offset < file_offset) { + // reading starts from the nearest full dirent + starting_offset += pfs_dirent->d_reclen; + buffer_position = bytes_written + starting_offset; + continue; } - dirent = dirents[++dirents_index]; + + if ((bytes_written + pfs_dirent->d_reclen) > nbytes) + // dirents are aligned to the last full one + break; + + // if this dirent breaks alignment, skip + // dirents are count-aligned here, excess data is simply not written + // if (Common::AlignUp(buffer_position, count) != + // Common::AlignUp(buffer_position + pfs_dirent->d_reclen, count)) + // break; + + // reclen for both is the same despite difference in var sizes, extra 0s are padded after + // the name + NormalDirectoryDirent normal_dirent{}; + normal_dirent.d_fileno = pfs_dirent->d_fileno; + normal_dirent.d_reclen = pfs_dirent->d_reclen; + normal_dirent.d_type = (pfs_dirent->d_type == 2) ? 8 : 4; + normal_dirent.d_namlen = pfs_dirent->d_namlen; + memcpy(normal_dirent.d_name, pfs_dirent->d_name, pfs_dirent->d_namlen); + + memcpy(static_cast(buf) + bytes_written, &normal_dirent, normal_dirent.d_reclen); + bytes_written += normal_dirent.d_reclen; + buffer_position = bytes_written + starting_offset; } + file_offset = (buffer_position >= this->dirent_cache_bin.size()) + ? directory_size + : (file_offset + bytes_written); return bytes_written; } } // namespace Core::Directories \ No newline at end of file diff --git a/src/core/file_sys/directories/pfs_directory.h b/src/core/file_sys/directories/pfs_directory.h index 8f3e8d1f5..23b7e1eb0 100644 --- a/src/core/file_sys/directories/pfs_directory.h +++ b/src/core/file_sys/directories/pfs_directory.h @@ -22,32 +22,28 @@ public: virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override; virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) override; - virtual s64 lseek(s64 offset, s32 whence) override; virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override; virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override; private: - static constexpr s32 MAX_LENGTH = 255; - static constexpr s32 DIRECTORY_ALIGNMENT = 0x10000; +#pragma pack(push, 1) struct PfsDirectoryDirent { u32 d_fileno; u32 d_type; u32 d_namlen; u32 d_reclen; - char d_name[MAX_LENGTH + 1]; + char d_name[256]; }; +#pragma pack(pop) +#pragma pack(push, 1) struct NormalDirectoryDirent { u32 d_fileno; u16 d_reclen; u8 d_type; u8 d_namlen; - char d_name[MAX_LENGTH + 1]; + char d_name[256]; }; - - u64 directory_size = 0; - u64 directory_content_size = 0; - s64 dirents_index = 0; - std::vector dirents; +#pragma pack(pop) }; } // namespace Core::Directories From b84c5ed1108b5935d81beca8318b7c10cc0d97d9 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 10 Jan 2026 09:23:42 -0600 Subject: [PATCH 05/14] Corrected physical base handling for memory pools (#3909) On real hardware, each pool commit can have multiple physical memory allocations, always allocating the lowest free physical addresses first. Currently, since we currently only support one physical mapping per VMA, I create a separate VMA representing each physical allocation we perform. --- src/core/memory.cpp | 161 +++++++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 71 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index b9fd7fd7d..3a4a16933 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -314,35 +314,47 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 prot |= MemoryProt::CpuRead; } - // Carve out the new VMA representing this mapping - const auto new_vma_handle = CarveVMA(mapped_addr, size); - auto& new_vma = new_vma_handle->second; - new_vma.disallow_merge = false; - new_vma.prot = prot; - new_vma.name = "anon"; - new_vma.type = Core::VMAType::Pooled; - new_vma.is_exec = false; - - // Find a suitable physical address + // Find suitable physical addresses auto handle = dmem_map.begin(); - while (handle != dmem_map.end() && - (handle->second.dma_type != Core::DMAType::Pooled || handle->second.size < size)) { + u64 remaining_size = size; + VAddr current_addr = mapped_addr; + while (handle != dmem_map.end() && remaining_size != 0) { + if (handle->second.dma_type != DMAType::Pooled) { + // Non-pooled means it's either not for pool use, or already committed. + handle++; + continue; + } + + // On PS4, commits can make sparse physical mappings. + // For now, it's easier to create separate memory mappings for each physical mapping. + u64 size_to_map = std::min(remaining_size, handle->second.size); + + // Carve out the new VMA representing this mapping + const auto new_vma_handle = CarveVMA(current_addr, size_to_map); + auto& new_vma = new_vma_handle->second; + new_vma.disallow_merge = false; + new_vma.prot = prot; + new_vma.name = "anon"; + new_vma.type = Core::VMAType::Pooled; + new_vma.is_exec = false; + + // Use the start of this area as the physical backing for this mapping. + const auto new_dmem_handle = CarveDmemArea(handle->second.base, size_to_map); + auto& new_dmem_area = new_dmem_handle->second; + new_dmem_area.dma_type = DMAType::Committed; + new_dmem_area.memory_type = mtype; + new_vma.phys_base = new_dmem_area.base; + handle = MergeAdjacent(dmem_map, new_dmem_handle); + + // Perform the mapping + void* out_addr = impl.Map(current_addr, size_to_map, alignment, new_vma.phys_base, false); + TRACK_ALLOC(out_addr, size_to_map, "VMEM"); + + current_addr += size_to_map; + remaining_size -= size_to_map; handle++; } - ASSERT_MSG(handle != dmem_map.end() && handle->second.dma_type == Core::DMAType::Pooled, - "No suitable physical memory areas to map"); - - // Use the start of this area as the physical backing for this mapping. - const auto new_dmem_handle = CarveDmemArea(handle->second.base, size); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Committed; - new_dmem_area.memory_type = mtype; - new_vma.phys_base = new_dmem_area.base; - MergeAdjacent(dmem_map, new_dmem_handle); - - // Perform the mapping - void* out_addr = impl.Map(mapped_addr, size, alignment, new_vma.phys_base, false); - TRACK_ALLOC(out_addr, size, "VMEM"); + ASSERT_MSG(remaining_size == 0, "Unable to map physical memory"); if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); @@ -609,57 +621,64 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) { virtual_addr); std::scoped_lock lk{mutex}; - const auto it = FindVMA(virtual_addr); - const auto& vma_base = it->second; - ASSERT_MSG(vma_base.Contains(virtual_addr, size), - "Existing mapping does not contain requested unmap range"); - - const auto vma_base_addr = vma_base.base; - const auto vma_base_size = vma_base.size; - const auto phys_base = vma_base.phys_base; - const bool is_exec = vma_base.is_exec; - const auto start_in_vma = virtual_addr - vma_base_addr; - const auto type = vma_base.type; - - if (type != VMAType::PoolReserved && type != VMAType::Pooled) { - LOG_ERROR(Kernel_Vmm, "Attempting to decommit non-pooled memory!"); - return ORBIS_KERNEL_ERROR_EINVAL; + // Do an initial search to ensure this decommit is valid. + auto it = FindVMA(virtual_addr); + while (it != vma_map.end() && it->second.base + it->second.size <= virtual_addr + size) { + if (it->second.type != VMAType::PoolReserved && it->second.type != VMAType::Pooled) { + LOG_ERROR(Kernel_Vmm, "Attempting to decommit non-pooled memory!"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + it++; } - if (type == VMAType::Pooled) { - // We always map PoolCommitted memory to GPU, so unmap when decomitting. - if (IsValidGpuMapping(virtual_addr, size)) { - rasterizer->UnmapMemory(virtual_addr, size); + // Loop through all vmas in the area, unmap them. + u64 remaining_size = size; + VAddr current_addr = virtual_addr; + while (remaining_size != 0) { + const auto it = FindVMA(current_addr); + const auto& vma_base = it->second; + const bool is_exec = vma_base.is_exec; + const auto start_in_vma = current_addr - vma_base.base; + const auto size_in_vma = std::min(remaining_size, vma_base.size - start_in_vma); + + if (vma_base.type == VMAType::Pooled) { + // We always map PoolCommitted memory to GPU, so unmap when decomitting. + if (IsValidGpuMapping(current_addr, size_in_vma)) { + rasterizer->UnmapMemory(current_addr, size_in_vma); + } + + // Track how much pooled memory is decommitted + pool_budget += size_in_vma; + + // Re-pool the direct memory used by this mapping + const auto unmap_phys_base = vma_base.phys_base + start_in_vma; + const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, size_in_vma); + auto& new_dmem_area = new_dmem_handle->second; + new_dmem_area.dma_type = DMAType::Pooled; + + // Coalesce with nearby direct memory areas. + MergeAdjacent(dmem_map, new_dmem_handle); } - // Track how much pooled memory is decommitted - pool_budget += size; + if (vma_base.type != VMAType::PoolReserved) { + // Unmap the memory region. + impl.Unmap(vma_base.base, vma_base.size, start_in_vma, start_in_vma + size_in_vma, + vma_base.phys_base, vma_base.is_exec, true, false); + TRACK_FREE(virtual_addr, "VMEM"); + } - // Re-pool the direct memory used by this mapping - const auto unmap_phys_base = phys_base + start_in_vma; - const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, size); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Pooled; + // Mark region as pool reserved and attempt to coalesce it with neighbours. + const auto new_it = CarveVMA(current_addr, size_in_vma); + auto& vma = new_it->second; + vma.type = VMAType::PoolReserved; + vma.prot = MemoryProt::NoAccess; + vma.phys_base = 0; + vma.disallow_merge = false; + vma.name = "anon"; + MergeAdjacent(vma_map, new_it); - // Coalesce with nearby direct memory areas. - MergeAdjacent(dmem_map, new_dmem_handle); - } - - // Mark region as pool reserved and attempt to coalesce it with neighbours. - const auto new_it = CarveVMA(virtual_addr, size); - auto& vma = new_it->second; - vma.type = VMAType::PoolReserved; - vma.prot = MemoryProt::NoAccess; - vma.phys_base = 0; - vma.disallow_merge = false; - vma.name = "anon"; - MergeAdjacent(vma_map, new_it); - - if (type != VMAType::PoolReserved) { - // Unmap the memory region. - impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, - is_exec, true, false); - TRACK_FREE(virtual_addr, "VMEM"); + current_addr += size_in_vma; + remaining_size -= size_in_vma; } return ORBIS_OK; From ed73d2b02cfe9bb5adf741c7ea595bfa3ff0ccbc Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 10 Jan 2026 09:33:28 -0600 Subject: [PATCH 06/14] Libraries: libSceNpWebApi2 stubs (#3908) * libSceNpWebApi2 stubs Based on some brief decompilation, brings some extra Unreal Engine titles futher. * Clang * Suggestions and fixes --- CMakeLists.txt | 2 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/libs.cpp | 2 + src/core/libraries/np/np_web_api2.cpp | 331 ++++++++++++++++++++++ src/core/libraries/np/np_web_api2.h | 40 +++ src/core/libraries/np/np_web_api2_error.h | 35 +++ 7 files changed, 412 insertions(+) create mode 100644 src/core/libraries/np/np_web_api2.cpp create mode 100644 src/core/libraries/np/np_web_api2.h create mode 100644 src/core/libraries/np/np_web_api2_error.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ef5e935a8..ab144aa37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -589,6 +589,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/trophy_ui.h src/core/libraries/np/np_web_api.cpp src/core/libraries/np/np_web_api.h + src/core/libraries/np/np_web_api2.cpp + src/core/libraries/np/np_web_api2.h src/core/libraries/np/np_party.cpp src/core/libraries/np/np_party.h src/core/libraries/np/np_auth.cpp diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index da683d9d1..d954f8601 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -109,6 +109,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, NpScore) \ SUB(Lib, NpTrophy) \ SUB(Lib, NpWebApi) \ + SUB(Lib, NpWebApi2) \ SUB(Lib, NpProfileDialog) \ SUB(Lib, NpSnsFacebookDialog) \ SUB(Lib, Screenshot) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 513ca4535..ee18cd161 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -76,6 +76,7 @@ enum class Class : u8 { Lib_NpScore, ///< The LibSceNpScore implementation Lib_NpTrophy, ///< The LibSceNpTrophy implementation Lib_NpWebApi, ///< The LibSceWebApi implementation + Lib_NpWebApi2, ///< The LibSceWebApi2 implementation Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation Lib_NpSnsFacebookDialog, ///< The LibSceNpSnsFacebookDialog implementation Lib_Screenshot, ///< The LibSceScreenshot implementation diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 4ac8532db..85923134c 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -41,6 +41,7 @@ #include "core/libraries/np/np_sns_facebook_dialog.h" #include "core/libraries/np/np_trophy.h" #include "core/libraries/np/np_web_api.h" +#include "core/libraries/np/np_web_api2.h" #include "core/libraries/pad/pad.h" #include "core/libraries/playgo/playgo.h" #include "core/libraries/playgo/playgo_dialog.h" @@ -101,6 +102,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Np::NpScore::RegisterLib(sym); Libraries::Np::NpTrophy::RegisterLib(sym); Libraries::Np::NpWebApi::RegisterLib(sym); + Libraries::Np::NpWebApi2::RegisterLib(sym); Libraries::Np::NpProfileDialog::RegisterLib(sym); Libraries::Np::NpSnsFacebookDialog::RegisterLib(sym); Libraries::Np::NpAuth::RegisterLib(sym); diff --git a/src/core/libraries/np/np_web_api2.cpp b/src/core/libraries/np/np_web_api2.cpp new file mode 100644 index 000000000..c03636e73 --- /dev/null +++ b/src/core/libraries/np/np_web_api2.cpp @@ -0,0 +1,331 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/config.h" +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/libraries/np/np_web_api2.h" +#include "core/libraries/np/np_web_api2_error.h" +#include "core/libraries/system/userservice.h" + +namespace Libraries::Np::NpWebApi2 { + +s32 PS4_SYSV_ABI sceNpWebApi2AbortRequest(s64 request_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}", request_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddHttpRequestHeader() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddMultipartPart() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddWebTraceTag() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +void PS4_SYSV_ABI sceNpWebApi2CheckTimeout() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateMultipartRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateUserContext(s32 lib_ctx_id, + UserService::OrbisUserServiceUserId user_id) { + if (lib_ctx_id >= 0x8000) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID; + } + if (user_id == UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}, user_id = {:#x}", lib_ctx_id, + user_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2DeleteRequest(s64 request_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}", request_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2DeleteUserContext(s32 user_ctx_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, user_ctx_id = {:#x}", user_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValue() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValueLength() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetMemoryPoolStats(s32 lib_ctx_id, + OrbisNpWebApi2MemoryPoolStats* stats) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}", lib_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2Initialize(s32 lib_http_ctx_id, u64 pool_size) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", + lib_http_ctx_id, pool_size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2InitializeForPresence(s32 lib_http_ctx_id, u64 pool_size) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", + lib_http_ctx_id, pool_size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntCreateRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize(const OrbisNpWebApi2IntInitializeArgs* args) { + if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitializeArgs)) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, + "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}'", + args->lib_http_ctx_id, args->pool_size, args->name); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize2(const OrbisNpWebApi2IntInitialize2Args* args) { + if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitialize2Args)) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR( + Lib_NpWebApi2, + "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}', group = {:#x}", + args->lib_http_ctx_id, args->pool_size, args->name, args->push_config_group); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntPushEventCreateCtxIndFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventAbortHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreatePushContext() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeletePushContext() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventSetHandleTimeout() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventStartPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2ReadData(s64 request_id, void* data, u64 size) { + if (data == nullptr || size == 0) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}, size = {:#x}", request_id, + size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SendMultipartRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SendRequest() { + if (!Config::getPSNSignedIn()) { + LOG_INFO(Lib_NpWebApi2, "called, returning PSN signed out."); + return ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SetMultipartContentType() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SetRequestTimeout(s64 request_id, u32 timeout) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}, timeout = {}", request_id, + timeout); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2Terminate(s32 lib_ctx_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}", lib_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_A9A31C5F6FBA6620() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_03D22863300D2B73() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_97296F7578AAD541() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_E0DF39A36F087DB9() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("zpiPsH7dbFQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2AbortRequest); + LIB_FUNCTION("egOOvrnF6mI", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddHttpRequestHeader); + LIB_FUNCTION("Io7kh1LHDoM", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddMultipartPart); + LIB_FUNCTION("MgsTa76wlEk", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddWebTraceTag); + LIB_FUNCTION("3Tt9zL3tkoc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CheckTimeout); + LIB_FUNCTION("+nz1Vq-NrDA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2CreateMultipartRequest); + LIB_FUNCTION("3EI-OSJ65Xc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CreateRequest); + LIB_FUNCTION("sk54bi6FtYM", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2CreateUserContext); + LIB_FUNCTION("vvzWO-DvG1s", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2DeleteRequest); + LIB_FUNCTION("9X9+cneTGUU", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2DeleteUserContext); + LIB_FUNCTION("hksbskNToEA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetHttpResponseHeaderValue); + LIB_FUNCTION("HwP3aM+c85c", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetHttpResponseHeaderValueLength); + LIB_FUNCTION("Xweb+naPZ8Y", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetMemoryPoolStats); + LIB_FUNCTION("+o9816YQhqQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Initialize); + LIB_FUNCTION("dowMWFgowXY", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2InitializeForPresence); + LIB_FUNCTION("qmINYLuqzaA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntCreateRequest); + LIB_FUNCTION("zXaFo7euxsQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2IntInitialize); + LIB_FUNCTION("9KSGFMRnp3k", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntInitialize2); + LIB_FUNCTION("2hlBNB96saE", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntPushEventCreateCtxIndFilter); + LIB_FUNCTION("1OLgvahaSco", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventAbortHandle); + LIB_FUNCTION("MsaFhR+lPE4", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreateFilter); + LIB_FUNCTION("WV1GwM32NgY", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreateHandle); + LIB_FUNCTION("NNVf18SlbT8", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreatePushContext); + LIB_FUNCTION("KJdPcOGmK58", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeleteFilter); + LIB_FUNCTION("fIATVMo4Y1w", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeleteHandle); + LIB_FUNCTION("QafxeZM3WK4", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeletePushContext); + LIB_FUNCTION("fY3QqeNkF8k", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventRegisterCallback); + LIB_FUNCTION("lxtHJMwBsaU", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventRegisterPushContextCallback); + LIB_FUNCTION("KWkc6Q3tjXc", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventSetHandleTimeout); + LIB_FUNCTION("AAj9X+4aGYA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventStartPushContextCallback); + LIB_FUNCTION("hOnIlcGrO6g", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventUnregisterCallback); + LIB_FUNCTION("PmyrbbJSFz0", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventUnregisterPushContextCallback); + LIB_FUNCTION("OOY9+ObfKec", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2ReadData); + LIB_FUNCTION("NKCwS8+5Fx8", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SendMultipartRequest); + LIB_FUNCTION("lQOCF84lvzw", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2SendRequest); + LIB_FUNCTION("bltDCAskmfE", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SetMultipartContentType); + LIB_FUNCTION("TjAutbrkr60", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SetRequestTimeout); + LIB_FUNCTION("bEvXpcEk200", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Terminate); + LIB_FUNCTION("qaMcX2+6ZiA", "libSceNpWebApi2", 1, "libSceNpWebApi2", Func_A9A31C5F6FBA6620); + LIB_FUNCTION("A9IoYzANK3M", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_03D22863300D2B73); + LIB_FUNCTION("lylvdXiq1UE", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_97296F7578AAD541); + LIB_FUNCTION("4N85o28Ifbk", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_E0DF39A36F087DB9); +}; + +} // namespace Libraries::Np::NpWebApi2 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2.h b/src/core/libraries/np/np_web_api2.h new file mode 100644 index 000000000..1e970d08e --- /dev/null +++ b/src/core/libraries/np/np_web_api2.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Np::NpWebApi2 { + +struct OrbisNpWebApi2IntInitializeArgs { + s32 lib_http_ctx_id; + s32 reserved; + u64 pool_size; + char* name; + u64 struct_size; +}; + +struct OrbisNpWebApi2IntInitialize2Args { + s32 lib_http_ctx_id; + s32 reserved; + u64 pool_size; + char* name; + u32 push_config_group; + s32 reserved2; + u64 struct_size; +}; + +struct OrbisNpWebApi2MemoryPoolStats { + u64 pool_size; + u64 max_inuse_size; + u64 current_inuse_size; + s32 reserved; +}; + +void RegisterLib(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Np::NpWebApi2 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2_error.h b/src/core/libraries/np/np_web_api2_error.h new file mode 100644 index 000000000..6ab4fdb31 --- /dev/null +++ b/src/core/libraries/np/np_web_api2_error.h @@ -0,0 +1,35 @@ +// 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_NP_WEBAPI2_ERROR_OUT_OF_MEMORY = 0x80553401; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT = 0x80553402; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID = 0x80553403; +constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80553404; +constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND = 0x80553405; +constexpr int ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND = 0x80553406; +constexpr int ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN = 0x80553407; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_CONTENT_PARAMETER = 0x80553408; +constexpr int ORBIS_NP_WEBAPI2_ERROR_ABORTED = 0x80553409; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_ALREADY_EXIST = 0x8055340a; +constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055340b; +constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055340c; +constexpr int ORBIS_NP_WEBAPI2_HANDLE_NOT_FOUND = 0x8055340d; +constexpr int ORBIS_NP_WEBAPI2_SIGNED_IN_USER_NOT_FOUND = 0x8055340e; +constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_BUSY = 0x8055340f; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_BUSY = 0x80553410; +constexpr int ORBIS_NP_WEBAPI2_REQUEST_BUSY = 0x80553411; +constexpr int ORBIS_NP_WEBAPI2_INVALID_HTTP_STATUS_CODE = 0x80553412; +constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_HTTP_HEADER = 0x80553413; +constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_FUNCTION_CALL = 0x80553414; +constexpr int ORBIS_NP_WEBAPI2_MULTIPART_PART_NOT_FOUND = 0x80553415; +constexpr int ORBIS_NP_WEBAPI2_PARAMETER_TOO_LONG = 0x80553416; +constexpr int ORBIS_NP_WEBAPI2_HANDLE_BUSY = 0x80553417; +constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_MAX = 0x80553418; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_MAX = 0x80553419; +constexpr int ORBIS_NP_WEBAPI2_AFTER_SEND = 0x8055341a; +constexpr int ORBIS_NP_WEBAPI2_TIMEOUT = 0x8055341b; +constexpr int ORBIS_NP_WEBAPI2_PUSH_CONTEXT_NOT_FOUND = 0x8055341c; \ No newline at end of file From 12cd27d0b723dccf39482dabf762a0b23312a2da Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 10 Jan 2026 13:15:06 -0600 Subject: [PATCH 07/14] Fix socket_is_ready (#3912) --- src/core/libraries/network/posix_sockets.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/network/posix_sockets.cpp b/src/core/libraries/network/posix_sockets.cpp index 7992fa217..f2adccf50 100644 --- a/src/core/libraries/network/posix_sockets.cpp +++ b/src/core/libraries/network/posix_sockets.cpp @@ -204,17 +204,18 @@ static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) { // On Windows, MSG_DONTWAIT is not handled natively by recv/send. // This function uses select() with zero timeout to simulate non-blocking behavior. static int socket_is_ready(int sock, bool is_read = true) { - fd_set fds; + fd_set fds{}; FD_ZERO(&fds); FD_SET(sock, &fds); timeval timeout{0, 0}; int res = select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout); - if (res == 0) - return ORBIS_NET_ERROR_EWOULDBLOCK; - else if (res < 0) + if (res == 0) { + *Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK; + return -1; + } else if (res < 0) { return ConvertReturnErrorCode(res); - + } return res; } From ae47dd5d54df31535ee5344686236f250b5dc3fd Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 10 Jan 2026 19:36:36 -0600 Subject: [PATCH 08/14] Handle exceptions from OutputDebugString API. (#3914) Needed after #3900 --- src/core/signals.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/signals.cpp b/src/core/signals.cpp index db6e4b6cc..8df4edea8 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.cpp @@ -33,6 +33,10 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept { case EXCEPTION_ILLEGAL_INSTRUCTION: handled = signals->DispatchIllegalInstruction(pExp); break; + case DBG_PRINTEXCEPTION_C: + case DBG_PRINTEXCEPTION_WIDE_C: + // Used by OutputDebugString functions. + return EXCEPTION_CONTINUE_EXECUTION; default: break; } From 3844a2fb54a2bdafac1a431dd2ee5501c4af95ae Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 11 Jan 2026 02:47:34 -0600 Subject: [PATCH 09/14] Lib.Videodec2: Stub sceVideodec2AllocateComputeQueue to return a valid computeQueue pointer. (#3915) * Stub compute queue output to cpuGpuMemory * Rename namespace This has bugged me for far too long * Oops --- src/core/libraries/libs.cpp | 4 +-- src/core/libraries/videodec/videodec2.cpp | 34 +++++++++++++++++-- src/core/libraries/videodec/videodec2.h | 4 +-- src/core/libraries/videodec/videodec2_avc.h | 4 +-- .../libraries/videodec/videodec2_impl.cpp | 4 +-- src/core/libraries/videodec/videodec2_impl.h | 4 +-- 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 85923134c..9867d132d 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -120,13 +120,13 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::ErrorDialog::RegisterLib(sym); Libraries::ImeDialog::RegisterLib(sym); Libraries::AvPlayer::RegisterLib(sym); - Libraries::Vdec2::RegisterLib(sym); + Libraries::Videodec::RegisterLib(sym); + Libraries::Videodec2::RegisterLib(sym); Libraries::Audio3d::RegisterLib(sym); Libraries::Ime::RegisterLib(sym); Libraries::GameLiveStreaming::RegisterLib(sym); Libraries::SharePlay::RegisterLib(sym); Libraries::Remoteplay::RegisterLib(sym); - Libraries::Videodec::RegisterLib(sym); Libraries::RazorCpu::RegisterLib(sym); Libraries::Move::RegisterLib(sym); Libraries::Fiber::RegisterLib(sym); diff --git a/src/core/libraries/videodec/videodec2.cpp b/src/core/libraries/videodec/videodec2.cpp index a1edb3b68..121818f39 100644 --- a/src/core/libraries/videodec/videodec2.cpp +++ b/src/core/libraries/videodec/videodec2.cpp @@ -8,7 +8,7 @@ #include "core/libraries/videodec/videodec2_impl.h" #include "core/libraries/videodec/videodec_error.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { static constexpr u64 kMinimumMemorySize = 16_MB; ///> Fake minimum memory size for querying @@ -34,7 +34,35 @@ s32 PS4_SYSV_ABI sceVideodec2AllocateComputeQueue(const OrbisVideodec2ComputeConfigInfo* computeCfgInfo, const OrbisVideodec2ComputeMemoryInfo* computeMemInfo, OrbisVideodec2ComputeQueue* computeQueue) { - LOG_INFO(Lib_Vdec2, "called"); + LOG_WARNING(Lib_Vdec2, "called"); + if (!computeCfgInfo || !computeMemInfo || !computeQueue) { + LOG_ERROR(Lib_Vdec2, "Invalid arguments"); + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (computeCfgInfo->thisSize != sizeof(OrbisVideodec2ComputeConfigInfo) || + computeMemInfo->thisSize != sizeof(OrbisVideodec2ComputeMemoryInfo)) { + LOG_ERROR(Lib_Vdec2, "Invalid struct size"); + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + if (computeCfgInfo->reserved0 != 0 || computeCfgInfo->reserved1 != 0) { + LOG_ERROR(Lib_Vdec2, "Invalid compute config"); + return ORBIS_VIDEODEC2_ERROR_CONFIG_INFO; + } + if (computeCfgInfo->computePipeId > 4) { + LOG_ERROR(Lib_Vdec2, "Invalid compute pipe id"); + return ORBIS_VIDEODEC2_ERROR_COMPUTE_PIPE_ID; + } + if (computeCfgInfo->computeQueueId > 7) { + LOG_ERROR(Lib_Vdec2, "Invalid compute queue id"); + return ORBIS_VIDEODEC2_ERROR_COMPUTE_QUEUE_ID; + } + if (!computeMemInfo->cpuGpuMemory) { + LOG_ERROR(Lib_Vdec2, "Invalid memory pointer"); + return ORBIS_VIDEODEC2_ERROR_MEMORY_POINTER; + } + + // The real library returns a pointer to memory inside cpuGpuMemory + *computeQueue = computeMemInfo->cpuGpuMemory; return ORBIS_OK; } @@ -233,4 +261,4 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceVideodec2GetPictureInfo); } -} // namespace Libraries::Vdec2 +} // namespace Libraries::Videodec2 diff --git a/src/core/libraries/videodec/videodec2.h b/src/core/libraries/videodec/videodec2.h index 0f080129f..0311e4d27 100644 --- a/src/core/libraries/videodec/videodec2.h +++ b/src/core/libraries/videodec/videodec2.h @@ -10,7 +10,7 @@ namespace Core::Loader { class SymbolsResolver; } -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { class VdecDecoder; @@ -138,4 +138,4 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp void* p1stPictureInfo, void* p2ndPictureInfo); void RegisterLib(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_avc.h b/src/core/libraries/videodec/videodec2_avc.h index 725d2335f..dcf6ae007 100644 --- a/src/core/libraries/videodec/videodec2_avc.h +++ b/src/core/libraries/videodec/videodec2_avc.h @@ -5,7 +5,7 @@ #include "common/types.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { struct OrbisVideodec2AvcPictureInfo { u64 thisSize; @@ -127,4 +127,4 @@ struct OrbisVideodec2LegacyAvcPictureInfo { }; static_assert(sizeof(OrbisVideodec2LegacyAvcPictureInfo) == 0x68); -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_impl.cpp b/src/core/libraries/videodec/videodec2_impl.cpp index 2ad3d7e19..08a7f8f00 100644 --- a/src/core/libraries/videodec/videodec2_impl.cpp +++ b/src/core/libraries/videodec/videodec2_impl.cpp @@ -9,7 +9,7 @@ #include "common/support/avdec.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { std::vector gPictureInfos; std::vector gLegacyPictureInfos; @@ -278,4 +278,4 @@ AVFrame* VdecDecoder::ConvertNV12Frame(AVFrame& frame) { return nv12_frame; } -} // namespace Libraries::Vdec2 +} // namespace Libraries::Videodec2 diff --git a/src/core/libraries/videodec/videodec2_impl.h b/src/core/libraries/videodec/videodec2_impl.h index 7ee3339db..da16b3baa 100644 --- a/src/core/libraries/videodec/videodec2_impl.h +++ b/src/core/libraries/videodec/videodec2_impl.h @@ -13,7 +13,7 @@ extern "C" { #include } -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { extern std::vector gPictureInfos; extern std::vector gLegacyPictureInfos; @@ -37,4 +37,4 @@ private: SwsContext* mSwsContext = nullptr; }; -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file From 910f7370791c0b55c6db0b1de852ab415e12ca41 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:48:41 -0600 Subject: [PATCH 10/14] Core: Add libSceRtc, libSceJpegDec, libSceJpegEnc, and libScePngEnc LLEs (#3918) * Run libSceRtc LLE The more we've used our HLE, the more issues we've had with it. While we debug these bugs, re-enabling LLE will address any regressions the swap caused. * libSceJpegDec LLE Needed for Trackmania until we implement HLE for this library * libScePngEnc LLE Needed for Minecraft until we implement HLE for this library * Update documentation appropriately * libSceJpegEnc LLE By @georgemoralis's request --- README.md | 4 ++-- src/core/libraries/libs.cpp | 4 ---- src/emulator.cpp | 5 +++++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dbb890f87..69ee64b13 100644 --- a/README.md +++ b/README.md @@ -148,9 +148,9 @@ The following firmware modules are supported and must be placed in shadPS4's `sy | Modules | Modules | Modules | Modules | |-------------------------|-------------------------|-------------------------|-------------------------| | libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx | -| libSceJson.sprx | libSceJson2.sprx | libSceLibcInternal.sprx | libSceNgs2.sprx | +| libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx | +| libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx | | libSceUlt.sprx | | | | - > [!Caution] diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 9867d132d..7f679e7c2 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -48,7 +48,6 @@ #include "core/libraries/random/random.h" #include "core/libraries/razor_cpu/razor_cpu.h" #include "core/libraries/remote_play/remoteplay.h" -#include "core/libraries/rtc/rtc.h" #include "core/libraries/rudp/rudp.h" #include "core/libraries/save_data/dialog/savedatadialog.h" #include "core/libraries/save_data/savedata.h" @@ -72,7 +71,6 @@ #include "core/libraries/web_browser_dialog/webbrowserdialog.h" #include "core/libraries/zlib/zlib_sce.h" #include "fiber/fiber.h" -#include "jpeg/jpegenc.h" namespace Libraries { @@ -130,7 +128,6 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::RazorCpu::RegisterLib(sym); Libraries::Move::RegisterLib(sym); Libraries::Fiber::RegisterLib(sym); - Libraries::JpegEnc::RegisterLib(sym); Libraries::Mouse::RegisterLib(sym); Libraries::WebBrowserDialog::RegisterLib(sym); Libraries::Zlib::RegisterLib(sym); @@ -143,7 +140,6 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::CompanionHttpd::RegisterLib(sym); Libraries::CompanionUtil::RegisterLib(sym); Libraries::Voice::RegisterLib(sym); - Libraries::Rtc::RegisterLib(sym); Libraries::Rudp::RegisterLib(sym); Libraries::VrTracker::RegisterLib(sym); diff --git a/src/emulator.cpp b/src/emulator.cpp index 4947beeb8..44f8b0e72 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -34,6 +34,7 @@ #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/libc_internal/libc_internal.h" #include "core/libraries/libs.h" #include "core/libraries/ngs2/ngs2.h" @@ -523,6 +524,10 @@ 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", nullptr}, {"libSceJson.sprx", nullptr}, {"libSceJson2.sprx", nullptr}, {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib}, From 540fdcc8124f3822b800edbbdf2ce7fe0521d1de Mon Sep 17 00:00:00 2001 From: b7k Date: Mon, 12 Jan 2026 10:21:52 +0100 Subject: [PATCH 11/14] changing the mouse speed does not affect the mouse speed offset (#3917) --- src/input/input_mouse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/input_mouse.cpp b/src/input/input_mouse.cpp index cbb07721b..996c35ef9 100644 --- a/src/input/input_mouse.cpp +++ b/src/input/input_mouse.cpp @@ -70,7 +70,7 @@ void EmulateJoystick(GameController* controller, u32 interval) { SDL_GetRelativeMouseState(&d_x, &d_y); float output_speed = - SDL_clamp((sqrt(d_x * d_x + d_y * d_y) + mouse_speed_offset * 128) * mouse_speed, + SDL_clamp(sqrt(d_x * d_x + d_y * d_y) * mouse_speed + mouse_speed_offset * 128, mouse_deadzone_offset * 128, 128.0); float angle = atan2(d_y, d_x); From 108cefaf537728b3d654457eb8c70b4b3afb4d00 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:30:56 -0600 Subject: [PATCH 12/14] Better message for missing game trophies directory (#3922) Also decreased the log level from critical to warning, as all this affects is the ability to earn trophies. --- src/core/file_format/trp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index 9d37b957e..ab873d451 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -59,7 +59,7 @@ static void hexToBytes(const char* hex, unsigned char* dst) { bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) { std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/"; if (!std::filesystem::exists(gameSysDir)) { - LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist"); + LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist"); return false; } From acb8d066366ce44fc9b92d612f40bf56d64f4c63 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:32:17 -0600 Subject: [PATCH 13/14] Lib.Audio3d: sceAudio3dGetDefaultOpenParameters fix (#3923) * OrbisAudio3dOpenParameters struct fix Not sure why we have the extra filler, but decomp suggests it shouldn't exist. This fixes stack_chk_fail issues in audio3d using titles. * Bring back filler, only copy 0x20 bytes. The library accepts variations on struct size, with the maximum size being the 0x28 size our current struct has. This fixes the issue without potentially breaking the struct. * Fix memcpy Prevent OOB read --- src/core/libraries/audio3d/audio3d.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp index bac497840..2ddbcd890 100644 --- a/src/core/libraries/audio3d/audio3d.cpp +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -189,7 +189,7 @@ s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() { s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) { LOG_DEBUG(Lib_Audio3d, "called"); if (params) { - *params = OrbisAudio3dOpenParameters{ + auto default_params = OrbisAudio3dOpenParameters{ .size_this = 0x20, .granularity = 0x100, .rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000, @@ -197,6 +197,7 @@ s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* .queue_depth = 2, .buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH, }; + memcpy(params, &default_params, 0x20); } return ORBIS_OK; } @@ -445,7 +446,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id, } *port_id = id; - std::memcpy(&state->ports[id].parameters, parameters, sizeof(OrbisAudio3dOpenParameters)); + std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this); return ORBIS_OK; } From 1a99ab7b098680bf9b4d6539d02f548cc24b89e3 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Tue, 13 Jan 2026 12:08:46 +0300 Subject: [PATCH 14/14] ajm: fix init params initialization (#3924) --- src/core/libraries/ajm/ajm_batch.cpp | 4 +--- src/core/libraries/ajm/ajm_batch.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp index 61f80e482..3ab2ed4ab 100644 --- a/src/core/libraries/ajm/ajm_batch.cpp +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -280,9 +280,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { job.input.resample_parameters = input_batch.Consume(); } if (True(control_flags & AjmJobControlFlags::Initialize)) { - job.input.init_params = AjmDecAt9InitializeParameters{}; - std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(), - input_batch.BytesRemaining()); + job.input.init_params = input_batch.Consume(); } } diff --git a/src/core/libraries/ajm/ajm_batch.h b/src/core/libraries/ajm/ajm_batch.h index 7f8890898..c18e9efbf 100644 --- a/src/core/libraries/ajm/ajm_batch.h +++ b/src/core/libraries/ajm/ajm_batch.h @@ -21,7 +21,7 @@ namespace Libraries::Ajm { struct AjmJob { struct Input { - std::optional init_params; + std::optional init_params; std::optional resample_parameters; std::optional statistics_engine_parameters; std::optional format;