diff --git a/stratosphere/loader/source/ldr_content_management.cpp b/stratosphere/loader/source/ldr_content_management.cpp index efb2129e1..4fa73c093 100644 --- a/stratosphere/loader/source/ldr_content_management.cpp +++ b/stratosphere/loader/source/ldr_content_management.cpp @@ -19,11 +19,12 @@ #include #include #include +#include #include "ldr_registration.hpp" #include "ldr_content_management.hpp" #include "ldr_hid.hpp" - +#include "ldr_npdm.hpp" #include "ini.h" @@ -43,6 +44,9 @@ static u64 g_override_hbl_tid = 0x010000000000100D; /* Static buffer for loader.ini contents at runtime. */ static char g_config_ini_data[0x800]; +/* SetExternalContentSource extension */ +static std::map g_external_content_sources; + Result ContentManagement::MountCode(u64 tid, FsStorageId sid) { char path[FS_MAX_PATH] = {0}; Result rc; @@ -312,3 +316,54 @@ bool ContentManagement::ShouldOverrideContents(u64 tid) { return g_has_initialized_fs_dev; } } + +/* SetExternalContentSource extension */ +ContentManagement::ExternalContentSource *ContentManagement::GetExternalContentSource(u64 tid) { + auto i = g_external_content_sources.find(tid); + if (i == g_external_content_sources.end()) { + return nullptr; + } else { + return &i->second; + } +} + +Result ContentManagement::SetExternalContentSource(u64 tid, FsFileSystem filesystem) { + if (g_external_content_sources.size() >= 16) { + return 0x409; /* LAUNCH_QUEUE_FULL */ + } + + /* Remove any existing ECS for this title. */ + ClearExternalContentSource(tid); + + char mountpoint[32]; + ExternalContentSource::GenerateMountpointName(tid, mountpoint, sizeof(mountpoint)); + if (fsdevMountDevice(mountpoint, filesystem) == -1) { + return 0x7802; /* specified mount name already exists */ + } + g_external_content_sources.emplace( + std::piecewise_construct, + std::make_tuple(tid), + std::make_tuple(tid, mountpoint)); + + return 0; +} + +void ContentManagement::ClearExternalContentSource(u64 tid) { + auto i = g_external_content_sources.find(tid); + if (i != g_external_content_sources.end()) { + g_external_content_sources.erase(i); + } +} + +void ContentManagement::ExternalContentSource::GenerateMountpointName(u64 tid, char *out, size_t max_length) { + snprintf(out, max_length, "ecs-%016lx", tid); +} + +ContentManagement::ExternalContentSource::ExternalContentSource(u64 tid, const char *mountpoint) : tid(tid) { + strncpy(this->mountpoint, mountpoint, sizeof(this->mountpoint)); + NpdmUtils::InvalidateCache(tid); +} + +ContentManagement::ExternalContentSource::~ExternalContentSource() { + fsdevUnmountDevice(mountpoint); +} diff --git a/stratosphere/loader/source/ldr_content_management.hpp b/stratosphere/loader/source/ldr_content_management.hpp index 9de9c2b29..a9974530a 100644 --- a/stratosphere/loader/source/ldr_content_management.hpp +++ b/stratosphere/loader/source/ldr_content_management.hpp @@ -39,4 +39,24 @@ class ContentManagement { static bool ShouldReplaceWithHBL(u64 tid); static bool ShouldOverrideContents(u64 tid); + + /* SetExternalContentSource extension */ + class ExternalContentSource { + public: + static void GenerateMountpointName(u64 tid, char *out, size_t max_length); + + ExternalContentSource(u64 tid, const char *mountpoint); + ~ExternalContentSource(); + + ExternalContentSource(const ExternalContentSource &other) = delete; + ExternalContentSource(ExternalContentSource &&other) = delete; + ExternalContentSource &operator=(const ExternalContentSource &other) = delete; + ExternalContentSource &operator=(ExternalContentSource &&other) = delete; + + const u64 tid; + char mountpoint[32]; + }; + static ExternalContentSource *GetExternalContentSource(u64 tid); /* returns nullptr if no ECS is set */ + static Result SetExternalContentSource(u64 tid, FsFileSystem filesystem); /* takes ownership of filesystem */ + static void ClearExternalContentSource(u64 tid); }; diff --git a/stratosphere/loader/source/ldr_npdm.cpp b/stratosphere/loader/source/ldr_npdm.cpp index ad688f85e..7eb114cfc 100644 --- a/stratosphere/loader/source/ldr_npdm.cpp +++ b/stratosphere/loader/source/ldr_npdm.cpp @@ -33,6 +33,12 @@ Result NpdmUtils::LoadNpdmFromCache(u64 tid, NpdmInfo *out) { return 0; } +FILE *NpdmUtils::OpenNpdmFromECS(ContentManagement::ExternalContentSource *ecs) { + std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0); + snprintf(g_npdm_path, FS_MAX_PATH, "%s:/main.npdm", ecs->mountpoint); + return fopen(g_npdm_path, "rb"); +} + FILE *NpdmUtils::OpenNpdmFromHBL() { std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0); snprintf(g_npdm_path, FS_MAX_PATH, "hbl:/main.npdm"); @@ -53,6 +59,11 @@ FILE *NpdmUtils::OpenNpdmFromSdCard(u64 title_id) { FILE *NpdmUtils::OpenNpdm(u64 title_id) { + ContentManagement::ExternalContentSource *ecs = nullptr; + if ((ecs = ContentManagement::GetExternalContentSource(title_id)) != nullptr) { + return OpenNpdmFromECS(ecs); + } + if (ContentManagement::ShouldOverrideContents(title_id)) { if (ContentManagement::ShouldReplaceWithHBL(title_id)) { return OpenNpdmFromHBL(); diff --git a/stratosphere/loader/source/ldr_npdm.hpp b/stratosphere/loader/source/ldr_npdm.hpp index 8b9c2dd1e..2d4610f58 100644 --- a/stratosphere/loader/source/ldr_npdm.hpp +++ b/stratosphere/loader/source/ldr_npdm.hpp @@ -19,6 +19,7 @@ #include #include "ldr_registration.hpp" +#include "ldr_content_management.hpp" /* for ExternalContentSource */ #define MAGIC_META 0x4154454D #define MAGIC_ACI0 0x30494341 @@ -101,7 +102,7 @@ class NpdmUtils { static Result ValidateCapabilityAgainstRestrictions(u32 *restrict_caps, size_t num_restrict_caps, u32 *&cur_cap, size_t &caps_remaining); static Result ValidateCapabilities(u32 *acid_caps, size_t num_acid_caps, u32 *aci0_caps, size_t num_aci0_caps); - + static FILE *OpenNpdmFromECS(ContentManagement::ExternalContentSource *ecs); static FILE *OpenNpdmFromHBL(); static FILE *OpenNpdmFromExeFS(); static FILE *OpenNpdmFromSdCard(u64 tid); diff --git a/stratosphere/loader/source/ldr_nso.cpp b/stratosphere/loader/source/ldr_nso.cpp index a90a03f94..bdc65eb3d 100644 --- a/stratosphere/loader/source/ldr_nso.cpp +++ b/stratosphere/loader/source/ldr_nso.cpp @@ -31,6 +31,12 @@ static bool g_nso_present[NSO_NUM_MAX] = {0}; static char g_nso_path[FS_MAX_PATH] = {0}; +FILE *NsoUtils::OpenNsoFromECS(unsigned int index, ContentManagement::ExternalContentSource *ecs) { + std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0); + snprintf(g_nso_path, FS_MAX_PATH, "%s:/%s", ecs->mountpoint, NsoUtils::GetNsoFileName(index)); + return fopen(g_nso_path, "rb"); +} + FILE *NsoUtils::OpenNsoFromHBL(unsigned int index) { std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0); snprintf(g_nso_path, FS_MAX_PATH, "hbl:/%s", NsoUtils::GetNsoFileName(index)); @@ -61,6 +67,11 @@ bool NsoUtils::CheckNsoStubbed(unsigned int index, u64 title_id) { } FILE *NsoUtils::OpenNso(unsigned int index, u64 title_id) { + ContentManagement::ExternalContentSource *ecs = nullptr; + if ((ecs = ContentManagement::GetExternalContentSource(title_id)) != nullptr) { + return OpenNsoFromECS(index, ecs); + } + if (ContentManagement::ShouldOverrideContents(title_id)) { if (ContentManagement::ShouldReplaceWithHBL(title_id)) { return OpenNsoFromHBL(index); diff --git a/stratosphere/loader/source/ldr_nso.hpp b/stratosphere/loader/source/ldr_nso.hpp index 364943346..6de7f6677 100644 --- a/stratosphere/loader/source/ldr_nso.hpp +++ b/stratosphere/loader/source/ldr_nso.hpp @@ -18,6 +18,8 @@ #include #include +#include "ldr_content_management.hpp" /* for ExternalContentSource */ + #define MAGIC_NSO0 0x304F534E #define NSO_NUM_MAX 13 @@ -96,7 +98,8 @@ class NsoUtils { return "?"; } } - + + static FILE *OpenNsoFromECS(unsigned int index, ContentManagement::ExternalContentSource *ecs); static FILE *OpenNsoFromHBL(unsigned int index); static FILE *OpenNsoFromExeFS(unsigned int index); static FILE *OpenNsoFromSdCard(unsigned int index, u64 title_id); diff --git a/stratosphere/loader/source/ldr_process_creation.cpp b/stratosphere/loader/source/ldr_process_creation.cpp index 40d9d716d..74c4b095e 100644 --- a/stratosphere/loader/source/ldr_process_creation.cpp +++ b/stratosphere/loader/source/ldr_process_creation.cpp @@ -215,6 +215,8 @@ Result ProcessCreation::CreateProcess(Handle *out_process_h, u64 index, char *nc rc = 0; CREATE_PROCESS_END: + /* ECS is a one-shot operation. */ + ContentManagement::ClearExternalContentSource(target_process->tid_sid.title_id); if (mounted_code) { if (R_SUCCEEDED(rc) && target_process->tid_sid.storage_id != FsStorageId_None) { rc = ContentManagement::UnmountCode(); diff --git a/stratosphere/loader/source/ldr_shell.cpp b/stratosphere/loader/source/ldr_shell.cpp index 664388d08..75b884a08 100644 --- a/stratosphere/loader/source/ldr_shell.cpp +++ b/stratosphere/loader/source/ldr_shell.cpp @@ -18,6 +18,7 @@ #include #include "ldr_shell.hpp" #include "ldr_launch_queue.hpp" +#include "ldr_content_management.hpp" Result ShellService::dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) { @@ -30,6 +31,9 @@ Result ShellService::dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id case Shell_Cmd_ClearLaunchQueue: rc = WrapIpcCommandImpl<&ShellService::clear_launch_queue>(this, r, out_c, pointer_buffer, pointer_buffer_size); break; + case Shell_Cmd_AtmosphereSetExternalContentSource: + rc = WrapIpcCommandImpl<&ShellService::set_external_content_source>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; default: break; } @@ -46,3 +50,19 @@ std::tuple ShellService::clear_launch_queue(u64 dat) { LaunchQueue::clear(); return {0}; } + +/* SetExternalContentSource extension */ +std::tuple ShellService::set_external_content_source(u64 tid) { + Handle server_h; + Handle client_h; + + Result rc; + if (R_FAILED(rc = svcCreateSession(&server_h, &client_h, 0, 0))) { + return {rc, 0}; + } + + Service service; + serviceCreate(&service, client_h); + ContentManagement::SetExternalContentSource(tid, FsFileSystem {service}); + return {0, server_h}; +} diff --git a/stratosphere/loader/source/ldr_shell.hpp b/stratosphere/loader/source/ldr_shell.hpp index 7cc95f858..7b1c153d2 100644 --- a/stratosphere/loader/source/ldr_shell.hpp +++ b/stratosphere/loader/source/ldr_shell.hpp @@ -20,7 +20,9 @@ enum ShellServiceCmd { Shell_Cmd_AddTitleToLaunchQueue = 0, - Shell_Cmd_ClearLaunchQueue = 1 + Shell_Cmd_ClearLaunchQueue = 1, + + Shell_Cmd_AtmosphereSetExternalContentSource = 65000, }; class ShellService final : public IServiceObject { @@ -39,4 +41,7 @@ class ShellService final : public IServiceObject { /* Actual commands. */ std::tuple add_title_to_launch_queue(u64 args_size, u64 tid, InPointer args); std::tuple clear_launch_queue(u64 dat); + + /* Atmosphere commands. */ + std::tuple set_external_content_source(u64 tid); };