diff --git a/src/common/address_space.inc b/src/common/address_space.inc index 4fce338661..9a8f6755aa 100644 --- a/src/common/address_space.inc +++ b/src/common/address_space.inc @@ -336,22 +336,22 @@ ALLOC_MEMBER(VaType)::Allocate(VaType size) { current_linear_alloc_end = alloc_start + size; } else { // If linear allocation overflows the AS then find a gap if (this->blocks.size() <= 2) { - ASSERT_MSG(false, "Unexpected allocator state!"); - } - - auto search_predecessor{std::next(this->blocks.begin())}; - auto search_successor{std::next(search_predecessor)}; + alloc_start = virt_start; + } else { + auto search_predecessor{std::next(this->blocks.begin())}; + auto search_successor{std::next(search_predecessor)}; - while (search_successor != this->blocks.end() && - (search_successor->virt - search_predecessor->virt < size || - search_predecessor->Mapped())) { - search_predecessor = search_successor++; - } + while (search_successor != this->blocks.end() && + (search_successor->virt - search_predecessor->virt < size || + search_predecessor->Mapped())) { + search_predecessor = search_successor++; + } - if (search_successor != this->blocks.end()) { - alloc_start = search_predecessor->virt; - } else { - return {}; // AS is full + if (search_successor != this->blocks.end()) { + alloc_start = search_predecessor->virt; + } else { + return {}; // AS is full + } } } @@ -364,6 +364,10 @@ ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) { } ALLOC_MEMBER(void)::Free(VaType virt, VaType size) { + const VaType virt_end = virt + size; this->Unmap(virt, size); + if (virt_end >= virt && virt_end == current_linear_alloc_end) { + current_linear_alloc_end = virt < virt_start ? virt_start : virt; + } } } // namespace Common diff --git a/src/core/hle/kernel/k_page_table_base.h b/src/core/hle/kernel/k_page_table_base.h index 160e722985..71534a9cfd 100644 --- a/src/core/hle/kernel/k_page_table_base.h +++ b/src/core/hle/kernel/k_page_table_base.h @@ -670,6 +670,11 @@ public: size_t GetHeapRegionSize() const { return m_heap_region_end - m_heap_region_start; } + size_t GetCurrentHeapSize() const { + KScopedLightLock lk(m_general_lock); + + return m_current_heap_end - m_heap_region_start; + } size_t GetAliasRegionSize() const { return m_alias_region_end - m_alias_region_start; } diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 7308b0ff87..e2e4add325 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -210,6 +210,10 @@ Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParamete m_arg_pointer = 0; m_arg_return_address = 0; m_main_thread_handle_addr = 0; + m_process_handle_addr = 0; + m_homebrew_next_load_path_addr = 0; + m_homebrew_next_load_argv_addr = 0; + m_is_homebrew_in_place_next_load = false; m_code_size = params.code_num_pages * PageSize; m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication); @@ -947,6 +951,7 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) { stack_top = stack_bottom + stack_size; m_main_thread_stack_size = stack_size; + m_main_thread_stack_top = stack_top; } // Ensure our stack is safe to clean up on exit. @@ -1005,6 +1010,11 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) { if (GetInteger(m_main_thread_handle_addr) != 0) { this->GetMemory().Write32(m_main_thread_handle_addr, thread_handle); } + if (GetInteger(m_process_handle_addr) != 0) { + Handle process_handle; + R_TRY(m_handle_table.Add(kernel, std::addressof(process_handle), this)); + this->GetMemory().Write32(m_process_handle_addr, process_handle); + } } else { main_thread->GetContext().r[0] = 0; main_thread->GetContext().r[1] = thread_handle; diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index c861a9ea51..11fc130571 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -87,6 +87,9 @@ private: KProcessAddress m_arg_pointer{}; KProcessAddress m_arg_return_address{}; KProcessAddress m_main_thread_handle_addr{}; + KProcessAddress m_process_handle_addr{}; + KProcessAddress m_homebrew_next_load_path_addr{}; + KProcessAddress m_homebrew_next_load_argv_addr{}; KHandleTable m_handle_table; KProcessAddress m_plr_address{}; ThreadList m_thread_list{}; @@ -112,6 +115,7 @@ private: size_t m_code_size{}; size_t m_main_thread_stack_size{}; + KProcessAddress m_main_thread_stack_top{}; size_t m_max_process_memory{}; size_t m_memory_release_hint{}; s64 m_schedule_count{}; @@ -139,6 +143,7 @@ private: bool m_is_suspended : 1 = false; bool m_is_immortal : 1 = false; bool m_is_handle_table_initialized : 1 = false; + bool m_is_homebrew_in_place_next_load : 1 = false; private: Result StartTermination(KernelCore& kernel); @@ -231,6 +236,31 @@ public: void SetMainThreadHandleAddr(KProcessAddress addr) { m_main_thread_handle_addr = addr; } + void SetProcessHandleAddr(KProcessAddress addr) { + m_process_handle_addr = addr; + } + void SetHomebrewNextLoadBufferAddrs(KProcessAddress path_addr, KProcessAddress argv_addr) { + m_homebrew_next_load_path_addr = path_addr; + m_homebrew_next_load_argv_addr = argv_addr; + } + void SetHomebrewInPlaceNextLoad(bool enabled) { + m_is_homebrew_in_place_next_load = enabled; + } + bool IsHomebrewInPlaceNextLoad() const { + return m_is_homebrew_in_place_next_load; + } + KProcessAddress GetHomebrewNextLoadPathAddr() const { + return m_homebrew_next_load_path_addr; + } + KProcessAddress GetHomebrewNextLoadArgvAddr() const { + return m_homebrew_next_load_argv_addr; + } + size_t GetCodeSize() const { + return m_code_size; + } + KProcessAddress GetMainThreadStackTop() const { + return m_main_thread_stack_top; + } size_t GetMainStackSize() const { return m_main_thread_stack_size; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index faf6a6432d..4136a983cf 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-late +// SPDX-License-Identifier: GPL-2.0-or-later // This file is automatically generated using svc_generator.py. // DO NOT MODIFY IT MANUALLY @@ -128,7 +129,7 @@ static void SvcWrap_QueryMemory64From32(Core::System& system, std::span args) { - ExitProcess64From32(system); + ExitProcess64From32(system, args); } static void SvcWrap_CreateThread64From32(Core::System& system, std::span args) { @@ -1298,7 +1299,7 @@ static void SvcWrap_QueryMemory64(Core::System& system, std::span a } static void SvcWrap_ExitProcess64(Core::System& system, std::span args) { - ExitProcess64(system); + ExitProcess64(system, args); } static void SvcWrap_CreateThread64(Core::System& system, std::span args) { diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index 356c1c109a..d3106adfe7 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-late +// SPDX-License-Identifier: GPL-2.0-or-later // This file is automatically generated using svc_generator.py. // DO NOT MODIFY IT MANUALLY @@ -25,7 +26,7 @@ Result SetMemoryAttribute(Core::System& system, uint64_t address, uint64_t size, Result MapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size); Result UnmapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size); Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address); -void ExitProcess(Core::System& system); +void ExitProcess(Core::System& system, std::span args); Result CreateThread(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id); Result StartThread(Core::System& system, Handle thread_handle); void ExitThread(Core::System& system); @@ -146,7 +147,7 @@ Result SetMemoryAttribute64From32(Core::System& system, uint32_t address, uint32 Result MapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size); Result UnmapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size); Result QueryMemory64From32(Core::System& system, uint32_t out_memory_info, PageInfo* out_page_info, uint32_t address); -void ExitProcess64From32(Core::System& system); +void ExitProcess64From32(Core::System& system, std::span args); Result CreateThread64From32(Core::System& system, Handle* out_handle, uint32_t func, uint32_t arg, uint32_t stack_bottom, int32_t priority, int32_t core_id); Result StartThread64From32(Core::System& system, Handle thread_handle); void ExitThread64From32(Core::System& system); @@ -267,7 +268,7 @@ Result SetMemoryAttribute64(Core::System& system, uint64_t address, uint64_t siz Result MapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size); Result UnmapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size); Result QueryMemory64(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address); -void ExitProcess64(Core::System& system); +void ExitProcess64(Core::System& system, std::span args); Result CreateThread64(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id); Result StartThread64(Core::System& system, Handle thread_handle); void ExitThread64(Core::System& system); diff --git a/src/core/hle/kernel/svc/svc_memory.cpp b/src/core/hle/kernel/svc/svc_memory.cpp index 970912fe50..71412b80c6 100644 --- a/src/core/hle/kernel/svc/svc_memory.cpp +++ b/src/core/hle/kernel/svc/svc_memory.cpp @@ -4,6 +4,9 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + #include "core/core.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/svc.h" @@ -22,6 +25,179 @@ constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) { } } +bool IsHomebrewInPlaceNextLoadCodeRange(const KProcess& process, u64 address, u64 size) { + if (!process.IsHomebrewInPlaceNextLoad()) { + return false; + } + + const u64 code_start = GetInteger(process.GetEntryPoint()); + const size_t code_size = process.GetCodeSize(); + if (code_start == 0 || code_size == 0 || size > code_size || address < code_start) { + return false; + } + + return address - code_start <= code_size - size; +} + +struct HomebrewInPlaceMemoryBlock { + u64 address; + u64 size; + KMemoryState state; + KMemoryPermission permission; + KMemoryAttribute attribute; + bool use_process_permission; +}; + +Result SetHomebrewInPlaceMemoryPermissionByBlocks(KProcess& process, u64 address, u64 size, + MemoryPermission perm, Result original_result) { + auto& page_table = process.GetPageTable(); + const u64 end = address + size; + const auto requested_permission = ConvertToKMemoryPermission(perm); + std::vector blocks; + + for (u64 cursor = address; cursor < end;) { + KMemoryInfo info; + PageInfo page_info; + const auto query_result = page_table.QueryInfo(std::addressof(info), + std::addressof(page_info), cursor); + if (query_result.IsError()) { + LOG_WARNING(Kernel_SVC, + "NextLoad in-place: split permission query failed " + "address=0x{:016X}, result={:#X}, original={:#X}", + cursor, query_result.raw, original_result.raw); + R_RETURN(original_result); + } + + const u64 block_end = (std::min)(info.GetEndAddress(), end); + if (block_end <= cursor) { + LOG_WARNING(Kernel_SVC, + "NextLoad in-place: split permission walk stalled " + "cursor=0x{:016X}, block=0x{:016X}/0x{:X}, original={:#X}", + cursor, info.GetAddress(), info.GetSize(), original_result.raw); + R_RETURN(original_result); + } + + const bool can_reprotect = True(info.GetState() & KMemoryState::FlagCanReprotect); + const bool can_process_reprotect = True(info.GetState() & KMemoryState::FlagCode); + if (!can_reprotect && !can_process_reprotect) { + LOG_WARNING(Kernel_SVC, + "NextLoad in-place: split permission unsupported block " + "address=0x{:016X}, size=0x{:X}, state=0x{:08X}, svc_state={}, " + "perm=0x{:08X}, attr=0x{:08X}, original={:#X}", + cursor, block_end - cursor, static_cast(info.GetState()), + static_cast(info.GetSvcState()), + static_cast(info.GetPermission()), + static_cast(info.GetAttribute()), original_result.raw); + R_RETURN(original_result); + } + + blocks.push_back({ + .address = cursor, + .size = block_end - cursor, + .state = info.GetState(), + .permission = info.GetPermission(), + .attribute = info.GetAttribute(), + .use_process_permission = !can_reprotect && can_process_reprotect, + }); + cursor = block_end; + } + + for (const auto& block : blocks) { + if (block.permission == requested_permission) { + continue; + } + + const auto block_result = + block.use_process_permission + ? page_table.SetProcessMemoryPermission(block.address, block.size, perm) + : page_table.SetMemoryPermission(block.address, block.size, perm); + if (block_result.IsError()) { + LOG_WARNING(Kernel_SVC, + "NextLoad in-place: split permission block failed " + "address=0x{:016X}, size=0x{:X}, state=0x{:08X}, perm=0x{:08X}, " + "attr=0x{:08X}, result={:#X}, original={:#X}", + block.address, block.size, static_cast(block.state), + static_cast(block.permission), static_cast(block.attribute), + block_result.raw, original_result.raw); + R_RETURN(block_result); + } + } + + R_SUCCEED(); +} + +struct HomebrewInPlaceDeviceSharedBlock { + u64 address; + u64 size; + u16 device_use_count; +}; + +Result UnlockHomebrewInPlaceDeviceSharedSource(KProcess& process, u64 address, u64 size, + Result original_result) { + auto& page_table = process.GetPageTable(); + const u64 end = address + size; + std::vector blocks; + + for (u64 cursor = address; cursor < end;) { + KMemoryInfo info; + PageInfo page_info; + const auto query_result = page_table.QueryInfo(std::addressof(info), + std::addressof(page_info), cursor); + if (query_result.IsError()) { + R_RETURN(original_result); + } + + const u64 block_end = (std::min)(info.GetEndAddress(), end); + if (block_end <= cursor) { + R_RETURN(original_result); + } + + const bool can_device_map = True(info.GetState() & KMemoryState::FlagCanDeviceMap); + const bool is_clean_memory = + can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite && + info.GetAttribute() == KMemoryAttribute::None && info.m_device_use_count == 0; + const bool is_stale_device_shared = + can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite && + info.GetAttribute() == KMemoryAttribute::DeviceShared && info.m_device_use_count > 0; + if (is_clean_memory) { + cursor = block_end; + continue; + } + if (!is_stale_device_shared) { + R_RETURN(original_result); + } + + blocks.push_back({ + .address = cursor, + .size = block_end - cursor, + .device_use_count = info.m_device_use_count, + }); + cursor = block_end; + } + + if (blocks.empty()) { + R_RETURN(original_result); + } + + for (const auto& block : blocks) { + for (u16 unlock = 0; unlock < block.device_use_count; unlock++) { + const auto unlock_result = + page_table.UnlockForDeviceAddressSpace(block.address, block.size); + if (unlock_result.IsError()) { + LOG_WARNING(Kernel_SVC, + "NextLoad in-place: device-shared source unlock failed " + "address=0x{:016X}, size=0x{:X}, remaining={}, result={:#X}, " + "original={:#X}", + block.address, block.size, block.device_use_count - unlock - 1, + unlock_result.raw, original_result.raw); + R_RETURN(original_result); + } + } + } + + R_SUCCEED(); +} + // Checks if address + size is greater than the given address // This can return false if the size causes an overflow of a 64-bit type // or if the given size is zero. @@ -92,11 +268,19 @@ Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPe R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission); // Validate that the region is in range for the current process. - auto& page_table = GetCurrentProcess(system.Kernel()).GetPageTable(); + auto& process = GetCurrentProcess(system.Kernel()); + auto& page_table = process.GetPageTable(); R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); // Set the memory attribute. - R_RETURN(page_table.SetMemoryPermission(address, size, perm)); + const auto result = page_table.SetMemoryPermission(address, size, perm); + if (result.raw == ResultInvalidCurrentMemory.raw && + IsHomebrewInPlaceNextLoadCodeRange(process, address, size)) { + R_RETURN( + SetHomebrewInPlaceMemoryPermissionByBlocks(process, address, size, perm, result)); + } + + R_RETURN(result); } Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, u32 attr) { @@ -135,14 +319,30 @@ Result MapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) { LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr, src_addr, size); - auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()}; + auto& process = GetCurrentProcess(system.Kernel()); + auto& page_table = process.GetPageTable(); if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; result.IsError()) { return result; } - R_RETURN(page_table.MapMemory(dst_addr, src_addr, size)); + const auto result = page_table.MapMemory(dst_addr, src_addr, size); + if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) { + if (UnlockHomebrewInPlaceDeviceSharedSource(process, src_addr, size, result).IsSuccess()) { + const auto retry_result = page_table.MapMemory(dst_addr, src_addr, size); + if (retry_result.IsError()) { + LOG_WARNING(Kernel_SVC, + "NextLoad in-place: svcMapMemory retry failed after " + "DeviceShared cleanup dst=0x{:016X}, src=0x{:016X}, size=0x{:X}, " + "result={:#X}", + dst_addr, src_addr, size, retry_result.raw); + } + R_RETURN(retry_result); + } + } + + R_RETURN(result); } /// Unmaps a region that was previously mapped with svcMapMemory @@ -150,14 +350,23 @@ Result UnmapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) { LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr, src_addr, size); - auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()}; + auto& process = GetCurrentProcess(system.Kernel()); + auto& page_table = process.GetPageTable(); if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; result.IsError()) { return result; } - R_RETURN(page_table.UnmapMemory(dst_addr, src_addr, size)); + const auto result = page_table.UnmapMemory(dst_addr, src_addr, size); + if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) { + LOG_WARNING(Kernel_SVC, + "NextLoad in-place: svcUnmapMemory failed dst=0x{:016X}, " + "src=0x{:016X}, size=0x{:X}, result={:#X}", + dst_addr, src_addr, size, result.raw); + } + + R_RETURN(result); } Result SetMemoryPermission64(Core::System& system, uint64_t address, uint64_t size, diff --git a/src/core/hle/kernel/svc/svc_process.cpp b/src/core/hle/kernel/svc/svc_process.cpp index af1bdc7807..3c56bc8b3e 100644 --- a/src/core/hle/kernel/svc/svc_process.cpp +++ b/src/core/hle/kernel/svc/svc_process.cpp @@ -4,17 +4,216 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include + +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/input.h" #include "core/core.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/svc.h" +#include "core/hle/service/hid/hid_server.h" +#include "core/hle/service/nvdrv/nvdrv_interface.h" +#include "core/hle/service/sm/sm.h" +#include "core/loader/nro.h" +#include "hid_core/frontend/emulated_controller.h" +#include "hid_core/hid_core.h" +#include "hid_core/resource_manager.h" namespace Kernel::Svc { +namespace { + +constexpr size_t HomebrewNextLoadPathSize = 0x200; +constexpr size_t HomebrewNextLoadArgvSize = 0x800; + +std::string ReadHomebrewString(Core::Memory::Memory& memory, KProcessAddress address, + size_t max_size) { + if (GetInteger(address) == 0) { + return {}; + } + return memory.ReadCString(Common::ProcessAddress{GetInteger(address)}, max_size); +} + +} // namespace + /// Exits the current process -void ExitProcess(Core::System& system) { +void ExitProcess(Core::System& system, std::span args) { auto* current_process = GetCurrentProcessPointer(system.Kernel()); + auto* current_thread = GetCurrentThreadPointer(system.Kernel()); LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessId()); + const auto next_load_path_addr = current_process->GetHomebrewNextLoadPathAddr(); + const auto next_load_argv_addr = current_process->GetHomebrewNextLoadArgvAddr(); + if (GetInteger(next_load_path_addr) != 0) { + auto& memory = current_process->GetMemory(); + const auto next_load_path = + ReadHomebrewString(memory, next_load_path_addr, HomebrewNextLoadPathSize); + const auto next_load_argv = + ReadHomebrewString(memory, next_load_argv_addr, HomebrewNextLoadArgvSize); + if (!next_load_path.empty()) { + auto guest_path = Common::FS::SanitizePath(next_load_path); + constexpr std::string_view SdmcPrefix{"sdmc:"}; + FileSys::VirtualFile file{}; + + const bool is_sdmc_path = guest_path.rfind(SdmcPrefix, 0) == 0; + const bool is_absolute_guest_path = !guest_path.empty() && guest_path.front() == '/'; + if (is_sdmc_path || is_absolute_guest_path) { + auto relative_path = + is_sdmc_path ? guest_path.substr(SdmcPrefix.size()) : guest_path; + while (!relative_path.empty() && relative_path.front() == '/') { + relative_path.erase(relative_path.begin()); + } + + const auto host_path = Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir) / + std::filesystem::path{Common::FS::ToU8String(relative_path)}; + const auto host_path_string = Common::FS::PathToUTF8String(host_path); + file = Core::GetGameFileFromPath(system.GetFilesystem(), host_path_string); + if (!file) { + LOG_WARNING(Kernel_SVC, + "NextLoad: failed to open guest_path='{}', host_path='{}'", + next_load_path, host_path_string); + } + } else { + file = Core::GetGameFileFromPath(system.GetFilesystem(), guest_path); + if (!file) { + LOG_WARNING(Kernel_SVC, "NextLoad: failed to open guest_path='{}'", + next_load_path); + } + } + + if (file) { + const auto nvdrv = + system.ServiceManager().GetService("nvdrv:s"); + if (!nvdrv) { + LOG_WARNING(Kernel_SVC, "NextLoad: NVDRV service unavailable for reset"); + } else { + nvdrv->GetModule()->ResetForProcess(current_process); + } + + auto& page_table = current_process->GetPageTable(); + const u64 heap_start = GetInteger(page_table.GetHeapRegionStart()); + const u64 heap_size = page_table.GetHeapRegionSize(); + const u64 heap_end = heap_start + heap_size; + if (heap_start != 0 && heap_size != 0 && heap_end > heap_start) { + struct DeviceSharedBlock { + u64 address; + u64 size; + u16 device_use_count; + }; + + std::vector blocks; + for (u64 cursor = heap_start; cursor < heap_end;) { + KMemoryInfo info; + PageInfo page_info; + const auto query_result = + page_table.QueryInfo(std::addressof(info), std::addressof(page_info), + cursor); + if (query_result.IsError()) { + LOG_WARNING(Kernel_SVC, + "NextLoad: DeviceShared heap cleanup query failed " + "address=0x{:016X}, result={:#X}", + cursor, query_result.raw); + break; + } + + const u64 block_end = (std::min)(info.GetEndAddress(), heap_end); + if (block_end <= cursor) { + LOG_WARNING(Kernel_SVC, + "NextLoad: DeviceShared heap cleanup walk stalled " + "cursor=0x{:016X}, block=0x{:016X}/0x{:X}", + cursor, info.GetAddress(), info.GetSize()); + break; + } + + const bool is_device_shared = + True(info.GetState() & KMemoryState::FlagCanDeviceMap) && + info.GetAttribute() == KMemoryAttribute::DeviceShared && + info.m_device_use_count > 0; + if (is_device_shared) { + blocks.push_back(DeviceSharedBlock{ + .address = cursor, + .size = block_end - cursor, + .device_use_count = info.m_device_use_count, + }); + } + cursor = block_end; + } + + for (const auto& block : blocks) { + for (u16 unlock = 0; unlock < block.device_use_count; unlock++) { + const auto unlock_result = + page_table.UnlockForDeviceAddressSpace(block.address, block.size); + if (unlock_result.IsError()) { + LOG_WARNING(Kernel_SVC, + "NextLoad: DeviceShared heap cleanup unlock failed " + "address=0x{:016X}, size=0x{:X}, remaining={}, " + "result={:#X}", + block.address, block.size, + block.device_use_count - unlock - 1, unlock_result.raw); + break; + } + } + } + } + + if (Loader::LoadNroInPlace(system, *current_process, *current_thread, file, + next_load_path, next_load_argv)) { + const auto aruid = current_process->GetProcessId(); + if (const auto hid = + system.ServiceManager().GetService("hid")) { + const auto resource_manager = hid->GetResourceManager(); + resource_manager->UnregisterAppletResourceUserId(aruid); + const auto register_result = + resource_manager->RegisterAppletResourceUserId(aruid, true); + if (register_result.IsError()) { + LOG_WARNING(Kernel_SVC, + "NextLoad: failed to register HID applet resource " + "aruid={}, result={:#X}", + aruid, register_result.raw); + } + } else { + LOG_WARNING(Kernel_SVC, "NextLoad: HID service unavailable for reset"); + } + + auto& hid_core = system.HIDCore(); + hid_core.DisableAllControllerConfiguration(); + hid_core.SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); + hid_core.ReloadInputDevices(); + + const auto activate_controller = [&](Core::HID::NpadIdType npad_id) { + auto* controller = hid_core.GetEmulatedController(npad_id); + if (controller == nullptr) { + return; + } + + (void)controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices, + Common::Input::PollingMode::Active); + }; + + activate_controller(Core::HID::NpadIdType::Player1); + activate_controller(Core::HID::NpadIdType::Handheld); + + system.Kernel().CurrentPhysicalCore().LoadContext(current_thread); + + const auto& context = current_thread->GetContext(); + for (size_t i = 0; i < args.size(); i++) { + args[i] = context.r[i]; + } + + return; + } + } + } + } ASSERT_MSG(current_process->GetState() == KProcess::State::Running, "Process has already exited"); @@ -132,8 +331,8 @@ Result TerminateProcess(Core::System& system, Handle process_handle) { R_THROW(ResultNotImplemented); } -void ExitProcess64(Core::System& system) { - ExitProcess(system); +void ExitProcess64(Core::System& system, std::span args) { + ExitProcess(system, args); } Result GetProcessId64(Core::System& system, uint64_t* out_process_id, Handle process_handle) { @@ -164,8 +363,8 @@ Result GetProcessInfo64(Core::System& system, int64_t* out_info, Handle process_ R_RETURN(GetProcessInfo(system, out_info, process_handle, info_type)); } -void ExitProcess64From32(Core::System& system) { - ExitProcess(system); +void ExitProcess64From32(Core::System& system, std::span args) { + ExitProcess(system, args); } Result GetProcessId64From32(Core::System& system, uint64_t* out_process_id, Handle process_handle) { diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 4ba4bde0bb..27bddb2428 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -4,17 +4,20 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include #include #include +#include #include #include #include "common/alignment.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/fs/path_util.h" #include "common/logging.h" #include "common/settings.h" #include "common/random.h" @@ -23,6 +26,8 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs/vfs_offset.h" +#include "core/hardware_properties.h" +#include "core/hle/api_version.h" #include "core/hle/kernel/code_set.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" @@ -152,8 +157,417 @@ static constexpr u32 PageAlignSize(u32 size) { return static_cast((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK); } +static std::string MakeHomebrewSdmcPath(std::string path) { + std::replace(path.begin(), path.end(), '\\', '/'); + while (!path.empty() && path.front() == '/') { + path.erase(path.begin()); + } + return "sdmc:/" + path; +} + +static std::optional TryMakeHomebrewSdmcPathFromHostPath(std::string path) { + std::replace(path.begin(), path.end(), '\\', '/'); + + std::string sdmc_root = + Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir)); + std::replace(sdmc_root.begin(), sdmc_root.end(), '\\', '/'); + while (!sdmc_root.empty() && sdmc_root.back() == '/') { + sdmc_root.pop_back(); + } + + if (!sdmc_root.empty() && path.size() > sdmc_root.size() && + path.compare(0, sdmc_root.size(), sdmc_root) == 0 && path[sdmc_root.size()] == '/') { + return MakeHomebrewSdmcPath(path.substr(sdmc_root.size() + 1)); + } + + constexpr std::string_view SdmcMarker{"/sdmc/"}; + if (const auto pos = path.rfind(SdmcMarker); pos != std::string::npos) { + return MakeHomebrewSdmcPath(path.substr(pos + SdmcMarker.size())); + } + + return std::nullopt; +} + +static std::string MakeHomebrewArgv0(std::string nro_path, std::string file_name) { + if (nro_path.empty()) { + nro_path = std::move(file_name); + } + + std::replace(nro_path.begin(), nro_path.end(), '\\', '/'); + + if (nro_path.rfind("sdmc:/", 0) == 0) { + return nro_path; + } + + if (auto sdmc_path = TryMakeHomebrewSdmcPathFromHostPath(nro_path)) { + return *sdmc_path; + } + +#ifdef __ANDROID__ + if (nro_path.find('%') != std::string::npos) { + const auto percent_decode = [](std::string value) { + const auto hex_value = [](char c) -> int { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return -1; + }; + + std::string decoded; + decoded.reserve(value.size()); + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + const int high = hex_value(value[i + 1]); + const int low = hex_value(value[i + 2]); + if (high >= 0 && low >= 0) { + decoded.push_back(static_cast((high << 4) | low)); + i += 2; + continue; + } + } + decoded.push_back(value[i]); + } + return decoded; + }; + + if (auto sdmc_path = TryMakeHomebrewSdmcPathFromHostPath(percent_decode(nro_path))) { + return *sdmc_path; + } + } +#endif + + if (!nro_path.empty() && nro_path.front() == '/') { + return "sdmc:" + nro_path; + } + + return nro_path.empty() ? "homebrew" : nro_path; +} + +static std::string QuoteHomebrewArgvComponent(const std::string& argument) { + if (argument.find_first_of(" \t\r\n") == std::string::npos) { + return argument; + } + + std::string quoted{"\""}; + quoted += argument; + quoted.push_back('"'); + return quoted; +} + +static std::string GetHomebrewInitialCwd(const std::string& argv0) { + constexpr std::string_view SdmcPrefix = "sdmc:"; + if (argv0.substr(0, SdmcPrefix.size()) != SdmcPrefix) { + return {}; + } + + const auto last_slash = argv0.find_last_of('/'); + if (last_slash == std::string_view::npos || last_slash < SdmcPrefix.size()) { + return {}; + } + + std::string cwd = argv0.substr(SdmcPrefix.size(), last_slash - SdmcPrefix.size()); + if (cwd.empty()) { + cwd = "/"; + } + while (cwd.size() > 1 && cwd.back() == '/') { + cwd.pop_back(); + } + return cwd; +} + +constexpr size_t HomebrewNextLoadPathSize = 0x200; +constexpr size_t HomebrewNextLoadArgvSize = 0x800; +constexpr u32 HomebrewSvcExitProcessInstruction = 0xD40000E1; +constexpr u32 HomebrewEntryEndOfList = 0; +constexpr u32 HomebrewEntryMainThreadHandle = 1; +constexpr u32 HomebrewEntryNextLoadPath = 2; +constexpr u32 HomebrewEntryOverrideHeap = 3; +constexpr u32 HomebrewEntryArgv = 5; +constexpr u32 HomebrewEntrySyscallAvailableHint = 6; +constexpr u32 HomebrewEntryAppletType = 7; +constexpr u32 HomebrewEntryProcessHandle = 10; +constexpr u32 HomebrewEntryRandomSeed = 14; +constexpr u32 HomebrewEntryHosVersion = 16; +constexpr u32 HomebrewEntrySyscallAvailableHint2 = 17; +constexpr u32 HomebrewAppletTypeApplication = 0; +constexpr u64 HomebrewAllSvcHints = ~u64{0}; +constexpr u32 HomebrewHosVersion = (u32{HLE::ApiVersion::HOS_VERSION_MAJOR} << 16) | + (u32{HLE::ApiVersion::HOS_VERSION_MINOR} << 8) | + u32{HLE::ApiVersion::HOS_VERSION_MICRO}; + +struct HomebrewConfigEntry { + u32_le key; + u32_le flags; + u64_le value[2]; +}; +static_assert(sizeof(HomebrewConfigEntry) == 0x18); +constexpr size_t HomebrewBaseConfigEntryCount = 10; +constexpr size_t HomebrewInPlaceConfigEntryCount = 11; +constexpr size_t HomebrewBaseConfigTableSize = + HomebrewBaseConfigEntryCount * sizeof(HomebrewConfigEntry); +constexpr size_t HomebrewInPlaceConfigTableSize = + HomebrewInPlaceConfigEntryCount * sizeof(HomebrewConfigEntry); + +struct HomebrewNroImage { + Kernel::CodeSet codeset; + size_t image_size{}; + size_t args_offset{}; + std::optional exit_process_offset; + std::string argv_string; +}; + +static void SetHomebrewConfigPointers(Kernel::KProcess& process, u64 config_addr, + u64 next_load_path_addr, u64 next_load_argv_addr) { + constexpr size_t MainThreadHandleEntryIndex = 0; + constexpr size_t ProcessHandleEntryIndex = 1; + constexpr size_t EntryValueOffset = offsetof(HomebrewConfigEntry, value); + + process.SetArgPointer(Kernel::KProcessAddress{config_addr}); + process.SetMainThreadHandleAddr(Kernel::KProcessAddress{ + config_addr + MainThreadHandleEntryIndex * sizeof(HomebrewConfigEntry) + EntryValueOffset}); + process.SetProcessHandleAddr(Kernel::KProcessAddress{ + config_addr + ProcessHandleEntryIndex * sizeof(HomebrewConfigEntry) + EntryValueOffset}); + process.SetHomebrewNextLoadBufferAddrs(Kernel::KProcessAddress{next_load_path_addr}, + Kernel::KProcessAddress{next_load_argv_addr}); +} + +static std::optional BuildHomebrewNroImage(const std::vector& data, + std::string nro_path, + std::string file_name, + std::string launch_argv) { + if (data.size() < sizeof(NroHeader)) { + return std::nullopt; + } + + NroHeader nro_header{}; + std::memcpy(&nro_header, data.data(), sizeof(NroHeader)); + if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) { + return std::nullopt; + } + if (data.size() < nro_header.file_size || + nro_header.module_header_offset + sizeof(ModHeader) > PageAlignSize(nro_header.file_size)) { + return std::nullopt; + } + + std::vector program_image(PageAlignSize(nro_header.file_size)); + std::memcpy(program_image.data(), data.data(), nro_header.file_size); + + Kernel::CodeSet codeset; + for (std::size_t i = 0; i < nro_header.segments.size(); ++i) { + codeset.segments[i].addr = nro_header.segments[i].offset; + codeset.segments[i].offset = nro_header.segments[i].offset; + codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size); + } + + u32 bss_size{PageAlignSize(nro_header.bss_size)}; + ModHeader mod_header{}; + std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset, + sizeof(ModHeader)); + + if (mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')) { + bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); + } + + codeset.DataSegment().size += bss_size; + program_image.resize(static_cast(program_image.size()) + bss_size); + + HomebrewNroImage image{.codeset = std::move(codeset)}; + const auto argv0 = MakeHomebrewArgv0(std::move(nro_path), std::move(file_name)); + + if (!launch_argv.empty()) { + image.argv_string = std::move(launch_argv); + } else { + image.argv_string = QuoteHomebrewArgvComponent(argv0); + } + if (image.argv_string.empty() || image.argv_string.back() != '\0') { + image.argv_string.push_back('\0'); + } + + const auto& code = image.codeset.CodeSegment(); + const size_t code_end = (std::min)(program_image.size(), code.offset + code.size); + for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) { + u32 instruction{}; + std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction)); + if (instruction == HomebrewSvcExitProcessInstruction) { + image.exit_process_offset = offset; + break; + } + } + + const size_t entries_and_buffers = + Common::AlignUp(HomebrewInPlaceConfigTableSize + HomebrewNextLoadPathSize + + HomebrewNextLoadArgvSize + image.argv_string.size(), + Core::Memory::YUZU_PAGESIZE); + + image.args_offset = program_image.size(); + image.codeset.DataSegment().size += static_cast(entries_and_buffers); + program_image.resize(image.args_offset + entries_and_buffers); + image.image_size = program_image.size(); + image.codeset.memory = std::move(program_image); + + return image; +} + +bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, Kernel::KThread& thread, + const FileSys::VirtualFile& nro_file, const std::string& nro_path, + const std::string& launch_argv) { + if (!nro_file) { + return false; + } + +#ifdef HAS_NCE + if (Settings::IsNceEnabled()) { + LOG_WARNING(Loader, + "Homebrew next-load: in-place handoff unavailable because NCE is enabled"); + return false; + } +#endif + + size_t live_threads = 0; + for (auto& candidate : process.GetThreadList()) { + if (candidate.GetState() != Kernel::ThreadState::Terminated) { + live_threads++; + } + } + if (live_threads != 1) { + LOG_WARNING(Loader, "NextLoad: in-place handoff failed because live_threads={}", + live_threads); + return false; + } + + const auto stack_top = process.GetMainThreadStackTop(); + if (GetInteger(stack_top) == 0) { + LOG_WARNING(Loader, "NextLoad: in-place handoff failed because stack_top=0"); + return false; + } + + auto image = + BuildHomebrewNroImage(nro_file->ReadAllBytes(), nro_path, nro_file->GetName(), launch_argv); + if (!image) { + LOG_WARNING(Loader, "NextLoad: in-place handoff failed because '{}' is invalid", + nro_path); + return false; + } + + const auto capacity = process.GetCodeSize(); + if (image->image_size > capacity) { + LOG_WARNING(Loader, + "NextLoad: in-place handoff failed because image_size=0x{:X} exceeds " + "capacity=0x{:X}", + image->image_size, capacity); + return false; + } + + const u64 base = GetInteger(process.GetEntryPoint()); + const u64 heap_addr = GetInteger(process.GetPageTable().GetHeapRegionStart()); + const size_t heap_size = process.GetPageTable().GetBasePageTable().GetCurrentHeapSize(); + if (heap_addr == 0 || heap_size == 0) { + LOG_WARNING(Loader, + "NextLoad: in-place handoff failed because heap override is " + "unavailable (addr=0x{:016X}, size=0x{:X})", + heap_addr, heap_size); + return false; + } + + const u64 config_addr = base + image->args_offset; + const u64 next_load_path_addr = config_addr + HomebrewInPlaceConfigTableSize; + const u64 next_load_argv_addr = next_load_path_addr + HomebrewNextLoadPathSize; + const u64 argv_addr = next_load_argv_addr + HomebrewNextLoadArgvSize; + const u64 argv_entry_addr = image->argv_string.empty() ? 0 : argv_addr; + const std::string argv0 = MakeHomebrewArgv0(nro_path, nro_file->GetName()); + const std::string homebrew_initial_cwd = GetHomebrewInitialCwd(argv0); + u64 program_id{}; + AppLoader_NRO loader{nro_file}; + if (loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) { + LOG_WARNING(Loader, "NextLoad: in-place handoff could not read NRO program id"); + } + + Kernel::Handle main_thread_handle{}; + if (process.GetHandleTable().Add(system.Kernel(), std::addressof(main_thread_handle), &thread) + .IsError()) { + LOG_WARNING(Loader, + "NextLoad: in-place handoff failed because main thread handle failed"); + return false; + } + + Kernel::Handle process_handle{}; + if (process.GetHandleTable().Add(system.Kernel(), std::addressof(process_handle), &process) + .IsError()) { + LOG_WARNING(Loader, + "NextLoad: in-place handoff failed because process handle failed"); + return false; + } + + if (!process.GetMemory().ZeroBlock(Common::ProcessAddress{heap_addr}, heap_size)) { + LOG_WARNING(Loader, + "NextLoad: in-place handoff failed because heap clear failed " + "(addr=0x{:016X}, size=0x{:X})", + heap_addr, heap_size); + return false; + } + + process.LoadModule(system.Kernel(), std::move(image->codeset), process.GetEntryPoint()); + + const HomebrewConfigEntry entries[HomebrewInPlaceConfigEntryCount] = { + {HomebrewEntryMainThreadHandle, 0, {main_thread_handle, 0}}, + {HomebrewEntryProcessHandle, 0, {process_handle, 0}}, + {HomebrewEntryNextLoadPath, 0, {next_load_path_addr, next_load_argv_addr}}, + {HomebrewEntryOverrideHeap, 0, {heap_addr, heap_size}}, + {HomebrewEntryAppletType, 0, {HomebrewAppletTypeApplication, 0}}, + {HomebrewEntryArgv, 0, {0, argv_entry_addr}}, + {HomebrewEntrySyscallAvailableHint, 0, {HomebrewAllSvcHints, HomebrewAllSvcHints}}, + {HomebrewEntryRandomSeed, 0, + {process.GetRandomEntropy(0), process.GetRandomEntropy(1)}}, + {HomebrewEntryHosVersion, 0, {HomebrewHosVersion, 0}}, + {HomebrewEntrySyscallAvailableHint2, 0, {HomebrewAllSvcHints, 0}}, + {HomebrewEntryEndOfList, 0, {0, 0}}, + }; + + process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries)); + process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, image->argv_string.data(), + image->argv_string.size()); + process.GetMemory().Write32(Common::ProcessAddress{GetInteger(thread.GetTlsAddress()) + 0x110}, + main_thread_handle); + + process.SetArgReturnAddress(Kernel::KProcessAddress{ + image->exit_process_offset ? base + *image->exit_process_offset : 0}); + SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr); + system.GetFileSystemController().RegisterProcess( + process.GetProcessId(), program_id, + std::make_unique(loader, system.GetContentProvider(), + system.GetFileSystemController()), + homebrew_initial_cwd); + process.SetHomebrewInPlaceNextLoad(true); + + auto& context = thread.GetContext(); + context = {}; + context.r[0] = config_addr; + context.r[1] = UINT64_MAX; + context.r[18] = Common::Random::Random64(0) | 1; + context.lr = image->exit_process_offset ? base + *image->exit_process_offset : 0; + context.pc = base; + context.sp = GetInteger(stack_top); + context.fpcr = 0; + context.fpsr = 0; + + for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + if (auto* arm = process.GetArmInterface(core); arm != nullptr) { + arm->ClearInstructionCache(); + } + } + + return true; +} + static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, - const std::vector& data) { + const std::vector& data, std::string nro_path, + std::string file_name) { if (data.size() < sizeof(NroHeader)) { return {}; } @@ -195,47 +609,44 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, codeset.DataSegment().size += bss_size; program_image.resize(static_cast(program_image.size()) + bss_size); - struct ConfigEntry { - u32_le key; - u32_le flags; - u64_le value[2]; - }; - static_assert(sizeof(ConfigEntry) == 0x18); - // AArch64 encoding for svc #0x7 (ExitProcess). - constexpr u32 kSvcExitProcessInstruction = 0xD40000E1; - constexpr size_t kNumEntries = 4; // MainThreadHandle, AppletType, Argv, EndOfList - constexpr size_t kConfigTableSize = kNumEntries * sizeof(ConfigEntry); std::string argv_string; size_t args_offset_in_image = 0; std::optional exit_process_offset_in_image; const auto& program_args = Settings::values.program_args.GetValue(); + const std::string argv0 = MakeHomebrewArgv0(std::move(nro_path), std::move(file_name)); + argv_string = QuoteHomebrewArgvComponent(argv0); if (!program_args.empty()) { - argv_string = "homebrew "; + argv_string.push_back(' '); argv_string += program_args; + } + if (argv_string.empty() || argv_string.back() != '\0') { argv_string.push_back('\0'); + } - const auto& code = codeset.CodeSegment(); - const size_t code_end = (std::min)(program_image.size(), code.offset + code.size); - for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) { - u32 instruction{}; - std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction)); - if (instruction == kSvcExitProcessInstruction) { - exit_process_offset_in_image = offset; - break; - } - } - if (!exit_process_offset_in_image) { - LOG_WARNING(Loader, - "Unable to find svcExitProcess in NRO; returning from main may fault"); + const auto& code_segment = codeset.CodeSegment(); + const size_t code_end = + (std::min)(program_image.size(), code_segment.offset + code_segment.size); + for (size_t offset = code_segment.offset; offset + sizeof(u32) <= code_end; + offset += sizeof(u32)) { + u32 instruction{}; + std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction)); + if (instruction == HomebrewSvcExitProcessInstruction) { + exit_process_offset_in_image = offset; + break; } + } + if (!exit_process_offset_in_image) { + LOG_WARNING(Loader, "Unable to find svcExitProcess in NRO; returning from main may fault"); + } - const size_t entries_and_argv = - Common::AlignUp(kConfigTableSize + argv_string.size(), Core::Memory::YUZU_PAGESIZE); + const size_t entries_and_buffers = + Common::AlignUp(HomebrewBaseConfigTableSize + HomebrewNextLoadPathSize + + HomebrewNextLoadArgvSize + argv_string.size(), + Core::Memory::YUZU_PAGESIZE); - args_offset_in_image = program_image.size(); - codeset.DataSegment().size += static_cast(entries_and_argv); - program_image.resize(args_offset_in_image + entries_and_argv); - } + args_offset_in_image = program_image.size(); + codeset.DataSegment().size += static_cast(entries_and_buffers); + program_image.resize(args_offset_in_image + entries_and_buffers); size_t image_size = program_image.size(); #ifdef HAS_NCE @@ -263,6 +674,9 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, image_size += patch_segment.size; } #endif + // In-place NextLoad reuses the original code mapping; leave room for larger homebrew cores. + constexpr size_t HomebrewCodeArenaSize = 192 * 1024 * 1024; + image_size = (std::max)(image_size, HomebrewCodeArenaSize); // Enable direct memory mapping in case of NCE. const u64 fastmem_base = [&]() -> size_t { @@ -297,31 +711,38 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, // Load codeset for current process codeset.memory = std::move(program_image); process.LoadModule(system.Kernel(), std::move(codeset), process.GetEntryPoint()); - if (!argv_string.empty()) { - constexpr u32 kEntryEndOfList = 0; - constexpr u32 kEntryMainThreadHandle = 1; - constexpr u32 kEntryArgv = 5; - constexpr u32 kEntryAppletType = 7; - constexpr u32 kAppletTypeApplication = 0; - + process.SetHomebrewInPlaceNextLoad(false); + { const u64 base = GetInteger(process.GetEntryPoint()); const u64 config_addr = base + args_offset_in_image; - const u64 argv_addr = config_addr + kConfigTableSize; - - const ConfigEntry entries[kNumEntries] = { - {kEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run() - {kEntryAppletType, 0, {kAppletTypeApplication, 0}}, - {kEntryArgv, 0, {0, argv_addr}}, - {kEntryEndOfList, 0, {0, 0}}, + const u64 next_load_path_addr = config_addr + HomebrewBaseConfigTableSize; + const u64 next_load_argv_addr = next_load_path_addr + HomebrewNextLoadPathSize; + const u64 argv_addr = next_load_argv_addr + HomebrewNextLoadArgvSize; + const u64 argv_entry_addr = argv_string.empty() ? 0 : argv_addr; + + const HomebrewConfigEntry entries[HomebrewBaseConfigEntryCount] = { + {HomebrewEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run() + {HomebrewEntryProcessHandle, 0, {0, 0}}, // Value[0] patched in Run() + {HomebrewEntryNextLoadPath, 0, {next_load_path_addr, next_load_argv_addr}}, + {HomebrewEntryAppletType, 0, {HomebrewAppletTypeApplication, 0}}, + {HomebrewEntryArgv, 0, {0, argv_entry_addr}}, + {HomebrewEntrySyscallAvailableHint, 0, {HomebrewAllSvcHints, HomebrewAllSvcHints}}, + {HomebrewEntryRandomSeed, 0, + {process.GetRandomEntropy(0), process.GetRandomEntropy(1)}}, + {HomebrewEntryHosVersion, 0, {HomebrewHosVersion, 0}}, + {HomebrewEntrySyscallAvailableHint2, 0, {HomebrewAllSvcHints, 0}}, + {HomebrewEntryEndOfList, 0, {0, 0}}, }; - process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries)); - process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(), argv_string.size()); - constexpr size_t kMainThreadHandleValueOffset = offsetof(ConfigEntry, value); - process.SetArgPointer(Kernel::KProcessAddress{config_addr}); + process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, + sizeof(entries)); + if (!argv_string.empty()) { + process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(), + argv_string.size()); + } if (exit_process_offset_in_image) { process.SetArgReturnAddress(Kernel::KProcessAddress{base + *exit_process_offset_in_image}); } - process.SetMainThreadHandleAddr(Kernel::KProcessAddress{config_addr + kMainThreadHandleValueOffset}); + SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr); } return true; @@ -329,7 +750,8 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, bool AppLoader_NRO::LoadNro(Core::System& system, Kernel::KProcess& process, const FileSys::VfsFile& nro_file) { - return LoadNroImpl(system, process, nro_file.ReadAllBytes()); + return LoadNroImpl(system, process, nro_file.ReadAllBytes(), nro_file.GetFullPath(), + nro_file.GetName()); } AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::System& system) { @@ -343,10 +765,13 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S u64 program_id{}; ReadProgramId(program_id); + const std::string argv0 = MakeHomebrewArgv0(file->GetFullPath(), file->GetName()); + const std::string homebrew_initial_cwd = GetHomebrewInitialCwd(argv0); system.GetFileSystemController().RegisterProcess( process.GetProcessId(), program_id, std::make_unique(*this, system.GetContentProvider(), - system.GetFileSystemController())); + system.GetFileSystemController()), + homebrew_initial_cwd); is_loaded = true; return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority, diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index d2928cba0e..a6370ef5b8 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -19,10 +22,15 @@ class NACP; namespace Kernel { class KProcess; +class KThread; } namespace Loader { +[[nodiscard]] bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, + Kernel::KThread& thread, const FileSys::VirtualFile& nro_file, + const std::string& nro_path, const std::string& launch_argv); + /// Loads an NRO file class AppLoader_NRO final : public AppLoader { public: