2018-09-07 16:00:13 +01:00
|
|
|
/*
|
2019-04-08 03:00:49 +01:00
|
|
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
2018-09-07 16:00:13 +01:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms and conditions of the GNU General Public License,
|
|
|
|
* version 2, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2019-06-26 23:46:19 +01:00
|
|
|
#include <dirent.h>
|
2018-04-19 22:28:27 +01:00
|
|
|
#include "ldr_content_management.hpp"
|
2019-06-26 23:46:19 +01:00
|
|
|
#include "ldr_ecs.hpp"
|
2018-05-01 23:49:20 +01:00
|
|
|
|
2019-10-24 10:30:10 +01:00
|
|
|
namespace ams::ldr {
|
2019-02-23 15:17:33 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
namespace {
|
2019-02-23 15:17:33 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* DeviceNames. */
|
|
|
|
constexpr const char *CodeFileSystemDeviceName = "code";
|
|
|
|
constexpr const char *HblFileSystemDeviceName = "hbl";
|
|
|
|
constexpr const char *SdCardFileSystemDeviceName = "sdmc";
|
2019-02-23 15:17:33 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
constexpr const char *SdCardStorageMountPoint = "@Sdcard";
|
2018-09-20 00:21:46 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Globals. */
|
|
|
|
bool g_has_mounted_sd_card = false;
|
2018-10-23 06:53:40 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
ncm::TitleId g_should_override_title_id;
|
|
|
|
bool g_should_override_hbl = false;
|
|
|
|
bool g_should_override_sd = false;
|
2018-10-25 20:52:01 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Helpers. */
|
|
|
|
inline void FixFileSystemPath(char *path) {
|
2019-06-28 01:37:33 +01:00
|
|
|
/* Paths will fail when passed to FS if they use the wrong kinds of slashes. */
|
2019-06-26 23:46:19 +01:00
|
|
|
for (size_t i = 0; i < FS_MAX_PATH && path[i]; i++) {
|
|
|
|
if (path[i] == '\\') {
|
|
|
|
path[i] = '/';
|
|
|
|
}
|
|
|
|
}
|
2018-04-21 06:58:42 +01:00
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
inline const char *GetRelativePathStart(const char *relative_path) {
|
2019-06-28 01:37:33 +01:00
|
|
|
/* We assume filenames don't start with slashes when formatting. */
|
2019-06-26 23:46:19 +01:00
|
|
|
while (*relative_path == '/' || *relative_path == '\\') {
|
|
|
|
relative_path++;
|
|
|
|
}
|
|
|
|
return relative_path;
|
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
void UpdateShouldOverrideCache(ncm::TitleId title_id) {
|
|
|
|
if (g_should_override_title_id != title_id) {
|
|
|
|
cfg::GetOverrideKeyHeldStatus(&g_should_override_hbl, &g_should_override_sd, title_id);
|
|
|
|
}
|
|
|
|
g_should_override_title_id = title_id;
|
|
|
|
}
|
2018-04-19 22:28:27 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
void InvalidateShouldOverrideCache() {
|
|
|
|
g_should_override_title_id = {};
|
|
|
|
}
|
2018-04-19 22:28:27 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
bool ShouldOverrideWithHbl(ncm::TitleId title_id) {
|
|
|
|
UpdateShouldOverrideCache(title_id);
|
|
|
|
return g_should_override_hbl;
|
|
|
|
}
|
2018-09-20 00:21:46 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
bool ShouldOverrideWithSd(ncm::TitleId title_id) {
|
|
|
|
UpdateShouldOverrideCache(title_id);
|
|
|
|
return g_should_override_sd;
|
2018-09-20 00:21:46 +01:00
|
|
|
}
|
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
Result MountSdCardFileSystem() {
|
|
|
|
return fsdevMountSdmc();
|
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
Result MountNspFileSystem(const char *device_name, const char *path) {
|
|
|
|
FsFileSystem fs;
|
|
|
|
R_TRY(fsOpenFileSystemWithId(&fs, 0, FsFileSystemType_ApplicationPackage, path));
|
2019-10-24 10:30:10 +01:00
|
|
|
AMS_ASSERT(fsdevMountDevice(device_name, fs) >= 0);
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-18 00:29:09 +01:00
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
FILE *OpenFile(const char *device_name, const char *relative_path) {
|
|
|
|
/* Allow nullptr device_name/relative path -- those are simply not openable. */
|
|
|
|
if (device_name == nullptr || relative_path == nullptr) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2018-04-19 22:28:27 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
char path[FS_MAX_PATH];
|
|
|
|
std::snprintf(path, FS_MAX_PATH, "%s:/%s", device_name, GetRelativePathStart(relative_path));
|
|
|
|
FixFileSystemPath(path);
|
|
|
|
return fopen(path, "rb");
|
|
|
|
}
|
2018-04-19 22:28:27 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
FILE *OpenLooseSdFile(ncm::TitleId title_id, const char *relative_path) {
|
|
|
|
/* Allow nullptr relative path -- those are simply not openable. */
|
|
|
|
if (relative_path == nullptr) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
char path[FS_MAX_PATH];
|
|
|
|
std::snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/exefs/%s", static_cast<u64>(title_id), GetRelativePathStart(relative_path));
|
|
|
|
FixFileSystemPath(path);
|
|
|
|
return OpenFile(SdCardFileSystemDeviceName, path);
|
|
|
|
}
|
2018-04-19 22:28:27 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
bool IsFileStubbed(ncm::TitleId title_id, const char *relative_path) {
|
|
|
|
/* Allow nullptr relative path -- those are simply not openable. */
|
|
|
|
if (relative_path == nullptr) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-05-01 23:49:20 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Only allow stubbing in the case where we're considering SD card content. */
|
|
|
|
if (!ShouldOverrideWithSd(title_id)) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-20 14:53:56 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
char path[FS_MAX_PATH];
|
|
|
|
std::snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/exefs/%s.stub", static_cast<u64>(title_id), GetRelativePathStart(relative_path));
|
|
|
|
FixFileSystemPath(path);
|
|
|
|
FILE *f = OpenFile(SdCardFileSystemDeviceName, path);
|
|
|
|
if (f == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
return true;
|
|
|
|
}
|
2019-03-20 14:53:56 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
FILE *OpenBaseExefsFile(ncm::TitleId title_id, const char *relative_path) {
|
|
|
|
/* Allow nullptr relative path -- those are simply not openable. */
|
|
|
|
if (relative_path == nullptr) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2019-03-20 14:53:56 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Check if stubbed. */
|
|
|
|
if (IsFileStubbed(title_id, relative_path)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2019-03-20 14:53:56 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
return OpenFile(CodeFileSystemDeviceName, relative_path);
|
|
|
|
}
|
2018-05-01 23:49:20 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-06-28 01:37:33 +01:00
|
|
|
/* ScopedCodeMount functionality. */
|
|
|
|
ScopedCodeMount::ScopedCodeMount(const ncm::TitleLocation &loc) : is_code_mounted(false), is_hbl_mounted(false) {
|
|
|
|
this->result = this->Initialize(loc);
|
|
|
|
}
|
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
ScopedCodeMount::~ScopedCodeMount() {
|
|
|
|
/* Unmount devices. */
|
|
|
|
if (this->is_code_mounted) {
|
|
|
|
fsdevUnmountDevice(CodeFileSystemDeviceName);
|
|
|
|
}
|
|
|
|
if (this->is_hbl_mounted) {
|
|
|
|
fsdevUnmountDevice(HblFileSystemDeviceName);
|
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Unmounting code means we should invalidate our configuration cache. */
|
|
|
|
InvalidateShouldOverrideCache();
|
2019-02-23 15:17:33 +00:00
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-28 01:37:33 +01:00
|
|
|
Result ScopedCodeMount::MountCodeFileSystem(const ncm::TitleLocation &loc) {
|
|
|
|
char path[FS_MAX_PATH];
|
|
|
|
|
|
|
|
/* Try to get the content path. */
|
|
|
|
R_TRY(ResolveContentPath(path, loc));
|
|
|
|
|
|
|
|
/* Try to mount the content path. */
|
|
|
|
FsFileSystem fs;
|
|
|
|
R_TRY(fsldrOpenCodeFileSystem(static_cast<u64>(loc.title_id), path, &fs));
|
2019-10-24 10:30:10 +01:00
|
|
|
AMS_ASSERT(fsdevMountDevice(CodeFileSystemDeviceName, fs) != -1);
|
2019-06-28 01:37:33 +01:00
|
|
|
|
|
|
|
/* Note that we mounted code. */
|
|
|
|
this->is_code_mounted = true;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-28 01:37:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Result ScopedCodeMount::MountSdCardCodeFileSystem(const ncm::TitleLocation &loc) {
|
|
|
|
char path[FS_MAX_PATH];
|
|
|
|
|
|
|
|
/* Print and fix path. */
|
|
|
|
std::snprintf(path, FS_MAX_PATH, "%s:/atmosphere/titles/%016lx/exefs.nsp", SdCardStorageMountPoint, static_cast<u64>(loc.title_id));
|
|
|
|
FixFileSystemPath(path);
|
|
|
|
R_TRY(MountNspFileSystem(CodeFileSystemDeviceName, path));
|
|
|
|
|
|
|
|
/* Note that we mounted code. */
|
|
|
|
this->is_code_mounted = true;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-28 01:37:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Result ScopedCodeMount::MountHblFileSystem() {
|
|
|
|
char path[FS_MAX_PATH];
|
|
|
|
|
|
|
|
/* Print and fix path. */
|
|
|
|
std::snprintf(path, FS_MAX_PATH, "%s:/%s", SdCardStorageMountPoint, GetRelativePathStart(cfg::GetHblPath()));
|
|
|
|
FixFileSystemPath(path);
|
|
|
|
R_TRY(MountNspFileSystem(HblFileSystemDeviceName, path));
|
|
|
|
|
|
|
|
/* Note that we mounted HBL. */
|
|
|
|
this->is_hbl_mounted = true;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-28 01:37:33 +01:00
|
|
|
}
|
|
|
|
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-28 01:37:33 +01:00
|
|
|
Result ScopedCodeMount::Initialize(const ncm::TitleLocation &loc) {
|
2019-06-26 23:46:19 +01:00
|
|
|
bool is_sd_initialized = cfg::IsSdCardInitialized();
|
2019-02-23 15:17:33 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Check if we're ready to mount the SD card. */
|
|
|
|
if (!g_has_mounted_sd_card) {
|
|
|
|
if (is_sd_initialized) {
|
|
|
|
R_ASSERT(MountSdCardFileSystem());
|
|
|
|
g_has_mounted_sd_card = true;
|
2018-09-20 00:21:46 +01:00
|
|
|
}
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Check if we should override contents. */
|
|
|
|
if (ShouldOverrideWithHbl(loc.title_id)) {
|
|
|
|
/* Try to mount HBL. */
|
2019-06-28 01:37:33 +01:00
|
|
|
this->MountHblFileSystem();
|
2019-02-23 15:17:33 +00:00
|
|
|
}
|
2019-06-26 23:46:19 +01:00
|
|
|
if (ShouldOverrideWithSd(loc.title_id)) {
|
|
|
|
/* Try to mount Code NSP on SD. */
|
2019-06-28 01:37:33 +01:00
|
|
|
this->MountSdCardCodeFileSystem(loc);
|
2019-02-23 15:17:33 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* If we haven't already mounted code, mount it. */
|
2019-06-28 01:37:33 +01:00
|
|
|
if (!this->IsCodeMounted()) {
|
|
|
|
R_TRY(this->MountCodeFileSystem(loc));
|
2018-09-20 00:21:46 +01:00
|
|
|
}
|
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2018-09-20 00:21:46 +01:00
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
Result OpenCodeFile(FILE *&out, ncm::TitleId title_id, const char *relative_path) {
|
|
|
|
FILE *f = nullptr;
|
|
|
|
const char *ecs_device_name = ecs::Get(title_id);
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-28 01:37:33 +01:00
|
|
|
if (ecs_device_name != nullptr) {
|
2019-06-26 23:46:19 +01:00
|
|
|
/* First priority: Open from external content. */
|
|
|
|
f = OpenFile(ecs_device_name, relative_path);
|
|
|
|
} else if (ShouldOverrideWithHbl(title_id)) {
|
2019-06-28 01:37:33 +01:00
|
|
|
/* Next, try to open from HBL. */
|
2019-06-26 23:46:19 +01:00
|
|
|
f = OpenFile(HblFileSystemDeviceName, relative_path);
|
|
|
|
} else {
|
|
|
|
/* If not ECS or HBL, try a loose file on the SD. */
|
|
|
|
if (ShouldOverrideWithSd(title_id)) {
|
|
|
|
f = OpenLooseSdFile(title_id, relative_path);
|
|
|
|
}
|
2018-09-20 00:21:46 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* If we fail, try the original exefs. */
|
|
|
|
if (f == nullptr) {
|
|
|
|
f = OpenBaseExefsFile(title_id, relative_path);
|
2018-05-08 09:59:18 +01:00
|
|
|
}
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* If nothing worked, we failed to find the path. */
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(f != nullptr, fs::ResultPathNotFound());
|
2018-09-20 00:21:46 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
out = f;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
2018-09-20 00:21:46 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
Result OpenCodeFileFromBaseExefs(FILE *&out, ncm::TitleId title_id, const char *relative_path) {
|
|
|
|
/* Open the file. */
|
|
|
|
FILE *f = OpenBaseExefsFile(title_id, relative_path);
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(f != nullptr, fs::ResultPathNotFound());
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
out = f;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Redirection API. */
|
|
|
|
Result ResolveContentPath(char *out_path, const ncm::TitleLocation &loc) {
|
|
|
|
char path[FS_MAX_PATH];
|
2019-02-23 15:17:33 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Try to get the path from the registered resolver. */
|
|
|
|
LrRegisteredLocationResolver reg;
|
|
|
|
R_TRY(lrOpenRegisteredLocationResolver(®));
|
|
|
|
ON_SCOPE_EXIT { serviceClose(®.s); };
|
2019-03-19 20:29:31 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
R_TRY_CATCH(lrRegLrResolveProgramPath(®, static_cast<u64>(loc.title_id), path)) {
|
2019-10-24 09:40:44 +01:00
|
|
|
R_CATCH(lr::ResultProgramNotFound) {
|
2019-06-26 23:46:19 +01:00
|
|
|
/* Program wasn't found via registered resolver, fall back to the normal resolver. */
|
|
|
|
LrLocationResolver lr;
|
|
|
|
R_TRY(lrOpenLocationResolver(static_cast<FsStorageId>(loc.storage_id), &lr));
|
|
|
|
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
2019-02-23 15:17:33 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
R_TRY(lrLrResolveProgramPath(&lr, static_cast<u64>(loc.title_id), path));
|
|
|
|
}
|
|
|
|
} R_END_TRY_CATCH;
|
2019-02-23 15:17:33 +00:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
std::strncpy(out_path, path, FS_MAX_PATH);
|
|
|
|
out_path[FS_MAX_PATH - 1] = '\0';
|
|
|
|
FixFileSystemPath(out_path);
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-02-23 15:17:33 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
Result RedirectContentPath(const char *path, const ncm::TitleLocation &loc) {
|
|
|
|
LrLocationResolver lr;
|
|
|
|
R_TRY(lrOpenLocationResolver(static_cast<FsStorageId>(loc.storage_id), &lr));
|
|
|
|
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
2018-10-25 20:52:01 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
return lrLrRedirectProgramPath(&lr, static_cast<u64>(loc.title_id), path);
|
2018-10-25 20:52:01 +01:00
|
|
|
}
|
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
Result RedirectHtmlDocumentPathForHbl(const ncm::TitleLocation &loc) {
|
|
|
|
char path[FS_MAX_PATH];
|
2018-10-25 20:52:01 +01:00
|
|
|
|
2019-06-28 01:37:33 +01:00
|
|
|
/* Open a location resolver. */
|
2019-06-26 23:46:19 +01:00
|
|
|
LrLocationResolver lr;
|
|
|
|
R_TRY(lrOpenLocationResolver(static_cast<FsStorageId>(loc.storage_id), &lr));
|
|
|
|
ON_SCOPE_EXIT { serviceClose(&lr.s); };
|
2018-10-25 20:52:01 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* If there's already a Html Document path, we don't need to set one. */
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(R_FAILED(lrLrResolveApplicationHtmlDocumentPath(&lr, static_cast<u64>(loc.title_id), path)), ResultSuccess());
|
2018-10-25 20:52:01 +01:00
|
|
|
|
2019-06-26 23:46:19 +01:00
|
|
|
/* We just need to set this to any valid NCA path. Let's use the executable path. */
|
|
|
|
R_TRY(lrLrResolveProgramPath(&lr, static_cast<u64>(loc.title_id), path));
|
2019-09-13 06:44:05 +01:00
|
|
|
R_TRY(lrLrRedirectApplicationHtmlDocumentPath(&lr, static_cast<u64>(loc.title_id), static_cast<u64>(loc.title_id), path));
|
2018-10-25 20:52:01 +01:00
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2018-10-25 20:52:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|