diff --git a/stratosphere/loader/source/ldr_map.cpp b/stratosphere/loader/source/ldr_map.cpp index c05157bca..3751fd592 100644 --- a/stratosphere/loader/source/ldr_map.cpp +++ b/stratosphere/loader/source/ldr_map.cpp @@ -70,7 +70,7 @@ Result MapUtils::LocateSpaceForMapModern(u64 *out, u64 out_size) { cur_base = address_space.map_end; } else { if (R_FAILED(svcQueryMemory(&mem_info, &page_info, cur_base))) { - /* TODO: panic. */ + std::abort(); } if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) { *out = cur_base; @@ -105,7 +105,7 @@ Result MapUtils::LocateSpaceForMapDeprecated(u64 *out, u64 out_size) { rc = ResultKernelOutOfMemory; while (true) { if (mem_info.type == 0x10) { - return rc; + return rc; } if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) { *out = cur_base; diff --git a/stratosphere/ro/source/ro_map.cpp b/stratosphere/ro/source/ro_map.cpp new file mode 100644 index 000000000..69046f9ae --- /dev/null +++ b/stratosphere/ro/source/ro_map.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "ro_map.hpp" + +bool MapUtils::CanAddGuardRegions(Handle process_handle, u64 address, u64 size) { + MemoryInfo mem_info; + u32 page_info; + + /* Nintendo doesn't validate SVC return values at all. */ + /* TODO: Should we allow these to fail? */ + if (R_FAILED(svcQueryProcessMemory(&mem_info, &page_info, process_handle, address - 1))) { + std::abort(); + } + if (mem_info.type == MemType_Unmapped && address - GuardRegionSize >= mem_info.addr) { + if (R_FAILED(svcQueryProcessMemory(&mem_info, &page_info, process_handle, address + size))) { + std::abort(); + } + return mem_info.type == MemType_Unmapped && address + size + GuardRegionSize <= mem_info.addr + mem_info.size; + } + + return false; +} + +Result MapUtils::LocateSpaceForMap(u64 *out, u64 out_size) { + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) { + return LocateSpaceForMapModern(out, out_size); + } else { + return LocateSpaceForMapDeprecated(out, out_size); + } +} + + +Result MapUtils::MapCodeMemoryForProcess(MappedCodeMemory &out_mcm, Handle process_handle, bool is_64_bit, u64 base_address, u64 size) { + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) { + return MapCodeMemoryForProcessModern(out_mcm, process_handle, base_address, size); + } else { + return MapCodeMemoryForProcessDeprecated(out_mcm, process_handle, is_64_bit, base_address, size); + } +} + +Result MapUtils::LocateSpaceForMapModern(u64 *out, u64 out_size) { + MemoryInfo mem_info = {}; + AddressSpaceInfo address_space = {}; + u32 page_info = 0; + u64 cur_base = 0, cur_end = 0; + Result rc; + + if (R_FAILED((rc = GetAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE)))) { + return rc; + } + + cur_base = address_space.addspace_base; + + rc = ResultKernelOutOfMemory; + cur_end = cur_base + out_size; + if (cur_end <= cur_base) { + return rc; + } + + while (true) { + if (address_space.heap_size && (address_space.heap_base <= cur_end - 1 && cur_base <= address_space.heap_end - 1)) { + /* If we overlap the heap region, go to the end of the heap region. */ + if (cur_base == address_space.heap_end) { + return rc; + } + cur_base = address_space.heap_end; + } else if (address_space.map_size && (address_space.map_base <= cur_end - 1 && cur_base <= address_space.map_end - 1)) { + /* If we overlap the map region, go to the end of the map region. */ + if (cur_base == address_space.map_end) { + return rc; + } + cur_base = address_space.map_end; + } else { + if (R_FAILED(svcQueryMemory(&mem_info, &page_info, cur_base))) { + std::abort(); + } + if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) { + *out = cur_base; + return ResultSuccess; + } + if (mem_info.addr + mem_info.size <= cur_base) { + return rc; + } + cur_base = mem_info.addr + mem_info.size; + if (cur_base >= address_space.addspace_end) { + return rc; + } + } + cur_end = cur_base + out_size; + if (cur_base + out_size <= cur_base) { + return rc; + } + } +} + + +Result MapUtils::LocateSpaceForMapDeprecated(u64 *out, u64 out_size) { + MemoryInfo mem_info = {}; + u32 page_info = 0; + Result rc; + + u64 cur_base = 0x8000000ULL; + if (R_FAILED((rc = svcQueryMemory(&mem_info, &page_info, cur_base)))) { + return rc; + } + + rc = ResultKernelOutOfMemory; + while (true) { + if (mem_info.type == 0x10) { + return rc; + } + if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) { + *out = cur_base; + return ResultSuccess; + } + u64 mem_end = mem_info.addr + mem_info.size; + if (mem_end < cur_base) { + return rc; + } + if (mem_end >> 31) { + break; + } + cur_base = mem_end; + if (R_FAILED((rc = svcQueryMemory(&mem_info, &page_info, cur_base)))) { + return rc; + } + } + return rc; +} + +Result MapUtils::MapCodeMemoryForProcessModern(MappedCodeMemory &out_mcm, Handle process_handle, u64 base_address, u64 size) { + AddressSpaceInfo address_space = {}; + Result rc; + + if (R_FAILED((rc = GetAddressSpaceInfo(&address_space, process_handle)))) { + return rc; + } + + if (size > address_space.addspace_size) { + return ResultRoInsufficientAddressSpace; + } + + u64 try_address; + for (unsigned int i = 0; i < LocateRetryCount; i++) { + while (true) { + try_address = address_space.addspace_base + (StratosphereRandomUtils::GetRandomU64((u64)(address_space.addspace_size - size) >> 12) << 12); + if (address_space.heap_size && (address_space.heap_base <= try_address + size - 1 && try_address <= address_space.heap_end - 1)) { + continue; + } + if (address_space.map_size && (address_space.map_base <= try_address + size - 1 && try_address <= address_space.map_end - 1)) { + continue; + } + break; + } + MappedCodeMemory tmp_mcm(process_handle, try_address, base_address, size); + rc = tmp_mcm.GetResult(); + if (rc == ResultKernelInvalidMemoryState) { + continue; + } + if (R_FAILED(rc)) { + return rc; + } + + if (!CanAddGuardRegions(process_handle, try_address, size)) { + continue; + } + + /* We're done searching. */ + out_mcm = std::move(tmp_mcm); + return ResultSuccess; + } + + return ResultRoInsufficientAddressSpace; +} + +Result MapUtils::MapCodeMemoryForProcessDeprecated(MappedCodeMemory &out_mcm, Handle process_handle, bool is_64_bit, u64 base_address, u64 size) { + Result rc; + u64 addspace_base, addspace_size; + if (is_64_bit) { + addspace_base = 0x8000000ULL; + addspace_size = 0x78000000ULL; + } else { + addspace_base = 0x200000ULL; + addspace_size = 0x3FE0000ULL; + } + + if (size > addspace_size) { + return ResultRoInsufficientAddressSpace; + } + + u64 try_address; + for (unsigned int i = 0; i < LocateRetryCount; i++) { + try_address = addspace_base + (StratosphereRandomUtils::GetRandomU64((u64)(addspace_size - size) >> 12) << 12); + + MappedCodeMemory tmp_mcm(process_handle, try_address, base_address, size); + rc = tmp_mcm.GetResult(); + if (rc == ResultKernelInvalidMemoryState) { + continue; + } + if (R_FAILED(rc)) { + return rc; + } + + if (!CanAddGuardRegions(process_handle, try_address, size)) { + continue; + } + + /* We're done searching. */ + out_mcm = std::move(tmp_mcm); + return ResultSuccess; + } + + return ResultRoInsufficientAddressSpace; +} + +Result MapUtils::GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h) { + Result rc; + if (R_FAILED((rc = svcGetInfo(&out->heap_base, 4, process_h, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->heap_size, 5, process_h, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->map_base, 2, process_h, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->map_size, 3, process_h, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->addspace_base, 12, process_h, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->addspace_size, 13, process_h, 0)))) { + return rc; + } + out->heap_end = out->heap_base + out->heap_size; + out->map_end = out->map_base + out->map_size; + out->addspace_end = out->addspace_base + out->addspace_size; + return ResultSuccess; +} \ No newline at end of file diff --git a/stratosphere/ro/source/ro_map.hpp b/stratosphere/ro/source/ro_map.hpp new file mode 100644 index 000000000..bfffee190 --- /dev/null +++ b/stratosphere/ro/source/ro_map.hpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +class MappedCodeMemory { + private: + Handle process_handle = INVALID_HANDLE; + Result result = ResultRoInternalError; + u64 dst_address = 0; + u64 src_address = 0; + u64 size = 0; + public: + MappedCodeMemory() : process_handle(INVALID_HANDLE), result(ResultRoInternalError), dst_address(0), src_address(0), size(0) { + /* ... */ + } + + MappedCodeMemory(Handle p_h, u64 dst, u64 src, u64 sz) : process_handle(p_h), dst_address(dst), src_address(src), size(sz) { + this->result = svcMapProcessCodeMemory(this->process_handle, this->dst_address, this->src_address, this->size); + } + + ~MappedCodeMemory() { + if (this->process_handle != INVALID_HANDLE && this->size > 0 && R_SUCCEEDED(this->result)) { + if (R_FAILED((this->result = svcUnmapProcessCodeMemory(this->process_handle, this->dst_address, this->src_address, this->size)))) { + std::abort(); + } + } + } + + u64 GetDstAddress() const { + return this->dst_address; + } + + Result GetResult() const { + return this->result; + } + + bool IsSuccess() const { + return R_SUCCEEDED(this->result); + } + + void Invalidate() { + this->process_handle = INVALID_HANDLE; + } + + MappedCodeMemory &operator=(MappedCodeMemory &&o) { + this->process_handle = o.process_handle; + this->result = o.result; + this->dst_address = o.dst_address; + this->src_address = o.src_address; + this->size = o.size; + o.Invalidate(); + return *this; + } +}; + +class AutoCloseMap { + private: + Handle process_handle; + Result result; + void *mapped_address; + u64 base_address; + u64 size; + public: + AutoCloseMap(void *mp, Handle p_h, u64 ba, u64 sz) : process_handle(p_h), mapped_address(mp), base_address(ba), size(sz) { + this->result = svcMapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size); + } + AutoCloseMap(u64 mp, Handle p_h, u64 ba, u64 sz) : process_handle(p_h), mapped_address(reinterpret_cast(mp)), base_address(ba), size(sz) { + this->result = svcMapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size); + } + + ~AutoCloseMap() { + if (this->process_handle != INVALID_HANDLE && R_SUCCEEDED(this->result)) { + if (R_FAILED((this->result = svcUnmapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size)))) { + std::abort(); + } + } + } + + Result GetResult() const { + return this->result; + } + + bool IsSuccess() const { + return R_SUCCEEDED(this->result); + } + + void Invalidate() { + this->process_handle = INVALID_HANDLE; + } +}; + + +class MapUtils { + public: + static constexpr size_t GuardRegionSize = 0x4000; + static constexpr size_t LocateRetryCount = 0x200; + public: + struct AddressSpaceInfo { + u64 heap_base; + u64 heap_size; + u64 heap_end; + u64 map_base; + u64 map_size; + u64 map_end; + u64 addspace_base; + u64 addspace_size; + u64 addspace_end; + }; + private: + static Result GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h); + static Result LocateSpaceForMapDeprecated(u64 *out, u64 out_size); + static Result LocateSpaceForMapModern(u64 *out, u64 out_size); + + static Result MapCodeMemoryForProcessDeprecated(MappedCodeMemory &out_mcm, Handle process_handle, bool is_64_bit, u64 base_address, u64 size); + static Result MapCodeMemoryForProcessModern(MappedCodeMemory &out_mcm, Handle process_handle, u64 base_address, u64 size); + public: + static Result LocateSpaceForMap(u64 *out, u64 out_size); + static Result MapCodeMemoryForProcess(MappedCodeMemory &out_mcm, Handle process_handle, bool is_64_bit, u64 base_address, u64 size); + static bool CanAddGuardRegions(Handle process_handle, u64 address, u64 size); +}; \ No newline at end of file diff --git a/stratosphere/ro/source/ro_nrr.cpp b/stratosphere/ro/source/ro_nrr.cpp new file mode 100644 index 000000000..ee752dc9c --- /dev/null +++ b/stratosphere/ro/source/ro_nrr.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "ro_nrr.hpp" +#include "ro_registration.hpp" + +Result NrrUtils::ValidateNrrSignature(const NrrHeader *header) { + /* TODO: Implement RSA-2048 PSS..... */ + + /* TODO: Check PSS fixed-key signature. */ + if (false) { + return ResultRoNotAuthorized; + } + + /* Check TitleID pattern is valid. */ + if ((header->title_id & header->title_id_mask) != header->title_id_pattern) { + return ResultRoNotAuthorized; + } + + /* TODO: Check PSS signature over hashes. */ + if (false) { + return ResultRoNotAuthorized; + } + + return ResultSuccess; +} + +Result NrrUtils::ValidateNrr(const NrrHeader *header, u64 size, u64 title_id, RoModuleType expected_type, bool enforce_type) { + if (header->magic != MagicNrr0) { + return ResultRoInvalidNrr; + } + if (header->nrr_size != size) { + return ResultRoInvalidSize; + } + + bool ease_nro_restriction = Registration::ShouldEaseNroRestriction(); + + /* Check signature. */ + Result rc = ValidateNrrSignature(header); + if (R_FAILED(rc)) { + if (!ease_nro_restriction) { + return rc; + } + } + + /* Check title id. */ + if (title_id != header->title_id) { + if (!ease_nro_restriction) { + return ResultRoInvalidNrr; + } + } + + /* Check type. */ + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_700) { + if (!enforce_type || expected_type != static_cast(header->nrr_type)) { + if (!ease_nro_restriction) { + return ResultRoInvalidNrrType; + } + } + } + + return ResultSuccess; +} \ No newline at end of file diff --git a/stratosphere/ro/source/ro_nrr.hpp b/stratosphere/ro/source/ro_nrr.hpp new file mode 100644 index 000000000..08e2204d9 --- /dev/null +++ b/stratosphere/ro/source/ro_nrr.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +#include + +enum RoModuleType : u32 { + RoModuleType_ForSelf = 0, + RoModuleType_ForOthers = 1, +}; + +struct NrrHeader { + u32 magic; + u32 _0x4; + u32 _0x8; + u32 _0xC; + u64 title_id_mask; + u64 title_id_pattern; + u64 _0x20; + u64 _0x28; + u8 modulus[0x100]; + u8 fixed_key_signature[0x100]; + u8 nrr_signature[0x100]; + u64 title_id; + u32 nrr_size; + u8 nrr_type; /* 7.0.0+ */ + u8 _0x33D[3]; + u32 hash_offset; + u32 num_hashes; + u64 _0x348; +}; +static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader definition!"); + +class NrrUtils { + public: + static constexpr u32 MagicNrr0 = 0x3052524E; + private: + static Result ValidateNrrSignature(const NrrHeader *header); + public: + static Result ValidateNrr(const NrrHeader *header, u64 size, u64 title_id, RoModuleType expected_type, bool enforce_type); +}; \ No newline at end of file diff --git a/stratosphere/ro/source/ro_registration.cpp b/stratosphere/ro/source/ro_registration.cpp index 03a8c7941..3697c675a 100644 --- a/stratosphere/ro/source/ro_registration.cpp +++ b/stratosphere/ro/source/ro_registration.cpp @@ -20,6 +20,8 @@ #include #include "ro_registration.hpp" +#include "ro_map.hpp" +#include "ro_nrr.hpp" /* Declare process contexts as global array. */ static Registration::RoProcessContext g_process_contexts[Registration::MaxSessions] = {}; @@ -45,6 +47,18 @@ void Registration::Initialize() { } } +bool Registration::ShouldEaseNroRestriction() { + bool should_ease = false; + + if (R_FAILED(setsysGetSettingsItemValue("ro", "ease_nro_restriction", &should_ease, sizeof(should_ease)))) { + return false; + } + + /* Nintendo only allows easing restriction on dev, we will allow on production, as well. */ + /* should_ease &= g_is_development_function_enabled; */ + return should_ease; +} + Result Registration::RegisterProcess(RoProcessContext **out_context, Handle process_handle, u64 process_id) { /* Check if a process context already exists. */ for (size_t i = 0; i < Registration::MaxSessions; i++) { @@ -132,7 +146,7 @@ Result Registration::LoadNrr(RoProcessContext *context, u64 title_id, u64 nrr_ad /* Map. */ NrrHeader *header = nullptr; u64 mapped_code_address = 0; - Result rc = MapAndValidateNrr(&header, &mapped_code_address, context->process_handle, title_id, nrr_address, nrr_size); + Result rc = MapAndValidateNrr(&header, &mapped_code_address, context->process_handle, title_id, nrr_address, nrr_size, expected_type, enforce_type); if (R_FAILED(rc)) { return rc; } @@ -179,9 +193,45 @@ Result Registration::UnloadNrr(RoProcessContext *context, u64 nrr_address) { return UnmapNrr(context->process_handle, nrr_info.header, nrr_info.nrr_heap_address, nrr_info.nrr_heap_size, nrr_info.mapped_code_address); } -Result Registration::MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, u64 title_id, u64 nrr_heap_address, u64 nrr_heap_size) { - /* TODO */ - return ResultKernelConnectionClosed; +Result Registration::MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, u64 title_id, u64 nrr_heap_address, u64 nrr_heap_size, RoModuleType expected_type, bool enforce_type) { + Result rc; + MappedCodeMemory nrr_mcm; + + /* First, map the NRR. */ + if (R_FAILED((rc = MapUtils::MapCodeMemoryForProcess(nrr_mcm, process_handle, true, nrr_heap_address, nrr_heap_size)))) { + if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) { + /* Try mapping as 32-bit, since we might have guessed wrong on < 3.0.0. */ + rc = MapUtils::MapCodeMemoryForProcess(nrr_mcm, process_handle, false, nrr_heap_address, nrr_heap_size); + } + if (R_FAILED(rc)) { + return rc; + } + } + + const u64 code_address = nrr_mcm.GetDstAddress(); + u64 map_address; + if (R_FAILED(MapUtils::LocateSpaceForMap(&map_address, nrr_heap_size))) { + return ResultRoInsufficientAddressSpace; + } + + /* Nintendo...does not check the return value of this map. We will check, instead of aborting if it fails. */ + AutoCloseMap nrr_map(map_address, process_handle, code_address, nrr_heap_size); + if (!nrr_map.IsSuccess()) { + return nrr_map.GetResult(); + } + + NrrHeader *nrr_header = reinterpret_cast(map_address); + if (R_FAILED((rc = NrrUtils::ValidateNrr(nrr_header, nrr_heap_size, title_id, expected_type, enforce_type)))) { + return rc; + } + + /* Invalidation here actually prevents them from unmapping at scope exit. */ + nrr_map.Invalidate(); + nrr_mcm.Invalidate(); + + *out_header = nrr_header; + *out_mapped_code_address = code_address; + return ResultSuccess; } Result Registration::UnmapNrr(Handle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address) { diff --git a/stratosphere/ro/source/ro_registration.hpp b/stratosphere/ro/source/ro_registration.hpp index bc1057c33..30f0910a3 100644 --- a/stratosphere/ro/source/ro_registration.hpp +++ b/stratosphere/ro/source/ro_registration.hpp @@ -19,10 +19,7 @@ #include -enum RoModuleType : u32 { - RoModuleType_ForSelf = 0, - RoModuleType_ForOthers = 1, -}; +#include "ro_nrr.hpp" class Registration { public: @@ -31,27 +28,6 @@ class Registration { static constexpr size_t MaxNrrInfos = 0x40; static constexpr size_t MaxNroInfos = 0x40; public: - struct NrrHeader { - u32 magic; - u32 _0x4; - u32 _0x8; - u32 _0xC; - u64 title_id_mask; - u64 title_id_pattern; - u64 _0x20; - u64 _0x28; - u8 modulus[0x100]; - u8 fixed_key_signature[0x100]; - u8 nrr_signature[0x100]; - u64 title_id_min; - u32 nrr_size; - u8 nrr_type; /* 7.0.0+ */ - u8 _0x33D[3]; - u32 hash_offset; - u32 num_hashes; - u64 _0x348; - }; - static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader definition!"); struct NroHeader { u32 entrypoint_insn; @@ -105,10 +81,11 @@ class Registration { bool in_use; }; private: - static Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, u64 title_id, u64 nrr_heap_address, u64 nrr_heap_size); + static Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, Handle process_handle, u64 title_id, u64 nrr_heap_address, u64 nrr_heap_size, RoModuleType expected_type, bool enforce_type); static Result UnmapNrr(Handle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address); public: static void Initialize(); + static bool ShouldEaseNroRestriction(); static Result RegisterProcess(RoProcessContext **out_context, Handle process_handle, u64 process_id); static void UnregisterProcess(RoProcessContext *context);