aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxbzk <xbzk@eden-emu.dev>2026-10-15 12:13:17 +0200
committercrueter <crueter@eden-emu.dev>2026-10-15 12:13:17 +0200
commitaaa0bcb9bf5694d62538c361b210147294a5ce3d (patch)
tree9d98ee51c095542d4ffba808443e814109e6e11e
parente255c20db4111485d228c10478bda63aabcfdde5 (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>
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt1
-rw-r--r--src/android/app/src/main/res/values/strings.xml2
-rw-r--r--src/common/settings.h2
-rw-r--r--src/core/arm/nce/patcher.cpp42
-rw-r--r--src/core/arm/nce/patcher.h7
-rw-r--r--src/core/hle/service/ro/ro.cpp62
-rw-r--r--src/qt_common/config/shared_translation.cpp4
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"