diff --git a/COMPILING.md b/COMPILING.md
index 78b7331b..99a062e7 100644
--- a/COMPILING.md
+++ b/COMPILING.md
@@ -5,4 +5,6 @@ Horizon OC Compilation Instructions
3. Clone the Horizon OC develop branch (``git clone https://github.com/Horizon-OC/Horizon-OC.git --recurse-submodules``)
4. Run ``./build.sh`` in the root directory
- If you want to compile with extensions, append ``--ext``
+ - To skip components, append ``-s``/``--skip`` followed by one or more of: ``ldr`` (loader), ``clk`` (hoc-clk), ``hek`` (hekate), ``btb`` (benchmark toolbox), ``exos`` (exosphere), ``sm`` (status-monitor download), ``zip`` (dist.zip packaging). Example: ``./build.sh --ext -s ldr hek``
+ - Building the custom Hekate (``--ext`` without skipping ``hek``) additionally requires devkitARM with ``DEVKITARM`` exported
- Installing ``ccache`` is optional; if it is on your PATH it is used automatically to speed up rebuilds
diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp
index a4d90b00..fd96684e 100644
--- a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp
@@ -13,6 +13,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
#include
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
@@ -121,14 +122,12 @@ namespace ams::ldr {
/* Global NSO header cache. */
NsoHeader g_nso_headers[Nso_Count];
- /* Pcv/Ptm/NvServices check cache */
+ /* Pcv/Ptm check cache */
bool g_is_pcv;
bool g_is_ptm;
- bool g_is_nvsrv;
/* Global Zstd decompression context. */
- constexpr size_t ZstdDctxWorkspaceSize = 0x176E8;
- alignas(8) u8 g_zstd_dctx_workspace[ZstdDctxWorkspaceSize];
+ alignas(8) u8 g_zstd_dctx_workspace[util::DecompressZstdWithBicWorkBufferSizeDefault];
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
@@ -182,8 +181,8 @@ namespace ams::ldr {
return (MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
}
- Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
- return static_cast((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
+ Npdm::ProcessAddressSpace GetProcessAddressSpace(const Meta *meta) {
+ return static_cast((meta->npdm->flags0 & Npdm::MetaFlag0_ProcessAddressSpaceMask) >> Npdm::MetaFlag0_ProcessAddressSpaceShift);
}
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
@@ -226,7 +225,7 @@ namespace ams::ldr {
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, g_nso_headers + ctx.nso_count, sizeof(NsoHeader)));
- R_UNLESS(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso());
+ R_UNLESS_LOG(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso(), "[ldr] NSO header truncated!\n");
/* Note nso is present. */
switch (i) {
@@ -265,7 +264,7 @@ namespace ams::ldr {
Result CheckAutoLoad(const AutoLoadModuleContext &ctx, u32 acid_flags) {
/* We must always have a main. */
- R_UNLESS(ctx.ali.has_main, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(ctx.ali.has_main, ldr::ResultInvalidNso(), "[ldr] Missing main!\n");
/* Validate flags and extents for all present NSOs. */
for (int i = 0; i < ctx.nso_count; ++i) {
@@ -273,11 +272,11 @@ namespace ams::ldr {
/* All NSOs must not be --X. */
/* This is "probably" not checked on Ounce? */
- R_UNLESS((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso());
+ R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] --x not allowed!\n", i);
/* Zstd compression only allowed on main, and only when both rtld+sdk are present. */
if (i != ctx.main_nso_idx || ctx.rtld_idx < 0 || ctx.sdk_nso_idx < 0) {
- R_UNLESS((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso());
+ R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] zbic not allowed!\n", i);
}
/* NSOs must have page-aligned segments. */
@@ -300,13 +299,13 @@ namespace ams::ldr {
const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
if (ctx.ali.has_rtld || ctx.ali.has_sdk) {
/* If we have sdk we must have rtld. */
- R_UNLESS(ctx.ali.has_rtld, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(ctx.ali.has_rtld, ldr::ResultInvalidNso(), "[ldr] Missing rtld!\n");
/* If we have rtld, we must not have browser core dll. */
- R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(!has_browser_dll, ldr::ResultInvalidNso(), "[ldr] BrowserCoreDll must not be present!\n");
} else {
/* We must not have both subsdk and browser dll. */
- R_UNLESS(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
+ R_UNLESS_LOG(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso(), "[ldr] Can't have both subsdk and BrowserCoreDll!\n");
}
R_SUCCEED();
@@ -380,7 +379,6 @@ namespace ams::ldr {
/* Check if NCA is PCV or PTM */
g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv;
g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm;
- g_is_nvsrv = meta->aci->program_id == ncm::SystemProgramId::NvServices;
/* If we have data to validate, validate it. */
if (meta->check_verification_data) {
@@ -396,6 +394,8 @@ namespace ams::ldr {
/* If the signature check fails, we need to check if this is allowable. */
if (!is_signature_valid) {
+ AMS_LOG("[ldr] invalid signature!\n");
+
/* We have to enforce signature checks on prod and when we have a signature to check on dev. */
R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
@@ -409,52 +409,56 @@ namespace ams::ldr {
R_SUCCEED();
}
- Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
- const u8 meta_flags = meta->npdm->flags;
+ Result GetCreateProcessParameterFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
+ const u8 meta_flags0 = meta->npdm->flags0;
+ const u8 meta_flags1 = meta->npdm->flags1;
u32 flags = 0;
/* Set Is64Bit. */
- if (meta_flags & Npdm::MetaFlag_Is64Bit) {
- flags |= svc::CreateProcessFlag_Is64Bit;
+ if (meta_flags0 & Npdm::MetaFlag0_Is64BitInstruction) {
+ flags |= svc::CreateProcessParameterFlag_64Bit;
}
- /* Set AddressSpaceType. */
- switch (GetAddressSpaceType(meta)) {
- case Npdm::AddressSpaceType_32Bit:
- flags |= svc::CreateProcessFlag_AddressSpace32Bit;
+ /* Set ProcessAddressSpace. */
+ switch (GetProcessAddressSpace(meta)) {
+ case Npdm::ProcessAddressSpace_32Bit:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace32Bit;
break;
- case Npdm::AddressSpaceType_64BitDeprecated:
- flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
+ case Npdm::ProcessAddressSpace_64Bit36:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit36;
break;
- case Npdm::AddressSpaceType_32BitWithoutAlias:
- flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
+ case Npdm::ProcessAddressSpace_32BitNoReserved:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved;
break;
- case Npdm::AddressSpaceType_64Bit:
- flags |= svc::CreateProcessFlag_AddressSpace64Bit;
+ case Npdm::ProcessAddressSpace_64Bit39:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit39;
+ break;
+ case Npdm::ProcessAddressSpace_64Bit42:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit42;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
- if (ldr_flags & CreateProcessFlag_EnableDebug) {
- flags |= svc::CreateProcessFlag_EnableDebug;
+ if (ldr_flags & CreateProcessParameterFlag_EnableJitDebug) {
+ flags |= svc::CreateProcessParameterFlag_EnableJitDebug;
}
/* Set Enable ASLR. */
- if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
- flags |= svc::CreateProcessFlag_EnableAslr;
+ if (!(ldr_flags & CreateProcessParameterFlag_DisableAslr)) {
+ flags |= svc::CreateProcessParameterFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
- flags |= svc::CreateProcessFlag_IsApplication;
+ flags |= svc::CreateProcessParameterFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
- if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
- flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
+ if (meta_flags0 & Npdm::MetaFlag0_OptimizeMemoryAllocation) {
+ flags |= svc::CreateProcessParameterFlag_OptimizeMemoryAllocation;
}
}
}
@@ -466,19 +470,19 @@ namespace ams::ldr {
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
- flags |= svc::CreateProcessFlag_PoolPartitionApplet;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
} else {
- flags |= svc::CreateProcessFlag_PoolPartitionApplication;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
- flags |= svc::CreateProcessFlag_PoolPartitionApplet;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
- flags |= svc::CreateProcessFlag_PoolPartitionSystem;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
- flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
@@ -486,18 +490,23 @@ namespace ams::ldr {
} else if (hos::GetVersion() >= hos::Version_4_0_0) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
- flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
+ flags |= svc::CreateProcessParameterFlag_DeprecatedUseSecureMemory;
}
}
- /* 11.0.0+/meso Set Disable DAS merge. */
- if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
- flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
+ /* 11.0.0+/meso Set Disable DAS Merge. */
+ if (meta_flags0 & Npdm::MetaFlag0_DisableDeviceAddressSpaceMerge) {
+ flags |= svc::CreateProcessParameterFlag_DisableDeviceAddressSpaceMerge;
}
- /* 18.0.0+/meso Set Alias region extra size. */
- if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
- flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
+ /* 18.0.0+/meso Set Enable Address Sanitizer . */
+ if (meta_flags0 & Npdm::MetaFlag0_EnableAddressSanitizer) {
+ flags |= svc::CreateProcessParameterFlag_EnableAddressSanitizer;
+ }
+
+ /* 23.0.0+/meso Set Enable Shadow Stack. */
+ if (meta_flags1 & Npdm::MetaFlag1_EnableShadowStack) {
+ flags |= svc::CreateProcessParameterFlag_EnableShadowStack;
}
*out = flags;
@@ -515,7 +524,7 @@ namespace ams::ldr {
out->reslimit = resource_limit;
/* Set flags. */
- R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
+ R_TRY(GetCreateProcessParameterFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
@@ -525,7 +534,7 @@ namespace ams::ldr {
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
- R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
+ R_UNLESS((out->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit39), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
@@ -598,25 +607,29 @@ namespace ams::ldr {
uintptr_t aslr_start = 0;
size_t aslr_size = 0;
if (hos::GetVersion() >= hos::Version_2_0_0) {
- switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) {
- case svc::CreateProcessFlag_AddressSpace32Bit:
- case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
+ switch (out_param->flags & svc::CreateProcessParameterFlag_AddressSpaceMask) {
+ case svc::CreateProcessParameterFlag_AddressSpace32Bit:
+ case svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved:
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
break;
- case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit36:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
- case svc::CreateProcessFlag_AddressSpace64Bit:
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit39:
aslr_start = svc::AddressMap39Start;
aslr_size = svc::AddressMap39Size;
break;
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit42:
+ aslr_start = svc::AddressMap42Start;
+ aslr_size = svc::AddressMap42Size;
+ break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
} else {
/* On 1.0.0, only 2 address space types existed. */
- if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
+ if (out_param->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit36) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
@@ -629,7 +642,7 @@ namespace ams::ldr {
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
size_t free_size = (aslr_size - total_size);
- if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
+ if (out_param->flags & svc::CreateProcessParameterFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
@@ -651,10 +664,13 @@ namespace ams::ldr {
R_SUCCEED();
}
- Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zstd, uintptr_t map_base, uintptr_t map_end) {
+ Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zbic, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
size_t file_size = is_compressed ? compressed_size : segment_size;
+ AMS_LOG("[ldr] Loading segment @ 0x%016lx: compressed=%d, file_size=0x%08lx, compressed_size=0x%08lx, segment_size=0x%08lx\n",
+ map_base, is_compressed, file_size, compressed_size, segment_size);
+
/* Validate size. */
R_UNLESS(file_size <= segment_size, ldr::ResultInvalidNso());
R_UNLESS(file_size <= std::numeric_limits::max(), ldr::ResultInvalidNso());
@@ -664,19 +680,19 @@ namespace ams::ldr {
uintptr_t load_address = is_compressed ? map_end - compressed_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, file_offset, reinterpret_cast(load_address), file_size));
- R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(read_size == file_size, ldr::ResultInvalidNso(), "[ldr] Couldn't read segment from file!\n");
/* Uncompress if necessary. */
R_SUCCEED_IF(!is_compressed);
auto compressed_data_buf = reinterpret_cast(load_address);
- if (is_zstd) {
- bool decompressed = util::DecompressZstdForLoader(reinterpret_cast(g_zstd_dctx_workspace), ZstdDctxWorkspaceSize, reinterpret_cast(map_base), static_cast(map_end - map_base), segment_size, compressed_data_buf, file_size);
- R_UNLESS(decompressed, ldr::ResultInvalidNso());
+ if (is_zbic) {
+ bool decompressed = util::DecompressZstdWithBic(reinterpret_cast(g_zstd_dctx_workspace), sizeof(g_zstd_dctx_workspace), reinterpret_cast(map_base), static_cast(map_end - map_base), segment_size, compressed_data_buf, file_size);
+ R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with zbic!\n");
} else {
bool decompressed = (util::DecompressLZ4(reinterpret_cast(map_base), segment_size, compressed_data_buf, file_size) == static_cast(segment_size));
- R_UNLESS(decompressed, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with lz4!\n");
}
R_SUCCEED();
@@ -691,12 +707,12 @@ namespace ams::ldr {
crypto::GenerateSha256(hash, sizeof(hash),
reinterpret_cast(map_address + nso_header->segments[segment].dst_offset),
nso_header->segments[segment].size);
- R_UNLESS(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso(), "[ldr] Invalid segment hash!\n");
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, size_t map_size) {
- const bool is_zstd = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
+ const bool is_zbic = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t module_size = static_cast(nso_header->rw_dst_offset) + util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
const size_t arena_size = (nso_size > module_size) ? (nso_size - module_size) : 0;
@@ -713,11 +729,11 @@ namespace ams::ldr {
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Text].file_offset, nso_header->text_compressed_size, nso_header->text_size,
- (nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zstd, map_address + nso_header->text_dst_offset, map_end));
+ (nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zbic, map_address + nso_header->text_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Ro].file_offset, nso_header->ro_compressed_size, nso_header->ro_size,
- (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zstd, map_address + nso_header->ro_dst_offset, map_end));
+ (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zbic, map_address + nso_header->ro_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Rw].file_offset, nso_header->rw_compressed_size, nso_header->rw_size,
- (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zstd, map_address + nso_header->rw_dst_offset, map_end));
+ (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zbic, map_address + nso_header->rw_dst_offset, map_end));
/* Clear unused space to zero. */
const size_t text_end = static_cast(nso_header->text_dst_offset) + static_cast(nso_header->text_size);
@@ -754,11 +770,6 @@ namespace ams::ldr {
if (g_is_ptm) {
hoc::ptm::Patch(map_address, nso_size);
}
-
- // TODO: figure out the cause of crashes with nvsrv patches
- if (g_is_nvsrv) {
- // hoc::nvsrv::Patch(map_address, nso_size, nso_address);
- }
}
/* Set permissions. */
@@ -785,15 +796,17 @@ namespace ams::ldr {
for (int i = 0; i < ctx.nso_count; i++) {
const NsoIndex nso_idx = static_cast(ctx.ali.nso_indices[i]);
+ const bool is_zbic = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
+ const size_t map_size = is_zbic ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
+
+ AMS_LOG("[ldr] module[%d]: idx=%d, path='%s', zbic=%d\n", i, (int)nso_idx, GetNsoPath(nso_idx), is_zbic);
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(nso_idx), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
- const bool is_zstd = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
- const size_t map_size = is_zstd ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
-
- R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i, process_info->nso_address[i], process_info->nso_size[i], map_size));
+ R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i,
+ process_info->nso_address[i], process_info->nso_size[i], map_size));
}
/* Load arguments, if present. */
@@ -834,7 +847,7 @@ namespace ams::ldr {
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
out->code_address = param.code_address;
- ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
+ ON_RESULT_FAILURE { R_DISCARD(svc::CloseHandle(process_handle)); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, ctx, argument));
@@ -892,8 +905,8 @@ namespace ams::ldr {
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
- const auto as_type = GetAddressSpaceType(std::addressof(meta));
- RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
+ const auto as_type = GetProcessAddressSpace(std::addressof(meta));
+ RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::ProcessAddressSpace_64Bit39 || as_type == Npdm::ProcessAddressSpace_64Bit36);
/* Register all NSOs. */
for (int i = 0; i < ctx.nso_count; i++) {
@@ -904,7 +917,7 @@ namespace ams::ldr {
/* If we're overriding for HBL, perform HTML document redirection. */
if (override_status.IsHbl()) {
/* Don't validate result, failure is okay. */
- RedirectHtmlDocumentPathForHbl(loc);
+ R_DISCARD(RedirectHtmlDocumentPathForHbl(loc));
}
/* Clear the external code for the program. */
diff --git a/Source/Old-Atmosphere-Patches/ldr_process_creation_1_12_0.cpp b/Source/Old-Atmosphere-Patches/ldr_process_creation_1_12_0.cpp
new file mode 100644
index 00000000..fd96684e
--- /dev/null
+++ b/Source/Old-Atmosphere-Patches/ldr_process_creation_1_12_0.cpp
@@ -0,0 +1,973 @@
+/*
+ * 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 .
+ */
+
+#include
+#include "ldr_capabilities.hpp"
+#include "ldr_content_management.hpp"
+#include "ldr_development_manager.hpp"
+#include "ldr_launch_record.hpp"
+#include "ldr_meta.hpp"
+#include "ldr_patcher.hpp"
+#include "ldr_process_creation.hpp"
+#include "ldr_ro_manager.hpp"
+#include "oc/oc_loader.hpp"
+
+namespace ams::ldr {
+
+ namespace {
+
+ /* Convenience defines. */
+ constexpr size_t SystemResourceSizeMax = 0x1FE00000;
+ constexpr size_t AutoLoadModuleSizeMax = 0x800000000;
+
+ /* Types. */
+ enum NsoIndex {
+ Nso_Rtld = 0,
+ Nso_Main = 1,
+ Nso_Wkc0 = 2,
+ Nso_Wkc1 = 3,
+ Nso_Wkc2 = 4,
+ Nso_Wkc3 = 5,
+ Nso_Wkc4 = 6,
+ Nso_Wkc5 = 7,
+ Nso_Wkc6 = 8,
+ Nso_Wkc7 = 9,
+ Nso_Wkc8 = 10,
+ Nso_Wkc9 = 11,
+ Nso_SubSdk0 = 12,
+ Nso_SubSdk1 = 13,
+ Nso_SubSdk2 = 14,
+ Nso_SubSdk3 = 15,
+ Nso_SubSdk4 = 16,
+ Nso_SubSdk5 = 17,
+ Nso_SubSdk6 = 18,
+ Nso_SubSdk7 = 19,
+ Nso_SubSdk8 = 20,
+ Nso_SubSdk9 = 21,
+ Nso_Sdk = 22,
+ Nso_Count,
+ };
+
+ constexpr inline const char *NsoPaths[Nso_Count] = {
+ ENCODE_ATMOSPHERE_CODE_PATH("/rtld"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/main"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc0"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc1"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc2"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc3"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc4"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc5"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc6"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc7"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc8"),
+ ENCODE_ATMOSPHERE_BDLL_PATH("/wkc9"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"),
+ ENCODE_ATMOSPHERE_CODE_PATH("/sdk"),
+ };
+
+ constexpr const char *GetNsoPath(size_t idx) {
+ AMS_ABORT_UNLESS(idx < Nso_Count);
+ return NsoPaths[idx];
+ }
+
+ struct ProcessInfo {
+ os::NativeHandle process_handle;
+ uintptr_t code_address;
+ size_t total_size;
+ uintptr_t args_address;
+ size_t args_size;
+ uintptr_t nso_address[Nso_Count];
+ size_t nso_size[Nso_Count];
+ };
+
+ struct AutoLoadModuleInfo {
+ bool has_rtld;
+ bool has_main;
+ bool has_sdk;
+ bool has_subsdk;
+ s8 nso_indices[Nso_Count];
+ };
+
+ struct AutoLoadModuleContext {
+ NsoHeader *headers;
+ int nso_count;
+ int rtld_idx;
+ int main_nso_idx;
+ int sdk_nso_idx;
+ AutoLoadModuleInfo ali;
+ };
+
+ /* Global NSO header cache. */
+ NsoHeader g_nso_headers[Nso_Count];
+
+ /* Pcv/Ptm check cache */
+ bool g_is_pcv;
+ bool g_is_ptm;
+
+ /* Global Zstd decompression context. */
+ alignas(8) u8 g_zstd_dctx_workspace[util::DecompressZstdWithBicWorkBufferSizeDefault];
+
+ Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
+ /* No version verification is done before 8.1.0. */
+ R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0);
+
+ /* No verification is done if development. */
+ R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck());
+
+ /* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */
+ AMS_UNUSED(program_id, version);
+
+ R_SUCCEED();
+ }
+
+ /* Helpers. */
+ Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) {
+ /* Copy basic info. */
+ out->main_thread_priority = meta->npdm->main_thread_priority;
+ out->default_cpu_id = meta->npdm->default_cpu_id;
+ out->main_thread_stack_size = meta->npdm->main_thread_stack_size;
+ out->program_id = meta->aci->program_id;
+
+ /* Copy access controls. */
+ size_t offset = 0;
+#define COPY_ACCESS_CONTROL(source, which) \
+ ({ \
+ const size_t size = meta->source->which##_size; \
+ R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \
+ out->source##_##which##_size = size; \
+ std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \
+ offset += size; \
+ })
+
+ /* Copy all access controls to buffer. */
+ COPY_ACCESS_CONTROL(acid, sac);
+ COPY_ACCESS_CONTROL(aci, sac);
+ COPY_ACCESS_CONTROL(acid, fac);
+ COPY_ACCESS_CONTROL(aci, fah);
+#undef COPY_ACCESS_CONTROL
+
+ /* Copy flags. */
+ out->flags = MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
+ R_SUCCEED();
+ }
+
+ bool IsApplet(const Meta *meta) {
+ return (MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
+ }
+
+ bool IsApplication(const Meta *meta) {
+ return (MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
+ }
+
+ Npdm::ProcessAddressSpace GetProcessAddressSpace(const Meta *meta) {
+ return static_cast((meta->npdm->flags0 & Npdm::MetaFlag0_ProcessAddressSpaceMask) >> Npdm::MetaFlag0_ProcessAddressSpaceShift);
+ }
+
+ Acid::PoolPartition GetPoolPartition(const Meta *meta) {
+ return static_cast((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
+ }
+
+ Result LoadAutoLoadHeaders(AutoLoadModuleContext &ctx, u32 acid_flags) {
+ /* Clear NSOs. */
+ std::memset(g_nso_headers, 0, sizeof(g_nso_headers));
+ ctx.headers = g_nso_headers;
+ ctx.nso_count = 0;
+ ctx.rtld_idx = -1;
+ ctx.main_nso_idx = -1;
+ ctx.sdk_nso_idx = -1;
+ ctx.ali = {};
+
+ for (size_t i = 0; i < Nso_Count; i++) {
+ /* Only load browser DLLs if acid flags say to do so. */
+ switch (i) {
+ case Nso_Wkc0:
+ case Nso_Wkc1:
+ case Nso_Wkc2:
+ case Nso_Wkc3:
+ case Nso_Wkc4:
+ case Nso_Wkc5:
+ case Nso_Wkc6:
+ case Nso_Wkc7:
+ case Nso_Wkc8:
+ case Nso_Wkc9:
+ if ((acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) == 0) {
+ continue;
+ }
+ break;
+ }
+
+ fs::FileHandle file;
+ if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) {
+ ON_SCOPE_EXIT { fs::CloseFile(file); };
+
+ /* Read NSO header. */
+ size_t read_size;
+ R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, g_nso_headers + ctx.nso_count, sizeof(NsoHeader)));
+ R_UNLESS_LOG(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso(), "[ldr] NSO header truncated!\n");
+
+ /* Note nso is present. */
+ switch (i) {
+ case Nso_Rtld:
+ ctx.rtld_idx = ctx.nso_count;
+ ctx.ali.has_rtld = true;
+ break;
+ case Nso_Main:
+ ctx.main_nso_idx = ctx.nso_count;
+ ctx.ali.has_main = true;
+ break;
+ case Nso_SubSdk0:
+ case Nso_SubSdk1:
+ case Nso_SubSdk2:
+ case Nso_SubSdk3:
+ case Nso_SubSdk4:
+ case Nso_SubSdk5:
+ case Nso_SubSdk6:
+ case Nso_SubSdk7:
+ case Nso_SubSdk8:
+ case Nso_SubSdk9:
+ ctx.ali.has_subsdk = true;
+ break;
+ case Nso_Sdk:
+ ctx.sdk_nso_idx = ctx.nso_count;
+ ctx.ali.has_sdk = true;
+ break;
+ }
+ ctx.ali.nso_indices[ctx.nso_count] = static_cast(i);
+ ctx.nso_count++;
+ }
+ }
+
+ R_SUCCEED();
+ }
+
+ Result CheckAutoLoad(const AutoLoadModuleContext &ctx, u32 acid_flags) {
+ /* We must always have a main. */
+ R_UNLESS_LOG(ctx.ali.has_main, ldr::ResultInvalidNso(), "[ldr] Missing main!\n");
+
+ /* Validate flags and extents for all present NSOs. */
+ for (int i = 0; i < ctx.nso_count; ++i) {
+ const auto &hdr = ctx.headers[i];
+
+ /* All NSOs must not be --X. */
+ /* This is "probably" not checked on Ounce? */
+ R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] --x not allowed!\n", i);
+
+ /* Zstd compression only allowed on main, and only when both rtld+sdk are present. */
+ if (i != ctx.main_nso_idx || ctx.rtld_idx < 0 || ctx.sdk_nso_idx < 0) {
+ R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] zbic not allowed!\n", i);
+ }
+
+ /* NSOs must have page-aligned segments. */
+ R_UNLESS(util::IsAligned(hdr.text_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
+ R_UNLESS(util::IsAligned(hdr.ro_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
+ R_UNLESS(util::IsAligned(hdr.rw_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
+
+ /* NSOs must have zero text offset. */
+ R_UNLESS(hdr.text_dst_offset == 0, ldr::ResultInvalidNso());
+
+ /* NSO .text must precede .rodata. */
+ const size_t text_end = static_cast(hdr.text_dst_offset) + static_cast(hdr.text_size);
+ R_UNLESS(text_end <= static_cast(hdr.ro_dst_offset), ldr::ResultInvalidNso());
+
+ /* NSO .rodata must precede .rwdata. */
+ const size_t ro_end = static_cast(hdr.ro_dst_offset) + static_cast(hdr.ro_size);
+ R_UNLESS(ro_end <= static_cast(hdr.rw_dst_offset), ldr::ResultInvalidNso());
+ }
+
+ const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
+ if (ctx.ali.has_rtld || ctx.ali.has_sdk) {
+ /* If we have sdk we must have rtld. */
+ R_UNLESS_LOG(ctx.ali.has_rtld, ldr::ResultInvalidNso(), "[ldr] Missing rtld!\n");
+
+ /* If we have rtld, we must not have browser core dll. */
+ R_UNLESS_LOG(!has_browser_dll, ldr::ResultInvalidNso(), "[ldr] BrowserCoreDll must not be present!\n");
+ } else {
+ /* We must not have both subsdk and browser dll. */
+ R_UNLESS_LOG(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso(), "[ldr] Can't have both subsdk and BrowserCoreDll!\n");
+ }
+
+ R_SUCCEED();
+ }
+
+ constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = {
+ { 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */
+ { 0x0100152000022000 }, /* Mario Kart 8 Deluxe */
+ { 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */
+ { 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */
+ { 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */
+ { 0x01002FF008C24000 }, /* Ring Fit Adventure */
+ { 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */
+ { 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */
+ { 0x01006F8002326000 }, /* Animal Crossing: New Horizons */
+ { 0x01006FB00F50E000 }, /* 宝可梦 走吧!伊布 [Pokemon: Let's Go, Eevee! for China] */
+ { 0x010070300F50C000 }, /* 宝可梦 走吧!皮卡丘 [Pokemon: Let's Go, Pikachu! for China] */
+ { 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */
+ { 0x01008DB008C2C000 }, /* Pokemon Shield */
+ { 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */
+ { 0x0100A66003384000 }, /* Hulu */
+ { 0x0100ABF008968000 }, /* Pokemon Sword */
+ { 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */
+ { 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */
+ { 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */
+ { 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */
+ };
+
+ /* Check that the unqualified approval programs are sorted. */
+ static_assert([]() -> bool {
+ for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) {
+ if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) {
+ return false;
+ }
+ }
+
+ return true;
+ }());
+
+ bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) {
+ /* Check if the program id is one with unqualified approval. */
+ return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id);
+ }
+
+ bool IsUnqualifiedApproval(const Meta *meta) {
+ /* If the meta has unqualified approval flag, it's unqualified approval. */
+ if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) {
+ return true;
+ }
+
+ /* If the unqualified approval flag is not set, the program must be an application. */
+ if (!IsApplication(meta)) {
+ return false;
+ }
+
+ /* The program id must be a force unqualified approval program id. */
+ return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max;
+ }
+
+ Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) {
+ /* Validate version. */
+ R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version));
+
+ /* Validate program id. */
+ R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId());
+ R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
+
+ /* Validate the kernel capabilities. */
+ R_TRY(TestCapability(static_cast(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)));
+
+ /* Check if NCA is PCV or PTM */
+ g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv;
+ g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm;
+
+ /* If we have data to validate, validate it. */
+ if (meta->check_verification_data) {
+ const u8 *sig = code_verification_data.signature;
+ const size_t sig_size = sizeof(code_verification_data.signature);
+ const u8 *mod = static_cast(meta->modulus);
+ const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize;
+ const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
+ const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
+ const u8 *hsh = code_verification_data.target_hash;
+ const size_t hsh_size = sizeof(code_verification_data.target_hash);
+ const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size);
+
+ /* If the signature check fails, we need to check if this is allowable. */
+ if (!is_signature_valid) {
+ AMS_LOG("[ldr] invalid signature!\n");
+
+ /* We have to enforce signature checks on prod and when we have a signature to check on dev. */
+ R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
+ R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
+
+ /* There was no signature to check on dev. Check if this is acceptable. */
+ R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature());
+ }
+ }
+
+ /* All good. */
+ R_SUCCEED();
+ }
+
+ Result GetCreateProcessParameterFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
+ const u8 meta_flags0 = meta->npdm->flags0;
+ const u8 meta_flags1 = meta->npdm->flags1;
+
+ u32 flags = 0;
+
+ /* Set Is64Bit. */
+ if (meta_flags0 & Npdm::MetaFlag0_Is64BitInstruction) {
+ flags |= svc::CreateProcessParameterFlag_64Bit;
+ }
+
+ /* Set ProcessAddressSpace. */
+ switch (GetProcessAddressSpace(meta)) {
+ case Npdm::ProcessAddressSpace_32Bit:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace32Bit;
+ break;
+ case Npdm::ProcessAddressSpace_64Bit36:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit36;
+ break;
+ case Npdm::ProcessAddressSpace_32BitNoReserved:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved;
+ break;
+ case Npdm::ProcessAddressSpace_64Bit39:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit39;
+ break;
+ case Npdm::ProcessAddressSpace_64Bit42:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit42;
+ break;
+ default:
+ R_THROW(ldr::ResultInvalidMeta());
+ }
+
+ /* Set Enable Debug. */
+ if (ldr_flags & CreateProcessParameterFlag_EnableJitDebug) {
+ flags |= svc::CreateProcessParameterFlag_EnableJitDebug;
+ }
+
+ /* Set Enable ASLR. */
+ if (!(ldr_flags & CreateProcessParameterFlag_DisableAslr)) {
+ flags |= svc::CreateProcessParameterFlag_EnableAslr;
+ }
+
+ /* Set Is Application. */
+ if (IsApplication(meta)) {
+ flags |= svc::CreateProcessParameterFlag_IsApplication;
+
+ /* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
+ if (hos::GetVersion() >= hos::Version_7_0_0) {
+ if (meta_flags0 & Npdm::MetaFlag0_OptimizeMemoryAllocation) {
+ flags |= svc::CreateProcessParameterFlag_OptimizeMemoryAllocation;
+ }
+ }
+ }
+
+ /* 5.0.0+ Set Pool Partition. */
+ if (hos::GetVersion() >= hos::Version_5_0_0) {
+ /* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */
+ /* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */
+ switch (GetPoolPartition(meta)) {
+ case Acid::PoolPartition_Application:
+ if (IsApplet(meta)) {
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
+ } else {
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplication;
+ }
+ break;
+ case Acid::PoolPartition_Applet:
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
+ break;
+ case Acid::PoolPartition_System:
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionSystem;
+ break;
+ case Acid::PoolPartition_SystemNonSecure:
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionSystemNonSecure;
+ break;
+ default:
+ R_THROW(ldr::ResultInvalidMeta());
+ }
+ } else if (hos::GetVersion() >= hos::Version_4_0_0) {
+ /* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
+ if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
+ flags |= svc::CreateProcessParameterFlag_DeprecatedUseSecureMemory;
+ }
+ }
+
+ /* 11.0.0+/meso Set Disable DAS Merge. */
+ if (meta_flags0 & Npdm::MetaFlag0_DisableDeviceAddressSpaceMerge) {
+ flags |= svc::CreateProcessParameterFlag_DisableDeviceAddressSpaceMerge;
+ }
+
+ /* 18.0.0+/meso Set Enable Address Sanitizer . */
+ if (meta_flags0 & Npdm::MetaFlag0_EnableAddressSanitizer) {
+ flags |= svc::CreateProcessParameterFlag_EnableAddressSanitizer;
+ }
+
+ /* 23.0.0+/meso Set Enable Shadow Stack. */
+ if (meta_flags1 & Npdm::MetaFlag1_EnableShadowStack) {
+ flags |= svc::CreateProcessParameterFlag_EnableShadowStack;
+ }
+
+ *out = flags;
+ R_SUCCEED();
+ }
+
+ Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) {
+ /* Clear output. */
+ std::memset(out, 0, sizeof(*out));
+
+ /* Set name, version, program id, resource limit handle. */
+ std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1);
+ out->version = meta->npdm->version;
+ out->program_id = meta->aci->program_id.value;
+ out->reslimit = resource_limit;
+
+ /* Set flags. */
+ R_TRY(GetCreateProcessParameterFlags(std::addressof(out->flags), meta, flags));
+
+ /* 3.0.0+ System Resource Size. */
+ if (hos::GetVersion() >= hos::Version_3_0_0) {
+ /* Validate size is aligned. */
+ R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize());
+
+ /* Validate system resource usage. */
+ if (meta->npdm->system_resource_size) {
+ /* Process must be 64-bit. */
+ R_UNLESS((out->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit39), ldr::ResultInvalidMeta());
+
+ /* Process must be application or applet. */
+ R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
+
+ /* Size must be less than or equal to max. */
+ R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta());
+ }
+ out->system_resource_num_pages = meta->npdm->system_resource_size >> 12;
+ }
+
+ R_SUCCEED();
+ }
+
+ 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);
+ }
+
+ Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument) {
+ /* Clear output. */
+ out->args_address = 0;
+ out->args_size = 0;
+ std::memset(out->nso_address, 0, sizeof(out->nso_address));
+ std::memset(out->nso_size, 0, sizeof(out->nso_size));
+
+ size_t total_size = 0;
+ bool argument_allocated = false;
+
+ /* Calculate base offsets. */
+ for (int i = 0; i < ctx.nso_count; i++) {
+ out->nso_address[i] = total_size;
+ const size_t text_end = static_cast(ctx.headers[i].text_dst_offset) + static_cast(ctx.headers[i].text_size);
+ const size_t ro_end = static_cast(ctx.headers[i].ro_dst_offset) + static_cast(ctx.headers[i].ro_size);
+ const size_t rw_end = static_cast(ctx.headers[i].rw_dst_offset) + static_cast(ctx.headers[i].rw_size);
+ out->nso_size[i] = text_end;
+ out->nso_size[i] = std::max(out->nso_size[i], ro_end);
+ out->nso_size[i] = std::max(out->nso_size[i], rw_end);
+ out->nso_size[i] += static_cast(ctx.headers[i].bss_size);
+
+ /* Reserve hook arena memory past pcv's bss. */
+ if (g_is_pcv && i == ctx.main_nso_idx) {
+ out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize) + hoc::pcv::PcvDataArenaSize;
+ }
+
+ const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
+ R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
+ R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
+
+ out->nso_size[i] = aligned_up_size;
+
+ R_UNLESS(util::CanAddWithoutOverflow(total_size, out->nso_size[i]), ldr::ResultInvalidNso());
+ total_size += out->nso_size[i];
+
+ if (!argument_allocated && argument != nullptr) {
+ out->args_address = total_size;
+ out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
+
+ R_UNLESS(util::CanAddWithoutOverflow(total_size, out->args_size), ldr::ResultInvalidNso());
+ total_size += out->args_size;
+
+ argument_allocated = true;
+ }
+ }
+
+ /* Calculate ASLR. */
+ uintptr_t aslr_start = 0;
+ size_t aslr_size = 0;
+ if (hos::GetVersion() >= hos::Version_2_0_0) {
+ switch (out_param->flags & svc::CreateProcessParameterFlag_AddressSpaceMask) {
+ case svc::CreateProcessParameterFlag_AddressSpace32Bit:
+ case svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved:
+ aslr_start = svc::AddressSmallMap32Start;
+ aslr_size = svc::AddressSmallMap32Size;
+ break;
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit36:
+ aslr_start = svc::AddressSmallMap36Start;
+ aslr_size = svc::AddressSmallMap36Size;
+ break;
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit39:
+ aslr_start = svc::AddressMap39Start;
+ aslr_size = svc::AddressMap39Size;
+ break;
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit42:
+ aslr_start = svc::AddressMap42Start;
+ aslr_size = svc::AddressMap42Size;
+ break;
+ AMS_UNREACHABLE_DEFAULT_CASE();
+ }
+ } else {
+ /* On 1.0.0, only 2 address space types existed. */
+ if (out_param->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit36) {
+ aslr_start = svc::AddressSmallMap36Start;
+ aslr_size = svc::AddressSmallMap36Size;
+ } else {
+ aslr_start = svc::AddressSmallMap32Start;
+ aslr_size = svc::AddressSmallMap32Size;
+ }
+ }
+ R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory());
+
+ /* Set Create Process output. */
+ uintptr_t aslr_slide = 0;
+ size_t free_size = (aslr_size - total_size);
+ if (out_param->flags & svc::CreateProcessParameterFlag_EnableAslr) {
+ aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
+ }
+
+ /* Set out. */
+ aslr_start += aslr_slide;
+ for (int i = 0; i < ctx.nso_count; i++) {
+ R_UNLESS(util::CanAddWithoutOverflow(out->nso_address[i], aslr_start), ldr::ResultInvalidNso());
+ out->nso_address[i] += aslr_start;
+ }
+ if (out->args_address) {
+ R_UNLESS(util::CanAddWithoutOverflow(out->args_address, aslr_start), ldr::ResultInvalidNso());
+ out->args_address += aslr_start;
+ }
+
+ out_param->code_address = aslr_start;
+ out_param->code_num_pages = total_size >> 12;
+ out->total_size = total_size;
+
+ R_SUCCEED();
+ }
+
+ Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zbic, uintptr_t map_base, uintptr_t map_end) {
+ /* Select read size based on compression. */
+ size_t file_size = is_compressed ? compressed_size : segment_size;
+
+ AMS_LOG("[ldr] Loading segment @ 0x%016lx: compressed=%d, file_size=0x%08lx, compressed_size=0x%08lx, segment_size=0x%08lx\n",
+ map_base, is_compressed, file_size, compressed_size, segment_size);
+
+ /* Validate size. */
+ R_UNLESS(file_size <= segment_size, ldr::ResultInvalidNso());
+ R_UNLESS(file_size <= std::numeric_limits::max(), ldr::ResultInvalidNso());
+ R_UNLESS(segment_size <= std::numeric_limits::max(), ldr::ResultInvalidNso());
+
+ /* Load data from file. */
+ uintptr_t load_address = is_compressed ? map_end - compressed_size : map_base;
+ size_t read_size;
+ R_TRY(fs::ReadFile(std::addressof(read_size), file, file_offset, reinterpret_cast(load_address), file_size));
+ R_UNLESS_LOG(read_size == file_size, ldr::ResultInvalidNso(), "[ldr] Couldn't read segment from file!\n");
+
+ /* Uncompress if necessary. */
+ R_SUCCEED_IF(!is_compressed);
+
+ auto compressed_data_buf = reinterpret_cast(load_address);
+
+ if (is_zbic) {
+ bool decompressed = util::DecompressZstdWithBic(reinterpret_cast(g_zstd_dctx_workspace), sizeof(g_zstd_dctx_workspace), reinterpret_cast(map_base), static_cast(map_end - map_base), segment_size, compressed_data_buf, file_size);
+ R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with zbic!\n");
+ } else {
+ bool decompressed = (util::DecompressLZ4(reinterpret_cast(map_base), segment_size, compressed_data_buf, file_size) == static_cast(segment_size));
+ R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with lz4!\n");
+ }
+
+ R_SUCCEED();
+ }
+
+ Result CheckSegmentHash(const NsoHeader *nso_header, uintptr_t map_address, NsoHeader::Segment segment) {
+ if ((nso_header->flags & (NsoHeader::Flag_CheckHashText << segment)) == 0) {
+ R_SUCCEED();
+ }
+
+ u8 hash[crypto::Sha256Generator::HashSize];
+ crypto::GenerateSha256(hash, sizeof(hash),
+ reinterpret_cast(map_address + nso_header->segments[segment].dst_offset),
+ nso_header->segments[segment].size);
+ R_UNLESS_LOG(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso(), "[ldr] Invalid segment hash!\n");
+ R_SUCCEED();
+ }
+
+ Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, size_t map_size) {
+ const bool is_zbic = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
+
+ const size_t module_size = static_cast(nso_header->rw_dst_offset) + util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
+ const size_t arena_size = (nso_size > module_size) ? (nso_size - module_size) : 0;
+
+ /* Map and read data from file. */
+ {
+ /* Map the process memory. */
+ void *mapped_memory = nullptr;
+ R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, map_size, GenerateSecureRandom));
+ ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, map_size); };
+
+ const uintptr_t map_address = reinterpret_cast(mapped_memory);
+ const uintptr_t map_end = map_address + map_size;
+
+ /* Load NSO segments. */
+ R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Text].file_offset, nso_header->text_compressed_size, nso_header->text_size,
+ (nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zbic, map_address + nso_header->text_dst_offset, map_end));
+ R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Ro].file_offset, nso_header->ro_compressed_size, nso_header->ro_size,
+ (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zbic, map_address + nso_header->ro_dst_offset, map_end));
+ R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Rw].file_offset, nso_header->rw_compressed_size, nso_header->rw_size,
+ (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zbic, map_address + nso_header->rw_dst_offset, map_end));
+
+ /* Clear unused space to zero. */
+ const size_t text_end = static_cast(nso_header->text_dst_offset) + static_cast(nso_header->text_size);
+ const size_t ro_end = static_cast(nso_header->ro_dst_offset) + static_cast(nso_header->ro_size);
+ const size_t rw_end = static_cast(nso_header->rw_dst_offset) + static_cast(nso_header->rw_size);
+ std::memset(reinterpret_cast(map_address + text_end), 0, nso_header->ro_dst_offset - text_end);
+ std::memset(reinterpret_cast(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end);
+ std::memset(reinterpret_cast(map_address + rw_end), 0, nso_size - rw_end);
+
+ /* Check segment hashes. */
+ R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Text));
+ R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Ro));
+ R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Rw));
+
+ /* Apply embedded patches. */
+ ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size);
+
+ /* Apply IPS patches. */
+ LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
+
+ /* Apply PCV and PTM patches */
+ if (g_is_pcv) {
+ const size_t text_end = static_cast(nso_header->text_size);
+ const size_t rx_end = util::AlignUp(text_end, os::MemoryPageSize);
+ const size_t ro_start = static_cast(nso_header->ro_dst_offset);
+ const size_t cave_end = (rx_end < ro_start) ? rx_end : ro_start;
+ const uintptr_t cave = map_address + text_end;
+ const size_t cave_size = (cave_end > text_end) ? (cave_end - text_end) : 0;
+
+ /* module_size is used rather than nso_size to exclude the extra data section. */
+ hoc::pcv::Patch(map_address, module_size, cave, cave_size, nso_address, arena_size ? (map_address + module_size) : 0);
+ }
+
+ if (g_is_ptm) {
+ hoc::ptm::Patch(map_address, nso_size);
+ }
+ }
+
+ /* Set permissions. */
+ const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
+ const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
+ const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize) + arena_size;
+ if (text_size) {
+ const bool prevent_code_reads = (nso_header->flags & NsoHeader::Flag_PreventCodeReads);
+ R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
+ }
+ if (ro_size) {
+ R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
+ }
+ if (rw_size) {
+ R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
+ }
+
+ R_SUCCEED();
+ }
+
+ Result LoadAutoLoadModules(const ProcessInfo *process_info, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument) {
+ /* Load each NSO. */
+ const uintptr_t total_end = process_info->code_address + process_info->total_size;
+
+ for (int i = 0; i < ctx.nso_count; i++) {
+ const NsoIndex nso_idx = static_cast(ctx.ali.nso_indices[i]);
+ const bool is_zbic = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
+ const size_t map_size = is_zbic ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
+
+ AMS_LOG("[ldr] module[%d]: idx=%d, path='%s', zbic=%d\n", i, (int)nso_idx, GetNsoPath(nso_idx), is_zbic);
+
+ fs::FileHandle file;
+ R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(nso_idx), fs::OpenMode_Read));
+ ON_SCOPE_EXIT { fs::CloseFile(file); };
+
+ R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i,
+ process_info->nso_address[i], process_info->nso_size[i], map_size));
+ }
+
+ /* Load arguments, if present. */
+ if (argument != nullptr) {
+ /* Write argument data into memory. */
+ {
+ void *map_address = nullptr;
+ R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom));
+ ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); };
+
+ ProgramArguments *args = static_cast(map_address);
+ std::memset(args, 0, sizeof(*args));
+ args->allocated_size = process_info->args_size;
+ args->arguments_size = argument->argument_size;
+ std::memcpy(args->arguments, argument->argument, argument->argument_size);
+ }
+
+ /* Set argument region permissions. */
+ /* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */
+ R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite));
+ }
+
+ R_SUCCEED();
+ }
+
+ Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
+ /* Get CreateProcessParameter. */
+ svc::CreateProcessParameter param;
+ R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
+
+ /* Decide on an NSO layout. */
+ R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), ctx, argument));
+
+ /* Actually create process. */
+ svc::Handle process_handle;
+ R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(u32)));
+
+ /* Set the output handle, and ensure that if we fail after this point we clean it up. */
+ out->process_handle = process_handle;
+ out->code_address = param.code_address;
+ ON_RESULT_FAILURE { R_DISCARD(svc::CloseHandle(process_handle)); };
+
+ /* Load all auto load modules. */
+ R_RETURN(LoadAutoLoadModules(out, ctx, argument));
+ }
+
+ }
+
+ /* Process Creation API. */
+ Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) {
+ /* Mount code. */
+ AMS_UNUSED(path);
+ ScopedCodeMountForCode mount(loc, override_status, attrs);
+ R_TRY(mount.GetResult());
+
+ /* Load meta, possibly from cache. */
+ Meta meta;
+ R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform));
+
+ /* Validate meta. */
+ R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData()));
+
+ /* If we should, load/validate the browser core dll. */
+ util::optional bdll_mount;
+ if ((meta.acid->flags & Acid::AcidFlag_LoadBrowserCoreDll)) {
+ /* NOTE: I'm unsure whether we should be getting a fresh override status (allowing for different override between main and bdll?) */
+ /* or whether we should be using the main override status. Going to go with main, for sanity's sake. */
+ /* Also noting that Nintendo always passes ProgramAttributes=0 here, but this "should" be different on Ounce? */
+ /* Kind of unclear how to handle this without knowing what exactly is being ifdef'd. */
+ const ncm::ProgramLocation bdll_loc = ncm::ProgramLocation::Make(ncm::SystemProgramId::BrowserCoreDll, ncm::StorageId::BuiltInSystem);
+ const cfg::OverrideStatus bdll_override_status = override_status;
+ const ldr::ProgramAttributes bdll_attrs = attrs;
+ bdll_mount.emplace(bdll_loc, bdll_override_status, bdll_attrs);
+ R_TRY(bdll_mount->GetResult());
+
+ /* Load browser dll meta, possibly from cache. */
+ Meta bdll_meta;
+ R_TRY(LoadMetaFromCacheForBrowserCoreDll(std::addressof(bdll_meta), bdll_loc, bdll_override_status, bdll_attrs.platform));
+
+ /* Validate browser dll meta. */
+ R_TRY(ValidateMeta(std::addressof(bdll_meta), loc, mount.GetCodeVerificationData()));
+ }
+
+ /* Load, validate NSO headers. */
+ AutoLoadModuleContext ctx;
+ R_TRY(LoadAutoLoadHeaders(ctx, meta.acid->flags));
+ R_TRY(CheckAutoLoad(ctx, meta.acid->flags));
+
+ /* Actually create the process and load NSOs into process memory. */
+ ProcessInfo info;
+ R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), ctx, argument, flags, resource_limit));
+
+ /* Register NSOs with the RoManager. */
+ {
+ /* Nintendo doesn't validate this get, but we do. */
+ os::ProcessId process_id = os::GetProcessId(info.process_handle);
+
+ /* Register new process. */
+ const auto as_type = GetProcessAddressSpace(std::addressof(meta));
+ RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::ProcessAddressSpace_64Bit39 || as_type == Npdm::ProcessAddressSpace_64Bit36);
+
+ /* Register all NSOs. */
+ for (int i = 0; i < ctx.nso_count; i++) {
+ RoManager::GetInstance().AddNso(pin_id, ctx.headers[i].module_id, info.nso_address[i], info.nso_size[i]);
+ }
+ }
+
+ /* If we're overriding for HBL, perform HTML document redirection. */
+ if (override_status.IsHbl()) {
+ /* Don't validate result, failure is okay. */
+ R_DISCARD(RedirectHtmlDocumentPathForHbl(loc));
+ }
+
+ /* Clear the external code for the program. */
+ fssystem::DestroyExternalCode(loc.program_id);
+
+ /* Note that we've created the program. */
+ SetLaunchedBootProgram(loc.program_id);
+
+ /* Move the process handle to output. */
+ *out = info.process_handle;
+
+ R_SUCCEED();
+ }
+
+ Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) {
+ Meta meta;
+
+ /* Load Meta. */
+ {
+ AMS_UNUSED(path);
+
+ ScopedCodeMountForCode mount(loc, attrs);
+ R_TRY(mount.GetResult());
+ R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false));
+ if (out_status != nullptr) {
+ *out_status = mount.GetOverrideStatus();
+ }
+ }
+
+ return GetProgramInfoFromMeta(out, std::addressof(meta));
+ }
+
+ Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) {
+ R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess());
+ R_SUCCEED();
+ }
+
+ Result UnpinProgram(PinId id) {
+ R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned());
+ R_SUCCEED();
+ }
+
+ Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
+ R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned());
+ R_SUCCEED();
+ }
+
+ Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) {
+ R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned());
+ R_SUCCEED();
+ }
+
+}
diff --git a/build.sh b/build.sh
index 3dc32ae3..6944a8cb 100755
--- a/build.sh
+++ b/build.sh
@@ -6,6 +6,7 @@ LDR_SET=0
NO_EXO=0
JOBS=""
DEBUG_BUILD=0
+SKIP=""
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DIST_DIR="$ROOT_DIR/dist"
@@ -25,6 +26,35 @@ while [ $# -gt 0 ]; do
--no-exo)
NO_EXO=1
;;
+ -s|--skip)
+ shift
+ SKIP_GOT=0
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ -*)
+ break
+ ;;
+ *)
+ SKIP="$SKIP $(printf '%s' "$1" | tr ',A-Z' ' a-z')"
+ SKIP_GOT=1
+ shift
+ ;;
+ esac
+ done
+ if [ "$SKIP_GOT" -eq 0 ]; then
+ echo "Missing value for -s/--skip: expected one or more of: ldr clk hek btb exos sm zip" >&2
+ exit 1
+ fi
+ continue
+ ;;
+ --skip=*)
+ SKIP_ADD="$(printf '%s' "${1#*=}" | tr ',A-Z' ' a-z')"
+ if [ -z "$(printf '%s' "$SKIP_ADD" | tr -d ' ')" ]; then
+ echo "Missing value for --skip: expected one or more of: ldr clk hek btb exos sm zip" >&2
+ exit 1
+ fi
+ SKIP="$SKIP $SKIP_ADD"
+ ;;
-j)
shift
JOBS="$1"
@@ -44,9 +74,80 @@ if [ "$DEBUG_BUILD" -eq 1 ] && [ "$LDR_SET" -eq 0 ]; then
LDR_MAKE="nx_audit"
fi
+should_skip() {
+ case " $SKIP " in
+ *" $1 "*)
+ return 0
+ ;;
+ esac
+ return 1
+}
+
+require_toolchain() {
+ _tk_var="$1"
+ _tk_gcc="$2"
+ _tk_extra="$3"
+ _tk_need="$4"
+ eval "_tk_dir=\$$_tk_var"
+ if [ -z "$_tk_dir" ] && [ -n "$DEVKITPRO" ]; then
+ _tk_cand="$DEVKITPRO/devkit${_tk_var#DEVKIT}"
+ if [ -d "$_tk_cand" ]; then
+ eval "export $_tk_var=""$_tk_cand"""
+ _tk_dir="$_tk_cand"
+ fi
+ fi
+ if [ -z "$_tk_dir" ]; then
+ echo "error: $_tk_var is not set, but it is required to build $_tk_need." >&2
+ echo "Install devkitPro (https://devkitpro.org/wiki/Getting_Started) and export $_tk_var." >&2
+ exit 1
+ fi
+ if [ ! -d "$_tk_dir" ]; then
+ echo "error: $_tk_var points to a missing directory: $_tk_dir" >&2
+ exit 1
+ fi
+ if [ -n "$_tk_extra" ] && [ ! -e "$_tk_dir/$_tk_extra" ]; then
+ echo "error: $_tk_dir/$_tk_extra not found. your devkitPro install looks incomplete." >&2
+ exit 1
+ fi
+ if ! command -v "$_tk_gcc" >/dev/null 2>&1 && [ ! -x "$_tk_dir/bin/$_tk_gcc" ]; then
+ echo "error: $_tk_gcc not found. Add $_tk_dir/bin to your PATH." >&2
+ exit 1
+ fi
+ unset _tk_var _tk_gcc _tk_extra _tk_need _tk_dir _tk_cand
+}
+
+for _s in $SKIP; do
+ case "$_s" in
+ ldr|clk|hek|btb|exos|sm|zip)
+ ;;
+ *)
+ echo "Unknown skip component: $_s (valid components: ldr clk hek btb exos sm zip)" >&2
+ exit 1
+ ;;
+ esac
+done
+unset _s
+
+if should_skip exos; then
+ NO_EXO=1
+fi
+
+_NEED_A64=0
+if ! should_skip ldr; then _NEED_A64=1; fi
+if ! should_skip clk; then _NEED_A64=1; fi
+if [ "$EXT" -eq 1 ] && ! should_skip btb; then _NEED_A64=1; fi
+if [ "$NO_EXO" -eq 0 ]; then _NEED_A64=1; fi
+if [ "$_NEED_A64" -eq 1 ]; then
+ require_toolchain DEVKITA64 aarch64-none-elf-gcc "" "loader/exosphere/hoc-clk/benchmark-toolbox (devkitA64)"
+fi
+unset _NEED_A64
+
LDR_BUILD_PATH="${LDR_MAKE#nx_}"
echo
+if [ -n "$SKIP" ]; then
+ echo "SKIP =$SKIP"
+fi
if [ "$EXT" -eq 1 ]; then
echo "EXT = 1"
fi
@@ -144,13 +245,18 @@ EOF
fi
done
-echo
-echo "*** Compiling loader ***"
-cd build/atmosphere/stratosphere/loader || exit 1
-make -j$JOBS HOC_UART_LOG=$DEBUG_BUILD "$LDR_MAKE"
-hactool -t kip1 "out/nintendo_nx_arm64_armv8a/$LDR_BUILD_PATH/loader.kip" --uncompress=hoc.kip
-cd "$ROOT_DIR" # exit
-cp -v build/atmosphere/stratosphere/loader/hoc.kip dist/atmosphere/kips/hoc.kip
+if should_skip ldr; then
+ echo
+ echo "*** Skipping loader ***"
+else
+ echo
+ echo "*** Compiling loader ***"
+ cd build/atmosphere/stratosphere/loader || exit 1
+ make -j$JOBS HOC_UART_LOG=$DEBUG_BUILD "$LDR_MAKE"
+ hactool -t kip1 "out/nintendo_nx_arm64_armv8a/$LDR_BUILD_PATH/loader.kip" --uncompress=hoc.kip
+ cd "$ROOT_DIR" # exit
+ cp -v build/atmosphere/stratosphere/loader/hoc.kip dist/atmosphere/kips/hoc.kip
+fi
if [ "$NO_EXO" -eq 0 ]; then
echo
@@ -167,67 +273,97 @@ if [ -n "$ATMOSPHERE_CCACHE" ]; then
ccache --show-stats
fi
-cd Source/hoc-clk/
-if [ "$DEBUG_BUILD" -eq 1 ]; then
- ./build.sh "" -d
+if should_skip clk; then
+ echo
+ echo "*** Skipping hoc-clk ***"
else
- ./build.sh
+ cd Source/hoc-clk/ || exit 1
+ if [ "$DEBUG_BUILD" -eq 1 ]; then
+ ./build.sh "" -d
+ else
+ ./build.sh
+ fi
+ cp -r dist/ ../../
+
+ cd "$ROOT_DIR"
fi
-cp -r dist/ ../../
-cd "$ROOT_DIR"
-
-echo "*** Downloading Status-Monitor ***"
-wget "https://github.com/ppkantorski/Status-monitor-overlay/releases/latest/download/Status-Monitor-Overlay.ovl"
-mv -v Status-Monitor-Overlay.ovl "$DIST_DIR"/switch/.overlays/Status-Monitor-Overlay.ovl
+if should_skip sm; then
+ echo
+ echo "*** Skipping Status-Monitor ***"
+else
+ echo "*** Downloading Status-Monitor ***"
+ wget "https://github.com/ppkantorski/Status-monitor-overlay/releases/latest/download/Status-Monitor-Overlay.ovl"
+ mv -v Status-Monitor-Overlay.ovl "$DIST_DIR"/switch/.overlays/Status-Monitor-Overlay.ovl
+fi
if [ "$EXT" -eq 1 ]; then
- cd Source/
-
echo
echo "*** Compiling extensions ***"
- HEKATE_DIR="hekate"
- HEKATE_URL="https://github.com/Horizon-OC/hekate.git"
-
- if [ ! -d "$HEKATE_DIR" ]; then
+ if should_skip hek; then
echo
- echo "*** Cloning custom Hekate ***"
- git clone "$HEKATE_URL" "$HEKATE_DIR"
+ echo "*** Skipping custom Hekate ***"
+ else
+ require_toolchain DEVKITARM arm-none-eabi-gcc base_rules "custom Hekate (devkitARM)"
+
+ HEKATE_DIR="$ROOT_DIR/Source/hekate"
+ HEKATE_URL="https://github.com/Horizon-OC/hekate.git"
+
+ if [ ! -d "$HEKATE_DIR" ]; then
+ echo
+ echo "*** Cloning custom Hekate ***"
+ git clone "$HEKATE_URL" "$HEKATE_DIR" || exit 1
+ else
+ echo
+ echo "*** Updating custom Hekate ***"
+ git -C "$HEKATE_DIR" pull --ff-only || exit 1
+ fi
+
+ cd "$HEKATE_DIR" || exit 1
+ echo
+ echo "*** Compiling custom Hekate ***"
+ # Prevent crash on MSYS2
+ make -j"$JOBS" SHELL=/usr/bin/bash || exit 1
+ echo
+
+ mkdir -p "$DIST_DIR/bootloader/sys/"
+ cp -v output/nyx.bin "$DIST_DIR/bootloader/sys/nyx.bin"
+ cp -v output/hekate.bin "$DIST_DIR/bootloader/update.bin"
+ cp -v output/hekate.bin "$DIST_DIR/payload.bin"
fi
- cd hekate/
- echo
- echo "*** Compiling custom Hekate ***"
- make -j$JOBS
- echo
-
- mkdir -p "$DIST_DIR/bootloader/sys/"
- cp -v output/nyx.bin "$ROOT_DIR"/dist/bootloader/sys/nyx.bin
- cp -v output/hekate.bin "$ROOT_DIR"/dist/bootloader/update.bin
- cp -v output/hekate.bin "$ROOT_DIR"/dist/payload.bin
-
- cd "$ROOT_DIR"/Source/Benchmark-Toolbox
- echo
- echo "*** Compiling Benchmark-Toolbox ***"
- make -j$JOBS
- cp -v Benchmark-Toolbox.nro "$DIST_DIR"/switch/Benchmark-Toolbox.nro
+ if should_skip btb; then
+ echo
+ echo "*** Skipping Benchmark-Toolbox ***"
+ else
+ cd "$ROOT_DIR/Source/Benchmark-Toolbox" || exit 1
+ echo
+ echo "*** Compiling Benchmark-Toolbox ***"
+ make -j"$JOBS" || exit 1
+ cp -v Benchmark-Toolbox.nro "$DIST_DIR/switch/Benchmark-Toolbox.nro"
+ fi
fi
-echo
-echo "*** Packaging dist.zip ***"
+if should_skip zip; then
+ echo
+ echo "*** Skipping dist.zip packaging ***"
+else
+ echo
+ echo "*** Packaging dist.zip ***"
-cd "$DIST_DIR" || exit 1
+ cd "$DIST_DIR" || exit 1
-rm -f dist.zip
-rm .gitignore
+ rm -f dist.zip
+ rm .gitignore
-zip -r dist.zip . >/dev/null
+ zip -r dist.zip . >/dev/null
-echo "*** dist.zip created ***"
-echo
+ echo "*** dist.zip created ***"
+ echo
-touch .gitignore
+ touch .gitignore
-cd "$ROOT_DIR" || exit 1
+ cd "$ROOT_DIR" || exit 1
+fi