From f4af4f8be02c7ad9d69012ded9e3dccf43ab646f Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 4 Mar 2020 18:39:52 -0800 Subject: [PATCH] ncm: use fs bindings, other refactoring --- .../include/stratosphere/fs.hpp | 1 + .../fs/fs_save_data_management.hpp | 3 + .../fs/fs_save_data_transaction.hpp | 24 + .../include/stratosphere/ncm/ncm_path.hpp | 4 +- .../include/stratosphere/ncm/ncm_types.hpp | 4 + .../include/stratosphere/os/os_condvar.hpp | 4 +- .../include/stratosphere/os/os_mutex.hpp | 12 +- .../source/fs/fs_save_data_management.cpp | 9 + .../source/fs/fsa/fs_mount_table.hpp | 2 +- .../source/fs/fsa/fs_mount_utils.cpp | 2 +- .../source/fs/fsa/fs_user_filesystem.cpp | 19 + .../include/vapours/results/ncm_results.hpp | 2 +- stratosphere/boot2/source/boot2_main.cpp | 8 +- .../source/impl/ncm_placeholder_accessor.cpp | 216 ------- .../source/impl/ncm_placeholder_accessor.hpp | 78 --- .../ncm/source/ncm_content_id_utils.cpp | 72 +++ ...ncm_utils.hpp => ncm_content_id_utils.hpp} | 19 +- .../ncm/source/ncm_content_manager_impl.cpp | 422 +++++++------ .../ncm/source/ncm_content_manager_impl.hpp | 162 +---- .../source/ncm_content_meta_database_impl.cpp | 2 +- .../ncm/source/ncm_content_storage_impl.cpp | 552 +++++++++++------- .../ncm/source/ncm_content_storage_impl.hpp | 34 +- .../source/ncm_content_storage_impl_base.hpp | 8 +- stratosphere/ncm/source/ncm_fs.cpp | 323 ---------- stratosphere/ncm/source/ncm_fs.hpp | 108 ---- stratosphere/ncm/source/ncm_fs_utils.cpp | 175 ++++++ .../{ncm_path_utils.hpp => ncm_fs_utils.hpp} | 30 +- stratosphere/ncm/source/ncm_make_path.cpp | 117 ++-- stratosphere/ncm/source/ncm_make_path.hpp | 18 +- stratosphere/ncm/source/ncm_path_utils.cpp | 76 --- .../ncm/source/ncm_placeholder_accessor.cpp | 232 ++++++++ .../ncm/source/ncm_placeholder_accessor.hpp | 83 +++ .../ncm_read_only_content_storage_impl.cpp | 158 ++--- .../ncm_read_only_content_storage_impl.hpp | 2 +- .../source/{impl => }/ncm_rights_cache.hpp | 2 +- stratosphere/ncm/source/ncm_utils.cpp | 83 --- 36 files changed, 1470 insertions(+), 1596 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/fs/fs_save_data_transaction.hpp delete mode 100644 stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp delete mode 100644 stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp create mode 100644 stratosphere/ncm/source/ncm_content_id_utils.cpp rename stratosphere/ncm/source/{ncm_utils.hpp => ncm_content_id_utils.hpp} (60%) delete mode 100644 stratosphere/ncm/source/ncm_fs.cpp delete mode 100644 stratosphere/ncm/source/ncm_fs.hpp create mode 100644 stratosphere/ncm/source/ncm_fs_utils.cpp rename stratosphere/ncm/source/{ncm_path_utils.hpp => ncm_fs_utils.hpp} (57%) delete mode 100644 stratosphere/ncm/source/ncm_path_utils.cpp create mode 100644 stratosphere/ncm/source/ncm_placeholder_accessor.cpp create mode 100644 stratosphere/ncm/source/ncm_placeholder_accessor.hpp rename stratosphere/ncm/source/{impl => }/ncm_rights_cache.hpp (99%) delete mode 100644 stratosphere/ncm/source/ncm_utils.cpp diff --git a/libraries/libstratosphere/include/stratosphere/fs.hpp b/libraries/libstratosphere/include/stratosphere/fs.hpp index fcbb5aac2..be40bb263 100644 --- a/libraries/libstratosphere/include/stratosphere/fs.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs.hpp @@ -34,4 +34,5 @@ #include "fs/fs_sd_card.hpp" #include "fs/fs_save_data_types.hpp" #include "fs/fs_save_data_management.hpp" +#include "fs/fs_save_data_transaction.hpp" #include "fs/fs_system_save_data.hpp" diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_management.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_management.hpp index 9b1eb374f..a6dbbd8ce 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_management.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_management.hpp @@ -19,6 +19,9 @@ namespace ams::fs { + Result DeleteSaveData(SaveDataId id); + Result DeleteSaveData(SaveDataSpaceId space_id, SaveDataId id); + Result GetSaveDataFlags(u32 *out, SaveDataId id); Result GetSaveDataFlags(u32 *out, SaveDataSpaceId space_id, SaveDataId id); Result SetSaveDataFlags(SaveDataId id, SaveDataSpaceId space_id, u32 flags); diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_transaction.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_transaction.hpp new file mode 100644 index 000000000..a39762441 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_transaction.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" +#include "fs_save_data_types.hpp" + +namespace ams::fs { + + Result CommitSaveData(const char *path); + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_path.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_path.hpp index a47080245..4c4d697a9 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_path.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_path.hpp @@ -39,7 +39,7 @@ namespace ams::ncm { using PathString = kvdb::BoundedString; - using MakeContentPathFunc = void (*)(PathString *out, ContentId content_id, const PathString &root); - using MakePlaceHolderPathFunc = void (*)(PathString *out, PlaceHolderId placeholder_id, const PathString &root); + using MakeContentPathFunction = void (*)(PathString *out, ContentId content_id, const char *root_path); + using MakePlaceHolderPathFunction = void (*)(PathString *out, PlaceHolderId placeholder_id,const char *root_path); } diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp index 167764a13..0cd3f52db 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp @@ -154,6 +154,10 @@ namespace ams::ncm { #undef DEFINE_ENUM_MEMBER }; + constexpr inline bool IsUniqueStorage(StorageId id) { + return id != StorageId::None && id != StorageId::Any; + } + /* Program IDs (Formerly: Title IDs). */ struct ProgramId { svc::ProgramId value; diff --git a/libraries/libstratosphere/include/stratosphere/os/os_condvar.hpp b/libraries/libstratosphere/include/stratosphere/os/os_condvar.hpp index bf1d53a7e..d2b05bd96 100644 --- a/libraries/libstratosphere/include/stratosphere/os/os_condvar.hpp +++ b/libraries/libstratosphere/include/stratosphere/os/os_condvar.hpp @@ -30,9 +30,7 @@ namespace ams::os { private: CondVar cv; public: - ConditionVariable() { - condvarInit(&cv); - } + constexpr ConditionVariable() : cv() { /* ... */ } ConditionVariableStatus TimedWait(::Mutex *m, u64 timeout) { if (timeout > 0) { diff --git a/libraries/libstratosphere/include/stratosphere/os/os_mutex.hpp b/libraries/libstratosphere/include/stratosphere/os/os_mutex.hpp index a1f3db374..5fdec6d43 100644 --- a/libraries/libstratosphere/include/stratosphere/os/os_mutex.hpp +++ b/libraries/libstratosphere/include/stratosphere/os/os_mutex.hpp @@ -28,13 +28,11 @@ namespace ams::os { private: ::Mutex m; private: - ::Mutex *GetMutex() { + constexpr ::Mutex *GetMutex() { return &this->m; } public: - Mutex() { - mutexInit(GetMutex()); - } + constexpr Mutex() : m() { /* ... */ } void lock() { mutexLock(GetMutex()); @@ -65,13 +63,11 @@ namespace ams::os { private: ::RMutex m; private: - ::RMutex *GetMutex() { + constexpr ::RMutex *GetMutex() { return &this->m; } public: - RecursiveMutex() { - rmutexInit(GetMutex()); - } + constexpr RecursiveMutex() : m() { /* ... */ } void lock() { rmutexLock(GetMutex()); diff --git a/libraries/libstratosphere/source/fs/fs_save_data_management.cpp b/libraries/libstratosphere/source/fs/fs_save_data_management.cpp index aa6216ebc..6185029fe 100644 --- a/libraries/libstratosphere/source/fs/fs_save_data_management.cpp +++ b/libraries/libstratosphere/source/fs/fs_save_data_management.cpp @@ -76,6 +76,15 @@ namespace ams::fs { return CreateSystemSaveData(SaveDataSpaceId::System, save_id, user_id, owner_id, size, journal_size, flags); } + Result DeleteSaveData(SaveDataId id) { + /* TODO: Libnx binding for DeleteSaveDataFileSystem */ + AMS_ABORT(); + } + + Result DeleteSaveData(SaveDataSpaceId space_id, SaveDataId id) { + return fsDeleteSaveDataFileSystemBySaveDataSpaceId(static_cast<::FsSaveDataSpaceId>(space_id), id); + } + Result DeleteSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id) { const auto attribute = SaveDataAttribute::Make(ncm::InvalidProgramId, SaveDataType::System, user_id, id); diff --git a/libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp b/libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp index 2feac310e..23145d840 100644 --- a/libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp +++ b/libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp @@ -28,7 +28,7 @@ namespace ams::fs::impl { FileSystemList fs_list; os::Mutex mutex; public: - MountTable() : fs_list(), mutex() { /* ... */ } + constexpr MountTable() : fs_list(), mutex() { /* ... */ } private: bool CanAcceptMountName(const char *name); public: diff --git a/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp b/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp index ea736a9b6..d75fff734 100644 --- a/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp +++ b/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp @@ -103,7 +103,7 @@ namespace ams::fs::impl { } bool IsReservedMountName(const char *name) { - return name[0] != ReservedMountNamePrefixCharacter; + return name[0] == ReservedMountNamePrefixCharacter; } Result CheckMountName(const char *name) { diff --git a/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp b/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp index 66c31c545..4022aea4f 100644 --- a/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp +++ b/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp @@ -166,5 +166,24 @@ namespace ams::fs { return ResultSuccess(); } + namespace { + + Result CommitImpl(const char *path) { + impl::FileSystemAccessor *accessor; + R_TRY(impl::FindFileSystem(std::addressof(accessor), path)); + + return accessor->Commit(); + } + + } + + Result Commit(const char *path) { + return CommitImpl(path); + } + + Result CommitSaveData(const char *path) { + return CommitImpl(path); + } + } diff --git a/libraries/libvapours/include/vapours/results/ncm_results.hpp b/libraries/libvapours/include/vapours/results/ncm_results.hpp index 08175b37c..58d2aef84 100644 --- a/libraries/libvapours/include/vapours/results/ncm_results.hpp +++ b/libraries/libvapours/include/vapours/results/ncm_results.hpp @@ -35,7 +35,7 @@ namespace ams::ncm { R_DEFINE_ERROR_RESULT(InvalidPlaceHolderFile, 170); R_DEFINE_ERROR_RESULT(BufferInsufficient, 180); - R_DEFINE_ERROR_RESULT(InvalidContentStorageOperation, 190); + R_DEFINE_ERROR_RESULT(WriteToReadOnlyContentStorage, 190); R_DEFINE_ERROR_RESULT(InvalidContentMetaKey, 240); R_DEFINE_ERROR_RESULT(ContentStorageBaseNotFound, 310); diff --git a/stratosphere/boot2/source/boot2_main.cpp b/stratosphere/boot2/source/boot2_main.cpp index 992e33249..390b87929 100644 --- a/stratosphere/boot2/source/boot2_main.cpp +++ b/stratosphere/boot2/source/boot2_main.cpp @@ -79,10 +79,14 @@ void __appInit(void) { R_ABORT_UNLESS(gpioInitialize()); }); + /* Mount the SD card. */ + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + ams::CheckApiVersion(); } void __appExit(void) { + fs::Unmount("sdmc"); gpioExit(); setsysExit(); pmshellExit(); @@ -93,10 +97,6 @@ void __appExit(void) { int main(int argc, char **argv) { - /* Mount the SD card. */ - R_ABORT_UNLESS(fs::MountSdCard("sdmc")); - ON_SCOPE_EXIT { fs::Unmount("sdmc"); }; - /* Launch all programs off of SYSTEM/the SD. */ boot2::LaunchPostSdCardBootPrograms(); } diff --git a/stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp b/stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp deleted file mode 100644 index 5be23b3ef..000000000 --- a/stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * 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 . - */ - -#include "ncm_placeholder_accessor.hpp" -#include "../ncm_fs.hpp" -#include "../ncm_utils.hpp" -#include "../ncm_make_path.hpp" -#include "../ncm_path_utils.hpp" - -namespace ams::ncm::impl { - - namespace { - - ALWAYS_INLINE Result ConvertNotFoundResult(Result r) { - R_TRY_CATCH(r) { - R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) - } R_END_TRY_CATCH; - return ResultSuccess(); - } - - } - - Result PlaceHolderAccessor::Open(FILE** out_handle, PlaceHolderId placeholder_id) { - R_UNLESS(!this->LoadFromCache(out_handle, placeholder_id), ResultSuccess()); - PathString placeholder_path; - this->MakePath(std::addressof(placeholder_path), placeholder_id); - - FILE *f = nullptr; - R_TRY(fs::OpenFile(&f, placeholder_path, FsOpenMode_Write)); - - *out_handle = f; - return ResultSuccess(); - } - - bool PlaceHolderAccessor::LoadFromCache(FILE** out_handle, PlaceHolderId placeholder_id) { - std::scoped_lock lk(this->cache_mutex); - CacheEntry *entry = this->FindInCache(placeholder_id); - if (!entry) { - return false; - } - *out_handle = entry->handle; - entry->id = InvalidPlaceHolderId; - entry->handle = nullptr; - return true; - } - - PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) { - for (size_t i = 0; i < MaxCaches; i++) { - if (placeholder_id == this->caches[i].id) { - return &this->caches[i]; - } - } - return nullptr; - } - - PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::GetFreeEntry() { - /* Try to find an already free entry. */ - if (CacheEntry *entry = this->FindInCache(InvalidPlaceHolderId); entry != nullptr) { - return entry; - } - - /* Get the oldest entry. */ - CacheEntry *entry = &this->caches[0]; - for (size_t i = 1; i < MaxCaches; i++) { - if (entry->counter > this->caches[i].counter) { - entry = &this->caches[i]; - } - } - this->Invalidate(entry); - return entry; - } - - void PlaceHolderAccessor::StoreToCache(FILE *handle, PlaceHolderId placeholder_id) { - std::scoped_lock lk(this->cache_mutex); - CacheEntry *entry = this->GetFreeEntry(); - entry->id = placeholder_id; - entry->handle = handle; - entry->counter = this->cur_counter++; - } - - void PlaceHolderAccessor::Invalidate(CacheEntry *entry) { - if (entry != nullptr) { - if (entry->handle != nullptr) { - fflush(entry->handle); - fclose(entry->handle); - entry->handle = nullptr; - } - entry->id = InvalidPlaceHolderId; - } - } - - void PlaceHolderAccessor::Initialize(const char *root, MakePlaceHolderPathFunc path_func, bool delay_flush) { - this->root_path = PathString(root); - this->make_placeholder_path_func = path_func; - this->delay_flush = delay_flush; - } - - unsigned int PlaceHolderAccessor::GetDirectoryDepth() { - if (this->make_placeholder_path_func == static_cast(path::MakePlaceHolderPathFlat)) { - return 1; - } else if (this->make_placeholder_path_func == static_cast(path::MakePlaceHolderPathHashByteLayered)) { - return 2; - } else { - AMS_ABORT(); - } - - __builtin_unreachable(); - } - - void PlaceHolderAccessor::GetPath(PathString *placeholder_path, PlaceHolderId placeholder_id) { - std::scoped_lock lock(this->cache_mutex); - CacheEntry *entry = this->FindInCache(placeholder_id); - this->Invalidate(entry); - this->MakePath(placeholder_path, placeholder_id); - } - - Result PlaceHolderAccessor::Create(PlaceHolderId placeholder_id, size_t size) { - this->EnsureRecursively(placeholder_id); - - PathString placeholder_path; - this->GetPath(std::addressof(placeholder_path), placeholder_id); - - R_TRY_CATCH(fsdevCreateFile(placeholder_path, size, FsCreateOption_BigFile)) { - R_CONVERT(ams::fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists()) - } R_END_TRY_CATCH; - - return ResultSuccess(); - } - - Result PlaceHolderAccessor::Delete(PlaceHolderId placeholder_id) { - PathString placeholder_path; - this->GetPath(std::addressof(placeholder_path), placeholder_id); - - R_UNLESS(std::remove(placeholder_path) == 0, ConvertNotFoundResult(fsdevGetLastResult())); - return ResultSuccess(); - } - - Result PlaceHolderAccessor::Write(PlaceHolderId placeholder_id, size_t offset, const void *buffer, size_t size) { - FILE *f = nullptr; - - R_TRY(ConvertNotFoundResult(this->Open(&f, placeholder_id))); - ON_SCOPE_EXIT { this->StoreToCache(f, placeholder_id); }; - - R_TRY(fs::WriteFile(f, offset, buffer, size, this->delay_flush ? ams::fs::WriteOption::Flush : ams::fs::WriteOption::None)); - return ResultSuccess(); - } - - Result PlaceHolderAccessor::SetSize(PlaceHolderId placeholder_id, size_t size) { - PathString placeholder_path; - this->MakePath(std::addressof(placeholder_path), placeholder_id); - - R_UNLESS(truncate(placeholder_path, size) != -1, ConvertNotFoundResult(fsdevGetLastResult())); - return ResultSuccess(); - } - - Result PlaceHolderAccessor::GetSize(bool *found_in_cache, size_t *out_size, PlaceHolderId placeholder_id) { - FILE *f = NULL; - - *found_in_cache = false; - - /* Set the scope for the scoped_lock. */ - { - std::scoped_lock lock(this->cache_mutex); - - /* If the placeholder id is invalid, return success early. */ - R_UNLESS(placeholder_id != InvalidPlaceHolderId, ResultSuccess()); - - CacheEntry *cache_entry = this->FindInCache(placeholder_id); - - /* If there is no entry in the cache, return success early. */ - R_UNLESS(cache_entry != nullptr, ResultSuccess()); - - cache_entry->id = InvalidPlaceHolderId; - f = cache_entry->handle; - } - - this->StoreToCache(f, placeholder_id); - - R_UNLESS(fseek(f, 0L, SEEK_END) == 0, fsdevGetLastResult()); - size_t size = ftell(f); - R_UNLESS(fseek(f, 0L, SEEK_SET) == 0, fsdevGetLastResult()); - - *found_in_cache = true; - *out_size = size; - return ResultSuccess(); - } - - Result PlaceHolderAccessor::EnsureRecursively(PlaceHolderId placeholder_id) { - PathString placeholder_path; - this->MakePath(std::addressof(placeholder_path), placeholder_id); - R_TRY(fs::EnsureParentDirectoryRecursively(placeholder_path)); - return ResultSuccess(); - } - - void PlaceHolderAccessor::InvalidateAll() { - for (auto &entry : this->caches) { - if (entry.id != InvalidPlaceHolderId) { - this->Invalidate(&entry); - } - } - } - -} diff --git a/stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp b/stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp deleted file mode 100644 index 3a656cc87..000000000 --- a/stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 -#include - -#include "../ncm_path_utils.hpp" - -namespace ams::ncm::impl { - - class PlaceHolderAccessor { - private: - class CacheEntry { - public: - PlaceHolderId id; - FILE *handle; - u64 counter; - }; - private: - static constexpr size_t MaxCaches = 0x2; - - std::array caches; - PathString root_path; - u64 cur_counter; - os::Mutex cache_mutex; - MakePlaceHolderPathFunc make_placeholder_path_func; - bool delay_flush; - private: - Result Open(FILE** out_handle, PlaceHolderId placeholder_id); - CacheEntry *FindInCache(PlaceHolderId placeholder_id); - bool LoadFromCache(FILE** out_handle, PlaceHolderId placeholder_id); - CacheEntry *GetFreeEntry(); - void StoreToCache(FILE *handle, PlaceHolderId placeholder_id); - void Invalidate(CacheEntry *entry); - public: - PlaceHolderAccessor() : cur_counter(0), delay_flush(false) { - for (size_t i = 0; i < MaxCaches; i++) { - caches[i].id = InvalidPlaceHolderId; - } - } - - inline void MakeRootPath(PathString *placeholder_root) { - path::GetPlaceHolderRootPath(placeholder_root, this->root_path); - } - - inline void MakePath(PathString *placeholder_path, PlaceHolderId placeholder_id) { - PathString root_path; - this->MakeRootPath(std::addressof(root_path)); - this->make_placeholder_path_func(placeholder_path, placeholder_id, root_path); - } - - void Initialize(const char *root, MakePlaceHolderPathFunc path_func, bool delay_flush); - unsigned int GetDirectoryDepth(); - void GetPath(PathString *out_placeholder_path, PlaceHolderId placeholder_id); - Result Create(PlaceHolderId placeholder_id, size_t size); - Result Delete(PlaceHolderId placeholder_id); - Result Write(PlaceHolderId placeholder_id, size_t offset, const void *buffer, size_t size); - Result SetSize(PlaceHolderId placeholder_id, size_t size); - Result GetSize(bool *found_in_cache, size_t *out_size, PlaceHolderId placeholder_id); - Result EnsureRecursively(PlaceHolderId placeholder_id); - void InvalidateAll(); - }; - -} diff --git a/stratosphere/ncm/source/ncm_content_id_utils.cpp b/stratosphere/ncm/source/ncm_content_id_utils.cpp new file mode 100644 index 000000000..d920a870a --- /dev/null +++ b/stratosphere/ncm/source/ncm_content_id_utils.cpp @@ -0,0 +1,72 @@ +/* + * 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 . + */ +#include "ncm_content_id_utils.hpp" + +namespace ams::ncm { + + namespace { + + void GetStringFromBytes(char *dst, const void *src, size_t count) { + for (size_t i = 0; i < count; i++) { + std::snprintf(dst + 2 * i, 3, "%02x", static_cast(src)[i]); + } + } + + bool GetBytesFromString(void *dst, size_t dst_size, const char *src, size_t src_size) { + if (!util::IsAligned(src_size, 2) || (dst_size * 2 < src_size)) { + return false; + } + + for (size_t i = 0; i < src_size; i += 2) { + char tmp[3]; + strlcpy(tmp, src + i, sizeof(tmp)); + + char *err = nullptr; + reinterpret_cast(dst)[i / 2] = static_cast(std::strtoul(tmp, std::addressof(err), 16)); + if (*err != '\x00') { + return false; + } + } + + return true; + } + + } + + + ContentIdString GetContentIdString(ContentId id) { + ContentIdString str; + + GetStringFromContentId(str.data, sizeof(str), id); + + return str; + } + + void GetStringFromContentId(char *dst, size_t dst_size, ContentId id) { + AMS_ABORT_UNLESS(dst_size > ContentIdStringLength); + GetStringFromBytes(dst, std::addressof(id), sizeof(id)); + } + + std::optional GetContentIdFromString(const char *str, size_t len) { + if (len < ContentIdStringLength) { + return std::nullopt; + } + + ContentId content_id; + return GetBytesFromString(std::addressof(content_id), sizeof(content_id), str, ContentIdStringLength) ? std::optional(content_id) : std::nullopt; + } + +} diff --git a/stratosphere/ncm/source/ncm_utils.hpp b/stratosphere/ncm/source/ncm_content_id_utils.hpp similarity index 60% rename from stratosphere/ncm/source/ncm_utils.hpp rename to stratosphere/ncm/source/ncm_content_id_utils.hpp index 2c18a6e93..aaa5608b2 100644 --- a/stratosphere/ncm/source/ncm_utils.hpp +++ b/stratosphere/ncm/source/ncm_content_id_utils.hpp @@ -15,16 +15,23 @@ */ #pragma once -#include #include -#include namespace ams::ncm { - void GetStringFromContentId(char *out, ContentId content_id); - void GetStringFromPlaceHolderId(char *out, PlaceHolderId placeholder_id); + constexpr inline size_t ContentIdStringLength = 2 * sizeof(ContentId); + constexpr inline size_t RightsIdStringLength = 2 * sizeof(fs::RightsId); + constexpr inline size_t TicketFileStringLength = RightsIdStringLength + 4; + constexpr inline size_t CertFileStringLength = RightsIdStringLength + 5; + + struct ContentIdString { + char data[ContentIdStringLength + 1]; + }; + + ContentIdString GetContentIdString(ContentId id); + + void GetStringFromContentId(char *dst, size_t dst_size, ContentId id); - Result GetPlaceHolderIdFromDirEntry(PlaceHolderId *out, struct dirent *dir_entry); std::optional GetContentIdFromString(const char *str, size_t len); -}; +} diff --git a/stratosphere/ncm/source/ncm_content_manager_impl.cpp b/stratosphere/ncm/source/ncm_content_manager_impl.cpp index c70ba4b11..0a6ee634d 100644 --- a/stratosphere/ncm/source/ncm_content_manager_impl.cpp +++ b/stratosphere/ncm/source/ncm_content_manager_impl.cpp @@ -25,75 +25,183 @@ namespace ams::ncm { namespace { - constexpr u64 BuiltInSystemSaveDataId = 0x8000000000000120; - constexpr u64 BuiltInSystemSaveDataSize = 0x6c000; - constexpr u64 BuiltInSystemSaveDataJournalSize = 0x6c000; - constexpr u32 BuiltInSystemSaveDataFlags = FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment; + constexpr fs::SystemSaveDataId BuiltInSystemSaveDataId = 0x8000000000000120; + constexpr u64 BuiltInSystemSaveDataSize = 0x6c000; + constexpr u64 BuiltInSystemSaveDataJournalSize = 0x6c000; + constexpr u32 BuiltInSystemSaveDataFlags = FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment; constexpr SystemSaveDataInfo BuiltInSystemSystemSaveDataInfo = { .id = BuiltInSystemSaveDataId, .size = BuiltInSystemSaveDataSize, .journal_size = BuiltInSystemSaveDataJournalSize, .flags = BuiltInSystemSaveDataFlags, - .space_id = FsSaveDataSpaceId_System + .space_id = fs::SaveDataSpaceId::System }; - constexpr u64 BuiltInUserSaveDataId = 0x8000000000000121; - constexpr u64 BuiltInUserSaveDataSize = 0x29e000; - constexpr u64 BuiltInUserSaveDataJournalSize = 0x29e000; - constexpr u32 BuiltInUserSaveDataFlags = 0; + constexpr fs::SystemSaveDataId BuiltInUserSaveDataId = 0x8000000000000121; + constexpr u64 BuiltInUserSaveDataSize = 0x29e000; + constexpr u64 BuiltInUserSaveDataJournalSize = 0x29e000; + constexpr u32 BuiltInUserSaveDataFlags = 0; constexpr SystemSaveDataInfo BuiltInUserSystemSaveDataInfo = { .id = BuiltInUserSaveDataId, .size = BuiltInUserSaveDataSize, .journal_size = BuiltInUserSaveDataJournalSize, .flags = BuiltInUserSaveDataFlags, - .space_id = FsSaveDataSpaceId_System + .space_id = fs::SaveDataSpaceId::System }; - constexpr u64 SdCardSaveDataId = 0x8000000000000124; - constexpr u64 SdCardSaveDataSize = 0xa08000; - constexpr u64 SdCardSaveDataJournalSize = 0xa08000; - constexpr u32 SdCardSaveDataFlags = 0; + constexpr fs::SystemSaveDataId SdCardSaveDataId = 0x8000000000000124; + constexpr u64 SdCardSaveDataSize = 0xa08000; + constexpr u64 SdCardSaveDataJournalSize = 0xa08000; + constexpr u32 SdCardSaveDataFlags = 0; constexpr SystemSaveDataInfo SdCardSystemSaveDataInfo = { .id = SdCardSaveDataId, .size = SdCardSaveDataSize, .journal_size = SdCardSaveDataJournalSize, .flags = SdCardSaveDataFlags, - .space_id = FsSaveDataSpaceId_SdSystem, + .space_id = fs::SaveDataSpaceId::SdSystem, }; constexpr size_t MaxBuiltInSystemContentMetaCount = 0x800; constexpr size_t MaxBuiltInUserContentMetaCount = 0x2000; constexpr size_t MaxSdCardContentMetaCount = 0x2000; + constexpr size_t MaxGameCardContentMetaCount = 0x800; + using RootPath = kvdb::BoundedString<32>; + + inline void ReplaceMountName(char *out_path, const char *mount_name, const char *path) { + std::strcpy(out_path, mount_name); + std::strcat(out_path, std::strchr(path, ':')); + } + + Result EnsureBuiltInSystemSaveDataFlags() { + u32 cur_flags = 0; + R_TRY(fs::GetSaveDataFlags(std::addressof(cur_flags), BuiltInSystemSaveDataId)); + if (cur_flags != BuiltInSystemSaveDataFlags) { + R_TRY(fs::SetSaveDataFlags(BuiltInSystemSaveDataId, fs::SaveDataSpaceId::System, BuiltInSystemSaveDataFlags)); + } + return ResultSuccess(); + } + + ALWAYS_INLINE Result GetContentStorageNotActiveResult(StorageId storage_id) { + switch (storage_id) { + case StorageId::GameCard: return ResultGameCardContentStorageNotActive(); + case StorageId::BuiltInSystem: return ResultNandSystemContentStorageNotActive(); + case StorageId::BuiltInUser: return ResultNandUserContentStorageNotActive(); + case StorageId::SdCard: return ResultSdCardContentStorageNotActive(); + default: return ResultUnknownContentStorageNotActive(); + } + } + + ALWAYS_INLINE Result GetContentMetaDatabaseNotActiveResult(StorageId storage_id) { + switch (storage_id) { + case StorageId::GameCard: return ResultGameCardContentMetaDatabaseNotActive(); + case StorageId::BuiltInSystem: return ResultNandSystemContentMetaDatabaseNotActive(); + case StorageId::BuiltInUser: return ResultNandUserContentMetaDatabaseNotActive(); + case StorageId::SdCard: return ResultSdCardContentMetaDatabaseNotActive(); + default: return ResultUnknownContentMetaDatabaseNotActive(); + } + } } ContentManagerImpl::~ContentManagerImpl() { - { - std::scoped_lock lk(this->mutex); + std::scoped_lock lk(this->mutex); - for (size_t i = 0; i < MaxContentStorageEntries; i++) { - ContentStorageRoot *entry = &this->content_storage_roots[i]; - this->InactivateContentStorage(entry->storage_id); + for (auto &root : this->content_storage_roots) { + this->InactivateContentStorage(root.storage_id); + } + + for (auto &root : this->content_meta_database_roots) { + this->InactivateContentMetaDatabase(root.storage_id); + } + } + + Result ContentManagerImpl::EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &info) const { + constexpr u64 OwnerId = 0; + + fs::DisableAutoSaveDataCreation(); + + 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)); + R_TRY(fs::MountSystemSaveData(mount_name, info.space_id, info.id)); } + } R_END_TRY_CATCH; - for (size_t i = 0; i < MaxContentMetaDatabaseEntries; i++) { - ContentMetaDatabaseEntry *entry = &this->content_meta_entries[i]; - this->InactivateContentMetaDatabase(entry->storage_id); + return ResultSuccess(); + } + + Result ContentManagerImpl::GetContentStorageRoot(ContentStorageRoot **out, StorageId id) { + R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage()); + + for (auto &root : this->content_storage_roots) { + if (root.storage_id == id) { + *out = std::addressof(root); + return ResultSuccess(); } } - for (size_t i = 0; i < MaxContentMetaDatabaseEntries; i++) { - ContentMetaDatabaseEntry *entry = &this->content_meta_entries[i]; - entry->kvs.reset(); + return ncm::ResultUnknownStorage(); + } + + Result ContentManagerImpl::GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id) { + R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage()); + + for (auto &root : this->content_meta_database_roots) { + if (root.storage_id == id) { + *out = std::addressof(root); + return ResultSuccess(); + } } - for (size_t i = 0; i < MaxContentStorageEntries; i++) { - ContentStorageRoot *entry = &this->content_storage_roots[i]; - entry->content_storage = nullptr; - } + return ncm::ResultUnknownStorage(); + } + + + Result ContentManagerImpl::InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, fs::ContentStorageId content_storage_id) { + out->storage_id = storage_id; + out->content_storage_id = content_storage_id; + out->content_storage = nullptr; + + std::strcpy(out->mount_name, impl::CreateUniqueMountName().str); + std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name); + + return ResultSuccess(); + } + + Result ContentManagerImpl::InitializeGameCardContentStorageRoot(ContentStorageRoot *out) { + out->storage_id = StorageId::GameCard; + out->content_storage = nullptr; + + std::strcpy(out->mount_name, impl::CreateUniqueMountName().str); + std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name); + + return ResultSuccess(); + } + + Result ContentManagerImpl::InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, const SystemSaveDataInfo &info, size_t max_content_metas) { + out->storage_id = storage_id; + out->info = info; + out->max_content_metas = max_content_metas; + out->content_meta_database = nullptr; + out->kvs = std::nullopt; + + std::strcpy(out->mount_name, impl::CreateUniqueMountName().str); + out->mount_name[0] = '#'; + std::snprintf(out->path, sizeof(out->path), "%s:/meta", out->mount_name); + + return ResultSuccess(); + } + + Result ContentManagerImpl::InitializeGameCardContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, size_t max_content_metas) { + out->storage_id = StorageId::GameCard; + out->max_content_metas = max_content_metas; + out->content_meta_database = nullptr; + out->kvs = std::nullopt; + + return ResultSuccess(); } Result ContentManagerImpl::Initialize() { @@ -102,56 +210,52 @@ namespace ams::ncm { /* Already initialized. */ R_UNLESS(!this->initialized, ResultSuccess()); - for (size_t i = 0; i < MaxContentStorageEntries; i++) { - ContentStorageRoot *entry = &this->content_storage_roots[i]; - entry->storage_id = StorageId::None; + /* Clear storage id for all roots. */ + for (auto &root : this->content_storage_roots) { + root.storage_id = StorageId::None; } - for (size_t i = 0; i < MaxContentMetaDatabaseEntries; i++) { - ContentMetaDatabaseEntry *entry = &this->content_meta_entries[i]; - entry->storage_id = StorageId::None; + for (auto &root : this->content_meta_database_roots) { + root.storage_id = StorageId::None; } /* First, setup the BuiltInSystem storage entry. */ - this->content_storage_roots[this->num_content_storage_entries++].Initialize(StorageId::BuiltInSystem, FsContentStorageId_System); - + R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInSystem, fs::ContentStorageId::System)); if (R_FAILED(this->VerifyContentStorage(StorageId::BuiltInSystem))) { R_TRY(this->CreateContentStorage(StorageId::BuiltInSystem)); } - R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem)); /* Next, the BuiltInSystem content meta entry. */ - R_TRY(this->content_meta_entries[this->num_content_meta_entries++].Initialize(StorageId::BuiltInSystem, BuiltInSystemSystemSaveDataInfo, MaxBuiltInSystemContentMetaCount)); + R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInSystem, BuiltInSystemSystemSaveDataInfo, MaxBuiltInSystemContentMetaCount)); if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) { R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem)); - /* TODO: N supports a number of unused modes here, we don't bother implementing them currently. */ + /* TODO: N supports building the database depending on config (unused on retail). */ } - u32 current_flags = 0; - if (hos::GetVersion() >= hos::Version_200 && R_SUCCEEDED(fs::GetSaveDataFlags(¤t_flags, BuiltInSystemSaveDataId)) && current_flags != (FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment)) { - fs::SetSaveDataFlags(BuiltInSystemSaveDataId, FsSaveDataSpaceId_System, FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment); + /* Ensure correct flags on the BuiltInSystem save data. */ + if (hos::GetVersion() >= hos::Version_200) { + R_TRY(EnsureBuiltInSystemSaveDataFlags()); } R_TRY(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem)); /* Now for BuiltInUser's content storage and content meta entries. */ - this->content_storage_roots[this->num_content_storage_entries++].Initialize(StorageId::BuiltInUser, FsContentStorageId_User); - R_TRY(this->content_meta_entries[this->num_content_meta_entries++].Initialize(StorageId::BuiltInUser, BuiltInUserSystemSaveDataInfo, MaxBuiltInUserContentMetaCount)); + R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInUser, fs::ContentStorageId::User)); + R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInUser, BuiltInUserSystemSaveDataInfo, MaxBuiltInUserContentMetaCount)); - /* Beyond this point N no longer appears to bother */ - /* incrementing the count for content storage entries or content meta entries. */ + /* Beyond this point, N uses hardcoded indices. */ /* Next SdCard's content storage and content meta entries. */ - this->content_storage_roots[2].Initialize(StorageId::SdCard, FsContentStorageId_SdCard); - R_TRY(this->content_meta_entries[2].Initialize(StorageId::SdCard, SdCardSystemSaveDataInfo, MaxSdCardContentMetaCount)); + R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[2], StorageId::SdCard, fs::ContentStorageId::SdCard)); + R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[2], StorageId::SdCard, SdCardSystemSaveDataInfo, MaxSdCardContentMetaCount)); /* GameCard's content storage and content meta entries. */ /* N doesn't set a content storage id for game cards, so we'll just use 0 (System). */ - this->content_storage_roots[3].Initialize(StorageId::GameCard, FsContentStorageId_System); - R_TRY(this->content_meta_entries[3].InitializeGameCard(0x800)); + R_TRY(this->InitializeGameCardContentStorageRoot(&this->content_storage_roots[3])); + R_TRY(this->InitializeGameCardContentMetaDatabaseRoot(&this->content_meta_database_roots[3], MaxGameCardContentMetaCount)); this->initialized = true; return ResultSuccess(); @@ -161,13 +265,13 @@ namespace ams::ncm { std::scoped_lock lk(this->mutex); ContentStorageRoot *root; - R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id)); + R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); - R_TRY(fs::MountContentStorage(root->mount_point, root->content_storage_id)); - ON_SCOPE_EXIT { fs::Unmount(root->mount_point); }; + R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id)); + ON_SCOPE_EXIT { fs::Unmount(root->mount_name); }; - R_TRY(fs::EnsureDirectoryRecursively(root->path)); - R_TRY(fs::EnsureContentAndPlaceHolderRoot(root->path)); + R_TRY(impl::EnsureDirectoryRecursively(root->path)); + R_TRY(ContentStorageImpl::InitializeBase(root->path)); return ResultSuccess(); } @@ -176,17 +280,16 @@ namespace ams::ncm { std::scoped_lock lk(this->mutex); R_UNLESS(storage_id != StorageId::GameCard, ncm::ResultUnknownStorage()); - ContentMetaDatabaseEntry *entry; - R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id)); - /* N doesn't bother checking the result of this. */ - fsDisableAutoSaveDataCreation(); + ContentMetaDatabaseRoot *root; + R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); - R_TRY(EnsureAndMountSystemSaveData(entry->mount_point, entry->save_meta)); - ON_SCOPE_EXIT { fs::Unmount(entry->mount_point); }; + R_TRY(this->EnsureAndMountSystemSaveData(root->mount_name, root->info)); + ON_SCOPE_EXIT { fs::Unmount(root->mount_name); }; - R_TRY(fs::EnsureDirectoryRecursively(entry->meta_path)); - R_TRY(fsdevCommitDevice(entry->mount_point)); + R_TRY(impl::EnsureDirectoryRecursively(root->path)); + + R_TRY(fs::CommitSaveData(root->mount_name)); return ResultSuccess(); } @@ -195,37 +298,38 @@ namespace ams::ncm { std::scoped_lock lk(this->mutex); ContentStorageRoot *root; - R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id)); + R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); - char mount_root[0x80] = {}; - auto mount_name = ncm::fs::CreateUniqueMountName(); /* should this be fs::? should it be ncm::? ncm::impl? */ - ReplaceMountName(mount_root, mount_name.name, root->path); + char path[0x80]; + auto mount_name = impl::CreateUniqueMountName(); /* should this be fs::? should it be ncm::? ncm::impl? */ + ReplaceMountName(path, mount_name.str, root->path); - R_TRY(fs::MountContentStorage(mount_name.name, root->content_storage_id)); - ON_SCOPE_EXIT { fs::Unmount(mount_name.name); }; + R_TRY(fs::MountContentStorage(mount_name.str, root->content_storage_id)); + ON_SCOPE_EXIT { fs::Unmount(mount_name.str); }; - R_TRY(fs::CheckContentStorageDirectoriesExist(mount_root)); + R_TRY(ContentStorageImpl::VerifyBase(path)); return ResultSuccess(); } Result ContentManagerImpl::VerifyContentMetaDatabase(StorageId storage_id) { + R_UNLESS(storage_id != StorageId::GameCard, ResultSuccess()); + std::scoped_lock lk(this->mutex); - R_UNLESS(storage_id != StorageId::GameCard, ResultSuccess()); - ContentMetaDatabaseEntry *entry; - R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id)); + ContentMetaDatabaseRoot *root; + R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); - auto mount_guard = SCOPE_GUARD { fs::Unmount(entry->mount_point); }; - if (!entry->content_meta_database) { - R_TRY(fs::MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id)); + 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)); } else { mount_guard.Cancel(); } - bool has_meta_path = false; - R_TRY(fs::HasDirectory(&has_meta_path, entry->meta_path)); - R_UNLESS(has_meta_path, ncm::ResultInvalidContentMetaDatabase()); + bool has_dir = false; + R_TRY(impl::HasDirectory(&has_dir, root->path)); + R_UNLESS(has_dir, ncm::ResultInvalidContentMetaDatabase()); return ResultSuccess(); } @@ -234,20 +338,18 @@ namespace ams::ncm { std::scoped_lock lk(this->mutex); ContentStorageRoot *root; - R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id)); - - auto content_storage = root->content_storage; + R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); if (hos::GetVersion() >= hos::Version_200) { - R_UNLESS(content_storage, GetContentStorageNotActiveResult(storage_id)); + R_UNLESS(root->content_storage, GetContentStorageNotActiveResult(storage_id)); } else { /* 1.0.0 activates content storages as soon as they are opened. */ - if (!content_storage) { + if (!root->content_storage) { R_TRY(this->ActivateContentStorage(storage_id)); - content_storage = root->content_storage; } } + auto content_storage = root->content_storage; out.SetValue(std::move(content_storage)); return ResultSuccess(); } @@ -255,73 +357,41 @@ namespace ams::ncm { Result ContentManagerImpl::OpenContentMetaDatabase(sf::Out> out, StorageId storage_id) { std::scoped_lock lk(this->mutex); - ContentMetaDatabaseEntry *entry; - R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id)); + ContentMetaDatabaseRoot *root; + R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); - auto content_meta_db = entry->content_meta_database; if (hos::GetVersion() >= hos::Version_200) { - R_UNLESS(content_meta_db, GetContentMetaDatabaseNotActiveResult(storage_id)); + R_UNLESS(root->content_meta_database, GetContentMetaDatabaseNotActiveResult(storage_id)); } else { /* 1.0.0 activates content meta databases as soon as they are opened. */ - if (!content_meta_db) { + if (!root->content_meta_database) { R_TRY(this->ActivateContentMetaDatabase(storage_id)); - content_meta_db = entry->content_meta_database; } } + auto content_meta_db = root->content_meta_database; out.SetValue(std::move(content_meta_db)); return ResultSuccess(); } Result ContentManagerImpl::CloseContentStorageForcibly(StorageId storage_id) { - std::scoped_lock lk(this->mutex); - - R_UNLESS(storage_id != StorageId::None, ncm::ResultUnknownStorage()); - ContentStorageRoot *root; - R_TRY(FindContentStorageRoot(std::addressof(root), storage_id)); - - if (root->content_storage) { - /* N doesn't bother checking the result of this */ - root->content_storage->DisableForcibly(); - fs::Unmount(root->mount_point); - root->content_storage = nullptr; - } - - return ResultSuccess(); + return this->InactivateContentStorage(storage_id); } Result ContentManagerImpl::CloseContentMetaDatabaseForcibly(StorageId storage_id) { - std::scoped_lock lk(this->mutex); - - R_UNLESS(storage_id != StorageId::None, ncm::ResultUnknownStorage()); - ContentMetaDatabaseEntry *entry; - R_TRY(FindContentMetaDatabaseEntry(&entry, storage_id)); - - auto content_meta_db = entry->content_meta_database; - - if (content_meta_db) { - /* N doesn't bother checking the result of this */ - content_meta_db->DisableForcibly(); - - if (storage_id != StorageId::GameCard) { - fs::Unmount(entry->mount_point); - } - - entry->content_meta_database = nullptr; - entry->kvs = std::nullopt; - } - - return ResultSuccess(); + return this->InactivateContentMetaDatabase(storage_id); } Result ContentManagerImpl::CleanupContentMetaDatabase(StorageId storage_id) { std::scoped_lock lk(this->mutex); - ContentMetaDatabaseEntry *entry; - R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id)); + R_TRY(this->InactivateContentMetaDatabase(storage_id)); - R_TRY(fsDeleteSaveDataFileSystemBySaveDataSpaceId(entry->save_meta.space_id, entry->save_meta.id)); + ContentMetaDatabaseRoot *root; + R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); + + R_TRY(fs::DeleteSaveData(root->info.space_id, root->info.id)); return ResultSuccess(); } @@ -329,45 +399,40 @@ namespace ams::ncm { std::scoped_lock lk(this->mutex); ContentStorageRoot *root; - R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id)); + R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); - /* Already activated. */ + /* Check if the storage is already activated. */ R_UNLESS(root->content_storage == nullptr, ResultSuccess()); if (storage_id == StorageId::GameCard) { - FsGameCardHandle gc_hnd; - R_TRY(fs::GetGameCardHandle(&gc_hnd)); - R_TRY(fs::MountGameCardPartition(root->mount_point, gc_hnd, FsGameCardPartition_Secure)); + fs::GameCardHandle handle; + R_TRY(fs::GetGameCardHandle(std::addressof(handle))); + R_TRY(fs::MountGameCardPartition(root->mount_name, handle, fs::GameCardPartition::Secure)); } else { - R_TRY(fs::MountContentStorage(root->mount_point, root->content_storage_id)); + R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id)); } - auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_point); }; + auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); }; if (storage_id == StorageId::GameCard) { auto content_storage = std::make_shared(); - R_TRY(content_storage->Initialize(root->path, path::MakeContentPathFlat)); + R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath)); root->content_storage = std::move(content_storage); } else { - MakeContentPathFunc content_path_func = nullptr; - MakePlaceHolderPathFunc placeholder_path_func = nullptr; - bool delay_flush = false; auto content_storage = std::make_shared(); switch (storage_id) { case StorageId::BuiltInSystem: - content_path_func = path::MakeContentPathFlat; - placeholder_path_func = path::MakePlaceHolderPathFlat; + R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath, MakeFlatPlaceHolderFilePath, false, std::addressof(this->rights_id_cache))); break; case StorageId::SdCard: - delay_flush = true; + R_TRY(content_storage->Initialize(root->path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, true, std::addressof(this->rights_id_cache))); + break; default: - content_path_func = path::MakeContentPathHashByteLayered; - placeholder_path_func = path::MakePlaceHolderPathHashByteLayered; + R_TRY(content_storage->Initialize(root->path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, false, std::addressof(this->rights_id_cache))); break; } - R_TRY(content_storage->Initialize(root->path, content_path_func, placeholder_path_func, delay_flush, &this->rights_id_cache)); root->content_storage = std::move(content_storage); } @@ -379,40 +444,42 @@ namespace ams::ncm { std::scoped_lock lk(this->mutex); ContentStorageRoot *root; - R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id)); + R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id)); - /* Already inactivated. */ - R_UNLESS(root->content_storage != nullptr, ResultSuccess()); + if (root->content_storage) { + /* N doesn't bother checking the result of this */ + root->content_storage->DisableForcibly(); + root->content_storage = nullptr; + fs::Unmount(root->mount_name); + } - root->content_storage->DisableForcibly(); - root->content_storage = nullptr; - fs::Unmount(root->mount_point); return ResultSuccess(); } Result ContentManagerImpl::ActivateContentMetaDatabase(StorageId storage_id) { std::scoped_lock lk(this->mutex); - ContentMetaDatabaseEntry *entry; - R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id)); + ContentMetaDatabaseRoot *root; + R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); /* Already activated. */ - R_UNLESS(entry->content_meta_database == nullptr, ResultSuccess()); + R_UNLESS(root->content_meta_database == nullptr, ResultSuccess()); - /* Make a brand new kvs. N doesn't quite do this, but we will for cleanliness. */ - entry->kvs.emplace(); + /* Make a new kvs. */ + root->kvs.emplace(); - if (storage_id != StorageId::GameCard) { - R_TRY(fs::MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id)); - auto mount_guard = SCOPE_GUARD { fs::Unmount(entry->mount_point); }; - R_TRY(entry->kvs->Initialize(entry->meta_path, entry->max_content_metas)); - R_TRY(entry->kvs->Load()); - entry->content_meta_database = std::make_shared(std::addressof(*entry->kvs), entry->mount_point); - mount_guard.Cancel(); + if (storage_id == StorageId::GameCard) { + R_TRY(root->kvs->Initialize(root->max_content_metas)); + root->content_meta_database = std::make_shared(std::addressof(*root->kvs)); } else { - R_TRY(entry->kvs->Initialize(entry->max_content_metas)); - R_TRY(entry->kvs->Load()); - entry->content_meta_database = std::make_shared(std::addressof(*entry->kvs)); + R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id)); + auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); }; + + R_TRY(root->kvs->Initialize(root->path, root->max_content_metas)); + R_TRY(root->kvs->Load()); + + root->content_meta_database = std::make_shared(std::addressof(*root->kvs), root->mount_name); + mount_guard.Cancel(); } return ResultSuccess(); @@ -421,18 +488,17 @@ namespace ams::ncm { Result ContentManagerImpl::InactivateContentMetaDatabase(StorageId storage_id) { std::scoped_lock lk(this->mutex); - ContentMetaDatabaseEntry *entry; - R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id)); + ContentMetaDatabaseRoot *root; + R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id)); - /* Already inactivated. */ - if (entry->content_meta_database != nullptr) { - entry->content_meta_database->DisableForcibly(); - entry->content_meta_database = nullptr; - /* This should lead to Index's destructor performing cleanup for us. */ - entry->kvs = std::nullopt; + 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; if (storage_id != StorageId::GameCard) { - fs::Unmount(entry->mount_point); + 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 db507e934..1614e5833 100644 --- a/stratosphere/ncm/source/ncm_content_manager_impl.hpp +++ b/stratosphere/ncm/source/ncm_content_manager_impl.hpp @@ -16,8 +16,8 @@ #pragma once #include -#include "impl/ncm_rights_cache.hpp" -#include "ncm_fs.hpp" +#include "ncm_rights_cache.hpp" +#include "ncm_fs_utils.hpp" namespace ams::ncm { @@ -26,177 +26,67 @@ namespace ams::ncm { u64 size; u64 journal_size; u32 flags; - FsSaveDataSpaceId space_id; + fs::SaveDataSpaceId space_id; }; - - static_assert(sizeof(SystemSaveDataInfo) == 0x20, "SystemSaveDataInfo definition!"); + static_assert(std::is_pod::value); class ContentManagerImpl final : public IContentManager { private: - constexpr static size_t MaxContentStorageEntries = 8; - constexpr static size_t MaxContentMetaDatabaseEntries = 8; + constexpr static size_t MaxContentStorageRoots = 8; + constexpr static size_t MaxContentMetaDatabaseRoots = 8; private: struct ContentStorageRoot { NON_COPYABLE(ContentStorageRoot); NON_MOVEABLE(ContentStorageRoot); - char mount_point[16]; + char mount_name[fs::MountNameLengthMax + 1]; char path[128]; StorageId storage_id; - FsContentStorageId content_storage_id; + fs::ContentStorageId content_storage_id; std::shared_ptr content_storage; - inline ContentStorageRoot() : storage_id(StorageId::None), - content_storage_id(FsContentStorageId_System), content_storage(nullptr) { - mount_point[0] = '\0'; - path[0] = '\0'; - } - - inline void Initialize(StorageId storage_id, FsContentStorageId content_storage_id) { - this->storage_id = storage_id; - this->content_storage_id = content_storage_id; - this->content_storage = nullptr; - MountName mount_name = ncm::fs::CreateUniqueMountName(); - std::strcpy(this->mount_point, mount_name.name); - snprintf(this->path, 0x80, "%s:/", this->mount_point); - } + ContentStorageRoot() { /* ... */ } }; - struct ContentMetaDatabaseEntry { - NON_COPYABLE(ContentMetaDatabaseEntry); - NON_MOVEABLE(ContentMetaDatabaseEntry); + struct ContentMetaDatabaseRoot { + NON_COPYABLE(ContentMetaDatabaseRoot); + NON_MOVEABLE(ContentMetaDatabaseRoot); - char mount_point[16]; - char meta_path[128]; + char mount_name[fs::MountNameLengthMax + 1]; + char path[128]; StorageId storage_id; - SystemSaveDataInfo save_meta; + SystemSaveDataInfo info; std::shared_ptr content_meta_database; std::optional> kvs; u32 max_content_metas; - inline ContentMetaDatabaseEntry() : storage_id(StorageId::None), save_meta({0}), - content_meta_database(nullptr), kvs(std::nullopt), max_content_metas(0) { - mount_point[0] = '\0'; - meta_path[0] = '\0'; - } - - Result Initialize(StorageId storage_id, const SystemSaveDataInfo& save_meta, size_t max_content_metas) { - this->storage_id = storage_id; - this->max_content_metas = max_content_metas; - this->save_meta = save_meta; - this->content_meta_database = nullptr; - this->kvs = std::nullopt; - MountName mount_name = ncm::fs::CreateUniqueMountName(); - strcpy(this->mount_point, mount_name.name); - this->mount_point[0] = '#'; - snprintf(this->meta_path, 0x80, "%s:/meta", this->mount_point); - return ResultSuccess(); - } - - Result InitializeGameCard(size_t max_content_metas) { - this->storage_id = StorageId::GameCard; - this->max_content_metas = max_content_metas; - this->content_meta_database = nullptr; - this->kvs = std::nullopt; - return ResultSuccess(); - } + ContentMetaDatabaseRoot() { /* ... */ } }; private: os::Mutex mutex; bool initialized = false; - ContentStorageRoot content_storage_roots[MaxContentStorageEntries]; - ContentMetaDatabaseEntry content_meta_entries[MaxContentMetaDatabaseEntries]; + ContentStorageRoot content_storage_roots[MaxContentStorageRoots]; + ContentMetaDatabaseRoot content_meta_database_roots[MaxContentMetaDatabaseRoots]; u32 num_content_storage_entries; u32 num_content_meta_entries; - impl::RightsIdCache rights_id_cache; + RightsIdCache rights_id_cache; public: ContentManagerImpl() { /* ... */ }; ~ContentManagerImpl(); public: Result Initialize(); private: - constexpr inline bool IsUniqueStorage(StorageId id) { - return id != StorageId::None && id != StorageId::Any; - } + Result GetContentStorageRoot(ContentStorageRoot **out, StorageId id); + Result GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id); - Result FindContentStorageRoot(ContentStorageRoot **out, StorageId storage_id) { - for (size_t i = 0; i < MaxContentStorageEntries; i++) { - ContentStorageRoot *root = &this->content_storage_roots[i]; + Result InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, fs::ContentStorageId content_storage_id); + Result InitializeGameCardContentStorageRoot(ContentStorageRoot *out); - if (root->storage_id == storage_id) { - *out = root; - return ResultSuccess(); - } - } - return ncm::ResultUnknownStorage(); - } + 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 GetUniqueContentStorageRoot(ContentStorageRoot **out, StorageId storage_id) { - R_UNLESS(IsUniqueStorage(storage_id), ncm::ResultUnknownStorage()); - return FindContentStorageRoot(out, storage_id); - } + Result EnsureAndMountSystemSaveData(const char *mount, const SystemSaveDataInfo &info) const; - Result FindContentMetaDatabaseEntry(ContentMetaDatabaseEntry **out, StorageId storage_id) { - for (size_t i = 0; i < MaxContentMetaDatabaseEntries; i++) { - ContentMetaDatabaseEntry *entry = &this->content_meta_entries[i]; - - if (entry->storage_id == storage_id) { - *out = entry; - return ResultSuccess(); - } - } - return ncm::ResultUnknownStorage(); - } - - Result GetUniqueContentMetaDatabaseEntry(ContentMetaDatabaseEntry **out, StorageId storage_id) { - R_UNLESS(IsUniqueStorage(storage_id), ncm::ResultUnknownStorage()); - return FindContentMetaDatabaseEntry(out, storage_id); - } - - ALWAYS_INLINE Result GetContentStorageNotActiveResult(StorageId storage_id) { - switch (storage_id) { - case StorageId::GameCard: - return ResultGameCardContentStorageNotActive(); - case StorageId::BuiltInSystem: - return ResultNandSystemContentStorageNotActive(); - case StorageId::BuiltInUser: - return ResultNandUserContentStorageNotActive(); - case StorageId::SdCard: - return ResultSdCardContentStorageNotActive(); - default: - return ResultUnknownContentStorageNotActive(); - } - } - - ALWAYS_INLINE Result GetContentMetaDatabaseNotActiveResult(StorageId storage_id) { - switch (storage_id) { - case StorageId::GameCard: - return ResultGameCardContentMetaDatabaseNotActive(); - case StorageId::BuiltInSystem: - return ResultNandSystemContentMetaDatabaseNotActive(); - case StorageId::BuiltInUser: - return ResultNandUserContentMetaDatabaseNotActive(); - case StorageId::SdCard: - return ResultSdCardContentMetaDatabaseNotActive(); - default: - return ResultUnknownContentMetaDatabaseNotActive(); - } - } - - Result EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &save_meta) { - R_TRY_CATCH(fs::MountSystemSaveData(mount_name, save_meta.space_id, save_meta.id)) { - R_CATCH(ams::fs::ResultTargetNotFound) { - R_TRY(fsCreate_SystemSaveData(save_meta.space_id, save_meta.id, save_meta.size, save_meta.journal_size, save_meta.flags)); - R_TRY(fs::MountSystemSaveData(mount_name, save_meta.space_id, save_meta.id)); - } - } R_END_TRY_CATCH; - return ResultSuccess(); - } - - inline void ReplaceMountName(char *out_path, const char *mount_name, const char *root_path) { - strcpy(out_path, mount_name); - strcat(out_path, strchr(root_path, ':')); - } public: virtual Result CreateContentStorage(StorageId storage_id) override; virtual Result CreateContentMetaDatabase(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 3c8989980..e76aad429 100644 --- a/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp +++ b/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp @@ -15,7 +15,7 @@ */ #include "ncm_content_meta_database_impl.hpp" -#include "ncm_utils.hpp" +#include "ncm_content_id_utils.hpp" namespace ams::ncm { diff --git a/stratosphere/ncm/source/ncm_content_storage_impl.cpp b/stratosphere/ncm/source/ncm_content_storage_impl.cpp index c474e2528..004ac57eb 100644 --- a/stratosphere/ncm/source/ncm_content_storage_impl.cpp +++ b/stratosphere/ncm/source/ncm_content_storage_impl.cpp @@ -15,62 +15,207 @@ */ #include "ncm_content_storage_impl.hpp" -#include "ncm_fs.hpp" +#include "ncm_fs_utils.hpp" #include "ncm_make_path.hpp" -#include "ncm_utils.hpp" +#include "ncm_content_id_utils.hpp" namespace ams::ncm { - ContentStorageImpl::~ContentStorageImpl() { - this->Finalize(); + namespace { + + constexpr inline const char * const BaseContentDirectory = "/registered"; + + void MakeBaseContentDirectoryPath(PathString *out, const char *root_path) { + out->SetFormat("%s%s", root_path, BaseContentDirectory); + } + + void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + PathString path; + MakeBaseContentDirectoryPath(std::addressof(path), root_path); + func(out, id, path); + } + + Result EnsureContentDirectory(ContentId id, MakeContentPathFunction func, const char *root_path) { + PathString path; + MakeContentPath(std::addressof(path), id, func, root_path); + return impl::EnsureParentDirectoryRecursively(path); + } + + Result DeleteContentFile(ContentId id, MakeContentPathFunction func, const char *root_path) { + PathString path; + MakeContentPath(std::addressof(path), id, func, root_path); + + R_TRY_CATCH(fs::DeleteFile(path)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultContentNotFound()) + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + + template + Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) { + R_UNLESS(max_level > 0, ResultSuccess()); + + bool retry_dir_read = true; + while (retry_dir_read) { + retry_dir_read = false; + + 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) { + fs::DirectoryEntry entry; + s64 entry_count; + R_TRY(fs::ReadDirectory(std::addressof(entry_count), std::addressof(entry), dir, 1)); + if (entry_count == 0) { + break; + } + + PathString current_path; + current_path.SetFormat("%s/%s", root_path, entry.name); + + bool should_continue = true; + bool should_retry_dir_read = false; + R_TRY(f(&should_continue, &should_retry_dir_read, current_path, entry)); + + /* If the provided function wishes to terminate immediately, we should respect it. */ + if (!should_continue) { + *out_should_continue = false; + return ResultSuccess(); + } + + if (should_retry_dir_read) { + retry_dir_read = true; + break; + } + + /* If the entry is a directory, recurse. */ + if (entry.type == fs::DirectoryEntryType_Directory) { + R_TRY(TraverseDirectory(std::addressof(should_continue), current_path, max_level - 1, f)); + + if (!should_continue) { + *out_should_continue = false; + return ResultSuccess(); + } + } + } + } + + return ResultSuccess(); + } + + + template + Result TraverseDirectory(const char *root_path, int max_level, F f) { + bool should_continue = false; + return TraverseDirectory(std::addressof(should_continue), root_path, max_level, f); + } + + bool IsContentPath(const char *path) { + impl::PathView view(path); + if (!view.HasSuffix(".nca")) { + return false; + } + + auto file_name = view.GetFileName(); + if (file_name.length() != ContentIdStringLength + 4) { + return false; + } + + for (size_t i = 0; i < ContentIdStringLength; i++) { + if (!std::isxdigit(static_cast(file_name[i]))) { + return false; + } + } + + return true; + } + + bool IsPlaceHolderPath(const char *path) { + return IsContentPath(path); + } + + Result CleanDirectoryRecursively(const PathString &path) { + if (hos::GetVersion() >= hos::Version_300) { + R_TRY(fs::CleanDirectoryRecursively(path)); + } else { + /* CleanDirectoryRecursively didn't exist on < 3.0.0, so we will polyfill it. */ + /* We'll delete the directory, then recreate it. */ + R_TRY(fs::DeleteDirectoryRecursively(path)); + R_TRY(fs::CreateDirectory(path)); + } + return ResultSuccess(); + } + } - Result ContentStorageImpl::Initialize(const char *path, MakeContentPathFunc content_path_func, MakePlaceHolderPathFunc placeholder_path_func, bool delay_flush, impl::RightsIdCache *rights_id_cache) { - R_TRY(this->EnsureEnabled()); - R_TRY(fs::CheckContentStorageDirectoriesExist(path)); + ContentStorageImpl::~ContentStorageImpl() { + this->InvalidateFileCache(); + } + + Result ContentStorageImpl::InitializeBase(const char *root_path) { + PathString path; + + MakeBaseContentDirectoryPath(std::addressof(path), root_path); + R_TRY(impl::EnsureDirectoryRecursively(path)); + + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); + R_TRY(impl::EnsureDirectoryRecursively(path)); - this->root_path = PathString(path); - this->make_content_path_func = *content_path_func; - this->placeholder_accessor.Initialize(this->root_path, *placeholder_path_func, delay_flush); - this->rights_id_cache = rights_id_cache; return ResultSuccess(); } - void ContentStorageImpl::Finalize() { - this->ClearContentCache(); - this->placeholder_accessor.InvalidateAll(); + Result ContentStorageImpl::CleanupBase(const char *root_path) { + PathString path; + + MakeBaseContentDirectoryPath(std::addressof(path), root_path); + R_TRY(CleanDirectoryRecursively(path)); + + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); + R_TRY(CleanDirectoryRecursively(path)); + + return ResultSuccess(); } - void ContentStorageImpl::ClearContentCache() { + Result ContentStorageImpl::VerifyBase(const char *root_path) { + PathString path; + + bool has_dir; + R_TRY(impl::HasDirectory(std::addressof(has_dir), root_path)); + R_UNLESS(has_dir, ncm::ResultContentStorageBaseNotFound()); + + bool has_registered; + MakeBaseContentDirectoryPath(std::addressof(path), root_path); + R_TRY(impl::HasDirectory(std::addressof(has_registered), path)); + + bool has_placeholder; + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); + R_TRY(impl::HasDirectory(std::addressof(has_placeholder), path)); + + R_UNLESS(has_registered || has_placeholder, ncm::ResultContentStorageBaseNotFound()); + R_UNLESS(has_registered, ncm::ResultInvalidContentStorageBase()); + R_UNLESS(has_placeholder, ncm::ResultInvalidContentStorageBase()); + + return ResultSuccess(); + } + + void ContentStorageImpl::InvalidateFileCache() { if (this->cached_content_id != InvalidContentId) { - fclose(this->content_cache_file_handle); + fs::CloseFile(this->cached_file_handle); this->cached_content_id = InvalidContentId; } } - unsigned int ContentStorageImpl::GetContentDirectoryDepth() { - if (this->make_content_path_func == static_cast(path::MakeContentPathFlat)) { - return 1; - } else if (this->make_content_path_func == static_cast(path::MakeContentPathHashByteLayered)) { - return 2; - } else if (this->make_content_path_func == static_cast(path::MakeContentPath10BitLayered)) { - return 2; - } else if (this->make_content_path_func == static_cast(path::MakeContentPathDualLayered)) { - return 3; - } - - AMS_ABORT(); - } - - Result ContentStorageImpl::OpenCachedContentFile(ContentId content_id) { + Result ContentStorageImpl::OpenContentIdFile(ContentId content_id) { R_UNLESS(this->cached_content_id != content_id, ResultSuccess()); - this->ClearContentCache(); + this->InvalidateFileCache(); - PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); + PathString path; + MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path); - R_TRY_CATCH(fs::OpenFile(&this->content_cache_file_handle, content_path, FsOpenMode_Read)) { + R_TRY_CATCH(fs::OpenFile(&this->cached_file_handle, path, fs::OpenMode_Read)) { R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultContentNotFound()) } R_END_TRY_CATCH; @@ -78,6 +223,19 @@ namespace ams::ncm { return ResultSuccess(); } + 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()); + + R_TRY(VerifyBase(path)); + + 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); + this->rights_id_cache = rights_id_cache; + + return ResultSuccess(); + } + Result ContentStorageImpl::GeneratePlaceHolderId(sf::Out out) { R_TRY(this->EnsureEnabled()); out.SetValue({util::GenerateUuid()}); @@ -87,18 +245,15 @@ namespace ams::ncm { Result ContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) { R_TRY(this->EnsureEnabled()); - PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); - - R_TRY(fs::EnsureParentDirectoryRecursively(content_path)); - R_TRY(this->placeholder_accessor.Create(placeholder_id, size)); + R_TRY(EnsureContentDirectory(content_id, this->make_content_path_func, this->root_path)); + R_TRY(this->placeholder_accessor.CreatePlaceHolderFile(placeholder_id, size)); return ResultSuccess(); } Result ContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) { R_TRY(this->EnsureEnabled()); - return this->placeholder_accessor.Delete(placeholder_id); + return this->placeholder_accessor.DeletePlaceHolderFile(placeholder_id); } Result ContentStorageImpl::HasPlaceHolder(sf::Out out, PlaceHolderId placeholder_id) { @@ -108,7 +263,7 @@ namespace ams::ncm { this->placeholder_accessor.MakePath(std::addressof(placeholder_path), placeholder_id); bool has = false; - R_TRY(fs::HasFile(&has, placeholder_path)); + R_TRY(impl::HasFile(&has, placeholder_path)); out.SetValue(has); return ResultSuccess(); @@ -118,25 +273,23 @@ namespace ams::ncm { /* Offset is too large */ R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); - R_TRY(this->placeholder_accessor.Write(placeholder_id, offset, data.GetPointer(), data.GetSize())); + return this->placeholder_accessor.WritePlaceHolderFile(placeholder_id, offset, data.GetPointer(), data.GetSize()); return ResultSuccess(); } Result ContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) { - this->ClearContentCache(); + this->InvalidateFileCache(); R_TRY(this->EnsureEnabled()); PathString placeholder_path; PathString content_path; this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); - this->GetContentPath(std::addressof(content_path), content_id); + MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - if (rename(placeholder_path, content_path) != 0) { - R_TRY_CATCH(fsdevGetLastResult()) { - R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) - R_CONVERT(ams::fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists()) - } R_END_TRY_CATCH; - } + R_TRY_CATCH(fs::RenameFile(placeholder_path, content_path)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists()) + } R_END_TRY_CATCH; return ResultSuccess(); } @@ -144,28 +297,19 @@ namespace ams::ncm { Result ContentStorageImpl::Delete(ContentId content_id) { R_TRY(this->EnsureEnabled()); - this->ClearContentCache(); + this->InvalidateFileCache(); - PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); - - if (std::remove(content_path) != 0) { - R_TRY_CATCH(fsdevGetLastResult()) { - R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultContentNotFound()) - } R_END_TRY_CATCH; - } - - return ResultSuccess(); + 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()); PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); + MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); bool has = false; - R_TRY(fs::HasFile(&has, content_path)); + R_TRY(impl::HasFile(&has, content_path)); out.SetValue(has); return ResultSuccess(); @@ -175,11 +319,12 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); + MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); Path common_path; - R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, content_path)); - out.SetValue(Path::Encode(common_path.str)); + R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path)); + + out.SetValue(common_path); return ResultSuccess(); } @@ -190,43 +335,44 @@ namespace ams::ncm { this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); Path common_path; - R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, placeholder_path)); - out.SetValue(Path::Encode(common_path.str)); + R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path)); + + out.SetValue(common_path); return ResultSuccess(); } Result ContentStorageImpl::CleanupAllPlaceHolder() { R_TRY(this->EnsureEnabled()); - PathString placeholder_root_path; this->placeholder_accessor.InvalidateAll(); - this->placeholder_accessor.MakeRootPath(std::addressof(placeholder_root_path)); - /* Nintendo uses CleanDirectoryRecursively which is 3.0.0+. - We'll just delete the directory and recreate it to support all firmwares. */ - R_TRY(fsdevDeleteDirectoryRecursively(placeholder_root_path)); - R_UNLESS(mkdir(placeholder_root_path, S_IRWXU) != -1, fsdevGetLastResult()); + PathString placeholder_dir; + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path); + + CleanDirectoryRecursively(placeholder_dir); + return ResultSuccess(); } Result ContentStorageImpl::ListPlaceHolder(sf::Out out_count, const sf::OutArray &out_buf) { R_TRY(this->EnsureEnabled()); - PathString placeholder_root_path; - this->placeholder_accessor.MakeRootPath(std::addressof(placeholder_root_path)); - const unsigned int dir_depth = this->placeholder_accessor.GetDirectoryDepth(); + 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; - R_TRY(fs::TraverseDirectory(placeholder_root_path, dir_depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, struct dirent *dir_entry) -> Result { + 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; - if (dir_entry->d_type == DT_REG) { - R_UNLESS(entry_count <= out_buf.GetSize(), ncm::ResultBufferInsufficient()); + if (entry.type == fs::DirectoryEntryType_File) { + R_UNLESS(entry_count <= max_entries, ncm::ResultBufferInsufficient()); - PlaceHolderId cur_entry_placeholder_id = {0}; - R_TRY(GetPlaceHolderIdFromDirEntry(&cur_entry_placeholder_id, dir_entry)); - out_buf[entry_count++] = cur_entry_placeholder_id; + PlaceHolderId placeholder_id; + R_TRY(PlaceHolderAccessor::GetPlaceHolderIdFromFileName(std::addressof(placeholder_id), entry.name)); + + out_buf[entry_count++] = placeholder_id; } return ResultSuccess(); @@ -239,72 +385,64 @@ namespace ams::ncm { Result ContentStorageImpl::GetContentCount(sf::Out out_count) { R_TRY(this->EnsureEnabled()); - PathString content_root_path; - this->GetContentRootPath(std::addressof(content_root_path)); - const unsigned int dir_depth = this->GetContentDirectoryDepth(); - u32 content_count = 0; + PathString path; + MakeBaseContentDirectoryPath(std::addressof(path), this->root_path); + const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func); + size_t count = 0; - R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, struct dirent *dir_entry) -> Result { + 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; - if (dir_entry->d_type == DT_REG) { - content_count++; + if (entry.type == fs::DirectoryEntryType_File) { + count++; } return ResultSuccess(); })); - out_count.SetValue(content_count); + out_count.SetValue(static_cast(count)); return ResultSuccess(); } - Result ContentStorageImpl::ListContentId(sf::Out out_count, const sf::OutArray &out_buf, u32 start_offset) { - R_UNLESS(start_offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); + Result ContentStorageImpl::ListContentId(sf::Out out_count, const sf::OutArray &out_buf, u32 offset) { + R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); - PathString content_root_path; - this->GetContentRootPath(std::addressof(content_root_path)); - - const unsigned int dir_depth = this->GetContentDirectoryDepth(); + PathString path; + MakeBaseContentDirectoryPath(std::addressof(path), this->root_path); + const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func); size_t entry_count = 0; - R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, struct dirent *dir_entry) { + 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; - if (dir_entry->d_type == DT_REG) { - /* Skip entries until we reach the start offset. */ - if (start_offset > 0) { - start_offset--; - return ResultSuccess(); - } + /* We have nothing to do if not working with a file. */ + if (entry.type != fs::DirectoryEntryType_File) { + return ResultSuccess(); + } - /* We don't necessarily expect to be able to completely fill the output buffer. */ - if (entry_count > out_buf.GetSize()) { - *should_continue = false; - return ResultSuccess(); - } + /* Skip entries until we reach the start offset. */ + if (offset > 0) { + --offset; + return ResultSuccess(); + } - size_t name_len = strlen(dir_entry->d_name); - std::optional content_id = GetContentIdFromString(dir_entry->d_name, name_len); - - /* Skip to the next entry if the id was invalid. */ - if (!content_id) { - return ResultSuccess(); - } + /* We don't necessarily expect to be able to completely fill the output buffer. */ + if (entry_count >= out_buf.GetSize()) { + *should_continue = false; + return ResultSuccess(); + } + auto content_id = GetContentIdFromString(entry.name, std::strlen(entry.name)); + if (content_id) { out_buf[entry_count++] = *content_id; } return ResultSuccess(); })); - for (size_t i = 0; i < entry_count; i++) { - char content_name[sizeof(ContentId)*2+1] = {0}; - GetStringFromContentId(content_name, out_buf[i]); - } - out_count.SetValue(static_cast(entry_count)); return ResultSuccess(); } @@ -313,17 +451,22 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); - struct stat st; + MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - R_UNLESS(stat(content_path, &st) != -1, fsdevGetLastResult()); - out_size.SetValue(st.st_size); + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), content_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + + out_size.SetValue(file_size); return ResultSuccess(); } Result ContentStorageImpl::DisableForcibly() { this->disabled = true; - this->ClearContentCache(); + this->InvalidateFileCache(); this->placeholder_accessor.InvalidateAll(); return ResultSuccess(); } @@ -331,31 +474,27 @@ namespace ams::ncm { Result ContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { R_TRY(this->EnsureEnabled()); - PathString old_content_path; - PathString new_content_path; + this->InvalidateFileCache(); + + R_TRY(EnsureContentDirectory(new_content_id, this->make_content_path_func, this->root_path)); + R_TRY(this->placeholder_accessor.EnsurePlaceHolderDirectory(placeholder_id)); + PathString placeholder_path; - - this->ClearContentCache(); - - /* Ensure the new content path is ready. */ - this->GetContentPath(std::addressof(new_content_path), new_content_id); - R_TRY(fs::EnsureParentDirectoryRecursively(new_content_path)); - - R_TRY(this->placeholder_accessor.EnsureRecursively(placeholder_id)); + PathString content_path; this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); - if (rename(old_content_path, placeholder_path) != 0) { - R_TRY_CATCH(fsdevGetLastResult()) { - R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) - R_CONVERT(ams::fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists()) - } R_END_TRY_CATCH; - } + MakeContentPath(std::addressof(content_path), old_content_id, this->make_content_path_func, this->root_path); + + R_TRY_CATCH(fs::RenameFile(content_path, placeholder_path)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists()) + } R_END_TRY_CATCH; return ResultSuccess(); } Result ContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) { R_TRY(this->EnsureEnabled()); - R_TRY(this->placeholder_accessor.SetSize(placeholder_id, size)); + R_TRY(this->placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size)); return ResultSuccess(); } @@ -365,10 +504,11 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); + MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - R_TRY(this->OpenCachedContentFile(content_id)); - R_TRY(fs::ReadFile(this->content_cache_file_handle, offset, buf.GetPointer(), buf.GetSize())); + R_TRY(this->OpenContentIdFile(content_id)); + + R_TRY(fs::ReadFile(this->cached_file_handle, offset, buf.GetPointer(), buf.GetSize())); return ResultSuccess(); } @@ -383,17 +523,10 @@ namespace ams::ncm { Result ContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out out_rights_id, PlaceHolderId placeholder_id) { R_TRY(this->EnsureEnabled()); - PathString placeholder_path; - this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + Path path; + R_TRY(this->GetPlaceHolderPath(std::addressof(path), placeholder_id)); - Path common_path; - R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, placeholder_path)); - - ncm::RightsId rights_id; - R_TRY(GetRightsId(&rights_id, common_path)); - out_rights_id.SetValue(rights_id); - - return ResultSuccess(); + return GetRightsId(out_rights_id.GetPointer(), path); } Result ContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out out_rights_id, ContentId content_id) { @@ -410,17 +543,13 @@ namespace ams::ncm { return ResultSuccess(); } - PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); - - Path common_path; - R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, content_path)); + Path path; + R_TRY(this->GetPath(std::addressof(path), content_id)); ncm::RightsId rights_id; - R_TRY(GetRightsId(&rights_id, common_path)); + R_TRY(GetRightsId(std::addressof(rights_id), path)); this->rights_id_cache->Store(content_id, rights_id); - /* Set output. */ out_rights_id.SetValue(rights_id); return ResultSuccess(); } @@ -430,39 +559,33 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); - bool is_development = false; + AMS_ABORT_UNLESS(spl::IsDevelopmentHardware()); - AMS_ABORT_UNLESS(R_SUCCEEDED(splIsDevelopment(&is_development))); - AMS_ABORT_UNLESS(is_development); + this->InvalidateFileCache(); - this->ClearContentCache(); + PathString path; + MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path); - PathString content_path; - this->GetContentPath(std::addressof(content_path), content_id); + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path.Get(), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; - FILE *f = nullptr; - R_TRY(fs::OpenFile(&f, content_path, FsOpenMode_Write)); - - ON_SCOPE_EXIT { - fclose(f); - }; - - R_TRY(fs::WriteFile(f, offset, data.GetPointer(), data.GetSize(), ams::fs::WriteOption::Flush)); + R_TRY(fs::WriteFile(file, offset, data.GetPointer(), data.GetSize(), fs::WriteOption::Flush)); return ResultSuccess(); } Result ContentStorageImpl::GetFreeSpaceSize(sf::Out out_size) { - struct statvfs st = {0}; - R_UNLESS(statvfs(this->root_path, &st) != -1, fsdevGetLastResult()); - out_size.SetValue(st.f_bfree); + s64 size; + R_TRY(fs::GetFreeSpaceSize(std::addressof(size), this->root_path)); + out_size.SetValue(size); return ResultSuccess(); } Result ContentStorageImpl::GetTotalSpaceSize(sf::Out out_size) { - struct statvfs st = {0}; - R_UNLESS(statvfs(this->root_path, &st) != -1, fsdevGetLastResult()); - out_size.SetValue(st.f_blocks); + s64 size; + R_TRY(fs::GetTotalSpaceSize(std::addressof(size), this->root_path)); + out_size.SetValue(size); return ResultSuccess(); } @@ -474,37 +597,39 @@ namespace ams::ncm { Result ContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out out_size, PlaceHolderId placeholder_id) { R_TRY(this->EnsureEnabled()); - bool found_in_cache = false; - size_t size = 0; - - R_TRY(this->placeholder_accessor.GetSize(&found_in_cache, &size, placeholder_id)); - - if (found_in_cache) { - out_size.SetValue(size); + bool found = false; + s64 file_size = 0; + R_TRY(this->placeholder_accessor.TryGetPlaceHolderFileSize(std::addressof(found), std::addressof(file_size), placeholder_id)); + if (found) { + out_size.SetValue(file_size); return ResultSuccess(); } PathString placeholder_path; - struct stat st; - this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); - R_UNLESS(stat(placeholder_path, &st) != -1, fsdevGetLastResult()); - out_size.SetValue(st.st_size); + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), placeholder_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + + out_size.SetValue(file_size); return ResultSuccess(); } Result ContentStorageImpl::RepairInvalidFileAttribute() { - PathString content_root_path; - this->GetContentRootPath(std::addressof(content_root_path)); - unsigned int dir_depth = this->GetContentDirectoryDepth(); - auto fix_file_attributes = [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, struct dirent *dir_entry) { + /* Callback for TraverseDirectory */ + using PathChecker = bool (*)(const char *); + PathChecker path_checker = nullptr; + + 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; - if (dir_entry->d_type == DT_DIR) { - if (path::IsNcaPath(current_path)) { - if (R_SUCCEEDED(fsdevSetConcatenationFileAttribute(current_path))) { + if (entry.type == fs::DirectoryEntryType_Directory) { + if (path_checker(current_path)) { + if (R_SUCCEEDED(fs::SetConcatenationFileAttribute(current_path))) { *should_retry_dir_read = true; } } @@ -513,14 +638,24 @@ namespace ams::ncm { return ResultSuccess(); }; - R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, fix_file_attributes)); + /* Fix Content */ + { + path_checker = IsContentPath; + PathString path; + MakeBaseContentDirectoryPath(std::addressof(path), this->root_path); - PathString placeholder_root_path; + R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes)); + } + + /* Fix placeholder. */ this->placeholder_accessor.InvalidateAll(); - this->placeholder_accessor.MakeRootPath(std::addressof(placeholder_root_path)); - dir_depth = this->placeholder_accessor.GetDirectoryDepth(); + { + path_checker = IsPlaceHolderPath; + PathString path; + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), this->root_path); - R_TRY(fs::TraverseDirectory(placeholder_root_path, dir_depth, fix_file_attributes)); + R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes)); + } return ResultSuccess(); } @@ -536,7 +671,7 @@ namespace ams::ncm { this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); Path common_path; - R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, placeholder_path)); + R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path)); ncm::RightsId rights_id; R_TRY(GetRightsId(&rights_id, common_path)); @@ -544,7 +679,6 @@ namespace ams::ncm { /* Set output. */ out_rights_id.SetValue(rights_id); - return ResultSuccess(); } diff --git a/stratosphere/ncm/source/ncm_content_storage_impl.hpp b/stratosphere/ncm/source/ncm_content_storage_impl.hpp index c7c3afbb6..582d7807d 100644 --- a/stratosphere/ncm/source/ncm_content_storage_impl.hpp +++ b/stratosphere/ncm/source/ncm_content_storage_impl.hpp @@ -18,38 +18,30 @@ #include #include -#include "impl/ncm_placeholder_accessor.hpp" -#include "impl/ncm_rights_cache.hpp" +#include "ncm_placeholder_accessor.hpp" +#include "ncm_rights_cache.hpp" #include "ncm_content_storage_impl_base.hpp" -#include "ncm_path_utils.hpp" +#include "ncm_fs_utils.hpp" namespace ams::ncm { class ContentStorageImpl : public ContentStorageImplBase { protected: - impl::PlaceHolderAccessor placeholder_accessor; + PlaceHolderAccessor placeholder_accessor; ContentId cached_content_id; - FILE *content_cache_file_handle; - impl::RightsIdCache *rights_id_cache; + fs::FileHandle cached_file_handle; + RightsIdCache *rights_id_cache; + public: + static Result InitializeBase(const char *root_path); + static Result CleanupBase(const char *root_path); + static Result VerifyBase(const char *root_path); public: ~ContentStorageImpl(); - Result Initialize(const char *root_path, MakeContentPathFunc content_path_func, MakePlaceHolderPathFunc placeholder_path_func, bool delay_flush, impl::RightsIdCache *rights_id_cache); - void Finalize(); + Result Initialize(const char *root_path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache); private: - void ClearContentCache(); - unsigned int GetContentDirectoryDepth(); - Result OpenCachedContentFile(ContentId content_id); - - inline void GetContentRootPath(PathString *content_root) { - path::GetContentRootPath(content_root, this->root_path); - } - - inline void GetContentPath(PathString *content_path, ContentId content_id) { - PathString root_path; - this->GetContentRootPath(std::addressof(root_path)); - this->make_content_path_func(content_path, content_id, root_path); - } + Result OpenContentIdFile(ContentId content_id); + void InvalidateFileCache(); public: virtual Result GeneratePlaceHolderId(sf::Out out) override; virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override; diff --git a/stratosphere/ncm/source/ncm_content_storage_impl_base.hpp b/stratosphere/ncm/source/ncm_content_storage_impl_base.hpp index 03f59caef..4e92ecd55 100644 --- a/stratosphere/ncm/source/ncm_content_storage_impl_base.hpp +++ b/stratosphere/ncm/source/ncm_content_storage_impl_base.hpp @@ -24,21 +24,21 @@ namespace ams::ncm { NON_MOVEABLE(ContentStorageImplBase); protected: PathString root_path; - MakeContentPathFunc make_content_path_func; + MakeContentPathFunction make_content_path_func; bool disabled; protected: ContentStorageImplBase() { /* ... */ } protected: - Result EnsureEnabled() { + Result EnsureEnabled() const { R_UNLESS(!this->disabled, ncm::ResultInvalidContentStorage()); return ResultSuccess(); } static Result GetRightsId(ncm::RightsId *out_rights_id, const Path &path) { if (hos::GetVersion() >= hos::Version_300) { - R_TRY(ams::fs::GetRightsId(std::addressof(out_rights_id->id), std::addressof(out_rights_id->key_generation), path.str)); + R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), std::addressof(out_rights_id->key_generation), path.str)); } else { - R_TRY(ams::fs::GetRightsId(std::addressof(out_rights_id->id), path.str)); + R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), path.str)); out_rights_id->key_generation = 0; } return ResultSuccess(); diff --git a/stratosphere/ncm/source/ncm_fs.cpp b/stratosphere/ncm/source/ncm_fs.cpp deleted file mode 100644 index def06f20e..000000000 --- a/stratosphere/ncm/source/ncm_fs.cpp +++ /dev/null @@ -1,323 +0,0 @@ -/* - * 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 . - */ - -#include -#include -#include - -#include "ncm_fs.hpp" -#include "ncm_path_utils.hpp" - -namespace ams::ncm::fs { - - Result OpenFile(FILE** out, const char *path, u32 mode) { - bool has = false; - - /* Manually check if the file already exists, so it doesn't get created automatically. */ - R_TRY(HasFile(&has, path)); - R_UNLESS(has, ams::fs::ResultPathNotFound()); - - const char *fopen_mode = ""; - - if (mode & FsOpenMode_Write) { - fopen_mode = "r+b"; - } else if (mode & FsOpenMode_Read) { - fopen_mode = "rb"; - } - FILE *f = fopen(path, fopen_mode); - R_UNLESS(f != nullptr, fsdevGetLastResult()); - - *out = f; - return ResultSuccess(); - } - - Result WriteFile(FILE *f, size_t offset, const void *buffer, size_t size, ams::fs::WriteOption option) { - R_UNLESS(fseek(f, 0, SEEK_END) == 0, fsdevGetLastResult()); - size_t existing_size = ftell(f); - - R_UNLESS(offset + size <= existing_size, ams::fs::ResultFileExtensionWithoutOpenModeAllowAppend()); - R_UNLESS(fseek(f, offset, SEEK_SET) == 0, fsdevGetLastResult()); - R_UNLESS(fwrite(buffer, 1, size, f) == size, fsdevGetLastResult()); - - if (option.HasFlushFlag()) { - fflush(f); - } - - return ResultSuccess(); - } - - Result ReadFile(FILE *f, size_t offset, void *buffer, size_t size) { - R_UNLESS(fseek(f, offset, SEEK_SET) == 0, fsdevGetLastResult()); - R_UNLESS(fread(buffer, 1, size, f) == size, fsdevGetLastResult()); - R_UNLESS(!ferror(f), fsdevGetLastResult()); - return ResultSuccess(); - } - - Result HasFile(bool *out, const char *path) { - struct stat st; - - if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) { - *out = true; - } else { - R_TRY_CATCH(fsdevGetLastResult()) { - R_CATCH(ams::fs::ResultPathNotFound) { - *out = false; - } - } R_END_TRY_CATCH; - } - - return ResultSuccess(); - } - - Result HasDirectory(bool *out, const char *path) { - struct stat st; - - if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { - *out = true; - } else { - R_TRY_CATCH(fsdevGetLastResult()) { - R_CATCH(ams::fs::ResultPathNotFound) { - *out = false; - } - } R_END_TRY_CATCH; - } - - return ResultSuccess(); - } - - Result CheckContentStorageDirectoriesExist(const char *path) { - const PathString root_path(path); - - bool has_root = false; - R_TRY(HasDirectory(&has_root, root_path)); - R_UNLESS(has_root, ncm::ResultContentStorageBaseNotFound()); - - PathString content_root; - path::GetContentRootPath(std::addressof(content_root), root_path); - bool has_content_root = false; - R_TRY(HasDirectory(&has_content_root, content_root)); - R_UNLESS(has_content_root, ncm::ResultInvalidContentStorageBase()); - - PathString placeholder_root; - path::GetPlaceHolderRootPath(std::addressof(placeholder_root), root_path); - bool has_placeholder_root = false; - R_TRY(HasDirectory(&has_placeholder_root, placeholder_root)); - R_UNLESS(has_placeholder_root, ncm::ResultInvalidContentStorageBase()); - - return ResultSuccess(); - } - - Result EnsureContentAndPlaceHolderRoot(const char *path) { - const PathString root_path(path); - - PathString content_root; - path::GetContentRootPath(std::addressof(content_root), root_path); - R_TRY(EnsureDirectoryRecursively(content_root)); - - PathString placeholder_root; - path::GetPlaceHolderRootPath(std::addressof(placeholder_root), root_path); - R_TRY(EnsureDirectoryRecursively(placeholder_root)); - - return ResultSuccess(); - } - - Result EnsureDirectoryRecursively(const char *dir_path) { - R_TRY(EnsureRecursively(dir_path)); - if (mkdir(dir_path, S_IRWXU) == -1) { - R_TRY_CATCH(fsdevGetLastResult()) { - R_CATCH(ams::fs::ResultPathAlreadyExists) { - /* If the path already exists, that's okay. Anything else is an error. */ - } - } R_END_TRY_CATCH; - } - return ResultSuccess(); - } - - Result EnsureRecursively(const char *path) { - R_UNLESS(path, ams::fs::ResultNullptrArgument()); - - size_t path_len = strlen(path); - char working_path_buf[FS_MAX_PATH] = {0}; - - R_UNLESS(path_len + 1 < FS_MAX_PATH, ncm::ResultAllocationFailed()); - strncpy(working_path_buf + 1, path, ams::fs::EntryNameLengthMax); - - if (path_len != 0) { - for (size_t i = 0; i < path_len; i++) { - if (i != 0 && working_path_buf[i + 1] == '/' && working_path_buf[i] != ':') { - /* Temporarily make the path terminate before the '/' */ - working_path_buf[i + 1] = 0; - if (mkdir(working_path_buf + 1, S_IRWXU) == -1) { - R_TRY_CATCH(fsdevGetLastResult()) { - R_CATCH(ams::fs::ResultPathAlreadyExists) { - /* If the path already exists, that's okay. Anything else is an error. */ - } - } R_END_TRY_CATCH; - } - - /* Restore the path to its former state */ - working_path_buf[i + 1] = '/'; - } - } - } - - return ResultSuccess(); - } - - Result EnsureParentDirectoryRecursively(const char *path) { - return EnsureRecursively(path); - } - - Result GetGameCardHandle(FsGameCardHandle *out_handle) { - FsDeviceOperator devop; - R_TRY(fsOpenDeviceOperator(&devop)); - - /* Ensure we close even on early return. */ - ON_SCOPE_EXIT { fsDeviceOperatorClose(&devop); }; - - R_TRY(fsDeviceOperatorGetGameCardHandle(&devop, out_handle)); - return ResultSuccess(); - } - - static u32 g_mount_index = 0; - static os::Mutex g_mount_index_lock; - - MountName CreateUniqueMountName() { - std::scoped_lock lk(g_mount_index_lock); - MountName mount_name; - g_mount_index++; - snprintf(mount_name.name, sizeof(MountName), "@ncm%08x", g_mount_index); - return mount_name; - } - - Result GetMountNameFromPath(MountName *mount_name, const char *path) { - const char *unqual_path = strchr(path, ':'); - - /* We should be given a qualified path. */ - R_UNLESS(unqual_path, ams::fs::ResultInvalidMountName()); - R_UNLESS(unqual_path <= path + 0xf, ams::fs::ResultInvalidMountName()); - - strncpy(mount_name->name, path, unqual_path - path); - return ResultSuccess(); - } - - Result MountSystemSaveData(const char *mount_point, FsSaveDataSpaceId space_id, u64 save_id) { - R_UNLESS(mount_point, ams::fs::ResultNullptrArgument()); - - FsSaveDataAttribute save = { - .system_save_data_id = save_id, - .save_data_type = FsSaveDataType_System, - }; - - FsFileSystem fs; - R_TRY(fsOpenSaveDataFileSystemBySystemSaveDataId(&fs, space_id, &save)); - AMS_ABORT_UNLESS(fsdevMountDevice(mount_point, fs) != -1); - - return ResultSuccess(); - } - - constexpr const char *SystemContentMountName = "@SystemContent"; - constexpr const char *UserContentMountName = "@UserContent"; - constexpr const char *SdCardContentMountName = "@SdCardContent"; - constexpr const char *GameCardMountNameBase = "@Gc"; - - constexpr const char *GameCardPartitionLetters[3] = { "U", "N", "S" }; - - /* Maps mount names to their common mount names. */ - std::map g_mount_content_storage; - - Result MountContentStorage(const char *mount_point, FsContentStorageId id) { - R_UNLESS(mount_point, ams::fs::ResultNullptrArgument()); - - FsFileSystem fs; - R_TRY(fsOpenContentStorageFileSystem(&fs, id)); - AMS_ABORT_UNLESS(fsdevMountDevice(mount_point, fs) != -1); - - switch (id) { - case FsContentStorageId_System: - g_mount_content_storage[mount_point] = SystemContentMountName; - break; - - case FsContentStorageId_User: - g_mount_content_storage[mount_point] = UserContentMountName; - break; - - case FsContentStorageId_SdCard: - g_mount_content_storage[mount_point] = SdCardContentMountName; - break; - - default: - AMS_ABORT(); - }; - return ResultSuccess(); - } - - Result MountGameCardPartition(const char *mount_point, const FsGameCardHandle handle, FsGameCardPartition partition) { - AMS_ABORT_UNLESS(partition <= 2); - - FsFileSystem fs; - R_TRY(fsOpenGameCardFileSystem(&fs, &handle, partition)); - AMS_ABORT_UNLESS(fsdevMountDevice(mount_point, fs) != -1); - - MountName mount = {0}; - snprintf(mount.name, sizeof(MountName), "%s%s%08x", GameCardMountNameBase, GameCardPartitionLetters[partition], handle.value); - g_mount_content_storage[mount_point] = mount.name; - return ResultSuccess(); - } - - Result Unmount(const char *mount_point) { - R_UNLESS(mount_point, ams::fs::ResultNullptrArgument()); - /* Erase any content storage mappings which may potentially exist. */ - g_mount_content_storage.erase(mount_point); - AMS_ABORT_UNLESS(fsdevUnmountDevice(mount_point) != -1); - return ResultSuccess(); - } - - Result ConvertToFsCommonPath(char *out_common_path, size_t out_len, const char *path) { - R_UNLESS(out_common_path, ams::fs::ResultNullptrArgument()); - R_UNLESS(path, ams::fs::ResultNullptrArgument()); - - MountName mount_name = {0}; - R_TRY(GetMountNameFromPath(&mount_name, path)); - R_UNLESS(fsdevGetDeviceFileSystem(mount_name.name), ams::fs::ResultNotMounted()); - R_UNLESS(g_mount_content_storage.find(mount_name.name) != g_mount_content_storage.end(), ams::fs::ResultNotMounted()); - - char translated_path[FS_MAX_PATH] = {0}; - std::string common_mount_name = g_mount_content_storage[mount_name.name]; - - AMS_ABORT_UNLESS(fsdevTranslatePath(path, NULL, translated_path) != -1); - snprintf(out_common_path, out_len, "%s:%s", common_mount_name.c_str(), translated_path); - return ResultSuccess(); - } - - Result GetSaveDataFlags(u32 *out_flags, u64 save_id) { - FsSaveDataExtraData extra_data; - - R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id)); - *out_flags = extra_data.flags; - return ResultSuccess(); - } - - Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags) { - FsSaveDataExtraData extra_data; - - R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id)); - extra_data.flags = flags; - R_TRY(fsWriteSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), space_id, save_id)); - return ResultSuccess(); - } - -} diff --git a/stratosphere/ncm/source/ncm_fs.hpp b/stratosphere/ncm/source/ncm_fs.hpp deleted file mode 100644 index e743c5a07..000000000 --- a/stratosphere/ncm/source/ncm_fs.hpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 -#include -#include - -namespace ams::ncm::fs { - - Result OpenFile(FILE** out, const char *path, u32 mode); - Result WriteFile(FILE *f, size_t offset, const void *buffer, size_t size, ams::fs::WriteOption option); - Result ReadFile(FILE *f, size_t offset, void *buffer, size_t size); - - Result HasFile(bool *out, const char *path); - Result HasDirectory(bool *out, const char *path); - - Result CheckContentStorageDirectoriesExist(const char *root_path); - Result EnsureContentAndPlaceHolderRoot(const char *root_path); - - Result EnsureDirectoryRecursively(const char *dir_path); - Result EnsureRecursively(const char *path); - /* Create all parent directories for a file path */ - Result EnsureParentDirectoryRecursively(const char *path); - - Result GetGameCardHandle(FsGameCardHandle *out_handle); - - MountName CreateUniqueMountName(); - Result GetMountNameFromPath(MountName *mount_name, const char *path); - - Result MountSystemSaveData(const char *mount_point, FsSaveDataSpaceId space_id, u64 save_id); - Result MountContentStorage(const char *mount_point, FsContentStorageId id); - Result MountGameCardPartition(const char *mount_point, const FsGameCardHandle handle, FsGameCardPartition partition); - Result Unmount(const char *mount_point); - Result ConvertToFsCommonPath(char *out_common_path, size_t len, const char *path); - - Result GetSaveDataFlags(u32 *out_flags, u64 save_id); - Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags); - - template - Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) { - DIR *dir; - struct dirent *dir_entry = nullptr; - R_UNLESS(max_level >= 1, ResultSuccess()); - - bool retry_dir_read = true; - while (retry_dir_read) { - retry_dir_read = false; - - R_UNLESS((dir = opendir(root_path)) != nullptr, fsdevGetLastResult()); - ON_SCOPE_EXIT { closedir(dir); }; - - while ((dir_entry = readdir(dir)) != nullptr) { - if (strcmp(dir_entry->d_name, ".") == 0 || strcmp(dir_entry->d_name, "..") == 0) { - continue; - } - - char current_path[FS_MAX_PATH]; - AMS_ABORT_UNLESS(snprintf(current_path, ams::fs::EntryNameLengthMax, "%s/%s", root_path, dir_entry->d_name) >= 0); - - bool should_continue = true; - bool should_retry_dir_read = false; - R_TRY(f(&should_continue, &should_retry_dir_read, current_path, dir_entry)); - - /* If the provided function wishes to terminate immediately, we should respect it. */ - if (!should_continue) { - *out_should_continue = false; - return ResultSuccess(); - } - if (should_retry_dir_read) { - retry_dir_read = true; - break; - } - - if (dir_entry->d_type == DT_DIR) { - R_TRY(TraverseDirectory(&should_continue, current_path, max_level-1, f)); - - if (!should_continue) { - *out_should_continue = false; - return ResultSuccess(); - } - } - } - } - - return ResultSuccess(); - }; - - template - Result TraverseDirectory(const char *root_path, int max_level, F f) { - bool should_continue = false; - return TraverseDirectory(&should_continue, root_path, max_level, f); - } - -} diff --git a/stratosphere/ncm/source/ncm_fs_utils.cpp b/stratosphere/ncm/source/ncm_fs_utils.cpp new file mode 100644 index 000000000..c395893ce --- /dev/null +++ b/stratosphere/ncm/source/ncm_fs_utils.cpp @@ -0,0 +1,175 @@ +/* + * 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 . + */ + +#include "ncm_fs_utils.hpp" + +namespace ams::ncm::impl { + + namespace { + + Result EnsureDirectory(const char *path) { + /* Create the path, and allow it to already exist. */ + R_TRY_CATCH(fs::CreateDirectory(path)) { + R_CONVERT(fs::ResultPathAlreadyExists, ResultSuccess()) + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + + Result EnsureDirectoryRecursivelyImpl(const char *path, bool create_last) { + char work_buf[fs::EntryNameLengthMax]; + + /* Ensure the path is not too long. */ + const size_t len = std::strlen(path); + R_UNLESS(len + 1 <= sizeof(work_buf), ResultAllocationFailed()); + + /* Copy in the path. */ + std::strncpy(work_buf, path, sizeof(work_buf)); + + /* Create all but the last directory. */ + for (size_t i = 0; i < len; i++) { + if (i > 0 && fs::PathTool::IsSeparator(work_buf[i]) && !fs::PathTool::IsDriveSeparator(work_buf[i-1])) { + work_buf[i] = fs::StringTraits::NullTerminator; + R_TRY(EnsureDirectory(work_buf)); + work_buf[i] = fs::StringTraits::DirectorySeparator; + } + } + + /* Create the last directory if requested. */ + if (create_last) { + R_TRY(EnsureDirectory(path)); + } + + return ResultSuccess(); + } + + Result HasEntry(bool *out, const char *path, fs::DirectoryEntryType type) { + /* Set out to false initially. */ + *out = false; + + /* Try to get the entry type. */ + fs::DirectoryEntryType entry_type; + R_TRY_CATCH(fs::GetEntryType(std::addressof(entry_type), path)) { + /* If the path doesn't exist, nothing has gone wrong. */ + R_CONVERT(fs::ResultPathNotFound, ResultSuccess()); + } R_END_TRY_CATCH; + + /* We succeeded. */ + *out = entry_type == type; + return ResultSuccess(); + } + + std::atomic g_mount_name_count; + + } + + Result HasFile(bool *out, const char *path) { + return HasEntry(out, path, fs::DirectoryEntryType_File); + } + + Result HasDirectory(bool *out, const char *path) { + return HasEntry(out, path, fs::DirectoryEntryType_Directory); + } + + Result EnsureDirectoryRecursively(const char *path) { + return EnsureDirectoryRecursivelyImpl(path, true); + } + + Result EnsureParentDirectoryRecursively(const char *path) { + return EnsureDirectoryRecursivelyImpl(path, false); + } + + bool PathView::HasPrefix(std::string_view prefix) const { + return this->path.compare(0, prefix.length(), prefix) == 0; + } + + bool PathView::HasSuffix(std::string_view suffix) const { + return this->path.compare(this->path.length() - suffix.length(), suffix.length(), suffix) == 0; + } + + std::string_view PathView::GetFileName() const { + auto pos = this->path.find_last_of("/"); + return pos != std::string_view::npos ? this->path.substr(pos + 1) : this->path; + } + + MountName CreateUniqueMountName() { + MountName name = {}; + std::snprintf(name.str, sizeof(name.str), "@ncm%08x", g_mount_name_count.fetch_add(1)); + return name; + } + + RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name) { + RootDirectoryPath path = {}; + std::snprintf(path.str, sizeof(path.str), "%s:/", mount_name.str); + + return path; + } + + Result CopyFile(const char *dst_path, const char *src_path) { + fs::FileHandle src_file, dst_file; + + /* Open the source file and get its size. */ + R_TRY(fs::OpenFile(std::addressof(src_file), src_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(src_file); }; + + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), src_file)); + + /* Create the destination file. */ + R_TRY(fs::CreateFile(dst_path, file_size)); + + /* Open the destination file. */ + R_TRY(fs::OpenFile(std::addressof(dst_file), dst_path, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(dst_file); }; + + /* Allocate a buffer with which to copy. */ + constexpr size_t BufferSize = 4_KB; + AutoBuffer buffer; + R_TRY(buffer.Initialize(BufferSize)); + + /* Repeatedly read until we've copied all the data. */ + s64 offset = 0; + while (offset < file_size) { + const size_t read_size = std::min(static_cast(file_size - offset), buffer.GetSize()); + R_TRY(fs::ReadFile(src_file, offset, buffer.Get(), read_size)); + R_TRY(fs::WriteFile(dst_file, offset, buffer.Get(), read_size, fs::WriteOption::None)); + offset += read_size; + } + + /* Flush the destination file. */ + R_TRY(fs::FlushFile(dst_file)); + + return ResultSuccess(); + } + + Result GetSaveDataFlags(u32 *out_flags, u64 save_id) { + FsSaveDataExtraData extra_data; + + R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id)); + *out_flags = extra_data.flags; + return ResultSuccess(); + } + + Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags) { + FsSaveDataExtraData extra_data; + + R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id)); + extra_data.flags = flags; + R_TRY(fsWriteSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), space_id, save_id)); + return ResultSuccess(); + } + +} diff --git a/stratosphere/ncm/source/ncm_path_utils.hpp b/stratosphere/ncm/source/ncm_fs_utils.hpp similarity index 57% rename from stratosphere/ncm/source/ncm_path_utils.hpp rename to stratosphere/ncm/source/ncm_fs_utils.hpp index edbbd3c21..ea5e4d617 100644 --- a/stratosphere/ncm/source/ncm_path_utils.hpp +++ b/stratosphere/ncm/source/ncm_fs_utils.hpp @@ -18,24 +18,19 @@ #include #include -namespace ams::ncm::path { +namespace ams::ncm::impl { - inline void GetContentRootPath(PathString *content_root, const PathString &root_path) { - content_root->SetFormat("%s%s", root_path.Get(), "/registered"); - } + Result HasFile(bool *out, const char *path); + Result HasDirectory(bool *out, const char *path); - inline void GetPlaceHolderRootPath(PathString *placeholder_root, const PathString &root_path) { - placeholder_root->SetFormat("%s%s", root_path.Get(), "/placehld"); - } + Result EnsureDirectoryRecursively(const char *path); + Result EnsureParentDirectoryRecursively(const char *path); - void GetContentMetaPath(PathString *out, ContentId content_id, MakeContentPathFunc path_func, const PathString &root_path); - void GetContentFileName(char *out, ContentId content_id); - void GetPlaceHolderFileName(char *out, PlaceHolderId placeholder_id); - bool IsNcaPath(const char *path); + Result CopyFile(const char *dst_path, const char *src_path); class PathView { private: - std::string_view path; /* Nintendo uses nn::util::string_view here. */ + std::string_view path; /* Nintendo uses util::string_view here. */ public: PathView(std::string_view p) : path(p) { /* ...*/ } bool HasPrefix(std::string_view prefix) const; @@ -43,4 +38,15 @@ namespace ams::ncm::path { std::string_view GetFileName() const; }; + struct MountName { + char str[fs::MountNameLengthMax + 1]; + }; + + struct RootDirectoryPath { + char str[fs::MountNameLengthMax + 3]; /* mount name + :/ */ + }; + + MountName CreateUniqueMountName(); + RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name); + } diff --git a/stratosphere/ncm/source/ncm_make_path.cpp b/stratosphere/ncm/source/ncm_make_path.cpp index 1f2f6e7cb..fb5658006 100644 --- a/stratosphere/ncm/source/ncm_make_path.cpp +++ b/stratosphere/ncm/source/ncm_make_path.cpp @@ -15,70 +15,113 @@ */ #include "ncm_make_path.hpp" -#include "ncm_path_utils.hpp" +#include "ncm_content_id_utils.hpp" -namespace ams::ncm::path { +namespace ams::ncm { namespace { - u16 Get16BitSha256HashPrefix(util::Uuid uuid) { - u8 hash[SHA256_HASH_SIZE]; - sha256CalculateHash(hash, uuid.data, sizeof(util::Uuid)); + void MakeContentName(PathString *out, ContentId id) { + out->SetFormat("%s.nca", GetContentIdString(id).data); + } + + void MakePlaceHolderName(PathString *out, PlaceHolderId id) { + auto &bytes = id.uuid.data; + char tmp[3]; + for (size_t i = 0; i < sizeof(bytes); i++) { + std::snprintf(tmp, util::size(tmp), "%02x", bytes[i]); + out->Append(tmp); + } + out->Append(".nca"); + } + + u16 Get16BitSha256HashPrefix(ContentId id) { + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id)); return static_cast(hash[0]) | (static_cast(hash[1]) << 8); } - u8 Get8BitSha256HashPrefix(util::Uuid uuid) { - u8 hash[SHA256_HASH_SIZE]; - sha256CalculateHash(hash, uuid.data, sizeof(util::Uuid)); + u8 Get8BitSha256HashPrefix(ContentId id) { + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id)); + return hash[0]; + } + + u8 Get8BitSha256HashPrefix(PlaceHolderId id) { + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id)); return hash[0]; } } - void MakeContentPathFlat(PathString *out, ContentId content_id, const PathString &root) { - Path content_name; - GetContentFileName(content_name.str, content_id); - out->SetFormat("%s/%s", root.Get(), content_name.str); + void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path) { + PathString content_name; + MakeContentName(std::addressof(content_name), content_id); + out->SetFormat("%s/%s", root_path, content_name.Get()); } - void MakeContentPathDualLayered(PathString *out, ContentId content_id, const PathString &root) { - const u16 hash = Get16BitSha256HashPrefix(content_id.uuid); - const u32 hash_lower = (hash >> 4) & 0x3f; + void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path) { + const u16 hash = Get16BitSha256HashPrefix(content_id); const u32 hash_upper = (hash >> 10) & 0x3f; + const u32 hash_lower = (hash >> 4) & 0x3f; - Path content_name; - GetContentFileName(content_name.str, content_id); - out->SetFormat("%s/%08X/%08X/%s", root.Get(), hash_upper, hash_lower, content_name.str); + PathString content_name; + MakeContentName(std::addressof(content_name), content_id); + out->SetFormat("%s/%08X/%08X/%s", root_path, hash_upper, hash_lower, content_name.Get()); } - void MakeContentPath10BitLayered(PathString *out, ContentId content_id, const PathString &root) { - const u32 hash = (Get16BitSha256HashPrefix(content_id.uuid) >> 6) & 0x3FF; + void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path) { + const u32 hash = (Get16BitSha256HashPrefix(content_id) >> 6) & 0x3FF; - Path content_name; - GetContentFileName(content_name.str, content_id); - out->SetFormat("%s/%08X/%s", root.Get(), hash, content_name.str); + PathString content_name; + MakeContentName(std::addressof(content_name), content_id); + out->SetFormat("%s/%08X/%s", root_path, hash, content_name.Get()); } - void MakeContentPathHashByteLayered(PathString *out, ContentId content_id, const PathString &root) { - const u32 hash_byte = static_cast(Get8BitSha256HashPrefix(content_id.uuid)); + void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path) { + const u32 hash_byte = static_cast(Get8BitSha256HashPrefix(content_id)); - Path content_name; - GetContentFileName(content_name.str, content_id); - out->SetFormat("%s/%08X/%s", root.Get(), hash_byte, content_name.str); + PathString content_name; + MakeContentName(std::addressof(content_name), content_id); + out->SetFormat("%s/%08X/%s", root_path, hash_byte, content_name.Get()); } - void MakePlaceHolderPathFlat(PathString *out, PlaceHolderId placeholder_id, const PathString &root) { - Path placeholder_name; - GetPlaceHolderFileName(placeholder_name.str, placeholder_id); - out->SetFormat("%s/%s", root.Get(), placeholder_name.str); + size_t GetHierarchicalContentDirectoryDepth(MakeContentPathFunction func) { + if (func == MakeFlatContentFilePath) { + return 1; + } else if (func == MakeSha256HierarchicalContentFilePath_ForFat4KCluster) { + return 3; + } else if (func == MakeSha256HierarchicalContentFilePath_ForFat16KCluster || + func == MakeSha256HierarchicalContentFilePath_ForFat32KCluster) { + return 2; + } else { + AMS_ABORT(); + } } - void MakePlaceHolderPathHashByteLayered(PathString *out, PlaceHolderId placeholder_id, const PathString &root) { - const u32 hash_byte = static_cast(Get8BitSha256HashPrefix(placeholder_id.uuid)); + void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path) { + PathString placeholder_name; + MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id); + out->SetFormat("%s/%s", root_path, placeholder_name.Get()); + } - Path placeholder_name; - GetPlaceHolderFileName(placeholder_name.str, placeholder_id); - out->SetFormat("%s/%08X/%s", root.Get(), hash_byte, placeholder_name.str); + void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_path) { + const u32 hash_byte = static_cast(Get8BitSha256HashPrefix(placeholder_id)); + + PathString placeholder_name; + MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id); + out->SetFormat("%s/%08X/%s", root_path, hash_byte, placeholder_name.Get()); + } + + size_t GetHierarchicalPlaceHolderDirectoryDepth(MakePlaceHolderPathFunction func) { + if (func == MakeFlatPlaceHolderFilePath) { + return 1; + } else if (func == MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster) { + return 2; + } else { + AMS_ABORT(); + } } } diff --git a/stratosphere/ncm/source/ncm_make_path.hpp b/stratosphere/ncm/source/ncm_make_path.hpp index 510f380e0..b4419bfdc 100644 --- a/stratosphere/ncm/source/ncm_make_path.hpp +++ b/stratosphere/ncm/source/ncm_make_path.hpp @@ -18,14 +18,18 @@ #include #include -namespace ams::ncm::path { +namespace ams::ncm { - void MakeContentPathFlat(PathString *out, ContentId content_id, const PathString &root); - void MakeContentPathHashByteLayered(PathString *out, ContentId content_id, const PathString &root); - void MakeContentPath10BitLayered(PathString *out, ContentId content_id, const PathString &root); - void MakeContentPathDualLayered(PathString *out, ContentId content_id, const PathString &root); + void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path); + void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path); + void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path); + void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path); - void MakePlaceHolderPathFlat(PathString *out, PlaceHolderId placeholder_id, const PathString &root); - void MakePlaceHolderPathHashByteLayered(PathString *out, PlaceHolderId placeholder_id, const PathString &root); + size_t GetHierarchicalContentDirectoryDepth(MakeContentPathFunction func); + + void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path); + void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_pathroot); + + size_t GetHierarchicalPlaceHolderDirectoryDepth(MakePlaceHolderPathFunction func); } diff --git a/stratosphere/ncm/source/ncm_path_utils.cpp b/stratosphere/ncm/source/ncm_path_utils.cpp deleted file mode 100644 index 998dc12e5..000000000 --- a/stratosphere/ncm/source/ncm_path_utils.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 . - */ - -#include "ncm_path_utils.hpp" -#include "ncm_utils.hpp" - -namespace ams::ncm::path { - - void GetContentMetaPath(PathString *out, ContentId content_id, MakeContentPathFunc path_func, const PathString &root_path) { - PathString content_path; - path_func(std::addressof(content_path), content_id, root_path); - - out->Set(content_path.GetSubstring(0, content_path.GetLength() - 4)); - out->Append(".cnmt.nca"); - } - - void GetContentFileName(char *out, ContentId content_id) { - char content_name[sizeof(ContentId)*2+1] = {0}; - GetStringFromContentId(content_name, content_id); - snprintf(out, ams::fs::EntryNameLengthMax, "%s%s", content_name, ".nca"); - } - - void GetPlaceHolderFileName(char *out, PlaceHolderId placeholder_id) { - char placeholder_name[sizeof(PlaceHolderId)*2+1] = {0}; - GetStringFromPlaceHolderId(placeholder_name, placeholder_id); - snprintf(out, ams::fs::EntryNameLengthMax, "%s%s", placeholder_name, ".nca"); - } - - bool IsNcaPath(const char *path) { - PathView path_view(path); - - if (!path_view.HasSuffix(".nca")) { - return false; - } - - std::string_view file_name = path_view.GetFileName(); - - if (file_name.length() != 0x24) { - return false; - } - - for (size_t i = 0; i < sizeof(util::Uuid)*2; i++) { - if (!std::isxdigit(file_name.at(i))) { - return false; - } - } - - return true; - } - - bool PathView::HasPrefix(std::string_view prefix) const { - return this->path.compare(0, prefix.length(), prefix) == 0; - } - - bool PathView::HasSuffix(std::string_view suffix) const { - return this->path.compare(this->path.length() - suffix.length(), suffix.length(), suffix) == 0; - } - - std::string_view PathView::GetFileName() const { - return this->path.substr(this->path.find_last_of("/") + 1); - } - -} diff --git a/stratosphere/ncm/source/ncm_placeholder_accessor.cpp b/stratosphere/ncm/source/ncm_placeholder_accessor.cpp new file mode 100644 index 000000000..c9df79dae --- /dev/null +++ b/stratosphere/ncm/source/ncm_placeholder_accessor.cpp @@ -0,0 +1,232 @@ +/* + * 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 . + */ + +#include "ncm_placeholder_accessor.hpp" +#include "ncm_make_path.hpp" + +namespace ams::ncm { + + namespace { + + constexpr inline const char * const BasePlaceHolderDirectory = "/registered"; + + constexpr inline const char * const PlaceHolderExtension = ".nca"; + constexpr inline size_t PlaceHolderExtensionLength = 4; + + constexpr inline size_t PlaceHolderFileNameLengthWithoutExtension = 2 * sizeof(PlaceHolderId); + constexpr inline size_t PlaceHolderFileNameLength = PlaceHolderFileNameLengthWithoutExtension + PlaceHolderExtensionLength; + + void MakeBasePlaceHolderDirectoryPath(PathString *out, const char *root_path) { + out->SetFormat("%s%s", root_path, BasePlaceHolderDirectory); + } + + void MakePlaceHolderFilePath(PathString *out, PlaceHolderId id, MakePlaceHolderPathFunction func, const char *root_path) { + PathString path; + MakeBasePlaceHolderDirectoryPath(std::addressof(path), root_path); + func(out, id, path); + } + + ALWAYS_INLINE Result ConvertNotFoundResult(Result r) { + R_TRY_CATCH(r) { + R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + } R_END_TRY_CATCH; + return ResultSuccess(); + } + + } + + void PlaceHolderAccessor::MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const { + MakePlaceHolderFilePath(out_placeholder_path, placeholder_id, this->make_placeholder_path_func, *this->root_path); + } + + void PlaceHolderAccessor::MakeBaseDirectoryPath(PathString *out, const char *root_path) { + MakeBasePlaceHolderDirectoryPath(out, root_path); + } + + Result PlaceHolderAccessor::EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id) { + PathString path; + this->MakePath(std::addressof(path), placeholder_id); + return impl::EnsureParentDirectoryRecursively(path); + } + + Result PlaceHolderAccessor::GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name) { + R_UNLESS(strnlen(name, PlaceHolderFileNameLength) == PlaceHolderFileNameLength, ncm::ResultInvalidPlaceHolderFile()); + R_UNLESS(strncmp(name + PlaceHolderFileNameLengthWithoutExtension, PlaceHolderExtension, PlaceHolderExtensionLength) == 0, ncm::ResultInvalidPlaceHolderFile()); + + PlaceHolderId placeholder_id = {}; + for (size_t i = 0; i < sizeof(placeholder_id); i++) { + char tmp[3]; + strlcpy(tmp, name + i * 2, sizeof(tmp)); + + char *err = nullptr; + reinterpret_cast(std::addressof(placeholder_id))[i] = static_cast(std::strtoul(tmp, std::addressof(err), 16)); + R_UNLESS(*err == '\x00', ncm::ResultInvalidPlaceHolderFile()); + } + + *out = placeholder_id; + return ResultSuccess(); + } + + Result PlaceHolderAccessor::Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) { + /* Try to load from the cache. */ + R_UNLESS(!this->LoadFromCache(out_handle, placeholder_id), ResultSuccess()); + + PathString placeholder_path; + this->MakePath(std::addressof(placeholder_path), placeholder_id); + + 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); + + CacheEntry *entry = this->FindInCache(placeholder_id); + if (!entry) { + return false; + } + + entry->id = InvalidPlaceHolderId; + *out_handle = entry->handle; + return true; + } + + void PlaceHolderAccessor::StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle) { + std::scoped_lock lk(this->cache_mutex); + + CacheEntry *entry = this->GetFreeEntry(); + entry->id = placeholder_id; + entry->handle = handle; + entry->counter = this->cur_counter++; + } + + void PlaceHolderAccessor::Invalidate(CacheEntry *entry) { + if (entry != nullptr) { + fs::FlushFile(entry->handle); + fs::CloseFile(entry->handle); + entry->id = InvalidPlaceHolderId; + } + } + + PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) { + if (placeholder_id == InvalidPlaceHolderId) { + return nullptr; + } + + for (size_t i = 0; i < MaxCacheEntries; i++) { + if (placeholder_id == this->caches[i].id) { + return &this->caches[i]; + } + } + return nullptr; + } + + PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::GetFreeEntry() { + /* Try to find an already free entry. */ + for (size_t i = 0; i < MaxCacheEntries; i++) { + if (this->caches[i].id == InvalidPlaceHolderId) { + return std::addressof(this->caches[i]); + } + } + + /* Get the oldest entry. */ + CacheEntry *entry = std::addressof(this->caches[0]); + for (size_t i = 1; i < MaxCacheEntries; i++) { + if (entry->counter < this->caches[i].counter) { + entry = std::addressof(this->caches[i]); + } + } + this->Invalidate(entry); + return entry; + } + + void PlaceHolderAccessor::GetPath(PathString *placeholder_path, PlaceHolderId placeholder_id) { + { + std::scoped_lock lock(this->cache_mutex); + this->Invalidate(this->FindInCache(placeholder_id)); + } + this->MakePath(placeholder_path, placeholder_id); + } + + Result PlaceHolderAccessor::CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size) { + R_TRY(this->EnsurePlaceHolderDirectory(placeholder_id)); + + PathString placeholder_path; + this->GetPath(std::addressof(placeholder_path), placeholder_id); + + R_TRY_CATCH(fs::CreateFile(placeholder_path, size, fs::CreateOption_BigFile)) { + R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists()) + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + + Result PlaceHolderAccessor::DeletePlaceHolderFile(PlaceHolderId placeholder_id) { + PathString placeholder_path; + this->GetPath(std::addressof(placeholder_path), placeholder_id); + + R_TRY_CATCH(fs::DeleteFile(placeholder_path)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + + Result PlaceHolderAccessor::WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size) { + fs::FileHandle file; + R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + } R_END_TRY_CATCH; + 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(); + } + + Result PlaceHolderAccessor::SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size) { + fs::FileHandle file; + R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + R_TRY(fs::SetFileSize(file, size)); + + return ResultSuccess(); + } + + Result PlaceHolderAccessor::TryGetPlaceHolderFileSize(bool *found_in_cache, s64 *out_size, PlaceHolderId placeholder_id) { + fs::FileHandle handle; + auto found = this->LoadFromCache(std::addressof(handle), placeholder_id); + if (found) { + this->StoreToCache(placeholder_id, handle); + R_TRY(fs::GetFileSize(out_size, handle)); + *found_in_cache = true; + } else { + *found_in_cache = false; + } + + return ResultSuccess(); + } + + void PlaceHolderAccessor::InvalidateAll() { + 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 new file mode 100644 index 000000000..d85508edd --- /dev/null +++ b/stratosphere/ncm/source/ncm_placeholder_accessor.hpp @@ -0,0 +1,83 @@ +/* + * 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 +#include + +#include "ncm_fs_utils.hpp" +#include "ncm_make_path.hpp" + +namespace ams::ncm { + + class PlaceHolderAccessor { + private: + class CacheEntry { + public: + PlaceHolderId id; + fs::FileHandle handle; + u64 counter; + }; + + static constexpr size_t MaxCacheEntries = 0x2; + private: + std::array caches; + PathString *root_path; + u64 cur_counter; + os::Mutex cache_mutex; + MakePlaceHolderPathFunction make_placeholder_path_func; + bool delay_flush; + private: + Result Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id); + bool LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id); + void StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle); + void Invalidate(CacheEntry *entry); + CacheEntry *FindInCache(PlaceHolderId placeholder_id); + CacheEntry *GetFreeEntry();; + public: + PlaceHolderAccessor() : cur_counter(0), delay_flush(false) { + for (size_t i = 0; i < MaxCacheEntries; i++) { + caches[i].id = InvalidPlaceHolderId; + } + } + + ~PlaceHolderAccessor() { this->InvalidateAll(); } + + static void MakeBaseDirectoryPath(PathString *out, const char *root_path); + static Result GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name); + public: + void Initialize(PathString *root, MakePlaceHolderPathFunction path_func, bool delay_flush) { + this->root_path = root; + this->make_placeholder_path_func = path_func; + this->delay_flush = delay_flush; + } + + Result CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size); + Result DeletePlaceHolderFile(PlaceHolderId placeholder_id); + Result WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size); + Result SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size); + Result TryGetPlaceHolderFileSize(bool *out_found, s64 *out_size, PlaceHolderId placeholder_id); + + void GetPath(PathString *out_placeholder_path, PlaceHolderId placeholder_id); + void MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const; + + void InvalidateAll(); + + Result EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id); + size_t GetHierarchicalDirectoryDepth() const { return GetHierarchicalPlaceHolderDirectoryDepth(this->make_placeholder_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 145125cb2..1aea23053 100644 --- a/stratosphere/ncm/source/ncm_read_only_content_storage_impl.cpp +++ b/stratosphere/ncm/source/ncm_read_only_content_storage_impl.cpp @@ -14,60 +14,88 @@ * along with this program. If not, see . */ -#include "ncm_fs.hpp" -#include "ncm_path_utils.hpp" +#include "ncm_fs_utils.hpp" #include "ncm_read_only_content_storage_impl.hpp" namespace ams::ncm { - Result ReadOnlyContentStorageImpl::Initialize(const char *path, MakeContentPathFunc content_path_func) { + namespace { + + void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + return func(out, id, root_path); + } + + void MakeGameCardContentMetaPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + PathString path; + func(std::addressof(path), id, root_path); + *out = path.GetSubstring(0, path.GetLength() - 4); + out->Append(".cnmt.nca"); + } + + Result OpenContentIdFileImpl(fs::FileHandle *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + PathString path; + MakeContentPath(std::addressof(path), id, func, root_path); + + R_TRY_CATCH(fs::OpenFile(out, path, fs::OpenMode_Read)) { + R_CATCH(fs::ResultPathNotFound) { + MakeGameCardContentMetaPath(std::addressof(path), id, func, root_path); + R_TRY(fs::OpenFile(out, path, fs::OpenMode_Read)); + } + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + + } + + Result ReadOnlyContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func) { R_TRY(this->EnsureEnabled()); - this->root_path = PathString(path); - this->make_content_path_func = *content_path_func; + this->root_path.Set(path); + this->make_content_path_func = content_path_func; return ResultSuccess(); } Result ReadOnlyContentStorageImpl::GeneratePlaceHolderId(sf::Out out) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::HasPlaceHolder(sf::Out out, PlaceHolderId placeholder_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, sf::InBuffer data) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::Delete(ContentId content_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::Has(sf::Out out, ContentId content_id) { R_TRY(this->EnsureEnabled()); PathString content_path; - this->make_content_path_func(std::addressof(content_path), content_id, this->root_path); + MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - bool has = false; - R_TRY(fs::HasFile(&has, content_path)); + bool has; + R_TRY(impl::HasFile(std::addressof(has), content_path)); if (!has) { - path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - R_TRY(fs::HasFile(&has, content_path)); + MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); + R_TRY(impl::HasFile(std::addressof(has), content_path)); } out.SetValue(has); @@ -78,58 +106,52 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); PathString content_path; - path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - - bool is_content_meta_file = false; - R_TRY(fs::HasFile(&is_content_meta_file, content_path)); + MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - if (!is_content_meta_file) { - this->make_content_path_func(std::addressof(content_path), content_id, this->root_path); + bool has_file; + R_TRY(impl::HasFile(std::addressof(has_file), content_path)); + if (!has_file) { + MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); } Path common_path; - R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, content_path)); - out.SetValue(Path::Encode(common_path.str)); + R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path)); + out.SetValue(common_path); return ResultSuccess(); } Result ReadOnlyContentStorageImpl::GetPlaceHolderPath(sf::Out out, PlaceHolderId placeholder_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::CleanupAllPlaceHolder() { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::ListPlaceHolder(sf::Out out_count, const sf::OutArray &out_buf) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::GetContentCount(sf::Out out_count) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::ListContentId(sf::Out out_count, const sf::OutArray &out_buf, u32 start_offset) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::GetSizeFromContentId(sf::Out out_size, ContentId content_id) { R_TRY(this->EnsureEnabled()); - PathString content_path; - this->make_content_path_func(std::addressof(content_path), content_id, this->root_path); + 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); }; - bool is_content_file = false; - R_TRY(fs::HasFile(&is_content_file, content_path)); + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); - if (!is_content_file) { - path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - } - - struct stat st; - R_UNLESS(stat(content_path, &st) != -1, fsdevGetLastResult()); - out_size.SetValue(st.st_size); + out_size.SetValue(file_size); return ResultSuccess(); } @@ -139,11 +161,11 @@ namespace ams::ncm { } Result ReadOnlyContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) { @@ -151,33 +173,21 @@ namespace ams::ncm { R_UNLESS(offset <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); - PathString content_path; - this->make_content_path_func(std::addressof(content_path), content_id, this->root_path); + 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); }; - bool is_content_file = false; - R_TRY(fs::HasFile(&is_content_file, content_path)); + R_TRY(fs::ReadFile(file, offset, buf.GetPointer(), buf.GetSize())); - if (!is_content_file) { - path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - } - - FILE *f = nullptr; - R_TRY(fs::OpenFile(&f, content_path, FsOpenMode_Read)); - - ON_SCOPE_EXIT { - fclose(f); - }; - - R_TRY(fs::ReadFile(f, offset, buf.GetPointer(), buf.GetSize())); return ResultSuccess(); } Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out out_rights_id, PlaceHolderId placeholder_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out out_rights_id, PlaceHolderId placeholder_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out out_rights_id, ContentId content_id) { @@ -190,28 +200,18 @@ namespace ams::ncm { Result ReadOnlyContentStorageImpl::GetRightsIdFromContentId(sf::Out out_rights_id, ContentId content_id) { R_TRY(this->EnsureEnabled()); - PathString content_path; - path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path); - - bool is_content_meta_file = false; - R_TRY(fs::HasFile(&is_content_meta_file, content_path)); - - if (!is_content_meta_file) { - this->make_content_path_func(std::addressof(content_path), content_id, this->root_path); - } - - Path common_path; - R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, content_path)); + Path path; + R_TRY(this->GetPath(std::addressof(path), content_id)); ncm::RightsId rights_id; - R_TRY(GetRightsId(&rights_id, common_path)); + R_TRY(GetRightsId(&rights_id, path)); out_rights_id.SetValue(rights_id); return ResultSuccess(); } Result ReadOnlyContentStorageImpl::WriteContentForDebug(ContentId content_id, u64 offset, sf::InBuffer data) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::GetFreeSpaceSize(sf::Out out_size) { @@ -225,19 +225,19 @@ namespace ams::ncm { } Result ReadOnlyContentStorageImpl::FlushPlaceHolder() { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out out, PlaceHolderId placeholder_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::RepairInvalidFileAttribute() { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) { - return ResultInvalidContentStorageOperation(); + return ncm::ResultWriteToReadOnlyContentStorage(); } } 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 3869dac01..a8579f1be 100644 --- a/stratosphere/ncm/source/ncm_read_only_content_storage_impl.hpp +++ b/stratosphere/ncm/source/ncm_read_only_content_storage_impl.hpp @@ -23,7 +23,7 @@ namespace ams::ncm { class ReadOnlyContentStorageImpl : public ContentStorageImplBase { public: - Result Initialize(const char *root_path, MakeContentPathFunc content_path_func); + Result Initialize(const char *root_path, MakeContentPathFunction content_path_func); public: virtual Result GeneratePlaceHolderId(sf::Out out) override; virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override; diff --git a/stratosphere/ncm/source/impl/ncm_rights_cache.hpp b/stratosphere/ncm/source/ncm_rights_cache.hpp similarity index 99% rename from stratosphere/ncm/source/impl/ncm_rights_cache.hpp rename to stratosphere/ncm/source/ncm_rights_cache.hpp index 3dc8baf5a..495e13c1e 100644 --- a/stratosphere/ncm/source/impl/ncm_rights_cache.hpp +++ b/stratosphere/ncm/source/ncm_rights_cache.hpp @@ -18,7 +18,7 @@ #include #include -namespace ams::ncm::impl { +namespace ams::ncm { class RightsIdCache { NON_COPYABLE(RightsIdCache); diff --git a/stratosphere/ncm/source/ncm_utils.cpp b/stratosphere/ncm/source/ncm_utils.cpp deleted file mode 100644 index 89d383fe5..000000000 --- a/stratosphere/ncm/source/ncm_utils.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 . - */ - -#include "ncm_utils.hpp" - -namespace ams::ncm { - - void GetStringFromContentId(char *out, ContentId content_id) { - for (size_t i = 0; i < sizeof(ContentId); i++) { - snprintf(out+i*2, 3, "%02x", content_id.uuid[i]); - } - } - - void GetStringFromPlaceHolderId(char *out, PlaceHolderId placeholder_id) { - for (size_t i = 0; i < sizeof(PlaceHolderId); i++) { - snprintf(out+i*2, 3, "%02x", placeholder_id.uuid[i]); - } - } - - Result GetPlaceHolderIdFromDirEntry(PlaceHolderId *out, struct dirent *dir_entry) { - R_UNLESS(strnlen(dir_entry->d_name, 0x30) == 0x24, ncm::ResultInvalidPlaceHolderFile()); - R_UNLESS(strncmp(dir_entry->d_name + 0x20, ".nca", 4) == 0, ncm::ResultInvalidPlaceHolderFile()); - - u8 tmp[sizeof(PlaceHolderId)] = {}; - char byte_string[2]; - char *end_ptr; - u64 converted_val; - - for (size_t i = 0; i < sizeof(PlaceHolderId); i++) { - char *name_char_pair = dir_entry->d_name + i * 2; - - byte_string[0] = name_char_pair[0]; - byte_string[1] = name_char_pair[1]; - - converted_val = strtoull(byte_string, &end_ptr, 0x10); - tmp[i] = (u8)converted_val; - } - - PlaceHolderId placeholder_id; - std::memcpy(placeholder_id.uuid.data, tmp, sizeof(PlaceHolderId)); - *out = placeholder_id; - return ResultSuccess(); - } - - std::optional GetContentIdFromString(const char *str, size_t len) { - if (len < 0x20) { - return std::nullopt; - } - - u8 tmp[sizeof(ContentId)] = {}; - char byte_string[2]; - char *end_ptr; - u64 converted_val; - - for (size_t i = 0; i < sizeof(ContentId); i++) { - const char *char_par = str + i * 2; - - byte_string[0] = char_par[0]; - byte_string[1] = char_par[1]; - - converted_val = strtoull(byte_string, &end_ptr, 0x10); - tmp[i] = (u8)converted_val; - } - - ContentId content_id; - std::memcpy(content_id.uuid.data, tmp, sizeof(ContentId)); - return std::optional(content_id); - } - -}