Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"
This reverts commit 15b7df8ef1.
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ro_nro_utils.hpp"
|
||||
#include "ro_random.hpp"
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
ALWAYS_INLINE size_t SetupNroProcessMemoryRegions(os::ProcessMemoryRegion *regions, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) {
|
||||
/* Reset region count. */
|
||||
size_t num_regions = 0;
|
||||
|
||||
/* We always want a region for the nro. */
|
||||
regions[num_regions++] = { nro_heap_address, nro_heap_size };
|
||||
|
||||
/* If we have bss, create a region for bss. */
|
||||
if (bss_heap_size > 0) {
|
||||
regions[num_regions++] = { bss_heap_address, bss_heap_size };
|
||||
}
|
||||
|
||||
return num_regions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result MapNro(u64 *out_base_address, os::NativeHandle process_handle, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) {
|
||||
/* Set up the process memory regions. */
|
||||
os::ProcessMemoryRegion regions[2];
|
||||
const size_t num_regions = SetupNroProcessMemoryRegions(regions, nro_heap_address, nro_heap_size, bss_heap_address, bss_heap_size);
|
||||
|
||||
/* Re-map the nro/bss as code memory in the destination process. */
|
||||
R_TRY_CATCH(os::MapProcessCodeMemory(out_base_address, process_handle, regions, num_regions, ro::impl::GenerateSecureRandom)) {
|
||||
R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result SetNroPerms(os::NativeHandle process_handle, u64 base_address, u64 rx_size, u64 ro_size, u64 rw_size, bool is_aligned_header) {
|
||||
const u64 rx_offset = is_aligned_header ? os::MemoryPageSize : 0;
|
||||
const u64 ro_offset = rx_offset + rx_size;
|
||||
const u64 rw_offset = ro_offset + ro_size;
|
||||
|
||||
if (is_aligned_header) {
|
||||
R_TRY(os::SetProcessMemoryPermission(process_handle, base_address, os::MemoryPageSize, os::MemoryPermission_ReadOnly));
|
||||
}
|
||||
|
||||
R_TRY(os::SetProcessMemoryPermission(process_handle, base_address + rx_offset, rx_size, os::MemoryPermission_ReadExecute));
|
||||
R_TRY(os::SetProcessMemoryPermission(process_handle, base_address + ro_offset, ro_size, os::MemoryPermission_ReadOnly));
|
||||
R_TRY(os::SetProcessMemoryPermission(process_handle, base_address + rw_offset, rw_size, os::MemoryPermission_ReadWrite));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result UnmapNro(os::NativeHandle process_handle, u64 base_address, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) {
|
||||
/* Set up the process memory regions. */
|
||||
os::ProcessMemoryRegion regions[2];
|
||||
const size_t num_regions = SetupNroProcessMemoryRegions(regions, nro_heap_address, nro_heap_size, bss_heap_address, bss_heap_size);
|
||||
|
||||
/* Unmap the nro/bss. */
|
||||
R_RETURN(os::UnmapProcessCodeMemory(process_handle, base_address, regions, num_regions));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
/* Utilities for working with NROs. */
|
||||
Result MapNro(u64 *out_base_address, os::NativeHandle process_handle, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size);
|
||||
Result SetNroPerms(os::NativeHandle process_handle, u64 base_address, u64 rx_size, u64 ro_size, u64 rw_size, bool is_aligned_header);
|
||||
Result UnmapNro(os::NativeHandle process_handle, u64 base_address, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size);
|
||||
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ro_nrr_utils.hpp"
|
||||
#include "ro_random.hpp"
|
||||
#include "ro_service_impl.hpp"
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t KeyGenerationMax100 = 0;
|
||||
constexpr size_t KeyGenerationMax910 = 1;
|
||||
constexpr size_t KeyGenerationMax = std::max(KeyGenerationMax100, KeyGenerationMax910);
|
||||
constexpr size_t KeyGenerationCount = KeyGenerationMax + 1;
|
||||
|
||||
constexpr size_t RsaKeySize = 0x100;
|
||||
|
||||
constexpr const u8 Exponent[3] = { 0x01, 0x00, 0x01 };
|
||||
|
||||
constexpr const u8 DevModuli[KeyGenerationCount][RsaKeySize] = {
|
||||
{
|
||||
0xC1, 0x15, 0x7C, 0x02, 0x26, 0xE5, 0x35, 0x6F, 0x99, 0xDB, 0xBE, 0xBD, 0xD7, 0x01, 0x07, 0x1C,
|
||||
0xC2, 0x3D, 0x1E, 0x6B, 0x7E, 0x08, 0x07, 0xBC, 0xE2, 0x6D, 0x49, 0xEC, 0x0B, 0xFF, 0xE4, 0x91,
|
||||
0x8C, 0x62, 0xB9, 0xFC, 0x69, 0xBF, 0x3A, 0xB6, 0x6C, 0x3A, 0x5D, 0x0E, 0x31, 0x5E, 0x6C, 0x1D,
|
||||
0x9A, 0x21, 0xD9, 0x9D, 0xD3, 0xB8, 0x50, 0x5F, 0x27, 0x7C, 0x4A, 0xD2, 0xFE, 0xE8, 0xDA, 0x1C,
|
||||
0xB9, 0x9E, 0x7E, 0x1E, 0x2F, 0x7D, 0xF9, 0x70, 0xA2, 0x98, 0x19, 0x6A, 0x53, 0x40, 0x64, 0xE7,
|
||||
0xC0, 0x92, 0xAE, 0x64, 0xD2, 0x01, 0xB2, 0x49, 0x30, 0x19, 0x7F, 0xF8, 0x6E, 0x0D, 0x49, 0x35,
|
||||
0xE9, 0x95, 0x77, 0x00, 0x65, 0xC5, 0x1E, 0xF5, 0x2A, 0xF9, 0xA1, 0x52, 0xA0, 0xA4, 0xFA, 0x87,
|
||||
0x3D, 0x8F, 0x51, 0xEC, 0x02, 0x80, 0xA4, 0xC7, 0x22, 0x74, 0xEF, 0x56, 0x61, 0x71, 0x39, 0xE2,
|
||||
0x2F, 0x03, 0x82, 0xDB, 0x50, 0xE9, 0xCC, 0x60, 0x48, 0x46, 0x71, 0xE2, 0xC6, 0x71, 0xF3, 0xF9,
|
||||
0x85, 0x52, 0x1A, 0xE2, 0xA8, 0x18, 0x77, 0x86, 0xD0, 0x12, 0xEB, 0x4F, 0x81, 0xA0, 0xDF, 0x20,
|
||||
0x42, 0xF0, 0xF8, 0xE3, 0x00, 0xE6, 0xFC, 0xA7, 0x44, 0xF0, 0xDC, 0x2B, 0x5B, 0xA0, 0xD3, 0x01,
|
||||
0x34, 0xD0, 0xD7, 0xFD, 0xEF, 0x66, 0x92, 0xB3, 0x87, 0x64, 0xD9, 0x76, 0xDA, 0x6E, 0x3A, 0x19,
|
||||
0x98, 0x1F, 0xBD, 0x1F, 0x25, 0x69, 0x9F, 0x28, 0xE6, 0x9E, 0xB7, 0x38, 0x92, 0x12, 0x16, 0xDE,
|
||||
0xDA, 0xE2, 0xB9, 0x7E, 0xFA, 0x98, 0x94, 0xF4, 0x9A, 0xDF, 0x2D, 0xC0, 0x99, 0x83, 0x61, 0xAD,
|
||||
0xB8, 0x3E, 0x27, 0x3F, 0x0E, 0xB8, 0x9E, 0x9B, 0x11, 0x78, 0xF1, 0x06, 0x30, 0x5B, 0xCA, 0xF4,
|
||||
0xEB, 0x72, 0x20, 0xD3, 0x15, 0x15, 0xC0, 0xC7, 0x1A, 0x08, 0xAE, 0x6E, 0xB2, 0x02, 0x43, 0xE9,
|
||||
},
|
||||
{
|
||||
0xB0, 0x3A, 0xC3, 0x11, 0x58, 0xAC, 0x95, 0x9D, 0xED, 0x88, 0x80, 0xD9, 0x93, 0x71, 0x8E, 0xA0,
|
||||
0xBD, 0x19, 0x68, 0x6A, 0x06, 0x63, 0x7F, 0x06, 0x93, 0xBA, 0x43, 0x24, 0x5E, 0xD0, 0x54, 0x76,
|
||||
0x2F, 0x8D, 0x6A, 0xF4, 0x7B, 0x16, 0x7A, 0x68, 0xE2, 0xE6, 0x95, 0x82, 0x57, 0xD9, 0x01, 0x34,
|
||||
0x87, 0x42, 0x24, 0x83, 0x91, 0x7F, 0xE7, 0xB4, 0xB7, 0xE5, 0x8B, 0x4B, 0x08, 0x1F, 0x70, 0x58,
|
||||
0x5A, 0x30, 0xE0, 0xC9, 0xAB, 0xB8, 0xB1, 0x96, 0xFA, 0x45, 0xA2, 0xF7, 0x3C, 0x94, 0x65, 0x97,
|
||||
0xE2, 0x72, 0x7A, 0x19, 0xE2, 0x5F, 0x30, 0xF0, 0xA9, 0x44, 0xD8, 0x4B, 0x00, 0xEE, 0xD0, 0x99,
|
||||
0xA7, 0xAB, 0xC7, 0x14, 0x94, 0xE8, 0xD8, 0x70, 0x8F, 0xC7, 0x3C, 0x95, 0x0F, 0xE9, 0x52, 0x44,
|
||||
0x7A, 0xA4, 0xCA, 0xB4, 0xCE, 0x31, 0xCD, 0xC8, 0xC0, 0xF3, 0x08, 0x51, 0xA3, 0xC1, 0x5D, 0xA5,
|
||||
0x3E, 0x4A, 0xCE, 0xE8, 0x17, 0xA2, 0xED, 0xB8, 0x94, 0xD7, 0x3C, 0xF0, 0x42, 0x5A, 0xC1, 0x83,
|
||||
0xE8, 0x65, 0x28, 0x79, 0x43, 0x2C, 0x17, 0xBB, 0x68, 0x91, 0x80, 0xE3, 0xDA, 0x6B, 0xD4, 0x0B,
|
||||
0xA3, 0x6A, 0x8B, 0xDB, 0x1E, 0x2E, 0x16, 0xFE, 0xAB, 0x81, 0x16, 0x84, 0x6E, 0x53, 0xFD, 0xB6,
|
||||
0x1F, 0x12, 0xDE, 0x77, 0x79, 0xE6, 0x9A, 0x02, 0x95, 0x6B, 0x22, 0xFF, 0xE3, 0x10, 0xD2, 0x0D,
|
||||
0x5B, 0x33, 0xB1, 0xC0, 0x5C, 0xF6, 0xAC, 0x69, 0xAF, 0xCF, 0xD2, 0x34, 0x4B, 0x76, 0x88, 0xC1,
|
||||
0x91, 0x66, 0x6C, 0x2F, 0x06, 0xBA, 0x2B, 0xC7, 0x35, 0x0A, 0xD8, 0xD7, 0x5C, 0xF1, 0x20, 0x67,
|
||||
0x31, 0x4A, 0x9B, 0xB7, 0xCA, 0x4A, 0x3F, 0xAE, 0x4F, 0xF3, 0x02, 0x71, 0x97, 0xD6, 0x78, 0xC6,
|
||||
0x8E, 0x4B, 0x41, 0x78, 0x5B, 0x56, 0x6F, 0x6C, 0xA0, 0x0D, 0x83, 0x28, 0x25, 0x07, 0x75, 0x15,
|
||||
}
|
||||
};
|
||||
|
||||
constexpr const u8 ProdModuli[KeyGenerationCount][RsaKeySize] = {
|
||||
{
|
||||
0xCF, 0x2F, 0x54, 0xF1, 0x7D, 0xA3, 0x11, 0x47, 0xC3, 0xBD, 0xED, 0x10, 0x3D, 0x64, 0xF8, 0x6B,
|
||||
0x51, 0x62, 0x05, 0x6F, 0x10, 0x5F, 0x50, 0x76, 0xF2, 0x40, 0x09, 0xA3, 0x16, 0xFC, 0xC3, 0xA8,
|
||||
0x75, 0xFD, 0x7C, 0xB4, 0x38, 0x9D, 0x30, 0x5C, 0x02, 0x6E, 0x2C, 0xCF, 0x03, 0x84, 0x23, 0x64,
|
||||
0x4A, 0xF9, 0x90, 0x44, 0x58, 0xAF, 0x43, 0x19, 0xD2, 0x27, 0xF1, 0x60, 0x28, 0xB8, 0x7B, 0x26,
|
||||
0xD3, 0x21, 0xE8, 0xF0, 0xED, 0x65, 0x01, 0x31, 0x51, 0x45, 0xD3, 0x93, 0xCF, 0x07, 0x65, 0xF1,
|
||||
0x8A, 0x72, 0x61, 0x55, 0x63, 0x91, 0x84, 0x63, 0x2C, 0x0B, 0x3E, 0xF6, 0x7E, 0x9B, 0x19, 0x4C,
|
||||
0x42, 0xE3, 0xB9, 0x83, 0xF3, 0xA1, 0x2B, 0x7B, 0xA1, 0x5F, 0x00, 0xDC, 0x80, 0x1A, 0x6D, 0x6C,
|
||||
0x22, 0x20, 0x2F, 0x19, 0x61, 0x96, 0x5E, 0xD9, 0x93, 0x45, 0x62, 0x48, 0xC1, 0xB1, 0x48, 0x8A,
|
||||
0x07, 0x6C, 0xBB, 0x3D, 0xCC, 0xE0, 0xEF, 0x43, 0x56, 0x1D, 0x2A, 0x1D, 0x8E, 0x19, 0xBF, 0x99,
|
||||
0x7A, 0x56, 0x9B, 0x72, 0xB9, 0x49, 0x86, 0xD4, 0x5D, 0xA0, 0xE5, 0x40, 0x2C, 0x2D, 0x31, 0x5A,
|
||||
0x68, 0x90, 0xA4, 0xDA, 0xED, 0x52, 0xCF, 0xE4, 0x3C, 0x35, 0x5E, 0x94, 0xC8, 0x36, 0x6E, 0x5F,
|
||||
0xF5, 0x50, 0x9D, 0xBD, 0x3F, 0x86, 0x28, 0xC4, 0xF7, 0x0D, 0xFA, 0x1D, 0x65, 0xCE, 0x7F, 0x83,
|
||||
0x8E, 0xEE, 0x75, 0x8B, 0x8D, 0xF3, 0x1C, 0x3E, 0x8B, 0xB7, 0x31, 0x60, 0x0E, 0x33, 0xC0, 0x8C,
|
||||
0xEF, 0xBA, 0xD0, 0xF0, 0x56, 0x35, 0x11, 0x70, 0x3D, 0x3A, 0x60, 0xFF, 0xBB, 0xC8, 0x49, 0xB3,
|
||||
0x50, 0x93, 0xB5, 0x0C, 0x4D, 0x62, 0x2A, 0x53, 0x76, 0x69, 0x5D, 0x81, 0x35, 0x79, 0x08, 0xAE,
|
||||
0x08, 0x7D, 0x51, 0x72, 0xE4, 0xE9, 0xD9, 0xBE, 0x91, 0xB9, 0x29, 0x89, 0xDD, 0xC9, 0xF9, 0xDB,
|
||||
},
|
||||
{
|
||||
0xBD, 0x8E, 0x75, 0x8D, 0x6A, 0x5E, 0xCF, 0x1A, 0x68, 0x7D, 0x9D, 0x06, 0x80, 0x60, 0x48, 0x56,
|
||||
0x73, 0x13, 0xB8, 0x8D, 0xA5, 0x83, 0x11, 0xE3, 0xF4, 0xB5, 0xBF, 0x21, 0x56, 0x50, 0x6F, 0x68,
|
||||
0xFD, 0x2E, 0xEF, 0x3A, 0xE1, 0x7D, 0x5D, 0xA2, 0x02, 0x21, 0xAD, 0x57, 0x7F, 0xA9, 0xD1, 0x95,
|
||||
0xA6, 0x5C, 0x80, 0x67, 0x2A, 0xAE, 0x29, 0xCD, 0x98, 0x4C, 0x71, 0x98, 0x22, 0xD3, 0x39, 0x77,
|
||||
0xA3, 0x39, 0xE8, 0xB0, 0x7E, 0x77, 0x4D, 0x54, 0xA5, 0x9F, 0xF3, 0xF1, 0xF0, 0x85, 0x74, 0x64,
|
||||
0xF7, 0x17, 0xAC, 0xEB, 0x95, 0xAD, 0x01, 0x3A, 0xA2, 0xFB, 0x18, 0xEC, 0x6C, 0x06, 0xD5, 0xBD,
|
||||
0x0F, 0x66, 0x99, 0x2C, 0xEE, 0x81, 0x87, 0x40, 0xDA, 0xBC, 0x30, 0xC9, 0x54, 0x21, 0x46, 0x6B,
|
||||
0xD5, 0xAE, 0xDF, 0xA8, 0x8F, 0x61, 0x19, 0x5E, 0x75, 0x68, 0xED, 0x80, 0x73, 0x5F, 0x4D, 0xF2,
|
||||
0xC2, 0x09, 0xC8, 0x2D, 0x73, 0x12, 0x18, 0x35, 0x7F, 0x5E, 0xA8, 0xAD, 0x37, 0x6B, 0x9C, 0x14,
|
||||
0x8C, 0x4E, 0x7D, 0xC0, 0xDC, 0x42, 0x45, 0xDD, 0x84, 0x5A, 0x0B, 0xED, 0x97, 0xBE, 0x66, 0xD1,
|
||||
0xC0, 0xC4, 0x7C, 0x48, 0x66, 0x22, 0xAA, 0xFB, 0x47, 0x95, 0x84, 0x45, 0x2E, 0x70, 0x35, 0x68,
|
||||
0x04, 0xF0, 0xBC, 0xEE, 0xCB, 0x6E, 0xB7, 0x09, 0xBA, 0xD9, 0xEA, 0xF9, 0x78, 0x32, 0x4C, 0xD5,
|
||||
0x0F, 0x6E, 0xEF, 0x4D, 0x21, 0xE0, 0xE7, 0xEA, 0x17, 0xA3, 0xEC, 0x0D, 0x76, 0xC0, 0xB9, 0xCC,
|
||||
0x1B, 0x8C, 0x7E, 0xFB, 0x56, 0x5B, 0x60, 0x94, 0x26, 0xB7, 0x9F, 0x2B, 0xAD, 0xC6, 0x1F, 0xBD,
|
||||
0xB6, 0xFA, 0x33, 0xDB, 0x1C, 0xBC, 0x28, 0xE1, 0x85, 0x1E, 0x2F, 0xF9, 0xFD, 0xEA, 0x83, 0x52,
|
||||
0x2F, 0xEE, 0x07, 0x7C, 0x1E, 0x2F, 0xCD, 0x76, 0x91, 0xEA, 0x27, 0xE6, 0x50, 0x9A, 0x86, 0x23,
|
||||
}
|
||||
};
|
||||
|
||||
/* Helper functions. */
|
||||
|
||||
Result GetCertificationModulus(const u8 **out, u32 key_generation) {
|
||||
if (hos::GetVersion() >= hos::Version_9_1_0) {
|
||||
R_UNLESS(key_generation <= KeyGenerationMax910, ro::ResultNotAuthorized());
|
||||
} else {
|
||||
R_UNLESS(key_generation <= KeyGenerationMax100, ro::ResultNotAuthorized());
|
||||
}
|
||||
|
||||
*out = IsDevelopmentHardware() ? DevModuli[key_generation] : ProdModuli[key_generation];
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ValidateNrrCertification(const NrrHeader *header, const u8 *mod) {
|
||||
static_assert(RsaKeySize == NrrCertification::RsaKeySize);
|
||||
|
||||
/* Verify the signature. */
|
||||
const u8 *sig = header->GetCertificationSignature();
|
||||
const size_t sig_size = RsaKeySize;
|
||||
const size_t mod_size = RsaKeySize;
|
||||
const u8 *exp = Exponent;
|
||||
const size_t exp_size = util::size(Exponent);
|
||||
const u8 *msg = header->GetCertificationSignedArea();
|
||||
const size_t msg_size = NrrCertification::SignedSize;
|
||||
R_UNLESS(crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size), ro::ResultNotAuthorized());
|
||||
|
||||
/* Check ProgramId pattern is valid. */
|
||||
R_UNLESS(header->IsProgramIdValid(), ro::ResultNotAuthorized());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ValidateNrrSignature(const NrrHeader *header) {
|
||||
static_assert(RsaKeySize == NrrCertification::RsaKeySize);
|
||||
|
||||
/* Verify the signature. */
|
||||
const u8 *sig = header->GetSignature();
|
||||
const size_t sig_size = RsaKeySize;
|
||||
const u8 *mod = header->GetCertificationModulus();
|
||||
const size_t mod_size = RsaKeySize;
|
||||
const u8 *exp = Exponent;
|
||||
const size_t exp_size = util::size(Exponent);
|
||||
const u8 *msg = header->GetSignedArea();
|
||||
const size_t msg_size = header->GetSignedAreaSize();
|
||||
R_UNLESS(crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size), ro::ResultNotAuthorized());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ValidateNrr(const NrrHeader *header, u64 size, ncm::ProgramId program_id, NrrKind nrr_kind, bool enforce_nrr_kind) {
|
||||
/* Check magic. */
|
||||
R_UNLESS(header->IsMagicValid(), ro::ResultInvalidNrr());
|
||||
|
||||
/* Check size. */
|
||||
R_UNLESS(header->GetSize() == size, ro::ResultInvalidSize());
|
||||
|
||||
/* Only perform checks if we must. */
|
||||
const bool ease_nro_restriction = ShouldEaseNroRestriction();
|
||||
|
||||
/* Get certification modulus. */
|
||||
/* NOTE: This must succeed even when ease_nro_restriction is true. */
|
||||
const u8 *modulus;
|
||||
R_TRY(GetCertificationModulus(std::addressof(modulus), header->GetKeyGeneration()));
|
||||
|
||||
if (!ease_nro_restriction) {
|
||||
/* Check certification signature. */
|
||||
R_TRY(ValidateNrrCertification(header, modulus));
|
||||
|
||||
/* Check NRR signature. */
|
||||
R_TRY(ValidateNrrSignature(header));
|
||||
|
||||
/* Check program id. */
|
||||
R_UNLESS(header->GetProgramId() == program_id, ro::ResultInvalidNrr());
|
||||
|
||||
/* Check nrr kind. */
|
||||
if (hos::GetVersion() >= hos::Version_7_0_0 && enforce_nrr_kind) {
|
||||
R_UNLESS(header->GetNrrKind() == nrr_kind, ro::ResultInvalidNrrKind());
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Utilities for working with NRRs. */
|
||||
Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, void *out_hash, size_t out_hash_size, os::NativeHandle process_handle, ncm::ProgramId program_id, u64 nrr_heap_address, u64 nrr_heap_size, NrrKind nrr_kind, bool enforce_nrr_kind) {
|
||||
/* Re-map the nrr as code memory in the destination process. */
|
||||
u64 code_address = 0;
|
||||
const os::ProcessMemoryRegion region = { nrr_heap_address, nrr_heap_size };
|
||||
R_TRY_CATCH(os::MapProcessCodeMemory(std::addressof(code_address), process_handle, std::addressof(region), 1, ro::impl::GenerateSecureRandom)) {
|
||||
R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* If we fail, unmap the nrr code memory. */
|
||||
ON_RESULT_FAILURE { R_ABORT_UNLESS(os::UnmapProcessCodeMemory(process_handle, code_address, std::addressof(region), 1)); };
|
||||
|
||||
/* Map the nrr in our process. */
|
||||
void *mapped_memory = nullptr;
|
||||
R_TRY_CATCH(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, code_address, region.size, ro::impl::GenerateSecureRandom)) {
|
||||
R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* If we fail, unmap the nrr memory. */
|
||||
ON_RESULT_FAILURE_2 { os::UnmapProcessMemory(mapped_memory, process_handle, code_address, region.size); };
|
||||
|
||||
/* Validate the nrr header. */
|
||||
NrrHeader *nrr_header = static_cast<NrrHeader *>(mapped_memory);
|
||||
R_TRY(ValidateNrr(nrr_header, nrr_heap_size, program_id, nrr_kind, enforce_nrr_kind));
|
||||
|
||||
/* Save a copy of the hash that we verified. */
|
||||
crypto::GenerateSha256(out_hash, out_hash_size, nrr_header->GetSignedArea(), nrr_header->GetSignedAreaSize());
|
||||
|
||||
*out_header = nrr_header;
|
||||
*out_mapped_code_address = code_address;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result UnmapNrr(os::NativeHandle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address) {
|
||||
/* Unmap our process mapping. */
|
||||
os::UnmapProcessMemory(const_cast<NrrHeader *>(header), process_handle, mapped_code_address, nrr_heap_size);
|
||||
|
||||
/* Unmap the code memory mapping. */
|
||||
const os::ProcessMemoryRegion region = { nrr_heap_address, nrr_heap_size };
|
||||
R_RETURN(os::UnmapProcessCodeMemory(process_handle, mapped_code_address, std::addressof(region), 1));
|
||||
}
|
||||
|
||||
bool ValidateNrrHashTableEntry(const void *signed_area, size_t signed_area_size, size_t hashes_offset, size_t num_hashes, const void *nrr_hash, const u8 *hash_table, const void *desired_hash) {
|
||||
crypto::Sha256Generator sha256;
|
||||
sha256.Initialize();
|
||||
|
||||
/* Hash data before the hash table. */
|
||||
const size_t pre_hash_table_size = hashes_offset - NrrHeader::GetSignedAreaOffset();
|
||||
sha256.Update(signed_area, pre_hash_table_size);
|
||||
|
||||
/* Hash the hash table, checking if the desired hash exists inside it. */
|
||||
size_t remaining_size = signed_area_size - pre_hash_table_size;
|
||||
bool found_hash = false;
|
||||
for (size_t i = 0; i < num_hashes; i++) {
|
||||
/* Get the current hash. */
|
||||
u8 cur_hash[crypto::Sha256Generator::HashSize];
|
||||
std::memcpy(cur_hash, hash_table, sizeof(cur_hash));
|
||||
|
||||
/* Hash the current hash. */
|
||||
sha256.Update(cur_hash, sizeof(cur_hash));
|
||||
|
||||
/* Check if the current hash is our target. */
|
||||
found_hash |= std::memcmp(cur_hash, desired_hash, sizeof(cur_hash)) == 0;
|
||||
|
||||
/* Advance our pointers. */
|
||||
hash_table += sizeof(cur_hash);
|
||||
remaining_size -= sizeof(cur_hash);
|
||||
}
|
||||
|
||||
/* Data after the hash table should be all zeroes. */
|
||||
u8 work_buf[crypto::Sha256Generator::HashSize];
|
||||
{
|
||||
crypto::ClearMemory(work_buf, sizeof(work_buf));
|
||||
while (remaining_size > 0) {
|
||||
const size_t cur_size = std::min(remaining_size, sizeof(work_buf));
|
||||
sha256.Update(work_buf, cur_size);
|
||||
remaining_size -= cur_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate the final hash. */
|
||||
sha256.GetHash(work_buf, sizeof(work_buf));
|
||||
|
||||
/* Use & operator to avoid short circuiting. */
|
||||
const bool is_valid = found_hash & (std::memcmp(work_buf, nrr_hash, sizeof(work_buf)) == 0);
|
||||
|
||||
/* Return result. */
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
/* Utilities for working with NRRs. */
|
||||
Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, void *out_hash, size_t out_hash_size, os::NativeHandle process_handle, ncm::ProgramId program_id, u64 nrr_heap_address, u64 nrr_heap_size, NrrKind nrr_kind, bool enforce_nrr_kind);
|
||||
Result UnmapNrr(os::NativeHandle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address);
|
||||
|
||||
bool ValidateNrrHashTableEntry(const void *signed_area, size_t signed_area_size, size_t hashes_offset, size_t num_hashes, const void *nrr_hash, const u8 *hash_table, const void *desired_hash);
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ro_patcher.hpp"
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char *NroPatchesDirectory = "nro_patches";
|
||||
|
||||
/* NRO patches want to prevent modification of header, */
|
||||
/* but don't want to adjust offset relative to mapped location. */
|
||||
constexpr size_t NroPatchesProtectedSize = sizeof(NroHeader);
|
||||
constexpr size_t NroPatchesProtectedOffset = 0;
|
||||
|
||||
}
|
||||
|
||||
/* Apply IPS patches. */
|
||||
void LocateAndApplyIpsPatchesToModule(const ModuleId *module_id, u8 *mapped_nro, size_t mapped_size) {
|
||||
ams::patcher::LocateAndApplyIpsPatchesToModule("sdmc", NroPatchesDirectory, NroPatchesProtectedSize, NroPatchesProtectedOffset, module_id, mapped_nro, mapped_size);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
/* Apply IPS patches. */
|
||||
void LocateAndApplyIpsPatchesToModule(const ModuleId *module_id, u8 *mapped_nro, size_t mapped_size);
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ro_random.hpp"
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
u64 GenerateSecureRandom(u64 max) {
|
||||
/* Generate a cryptographically random number. */
|
||||
u64 rand;
|
||||
crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand));
|
||||
|
||||
/* Coerce into range. */
|
||||
return rand % (max + 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
u64 GenerateSecureRandom(u64 max);
|
||||
|
||||
}
|
||||
@@ -1,605 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ro_nrr_utils.hpp"
|
||||
#include "ro_nro_utils.hpp"
|
||||
#include "ro_patcher.hpp"
|
||||
#include "ro_random.hpp"
|
||||
#include "ro_service_impl.hpp"
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr size_t MaxSessions = 0x3; /* 2 official sessions (applet + application, 1 homebrew session). */
|
||||
constexpr size_t MaxNrrInfos = 0x40;
|
||||
constexpr size_t MaxNroInfos = 0x40;
|
||||
|
||||
/* Types. */
|
||||
struct Sha256Hash {
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
|
||||
bool operator==(const Sha256Hash &o) const {
|
||||
return std::memcmp(this, std::addressof(o), sizeof(*this)) == 0;
|
||||
}
|
||||
bool operator!=(const Sha256Hash &o) const {
|
||||
return std::memcmp(this, std::addressof(o), sizeof(*this)) != 0;
|
||||
}
|
||||
bool operator<(const Sha256Hash &o) const {
|
||||
return std::memcmp(this, std::addressof(o), sizeof(*this)) < 0;
|
||||
}
|
||||
bool operator>(const Sha256Hash &o) const {
|
||||
return std::memcmp(this, std::addressof(o), sizeof(*this)) > 0;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Sha256Hash) == sizeof(Sha256Hash::hash));
|
||||
|
||||
struct NroInfo {
|
||||
u64 base_address;
|
||||
u64 nro_heap_address;
|
||||
u64 nro_heap_size;
|
||||
u64 bss_heap_address;
|
||||
u64 bss_heap_size;
|
||||
u64 code_size;
|
||||
u64 rw_size;
|
||||
ModuleId module_id;
|
||||
};
|
||||
|
||||
struct NrrInfo {
|
||||
const NrrHeader *mapped_header;
|
||||
u64 nrr_heap_address;
|
||||
u64 nrr_heap_size;
|
||||
u64 mapped_code_address;
|
||||
|
||||
/* Verification. */
|
||||
u32 cached_signed_area_size;
|
||||
u32 cached_hashes_offset;
|
||||
u32 cached_num_hashes;
|
||||
u8 cached_signed_area[sizeof(NrrHeader) - NrrHeader::GetSignedAreaOffset()];
|
||||
Sha256Hash signed_area_hash;
|
||||
};
|
||||
|
||||
struct ProcessContext {
|
||||
private:
|
||||
bool m_nro_in_use[MaxNroInfos]{};
|
||||
bool m_nrr_in_use[MaxNrrInfos]{};
|
||||
NroInfo m_nro_infos[MaxNroInfos]{};
|
||||
NrrInfo m_nrr_infos[MaxNrrInfos]{};
|
||||
os::NativeHandle m_process_handle = os::InvalidNativeHandle;
|
||||
os::ProcessId m_process_id = os::InvalidProcessId;
|
||||
bool m_in_use{};
|
||||
public:
|
||||
constexpr ProcessContext() = default;
|
||||
|
||||
void Initialize(os::NativeHandle process_handle, os::ProcessId process_id) {
|
||||
AMS_ABORT_UNLESS(!m_in_use);
|
||||
|
||||
std::memset(m_nro_in_use, 0, sizeof(m_nro_in_use));
|
||||
std::memset(m_nrr_in_use, 0, sizeof(m_nrr_in_use));
|
||||
std::memset(m_nro_infos, 0, sizeof(m_nro_infos));
|
||||
std::memset(m_nrr_infos, 0, sizeof(m_nrr_infos));
|
||||
|
||||
m_process_handle = process_handle;
|
||||
m_process_id = process_id;
|
||||
m_in_use = true;
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
AMS_ABORT_UNLESS(m_in_use);
|
||||
|
||||
if (m_process_handle != os::InvalidNativeHandle) {
|
||||
for (size_t i = 0; i < MaxNrrInfos; i++) {
|
||||
if (m_nrr_in_use[i]) {
|
||||
UnmapNrr(m_process_handle, m_nrr_infos[i].mapped_header, m_nrr_infos[i].nrr_heap_address, m_nrr_infos[i].nrr_heap_size, m_nrr_infos[i].mapped_code_address);
|
||||
}
|
||||
}
|
||||
os::CloseNativeHandle(m_process_handle);
|
||||
}
|
||||
|
||||
std::memset(m_nro_in_use, 0, sizeof(m_nro_in_use));
|
||||
std::memset(m_nrr_in_use, 0, sizeof(m_nrr_in_use));
|
||||
std::memset(m_nro_infos, 0, sizeof(m_nro_infos));
|
||||
std::memset(m_nrr_infos, 0, sizeof(m_nrr_infos));
|
||||
|
||||
m_process_handle = os::InvalidNativeHandle;
|
||||
m_process_id = os::InvalidProcessId;
|
||||
|
||||
m_in_use = false;
|
||||
}
|
||||
|
||||
os::NativeHandle GetProcessHandle() const {
|
||||
return m_process_handle;
|
||||
}
|
||||
|
||||
os::ProcessId GetProcessId() const {
|
||||
return m_process_id;
|
||||
}
|
||||
|
||||
bool IsFree() const {
|
||||
return !m_in_use;
|
||||
}
|
||||
|
||||
ncm::ProgramId GetProgramId(os::NativeHandle other_process_h) const {
|
||||
/* Automatically select a handle, allowing for override. */
|
||||
if (other_process_h != os::InvalidNativeHandle) {
|
||||
return os::GetProgramId(other_process_h);
|
||||
} else {
|
||||
return os::GetProgramId(m_process_handle);
|
||||
}
|
||||
}
|
||||
|
||||
Result GetNrrInfoByAddress(NrrInfo **out, u64 nrr_heap_address) {
|
||||
for (size_t i = 0; i < MaxNrrInfos; i++) {
|
||||
if (m_nrr_in_use[i] && m_nrr_infos[i].nrr_heap_address == nrr_heap_address) {
|
||||
if (out != nullptr) {
|
||||
*out = m_nrr_infos + i;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
R_THROW(ro::ResultNotRegistered());
|
||||
}
|
||||
|
||||
Result GetFreeNrrInfo(NrrInfo **out) {
|
||||
for (size_t i = 0; i < MaxNrrInfos; i++) {
|
||||
if (!m_nrr_in_use[i]) {
|
||||
if (out != nullptr) {
|
||||
*out = m_nrr_infos + i;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
R_THROW(ro::ResultTooManyNrr());
|
||||
}
|
||||
|
||||
Result GetNroInfoByAddress(NroInfo **out, u64 nro_address) {
|
||||
for (size_t i = 0; i < MaxNroInfos; i++) {
|
||||
if (m_nro_in_use[i] && m_nro_infos[i].base_address == nro_address) {
|
||||
if (out != nullptr) {
|
||||
*out = m_nro_infos + i;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
R_THROW(ro::ResultNotLoaded());
|
||||
}
|
||||
|
||||
Result GetNroInfoByModuleId(NroInfo **out, const ModuleId *module_id) {
|
||||
for (size_t i = 0; i < MaxNroInfos; i++) {
|
||||
if (m_nro_in_use[i] && std::memcmp(std::addressof(m_nro_infos[i].module_id), module_id, sizeof(*module_id)) == 0) {
|
||||
if (out != nullptr) {
|
||||
*out = m_nro_infos + i;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
R_THROW(ro::ResultNotLoaded());
|
||||
}
|
||||
|
||||
Result GetFreeNroInfo(NroInfo **out) {
|
||||
for (size_t i = 0; i < MaxNroInfos; i++) {
|
||||
if (!m_nro_in_use[i]) {
|
||||
if (out != nullptr) {
|
||||
*out = m_nro_infos + i;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
R_THROW(ro::ResultTooManyNro());
|
||||
}
|
||||
|
||||
Result ValidateHasNroHash(const NroHeader *nro_header) const {
|
||||
/* Calculate hash. */
|
||||
Sha256Hash hash;
|
||||
crypto::GenerateSha256(std::addressof(hash), sizeof(hash), nro_header, nro_header->GetSize());
|
||||
|
||||
for (size_t i = 0; i < MaxNrrInfos; i++) {
|
||||
/* Ensure we only check NRRs that are used. */
|
||||
if (!m_nrr_in_use[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the mapped header, ensure that it has hashes. */
|
||||
const NrrHeader *mapped_nrr_header = m_nrr_infos[i].mapped_header;
|
||||
const size_t mapped_num_hashes = mapped_nrr_header->GetNumHashes();
|
||||
if (mapped_num_hashes == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Locate the hash within the mapped array. */
|
||||
const Sha256Hash *mapped_nro_hashes_start = reinterpret_cast<const Sha256Hash *>(mapped_nrr_header->GetHashes());
|
||||
const Sha256Hash *mapped_nro_hashes_end = mapped_nro_hashes_start + mapped_nrr_header->GetNumHashes();
|
||||
|
||||
const Sha256Hash *mapped_lower_bound = std::lower_bound(mapped_nro_hashes_start, mapped_nro_hashes_end, hash);
|
||||
if (mapped_lower_bound == mapped_nro_hashes_end || (*mapped_lower_bound != hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check that the hash entry is valid, since our heuristic passed. */
|
||||
const void *nrr_hash = std::addressof(m_nrr_infos[i].signed_area_hash);
|
||||
const void *signed_area = m_nrr_infos[i].cached_signed_area;
|
||||
const size_t signed_area_size = m_nrr_infos[i].cached_signed_area_size;
|
||||
const size_t hashes_offset = m_nrr_infos[i].cached_hashes_offset;
|
||||
const size_t num_hashes = m_nrr_infos[i].cached_num_hashes;
|
||||
const u8 *hash_table = reinterpret_cast<const u8 *>(mapped_nro_hashes_start);
|
||||
if (!ValidateNrrHashTableEntry(signed_area, signed_area_size, hashes_offset, num_hashes, nrr_hash, hash_table, std::addressof(hash))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The hash is valid! */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
R_THROW(ro::ResultNotAuthorized());
|
||||
}
|
||||
|
||||
Result ValidateNro(ModuleId *out_module_id, u64 *out_rx_size, u64 *out_ro_size, u64 *out_rw_size, bool *out_aligned_header, u64 base_address, u64 expected_nro_size, u64 expected_bss_size) {
|
||||
/* Map the NRO. */
|
||||
void *mapped_memory = nullptr;
|
||||
R_TRY_CATCH(os::MapProcessMemory(std::addressof(mapped_memory), m_process_handle, base_address, expected_nro_size, ro::impl::GenerateSecureRandom)) {
|
||||
R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* When we're done, unmap the memory. */
|
||||
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, m_process_handle, base_address, expected_nro_size); };
|
||||
|
||||
/* Validate header. */
|
||||
const NroHeader *header = static_cast<const NroHeader *>(mapped_memory);
|
||||
R_UNLESS(header->IsMagicValid(), ro::ResultInvalidNro());
|
||||
|
||||
/* Read sizes from header. */
|
||||
const u64 nro_size = header->GetSize();
|
||||
const u64 text_ofs = header->GetTextOffset();
|
||||
const u64 text_size = header->GetTextSize();
|
||||
const u64 ro_ofs = header->GetRoOffset();
|
||||
const u64 ro_size = header->GetRoSize();
|
||||
const u64 rw_ofs = header->GetRwOffset();
|
||||
const u64 rw_size = header->GetRwSize();
|
||||
const u64 bss_size = header->GetBssSize();
|
||||
|
||||
/* Validate sizes meet expected. */
|
||||
R_UNLESS(nro_size == expected_nro_size, ro::ResultInvalidNro());
|
||||
R_UNLESS(bss_size == expected_bss_size, ro::ResultInvalidNro());
|
||||
|
||||
/* Validate all sizes are aligned. */
|
||||
R_UNLESS(util::IsAligned(text_size, os::MemoryPageSize), ro::ResultInvalidNro());
|
||||
R_UNLESS(util::IsAligned(ro_size, os::MemoryPageSize), ro::ResultInvalidNro());
|
||||
R_UNLESS(util::IsAligned(rw_size, os::MemoryPageSize), ro::ResultInvalidNro());
|
||||
R_UNLESS(util::IsAligned(bss_size, os::MemoryPageSize), ro::ResultInvalidNro());
|
||||
|
||||
/* Validate sections are in order. */
|
||||
R_UNLESS(text_ofs <= ro_ofs, ro::ResultInvalidNro());
|
||||
R_UNLESS(ro_ofs <= rw_ofs, ro::ResultInvalidNro());
|
||||
|
||||
/* Validate sections are sequential and contiguous. */
|
||||
R_UNLESS(text_ofs == 0, ro::ResultInvalidNro());
|
||||
R_UNLESS(text_ofs + text_size == ro_ofs, ro::ResultInvalidNro());
|
||||
R_UNLESS(ro_ofs + ro_size == rw_ofs, ro::ResultInvalidNro());
|
||||
R_UNLESS(rw_ofs + rw_size == nro_size, ro::ResultInvalidNro());
|
||||
|
||||
/* Verify NRO hash. */
|
||||
R_TRY(this->ValidateHasNroHash(header));
|
||||
|
||||
/* Check if NRO has already been loaded. */
|
||||
const ModuleId *module_id = header->GetModuleId();
|
||||
R_UNLESS(R_FAILED(this->GetNroInfoByModuleId(nullptr, module_id)), ro::ResultAlreadyLoaded());
|
||||
|
||||
/* Apply patches to NRO. */
|
||||
LocateAndApplyIpsPatchesToModule(module_id, static_cast<u8 *>(mapped_memory), nro_size);
|
||||
|
||||
/* Copy to output. */
|
||||
*out_module_id = *module_id;
|
||||
*out_rx_size = text_size;
|
||||
*out_ro_size = ro_size;
|
||||
*out_rw_size = rw_size;
|
||||
*out_aligned_header = header->IsAlignedHeader();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void SetNrrInfoInUse(const NrrInfo *info, bool in_use) {
|
||||
AMS_ASSERT(std::addressof(m_nrr_infos[0]) <= info && info <= std::addressof(m_nrr_infos[MaxNrrInfos - 1]));
|
||||
const size_t index = info - std::addressof(m_nrr_infos[0]);
|
||||
m_nrr_in_use[index] = in_use;
|
||||
}
|
||||
|
||||
void SetNroInfoInUse(const NroInfo *info, bool in_use) {
|
||||
AMS_ASSERT(std::addressof(m_nro_infos[0]) <= info && info <= std::addressof(m_nro_infos[MaxNroInfos - 1]));
|
||||
const size_t index = info - std::addressof(m_nro_infos[0]);
|
||||
m_nro_in_use[index] = in_use;
|
||||
}
|
||||
|
||||
void GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out_infos, size_t max_out_count) const {
|
||||
size_t count = 0;
|
||||
|
||||
for (size_t i = 0; i < MaxNroInfos && count < max_out_count; i++) {
|
||||
if (!m_nro_in_use[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const NroInfo *nro_info = m_nro_infos + i;
|
||||
|
||||
/* Just copy out the info. */
|
||||
auto &out_info = out_infos[count++];
|
||||
|
||||
std::memcpy(out_info.module_id, nro_info->module_id.data, sizeof(out_info.module_id));
|
||||
out_info.address = nro_info->base_address;
|
||||
out_info.size = nro_info->nro_heap_size + nro_info->bss_heap_size;
|
||||
}
|
||||
|
||||
*out_count = static_cast<u32>(count);
|
||||
}
|
||||
};
|
||||
|
||||
/* Globals. */
|
||||
constinit ProcessContext g_process_contexts[MaxSessions] = {};
|
||||
constinit bool g_is_development_hardware = false;
|
||||
constinit bool g_is_development_function_enabled = false;
|
||||
|
||||
/* Context Helpers. */
|
||||
ProcessContext *GetContextById(size_t context_id) {
|
||||
if (context_id == InvalidContextId) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AMS_ABORT_UNLESS(context_id < MaxSessions);
|
||||
return g_process_contexts + context_id;
|
||||
}
|
||||
|
||||
ProcessContext *GetContextByProcessId(os::ProcessId process_id) {
|
||||
for (size_t i = 0; i < MaxSessions; i++) {
|
||||
if (g_process_contexts[i].GetProcessId() == process_id) {
|
||||
return g_process_contexts + i;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t AllocateContext(os::NativeHandle process_handle, os::ProcessId process_id) {
|
||||
/* Find a free process context. */
|
||||
for (size_t i = 0; i < MaxSessions; i++) {
|
||||
ProcessContext *context = g_process_contexts + i;
|
||||
|
||||
if (context->IsFree()) {
|
||||
context->Initialize(process_handle, process_id);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
/* Failure to find a free context is actually an abort condition. */
|
||||
AMS_ABORT_UNLESS(false);
|
||||
}
|
||||
|
||||
void FreeContext(size_t context_id) {
|
||||
if (ProcessContext *context = GetContextById(context_id); context != nullptr) {
|
||||
context->Finalize();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr inline Result ValidateAddressAndNonZeroSize(u64 address, u64 size) {
|
||||
R_UNLESS(util::IsAligned(address, os::MemoryPageSize), ro::ResultInvalidAddress());
|
||||
R_UNLESS(size != 0, ro::ResultInvalidSize());
|
||||
R_UNLESS(util::IsAligned(size, os::MemoryPageSize), ro::ResultInvalidSize());
|
||||
R_UNLESS(address < address + size, ro::ResultInvalidSize());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
constexpr inline Result ValidateAddressAndSize(u64 address, u64 size) {
|
||||
R_UNLESS(util::IsAligned(address, os::MemoryPageSize), ro::ResultInvalidAddress());
|
||||
R_UNLESS(util::IsAligned(size, os::MemoryPageSize), ro::ResultInvalidSize());
|
||||
R_UNLESS(size == 0 || address < address + size, ro::ResultInvalidSize());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Access utilities. */
|
||||
void SetDevelopmentHardware(bool is_development_hardware) {
|
||||
g_is_development_hardware = is_development_hardware;
|
||||
}
|
||||
|
||||
void SetDevelopmentFunctionEnabled(bool is_development_function_enabled) {
|
||||
g_is_development_function_enabled = is_development_function_enabled;
|
||||
}
|
||||
|
||||
bool IsDevelopmentHardware() {
|
||||
return g_is_development_hardware;
|
||||
}
|
||||
|
||||
bool IsDevelopmentFunctionEnabled() {
|
||||
return g_is_development_function_enabled;
|
||||
}
|
||||
|
||||
bool ShouldEaseNroRestriction() {
|
||||
/* Retrieve whether we should ease restrictions from set:sys. */
|
||||
u8 should_ease = 0;
|
||||
if (settings::fwdbg::GetSettingsItemValue(std::addressof(should_ease), sizeof(should_ease), "ro", "ease_nro_restriction") != sizeof(should_ease)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Nintendo only allows easing restriction on dev, we will allow on production, as well. */
|
||||
/* should_ease &= IsDevelopmentFunctionEnabled(); */
|
||||
return should_ease != 0;
|
||||
}
|
||||
|
||||
/* Context utilities. */
|
||||
Result RegisterProcess(size_t *out_context_id, sf::NativeHandle &&process_handle, os::ProcessId process_id) {
|
||||
/* Validate process handle. */
|
||||
{
|
||||
/* Validate handle is a valid process handle. */
|
||||
os::ProcessId handle_pid;
|
||||
R_UNLESS(R_SUCCEEDED(os::GetProcessId(std::addressof(handle_pid), process_handle.GetOsHandle())), ro::ResultInvalidProcess());
|
||||
|
||||
/* Validate process id. */
|
||||
R_UNLESS(handle_pid == process_id, ro::ResultInvalidProcess());
|
||||
}
|
||||
|
||||
/* Check if a process context already exists. */
|
||||
R_UNLESS(GetContextByProcessId(process_id) == nullptr, ro::ResultInvalidSession());
|
||||
|
||||
/* Allocate a context to manage the process handle. */
|
||||
*out_context_id = AllocateContext(process_handle.GetOsHandle(), process_id);
|
||||
process_handle.Detach();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ValidateProcess(size_t context_id, os::ProcessId process_id) {
|
||||
const ProcessContext *ctx = GetContextById(context_id);
|
||||
R_UNLESS(ctx != nullptr, ro::ResultInvalidProcess());
|
||||
R_UNLESS(ctx->GetProcessId() == process_id, ro::ResultInvalidProcess());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void UnregisterProcess(size_t context_id) {
|
||||
FreeContext(context_id);
|
||||
}
|
||||
|
||||
/* Service implementations. */
|
||||
Result RegisterModuleInfo(size_t context_id, os::NativeHandle process_handle, u64 nrr_address, u64 nrr_size, NrrKind nrr_kind, bool enforce_nrr_kind) {
|
||||
/* Get context. */
|
||||
ProcessContext *context = GetContextById(context_id);
|
||||
AMS_ABORT_UNLESS(context != nullptr);
|
||||
|
||||
/* Get program id. */
|
||||
const ncm::ProgramId program_id = context->GetProgramId(process_handle);
|
||||
|
||||
/* Validate address/size. */
|
||||
R_TRY(ValidateAddressAndNonZeroSize(nrr_address, nrr_size));
|
||||
|
||||
/* Check we have space for a new NRR. */
|
||||
NrrInfo *nrr_info = nullptr;
|
||||
R_TRY(context->GetFreeNrrInfo(std::addressof(nrr_info)));
|
||||
|
||||
/* Prepare to cache the NRR's signature hash. */
|
||||
Sha256Hash signed_area_hash;
|
||||
ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(signed_area_hash), sizeof(signed_area_hash)); };
|
||||
|
||||
/* Map. */
|
||||
NrrHeader *header = nullptr;
|
||||
u64 mapped_code_address = 0;
|
||||
R_TRY(MapAndValidateNrr(std::addressof(header), std::addressof(mapped_code_address), std::addressof(signed_area_hash), sizeof(signed_area_hash), context->GetProcessHandle(), program_id, nrr_address, nrr_size, nrr_kind, enforce_nrr_kind));
|
||||
|
||||
/* Set NRR info. */
|
||||
context->SetNrrInfoInUse(nrr_info, true);
|
||||
nrr_info->mapped_header = header;
|
||||
nrr_info->nrr_heap_address = nrr_address;
|
||||
nrr_info->nrr_heap_size = nrr_size;
|
||||
nrr_info->mapped_code_address = mapped_code_address;
|
||||
|
||||
nrr_info->cached_signed_area_size = header->GetSignedAreaSize();
|
||||
nrr_info->cached_hashes_offset = header->GetHashesOffset();
|
||||
nrr_info->cached_num_hashes = header->GetNumHashes();
|
||||
|
||||
std::memcpy(nrr_info->cached_signed_area, header->GetSignedArea(), std::min(sizeof(nrr_info->cached_signed_area), header->GetHashesOffset() - header->GetSignedAreaOffset()));
|
||||
std::memcpy(std::addressof(nrr_info->signed_area_hash), std::addressof(signed_area_hash), sizeof(signed_area_hash));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result UnregisterModuleInfo(size_t context_id, u64 nrr_address) {
|
||||
/* Get context. */
|
||||
ProcessContext *context = GetContextById(context_id);
|
||||
AMS_ABORT_UNLESS(context != nullptr);
|
||||
|
||||
/* Validate address. */
|
||||
R_UNLESS(util::IsAligned(nrr_address, os::MemoryPageSize), ro::ResultInvalidAddress());
|
||||
|
||||
/* Check the NRR is loaded. */
|
||||
NrrInfo *nrr_info = nullptr;
|
||||
R_TRY(context->GetNrrInfoByAddress(std::addressof(nrr_info), nrr_address));
|
||||
|
||||
/* Unmap. */
|
||||
const NrrInfo nrr_backup = *nrr_info;
|
||||
{
|
||||
/* Nintendo does this unconditionally, whether or not the actual unmap succeeds. */
|
||||
context->SetNrrInfoInUse(nrr_info, false);
|
||||
std::memset(nrr_info, 0, sizeof(*nrr_info));
|
||||
}
|
||||
R_RETURN(UnmapNrr(context->GetProcessHandle(), nrr_backup.mapped_header, nrr_backup.nrr_heap_address, nrr_backup.nrr_heap_size, nrr_backup.mapped_code_address));
|
||||
}
|
||||
|
||||
Result MapManualLoadModuleMemory(u64 *out_address, size_t context_id, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) {
|
||||
/* Get context. */
|
||||
ProcessContext *context = GetContextById(context_id);
|
||||
AMS_ABORT_UNLESS(context != nullptr);
|
||||
|
||||
/* Validate address/size. */
|
||||
R_TRY(ValidateAddressAndNonZeroSize(nro_address, nro_size));
|
||||
R_TRY(ValidateAddressAndSize(bss_address, bss_size));
|
||||
|
||||
const u64 total_size = nro_size + bss_size;
|
||||
R_UNLESS(total_size >= nro_size, ro::ResultInvalidSize());
|
||||
R_UNLESS(total_size >= bss_size, ro::ResultInvalidSize());
|
||||
|
||||
/* Check we have space for a new NRO. */
|
||||
NroInfo *nro_info = nullptr;
|
||||
R_TRY(context->GetFreeNroInfo(std::addressof(nro_info)));
|
||||
nro_info->nro_heap_address = nro_address;
|
||||
nro_info->nro_heap_size = nro_size;
|
||||
nro_info->bss_heap_address = bss_address;
|
||||
nro_info->bss_heap_size = bss_size;
|
||||
|
||||
/* Map the NRO. */
|
||||
R_TRY(MapNro(std::addressof(nro_info->base_address), context->GetProcessHandle(), nro_address, nro_size, bss_address, bss_size));
|
||||
ON_RESULT_FAILURE { UnmapNro(context->GetProcessHandle(), nro_info->base_address, nro_address, nro_size, bss_address, bss_size); };
|
||||
|
||||
/* Validate the NRO (parsing region extents). */
|
||||
u64 rx_size = 0, ro_size = 0, rw_size = 0;
|
||||
bool aligned_header = false;
|
||||
R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size), std::addressof(ro_size), std::addressof(rw_size), std::addressof(aligned_header), nro_info->base_address, nro_size, bss_size));
|
||||
|
||||
/* Set NRO perms. */
|
||||
R_TRY(SetNroPerms(context->GetProcessHandle(), nro_info->base_address, rx_size, ro_size, rw_size + bss_size, aligned_header));
|
||||
|
||||
context->SetNroInfoInUse(nro_info, true);
|
||||
nro_info->code_size = rx_size + ro_size;
|
||||
nro_info->rw_size = rw_size;
|
||||
*out_address = nro_info->base_address;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result UnmapManualLoadModuleMemory(size_t context_id, u64 nro_address) {
|
||||
/* Get context. */
|
||||
ProcessContext *context = GetContextById(context_id);
|
||||
AMS_ABORT_UNLESS(context != nullptr);
|
||||
|
||||
/* Validate address. */
|
||||
R_UNLESS(util::IsAligned(nro_address, os::MemoryPageSize), ro::ResultInvalidAddress());
|
||||
|
||||
/* Check the NRO is loaded. */
|
||||
NroInfo *nro_info = nullptr;
|
||||
R_TRY(context->GetNroInfoByAddress(std::addressof(nro_info), nro_address));
|
||||
|
||||
/* Unmap. */
|
||||
const NroInfo nro_backup = *nro_info;
|
||||
{
|
||||
/* Nintendo does this unconditionally, whether or not the actual unmap succeeds. */
|
||||
context->SetNroInfoInUse(nro_info, false);
|
||||
std::memset(nro_info, 0, sizeof(*nro_info));
|
||||
}
|
||||
R_RETURN(UnmapNro(context->GetProcessHandle(), nro_backup.base_address, nro_backup.nro_heap_address, nro_backup.code_size + nro_backup.rw_size, nro_backup.bss_heap_address, nro_backup.bss_heap_size));
|
||||
}
|
||||
|
||||
/* Debug service implementations. */
|
||||
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out_infos, size_t max_out_count, os::ProcessId process_id) {
|
||||
if (const ProcessContext *context = GetContextByProcessId(process_id); context != nullptr) {
|
||||
context->GetProcessModuleInfo(out_count, out_infos, max_out_count);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ro::impl {
|
||||
|
||||
/* Definitions. */
|
||||
constexpr size_t InvalidContextId = static_cast<size_t>(-1);
|
||||
|
||||
/* Access utilities. */
|
||||
void SetDevelopmentHardware(bool is_development_hardware);
|
||||
void SetDevelopmentFunctionEnabled(bool is_development_function_enabled);
|
||||
bool IsDevelopmentHardware();
|
||||
bool IsDevelopmentFunctionEnabled();
|
||||
bool ShouldEaseNroRestriction();
|
||||
|
||||
/* Context utilities. */
|
||||
Result RegisterProcess(size_t *out_context_id, sf::NativeHandle &&process_handle, os::ProcessId process_id);
|
||||
Result ValidateProcess(size_t context_id, os::ProcessId process_id);
|
||||
void UnregisterProcess(size_t context_id);
|
||||
|
||||
/* Service implementations. */
|
||||
Result RegisterModuleInfo(size_t context_id, os::NativeHandle process_h, u64 nrr_address, u64 nrr_size, NrrKind nrr_kind, bool enforce_nrr_kind);
|
||||
Result UnregisterModuleInfo(size_t context_id, u64 nrr_address);
|
||||
Result MapManualLoadModuleMemory(u64 *out_address, size_t context_id, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size);
|
||||
Result UnmapManualLoadModuleMemory(size_t context_id, u64 nro_address);
|
||||
|
||||
/* Debug service implementations. */
|
||||
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out_infos, size_t max_out_count, os::ProcessId process_id);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user