diff --git a/Makefile b/Makefile
index 79d160ac..0fd81506 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules
# INCLUDES is a list of directories containing header files
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
-SOURCES := source source/ams source/os source/os/impl source/sf source/sf/cmif source/sf/hipc source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr source/kvdb
+SOURCES := source source/ams source/os source/os/impl source/sf source/sf/cmif source/sf/hipc source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr source/kvdb source/boot2
DATA := data
INCLUDES := include
diff --git a/include/stratosphere/boot2.hpp b/include/stratosphere/boot2.hpp
new file mode 100644
index 00000000..55b3fc3b
--- /dev/null
+++ b/include/stratosphere/boot2.hpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+#include
+
+#include "boot2/boot2_api.hpp"
diff --git a/include/stratosphere/boot2/boot2_api.hpp b/include/stratosphere/boot2/boot2_api.hpp
new file mode 100644
index 00000000..4dc788e0
--- /dev/null
+++ b/include/stratosphere/boot2/boot2_api.hpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+#include
+#include
+
+namespace sts::boot2 {
+
+ /* Boot2 API. */
+
+ /* Normally invoked by PM. */
+ void LaunchPreSdCardBootProgramsAndBoot2();
+
+ /* Normally invoked by boot2. */
+ void LaunchPostSdCardBootPrograms();
+
+}
diff --git a/include/stratosphere/cfg/cfg_api.hpp b/include/stratosphere/cfg/cfg_api.hpp
index 9d4d88ec..e00e0f79 100644
--- a/include/stratosphere/cfg/cfg_api.hpp
+++ b/include/stratosphere/cfg/cfg_api.hpp
@@ -26,6 +26,8 @@ namespace sts::cfg {
void GetInitialProcessRange(os::ProcessId *out_min, os::ProcessId *out_max);
/* SD card configuration. */
+ bool IsSdCardRequiredServicesReady();
+ void WaitSdCardRequiredServicesReady();
bool IsSdCardInitialized();
void WaitSdCardInitialized();
diff --git a/source/boot2/boot2_api.cpp b/source/boot2/boot2_api.cpp
new file mode 100644
index 00000000..b6808826
--- /dev/null
+++ b/source/boot2/boot2_api.cpp
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace sts::boot2 {
+
+ namespace {
+
+ /* Launch lists. */
+
+ /* psc, bus, pcv is the minimal set of required titles to get SD card. */
+ /* bus depends on pcie, and pcv depends on settings. */
+ constexpr ncm::TitleId PreSdCardLaunchPrograms[] = {
+ ncm::TitleId::Psc, /* psc */
+ ncm::TitleId::Pcie, /* pcie */
+ ncm::TitleId::Bus, /* bus */
+ ncm::TitleId::Settings, /* settings */
+ ncm::TitleId::Pcv, /* pcv */
+ ncm::TitleId::Usb, /* usb */
+ };
+ constexpr size_t NumPreSdCardLaunchPrograms = util::size(PreSdCardLaunchPrograms);
+
+ constexpr ncm::TitleId AdditionalLaunchPrograms[] = {
+ ncm::TitleId::Tma, /* tma */
+ ncm::TitleId::Am, /* am */
+ ncm::TitleId::NvServices, /* nvservices */
+ ncm::TitleId::NvnFlinger, /* nvnflinger */
+ ncm::TitleId::Vi, /* vi */
+ ncm::TitleId::Ns, /* ns */
+ ncm::TitleId::LogManager, /* lm */
+ ncm::TitleId::Ppc, /* ppc */
+ ncm::TitleId::Ptm, /* ptm */
+ ncm::TitleId::Hid, /* hid */
+ ncm::TitleId::Audio, /* audio */
+ ncm::TitleId::Lbl, /* lbl */
+ ncm::TitleId::Wlan, /* wlan */
+ ncm::TitleId::Bluetooth, /* bluetooth */
+ ncm::TitleId::BsdSockets, /* bsdsockets */
+ ncm::TitleId::Nifm, /* nifm */
+ ncm::TitleId::Ldn, /* ldn */
+ ncm::TitleId::Account, /* account */
+ ncm::TitleId::Friends, /* friends */
+ ncm::TitleId::Nfc, /* nfc */
+ ncm::TitleId::JpegDec, /* jpegdec */
+ ncm::TitleId::CapSrv, /* capsrv */
+ ncm::TitleId::Ssl, /* ssl */
+ ncm::TitleId::Nim, /* nim */
+ ncm::TitleId::Bcat, /* bcat */
+ ncm::TitleId::Erpt, /* erpt */
+ ncm::TitleId::Es, /* es */
+ ncm::TitleId::Pctl, /* pctl */
+ ncm::TitleId::Btm, /* btm */
+ ncm::TitleId::Eupld, /* eupld */
+ ncm::TitleId::Glue, /* glue */
+ /* ncm::TitleId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */
+ ncm::TitleId::Npns, /* npns */
+ ncm::TitleId::Fatal, /* fatal */
+ ncm::TitleId::Ro, /* ro */
+ ncm::TitleId::Profiler, /* profiler */
+ ncm::TitleId::Sdb, /* sdb */
+ ncm::TitleId::Migration, /* migration */
+ ncm::TitleId::Grc, /* grc */
+ ncm::TitleId::Olsc, /* olsc */
+ ncm::TitleId::Ngct, /* ngct */
+ };
+ constexpr size_t NumAdditionalLaunchPrograms = util::size(AdditionalLaunchPrograms);
+
+ constexpr ncm::TitleId AdditionalMaintenanceLaunchPrograms[] = {
+ ncm::TitleId::Tma, /* tma */
+ ncm::TitleId::Am, /* am */
+ ncm::TitleId::NvServices, /* nvservices */
+ ncm::TitleId::NvnFlinger, /* nvnflinger */
+ ncm::TitleId::Vi, /* vi */
+ ncm::TitleId::Ns, /* ns */
+ ncm::TitleId::LogManager, /* lm */
+ ncm::TitleId::Ppc, /* ppc */
+ ncm::TitleId::Ptm, /* ptm */
+ ncm::TitleId::Hid, /* hid */
+ ncm::TitleId::Audio, /* audio */
+ ncm::TitleId::Lbl, /* lbl */
+ ncm::TitleId::Wlan, /* wlan */
+ ncm::TitleId::Bluetooth, /* bluetooth */
+ ncm::TitleId::BsdSockets, /* bsdsockets */
+ ncm::TitleId::Nifm, /* nifm */
+ ncm::TitleId::Ldn, /* ldn */
+ ncm::TitleId::Account, /* account */
+ ncm::TitleId::Nfc, /* nfc */
+ ncm::TitleId::JpegDec, /* jpegdec */
+ ncm::TitleId::CapSrv, /* capsrv */
+ ncm::TitleId::Ssl, /* ssl */
+ ncm::TitleId::Nim, /* nim */
+ ncm::TitleId::Erpt, /* erpt */
+ ncm::TitleId::Es, /* es */
+ ncm::TitleId::Pctl, /* pctl */
+ ncm::TitleId::Btm, /* btm */
+ ncm::TitleId::Glue, /* glue */
+ /* ncm::TitleId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */
+ ncm::TitleId::Fatal, /* fatal */
+ ncm::TitleId::Ro, /* ro */
+ ncm::TitleId::Profiler, /* profiler */
+ ncm::TitleId::Sdb, /* sdb */
+ ncm::TitleId::Migration, /* migration */
+ ncm::TitleId::Grc, /* grc */
+ ncm::TitleId::Olsc, /* olsc */
+ ncm::TitleId::Ngct, /* ngct */
+ };
+ constexpr size_t NumAdditionalMaintenanceLaunchPrograms = util::size(AdditionalMaintenanceLaunchPrograms);
+
+ /* Helpers. */
+ inline bool IsHexadecimal(const char *str) {
+ while (*str) {
+ if (!std::isxdigit(static_cast(*(str++)))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ inline bool IsNewLine(char c) {
+ return c == '\r' || c == '\n';
+ }
+
+ void LaunchTitle(os::ProcessId *out_process_id, const ncm::TitleLocation &loc, u32 launch_flags) {
+ os::ProcessId process_id = os::InvalidProcessId;
+
+ switch (pm::shell::LaunchTitle(&process_id, loc, launch_flags)) {
+ case ResultKernelResourceExhausted:
+ case ResultKernelOutOfMemory:
+ case ResultKernelLimitReached:
+ STS_ASSERT(false);
+ default:
+ /* We don't care about other issues. */
+ break;
+ }
+
+
+ if (out_process_id) {
+ *out_process_id = process_id;
+ }
+ }
+
+ void LaunchList(const ncm::TitleId *launch_list, size_t num_entries) {
+ for (size_t i = 0; i < num_entries; i++) {
+ LaunchTitle(nullptr, ncm::TitleLocation::Make(launch_list[i], ncm::StorageId::NandSystem), 0);
+ }
+ }
+
+ bool GetGpioPadLow(GpioPadName pad) {
+ GpioPadSession button;
+ if (R_FAILED(gpioOpenSession(&button, pad))) {
+ return false;
+ }
+
+ /* Ensure we close even on early return. */
+ ON_SCOPE_EXIT { gpioPadClose(&button); };
+
+ /* Set direction input. */
+ gpioPadSetDirection(&button, GpioDirection_Input);
+
+ GpioValue val;
+ return R_SUCCEEDED(gpioPadGetValue(&button, &val)) && val == GpioValue_Low;
+ }
+
+ bool IsMaintenanceMode() {
+ /* Contact set:sys, retrieve boot!force_maintenance. */
+ {
+ u8 force_maintenance = 1;
+ u64 size_out;
+ setsysGetSettingsItemValue("boot", "force_maintenance", &force_maintenance, sizeof(force_maintenance), &size_out);
+ if (force_maintenance != 0) {
+ return true;
+ }
+ }
+
+ /* Contact GPIO, read plus/minus buttons. */
+ {
+ return GetGpioPadLow(GpioPadName_ButtonVolUp) && GetGpioPadLow(GpioPadName_ButtonVolDown);
+ }
+ }
+
+ template
+ void IterateOverFlaggedTitlesOnSdCard(F f) {
+ /* Validate that the titles directory exists. */
+ DIR *titles_dir = opendir("sdmc:/atmosphere/titles");
+ if (titles_dir == nullptr) {
+ return;
+ }
+ ON_SCOPE_EXIT { closedir(titles_dir); };
+
+ /* Iterate over entries in the titles directory */
+ struct dirent *ent;
+ while ((ent = readdir(titles_dir)) != nullptr) {
+ /* Check that the subdirectory can be converted to a title id. */
+ if (std::strlen(ent->d_name) == 2 * sizeof(ncm::TitleId) && IsHexadecimal(ent->d_name)) {
+ /* Check if we've already launched the title. */
+ ncm::TitleId title_id{std::strtoul(ent->d_name, nullptr, 16)};
+
+ /* Check if the title is flagged. */
+ if (!cfg::HasTitleSpecificFlag(title_id, "boot2")) {
+ continue;
+ }
+
+ /* Call the iteration callback. */
+ f(title_id);
+ }
+ }
+ }
+
+ void DetectAndDeclareFutureMitms() {
+ IterateOverFlaggedTitlesOnSdCard([](ncm::TitleId title_id) {
+ /* When we find a flagged program, check if it has a mitm list. */
+ char mitm_list[0x400];
+ size_t mitm_list_size = 0;
+
+ /* Read the mitm list off the SD card. */
+ {
+ char path[FS_MAX_PATH];
+ std::snprintf(mitm_list, sizeof(mitm_list), "sdmc:/atmosphere/titles/%016lx/mitm.lst", static_cast(title_id));
+ FILE *f = fopen(path, "rb");
+ if (f == nullptr) {
+ return;
+ }
+ mitm_list_size = static_cast(fread(mitm_list, 1, sizeof(mitm_list), f));
+ fclose(f);
+ }
+
+ /* Validate read size. */
+ if (mitm_list_size > sizeof(mitm_list) || mitm_list_size == 0) {
+ return;
+ }
+
+ /* Iterate over the contents of the file. */
+ /* We expect one service name per non-empty line, 1-8 characters. */
+ size_t offset = 0;
+ while (offset < mitm_list_size) {
+ /* Continue to the start of the next name. */
+ while (IsNewLine(mitm_list[offset])) {
+ offset++;
+ }
+ if (offset >= mitm_list_size) {
+ break;
+ }
+
+ /* Get the length of the current name. */
+ size_t name_len;
+ for (name_len = 0; name_len <= sizeof(sm::ServiceName) && offset + name_len < mitm_list_size; name_len++) {
+ if (IsNewLine(mitm_list[offset + name_len])) {
+ break;
+ }
+ }
+
+ /* Allow empty lines. */
+ if (name_len == 0) {
+ continue;
+ }
+
+ /* Don't allow invalid lines. */
+ STS_ASSERT(name_len <= sizeof(sm::ServiceName));
+
+ /* Declare the service. */
+ R_ASSERT(sm::mitm::DeclareFutureMitm(sm::ServiceName::Encode(mitm_list + offset, name_len)));
+
+ /* Advance to the next line. */
+ offset += name_len;
+ }
+ });
+ }
+
+ void LaunchFlaggedProgramsOnSdCard() {
+ IterateOverFlaggedTitlesOnSdCard([](ncm::TitleId title_id) {
+ /* Check if we've already launched the title. */
+ if (pm::info::HasLaunchedTitle(title_id)) {
+ return;
+ }
+
+ /* Launch the title. */
+ LaunchTitle(nullptr, ncm::TitleLocation::Make(title_id, ncm::StorageId::None), 0);
+ });
+ }
+
+ }
+
+ /* Boot2 API. */
+
+ void LaunchPreSdCardBootProgramsAndBoot2() {
+ /* This code is normally run by PM. */
+
+ /* Wait until fs.mitm has installed itself. We want this to happen as early as possible. */
+ R_ASSERT(sm::mitm::WaitMitm(sm::ServiceName::Encode("fsp-srv")));
+
+ /* Launch programs required to mount the SD card. */
+ LaunchList(PreSdCardLaunchPrograms, NumPreSdCardLaunchPrograms);
+
+ /* Wait for the SD card required services to be ready. */
+ cfg::WaitSdCardRequiredServicesReady();
+
+ /* Wait for other atmosphere mitm modules to initialize. */
+ R_ASSERT(sm::mitm::WaitMitm(sm::ServiceName::Encode("set:sys")));
+ if (hos::GetVersion() >= hos::Version_200) {
+ R_ASSERT(sm::mitm::WaitMitm(sm::ServiceName::Encode("bpc")));
+ } else {
+ R_ASSERT(sm::mitm::WaitMitm(sm::ServiceName::Encode("bpc:c")));
+ }
+
+ /* Launch Atmosphere boot2, using FsStorageId_None to force SD card boot. */
+ LaunchTitle(nullptr, ncm::TitleLocation::Make(ncm::TitleId::Boot2, ncm::StorageId::None), 0);
+ }
+
+ void LaunchPostSdCardBootPrograms() {
+ /* This code is normally run by boot2. */
+
+ /* Find out whether we are maintenance mode. */
+ const bool maintenance = IsMaintenanceMode();
+ if (maintenance) {
+ pm::bm::SetMaintenanceBoot();
+ }
+
+ /* Launch Atmosphere dmnt, using FsStorageId_None to force SD card boot. */
+ LaunchTitle(nullptr, ncm::TitleLocation::Make(ncm::TitleId::Dmnt, ncm::StorageId::None), 0);
+
+ /* Check for and forward declare non-atmosphere mitm modules. */
+ DetectAndDeclareFutureMitms();
+
+ /* Launch additional programs. */
+ if (maintenance) {
+ LaunchList(AdditionalMaintenanceLaunchPrograms, NumAdditionalMaintenanceLaunchPrograms);
+ /* Starting in 7.0.0, npns is launched during maintenance boot. */
+ if (hos::GetVersion() >= hos::Version_700) {
+ LaunchTitle(nullptr, ncm::TitleLocation::Make(ncm::TitleId::Npns, ncm::StorageId::NandSystem), 0);
+ }
+ } else {
+ LaunchList(AdditionalLaunchPrograms, NumAdditionalLaunchPrograms);
+ }
+
+ /* Launch user programs off of the SD. */
+ LaunchFlaggedProgramsOnSdCard();
+ }
+
+}
diff --git a/source/cfg/cfg_sd_card.cpp b/source/cfg/cfg_sd_card.cpp
index e2276fac..fb094b6b 100644
--- a/source/cfg/cfg_sd_card.cpp
+++ b/source/cfg/cfg_sd_card.cpp
@@ -38,7 +38,7 @@ namespace sts::cfg {
FsFileSystem g_sd_card_filesystem = {};
/* SD card helpers. */
- Result TryInitializeSdCard() {
+ Result CheckSdCardServicesReady() {
for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
bool service_present = false;
R_TRY(sm::HasService(&service_present, RequiredServicesForSdCardAccess[i]));
@@ -47,15 +47,24 @@ namespace sts::cfg {
}
}
+ return ResultSuccess;
+ }
+
+ void WaitSdCardServicesReadyImpl() {
+ for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
+ R_ASSERT(sm::WaitService(RequiredServicesForSdCardAccess[i]));
+ }
+ }
+
+ Result TryInitializeSdCard() {
+ R_TRY(CheckSdCardServicesReady());
R_ASSERT(fsMountSdcard(&g_sd_card_filesystem));
g_sd_card_initialized = true;
return ResultSuccess;
}
void InitializeSdCard() {
- for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
- R_ASSERT(sm::WaitService(RequiredServicesForSdCardAccess[i]));
- }
+ WaitSdCardServicesReadyImpl();
R_ASSERT(fsMountSdcard(&g_sd_card_filesystem));
g_sd_card_initialized = true;
}
@@ -63,6 +72,14 @@ namespace sts::cfg {
}
/* SD card utilities. */
+ bool IsSdCardRequiredServicesReady() {
+ return R_SUCCEEDED(CheckSdCardServicesReady());
+ }
+
+ void WaitSdCardRequiredServicesReady() {
+ WaitSdCardServicesReadyImpl();
+ }
+
bool IsSdCardInitialized() {
std::scoped_lock lk(g_sd_card_lock);