Handle unreachable NCE patch relocations

This commit is contained in:
bdm
2026-07-01 20:38:09 +08:00
parent 06cc52cc02
commit 6c4f9cd9f8
7 changed files with 317 additions and 66 deletions
+4
View File
@@ -199,6 +199,10 @@ bool IsNceEnabled() {
return is_nce_enabled;
}
void DisableNceForCurrentProcess() {
is_nce_enabled = false;
}
bool IsDockedMode() {
return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
}
+1
View File
@@ -833,6 +833,7 @@ bool IsGPULevelNormal();
bool IsFastmemEnabled();
bool IsCpuUltraLowAccuracy();
void SetNceEnabled(bool is_64bit);
void DisableNceForCurrentProcess();
bool IsNceEnabled();
bool IsDockedMode();
+97 -11
View File
@@ -2,8 +2,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <cstring>
#include <memory>
#include "common/assert.h"
#include "common/logging.h"
#include "common/signal_chain.h"
#include "core/arm/nce/arm_nce.h"
#include "core/arm/nce/interpreter_visitor.h"
@@ -24,6 +27,34 @@ namespace {
struct sigaction g_orig_bus_action;
struct sigaction g_orig_segv_action;
void ForwardSignalToOriginalAction(int sig, siginfo_t* info, void* raw_context,
const struct sigaction& original) {
if ((original.sa_flags & SA_SIGINFO) != 0 && original.sa_sigaction != nullptr) {
original.sa_sigaction(sig, info, raw_context);
return;
}
if (original.sa_handler == SIG_IGN) {
return;
}
if (original.sa_handler == SIG_DFL) {
Common::SigAction(sig, &original, nullptr);
syscall(SYS_tkill, gettid(), sig);
return;
}
if (original.sa_handler != nullptr) {
original.sa_handler(sig);
return;
}
struct sigaction default_action {};
default_action.sa_handler = SIG_DFL;
Common::SigAction(sig, &default_action, nullptr);
syscall(SYS_tkill, gettid(), sig);
}
// Verify assembly offsets.
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
static_assert(offsetof(NativeExecutionParameters, native_context) == TpidrEl0NativeContext);
@@ -31,11 +62,43 @@ static_assert(offsetof(NativeExecutionParameters, lock) == TpidrEl0Lock);
static_assert(offsetof(NativeExecutionParameters, magic) == TpidrEl0TlsMagic);
fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
_aarch64_ctx* header = reinterpret_cast<_aarch64_ctx*>(&host_ctx.__reserved);
while (header->magic != FPSIMD_MAGIC) {
header = reinterpret_cast<_aarch64_ctx*>(reinterpret_cast<char*>(header) + header->size);
auto* const begin = reinterpret_cast<char*>(&host_ctx.__reserved);
auto* const end = begin + sizeof(host_ctx.__reserved);
for (auto* ptr = begin; ptr + sizeof(_aarch64_ctx) <= end;) {
auto* header = reinterpret_cast<_aarch64_ctx*>(ptr);
if (header->magic == FPSIMD_MAGIC) {
if (header->size >= sizeof(fpsimd_context) &&
static_cast<size_t>(end - ptr) >= sizeof(fpsimd_context)) {
return reinterpret_cast<fpsimd_context*>(header);
}
LOG_CRITICAL(Core_ARM,
"Malformed NCE signal frame FPSIMD context: size={:#x}, "
"remaining={:#x}",
header->size, static_cast<size_t>(end - ptr));
return nullptr;
}
if (header->magic == 0 && header->size == 0) {
break;
}
if (header->size < sizeof(_aarch64_ctx) ||
header->size > static_cast<size_t>(end - ptr)) {
LOG_CRITICAL(Core_ARM,
"Malformed NCE signal frame context entry: magic={:#x}, size={:#x}, "
"remaining={:#x}",
header->magic, header->size, static_cast<size_t>(end - ptr));
return nullptr;
}
ptr += header->size;
}
return reinterpret_cast<fpsimd_context*>(header);
LOG_CRITICAL(Core_ARM, "NCE signal frame is missing FPSIMD context");
return nullptr;
}
using namespace Common::Literals;
@@ -53,6 +116,9 @@ void* ArmNce::RestoreGuestContext(void* raw_context) {
// Retrieve the host floating point state.
auto* fpctx = GetFloatingPointState(host_ctx);
if (fpctx == nullptr) {
UNREACHABLE_MSG("NCE cannot restore guest context without FPSIMD signal state");
}
// Save host callee-saved registers.
std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->vregs[8],
@@ -82,6 +148,9 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
// Retrieve the host floating point state.
auto* fpctx = GetFloatingPointState(host_ctx);
if (fpctx == nullptr) {
UNREACHABLE_MSG("NCE cannot save guest context without FPSIMD signal state");
}
// Save all guest registers except tpidr_el0.
std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs, sizeof(host_ctx.regs));
@@ -115,14 +184,26 @@ bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, voi
// We can't handle the access, so determine why we crashed.
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
// For data aborts, skip the instruction and return to guest code.
// This will allow games to continue in many scenarios where they would otherwise crash.
if (!is_prefetch_abort) {
host_ctx.pc += 4;
return true;
u32 instruction{};
std::memcpy(&instruction, reinterpret_cast<const void*>(host_ctx.pc), sizeof(instruction));
LOG_CRITICAL(Core_ARM,
"Unhandled NCE data abort: pc={:#x}, fault_addr={:#x}, instruction={:#010x}",
host_ctx.pc, reinterpret_cast<u64>(info->si_addr), instruction);
guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::DataAbort));
// Forcibly mark the context as locked. We are still running.
auto& thread_params = guest_ctx->parent->m_running_thread->GetNativeExecutionParameters();
thread_params.lock.store(SpinLockLocked);
// Return to host.
SaveGuestContext(guest_ctx, raw_context);
return false;
}
// This is a prefetch abort.
LOG_CRITICAL(Core_ARM, "Unhandled NCE prefetch abort: pc={:#x}, fault_addr={:#x}",
host_ctx.pc, reinterpret_cast<u64>(info->si_addr));
guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::PrefetchAbort));
// Forcibly mark the context as locked. We are still running.
@@ -141,6 +222,9 @@ bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, voi
bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
auto* fpctx = GetFloatingPointState(host_ctx);
if (fpctx == nullptr) {
UNREACHABLE_MSG("NCE cannot handle guest alignment fault without FPSIMD signal state");
}
auto& memory = guest_ctx->system->ApplicationMemory();
// Match and execute an instruction.
@@ -171,11 +255,13 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
}
void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) {
return g_orig_bus_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
ForwardSignalToOriginalAction(sig, static_cast<siginfo_t*>(raw_info), raw_context,
g_orig_bus_action);
}
void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) {
return g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
ForwardSignalToOriginalAction(sig, static_cast<siginfo_t*>(raw_info), raw_context,
g_orig_segv_action);
}
void ArmNce::LockThread(Kernel::KThread* thread) {
@@ -302,7 +388,7 @@ void ArmNce::Initialize() {
alignment_fault_action.sa_sigaction =
reinterpret_cast<HandlerType>(&ArmNce::GuestAlignmentFaultSignalHandler);
alignment_fault_action.sa_mask = signal_mask;
Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr);
Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, &g_orig_bus_action);
struct sigaction access_fault_action {};
access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
+131 -27
View File
@@ -42,6 +42,35 @@ Patcher::Patcher() : c(m_patch_instructions) {
Patcher::~Patcher() = default;
bool Patcher::IsValidBranchOffset(ptrdiff_t offset) noexcept {
return offset >= MinRelativeBranchOffset && offset <= MaxRelativeBranchOffset &&
offset % sizeof(u32) == 0;
}
ptrdiff_t Patcher::CalculateBranchToPatchOffset(PatchMode patch_mode, size_t patch_size,
size_t remaining_program_size,
const Relocation& rel) noexcept {
if (patch_mode == PatchMode::PreText) {
return rel.patch_offset - static_cast<ptrdiff_t>(patch_size) -
static_cast<ptrdiff_t>(rel.module_offset);
}
return static_cast<ptrdiff_t>(remaining_program_size) -
static_cast<ptrdiff_t>(rel.module_offset) + rel.patch_offset;
}
ptrdiff_t Patcher::CalculateBranchToModuleOffset(PatchMode patch_mode, size_t patch_size,
size_t remaining_program_size,
const Relocation& rel) noexcept {
if (patch_mode == PatchMode::PreText) {
return static_cast<ptrdiff_t>(patch_size) - rel.patch_offset +
static_cast<ptrdiff_t>(rel.module_offset);
}
return static_cast<ptrdiff_t>(rel.module_offset) -
static_cast<ptrdiff_t>(remaining_program_size) - rel.patch_offset;
}
bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
@@ -53,6 +82,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// Add a new module patch to our list
modules.emplace_back();
curr_patch = &modules.back();
curr_patch->image_size = image_size;
// The first word of the patch section is always a branch to the first instruction of the
// module.
@@ -120,6 +150,10 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// Determine patching mode for the final relocation step
total_program_size += image_size;
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
if (mode == PatchMode::PreText) {
ASSERT(modules.size() == 1);
ASSERT(total_program_size == image_size);
}
return true;
}
@@ -135,11 +169,6 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
const auto text_words =
std::span<u32>{reinterpret_cast<u32*>(text.data()), text.size() / sizeof(u32)};
const auto IsValidBranchOffset = [](ptrdiff_t offset) {
return offset >= MinRelativeBranchOffset && offset <= MaxRelativeBranchOffset &&
offset % sizeof(u32) == 0;
};
const auto LogBranchRelocation = [&](const char* kind, ptrdiff_t branch_offset,
const Relocation& rel) {
if (IsValidBranchOffset(branch_offset)) {
@@ -159,29 +188,23 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
const auto ApplyBranchToPatchRelocation = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
ptrdiff_t branch_offset;
if (mode == PatchMode::PreText) {
branch_offset = rel.patch_offset - static_cast<ptrdiff_t>(patch_size) -
static_cast<ptrdiff_t>(rel.module_offset);
} else {
branch_offset = static_cast<ptrdiff_t>(total_program_size) -
static_cast<ptrdiff_t>(rel.module_offset) + rel.patch_offset;
}
const ptrdiff_t branch_offset =
CalculateBranchToPatchOffset(mode, patch_size, total_program_size, rel);
LogBranchRelocation("module_to_patch", branch_offset, rel);
if (!IsValidBranchOffset(branch_offset)) {
UNREACHABLE_MSG("NCE branch relocation must be validated before RelocateAndCopy");
}
rc.B(branch_offset);
};
const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
ptrdiff_t branch_offset;
if (mode == PatchMode::PreText) {
branch_offset = static_cast<ptrdiff_t>(patch_size) - rel.patch_offset +
static_cast<ptrdiff_t>(rel.module_offset);
} else {
branch_offset = static_cast<ptrdiff_t>(rel.module_offset) -
static_cast<ptrdiff_t>(total_program_size) - rel.patch_offset;
}
const ptrdiff_t branch_offset =
CalculateBranchToModuleOffset(mode, patch_size, total_program_size, rel);
LogBranchRelocation("patch_to_module", branch_offset, rel);
if (!IsValidBranchOffset(branch_offset)) {
UNREACHABLE_MSG("NCE branch relocation must be validated before RelocateAndCopy");
}
rc.B(branch_offset);
};
@@ -202,7 +225,10 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
};
// We are now ready to relocate!
auto& patch = modules[m_relocate_module_index++];
ASSERT(m_relocate_module_index < modules.size());
const size_t module_index = m_relocate_module_index++;
auto& patch = modules[module_index];
const size_t module_image_size = patch.image_size;
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
}
@@ -229,20 +255,24 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
// Remove the patched module size from the total. This is done so total_program_size
// always represents the distance from the currently patched module to the patch section.
total_program_size -= image_size;
total_program_size -= module_image_size;
// Only copy to the program image of the last module
if (m_relocate_module_index == modules.size()) {
if (this->mode == PatchMode::PreText) {
if (image_size != total_program_size) {
const size_t expected_image_size = module_image_size + patch_size;
if (image_size != expected_image_size || total_program_size != 0) {
LOG_CRITICAL(Core_ARM,
"NCE PreText final copy size mismatch: image_size={:#x}, "
"NCE PreText final copy state mismatch: image_size={:#x}, "
"expected_image_size={:#x}, module_image_size={:#x}, "
"total_program_size={:#x}, patch_size={:#x}, relocate_module={}, "
"modules={}, load_base={:#x}",
image_size, total_program_size, patch_size, m_relocate_module_index,
image_size, expected_image_size, module_image_size,
total_program_size, patch_size, m_relocate_module_index,
modules.size(), GetInteger(load_base));
}
ASSERT(image_size == total_program_size);
ASSERT(image_size == expected_image_size);
ASSERT(total_program_size == 0);
std::memcpy(program_image.data(), m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
} else {
@@ -260,6 +290,80 @@ size_t Patcher::GetSectionSize() const noexcept {
return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::CITRON_PAGESIZE);
}
bool Patcher::CanRelocateBranches() const noexcept {
if (modules.empty() || mode == PatchMode::None) {
LOG_CRITICAL(Core_ARM,
"NCE branch relocation preflight called without patched modules: mode={}, "
"modules={}, total_program_size={:#x}",
mode, modules.size(), total_program_size);
return false;
}
const size_t patch_size = GetSectionSize();
const auto LogInvalidBranch = [&](const char* kind, size_t module_index,
size_t remaining_program_size,
const Relocation& rel, ptrdiff_t branch_offset) {
LOG_CRITICAL(Core_ARM,
"NCE branch relocation preflight failed: kind={}, mode={}, "
"relocate_module={}, modules={}, branch_offset={:#x}, valid_range=[{:#x}, "
"{:#x}], patch_offset={:#x}, module_offset={:#x}, patch_size={:#x}, "
"image_size={:#x}, total_program_size={:#x}",
kind, mode, module_index, modules.size(), branch_offset,
MinRelativeBranchOffset, MaxRelativeBranchOffset, rel.patch_offset,
rel.module_offset, patch_size, modules[module_index].image_size,
remaining_program_size);
};
size_t remaining_program_size = total_program_size;
for (size_t module_index = 0; module_index < modules.size(); module_index++) {
const auto& patch = modules[module_index];
if (patch.image_size > remaining_program_size) {
LOG_CRITICAL(Core_ARM,
"NCE branch relocation preflight size state mismatch: mode={}, "
"relocate_module={}, modules={}, image_size={:#x}, "
"remaining_program_size={:#x}, total_program_size={:#x}",
mode, module_index, modules.size(), patch.image_size,
remaining_program_size, total_program_size);
return false;
}
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
const ptrdiff_t branch_offset =
CalculateBranchToPatchOffset(mode, patch_size, remaining_program_size, rel);
if (!IsValidBranchOffset(branch_offset)) {
LogInvalidBranch("module_to_patch", module_index, remaining_program_size, rel,
branch_offset);
return false;
}
}
for (const Relocation& rel : patch.m_branch_to_module_relocations) {
const ptrdiff_t branch_offset =
CalculateBranchToModuleOffset(mode, patch_size, remaining_program_size, rel);
if (!IsValidBranchOffset(branch_offset)) {
LogInvalidBranch("patch_to_module", module_index, remaining_program_size, rel,
branch_offset);
return false;
}
}
remaining_program_size -= patch.image_size;
}
if (remaining_program_size != 0) {
LOG_CRITICAL(Core_ARM,
"NCE branch relocation preflight size state mismatch after scan: mode={}, "
"modules={}, remaining_program_size={:#x}, total_program_size={:#x}",
mode, modules.size(), remaining_program_size, total_program_size);
return false;
}
return true;
}
void Patcher::WriteLoadContext() {
// This function was called, which modifies X30, so use that as a scratch register.
// SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes
+12
View File
@@ -36,6 +36,7 @@ public:
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
size_t GetSectionSize() const noexcept;
bool CanRelocateBranches() const noexcept;
[[nodiscard]] PatchMode GetPatchMode() const noexcept {
return mode;
@@ -84,7 +85,18 @@ private:
uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
};
[[nodiscard]] static bool IsValidBranchOffset(ptrdiff_t offset) noexcept;
[[nodiscard]] static ptrdiff_t CalculateBranchToPatchOffset(PatchMode patch_mode,
size_t patch_size,
size_t remaining_program_size,
const Relocation& rel) noexcept;
[[nodiscard]] static ptrdiff_t CalculateBranchToModuleOffset(PatchMode patch_mode,
size_t patch_size,
size_t remaining_program_size,
const Relocation& rel) noexcept;
struct ModulePatch {
size_t image_size{};
std::vector<Trampoline> m_trampolines;
std::vector<Relocation> m_branch_to_patch_relocations{};
std::vector<Relocation> m_branch_to_module_relocations{};
+4 -12
View File
@@ -128,22 +128,14 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
// Notify the debugger and go to sleep on data abort.
if (data_abort) {
if (system.DebuggerEnabled()) {
if (system.DebuggerEnabled() && interface->HaltedWatchpoint() != nullptr) {
system.GetDebugger().NotifyThreadWatchpoint(thread, *interface->HaltedWatchpoint());
} else {
// Enhanced data abort handling for Nintendo SDK crashes
LOG_WARNING(Core_ARM, "Data abort detected - checking if recoverable...");
// For Nintendo SDK crashes, try to continue execution
// Many data aborts in Nintendo SDK are recoverable
LOG_INFO(Core_ARM, "Attempting to continue execution after data abort");
// Don't suspend the thread, let it continue
LOG_CRITICAL(Core_ARM, "Data abort halted execution");
}
if (system.DebuggerEnabled()) {
thread->RequestSuspend(SuspendType::Debug);
return;
}
thread->RequestSuspend(SuspendType::Debug);
return;
}
// Handle system calls.
+68 -16
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <optional>
#include "common/logging.h"
#include "common/settings.h"
#include "core/core.h"
@@ -29,7 +30,12 @@ namespace Loader {
struct PatchCollection {
explicit PatchCollection(bool is_application_) : is_application{is_application_} {
Reset();
}
void Reset() {
module_patcher_indices.fill(-1);
patchers.clear();
patchers.emplace_back();
}
@@ -41,6 +47,10 @@ struct PatchCollection {
}
size_t GetTotalPatchSize() const {
if (!is_application || !Settings::IsNceEnabled()) {
return 0;
}
size_t total_size{};
#ifdef HAS_NCE
for (auto& patcher : patchers) {
@@ -62,6 +72,21 @@ struct PatchCollection {
return static_cast<s32>(patchers.size()) - 1;
}
bool CanRelocateBranches() const {
#ifdef HAS_NCE
if (!is_application || !Settings::IsNceEnabled()) {
return true;
}
for (const auto& patcher : patchers) {
if (!patcher.CanRelocateBranches()) {
return false;
}
}
#endif
return true;
}
bool is_application;
std::vector<Core::NCE::Patcher> patchers;
std::array<s32, 13> module_patcher_indices{};
@@ -185,30 +210,57 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7",
"subsdk8", "subsdk9", "sdk"};
std::size_t code_size{};
// Define an nce patch context for each potential module.
PatchCollection patch_ctx{is_application};
// Use the NSO module loader to figure out the code layout
for (size_t i = 0; i < static_modules.size(); i++) {
const auto& module = static_modules[i];
const FileSys::VirtualFile module_file{dir->GetFile(module)};
if (!module_file) {
continue;
const auto CalculateCodeLayout = [&]() -> std::optional<std::size_t> {
std::size_t next_code_size{};
patch_ctx.Reset();
// Use the NSO module loader to figure out the code layout.
for (size_t i = 0; i < static_modules.size(); i++) {
const auto& module = static_modules[i];
const FileSys::VirtualFile module_file{dir->GetFile(module)};
if (!module_file) {
continue;
}
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
process, system, *module_file, next_code_size, should_pass_arguments, false, {},
patch_ctx.GetPatchers(), patch_ctx.GetLastIndex());
if (!tentative_next_load_addr) {
return std::nullopt;
}
patch_ctx.SaveIndex(i);
next_code_size = *tentative_next_load_addr;
}
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
process, system, *module_file, code_size, should_pass_arguments, false, {},
patch_ctx.GetPatchers(), patch_ctx.GetLastIndex());
if (!tentative_next_load_addr) {
return next_code_size;
};
auto calculated_code_size = CalculateCodeLayout();
if (!calculated_code_size) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
#ifdef HAS_NCE
if (!patch_ctx.CanRelocateBranches()) {
LOG_WARNING(Loader,
"NCE patch layout contains an out-of-range branch relocation for "
"program_id={:016X}; falling back to Dynarmic for this boot",
metadata.GetTitleID());
Settings::DisableNceForCurrentProcess();
calculated_code_size = CalculateCodeLayout();
if (!calculated_code_size) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
patch_ctx.SaveIndex(i);
code_size = *tentative_next_load_addr;
}
#endif
std::size_t code_size = *calculated_code_size;
// Enable direct memory mapping in case of NCE.
const u64 fastmem_base = [&]() -> size_t {