diff --git a/libraries/libstratosphere/include/stratosphere/fs.hpp b/libraries/libstratosphere/include/stratosphere/fs.hpp index 94804ee82..94c2498d1 100644 --- a/libraries/libstratosphere/include/stratosphere/fs.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs.hpp @@ -35,9 +35,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_bis.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_bis.hpp new file mode 100644 index 000000000..a6831973e --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_bis.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-2020 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 ams::fs { + + enum class BisPartitionId { + /* Boot0 */ + BootPartition1Root = 0, + + /* Boot1 */ + BootPartition2Root = 10, + + /* Non-Boot */ + UserDataRoot = 20, + BootConfigAndPackage2Part1 = 21, + BootConfigAndPackage2Part2 = 22, + BootConfigAndPackage2Part3 = 23, + BootConfigAndPackage2Part4 = 24, + BootConfigAndPackage2Part5 = 25, + BootConfigAndPackage2Part6 = 26, + CalibrationBinary = 27, + CalibrationFile = 28, + SafeMode = 29, + User = 30, + System = 31, + SystemProperEncryption = 32, + SystemProperPartition = 33, + SignedSystemPartitionOnSafeMode = 34, + }; + + const char *GetBisMountName(BisPartitionId id); + + Result MountBis(BisPartitionId id, const char *root_path); + Result MountBis(const char *name, BisPartitionId id); + + void SetBisRootForHost(BisPartitionId id, const char *root_path); + + Result OpenBisPartition(std::unique_ptr *out, BisPartitionId id); + + Result InvalidateBisCache(); + +} diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_signed_system_partition.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_signed_system_partition.hpp new file mode 100644 index 000000000..1c0ff5b28 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_signed_system_partition.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2020 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" + +namespace ams::fs { + + bool IsSignedSystemPartitionOnSdCardValid(const char *system_root_path); + bool IsSignedSystemPartitionOnSdCardValidDeprecated(); + +} diff --git a/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp b/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp index a1897ca8e..f785b18fe 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp @@ -24,7 +24,8 @@ namespace ams::fs::fsa { class IDirectory; enum class QueryId { - SetConcatenationFileAttribute = FsFileSystemQueryId_SetConcatenationFileAttribute + SetConcatenationFileAttribute = 0, + IsSignedSystemPartitionOnSdCardValid = 2, }; class IFileSystem { diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_config.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_config.hpp new file mode 100644 index 000000000..041582e00 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_config.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019-2020 Adubbz, 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::ncm { + + struct ContentManagerConfig { + bool import_database_from_system; + bool import_database_from_system_on_sd; + + constexpr bool HasAnyImport() const { + return this->import_database_from_system || this->import_database_from_system_on_sd; + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp index e3663c66f..507d7b24c 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp @@ -15,10 +15,12 @@ */ #pragma once #include -#include #include +#include +#include #include #include +#include #include #include #include @@ -68,7 +70,7 @@ namespace ams::ncm { ContentMetaDatabaseRoot() { /* ... */ } }; private: - os::Mutex mutex; + os::RecursiveMutex mutex; bool initialized; ContentStorageRoot content_storage_roots[MaxContentStorageRoots]; ContentMetaDatabaseRoot content_meta_database_roots[MaxContentMetaDatabaseRoots]; @@ -79,7 +81,7 @@ namespace ams::ncm { ContentManagerImpl() : initialized(false) { /* ... */ }; ~ContentManagerImpl(); public: - Result Initialize(); + Result Initialize(const ContentManagerConfig &config); private: /* Helpers. */ Result GetContentStorageRoot(ContentStorageRoot **out, StorageId id); @@ -91,8 +93,9 @@ namespace ams::ncm { Result InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, const SystemSaveDataInfo &info, size_t max_content_metas); Result InitializeGameCardContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, size_t max_content_metas); - Result EnsureAndMountSystemSaveData(const char *mount, const SystemSaveDataInfo &info) const; + Result ImportContentMetaDatabase(StorageId storage_id, const char *import_mount_name, const char *path); + Result EnsureAndMountSystemSaveData(const char *mount, const SystemSaveDataInfo &info) const; public: /* Actual commands. */ virtual Result CreateContentStorage(StorageId storage_id) override; diff --git a/libraries/libstratosphere/source/fs/fs_bis.cpp b/libraries/libstratosphere/source/fs/fs_bis.cpp new file mode 100644 index 000000000..b906be79d --- /dev/null +++ b/libraries/libstratosphere/source/fs/fs_bis.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018-2020 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 "fsa/fs_mount_utils.hpp" + +namespace ams::fs { + + namespace { + + class BisCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable { + private: + const BisPartitionId id; + public: + explicit BisCommonMountNameGenerator(BisPartitionId i) : id(i) { /* ... */ } + + virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override { + /* Determine how much space we need. */ + const char *bis_mount_name = GetBisMountName(this->id); + const size_t needed_size = strnlen(bis_mount_name, MountNameLengthMax) + 2; + AMS_ABORT_UNLESS(dst_size >= needed_size); + + /* Generate the name. */ + auto size = std::snprintf(dst, dst_size, "%s:", bis_mount_name); + AMS_ASSERT(static_cast(size) == needed_size - 1); + + return ResultSuccess(); + } + }; + + } + + namespace impl { + + Result MountBisImpl(const char *name, BisPartitionId id, const char *root_path) { + /* Validate the mount name. */ + R_TRY(impl::CheckMountNameAllowingReserved(name)); + + /* Open the partition. This uses libnx bindings. */ + /* Note: Nintendo ignores the root_path here. */ + FsFileSystem fs; + R_TRY(fsOpenBisFileSystem(std::addressof(fs), static_cast<::FsBisPartitionId>(id), "")); + + /* Allocate a new mountname generator. */ + std::unique_ptr generator(new BisCommonMountNameGenerator(id)); + R_UNLESS(generator != nullptr, fs::ResultAllocationFailureInBisA()); + + /* Allocate a new filesystem wrapper. */ + std::unique_ptr fsa(new RemoteFileSystem(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInBisB()); + + /* Register. */ + return fsa::Register(name, std::move(fsa), std::move(generator)); + } + + Result SetBisRootForHostImpl(BisPartitionId id, const char *root_path) { + /* Ensure the path isn't too long. */ + size_t len = strnlen(root_path, fs::EntryNameLengthMax + 1); + R_UNLESS(len <= fs::EntryNameLengthMax, fs::ResultTooLongPath()); + + fssrv::sf::Path sf_path; + if (len > 0) { + const bool ending_sep = PathTool::IsSeparator(root_path[len - 1]); + FspPathPrintf(std::addressof(sf_path), "%s%s", root_path, ending_sep ? "" : "/"); + } else { + sf_path.str[0] = '\x00'; + } + + /* TODO: Libnx binding for fsSetBisRootForHost */ + AMS_ABORT(); + } + + } + + const char *GetBisMountName(BisPartitionId id) { + switch (id) { + case BisPartitionId::CalibrationFile: return impl::BisCalibrationFilePartitionMountName; + case BisPartitionId::SafeMode: return impl::BisSafeModePartitionMountName; + case BisPartitionId::User: return impl::BisUserPartitionMountName; + case BisPartitionId::System: return impl::BisSystemPartitionMountName; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result MountBis(BisPartitionId id, const char *root_path) { + return impl::MountBisImpl(GetBisMountName(id), id, root_path); + } + + Result MountBis(const char *name, BisPartitionId id) { + return impl::MountBisImpl(name, id, nullptr); + } + + void SetBisRootForHost(BisPartitionId id, const char *root_path) { + R_ABORT_UNLESS(impl::SetBisRootForHostImpl(id, root_path)); + } + + Result OpenBisPartition(std::unique_ptr *out, BisPartitionId id) { + /* Open the partition. This uses libnx bindings. */ + FsStorage s; + R_TRY(fsOpenBisStorage(std::addressof(s), static_cast<::FsBisPartitionId>(id))); + + /* Allocate a new storage wrapper. */ + std::unique_ptr storage(new RemoteStorage(s)); + R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInBisC()); + + *out = std::move(storage); + return ResultSuccess(); + } + + Result InvalidateBisCache() { + /* TODO: Libnx binding for this command. */ + AMS_ABORT(); + } + +} diff --git a/libraries/libstratosphere/source/fs/fs_signed_system_partition.cpp b/libraries/libstratosphere/source/fs/fs_signed_system_partition.cpp new file mode 100644 index 000000000..c41bcf9ee --- /dev/null +++ b/libraries/libstratosphere/source/fs/fs_signed_system_partition.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020 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 "fsa/fs_filesystem_accessor.hpp" +#include "fsa/fs_mount_utils.hpp" + +namespace ams::fs { + + bool IsSignedSystemPartitionOnSdCardValid(const char *system_root_path) { + /* Get the accessor for the system filesystem. */ + impl::FileSystemAccessor *accessor; + const char *sub_path; + R_ABORT_UNLESS(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), system_root_path)); + + char is_valid; + R_TRY_CATCH(accessor->QueryEntry(std::addressof(is_valid), 1, nullptr, 0, fsa::QueryId::IsSignedSystemPartitionOnSdCardValid, "/")) { + /* If querying isn't supported, then the partition isn't valid. */ + R_CATCH(fs::ResultUnsupportedOperation) { is_valid = false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return is_valid; + } + + bool IsSignedSystemPartitionOnSdCardValidDeprecated() { + /* Ensure we only call with correct version. */ + auto version = hos::GetVersion(); + AMS_ABORT_UNLESS(hos::Version_400 <= version && version < hos::Version_800); + + /* Check that the partition is valid. */ + bool is_valid; + R_ABORT_UNLESS(fsIsSignedSystemPartitionOnSdCardValid(std::addressof(is_valid))); + return is_valid; + } + +} diff --git a/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp b/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp index 3294c16d8..7618f7b11 100644 --- a/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp +++ b/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp @@ -156,7 +156,6 @@ namespace ams::fs { return accessor->GetTotalSpaceSize(out, sub_path); } - Result SetConcatenationFileAttribute(const char *path) { impl::FileSystemAccessor *accessor; const char *sub_path; diff --git a/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp b/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp index 0632efe4a..530780437 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp @@ -107,6 +107,28 @@ namespace ams::ncm { default: return ResultUnknownContentMetaDatabaseNotActive(); } } + + ALWAYS_INLINE bool ShouldPerformImport(const ContentManagerConfig &config, const char *bis_mount_name) { + AMS_ASSERT(config.HasAnyImport()); + if (config.import_database_from_system) { + /* If we're importing from system, just do the import. */ + return true; + } else /* if (config.import_database_from_system_on_sd) */ { + /* If we're importing from system on SD, make sure that the signed system partition is valid. */ + const auto version = hos::GetVersion(); + if (version >= hos::Version_800 || version < hos::Version_400) { + /* On >= 8.0.0, a simpler method was added to check validity. */ + /* This also works on < 4.0.0 (though the system partition will never be on-sd there), */ + /* and so this will always return false. */ + char path[fs::MountNameLengthMax + 2 /* :/ */ + 1]; + std::snprintf(path, sizeof(path), "%s:/", bis_mount_name); + return fs::IsSignedSystemPartitionOnSdCardValid(path); + } else { + /* On 4.0.0-7.0.1, use the remote command to validate the system partition. */ + return fs::IsSignedSystemPartitionOnSdCardValidDeprecated(); + } + } + } } ContentManagerImpl::~ContentManagerImpl() { @@ -218,7 +240,36 @@ namespace ams::ncm { return ResultSuccess(); } - Result ContentManagerImpl::Initialize() { + Result ContentManagerImpl::ImportContentMetaDatabase(StorageId storage_id, const char *import_mount_name, const char *path) { + std::scoped_lock lk(this->mutex); + + /* Obtain the content meta database root. */ + ContentMetaDatabaseRoot *root; + R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); + + /* Print the savedata path. */ + PathString savedata_db_path; + savedata_db_path.SetFormat("%s/%s", root->path, "imkvdb.arc"); + + /* Print a path for the mounted partition. */ + PathString bis_db_path; + bis_db_path.SetFormat("%s:/%s", import_mount_name, path); + + /* Mount the savedata. */ + R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id)); + ON_SCOPE_EXIT { fs::Unmount(root->mount_name); }; + + /* Ensure the path exists for us to import to. */ + R_TRY(impl::EnsureDirectoryRecursively(root->path)); + + /* Copy the file from bis to our save. */ + R_TRY(impl::CopyFile(savedata_db_path, bis_db_path)); + + /* Commit the import. */ + return fs::CommitSaveData(root->mount_name); + } + + Result ContentManagerImpl::Initialize(const ContentManagerConfig &config) { std::scoped_lock lk(this->mutex); /* Check if we've already initialized. */ @@ -246,7 +297,23 @@ namespace ams::ncm { if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) { R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem)); - /* TODO: N supports building the database depending on config (unused on retail). */ + /* NOTE: Nintendo added support for building/importing in 4.0.0. */ + /* However, there's no reason to restrict this on a per-version basis. */ + + /* If we should import the database from system, do so and verify it. */ + if (config.HasAnyImport()) { + /* Get a mount name for the system partition. */ + auto bis_mount_name = impl::CreateUniqueMountName(); + + /* Mount the BIS partition that contains the database we're importing. */ + R_TRY(fs::MountBis(bis_mount_name.str, fs::BisPartitionId::System)); + ON_SCOPE_EXIT { fs::Unmount(bis_mount_name.str); }; + + if (ShouldPerformImport(config, bis_mount_name.str)) { + R_TRY(this->ImportContentMetaDatabase(StorageId::BuiltInSystem, bis_mount_name.str, "cnmtdb.arc")); + R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem)); + } + } } /* Ensure correct flags on the BuiltInSystem save data. */ diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 9ebbaddd2..be0cb5c36 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -53,6 +53,9 @@ namespace ams::fs { R_DEFINE_ERROR_RANGE(AllocationFailure, 3200, 3499); R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemAccessorA, 3211); R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemAccessorB, 3212); + R_DEFINE_ERROR_RESULT(AllocationFailureInBisA, 3215); + R_DEFINE_ERROR_RESULT(AllocationFailureInBisB, 3216); + R_DEFINE_ERROR_RESULT(AllocationFailureInBisC, 3217); R_DEFINE_ERROR_RESULT(AllocationFailureInContentStorageA, 3220); R_DEFINE_ERROR_RESULT(AllocationFailureInContentStorageB, 3221); R_DEFINE_ERROR_RESULT(AllocationFailureInDataA, 3222); diff --git a/stratosphere/ncm/source/ncm_main.cpp b/stratosphere/ncm/source/ncm_main.cpp index d44efde10..2f0d6cd6e 100644 --- a/stratosphere/ncm/source/ncm_main.cpp +++ b/stratosphere/ncm/source/ncm_main.cpp @@ -187,13 +187,30 @@ namespace { return sf::ServiceObjectTraits::SharedPointerHelper::GetEmptyDeleteSharedPointer(std::addressof(g_ncm_manager_service_object)); } + /* Compile-time configuration. */ +#ifdef NCM_BUILD_FOR_INTITIALIZE + constexpr inline bool ImportSystemDatabase = true; +#else + constexpr inline bool ImportSystemDatabase = false; +#endif + +#ifdef NCM_BUILD_FOR_SAFEMODE + constexpr inline bool ImportSystemDatabaseFromSignedSystemPartitionOnSdCard = true; +#else + constexpr inline bool ImportSystemDatabaseFromSignedSystemPartitionOnSdCard = false; +#endif + + static_assert(!(ImportSystemDatabase && ImportSystemDatabaseFromSignedSystemPartitionOnSdCard), "Invalid NCM build configuration!"); + + constexpr inline ncm::ContentManagerConfig ManagerConfig = { ImportSystemDatabase, ImportSystemDatabaseFromSignedSystemPartitionOnSdCard }; + } int main(int argc, char **argv) { /* Create and initialize the content manager. */ auto content_manager = GetSharedPointerToContentManager(); - R_ABORT_UNLESS(content_manager->Initialize()); + R_ABORT_UNLESS(content_manager->Initialize(ManagerConfig)); /* Initialize ncm's server and start threads. */ R_ABORT_UNLESS(g_ncm_server_manager.Initialize(content_manager));