diff options
| author | xbzk <xbzk@eden-emu.dev> | 2026-10-15 12:13:17 +0200 |
|---|---|---|
| committer | crueter <crueter@eden-emu.dev> | 2026-10-15 12:13:17 +0200 |
| commit | aaa0bcb9bf5694d62538c361b210147294a5ce3d (patch) | |
| tree | 9d98ee51c095542d4ffba808443e814109e6e11e /src | |
| parent | e255c20db4111485d228c10478bda63aabcfdde5 (diff) | |
[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 <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Diffstat (limited to 'src')
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 @@ -868,13 +868,6 @@ abstract class SettingsItem( ) 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, titleId = R.string.fix_bloom_effects, descriptionId = R.string.fix_bloom_effects_description 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 @@ <string name="skip_cpu_inner_invalidation_description">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.</string> <string name="nce_invalidation_gpu_readback">GPU Readback Before NCE Invalidation</string> <string name="nce_invalidation_gpu_readback_description">Temporary. Fix Ender Magnolia (and other UE titles) crashes. May reduce performance. Improvement in progress.</string> - <string name="nce_runtime_nro_patch">Patch Runtime NROs for NCE</string> - <string name="nce_runtime_nro_patch_description">Temporary. Fix Biomutant boot. May impact performance. Improvement in progress.</string> <string name="fix_bloom_effects">Fix Bloom Effects</string> <string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string> <string name="emulate_bgr565">Emulate BGR565</string> 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<bool> nce_invalidation_gpu_readback{ linkage, false, "nce_invalidation_gpu_readback", Category::RendererHacks}; - SwitchableSetting<bool> nce_runtime_nro_patch{ - linkage, false, "nce_runtime_nro_patch", Category::RendererHacks}; SwitchableSetting<bool> 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<const u8> text) : c(m_patch_instructions), c_pre(m_patch_instructions_pre) { + modules.emplace_back(); + curr_patch = &modules.back(); + const auto words = std::span<const u32>{reinterpret_cast<const u32*>(text.data()), text.size() / sizeof(u32)}; + for (u32 i = ModuleCodeIndex; i < static_cast<u32>(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<int>(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<int>(msr.GetRt())}); + } + } +} + Patcher::~Patcher() = default; bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code) { @@ -182,6 +199,31 @@ bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet return true; } +std::span<const u32> Patcher::RelocateRuntimeTls( + s64 patch_offset, std::vector<InstructionPatch>& out_patches) { + const auto& patch = modules.front(); + const auto can_branch = [](s64 offset) { + return offset % 4 == 0 && offset >= -static_cast<s64>(MaxRelativeBranch) && offset < static_cast<s64>(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<s64>(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<s64>(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<u8>& 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<ModuleTextAddress, PatchTextAddress>; +struct InstructionPatch { + u64 offset; + u32 replacement; +}; + class Patcher { public: void SetModuleID(const ModuleID& id) { module_id = id; } explicit Patcher(); + explicit Patcher(std::span<const u8> text); ~Patcher(); bool PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code); bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines); + std::span<const u32> RelocateRuntimeTls(s64 patch_offset, std::vector<InstructionPatch>& 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 <openssl/err.h> #include <openssl/evp.h> +#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<u8> image(total_size); - memory.ReadBlock(nro_info->base_address, image.data(), rx_size); - - Kernel::CodeSet::Segment code{.size = static_cast<u32>(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<u8> 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<s64, 2> offsets{static_cast<s64>(total_size + Kernel::PageSize),-static_cast<s64>(patch_size + Kernel::PageSize)}; + std::vector<Core::NCE::InstructionPatch> tls_patches; + std::span<const u32> 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<u64>(static_cast<s64>(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<char*>(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<char*>(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<TranslationMap> 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" |
