ncm: implement ncm.for-initialize + ncm.for-safemode

This commit is contained in:
Michael Scire 2020-03-07 02:26:36 -08:00
parent 6fa9276e57
commit 67c858e297
12 changed files with 388 additions and 9 deletions

View file

@ -35,9 +35,11 @@
#include <stratosphere/fs/fs_romfs_filesystem.hpp>
#include <stratosphere/fs/impl/fs_data.hpp>
#include <stratosphere/fs/fs_system_data.hpp>
#include <stratosphere/fs/fs_bis.hpp>
#include <stratosphere/fs/fs_content_storage.hpp>
#include <stratosphere/fs/fs_game_card.hpp>
#include <stratosphere/fs/fs_sd_card.hpp>
#include <stratosphere/fs/fs_signed_system_partition.hpp>
#include <stratosphere/fs/fs_save_data_types.hpp>
#include <stratosphere/fs/fs_save_data_management.hpp>
#include <stratosphere/fs/fs_save_data_transaction.hpp>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fs/fs_common.hpp>
#include <stratosphere/fs/fs_istorage.hpp>
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<fs::IStorage> *out, BisPartitionId id);
Result InvalidateBisCache();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "fs_common.hpp"
namespace ams::fs {
bool IsSignedSystemPartitionOnSdCardValid(const char *system_root_path);
bool IsSignedSystemPartitionOnSdCardValidDeprecated();
}

View file

@ -24,7 +24,8 @@ namespace ams::fs::fsa {
class IDirectory;
enum class QueryId {
SetConcatenationFileAttribute = FsFileSystemQueryId_SetConcatenationFileAttribute
SetConcatenationFileAttribute = 0,
IsSignedSystemPartitionOnSdCardValid = 2,
};
class IFileSystem {

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
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;
}
};
}

View file

@ -15,10 +15,12 @@
*/
#pragma once
#include <stratosphere/os.hpp>
#include <stratosphere/fs/fs_content_storage.hpp>
#include <stratosphere/fs/fs_mount.hpp>
#include <stratosphere/fs/fs_bis.hpp>
#include <stratosphere/fs/fs_content_storage.hpp>
#include <stratosphere/fs/fs_system_save_data.hpp>
#include <stratosphere/ncm/ncm_i_content_manager.hpp>
#include <stratosphere/ncm/ncm_content_manager_config.hpp>
#include <stratosphere/ncm/ncm_content_meta_database.hpp>
#include <stratosphere/ncm/ncm_bounded_map.hpp>
#include <stratosphere/ncm/ncm_rights_id_cache.hpp>
@ -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;

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#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_t>(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<BisCommonMountNameGenerator> generator(new BisCommonMountNameGenerator(id));
R_UNLESS(generator != nullptr, fs::ResultAllocationFailureInBisA());
/* Allocate a new filesystem wrapper. */
std::unique_ptr<fsa::IFileSystem> 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<fs::IStorage> *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<fs::IStorage> 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();
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#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;
}
}

View file

@ -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;

View file

@ -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. */

View file

@ -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);

View file

@ -187,13 +187,30 @@ namespace {
return sf::ServiceObjectTraits<ncm::ContentManagerImpl>::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));