mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-11-15 16:14:37 +01:00
[core] Support libnx homebrew NextLoad handoff
Implement the homebrew NextLoad path used by libnx NROs to request another NRO from svcExitProcess. Keep the existing process alive, rebuild the homebrew config and argv buffers, reset thread context, refresh process metadata, and add memory/address-space fallbacks needed for repeated in-place handoffs. Reference: https://switchbrew.github.io/libnx/env_8h.html
This commit is contained in:
parent
3469f3789f
commit
6bad422d08
10 changed files with 982 additions and 90 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<uint64_t
|
|||
}
|
||||
|
||||
static void SvcWrap_ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess64From32(system);
|
||||
ExitProcess64From32(system, args);
|
||||
}
|
||||
|
||||
static void SvcWrap_CreateThread64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
|
|
@ -1298,7 +1299,7 @@ static void SvcWrap_QueryMemory64(Core::System& system, std::span<uint64_t, 8> a
|
|||
}
|
||||
|
||||
static void SvcWrap_ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess64(system);
|
||||
ExitProcess64(system, args);
|
||||
}
|
||||
|
||||
static void SvcWrap_CreateThread64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
|
|
|
|||
|
|
@ -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<uint64_t, 8> 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<uint64_t, 8> 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<uint64_t, 8> 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);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#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<HomebrewInPlaceMemoryBlock> 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<u64>)(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<u32>(info.GetState()),
|
||||
static_cast<u32>(info.GetSvcState()),
|
||||
static_cast<u32>(info.GetPermission()),
|
||||
static_cast<u32>(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<u32>(block.state),
|
||||
static_cast<u32>(block.permission), static_cast<u32>(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<HomebrewInPlaceDeviceSharedBlock> 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<u64>)(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,
|
||||
|
|
|
|||
|
|
@ -4,17 +4,216 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#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<uint64_t, 8> 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<Service::Nvidia::NVDRV>("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<DeviceSharedBlock> 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<u64>)(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<Service::HID::IHidServer>("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<uint64_t, 8> 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<uint64_t, 8> args) {
|
||||
ExitProcess(system, args);
|
||||
}
|
||||
|
||||
Result GetProcessId64From32(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue