From 5666c596577df2eddc095529d1ce802ef9f842ed Mon Sep 17 00:00:00 2001 From: Adubbz Date: Sat, 20 Mar 2021 20:54:44 +1100 Subject: [PATCH] ncm: Updated ListContentId for 11.0.0 --- .../source/ncm/ncm_content_storage_impl.cpp | 202 +++++++++++++++--- .../source/ncm/ncm_content_storage_impl.hpp | 29 +++ 2 files changed, 200 insertions(+), 31 deletions(-) diff --git a/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp b/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp index bb18337b9..2787fe0e8 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp @@ -166,6 +166,134 @@ namespace ams::ncm { } + ContentStorageImpl::ContentIterator::~ContentIterator() { + for (size_t i = 0; i < this->depth; i++) { + fs::CloseDirectory(this->handles[i]); + } + } + + Result ContentStorageImpl::ContentIterator::Initialize(const char *root_path, size_t max_depth) { + /* Initialize tracking variables. */ + this->depth = 0; + this->max_depth = max_depth; + this->entry_count = 0; + + /* Create the base content directory path. */ + MakeBaseContentDirectoryPath(std::addressof(this->path), root_path); + + /* Open the base directory. */ + R_TRY(this->OpenCurrentDirectory()); + + return ResultSuccess(); + } + + Result ContentStorageImpl::ContentIterator::OpenCurrentDirectory() { + /* Determine valid directory mode (prior to 2.0.0, NotRequireFileSize was not valid). */ + const auto open_mode = hos::GetVersion() >= hos::Version_2_0_0 ? (fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize) : (fs::OpenDirectoryMode_All); + + /* Open the directory for our current path. */ + R_TRY(fs::OpenDirectory(std::addressof(this->handles[this->depth]), this->path, open_mode)); + + /* Increase our depth. */ + ++this->depth; + + return ResultSuccess(); + } + + Result ContentStorageImpl::ContentIterator::OpenDirectory(const char *dir) { + /* Set our current path. */ + this->path.Set(dir); + + /* Open the directory. */ + return this->OpenCurrentDirectory(); + } + + Result ContentStorageImpl::ContentIterator::GetNext(std::optional *out) { + /* Iterate until we get the next entry. */ + while (true) { + /* Ensure that we have entries loaded. */ + R_TRY(this->LoadEntries()); + + /* If we failed to load any entries, there's nothing to get. */ + if (this->entry_count <= 0) { + *out = std::nullopt; + return ResultSuccess(); + } + + /* Get the next entry. */ + const auto &entry = this->entries[--this->entry_count]; + + /* Process the current entry. */ + switch (entry.type) { + case fs::DirectoryEntryType_Directory: + /* If the entry if a directory, we want to recurse into it if we can. */ + if (this->depth < this->max_depth) { + /* Construct the full path for the subdirectory. */ + PathString entry_path; + entry_path.SetFormat("%s/%s", this->path.Get(), entry.name); + + /* Open the subdirectory. */ + R_TRY(this->OpenDirectory(entry_path.Get())); + + } + break; + case fs::DirectoryEntryType_File: + /* Otherwise, if the entry is a file, return it. */ + *out = entry; + return ResultSuccess(); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + return ResultSuccess(); + } + + Result ContentStorageImpl::ContentIterator::LoadEntries() { + /* If we already have entries loaded, we don't need to do anything. */ + R_SUCCEED_IF(this->entry_count != 0); + + /* If we have no directories open, there's nothing for us to load. */ + if (this->depth == 0) { + this->entry_count = 0; + return ResultSuccess(); + } + + /* Determine the maximum entries that we can load. */ + const s64 max_entries = this->depth == this->max_depth ? MaxDirectoryEntries : 1; + + /* Read entries from the current directory. */ + s64 num_entries; + R_TRY(fs::ReadDirectory(std::addressof(num_entries), this->entries, this->handles[this->depth - 1], max_entries)); + + /* If we successfully read entries, load them. */ + if (num_entries > 0) { + /* Reverse the order of the loaded entries, for our future convenience. */ + for (fs::DirectoryEntry *start_entry = this->entries, *end_entry = this->entries + num_entries - 1; start_entry < end_entry; ++start_entry, --end_entry) { + std::swap(*start_entry, *end_entry); + } + + /* Set our entry count. */ + this->entry_count = num_entries; + + return ResultSuccess(); + } + + /* We didn't read any entries, so we need to advance to the next directory. */ + fs::CloseDirectory(this->handles[--this->depth]); + + /* Find the index of the parent directory's substring. */ + size_t i = this->path.GetLength() - 1; + while (this->path.Get()[i--] != '/') { + AMS_ABORT_UNLESS(i > 0); + } + + /* Set the path to the parent directory. */ + this->path.Set(this->path.GetSubstring(0, i)); + + /* Try to load again from the parent directory. */ + return this->LoadEntries(); + } + ContentStorageImpl::~ContentStorageImpl() { this->InvalidateFileCache(); } @@ -225,6 +353,7 @@ namespace ams::ncm { fs::CloseFile(this->cached_file_handle); this->cached_content_id = InvalidContentId; } + this->content_iterator = std::nullopt; } Result ContentStorageImpl::OpenContentIdFile(ContentId content_id) { @@ -445,48 +574,59 @@ namespace ams::ncm { return ResultSuccess(); } - Result ContentStorageImpl::ListContentId(sf::Out out_count, const sf::OutArray &out_buf, s32 offset) { + Result ContentStorageImpl::ListContentId(sf::Out out_count, const sf::OutArray &out, s32 offset) { R_UNLESS(offset >= 0, ncm::ResultInvalidOffset()); R_TRY(this->EnsureEnabled()); - /* Obtain the content base directory path. */ - PathString path; - MakeBaseContentDirectoryPath(std::addressof(path), this->root_path); + if (!this->content_iterator.has_value() || !this->last_content_offset.has_value() || this->last_content_offset != offset) { + /* Create and initialize the content cache. */ + this->content_iterator.emplace(); + R_TRY(this->content_iterator->Initialize(this->root_path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func))); - const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func); - size_t entry_count = 0; + /* Advance to the desired offset. */ + for (auto current_offset = 0; current_offset < offset; /* ... */) { + /* Get the next directory entry. */ + std::optional dir_entry; + R_TRY(this->content_iterator->GetNext(std::addressof(dir_entry))); - /* Traverse the content base directory finding all valid content. */ - R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) { - *should_retry_dir_read = false; - *should_continue = true; + /* If we run out of entries before reaching the desired offset, we're done. */ + if (!dir_entry) { + out_count.SetValue(0); + return ResultSuccess(); + } - /* We have nothing to do if not working with a file. */ - if (entry.type != fs::DirectoryEntryType_File) { - return ResultSuccess(); + /* If the current entry is a valid content id, advance. */ + if (GetContentIdFromString(dir_entry->name, std::strlen(dir_entry->name))) { + ++current_offset; + } + } + } + + /* Iterate, reading as many entries as we can. */ + s32 count = 0; + while (count < static_cast(out.GetSize())) { + /* Get the next directory entry. */ + std::optional dir_entry; + R_TRY(this->content_iterator->GetNext(std::addressof(dir_entry))); + + /* Don't continue if the directory entry is absent. */ + if (!dir_entry) { + break; } - /* Skip entries until we reach the start offset. */ - if (offset > 0) { - --offset; - return ResultSuccess(); + /* Process the entry, if it's a valid content id. */ + if (auto content_id = GetContentIdFromString(dir_entry->name, std::strlen(dir_entry->name)); content_id.has_value()) { + /* Output the content id. */ + out[count++] = *content_id; + + /* Update our last content offset. */ + this->last_content_offset = offset + count; } + } - /* 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(); - } + /* Set the output count. */ + *out_count = count; - auto content_id = GetContentIdFromString(entry.name, std::strlen(entry.name)); - if (content_id) { - out_buf[entry_count++] = *content_id; - } - - return ResultSuccess(); - })); - - out_count.SetValue(static_cast(entry_count)); return ResultSuccess(); } diff --git a/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.hpp b/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.hpp index f68e22fb4..8d8d5fc1a 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.hpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.hpp @@ -22,16 +22,45 @@ namespace ams::ncm { class ContentStorageImpl : public ContentStorageImplBase { + private: + class ContentIterator { + NON_COPYABLE(ContentIterator); + NON_MOVEABLE(ContentIterator); + + static constexpr size_t MaxDirectoryHandles = 0x8; + static constexpr size_t MaxDirectoryEntries = 0x10; + + public: + fs::DirectoryHandle handles[MaxDirectoryHandles]{}; + size_t depth{}; + size_t max_depth{}; + PathString path{}; + fs::DirectoryEntry entries[MaxDirectoryEntries]{}; + s64 entry_count{}; + public: + constexpr ContentIterator() = default; + ~ContentIterator(); + + Result Initialize(const char *root_path, size_t max_depth); + Result GetNext(std::optional *out); + private: + Result OpenCurrentDirectory(); + Result OpenDirectory(const char *dir); + Result LoadEntries(); + }; protected: PlaceHolderAccessor placeholder_accessor; ContentId cached_content_id; fs::FileHandle cached_file_handle; RightsIdCache *rights_id_cache; + std::optional content_iterator; + std::optional last_content_offset; public: static Result InitializeBase(const char *root_path); static Result CleanupBase(const char *root_path); static Result VerifyBase(const char *root_path); public: + ContentStorageImpl() : rights_id_cache(nullptr), content_iterator(std::nullopt), last_content_offset(std::nullopt) { /* ... */ } ~ContentStorageImpl(); Result Initialize(const char *root_path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache);