From aaa0bcb9bf5694d62538c361b210147294a5ce3d Mon Sep 17 00:00:00 2001 From: xbzk Date: Thu, 15 Oct 2026 12:13:17 +0200 Subject: [PATCH] [patcher, ro, settings] runtime tls patch (#4492) optimization and nuke toggle (#4502) (#4523) - [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- This is a follow up for the biomutant fix. By the time i knew it was shallow, coz i was running patchtext routine even for svc and modules, causing a routine which were needed to run 14 times to run 4000+ instead. Since some perf loss was reported in 30 sep nightly, and it caused ac3r to not boot, we had added a temp toggle, which is now being removed as optimizations + fix are in. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4523 Reviewed-by: lizzie Reviewed-by: MaranBr --- .../features/settings/model/BooleanSetting.kt | 3 +- .../settings/model/view/SettingsItem.kt | 7 --- .../settings/ui/SettingsFragmentPresenter.kt | 1 - .../app/src/main/res/values/strings.xml | 2 - src/common/settings.h | 2 - src/core/arm/nce/patcher.cpp | 42 +++++++++++++ src/core/arm/nce/patcher.h | 7 +++ src/core/hle/service/ro/ro.cpp | 62 ++++++++++++------- src/qt_common/config/shared_translation.cpp | 4 -- 9 files changed, 89 insertions(+), 41 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 2f2d3823f8..0548004cf5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -17,7 +17,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"), SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"), NCE_INVALIDATION_GPU_READBACK("nce_invalidation_gpu_readback"), - NCE_RUNTIME_NRO_PATCH("nce_runtime_nro_patch"), FIX_BLOOM_EFFECTS("fix_bloom_effects"), EMULATE_BGR565("emulate_bgr565"), RESCALE_HACK("rescale_hack"), @@ -95,7 +94,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { ENABLE_QUICK_SETTINGS("enable_quick_settings"); -// external fun isFrameSkippingEnabled(): Boolean + //external fun isFrameSkippingEnabled(): Boolean external fun isFrameInterpolationEnabled(): Boolean override fun getBoolean(needsGlobal: Boolean): Boolean = diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 8346339ee4..690c746628 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -866,13 +866,6 @@ abstract class SettingsItem( descriptionId = R.string.nce_invalidation_gpu_readback_description ) ) - put( - SwitchSetting( - BooleanSetting.NCE_RUNTIME_NRO_PATCH, - titleId = R.string.nce_runtime_nro_patch, - descriptionId = R.string.nce_runtime_nro_patch_description - ) - ) put( SwitchSetting( BooleanSetting.FIX_BLOOM_EFFECTS, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 74e1e90836..f9185e39e6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -555,7 +555,6 @@ class SettingsFragmentPresenter( add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key) add(BooleanSetting.NCE_INVALIDATION_GPU_READBACK.key) - add(BooleanSetting.NCE_RUNTIME_NRO_PATCH.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.EMULATE_BGR565.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 703a91fd02..191506c818 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -595,8 +595,6 @@ Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games. GPU Readback Before NCE Invalidation Temporary. Fix Ender Magnolia (and other UE titles) crashes. May reduce performance. Improvement in progress. - Patch Runtime NROs for NCE - Temporary. Fix Biomutant boot. May impact performance. Improvement in progress. Fix Bloom Effects Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games. Emulate BGR565 diff --git a/src/common/settings.h b/src/common/settings.h index 2a4bf3e9e4..d1f891607b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -641,8 +641,6 @@ struct Values { true}; SwitchableSetting nce_invalidation_gpu_readback{ linkage, false, "nce_invalidation_gpu_readback", Category::RendererHacks}; - SwitchableSetting nce_runtime_nro_patch{ - linkage, false, "nce_runtime_nro_patch", Category::RendererHacks}; SwitchableSetting async_presentation{linkage, #ifdef __ANDROID__ true, diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index 7d2041d4d2..f21d7cacdc 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -45,6 +45,23 @@ Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) { WriteLoadContext(c_pre); } +Patcher::Patcher(std::span text) : c(m_patch_instructions), c_pre(m_patch_instructions_pre) { + modules.emplace_back(); + curr_patch = &modules.back(); + const auto words = std::span{reinterpret_cast(text.data()), text.size() / sizeof(u32)}; + for (u32 i = ModuleCodeIndex; i < static_cast(words.size()); ++i) { + const uintptr_t offset = i * sizeof(u32); + if (auto mrs = MRS{words[i]}; mrs.Verify() && mrs.GetRt() != 31 && (mrs.GetSystemReg() == TpidrEl0 || mrs.GetSystemReg() == TpidrroEl0)) { + BranchToPatch(offset); + const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0 : oaknut::SystemReg::TPIDR_EL0; + WriteMrsHandler(offset + sizeof(u32), oaknut::XReg{static_cast(mrs.GetRt())}, src_reg); + } else if (auto msr = MSR{words[i]}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) { + BranchToPatch(offset); + WriteMsrHandler(offset + sizeof(u32), oaknut::XReg{static_cast(msr.GetRt())}); + } + } +} + Patcher::~Patcher() = default; bool Patcher::PatchText(std::span program_image, const Kernel::CodeSet::Segment& code) { @@ -182,6 +199,31 @@ bool Patcher::PatchText(std::span program_image, const Kernel::CodeSet return true; } +std::span Patcher::RelocateRuntimeTls( + s64 patch_offset, std::vector& out_patches) { + const auto& patch = modules.front(); + const auto can_branch = [](s64 offset) { + return offset % 4 == 0 && offset >= -static_cast(MaxRelativeBranch) && offset < static_cast(MaxRelativeBranch); + }; + out_patches.clear(); + out_patches.reserve(patch.m_branch_to_patch_relocations.size()); + for (const auto& rel : patch.m_branch_to_patch_relocations) { + const s64 offset = patch_offset + rel.patch_offset - static_cast(rel.module_offset); + if (!can_branch(offset)) {return {};} + u32 replacement; + oaknut::CodeGenerator rc{&replacement}; + rc.B(offset); + out_patches.push_back({rel.module_offset, replacement}); + } + for (const auto& rel : patch.m_branch_to_module_relocations) { + const s64 offset = static_cast(rel.module_offset) - patch_offset - rel.patch_offset; + if (!can_branch(offset)) {return {};} + oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)}; + rc.B(offset); + } + return m_patch_instructions; +} + bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector& program_image, EntryTrampolines* out_trampolines) { const size_t patch_size = GetSectionSize(); const size_t pre_patch_size = GetPreSectionSize(); diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index a3b314000c..695161cd18 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -48,15 +48,22 @@ using ModuleTextAddress = u64; using PatchTextAddress = u64; using EntryTrampolines = ::Common::unordered_map; +struct InstructionPatch { + u64 offset; + u32 replacement; +}; + class Patcher { public: void SetModuleID(const ModuleID& id) { module_id = id; } explicit Patcher(); + explicit Patcher(std::span text); ~Patcher(); bool PatchText(std::span program_image, const Kernel::CodeSet::Segment& code); bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector& program_image, EntryTrampolines* out_trampolines); + std::span RelocateRuntimeTls(s64 patch_offset, std::vector& out_patches); size_t GetSectionSize() const noexcept; size_t GetPreSectionSize() const noexcept; diff --git a/src/core/hle/service/ro/ro.cpp b/src/core/hle/service/ro/ro.cpp index 167bf04c9a..335efa3ad3 100644 --- a/src/core/hle/service/ro/ro.cpp +++ b/src/core/hle/service/ro/ro.cpp @@ -7,6 +7,7 @@ #include #include +#include "common/scope_exit.h" #include "core/hle/kernel/k_process.h" #include "core/hle/service/cmif_serialization.h" @@ -425,36 +426,51 @@ public: R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size), std::addressof(ro_size), std::addressof(rw_size), nro_info->base_address, nro_size, bss_size)); + R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size, rw_size + bss_size)); #ifdef HAS_NCE - if (Settings::values.nce_runtime_nro_patch.GetValue()) { - if (Settings::IsNceEnabled()) { - auto* process = context->GetProcess(); - auto& memory = process->GetMemory(); - - std::vector image(total_size); - memory.ReadBlock(nro_info->base_address, image.data(), rx_size); - - Kernel::CodeSet::Segment code{.size = static_cast(rx_size)}; - Core::NCE::Patcher patch; - patch.PatchText(image, code); - patch.RelocateAndCopy(nro_info->base_address, code, image, nullptr); - - const u64 patch_address = nro_info->base_address + total_size; - const size_t patch_size = patch.GetSectionSize(); + if (Settings::IsNceEnabled()) { + auto* process = context->GetProcess(); + auto& memory = process->GetMemory(); + std::vector text(rx_size); + memory.ReadBlock(nro_info->base_address, text.data(), text.size()); + Core::NCE::Patcher patch{text}; + const size_t patch_size = patch.GetSectionSize(); + if (patch_size != 0) { constexpr auto permission = Kernel::Svc::MemoryPermission::ReadExecute; - auto* patch_memory = Kernel::KSharedMemory::Create(kernel); + SCOPE_EXIT { patch_memory->Close(kernel); }; R_TRY(patch_memory->Initialize(kernel, kernel.System().DeviceMemory(), process, permission, permission, patch_size)); - std::memcpy(patch_memory->GetPointer(), image.data() + total_size, patch_size); - R_TRY(process->AddSharedMemory(kernel, patch_memory, patch_address, patch_size)); - R_TRY(patch_memory->Map(*process, patch_address, patch_size, permission)); - memory.WriteBlock(nro_info->base_address, image.data(), rx_size); + Kernel::KSharedMemory::Register(kernel, patch_memory); + const std::array offsets{static_cast(total_size + Kernel::PageSize),-static_cast(patch_size + Kernel::PageSize)}; + std::vector tls_patches; + std::span instructions; + u64 patch_address = 0; + for (const s64 offset : offsets) { + instructions = patch.RelocateRuntimeTls(offset, tls_patches); + if (instructions.empty()) {continue;} + const u64 address = static_cast(static_cast(nro_info->base_address) + offset); + if (R_FAILED(patch_memory->Map(*process, address, patch_size, permission))) {continue;} + patch_address = address; + break; + } + R_UNLESS(patch_address != 0, RO::ResultOutOfAddressSpace); + const Result add_shared_memory_result = process->AddSharedMemory(kernel, patch_memory, patch_address, patch_size); + if (R_FAILED(add_shared_memory_result)) { + R_ASSERT(patch_memory->Unmap(*process, patch_address, patch_size)); + } + R_TRY(add_shared_memory_result); + std::memcpy(patch_memory->GetPointer(), instructions.data(), instructions.size_bytes()); + auto* ptr = reinterpret_cast(patch_address); + __builtin___clear_cache(ptr, ptr + instructions.size_bytes()); + for (const auto& instruction : tls_patches) { + const u64 address = nro_info->base_address + instruction.offset; + memory.Write32(address, instruction.replacement); + ptr = reinterpret_cast(address); + __builtin___clear_cache(ptr, ptr + sizeof(u32)); + } } } #endif - // Set NRO perms. - R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size, - rw_size + bss_size)); context->SetNroInfoInUse(nro_info, true); nro_info->code_size = rx_size + ro_size; diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 964a4cdad4..09e64b98f0 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -198,10 +198,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) { tr("GPU Readback Before NCE Invalidation"), tr("Flushes GPU buffer data back to guest memory before NCE invalidates the whole " "guest page, preserving it while the CPU store proceeds.")); - INSERT(Settings, nce_runtime_nro_patch, - tr("Patch Runtime NROs for NCE"), - tr("Applies NCE code patches to runtime-loaded NRO modules." - "This is required for some games to run correctly.")); INSERT(Settings, vsync_mode, tr("VSync Mode:"), tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " "refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n"