From 733f2b3cdd3e3fcdeb7d8e3a4919aef0dd3b5710 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 4 Dec 2019 06:53:52 -0800 Subject: [PATCH] ams_mitm: implement layeredfs --- .../ams_mitm/source/amsmitm_fs_utils.cpp | 105 ++++ .../ams_mitm/source/amsmitm_fs_utils.hpp | 12 + .../source/bpc_mitm/bpc_ams_power_utils.cpp | 2 +- .../source/fs_mitm/fs_mitm_service.cpp | 115 +++++ .../source/fs_mitm/fs_mitm_service.hpp | 8 +- .../ams_mitm/source/fs_mitm/fs_shim.c | 19 + .../ams_mitm/source/fs_mitm/fs_shim.h | 3 + .../fs_mitm/fsmitm_layered_romfs_storage.cpp | 154 ++++++ .../fs_mitm/fsmitm_layered_romfs_storage.hpp | 44 ++ .../ams_mitm/source/fs_mitm/fsmitm_romfs.cpp | 453 ++++++++++++++++++ .../ams_mitm/source/fs_mitm/fsmitm_romfs.hpp | 220 +++++++++ .../source/set_mitm/settings_sd_kvs.cpp | 2 +- stratosphere/libstratosphere/Makefile | 2 +- .../include/stratosphere/fs.hpp | 1 + .../stratosphere/fs/fs_file_storage.hpp | 56 +++ .../source/fs/fs_file_storage.cpp | 93 ++++ 16 files changed, 1282 insertions(+), 7 deletions(-) create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.cpp create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.hpp create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp create mode 100644 stratosphere/libstratosphere/include/stratosphere/fs/fs_file_storage.hpp create mode 100644 stratosphere/libstratosphere/source/fs/fs_file_storage.cpp diff --git a/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp b/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp index af7b8d454..369241707 100644 --- a/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp @@ -19,6 +19,8 @@ namespace ams::mitm::fs { + using namespace ams::fs; + namespace { /* Globals. */ @@ -46,6 +48,18 @@ namespace ams::mitm::fs { } } + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, ncm::ProgramId program_id, const char *subdir, const char *src_path) { + if (src_path[0] == '/') { + std::snprintf(dst_path, dst_path_size, "/atmosphere/contents/%016lx/%s%s", static_cast(program_id), subdir, src_path); + } else { + std::snprintf(dst_path, dst_path_size, "/atmosphere/contents/%016lx/%s/%s", static_cast(program_id), subdir, src_path); + } + } + + void FormatAtmosphereRomfsPath(char *dst_path, size_t dst_path_size, ncm::ProgramId program_id, const char *src_path) { + return FormatAtmosphereSdPath(dst_path, dst_path_size, program_id, "romfs", src_path); + } + } void OpenGlobalSdCardFileSystem() { @@ -69,4 +83,95 @@ namespace ams::mitm::fs { return OpenSdFile(out, fixed_path, mode); } + Result OpenAtmosphereSdRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode) { + char fixed_path[FS_MAX_PATH]; + FormatAtmosphereRomfsPath(fixed_path, sizeof(fixed_path), program_id, path); + return OpenSdFile(out, fixed_path, mode); + } + + Result OpenAtmosphereRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs) { + char fixed_path[FS_MAX_PATH]; + FormatAtmosphereRomfsPath(fixed_path, sizeof(fixed_path), program_id, path); + return fsFsOpenFile(fs, fixed_path, mode, out); + } + + Result OpenSdDirectory(FsDir *out, const char *path, u32 mode) { + R_TRY(EnsureSdInitialized()); + return fsFsOpenDirectory(&g_sd_filesystem, path, mode, out); + } + + Result OpenAtmosphereSdDirectory(FsDir *out, const char *path, u32 mode) { + char fixed_path[FS_MAX_PATH]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + return OpenSdDirectory(out, fixed_path, mode); + } + + Result OpenAtmosphereSdDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode) { + char fixed_path[FS_MAX_PATH]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), program_id, path); + return OpenSdDirectory(out, fixed_path, mode); + } + + Result OpenAtmosphereSdRomfsDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode) { + char fixed_path[FS_MAX_PATH]; + FormatAtmosphereRomfsPath(fixed_path, sizeof(fixed_path), program_id, path); + return OpenSdDirectory(out, fixed_path, mode); + } + + Result OpenAtmosphereRomfsDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs) { + char fixed_path[FS_MAX_PATH]; + FormatAtmosphereRomfsPath(fixed_path, sizeof(fixed_path), program_id, path); + return fsFsOpenDirectory(fs, fixed_path, mode, out); + } + + + bool HasSdRomfsContent(ncm::ProgramId program_id) { + /* Check if romfs.bin is present. */ + { + FsFile romfs_file; + if (R_SUCCEEDED(OpenAtmosphereSdFile(&romfs_file, program_id, "romfs.bin", OpenMode_Read))) { + fsFileClose(&romfs_file); + return true; + } + } + + /* Check for romfs folder with content. */ + FsDir romfs_dir; + if (R_FAILED(OpenAtmosphereSdRomfsDirectory(&romfs_dir, program_id, "", OpenDirectoryMode_All))) { + return false; + } + ON_SCOPE_EXIT { fsDirClose(&romfs_dir); }; + + /* Verify the folder has at least one entry. */ + s64 num_entries = 0; + return R_SUCCEEDED(fsDirGetEntryCount(&romfs_dir, &num_entries)) && num_entries > 0; + } + + Result SaveAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, void *data, size_t size) { + R_TRY(EnsureSdInitialized()); + + char fixed_path[FS_MAX_PATH]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), program_id, path); + + /* Unconditionally create. */ + /* Don't check error, as a failure here should be okay. */ + FsFile f; + fsFsCreateFile(&g_sd_filesystem, fixed_path, size, 0); + + /* Try to open. */ + R_TRY(fsFsOpenFile(&g_sd_filesystem, fixed_path, OpenMode_ReadWrite, &f)); + auto file_guard = SCOPE_GUARD { fsFileClose(&f); }; + + /* Try to set the size. */ + R_TRY(fsFileSetSize(&f, static_cast(size))); + + /* Try to write data. */ + R_TRY(fsFileWrite(&f, 0, data, size, FsWriteOption_Flush)); + + /* Set output. */ + file_guard.Cancel(); + *out = f; + return ResultSuccess(); + } + } diff --git a/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp b/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp index 17e972daf..26d9b4a1b 100644 --- a/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp +++ b/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp @@ -25,5 +25,17 @@ namespace ams::mitm::fs { Result OpenSdFile(FsFile *out, const char *path, u32 mode); Result OpenAtmosphereSdFile(FsFile *out, const char *path, u32 mode); Result OpenAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode); + Result OpenAtmosphereSdRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode); + Result OpenAtmosphereRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs); + + Result OpenSdDirectory(FsDir *out, const char *path, u32 mode); + Result OpenAtmosphereSdDirectory(FsDir *out, const char *path, u32 mode); + Result OpenAtmosphereSdDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode); + Result OpenAtmosphereSdRomfsDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode); + Result OpenAtmosphereRomfsDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs); + + bool HasSdRomfsContent(ncm::ProgramId program_id); + + Result SaveAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, void *data, size_t size); } diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp index 46ce4ff99..2584d195a 100644 --- a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp @@ -103,7 +103,7 @@ namespace ams::mitm::bpc { /* Open payload file. */ FsFile payload_file; - R_TRY(fs::OpenAtmosphereSdFile(&payload_file, "/reboot_payload.bin", FsOpenMode_Read)); + R_TRY(fs::OpenAtmosphereSdFile(&payload_file, "/reboot_payload.bin", ams::fs::OpenMode_Read)); ON_SCOPE_EXIT { fsFileClose(&payload_file); }; /* Read payload file. Discard result. */ diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp index b57b1da41..7ae9f5a6e 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp @@ -13,9 +13,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "../amsmitm_fs_utils.hpp" #include "fs_shim.h" #include "fs_mitm_service.hpp" #include "fsmitm_boot0storage.hpp" +#include "fsmitm_layered_romfs_storage.hpp" namespace ams::mitm::fs { @@ -23,6 +25,35 @@ namespace ams::mitm::fs { namespace { + os::Mutex g_storage_cache_lock; + std::unordered_map> g_storage_cache; + + std::shared_ptr GetStorageCacheEntry(ncm::ProgramId program_id) { + std::scoped_lock lk(g_storage_cache_lock); + + auto it = g_storage_cache.find(static_cast(program_id)); + if (it == g_storage_cache.end()) { + return nullptr; + } + + return it->second.lock(); + } + + void SetStorageCacheEntry(ncm::ProgramId program_id, std::shared_ptr *new_intf) { + std::scoped_lock lk(g_storage_cache_lock); + + auto it = g_storage_cache.find(static_cast(program_id)); + if (it != g_storage_cache.end()) { + auto cur_intf = it->second.lock(); + if (cur_intf != nullptr) { + *new_intf = cur_intf; + return; + } + } + + g_storage_cache[static_cast(program_id)] = *new_intf; + } + bool GetSettingsItemBooleanValue(const char *name, const char *key) { u8 tmp = 0; AMS_ASSERT(settings::fwdbg::GetSettingsItemValue(&tmp, sizeof(tmp), name, key) == sizeof(tmp)); @@ -77,4 +108,88 @@ namespace ams::mitm::fs { return ResultSuccess(); } + Result FsMitmService::OpenDataStorageByCurrentProcess(sf::Out> out) { + /* Only mitm if we should override contents for the current process. */ + R_UNLESS(this->client_info.override_status.IsProgramSpecific(), sm::mitm::ResultShouldForwardToSession()); + + /* Only mitm if there is actually an override romfs. */ + R_UNLESS(mitm::fs::HasSdRomfsContent(this->client_info.program_id), sm::mitm::ResultShouldForwardToSession()); + + /* Try to open the process romfs. */ + FsStorage data_storage; + R_TRY(fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &data_storage)); + const sf::cmif::DomainObjectId target_object_id{data_storage.s.object_id}; + + /* Try to get a storage from the cache. */ + { + std::shared_ptr cached_storage = GetStorageCacheEntry(this->client_info.program_id); + if (cached_storage != nullptr) { + out.SetValue(std::move(cached_storage), target_object_id); + return ResultSuccess(); + } + } + + /* Make a new layered romfs, and cache to storage. */ + { + std::shared_ptr new_storage_intf = nullptr; + + /* Create the layered storage. */ + FsFile data_file; + if (R_SUCCEEDED(OpenAtmosphereSdFile(&data_file, this->client_info.program_id, "romfs.bin", OpenMode_Read))) { + auto *layered_storage = new LayeredRomfsStorage(std::make_unique(new RemoteStorage(data_storage)), std::make_unique(new FileStorage(new RemoteFile(data_file))), this->client_info.program_id); + new_storage_intf = std::make_shared(layered_storage); + } else { + auto *layered_storage = new LayeredRomfsStorage(std::make_unique(new RemoteStorage(data_storage)), nullptr, this->client_info.program_id); + new_storage_intf = std::make_shared(layered_storage); + } + + SetStorageCacheEntry(this->client_info.program_id, &new_storage_intf); + out.SetValue(std::move(new_storage_intf), target_object_id); + } + + return ResultSuccess(); + } + + Result FsMitmService::OpenDataStorageByDataId(sf::Out> out, ncm::ProgramId /* TODO: ncm::DataId */ data_id, u8 storage_id) { + /* Only mitm if we should override contents for the current process. */ + R_UNLESS(this->client_info.override_status.IsProgramSpecific(), sm::mitm::ResultShouldForwardToSession()); + + /* Only mitm if there is actually an override romfs. */ + R_UNLESS(mitm::fs::HasSdRomfsContent(data_id), sm::mitm::ResultShouldForwardToSession()); + + /* Try to open the process romfs. */ + FsStorage data_storage; + R_TRY(fsOpenDataStorageByDataIdFwd(this->forward_service.get(), &data_storage, static_cast(data_id), static_cast(storage_id))); + const sf::cmif::DomainObjectId target_object_id{data_storage.s.object_id}; + + /* Try to get a storage from the cache. */ + { + std::shared_ptr cached_storage = GetStorageCacheEntry(data_id); + if (cached_storage != nullptr) { + out.SetValue(std::move(cached_storage), target_object_id); + return ResultSuccess(); + } + } + + /* Make a new layered romfs, and cache to storage. */ + { + std::shared_ptr new_storage_intf = nullptr; + + /* Create the layered storage. */ + FsFile data_file; + if (R_SUCCEEDED(OpenAtmosphereSdFile(&data_file, data_id, "romfs.bin", OpenMode_Read))) { + auto *layered_storage = new LayeredRomfsStorage(std::make_unique(new RemoteStorage(data_storage)), std::make_unique(new FileStorage(new RemoteFile(data_file))), data_id); + new_storage_intf = std::make_shared(layered_storage); + } else { + auto *layered_storage = new LayeredRomfsStorage(std::make_unique(new RemoteStorage(data_storage)), nullptr, data_id); + new_storage_intf = std::make_shared(layered_storage); + } + + SetStorageCacheEntry(data_id, &new_storage_intf); + out.SetValue(std::move(new_storage_intf), target_object_id); + } + + return ResultSuccess(); + } + } diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp index 725152e34..c7d5c9c4d 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp @@ -62,8 +62,8 @@ namespace ams::mitm::fs { /* Result OpenSdCardFileSystem(Out> out); */ /* Result OpenSaveDataFileSystem(Out> out, u8 space_id, FsSave save_struct); */ Result OpenBisStorage(sf::Out> out, u32 bis_partition_id); - /* Result OpenDataStorageByCurrentProcess(Out> out); */ - /* Result OpenDataStorageByDataId(Out> out, u64 data_id, u8 storage_id); */ + Result OpenDataStorageByCurrentProcess(sf::Out> out); + Result OpenDataStorageByDataId(sf::Out> out, ncm::ProgramId /* TODO: ncm::DataId */ data_id, u8 storage_id); public: DEFINE_SERVICE_DISPATCH_TABLE { /* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithPatch, hos::Version_200), */ @@ -71,8 +71,8 @@ namespace ams::mitm::fs { /* MAKE_SERVICE_COMMAND_META(OpenSdCardFileSystem), */ /* MAKE_SERVICE_COMMAND_META(OpenSaveDataFileSystem), */ MAKE_SERVICE_COMMAND_META(OpenBisStorage), - /* MAKE_SERVICE_COMMAND_META(OpenDataStorageByCurrentProcess), */ - /* MAKE_SERVICE_COMMAND_META(OpenDataStorageByDataId), */ + MAKE_SERVICE_COMMAND_META(OpenDataStorageByCurrentProcess), + MAKE_SERVICE_COMMAND_META(OpenDataStorageByDataId), }; }; diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c index 5cae82712..65ad02a26 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c @@ -23,3 +23,22 @@ Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partitio .out_objects = &out->s, ); } + +Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out) { + return serviceDispatch(s, 200, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} + +Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id) { + const struct { + u8 storage_id; + u64 data_id; + } in = { storage_id, data_id }; + + return serviceDispatchIn(s, 202, in, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h index 2756be20b..7591b922f 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h @@ -13,6 +13,9 @@ extern "C" { /* Missing fsp-srv commands. */ Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id); +Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out); +Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id); + #ifdef __cplusplus } diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.cpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.cpp new file mode 100644 index 000000000..4841cc3e8 --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.cpp @@ -0,0 +1,154 @@ +/* + * 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 "../amsmitm_initialization.hpp" +#include "../amsmitm_fs_utils.hpp" +#include "fsmitm_layered_romfs_storage.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + LayeredRomfsStorage::LayeredRomfsStorage(std::unique_ptr s_r, std::unique_ptr f_r, ncm::ProgramId pr_id) : storage_romfs(std::move(s_r)), file_romfs(std::move(f_r)), program_id(std::move(pr_id)) { + /* Build new virtual romfs. */ + romfs::Builder builder(this->program_id); + + if (mitm::IsInitialized()) { + builder.AddSdFiles(); + } + if (this->file_romfs) { + builder.AddStorageFiles(this->file_romfs.get(), romfs::DataSourceType::File); + } + if (this->storage_romfs) { + builder.AddStorageFiles(this->storage_romfs.get(), romfs::DataSourceType::Storage); + } + + builder.Build(&this->source_infos); + } + + LayeredRomfsStorage::~LayeredRomfsStorage() { + for (size_t i = 0; i < this->source_infos.size(); i++) { + this->source_infos[i].Cleanup(); + } + } + + Result LayeredRomfsStorage::Read(s64 offset, void *buffer, size_t size) { + /* Check if we can succeed immediately. */ + R_UNLESS(size >= 0, fs::ResultInvalidSize()); + R_UNLESS(size > 0, ResultSuccess()); + + + /* Validate offset/size. */ + const s64 virt_size = this->GetSize(); + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(offset < virt_size, fs::ResultInvalidOffset()); + if (size_t(virt_size - offset) < size) { + size = size_t(virt_size - offset); + } + + /* Find first source info via binary search. */ + auto it = std::lower_bound(this->source_infos.begin(), this->source_infos.end(), offset); + u8 *cur_dst = static_cast(buffer); + + /* Our operator < compares against start of info instead of end, so we need to subtract one from lower bound. */ + it--; + + size_t read_so_far = 0; + while (read_so_far < size) { + const auto &cur_source = *it; + AMS_ASSERT(offset >= cur_source.virtual_offset); + + if (offset <= cur_source.virtual_offset + cur_source.size) { + const s64 offset_within_source = offset - cur_source.virtual_offset; + const size_t cur_read_size = std::min(size - read_so_far, size_t(cur_source.size - offset_within_source)); + switch (cur_source.source_type) { + case romfs::DataSourceType::Storage: + R_ASSERT(this->storage_romfs->Read(cur_source.storage_source_info.offset + offset_within_source, cur_dst, cur_read_size)); + break; + case romfs::DataSourceType::File: + R_ASSERT(this->file_romfs->Read(cur_source.file_source_info.offset + offset_within_source, cur_dst, cur_read_size)); + break; + case romfs::DataSourceType::LooseSdFile: + { + FsFile file; + R_ASSERT(mitm::fs::OpenAtmosphereSdRomfsFile(&file, this->program_id, cur_source.loose_source_info.path, OpenMode_Read)); + ON_SCOPE_EXIT { fsFileClose(&file); }; + + u64 out_read = 0; + R_ASSERT(fsFileRead(&file, offset_within_source, cur_dst, cur_read_size, FsReadOption_None, &out_read)); + AMS_ASSERT(out_read == cur_read_size); + } + break; + case romfs::DataSourceType::Memory: + std::memcpy(cur_dst, cur_source.memory_source_info.data + offset_within_source, cur_read_size); + break; + case romfs::DataSourceType::Metadata: + { + size_t out_read = 0; + R_ASSERT(cur_source.metadata_source_info.file->Read(&out_read, offset_within_source, cur_dst, cur_read_size)); + AMS_ASSERT(out_read == cur_read_size); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + read_so_far += cur_read_size; + cur_dst += cur_read_size; + offset += cur_read_size; + } else { + /* Explicitly handle padding. */ + const auto &next_source = *(++it); + const size_t padding_size = size_t(next_source.virtual_offset - offset); + + std::memset(cur_dst, 0, padding_size); + read_so_far += padding_size; + cur_dst += padding_size; + offset += padding_size; + } + } + + return ResultSuccess(); + } + + Result LayeredRomfsStorage::GetSize(s64 *out_size) { + *out_size = this->GetSize(); + return ResultSuccess(); + } + + Result LayeredRomfsStorage::Flush() { + return ResultSuccess(); + } + + Result LayeredRomfsStorage::OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case OperationId::InvalidateCache: + case OperationId::QueryRange: + if (size == 0) { + if (op_id == OperationId::QueryRange) { + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(QueryRangeInfo), fs::ResultInvalidSize()); + reinterpret_cast(dst)->Clear(); + } + return ResultSuccess(); + } + /* TODO: How to deal with this? */ + return fs::ResultUnsupportedOperation(); + default: + return fs::ResultUnsupportedOperation(); + } + } + + +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.hpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.hpp new file mode 100644 index 000000000..e174f2505 --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.hpp @@ -0,0 +1,44 @@ +/* + * 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 "fsmitm_romfs.hpp" + +namespace ams::mitm::fs { + + class LayeredRomfsStorage : public ams::fs::IStorage { + private: + std::vector source_infos; + std::unique_ptr storage_romfs; + std::unique_ptr file_romfs; + ncm::ProgramId program_id; + protected: + inline s64 GetSize() const { + const auto &back = this->source_infos.back(); + return back.virtual_offset + back.size; + } + public: + LayeredRomfsStorage(std::unique_ptr s_r, std::unique_ptr f_r, ncm::ProgramId pr_id); + virtual ~LayeredRomfsStorage(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result GetSize(s64 *out_size) override; + virtual Result Flush() override; + virtual Result OperateRange(void *dst, size_t dst_size, ams::fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + }; + +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp new file mode 100644 index 000000000..9e5ff36ac --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp @@ -0,0 +1,453 @@ +/* + * 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 "../amsmitm_fs_utils.hpp" +#include "fsmitm_romfs.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace romfs { + + namespace { + + constexpr u32 EmptyEntry = 0xFFFFFFFF; + constexpr size_t FilePartitionOffset = 0x200; + + struct Header { + s64 header_size; + s64 dir_hash_table_ofs; + s64 dir_hash_table_size; + s64 dir_table_ofs; + s64 dir_table_size; + s64 file_hash_table_ofs; + s64 file_hash_table_size; + s64 file_table_ofs; + s64 file_table_size; + s64 file_partition_ofs; + }; + static_assert(std::is_pod
::value && sizeof(Header) == 0x50); + + struct DirectoryEntry { + u32 parent; + u32 sibling; + u32 child; + u32 file; + u32 hash; + u32 name_size; + char name[]; + }; + static_assert(std::is_pod::value && sizeof(DirectoryEntry) == 0x18); + + struct FileEntry { + u32 parent; + u32 sibling; + s64 offset; + s64 size; + u32 hash; + u32 name_size; + char name[]; + }; + static_assert(std::is_pod::value && sizeof(FileEntry) == 0x20); + + constexpr inline DirectoryEntry *GetDirectoryEntry(void *dir_table, u32 offset) { + return reinterpret_cast(reinterpret_cast(dir_table) + offset); + } + + constexpr inline FileEntry *GetFileEntry(void *file_table, u32 offset) { + return reinterpret_cast(reinterpret_cast(file_table) + offset); + } + + constexpr inline const DirectoryEntry *GetDirectoryEntry(const void *dir_table, u32 offset) { + return reinterpret_cast(reinterpret_cast(dir_table) + offset); + } + + constexpr inline const FileEntry *GetFileEntry(const void *file_table, u32 offset) { + return reinterpret_cast(reinterpret_cast(file_table) + offset); + } + + constexpr inline u32 CalculatePathHash(u32 parent, const char *_path, u32 start, size_t path_len) { + const unsigned char *path = reinterpret_cast(_path); + u32 hash = parent ^ 123456789; + for (size_t i = 0; i < path_len; i++) { + hash = (hash >> 5) | (hash << 27); + hash ^= path[start + i]; + } + return hash; + } + + constexpr inline size_t GetHashTableSize(size_t num_entries) { + if (num_entries < 3) { + return 3; + } else if (num_entries < 19) { + return num_entries | 1; + } else { + size_t count = num_entries; + while ((count % 2 == 0) || + (count % 3 == 0) || + (count % 5 == 0) || + (count % 7 == 0) || + (count % 11 == 0) || + (count % 13 == 0) || + (count % 17 == 0)) + { + count++; + } + return count; + } + } + + } + + Builder::Builder(ncm::ProgramId pr_id) : program_id(pr_id), num_dirs(0), num_files(0), dir_table_size(0), file_table_size(0), dir_hash_table_size(0), file_hash_table_size(0), file_partition_size(0) { + auto res = this->directories.emplace("", std::make_unique(BuildDirectoryContext::RootTag{})); + AMS_ASSERT(res.second); + this->root = res.first->second.get(); + this->num_dirs = 1; + this->dir_table_size = 0x18; + } + + void Builder::AddDirectory(BuildDirectoryContext **out, BuildDirectoryContext *parent_ctx, std::unique_ptr child_ctx) { + /* Check if the directory already exists. */ + auto existing = this->directories.find(child_ctx->path.get()); + if (existing != this->directories.end()) { + *out = existing->second.get(); + return; + } + + /* Add a new directory. */ + this->num_dirs++; + this->dir_table_size += sizeof(DirectoryEntry) + util::AlignUp(child_ctx->path_len - child_ctx->cur_path_ofs, 4); + child_ctx->parent = parent_ctx; + + *out = child_ctx.get(); + this->directories.emplace(child_ctx->path.get(), std::move(child_ctx)); + } + + void Builder::AddFile(BuildDirectoryContext *parent_ctx, std::unique_ptr file_ctx) { + /* Check if the file already exists. */ + if (this->files.find(file_ctx->path.get()) != this->files.end()) { + return; + } + + /* Add a new file. */ + this->num_files++; + this->file_table_size += sizeof(FileEntry) + util::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4); + file_ctx->parent = parent_ctx; + this->files.emplace(file_ctx->path.get(), std::move(file_ctx)); + } + + void Builder::VisitDirectory(FsFileSystem *fs, BuildDirectoryContext *parent) { + FsDir dir; + + /* Get number of child directories. */ + s64 num_child_dirs = 0; + { + R_ASSERT(mitm::fs::OpenAtmosphereRomfsDirectory(&dir, this->program_id, parent->path.get(), OpenDirectoryMode_Directory, fs)); + ON_SCOPE_EXIT { fsDirClose(&dir); }; + R_ASSERT(fsDirGetEntryCount(&dir, &num_child_dirs)); + } + AMS_ASSERT(num_child_dirs >= 0); + + { + BuildDirectoryContext **child_dirs = reinterpret_cast(std::malloc(sizeof(BuildDirectoryContext *) * num_child_dirs)); + ON_SCOPE_EXIT { std::free(child_dirs); }; + AMS_ASSERT(child_dirs != nullptr); + s64 cur_child_dir_ind = 0; + + R_ASSERT(mitm::fs::OpenAtmosphereRomfsDirectory(&dir, this->program_id, parent->path.get(), OpenDirectoryMode_All, fs)); + { + ON_SCOPE_EXIT { fsDirClose(&dir); }; + + s64 read_entries = 0; + while (true) { + R_ASSERT(fsDirRead(&dir, &read_entries, 1, &this->dir_entry)); + if (read_entries != 1) { + break; + } + + AMS_ASSERT(this->dir_entry.type == FsDirEntryType_Dir || this->dir_entry.type == FsDirEntryType_File); + if (this->dir_entry.type == FsDirEntryType_Dir) { + BuildDirectoryContext *real_child = nullptr; + this->AddDirectory(&real_child, parent, std::make_unique(parent->path.get(), parent->path_len, this->dir_entry.name, strlen(this->dir_entry.name))); + AMS_ASSERT(real_child != nullptr); + child_dirs[cur_child_dir_ind++] = real_child; + AMS_ASSERT(cur_child_dir_ind <= num_child_dirs); + } else /* if (this->dir_entry.type == FsDirEntryType_File) */ { + this->AddFile(parent, std::make_unique(parent->path.get(), parent->path_len, this->dir_entry.name, strlen(this->dir_entry.name), this->dir_entry.file_size, 0, this->cur_source_type)); + } + } + } + + AMS_ASSERT(num_child_dirs == cur_child_dir_ind); + for (s64 i = 0; i < num_child_dirs; i++) { + this->VisitDirectory(fs, child_dirs[i]); + } + } + + } + + void Builder::VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, const void *dir_table, size_t dir_table_size, const void *file_table, size_t file_table_size) { + const DirectoryEntry *parent_entry = GetDirectoryEntry(dir_table, parent_offset); + if (parent_entry->file != EmptyEntry) { + const FileEntry *cur_file = GetFileEntry(file_table, parent_entry->file); + while (true) { + this->AddFile(parent, std::make_unique(parent->path.get(), parent->path_len, cur_file->name, cur_file->name_size, cur_file->size, cur_file->offset, this->cur_source_type)); + if (cur_file->sibling == EmptyEntry) { + break; + } + cur_file = GetFileEntry(file_table, cur_file->sibling); + } + } + if (parent_entry->child != EmptyEntry) { + const DirectoryEntry *cur_child = GetDirectoryEntry(dir_table, parent_entry->child); + u32 cur_child_offset = parent_entry->child; + while (true) { + BuildDirectoryContext *real_child = nullptr; + this->AddDirectory(&real_child, parent, std::make_unique(parent->path.get(), parent->path_len, cur_child->name, cur_child->name_size)); + AMS_ASSERT(real_child != nullptr); + + this->VisitDirectory(real_child, cur_child_offset, dir_table, dir_table_size, file_table, file_table_size); + + if (cur_child->sibling == EmptyEntry) { + break; + } + cur_child_offset = cur_child->sibling; + cur_child = GetDirectoryEntry(dir_table, cur_child_offset); + } + } + } + + + void Builder::AddSdFiles() { + /* Open Sd Card filesystem. */ + FsFileSystem sd_filesystem; + R_ASSERT(fsOpenSdCardFileSystem(&sd_filesystem)); + ON_SCOPE_EXIT { fsFsClose(&sd_filesystem); }; + + this->cur_source_type = DataSourceType::LooseSdFile; + this->VisitDirectory(&sd_filesystem, this->root); + } + + void Builder::AddStorageFiles(ams::fs::IStorage *storage, DataSourceType source_type) { + Header header; + R_ASSERT(storage->Read(0, &header, sizeof(Header))); + AMS_ASSERT(header.header_size == sizeof(Header)); + + /* Read tables. */ + auto tables = std::unique_ptr(new u8[header.dir_table_size + header.file_table_size]); + void *dir_table = tables.get(); + void *file_table = tables.get() + header.dir_table_size; + R_ASSERT(storage->Read(header.dir_table_ofs, dir_table, size_t(header.dir_table_size))); + R_ASSERT(storage->Read(header.file_table_ofs, file_table, size_t(header.file_table_size))); + + this->cur_source_type = source_type; + this->VisitDirectory(this->root, 0x0, dir_table, size_t(header.dir_table_size), file_table, size_t(header.file_table_size)); + } + + void Builder::Build(std::vector *out_infos) { + /* Clear output. */ + out_infos->clear(); + + /* Open an SD card filesystem. */ + FsFileSystem sd_filesystem; + R_ASSERT(fsOpenSdCardFileSystem(&sd_filesystem)); + ON_SCOPE_EXIT { fsFsClose(&sd_filesystem); }; + + /* Calculate hash table sizes. */ + const size_t num_dir_hash_table_entries = GetHashTableSize(this->num_dirs); + const size_t num_file_hash_table_entries = GetHashTableSize(this->num_files); + this->dir_hash_table_size = sizeof(u32) * num_dir_hash_table_entries; + this->file_hash_table_size = sizeof(u32) * num_file_hash_table_entries; + + /* Allocate metadata, make pointers. */ + Header *header = reinterpret_cast
(std::malloc(sizeof(Header))); + std::memset(header, 0x00, sizeof(*header)); + + const size_t metadata_size = this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size + this->file_table_size; + std::unique_ptr metadata(new u8[metadata_size]); + u32 *dir_hash_table = reinterpret_cast(metadata.get()); + DirectoryEntry *dir_table = reinterpret_cast(reinterpret_cast(dir_hash_table) + this->dir_hash_table_size); + u32 *file_hash_table = reinterpret_cast(reinterpret_cast(dir_table) + this->dir_table_size); + FileEntry *file_table = reinterpret_cast(reinterpret_cast(file_hash_table) + this->file_hash_table_size); + + /* Clear hash tables. */ + static_assert(EmptyEntry == 0xFFFFFFFF); + std::memset(dir_hash_table, 0xFF, this->dir_hash_table_size); + std::memset(file_hash_table, 0xFF, this->file_hash_table_size); + + /* Emplace metadata source info. */ + out_infos->emplace_back(0, sizeof(*header), DataSourceType::Memory, header); + + /* Process Files. */ + { + u32 entry_offset = 0; + BuildFileContext *cur_file = nullptr; + BuildFileContext *prev_file = nullptr; + for (const auto &it : this->files) { + cur_file = it.second.get(); + + /* By default, pad to 0x10 alignment. */ + this->file_partition_size = util::AlignUp(this->file_partition_size, 0x10); + + /* Check if extra padding is present in original source, preserve it to make our life easier. */ + const bool is_storage_or_file = cur_file->source_type == DataSourceType::Storage || cur_file->source_type == DataSourceType::File; + if (prev_file != nullptr && prev_file->source_type == cur_file->source_type && is_storage_or_file) { + const s64 expected = this->file_partition_size - prev_file->offset + prev_file->orig_offset; + if (expected != cur_file->orig_offset) { + AMS_ASSERT(expected <= cur_file->orig_offset); + this->file_partition_size += cur_file->orig_offset - expected; + } + } + + /* Calculate offsets. */ + cur_file->offset = this->file_partition_size; + this->file_partition_size += cur_file->size; + cur_file->entry_offset = entry_offset; + entry_offset += sizeof(FileEntry) + util::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4); + + /* Save current file as prev for next iteration. */ + prev_file = cur_file; + } + /* Assign deferred parent/sibling ownership. */ + for (auto it = this->files.rbegin(); it != this->files.rend(); it++) { + cur_file = it->second.get(); + cur_file->sibling = cur_file->parent->file; + cur_file->parent->file = cur_file; + } + } + + /* Process Directories. */ + { + u32 entry_offset = 0; + BuildDirectoryContext *cur_dir = nullptr; + for (const auto &it : this->directories) { + cur_dir = it.second.get(); + cur_dir->entry_offset = entry_offset; + entry_offset += sizeof(DirectoryEntry) + util::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4); + } + /* Assign deferred parent/sibling ownership. */ + for (auto it = this->directories.rbegin(); it != this->directories.rend(); it++) { + cur_dir = it->second.get(); + if (cur_dir == this->root) { + continue; + } + cur_dir->sibling = cur_dir->parent->child; + cur_dir->parent->child = cur_dir; + } + } + + /* Populate file tables. */ + for (const auto &it : this->files) { + BuildFileContext *cur_file = it.second.get(); + FileEntry *cur_entry = GetFileEntry(file_table, cur_file->entry_offset); + + /* Set entry fields. */ + cur_entry->parent = cur_file->parent->entry_offset; + cur_entry->sibling = (cur_file->sibling == nullptr) ? EmptyEntry : cur_file->sibling->entry_offset; + cur_entry->offset = cur_file->offset; + cur_entry->size = cur_file->size; + + /* Insert into hash table. */ + const u32 name_size = cur_file->path_len - cur_file->cur_path_ofs; + const size_t hash_ind = CalculatePathHash(cur_entry->parent, cur_file->path.get() + cur_file->cur_path_ofs, 0, name_size) % num_file_hash_table_entries; + cur_entry->hash = file_hash_table[hash_ind]; + file_hash_table[hash_ind] = cur_file->entry_offset; + + /* Set name. */ + cur_entry->name_size = name_size; + if (name_size) { + std::memset(cur_entry->name + util::AlignDown(name_size, 4), 0, 4); + std::memcpy(cur_entry->name, cur_file->path.get() + cur_file->cur_path_ofs, name_size); + } + + /* Emplace a source. */ + switch (cur_file->source_type) { + case DataSourceType::Storage: + case DataSourceType::File: + { + /* Try to compact if possible. */ + auto &back = out_infos->back(); + if (back.source_type == cur_file->source_type) { + back.size = cur_file->offset + FilePartitionOffset + cur_file->size - back.virtual_offset; + } else { + out_infos->emplace_back(cur_file->offset + FilePartitionOffset, cur_file->size, cur_file->source_type, cur_file->orig_offset + FilePartitionOffset); + } + } + break; + case DataSourceType::LooseSdFile: + { + out_infos->emplace_back(cur_file->offset + FilePartitionOffset, cur_file->size, cur_file->source_type, cur_file->path.release()); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* Populate directory tables. */ + for (const auto &it : this->directories) { + BuildDirectoryContext *cur_dir = it.second.get(); + DirectoryEntry *cur_entry = GetDirectoryEntry(dir_table, cur_dir->entry_offset); + + /* Set entry fields. */ + cur_entry->parent = cur_dir == this->root ? 0 : cur_dir->parent->entry_offset; + cur_entry->sibling = (cur_dir->sibling == nullptr) ? EmptyEntry : cur_dir->sibling->entry_offset; + cur_entry->child = (cur_dir->child == nullptr) ? EmptyEntry : cur_dir->child->entry_offset; + cur_entry->file = (cur_dir->file == nullptr) ? EmptyEntry : cur_dir->file->entry_offset; + + /* Insert into hash table. */ + const u32 name_size = cur_dir->path_len - cur_dir->cur_path_ofs; + const size_t hash_ind = CalculatePathHash(cur_entry->parent, cur_dir->path.get() + cur_dir->cur_path_ofs, 0, name_size) % num_dir_hash_table_entries; + cur_entry->hash = dir_hash_table[hash_ind]; + dir_hash_table[hash_ind] = cur_dir->entry_offset; + + /* Set name. */ + cur_entry->name_size = name_size; + if (name_size) { + std::memset(cur_entry->name + util::AlignDown(name_size, 4), 0, 4); + std::memcpy(cur_entry->name, cur_dir->path.get() + cur_dir->cur_path_ofs, name_size); + } + } + + /* Delete maps. */ + this->root = nullptr; + this->directories.clear(); + this->files.clear(); + + /* Set header fields. */ + header->header_size = sizeof(*header); + header->file_hash_table_size = this->file_hash_table_size; + header->file_table_size = this->file_table_size; + header->dir_hash_table_size = this->dir_hash_table_size; + header->dir_table_size = this->dir_table_size; + header->file_partition_ofs = FilePartitionOffset; + header->dir_hash_table_ofs = util::AlignUp(FilePartitionOffset + this->file_partition_size, 4); + header->dir_table_ofs = header->dir_hash_table_ofs + header->dir_hash_table_size; + header->file_hash_table_ofs = header->dir_table_ofs + header->dir_table_size; + header->file_table_ofs = header->file_hash_table_ofs + header->file_hash_table_size; + + /* Save metadata to the SD card, to save on memory space. */ + { + FsFile metadata_file; + R_ASSERT(mitm::fs::SaveAtmosphereSdFile(&metadata_file, this->program_id, "romfs_metadata.bin", metadata.get(), metadata_size)); + out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, DataSourceType::Metadata, new RemoteFile(metadata_file)); + } + } + + } + +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp new file mode 100644 index 000000000..5ab2d6887 --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp @@ -0,0 +1,220 @@ +/* + * 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 + +namespace ams::mitm::fs::romfs { + + enum class DataSourceType { + Storage, + File, + LooseSdFile, + Metadata, + Memory, + }; + + struct SourceInfo { + s64 virtual_offset; + s64 size; + union { + struct { + s64 offset; + } storage_source_info; + struct { + s64 offset; + } file_source_info; + struct { + char *path; + } loose_source_info; + struct { + ams::fs::fsa::IFile *file; + } metadata_source_info; + struct { + u8 *data; + } memory_source_info; + }; + DataSourceType source_type; + bool cleaned_up; + + SourceInfo(s64 v_o, s64 sz, DataSourceType type, s64 p_o) : virtual_offset(v_o), size(sz), source_type(type), cleaned_up(false) { + switch (this->source_type) { + case DataSourceType::Storage: + this->storage_source_info.offset = p_o; + break; + case DataSourceType::File: + this->file_source_info.offset = p_o; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + SourceInfo(s64 v_o, s64 sz, DataSourceType type, void *arg) : virtual_offset(v_o), size(sz), source_type(type), cleaned_up(false) { + switch (this->source_type) { + case DataSourceType::LooseSdFile: + this->loose_source_info.path = static_cast(arg); + break; + case DataSourceType::Metadata: + this->metadata_source_info.file = static_cast(arg); + break; + case DataSourceType::Memory: + this->memory_source_info.data = static_cast(arg); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void Cleanup() { + AMS_ASSERT(!this->cleaned_up); + this->cleaned_up = true; + + switch (this->source_type) { + case DataSourceType::Storage: + case DataSourceType::File: + break; + case DataSourceType::Metadata: + delete this->metadata_source_info.file; + break; + case DataSourceType::LooseSdFile: + delete[] this->loose_source_info.path; + break; + case DataSourceType::Memory: + std::free(static_cast(this->memory_source_info.data)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + constexpr inline bool operator<(const SourceInfo &lhs, const SourceInfo &rhs) { + return lhs.virtual_offset < rhs.virtual_offset; + } + + constexpr inline bool operator<(const SourceInfo &lhs, const s64 rhs) { + return lhs.virtual_offset <= rhs; + } + + struct BuildFileContext; + + struct BuildDirectoryContext { + NON_COPYABLE(BuildDirectoryContext); + NON_MOVEABLE(BuildDirectoryContext); + + std::unique_ptr path; + BuildDirectoryContext *parent; + BuildDirectoryContext *child; + BuildDirectoryContext *sibling; + BuildFileContext *file; + u32 cur_path_ofs; + u32 path_len; + u32 entry_offset; + + struct RootTag{}; + + BuildDirectoryContext(RootTag) : parent(nullptr), child(nullptr), sibling(nullptr), file(nullptr), cur_path_ofs(0), path_len(0), entry_offset(0) { + this->path = std::make_unique(1); + } + + BuildDirectoryContext(const char *parent_path, size_t parent_path_len, const char *entry_name, size_t entry_name_len) : parent(nullptr), child(nullptr), sibling(nullptr), file(nullptr), entry_offset(0) { + this->cur_path_ofs = parent_path_len + 1; + this->path_len = this->cur_path_ofs + entry_name_len; + this->path = std::unique_ptr(new char[this->path_len + 1]); + std::memcpy(this->path.get(), parent_path, parent_path_len); + this->path[parent_path_len] = '/'; + std::memcpy(this->path.get() + parent_path_len + 1, entry_name, entry_name_len); + this->path[this->path_len] = 0; + } + }; + + struct BuildFileContext { + NON_COPYABLE(BuildFileContext); + NON_MOVEABLE(BuildFileContext); + + std::unique_ptr path; + BuildDirectoryContext *parent; + BuildFileContext *sibling; + s64 offset; + s64 size; + s64 orig_offset; + u32 cur_path_ofs; + u32 path_len; + u32 entry_offset; + DataSourceType source_type; + + BuildFileContext(const char *parent_path, size_t parent_path_len, const char *entry_name, size_t entry_name_len, s64 sz, s64 o_o, DataSourceType type) : parent(nullptr), sibling(nullptr), offset(0), size(sz), orig_offset(o_o), entry_offset(0), source_type(type) { + this->cur_path_ofs = parent_path_len + 1; + this->path_len = this->cur_path_ofs + entry_name_len; + this->path = std::unique_ptr(new char[this->path_len + 1]); + std::memcpy(this->path.get(), parent_path, parent_path_len); + this->path[parent_path_len] = '/'; + std::memcpy(this->path.get() + parent_path_len + 1, entry_name, entry_name_len); + this->path[this->path_len] = 0; + } + }; + + struct Builder { + NON_COPYABLE(Builder); + NON_MOVEABLE(Builder); + private: + struct PathCompare { + static constexpr inline int Compare(const char *a, const char *b) { + unsigned char c1{}, c2{}; + while ((c1 = *a++) == (c2 = *b++)) { + if (c1 == '\x00') { + return 0; + } + } + return (c1 - c2); + } + + constexpr bool operator()(const char *a, const char *b) const { + return PathCompare::Compare(a, b) < 0; + } + }; + + template + using PathMap = std::map, PathCompare>; + private: + ncm::ProgramId program_id; + BuildDirectoryContext *root; + PathMap directories; + PathMap files; + size_t num_dirs; + size_t num_files; + size_t dir_table_size; + size_t file_table_size; + size_t dir_hash_table_size; + size_t file_hash_table_size; + size_t file_partition_size; + + ams::fs::DirectoryEntry dir_entry; + DataSourceType cur_source_type; + private: + void VisitDirectory(FsFileSystem *fs, BuildDirectoryContext *parent); + void VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, const void *dir_table, size_t dir_table_size, const void *file_table, size_t file_table_size); + + void AddDirectory(BuildDirectoryContext **out, BuildDirectoryContext *parent_ctx, std::unique_ptr file_ctx); + void AddFile(BuildDirectoryContext *parent_ctx, std::unique_ptr file_ctx); + public: + Builder(ncm::ProgramId pr_id); + + void AddSdFiles(); + void AddStorageFiles(ams::fs::IStorage *storage, DataSourceType source_type); + + void Build(std::vector *out_infos); + }; + +} diff --git a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp index f28fc576c..b99e68d87 100644 --- a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp @@ -291,7 +291,7 @@ namespace ams::settings::fwdbg { Result LoadSdCardKeyValueStore() { /* Open file. */ FsFile config_file; - if (R_FAILED(ams::mitm::fs::OpenAtmosphereSdFile(&config_file, "/system_settings.ini", FsOpenMode_Read))) { + if (R_FAILED(ams::mitm::fs::OpenAtmosphereSdFile(&config_file, "/system_settings.ini", fs::OpenMode_Read))) { /* It's okay if the file isn't readable/present, because we already loaded defaults. */ return ResultSuccess(); } diff --git a/stratosphere/libstratosphere/Makefile b/stratosphere/libstratosphere/Makefile index 7fe393514..7a616fecb 100644 --- a/stratosphere/libstratosphere/Makefile +++ b/stratosphere/libstratosphere/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/hos source/result source/os source/os/impl source/dd source/sf source/sf/cmif source/sf/hipc source/dmnt 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/settings source/boot2 +SOURCES := source source/ams source/hos source/result source/os source/os/impl source/dd source/fs source/sf source/sf/cmif source/sf/hipc source/dmnt 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/settings source/boot2 DATA := data INCLUDES := include diff --git a/stratosphere/libstratosphere/include/stratosphere/fs.hpp b/stratosphere/libstratosphere/include/stratosphere/fs.hpp index d1347e3a5..08a38d897 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fs.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fs.hpp @@ -22,4 +22,5 @@ #include "fs/fs_remote_filesystem.hpp" #include "fs/fs_istorage.hpp" #include "fs/fs_remote_storage.hpp" +#include "fs/fs_file_storage.hpp" #include "fs/fs_query_range.hpp" diff --git a/stratosphere/libstratosphere/include/stratosphere/fs/fs_file_storage.hpp b/stratosphere/libstratosphere/include/stratosphere/fs/fs_file_storage.hpp new file mode 100644 index 000000000..892d3d3b4 --- /dev/null +++ b/stratosphere/libstratosphere/include/stratosphere/fs/fs_file_storage.hpp @@ -0,0 +1,56 @@ +/* + * 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 "fs_common.hpp" +#include "fs_istorage.hpp" +#include "fsa/fs_ifile.hpp" + +namespace ams::fs { + + class FileStorage : public IStorage { + private: + static constexpr s64 InvalidSize = -1; + private: + std::unique_ptr unique_file; + std::shared_ptr shared_file; + fsa::IFile *base_file; + s64 size; + public: + FileStorage(fsa::IFile *f) : unique_file(f), size(InvalidSize) { + this->base_file = this->unique_file.get(); + } + + FileStorage(std::unique_ptr f) : unique_file(std::move(f)), size(InvalidSize) { + this->base_file = this->unique_file.get(); + } + + FileStorage(std::shared_ptr f) : shared_file(f), size(InvalidSize) { + this->base_file = this->shared_file.get(); + } + + virtual ~FileStorage() { /* ... */ } + protected: + Result UpdateSize(); + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result Flush() override; + virtual Result GetSize(s64 *out_size) override; + virtual Result SetSize(s64 size) override; + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + }; + +} diff --git a/stratosphere/libstratosphere/source/fs/fs_file_storage.cpp b/stratosphere/libstratosphere/source/fs/fs_file_storage.cpp new file mode 100644 index 000000000..cfb055163 --- /dev/null +++ b/stratosphere/libstratosphere/source/fs/fs_file_storage.cpp @@ -0,0 +1,93 @@ +/* + * 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 + +namespace ams::fs { + + Result FileStorage::UpdateSize() { + R_UNLESS(this->size == InvalidSize, ResultSuccess()); + return this->base_file->GetSize(&this->size); + } + + Result FileStorage::Read(s64 offset, void *buffer, size_t size) { + /* Immediately succeed if there's nothing to read. */ + R_UNLESS(size > 0, ResultSuccess()); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_UNLESS(IStorage::IsRangeValid(offset, size, this->size), fs::ResultOutOfRange()); + + size_t read_size; + return this->base_file->Read(&read_size, offset, buffer, size); + } + + Result FileStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Immediately succeed if there's nothing to write. */ + R_UNLESS(size > 0, ResultSuccess()); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_UNLESS(IStorage::IsRangeValid(offset, size, this->size), fs::ResultOutOfRange()); + + return this->base_file->Write(offset, buffer, size, fs::WriteOption()); + } + + Result FileStorage::Flush() { + return this->base_file->Flush(); + } + + Result FileStorage::GetSize(s64 *out_size) { + R_TRY(this->UpdateSize()); + *out_size = this->size; + return ResultSuccess(); + } + + Result FileStorage::SetSize(s64 size) { + this->size = InvalidSize; + return this->base_file->SetSize(size); + } + + Result FileStorage::OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case OperationId::InvalidateCache: + case OperationId::QueryRange: + if (size == 0) { + if (op_id == OperationId::QueryRange) { + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(QueryRangeInfo), fs::ResultInvalidSize()); + reinterpret_cast(dst)->Clear(); + } + return ResultSuccess(); + } + R_TRY(this->UpdateSize()); + R_UNLESS(IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultOutOfRange()); + return this->base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size); + default: + return fs::ResultUnsupportedOperation(); + } + } + +}