diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 95fbfc9383..96fc5afe95 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -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; } diff --git a/src/common/settings.h b/src/common/settings.h index 86d4a336ec..2b422764a6 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -833,6 +833,7 @@ bool IsGPULevelNormal(); bool IsFastmemEnabled(); bool IsCpuUltraLowAccuracy(); void SetNceEnabled(bool is_64bit); +void DisableNceForCurrentProcess(); bool IsNceEnabled(); bool IsDockedMode(); diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 9143928a9c..6596bc376d 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -2,8 +2,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include +#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(header) + header->size); + auto* const begin = reinterpret_cast(&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(end - ptr) >= sizeof(fpsimd_context)) { + return reinterpret_cast(header); + } + + LOG_CRITICAL(Core_ARM, + "Malformed NCE signal frame FPSIMD context: size={:#x}, " + "remaining={:#x}", + header->size, static_cast(end - ptr)); + return nullptr; + } + + if (header->magic == 0 && header->size == 0) { + break; + } + + if (header->size < sizeof(_aarch64_ctx) || + header->size > static_cast(end - ptr)) { + LOG_CRITICAL(Core_ARM, + "Malformed NCE signal frame context entry: magic={:#x}, size={:#x}, " + "remaining={:#x}", + header->magic, header->size, static_cast(end - ptr)); + return nullptr; + } + + ptr += header->size; } - return reinterpret_cast(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(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(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(info->si_addr), instruction); + guest_ctx->esr_el1.fetch_or(static_cast(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(info->si_addr)); guest_ctx->esr_el1.fetch_or(static_cast(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(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(raw_info), raw_context); + ForwardSignalToOriginalAction(sig, static_cast(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(raw_info), raw_context); + ForwardSignalToOriginalAction(sig, static_cast(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(&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; diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index 9789c208dd..f50eb24d02 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -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(patch_size) - + static_cast(rel.module_offset); + } + + return static_cast(remaining_program_size) - + static_cast(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(patch_size) - rel.patch_offset + + static_cast(rel.module_offset); + } + + return static_cast(rel.module_offset) - + static_cast(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{reinterpret_cast(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(patch_size) - - static_cast(rel.module_offset); - } else { - branch_offset = static_cast(total_program_size) - - static_cast(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(patch_size) - rel.patch_offset + - static_cast(rel.module_offset); - } else { - branch_offset = static_cast(rel.module_offset) - - static_cast(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 diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index a44f385e2e..a47f81cc23 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -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 m_trampolines; std::vector m_branch_to_patch_relocations{}; std::vector m_branch_to_module_relocations{}; diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index 37b4541949..85dbfceee6 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -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. diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index d285b7327d..30bddf1f8a 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #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(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 patchers; std::array 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 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 {