diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_management_utils.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_management_utils.hpp index 4f84e09a4..7218e58f7 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_management_utils.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_management_utils.hpp @@ -29,8 +29,11 @@ namespace ams::ncm { explicit ContentMetaDatabaseBuilder(ContentMetaDatabase *d) : db(d) { /* ... */ } Result BuildFromStorage(ContentStorage *storage); + Result BuildFromPackage(const char *package_root_path); Result Cleanup(); }; + Result ListApplicationPackage(s32 *out_count, ApplicationId *out_ids, s32 max_out_ids, const char *package_root_path); + } diff --git a/libraries/libstratosphere/source/ncm/ncm_content_management_utils.cpp b/libraries/libstratosphere/source/ncm/ncm_content_management_utils.cpp index f130793a3..291237088 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_management_utils.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_management_utils.cpp @@ -14,9 +14,58 @@ * along with this program. If not, see . */ #include +#include "ncm_fs_utils.hpp" namespace ams::ncm { + namespace { + + constexpr inline size_t MaxPackagePathLength = 0x100; + + Result ConvertToFsCommonPath(char *dst, size_t dst_size, const char *package_root_path, const char *entry_path) { + char package_path[MaxPackagePathLength]; + + const size_t path_len = std::snprintf(package_path, sizeof(package_path), "%s%s", package_root_path, entry_path); + AMS_ABORT_UNLESS(path_len < MaxPackagePathLength); + + return fs::ConvertToFsCommonPath(dst, dst_size, package_path); + } + + Result LoadContentMeta(ncm::AutoBuffer *out, const char *package_root_path, const fs::DirectoryEntry &entry) { + AMS_ABORT_UNLESS(impl::PathView(entry.name).HasSuffix(".cnmt.nca")); + + char path[MaxPackagePathLength]; + R_TRY(ConvertToFsCommonPath(path, sizeof(path), package_root_path, entry.name)); + + return ncm::ReadContentMetaPath(out, path); + } + + template + Result ForEachFileInDirectory(const char *root_path, F f) { + /* Open the directory. */ + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + while (true) { + /* Read the current entry. */ + s64 count; + fs::DirectoryEntry entry; + R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1)); + if (count == 0) { + break; + } + + /* Invoke our handler on the entry. */ + bool done; + R_TRY(f(std::addressof(done), entry)); + R_SUCCEED_IF(done); + } + + return ResultSuccess(); + } + + } Result ContentMetaDatabaseBuilder::BuildFromPackageContentMeta(void *buf, size_t size, const ContentInfo &meta_info) { /* Create a reader for the content meta. */ @@ -78,6 +127,31 @@ namespace ams::ncm { return this->db->Commit(); } + Result ContentMetaDatabaseBuilder::BuildFromPackage(const char *package_root_path) { + /* Build the database by writing every entry in the package. */ + R_TRY(ForEachFileInDirectory(package_root_path, [&](bool *done, const fs::DirectoryEntry &entry) -> Result { + /* Never early terminate. */ + *done = false; + + /* We have nothing to list if we're not looking at a meta. */ + R_SUCCEED_IF(!impl::PathView(entry.name).HasSuffix(".cnmt.nca")); + + /* Read the content meta path, and build. */ + ncm::AutoBuffer package_meta; + R_TRY(LoadContentMeta(std::addressof(package_meta), package_root_path, entry)); + + /* Try to parse a content id from the name. */ + auto content_id = GetContentIdFromString(entry.name, sizeof(entry.name)); + R_UNLESS(content_id, ncm::ResultInvalidPackageFormat()); + + /* Build using the meta. */ + return this->BuildFromPackageContentMeta(package_meta.Get(), package_meta.GetSize(), ContentInfo::Make(*content_id, entry.file_size, ContentType::Meta)); + })); + + /* Commit our changes. */ + return this->db->Commit(); + } + Result ContentMetaDatabaseBuilder::Cleanup() { /* This cleans up the content meta by removing all entries. */ while (true) { @@ -101,4 +175,35 @@ namespace ams::ncm { return this->db->Commit(); } + Result ListApplicationPackage(s32 *out_count, ApplicationId *out_ids, size_t max_out_ids, const char *package_root_path) { + size_t count = 0; + R_TRY(ForEachFileInDirectory(package_root_path, [&](bool *done, const fs::DirectoryEntry &entry) -> Result { + /* Never early terminate. */ + *done = false; + + /* We have nothing to list if we're not looking at a meta. */ + R_SUCCEED_IF(!impl::PathView(entry.name).HasSuffix(".cnmt.nca")); + + /* Read the content meta path, and build. */ + ncm::AutoBuffer package_meta; + R_TRY(LoadContentMeta(std::addressof(package_meta), package_root_path, entry)); + + /* Create a reader for the meta. */ + ncm::PackagedContentMetaReader package_meta_reader(package_meta.Get(), package_meta.GetSize()); + + /* Write the key to output if we're reading an application. */ + const auto &key = package_meta_reader.GetKey(); + if (key.type == ContentMetaType::Application) { + R_UNLESS(count < max_out_ids, ncm::ResultBufferInsufficient()); + + out_ids[count++] = { key.id }; + } + + return ResultSuccess(); + })); + + *out_count = static_cast(count); + return ResultSuccess(); + } + } diff --git a/libraries/libvapours/include/vapours/results/ncm_results.hpp b/libraries/libvapours/include/vapours/results/ncm_results.hpp index 3bd912870..494d7b4bd 100644 --- a/libraries/libvapours/include/vapours/results/ncm_results.hpp +++ b/libraries/libvapours/include/vapours/results/ncm_results.hpp @@ -33,6 +33,8 @@ namespace ams::ncm { R_DEFINE_ERROR_RESULT(InvalidContentStorage, 100); R_DEFINE_ERROR_RESULT(InvalidContentMetaDatabase, 110); + R_DEFINE_ERROR_RESULT(InvalidPackageFormat, 130); + R_DEFINE_ERROR_RESULT(InvalidPlaceHolderFile, 170); R_DEFINE_ERROR_RESULT(BufferInsufficient, 180); R_DEFINE_ERROR_RESULT(WriteToReadOnlyContentStorage, 190);