Atmosphere/fusee/program/source/fusee_stratosphere.cpp
Michael Scire 46a4357882 fusee: remove ips patch parsing from sd filesystem
Parsing the SD fs is very slow. In addition, the only KIPs are either a) atmosphere modules, or b) FS.

The IPS subsystem was originally designed to make nogc/etc patches work for FS,
but these are now internal, and it appears that the literal only kip patches
that exist are for piracy.

It just doesn't make sense to slow down boot for every normal user for a feature
that has no actual usecase, and especially when fusee is already so minimal.
2023-11-13 12:45:00 -07:00

1050 lines
44 KiB
C++

/*
* 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 <exosphere.hpp>
#include "fusee_stratosphere.hpp"
#include "fusee_fatal.hpp"
#include "fusee_malloc.hpp"
#include "fusee_external_package.hpp"
#include "fs/fusee_fs_api.hpp"
namespace ams::nxboot {
namespace {
constexpr u32 MesoshereMetadataLayout0Magic = util::FourCC<'M','S','S','0'>::Code;
constexpr u32 MesoshereMetadataLayout1Magic = util::FourCC<'M','S','S','1'>::Code;
struct InitialProcessBinaryHeader {
static constexpr u32 Magic = util::FourCC<'I','N','I','1'>::Code;
u32 magic;
u32 size;
u32 num_processes;
u32 reserved;
};
struct InitialProcessHeader {
static constexpr u32 Magic = util::FourCC<'K','I','P','1'>::Code;
u32 magic;
u8 name[12];
u64 program_id;
u32 version;
u8 priority;
u8 ideal_core_id;
u8 _1E;
u8 flags;
u32 rx_address;
u32 rx_size;
u32 rx_compressed_size;
u32 affinity_mask;
u32 ro_address;
u32 ro_size;
u32 ro_compressed_size;
u32 stack_size;
u32 rw_address;
u32 rw_size;
u32 rw_compressed_size;
u32 _4C;
u32 bss_address;
u32 bss_size;
u32 pad[(0x80 - 0x58) / sizeof(u32)];
u32 capabilities[0x80 / sizeof(u32)];
};
static_assert(sizeof(InitialProcessHeader) == 0x100);
struct PatchMeta {
PatchMeta *next;
bool is_memset;
u32 start_segment;
u32 rel_offset;
const void *data;
u32 size;
};
struct alignas(0x10) InitialProcessMeta {
InitialProcessMeta *next = nullptr;
const InitialProcessHeader *kip;
u32 kip_size;
PatchMeta *patches_head;
PatchMeta *patches_tail;
u32 patch_segments;
u64 program_id;
se::Sha256Hash kip_hash;
};
static_assert(sizeof(InitialProcessMeta) == 0x40);
static_assert(alignof(InitialProcessMeta) == 0x10);
constexpr inline const u64 FsProgramId = 0x0100000000000000;
enum FsVersion {
FsVersion_1_0_0 = 0,
FsVersion_2_0_0,
FsVersion_2_0_0_Exfat,
FsVersion_2_1_0,
FsVersion_2_1_0_Exfat,
FsVersion_3_0_0,
FsVersion_3_0_0_Exfat,
FsVersion_3_0_1,
FsVersion_3_0_1_Exfat,
FsVersion_4_0_0,
FsVersion_4_0_0_Exfat,
FsVersion_4_1_0,
FsVersion_4_1_0_Exfat,
FsVersion_5_0_0,
FsVersion_5_0_0_Exfat,
FsVersion_5_1_0,
FsVersion_5_1_0_Exfat,
FsVersion_6_0_0,
FsVersion_6_0_0_Exfat,
FsVersion_7_0_0,
FsVersion_7_0_0_Exfat,
FsVersion_8_0_0,
FsVersion_8_0_0_Exfat,
FsVersion_8_1_0,
FsVersion_8_1_0_Exfat,
FsVersion_9_0_0,
FsVersion_9_0_0_Exfat,
FsVersion_9_1_0,
FsVersion_9_1_0_Exfat,
FsVersion_10_0_0,
FsVersion_10_0_0_Exfat,
FsVersion_10_2_0,
FsVersion_10_2_0_Exfat,
FsVersion_11_0_0,
FsVersion_11_0_0_Exfat,
FsVersion_12_0_0,
FsVersion_12_0_0_Exfat,
FsVersion_12_0_3,
FsVersion_12_0_3_Exfat,
FsVersion_13_0_0,
FsVersion_13_0_0_Exfat,
FsVersion_13_1_0,
FsVersion_13_1_0_Exfat,
FsVersion_14_0_0,
FsVersion_14_0_0_Exfat,
FsVersion_15_0_0,
FsVersion_15_0_0_Exfat,
FsVersion_16_0_0,
FsVersion_16_0_0_Exfat,
FsVersion_16_0_3,
FsVersion_16_0_3_Exfat,
FsVersion_17_0_0,
FsVersion_17_0_0_Exfat,
FsVersion_Count,
};
constexpr const u8 FsHashes[FsVersion_Count][8] = {
{ 0xDE, 0x9F, 0xDD, 0xA4, 0x08, 0x5D, 0xD5, 0xFE }, /* FsVersion_1_0_0 */
{ 0xCD, 0x7B, 0xBE, 0x18, 0xD6, 0x13, 0x0B, 0x28 }, /* FsVersion_2_0_0 */
{ 0xE7, 0x66, 0x92, 0xDF, 0xAA, 0x04, 0x20, 0xE9 }, /* FsVersion_2_0_0_Exfat */
{ 0x0D, 0x70, 0x05, 0x62, 0x7B, 0x07, 0x76, 0x7C }, /* FsVersion_2_1_0 */
{ 0xDB, 0xD8, 0x5F, 0xCA, 0xCC, 0x19, 0x3D, 0xA8 }, /* FsVersion_2_1_0_Exfat */
{ 0xA8, 0x6D, 0xA5, 0xE8, 0x7E, 0xF1, 0x09, 0x7B }, /* FsVersion_3_0_0 */
{ 0x98, 0x1C, 0x57, 0xE7, 0xF0, 0x2F, 0x70, 0xF7 }, /* FsVersion_3_0_0_Exfat */
{ 0x57, 0x39, 0x7C, 0x06, 0x3F, 0x10, 0xB6, 0x31 }, /* FsVersion_3_0_1 */
{ 0x07, 0x30, 0x99, 0xD7, 0xC6, 0xAD, 0x7D, 0x89 }, /* FsVersion_3_0_1_Exfat */
{ 0x06, 0xE9, 0x07, 0x19, 0x59, 0x5A, 0x01, 0x0C }, /* FsVersion_4_0_0 */
{ 0x54, 0x9B, 0x0F, 0x8D, 0x6F, 0x72, 0xC4, 0xE9 }, /* FsVersion_4_0_0_Exfat */
{ 0x80, 0x96, 0xAF, 0x7C, 0x6A, 0x35, 0xAA, 0x82 }, /* FsVersion_4_1_0 */
{ 0x02, 0xD5, 0xAB, 0xAA, 0xFD, 0x20, 0xC8, 0xB0 }, /* FsVersion_4_1_0_Exfat */
{ 0xA6, 0xF2, 0x7A, 0xD9, 0xAC, 0x7C, 0x73, 0xAD }, /* FsVersion_5_0_0 */
{ 0xCE, 0x3E, 0xCB, 0xA2, 0xF2, 0xF0, 0x62, 0xF5 }, /* FsVersion_5_0_0_Exfat */
{ 0x76, 0xF8, 0x74, 0x02, 0xC9, 0x38, 0x7C, 0x0F }, /* FsVersion_5_1_0 */
{ 0x10, 0xB2, 0xD8, 0x16, 0x05, 0x48, 0x85, 0x99 }, /* FsVersion_5_1_0_Exfat */
{ 0x3A, 0x57, 0x4D, 0x43, 0x61, 0x86, 0x19, 0x1D }, /* FsVersion_6_0_0 */
{ 0x33, 0x05, 0x53, 0xF6, 0xB5, 0xFB, 0x55, 0xC4 }, /* FsVersion_6_0_0_Exfat */
{ 0x2A, 0xDB, 0xE9, 0x7E, 0x9B, 0x5F, 0x41, 0x77 }, /* FsVersion_7_0_0 */
{ 0x2C, 0xCE, 0x65, 0x9C, 0xEC, 0x53, 0x6A, 0x8E }, /* FsVersion_7_0_0_Exfat */
{ 0xB2, 0xF5, 0x17, 0x6B, 0x35, 0x48, 0x36, 0x4D }, /* FsVersion_8_0_0 */
{ 0xDB, 0xD9, 0x41, 0xC0, 0xC5, 0x3C, 0x52, 0xCC }, /* FsVersion_8_0_0_Exfat */
{ 0x6B, 0x09, 0xB6, 0x7B, 0x29, 0xC0, 0x20, 0x24 }, /* FsVersion_8_1_0 */
{ 0xB4, 0xCA, 0xE1, 0xF2, 0x49, 0x65, 0xD9, 0x2E }, /* FsVersion_8_1_0_Exfat */
{ 0x46, 0x87, 0x40, 0x76, 0x1E, 0x19, 0x3E, 0xB7 }, /* FsVersion_9_0_0 */
{ 0x7C, 0x95, 0x13, 0x76, 0xE5, 0xC1, 0x2D, 0xF8 }, /* FsVersion_9_0_0_Exfat */
{ 0xB5, 0xE7, 0xA6, 0x4C, 0x6F, 0x5C, 0x4F, 0xE3 }, /* FsVersion_9_1_0 */
{ 0xF1, 0x96, 0xD1, 0x44, 0xD0, 0x44, 0x45, 0xB6 }, /* FsVersion_9_1_0_Exfat */
{ 0x3E, 0xEB, 0xD9, 0xB7, 0xBC, 0xD1, 0xB5, 0xE0 }, /* FsVersion_10_0_0 */
{ 0x81, 0x7E, 0xA2, 0xB0, 0xB7, 0x02, 0xC1, 0xF3 }, /* FsVersion_10_0_0_Exfat */
{ 0xA9, 0x52, 0xB6, 0x57, 0xAD, 0xF9, 0xC2, 0xBA }, /* FsVersion_10_2_0 */
{ 0x16, 0x0D, 0x3E, 0x10, 0x4E, 0xAD, 0x61, 0x76 }, /* FsVersion_10_2_0_Exfat */
{ 0xE3, 0x99, 0x15, 0x6E, 0x84, 0x4E, 0xB0, 0xAA }, /* FsVersion_11_0_0 */
{ 0x0B, 0xA1, 0x5B, 0xB3, 0x04, 0xB5, 0x05, 0x63 }, /* FsVersion_11_0_0_Exfat */
{ 0xDC, 0x2A, 0x08, 0x49, 0x96, 0xBB, 0x3C, 0x01 }, /* FsVersion_12_0_0 */
{ 0xD5, 0xA5, 0xBF, 0x36, 0x64, 0x0C, 0x49, 0xEA }, /* FsVersion_12_0_0_Exfat */
{ 0xC8, 0x67, 0x62, 0xBE, 0x19, 0xA5, 0x1F, 0xA0 }, /* FsVersion_12_0_3 */
{ 0xE1, 0xE8, 0xD3, 0xD6, 0xA2, 0xFE, 0x0B, 0x10 }, /* FsVersion_12_0_3_Exfat */
{ 0x7D, 0x20, 0x05, 0x47, 0x17, 0x8A, 0x83, 0x6A }, /* FsVersion_13_0_0 */
{ 0x51, 0xEB, 0xFA, 0x9C, 0xCF, 0x66, 0xC0, 0x9E }, /* FsVersion_13_0_0_Exfat */
{ 0x91, 0xBA, 0x65, 0xA2, 0x1C, 0x1D, 0x50, 0xAE }, /* FsVersion_13_1_0 */
{ 0x76, 0x38, 0x27, 0xEE, 0x9C, 0x20, 0x7E, 0x5B }, /* FsVersion_13_1_0_Exfat */
{ 0x88, 0x7A, 0xC1, 0x50, 0x80, 0x6C, 0x75, 0xCC }, /* FsVersion_14_0_0 */
{ 0xD4, 0x88, 0xD1, 0xF2, 0x92, 0x17, 0x35, 0x5C }, /* FsVersion_14_0_0_Exfat */
{ 0xD0, 0xD4, 0x49, 0x18, 0x14, 0xB5, 0x62, 0xAF }, /* FsVersion_15_0_0 */
{ 0x34, 0xC0, 0xD9, 0xED, 0x6A, 0xD1, 0x87, 0x3D }, /* FsVersion_15_0_0_Exfat */
{ 0x56, 0xE8, 0x56, 0x56, 0x6C, 0x38, 0xD8, 0xBE }, /* FsVersion_16_0_0 */
{ 0xCF, 0xAB, 0x45, 0x0C, 0x2C, 0x53, 0x9D, 0xA9 }, /* FsVersion_16_0_0_Exfat */
{ 0x39, 0xEE, 0x1F, 0x1E, 0x0E, 0xA7, 0x32, 0x5D }, /* FsVersion_16_0_3 */
{ 0x62, 0xC6, 0x5E, 0xFD, 0x9A, 0xBF, 0x7C, 0x43 }, /* FsVersion_16_0_3_Exfat */
{ 0x27, 0x07, 0x3B, 0xF0, 0xA1, 0xB8, 0xCE, 0x61 }, /* FsVersion_17_0_0 */
{ 0xEE, 0x0F, 0x4B, 0xAC, 0x6D, 0x1F, 0xFC, 0x4B }, /* FsVersion_17_0_0_Exfat */
};
const InitialProcessBinaryHeader *FindInitialProcessBinary(const pkg2::Package2Header *header, const u8 *data, ams::TargetFirmware target_firmware) {
if (target_firmware >= ams::TargetFirmware_17_0_0) {
const u32 *data_32 = reinterpret_cast<const u32 *>(data);
const u32 branch_target = (data_32[0] & 0x00FFFFFF);
for (size_t i = branch_target; i < branch_target + 0x1000 / sizeof(u32); ++i) {
const u32 ini_offset = (i * sizeof(u32)) + data_32[i];
if (data_32[i + 1] == 0 && ini_offset <= header->meta.payload_sizes[0] && std::memcmp(data + ini_offset, "INI1", 4) == 0) {
return reinterpret_cast<const InitialProcessBinaryHeader *>(data + ini_offset);
}
}
return nullptr;
} else if (target_firmware >= ams::TargetFirmware_8_0_0) {
/* Try to find initial process binary. */
const u32 *data_32 = reinterpret_cast<const u32 *>(data);
for (size_t i = 0; i < 0x1000 / sizeof(u32); ++i) {
if (data_32[i] == 0 && data_32[i + 8] <= header->meta.payload_sizes[0] && std::memcmp(data + data_32[i + 8], "INI1", 4) == 0) {
return reinterpret_cast<const InitialProcessBinaryHeader *>(data + data_32[i + 8]);
}
}
return nullptr;
} else {
return reinterpret_cast<const InitialProcessBinaryHeader *>(data + header->meta.payload_sizes[0]);
}
}
constexpr size_t GetInitialProcessSize(const InitialProcessHeader *kip) {
return sizeof(*kip) + kip->rx_compressed_size + kip->ro_compressed_size + kip->rw_compressed_size;
}
const InitialProcessHeader *FindInitialProcessInBinary(const InitialProcessBinaryHeader *ini, u64 program_id) {
const u8 *data = reinterpret_cast<const u8 *>(ini + 1);
for (u32 i = 0; i < ini->num_processes; ++i) {
const InitialProcessHeader *kip = reinterpret_cast<const InitialProcessHeader *>(data);
if (kip->magic != InitialProcessHeader::Magic) {
return nullptr;
}
if (kip->program_id == program_id) {
return kip;
}
data += GetInitialProcessSize(kip);
}
return nullptr;
}
FsVersion GetFsVersion(const se::Sha256Hash &fs_hash) {
for (size_t i = 0; i < util::size(FsHashes); ++i) {
if (std::memcmp(fs_hash.bytes, FsHashes[i], sizeof(FsHashes[i])) == 0) {
return static_cast<FsVersion>(i);
}
}
return FsVersion_Count;
}
constinit InitialProcessMeta g_initial_process_meta = {};
constinit size_t g_initial_process_binary_size = 0;
void AddInitialProcessImpl(InitialProcessMeta *meta, const InitialProcessHeader *kip, const se::Sha256Hash *hash) {
/* Set the meta's fields. */
meta->next = nullptr;
meta->program_id = kip->program_id;
meta->kip = kip;
meta->kip_size = GetInitialProcessSize(kip);
/* Copy or calculate hash. */
if (hash != nullptr) {
std::memcpy(std::addressof(meta->kip_hash), hash, sizeof(meta->kip_hash));
} else {
se::CalculateSha256(std::addressof(meta->kip_hash), kip, meta->kip_size);
}
/* Clear patches. */
meta->patches_head = nullptr;
meta->patches_tail = nullptr;
meta->patch_segments = 0;
/* Increase the initial process binary's size. */
g_initial_process_binary_size += meta->kip_size;
}
bool AddInitialProcess(const InitialProcessHeader *kip, const se::Sha256Hash *hash = nullptr) {
/* Check kip magic. */
if (kip->magic != InitialProcessHeader::Magic) {
ShowFatalError("KIP seems corrupted!\n");
}
/* Handle the initial case. */
if (g_initial_process_binary_size == 0) {
AddInitialProcessImpl(std::addressof(g_initial_process_meta), kip, hash);
return true;
}
/* Check if we've already added the program id. */
InitialProcessMeta *cur = std::addressof(g_initial_process_meta);
while (true) {
if (cur->program_id == kip->program_id) {
return false;
}
if (cur->next != nullptr) {
cur = cur->next;
} else {
break;
}
}
/* Allocate an initial process meta. */
auto *new_meta = static_cast<InitialProcessMeta *>(AllocateAligned(sizeof(InitialProcessMeta), alignof(InitialProcessMeta)));
/* Insert the new meta. */
cur->next = new_meta;
AddInitialProcessImpl(new_meta, kip, hash);
return true;
}
InitialProcessMeta *FindInitialProcess(u64 program_id) {
for (InitialProcessMeta *cur = std::addressof(g_initial_process_meta); cur != nullptr; cur = cur->next) {
if (cur->program_id == program_id) {
return cur;
}
}
return nullptr;
}
u32 GetPatchSegments(const InitialProcessHeader *kip, u32 offset, size_t size) {
/* Create segment mask. */
u32 segments = 0;
/* Get the segment extents. */
const u32 rx_start = kip->rx_address;
const u32 ro_start = kip->ro_address;
const u32 rw_start = kip->rw_address;
const u32 rx_end = ro_start;
const u32 ro_end = rw_start;
const u32 rw_end = rw_start + kip->rw_size;
/* If the offset is below the kip header, ignore it. */
if (offset < sizeof(*kip)) {
return segments;
}
/* Adjust the offset in bounds. */
offset -= sizeof(*kip);
/* Check if the offset strays out of bounds. */
if (offset + size > rw_end) {
return segments;
}
/* Set bits for the affected segments. */
if (util::HasOverlap(offset, size, rx_start, rx_end - rx_start)) {
segments |= (1 << 0);
}
if (util::HasOverlap(offset, size, ro_start, ro_end - ro_start)) {
segments |= (1 << 1);
}
if (util::HasOverlap(offset, size, rw_start, rw_end - rw_start)) {
segments |= (1 << 2);
}
return segments;
}
void AddPatch(InitialProcessMeta *meta, u32 offset, const void *data, size_t data_size, bool is_memset = false) {
/* Determine the segment. */
const u32 segments = GetPatchSegments(meta->kip, offset, data_size);
/* If the patch hits no segments, we don't need it. */
if (segments == 0) {
return;
}
/* Update patch segments. */
meta->patch_segments |= segments;
/* Adjust offset. */
const u32 start_segment = util::CountTrailingZeros(segments);
offset -= sizeof(*meta->kip);
switch (start_segment) {
case 0: offset -= meta->kip->rx_address; break;
case 1: offset -= meta->kip->ro_address; break;
case 2: offset -= meta->kip->rw_address; break;
}
/* Create patch. */
auto *new_patch = static_cast<PatchMeta *>(AllocateAligned(sizeof(PatchMeta), alignof(PatchMeta)));
new_patch->next = nullptr;
new_patch->is_memset = is_memset;
new_patch->start_segment = start_segment;
new_patch->rel_offset = offset;
new_patch->data = data;
new_patch->size = data_size;
/* Add the patch. */
if (meta->patches_head == nullptr) {
meta->patches_head = new_patch;
} else {
meta->patches_tail->next = new_patch;
}
meta->patches_tail = new_patch;
}
constexpr const u8 NogcPatch0[] = {
0x80
};
constexpr const u8 NogcPatch1[] = {
0xE0, 0x03, 0x1F, 0x2A, 0xC0, 0x03, 0x5F, 0xD6,
};
void AddNogcPatches(InitialProcessMeta *fs_meta, FsVersion fs_version) {
switch (fs_version) {
case FsVersion_1_0_0:
case FsVersion_2_0_0:
case FsVersion_2_0_0_Exfat:
case FsVersion_2_1_0:
case FsVersion_2_1_0_Exfat:
case FsVersion_3_0_0:
case FsVersion_3_0_0_Exfat:
case FsVersion_3_0_1:
case FsVersion_3_0_1_Exfat:
/* There were no lotus firmware updates prior to 4.0.0. */
/* TODO: Implement patches, regardless? */
break;
case FsVersion_4_0_0:
case FsVersion_4_0_0_Exfat:
AddPatch(fs_meta, 0x0A3539, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x0AAC44, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_4_1_0:
case FsVersion_4_1_0_Exfat:
AddPatch(fs_meta, 0x0A35BD, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x0AACA8, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_5_0_0:
case FsVersion_5_0_0_Exfat:
AddPatch(fs_meta, 0x0CF4C5, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x0D74A0, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_5_1_0:
case FsVersion_5_1_0_Exfat:
AddPatch(fs_meta, 0x0CF895, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x0D7870, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_6_0_0:
AddPatch(fs_meta, 0x1539F5, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x12CD20, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_6_0_0_Exfat:
AddPatch(fs_meta, 0x15F0F5, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x138420, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_7_0_0:
AddPatch(fs_meta, 0x15C005, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x134260, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_7_0_0_Exfat:
AddPatch(fs_meta, 0x1675B5, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x13F810, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_8_0_0:
case FsVersion_8_1_0:
AddPatch(fs_meta, 0x15EC95, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x136900, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_8_0_0_Exfat:
case FsVersion_8_1_0_Exfat:
AddPatch(fs_meta, 0x16A245, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x141EB0, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_9_0_0:
case FsVersion_9_0_0_Exfat:
AddPatch(fs_meta, 0x143369, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x129520, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_9_1_0:
case FsVersion_9_1_0_Exfat:
AddPatch(fs_meta, 0x143379, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x129530, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_10_0_0:
case FsVersion_10_0_0_Exfat:
AddPatch(fs_meta, 0x14DF09, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x13BF90, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_10_2_0:
case FsVersion_10_2_0_Exfat:
AddPatch(fs_meta, 0x14E369, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x13C3F0, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_11_0_0:
case FsVersion_11_0_0_Exfat:
AddPatch(fs_meta, 0x156FB9, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x1399B4, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_12_0_0:
case FsVersion_12_0_0_Exfat:
AddPatch(fs_meta, 0x155469, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x13EB24, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_12_0_3:
case FsVersion_12_0_3_Exfat:
AddPatch(fs_meta, 0x155579, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x13EC34, NogcPatch1, sizeof(NogcPatch1));
case FsVersion_13_0_0:
case FsVersion_13_0_0_Exfat:
AddPatch(fs_meta, 0x159119, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x1426D0, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_13_1_0:
case FsVersion_13_1_0_Exfat:
AddPatch(fs_meta, 0x1590B9, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x142670, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_14_0_0:
AddPatch(fs_meta, 0x18A3E9, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x164330, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_14_0_0_Exfat:
AddPatch(fs_meta, 0x195769, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x16F6B0, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_15_0_0:
AddPatch(fs_meta, 0x184259, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x15EDE4, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_15_0_0_Exfat:
AddPatch(fs_meta, 0x18F1E9, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x169D74, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_16_0_0:
AddPatch(fs_meta, 0x1866D9, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x160C70, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_16_0_0_Exfat:
AddPatch(fs_meta, 0x1913B9, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x16B950, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_16_0_3:
AddPatch(fs_meta, 0x186729, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x160CC0, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_16_0_3_Exfat:
AddPatch(fs_meta, 0x191409, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x16B9A0, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_17_0_0:
AddPatch(fs_meta, 0x18B149, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x165200, NogcPatch1, sizeof(NogcPatch1));
break;
case FsVersion_17_0_0_Exfat:
AddPatch(fs_meta, 0x195FA9, NogcPatch0, sizeof(NogcPatch0));
AddPatch(fs_meta, 0x170060, NogcPatch1, sizeof(NogcPatch1));
break;
default:
break;
}
}
struct BlzSegmentFlags {
using Offset = util::BitPack16::Field<0, 12, u32>;
using Size = util::BitPack16::Field<Offset::Next, 4, u32>;
};
void BlzUncompress(void *_end) {
/* Parse the footer, endian agnostic. */
static_assert(sizeof(u32) == 4);
static_assert(sizeof(u16) == 2);
static_assert(sizeof(u8) == 1);
u8 *end = static_cast<u8 *>(_end);
const u32 total_size = (end[-12] << 0) | (end[-11] << 8) | (end[-10] << 16) | (end[- 9] << 24);
const u32 footer_size = (end[- 8] << 0) | (end[- 7] << 8) | (end[- 6] << 16) | (end[- 5] << 24);
const u32 additional_size = (end[- 4] << 0) | (end[- 3] << 8) | (end[- 2] << 16) | (end[- 1] << 24);
/* Prepare to decompress. */
u8 *cmp_start = end - total_size;
u32 cmp_ofs = total_size - footer_size;
u32 out_ofs = total_size + additional_size;
/* Decompress. */
while (out_ofs) {
u8 control = cmp_start[--cmp_ofs];
/* Each bit in the control byte is a flag indicating compressed or not compressed. */
for (size_t i = 0; i < 8 && out_ofs; ++i, control <<= 1) {
if (control & 0x80) {
/* NOTE: Nintendo does not check if it's possible to decompress. */
/* As such, we will leave the following as a debug assertion, and not a release assertion. */
AMS_AUDIT(cmp_ofs >= sizeof(u16));
cmp_ofs -= sizeof(u16);
/* Extract segment bounds. */
const util::BitPack16 seg_flags{static_cast<u16>((cmp_start[cmp_ofs] << 0) | (cmp_start[cmp_ofs + 1] << 8))};
const u32 seg_ofs = seg_flags.Get<BlzSegmentFlags::Offset>() + 3;
const u32 seg_size = std::min(seg_flags.Get<BlzSegmentFlags::Size>() + 3, out_ofs);
AMS_AUDIT(out_ofs + seg_ofs <= total_size + additional_size);
/* Copy the data. */
out_ofs -= seg_size;
for (size_t j = 0; j < seg_size; j++) {
cmp_start[out_ofs + j] = cmp_start[out_ofs + seg_ofs + j];
}
} else {
/* NOTE: Nintendo does not check if it's possible to copy. */
/* As such, we will leave the following as a debug assertion, and not a release assertion. */
AMS_AUDIT(cmp_ofs >= sizeof(u8));
cmp_start[--out_ofs] = cmp_start[--cmp_ofs];
}
}
}
}
void *ReadFile(s64 *out_size, const char *path, size_t align = 0x10) {
fs::FileHandle file;
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read))) {
ON_SCOPE_EXIT { fs::CloseFile(file); };
Result result;
/* Get the kip size. */
if (R_FAILED((result = fs::GetFileSize(out_size, file)))) {
ShowFatalError("Failed to get size (0x%08" PRIx32 ") of %s!\n", result.GetValue(), path);
}
/* Allocate file. */
void *data = AllocateAligned(*out_size, std::max<size_t>(align, 0x10));
/* Read the file. */
if (R_FAILED((result = fs::ReadFile(file, 0, data, *out_size)))) {
ShowFatalError("Failed to read (0x%08" PRIx32 ") %s!\n", result.GetValue(), path);
}
return data;
} else {
return nullptr;
}
}
}
u32 ConfigureStratosphere(const u8 *nn_package2, ams::TargetFirmware target_firmware, bool emummc_enabled, bool nogc_enabled) {
/* Load KIPs off the SD card. */
{
/* Create kip dir path. */
char kip_path[0x120];
std::memcpy(kip_path, "sdmc:/atmosphere/kips", 0x16);
fs::DirectoryHandle kip_dir;
if (R_SUCCEEDED(fs::OpenDirectory(std::addressof(kip_dir), kip_path))) {
ON_SCOPE_EXIT { fs::CloseDirectory(kip_dir); };
s64 count;
fs::DirectoryEntry entries[1];
while (R_SUCCEEDED(fs::ReadDirectory(std::addressof(count), entries, kip_dir, util::size(entries))) && count > 0) {
/* Check that file is a file. */
if (fs::GetEntryType(entries[0]) != fs::DirectoryEntryType_File) {
continue;
}
/* Get filename length. */
const int name_len = std::strlen(entries[0].file_name);
/* Adjust kip path. */
kip_path[0x15] = '/';
std::memcpy(kip_path + 0x16, entries[0].file_name, name_len + 1);
/* Check that file is ".kip" or ".kip1" file. */
const int path_len = 0x16 + name_len;
if (std::memcmp(kip_path + path_len - 4, ".kip", 5) != 0 && std::memcmp(kip_path + path_len - 5, ".kip1", 6) != 0) {
continue;
}
/* Read the kip. */
s64 file_size;
if (InitialProcessHeader *kip = static_cast<InitialProcessHeader *>(ReadFile(std::addressof(file_size), kip_path, alignof(InitialProcessHeader))); kip != nullptr) {
/* If the kip is valid, add it. */
if (kip->magic == InitialProcessHeader::Magic && file_size == GetInitialProcessSize(kip)) {
AddInitialProcess(kip);
}
}
}
}
}
/* Add the stratosphere kips. */
{
const auto &external_package = GetExternalPackage();
for (u32 i = 0; i < external_package.header.num_kips; ++i) {
const auto &meta = external_package.header.kip_metas[i];
AddInitialProcess(reinterpret_cast<const InitialProcessHeader *>(external_package.kips + meta.offset), std::addressof(meta.hash));
}
}
/* Get meta for FS process. */
auto *fs_meta = FindInitialProcess(FsProgramId);
if (fs_meta == nullptr) {
/* Get nintendo header/data. */
const pkg2::Package2Header *nn_header = reinterpret_cast<const pkg2::Package2Header *>(nn_package2);
const u8 *nn_data = nn_package2 + sizeof(*nn_header);
/* Get Nintendo INI1. */
const InitialProcessBinaryHeader *nn_ini = FindInitialProcessBinary(nn_header, nn_data, target_firmware);
if (nn_ini == nullptr || nn_ini->magic != InitialProcessBinaryHeader::Magic) {
ShowFatalError("Failed to find INI1!\n");
}
/* Find FS KIP. */
const InitialProcessHeader *nn_fs_kip = FindInitialProcessInBinary(nn_ini, FsProgramId);
if (nn_fs_kip == nullptr) {
ShowFatalError("Failed to find FS!\n");
}
/* Add to binary. */
AddInitialProcess(nn_fs_kip);
/* Re-find meta. */
fs_meta = FindInitialProcess(FsProgramId);
}
/* Check that we found FS. */
if (fs_meta == nullptr) {
ShowFatalError("Failed to find FS!\n");
}
/* Get FS version. */
const auto fs_version = GetFsVersion(fs_meta->kip_hash);
if (fs_version >= FsVersion_Count) {
if (emummc_enabled || nogc_enabled) {
ShowFatalError("Failed to identify FS!\n");
}
}
/* If emummc is enabled, we need to decompress fs .text. */
if (emummc_enabled) {
fs_meta->patch_segments |= (1 << 0);
}
/* Parse/prepare relevant nogc/kip patches. */
{
/* Add nogc patches. */
if (nogc_enabled) {
AddNogcPatches(fs_meta, fs_version);
}
/* TODO ams.tma2: add mount_host patches. */
}
/* Return the fs version we're using. */
return static_cast<u32>(fs_version);
}
void RebuildPackage2(ams::TargetFirmware target_firmware, bool emummc_enabled) {
/* Get the external package. */
const auto &external_package = GetExternalPackage();
/* Clear package2 header. */
auto *package2 = secmon::MemoryRegionDramPackage2.GetPointer<pkg2::Package2Header>();
std::memset(package2, 0, sizeof(*package2));
/* Get payload data pointer. */
u8 * const payload_data = reinterpret_cast<u8 *>(package2 + 1);
/* Useful values. */
constexpr u32 KernelPayloadBase = 0x60000;
/* Set fields. */
package2->meta.key_generation = pkg1::KeyGeneration_Current;
std::memcpy(package2->meta.magic, pkg2::Package2Meta::Magic::String, sizeof(package2->meta.magic));
package2->meta.entrypoint = KernelPayloadBase;
package2->meta.bootloader_version = pkg2::CurrentBootloaderVersion;
package2->meta.package2_version = pkg2::MinimumValidDataVersion;
/* Load mesosphere. */
s64 meso_size;
if (void *sd_meso = ReadFile(std::addressof(meso_size), "sdmc:/atmosphere/mesosphere.bin"); sd_meso != nullptr) {
std::memcpy(payload_data, sd_meso, meso_size);
} else {
meso_size = external_package.header.meso_size;
std::memcpy(payload_data, external_package.mesosphere, meso_size);
}
/* Read emummc, if needed. */
const InitialProcessHeader *emummc;
s64 emummc_size;
if (emummc_enabled) {
emummc = static_cast<const InitialProcessHeader *>(ReadFile(std::addressof(emummc_size), "sdmc:/atmosphere/emummc.kip"));
if (emummc == nullptr) {
emummc = reinterpret_cast<const InitialProcessHeader *>(external_package.kips + external_package.header.emummc_meta.offset);
emummc_size = external_package.header.emummc_meta.size;
}
}
/* Set the embedded ini pointer. */
const u32 magic = *reinterpret_cast<const u32 *>(payload_data + 4);
if (magic == MesoshereMetadataLayout0Magic) {
std::memcpy(payload_data + 8, std::addressof(meso_size), sizeof(meso_size));
} else if (magic == MesoshereMetadataLayout1Magic) {
if (const u32 meta_offset = *reinterpret_cast<const u32 *>(payload_data + 8); meta_offset <= meso_size - sizeof(meso_size)) {
s64 relative_offset = meso_size - meta_offset;
std::memcpy(payload_data + meta_offset, std::addressof(relative_offset), sizeof(relative_offset));
} else {
ShowFatalError("Invalid mesosphere metadata layout!\n");
}
} else {
ShowFatalError("Unknown mesosphere metadata version!\n");
}
/* Get the ini pointer. */
InitialProcessBinaryHeader * const ini = reinterpret_cast<InitialProcessBinaryHeader *>(payload_data + meso_size);
/* Set ini fields. */
ini->magic = InitialProcessBinaryHeader::Magic;
ini->num_processes = 0;
ini->reserved = 0;
/* Iterate all processes. */
u8 * const dst_kip_start = reinterpret_cast<u8 *>(ini + 1);
u8 * dst_kip_cur = dst_kip_start;
for (InitialProcessMeta *meta = std::addressof(g_initial_process_meta); meta != nullptr; meta = meta->next) {
/* Get the current kip. */
const auto *src_kip = meta->kip;
auto *dst_kip = reinterpret_cast<InitialProcessHeader *>(dst_kip_cur);
/* Copy the kip header */
std::memcpy(dst_kip, src_kip, sizeof(*src_kip));
const u8 *src_kip_data = reinterpret_cast<const u8 *>(src_kip + 1);
u8 *dst_kip_data = reinterpret_cast< u8 *>(dst_kip + 1);
/* If necessary, inject emummc. */
u32 addl_text_offset = 0;
if (dst_kip->program_id == FsProgramId && emummc_enabled) {
/* Get emummc extents. */
addl_text_offset = emummc->bss_address + emummc->bss_size;
if ((emummc->flags & 7) || !util::IsAligned(addl_text_offset, 0x1000)) {
ShowFatalError("Invalid emummc kip!\n");
}
/* Copy emummc capabilities. */
{
std::memcpy(dst_kip->capabilities, emummc->capabilities, sizeof(emummc->capabilities));
if (target_firmware <= ams::TargetFirmware_1_0_0) {
for (size_t i = 0; i < util::size(dst_kip->capabilities); ++i) {
if (dst_kip->capabilities[i] == 0xFFFFFFFF) {
dst_kip->capabilities[i] = 0x07000E7F;
break;
}
}
}
}
/* Update section headers. */
dst_kip->ro_address += addl_text_offset;
dst_kip->rw_address += addl_text_offset;
dst_kip->bss_address += addl_text_offset;
/* Get emummc sections. */
const u8 *emummc_data = reinterpret_cast<const u8 *>(emummc + 1);
/* Copy emummc sections. */
std::memcpy(dst_kip_data + emummc->rx_address, emummc_data, emummc->rx_compressed_size);
std::memcpy(dst_kip_data + emummc->ro_address, emummc_data + emummc->rx_compressed_size, emummc->ro_compressed_size);
std::memcpy(dst_kip_data + emummc->rw_address, emummc_data + emummc->rx_compressed_size + emummc->ro_compressed_size, emummc->rw_compressed_size);
std::memset(dst_kip_data + emummc->bss_address, 0, emummc->bss_size);
/* Advance. */
dst_kip_data += addl_text_offset;
}
/* Prepare to process segments. */
u8 *dst_rx_data, *dst_ro_data, *dst_rw_data;
/* Process .text. */
{
dst_rx_data = dst_kip_data;
std::memcpy(dst_kip_data, src_kip_data, src_kip->rx_compressed_size);
/* Uncompress, if necessary. */
if ((meta->patch_segments & src_kip->flags) & (1 << 0)) {
BlzUncompress(dst_kip_data + dst_kip->rx_compressed_size);
dst_kip->rx_compressed_size = dst_kip->rx_size;
}
/* Advance. */
dst_kip_data += dst_kip->rx_compressed_size;
src_kip_data += src_kip->rx_compressed_size;
/* Account for potential emummc. */
dst_kip->rx_size += addl_text_offset;
dst_kip->rx_compressed_size += addl_text_offset;
}
/* Process .rodata. */
{
dst_ro_data = dst_kip_data;
std::memcpy(dst_kip_data, src_kip_data, src_kip->ro_compressed_size);
/* Uncompress, if necessary. */
if ((meta->patch_segments & src_kip->flags) & (1 << 1)) {
BlzUncompress(dst_kip_data + dst_kip->ro_compressed_size);
dst_kip->ro_compressed_size = dst_kip->ro_size;
}
/* Advance. */
dst_kip_data += dst_kip->ro_compressed_size;
src_kip_data += src_kip->ro_compressed_size;
}
/* Process .rwdata. */
{
dst_rw_data = dst_kip_data;
std::memcpy(dst_kip_data, src_kip_data, src_kip->rw_compressed_size);
/* Uncompress, if necessary. */
if ((meta->patch_segments & src_kip->flags) & (1 << 2)) {
BlzUncompress(dst_kip_data + dst_kip->rw_compressed_size);
dst_kip->rw_compressed_size = dst_kip->rw_size;
}
/* Advance. */
dst_kip_data += dst_kip->rw_compressed_size;
src_kip_data += src_kip->rw_compressed_size;
}
/* Adjust flags. */
dst_kip->flags &= ~meta->patch_segments;
/* Apply patches. */
for (auto *patch = meta->patches_head; patch != nullptr; patch = patch->next) {
/* Get the destination segment. */
u8 *patch_dst_segment;
switch (patch->start_segment) {
case 0: patch_dst_segment = dst_rx_data; break;
case 1: patch_dst_segment = dst_ro_data; break;
case 2: patch_dst_segment = dst_rw_data; break;
default: ShowFatalError("Unknown patch segment %" PRIu32 "\n", patch->start_segment); break;
}
/* Get the destination. */
u8 * const patch_dst = patch_dst_segment + patch->rel_offset;
/* Apply the patch. */
if (patch->is_memset) {
const u8 val = *static_cast<const u8 *>(patch->data);
std::memset(patch_dst, val, patch->size);
} else {
std::memcpy(patch_dst, patch->data, patch->size);
}
}
/* Advance. */
dst_kip_cur += GetInitialProcessSize(dst_kip);
/* Increment num kips. */
++ini->num_processes;
}
/* Set INI size. */
ini->size = sizeof(*ini) + (dst_kip_cur - dst_kip_start);
if (ini->size > 12_MB) {
ShowFatalError("INI is too big! (0x%08" PRIx32 ")\n", ini->size);
}
/* Set the payload size/offset. */
package2->meta.payload_offsets[0] = KernelPayloadBase;
package2->meta.payload_sizes[0] = util::AlignUp(meso_size + ini->size, 0x10);
/* Set total size. */
package2->meta.package2_size = sizeof(*package2) + package2->meta.payload_sizes[0];
}
}