From 8d4cbdbca990d704c33cdeef1c9aa804ef23cfa5 Mon Sep 17 00:00:00 2001 From: Niram7777 Date: Sat, 14 Feb 2026 20:31:19 +0000 Subject: [PATCH 01/19] Log actually not always compact (#4035) --- documents/Debugging/Debugging.md | 2 + src/common/config.cpp | 12 ++++ src/common/config.h | 2 + src/common/logging/backend.cpp | 111 +++++++++++++++++++------------ src/emulator.cpp | 1 + 5 files changed, 84 insertions(+), 44 deletions(-) diff --git a/documents/Debugging/Debugging.md b/documents/Debugging/Debugging.md index 8bb4b8fbd..013ca15fb 100644 --- a/documents/Debugging/Debugging.md +++ b/documents/Debugging/Debugging.md @@ -73,6 +73,8 @@ You can configure the emulator by editing the `config.toml` file found in the `u - Examples: - If the log is being spammed with messages coming from Lib.Pad, you can use `Lib.Pad:Critical` to only log critical-level messages. - If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `*:Critical Render.Vulkan:Info` + - `isIdenticalLogGrouped`: Group same logs in one line with a counter (`true`/`false`) + - By default, the emulator will not rewrite the same line, and instead add a counter. - `Fullscreen`: Display the game in a full screen borderless window. diff --git a/src/common/config.cpp b/src/common/config.cpp index a5eea0a64..fb1181d62 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -141,6 +141,7 @@ static ConfigEntry isTrophyPopupDisabled(false); static ConfigEntry trophyNotificationDuration(6.0); static ConfigEntry logFilter(""); static ConfigEntry logType("sync"); +static ConfigEntry isIdenticalLogGrouped(true); static ConfigEntry userName("shadPS4"); static ConfigEntry isShowSplash(false); static ConfigEntry isSideTrophy("right"); @@ -395,6 +396,10 @@ string getLogType() { return logType.get(); } +bool groupIdenticalLogs() { + return isIdenticalLogGrouped.get(); +} + string getUserName() { return userName.get(); } @@ -694,6 +699,10 @@ void setLogType(const string& type, bool is_game_specific) { logType.set(type, is_game_specific); } +void setIdenticalLogGrouped(bool enable, bool is_game_specific) { + isIdenticalLogGrouped.set(enable, is_game_specific); +} + void setLogFilter(const string& type, bool is_game_specific) { logFilter.set(type, is_game_specific); } @@ -893,6 +902,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); logFilter.setFromToml(general, "logFilter", is_game_specific); logType.setFromToml(general, "logType", is_game_specific); + isIdenticalLogGrouped.setFromToml(general, "isIdenticalLogGrouped", is_game_specific); userName.setFromToml(general, "userName", is_game_specific); isShowSplash.setFromToml(general, "showSplash", is_game_specific); isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific); @@ -1081,6 +1091,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { is_game_specific); logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); logType.setTomlValue(data, "General", "logType", is_game_specific); + isIdenticalLogGrouped.setTomlValue(data, "General", "isIdenticalLogGrouped", is_game_specific); userName.setTomlValue(data, "General", "userName", is_game_specific); isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific); @@ -1224,6 +1235,7 @@ void setDefaultValues(bool is_game_specific) { trophyNotificationDuration.set(6.0, is_game_specific); logFilter.set("", is_game_specific); logType.set("sync", is_game_specific); + isIdenticalLogGrouped.set("isIdenticalLogGrouped", is_game_specific); userName.set("shadPS4", is_game_specific); isShowSplash.set(false, is_game_specific); isSideTrophy.set("right", is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index a9e0f7010..eb2b91f52 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -101,6 +101,8 @@ void setPipelineCacheEnabled(bool enable, bool is_game_specific = false); void setPipelineCacheArchived(bool enable, bool is_game_specific = false); std::string getLogType(); void setLogType(const std::string& type, bool is_game_specific = false); +bool groupIdenticalLogs(); +void setGroupIdenticalLogs(bool enable, bool is_game_specific = false); std::string getLogFilter(); void setLogFilter(const std::string& type, bool is_game_specific = false); double getTrophyNotificationDuration(); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 168350b96..9b7ea9cd1 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -209,41 +209,62 @@ 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; - 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 = message, - .thread = Common::GetCurrentThreadName(), - .counter = 1, - }; + if (Config::groupIdenticalLogs()) { + 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); + } + } + + 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 = message, + .thread = Common::GetCurrentThreadName(), + .counter = 1, + }; + } else { + const Entry 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 = 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: @@ -275,21 +296,23 @@ 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); + if (Config::groupIdenticalLogs()) { + // log last message + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); } - } - this->_last_entry = {}; + 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()) { diff --git a/src/emulator.cpp b/src/emulator.cpp index 87ce82326..5d3a652b8 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -239,6 +239,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "Game-specific config exists: {}", has_game_config); LOG_INFO(Config, "General LogType: {}", Config::getLogType()); + LOG_INFO(Config, "General isIdenticalLogGrouped: {}", Config::groupIdenticalLogs()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole()); LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole()); LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork()); From 98af227a8d98b81cd4f74bf6a038cd851888a498 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:47:28 -0600 Subject: [PATCH 02/19] More hotfixes (#4036) User events don't increment fflags, so don't increment it. Also added a bugfix for dce events, as static_cast wasn't properly unpacking event.data. --- src/core/libraries/kernel/equeue.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 332aa15c2..06b667008 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -98,14 +98,13 @@ struct EqueueEvent { void TriggerUser(void* data) { is_triggered = true; - event.fflags++; event.udata = data; } void TriggerDisplay(void* data) { is_triggered = true; if (data != nullptr) { - auto event_data = static_cast(event.data); + auto event_data = std::bit_cast(event.data); auto event_hint_raw = reinterpret_cast(data); auto event_hint = static_cast(event_hint_raw); if (event_hint.event_id == event.ident && event.ident != 0xfe) { From 0f92285e5071a788bd078f1c1c7210f2506fdd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Bogd=C4=81ns?= Date: Sat, 14 Feb 2026 23:30:09 +0200 Subject: [PATCH 03/19] GR2-win-crash-fix (#4033) * Improve stack clearing logic in ExecuteGuest Added a check for fiber stacks before clearing the stack in ExecuteGuest. That fixes Gravity Rush 2 crash on Windows. * Refactor ExecuteGuest to simplify stack clearing logic This enough for GR2 * Recover thread initialization in ExecuteGuest function * Enhance null check for thread control block * Fix condition to check tcb before clearing stack --- src/core/tls.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/tls.h b/src/core/tls.h index 83940be7a..27de518ea 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -61,7 +61,10 @@ template ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { EnsureThreadInitialized(); // clear stack to avoid trash from EnsureThreadInitialized - ClearStack<12_KB>(); + auto* tcb = GetTcbBase(); + if (tcb != nullptr && tcb->tcb_fiber == nullptr) { + ClearStack<12_KB>(); + } return func(std::forward(args)...); } From fba7304d628eb0858778024336f36ebd0ef5e3d8 Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier Date: Sun, 15 Feb 2026 07:12:52 +0000 Subject: [PATCH 04/19] cmake: prefer more system libs (#4037) --- CMakeLists.txt | 4 +++- externals/CMakeLists.txt | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1d7d9530..b07dcea87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,6 +227,8 @@ find_package(fmt 10.2.0 CONFIG) find_package(glslang 15 CONFIG) find_package(half 1.12.0 MODULE) find_package(magic_enum 0.9.7 CONFIG) +find_package(miniz 3.1 CONFIG) +find_package(nlohmann_json 3.12 CONFIG) find_package(PNG 1.6 MODULE) find_package(OpenAL CONFIG) find_package(RenderDoc 1.6.0 MODULE) @@ -1116,7 +1118,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 OpenAL::OpenAL Cpp_Httplib) +target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::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/CMakeLists.txt b/externals/CMakeLists.txt index 7f6e6ec4e..41a0f71c7 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -259,16 +259,19 @@ if (WIN32) add_subdirectory(ext-wepoll) endif() -if (NOT TARGET fdk-aac) add_subdirectory(aacdec) -endif() #nlohmann json +if (NOT TARGET nlohmann_json::nlohmann_json) set(JSON_BuildTests OFF CACHE INTERNAL "") add_subdirectory(json) +endif() # miniz +if (NOT TARGET miniz::miniz) add_subdirectory(miniz) +add_library(miniz::miniz ALIAS miniz) +endif() # cli11 if (NOT TARGET CLI11::CLI11) From 4a370519a53e579e849e734ef8a71105cd4d16fd Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:29:33 -0600 Subject: [PATCH 05/19] Lib.GnmDriver: Fix flip arg for sceGnmSubmitAndFlipCommandBuffers (#4038) * There is a mountain of evidence suggesting that flip_arg for these functions should be a 64-bit integer. This fixes "memory" errors in some Unity titles. * oops * Fix sceVideoOutGetEventData This bug went unnoticed for a while because the selection of Unity games I had at the time didn't actually care. This + the prior fix is needed for Unity titles. --- src/core/libraries/gnmdriver/gnmdriver.cpp | 6 +++--- src/core/libraries/gnmdriver/gnmdriver.h | 4 ++-- src/core/libraries/videoout/video_out.cpp | 6 +++--- src/core/libraries/videoout/video_out.h | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 1181f6299..25682ada0 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -2052,7 +2052,7 @@ int PS4_SYSV_ABI sceGnmSqttWaitForEvent() { } static inline s32 PatchFlipRequest(u32* cmdbuf, u32 size, u32 vo_handle, u32 buf_idx, u32 flip_mode, - u32 flip_arg, void* unk) { + s64 flip_arg, void* unk) { // check for `prepareFlip` packet cmdbuf += size - 64; ASSERT_MSG(cmdbuf[0] == 0xc03e1000, "Can't find `prepareFlip` packet"); @@ -2138,7 +2138,7 @@ static inline s32 PatchFlipRequest(u32* cmdbuf, u32 size, u32 vo_handle, u32 buf s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffers(u32 count, u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], u32* ccb_sizes_in_bytes, u32 vo_handle, - u32 buf_idx, u32 flip_mode, u32 flip_arg) { + u32 buf_idx, u32 flip_mode, s64 flip_arg) { return sceGnmSubmitAndFlipCommandBuffersForWorkload( count, count, dcb_gpu_addrs, dcb_sizes_in_bytes, ccb_gpu_addrs, ccb_sizes_in_bytes, vo_handle, buf_idx, flip_mode, flip_arg); @@ -2146,7 +2146,7 @@ s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffers(u32 count, u32* dcb_gpu_addrs s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffersForWorkload( u32 workload, u32 count, u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], - u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, u32 flip_arg) { + u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, s64 flip_arg) { LOG_DEBUG(Lib_GnmDriver, "called [buf = {}]", buf_idx); auto* cmdbuf = dcb_gpu_addrs[count - 1]; diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 4001f4661..5f3462dd9 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -211,10 +211,10 @@ int PS4_SYSV_ABI sceGnmSqttWaitForEvent(); s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffers(u32 count, u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], u32* ccb_sizes_in_bytes, u32 vo_handle, - u32 buf_idx, u32 flip_mode, u32 flip_arg); + u32 buf_idx, u32 flip_mode, s64 flip_arg); int PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffersForWorkload( u32 workload, u32 count, u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], - u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, u32 flip_arg); + u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, s64 flip_arg); s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, const u32* ccb_gpu_addrs[], u32* ccb_sizes_in_bytes); diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index e9176afdc..da58772a0 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -217,7 +217,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* } auto event_data = ev->data >> 0x10; - if (ev->ident != static_cast(OrbisVideoOutInternalEventId::Flip) || ev->data == 0) { + if (ev->ident != static_cast(OrbisVideoOutInternalEventId::Flip) || ev->data >= 0) { *data = event_data; } else { *data = event_data | 0xffff000000000000; @@ -338,7 +338,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_a return 16; } -s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk) { +s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, s64 flip_arg, void** unk) { auto* port = driver->GetPort(handle); if (!port) { return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; @@ -348,7 +348,7 @@ s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** u Platform::InterruptId::GfxFlip, [=](Platform::InterruptId irq) { ASSERT_MSG(irq == Platform::InterruptId::GfxFlip, "An unexpected IRQ occured"); ASSERT_MSG(port->buffer_labels[buf_id] == 1, "Out of order flip IRQ"); - const auto result = driver->SubmitFlip(port, buf_id, arg, true); + const auto result = driver->SubmitFlip(port, buf_id, flip_arg, true); ASSERT_MSG(result, "EOP flip submission failed"); }); diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index ba2732ff7..2c99a4d1c 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -139,7 +139,7 @@ s32 PS4_SYSV_ABI sceVideoOutColorSettingsSetGamma(SceVideoOutColorSettings* sett s32 PS4_SYSV_ABI sceVideoOutAdjustColor(s32 handle, const SceVideoOutColorSettings* settings); // Internal system functions -s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk); +s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, s64 flip_arg, void** unk); void RegisterLib(Core::Loader::SymbolsResolver* sym); From 547198af6fddf804e9d012ce3d56070d6fb765b9 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Mon, 16 Feb 2026 00:31:35 -0600 Subject: [PATCH 06/19] Lib.VideoOut: Fix pending flips limit (#4039) * Hardcoded limit to pending flips Real hardware has a fixed-size queue, and doesn't depend on the number of registered buffers. While the kernel supposedly uses an array of 18 elements, my tests suggest the cap is 16 pending flips. * Assert on trying to flip unregistered buffer I haven't seen anything do this intentionally yet, but I do have cases where games do this unintentionally (do to unimplemented functions). --- src/core/libraries/videoout/driver.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 4951c4f1a..21163119d 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -224,7 +224,7 @@ bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop /*= false*/) { { std::unique_lock lock{port->port_mutex}; - if (index != -1 && port->flip_status.flip_pending_num >= port->NumRegisteredBuffers()) { + if (index != -1 && port->flip_status.flip_pending_num > 16) { LOG_ERROR(Lib_VideoOut, "Flip queue is full"); return false; } @@ -252,6 +252,7 @@ void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_ frame = presenter->PrepareBlankFrame(false); } else { const auto& buffer = port->buffer_slots[index]; + ASSERT_MSG(buffer.group_index >= 0, "Trying to flip an unregistered buffer!"); const auto& group = port->groups[buffer.group_index]; frame = presenter->PrepareFrame(group, buffer.address_left); } From 3a99051df910ead12048e43c685388613fcc928e Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Mon, 16 Feb 2026 21:56:35 +0100 Subject: [PATCH 07/19] filesystem: fix crashes caused by returning a pointer from an std::vector (#4043) Co-authored-by: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> --- src/core/file_sys/fs.cpp | 2 +- src/core/file_sys/fs.h | 8 ++++++-- src/core/libraries/save_data/save_instance.cpp | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index cba95fe37..96a04ee5e 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -55,7 +55,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea if (path.length() > 255) return ""; - const MntPair* mount = GetMount(corrected_path); + const std::optional mount = GetMount(corrected_path); if (!mount) { return ""; } diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 599d9e823..6fc6c570f 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -58,14 +59,17 @@ public: return it == m_mnt_pairs.end() ? nullptr : &*it; } - const MntPair* GetMount(const std::string& guest_path) { + const std::optional GetMount(const std::string& guest_path) { std::scoped_lock lock{m_mutex}; const auto it = std::ranges::find_if(m_mnt_pairs, [&](const auto& mount) { // When doing starts-with check, add a trailing slash to make sure we don't match // against only part of the mount path. return guest_path == mount.mount || guest_path.starts_with(mount.mount + "/"); }); - return it == m_mnt_pairs.end() ? nullptr : &*it; + if (it == m_mnt_pairs.end()) { + return std::nullopt; + } + return *it; } private: diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index bc6bbfd72..baeec5d2c 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -105,7 +105,7 @@ SaveInstance::SaveInstance(int slot_num, Libraries::UserService::OrbisUserServic mount_point = "/savedata" + std::to_string(slot_num); this->exists = fs::exists(param_sfo_path); - this->mounted = g_mnt->GetMount(mount_point) != nullptr; + this->mounted = g_mnt->GetMount(mount_point) != std::nullopt; } SaveInstance::~SaveInstance() { From a99c8147392c806b2b7ffdf876720ba170f17c23 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 17 Feb 2026 22:41:18 +0200 Subject: [PATCH 08/19] Image copy Enhancement (#4041) * copy image handle 2d->3d copies and opossite now * make gcc happy * fixed colouring issue --- src/video_core/texture_cache/image.cpp | 128 ++++++++++++++++++++----- 1 file changed, 105 insertions(+), 23 deletions(-) diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index cce0cd281..6a292d914 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -462,50 +462,132 @@ static std::pair SanitizeCopyLayers(const ImageInfo& src_info, const I void Image::CopyImage(Image& src_image) { const auto& src_info = src_image.info; const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); - ASSERT(src_info.resources.layers == info.resources.layers || num_mips == 1); + + // Check format compatibility + if (src_info.pixel_format != info.pixel_format) { + LOG_WARNING(Render_Vulkan, + "Copy between different formats: src={}, dst={}. Color may be incorrect.", + vk::to_string(src_info.pixel_format), vk::to_string(info.pixel_format)); + } const u32 width = src_info.size.width; const u32 height = src_info.size.height; - const u32 depth = + const u32 base_depth = info.type == AmdGpu::ImageType::Color3D ? info.size.depth : src_info.size.depth; + auto [test_src_layers, test_dst_layers] = SanitizeCopyLayers(src_info, info, base_depth); + + ASSERT(test_src_layers == test_dst_layers || num_mips == 1 || + (ConvertImageType(src_info.type) != ConvertImageType(info.type) && + (test_src_layers == 1 || test_dst_layers == 1))); + SetBackingSamples(info.num_samples, false); src_image.SetBackingSamples(src_info.num_samples); boost::container::small_vector image_copies; + + const bool src_is_2d = ConvertImageType(src_info.type) == vk::ImageType::e2D; + const bool src_is_3d = ConvertImageType(src_info.type) == vk::ImageType::e3D; + const bool dst_is_2d = ConvertImageType(info.type) == vk::ImageType::e2D; + const bool dst_is_3d = ConvertImageType(info.type) == vk::ImageType::e3D; + + const bool is_2d_to_3d = src_is_2d && dst_is_3d; + const bool is_3d_to_2d = src_is_3d && dst_is_2d; + const bool is_same_type = !is_2d_to_3d && !is_3d_to_2d; + + // Use full aspect mask for color images + vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor; + + // If images have depth/stencil, we might need to handle separately + if (src_image.aspect_mask & vk::ImageAspectFlagBits::eDepth) { + aspect = vk::ImageAspectFlagBits::eDepth; + } else if (src_image.aspect_mask & vk::ImageAspectFlagBits::eStencil) { + aspect = vk::ImageAspectFlagBits::eStencil; + } + for (u32 mip = 0; mip < num_mips; ++mip) { const auto mip_w = std::max(width >> mip, 1u); const auto mip_h = std::max(height >> mip, 1u); - const auto mip_d = std::max(depth >> mip, 1u); - const auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d); + const auto mip_d = std::max(base_depth >> mip, 1u); - image_copies.emplace_back(vk::ImageCopy{ - .srcSubresource{ - .aspectMask = src_image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = src_layers, - }, - .dstSubresource{ - .aspectMask = aspect_mask & ~vk::ImageAspectFlagBits::eStencil, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = dst_layers, - }, - .extent = {mip_w, mip_h, mip_d}, - }); + auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d); + + if (is_same_type) { + u32 copy_layers = std::min(src_layers, dst_layers); + + if (src_is_3d) + src_layers = 1; + if (dst_is_3d) + dst_layers = 1; + + vk::ImageCopy copy_region = { + .srcSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = copy_layers, + }, + .dstSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = copy_layers, + }, + .extent = vk::Extent3D(mip_w, mip_h, mip_d), + }; + image_copies.push_back(copy_region); + } else if (is_2d_to_3d) { + vk::ImageCopy copy_region = { + .srcSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = src_layers, + }, + .dstSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .extent = vk::Extent3D(mip_w, mip_h, src_layers), + }; + image_copies.push_back(copy_region); + } else if (is_3d_to_2d) { + vk::ImageCopy copy_region = { + .srcSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .dstSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = dst_layers, + }, + .extent = vk::Extent3D(mip_w, mip_h, dst_layers), + }; + image_copies.push_back(copy_region); + } } scheduler->EndRendering(); + src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); - cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(), - backing->state.layout, image_copies); - Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); + if (!image_copies.empty()) { + cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(), + backing->state.layout, image_copies); + } + + // Simplified final layout - always use ShaderReadOnlyOptimal + // This is safe for most cases and avoids the usage check + Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}); } void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) { From f6d71646c0a6e450e9edab472ccbbb435bdc35c1 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Thu, 19 Feb 2026 21:26:33 +0300 Subject: [PATCH 09/19] threads: initialize TLS on thread creation (#4048) * initialize TLS on thread creation * initialize tls in dimensions toypad writer thread * clear most of the stack on thread init with some black magic --- src/core/libraries/avplayer/avplayer_impl.cpp | 16 ++++++------- .../libraries/avplayer/avplayer_state.cpp | 2 +- src/core/libraries/ime/ime.cpp | 8 +++---- src/core/libraries/ime/ime_dialog_ui.cpp | 5 ++-- src/core/libraries/kernel/threads/pthread.cpp | 22 +++++++++++++++++- .../libraries/kernel/threads/pthread_spec.cpp | 2 +- src/core/libraries/network/net_ctl_obj.cpp | 4 ++-- src/core/libraries/ngs2/ngs2.cpp | 4 ++-- .../libraries/usbd/emulated/dimensions.cpp | 4 ++++ src/core/linker.cpp | 10 ++++---- src/core/module.cpp | 3 ++- src/core/tls.h | 23 ------------------- 12 files changed, 52 insertions(+), 51 deletions(-) diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index 138747da4..db32862ad 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -12,28 +12,28 @@ void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(allocate, ptr, alignment, size); + return allocate(ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(deallocate, ptr, memory); + return deallocate(ptr, memory); } void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(allocate, ptr, alignment, size); + return allocate(ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(deallocate, ptr, memory); + return deallocate(ptr, memory); } int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { @@ -42,7 +42,7 @@ int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { const auto open = self->m_init_data_original.file_replacement.open; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(open, ptr, filename); + return open(ptr, filename); } int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { @@ -51,7 +51,7 @@ int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { const auto close = self->m_init_data_original.file_replacement.close; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(close, ptr); + return close(ptr); } int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) { @@ -60,7 +60,7 @@ int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position const auto read_offset = self->m_init_data_original.file_replacement.read_offset; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(read_offset, ptr, buffer, position, length); + return read_offset(ptr, buffer, position, length); } u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { @@ -69,7 +69,7 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { const auto size = self->m_init_data_original.file_replacement.size; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(size, ptr); + return size(ptr); } AvPlayerInitData AvPlayer::StubInitData(const AvPlayerInitData& data) { diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index e1b11840e..dbaa36d18 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -92,7 +92,7 @@ void AvPlayerState::DefaultEventCallback(void* opaque, AvPlayerEvents event_id, const auto callback = self->m_event_replacement.event_callback; const auto ptr = self->m_event_replacement.object_ptr; if (callback != nullptr) { - Core::ExecuteGuest(callback, ptr, event_id, 0, event_data); + callback(ptr, event_id, 0, event_data); } } diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index 258cc61e1..96ae446fa 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -99,16 +99,16 @@ public: if (m_ime_mode) { OrbisImeParam param = m_param.ime; if (use_param_handler) { - Core::ExecuteGuest(param.handler, param.arg, event); + param.handler(param.arg, event); } else { - Core::ExecuteGuest(handler, param.arg, event); + handler(param.arg, event); } } else { OrbisImeKeyboardParam param = m_param.key; if (use_param_handler) { - Core::ExecuteGuest(param.handler, param.arg, event); + param.handler(param.arg, event); } else { - Core::ExecuteGuest(handler, param.arg, event); + handler(param.arg, event); } } } diff --git a/src/core/libraries/ime/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp index 4a95c60c9..9611e7c49 100644 --- a/src/core/libraries/ime/ime_dialog_ui.cpp +++ b/src/core/libraries/ime/ime_dialog_ui.cpp @@ -131,8 +131,7 @@ bool ImeDialogState::CallTextFilter() { return false; } - int ret = - Core::ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length); + int ret = text_filter(out_text, &out_text_length, src_text, src_text_length); if (ret != 0) { return false; @@ -153,7 +152,7 @@ bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* return true; } - int ret = Core::ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr); + int ret = keyboard_filter(src_keycode, out_keycode, out_status, nullptr); return ret == 0; } diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp index 20bd20f4b..0218285f7 100644 --- a/src/core/libraries/kernel/threads/pthread.cpp +++ b/src/core/libraries/kernel/threads/pthread.cpp @@ -194,6 +194,21 @@ int PS4_SYSV_ABI posix_pthread_detach(PthreadT pthread) { return 0; } +#ifdef __clang__ +__attribute__((optnone)) +#else +__attribute__((optimize("O0"))) +#endif +void ClearStack(const PthreadAttr& attr) { + void* sp; + asm("mov %%rsp, %0" : "=rm"(sp)); + // leave a safety net of 128 bytes for memset + const u64 size = (u64)sp - (u64)attr.stackaddr_attr - 128; + volatile void* buf = alloca(size); + memset(const_cast(buf), 0, size); + buf = nullptr; +} + static void RunThread(void* arg) { auto* curthread = static_cast(arg); g_curthread = curthread; @@ -202,7 +217,12 @@ static void RunThread(void* arg) { /* Run the current thread's start routine with argument: */ curthread->native_thr.Initialize(); - void* ret = Core::ExecuteGuest(curthread->start_routine, curthread->arg); + Core::EnsureThreadInitialized(); + + // Clear the stack before running the guest thread + ClearStack(curthread->attr); + + void* ret = curthread->start_routine(curthread->arg); /* Remove thread from tracking */ DebugState.RemoveCurrentThreadFromGuestList(); diff --git a/src/core/libraries/kernel/threads/pthread_spec.cpp b/src/core/libraries/kernel/threads/pthread_spec.cpp index 094866a5a..38032f174 100644 --- a/src/core/libraries/kernel/threads/pthread_spec.cpp +++ b/src/core/libraries/kernel/threads/pthread_spec.cpp @@ -84,7 +84,7 @@ void _thread_cleanupspecific() { * destructor: */ lk.unlock(); - Core::ExecuteGuest(destructor, data); + destructor(data); lk.lock(); } } diff --git a/src/core/libraries/network/net_ctl_obj.cpp b/src/core/libraries/network/net_ctl_obj.cpp index a295477b6..a4081cd11 100644 --- a/src/core/libraries/network/net_ctl_obj.cpp +++ b/src/core/libraries/network/net_ctl_obj.cpp @@ -50,7 +50,7 @@ void NetCtlInternal::CheckCallback() { : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : callbacks) { if (func != nullptr) { - Core::ExecuteGuest(func, event, arg); + func(event, arg); } } } @@ -61,7 +61,7 @@ void NetCtlInternal::CheckNpToolkitCallback() { : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : nptool_callbacks) { if (func != nullptr) { - Core::ExecuteGuest(func, event, arg); + func(event, arg); } } } diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index 2f785f9a0..97d19c352 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -160,13 +160,13 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* o result = SystemSetup(option, &bufferInfo, 0, 0); if (result >= 0) { uintptr_t sysUserData = allocator->userData; - result = Core::ExecuteGuest(hostAlloc, &bufferInfo); + result = hostAlloc(&bufferInfo); if (result >= 0) { OrbisNgs2Handle* handleCopy = outHandle; result = SystemSetup(option, &bufferInfo, hostFree, handleCopy); if (result < 0) { if (hostFree) { - Core::ExecuteGuest(hostFree, &bufferInfo); + hostFree(&bufferInfo); } } } diff --git a/src/core/libraries/usbd/emulated/dimensions.cpp b/src/core/libraries/usbd/emulated/dimensions.cpp index 272f2f649..4d38c66fa 100644 --- a/src/core/libraries/usbd/emulated/dimensions.cpp +++ b/src/core/libraries/usbd/emulated/dimensions.cpp @@ -3,6 +3,8 @@ #include "dimensions.h" +#include "core/tls.h" + #include #include @@ -622,6 +624,8 @@ libusb_transfer_status DimensionsBackend::HandleAsyncTransfer(libusb_transfer* t s32 DimensionsBackend::SubmitTransfer(libusb_transfer* transfer) { if (transfer->endpoint == 0x01) { std::thread write_thread([this, transfer] { + Core::EnsureThreadInitialized(); + HandleAsyncTransfer(transfer); const u8 flags = transfer->flags; diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 7a0653e9f..20d81409e 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -135,7 +135,8 @@ void Linker::Execute(const std::vector& args) { } } params.entry_addr = module->GetEntryAddress(); - ExecuteGuest(RunMainEntry, ¶ms); + Core::EnsureThreadInitialized(); + RunMainEntry(¶ms); }); } @@ -379,8 +380,7 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) { if (!addr) { // Module was just loaded by above code. Allocate TLS block for it. const u32 init_image_size = module->tls.init_image_size; - u8* dest = reinterpret_cast( - Core::ExecuteGuest(heap_api->heap_malloc, module->tls.image_size)); + u8* dest = reinterpret_cast(heap_api->heap_malloc(module->tls.image_size)); const u8* src = reinterpret_cast(module->tls.image_virtual_addr); std::memcpy(dest, src, init_image_size); std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size); @@ -412,7 +412,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) { ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread"); } else { if (heap_api) { - addr_out = Core::ExecuteGuest(heap_api->heap_malloc, total_tls_size); + addr_out = heap_api->heap_malloc(total_tls_size); } else { addr_out = std::malloc(total_tls_size); } @@ -422,7 +422,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) { void Linker::FreeTlsForNonPrimaryThread(void* pointer) { if (heap_api) { - Core::ExecuteGuest(heap_api->heap_free, pointer); + heap_api->heap_free(pointer); } else { std::free(pointer); } diff --git a/src/core/module.cpp b/src/core/module.cpp index 127e74293..d0fae3a9f 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -97,7 +97,8 @@ Module::~Module() = default; s32 Module::Start(u64 args, const void* argp, void* param) { LOG_INFO(Core_Linker, "Module started : {}", name); const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress(); - return ExecuteGuest(reinterpret_cast(addr), args, argp, param); + Core::EnsureThreadInitialized(); + return reinterpret_cast(addr)(args, argp, param); } void Module::LoadModuleToMemory(u32& max_tls_index) { diff --git a/src/core/tls.h b/src/core/tls.h index 27de518ea..00eba188e 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -45,29 +45,6 @@ Tcb* GetTcbBase(); /// Makes sure TLS is initialized for the thread before entering guest. void EnsureThreadInitialized(); -template -#ifdef __clang__ -__attribute__((optnone)) -#else -__attribute__((optimize("O0"))) -#endif -void ClearStack() { - volatile void* buf = alloca(size); - memset(const_cast(buf), 0, size); - buf = nullptr; -} - -template -ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { - EnsureThreadInitialized(); - // clear stack to avoid trash from EnsureThreadInitialized - auto* tcb = GetTcbBase(); - if (tcb != nullptr && tcb->tcb_fiber == nullptr) { - ClearStack<12_KB>(); - } - return func(std::forward(args)...); -} - template struct HostCallWrapperImpl; From 06b901a47b12b2fea423a78ac34f2b5d13300c58 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 19 Feb 2026 21:11:33 +0200 Subject: [PATCH 10/19] Gpu fixes misc (#4050) * fixed image copy to ignore stencil aspect * Added logging for "Encountered unresolvable image overlap with equal memory address." * fixed overlap issues with different pitch , added more detailed logging for rest of overlap issues * improved log error * maybe mipmaps ? * array layers or different mip map range * rewrote case new image has fewer mip levels than cached image * array with 2 layers? * last case * improved * no it didn't work --- src/video_core/texture_cache/image.cpp | 24 ++-- .../texture_cache/texture_cache.cpp | 113 ++++++++++++++++++ 2 files changed, 126 insertions(+), 11 deletions(-) diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 6a292d914..418641bc3 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -465,9 +465,9 @@ void Image::CopyImage(Image& src_image) { // Check format compatibility if (src_info.pixel_format != info.pixel_format) { - LOG_WARNING(Render_Vulkan, - "Copy between different formats: src={}, dst={}. Color may be incorrect.", - vk::to_string(src_info.pixel_format), vk::to_string(info.pixel_format)); + LOG_DEBUG(Render_Vulkan, + "Copy between different formats: src={}, dst={}. Color may be incorrect.", + vk::to_string(src_info.pixel_format), vk::to_string(info.pixel_format)); } const u32 width = src_info.size.width; @@ -495,14 +495,12 @@ void Image::CopyImage(Image& src_image) { const bool is_3d_to_2d = src_is_3d && dst_is_2d; const bool is_same_type = !is_2d_to_3d && !is_3d_to_2d; - // Use full aspect mask for color images + // Determine aspect mask - exclude stencil vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor; - // If images have depth/stencil, we might need to handle separately + // For depth/stencil images, only copy the depth aspect (skip stencil) if (src_image.aspect_mask & vk::ImageAspectFlagBits::eDepth) { aspect = vk::ImageAspectFlagBits::eDepth; - } else if (src_image.aspect_mask & vk::ImageAspectFlagBits::eStencil) { - aspect = vk::ImageAspectFlagBits::eStencil; } for (u32 mip = 0; mip < num_mips; ++mip) { @@ -575,18 +573,22 @@ void Image::CopyImage(Image& src_image) { scheduler->EndRendering(); + // Remove the pipeline stage flags - they don't belong here src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); if (!image_copies.empty()) { - cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(), - backing->state.layout, image_copies); + cmdbuf.copyImage(src_image.GetImage(), vk::ImageLayout::eTransferSrcOptimal, GetImage(), + vk::ImageLayout::eTransferDstOptimal, image_copies); } - // Simplified final layout - always use ShaderReadOnlyOptimal - // This is safe for most cases and avoids the usage check + // Remove pipeline stage flags here too + src_image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, + {}); + Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}); } diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 17c7e67b3..a8d846cfc 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -297,6 +297,14 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag if (image_info.guest_address == cache_image.info.guest_address) { const u32 lhs_block_size = image_info.num_bits * image_info.num_samples; const u32 rhs_block_size = cache_image.info.num_bits * cache_image.info.num_samples; + + if (image_info.pitch != cache_image.info.pitch) { + if (safe_to_delete) { + FreeImage(cache_image_id); + } + return {merged_image_id, -1, -1}; + } + if (image_info.BlockDim() != cache_image.info.BlockDim() || lhs_block_size != rhs_block_size) { // Very likely this kind of overlap is caused by allocation from a pool. @@ -346,6 +354,111 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag return {merged_image_id, -1, -1}; } + // Enhanced debug logging for unreachable case + // Calculate expected size based on format and dimensions + u64 expected_size = + (static_cast(image_info.size.width) * static_cast(image_info.size.height) * + static_cast(image_info.size.depth) * static_cast(image_info.num_bits) / 8); + LOG_ERROR(Render_Vulkan, + "Unresolvable image overlap with equal memory address:\n" + "=== OLD IMAGE (cached) ===\n" + " Address: {:#x}\n" + " Size: {:#x} bytes\n" + " Format: {}\n" + " Type: {}\n" + " Width: {}\n" + " Height: {}\n" + " Depth: {}\n" + " Pitch: {}\n" + " Mip levels: {}\n" + " Array layers: {}\n" + " Samples: {}\n" + " Tile mode: {:#x}\n" + " Block size: {} bits\n" + " Is block-comp: {}\n" + " Guest size: {:#x}\n" + " Last accessed: tick {}\n" + " Safe to delete: {}\n" + "\n" + "=== NEW IMAGE (requested) ===\n" + " Address: {:#x}\n" + " Size: {:#x} bytes\n" + " Format: {}\n" + " Type: {}\n" + " Width: {}\n" + " Height: {}\n" + " Depth: {}\n" + " Pitch: {}\n" + " Mip levels: {}\n" + " Array layers: {}\n" + " Samples: {}\n" + " Tile mode: {:#x}\n" + " Block size: {} bits\n" + " Is block-comp: {}\n" + " Guest size: {:#x}\n" + "\n" + "=== COMPARISON ===\n" + " Same format: {}\n" + " Same type: {}\n" + " Same tile mode: {}\n" + " Same block size: {}\n" + " Same BlockDim: {}\n" + " Same pitch: {}\n" + " Old resources <= new: {} (old: {}, new: {})\n" + " Old size <= new size: {}\n" + " Expected size (calc): {} bytes\n" + " Size ratio (new/expected): {:.2f}x\n" + " Size ratio (new/old): {:.2f}x\n" + " Old vs expected diff: {} bytes ({:+.2f}%)\n" + " New vs expected diff: {} bytes ({:+.2f}%)\n" + " Merged image ID: {}\n" + " Binding type: {}\n" + " Current tick: {}\n" + " Age (ticks since last access): {}", + + // Old image details + cache_image.info.guest_address, cache_image.info.guest_size, + vk::to_string(cache_image.info.pixel_format), + static_cast(cache_image.info.type), cache_image.info.size.width, + cache_image.info.size.height, cache_image.info.size.depth, cache_image.info.pitch, + cache_image.info.resources.levels, cache_image.info.resources.layers, + cache_image.info.num_samples, static_cast(cache_image.info.tile_mode), + cache_image.info.num_bits, cache_image.info.props.is_block, + cache_image.info.guest_size, cache_image.tick_accessed_last, safe_to_delete, + + // New image details + image_info.guest_address, image_info.guest_size, + vk::to_string(image_info.pixel_format), static_cast(image_info.type), + image_info.size.width, image_info.size.height, image_info.size.depth, + image_info.pitch, image_info.resources.levels, image_info.resources.layers, + image_info.num_samples, static_cast(image_info.tile_mode), + image_info.num_bits, image_info.props.is_block, image_info.guest_size, + + // Comparison + (image_info.pixel_format == cache_image.info.pixel_format), + (image_info.type == cache_image.info.type), + (image_info.tile_mode == cache_image.info.tile_mode), + (image_info.num_bits == cache_image.info.num_bits), + (image_info.BlockDim() == cache_image.info.BlockDim()), + (image_info.pitch == cache_image.info.pitch), + (cache_image.info.resources <= image_info.resources), + cache_image.info.resources.levels, image_info.resources.levels, + (cache_image.info.guest_size <= image_info.guest_size), expected_size, + + // Size ratios + static_cast(image_info.guest_size) / expected_size, + static_cast(image_info.guest_size) / cache_image.info.guest_size, + + // Difference between actual and expected sizes with percentages + static_cast(cache_image.info.guest_size) - static_cast(expected_size), + (static_cast(cache_image.info.guest_size) / expected_size - 1.0) * 100.0, + + static_cast(image_info.guest_size) - static_cast(expected_size), + (static_cast(image_info.guest_size) / expected_size - 1.0) * 100.0, + + merged_image_id.index, static_cast(binding), scheduler.CurrentTick(), + scheduler.CurrentTick() - cache_image.tick_accessed_last); + UNREACHABLE_MSG("Encountered unresolvable image overlap with equal memory address."); } From 1eb09ab440443fa51fae1d9549bb83a73cf61101 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:37:59 -0600 Subject: [PATCH 11/19] Kernel.Equeue: Various event fixes (#4059) * Fix vblank event data * Various logical fixes for timer events Store timer timeouts in nanoseconds now, properly handle event "replacement", avoid employing small timers when adding events to equeues, fix stored timer data * Clang --- src/core/libraries/kernel/equeue.cpp | 67 +++++++++++++------------- src/core/libraries/kernel/equeue.h | 10 ++-- src/core/libraries/videoout/driver.cpp | 21 +++++--- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 72e38b265..9291c0a1a 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -8,6 +8,7 @@ #include "common/logging/log.h" #include "core/libraries/kernel/equeue.h" #include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" namespace Libraries::Kernel { @@ -15,23 +16,39 @@ namespace Libraries::Kernel { extern boost::asio::io_context io_context; extern void KernelSignalRequest(); -static constexpr auto HrTimerSpinlockThresholdUs = 1200u; +static constexpr auto HrTimerSpinlockThresholdNs = 1200000u; // Events are uniquely identified by id and filter. - bool EqueueInternal::AddEvent(EqueueEvent& event) { std::scoped_lock lock{m_mutex}; + // Calculate timer interval event.time_added = std::chrono::steady_clock::now(); if (event.event.filter == SceKernelEvent::Filter::Timer || event.event.filter == SceKernelEvent::Filter::HrTimer) { - // HrTimer events are offset by the threshold of time at the end that we spinlock for - // greater accuracy. - const auto offset = - event.event.filter == SceKernelEvent::Filter::HrTimer ? HrTimerSpinlockThresholdUs : 0u; - event.timer_interval = std::chrono::microseconds(event.event.data - offset); + // Set timer interval + event.timer_interval = std::chrono::nanoseconds(event.event.data); } + // First, check if there's already an event with the same id and filter. + u64 id = event.event.ident; + SceKernelEvent::Filter filter = event.event.filter; + const auto& find_it = std::ranges::find_if(m_events, [id, filter](auto& ev) { + return ev.event.ident == id && ev.event.filter == filter; + }); + // If there is a duplicate event, we need to update that instead. + if (find_it != m_events.cend()) { + // Specifically, update user data and timer_interval. + // Trigger status and event data should remain intact. + auto& old_event = *find_it; + old_event.timer_interval = event.timer_interval; + old_event.event.udata = event.event.udata; + return true; + } + + // Clear input data from event. + event.event.data = 0; + // Remove add flag from event event.event.flags &= ~SceKernelEvent::Flags::Add; @@ -157,6 +174,9 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) { event.TriggerDisplay(trigger_data); } else if (filter == SceKernelEvent::Filter::User) { event.TriggerUser(trigger_data); + } else if (filter == SceKernelEvent::Filter::Timer || + filter == SceKernelEvent::Filter::HrTimer) { + event.TriggerTimer(); } else { event.Trigger(trigger_data); } @@ -197,7 +217,7 @@ bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) { SmallTimer st; st.event = ev.event; st.added = std::chrono::steady_clock::now(); - st.interval = std::chrono::microseconds{ev.event.data}; + st.interval = std::chrono::nanoseconds{ev.event.data}; { std::scoped_lock lock{m_mutex}; m_small_timers[st.event.ident] = std::move(st); @@ -307,30 +327,23 @@ int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int } static void HrTimerCallback(SceKernelEqueue eq, const SceKernelEvent& kevent) { - static EqueueEvent event; - event.event = kevent; - event.event.data = HrTimerSpinlockThresholdUs; - eq->AddSmallTimer(event); eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::HrTimer, kevent.udata); } -s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* ts, void* udata) { +s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, OrbisKernelTimespec* ts, + void* udata) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } - if (ts->tv_sec > 100 || ts->tv_nsec < 100'000) { - return ORBIS_KERNEL_ERROR_EINVAL; - } - ASSERT(ts->tv_nsec > 1000); // assume 1us resolution - const auto total_us = ts->tv_sec * 1000'000 + ts->tv_nsec / 1000; + const auto total_ns = ts->tv_sec * 1000000000 + ts->tv_nsec; EqueueEvent event{}; event.event.ident = id; event.event.filter = SceKernelEvent::Filter::HrTimer; event.event.flags = SceKernelEvent::Flags::Add | SceKernelEvent::Flags::OneShot; event.event.fflags = 0; - event.event.data = total_us; + event.event.data = total_ns; event.event.udata = udata; // HR timers cannot be implemented within the existing event queue architecture due to the @@ -340,12 +353,7 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* // `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is // large. Even for large delays, we truncate a small portion to complete the wait // using the spinlock, prioritizing precision. - - if (eq->EventExists(event.event.ident, event.event.filter)) { - eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer); - } - - if (total_us < HrTimerSpinlockThresholdUs) { + if (total_ns < HrTimerSpinlockThresholdNs) { return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } @@ -391,16 +399,9 @@ int PS4_SYSV_ABI sceKernelAddTimerEvent(SceKernelEqueue eq, int id, SceKernelUse event.event.filter = SceKernelEvent::Filter::Timer; event.event.flags = SceKernelEvent::Flags::Add; event.event.fflags = 0; - event.event.data = usec; + event.event.data = usec * 1000; event.event.udata = udata; - if (eq->EventExists(event.event.ident, event.event.filter)) { - eq->RemoveEvent(id, SceKernelEvent::Filter::Timer); - LOG_DEBUG(Kernel_Event, - "Timer event already exists, removing it: queue name={}, queue id={}", - eq->GetName(), event.event.ident); - } - LOG_DEBUG(Kernel_Event, "Added timing event: queue name={}, queue id={}, usec={}, pointer={:x}", eq->GetName(), event.event.ident, usec, reinterpret_cast(udata)); diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 06b667008..83b4b8689 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -81,7 +81,7 @@ struct EqueueEvent { SceKernelEvent event; void* data = nullptr; std::chrono::steady_clock::time_point time_added; - std::chrono::microseconds timer_interval; + std::chrono::nanoseconds timer_interval; std::unique_ptr timer; void Clear() { @@ -92,7 +92,6 @@ struct EqueueEvent { void Trigger(void* data) { is_triggered = true; - event.fflags++; event.data = reinterpret_cast(data); } @@ -101,6 +100,11 @@ struct EqueueEvent { event.udata = data; } + void TriggerTimer() { + is_triggered = true; + event.data++; + } + void TriggerDisplay(void* data) { is_triggered = true; if (data != nullptr) { @@ -135,7 +139,7 @@ class EqueueInternal { struct SmallTimer { SceKernelEvent event; std::chrono::steady_clock::time_point added; - std::chrono::microseconds interval; + std::chrono::nanoseconds interval; }; public: diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 21163119d..2f44b1c99 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -315,20 +315,25 @@ void VideoOutDriver::PresentThread(std::stop_token token) { { // Needs lock here as can be concurrently read by `sceVideoOutGetVblankStatus` std::scoped_lock lock{main_port.vo_mutex}; + + // Trigger flip events for the port + for (auto& event : main_port.vblank_events) { + if (event != nullptr) { + event->TriggerEvent(static_cast(OrbisVideoOutInternalEventId::Vblank), + Kernel::SceKernelEvent::Filter::VideoOut, + reinterpret_cast( + static_cast(OrbisVideoOutInternalEventId::Vblank) | + (vblank_status.count << 16))); + } + } + + // Update vblank status vblank_status.count++; vblank_status.process_time = Libraries::Kernel::sceKernelGetProcessTime(); vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc(); main_port.vblank_cv.notify_all(); } - // Trigger flip events for the port. - for (auto& event : main_port.vblank_events) { - if (event != nullptr) { - event->TriggerEvent(static_cast(OrbisVideoOutInternalEventId::Vblank), - Kernel::SceKernelEvent::Filter::VideoOut, nullptr); - } - } - timer.End(); } } From 248b3e2d3086a90352787e14de5de0c1718b5db5 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:53:28 -0600 Subject: [PATCH 12/19] Core: Force logging of critical errors (#4061) * Ignore enabled flag on critical log entries This ensures critical errors (asserts and unreachables) are logged when the log file exceeds 100MB, or when logging is disabled. * I apparently need sleep --- src/common/logging/backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 9b7ea9cd1..930b1ac30 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -70,7 +70,7 @@ public: ~FileBackend() = default; void Write(const Entry& entry) { - if (!enabled) { + if (!enabled && entry.log_level != Level::Critical) { return; } From 9241ebd4dd48ace4f41d78dcba5519e08e84c5d5 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Sat, 21 Feb 2026 14:10:25 +0300 Subject: [PATCH 13/19] Revert "threads: initialize TLS on thread creation (#4048)" (#4062) This reverts commit f6d71646c0a6e450e9edab472ccbbb435bdc35c1. --- src/core/libraries/avplayer/avplayer_impl.cpp | 16 ++++++------- .../libraries/avplayer/avplayer_state.cpp | 2 +- src/core/libraries/ime/ime.cpp | 8 +++---- src/core/libraries/ime/ime_dialog_ui.cpp | 5 ++-- src/core/libraries/kernel/threads/pthread.cpp | 22 +----------------- .../libraries/kernel/threads/pthread_spec.cpp | 2 +- src/core/libraries/network/net_ctl_obj.cpp | 4 ++-- src/core/libraries/ngs2/ngs2.cpp | 4 ++-- .../libraries/usbd/emulated/dimensions.cpp | 4 ---- src/core/linker.cpp | 10 ++++---- src/core/module.cpp | 3 +-- src/core/tls.h | 23 +++++++++++++++++++ 12 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index db32862ad..138747da4 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -12,28 +12,28 @@ void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return allocate(ptr, alignment, size); + return Core::ExecuteGuest(allocate, ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return deallocate(ptr, memory); + return Core::ExecuteGuest(deallocate, ptr, memory); } void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return allocate(ptr, alignment, size); + return Core::ExecuteGuest(allocate, ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return deallocate(ptr, memory); + return Core::ExecuteGuest(deallocate, ptr, memory); } int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { @@ -42,7 +42,7 @@ int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { const auto open = self->m_init_data_original.file_replacement.open; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return open(ptr, filename); + return Core::ExecuteGuest(open, ptr, filename); } int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { @@ -51,7 +51,7 @@ int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { const auto close = self->m_init_data_original.file_replacement.close; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return close(ptr); + return Core::ExecuteGuest(close, ptr); } int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) { @@ -60,7 +60,7 @@ int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position const auto read_offset = self->m_init_data_original.file_replacement.read_offset; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return read_offset(ptr, buffer, position, length); + return Core::ExecuteGuest(read_offset, ptr, buffer, position, length); } u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { @@ -69,7 +69,7 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { const auto size = self->m_init_data_original.file_replacement.size; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return size(ptr); + return Core::ExecuteGuest(size, ptr); } AvPlayerInitData AvPlayer::StubInitData(const AvPlayerInitData& data) { diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index dbaa36d18..e1b11840e 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -92,7 +92,7 @@ void AvPlayerState::DefaultEventCallback(void* opaque, AvPlayerEvents event_id, const auto callback = self->m_event_replacement.event_callback; const auto ptr = self->m_event_replacement.object_ptr; if (callback != nullptr) { - callback(ptr, event_id, 0, event_data); + Core::ExecuteGuest(callback, ptr, event_id, 0, event_data); } } diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index 96ae446fa..258cc61e1 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -99,16 +99,16 @@ public: if (m_ime_mode) { OrbisImeParam param = m_param.ime; if (use_param_handler) { - param.handler(param.arg, event); + Core::ExecuteGuest(param.handler, param.arg, event); } else { - handler(param.arg, event); + Core::ExecuteGuest(handler, param.arg, event); } } else { OrbisImeKeyboardParam param = m_param.key; if (use_param_handler) { - param.handler(param.arg, event); + Core::ExecuteGuest(param.handler, param.arg, event); } else { - handler(param.arg, event); + Core::ExecuteGuest(handler, param.arg, event); } } } diff --git a/src/core/libraries/ime/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp index 9611e7c49..4a95c60c9 100644 --- a/src/core/libraries/ime/ime_dialog_ui.cpp +++ b/src/core/libraries/ime/ime_dialog_ui.cpp @@ -131,7 +131,8 @@ bool ImeDialogState::CallTextFilter() { return false; } - int ret = text_filter(out_text, &out_text_length, src_text, src_text_length); + int ret = + Core::ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length); if (ret != 0) { return false; @@ -152,7 +153,7 @@ bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* return true; } - int ret = keyboard_filter(src_keycode, out_keycode, out_status, nullptr); + int ret = Core::ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr); return ret == 0; } diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp index 0218285f7..20bd20f4b 100644 --- a/src/core/libraries/kernel/threads/pthread.cpp +++ b/src/core/libraries/kernel/threads/pthread.cpp @@ -194,21 +194,6 @@ int PS4_SYSV_ABI posix_pthread_detach(PthreadT pthread) { return 0; } -#ifdef __clang__ -__attribute__((optnone)) -#else -__attribute__((optimize("O0"))) -#endif -void ClearStack(const PthreadAttr& attr) { - void* sp; - asm("mov %%rsp, %0" : "=rm"(sp)); - // leave a safety net of 128 bytes for memset - const u64 size = (u64)sp - (u64)attr.stackaddr_attr - 128; - volatile void* buf = alloca(size); - memset(const_cast(buf), 0, size); - buf = nullptr; -} - static void RunThread(void* arg) { auto* curthread = static_cast(arg); g_curthread = curthread; @@ -217,12 +202,7 @@ static void RunThread(void* arg) { /* Run the current thread's start routine with argument: */ curthread->native_thr.Initialize(); - Core::EnsureThreadInitialized(); - - // Clear the stack before running the guest thread - ClearStack(curthread->attr); - - void* ret = curthread->start_routine(curthread->arg); + void* ret = Core::ExecuteGuest(curthread->start_routine, curthread->arg); /* Remove thread from tracking */ DebugState.RemoveCurrentThreadFromGuestList(); diff --git a/src/core/libraries/kernel/threads/pthread_spec.cpp b/src/core/libraries/kernel/threads/pthread_spec.cpp index 38032f174..094866a5a 100644 --- a/src/core/libraries/kernel/threads/pthread_spec.cpp +++ b/src/core/libraries/kernel/threads/pthread_spec.cpp @@ -84,7 +84,7 @@ void _thread_cleanupspecific() { * destructor: */ lk.unlock(); - destructor(data); + Core::ExecuteGuest(destructor, data); lk.lock(); } } diff --git a/src/core/libraries/network/net_ctl_obj.cpp b/src/core/libraries/network/net_ctl_obj.cpp index a4081cd11..a295477b6 100644 --- a/src/core/libraries/network/net_ctl_obj.cpp +++ b/src/core/libraries/network/net_ctl_obj.cpp @@ -50,7 +50,7 @@ void NetCtlInternal::CheckCallback() { : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : callbacks) { if (func != nullptr) { - func(event, arg); + Core::ExecuteGuest(func, event, arg); } } } @@ -61,7 +61,7 @@ void NetCtlInternal::CheckNpToolkitCallback() { : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : nptool_callbacks) { if (func != nullptr) { - func(event, arg); + Core::ExecuteGuest(func, event, arg); } } } diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index 97d19c352..2f785f9a0 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -160,13 +160,13 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* o result = SystemSetup(option, &bufferInfo, 0, 0); if (result >= 0) { uintptr_t sysUserData = allocator->userData; - result = hostAlloc(&bufferInfo); + result = Core::ExecuteGuest(hostAlloc, &bufferInfo); if (result >= 0) { OrbisNgs2Handle* handleCopy = outHandle; result = SystemSetup(option, &bufferInfo, hostFree, handleCopy); if (result < 0) { if (hostFree) { - hostFree(&bufferInfo); + Core::ExecuteGuest(hostFree, &bufferInfo); } } } diff --git a/src/core/libraries/usbd/emulated/dimensions.cpp b/src/core/libraries/usbd/emulated/dimensions.cpp index 4d38c66fa..272f2f649 100644 --- a/src/core/libraries/usbd/emulated/dimensions.cpp +++ b/src/core/libraries/usbd/emulated/dimensions.cpp @@ -3,8 +3,6 @@ #include "dimensions.h" -#include "core/tls.h" - #include #include @@ -624,8 +622,6 @@ libusb_transfer_status DimensionsBackend::HandleAsyncTransfer(libusb_transfer* t s32 DimensionsBackend::SubmitTransfer(libusb_transfer* transfer) { if (transfer->endpoint == 0x01) { std::thread write_thread([this, transfer] { - Core::EnsureThreadInitialized(); - HandleAsyncTransfer(transfer); const u8 flags = transfer->flags; diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 20d81409e..7a0653e9f 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -135,8 +135,7 @@ void Linker::Execute(const std::vector& args) { } } params.entry_addr = module->GetEntryAddress(); - Core::EnsureThreadInitialized(); - RunMainEntry(¶ms); + ExecuteGuest(RunMainEntry, ¶ms); }); } @@ -380,7 +379,8 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) { if (!addr) { // Module was just loaded by above code. Allocate TLS block for it. const u32 init_image_size = module->tls.init_image_size; - u8* dest = reinterpret_cast(heap_api->heap_malloc(module->tls.image_size)); + u8* dest = reinterpret_cast( + Core::ExecuteGuest(heap_api->heap_malloc, module->tls.image_size)); const u8* src = reinterpret_cast(module->tls.image_virtual_addr); std::memcpy(dest, src, init_image_size); std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size); @@ -412,7 +412,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) { ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread"); } else { if (heap_api) { - addr_out = heap_api->heap_malloc(total_tls_size); + addr_out = Core::ExecuteGuest(heap_api->heap_malloc, total_tls_size); } else { addr_out = std::malloc(total_tls_size); } @@ -422,7 +422,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) { void Linker::FreeTlsForNonPrimaryThread(void* pointer) { if (heap_api) { - heap_api->heap_free(pointer); + Core::ExecuteGuest(heap_api->heap_free, pointer); } else { std::free(pointer); } diff --git a/src/core/module.cpp b/src/core/module.cpp index d0fae3a9f..127e74293 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -97,8 +97,7 @@ Module::~Module() = default; s32 Module::Start(u64 args, const void* argp, void* param) { LOG_INFO(Core_Linker, "Module started : {}", name); const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress(); - Core::EnsureThreadInitialized(); - return reinterpret_cast(addr)(args, argp, param); + return ExecuteGuest(reinterpret_cast(addr), args, argp, param); } void Module::LoadModuleToMemory(u32& max_tls_index) { diff --git a/src/core/tls.h b/src/core/tls.h index 00eba188e..27de518ea 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -45,6 +45,29 @@ Tcb* GetTcbBase(); /// Makes sure TLS is initialized for the thread before entering guest. void EnsureThreadInitialized(); +template +#ifdef __clang__ +__attribute__((optnone)) +#else +__attribute__((optimize("O0"))) +#endif +void ClearStack() { + volatile void* buf = alloca(size); + memset(const_cast(buf), 0, size); + buf = nullptr; +} + +template +ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { + EnsureThreadInitialized(); + // clear stack to avoid trash from EnsureThreadInitialized + auto* tcb = GetTcbBase(); + if (tcb != nullptr && tcb->tcb_fiber == nullptr) { + ClearStack<12_KB>(); + } + return func(std::forward(args)...); +} + template struct HostCallWrapperImpl; From b37e0a6ea605e6e00e42345b02d5e5d10c82cc25 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sun, 22 Feb 2026 16:08:06 +0100 Subject: [PATCH 14/19] Miscallenous function exports and implementations (#4068) * _nanosleep * pthread_mutexattr_setpshared * pthread_attr_setschedpolicy * getuid * copyright 2026 --- src/core/libraries/kernel/process.cpp | 7 +- src/core/libraries/kernel/threads/mutex.cpp | 79 +++++++++++-------- .../libraries/kernel/threads/pthread_attr.cpp | 4 +- src/core/libraries/kernel/time.cpp | 4 +- 4 files changed, 56 insertions(+), 38 deletions(-) diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 31cc9a81b..a79da62ee 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.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/config.h" @@ -275,6 +275,10 @@ s32 PS4_SYSV_ABI sceKernelGetModuleList2(s32* handles, u64 num_array, u64* out_c return ORBIS_OK; } +u32 PS4_SYSV_ABI posix_getuid() { + return 1; +} + s32 PS4_SYSV_ABI exit(s32 status) { UNREACHABLE_MSG("Exiting with status code {}", status); return 0; @@ -299,6 +303,7 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("HZO7xOos4xc", "libkernel", 1, "libkernel", sceKernelGetModuleInfoInternal); LIB_FUNCTION("IuxnUuXk6Bg", "libkernel", 1, "libkernel", sceKernelGetModuleList); LIB_FUNCTION("ZzzC3ZGVAkc", "libkernel", 1, "libkernel", sceKernelGetModuleList2); + LIB_FUNCTION("kg4x8Prhfxw", "libkernel", 1, "libkernel", posix_getuid); LIB_FUNCTION("6Z83sYWFlA8", "libkernel", 1, "libkernel", exit); } diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp index 31e8b900b..51d2d3bcd 100644 --- a/src/core/libraries/kernel/threads/mutex.cpp +++ b/src/core/libraries/kernel/threads/mutex.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 @@ -39,7 +39,7 @@ static constexpr PthreadMutexAttr PthreadMutexattrAdaptiveDefault = { using CallocFun = void* (*)(size_t, size_t); -static int MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, const char* name) { +static s32 MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, const char* name) { const PthreadMutexAttr* attr; if (mutex_attr == nullptr) { attr = &PthreadMutexattrDefault; @@ -60,7 +60,7 @@ static int MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, c if (name) { pmutex->name = name; } else { - static int MutexId = 0; + static s32 MutexId = 0; pmutex->name = fmt::format("Mutex{}", MutexId++); } @@ -79,7 +79,7 @@ static int MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, c return 0; } -static int InitStatic(Pthread* thread, PthreadMutexT* mutex) { +static s32 InitStatic(Pthread* thread, PthreadMutexT* mutex) { std::scoped_lock lk{MutxStaticLock}; if (*mutex == THR_MUTEX_INITIALIZER) { @@ -90,17 +90,17 @@ static int InitStatic(Pthread* thread, PthreadMutexT* mutex) { return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_init(PthreadMutexT* mutex, +s32 PS4_SYSV_ABI posix_pthread_mutex_init(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr) { return MutexInit(mutex, mutex_attr ? *mutex_attr : nullptr, nullptr); } -int PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr, +s32 PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr, const char* name) { return MutexInit(mutex, mutex_attr ? *mutex_attr : nullptr, name); } -int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex) { PthreadMutexT m = *mutex; if (m < THR_MUTEX_DESTROYED) { return 0; @@ -116,7 +116,7 @@ int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex) { return 0; } -int PthreadMutex::SelfTryLock() { +s32 PthreadMutex::SelfTryLock() { switch (Type()) { case PthreadMutexType::ErrorCheck: case PthreadMutexType::Normal: @@ -135,7 +135,7 @@ int PthreadMutex::SelfTryLock() { } } -int PthreadMutex::SelfLock(const OrbisKernelTimespec* abstime, u64 usec) { +s32 PthreadMutex::SelfLock(const OrbisKernelTimespec* abstime, u64 usec) { const auto DoSleep = [&] { if (abstime == THR_RELTIME) { std::this_thread::sleep_for(std::chrono::microseconds(usec)); @@ -185,7 +185,7 @@ int PthreadMutex::SelfLock(const OrbisKernelTimespec* abstime, u64 usec) { } } -int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { +s32 PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { Pthread* curthread = g_curthread; if (m_owner == curthread) { return SelfLock(abstime, usec); @@ -198,7 +198,7 @@ int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { * faster than entering the kernel */ if (m_protocol == PthreadMutexProt::None) [[likely]] { - int count = m_spinloops; + s32 count = m_spinloops; while (count--) { if (m_lock.try_lock()) { m_owner = curthread; @@ -217,7 +217,7 @@ int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { } } - int ret = 0; + s32 ret = 0; if (abstime == nullptr) { m_lock.lock(); } else if (abstime != THR_RELTIME && (abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)) @@ -236,40 +236,40 @@ int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { return ret; } -int PthreadMutex::TryLock() { +s32 PthreadMutex::TryLock() { Pthread* curthread = g_curthread; if (m_owner == curthread) { return SelfTryLock(); } - const int ret = m_lock.try_lock() ? 0 : POSIX_EBUSY; + const s32 ret = m_lock.try_lock() ? 0 : POSIX_EBUSY; if (ret == 0) { m_owner = curthread; } return ret; } -int PS4_SYSV_ABI posix_pthread_mutex_trylock(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_trylock(PthreadMutexT* mutex) { CHECK_AND_INIT_MUTEX return (*mutex)->TryLock(); } -int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) { CHECK_AND_INIT_MUTEX return (*mutex)->Lock(nullptr); } -int PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex, +s32 PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex, const OrbisKernelTimespec* abstime) { CHECK_AND_INIT_MUTEX return (*mutex)->Lock(abstime); } -int PS4_SYSV_ABI posix_pthread_mutex_reltimedlock_np(PthreadMutexT* mutex, u64 usec) { +s32 PS4_SYSV_ABI posix_pthread_mutex_reltimedlock_np(PthreadMutexT* mutex, u64 usec) { CHECK_AND_INIT_MUTEX return (*mutex)->Lock(THR_RELTIME, usec); } -int PthreadMutex::Unlock() { +s32 PthreadMutex::Unlock() { Pthread* curthread = g_curthread; /* * Check if the running thread is not the owner of the mutex. @@ -294,7 +294,7 @@ int PthreadMutex::Unlock() { return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex) { PthreadMutex* mp = *mutex; if (mp <= THR_MUTEX_DESTROYED) [[unlikely]] { if (mp == THR_MUTEX_DESTROYED) { @@ -305,29 +305,29 @@ int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex) { return mp->Unlock(); } -int PS4_SYSV_ABI posix_pthread_mutex_getspinloops_np(PthreadMutexT* mutex, int* count) { +s32 PS4_SYSV_ABI posix_pthread_mutex_getspinloops_np(PthreadMutexT* mutex, int* count) { CHECK_AND_INIT_MUTEX *count = (*mutex)->m_spinloops; return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_setspinloops_np(PthreadMutexT* mutex, int count) { +s32 PS4_SYSV_ABI posix_pthread_mutex_setspinloops_np(PthreadMutexT* mutex, s32 count) { CHECK_AND_INIT_MUTEX(*mutex)->m_spinloops = count; return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_getyieldloops_np(PthreadMutexT* mutex, int* count) { +s32 PS4_SYSV_ABI posix_pthread_mutex_getyieldloops_np(PthreadMutexT* mutex, int* count) { CHECK_AND_INIT_MUTEX *count = (*mutex)->m_yieldloops; return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_setyieldloops_np(PthreadMutexT* mutex, int count) { +s32 PS4_SYSV_ABI posix_pthread_mutex_setyieldloops_np(PthreadMutexT* mutex, s32 count) { CHECK_AND_INIT_MUTEX(*mutex)->m_yieldloops = count; return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_isowned_np(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_isowned_np(PthreadMutexT* mutex) { PthreadMutex* m = *mutex; if (m <= THR_MUTEX_DESTROYED) { return 0; @@ -335,7 +335,7 @@ int PS4_SYSV_ABI posix_pthread_mutex_isowned_np(PthreadMutexT* mutex) { return m->m_owner == g_curthread; } -int PthreadMutex::IsOwned(Pthread* curthread) const { +s32 PthreadMutex::IsOwned(Pthread* curthread) const { if (this <= THR_MUTEX_DESTROYED) [[unlikely]] { if (this == THR_MUTEX_DESTROYED) { return POSIX_EINVAL; @@ -348,7 +348,7 @@ int PthreadMutex::IsOwned(Pthread* curthread) const { return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) { auto pattr = new (std::nothrow) PthreadMutexAttr{}; if (pattr == nullptr) { return POSIX_ENOMEM; @@ -358,7 +358,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) { return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_setkind_np(PthreadMutexAttrT* attr, +s32 PS4_SYSV_ABI posix_pthread_mutexattr_setkind_np(PthreadMutexAttrT* attr, PthreadMutexType kind) { if (attr == nullptr || *attr == nullptr) { *__Error() = POSIX_EINVAL; @@ -368,7 +368,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_setkind_np(PthreadMutexAttrT* attr, return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) { if (attr == nullptr) { *__Error() = POSIX_EINVAL; return -1; @@ -376,7 +376,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) { return static_cast(attr->m_type); } -int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) { if (attr == nullptr || *attr == nullptr || type < PthreadMutexType::ErrorCheck || type >= PthreadMutexType::Max) { return POSIX_EINVAL; @@ -385,7 +385,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, Pthrea return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_gettype(PthreadMutexAttrT* attr, PthreadMutexType* type) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_gettype(PthreadMutexAttrT* attr, PthreadMutexType* type) { if (attr == nullptr || *attr == nullptr || (*attr)->m_type >= PthreadMutexType::Max) { return POSIX_EINVAL; } @@ -393,7 +393,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_gettype(PthreadMutexAttrT* attr, Pthrea return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) { if (attr == nullptr || *attr == nullptr) { return POSIX_EINVAL; } @@ -402,7 +402,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) { return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_getprotocol(PthreadMutexAttrT* mattr, +s32 PS4_SYSV_ABI posix_pthread_mutexattr_getprotocol(PthreadMutexAttrT* mattr, PthreadMutexProt* protocol) { if (mattr == nullptr || *mattr == nullptr) { return POSIX_EINVAL; @@ -411,7 +411,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getprotocol(PthreadMutexAttrT* mattr, return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(PthreadMutexAttrT* mattr, +s32 PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(PthreadMutexAttrT* mattr, PthreadMutexProt protocol) { if (mattr == nullptr || *mattr == nullptr || (protocol < PthreadMutexProt::None) || (protocol > PthreadMutexProt::Protect)) { @@ -422,6 +422,15 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(PthreadMutexAttrT* mattr, return 0; } +s32 PS4_SYSV_ABI posix_pthread_mutexattr_setpshared(PthreadMutexAttrT* attr, s32 pshared) { + constexpr s32 POSIX_PTHREAD_PROCESS_PRIVATE = 0; + constexpr s32 POSIX_PTHREAD_PROCESS_SHARED = 1; + if (!attr || !*attr || pshared != POSIX_PTHREAD_PROCESS_PRIVATE) { + return POSIX_EINVAL; + } + return ORBIS_OK; +} + void RegisterMutex(Core::Loader::SymbolsResolver* sym) { // Posix LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", posix_pthread_mutex_init); @@ -434,6 +443,7 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("5txKfcMUAok", "libScePosix", 1, "libkernel", posix_pthread_mutexattr_setprotocol); LIB_FUNCTION("HF7lK46xzjY", "libScePosix", 1, "libkernel", posix_pthread_mutexattr_destroy); LIB_FUNCTION("K-jXhbt2gn4", "libScePosix", 1, "libkernel", posix_pthread_mutex_trylock); + LIB_FUNCTION("EXv3ztGqtDM", "libScePosix", 1, "libkernel", posix_pthread_mutexattr_setpshared); // Posix-Kernel LIB_FUNCTION("ttHNfU+qDBU", "libkernel", 1, "libkernel", posix_pthread_mutex_init); @@ -444,6 +454,7 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("mDmgMOGVUqg", "libkernel", 1, "libkernel", posix_pthread_mutexattr_settype); LIB_FUNCTION("HF7lK46xzjY", "libkernel", 1, "libkernel", posix_pthread_mutexattr_destroy); LIB_FUNCTION("K-jXhbt2gn4", "libkernel", 1, "libkernel", posix_pthread_mutex_trylock); + LIB_FUNCTION("EXv3ztGqtDM", "libkernel", 1, "libkernel", posix_pthread_mutexattr_setpshared); // Orbis LIB_FUNCTION("cmo1RIYva9o", "libkernel", 1, "libkernel", ORBIS(scePthreadMutexInit)); diff --git a/src/core/libraries/kernel/threads/pthread_attr.cpp b/src/core/libraries/kernel/threads/pthread_attr.cpp index aa5781f4f..2f3f26049 100644 --- a/src/core/libraries/kernel/threads/pthread_attr.cpp +++ b/src/core/libraries/kernel/threads/pthread_attr.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 "core/libraries/kernel/kernel.h" @@ -291,7 +291,7 @@ void RegisterThreadAttr(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("2Q0z6rnBrTE", "libScePosix", 1, "libkernel", posix_pthread_attr_setstacksize); LIB_FUNCTION("Ucsu-OK+els", "libScePosix", 1, "libkernel", posix_pthread_attr_get_np); LIB_FUNCTION("RtLRV-pBTTY", "libScePosix", 1, "libkernel", posix_pthread_attr_getschedpolicy); - LIB_FUNCTION("JarMIy8kKEY", "libkernel", 1, "libkernel", posix_pthread_attr_setschedpolicy); + LIB_FUNCTION("JarMIy8kKEY", "libScePosix", 1, "libkernel", posix_pthread_attr_setschedpolicy); LIB_FUNCTION("E+tyo3lp5Lw", "libScePosix", 1, "libkernel", posix_pthread_attr_setdetachstate); LIB_FUNCTION("zHchY8ft5pk", "libScePosix", 1, "libkernel", posix_pthread_attr_destroy); LIB_FUNCTION("euKRgm0Vn2M", "libScePosix", 1, "libkernel", posix_pthread_attr_setschedparam); diff --git a/src/core/libraries/kernel/time.cpp b/src/core/libraries/kernel/time.cpp index 51f86e2c7..3e1648b98 100644 --- a/src/core/libraries/kernel/time.cpp +++ b/src/core/libraries/kernel/time.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 @@ -548,6 +548,8 @@ void RegisterTime(Core::Loader::SymbolsResolver* sym) { initial_ptc = clock->GetUptime(); // POSIX + LIB_FUNCTION("NhpspxdjEKU", "libkernel", 1, "libkernel", posix_nanosleep); + LIB_FUNCTION("NhpspxdjEKU", "libScePosix", 1, "libkernel", posix_nanosleep); LIB_FUNCTION("yS8U2TGCe1A", "libkernel", 1, "libkernel", posix_nanosleep); LIB_FUNCTION("yS8U2TGCe1A", "libScePosix", 1, "libkernel", posix_nanosleep); LIB_FUNCTION("QcteRwbsnV0", "libkernel", 1, "libkernel", posix_usleep); From fb5c36fa115ab3127a56c7f2577b48b0fa1a4d78 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:56:54 +0100 Subject: [PATCH 15/19] Implement guest signal handlers (#4064) * Change thread pausing to use SIGTRMIN on UNIX * Allow handling of the rest of the signals * Add orbis-native signal number conversion and fix a few bugs * ifdefing away the issues * add check for mac for the signal that's used for thread pausing there * Add a few more registers * Don't break HLE memory tracking Now, if a guest app installs a handler for SIGSEGV/SIGBUS/SIGILL, that'll be handled by keeping the original signal handler, and if we can't handle the signal ourselves (as in it didn't come from HLE memory tracking), we pass it on to the guest * copyright 2026 * + --- src/core/debug_state.cpp | 5 +- .../libraries/kernel/threads/exception.cpp | 234 +++++++++++++++--- src/core/libraries/kernel/threads/exception.h | 37 ++- src/core/signals.cpp | 37 ++- src/core/signals.h | 14 +- 5 files changed, 283 insertions(+), 44 deletions(-) diff --git a/src/core/debug_state.cpp b/src/core/debug_state.cpp index f898117ec..3cc6010b5 100644 --- a/src/core/debug_state.cpp +++ b/src/core/debug_state.cpp @@ -6,6 +6,7 @@ #include "common/assert.h" #include "common/native_clock.h" #include "common/singleton.h" +#include "core/signals.h" #include "debug_state.h" #include "devtools/widget/common.h" #include "libraries/kernel/time.h" @@ -33,7 +34,7 @@ static void PauseThread(ThreadID id) { SuspendThread(handle); CloseHandle(handle); #else - pthread_kill(id, SIGUSR1); + pthread_kill(id, SIGSLEEP); #endif } @@ -43,7 +44,7 @@ static void ResumeThread(ThreadID id) { ResumeThread(handle); CloseHandle(handle); #else - pthread_kill(id, SIGUSR1); + pthread_kill(id, SIGSLEEP); #endif } diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 95ced79c0..dfb3b05f4 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.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/assert.h" @@ -6,6 +6,7 @@ #include "core/libraries/kernel/threads/exception.h" #include "core/libraries/kernel/threads/pthread.h" #include "core/libraries/libs.h" +#include "core/signals.h" #ifdef _WIN64 #include "common/ntapi.h" @@ -15,11 +16,156 @@ namespace Libraries::Kernel { -static std::array Handlers{}; +#ifdef _WIN32 + +// Windows doesn't have native versions of these, and we don't need to use them either. +static s32 NativeToOrbisSignal(s32 s) { + return s; +} + +static s32 OrbisToNativeSignal(s32 s) { + return s; +} + +#else + +static s32 NativeToOrbisSignal(s32 s) { + switch (s) { + case SIGHUP: + return POSIX_SIGHUP; + case SIGINT: + return POSIX_SIGINT; + case SIGQUIT: + return POSIX_SIGQUIT; + case SIGILL: + return POSIX_SIGILL; + case SIGTRAP: + return POSIX_SIGTRAP; + case SIGABRT: + return POSIX_SIGABRT; + case SIGFPE: + return POSIX_SIGFPE; + case SIGKILL: + return POSIX_SIGKILL; + case SIGBUS: + return POSIX_SIGBUS; + case SIGSEGV: + return POSIX_SIGSEGV; + case SIGSYS: + return POSIX_SIGSYS; + case SIGPIPE: + return POSIX_SIGPIPE; + case SIGALRM: + return POSIX_SIGALRM; + case SIGTERM: + return POSIX_SIGTERM; + case SIGURG: + return POSIX_SIGURG; + case SIGSTOP: + return POSIX_SIGSTOP; + case SIGTSTP: + return POSIX_SIGTSTP; + case SIGCONT: + return POSIX_SIGCONT; + case SIGCHLD: + return POSIX_SIGCHLD; + case SIGTTIN: + return POSIX_SIGTTIN; + case SIGTTOU: + return POSIX_SIGTTOU; + case SIGIO: + return POSIX_SIGIO; + case SIGXCPU: + return POSIX_SIGXCPU; + case SIGXFSZ: + return POSIX_SIGXFSZ; + case SIGVTALRM: + return POSIX_SIGVTALRM; + case SIGPROF: + return POSIX_SIGPROF; + case SIGWINCH: + return POSIX_SIGWINCH; + case SIGUSR1: + return POSIX_SIGUSR1; + case SIGUSR2: + return POSIX_SIGUSR2; + default: + UNREACHABLE_MSG("Unknown signal {}", s); + } +} + +static s32 OrbisToNativeSignal(s32 s) { + switch (s) { + case POSIX_SIGHUP: + return SIGHUP; + case POSIX_SIGINT: + return SIGINT; + case POSIX_SIGQUIT: + return SIGQUIT; + case POSIX_SIGILL: + return SIGILL; + case POSIX_SIGTRAP: + return SIGTRAP; + case POSIX_SIGABRT: + return SIGABRT; + case POSIX_SIGFPE: + return SIGFPE; + case POSIX_SIGKILL: + return SIGKILL; + case POSIX_SIGBUS: + return SIGBUS; + case POSIX_SIGSEGV: + return SIGSEGV; + case POSIX_SIGSYS: + return SIGSYS; + case POSIX_SIGPIPE: + return SIGPIPE; + case POSIX_SIGALRM: + return SIGALRM; + case POSIX_SIGTERM: + return SIGTERM; + case POSIX_SIGURG: + return SIGURG; + case POSIX_SIGSTOP: + return SIGSTOP; + case POSIX_SIGTSTP: + return SIGTSTP; + case POSIX_SIGCONT: + return SIGCONT; + case POSIX_SIGCHLD: + return SIGCHLD; + case POSIX_SIGTTIN: + return SIGTTIN; + case POSIX_SIGTTOU: + return SIGTTOU; + case POSIX_SIGIO: + return SIGIO; + case POSIX_SIGXCPU: + return SIGXCPU; + case POSIX_SIGXFSZ: + return SIGXFSZ; + case POSIX_SIGVTALRM: + return SIGVTALRM; + case POSIX_SIGPROF: + return SIGPROF; + case POSIX_SIGWINCH: + return SIGWINCH; + case POSIX_SIGUSR1: + return SIGUSR1; + case POSIX_SIGUSR2: + return SIGUSR2; + default: + UNREACHABLE_MSG("Unknown signal {}", s); + } +} + +#endif + +std::array Handlers{}; #ifndef _WIN64 -void SigactionHandler(int signum, siginfo_t* inf, ucontext_t* raw_context) { - const auto handler = Handlers[POSIX_SIGUSR1]; +void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context) { + const auto handler = Handlers[native_signum]; if (handler) { auto ctx = Ucontext{}; #ifdef __APPLE__ @@ -42,6 +188,8 @@ void SigactionHandler(int signum, siginfo_t* inf, ucontext_t* raw_context) { ctx.uc_mcontext.mc_rsp = regs.__rsp; ctx.uc_mcontext.mc_fs = regs.__fs; ctx.uc_mcontext.mc_gs = regs.__gs; + ctx.uc_mcontext.mc_gs = regs.__rip; + ctx.uc_mcontext.mc_addr = reinterpret_cast(inf->si_addr); #else const auto& regs = raw_context->uc_mcontext.gregs; ctx.uc_mcontext.mc_r8 = regs[REG_R8]; @@ -62,15 +210,18 @@ void SigactionHandler(int signum, siginfo_t* inf, ucontext_t* raw_context) { ctx.uc_mcontext.mc_rsp = regs[REG_RSP]; ctx.uc_mcontext.mc_fs = (regs[REG_CSGSFS] >> 32) & 0xFFFF; ctx.uc_mcontext.mc_gs = (regs[REG_CSGSFS] >> 16) & 0xFFFF; + ctx.uc_mcontext.mc_rip = (regs[REG_RIP]); + ctx.uc_mcontext.mc_addr = reinterpret_cast(inf->si_addr); #endif - handler(POSIX_SIGUSR1, &ctx); + handler(NativeToOrbisSignal(native_signum), &ctx); } } #else void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) { const char* thrName = (char*)arg1; + int native_signum = reinterpret_cast(arg2); LOG_INFO(Lib_Kernel, "Exception raised successfully on thread '{}'", thrName); - const auto handler = Handlers[POSIX_SIGUSR1]; + const auto handler = Handlers[native_signum]; if (handler) { auto ctx = Ucontext{}; ctx.uc_mcontext.mc_r8 = context->R8; @@ -91,49 +242,71 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) { ctx.uc_mcontext.mc_rsp = context->Rsp; ctx.uc_mcontext.mc_fs = context->SegFs; ctx.uc_mcontext.mc_gs = context->SegGs; - handler(POSIX_SIGUSR1, &ctx); + handler(NativeToOrbisSignal(native_signum), &ctx); } } #endif int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, SceKernelExceptionHandler handler) { - if (signum != POSIX_SIGUSR1) { - LOG_ERROR(Lib_Kernel, "Installing non-supported exception handler for signal {}", signum); - return 0; + if (signum > POSIX_SIGUSR2) { + return ORBIS_KERNEL_ERROR_EINVAL; } - ASSERT_MSG(!Handlers[POSIX_SIGUSR1], "Invalid parameters"); - Handlers[POSIX_SIGUSR1] = handler; + LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum); + int const native_signum = OrbisToNativeSignal(signum); +#ifdef __APPLE__ + ASSERT_MSG(native_signum != SIGVTALRM, "SIGVTALRM is HLE-reserved on macOS!"); +#endif + ASSERT_MSG(!Handlers[native_signum], "Invalid parameters"); + Handlers[native_signum] = handler; #ifndef _WIN64 + if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) { + return ORBIS_OK; // These are handled in Core::SignalHandler + } struct sigaction act = {}; act.sa_flags = SA_SIGINFO | SA_RESTART; act.sa_sigaction = reinterpret_cast(SigactionHandler); - sigaction(SIGUSR2, &act, nullptr); + sigemptyset(&act.sa_mask); + sigaction(native_signum, &act, nullptr); #endif - return 0; + return ORBIS_OK; } int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) { - if (signum != POSIX_SIGUSR1) { - LOG_ERROR(Lib_Kernel, "Installing non-supported exception handler for signal {}", signum); - return 0; + if (signum > POSIX_SIGUSR2) { + return ORBIS_KERNEL_ERROR_EINVAL; } - ASSERT_MSG(Handlers[POSIX_SIGUSR1], "Invalid parameters"); - Handlers[POSIX_SIGUSR1] = nullptr; + int const native_signum = OrbisToNativeSignal(signum); + ASSERT_MSG(Handlers[native_signum], "Invalid parameters"); + Handlers[native_signum] = nullptr; #ifndef _WIN64 - struct sigaction act = {}; - act.sa_flags = SA_SIGINFO | SA_RESTART; - act.sa_sigaction = nullptr; - sigaction(SIGUSR2, &act, nullptr); + if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) { + struct sigaction action{}; + action.sa_sigaction = Core::SignalHandler; + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&action.sa_mask); + + ASSERT_MSG(sigaction(native_signum, &action, nullptr) == 0, + "Failed to reinstate original signal handler for signal {}", native_signum); + } else { + struct sigaction act = {}; + act.sa_flags = SA_SIGINFO | SA_RESTART; + act.sa_sigaction = nullptr; + sigemptyset(&act.sa_mask); + sigaction(native_signum, &act, nullptr); + } #endif - return 0; + return ORBIS_OK; } int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { + if (signum != POSIX_SIGUSR1) { + return ORBIS_KERNEL_ERROR_EINVAL; + } LOG_WARNING(Lib_Kernel, "Raising exception on thread '{}'", thread->name); - ASSERT_MSG(signum == POSIX_SIGUSR1, "Attempting to raise non user defined signal!"); + int const native_signum = OrbisToNativeSignal(signum); #ifndef _WIN64 const auto pthr = reinterpret_cast(thread->native_thr.GetHandle()); - const auto ret = pthread_kill(pthr, SIGUSR2); + const auto ret = pthread_kill(pthr, native_signum); if (ret != 0) { LOG_ERROR(Kernel, "Failed to send exception signal to thread '{}': {}", thread->name, strerror(ret)); @@ -143,10 +316,11 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { option.UserApcFlags = QueueUserApcFlagsSpecialUserApc; u64 res = NtQueueApcThreadEx(reinterpret_cast(thread->native_thr.GetHandle()), option, - ExceptionHandler, (void*)thread->name.c_str(), nullptr, nullptr); + ExceptionHandler, (void*)thread->name.c_str(), + (void*)native_signum, nullptr); ASSERT(res == 0); #endif - return 0; + return ORBIS_OK; } s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) { @@ -154,7 +328,7 @@ s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) { return ORBIS_KERNEL_ERROR_EINVAL; } UNREACHABLE_MSG("error {:#x}", error); - return 0; + return ORBIS_OK; } s32 PS4_SYSV_ABI sceKernelDebugRaiseExceptionOnReleaseMode(s32 error, s64 unk) { @@ -162,7 +336,7 @@ s32 PS4_SYSV_ABI sceKernelDebugRaiseExceptionOnReleaseMode(s32 error, s64 unk) { return ORBIS_KERNEL_ERROR_EINVAL; } UNREACHABLE_MSG("error {:#x}", error); - return 0; + return ORBIS_OK; } void RegisterException(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/kernel/threads/exception.h b/src/core/libraries/kernel/threads/exception.h index 6a851e9d8..42c92ab2e 100644 --- a/src/core/libraries/kernel/threads/exception.h +++ b/src/core/libraries/kernel/threads/exception.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 @@ -13,8 +13,39 @@ namespace Libraries::Kernel { using SceKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*); -constexpr int POSIX_SIGSEGV = 11; -constexpr int POSIX_SIGUSR1 = 30; +constexpr s32 POSIX_SIGHUP = 1; +constexpr s32 POSIX_SIGINT = 2; +constexpr s32 POSIX_SIGQUIT = 3; +constexpr s32 POSIX_SIGILL = 4; +constexpr s32 POSIX_SIGTRAP = 5; +constexpr s32 POSIX_SIGABRT = 6; +constexpr s32 POSIX_SIGEMT = 7; +constexpr s32 POSIX_SIGFPE = 8; +constexpr s32 POSIX_SIGKILL = 9; +constexpr s32 POSIX_SIGBUS = 10; +constexpr s32 POSIX_SIGSEGV = 11; +constexpr s32 POSIX_SIGSYS = 12; +constexpr s32 POSIX_SIGPIPE = 13; +constexpr s32 POSIX_SIGALRM = 14; +constexpr s32 POSIX_SIGTERM = 15; +constexpr s32 POSIX_SIGURG = 16; +constexpr s32 POSIX_SIGSTOP = 17; +constexpr s32 POSIX_SIGTSTP = 18; +constexpr s32 POSIX_SIGCONT = 19; +constexpr s32 POSIX_SIGCHLD = 20; +constexpr s32 POSIX_SIGTTIN = 21; +constexpr s32 POSIX_SIGTTOU = 22; +constexpr s32 POSIX_SIGIO = 23; +constexpr s32 POSIX_SIGXCPU = 24; +constexpr s32 POSIX_SIGXFSZ = 25; +constexpr s32 POSIX_SIGVTALRM = 26; +constexpr s32 POSIX_SIGPROF = 27; +constexpr s32 POSIX_SIGWINCH = 28; +constexpr s32 POSIX_SIGINFO = 29; +constexpr s32 POSIX_SIGUSR1 = 30; +constexpr s32 POSIX_SIGUSR2 = 31; +constexpr s32 POSIX_SIGTHR = 32; +constexpr s32 POSIX_SIGLIBRT = 33; struct Mcontext { u64 mc_onstack; diff --git a/src/core/signals.cpp b/src/core/signals.cpp index 8df4edea8..70b431d39 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/decoder.h" #include "common/signal_context.h" +#include "core/libraries/kernel/threads/exception.h" #include "core/signals.h" #ifdef _WIN32 @@ -17,6 +18,13 @@ #endif #endif +#ifndef _WIN32 +namespace Libraries::Kernel { +void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context); +extern std::array Handlers; +} // namespace Libraries::Kernel +#endif + namespace Core { #if defined(_WIN32) @@ -66,7 +74,7 @@ static std::string DisassembleInstruction(void* code_address) { return buffer; } -static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { +void SignalHandler(int sig, siginfo_t* info, void* raw_context) { const auto* signals = Signals::Instance(); auto* code_address = Common::GetRip(raw_context); @@ -76,6 +84,13 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { case SIGBUS: { const bool is_write = Common::IsWriteError(raw_context); if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) { + // If the guest has installed a custom signal handler, and the access violation didn't + // come from HLE memory tracking, pass the signal on + if (Libraries::Kernel::Handlers[sig]) { + Libraries::Kernel::SigactionHandler(sig, info, + reinterpret_cast(raw_context)); + return; + } UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}", fmt::ptr(code_address), is_write ? "Write to" : "Read from", fmt::ptr(info->si_addr)); @@ -84,17 +99,23 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { } case SIGILL: if (!signals->DispatchIllegalInstruction(raw_context)) { + if (Libraries::Kernel::Handlers[sig]) { + Libraries::Kernel::SigactionHandler(sig, info, + reinterpret_cast(raw_context)); + return; + } UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}", fmt::ptr(code_address), DisassembleInstruction(code_address)); } break; - case SIGUSR1: { // Sleep thread until signal is received - sigset_t sigset; - sigemptyset(&sigset); - sigaddset(&sigset, SIGUSR1); - sigwait(&sigset, &sig); - } break; default: + if (sig == SIGSLEEP) { + // Sleep thread until signal is received again + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGSLEEP); + sigwait(&sigset, &sig); + } break; } } @@ -116,7 +137,7 @@ SignalDispatch::SignalDispatch() { "Failed to register access violation signal handler."); ASSERT_MSG(sigaction(SIGILL, &action, nullptr) == 0, "Failed to register illegal instruction signal handler."); - ASSERT_MSG(sigaction(SIGUSR1, &action, nullptr) == 0, + ASSERT_MSG(sigaction(SIGSLEEP, &action, nullptr) == 0, "Failed to register sleep signal handler."); #endif } diff --git a/src/core/signals.h b/src/core/signals.h index 0409b73ae..90801debb 100644 --- a/src/core/signals.h +++ b/src/core/signals.h @@ -1,17 +1,29 @@ -// 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 #include +#include #include "common/singleton.h" #include "common/types.h" +#ifdef _WIN32 +#define SIGSLEEP -1 +#elif defined(__APPLE__) +#define SIGSLEEP SIGVTALRM +#else +#define SIGSLEEP SIGRTMAX +#endif namespace Core { using AccessViolationHandler = bool (*)(void* context, void* fault_address); using IllegalInstructionHandler = bool (*)(void* context); +#ifndef _WIN32 +void SignalHandler(int sig, siginfo_t* info, void* raw_context); +#endif + /// Receives OS signals and dispatches to the appropriate handlers. class SignalDispatch { public: From 607d704707799abbe38cf22368c6a63399069348 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:10:55 +0100 Subject: [PATCH 16/19] Mount /data to /data instead of /data/gameid (#4066) --- src/emulator.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index 5d3a652b8..27def3565 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -341,11 +341,8 @@ void Emulator::Run(std::filesystem::path file, std::vector args, g_window = window.get(); - const auto& mount_data_dir = Common::FS::GetUserPath(Common::FS::PathType::GameDataDir) / id; - if (!std::filesystem::exists(mount_data_dir)) { - std::filesystem::create_directory(mount_data_dir); - } - mnt->Mount(mount_data_dir, "/data"); // should just exist, manually create with game serial + const auto& mount_data_dir = Common::FS::GetUserPath(Common::FS::PathType::GameDataDir); + mnt->Mount(mount_data_dir, "/data"); // Mounting temp folders const auto& mount_temp_dir = Common::FS::GetUserPath(Common::FS::PathType::TempDataDir) / id; From 6cbab8774532a2a4b0b0bf967e5417f7809f6b3e Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:41:05 -0600 Subject: [PATCH 17/19] Kernel.Event: Implement kqueue and kevent (#4065) * Remove dead code from EqueueInternal::WaitForEvents No longer necessary now that we avoid using small timers when falling back on equeue logic. * Refactor type names Might as well * Properly define OrbisKernelEqueue as a handle Most of the functions using an "OrbisKernelEqueue" call directly into kevent. Therefore, OrbisKernelEqueue should be a equeue handle. * Clang * Widen OrbisKernelEqueue type On real hardware, it's some value that contains the handle, as opposed to just the handle itself. * kqueue implementation The easy part * Hardware-accurate timer data Needed to make kevent simpler for these uses. * Move callback scheduling to EqueueInternal::AddEvent kevent would become excessively bloated if I needed to deal with that in there. * posix_kevent kevent is a bit of a pain, for now I've implemented as much as libkernel actually uses for it's wrappers, and left error logs to skip behavior when necessary. * Log calls * Apple, why are you calling fstat on an equeue? --- src/core/file_sys/fs.h | 3 +- src/core/libraries/gnmdriver/gnmdriver.cpp | 30 +- src/core/libraries/gnmdriver/gnmdriver.h | 6 +- src/core/libraries/kernel/equeue.cpp | 427 ++++++++++++++------- src/core/libraries/kernel/equeue.h | 30 +- src/core/libraries/kernel/file_system.cpp | 3 +- src/core/libraries/videoout/driver.cpp | 4 +- src/core/libraries/videoout/driver.h | 4 +- src/core/libraries/videoout/video_out.cpp | 56 +-- src/core/libraries/videoout/video_out.h | 8 +- 10 files changed, 370 insertions(+), 201 deletions(-) diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 6fc6c570f..0522c3d8a 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -85,7 +85,8 @@ enum class FileType { Device, Socket, Epoll, - Resolver + Resolver, + Equeue }; struct File { diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 25682ada0..326dc2418 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -123,21 +123,23 @@ static inline bool IsValidEventType(Platform::InterruptId id) { static_cast(id) == static_cast(Platform::InterruptId::GfxEop); } -s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata) { +s32 PS4_SYSV_ABI sceGnmAddEqEvent(OrbisKernelEqueue eq, u64 id, void* udata) { LOG_TRACE(Lib_GnmDriver, "called"); - if (!eq) { + auto equeue = GetEqueue(eq); + if (!equeue) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent kernel_event{}; kernel_event.event.ident = id; - kernel_event.event.filter = SceKernelEvent::Filter::GraphicsCore; - kernel_event.event.flags = SceKernelEvent::Flags::Add; + kernel_event.event.filter = OrbisKernelEvent::Filter::GraphicsCore; + kernel_event.event.flags = OrbisKernelEvent::Flags::Add; kernel_event.event.fflags = 0; kernel_event.event.data = id; kernel_event.event.udata = udata; - eq->AddEvent(kernel_event); + + equeue->AddEvent(kernel_event); Platform::IrqC::Instance()->Register( static_cast(id), @@ -149,10 +151,11 @@ s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata) { return; // Event data is expected to be an event type as per sceGnmGetEqEventType. - eq->TriggerEvent(static_cast(id), SceKernelEvent::Filter::GraphicsCore, - reinterpret_cast(id)); + equeue->TriggerEvent(static_cast(id), + OrbisKernelEvent::Filter::GraphicsCore, + reinterpret_cast(id)); }, - eq); + equeue); return ORBIS_OK; } @@ -267,16 +270,17 @@ int PS4_SYSV_ABI sceGnmDebugHardwareStatus() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(SceKernelEqueue eq, u64 id) { +s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(OrbisKernelEqueue eq, u64 id) { LOG_TRACE(Lib_GnmDriver, "called"); - if (!eq) { + auto equeue = GetEqueue(eq); + if (!equeue) { return ORBIS_KERNEL_ERROR_EBADF; } - eq->RemoveEvent(id, SceKernelEvent::Filter::GraphicsCore); + equeue->RemoveEvent(id, OrbisKernelEvent::Filter::GraphicsCore); - Platform::IrqC::Instance()->Unregister(static_cast(id), eq); + Platform::IrqC::Instance()->Unregister(static_cast(id), equeue); return ORBIS_OK; } @@ -895,7 +899,7 @@ int PS4_SYSV_ABI sceGnmGetDebugTimestamp() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmGetEqEventType(const SceKernelEvent* ev) { +int PS4_SYSV_ABI sceGnmGetEqEventType(const OrbisKernelEvent* ev) { LOG_TRACE(Lib_GnmDriver, "called"); return sceKernelGetEventData(ev); } diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 5f3462dd9..9f5fde628 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -14,7 +14,7 @@ namespace Libraries::GnmDriver { using namespace Kernel; -s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata); +s32 PS4_SYSV_ABI sceGnmAddEqEvent(OrbisKernelEqueue eq, u64 id, void* udata); int PS4_SYSV_ABI sceGnmAreSubmitsAllowed(); int PS4_SYSV_ABI sceGnmBeginWorkload(u32 workload_stream, u64* workload); s32 PS4_SYSV_ABI sceGnmComputeWaitOnAddress(u32* cmdbuf, u32 size, uintptr_t addr, u32 mask, @@ -31,7 +31,7 @@ int PS4_SYSV_ABI sceGnmDebuggerSetAddressWatch(); int PS4_SYSV_ABI sceGnmDebuggerWriteGds(); int PS4_SYSV_ABI sceGnmDebuggerWriteSqIndirectRegister(); int PS4_SYSV_ABI sceGnmDebugHardwareStatus(); -s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(SceKernelEqueue eq, u64 id); +s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(OrbisKernelEqueue eq, u64 id); int PS4_SYSV_ABI sceGnmDestroyWorkloadStream(); void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw); void PS4_SYSV_ABI sceGnmDingDongForWorkload(u32 gnm_vqid, u32 next_offs_dw, u64 workload_id); @@ -87,7 +87,7 @@ int PS4_SYSV_ABI sceGnmGetCoredumpMode(); int PS4_SYSV_ABI sceGnmGetCoredumpProtectionFaultTimestamp(); int PS4_SYSV_ABI sceGnmGetDbgGcHandle(); int PS4_SYSV_ABI sceGnmGetDebugTimestamp(); -int PS4_SYSV_ABI sceGnmGetEqEventType(const SceKernelEvent* ev); +int PS4_SYSV_ABI sceGnmGetEqEventType(const OrbisKernelEvent* ev); int PS4_SYSV_ABI sceGnmGetEqTimeStamp(); int PS4_SYSV_ABI sceGnmGetGpuBlockStatus(); u32 PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency(); diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 9291c0a1a..68a6dae24 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -2,12 +2,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/assert.h" #include "common/debug.h" #include "common/logging/log.h" +#include "common/singleton.h" +#include "core/file_sys/fs.h" #include "core/libraries/kernel/equeue.h" +#include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/posix_error.h" #include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" @@ -16,59 +21,104 @@ namespace Libraries::Kernel { extern boost::asio::io_context io_context; extern void KernelSignalRequest(); +static std::unordered_map kqueues; static constexpr auto HrTimerSpinlockThresholdNs = 1200000u; +EqueueInternal* GetEqueue(OrbisKernelEqueue eq) { + if (!kqueues.contains(eq)) { + return nullptr; + } + return kqueues[eq]; +} + +static void HrTimerCallback(OrbisKernelEqueue eq, const OrbisKernelEvent& kevent) { + if (kqueues.contains(eq)) { + kqueues[eq]->TriggerEvent(kevent.ident, OrbisKernelEvent::Filter::HrTimer, kevent.udata); + } +} + +static void TimerCallback(OrbisKernelEqueue eq, const OrbisKernelEvent& kevent) { + if (kqueues.contains(eq) && kqueues[eq]->EventExists(kevent.ident, kevent.filter)) { + kqueues[eq]->TriggerEvent(kevent.ident, OrbisKernelEvent::Filter::Timer, kevent.udata); + if (!(kevent.flags & OrbisKernelEvent::Flags::OneShot)) { + // Reschedule the event for its next period. + kqueues[eq]->ScheduleEvent(kevent.ident, kevent.filter, TimerCallback); + } + } +} + // Events are uniquely identified by id and filter. bool EqueueInternal::AddEvent(EqueueEvent& event) { - std::scoped_lock lock{m_mutex}; + { + std::scoped_lock lock{m_mutex}; - // Calculate timer interval - event.time_added = std::chrono::steady_clock::now(); - if (event.event.filter == SceKernelEvent::Filter::Timer || - event.event.filter == SceKernelEvent::Filter::HrTimer) { - // Set timer interval - event.timer_interval = std::chrono::nanoseconds(event.event.data); + // Calculate timer interval + event.time_added = std::chrono::steady_clock::now(); + if (event.event.filter == OrbisKernelEvent::Filter::Timer) { + // Set timer interval, this is stored in milliseconds for timers. + event.timer_interval = std::chrono::milliseconds(event.event.data); + } else if (event.event.filter == OrbisKernelEvent::Filter::HrTimer) { + // Retrieve inputted time, this is stored in the bintime format. + OrbisKernelBintime* time = reinterpret_cast(event.event.data); + + // Convert the bintime format to a timespec. + OrbisKernelTimespec ts; + ts.tv_sec = time->sec; + ts.tv_nsec = (1000000000 * (time->frac >> 32)) >> 32; + + // Then use the timespec to set the timer interval. + event.timer_interval = std::chrono::nanoseconds(ts.tv_nsec + ts.tv_sec * 1000000000); + } + + // First, check if there's already an event with the same id and filter. + u64 id = event.event.ident; + OrbisKernelEvent::Filter filter = event.event.filter; + const auto& find_it = std::ranges::find_if(m_events, [id, filter](auto& ev) { + return ev.event.ident == id && ev.event.filter == filter; + }); + // If there is a duplicate event, we need to update that instead. + if (find_it != m_events.cend()) { + // Specifically, update user data and timer_interval. + // Trigger status and event data should remain intact. + auto& old_event = *find_it; + old_event.timer_interval = event.timer_interval; + old_event.event.udata = event.event.udata; + return true; + } + + // Clear input data from event. + event.event.data = 0; + + // Remove add flag from event + event.event.flags &= ~OrbisKernelEvent::Flags::Add; + + // Clear flag is appended to most event types internally. + if (event.event.filter != OrbisKernelEvent::Filter::User) { + event.event.flags |= OrbisKernelEvent::Flags::Clear; + } + + const auto& it = std::ranges::find(m_events, event); + if (it != m_events.cend()) { + *it = std::move(event); + } else { + m_events.emplace_back(std::move(event)); + } } - // First, check if there's already an event with the same id and filter. - u64 id = event.event.ident; - SceKernelEvent::Filter filter = event.event.filter; - const auto& find_it = std::ranges::find_if(m_events, [id, filter](auto& ev) { - return ev.event.ident == id && ev.event.filter == filter; - }); - // If there is a duplicate event, we need to update that instead. - if (find_it != m_events.cend()) { - // Specifically, update user data and timer_interval. - // Trigger status and event data should remain intact. - auto& old_event = *find_it; - old_event.timer_interval = event.timer_interval; - old_event.event.udata = event.event.udata; - return true; - } - - // Clear input data from event. - event.event.data = 0; - - // Remove add flag from event - event.event.flags &= ~SceKernelEvent::Flags::Add; - - // Clear flag is appended to most event types internally. - if (event.event.filter != SceKernelEvent::Filter::User) { - event.event.flags |= SceKernelEvent::Flags::Clear; - } - - const auto& it = std::ranges::find(m_events, event); - if (it != m_events.cend()) { - *it = std::move(event); - } else { - m_events.emplace_back(std::move(event)); + // Schedule callbacks for timer events + if (event.event.filter == OrbisKernelEvent::Timer) { + return this->ScheduleEvent(event.event.ident, OrbisKernelEvent::Filter::Timer, + TimerCallback); + } else if (event.event.filter == OrbisKernelEvent::HrTimer) { + return this->ScheduleEvent(event.event.ident, OrbisKernelEvent::Filter::HrTimer, + HrTimerCallback); } return true; } bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, - void (*callback)(SceKernelEqueue, const SceKernelEvent&)) { + void (*callback)(OrbisKernelEqueue, const OrbisKernelEvent&)) { std::scoped_lock lock{m_mutex}; const auto& it = std::ranges::find_if(m_events, [id, filter](auto& ev) { @@ -79,8 +129,8 @@ bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, } const auto& event = *it; - ASSERT(event.event.filter == SceKernelEvent::Filter::Timer || - event.event.filter == SceKernelEvent::Filter::HrTimer); + ASSERT(event.event.filter == OrbisKernelEvent::Filter::Timer || + event.event.filter == OrbisKernelEvent::Filter::HrTimer); if (!it->timer) { it->timer = std::make_unique(io_context, event.timer_interval); @@ -101,7 +151,7 @@ bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, } return; } - callback(this, event_data); + callback(this->m_handle, event_data); }); KernelSignalRequest(); @@ -122,7 +172,7 @@ bool EqueueInternal::RemoveEvent(u64 id, s16 filter) { return has_found; } -int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, const SceKernelUseconds* timo) { +int EqueueInternal::WaitForEvents(OrbisKernelEvent* ev, int num, const OrbisKernelUseconds* timo) { if (timo != nullptr && *timo == 0) { // Effectively acts as a poll; only events that have already // arrived at the time of this function call can be received @@ -152,15 +202,6 @@ int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, const SceKernelUs m_cond.wait_for(lock, std::chrono::microseconds(micros), predicate); } - if (HasSmallTimer()) { - if (count > 0) { - const auto time_waited = std::chrono::duration_cast( - std::chrono::steady_clock::now() - m_events[0].time_added) - .count(); - count = WaitForSmallTimer(ev, num, std::max(0l, long(micros - time_waited))); - } - } - return count; } @@ -170,12 +211,12 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) { std::scoped_lock lock{m_mutex}; for (auto& event : m_events) { if (event.event.ident == ident && event.event.filter == filter) { - if (filter == SceKernelEvent::Filter::VideoOut) { + if (filter == OrbisKernelEvent::Filter::VideoOut) { event.TriggerDisplay(trigger_data); - } else if (filter == SceKernelEvent::Filter::User) { + } else if (filter == OrbisKernelEvent::Filter::User) { event.TriggerUser(trigger_data); - } else if (filter == SceKernelEvent::Filter::Timer || - filter == SceKernelEvent::Filter::HrTimer) { + } else if (filter == OrbisKernelEvent::Filter::Timer || + filter == OrbisKernelEvent::Filter::HrTimer) { event.TriggerTimer(); } else { event.Trigger(trigger_data); @@ -188,15 +229,15 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) { return has_found; } -int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) { +int EqueueInternal::GetTriggeredEvents(OrbisKernelEvent* ev, int num) { int count = 0; for (auto it = m_events.begin(); it != m_events.end();) { if (it->IsTriggered()) { ev[count++] = it->event; - if (it->event.flags & SceKernelEvent::Flags::Clear) { + if (it->event.flags & OrbisKernelEvent::Flags::Clear) { it->Clear(); } - if (it->event.flags & SceKernelEvent::Flags::OneShot) { + if (it->event.flags & OrbisKernelEvent::Flags::OneShot) { it = m_events.erase(it); } else { ++it; @@ -214,10 +255,17 @@ int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) { } bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) { + // Retrieve inputted time, this is stored in the bintime format + OrbisKernelBintime* time = reinterpret_cast(ev.event.data); + OrbisKernelTimespec ts; + ts.tv_sec = time->sec; + ts.tv_nsec = ((1000000000 * (time->frac >> 32)) >> 32); + + // Create the small timer SmallTimer st; st.event = ev.event; st.added = std::chrono::steady_clock::now(); - st.interval = std::chrono::nanoseconds{ev.event.data}; + st.interval = std::chrono::nanoseconds(ts.tv_nsec + ts.tv_sec * 1000000000); { std::scoped_lock lock{m_mutex}; m_small_timers[st.event.ident] = std::move(st); @@ -225,7 +273,7 @@ bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) { return true; } -int EqueueInternal::WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros) { +int EqueueInternal::WaitForSmallTimer(OrbisKernelEvent* ev, int num, u32 micros) { ASSERT(num >= 1); auto curr_clock = std::chrono::steady_clock::now(); @@ -266,18 +314,119 @@ bool EqueueInternal::EventExists(u64 id, s16 filter) { return it != m_events.cend(); } -int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name) { +s32 PS4_SYSV_ABI posix_kqueue() { + // Reserve a file handle for the kqueue + auto* handles = Common::Singleton::Instance(); + s32 kqueue_handle = handles->CreateHandle(); + auto* kqueue_file = handles->GetFile(kqueue_handle); + kqueue_file->type = Core::FileSys::FileType::Equeue; + + // Plenty of equeue logic uses names to identify queues. + // Create a unique name for the queue we create. + char name[32]; + memset(name, 0, sizeof(name)); + snprintf(name, sizeof(name), "kqueue%i", kqueue_handle); + + // Create the queue + kqueues[kqueue_handle] = new EqueueInternal(kqueue_handle, name); + LOG_INFO(Kernel_Event, "kqueue created with name {}", name); + + // Return handle. + return kqueue_handle; +} + +// Helper method to detect supported filters. +// We don't want to allow adding events we don't handle properly. +bool SupportedEqueueFilter(OrbisKernelEvent::Filter filter) { + return filter == OrbisKernelEvent::Filter::GraphicsCore || + filter == OrbisKernelEvent::Filter::HrTimer || + filter == OrbisKernelEvent::Filter::Timer || filter == OrbisKernelEvent::Filter::User || + filter == OrbisKernelEvent::Filter::VideoOut; +} + +s32 PS4_SYSV_ABI posix_kevent(s32 handle, OrbisKernelEvent* changelist, u64 nchanges, + OrbisKernelEvent* eventlist, u64 nevents, + OrbisKernelTimespec* timeout) { + LOG_INFO(Kernel_Event, "called, eq = {}, nchanges = {}, nevents = {}", handle, nchanges, + nevents); + + // Get the equeue + if (!kqueues.contains(handle)) { + *__Error() = POSIX_EBADF; + return ORBIS_FAIL; + } + auto equeue = kqueues[handle]; + + // First step is to apply all changes in changelist. + for (u64 i = 0; i < nchanges; i++) { + auto event = changelist[i]; + if (!SupportedEqueueFilter(event.filter)) { + LOG_ERROR(Kernel_Event, "Unsupported event filter {}", + magic_enum::enum_name(event.filter)); + continue; + } + + // Check the event flags to determine the appropriate action + if (event.flags & OrbisKernelEvent::Flags::Add) { + // The caller is requesting to add an event. + EqueueEvent internal_event{}; + internal_event.event = event; + if (!equeue->AddEvent(internal_event)) { + // Failed to add event, return error. + *__Error() = POSIX_ENOMEM; + return ORBIS_FAIL; + } + } + + if (event.flags & OrbisKernelEvent::Flags::Delete) { + // The caller is requesting to remove an event. + if (!equeue->RemoveEvent(event.ident, event.filter)) { + // Failed to remove event, return error. + *__Error() = POSIX_ENOENT; + return ORBIS_FAIL; + } + } + + if (event.filter == OrbisKernelEvent::Filter::User && event.fflags == 0x1000000) { + // For user events, this fflags value indicates we need to trigger the event. + if (!equeue->TriggerEvent(event.ident, OrbisKernelEvent::Filter::User, event.udata)) { + *__Error() = POSIX_ENOENT; + return ORBIS_FAIL; + } + } else if (event.fflags != 0) { + // The title is using filter-specific flags. Right now, these are unhandled. + LOG_ERROR(Kernel_Event, "Unhandled fflags {:#x} for event filter {}", event.fflags, + magic_enum::enum_name(event.filter)); + continue; + } + } + + // Now we need to wait on the event list. + s32 count = 0; + if (nevents > 0) { + if (timeout != nullptr) { + OrbisKernelUseconds micros = (timeout->tv_sec * 1000000) + (timeout->tv_nsec / 1000); + count = equeue->WaitForEvents(eventlist, nevents, µs); + } else { + count = equeue->WaitForEvents(eventlist, nevents, nullptr); + } + } + return count; +} + +int PS4_SYSV_ABI sceKernelCreateEqueue(OrbisKernelEqueue* eq, const char* name) { if (eq == nullptr) { LOG_ERROR(Kernel_Event, "Event queue is null!"); return ORBIS_KERNEL_ERROR_EINVAL; } + if (name == nullptr) { LOG_ERROR(Kernel_Event, "Event queue name is null!"); return ORBIS_KERNEL_ERROR_EINVAL; } // Maximum is 32 including null terminator - static constexpr size_t MaxEventQueueNameSize = 32; + static constexpr u64 MaxEventQueueNameSize = 32; if (std::strlen(name) > MaxEventQueueNameSize) { LOG_ERROR(Kernel_Event, "Event queue name exceeds 32 bytes!"); return ORBIS_KERNEL_ERROR_ENAMETOOLONG; @@ -285,29 +434,42 @@ int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name) { LOG_INFO(Kernel_Event, "name = {}", name); - *eq = new EqueueInternal(name); + // Reserve a file handle for the kqueue + auto* handles = Common::Singleton::Instance(); + OrbisKernelEqueue kqueue_handle = handles->CreateHandle(); + auto* kqueue_file = handles->GetFile(kqueue_handle); + kqueue_file->type = Core::FileSys::FileType::Equeue; + + // Create the equeue + kqueues[kqueue_handle] = new EqueueInternal(kqueue_handle, name); + *eq = kqueue_handle; + return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelDeleteEqueue(SceKernelEqueue eq) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelDeleteEqueue(OrbisKernelEqueue eq) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - delete eq; + auto* handles = Common::Singleton::Instance(); + handles->DeleteHandle(eq); + kqueues.erase(eq); return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int num, int* out, - SceKernelUseconds* timo) { +int PS4_SYSV_ABI sceKernelWaitEqueue(OrbisKernelEqueue eq, OrbisKernelEvent* ev, int num, int* out, + OrbisKernelUseconds* timo) { HLE_TRACE; - TRACE_HINT(eq->GetName()); - LOG_TRACE(Kernel_Event, "equeue = {} num = {}", eq->GetName(), num); - - if (eq == nullptr) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } + auto& equeue = kqueues[eq]; + + TRACE_HINT(equeue->GetName()); + LOG_TRACE(Kernel_Event, "equeue = {} num = {}", equeue->GetName(), num); + if (ev == nullptr) { return ORBIS_KERNEL_ERROR_EFAULT; } @@ -317,7 +479,7 @@ int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int return ORBIS_KERNEL_ERROR_EINVAL; } - *out = eq->WaitForEvents(ev, num, timo); + *out = equeue->WaitForEvents(ev, num, timo); if (*out == 0) { return ORBIS_KERNEL_ERROR_ETIMEDOUT; @@ -326,13 +488,9 @@ int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int return ORBIS_OK; } -static void HrTimerCallback(SceKernelEqueue eq, const SceKernelEvent& kevent) { - eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::HrTimer, kevent.udata); -} - -s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, OrbisKernelTimespec* ts, +s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(OrbisKernelEqueue eq, int id, OrbisKernelTimespec* ts, void* udata) { - if (eq == nullptr) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } @@ -340,10 +498,12 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, OrbisKerne EqueueEvent event{}; event.event.ident = id; - event.event.filter = SceKernelEvent::Filter::HrTimer; - event.event.flags = SceKernelEvent::Flags::Add | SceKernelEvent::Flags::OneShot; + event.event.filter = OrbisKernelEvent::Filter::HrTimer; + event.event.flags = OrbisKernelEvent::Flags::Add | OrbisKernelEvent::Flags::OneShot; event.event.fflags = 0; - event.event.data = total_ns; + // Data is stored as the address of a OrbisKernelBintime struct. + OrbisKernelBintime time{ts->tv_sec, ts->tv_nsec * 0x44b82fa09}; + event.event.data = reinterpret_cast(&time); event.event.udata = udata; // HR timers cannot be implemented within the existing event queue architecture due to the @@ -353,146 +513,137 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, OrbisKerne // `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is // large. Even for large delays, we truncate a small portion to complete the wait // using the spinlock, prioritizing precision. + auto& equeue = kqueues[eq]; if (total_ns < HrTimerSpinlockThresholdNs) { - return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; + return equeue->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } - if (!eq->AddEvent(event) || - !eq->ScheduleEvent(id, SceKernelEvent::Filter::HrTimer, HrTimerCallback)) { + if (!equeue->AddEvent(event)) { return ORBIS_KERNEL_ERROR_ENOMEM; } return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelDeleteHRTimerEvent(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelDeleteHRTimerEvent(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - if (eq->HasSmallTimer()) { - return eq->RemoveSmallTimer(id) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOENT; + auto& equeue = kqueues[eq]; + if (equeue->HasSmallTimer()) { + return equeue->RemoveSmallTimer(id) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOENT; } else { - return eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer) ? ORBIS_OK - : ORBIS_KERNEL_ERROR_ENOENT; + return equeue->RemoveEvent(id, OrbisKernelEvent::Filter::HrTimer) + ? ORBIS_OK + : ORBIS_KERNEL_ERROR_ENOENT; } } -static void TimerCallback(SceKernelEqueue eq, const SceKernelEvent& kevent) { - if (eq->EventExists(kevent.ident, kevent.filter)) { - eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::Timer, kevent.udata); - - if (!(kevent.flags & SceKernelEvent::Flags::OneShot)) { - // Reschedule the event for its next period. - eq->ScheduleEvent(kevent.ident, kevent.filter, TimerCallback); - } - } -} - -int PS4_SYSV_ABI sceKernelAddTimerEvent(SceKernelEqueue eq, int id, SceKernelUseconds usec, +int PS4_SYSV_ABI sceKernelAddTimerEvent(OrbisKernelEqueue eq, int id, OrbisKernelUseconds usec, void* udata) { - if (eq == nullptr) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent event{}; event.event.ident = static_cast(id); - event.event.filter = SceKernelEvent::Filter::Timer; - event.event.flags = SceKernelEvent::Flags::Add; + event.event.filter = OrbisKernelEvent::Filter::Timer; + event.event.flags = OrbisKernelEvent::Flags::Add; event.event.fflags = 0; - event.event.data = usec * 1000; + event.event.data = usec / 1000; event.event.udata = udata; - LOG_DEBUG(Kernel_Event, "Added timing event: queue name={}, queue id={}, usec={}, pointer={:x}", - eq->GetName(), event.event.ident, usec, reinterpret_cast(udata)); - - if (!eq->AddEvent(event) || - !eq->ScheduleEvent(id, SceKernelEvent::Filter::Timer, TimerCallback)) { + auto& equeue = kqueues[eq]; + if (!equeue->AddEvent(event)) { return ORBIS_KERNEL_ERROR_ENOMEM; } return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelDeleteTimerEvent(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelDeleteTimerEvent(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - return eq->RemoveEvent(id, SceKernelEvent::Filter::Timer) ? ORBIS_OK - : ORBIS_KERNEL_ERROR_ENOENT; + return kqueues[eq]->RemoveEvent(id, OrbisKernelEvent::Filter::Timer) + ? ORBIS_OK + : ORBIS_KERNEL_ERROR_ENOENT; } -int PS4_SYSV_ABI sceKernelAddUserEvent(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelAddUserEvent(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent event{}; event.event.ident = id; - event.event.filter = SceKernelEvent::Filter::User; + event.event.filter = OrbisKernelEvent::Filter::User; event.event.udata = 0; - event.event.flags = SceKernelEvent::Flags::Add; + event.event.flags = OrbisKernelEvent::Flags::Add; event.event.fflags = 0; event.event.data = 0; - return eq->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; + return kqueues[eq]->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } -int PS4_SYSV_ABI sceKernelAddUserEventEdge(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelAddUserEventEdge(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent event{}; event.event.ident = id; - event.event.filter = SceKernelEvent::Filter::User; + event.event.filter = OrbisKernelEvent::Filter::User; event.event.udata = 0; - event.event.flags = SceKernelEvent::Flags::Add | SceKernelEvent::Flags::Clear; + event.event.flags = OrbisKernelEvent::Flags::Add | OrbisKernelEvent::Flags::Clear; event.event.fflags = 0; event.event.data = 0; - return eq->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; + return kqueues[eq]->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } -void* PS4_SYSV_ABI sceKernelGetEventUserData(const SceKernelEvent* ev) { +void* PS4_SYSV_ABI sceKernelGetEventUserData(const OrbisKernelEvent* ev) { ASSERT(ev); return ev->udata; } -u64 PS4_SYSV_ABI sceKernelGetEventId(const SceKernelEvent* ev) { +u64 PS4_SYSV_ABI sceKernelGetEventId(const OrbisKernelEvent* ev) { return ev->ident; } -int PS4_SYSV_ABI sceKernelTriggerUserEvent(SceKernelEqueue eq, int id, void* udata) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelTriggerUserEvent(OrbisKernelEqueue eq, int id, void* udata) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - if (!eq->TriggerEvent(id, SceKernelEvent::Filter::User, udata)) { + if (!kqueues[eq]->TriggerEvent(id, OrbisKernelEvent::Filter::User, udata)) { return ORBIS_KERNEL_ERROR_ENOENT; } return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelDeleteUserEvent(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelDeleteUserEvent(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - if (!eq->RemoveEvent(id, SceKernelEvent::Filter::User)) { + if (!kqueues[eq]->RemoveEvent(id, OrbisKernelEvent::Filter::User)) { return ORBIS_KERNEL_ERROR_ENOENT; } return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelGetEventFilter(const SceKernelEvent* ev) { +int PS4_SYSV_ABI sceKernelGetEventFilter(const OrbisKernelEvent* ev) { return ev->filter; } -u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev) { +u64 PS4_SYSV_ABI sceKernelGetEventData(const OrbisKernelEvent* ev) { return ev->data; } void RegisterEventQueue(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("nh2IFMgKTv8", "libScePosix", 1, "libkernel", posix_kqueue); + LIB_FUNCTION("RW-GEfpnsqg", "libScePosix", 1, "libkernel", posix_kevent); LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", sceKernelCreateEqueue); LIB_FUNCTION("jpFjmgAC5AE", "libkernel", 1, "libkernel", sceKernelDeleteEqueue); LIB_FUNCTION("fzyMKs9kim0", "libkernel", 1, "libkernel", sceKernelWaitEqueue); diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 83b4b8689..9b3c73c19 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -22,10 +22,15 @@ namespace Libraries::Kernel { class EqueueInternal; struct EqueueEvent; -using SceKernelUseconds = u32; -using SceKernelEqueue = EqueueInternal*; +struct OrbisKernelBintime { + s64 sec; + s64 frac; +}; -struct SceKernelEvent { +using OrbisKernelUseconds = u32; +using OrbisKernelEqueue = s64; + +struct OrbisKernelEvent { enum Filter : s16 { None = 0, Read = -1, @@ -78,7 +83,7 @@ struct OrbisVideoOutEventData { }; struct EqueueEvent { - SceKernelEvent event; + OrbisKernelEvent event; void* data = nullptr; std::chrono::steady_clock::time_point time_added; std::chrono::nanoseconds timer_interval; @@ -137,13 +142,14 @@ private: class EqueueInternal { struct SmallTimer { - SceKernelEvent event; + OrbisKernelEvent event; std::chrono::steady_clock::time_point added; std::chrono::nanoseconds interval; }; public: - explicit EqueueInternal(std::string_view name) : m_name(name) {} + explicit EqueueInternal(OrbisKernelEqueue handle, std::string_view name) + : m_handle(handle), m_name(name) {} std::string_view GetName() const { return m_name; @@ -151,11 +157,11 @@ public: bool AddEvent(EqueueEvent& event); bool ScheduleEvent(u64 id, s16 filter, - void (*callback)(SceKernelEqueue, const SceKernelEvent&)); + void (*callback)(OrbisKernelEqueue, const OrbisKernelEvent&)); bool RemoveEvent(u64 id, s16 filter); - int WaitForEvents(SceKernelEvent* ev, int num, const SceKernelUseconds* timo); + int WaitForEvents(OrbisKernelEvent* ev, int num, const OrbisKernelUseconds* timo); bool TriggerEvent(u64 ident, s16 filter, void* trigger_data); - int GetTriggeredEvents(SceKernelEvent* ev, int num); + int GetTriggeredEvents(OrbisKernelEvent* ev, int num); bool AddSmallTimer(EqueueEvent& event); bool HasSmallTimer() { @@ -170,11 +176,12 @@ public: return false; } - int WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros); + int WaitForSmallTimer(OrbisKernelEvent* ev, int num, u32 micros); bool EventExists(u64 id, s16 filter); private: + OrbisKernelEqueue m_handle; std::string m_name; std::mutex m_mutex; std::vector m_events; @@ -182,7 +189,8 @@ private: std::unordered_map m_small_timers; }; -u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev); +EqueueInternal* GetEqueue(OrbisKernelEqueue eq); +u64 PS4_SYSV_ABI sceKernelGetEventData(const OrbisKernelEvent* ev); void RegisterEventQueue(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index bc4e2def6..184343801 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -792,7 +792,8 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) { return file->socket->fstat(sb); } case Core::FileSys::FileType::Epoll: - case Core::FileSys::FileType::Resolver: { + case Core::FileSys::FileType::Resolver: + case Core::FileSys::FileType::Equeue: { LOG_ERROR(Kernel_Fs, "(STUBBED) file type {}", magic_enum::enum_name(file->type.load())); break; } diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 2f44b1c99..bebbf9602 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -193,7 +193,7 @@ void VideoOutDriver::Flip(const Request& req) { if (event != nullptr) { event->TriggerEvent( static_cast(OrbisVideoOutInternalEventId::Flip), - Kernel::SceKernelEvent::Filter::VideoOut, + Kernel::OrbisKernelEvent::Filter::VideoOut, reinterpret_cast(static_cast(OrbisVideoOutInternalEventId::Flip) | (req.flip_arg << 16))); } @@ -320,7 +320,7 @@ void VideoOutDriver::PresentThread(std::stop_token token) { for (auto& event : main_port.vblank_events) { if (event != nullptr) { event->TriggerEvent(static_cast(OrbisVideoOutInternalEventId::Vblank), - Kernel::SceKernelEvent::Filter::VideoOut, + Kernel::OrbisKernelEvent::Filter::VideoOut, reinterpret_cast( static_cast(OrbisVideoOutInternalEventId::Vblank) | (vblank_status.count << 16))); diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index e0eace791..96bd58500 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -25,8 +25,8 @@ struct VideoOutPort { std::array groups; FlipStatus flip_status; SceVideoOutVblankStatus vblank_status; - std::vector flip_events; - std::vector vblank_events; + std::vector flip_events; + std::vector vblank_events; std::mutex vo_mutex; std::mutex port_mutex; std::condition_variable vo_cv; diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index da58772a0..1b8a6b59d 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -38,7 +38,7 @@ void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, Pixe attribute->option = SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE; } -s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata) { +s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::OrbisKernelEqueue eq, s32 handle, void* udata) { LOG_INFO(Lib_VideoOut, "handle = {}", handle); auto* port = driver->GetPort(handle); @@ -46,39 +46,41 @@ s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (eq == nullptr) { + auto equeue = Kernel::GetEqueue(eq); + if (equeue == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } Kernel::EqueueEvent event{}; event.event.ident = static_cast(OrbisVideoOutInternalEventId::Flip); - event.event.filter = Kernel::SceKernelEvent::Filter::VideoOut; - event.event.flags = Kernel::SceKernelEvent::Flags::Add; + event.event.filter = Kernel::OrbisKernelEvent::Filter::VideoOut; + event.event.flags = Kernel::OrbisKernelEvent::Flags::Add; event.event.udata = udata; event.event.fflags = 0; event.event.data = 0; event.data = port; - eq->AddEvent(event); + equeue->AddEvent(event); - port->flip_events.push_back(eq); + port->flip_events.push_back(equeue); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutDeleteFlipEvent(Kernel::SceKernelEqueue eq, s32 handle) { +s32 PS4_SYSV_ABI sceVideoOutDeleteFlipEvent(Kernel::OrbisKernelEqueue eq, s32 handle) { auto* port = driver->GetPort(handle); if (port == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (eq == nullptr) { + auto equeue = Kernel::GetEqueue(eq); + if (equeue == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } - eq->RemoveEvent(handle, Kernel::SceKernelEvent::Filter::VideoOut); - port->flip_events.erase(find(port->flip_events.begin(), port->flip_events.end(), eq)); + equeue->RemoveEvent(handle, Kernel::OrbisKernelEvent::Filter::VideoOut); + port->flip_events.erase(find(port->flip_events.begin(), port->flip_events.end(), equeue)); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata) { +s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::OrbisKernelEqueue eq, s32 handle, void* udata) { LOG_INFO(Lib_VideoOut, "handle = {}", handle); auto* port = driver->GetPort(handle); @@ -86,35 +88,37 @@ s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handl return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (eq == nullptr) { + auto equeue = Kernel::GetEqueue(eq); + if (equeue == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } Kernel::EqueueEvent event{}; event.event.ident = static_cast(OrbisVideoOutInternalEventId::Vblank); - event.event.filter = Kernel::SceKernelEvent::Filter::VideoOut; - event.event.flags = Kernel::SceKernelEvent::Flags::Add; + event.event.filter = Kernel::OrbisKernelEvent::Filter::VideoOut; + event.event.flags = Kernel::OrbisKernelEvent::Flags::Add; event.event.udata = udata; event.event.fflags = 0; event.event.data = 0; event.data = port; - eq->AddEvent(event); + equeue->AddEvent(event); - port->vblank_events.push_back(eq); + port->vblank_events.push_back(equeue); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutDeleteVblankEvent(Kernel::SceKernelEqueue eq, s32 handle) { +s32 PS4_SYSV_ABI sceVideoOutDeleteVblankEvent(Kernel::OrbisKernelEqueue eq, s32 handle) { auto* port = driver->GetPort(handle); if (port == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (eq == nullptr) { + auto equeue = Kernel::GetEqueue(eq); + if (equeue == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } - eq->RemoveEvent(handle, Kernel::SceKernelEvent::Filter::VideoOut); - port->vblank_events.erase(find(port->vblank_events.begin(), port->vblank_events.end(), eq)); + equeue->RemoveEvent(handle, Kernel::OrbisKernelEvent::Filter::VideoOut); + port->vblank_events.erase(find(port->vblank_events.begin(), port->vblank_events.end(), equeue)); return ORBIS_OK; } @@ -180,11 +184,11 @@ s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) { +s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::OrbisKernelEvent* ev) { if (ev == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } - if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) { + if (ev->filter != Kernel::OrbisKernelEvent::Filter::VideoOut) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; } @@ -208,11 +212,11 @@ s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) { } } -s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data) { +s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::OrbisKernelEvent* ev, s64* data) { if (ev == nullptr || data == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } - if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) { + if (ev->filter != Kernel::OrbisKernelEvent::Filter::VideoOut) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; } @@ -225,11 +229,11 @@ s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutGetEventCount(const Kernel::SceKernelEvent* ev) { +s32 PS4_SYSV_ABI sceVideoOutGetEventCount(const Kernel::OrbisKernelEvent* ev) { if (ev == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } - if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) { + if (ev->filter != Kernel::OrbisKernelEvent::Filter::VideoOut) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; } diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index 2c99a4d1c..09b79e85d 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -119,8 +119,8 @@ struct OrbisVideoOutEventData { void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat, u32 tilingMode, u32 aspectRatio, u32 width, u32 height, u32 pitchInPixel); -s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); -s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); +s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::OrbisKernelEqueue eq, s32 handle, void* udata); +s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::OrbisKernelEqueue eq, s32 handle, void* udata); s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum, const BufferAttribute* attribute); s32 PS4_SYSV_ABI sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr); @@ -133,8 +133,8 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio s32 PS4_SYSV_ABI sceVideoOutOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 busType, s32 index, const void* param); s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle); -s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev); -s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data); +s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::OrbisKernelEvent* ev); +s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::OrbisKernelEvent* ev, s64* data); s32 PS4_SYSV_ABI sceVideoOutColorSettingsSetGamma(SceVideoOutColorSettings* settings, float gamma); s32 PS4_SYSV_ABI sceVideoOutAdjustColor(s32 handle, const SceVideoOutColorSettings* settings); From c265a39227c4a654592b5c91fd21568ece816c77 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:08:39 +0100 Subject: [PATCH 18/19] fix typo --- src/core/libraries/kernel/threads/exception.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index dfb3b05f4..0b27f2bd8 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -188,7 +188,7 @@ void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context ctx.uc_mcontext.mc_rsp = regs.__rsp; ctx.uc_mcontext.mc_fs = regs.__fs; ctx.uc_mcontext.mc_gs = regs.__gs; - ctx.uc_mcontext.mc_gs = regs.__rip; + ctx.uc_mcontext.mc_rip = regs.__rip; ctx.uc_mcontext.mc_addr = reinterpret_cast(inf->si_addr); #else const auto& regs = raw_context->uc_mcontext.gregs; From 407d287fb1c34cfe7134ec8e500826798f2a21fa Mon Sep 17 00:00:00 2001 From: ElBread3 <92335081+ElBread3@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:30:57 +0000 Subject: [PATCH 19/19] tweak LoadFigure and RemoveFigure (#4071) --- src/core/ipc/ipc.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/ipc/ipc.cpp b/src/core/ipc/ipc.cpp index ea7cd38b4..aab3e7de5 100644 --- a/src/core/ipc/ipc.cpp +++ b/src/core/ipc/ipc.cpp @@ -174,12 +174,18 @@ void IPC::InputLoop() { } else if (cmd == "USB_LOAD_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { - ref->LoadFigure(next_str(), next_u64(), next_u64()); + std::string file_name = next_str(); + const u8 pad = next_u64(); + const u8 slot = next_u64(); + ref->LoadFigure(file_name, pad, slot); } } else if (cmd == "USB_REMOVE_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { - ref->RemoveFigure(next_u64(), next_u64(), next_u64() != 0); + const u8 pad = next_u64(); + const u8 slot = next_u64(); + bool full_remove = next_u64() != 0; + ref->RemoveFigure(pad, slot, full_remove); } } else if (cmd == "USB_MOVE_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef();