From 2e789649ecc34373238a31e8388b14ffccd46db8 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:39:08 +0100 Subject: [PATCH 01/18] Automatically add missing hotkeys to the global input config (#4003) --- src/common/config.cpp | 53 ++++++++++++++++++++++++++----------- src/common/config.h | 4 +-- src/input/input_handler.cpp | 13 ++++++--- src/input/input_handler.h | 4 ++- 4 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index aece6916f..8fe624c42 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -14,6 +15,8 @@ #include "common/path_util.h" #include "common/scm_rev.h" +#include "input/input_handler.h" + using std::nullopt; using std::optional; using std::string; @@ -1289,19 +1292,6 @@ void setDefaultValues(bool is_game_specific) { constexpr std::string_view GetDefaultGlobalConfig() { return R"(# Anything put here will be loaded for all games, # alongside the game's config or default.ini depending on your preference. - -hotkey_renderdoc_capture = f12 -hotkey_fullscreen = f11 -hotkey_show_fps = f10 -hotkey_pause = f9 -hotkey_reload_inputs = f8 -hotkey_toggle_mouse_to_joystick = f7 -hotkey_toggle_mouse_to_gyro = f6 -hotkey_toggle_mouse_to_touchpad = delete -hotkey_quit = lctrl, lshift, end - -hotkey_volume_up = kpplus -hotkey_volume_down = kpminus )"; } @@ -1379,7 +1369,7 @@ analog_deadzone = rightjoystick, 2, 127 override_controller_color = false, 0, 0, 255 )"; } -std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { +std::filesystem::path GetInputConfigFile(const string& game_id) { // Read configuration file of the game, and if it doesn't exist, generate it from default // If that doesn't exist either, generate that from getDefaultConfig() and try again // If even the folder is missing, we start with that. @@ -1418,6 +1408,39 @@ std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { } } } + if (game_id == "global") { + std::map default_bindings_to_add = { + {"hotkey_renderdoc_capture", "f12"}, + {"hotkey_fullscreen", "f11"}, + {"hotkey_show_fps", "f10"}, + {"hotkey_pause", "f9"}, + {"hotkey_reload_inputs", "f8"}, + {"hotkey_toggle_mouse_to_joystick", "f7"}, + {"hotkey_toggle_mouse_to_gyro", "f6"}, + {"hotkey_toggle_mouse_to_touchpad", "delete"}, + {"hotkey_quit", "lctrl, lshift, end"}, + {"hotkey_volume_up", "kpplus"}, + {"hotkey_volume_down", "kpminus"}, + }; + std::ifstream global_in(config_file); + string line; + while (std::getline(global_in, line)) { + line.erase(std::remove_if(line.begin(), line.end(), + [](unsigned char c) { return std::isspace(c); }), + line.end()); + std::size_t equal_pos = line.find('='); + if (equal_pos == std::string::npos) { + continue; + } + std::string output_string = line.substr(0, equal_pos); + default_bindings_to_add.erase(output_string); + } + global_in.close(); + std::ofstream global_out(config_file, std::ios::app); + for (auto const& b : default_bindings_to_add) { + global_out << b.first << " = " << b.second << "\n"; + } + } // If game-specific config doesn't exist, create it from the default config if (!std::filesystem::exists(config_file)) { diff --git a/src/common/config.h b/src/common/config.h index 2a95e6cf0..036d04c99 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -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 #pragma once @@ -183,6 +183,6 @@ std::filesystem::path getAddonInstallDir(); void setDefaultValues(bool is_game_specific = false); constexpr std::string_view GetDefaultGlobalConfig(); -std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id = ""); +std::filesystem::path GetInputConfigFile(const std::string& game_id = ""); }; // namespace Config diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index e6705edad..17a82b9cb 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.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 "input_handler.h" @@ -200,6 +200,8 @@ InputBinding GetBindingFromString(std::string& line) { input = InputID(InputType::Axis, string_to_axis_map.at(t).axis); } else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) { input = InputID(InputType::Controller, string_to_cbutton_map.at(t)); + } else if (string_to_hotkey_map.find(t) != string_to_hotkey_map.end()) { + input = InputID(InputType::Controller, string_to_hotkey_map.at(t)); } else { // Invalid token found; return default binding LOG_DEBUG(Input, "Invalid token found: {}", t); @@ -220,8 +222,8 @@ InputBinding GetBindingFromString(std::string& line) { void ParseInputConfig(const std::string game_id = "") { std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id; - const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default); - const auto global_config_file = Config::GetFoolproofInputConfigFile("global"); + const auto config_file = Config::GetInputConfigFile(game_id_or_default); + const auto global_config_file = Config::GetInputConfigFile("global"); // we reset these here so in case the user fucks up or doesn't include some of these, // we can fall back to default @@ -396,13 +398,16 @@ void ParseInputConfig(const std::string game_id = "") { InputBinding binding = GetBindingFromString(input_string); BindingConnection connection(InputID(), nullptr); auto button_it = string_to_cbutton_map.find(output_string); + if (button_it == string_to_cbutton_map.end()) { + button_it = string_to_hotkey_map.find(output_string); + } auto axis_it = string_to_axis_map.find(output_string); if (binding.IsEmpty()) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); return; } - if (button_it != string_to_cbutton_map.end()) { + if (button_it != string_to_hotkey_map.end()) { connection = BindingConnection( binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second))); connections.insert(connections.end(), connection); diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 43c09ba55..844870b5d 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -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 #pragma once @@ -138,6 +138,8 @@ const std::map string_to_cbutton_map = { {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, {"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE}, +}; +const std::map string_to_hotkey_map = { {"hotkey_pause", HOTKEY_PAUSE}, {"hotkey_fullscreen", HOTKEY_FULLSCREEN}, {"hotkey_show_fps", HOTKEY_SIMPLE_FPS}, From 341de9aa176440e68861880c04c253cd05816f46 Mon Sep 17 00:00:00 2001 From: Kyoskii <125975056+Kyoskii@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:03:45 -0600 Subject: [PATCH 02/18] Update config.cpp (#4004) InternalScreenWidth would get internalScreenHeight instead of InternalScreenWidth --- src/common/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 8fe624c42..657943c95 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -364,7 +364,7 @@ u32 getWindowHeight() { } u32 getInternalScreenWidth() { - return internalScreenHeight.get(); + return internalScreenWidth.get(); } u32 getInternalScreenHeight() { From be86b5fe3231570cac207b470449f3edcd87f122 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:33:20 -0600 Subject: [PATCH 03/18] Kernel.Pthreads: Remove unreachable in posix_pthread_mutex_timedlock (#4005) * Fix assert Just a typical day of me pushing something a month ago, nobody testing/reviewing it, then finding out it's broken when that code inevitably makes it into production. * Remove unreachable in posix_pthread_mutex_timedlock It's apparently something that was added during pthreads rewrite, but the actual code for this function seems to be fully implemented? --- src/core/libraries/kernel/threads/mutex.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp index 7a046e973..31e8b900b 100644 --- a/src/core/libraries/kernel/threads/mutex.cpp +++ b/src/core/libraries/kernel/threads/mutex.cpp @@ -261,7 +261,6 @@ int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) { int PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex, const OrbisKernelTimespec* abstime) { CHECK_AND_INIT_MUTEX - UNREACHABLE(); return (*mutex)->Lock(abstime); } From c9a9cf2e75e342985e82c058408e78d4ad6ce0ee Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sun, 8 Feb 2026 10:43:15 +0100 Subject: [PATCH 04/18] fix debug assert (#4006) --- src/input/input_handler.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 17a82b9cb..7c5263f8f 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -396,22 +396,23 @@ void ParseInputConfig(const std::string game_id = "") { // normal cases InputBinding binding = GetBindingFromString(input_string); - BindingConnection connection(InputID(), nullptr); - auto button_it = string_to_cbutton_map.find(output_string); - if (button_it == string_to_cbutton_map.end()) { - button_it = string_to_hotkey_map.find(output_string); - } - auto axis_it = string_to_axis_map.find(output_string); if (binding.IsEmpty()) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); return; } - if (button_it != string_to_hotkey_map.end()) { + BindingConnection connection(InputID(), nullptr); + auto button_it = string_to_cbutton_map.find(output_string); + auto hotkey_it = string_to_hotkey_map.find(output_string); + auto axis_it = string_to_axis_map.find(output_string); + if (button_it != string_to_cbutton_map.end()) { connection = BindingConnection( binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second))); connections.insert(connections.end(), connection); - + } else if (hotkey_it != string_to_hotkey_map.end()) { + connection = BindingConnection( + binding, &*std::ranges::find(output_array, ControllerOutput(hotkey_it->second))); + connections.insert(connections.end(), connection); } else if (axis_it != string_to_axis_map.end()) { int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value; connection = BindingConnection( From b44ad1e087eb6131b6b169dfc22cc5df3194bd98 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:15:11 +0800 Subject: [PATCH 05/18] Volume hotkey: show volume value, set game_specific arg correctly, clamp value (#4009) --- src/core/devtools/layer.cpp | 29 +++++++++++++++++++++++++++++ src/core/devtools/layer.h | 1 + src/core/emulator_state.cpp | 8 ++++++++ src/core/emulator_state.h | 5 ++++- src/emulator.cpp | 8 ++++++++ src/input/input_handler.cpp | 11 +++++++++-- 6 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 928040fec..4be107713 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -32,6 +32,9 @@ static bool show_simple_fps = false; static bool visibility_toggled = false; static bool show_quit_window = false; +static bool show_volume = false; +static float volume_start_time; + static float fps_scale = 1.0f; static int dump_frame_count = 1; @@ -454,6 +457,27 @@ void L::Draw() { End(); } + if (show_volume) { + float current_time = ImGui::GetTime(); + + // Show volume for 3 seconds + if (current_time - volume_start_time >= 3.0) { + show_volume = false; + } else { + SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->WorkPos.x + + ImGui::GetMainViewport()->WorkSize.x - 10, + ImGui::GetMainViewport()->WorkPos.y + 10), + ImGuiCond_Always, ImVec2(1.0f, 0.0f)); + + if (ImGui::Begin("Volume Window", &show_volume, + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) { + Text("Volume: %d", Config::getVolumeSlider()); + } + End(); + } + } + PopID(); } @@ -482,4 +506,9 @@ void ToggleQuitWindow() { show_quit_window = !show_quit_window; } +void ShowVolume() { + volume_start_time = ImGui::GetTime(); + show_volume = true; +} + } // namespace Overlay diff --git a/src/core/devtools/layer.h b/src/core/devtools/layer.h index 96b48a7f0..761135baf 100644 --- a/src/core/devtools/layer.h +++ b/src/core/devtools/layer.h @@ -32,5 +32,6 @@ namespace Overlay { void ToggleSimpleFps(); void SetSimpleFps(bool enabled); void ToggleQuitWindow(); +void ShowVolume(); } // namespace Overlay diff --git a/src/core/emulator_state.cpp b/src/core/emulator_state.cpp index 1f02043a3..20cffe53c 100644 --- a/src/core/emulator_state.cpp +++ b/src/core/emulator_state.cpp @@ -35,3 +35,11 @@ bool EmulatorState::IsAutoPatchesLoadEnabled() const { void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) { m_load_patches_auto = enable; } + +bool EmulatorState::IsGameSpecifigConfigUsed() const { + return m_game_specific_config_used; +} + +void EmulatorState::SetGameSpecifigConfigUsed(bool used) { + m_game_specific_config_used = used; +} diff --git a/src/core/emulator_state.h b/src/core/emulator_state.h index c12af5401..0764b8a81 100644 --- a/src/core/emulator_state.h +++ b/src/core/emulator_state.h @@ -18,6 +18,8 @@ public: void SetGameRunning(bool running); bool IsAutoPatchesLoadEnabled() const; void SetAutoPatchesLoadEnabled(bool enable); + bool IsGameSpecifigConfigUsed() const; + void SetGameSpecifigConfigUsed(bool used); private: static std::shared_ptr s_instance; @@ -26,4 +28,5 @@ private: // state variables bool m_running = false; bool m_load_patches_auto = true; -}; \ No newline at end of file + bool m_game_specific_config_used = false; +}; diff --git a/src/emulator.cpp b/src/emulator.cpp index 6ba80b096..0dde0b7fa 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -28,6 +28,7 @@ #include "common/singleton.h" #include "core/debugger.h" #include "core/devtools/widget/module_list.h" +#include "core/emulator_state.h" #include "core/file_format/psf.h" #include "core/file_format/trp.h" #include "core/file_sys/fs.h" @@ -206,6 +207,13 @@ void Emulator::Run(std::filesystem::path file, std::vector args, Config::load(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"), true); + if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (id + ".toml"))) { + EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(true); + } else { + EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(false); + } + // Initialize logging as soon as possible if (!id.empty() && Config::getSeparateLogFilesEnabled()) { Common::Log::Initialize(id + ".log"); diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 7c5263f8f..6e5014c1b 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -22,6 +22,8 @@ #include "common/elf_info.h" #include "common/io_file.h" #include "common/path_util.h" +#include "core/devtools/layer.h" +#include "core/emulator_state.h" #include "input/controller.h" #include "input/input_mouse.h" @@ -550,6 +552,7 @@ void ControllerOutput::FinalizeUpdate() { } old_button_state = new_button_state; old_param = *new_param; + bool is_game_specific = EmulatorState::GetInstance()->IsGameSpecifigConfigUsed(); if (button != SDL_GAMEPAD_BUTTON_INVALID) { switch (button) { case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: @@ -595,10 +598,14 @@ void ControllerOutput::FinalizeUpdate() { PushSDLEvent(SDL_EVENT_RDOC_CAPTURE); break; case HOTKEY_VOLUME_UP: - Config::setVolumeSlider(Config::getVolumeSlider() + 10, true); + Config::setVolumeSlider(std::clamp(Config::getVolumeSlider() + 10, 0, 500), + is_game_specific); + Overlay::ShowVolume(); break; case HOTKEY_VOLUME_DOWN: - Config::setVolumeSlider(Config::getVolumeSlider() - 10, true); + Config::setVolumeSlider(std::clamp(Config::getVolumeSlider() - 10, 0, 500), + is_game_specific); + Overlay::ShowVolume(); break; case HOTKEY_QUIT: PushSDLEvent(SDL_EVENT_QUIT_DIALOG); From 2a61851a8865c9fef15c78f7b06fc7417f7018ac Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 8 Feb 2026 12:07:17 -0600 Subject: [PATCH 06/18] Kernel.Process: Implement sceKernelGetModuleInfo2, sceKernelGetModuleList2 (#4001) * twos * Fixes Still can't test properly, but this seems to hide system libs, which I'm pretty sure is the necessary difference here. * Clang * Extra export for sceKernelGetModuleInfo2 --- src/core/libraries/kernel/process.cpp | 48 +++++++++++++++++++++++++++ src/core/module.h | 9 +++++ 2 files changed, 57 insertions(+) diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 4ea7fa062..31cc9a81b 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -191,6 +191,26 @@ s32 PS4_SYSV_ABI sceKernelGetModuleInfo(s32 handle, Core::OrbisKernelModuleInfo* return ORBIS_OK; } +s32 PS4_SYSV_ABI sceKernelGetModuleInfo2(s32 handle, Core::OrbisKernelModuleInfo* info) { + if (info == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (info->st_size != sizeof(Core::OrbisKernelModuleInfo)) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + auto* linker = Common::Singleton::Instance(); + auto* module = linker->GetModule(handle); + if (module == nullptr) { + return ORBIS_KERNEL_ERROR_ESRCH; + } + if (module->IsSystemLib()) { + return ORBIS_KERNEL_ERROR_EPERM; + } + *info = module->GetModuleInfo(); + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceKernelGetModuleInfoInternal(s32 handle, Core::OrbisKernelModuleInfoEx* info) { if (info == nullptr) { return ORBIS_KERNEL_ERROR_EFAULT; @@ -230,6 +250,31 @@ s32 PS4_SYSV_ABI sceKernelGetModuleList(s32* handles, u64 num_array, u64* out_co return ORBIS_OK; } +s32 PS4_SYSV_ABI sceKernelGetModuleList2(s32* handles, u64 num_array, u64* out_count) { + if (handles == nullptr || out_count == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + + auto* linker = Common::Singleton::Instance(); + u64 id = 0; + u64 index = 0; + auto* module = linker->GetModule(id); + while (module != nullptr && index < num_array) { + if (!module->IsSystemLib()) { + handles[index++] = id; + } + id++; + module = linker->GetModule(id); + } + + if (index == num_array && module != nullptr) { + return ORBIS_KERNEL_ERROR_ENOMEM; + } + + *out_count = index; + return ORBIS_OK; +} + s32 PS4_SYSV_ABI exit(s32 status) { UNREACHABLE_MSG("Exiting with status code {}", status); return 0; @@ -249,8 +294,11 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("RpQJJVKTiFM", "libkernel", 1, "libkernel", sceKernelGetModuleInfoForUnwind); LIB_FUNCTION("f7KBOafysXo", "libkernel", 1, "libkernel", sceKernelGetModuleInfoFromAddr); LIB_FUNCTION("kUpgrXIrz7Q", "libkernel", 1, "libkernel", sceKernelGetModuleInfo); + LIB_FUNCTION("QgsKEUfkqMA", "libkernel", 1, "libkernel", sceKernelGetModuleInfo2); + LIB_FUNCTION("QgsKEUfkqMA", "libkernel_module_info", 1, "libkernel", sceKernelGetModuleInfo2); LIB_FUNCTION("HZO7xOos4xc", "libkernel", 1, "libkernel", sceKernelGetModuleInfoInternal); LIB_FUNCTION("IuxnUuXk6Bg", "libkernel", 1, "libkernel", sceKernelGetModuleList); + LIB_FUNCTION("ZzzC3ZGVAkc", "libkernel", 1, "libkernel", sceKernelGetModuleList2); LIB_FUNCTION("6Z83sYWFlA8", "libkernel", 1, "libkernel", exit); } diff --git a/src/core/module.h b/src/core/module.h index c39310406..778344e33 100644 --- a/src/core/module.h +++ b/src/core/module.h @@ -5,6 +5,7 @@ #include #include +#include "common/config.h" #include "common/types.h" #include "core/loader/elf.h" #include "core/loader/symbols_resolver.h" @@ -164,6 +165,14 @@ public: return elf.IsSharedLib(); } + bool IsSystemLib() { + auto system_path = Config::getSysModulesPath(); + if (file.string().starts_with(system_path.string().c_str())) { + return true; + } + return false; + } + template T GetProcParam() const noexcept { return reinterpret_cast(proc_param_virtual_addr); From afc98937157262e32c8abf2ce372c3112fd44da9 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 8 Feb 2026 22:39:36 +0200 Subject: [PATCH 07/18] Added sceAudioOutGetSystemState (#4011) --- src/core/libraries/audio/audioout.cpp | 30 +++++++++++++++++---------- src/core/libraries/audio/audioout.h | 8 ++++++- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 6c58df94f..100ddd51c 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -496,11 +496,11 @@ s32 PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta state->rerouteCounter = 0; state->flag = 0; - LOG_INFO(Lib_AudioOut, - "called, handle={:#x}, state={}, output={}, channel={}, volume={}, " - "rerouteCounter={}, flag={}", - handle, fmt::ptr(state), state->output, state->channel, state->volume, - state->rerouteCounter, state->flag); + LOG_DEBUG(Lib_AudioOut, + "called, handle={:#x}, state={}, output={}, channel={}, volume={}, " + "rerouteCounter={}, flag={}", + handle, fmt::ptr(state), state->output, state->channel, state->volume, + state->rerouteCounter, state->flag); return ORBIS_OK; } @@ -729,7 +729,7 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { } s32 PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(s32 handle, s32 mixLevel) { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + LOG_INFO(Lib_AudioOut, "(STUBBED) called"); if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { LOG_ERROR(Lib_AudioOut, "audio is not init"); return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; @@ -769,6 +769,19 @@ s32 PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(s32 handle, s32 mixLevel) { return ORBIS_OK; } +s32 PS4_SYSV_ABI sceAudioOutGetSystemState(OrbisAudioOutSystemState* state) { + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "audio is not init"); + return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; + } + if (state == nullptr) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER; + } + memset(state, 0, sizeof(*state)); + LOG_DEBUG(Lib_AudioOut, "called"); + return ORBIS_OK; +} + /* * Stubbed functions **/ @@ -892,11 +905,6 @@ s32 PS4_SYSV_ABI sceAudioOutGetSparkVss() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudioOutGetSystemState() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - s32 PS4_SYSV_ABI sceAudioOutInitIpmiGetSession() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index 93db8150f..ffc1f9eb1 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -97,6 +97,12 @@ struct OrbisAudioOutPortState { u64 reserved64[2]; }; +struct OrbisAudioOutSystemState { + float loudness; + u8 reserved8[4]; + u64 reserved64[3]; +}; + struct AudioFormatInfo { bool is_float; u8 sample_size; @@ -162,7 +168,7 @@ s32 PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType(); s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo(); s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2(); s32 PS4_SYSV_ABI sceAudioOutGetSparkVss(); -s32 PS4_SYSV_ABI sceAudioOutGetSystemState(); +s32 PS4_SYSV_ABI sceAudioOutGetSystemState(OrbisAudioOutSystemState* state); s32 PS4_SYSV_ABI sceAudioOutInit(); s32 PS4_SYSV_ABI sceAudioOutInitIpmiGetSession(); s32 PS4_SYSV_ABI sceAudioOutMasteringGetState(); From 42f2697b500f251b2268c7e46707276e586a033f Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Mon, 9 Feb 2026 15:40:47 +0100 Subject: [PATCH 08/18] Fix deadlock from missed unlock call after #3946 (#4013) * Fix deadlock from missed unlock call after #3946 * copyright 2026 * Add the same fix to PoolCommit --- src/core/memory.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 561e72617..9d26142ce 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.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 "common/alignment.h" @@ -73,7 +73,7 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1 } u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) { - static constexpr u64 MinSizeToClamp = 3_GB; + static constexpr u64 MinSizeToClamp = 1_GB; // Dont bother with clamping if the size is small so we dont pay a map lookup on every buffer. if (size < MinSizeToClamp) { return size; @@ -349,7 +349,8 @@ s32 MemoryManager::Free(PAddr phys_addr, u64 size, bool is_checked) { } s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype) { - std::scoped_lock lk{mutex, unmap_mutex}; + std::scoped_lock lk{unmap_mutex}; + std::unique_lock lk2{mutex}; ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}", virtual_addr); @@ -434,6 +435,7 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 // Merge this VMA with similar nearby areas MergeAdjacent(vma_map, new_vma_handle); + lk2.unlock(); if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); } @@ -554,7 +556,7 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo } // Acquire writer lock. - std::scoped_lock lk2{mutex}; + std::unique_lock lk2{mutex}; // Create VMA representing this mapping. auto new_vma_handle = CreateArea(virtual_addr, size, prot, flags, type, name, alignment); @@ -650,6 +652,8 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo // TRACK_ALLOC(mapped_addr, size, "VMEM"); } + lk2.unlock(); + // If this is not a reservation, then map to GPU and address space if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); From ffae535a5ca879f7f543335adbd9035340380702 Mon Sep 17 00:00:00 2001 From: Niram7777 Date: Mon, 9 Feb 2026 18:19:39 +0000 Subject: [PATCH 09/18] [LOG] group same lines with counter (#4010) * [LOG] group same lines with counter * Log in single line counter * Protect log singleton from ps4 threads * Log always compact --- src/common/logging/backend.cpp | 50 ++++++++++++++++++++++++++++------ src/common/logging/log_entry.h | 1 + 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index d7c816da3..168350b96 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -208,26 +209,41 @@ public: } } + std::unique_lock entry_loc(_mutex); + + if (_last_entry.message == message) { + ++_last_entry.counter; + return; + } + + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); + } + + if (_last_entry.counter >= 1) { + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(_last_entry); + } else { + ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); + std::fflush(stdout); + } + } + using std::chrono::duration_cast; using std::chrono::microseconds; using std::chrono::steady_clock; - const Entry entry = { + this->_last_entry = { .timestamp = duration_cast(steady_clock::now() - time_origin), .log_class = log_class, .log_level = log_level, .filename = filename, .line_num = line_num, .function = function, - .message = std::move(message), + .message = message, .thread = Common::GetCurrentThreadName(), + .counter = 1, }; - if (Config::getLogType() == "async") { - message_queue.EmplaceWait(entry); - } else { - ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); - std::fflush(stdout); - } } private: @@ -259,6 +275,22 @@ private: } void StopBackendThread() { + // log last message + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); + } + + if (_last_entry.counter >= 1) { + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(_last_entry); + } else { + ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); + std::fflush(stdout); + } + } + + this->_last_entry = {}; + backend_thread.request_stop(); if (backend_thread.joinable()) { backend_thread.join(); @@ -292,6 +324,8 @@ private: MPSCQueue message_queue{}; std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; std::jthread backend_thread; + Entry _last_entry; + std::mutex _mutex; }; } // namespace diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h index 6c529f878..7b52ad7e1 100644 --- a/src/common/logging/log_entry.h +++ b/src/common/logging/log_entry.h @@ -22,6 +22,7 @@ struct Entry { std::string function; std::string message; std::string thread; + u32 counter = 0; }; } // namespace Common::Log From a706b325f42e362eb1f41a527d9967a3b38ef323 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 9 Feb 2026 22:59:39 +0200 Subject: [PATCH 10/18] optimize sdl3 audio out (#4015) --- src/core/libraries/audio/sdl_audio_out.cpp | 585 +++++++++++++-------- 1 file changed, 365 insertions(+), 220 deletions(-) diff --git a/src/core/libraries/audio/sdl_audio_out.cpp b/src/core/libraries/audio/sdl_audio_out.cpp index 572525c85..ce2598759 100644 --- a/src/core/libraries/audio/sdl_audio_out.cpp +++ b/src/core/libraries/audio/sdl_audio_out.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -14,15 +15,32 @@ #include "core/libraries/audio/audioout_backend.h" #include "core/libraries/kernel/threads.h" +// SIMD support detection +#if defined(__x86_64__) || defined(_M_X64) +#include +#define HAS_SSE2 +#endif + #define SDL_INVALID_AUDIODEVICEID 0 namespace Libraries::AudioOut { // Volume constants constexpr float VOLUME_0DB = 32768.0f; // 1 << 15 +constexpr float INV_VOLUME_0DB = 1.0f / VOLUME_0DB; +constexpr float VOLUME_EPSILON = 0.001f; +// Timing constants +constexpr u64 VOLUME_CHECK_INTERVAL_US = 50000; // Check every 50ms +constexpr u64 MIN_SLEEP_THRESHOLD_US = 10; +constexpr u64 TIMING_RESYNC_THRESHOLD_US = 100000; // Resync if >100ms behind + +// Queue management +constexpr u32 QUEUE_MULTIPLIER = 4; +// Memory alignment for SIMD +constexpr size_t AUDIO_BUFFER_ALIGNMENT = 32; // Channel positions -enum ChannelPos { +enum ChannelPos : u8 { FL = 0, FR = 1, FC = 2, @@ -45,187 +63,210 @@ public: num_channels(port.format_info.num_channels), is_float(port.format_info.is_float), is_std(port.format_info.is_std), channel_layout(port.format_info.channel_layout) { - // Calculate timing - period_us = (1000000ULL * buffer_frames + sample_rate / 2) / sample_rate; - last_output_time = 0; - next_output_time = 0; - - // Allocate internal buffer - internal_buffer_size = buffer_frames * sizeof(float) * num_channels; - internal_buffer = std::malloc(internal_buffer_size); - if (!internal_buffer) { - LOG_ERROR(Lib_AudioOut, "Failed to allocate internal audio buffer"); - return; + if (!Initialize(port.type)) { + LOG_ERROR(Lib_AudioOut, "Failed to initialize SDL audio backend"); } - - // Initialize current gain - current_gain.store(Config::getVolumeSlider() / 100.0f); - - // Select converter function - SelectConverter(); - - // Open SDL device - if (!OpenDevice(port.type)) { - std::free(internal_buffer); - internal_buffer = nullptr; - return; - } - - CalculateQueueThreshold(); } ~SDLPortBackend() override { - if (stream) { - SDL_DestroyAudioStream(stream); - } - if (internal_buffer) { - std::free(internal_buffer); - } + Cleanup(); } void Output(void* ptr) override { - if (!stream || !internal_buffer) { + if (!stream || !internal_buffer || !convert) [[unlikely]] { return; } - // Check for volume changes and update if needed - UpdateVolumeIfChanged(); - - // Get current time in microseconds - u64 current_time = Kernel::sceKernelGetProcessTime(); - - if (ptr != nullptr) { - // Simple format conversion (no volume application) - convert(ptr, internal_buffer, buffer_frames, nullptr); - - if (next_output_time == 0) { - next_output_time = current_time + period_us; - } else if (current_time > next_output_time) { - next_output_time = current_time + period_us; - } else { - u64 wait_until = next_output_time; - next_output_time += period_us; - - if (current_time < wait_until) { - u64 sleep_us = wait_until - current_time; - if (sleep_us > 10) { - sleep_us -= 10; - std::this_thread::sleep_for(std::chrono::microseconds(sleep_us)); - } - } - } - - last_output_time = current_time; - - // Check queue and clear if backed up - if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) { - LOG_DEBUG(Lib_AudioOut, "Clearing backed up audio queue ({} >= {})", queued, - queue_threshold); - SDL_ClearAudioStream(stream); - CalculateQueueThreshold(); - } - - if (!SDL_PutAudioStreamData(stream, internal_buffer, internal_buffer_size)) { - LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError()); - } + if (ptr == nullptr) [[unlikely]] { + return; } + + UpdateVolumeIfChanged(); + const u64 current_time = Kernel::sceKernelGetProcessTime(); + convert(ptr, internal_buffer, buffer_frames, nullptr); + HandleTiming(current_time); + + if ((output_count++ & 0xF) == 0) { // Check every 16 outputs + ManageAudioQueue(); + } + + if (!SDL_PutAudioStreamData(stream, internal_buffer, internal_buffer_size)) [[unlikely]] { + LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError()); + } + + last_output_time.store(current_time, std::memory_order_release); } void SetVolume(const std::array& ch_volumes) override { - if (!stream) { + if (!stream) [[unlikely]] { return; } + float max_channel_gain = 0.0f; - for (int i = 0; i < num_channels && i < 8; i++) { - float channel_gain = static_cast(ch_volumes[i]) / VOLUME_0DB; + const u32 channels_to_check = std::min(num_channels, 8u); + + for (u32 i = 0; i < channels_to_check; i++) { + const float channel_gain = static_cast(ch_volumes[i]) * INV_VOLUME_0DB; max_channel_gain = std::max(max_channel_gain, channel_gain); } - // Combine with global volume slider - float total_gain = max_channel_gain * (Config::getVolumeSlider() / 100.0f); + const float slider_gain = Config::getVolumeSlider() * 0.01f; // Faster than /100.0f + const float total_gain = max_channel_gain * slider_gain; - std::lock_guard lock(volume_mutex); + const float current = current_gain.load(std::memory_order_acquire); + if (std::abs(total_gain - current) < VOLUME_EPSILON) { + return; + } + + // Apply volume change if (SDL_SetAudioStreamGain(stream, total_gain)) { - current_gain.store(total_gain); + current_gain.store(total_gain, std::memory_order_release); LOG_DEBUG(Lib_AudioOut, "Set combined audio gain to {:.3f} (channel: {:.3f}, slider: {:.3f})", - total_gain, max_channel_gain, Config::getVolumeSlider() / 100.0f); + total_gain, max_channel_gain, slider_gain); } else { LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError()); } } u64 GetLastOutputTime() const { - return last_output_time; + return last_output_time.load(std::memory_order_acquire); } private: - std::atomic volume_update_needed{false}; - u64 last_volume_check_time{0}; - static constexpr u64 VOLUME_CHECK_INTERVAL_US = 50000; // Check every 50ms + bool Initialize(OrbisAudioOutPort type) { + // Calculate timing parameters + period_us = (1000000ULL * buffer_frames + sample_rate / 2) / sample_rate; + + // Allocate aligned internal buffer for SIMD operations + internal_buffer_size = buffer_frames * sizeof(float) * num_channels; + +#ifdef _WIN32 + internal_buffer = _aligned_malloc(internal_buffer_size, AUDIO_BUFFER_ALIGNMENT); +#else + if (posix_memalign(&internal_buffer, AUDIO_BUFFER_ALIGNMENT, internal_buffer_size) != 0) { + internal_buffer = nullptr; + } +#endif + + if (!internal_buffer) { + LOG_ERROR(Lib_AudioOut, "Failed to allocate aligned audio buffer of size {}", + internal_buffer_size); + return false; + } + + // Initialize current gain + current_gain.store(Config::getVolumeSlider() * 0.01f, std::memory_order_relaxed); + + if (!SelectConverter()) { + FreeAlignedBuffer(); + return false; + } + + // Open SDL device + if (!OpenDevice(type)) { + FreeAlignedBuffer(); + return false; + } + + CalculateQueueThreshold(); + return true; + } + + void Cleanup() { + if (stream) { + SDL_DestroyAudioStream(stream); + stream = nullptr; + } + FreeAlignedBuffer(); + } + + void FreeAlignedBuffer() { + if (internal_buffer) { +#ifdef _WIN32 + _aligned_free(internal_buffer); +#else + free(internal_buffer); +#endif + internal_buffer = nullptr; + } + } void UpdateVolumeIfChanged() { - u64 current_time = Kernel::sceKernelGetProcessTime(); + const u64 current_time = Kernel::sceKernelGetProcessTime(); - // Only check volume every 50ms to reduce overhead - if (current_time - last_volume_check_time >= VOLUME_CHECK_INTERVAL_US) { - last_volume_check_time = current_time; + if (current_time - last_volume_check_time < VOLUME_CHECK_INTERVAL_US) { + return; + } - float config_volume = Config::getVolumeSlider() / 100.0f; - float stored_gain = current_gain.load(); + last_volume_check_time = current_time; - if (std::abs(config_volume - stored_gain) > 0.001f) { - if (SDL_SetAudioStreamGain(stream, config_volume)) { - current_gain.store(config_volume); - LOG_DEBUG(Lib_AudioOut, "Updated audio gain to {:.3f}", config_volume); - } else { - LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError()); - } + const float config_volume = Config::getVolumeSlider() * 0.01f; + const float stored_gain = current_gain.load(std::memory_order_acquire); + + // Only update if the difference is significant + if (std::abs(config_volume - stored_gain) > VOLUME_EPSILON) { + if (SDL_SetAudioStreamGain(stream, config_volume)) { + current_gain.store(config_volume, std::memory_order_release); + LOG_DEBUG(Lib_AudioOut, "Updated audio gain to {:.3f}", config_volume); + } else { + LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError()); } } } + + void HandleTiming(u64 current_time) { + if (next_output_time == 0) [[unlikely]] { + // First output - set initial timing + next_output_time = current_time + period_us; + return; + } + + const s64 time_diff = static_cast(current_time - next_output_time); + + if (time_diff > static_cast(TIMING_RESYNC_THRESHOLD_US)) [[unlikely]] { + // We're far behind - resync + next_output_time = current_time + period_us; + } else if (time_diff < 0) { + // We're ahead of schedule - wait + const u64 time_to_wait = static_cast(-time_diff); + next_output_time += period_us; + + if (time_to_wait > MIN_SLEEP_THRESHOLD_US) { + // Sleep for most of the wait period + const u64 sleep_duration = time_to_wait - MIN_SLEEP_THRESHOLD_US; + std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration)); + } + } else { + // Slightly behind or on time - just advance + next_output_time += period_us; + } + } + + void ManageAudioQueue() { + const auto queued = SDL_GetAudioStreamQueued(stream); + + if (queued >= queue_threshold) [[unlikely]] { + LOG_DEBUG(Lib_AudioOut, "Clearing backed up audio queue ({} >= {})", queued, + queue_threshold); + SDL_ClearAudioStream(stream); + CalculateQueueThreshold(); + } + } + bool OpenDevice(OrbisAudioOutPort type) { const SDL_AudioSpec fmt = { - .format = SDL_AUDIO_F32LE, // Always use float for internal processing + .format = SDL_AUDIO_F32LE, .channels = static_cast(num_channels), .freq = static_cast(sample_rate), }; - // Determine device name - std::string device_name = GetDeviceName(type); - SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID; + // Determine device + const std::string device_name = GetDeviceName(type); + const SDL_AudioDeviceID dev_id = SelectAudioDevice(device_name, type); - if (device_name == "None") { - LOG_INFO(Lib_AudioOut, "Audio device disabled for port type {}", - static_cast(type)); + if (dev_id == SDL_INVALID_AUDIODEVICEID) { return false; - } else if (device_name.empty() || device_name == "Default Device") { - dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; - } else { - int num_devices = 0; - SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(&num_devices); - - if (dev_array) { - bool found = false; - for (int i = 0; i < num_devices; i++) { - const char* dev_name = SDL_GetAudioDeviceName(dev_array[i]); - if (dev_name && std::string(dev_name) == device_name) { - dev_id = dev_array[i]; - found = true; - break; - } - } - SDL_free(dev_array); - - if (!found) { - LOG_WARNING(Lib_AudioOut, "Audio device '{}' not found, using default", - device_name); - dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; - } - } else { - LOG_WARNING(Lib_AudioOut, "No audio devices found, using default"); - dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; - } } // Create audio stream @@ -235,30 +276,15 @@ private: return false; } - // Set channel map - if (num_channels > 0) { - std::vector channel_map(num_channels); - - if (is_std && num_channels == 8) { - // Standard 8CH layout - channel_map = {FL, FR, FC, LF, STD_SL, STD_SR, STD_BL, STD_BR}; - } else { - // Use provided channel layout - for (int i = 0; i < num_channels; i++) { - channel_map[i] = channel_layout[i]; - } - } - - if (!SDL_SetAudioStreamInputChannelMap(stream, channel_map.data(), num_channels)) { - LOG_ERROR(Lib_AudioOut, "Failed to set channel map: {}", SDL_GetError()); - SDL_DestroyAudioStream(stream); - stream = nullptr; - return false; - } + // Configure channel mapping + if (!ConfigureChannelMap()) { + SDL_DestroyAudioStream(stream); + stream = nullptr; + return false; } // Set initial volume - float initial_gain = current_gain.load(); + const float initial_gain = current_gain.load(std::memory_order_relaxed); if (!SDL_SetAudioStreamGain(stream, initial_gain)) { LOG_WARNING(Lib_AudioOut, "Failed to set initial audio gain: {}", SDL_GetError()); } @@ -276,24 +302,81 @@ private: return true; } - std::string GetDeviceName(OrbisAudioOutPort type) { + SDL_AudioDeviceID SelectAudioDevice(const std::string& device_name, OrbisAudioOutPort type) { + if (device_name == "None") { + LOG_INFO(Lib_AudioOut, "Audio device disabled for port type {}", + static_cast(type)); + return SDL_INVALID_AUDIODEVICEID; + } + + if (device_name.empty() || device_name == "Default Device") { + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + // Search for specific device + int num_devices = 0; + SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(&num_devices); + + if (!dev_array) { + LOG_WARNING(Lib_AudioOut, "No audio devices found, using default"); + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + SDL_AudioDeviceID selected_device = SDL_INVALID_AUDIODEVICEID; + + for (int i = 0; i < num_devices; i++) { + const char* dev_name = SDL_GetAudioDeviceName(dev_array[i]); + if (dev_name && device_name == dev_name) { + selected_device = dev_array[i]; + break; + } + } + + SDL_free(dev_array); + + if (selected_device == SDL_INVALID_AUDIODEVICEID) { + LOG_WARNING(Lib_AudioOut, "Audio device '{}' not found, using default", device_name); + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + return selected_device; + } + + bool ConfigureChannelMap() { + if (num_channels == 0) { + return true; + } + + std::vector channel_map(num_channels); + + if (is_std && num_channels == 8) { + // Standard 8CH layout requires remapping + channel_map = {FL, FR, FC, LF, STD_SL, STD_SR, STD_BL, STD_BR}; + } else { + std::copy_n(channel_layout.begin(), num_channels, channel_map.begin()); + } + + if (!SDL_SetAudioStreamInputChannelMap(stream, channel_map.data(), num_channels)) { + LOG_ERROR(Lib_AudioOut, "Failed to set channel map: {}", SDL_GetError()); + return false; + } + + return true; + } + + std::string GetDeviceName(OrbisAudioOutPort type) const { switch (type) { case OrbisAudioOutPort::Main: case OrbisAudioOutPort::Bgm: return Config::getMainOutputDevice(); - // case OrbisAudioOutPort::Voice: - // case OrbisAudioOutPort::Personal: - // return Config::getHeadphoneOutputDevice(); case OrbisAudioOutPort::PadSpk: return Config::getPadSpkOutputDevice(); - // case OrbisAudioOutPort::Aux: - // return Config::getSpecialOutputDevice(); default: return Config::getMainOutputDevice(); } } - void SelectConverter() { + bool SelectConverter() { if (is_float) { switch (num_channels) { case 1: @@ -303,15 +386,11 @@ private: convert = &ConvertF32Stereo; break; case 8: - if (is_std) { - convert = &ConvertF32Std8CH; - } else { - convert = &ConvertF32_8CH; - } + convert = is_std ? &ConvertF32Std8CH : &ConvertF32_8CH; break; default: LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels); - convert = nullptr; + return false; } } else { switch (num_channels) { @@ -319,32 +398,43 @@ private: convert = &ConvertS16Mono; break; case 2: +#if defined(HAS_SSE2) + convert = &ConvertS16StereoSIMD; +#else convert = &ConvertS16Stereo; +#endif break; case 8: +#if defined(HAS_SSE2) + convert = &ConvertS16_8CH_SIMD; +#else convert = &ConvertS16_8CH; +#endif break; default: LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels); - convert = nullptr; + return false; } } + + return true; } void CalculateQueueThreshold() { - if (!stream) + if (!stream) { return; + } SDL_AudioSpec discard; - int sdl_buffer_frames; + int sdl_buffer_frames = 0; + if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard, &sdl_buffer_frames)) { LOG_WARNING(Lib_AudioOut, "Failed to get SDL buffer size: {}", SDL_GetError()); - sdl_buffer_frames = 0; } - u32 sdl_buffer_size = sdl_buffer_frames * sizeof(float) * num_channels; - queue_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4; + const u32 sdl_buffer_size = sdl_buffer_frames * sizeof(float) * num_channels; + queue_threshold = std::max(guest_buffer_size, sdl_buffer_size) * QUEUE_MULTIPLIER; LOG_DEBUG(Lib_AudioOut, "Audio queue threshold: {} bytes (SDL buffer: {} frames)", queue_threshold, sdl_buffer_frames); @@ -352,15 +442,12 @@ private: using ConverterFunc = void (*)(const void* src, void* dst, u32 frames, const float* volumes); - // Remove volume parameter and application from all converters static void ConvertS16Mono(const void* src, void* dst, u32 frames, const float*) { const s16* s = static_cast(src); float* d = static_cast(dst); - constexpr float inv_scale = 1.0f / VOLUME_0DB; - for (u32 i = 0; i < frames; i++) { - d[i] = s[i] * inv_scale; + d[i] = s[i] * INV_VOLUME_0DB; } } @@ -368,28 +455,82 @@ private: const s16* s = static_cast(src); float* d = static_cast(dst); - constexpr float inv_scale = 1.0f / VOLUME_0DB; - - for (u32 i = 0; i < frames; i++) { - d[i * 2] = s[i * 2] * inv_scale; - d[i * 2 + 1] = s[i * 2 + 1] * inv_scale; + const u32 num_samples = frames << 1; // * 2 + for (u32 i = 0; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; } } +#ifdef HAS_SSE2 + static void ConvertS16StereoSIMD(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const __m128 scale = _mm_set1_ps(INV_VOLUME_0DB); + const u32 num_samples = frames << 1; + u32 i = 0; + + // Process 8 samples at a time (4 stereo frames) + for (; i + 8 <= num_samples; i += 8) { + // Load 8 s16 values + __m128i s16_vals = _mm_loadu_si128(reinterpret_cast(&s[i])); + + // Convert to 32-bit integers + __m128i s32_lo = _mm_cvtepi16_epi32(s16_vals); + __m128i s32_hi = _mm_cvtepi16_epi32(_mm_srli_si128(s16_vals, 8)); + + // Convert to float and scale + __m128 f_lo = _mm_mul_ps(_mm_cvtepi32_ps(s32_lo), scale); + __m128 f_hi = _mm_mul_ps(_mm_cvtepi32_ps(s32_hi), scale); + + // Store results + _mm_storeu_ps(&d[i], f_lo); + _mm_storeu_ps(&d[i + 4], f_hi); + } + + // Handle remaining samples + for (; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } +#endif + static void ConvertS16_8CH(const void* src, void* dst, u32 frames, const float*) { const s16* s = static_cast(src); float* d = static_cast(dst); - constexpr float inv_scale = 1.0f / VOLUME_0DB; - - for (u32 i = 0; i < frames; i++) { - for (int ch = 0; ch < 8; ch++) { - d[i * 8 + ch] = s[i * 8 + ch] * inv_scale; - } + const u32 num_samples = frames << 3; // * 8 + for (u32 i = 0; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; } } - // Float converters become simple memcpy or passthrough +#ifdef HAS_SSE2 + static void ConvertS16_8CH_SIMD(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const __m128 scale = _mm_set1_ps(INV_VOLUME_0DB); + const u32 num_samples = frames << 3; + u32 i = 0; + + // Process 8 samples at a time + for (; i + 8 <= num_samples; i += 8) { + __m128i s16_vals = _mm_loadu_si128(reinterpret_cast(&s[i])); + __m128i s32_lo = _mm_cvtepi16_epi32(s16_vals); + __m128i s32_hi = _mm_cvtepi16_epi32(_mm_srli_si128(s16_vals, 8)); + __m128 f_lo = _mm_mul_ps(_mm_cvtepi32_ps(s32_lo), scale); + __m128 f_hi = _mm_mul_ps(_mm_cvtepi32_ps(s32_hi), scale); + _mm_storeu_ps(&d[i], f_lo); + _mm_storeu_ps(&d[i + 4], f_hi); + } + + for (; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } +#endif + static void ConvertF32Mono(const void* src, void* dst, u32 frames, const float*) { std::memcpy(dst, src, frames * sizeof(float)); } @@ -406,50 +547,54 @@ private: const float* s = static_cast(src); float* d = static_cast(dst); + // Channel remapping for standard 8CH layout for (u32 i = 0; i < frames; i++) { - d[i * 8 + FL] = s[i * 8 + FL]; - d[i * 8 + FR] = s[i * 8 + FR]; - d[i * 8 + FC] = s[i * 8 + FC]; - d[i * 8 + LF] = s[i * 8 + LF]; - d[i * 8 + SL] = s[i * 8 + STD_SL]; // Channel remapping still needed - d[i * 8 + SR] = s[i * 8 + STD_SR]; - d[i * 8 + BL] = s[i * 8 + STD_BL]; - d[i * 8 + BR] = s[i * 8 + STD_BR]; + const u32 offset = i << 3; // * 8 + + d[offset + FL] = s[offset + FL]; + d[offset + FR] = s[offset + FR]; + d[offset + FC] = s[offset + FC]; + d[offset + LF] = s[offset + LF]; + d[offset + SL] = s[offset + STD_SL]; + d[offset + SR] = s[offset + STD_SR]; + d[offset + BL] = s[offset + STD_BL]; + d[offset + BR] = s[offset + STD_BR]; } } - // Member variables - u32 frame_size; - u32 guest_buffer_size; - u32 buffer_frames; - u32 sample_rate; - u32 num_channels; - bool is_float; - bool is_std; - std::array channel_layout; + // Audio format parameters + const u32 frame_size; + const u32 guest_buffer_size; + const u32 buffer_frames; + const u32 sample_rate; + const u32 num_channels; + const bool is_float; + const bool is_std; + const std::array channel_layout; - u64 period_us; - u64 last_output_time; - u64 next_output_time; + alignas(64) u64 period_us{0}; + alignas(64) std::atomic last_output_time{0}; + u64 next_output_time{0}; + u64 last_volume_check_time{0}; + u32 output_count{0}; // Buffers - u32 internal_buffer_size; - void* internal_buffer; + u32 internal_buffer_size{0}; + void* internal_buffer{nullptr}; - // Converter function - ConverterFunc convert; + // Converter function pointer + ConverterFunc convert{nullptr}; - // Volume tracking - std::atomic current_gain{1.0f}; - mutable std::mutex volume_mutex; + // Volume management + alignas(64) std::atomic current_gain{1.0f}; - // SDL - SDL_AudioStream* stream{}; - u32 queue_threshold{}; + // SDL audio stream + SDL_AudioStream* stream{nullptr}; + u32 queue_threshold{0}; }; std::unique_ptr SDLAudioOut::Open(PortOut& port) { return std::make_unique(port); } -} // namespace Libraries::AudioOut \ No newline at end of file +} // namespace Libraries::AudioOut From 99661aa6b3ba7da57eec720d41bca55ae631f8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 10 Feb 2026 22:59:07 +0100 Subject: [PATCH 11/18] RE encountered NpTus functions (#4018) --- CMakeLists.txt | 1 + src/core/libraries/np/np_error.h | 10 +- src/core/libraries/np/np_manager.h | 2 + src/core/libraries/np/np_tus.cpp | 573 ++++++++++++++++++++++--- src/core/libraries/np/np_tus.h | 213 +++------ src/core/libraries/np/object_manager.h | 56 +++ 6 files changed, 647 insertions(+), 208 deletions(-) create mode 100644 src/core/libraries/np/object_manager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cef2df24..ba16732ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -613,6 +613,7 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/np_sns_facebook_dialog.h src/core/libraries/np/np_partner.cpp src/core/libraries/np/np_partner.h + src/core/libraries/np/object_manager.h ) set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp diff --git a/src/core/libraries/np/np_error.h b/src/core/libraries/np/np_error.h index cf2014148..518344ba3 100644 --- a/src/core/libraries/np/np_error.h +++ b/src/core/libraries/np/np_error.h @@ -13,4 +13,12 @@ constexpr int ORBIS_NP_ERROR_INVALID_SIZE = 0x80550011; constexpr int ORBIS_NP_ERROR_ABORTED = 0x80550012; constexpr int ORBIS_NP_ERROR_REQUEST_MAX = 0x80550013; constexpr int ORBIS_NP_ERROR_REQUEST_NOT_FOUND = 0x80550014; -constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015; \ No newline at end of file +constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015; + +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = 0x80550704; +constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = 0x80550706; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = 0x8055070c; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ID = 0x8055070e; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT = 0x80550714; +constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID = 0x80550718; +constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID = 0x80550719; \ No newline at end of file diff --git a/src/core/libraries/np/np_manager.h b/src/core/libraries/np/np_manager.h index 078fa804a..49250db03 100644 --- a/src/core/libraries/np/np_manager.h +++ b/src/core/libraries/np/np_manager.h @@ -93,6 +93,8 @@ struct OrbisNpCreateAsyncRequestParameter { void RegisterNpCallback(std::string key, std::function cb); void DeregisterNpCallback(std::string key); +s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpId* np_id); s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpOnlineId* online_id); diff --git a/src/core/libraries/np/np_tus.cpp b/src/core/libraries/np/np_tus.cpp index e0d0aaad1..d18cf4ec0 100644 --- a/src/core/libraries/np/np_tus.cpp +++ b/src/core/libraries/np/np_tus.cpp @@ -1,15 +1,87 @@ // SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +#include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_manager.h" #include "core/libraries/np/np_tus.h" +#include "core/libraries/np/np_types.h" +#include "core/libraries/np/object_manager.h" +#include "core/libraries/system/userservice.h" namespace Libraries::Np::NpTus { -s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +int PackReqId(int libCtxId, int reqId) { + return ((libCtxId & 0xFFFF) << 16) | (reqId & 0xFFFF); +} + +std::pair UnpackReqId(int reqId) { + return {reqId >> 16, reqId & 0xFFFF}; +} + +bool IsReqId(int id) { + return id > (1 << 16); +} + +struct NpTusRequest { + std::future task; + + void Start(auto lambda) { + this->task = std::async(std::launch::async, lambda); + } +}; + +using NpTusRequestsManager = + ObjectManager; + +struct NpTusTitleContext { + u32 serviceLabel; + OrbisNpId npId; + NpTusRequestsManager requestsManager; + + s32 GetRequest(int reqId, NpTusRequest** out) { + NpTusRequest* req = nullptr; + if (auto ret = requestsManager.GetObject(reqId, &req); ret < 0) { + return ret; + } + + *out = req; + + return ORBIS_OK; + } + + s32 DeleteRequest(int reqId) { + return requestsManager.DeleteObject(reqId); + } +}; + +using NpTusContextManager = + ObjectManager; + +static NpTusContextManager ctxManager; + +s32 GetRequest(int requestId, NpTusRequest** out) { + auto [ctxId, reqId] = UnpackReqId(requestId); + + NpTusTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + NpTusRequest* req = nullptr; + if (auto ret = ctx->GetRequest(reqId, &req); ret < 0) { + return ret; + } + + *out = req; + return ORBIS_OK; } @@ -33,9 +105,34 @@ s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUserAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtx() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, OrbisNpId* npId) { + if (!npId) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (serviceLabel == ORBIS_NP_INVALID_SERVICE_LABEL) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpTus, "serviceLabel = {}, npId->data = {}", serviceLabel, npId->handle.data); + + return ctxManager.CreateObject(serviceLabel, *npId); +} + +s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtxA(OrbisNpServiceLabel serviceLabel, + Libraries::UserService::OrbisUserServiceUserId userId) { + LOG_ERROR(Lib_NpTus, "serviceLabel = {}, userId = {}", serviceLabel, userId); + OrbisNpId npId; + auto ret = NpManager::sceNpGetNpId(userId, &npId); + + if (ret < 0) { + return ret; + } + + return sceNpTusCreateNpTitleCtx(serviceLabel, &npId); +} + +s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, OrbisNpId* npId) { + LOG_INFO(Lib_NpTus, "redirecting"); + return sceNpTusCreateNpTitleCtx(serviceLabel, npId); } s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotData() { @@ -58,14 +155,33 @@ s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusGetData() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusGetDataAsync(int reqId, OrbisNpId* npId, OrbisNpTusSlotId slotId, + OrbisNpTusDataStatus* dataStatus, u64 dataStatusSize, + void* data, u64 dataSize, void* option) { + LOG_INFO( + Lib_NpTus, + "reqId = {:#x}, slotId = {}, dataStatusSize = {}, data = {}, dataSize = {}, option = {}", + reqId, slotId, dataStatusSize, data, dataSize, fmt::ptr(option)); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusGetDataAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpTusGetData(int reqId, OrbisNpId* npId, OrbisNpTusSlotId slotId, + OrbisNpTusDataStatus* dataStatus, u64 dataStatusSize, void* data, + u64 dataSize, void* option) { + LOG_ERROR( + Lib_NpTus, + "reqId = {:#x}, slotId = {}, dataStatusSize = {}, data = {}, dataSize = {}, option = {}", + reqId, slotId, dataStatusSize, data, dataSize, fmt::ptr(option)); + + auto ret = sceNpTusGetDataAsync(reqId, npId, slotId, dataStatus, dataStatusSize, data, dataSize, + option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ret; } s32 PS4_SYSV_ABI sceNpTusGetDataVUser() { @@ -118,13 +234,45 @@ s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUserAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariable() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync(int reqId, OrbisNpId* npId, + OrbisNpTusSlotId* slotIds, s64* variables, + u64 variablesSize, int arrayLen, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {}, npId = {}, slotIds = {}, variables = {}, variablesSize = {}, arrayLen = " + "{}, option = {}", + reqId, npId ? npId->handle.data : "", fmt::ptr(slotIds), fmt::ptr(variables), + variablesSize, arrayLen, fmt::ptr(option)); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariable(int reqId, OrbisNpId* npId, OrbisNpTusSlotId* slotIds, + s64* variables, u64 variablesSize, int arrayLen, + void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {}, npId = {}, slotIds = {}, variables = {}, variablesSize = {}, arrayLen = " + "{}, option = {}", + reqId, npId ? npId->handle.data : "", fmt::ptr(slotIds), fmt::ptr(variables), + variablesSize, arrayLen, fmt::ptr(option)); + + auto ret = sceNpTusGetMultiSlotVariableAsync(reqId, npId, slotIds, variables, variablesSize, + arrayLen, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + return ORBIS_OK; } @@ -143,8 +291,24 @@ s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatus() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAsync(int reqId, OrbisNpId* npIds, + OrbisNpTusSlotId slotId, + OrbisNpTusDataStatus* statuses, + u64 statusesBytes, int arrayLen, + void* option) { + LOG_ERROR(Lib_NpTus, "(STUBBED) reqId = {:#x}, slotId = {}, arrayLen = {}, option = {}", reqId, + slotId, arrayLen, fmt::ptr(option)); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + return ORBIS_OK; } @@ -178,13 +342,49 @@ s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUserAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusSetData() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusSetDataAsync(int reqId, OrbisNpId* npId, OrbisNpTusSlotId slotId, + u64 totalSize, u64 sendSize, const void* data, + const OrbisNpTusDataInfo* info, u64 infoSize, + const OrbisNpId* lastChangedAuthor, + Libraries::Rtc::OrbisRtcTick lastChanged, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, npId = {}, slotId = {}, totalSize = {}, sendSize = {}, " + "info->size = {}, infoSize = {}, lastChangedAuthor = {}", + reqId, npId ? npId->handle.data : "", slotId, totalSize, sendSize, + info ? info->size : 0, infoSize, + lastChangedAuthor ? lastChangedAuthor->handle.data : ""); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + req->Start([=]() { + // + return 0; + }); + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusSetDataAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusSetData(int reqId, OrbisNpId* npId, OrbisNpTusSlotId slotId, u64 totalSize, + u64 sendSize, const void* data, const OrbisNpTusDataInfo* info, + u64 infoSize, const OrbisNpId* lastChangedAuthor, + Libraries::Rtc::OrbisRtcTick lastChanged, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, npId = {}, slotId = {}, totalSize = {}, sendSize = {}, " + "info->size = {}, infoSize = {}, lastChangedAuthor = {}", + reqId, npId ? npId->handle.data : "", slotId, totalSize, sendSize, + info ? info->size : 0, infoSize, + lastChangedAuthor ? lastChangedAuthor->handle.data : ""); + + auto ret = sceNpTusSetDataAsync(reqId, npId, slotId, totalSize, sendSize, data, info, infoSize, + lastChangedAuthor, lastChanged, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + return ORBIS_OK; } @@ -203,8 +403,39 @@ s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariable() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAsync(int reqId, OrbisNpId* npId, + OrbisNpTusSlotId* slotIds, s64* variables, + int arrayLen, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {}, npId = {}, slotIds = {}, variables = {}, arrayLen = {}, option = {}", + reqId, npId ? npId->handle.data : "", fmt::ptr(slotIds), fmt::ptr(variables), arrayLen, + fmt::ptr(option)); + + if (!slotIds || !variables) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (arrayLen < 1 || option) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayLen > 64) { + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + } + if (std::ranges::any_of( + std::vector>(slotIds, slotIds + arrayLen), + [](auto id) { return id < 0; })) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + return ORBIS_OK; } @@ -228,19 +459,59 @@ s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUserAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtxA() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtxA(OrbisNpServiceLabel serviceLabel, + Libraries::UserService::OrbisUserServiceUserId userId) { + LOG_DEBUG(Lib_NpTus, "redirecting"); + return sceNpTusCreateNpTitleCtxA(serviceLabel, userId); +} + +s32 PS4_SYSV_ABI sceNpTssGetDataAsync(int reqId, OrbisNpTssSlotId slotId, + OrbisNpTssDataStatus* dataStatus, u64 dataStatusSize, + void* data, u64 dataSize, OrbisNpTssGetDataOptParam* option) { + LOG_INFO(Lib_NpTus, "reqId = {:#x}, slotId = {}, dataStatusSize = {}, dataSize = {}", reqId, + slotId, dataStatusSize, dataSize); + + if (option && option->size != 0x20) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (dataStatus && dataStatusSize != 0x18) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (slotId < 0 || slotId > 15) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + if (dataStatus) { + dataStatus->status = OrbisNpTssStatus::Ok; + dataStatus->contentLength = 0; + } + return 0; + }); + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTssGetData() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; -} +s32 PS4_SYSV_ABI sceNpTssGetData(int reqId, OrbisNpTssSlotId slotId, + OrbisNpTssDataStatus* dataStatus, u64 dataStatusSize, void* data, + u64 dataSize, OrbisNpTssGetDataOptParam* option) { + LOG_INFO(Lib_NpTus, "reqId = {:#x}, slotId = {}, dataStatusSize = {}, dataSize = {}", reqId, + slotId, dataStatusSize, dataSize); -s32 PS4_SYSV_ABI sceNpTssGetDataAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; + auto ret = + sceNpTssGetDataAsync(reqId, slotId, dataStatus, dataStatusSize, data, dataSize, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ret; } s32 PS4_SYSV_ABI sceNpTssGetSmallStorage() { @@ -313,14 +584,20 @@ s32 PS4_SYSV_ABI sceNpTusChangeModeForOtherSaveDataOwners() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtxA() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; -} +s32 PS4_SYSV_ABI sceNpTusCreateRequest(int libCtxId) { + LOG_INFO(Lib_NpTus, "libCtxId = {}", libCtxId); -s32 PS4_SYSV_ABI sceNpTusCreateRequest() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; + NpTusTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(libCtxId, &ctx); ret < 0) { + return ret; + } + + auto req = ctx->requestsManager.CreateObject(); + if (req < 0) { + return req; + } + + return PackReqId(libCtxId, req); } s32 PS4_SYSV_ABI sceNpTusCreateTitleCtx() { @@ -368,24 +645,70 @@ s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUserAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusDeleteNpTitleCtx() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusDeleteNpTitleCtx(int ctxId) { + LOG_INFO(Lib_NpTus, "ctxId = {}", ctxId); + + return ctxManager.DeleteObject(ctxId); +} + +s32 PS4_SYSV_ABI sceNpTusDeleteRequest(int requestId) { + LOG_INFO(Lib_NpTus, "requestId = {:#x}", requestId); + + auto [ctxId, reqId] = UnpackReqId(requestId); + + NpTusTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + return ctx->DeleteRequest(reqId); +} + +s32 PS4_SYSV_ABI sceNpTusGetDataAAsync(int reqId, OrbisNpAccountId accountId, + OrbisNpTusSlotId slotId, OrbisNpTusDataStatusA* dataStatus, + u64 dataStatusSize, void* data, u64 dataSize, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, accountId = {:#x}, slotId = {}, dataStatus = {}, dataStatusSize = {}, " + "dataSize = {}", + reqId, accountId, slotId, fmt::ptr(dataStatus), dataStatusSize, dataSize); + + if (slotId < 0 || option) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (dataStatusSize != sizeof(OrbisNpTusDataStatusA)) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusDeleteRequest() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; -} +s32 PS4_SYSV_ABI sceNpTusGetDataA(int reqId, OrbisNpAccountId accountId, OrbisNpTusSlotId slotId, + OrbisNpTusDataStatusA* dataStatus, u64 dataStatusSize, void* data, + u64 dataSize, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, accountId = {:#x}, slotId = {}, dataStatus = {}, dataStatusSize = {}, " + "dataSize = {}", + reqId, accountId, slotId, fmt::ptr(dataStatus), dataStatusSize, dataSize); -s32 PS4_SYSV_ABI sceNpTusGetDataA() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; -} + auto ret = sceNpTusGetDataAAsync(reqId, accountId, slotId, dataStatus, dataStatusSize, data, + dataSize, option); + if (ret < 0) { + return ret; + } -s32 PS4_SYSV_ABI sceNpTusGetDataAAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; + sceNpTusWaitAsync(reqId, &ret); + + return ret; } s32 PS4_SYSV_ABI sceNpTusGetDataAVUser() { @@ -458,13 +781,62 @@ s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSaveAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusA() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync(int reqId, OrbisNpAccountId accountId, + OrbisNpTusSlotId* slotIds, + OrbisNpTusDataStatusA* statuses, + u64 statusesSize, int arrayLen, + void* option) { + LOG_ERROR(Lib_NpTus, "reqId = {:#x}, accountId = {}, arrayLen = {}, option = {}", reqId, + accountId, arrayLen, fmt::ptr(option)); + + if (!slotIds || !statuses) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (arrayLen < 1 || option) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayLen * sizeof(OrbisNpTusDataStatusA) != statusesSize) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (arrayLen > 64) { + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + } + if (std::ranges::any_of( + std::vector>(slotIds, slotIds + arrayLen), + [](auto id) { return id < 0; })) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + // if sdk_ver >= 5.50 clear the statuses array + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusA(int reqId, OrbisNpAccountId accountId, + OrbisNpTusSlotId* slotIds, + OrbisNpTusDataStatusA* statuses, u64 statusesSize, + int arrayLen, void* option) { + LOG_ERROR(Lib_NpTus, "reqId = {:#x}, accountId = {}, arrayLen = {}, option = {}", reqId, + accountId, arrayLen, fmt::ptr(option)); + + auto ret = sceNpTusGetMultiSlotDataStatusAAsync(reqId, accountId, slotIds, statuses, + statusesSize, arrayLen, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + return ORBIS_OK; } @@ -618,18 +990,72 @@ s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusPollAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusPollAsync(int reqId, int* result) { + LOG_INFO(Lib_NpTus, "reqId = {:#x}", reqId); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + if (!req->task.valid()) { + LOG_ERROR(Lib_NpTus, "request not started"); + return 1; + } + if (req->task.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + LOG_DEBUG(Lib_NpTus, "request finished"); + if (result) { + *result = req->task.get(); + } + return 0; + } + + return 1; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataAAsync(int reqId, OrbisNpAccountId accountId, + OrbisNpTusSlotId slotId, u64 totalSize, u64 sendSize, + const void* data, const OrbisNpTusDataInfo* info, + u64 infoSize, const OrbisNpAccountId* lastChangedAuthor, + Libraries::Rtc::OrbisRtcTick* lastChanged, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, accountId = {}, slotId = {}, totalSize = {}, sendSize = {}, " + "info->size = {}, infoSize = {}, lastChangedAuthor = {}", + reqId, accountId, slotId, totalSize, sendSize, info ? info->size : 0, infoSize, + lastChangedAuthor ? *lastChangedAuthor : 0); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusSetDataA() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); - return ORBIS_OK; -} +s32 PS4_SYSV_ABI sceNpTusSetDataA(int reqId, OrbisNpAccountId accountId, OrbisNpTusSlotId slotId, + u64 totalSize, u64 sendSize, const void* data, + const OrbisNpTusDataInfo* info, u64 infoSize, + const OrbisNpAccountId* lastChangedAuthor, + Libraries::Rtc::OrbisRtcTick* lastChanged, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, accountId = {}, slotId = {}, totalSize = {}, sendSize = {}, " + "info->size = {}, infoSize = {}, lastChangedAuthor = {}", + reqId, accountId, slotId, totalSize, sendSize, info ? info->size : 0, infoSize, + lastChangedAuthor ? *lastChangedAuthor : 0); + + auto ret = sceNpTusSetDataAAsync(reqId, accountId, slotId, totalSize, sendSize, data, info, + infoSize, lastChangedAuthor, lastChanged, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); -s32 PS4_SYSV_ABI sceNpTusSetDataAAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); return ORBIS_OK; } @@ -713,8 +1139,25 @@ s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTusWaitAsync() { - LOG_ERROR(Lib_NpTus, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpTusWaitAsync(int reqId, int* result) { + LOG_INFO(Lib_NpTus, "reqId = {:#x}", reqId); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + if (!req->task.valid()) { + LOG_ERROR(Lib_NpTus, "request not started"); + return 1; + } + + req->task.wait(); + + LOG_DEBUG(Lib_NpTus, "request finished"); + if (result) { + *result = req->task.get(); + } return ORBIS_OK; } diff --git a/src/core/libraries/np/np_tus.h b/src/core/libraries/np/np_tus.h index 3c18099b2..ef4554ec3 100644 --- a/src/core/libraries/np/np_tus.h +++ b/src/core/libraries/np/np_tus.h @@ -4,6 +4,7 @@ #pragma once #include "common/types.h" +#include "core/libraries/rtc/rtc.h" namespace Core::Loader { class SymbolsResolver; @@ -11,148 +12,76 @@ class SymbolsResolver; namespace Libraries::Np::NpTus { -s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariable(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAsync(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUser(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtx(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotData(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAsync(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariable(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAsync(); -s32 PS4_SYSV_ABI sceNpTusGetData(); -s32 PS4_SYSV_ABI sceNpTusGetDataAsync(); -s32 PS4_SYSV_ABI sceNpTusGetDataVUser(); -s32 PS4_SYSV_ABI sceNpTusGetDataVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatus(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAsync(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsVariable(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatus(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariable(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatus(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariable(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusSetData(); -s32 PS4_SYSV_ABI sceNpTusSetDataAsync(); -s32 PS4_SYSV_ABI sceNpTusSetDataVUser(); -s32 PS4_SYSV_ABI sceNpTusSetDataVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariable(); -s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAsync(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariable(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAsync(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUser(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUserAsync(); -s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtxA(); -s32 PS4_SYSV_ABI sceNpTssGetData(); -s32 PS4_SYSV_ABI sceNpTssGetDataAsync(); -s32 PS4_SYSV_ABI sceNpTssGetSmallStorage(); -s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync(); -s32 PS4_SYSV_ABI sceNpTssGetStorage(); -s32 PS4_SYSV_ABI sceNpTssGetStorageAsync(); -s32 PS4_SYSV_ABI sceNpTusAbortRequest(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableA(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAAsync(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUser(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUser(); -s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusChangeModeForOtherSaveDataOwners(); -s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtxA(); -s32 PS4_SYSV_ABI sceNpTusCreateRequest(); -s32 PS4_SYSV_ABI sceNpTusCreateTitleCtx(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataA(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAAsync(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUser(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableA(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAAsync(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUser(); -s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusDeleteNpTitleCtx(); -s32 PS4_SYSV_ABI sceNpTusDeleteRequest(); -s32 PS4_SYSV_ABI sceNpTusGetDataA(); -s32 PS4_SYSV_ABI sceNpTusGetDataAAsync(); -s32 PS4_SYSV_ABI sceNpTusGetDataAVUser(); -s32 PS4_SYSV_ABI sceNpTusGetDataAVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUser(); -s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusA(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAAsync(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableA(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAAsync(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusA(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableA(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusA(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableA(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUser(); -s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusPollAsync(); -s32 PS4_SYSV_ABI sceNpTusSetDataA(); -s32 PS4_SYSV_ABI sceNpTusSetDataAAsync(); -s32 PS4_SYSV_ABI sceNpTusSetDataAVUser(); -s32 PS4_SYSV_ABI sceNpTusSetDataAVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableA(); -s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAAsync(); -s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUser(); -s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusSetThreadParam(); -s32 PS4_SYSV_ABI sceNpTusSetTimeout(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableA(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAAsync(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUser(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSave(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveAsync(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUser(); -s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync(); -s32 PS4_SYSV_ABI sceNpTusWaitAsync(); +using OrbisNpTssSlotId = s32; +using OrbisNpTusSlotId = s32; + +struct OrbisNpTusVariable { + OrbisNpId npId; + int set; + Libraries::Rtc::OrbisRtcTick lastChanged; + u8 pad1[4]; + OrbisNpId lastChangedAuthor; + s64 variable; + s64 oldVariable; + OrbisNpAccountId owner; + OrbisNpAccountId lastChangedAuthorId; +}; + +struct OrbisNpTusDataInfo { + u64 size; + u8 data[384]; +}; + +struct OrbisNpTusDataStatus { + OrbisNpId npId; + int set; + Libraries::Rtc::OrbisRtcTick lastChanged; + OrbisNpId lastChangedAuthor; + u8 pad2[4]; + void* data; + u64 dataSize; + OrbisNpTusDataInfo info; +}; + +static_assert(sizeof(OrbisNpTusDataStatus) == 0x1F0); + +struct OrbisNpTusDataStatusA { + OrbisNpOnlineId onlineId; + u8 pad[16]; + int set; + Libraries::Rtc::OrbisRtcTick lastChanged; + OrbisNpOnlineId lastChangedAuthor; + u8 pad2[20]; + void* data; + u64 dataSize; + OrbisNpTusDataInfo info; + OrbisNpAccountId owner; + OrbisNpAccountId lastChangedAuthorId; + u8 pad3[16]; +}; + +static_assert(sizeof(OrbisNpTusDataStatusA) == 0x210); + +enum class OrbisNpTssStatus : int { + Ok = 0, + Partial = 1, + NotModified = 2, +}; + +struct OrbisNpTssDataStatus { + Libraries::Rtc::OrbisRtcTick modified; + OrbisNpTssStatus status; + u64 contentLength; +}; + +struct OrbisNpTssGetDataOptParam { + u64 size; + u64* offset; + u64* last; + void* param; +}; + +s32 PS4_SYSV_ABI sceNpTusWaitAsync(int reqId, int* result); void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpTus \ No newline at end of file diff --git a/src/core/libraries/np/object_manager.h b/src/core/libraries/np/object_manager.h new file mode 100644 index 000000000..5f6ed9663 --- /dev/null +++ b/src/core/libraries/np/object_manager.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +template +struct ObjectManager { + s32 GetObject(int objectId, T** out) { + std::scoped_lock lk{mutex}; + if (objectId < 1 || objectId > N) { + return INVALID_OBJECT_ID_ERROR; + } + auto obj = objects[objectId - 1]; + if (!obj) { + return OBJECT_NOT_FOUND_ERROR; + } + *out = obj; + return ORBIS_OK; + } + + template + s32 CreateObject(Args&&... args) { + std::scoped_lock lk{mutex}; + + if (auto slot = std::ranges::find(objects, nullptr); slot != objects.end()) { + *slot = new T{args...}; + + return std::ranges::distance(objects.begin(), slot) + 1; + } + + return MAX_OBJECTS_ERROR; + } + + s32 DeleteObject(int objectId) { + std::scoped_lock lk{mutex}; + + if (objectId < 1 || objectId > N) { + return INVALID_OBJECT_ID_ERROR; + } + auto obj = objects[objectId - 1]; + if (!obj) { + return OBJECT_NOT_FOUND_ERROR; + } + + delete obj; + objects[objectId - 1] = nullptr; + + return ORBIS_OK; + } + +private: + std::mutex mutex; + std::array objects = {nullptr}; +}; \ No newline at end of file From 6a7f8a9e5aa9a5dd78222b2cc2aba2ac97c0526b Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:13:28 -0600 Subject: [PATCH 12/18] Libs.LibcInternal: _Fofind, _Lockfilelock, _Unlockfilelock, _Foprep, _Fopen, fopen, fflush, _Nnl, _Fspos, fseek, _Frprep, fread, _Fofree, fclose, _Mtxinit, _Mtxlock, _Mtxunlock, _Mtxdst implementations (#4019) * Initial definitions * internal__Fofind * Libcinternal threads fopen stores a valid pthread mutex in the FILE struct. Since this is exposed to the game/app, we need to handle this accurately. * internal__Foprep (and various other functions called in it) * Actual fopen implementation At long last, an actual function I'm supposed to implement. * fflush + compile fixes * fseek implementation Comes with functions fseek calls, aside from fflush which I pushed earlier. * fread, _Frprep Also changed some parameter names a tad to match how I named things in my decomp. And fixed some bugs with how I was handling the weird offseted mode thing * fclose, _Fofree Not confident on this one, but we'll see I guess. * Bug fixing No more crashes at least, fread seems to be broken though. * fopen bugfixes Behavior now matches LLE, at least in how LLE font seems to use it. * Fix _Frprep Seems like everything works now? * Logging Probably going to need to swap lseek and read logs to debug/trace later but this is for debugging. * Remove alignment check Seems I must've misinterpreted some of what Ghidra spat out, since libSceNgs2 is calling with size 1, nmemb 4. * Reduce fseek, fread logs to trace * Clang --- CMakeLists.txt | 2 + src/core/libraries/kernel/file_system.h | 3 + src/core/libraries/kernel/threads.h | 10 + .../libraries/libc_internal/libc_internal.cpp | 7 + .../libraries/libc_internal/libc_internal.h | 1 + .../libc_internal/libc_internal_io.cpp | 467 +++++++++++++++++- .../libc_internal/libc_internal_io.h | 84 ++++ .../libc_internal/libc_internal_threads.cpp | 65 +++ .../libc_internal/libc_internal_threads.h | 22 + src/core/libraries/libs.cpp | 1 + 10 files changed, 660 insertions(+), 2 deletions(-) create mode 100644 src/core/libraries/libc_internal/libc_internal_threads.cpp create mode 100644 src/core/libraries/libc_internal/libc_internal_threads.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ba16732ff..484a1d4d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -496,6 +496,8 @@ set(HLE_LIBC_INTERNAL_LIB src/core/libraries/libc_internal/libc_internal.cpp src/core/libraries/libc_internal/libc_internal_str.h src/core/libraries/libc_internal/libc_internal_math.cpp src/core/libraries/libc_internal/libc_internal_math.h + src/core/libraries/libc_internal/libc_internal_threads.cpp + src/core/libraries/libc_internal/libc_internal_threads.h src/core/libraries/libc_internal/printf.h ) diff --git a/src/core/libraries/kernel/file_system.h b/src/core/libraries/kernel/file_system.h index 507f0952c..d8989828a 100644 --- a/src/core/libraries/kernel/file_system.h +++ b/src/core/libraries/kernel/file_system.h @@ -65,6 +65,9 @@ constexpr int ORBIS_KERNEL_O_DSYNC = 0x1000; constexpr int ORBIS_KERNEL_O_DIRECT = 0x00010000; constexpr int ORBIS_KERNEL_O_DIRECTORY = 0x00020000; +s32 PS4_SYSV_ABI posix_open(const char* path, s32 flags, u16 mode); +s32 PS4_SYSV_ABI posix_close(s32 fd); +s64 PS4_SYSV_ABI posix_lseek(s32 fd, s64 offset, s32 whence); s64 PS4_SYSV_ABI sceKernelWrite(s32 fd, const void* buf, u64 nbytes); s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes); s64 PS4_SYSV_ABI sceKernelPread(s32 fd, void* buf, u64 nbytes, s64 offset); diff --git a/src/core/libraries/kernel/threads.h b/src/core/libraries/kernel/threads.h index bcccf1695..42ab0b13f 100644 --- a/src/core/libraries/kernel/threads.h +++ b/src/core/libraries/kernel/threads.h @@ -28,6 +28,16 @@ int PS4_SYSV_ABI posix_pthread_create(PthreadT* thread, const PthreadAttrT* attr int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return); +int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr); +int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type); +int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr); + +int PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr, + const char* name); +int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex); +int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex); +int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex); + void RegisterThreads(Core::Loader::SymbolsResolver* sym); class Thread { diff --git a/src/core/libraries/libc_internal/libc_internal.cpp b/src/core/libraries/libc_internal/libc_internal.cpp index df35680a4..6a92d2317 100644 --- a/src/core/libraries/libc_internal/libc_internal.cpp +++ b/src/core/libraries/libc_internal/libc_internal.cpp @@ -11,6 +11,7 @@ #include "libc_internal_math.h" #include "libc_internal_memory.h" #include "libc_internal_str.h" +#include "libc_internal_threads.h" #include "printf.h" namespace Libraries::LibcInternal { @@ -20,5 +21,11 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { RegisterlibSceLibcInternalStr(sym); RegisterlibSceLibcInternalMemory(sym); RegisterlibSceLibcInternalIo(sym); + RegisterlibSceLibcInternalThreads(sym); +} + +void ForceRegisterLib(Core::Loader::SymbolsResolver* sym) { + // Used to forcibly enable HLEs for broken LLE functions. + ForceRegisterlibSceLibcInternalIo(sym); } } // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal.h b/src/core/libraries/libc_internal/libc_internal.h index 9b00b72d8..1494a2ab2 100644 --- a/src/core/libraries/libc_internal/libc_internal.h +++ b/src/core/libraries/libc_internal/libc_internal.h @@ -15,4 +15,5 @@ namespace Libraries::LibcInternal { // so everything is just in the .cpp file void RegisterLib(Core::Loader::SymbolsResolver* sym); +void ForceRegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal_io.cpp b/src/core/libraries/libc_internal/libc_internal_io.cpp index 8105b66cc..504ba5b48 100644 --- a/src/core/libraries/libc_internal/libc_internal_io.cpp +++ b/src/core/libraries/libc_internal/libc_internal_io.cpp @@ -3,21 +3,484 @@ #include #include +#include #include +#include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/file_system.h" +#include "core/libraries/kernel/kernel.h" +#include "core/libraries/kernel/posix_error.h" +#include "core/libraries/libc_internal/libc_internal_io.h" +#include "core/libraries/libc_internal/libc_internal_threads.h" #include "core/libraries/libs.h" -#include "libc_internal_io.h" #include "printf.h" namespace Libraries::LibcInternal { -int PS4_SYSV_ABI internal_snprintf(char* s, size_t n, VA_ARGS) { + +s32 PS4_SYSV_ABI internal_snprintf(char* s, u64 n, VA_ARGS) { VA_CTX(ctx); return snprintf_ctx(s, n, &ctx); } + +std::map g_files{}; +// Constants for tracking accurate file indexes. +// Since the file struct is exposed to the application, accuracy is important. +static constexpr s32 g_initial_files = 5; +static constexpr s32 g_max_files = 0x100; + +OrbisFILE* PS4_SYSV_ABI internal__Fofind() { + u64 index = g_initial_files; + while (index != g_max_files) { + OrbisFILE* file = g_files[index]; + // If file doesn't exist, create it. + if (file == nullptr) { + file = new OrbisFILE(); + if (file == nullptr) { + return nullptr; + } + // Store new file in the array, initialize default values, and return it. + g_files[index] = file; + file->_Mode = 0x80; + file->_Idx = index; + return file; + } + // Special case, files with mode 0 are returned? + if (file->_Mode == 0) { + file->_Mode = 0xff7f; + return file; + } + index++; + } + return nullptr; +} + +void PS4_SYSV_ABI internal__Lockfilelock(OrbisFILE* file) { + if (file != nullptr && file->_Mutex != nullptr) { + internal__Mtxlock(&file->_Mutex); + } +} + +void PS4_SYSV_ABI internal__Unlockfilelock(OrbisFILE* file) { + if (file != nullptr && file->_Mutex != nullptr) { + internal__Mtxunlock(&file->_Mutex); + } +} + +OrbisFILE* PS4_SYSV_ABI internal__Foprep(const char* path, const char* mode, OrbisFILE* file, + s32 fd, s32 s_mode, s32 flag) { + if (file == nullptr) { + *Kernel::__Error() = POSIX_ENOMEM; + } + + // Preserve mode and index + Libraries::Kernel::PthreadMutexT mtx = file->_Mutex; + Libraries::Kernel::PthreadMutexT* mtx_ptr = &file->_Mutex; + u8 file_index = file->_Idx; + u16 file_mode = file->_Mode & 0x80; + + // Real library does a memcpy using a static global FILE object. + // This stored file is just zeros, with the only exception being a handle of -1. + memset(file, 0, sizeof(OrbisFILE)); + file->_Handle = -1; + + // Not sure what this magic is for, but I'll replicate it. + u8* ptr = &file->_Cbuf; + // Note: this field is supposed to be a pthread mutex. + // Since we don't export pthread HLEs for other functions, I'll avoid handling this for now. + file->_Mutex = nullptr; + file->_Idx = file_index; + file->_Buf = ptr; + file->_Bend = &file->unk2; + file->_Next = ptr; + file->_Rend = ptr; + file->_WRend = ptr; + file->_Wend = ptr; + file->_WWend = ptr; + file->_Rback = ptr; + file->_WRback = &file->unk1; + + // Parse inputted mode string + const char* mode_str = mode; + u16 calc_mode = 0; + u16 access_mode = 0; + if (mode_str[0] == 'r') { + calc_mode = 1 | file_mode; + } else if (mode_str[0] == 'w') { + calc_mode = 0x1a | file_mode; + } else if (mode_str[0] == 'a') { + calc_mode = 0x16 | file_mode; + } else { + // Closes the file and returns EINVAL. + file->_Mode = file_mode; + if (flag == 0) { + internal__Mtxinit(mtx_ptr, nullptr); + } else { + file->_Mutex = mtx; + internal__Unlockfilelock(file); + } + internal_fclose(file); + *Kernel::__Error() = POSIX_EINVAL; + return nullptr; + } + file->_Mode = calc_mode; + + do { + // This is all basically straight from decomp, need to cleanup at some point. + if (mode_str[1] == '+') { + file_mode = 3; + if ((~calc_mode & 3) == 0) { + break; + } + } else if (mode_str[1] != 'b') { + file_mode = 0x20; + if ((calc_mode & 0x20) != 0) { + break; + } + } + mode_str++; + calc_mode = file_mode | calc_mode; + file->_Mode = calc_mode; + } while (true); + + if (path == nullptr && fd >= 0) { + // I guess this is for some internal behavior? + file->_Handle = fd; + } else { + fd = internal__Fopen(path, calc_mode, s_mode == 0x55); + file->_Handle = fd; + } + + // Error case + if (fd < 0) { + // Closes the file, but ensures errno is unchanged. + if (flag == 0) { + internal__Mtxinit(mtx_ptr, nullptr); + } else { + file->_Mutex = mtx; + internal__Unlockfilelock(file); + } + s32 old_errno = *Kernel::__Error(); + internal_fclose(file); + *Kernel::__Error() = old_errno; + return nullptr; + } + + if (flag == 0) { + char mtx_name[0x20]; + std::snprintf(mtx_name, 0x20, "FileFD:0x%08X", fd); + internal__Mtxinit(mtx_ptr, mtx_name); + } else { + file->_Mutex = mtx; + } + return file; +} + +s32 PS4_SYSV_ABI internal__Fopen(const char* path, u16 mode, bool flag) { + u32 large_mode = mode; + u16 open_mode = 0600; + if (!flag) { + open_mode = 0666; + } + // Straight from decomp, should probably get cleaned up at some point. + s32 creat_flag = large_mode << 5 & 0x200; + s32 excl_flag = large_mode << 5 & 0x800; + s32 misc_flags = (large_mode & 8) * 0x80 + (large_mode & 4) * 2; + // Real library has an array for this, where large_mode & 3 is used as an index. + // That array has values [0, 0, 1, 2], so this call should match the result. + s32 access_flag = std::max((large_mode & 3) - 1, 0); + s32 open_flags = creat_flag | misc_flags | excl_flag | access_flag; + return Libraries::Kernel::posix_open(path, open_flags, open_mode); +} + +OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode) { + std::scoped_lock lk{g_file_mtx}; + LOG_INFO(Lib_LibcInternal, "called, path {}, mode {}", path, mode); + OrbisFILE* file = internal__Fofind(); + return internal__Foprep(path, mode, file, -1, 0, 0); +} + +s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file) { + if (file == nullptr) { + std::scoped_lock lk{g_file_mtx}; + s32 fflush_result = 0; + for (auto& file : g_files) { + s32 res = internal_fflush(file.second); + if (res < 0) { + fflush_result = -1; + } + } + return fflush_result; + } + if ((file->_Mode & 0x2000) != 0) { + internal__Lockfilelock(file); + u16 file_mode = file->_Mode; + u8* file_buf_start = file->_Buf; + u8* file_buf_end = file->_Next; + while (file_buf_start < file_buf_end) { + u64 size_to_write = static_cast(file_buf_end - file_buf_start); + s32 write_bytes = + Libraries::Kernel::sceKernelWrite(file->_Handle, file_buf_start, size_to_write); + if (write_bytes < 1) { + file_buf_start = file->_Buf; + file->_Next = file_buf_start; + file->_Wend = file_buf_start; + file->_WWend = file_buf_start; + u8* off_mode = reinterpret_cast(&file->_Mode) + 1; + *off_mode = *off_mode | 2; + internal__Unlockfilelock(file); + return -1; + } + file_buf_end = file->_Next; + file_buf_start += write_bytes; + } + file->_Next = file_buf_start; + file->_Wend = file_buf_start; + file->_WWend = file_buf_start; + file->_Mode = file_mode & 0xdfff; + internal__Unlockfilelock(file); + } + return 0; +} + +s64 PS4_SYSV_ABI internal__Nnl(OrbisFILE* file, u8* val1, u8* val2) { + if (val1 < val2) { + return val2 - val1; + } + return 0; +} + +s32 PS4_SYSV_ABI internal__Fspos(OrbisFILE* file, Orbisfpos_t* file_pos, s64 offset, s32 whence) { + if ((file->_Mode & 3) == 0) { + return -1; + } + if (internal_fflush(file) != 0) { + return -1; + } + if (whence >= 3) { + *Libraries::Kernel::__Error() = POSIX_EINVAL; + return -1; + } + if (file_pos != nullptr) { + offset = offset + file_pos->_Off; + } + if (whence == 1 && (file->_Mode & 0x1000) != 0) { + s64 val1 = internal__Nnl(file, file->_Rback, &file->_Cbuf); + u8* rsave_ptr = file->_Rsave; + if (rsave_ptr == nullptr) { + rsave_ptr = file->_Rend; + } + s64 val2 = internal__Nnl(file, file->_Next, rsave_ptr); + s64 val3 = internal__Nnl(file, file->_Next, file->_WRend); + offset = offset - (val1 + val2 + val3); + } + s64 result = 0; + if (whence == 2 || (whence == 1 && offset != 0) || (whence == 0 && offset != -1)) { + result = Libraries::Kernel::posix_lseek(file->_Handle, offset, whence); + } + if (result == -1) { + return -1; + } + + u16 file_mode = file->_Mode; + if ((file_mode & 0x3000) != 0) { + u8* file_buf = file->_Buf; + file->_Next = file_buf; + file->_Rend = file_buf; + file->_WRend = file_buf; + file->_Wend = file_buf; + file->_WWend = file_buf; + file->_Rback = &file->_Cbuf; + file->_WRback = &file->unk1; + file->_Rsave = nullptr; + } + if (file_pos != nullptr) { + std::memcpy(&file->_Wstate, &file_pos->_Wstate, sizeof(Orbis_Mbstatet)); + } + file->_Mode = file_mode & 0xceff; + return 0; +} + +s32 PS4_SYSV_ABI internal_fseek(OrbisFILE* file, s64 offset, s32 whence) { + internal__Lockfilelock(file); + LOG_TRACE(Lib_LibcInternal, "called, file handle {:#x}, offset {:#x}, whence {:#x}", + file->_Handle, offset, whence); + s32 result = internal__Fspos(file, nullptr, offset, whence); + internal__Unlockfilelock(file); + return result; +} + +s32 PS4_SYSV_ABI internal__Frprep(OrbisFILE* file) { + if (file->_Rend > file->_Next) { + return 1; + } + if ((file->_Mode & 0x100) != 0) { + return 0; + } + u16 mode = file->_Mode; + if ((mode & 0xa001) != 1) { + // Lot of magic here, might be valuable to figure out what this does. + file->_Mode = (((mode ^ 0x8000) >> 0xf) << 0xe) | mode | 0x200; + return -1; + } + + u8* file_buf = file->_Buf; + if ((mode & 0x800) == 0 && file_buf == &file->_Cbuf) { + // Allocate a new file buffer, for now, we'll use host malloc to create it. + // When we have an HLE for malloc, that should be used instead. + u8* new_buffer = std::bit_cast(std::malloc(0x10000)); + if (new_buffer == nullptr) { + file->_Buf = file_buf; + file->_Bend = file_buf + 1; + } else { + file->_Mode = file->_Mode | 0x40; + file->_Buf = new_buffer; + file->_Bend = new_buffer + 0x10000; + file->_WRend = new_buffer; + file->_WWend = new_buffer; + file_buf = new_buffer; + } + } + file->_Next = file_buf; + file->_Rend = file_buf; + file->_Wend = file_buf; + // Intentional shrinking here, library treats value as 32-bit. + s32 read_result = + Libraries::Kernel::sceKernelRead(file->_Handle, file_buf, file->_Bend - file_buf); + if (read_result < 0) { + u8* off_mode = reinterpret_cast(&file->_Mode) + 1; + *off_mode = *off_mode | 0x42; + return -1; + } else if (read_result != 0) { + file->_Mode = file->_Mode | 0x5000; + file->_Rend = file->_Rend + read_result; + return 1; + } + file->_Mode = (file->_Mode & 0xaeff) | 0x4100; + return 0; +} + +u64 PS4_SYSV_ABI internal_fread(char* ptr, u64 size, u64 nmemb, OrbisFILE* file) { + if (size == 0 || nmemb == 0) { + return 0; + } + internal__Lockfilelock(file); + LOG_TRACE(Lib_LibcInternal, "called, file handle {:#x}, size {:#x}, nmemb {:#x}", file->_Handle, + size, nmemb); + s64 total_size = size * nmemb; + s64 remaining_size = total_size; + if ((file->_Mode & 0x4000) != 0) { + while (remaining_size != 0) { + u8* rback_ptr = file->_Rback; + if (&file->_Cbuf <= rback_ptr) { + break; + } + file->_Rback = rback_ptr + 1; + *ptr = *rback_ptr; + ptr++; + remaining_size--; + } + } + + while (remaining_size != 0) { + u8* file_ptr = file->_Rsave; + if (file_ptr == nullptr) { + file_ptr = file->_Rend; + } else { + file->_Rend = file_ptr; + file->_Rsave = nullptr; + } + u8* src = file->_Next; + if (file_ptr <= src) { + s32 res = internal__Frprep(file); + if (res < 1) { + internal__Unlockfilelock(file); + return (total_size - remaining_size) / size; + } + src = file->_Next; + file_ptr = file->_Rend; + } + u64 copy_bytes = std::min(file_ptr - src, remaining_size); + std::memcpy(ptr, src, copy_bytes); + file->_Next += copy_bytes; + ptr += copy_bytes; + remaining_size -= copy_bytes; + } + internal__Unlockfilelock(file); + return (total_size - remaining_size) / size; +} + +void PS4_SYSV_ABI internal__Fofree(OrbisFILE* file) { + u8* cbuf_ptr = &file->_Cbuf; + s8 trunc_mode = static_cast(file->_Mode); + + file->_Mode = 0; + file->_Handle = -1; + file->_Buf = cbuf_ptr; + file->_Next = cbuf_ptr; + file->_Rend = cbuf_ptr; + file->_WRend = cbuf_ptr; + file->_Wend = cbuf_ptr; + file->_WWend = cbuf_ptr; + file->_Rback = cbuf_ptr; + file->_WRback = &file->unk1; + if (trunc_mode < 0) { + // Remove file from vector + g_files.erase(file->_Idx); + internal__Mtxdst(&file->_Mutex); + free(file); + } +} + +s32 PS4_SYSV_ABI internal_fclose(OrbisFILE* file) { + if (file == nullptr) { + return -1; + } + + LOG_INFO(Lib_LibcInternal, "called, file handle {:#x}", file->_Handle); + if ((file->_Mode & 3) == 0 || file->_Handle < 0) { + std::scoped_lock lk{g_file_mtx}; + internal__Fofree(file); + *Libraries::Kernel::__Error() = POSIX_EBADF; + } else { + s32 fflush_result = internal_fflush(file); + std::scoped_lock lk{g_file_mtx}; + if ((file->_Mode & 0x40) != 0) { + std::free(file->_Buf); + } + file->_Buf = nullptr; + s32 close_result = Libraries::Kernel::posix_close(file->_Handle); + internal__Fofree(file); + // Need to figure out what exactly this means. + return ~-(close_result == 0) | fflush_result; + } + return 0; +} + void RegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("eLdDw6l0-bU", "libSceLibcInternal", 1, "libSceLibcInternal", internal_snprintf); + LIB_FUNCTION("MUjC4lbHrK4", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fflush); + LIB_FUNCTION("xGT4Mc55ViQ", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fofind); + LIB_FUNCTION("dREVnZkAKRE", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Foprep); + LIB_FUNCTION("sQL8D-jio7U", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fopen); + LIB_FUNCTION("A+Y3xfrWLLo", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fspos); + LIB_FUNCTION("Ss3108pBuZY", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Nnl); + LIB_FUNCTION("9s3P+LCvWP8", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Frprep); + LIB_FUNCTION("jVDuvE3s5Bs", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fofree); + LIB_FUNCTION("vZkmJmvqueY", "libSceLibcInternal", 1, "libSceLibcInternal", + internal__Lockfilelock); + LIB_FUNCTION("0x7rx8TKy2Y", "libSceLibcInternal", 1, "libSceLibcInternal", + internal__Unlockfilelock); } + +void ForceRegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym) { + // Goal is to be minimally intrusive here to allow LLE for printf/stdout writes. + LIB_FUNCTION("xeYO4u7uyJ0", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fopen); + LIB_FUNCTION("rQFVBXp-Cxg", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fseek); + LIB_FUNCTION("lbB+UlZqVG0", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fread); + LIB_FUNCTION("uodLYyUip20", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fclose); +} + } // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal_io.h b/src/core/libraries/libc_internal/libc_internal_io.h index f5291526b..27915d8fa 100644 --- a/src/core/libraries/libc_internal/libc_internal_io.h +++ b/src/core/libraries/libc_internal/libc_internal_io.h @@ -3,12 +3,96 @@ #pragma once +#include #include "common/types.h" +#include "core/libraries/kernel/threads.h" namespace Core::Loader { class SymbolsResolver; } namespace Libraries::LibcInternal { + +static std::recursive_mutex g_file_mtx{}; + +union Orbis__mbstate_t { + u8 __mbstate8[128]; + s64 _mbstateL; +}; + +struct Orbis_Mbstatet { + u64 _Wchar; + u16 _Byte, _State; + s32 : 32; +}; + +struct Orbisfpos_t { + s64 _Off; + Orbis_Mbstatet _Wstate; +}; + +struct Orbis__sbuf { + u8* _base; + s32 _size; +}; + +struct OrbisFILE { + u16 _Mode; + u8 _Idx; + s32 _Handle; + u8 *_Buf, *_Bend, *_Next; + u8 *_Rend, *_Wend, *_Rback; + u16 *_WRback, _WBack[2]; + u16 unk1; + u8 *_Rsave, *_WRend, *_WWend; + Orbis_Mbstatet _Wstate; + u8* _Tmpnam; + u8 _Back[6], _Cbuf; + u8 unk2; + Libraries::Kernel::PthreadMutexT _Mutex; + u8* _p; + s32 _r; + s32 _w; + s16 _flags; + s16 _file; + Orbis__sbuf _bf; + s32 _lbfsize; + void* _cookie; + s32 PS4_SYSV_ABI (*_close)(void*); + s32 PS4_SYSV_ABI (*_read)(void*, char*, s32); + Orbisfpos_t PS4_SYSV_ABI (*_seek)(void*, Orbisfpos_t, s32); + s32 (*_write)(void*, const char*, s32); + Orbis__sbuf _ub; + u8* _up; + s32 _ur; + u8 _ubuf[3]; + u8 _nbuf[1]; + Orbis__sbuf _lb; + s32 _blksize; + Orbisfpos_t _offset; + void* _fl_mutex; + void* _fl_owner; + s32 _fl_count; + s32 _orientation; + Orbis__mbstate_t _mbstate; +}; + +s32 PS4_SYSV_ABI internal_snprintf(char* s, u64 n, VA_ARGS); +void PS4_SYSV_ABI internal__Lockfilelock(OrbisFILE* file); +void PS4_SYSV_ABI internal__Unlockfilelock(OrbisFILE* file); +OrbisFILE* PS4_SYSV_ABI internal__Fofind(); +OrbisFILE* PS4_SYSV_ABI internal__Foprep(const char* path, const char* mode, OrbisFILE* file, + s32 fd, s32 flag1, s32 flag2); +s32 PS4_SYSV_ABI internal__Fopen(const char* path, u16 mode, bool flag); +OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode); +s64 PS4_SYSV_ABI internal__Nnl(OrbisFILE* file, u8* val1, u8* val2); +s32 PS4_SYSV_ABI internal__Fspos(OrbisFILE* file, Orbisfpos_t* file_pos, s64 offset, s32 whence); +s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file); +s32 PS4_SYSV_ABI internal_fseek(OrbisFILE* file, s64 offset, s32 whence); +s32 PS4_SYSV_ABI internal__Frprep(OrbisFILE* file); +u64 PS4_SYSV_ABI internal_fread(char* ptr, u64 size, u64 nmemb, OrbisFILE* file); +s32 PS4_SYSV_ABI internal_fclose(OrbisFILE* file); + void RegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym); +void ForceRegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal_threads.cpp b/src/core/libraries/libc_internal/libc_internal_threads.cpp new file mode 100644 index 000000000..2d8ddaccb --- /dev/null +++ b/src/core/libraries/libc_internal/libc_internal_threads.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/libraries/kernel/threads.h" +#include "core/libraries/libc_internal/libc_internal_threads.h" +#include "core/libraries/libs.h" + +namespace Libraries::LibcInternal { + +void getMutexName(char* buf, u64 size, const char* name) { + if (name != nullptr) { + std::snprintf(buf, size, "SceLibcI_%s", name); + } else { + std::snprintf(buf, size, "SceLibcI"); + } +} + +s32 PS4_SYSV_ABI internal__Mtxinit(Libraries::Kernel::PthreadMutexT* mtx, const char* name) { + char mtx_name[0x20]; + getMutexName(mtx_name, sizeof(mtx_name), name); + + Libraries::Kernel::PthreadMutexAttrT attr{}; + s32 result = Libraries::Kernel::posix_pthread_mutexattr_init(&attr); + if (result != 0) { + return 1; + } + + result = Libraries::Kernel::posix_pthread_mutexattr_settype( + &attr, Libraries::Kernel::PthreadMutexType::Recursive); + if (result == 0) { + s32 mtx_init_result = Libraries::Kernel::scePthreadMutexInit(mtx, &attr, mtx_name); + result = Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr); + if (mtx_init_result == 0 && result == 0) { + return 0; + } + } else { + Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr); + } + + return 1; +} + +s32 PS4_SYSV_ABI internal__Mtxlock(Libraries::Kernel::PthreadMutexT* mtx) { + s32 result = Libraries::Kernel::posix_pthread_mutex_lock(mtx); + return result != 0; +} + +s32 PS4_SYSV_ABI internal__Mtxunlock(Libraries::Kernel::PthreadMutexT* mtx) { + s32 result = Libraries::Kernel::posix_pthread_mutex_unlock(mtx); + return result != 0; +} + +s32 PS4_SYSV_ABI internal__Mtxdst(Libraries::Kernel::PthreadMutexT* mtx) { + s32 result = Libraries::Kernel::posix_pthread_mutex_destroy(mtx); + return result != 0; +} + +void RegisterlibSceLibcInternalThreads(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("z7STeF6abuU", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxinit); + LIB_FUNCTION("pE4Ot3CffW0", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxlock); + LIB_FUNCTION("cMwgSSmpE5o", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxunlock); + LIB_FUNCTION("LaPaA6mYA38", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxdst); +} + +} // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal_threads.h b/src/core/libraries/libc_internal/libc_internal_threads.h new file mode 100644 index 000000000..74e6d41b1 --- /dev/null +++ b/src/core/libraries/libc_internal/libc_internal_threads.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/types.h" +#include "core/libraries/kernel/threads.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::LibcInternal { + +s32 PS4_SYSV_ABI internal__Mtxinit(Libraries::Kernel::PthreadMutexT* mtx, const char* name); +s32 PS4_SYSV_ABI internal__Mtxlock(Libraries::Kernel::PthreadMutexT* mtx); +s32 PS4_SYSV_ABI internal__Mtxunlock(Libraries::Kernel::PthreadMutexT* mtx); +s32 PS4_SYSV_ABI internal__Mtxdst(Libraries::Kernel::PthreadMutexT* mtx); + +void RegisterlibSceLibcInternalThreads(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 67c3d4b7d..eebb991dc 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -80,6 +80,7 @@ namespace Libraries { void InitHLELibs(Core::Loader::SymbolsResolver* sym) { LOG_INFO(Lib_Kernel, "Initializing HLE libraries"); Libraries::Kernel::RegisterLib(sym); + Libraries::LibcInternal::ForceRegisterLib(sym); Libraries::GnmDriver::RegisterLib(sym); Libraries::VideoOut::RegisterLib(sym); Libraries::UserService::RegisterLib(sym); From 68679b24aae061f8ef5747afd8d03ead5ac61a55 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:31:23 -0600 Subject: [PATCH 13/18] Fix log errors (#4020) Just a typical day of me pushing something a month ago, nobody testing/reviewing it, then finding out it's broken when that code inevitably makes it into production. --- src/core/libraries/np/np_web_api.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/np/np_web_api.cpp b/src/core/libraries/np/np_web_api.cpp index e51b79a3c..0c633e0d1 100644 --- a/src/core/libraries/np/np_web_api.cpp +++ b/src/core/libraries/np/np_web_api.cpp @@ -54,12 +54,12 @@ s32 PS4_SYSV_ABI sceNpWebApiCreateServicePushEventFilter( } s32 PS4_SYSV_ABI sceNpWebApiDeletePushEventFilter(s32 libCtxId, s32 filterId) { - LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}"); + LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}", libCtxId, filterId); return deletePushEventFilter(libCtxId, filterId); } s32 PS4_SYSV_ABI sceNpWebApiDeleteServicePushEventFilter(s32 libCtxId, s32 filterId) { - LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}"); + LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}", libCtxId, filterId); return deleteServicePushEventFilter(libCtxId, filterId); } From 8c59571961df10f5e05805ad6aa4e9b47b7db68f Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Wed, 11 Feb 2026 12:15:33 +0300 Subject: [PATCH 14/18] pad: refactoring (#4016) * pad: refactoring * clang is not my friend --- src/core/libraries/pad/pad.cpp | 154 +++++++-------------------------- src/input/controller.cpp | 144 +++++++----------------------- src/input/controller.h | 59 +++++++++---- src/input/input_handler.cpp | 12 +-- src/input/input_mouse.cpp | 6 +- src/sdl_window.cpp | 6 +- 6 files changed, 116 insertions(+), 265 deletions(-) diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index f433a87cc..5f50b8a7d 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.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 "common/config.h" @@ -94,18 +94,6 @@ int PS4_SYSV_ABI scePadGetCapability() { int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerInformation* pInfo) { LOG_DEBUG(Lib_Pad, "called handle = {}", handle); - if (handle < 0) { - pInfo->touchPadInfo.pixelDensity = 1; - pInfo->touchPadInfo.resolution.x = 1920; - pInfo->touchPadInfo.resolution.y = 950; - pInfo->stickInfo.deadZoneLeft = 1; - pInfo->stickInfo.deadZoneRight = 1; - pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; - pInfo->connectedCount = 1; - pInfo->connected = false; - pInfo->deviceClass = OrbisPadDeviceClass::Standard; - return ORBIS_OK; - } pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; pInfo->touchPadInfo.resolution.y = 950; @@ -113,8 +101,12 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn pInfo->stickInfo.deadZoneRight = 1; pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; pInfo->connectedCount = 1; - pInfo->connected = true; pInfo->deviceClass = OrbisPadDeviceClass::Standard; + if (handle < 0) { + pInfo->connected = false; + return ORBIS_OK; + } + pInfo->connected = true; if (Config::getUseSpecialPad()) { pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL; pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass(); @@ -302,20 +294,16 @@ int PS4_SYSV_ABI scePadOutputReport() { return ORBIS_OK; } -int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { - LOG_TRACE(Lib_Pad, "called"); - int connected_count = 0; - bool connected = false; - Input::State states[64]; - auto* controller = Common::Singleton::Instance(); - const auto* engine = controller->GetEngine(); - int ret_num = controller->ReadStates(states, num, &connected, &connected_count); - +int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num, bool connected, + u32 connected_count) { if (!connected) { - ret_num = 1; + pData[0] = {}; + pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; + pData[0].connected = false; + return 1; } - for (int i = 0; i < ret_num; i++) { + for (int i = 0; i < num; i++) { pData[i].buttons = states[i].buttonsState; pData[i].leftStick.x = states[i].axes[static_cast(Input::Axis::LeftX)]; pData[i].leftStick.y = states[i].axes[static_cast(Input::Axis::LeftY)]; @@ -323,20 +311,16 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { pData[i].rightStick.y = states[i].axes[static_cast(Input::Axis::RightY)]; pData[i].analogButtons.l2 = states[i].axes[static_cast(Input::Axis::TriggerLeft)]; pData[i].analogButtons.r2 = states[i].axes[static_cast(Input::Axis::TriggerRight)]; - pData[i].acceleration.x = states[i].acceleration.x; - pData[i].acceleration.y = states[i].acceleration.y; - pData[i].acceleration.z = states[i].acceleration.z; - pData[i].angularVelocity.x = states[i].angularVelocity.x; - pData[i].angularVelocity.y = states[i].angularVelocity.y; - pData[i].angularVelocity.z = states[i].angularVelocity.z; - pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; pData[i].acceleration.x = states[i].acceleration.x * 0.098; pData[i].acceleration.y = states[i].acceleration.y * 0.098; pData[i].acceleration.z = states[i].acceleration.z * 0.098; pData[i].angularVelocity.x = states[i].angularVelocity.x; pData[i].angularVelocity.y = states[i].angularVelocity.y; pData[i].angularVelocity.z = states[i].angularVelocity.z; + pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; + auto* controller = Common::Singleton::Instance(); + const auto* engine = controller->GetEngine(); if (engine && handle == 1) { const auto gyro_poll_rate = engine->GetAccelPollRate(); if (gyro_poll_rate != 0.0f) { @@ -354,7 +338,6 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { controller->SetLastOrientation(outputOrientation); } } - pData[i].touchData.touchNum = (states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0); @@ -409,7 +392,18 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { pData[i].deviceUniqueDataLen = 0; } - return ret_num; + return num; +} + +int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { + LOG_TRACE(Lib_Pad, "called"); + int connected_count = 0; + bool connected = false; + std::vector states(64); + auto* controller = Common::Singleton::Instance(); + const auto* engine = controller->GetEngine(); + int ret_num = controller->ReadStates(states.data(), num, &connected, &connected_count); + return ProcessStates(handle, pData, states.data(), ret_num, connected, connected_count); } int PS4_SYSV_ABI scePadReadBlasterForTracker() { @@ -439,95 +433,11 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { } auto* controller = Common::Singleton::Instance(); const auto* engine = controller->GetEngine(); - int connectedCount = 0; - bool isConnected = false; + int connected_count = 0; + bool connected = false; Input::State state; - controller->ReadState(&state, &isConnected, &connectedCount); - pData->buttons = state.buttonsState; - pData->leftStick.x = state.axes[static_cast(Input::Axis::LeftX)]; - pData->leftStick.y = state.axes[static_cast(Input::Axis::LeftY)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.y = state.axes[static_cast(Input::Axis::RightY)]; - pData->analogButtons.l2 = state.axes[static_cast(Input::Axis::TriggerLeft)]; - pData->analogButtons.r2 = state.axes[static_cast(Input::Axis::TriggerRight)]; - pData->acceleration.x = state.acceleration.x * 0.098; - pData->acceleration.y = state.acceleration.y * 0.098; - pData->acceleration.z = state.acceleration.z * 0.098; - pData->angularVelocity.x = state.angularVelocity.x; - pData->angularVelocity.y = state.angularVelocity.y; - pData->angularVelocity.z = state.angularVelocity.z; - pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f}; - - // Only do this on handle 1 for now - if (engine && handle == 1) { - auto now = std::chrono::steady_clock::now(); - float deltaTime = - std::chrono::duration_cast(now - controller->GetLastUpdate()) - .count() / - 1000000.0f; - controller->SetLastUpdate(now); - Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); - Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; - GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime, - lastOrientation, outputOrientation); - pData->orientation = outputOrientation; - controller->SetLastOrientation(outputOrientation); - } - pData->touchData.touchNum = - (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); - - // Only do this on handle 1 for now - if (handle == 1) { - if (controller->GetTouchCount() >= 127) { - controller->SetTouchCount(0); - } - - if (controller->GetSecondaryTouchCount() >= 127) { - controller->SetSecondaryTouchCount(0); - } - - if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) { - controller->SetTouchCount(controller->GetTouchCount() + 1); - controller->SetSecondaryTouchCount(controller->GetTouchCount()); - } else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) { - controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1); - } else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) { - if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) { - controller->SetTouchCount(controller->GetSecondaryTouchCount()); - } else { - if (controller->WasSecondaryTouchReset()) { - controller->SetTouchCount(controller->GetSecondaryTouchCount()); - controller->UnsetSecondaryTouchResetBool(); - } - } - } - - controller->SetPreviousTouchNum(pData->touchData.touchNum); - - if (pData->touchData.touchNum == 1) { - state.touchpad[0].ID = controller->GetTouchCount(); - state.touchpad[1].ID = 0; - } else if (pData->touchData.touchNum == 2) { - state.touchpad[0].ID = controller->GetTouchCount(); - state.touchpad[1].ID = controller->GetSecondaryTouchCount(); - } - } else { - state.touchpad[0].ID = 1; - state.touchpad[1].ID = 2; - } - - pData->touchData.touch[0].x = state.touchpad[0].x; - pData->touchData.touch[0].y = state.touchpad[0].y; - pData->touchData.touch[0].id = state.touchpad[0].ID; - pData->touchData.touch[1].x = state.touchpad[1].x; - pData->touchData.touch[1].y = state.touchpad[1].y; - pData->touchData.touch[1].id = state.touchpad[1].ID; - pData->timestamp = state.time; - pData->connected = true; // isConnected; //TODO fix me proper - pData->connectedCount = 1; // connectedCount; - pData->deviceUniqueDataLen = 0; - + controller->ReadState(&state, &connected, &connected_count); + ProcessStates(handle, pData, &state, 1, connected, connected_count); return ORBIS_OK; } diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 6657f4036..3606ad5d2 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.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 @@ -61,110 +61,51 @@ void State::OnAccel(const float accel[3]) { acceleration.z = accel[2]; } -GameController::GameController() { - m_states_num = 0; - m_last_state = State(); -} +GameController::GameController() : m_states_queue(64) {} void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) { - std::scoped_lock lock{m_mutex}; - *isConnected = m_connected; *connectedCount = m_connected_count; - *state = GetLastState(); + *state = m_state; } int GameController::ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount) { - std::scoped_lock lock{m_mutex}; - *isConnected = m_connected; *connectedCount = m_connected_count; int ret_num = 0; - if (m_connected) { - if (m_states_num == 0) { - ret_num = 1; - states[0] = m_last_state; - } else { - for (uint32_t i = 0; i < m_states_num; i++) { - if (ret_num >= states_num) { - break; - } - auto index = (m_first_state + i) % MAX_STATES; - if (!m_private[index].obtained) { - m_private[index].obtained = true; - - states[ret_num++] = m_states[index]; - } + std::lock_guard lg(m_states_queue_mutex); + for (int i = 0; i < states_num; i++) { + auto o_state = m_states_queue.Pop(); + if (!o_state) { + break; } + states[ret_num++] = *o_state; } } - return ret_num; } -State GameController::GetLastState() const { - if (m_states_num == 0) { - return m_last_state; - } - const u32 last = (m_first_state + m_states_num - 1) % MAX_STATES; - return m_states[last]; -} - -void GameController::AddState(const State& state) { - if (m_states_num >= MAX_STATES) { - m_states_num = MAX_STATES - 1; - m_first_state = (m_first_state + 1) % MAX_STATES; - } - - const u32 index = (m_first_state + m_states_num) % MAX_STATES; - m_states[index] = state; - m_last_state = state; - m_private[index].obtained = false; - m_states_num++; -} - -void GameController::CheckButton(int id, OrbisPadButtonDataOffset button, bool is_pressed) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnButton(button, is_pressed); - - AddState(state); +void GameController::Button(int id, OrbisPadButtonDataOffset button, bool is_pressed) { + m_state.OnButton(button, is_pressed); + PushState(); } void GameController::Axis(int id, Input::Axis axis, int value) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnAxis(axis, value); - - AddState(state); + m_state.OnAxis(axis, value); + PushState(); } void GameController::Gyro(int id, const float gyro[3]) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - - // Update the angular velocity (gyro data) - state.OnGyro(gyro); - - AddState(state); + m_state.OnGyro(gyro); + PushState(); } + void GameController::Acceleration(int id, const float acceleration[3]) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - - // Update the acceleration values - state.OnAccel(acceleration); - - AddState(state); + m_state.OnAccel(acceleration); + PushState(); } void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration, @@ -206,7 +147,6 @@ void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) { if (!m_engine) { return; } - std::scoped_lock _{m_mutex}; m_engine->SetLightBarRGB(r, g, b); } @@ -214,39 +154,29 @@ void GameController::SetVibration(u8 smallMotor, u8 largeMotor) { if (!m_engine) { return; } - std::scoped_lock _{m_mutex}; m_engine->SetVibration(smallMotor, largeMotor); } void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) { if (touchIndex < 2) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnTouchpad(touchIndex, touchDown, x, y); - - AddState(state); + m_state.OnTouchpad(touchIndex, touchDown, x, y); + PushState(); } } u8 GameController::GetTouchCount() { - std::scoped_lock lock{m_mutex}; return m_touch_count; } void GameController::SetTouchCount(u8 touchCount) { - std::scoped_lock lock{m_mutex}; m_touch_count = touchCount; } u8 GameController::GetSecondaryTouchCount() { - std::scoped_lock lock{m_mutex}; return m_secondary_touch_count; } void GameController::SetSecondaryTouchCount(u8 touchCount) { - std::scoped_lock lock{m_mutex}; m_secondary_touch_count = touchCount; if (touchCount == 0) { m_was_secondary_reset = true; @@ -254,47 +184,38 @@ void GameController::SetSecondaryTouchCount(u8 touchCount) { } u8 GameController::GetPreviousTouchNum() { - std::scoped_lock lock{m_mutex}; return m_previous_touchnum; } void GameController::SetPreviousTouchNum(u8 touchNum) { - std::scoped_lock lock{m_mutex}; m_previous_touchnum = touchNum; } bool GameController::WasSecondaryTouchReset() { - std::scoped_lock lock{m_mutex}; return m_was_secondary_reset; } void GameController::UnsetSecondaryTouchResetBool() { - std::scoped_lock lock{m_mutex}; m_was_secondary_reset = false; } void GameController::SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation) { - std::scoped_lock lock{m_mutex}; m_orientation = orientation; } Libraries::Pad::OrbisFQuaternion GameController::GetLastOrientation() { - std::scoped_lock lock{m_mutex}; return m_orientation; } std::chrono::steady_clock::time_point GameController::GetLastUpdate() { - std::scoped_lock lock{m_mutex}; return m_last_update; } void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate) { - std::scoped_lock lock{m_mutex}; m_last_update = lastUpdate; } void GameController::SetEngine(std::unique_ptr engine) { - std::scoped_lock _{m_mutex}; m_engine = std::move(engine); if (m_engine) { m_engine->Init(); @@ -305,24 +226,17 @@ Engine* GameController::GetEngine() { return m_engine.get(); } +void GameController::PushState() { + std::lock_guard lg(m_states_queue_mutex); + m_state.time = Libraries::Kernel::sceKernelGetProcessTime(); + m_states_queue.Push(m_state); +} + u32 GameController::Poll() { if (m_connected) { - std::scoped_lock lock{m_mutex}; - auto time = Libraries::Kernel::sceKernelGetProcessTime(); - if (m_states_num == 0) { - auto diff = (time - m_last_state.time) / 1000; - if (diff >= 100) { - AddState(GetLastState()); - } - } else { - auto index = (m_first_state - 1 + m_states_num) % MAX_STATES; - auto diff = (time - m_states[index].time) / 1000; - if (m_private[index].obtained && diff >= 100) { - AddState(GetLastState()); - } - } + PushState(); } - return 100; + return 33; } } // namespace Input diff --git a/src/input/controller.h b/src/input/controller.h index dfde521be..6c13fdf99 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -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 #pragma once @@ -6,7 +6,10 @@ #include #include #include +#include + #include + #include "common/types.h" #include "core/libraries/pad/pad.h" @@ -63,7 +66,36 @@ inline int GetAxis(int min, int max, int value) { return std::clamp((255 * (value - min)) / (max - min), 0, 255); } -constexpr u32 MAX_STATES = 32; +template +class RingBufferQueue { +public: + RingBufferQueue(size_t size) : m_storage(size) {} + + void Push(T item) { + const size_t index = (m_begin + m_size) % m_storage.size(); + m_storage[index] = std::move(item); + if (m_size < m_storage.size()) { + m_size += 1; + } else { + m_begin = (m_begin + 1) % m_storage.size(); + } + } + + std::optional Pop() { + if (m_size == 0) { + return {}; + } + const size_t index = m_begin; + m_begin = (m_begin + 1) % m_storage.size(); + m_size -= 1; + return std::move(m_storage[index]); + } + +private: + size_t m_begin = 0; + size_t m_size = 0; + std::vector m_storage; +}; class GameController { public: @@ -72,9 +104,8 @@ public: void ReadState(State* state, bool* isConnected, int* connectedCount); int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount); - State GetLastState() const; - void CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed); - void AddState(const State& state); + + void Button(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed); void Axis(int id, Input::Axis axis, int value); void Gyro(int id, const float gyro[3]); void Acceleration(int id, const float acceleration[3]); @@ -105,26 +136,22 @@ public: Libraries::Pad::OrbisFQuaternion& orientation); private: - struct StateInternal { - bool obtained = false; - }; + void PushState(); - std::mutex m_mutex; bool m_connected = true; - State m_last_state; - int m_connected_count = 0; - u32 m_states_num = 0; - u32 m_first_state = 0; + int m_connected_count = 1; u8 m_touch_count = 0; u8 m_secondary_touch_count = 0; - u8 m_previous_touch_count = 0; u8 m_previous_touchnum = 0; bool m_was_secondary_reset = false; - std::array m_states; - std::array m_private; std::chrono::steady_clock::time_point m_last_update = {}; Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f}; + State m_state; + + std::mutex m_states_queue_mutex; + RingBufferQueue m_states_queue; + std::unique_ptr m_engine = nullptr; }; diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 6e5014c1b..e44693fbf 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -557,15 +557,15 @@ void ControllerOutput::FinalizeUpdate() { switch (button) { case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f); - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state); break; case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER: controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f); - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state); break; case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT: controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f); - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state); break; case LEFTJOYSTICK_HALFMODE: leftjoystick_halfmode = new_button_state; @@ -617,7 +617,7 @@ void ControllerOutput::FinalizeUpdate() { SetMouseGyroRollMode(new_button_state); break; default: // is a normal key (hopefully) - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state); break; } } else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) { @@ -648,12 +648,12 @@ void ControllerOutput::FinalizeUpdate() { case Axis::TriggerLeft: ApplyDeadzone(new_param, lefttrigger_deadzone); controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); - controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20); + controller->Button(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20); return; case Axis::TriggerRight: ApplyDeadzone(new_param, righttrigger_deadzone); controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); - controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20); + controller->Button(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20); return; default: break; diff --git a/src/input/input_mouse.cpp b/src/input/input_mouse.cpp index 996c35ef9..0dc44608b 100644 --- a/src/input/input_mouse.cpp +++ b/src/input/input_mouse.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 @@ -104,8 +104,8 @@ void EmulateTouchpad(GameController* controller, u32 interval) { controller->SetTouchpadState(0, (mouse_buttons & SDL_BUTTON_LMASK) != 0, std::clamp(x / g_window->GetWidth(), 0.0f, 1.0f), std::clamp(y / g_window->GetHeight(), 0.0f, 1.0f)); - controller->CheckButton(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad, - (mouse_buttons & SDL_BUTTON_RMASK) != 0); + controller->Button(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad, + (mouse_buttons & SDL_BUTTON_RMASK) != 0); } void ApplyMouseInputBlockers() { diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 476a56b52..c9183f301 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.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 "SDL3/SDL_events.h" @@ -471,7 +471,7 @@ void WindowSDL::WaitEvent() { } void WindowSDL::InitTimers() { - SDL_AddTimer(100, &PollController, controller); + SDL_AddTimer(33, &PollController, controller); SDL_AddTimer(33, Input::MousePolling, (void*)controller); } @@ -540,7 +540,7 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) { // as it would break the entire touchpad handling // You can still bind other things to it though if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) { - controller->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down); + controller->Button(0, OrbisPadButtonDataOffset::TouchPad, input_down); return; } From c2a47d2a9986582b24c04ed6cc4f3b3712deeb02 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:00:13 +0100 Subject: [PATCH 15/18] Handle operand fields execlo and exechi for S_MOV (#4023) Co-authored-by: TheTurtle --- src/shader_recompiler/frontend/translate/scalar_alu.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index b2e981d6a..8931e8dde 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -583,6 +583,14 @@ void Translator::S_MULK_I32(const GcnInst& inst) { // SOP1 void Translator::S_MOV(const GcnInst& inst) { + if (inst.dst[0].field == OperandField::ScalarGPR) { + if (inst.src[0].field == OperandField::ExecLo) { + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), ir.GetExec()); + return; + } else if (inst.src[0].field == OperandField::ExecHi) { + return; + } + } SetDst(inst.dst[0], GetSrc(inst.src[0])); } From 7bdb5eb7e104742436ee37a9381b33d045d51660 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:09:34 +0100 Subject: [PATCH 16/18] improved motion controls emulation (#4022) --- src/sdl_window.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index c9183f301..1ee107efe 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -269,9 +269,14 @@ namespace Frontend { using namespace Libraries::Pad; -static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) { +std::mutex motion_control_mutex; +float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, -9.81f, 0.0f}; +static Uint32 SDLCALL PollGyroAndAccel(void* userdata, SDL_TimerID timer_id, Uint32 interval) { auto* controller = reinterpret_cast(userdata); - return controller->Poll(); + std::scoped_lock l{motion_control_mutex}; + controller->Gyro(0, gyro_buf); + controller->Acceleration(0, accel_buf); + return 4; } WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, @@ -407,12 +412,16 @@ void WindowSDL::WaitEvent() { // AND IT DOESN'T EVEN USE PROPER ENUMS case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: switch ((SDL_SensorType)event.gsensor.sensor) { - case SDL_SENSOR_GYRO: - controller->Gyro(0, event.gsensor.data); + case SDL_SENSOR_GYRO: { + std::scoped_lock l{motion_control_mutex}; + memcpy(gyro_buf, event.gsensor.data, sizeof(gyro_buf)); break; - case SDL_SENSOR_ACCEL: - controller->Acceleration(0, event.gsensor.data); + } + case SDL_SENSOR_ACCEL: { + std::scoped_lock l{motion_control_mutex}; + memcpy(accel_buf, event.gsensor.data, sizeof(accel_buf)); break; + } default: break; } @@ -471,7 +480,7 @@ void WindowSDL::WaitEvent() { } void WindowSDL::InitTimers() { - SDL_AddTimer(33, &PollController, controller); + SDL_AddTimer(4, &PollGyroAndAccel, controller); SDL_AddTimer(33, Input::MousePolling, (void*)controller); } From 0435ada3a590f0f3f1e72b43eef94551aa02f168 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:22:38 -0600 Subject: [PATCH 17/18] Update np_web_api_internal.cpp (#4025) --- src/core/libraries/np/np_web_api_internal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/np/np_web_api_internal.cpp b/src/core/libraries/np/np_web_api_internal.cpp index 3d6c7de86..f598344c7 100644 --- a/src/core/libraries/np/np_web_api_internal.cpp +++ b/src/core/libraries/np/np_web_api_internal.cpp @@ -65,7 +65,7 @@ s32 createLibraryContext(s32 libHttpCtxId, u64 poolSize, const char* name, s32 t OrbisNpWebApiContext* findAndValidateContext(s32 libCtxId, s32 flag) { std::scoped_lock lk{g_global_mutex}; - if (libCtxId < 1 || libCtxId >= 0x8000) { + if (libCtxId < 1 || libCtxId >= 0x8000 || !g_contexts.contains(libCtxId)) { return nullptr; } auto& context = g_contexts[libCtxId]; From b4daf3766227a79e54caa16a21eb085f86d96a32 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 12 Feb 2026 16:17:40 +0200 Subject: [PATCH 18/18] Submodules update (#4026) * added openal-soft , cpp-httplib * CLI11,sdl3 update --- .gitmodules | 7 +++++++ CMakeLists.txt | 3 ++- externals/CLI11 | 2 +- externals/CMakeLists.txt | 38 ++++++++++++++++++++++++++++++++++++++ externals/cpp-httplib | 1 + externals/openal-soft | 1 + externals/sdl3 | 2 +- 7 files changed, 51 insertions(+), 3 deletions(-) create mode 160000 externals/cpp-httplib create mode 160000 externals/openal-soft diff --git a/.gitmodules b/.gitmodules index e54658932..55ae48ea3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -126,3 +126,10 @@ path = externals/sdl3 url = https://github.com/shadexternals/sdl3.git + branch = main +[submodule "externals/cpp-httplib"] + path = externals/cpp-httplib + url = https://github.com/shadexternals/cpp-httplib.git +[submodule "externals/openal-soft"] + path = externals/openal-soft + url = https://github.com/shadexternals/openal-soft.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 484a1d4d7..a1d7d9530 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,6 +228,7 @@ find_package(glslang 15 CONFIG) find_package(half 1.12.0 MODULE) find_package(magic_enum 0.9.7 CONFIG) find_package(PNG 1.6 MODULE) +find_package(OpenAL CONFIG) find_package(RenderDoc 1.6.0 MODULE) find_package(SDL3_mixer 2.8.1 CONFIG) if (SDL3_mixer_FOUND) @@ -1115,7 +1116,7 @@ create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml) -target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11) +target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/externals/CLI11 b/externals/CLI11 index bf5a16a26..617af2722 160000 --- a/externals/CLI11 +++ b/externals/CLI11 @@ -1 +1 @@ -Subproject commit bf5a16a26a34a9a7ad75f4a7705585e44675fef0 +Subproject commit 617af272277f8c5aefdc20894b0ebef1cd6b0104 diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 80a6ff7e2..7f6e6ec4e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -276,3 +276,41 @@ if (NOT TARGET CLI11::CLI11) set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) add_subdirectory(CLI11) endif() + + +#openal +if (NOT TARGET OpenAL::OpenAL) +set(ALSOFT_ENABLE_MODULES OFF CACHE BOOL "" FORCE) +set(LIBTYPE "STATIC" CACHE STRING "" FORCE) +# Disable everything we don't need +set(ALSOFT_UTILS OFF CACHE BOOL "" FORCE) +set(ALSOFT_EXAMPLES OFF CACHE BOOL "" FORCE) +set(ALSOFT_TESTS OFF CACHE BOOL "" FORCE) +set(ALSOFT_INSTALL OFF CACHE BOOL "" FORCE) +set(ALSOFT_CONFIG OFF CACHE BOOL "" FORCE) + +# Backends (platform-specific) +if (WIN32) + set(ALSOFT_BACKEND_WASAPI ON CACHE BOOL "" FORCE) + set(ALSOFT_BACKEND_DSOUND OFF CACHE BOOL "" FORCE) + set(ALSOFT_BACKEND_WINMM OFF CACHE BOOL "" FORCE) +elseif (APPLE) + set(ALSOFT_BACKEND_COREAUDIO ON CACHE BOOL "" FORCE) +else() + set(ALSOFT_BACKEND_ALSA ON CACHE BOOL "" FORCE) + set(ALSOFT_BACKEND_PULSEAUDIO ON CACHE BOOL "" FORCE) + set(ALSOFT_BACKEND_PIPEWIRE OFF CACHE BOOL "" FORCE) +endif() +# Headless-safe +set(ALSOFT_BACKEND_NULL ON CACHE BOOL "" FORCE) + +# Static build +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + +add_subdirectory(openal-soft EXCLUDE_FROM_ALL) +endif() + +# cpp-httplib +add_library(Cpp_Httplib INTERFACE) +target_include_directories(Cpp_Httplib INTERFACE cpp-httplib/) + diff --git a/externals/cpp-httplib b/externals/cpp-httplib new file mode 160000 index 000000000..f80864ca0 --- /dev/null +++ b/externals/cpp-httplib @@ -0,0 +1 @@ +Subproject commit f80864ca031932351abef49b74097c67f14719c6 diff --git a/externals/openal-soft b/externals/openal-soft new file mode 160000 index 000000000..f120be6e2 --- /dev/null +++ b/externals/openal-soft @@ -0,0 +1 @@ +Subproject commit f120be6e2e7d2eb37a70f8adb5a98e5a645c5349 diff --git a/externals/sdl3 b/externals/sdl3 index bdb72bb3f..4e2fd57e7 160000 --- a/externals/sdl3 +++ b/externals/sdl3 @@ -1 +1 @@ -Subproject commit bdb72bb3f051de32c91f5deb439a50bfd51499dc +Subproject commit 4e2fd57e77fb4a28c0eeef0670fc4121cc2cf1f9