From 8f4cf2deb3ec82599217003c62aa214be653238f Mon Sep 17 00:00:00 2001 From: Adubbz Date: Fri, 6 Mar 2020 23:10:42 +1100 Subject: [PATCH] ncm: added comments --- .../ncm/source/ncm_content_id_utils.cpp | 2 + .../ncm/source/ncm_content_manager_impl.cpp | 63 +++++++++- .../ncm/source/ncm_content_manager_impl.hpp | 2 + .../source/ncm_content_meta_database_impl.cpp | 39 ++++++ .../source/ncm_content_meta_database_impl.hpp | 2 + .../ncm_content_meta_database_impl_base.hpp | 1 + .../ncm/source/ncm_content_storage_impl.cpp | 116 ++++++++++++++++-- .../ncm/source/ncm_content_storage_impl.hpp | 2 + .../source/ncm_content_storage_impl_base.hpp | 1 + stratosphere/ncm/source/ncm_main.cpp | 5 + stratosphere/ncm/source/ncm_make_path.cpp | 26 ++++ ...m_on_memory_content_meta_database_impl.cpp | 7 +- ...m_on_memory_content_meta_database_impl.hpp | 1 + .../ncm/source/ncm_placeholder_accessor.cpp | 34 ++++- .../ncm/source/ncm_placeholder_accessor.hpp | 1 + .../ncm_read_only_content_storage_impl.cpp | 25 +++- .../ncm_read_only_content_storage_impl.hpp | 1 + 17 files changed, 303 insertions(+), 25 deletions(-) diff --git a/stratosphere/ncm/source/ncm_content_id_utils.cpp b/stratosphere/ncm/source/ncm_content_id_utils.cpp index d920a870a..e91cca988 100644 --- a/stratosphere/ncm/source/ncm_content_id_utils.cpp +++ b/stratosphere/ncm/source/ncm_content_id_utils.cpp @@ -26,10 +26,12 @@ namespace ams::ncm { } bool GetBytesFromString(void *dst, size_t dst_size, const char *src, size_t src_size) { + /* Each byte is comprised of hex characters. */ if (!util::IsAligned(src_size, 2) || (dst_size * 2 < src_size)) { return false; } + /* Convert each character pair to a byte until we reach the end. */ for (size_t i = 0; i < src_size; i += 2) { char tmp[3]; strlcpy(tmp, src + i, sizeof(tmp)); diff --git a/stratosphere/ncm/source/ncm_content_manager_impl.cpp b/stratosphere/ncm/source/ncm_content_manager_impl.cpp index 0a6ee634d..7bbc8f550 100644 --- a/stratosphere/ncm/source/ncm_content_manager_impl.cpp +++ b/stratosphere/ncm/source/ncm_content_manager_impl.cpp @@ -78,7 +78,11 @@ namespace ams::ncm { Result EnsureBuiltInSystemSaveDataFlags() { u32 cur_flags = 0; + + /* Obtain the existing flags. */ R_TRY(fs::GetSaveDataFlags(std::addressof(cur_flags), BuiltInSystemSaveDataId)); + + /* Update the flags if needed. */ if (cur_flags != BuiltInSystemSaveDataFlags) { R_TRY(fs::SetSaveDataFlags(BuiltInSystemSaveDataId, fs::SaveDataSpaceId::System, BuiltInSystemSaveDataFlags)); } @@ -109,10 +113,12 @@ namespace ams::ncm { ContentManagerImpl::~ContentManagerImpl() { std::scoped_lock lk(this->mutex); + /* Disable and unmount all content storage roots. */ for (auto &root : this->content_storage_roots) { this->InactivateContentStorage(root.storage_id); } + /* Disable and unmount all content meta database roots. */ for (auto &root : this->content_meta_database_roots) { this->InactivateContentMetaDatabase(root.storage_id); } @@ -121,8 +127,10 @@ namespace ams::ncm { Result ContentManagerImpl::EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &info) const { constexpr u64 OwnerId = 0; + /* Don't create save if absent - We want to handle this case ourselves. */ fs::DisableAutoSaveDataCreation(); + /* Mount existing system save data if present, otherwise create it then mount. */ R_TRY_CATCH(fs::MountSystemSaveData(mount_name, info.space_id, info.id)) { R_CATCH(fs::ResultTargetNotFound) { R_TRY(fs::CreateSystemSaveData(info.space_id, info.id, OwnerId, info.size, info.journal_size, info.flags)); @@ -134,8 +142,10 @@ namespace ams::ncm { } Result ContentManagerImpl::GetContentStorageRoot(ContentStorageRoot **out, StorageId id) { + /* Storage must not be StorageId::Any or StorageId::None. */ R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage()); + /* Find a root with a matching storage id. */ for (auto &root : this->content_storage_roots) { if (root.storage_id == id) { *out = std::addressof(root); @@ -147,8 +157,10 @@ namespace ams::ncm { } Result ContentManagerImpl::GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id) { + /* Storage must not be StorageId::Any or StorageId::None. */ R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage()); + /* Find a root with a matching storage id. */ for (auto &root : this->content_meta_database_roots) { if (root.storage_id == id) { *out = std::addressof(root); @@ -165,6 +177,7 @@ namespace ams::ncm { out->content_storage_id = content_storage_id; out->content_storage = nullptr; + /* Create a new mount name and copy it to out. */ std::strcpy(out->mount_name, impl::CreateUniqueMountName().str); std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name); @@ -175,6 +188,7 @@ namespace ams::ncm { out->storage_id = StorageId::GameCard; out->content_storage = nullptr; + /* Create a new mount name and copy it to out. */ std::strcpy(out->mount_name, impl::CreateUniqueMountName().str); std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name); @@ -188,6 +202,7 @@ namespace ams::ncm { out->content_meta_database = nullptr; out->kvs = std::nullopt; + /* Create a new mount name and copy it to out. */ std::strcpy(out->mount_name, impl::CreateUniqueMountName().str); out->mount_name[0] = '#'; std::snprintf(out->path, sizeof(out->path), "%s:/meta", out->mount_name); @@ -264,13 +279,18 @@ namespace ams::ncm { Result ContentManagerImpl::CreateContentStorage(StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Obtain the content storage root. */ ContentStorageRoot *root; R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); + /* Mount the relevant content storage. */ R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id)); ON_SCOPE_EXIT { fs::Unmount(root->mount_name); }; + /* Ensure the content storage root's path exists. */ R_TRY(impl::EnsureDirectoryRecursively(root->path)); + + /* Initialize content and placeholder directories for the root. */ R_TRY(ContentStorageImpl::InitializeBase(root->path)); return ResultSuccess(); @@ -281,14 +301,18 @@ namespace ams::ncm { R_UNLESS(storage_id != StorageId::GameCard, ncm::ResultUnknownStorage()); + /* Obtain the content meta database root. */ ContentMetaDatabaseRoot *root; R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); + /* Mount (and optionally create) save data for the root. */ R_TRY(this->EnsureAndMountSystemSaveData(root->mount_name, root->info)); ON_SCOPE_EXIT { fs::Unmount(root->mount_name); }; + /* Ensure the content meta database root's path exists. */ R_TRY(impl::EnsureDirectoryRecursively(root->path)); + /* Commit our changes. */ R_TRY(fs::CommitSaveData(root->mount_name)); return ResultSuccess(); @@ -297,29 +321,36 @@ namespace ams::ncm { Result ContentManagerImpl::VerifyContentStorage(StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Obtain the content storage root. */ ContentStorageRoot *root; R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); + /* Substitute the mount name in the root's path with a unique one. */ char path[0x80]; - auto mount_name = impl::CreateUniqueMountName(); /* should this be fs::? should it be ncm::? ncm::impl? */ + auto mount_name = impl::CreateUniqueMountName(); ReplaceMountName(path, mount_name.str, root->path); + /* Mount the relevant content storage. */ R_TRY(fs::MountContentStorage(mount_name.str, root->content_storage_id)); ON_SCOPE_EXIT { fs::Unmount(mount_name.str); }; + /* Ensure the root, content and placeholder directories exist for the storage. */ R_TRY(ContentStorageImpl::VerifyBase(path)); return ResultSuccess(); } Result ContentManagerImpl::VerifyContentMetaDatabase(StorageId storage_id) { + /* Game card content meta databases will always be valid. */ R_UNLESS(storage_id != StorageId::GameCard, ResultSuccess()); std::scoped_lock lk(this->mutex); + /* Obtain the content meta database root. */ ContentMetaDatabaseRoot *root; R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); + /* Mount save data for non-existing content meta databases. */ auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); }; if (!root->content_meta_database) { R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id)); @@ -327,6 +358,7 @@ namespace ams::ncm { mount_guard.Cancel(); } + /* Ensure the root path exists. */ bool has_dir = false; R_TRY(impl::HasDirectory(&has_dir, root->path)); R_UNLESS(has_dir, ncm::ResultInvalidContentMetaDatabase()); @@ -337,10 +369,12 @@ namespace ams::ncm { Result ContentManagerImpl::OpenContentStorage(sf::Out> out, StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Obtain the content storage root. */ ContentStorageRoot *root; R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); if (hos::GetVersion() >= hos::Version_200) { + /* Obtain the content storage if already active. */ R_UNLESS(root->content_storage, GetContentStorageNotActiveResult(storage_id)); } else { /* 1.0.0 activates content storages as soon as they are opened. */ @@ -357,11 +391,12 @@ namespace ams::ncm { Result ContentManagerImpl::OpenContentMetaDatabase(sf::Out> out, StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Obtain the content meta database root. */ ContentMetaDatabaseRoot *root; R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); - if (hos::GetVersion() >= hos::Version_200) { + /* Obtain the content meta database if already active. */ R_UNLESS(root->content_meta_database, GetContentMetaDatabaseNotActiveResult(storage_id)); } else { /* 1.0.0 activates content meta databases as soon as they are opened. */ @@ -386,11 +421,14 @@ namespace ams::ncm { Result ContentManagerImpl::CleanupContentMetaDatabase(StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Disable and unmount content meta database root. */ R_TRY(this->InactivateContentMetaDatabase(storage_id)); + /* Obtain the content meta database root. */ ContentMetaDatabaseRoot *root; R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); + /* Delete save data for the content meta database root. */ R_TRY(fs::DeleteSaveData(root->info.space_id, root->info.id)); return ResultSuccess(); } @@ -398,12 +436,14 @@ namespace ams::ncm { Result ContentManagerImpl::ActivateContentStorage(StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Obtain the content storage root. */ ContentStorageRoot *root; R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); /* Check if the storage is already activated. */ R_UNLESS(root->content_storage == nullptr, ResultSuccess()); + /* Mount based on the storage type. */ if (storage_id == StorageId::GameCard) { fs::GameCardHandle handle; R_TRY(fs::GetGameCardHandle(std::addressof(handle))); @@ -412,15 +452,19 @@ namespace ams::ncm { R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id)); } + /* Unmount on failure. */ auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); }; if (storage_id == StorageId::GameCard) { + /* Game card content storage is read only. */ auto content_storage = std::make_shared(); R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath)); root->content_storage = std::move(content_storage); } else { + /* Create a content storage. */ auto content_storage = std::make_shared(); + /* Initialize content storage with an appropriate path function. */ switch (storage_id) { case StorageId::BuiltInSystem: R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath, MakeFlatPlaceHolderFilePath, false, std::addressof(this->rights_id_cache))); @@ -436,6 +480,7 @@ namespace ams::ncm { root->content_storage = std::move(content_storage); } + /* Prevent unmounting. */ mount_guard.Cancel(); return ResultSuccess(); } @@ -443,9 +488,11 @@ namespace ams::ncm { Result ContentManagerImpl::InactivateContentStorage(StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Obtain the content storage root. */ ContentStorageRoot *root; R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); + /* Disable and unmount the content storage, if present. */ if (root->content_storage) { /* N doesn't bother checking the result of this */ root->content_storage->DisableForcibly(); @@ -459,6 +506,7 @@ namespace ams::ncm { Result ContentManagerImpl::ActivateContentMetaDatabase(StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Obtain the content meta database root. */ ContentMetaDatabaseRoot *root; R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); @@ -469,15 +517,23 @@ namespace ams::ncm { root->kvs.emplace(); if (storage_id == StorageId::GameCard) { + /* Initialize the key value store. */ R_TRY(root->kvs->Initialize(root->max_content_metas)); + + /* Create an on memory content meta database for game cards. */ root->content_meta_database = std::make_shared(std::addressof(*root->kvs)); } else { + /* Mount save data for this root. */ R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id)); + + /* Unmount on failure. */ auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); }; + /* Initialize and load the key value store from the filesystem. */ R_TRY(root->kvs->Initialize(root->path, root->max_content_metas)); R_TRY(root->kvs->Load()); + /* Create the content meta database. */ root->content_meta_database = std::make_shared(std::addressof(*root->kvs), root->mount_name); mount_guard.Cancel(); } @@ -488,15 +544,18 @@ namespace ams::ncm { Result ContentManagerImpl::InactivateContentMetaDatabase(StorageId storage_id) { std::scoped_lock lk(this->mutex); + /* Obtain the content meta database root. */ ContentMetaDatabaseRoot *root; R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); + /* Disable the content meta database, if present. */ if (root->content_meta_database) { /* N doesn't bother checking the result of this */ root->content_meta_database->DisableForcibly(); root->content_meta_database = nullptr; root->kvs = std::nullopt; + /* Also unmount, except in the case of game cards. */ if (storage_id != StorageId::GameCard) { fs::Unmount(root->mount_name); } diff --git a/stratosphere/ncm/source/ncm_content_manager_impl.hpp b/stratosphere/ncm/source/ncm_content_manager_impl.hpp index 1614e5833..27d1bd6c8 100644 --- a/stratosphere/ncm/source/ncm_content_manager_impl.hpp +++ b/stratosphere/ncm/source/ncm_content_manager_impl.hpp @@ -76,6 +76,7 @@ namespace ams::ncm { public: Result Initialize(); private: + /* Helpers. */ Result GetContentStorageRoot(ContentStorageRoot **out, StorageId id); Result GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id); @@ -88,6 +89,7 @@ namespace ams::ncm { Result EnsureAndMountSystemSaveData(const char *mount, const SystemSaveDataInfo &info) const; public: + /* Actual commands. */ virtual Result CreateContentStorage(StorageId storage_id) override; virtual Result CreateContentMetaDatabase(StorageId storage_id) override; virtual Result VerifyContentStorage(StorageId storage_id) override; diff --git a/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp b/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp index 735c49847..e216474a7 100644 --- a/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp +++ b/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp @@ -52,15 +52,21 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); std::optional found_key = std::nullopt; + + /* Find the last key with the desired program id. */ for (auto entry = this->kvs->lower_bound(ContentMetaKey::Make(id, 0, ContentMetaType::Unknown)); entry != this->kvs->end(); entry++) { + /* No further entries will match the program id, discontinue. */ if (entry->GetKey().id != id) { break; } + /* We are only interested in keys with the Full content install type. */ if (entry->GetKey().install_type == ContentInstallType::Full) { found_key = entry->GetKey(); } } + + /* Check if the key is absent. */ R_UNLESS(found_key, ncm::ResultContentMetaNotFound()); *out_key = *found_key; @@ -90,12 +96,15 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); + /* Obtain the content meta for the given key. */ const void *meta; size_t meta_size; R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + /* Create a reader. */ ContentMetaReader reader(meta, meta_size); + /* Read content infos from the given offset up to the given count. */ size_t count; for (count = 0; count < out_info.GetSize() && count + offset < reader.GetContentCount(); count++) { out_info[count] = *reader.GetContentInfo(offset + count); @@ -111,6 +120,7 @@ namespace ams::ncm { size_t entries_total = 0; size_t entries_written = 0; + /* Iterate over all entries. */ for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) { ContentMetaKey key = entry->GetKey(); @@ -121,12 +131,15 @@ namespace ams::ncm { /* If application id is present, check if it matches the filter. */ if (application_id != InvalidProgramId) { + /* Obtain the content meta for the key. */ const void *meta; size_t meta_size; R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + /* Create a reader. */ ContentMetaReader reader(meta, meta_size); + /* Ensure application id matches, if present. */ if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id && application_id != *entry_application_id) { continue; } @@ -155,6 +168,7 @@ namespace ams::ncm { size_t entries_total = 0; size_t entries_written = 0; + /* Iterate over all entries. */ for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) { ContentMetaKey key = entry->GetKey(); @@ -185,6 +199,7 @@ namespace ams::ncm { *out = false; + /* Check if key is present. */ size_t size; R_TRY_CATCH(this->kvs->GetValueSize(&size, key)) { R_CONVERT(kvdb::ResultKeyNotFound, ResultSuccess()); @@ -198,6 +213,7 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); *out = false; + /* Check if keys are present. */ for (size_t i = 0; i < keys.GetSize(); i++) { bool has; R_TRY(this->Has(std::addressof(has), keys[i])); @@ -211,6 +227,7 @@ namespace ams::ncm { Result ContentMetaDatabaseImpl::GetSize(sf::Out out_size, const ContentMetaKey &key) { R_TRY(this->EnsureEnabled()); + /* Determine the content meta size for the key. */ size_t size; R_TRY(this->GetContentMetaSize(&size, key)); @@ -222,12 +239,15 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); R_UNLESS(key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch, ncm::ResultInvalidContentMetaKey()); + /* Obtain the content meta for the key. */ const void *meta; size_t meta_size; R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + /* Create a reader. */ ContentMetaReader reader(meta, meta_size); + /* Obtain the required system version. */ if (key.type == ContentMetaType::Application) { out_version.SetValue(reader.GetExtendedHeader()->required_system_version); } else { @@ -241,12 +261,15 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); R_UNLESS(key.type == ContentMetaType::Application, ncm::ResultInvalidContentMetaKey()); + /* Obtain the content meta for the key. */ const void *meta; size_t meta_size; R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + /* Create a reader. */ ContentMetaReader reader(meta, meta_size); + /* Obtain the patch id. */ out_patch_id.SetValue(reader.GetExtendedHeader()->patch_id); return ResultSuccess(); } @@ -265,9 +288,11 @@ namespace ams::ncm { out_orphaned[i] = true; } + /* If key value store is empty, all content is orphaned. */ R_UNLESS(this->kvs->GetCount() != 0, ResultSuccess()); auto IsOrphanedContent = [](const sf::InArray &list, const ncm::ContentId &id) ALWAYS_INLINE_LAMBDA { + /* Check if any input content ids match our found content id. */ for (size_t i = 0; i < list.GetSize(); i++) { if (list[i] == id) { return std::make_optional(i); @@ -276,6 +301,7 @@ namespace ams::ncm { return std::optional(std::nullopt); }; + /* Iterate over all entries. */ for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) { ContentMetaReader reader(entry->GetValuePointer(), entry->GetValueSize()); @@ -293,16 +319,22 @@ namespace ams::ncm { Result ContentMetaDatabaseImpl::Commit() { R_TRY(this->EnsureEnabled()); + + /* Save and commit. */ R_TRY(this->kvs->Save()); return fs::CommitSaveData(this->mount_name); } Result ContentMetaDatabaseImpl::HasContent(sf::Out out, const ContentMetaKey &key, const ContentId &content_id) { + /* Obtain the content meta for the key. */ const void *meta; size_t meta_size; R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + /* Create a reader. */ ContentMetaReader reader(meta, meta_size); + + /* Check if any content infos contain a matching id. */ for (size_t i = 0; i < reader.GetContentCount(); i++) { if (content_id == reader.GetContentInfo(i)->GetId()) { out.SetValue(true); @@ -318,12 +350,15 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); + /* Obtain the content meta for the key. */ const void *meta; size_t meta_size; R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + /* Create a reader. */ ContentMetaReader reader(meta, meta_size); + /* Read content meta infos from the given offset up to the given count. */ size_t count; for (count = 0; count < out_meta_info.GetSize() && count + offset <= reader.GetContentMetaCount(); count++) { out_meta_info[count] = *reader.GetContentMetaInfo(count + offset); @@ -336,10 +371,12 @@ namespace ams::ncm { Result ContentMetaDatabaseImpl::GetAttributes(sf::Out out_attributes, const ContentMetaKey &key) { R_TRY(this->EnsureEnabled()); + /* Obtain the content meta for the key. */ const void *meta; size_t meta_size; R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + /* Create a reader. */ ContentMetaReader reader(meta, meta_size); out_attributes.SetValue(reader.GetHeader()->attributes); @@ -349,10 +386,12 @@ namespace ams::ncm { Result ContentMetaDatabaseImpl::GetRequiredApplicationVersion(sf::Out out_version, const ContentMetaKey &key) { R_TRY(this->EnsureEnabled()); + /* Obtain the content meta for the key. */ const void *meta; size_t meta_size; R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + /* Create a reader. */ ContentMetaReader reader(meta, meta_size); /* Get the required version. */ diff --git a/stratosphere/ncm/source/ncm_content_meta_database_impl.hpp b/stratosphere/ncm/source/ncm_content_meta_database_impl.hpp index 24c4710ec..155ba7248 100644 --- a/stratosphere/ncm/source/ncm_content_meta_database_impl.hpp +++ b/stratosphere/ncm/source/ncm_content_meta_database_impl.hpp @@ -26,9 +26,11 @@ namespace ams::ncm { ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs, const char *mount_name) : ContentMetaDatabaseImplBase(kvs, mount_name) { /* ... */ } ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs) : ContentMetaDatabaseImplBase(kvs) { /* ... */ } private: + /* Helpers. */ Result GetContentIdByTypeImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional id_offset); Result GetLatestContentMetaKeyImpl(ContentMetaKey *out_key, ProgramId id); public: + /* Actual commands. */ virtual Result Set(const ContentMetaKey &key, sf::InBuffer value) override; virtual Result Get(sf::Out out_size, const ContentMetaKey &key, sf::OutBuffer out_value) override; virtual Result Remove(const ContentMetaKey &key) override; diff --git a/stratosphere/ncm/source/ncm_content_meta_database_impl_base.hpp b/stratosphere/ncm/source/ncm_content_meta_database_impl_base.hpp index 951e23f84..588f25c34 100644 --- a/stratosphere/ncm/source/ncm_content_meta_database_impl_base.hpp +++ b/stratosphere/ncm/source/ncm_content_meta_database_impl_base.hpp @@ -35,6 +35,7 @@ namespace ams::ncm { std::strcpy(this->mount_name, mount_name); } protected: + /* Helpers. */ Result EnsureEnabled() const { R_UNLESS(!this->disabled, ncm::ResultInvalidContentMetaDatabase()); return ResultSuccess(); diff --git a/stratosphere/ncm/source/ncm_content_storage_impl.cpp b/stratosphere/ncm/source/ncm_content_storage_impl.cpp index 004ac57eb..52abfe22b 100644 --- a/stratosphere/ncm/source/ncm_content_storage_impl.cpp +++ b/stratosphere/ncm/source/ncm_content_storage_impl.cpp @@ -42,9 +42,11 @@ namespace ams::ncm { } Result DeleteContentFile(ContentId id, MakeContentPathFunction func, const char *root_path) { + /* Create the content path. */ PathString path; MakeContentPath(std::addressof(path), id, func, root_path); + /* Delete the content. */ R_TRY_CATCH(fs::DeleteFile(path)) { R_CONVERT(fs::ResultPathNotFound, ncm::ResultContentNotFound()) } R_END_TRY_CATCH; @@ -56,25 +58,32 @@ namespace ams::ncm { Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) { R_UNLESS(max_level > 0, ResultSuccess()); + /* Retry traversal upon request. */ bool retry_dir_read = true; while (retry_dir_read) { retry_dir_read = false; + /* Open the directory at the given path. All entry types are allowed. */ fs::DirectoryHandle dir; R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize)); ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; while (true) { + /* Read a single directory entry. */ fs::DirectoryEntry entry; s64 entry_count; R_TRY(fs::ReadDirectory(std::addressof(entry_count), std::addressof(entry), dir, 1)); + + /* Directory has no entries to process. */ if (entry_count == 0) { break; } + /* Path of the current entry. */ PathString current_path; current_path.SetFormat("%s/%s", root_path, entry.name); + /* Call the process function. */ bool should_continue = true; bool should_retry_dir_read = false; R_TRY(f(&should_continue, &should_retry_dir_read, current_path, entry)); @@ -85,6 +94,7 @@ namespace ams::ncm { return ResultSuccess(); } + /* Mark for retry. */ if (should_retry_dir_read) { retry_dir_read = true; break; @@ -114,15 +124,19 @@ namespace ams::ncm { bool IsContentPath(const char *path) { impl::PathView view(path); + + /* Ensure nca suffix. */ if (!view.HasSuffix(".nca")) { return false; } + /* File name should be the size of a content id plus the nca file extension. */ auto file_name = view.GetFileName(); if (file_name.length() != ContentIdStringLength + 4) { return false; } + /* Ensure file name is comprised of hex characters. */ for (size_t i = 0; i < ContentIdStringLength; i++) { if (!std::isxdigit(static_cast(file_name[i]))) { return false; @@ -157,9 +171,11 @@ namespace ams::ncm { Result ContentStorageImpl::InitializeBase(const char *root_path) { PathString path; + /* Create the content directory. */ MakeBaseContentDirectoryPath(std::addressof(path), root_path); R_TRY(impl::EnsureDirectoryRecursively(path)); + /* Create the placeholder directory. */ PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); R_TRY(impl::EnsureDirectoryRecursively(path)); @@ -169,9 +185,11 @@ namespace ams::ncm { Result ContentStorageImpl::CleanupBase(const char *root_path) { PathString path; + /* Create the content directory. */ MakeBaseContentDirectoryPath(std::addressof(path), root_path); R_TRY(CleanDirectoryRecursively(path)); + /* Create the placeholder directory. */ PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); R_TRY(CleanDirectoryRecursively(path)); @@ -181,18 +199,22 @@ namespace ams::ncm { Result ContentStorageImpl::VerifyBase(const char *root_path) { PathString path; + /* Check if root directory exists. */ bool has_dir; R_TRY(impl::HasDirectory(std::addressof(has_dir), root_path)); R_UNLESS(has_dir, ncm::ResultContentStorageBaseNotFound()); + /* Check if content directory exists. */ bool has_registered; MakeBaseContentDirectoryPath(std::addressof(path), root_path); R_TRY(impl::HasDirectory(std::addressof(has_registered), path)); + /* Check if placeholder directory exists. */ bool has_placeholder; PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); R_TRY(impl::HasDirectory(std::addressof(has_placeholder), path)); + /* Convert findings to results. */ R_UNLESS(has_registered || has_placeholder, ncm::ResultContentStorageBaseNotFound()); R_UNLESS(has_registered, ncm::ResultInvalidContentStorageBase()); R_UNLESS(has_placeholder, ncm::ResultInvalidContentStorageBase()); @@ -210,11 +232,14 @@ namespace ams::ncm { Result ContentStorageImpl::OpenContentIdFile(ContentId content_id) { R_UNLESS(this->cached_content_id != content_id, ResultSuccess()); + /* Close any cached file. */ this->InvalidateFileCache(); + /* Create the content path. */ PathString path; MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path); + /* Open the content file and store to the cache. */ R_TRY_CATCH(fs::OpenFile(&this->cached_file_handle, path, fs::OpenMode_Read)) { R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultContentNotFound()) } R_END_TRY_CATCH; @@ -226,8 +251,10 @@ namespace ams::ncm { Result ContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache) { R_TRY(this->EnsureEnabled()); + /* Check paths exists for this content storage. */ R_TRY(VerifyBase(path)); + /* Initialize members. */ this->root_path = PathString(path); this->make_content_path_func = content_path_func; this->placeholder_accessor.Initialize(std::addressof(this->root_path), placeholder_path_func, delay_flush); @@ -259,9 +286,11 @@ namespace ams::ncm { Result ContentStorageImpl::HasPlaceHolder(sf::Out out, PlaceHolderId placeholder_id) { R_TRY(this->EnsureEnabled()); + /* Create the placeholder path. */ PathString placeholder_path; this->placeholder_accessor.MakePath(std::addressof(placeholder_path), placeholder_id); + /* Check if placeholder file exists. */ bool has = false; R_TRY(impl::HasFile(&has, placeholder_path)); out.SetValue(has); @@ -274,18 +303,21 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); return this->placeholder_accessor.WritePlaceHolderFile(placeholder_id, offset, data.GetPointer(), data.GetSize()); - return ResultSuccess(); } Result ContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) { this->InvalidateFileCache(); R_TRY(this->EnsureEnabled()); + /* Create the placeholder path. */ PathString placeholder_path; - PathString content_path; this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Create the content path. */ + PathString content_path; MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); + /* Move the placeholder to the content path. */ R_TRY_CATCH(fs::RenameFile(placeholder_path, content_path)) { R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists()) @@ -296,18 +328,18 @@ namespace ams::ncm { Result ContentStorageImpl::Delete(ContentId content_id) { R_TRY(this->EnsureEnabled()); - this->InvalidateFileCache(); - return DeleteContentFile(content_id, this->make_content_path_func, this->root_path); } Result ContentStorageImpl::Has(sf::Out out, ContentId content_id) { R_TRY(this->EnsureEnabled()); + /* Create the content path. */ PathString content_path; MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); + /* Check if the content file exists. */ bool has = false; R_TRY(impl::HasFile(&has, content_path)); out.SetValue(has); @@ -318,9 +350,11 @@ namespace ams::ncm { Result ContentStorageImpl::GetPath(sf::Out out, ContentId content_id) { R_TRY(this->EnsureEnabled()); + /* Create the content path. */ PathString content_path; MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); + /* Substitute our mount name for the common mount name. */ Path common_path; R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path)); @@ -331,9 +365,11 @@ namespace ams::ncm { Result ContentStorageImpl::GetPlaceHolderPath(sf::Out out, PlaceHolderId placeholder_id) { R_TRY(this->EnsureEnabled()); + /* Obtain the placeholder path. */ PathString placeholder_path; this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + /* Substitute our mount name for the common mount name. */ Path common_path; R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path)); @@ -344,31 +380,38 @@ namespace ams::ncm { Result ContentStorageImpl::CleanupAllPlaceHolder() { R_TRY(this->EnsureEnabled()); + /* Clear the cache. */ this->placeholder_accessor.InvalidateAll(); + /* Obtain the placeholder base directory path. */ PathString placeholder_dir; PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path); + /* Cleanup the placeholder base directory. */ CleanDirectoryRecursively(placeholder_dir); - return ResultSuccess(); } Result ContentStorageImpl::ListPlaceHolder(sf::Out out_count, const sf::OutArray &out_buf) { R_TRY(this->EnsureEnabled()); + /* Obtain the placeholder base directory path. */ PathString placeholder_dir; PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path); + const size_t max_entries = out_buf.GetSize(); size_t entry_count = 0; + /* Traverse the placeholder base directory finding valid placeholder files. */ R_TRY(TraverseDirectory(placeholder_dir, placeholder_accessor.GetHierarchicalDirectoryDepth(), [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result { *should_continue = true; *should_retry_dir_read = false; + /* We are only looking for files. */ if (entry.type == fs::DirectoryEntryType_File) { R_UNLESS(entry_count <= max_entries, ncm::ResultBufferInsufficient()); + /* Get the placeholder id from the filename. */ PlaceHolderId placeholder_id; R_TRY(PlaceHolderAccessor::GetPlaceHolderIdFromFileName(std::addressof(placeholder_id), entry.name)); @@ -385,15 +428,19 @@ namespace ams::ncm { Result ContentStorageImpl::GetContentCount(sf::Out out_count) { R_TRY(this->EnsureEnabled()); + /* Obtain the content base directory path. */ PathString path; MakeBaseContentDirectoryPath(std::addressof(path), this->root_path); + const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func); size_t count = 0; + /* Traverse the content base directory finding all files. */ R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result { *should_continue = true; *should_retry_dir_read = false; + /* Increment the count for each file found. */ if (entry.type == fs::DirectoryEntryType_File) { count++; } @@ -409,11 +456,14 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); + /* Obtain the content base directory path. */ PathString path; MakeBaseContentDirectoryPath(std::addressof(path), this->root_path); + const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func); size_t entry_count = 0; + /* Traverse the content base directory finding all valid content. */ R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) { *should_retry_dir_read = false; *should_continue = true; @@ -450,13 +500,16 @@ namespace ams::ncm { Result ContentStorageImpl::GetSizeFromContentId(sf::Out out_size, ContentId content_id) { R_TRY(this->EnsureEnabled()); + /* Create the content path. */ PathString content_path; MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); + /* Open the content file. */ fs::FileHandle file; R_TRY(fs::OpenFile(std::addressof(file), content_path, fs::OpenMode_Read)); ON_SCOPE_EXIT { fs::CloseFile(file); }; + /* Obtain the size of the content. */ s64 file_size; R_TRY(fs::GetFileSize(std::addressof(file_size), file)); @@ -474,16 +527,24 @@ namespace ams::ncm { Result ContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { R_TRY(this->EnsureEnabled()); + /* Close any cached file. */ this->InvalidateFileCache(); + /* Ensure the future content directory exists. */ R_TRY(EnsureContentDirectory(new_content_id, this->make_content_path_func, this->root_path)); + + /* Ensure the destination placeholder directory exists. */ R_TRY(this->placeholder_accessor.EnsurePlaceHolderDirectory(placeholder_id)); + /* Obtain the placeholder path. */ PathString placeholder_path; - PathString content_path; this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Make the old content path. */ + PathString content_path; MakeContentPath(std::addressof(content_path), old_content_id, this->make_content_path_func, this->root_path); + /* Move the content to the placeholder path. */ R_TRY_CATCH(fs::RenameFile(content_path, placeholder_path)) { R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists()) @@ -494,8 +555,7 @@ namespace ams::ncm { Result ContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) { R_TRY(this->EnsureEnabled()); - R_TRY(this->placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size)); - return ResultSuccess(); + return this->placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size); } Result ContentStorageImpl::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) { @@ -503,19 +563,23 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); + /* Create the content path. */ PathString content_path; MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); + /* Open the content file. */ R_TRY(this->OpenContentIdFile(content_id)); - R_TRY(fs::ReadFile(this->cached_file_handle, offset, buf.GetPointer(), buf.GetSize())); - - return ResultSuccess(); + /* Read from the requested offset up to the requested size. */ + return fs::ReadFile(this->cached_file_handle, offset, buf.GetPointer(), buf.GetSize()); } Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out out_rights_id, PlaceHolderId placeholder_id) { + /* Obtain the regular rights id for the placeholder id. */ ncm::RightsId rights_id; R_TRY(this->GetRightsIdFromPlaceHolderId(&rights_id, placeholder_id)); + + /* Output the fs rights id. */ out_rights_id.SetValue(rights_id.id); return ResultSuccess(); } @@ -523,15 +587,20 @@ namespace ams::ncm { Result ContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out out_rights_id, PlaceHolderId placeholder_id) { R_TRY(this->EnsureEnabled()); + /* Get the placeholder path. */ Path path; R_TRY(this->GetPlaceHolderPath(std::addressof(path), placeholder_id)); + /* Get the rights id for the placeholder id. */ return GetRightsId(out_rights_id.GetPointer(), path); } Result ContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out out_rights_id, ContentId content_id) { + /* Obtain the regular rights id for the content id. */ ncm::RightsId rights_id; R_TRY(this->GetRightsIdFromContentId(&rights_id, content_id)); + + /* Output the fs rights id. */ out_rights_id.SetValue(rights_id.id); return ResultSuccess(); } @@ -539,15 +608,20 @@ namespace ams::ncm { Result ContentStorageImpl::GetRightsIdFromContentId(sf::Out out_rights_id, ContentId content_id) { R_TRY(this->EnsureEnabled()); + /* Attempt to obtain the rights id from the cache. */ if (this->rights_id_cache->Find(out_rights_id.GetPointer(), content_id)) { return ResultSuccess(); } + /* Get the path of the content. */ Path path; R_TRY(this->GetPath(std::addressof(path), content_id)); + /* Obtain the rights id for the content. */ ncm::RightsId rights_id; R_TRY(GetRightsId(std::addressof(rights_id), path)); + + /* Store the rights id to the cache. */ this->rights_id_cache->Store(content_id, rights_id); out_rights_id.SetValue(rights_id); @@ -559,17 +633,22 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); + /* This command is for development hardware only. */ AMS_ABORT_UNLESS(spl::IsDevelopmentHardware()); + /* Close any cached file. */ this->InvalidateFileCache(); + /* Make the content path. */ PathString path; MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path); + /* Open the content file. */ fs::FileHandle file; R_TRY(fs::OpenFile(std::addressof(file), path.Get(), fs::OpenMode_Write)); ON_SCOPE_EXIT { fs::CloseFile(file); }; + /* Write the provided data to the file. */ R_TRY(fs::WriteFile(file, offset, data.GetPointer(), data.GetSize(), fs::WriteOption::Flush)); return ResultSuccess(); @@ -597,21 +676,27 @@ namespace ams::ncm { Result ContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out out_size, PlaceHolderId placeholder_id) { R_TRY(this->EnsureEnabled()); + /* Attempt to get the placeholder file size. */ bool found = false; s64 file_size = 0; R_TRY(this->placeholder_accessor.TryGetPlaceHolderFileSize(std::addressof(found), std::addressof(file_size), placeholder_id)); + + /* Set the output if placeholder file is found. */ if (found) { out_size.SetValue(file_size); return ResultSuccess(); } + /* Get the path of the placeholder. */ PathString placeholder_path; this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + /* Open the placeholder file. */ fs::FileHandle file; R_TRY(fs::OpenFile(std::addressof(file), placeholder_path, fs::OpenMode_Read)); ON_SCOPE_EXIT { fs::CloseFile(file); }; + /* Get the size of the placeholder file. */ R_TRY(fs::GetFileSize(std::addressof(file_size), file)); out_size.SetValue(file_size); @@ -623,6 +708,7 @@ namespace ams::ncm { using PathChecker = bool (*)(const char *); PathChecker path_checker = nullptr; + /* Set the archive bit appropriately for content/placeholders. */ auto fix_file_attributes = [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) { *should_retry_dir_read = false; *should_continue = true; @@ -638,7 +724,7 @@ namespace ams::ncm { return ResultSuccess(); }; - /* Fix Content */ + /* Fix content. */ { path_checker = IsContentPath; PathString path; @@ -647,7 +733,7 @@ namespace ams::ncm { R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes)); } - /* Fix placeholder. */ + /* Fix placeholders. */ this->placeholder_accessor.InvalidateAll(); { path_checker = IsPlaceHolderPath; @@ -663,16 +749,20 @@ namespace ams::ncm { Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) { R_TRY(this->EnsureEnabled()); + /* Attempt to find the rights id in the cache. */ if (this->rights_id_cache->Find(out_rights_id.GetPointer(), cache_content_id)) { return ResultSuccess(); } + /* Get the placeholder path. */ PathString placeholder_path; this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + /* Substitute mount name with the common mount name. */ Path common_path; R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path)); + /* Get the rights id. */ ncm::RightsId rights_id; R_TRY(GetRightsId(&rights_id, common_path)); this->rights_id_cache->Store(cache_content_id, rights_id); diff --git a/stratosphere/ncm/source/ncm_content_storage_impl.hpp b/stratosphere/ncm/source/ncm_content_storage_impl.hpp index 582d7807d..c4e76f141 100644 --- a/stratosphere/ncm/source/ncm_content_storage_impl.hpp +++ b/stratosphere/ncm/source/ncm_content_storage_impl.hpp @@ -40,9 +40,11 @@ namespace ams::ncm { Result Initialize(const char *root_path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache); private: + /* Helpers. */ Result OpenContentIdFile(ContentId content_id); void InvalidateFileCache(); public: + /* Actual commands. */ virtual Result GeneratePlaceHolderId(sf::Out out) override; virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override; virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override; diff --git a/stratosphere/ncm/source/ncm_content_storage_impl_base.hpp b/stratosphere/ncm/source/ncm_content_storage_impl_base.hpp index 4e92ecd55..f28c6ec83 100644 --- a/stratosphere/ncm/source/ncm_content_storage_impl_base.hpp +++ b/stratosphere/ncm/source/ncm_content_storage_impl_base.hpp @@ -29,6 +29,7 @@ namespace ams::ncm { protected: ContentStorageImplBase() { /* ... */ } protected: + /* Helpers. */ Result EnsureEnabled() const { R_UNLESS(!this->disabled, ncm::ResultInvalidContentStorage()); return ResultSuccess(); diff --git a/stratosphere/ncm/source/ncm_main.cpp b/stratosphere/ncm/source/ncm_main.cpp index 5b6d69e8a..d3f67b13f 100644 --- a/stratosphere/ncm/source/ncm_main.cpp +++ b/stratosphere/ncm/source/ncm_main.cpp @@ -193,17 +193,22 @@ namespace { int main(int argc, char **argv) { + /* Create and initialize the content manager. */ auto content_manager = GetSharedPointerToContentManager(); R_ABORT_UNLESS(content_manager->Initialize()); + /* Initialize ncm's server and start threads. */ R_ABORT_UNLESS(g_ncm_server_manager.Initialize(content_manager)); R_ABORT_UNLESS(g_ncm_server_manager.StartThreads()); + /* Initialize ncm api. */ ncm::InitializeWithObject(content_manager); + /* Initialize lr's server and start threads. */ R_ABORT_UNLESS(g_lr_server_manager.Initialize()); R_ABORT_UNLESS(g_lr_server_manager.StartThreads()); + /* Wait indefinitely. */ g_ncm_server_manager.Wait(); g_lr_server_manager.Wait(); diff --git a/stratosphere/ncm/source/ncm_make_path.cpp b/stratosphere/ncm/source/ncm_make_path.cpp index fb5658006..d5d499f48 100644 --- a/stratosphere/ncm/source/ncm_make_path.cpp +++ b/stratosphere/ncm/source/ncm_make_path.cpp @@ -28,10 +28,14 @@ namespace ams::ncm { void MakePlaceHolderName(PathString *out, PlaceHolderId id) { auto &bytes = id.uuid.data; char tmp[3]; + + /* Create a hex string from bytes. */ for (size_t i = 0; i < sizeof(bytes); i++) { std::snprintf(tmp, util::size(tmp), "%02x", bytes[i]); out->Append(tmp); } + + /* Append file extension. */ out->Append(".nca"); } @@ -56,34 +60,49 @@ namespace ams::ncm { } void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path) { + /* Create the content name from the content id. */ PathString content_name; MakeContentName(std::addressof(content_name), content_id); + + /* Format the output path. */ out->SetFormat("%s/%s", root_path, content_name.Get()); } void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path) { + /* Hash the content id. */ const u16 hash = Get16BitSha256HashPrefix(content_id); const u32 hash_upper = (hash >> 10) & 0x3f; const u32 hash_lower = (hash >> 4) & 0x3f; + /* Create the content name from the content id. */ PathString content_name; MakeContentName(std::addressof(content_name), content_id); + + /* Format the output path. */ out->SetFormat("%s/%08X/%08X/%s", root_path, hash_upper, hash_lower, content_name.Get()); } void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path) { + /* Hash the content id. */ const u32 hash = (Get16BitSha256HashPrefix(content_id) >> 6) & 0x3FF; + /* Create the content name from the content id. */ PathString content_name; MakeContentName(std::addressof(content_name), content_id); + + /* Format the output path. */ out->SetFormat("%s/%08X/%s", root_path, hash, content_name.Get()); } void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path) { + /* Hash the content id. */ const u32 hash_byte = static_cast(Get8BitSha256HashPrefix(content_id)); + /* Create the content name from the content id. */ PathString content_name; MakeContentName(std::addressof(content_name), content_id); + + /* Format the output path. */ out->SetFormat("%s/%08X/%s", root_path, hash_byte, content_name.Get()); } @@ -101,16 +120,23 @@ namespace ams::ncm { } void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path) { + /* Create the placeholder name from the placeholder id. */ PathString placeholder_name; MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id); + + /* Format the output path. */ out->SetFormat("%s/%s", root_path, placeholder_name.Get()); } void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_path) { + /* Hash the placeholder id. */ const u32 hash_byte = static_cast(Get8BitSha256HashPrefix(placeholder_id)); + /* Create the placeholder name from the placeholder id. */ PathString placeholder_name; MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id); + + /* Format the output path. */ out->SetFormat("%s/%08X/%s", root_path, hash_byte, placeholder_name.Get()); } diff --git a/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.cpp b/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.cpp index 0bbe14885..2c48a7641 100644 --- a/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.cpp +++ b/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.cpp @@ -21,10 +21,11 @@ namespace ams::ncm { Result OnMemoryContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out out_key, ProgramId program_id) { R_TRY(this->EnsureEnabled()); - const ContentMetaKey key = ContentMetaKey::Make(program_id, 0, ContentMetaType::Unknown); - std::optional found_key; - for (auto entry = this->kvs->lower_bound(key); entry != this->kvs->end(); entry++) { + + /* Find the last key with the desired program id. */ + for (auto entry = this->kvs->lower_bound(ContentMetaKey::Make(program_id, 0, ContentMetaType::Unknown)); entry != this->kvs->end(); entry++) { + /* No further entries will match the program id, discontinue. */ if (entry->GetKey().id != program_id) { break; } diff --git a/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.hpp b/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.hpp index c7c1dc1df..2635da783 100644 --- a/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.hpp +++ b/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.hpp @@ -25,6 +25,7 @@ namespace ams::ncm { public: OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore *kvs) : ContentMetaDatabaseImpl(kvs) { /* ... */ } public: + /* Actual commands. */ virtual Result GetLatestContentMetaKey(sf::Out out_key, ProgramId id) override; virtual Result LookupOrphanContent(const sf::OutArray &out_orphaned, const sf::InArray &content_ids) override; virtual Result Commit() override; diff --git a/stratosphere/ncm/source/ncm_placeholder_accessor.cpp b/stratosphere/ncm/source/ncm_placeholder_accessor.cpp index 40aa9811c..70fde768b 100644 --- a/stratosphere/ncm/source/ncm_placeholder_accessor.cpp +++ b/stratosphere/ncm/source/ncm_placeholder_accessor.cpp @@ -63,9 +63,11 @@ namespace ams::ncm { } Result PlaceHolderAccessor::GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name) { + /* Ensure placeholder name is valid. */ R_UNLESS(strnlen(name, PlaceHolderFileNameLength) == PlaceHolderFileNameLength, ncm::ResultInvalidPlaceHolderFile()); R_UNLESS(strncmp(name + PlaceHolderFileNameLengthWithoutExtension, PlaceHolderExtension, PlaceHolderExtensionLength) == 0, ncm::ResultInvalidPlaceHolderFile()); + /* Convert each character pair to a byte until we reach the end. */ PlaceHolderId placeholder_id = {}; for (size_t i = 0; i < sizeof(placeholder_id); i++) { char tmp[3]; @@ -84,20 +86,24 @@ namespace ams::ncm { /* Try to load from the cache. */ R_UNLESS(!this->LoadFromCache(out_handle, placeholder_id), ResultSuccess()); + /* Make the path of the placeholder. */ PathString placeholder_path; this->MakePath(std::addressof(placeholder_path), placeholder_id); + /* Open the placeholder file. */ return fs::OpenFile(out_handle, placeholder_path, fs::OpenMode_Write); } bool PlaceHolderAccessor::LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) { std::scoped_lock lk(this->cache_mutex); + /* attempt to find an entry in the cache. */ CacheEntry *entry = this->FindInCache(placeholder_id); if (!entry) { return false; } + /* No cached entry found. */ entry->id = InvalidPlaceHolderId; *out_handle = entry->handle; return true; @@ -106,6 +112,7 @@ namespace ams::ncm { void PlaceHolderAccessor::StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle) { std::scoped_lock lk(this->cache_mutex); + /* Store placeholder id and file handle to a free entry. */ CacheEntry *entry = this->GetFreeEntry(); entry->id = placeholder_id; entry->handle = handle; @@ -113,6 +120,7 @@ namespace ams::ncm { } void PlaceHolderAccessor::Invalidate(CacheEntry *entry) { + /* Flush and close the cached entry's file. */ if (entry != nullptr) { fs::FlushFile(entry->handle); fs::CloseFile(entry->handle); @@ -121,10 +129,12 @@ namespace ams::ncm { } PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) { + /* Ensure placeholder id is valid. */ if (placeholder_id == InvalidPlaceHolderId) { return nullptr; } + /* Attempt to find a cache entry with the same placeholder id. */ for (size_t i = 0; i < MaxCacheEntries; i++) { if (placeholder_id == this->caches[i].id) { return &this->caches[i]; @@ -161,11 +171,14 @@ namespace ams::ncm { } Result PlaceHolderAccessor::CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size) { + /* Ensure the destination directory exists. */ R_TRY(this->EnsurePlaceHolderDirectory(placeholder_id)); + /* Get the placeholder path. */ PathString placeholder_path; this->GetPath(std::addressof(placeholder_path), placeholder_id); + /* Create the placeholder file. */ R_TRY_CATCH(fs::CreateFile(placeholder_path, size, fs::CreateOption_BigFile)) { R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists()) } R_END_TRY_CATCH; @@ -174,9 +187,11 @@ namespace ams::ncm { } Result PlaceHolderAccessor::DeletePlaceHolderFile(PlaceHolderId placeholder_id) { + /* Get the placeholder path. */ PathString placeholder_path; this->GetPath(std::addressof(placeholder_path), placeholder_id); + /* Delete the placeholder file. */ R_TRY_CATCH(fs::DeleteFile(placeholder_path)) { R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) } R_END_TRY_CATCH; @@ -185,32 +200,40 @@ namespace ams::ncm { } Result PlaceHolderAccessor::WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size) { + /* Open the placeholder file. */ fs::FileHandle file; R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) { R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) } R_END_TRY_CATCH; + + /* Store opened files to the cache regardless of write failures. */ ON_SCOPE_EXIT { this->StoreToCache(placeholder_id, file); }; - R_TRY(fs::WriteFile(file, offset, buffer, size, this->delay_flush ? fs::WriteOption::Flush : fs::WriteOption::None)); - return ResultSuccess(); + /* Write data to the placeholder file. */ + return fs::WriteFile(file, offset, buffer, size, this->delay_flush ? fs::WriteOption::Flush : fs::WriteOption::None); } Result PlaceHolderAccessor::SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size) { + /* Open the placeholder file. */ fs::FileHandle file; R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) { R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) } R_END_TRY_CATCH; + + /* Close the file on exit. */ ON_SCOPE_EXIT { fs::CloseFile(file); }; - R_TRY(fs::SetFileSize(file, size)); - - return ResultSuccess(); + /* Set the size of the placeholder file. */ + return fs::SetFileSize(file, size); } Result PlaceHolderAccessor::TryGetPlaceHolderFileSize(bool *found_in_cache, s64 *out_size, PlaceHolderId placeholder_id) { + /* Attempt to find the placeholder in the cache. */ fs::FileHandle handle; auto found = this->LoadFromCache(std::addressof(handle), placeholder_id); + if (found) { + /* Renew the entry in the cache. */ this->StoreToCache(placeholder_id, handle); R_TRY(fs::GetFileSize(out_size, handle)); *found_in_cache = true; @@ -222,6 +245,7 @@ namespace ams::ncm { } void PlaceHolderAccessor::InvalidateAll() { + /* Invalidate all cache entries. */ for (auto &entry : this->caches) { if (entry.id != InvalidPlaceHolderId) { this->Invalidate(std::addressof(entry)); diff --git a/stratosphere/ncm/source/ncm_placeholder_accessor.hpp b/stratosphere/ncm/source/ncm_placeholder_accessor.hpp index d85508edd..be4a1d44f 100644 --- a/stratosphere/ncm/source/ncm_placeholder_accessor.hpp +++ b/stratosphere/ncm/source/ncm_placeholder_accessor.hpp @@ -59,6 +59,7 @@ namespace ams::ncm { static void MakeBaseDirectoryPath(PathString *out, const char *root_path); static Result GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name); public: + /* API. */ void Initialize(PathString *root, MakePlaceHolderPathFunction path_func, bool delay_flush) { this->root_path = root; this->make_placeholder_path_func = path_func; diff --git a/stratosphere/ncm/source/ncm_read_only_content_storage_impl.cpp b/stratosphere/ncm/source/ncm_read_only_content_storage_impl.cpp index 1aea23053..adca31783 100644 --- a/stratosphere/ncm/source/ncm_read_only_content_storage_impl.cpp +++ b/stratosphere/ncm/source/ncm_read_only_content_storage_impl.cpp @@ -26,8 +26,11 @@ namespace ams::ncm { } void MakeGameCardContentMetaPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + /* Determine the content path. */ PathString path; func(std::addressof(path), id, root_path); + + /* Substitute the .nca extension with .cmnt.nca. */ *out = path.GetSubstring(0, path.GetLength() - 4); out->Append(".cnmt.nca"); } @@ -36,6 +39,8 @@ namespace ams::ncm { PathString path; MakeContentPath(std::addressof(path), id, func, root_path); + /* Open the content file. */ + /* If absent, make the path for game card content meta and open again. */ R_TRY_CATCH(fs::OpenFile(out, path, fs::OpenMode_Read)) { R_CATCH(fs::ResultPathNotFound) { MakeGameCardContentMetaPath(std::addressof(path), id, func, root_path); @@ -50,7 +55,6 @@ namespace ams::ncm { Result ReadOnlyContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func) { R_TRY(this->EnsureEnabled()); - this->root_path.Set(path); this->make_content_path_func = content_path_func; return ResultSuccess(); @@ -87,12 +91,15 @@ namespace ams::ncm { Result ReadOnlyContentStorageImpl::Has(sf::Out out, ContentId content_id) { R_TRY(this->EnsureEnabled()); + /* Make the content path. */ PathString content_path; MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); + /* Check if the file exists. */ bool has; R_TRY(impl::HasFile(std::addressof(has), content_path)); + /* If the file is absent, make the path for game card content meta and check presence again. */ if (!has) { MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); R_TRY(impl::HasFile(std::addressof(has), content_path)); @@ -105,19 +112,24 @@ namespace ams::ncm { Result ReadOnlyContentStorageImpl::GetPath(sf::Out out, ContentId content_id) { R_TRY(this->EnsureEnabled()); + /* Make the path for game card content meta. */ PathString content_path; MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); + /* Check if the file exists. */ bool has_file; R_TRY(impl::HasFile(std::addressof(has_file), content_path)); + + /* If the file is absent, make the path for regular content. */ if (!has_file) { MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); } + /* Substitute mount name with the common mount name. */ Path common_path; R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path)); + out.SetValue(common_path); - return ResultSuccess(); } @@ -144,10 +156,12 @@ namespace ams::ncm { Result ReadOnlyContentStorageImpl::GetSizeFromContentId(sf::Out out_size, ContentId content_id) { R_TRY(this->EnsureEnabled()); + /* Open the file for the content id. */ fs::FileHandle file; R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, this->make_content_path_func, this->root_path)); ON_SCOPE_EXIT { fs::CloseFile(file); }; + /* Determine the file size. */ s64 file_size; R_TRY(fs::GetFileSize(std::addressof(file_size), file)); @@ -173,10 +187,12 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); + /* Open the file for the content id. */ fs::FileHandle file; R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, this->make_content_path_func, this->root_path)); ON_SCOPE_EXIT { fs::CloseFile(file); }; + /* Read from the given offset up to the given size. */ R_TRY(fs::ReadFile(file, offset, buf.GetPointer(), buf.GetSize())); return ResultSuccess(); @@ -191,8 +207,11 @@ namespace ams::ncm { } Result ReadOnlyContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out out_rights_id, ContentId content_id) { + /* Obtain the regular rights id for the content id. */ ncm::RightsId rights_id; R_TRY(this->GetRightsIdFromContentId(&rights_id, content_id)); + + /* Output the fs rights id. */ out_rights_id.SetValue(rights_id.id); return ResultSuccess(); } @@ -200,9 +219,11 @@ namespace ams::ncm { Result ReadOnlyContentStorageImpl::GetRightsIdFromContentId(sf::Out out_rights_id, ContentId content_id) { R_TRY(this->EnsureEnabled()); + /* Get the content path. */ Path path; R_TRY(this->GetPath(std::addressof(path), content_id)); + /* Get the rights id. */ ncm::RightsId rights_id; R_TRY(GetRightsId(&rights_id, path)); out_rights_id.SetValue(rights_id); diff --git a/stratosphere/ncm/source/ncm_read_only_content_storage_impl.hpp b/stratosphere/ncm/source/ncm_read_only_content_storage_impl.hpp index a8579f1be..4d4ce7bb7 100644 --- a/stratosphere/ncm/source/ncm_read_only_content_storage_impl.hpp +++ b/stratosphere/ncm/source/ncm_read_only_content_storage_impl.hpp @@ -25,6 +25,7 @@ namespace ams::ncm { public: Result Initialize(const char *root_path, MakeContentPathFunction content_path_func); public: + /* Actual commands. */ virtual Result GeneratePlaceHolderId(sf::Out out) override; virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override; virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;