From 97d3fd396e8843f8b4736a67e343f55c59d0cc44 Mon Sep 17 00:00:00 2001 From: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Tue, 13 May 2025 23:51:06 +0100 Subject: [PATCH] optimise game menu for fw 20 - loading the control data is ran on its own thread, it does not block the main thread. allows for smooth scrolling like nintendos home menu. - on fw20+, sphaira manually parses the control data, rather than using ns. manually parsing takes 20-40ms, which is faster than ms which can take 50-500ms. - on fw19 and below, if the control data is not in ns cache, sphaira will manually parse the data as its twice as fast as ns. You can see how fast this is by loading the gamecard menu as that manually parses everything, and it loads the gamecard faster than the home menu --- sphaira/include/ui/menus/game_menu.hpp | 41 +++- sphaira/include/yati/nx/nca.hpp | 9 +- sphaira/include/yati/yati.hpp | 4 - sphaira/source/ui/menus/game_menu.cpp | 287 +++++++++++++++++++++---- sphaira/source/ui/menus/gc_menu.cpp | 7 +- sphaira/source/yati/nx/nca.cpp | 68 ++++++ sphaira/source/yati/yati.cpp | 71 +----- 7 files changed, 363 insertions(+), 124 deletions(-) diff --git a/sphaira/include/ui/menus/game_menu.hpp b/sphaira/include/ui/menus/game_menu.hpp index 0ed3921..0d745db 100644 --- a/sphaira/include/ui/menus/game_menu.hpp +++ b/sphaira/include/ui/menus/game_menu.hpp @@ -5,12 +5,15 @@ #include "fs.hpp" #include "option.hpp" #include +#include namespace sphaira::ui::menu::game { enum class NacpLoadStatus { // not yet attempted to be loaded. None, + // started loading. + Progress, // loaded, ready to parse. Loaded, // failed to load, do not attempt to load again! @@ -19,12 +22,11 @@ enum class NacpLoadStatus { struct Entry { u64 app_id{}; - s64 size{}; char display_version[0x10]{}; NacpLanguageEntry lang{}; int image{}; - std::unique_ptr control{}; + std::shared_ptr control{}; u64 control_size{}; NacpLoadStatus status{NacpLoadStatus::None}; @@ -41,6 +43,38 @@ struct Entry { } }; +struct ThreadResultData { + u64 id{}; + std::shared_ptr control{}; + u64 control_size{}; + char display_version[0x10]{}; + NacpLanguageEntry lang{}; + NacpLoadStatus status{NacpLoadStatus::None}; +}; + +struct ThreadData { + ThreadData(); + + auto IsRunning() const -> bool; + void Run(); + void Close(); + void Push(u64 id); + void Push(std::span entries); + void Pop(std::vector& out); + +private: + UEvent m_uevent{}; + Mutex m_mutex_id{}; + Mutex m_mutex_result{}; + + // app_ids pushed to the queue, signal uevent when pushed. + std::vector m_ids{}; + // control data pushed to the queue. + std::vector m_result{}; + + std::atomic_bool m_running{}; +}; + enum SortType { SortType_Updated, }; @@ -79,6 +113,9 @@ private: bool m_is_reversed{}; bool m_dirty{}; + ThreadData m_thread_data{}; + Thread m_thread{}; + option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending}; option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail}; diff --git a/sphaira/include/yati/nx/nca.hpp b/sphaira/include/yati/nx/nca.hpp index f23a9f2..93b8fdd 100644 --- a/sphaira/include/yati/nx/nca.hpp +++ b/sphaira/include/yati/nx/nca.hpp @@ -1,7 +1,10 @@ #pragma once -#include +#include "fs.hpp" #include "keys.hpp" +#include "ncm.hpp" +#include +#include namespace sphaira::nca { @@ -216,6 +219,10 @@ Result DecryptKeak(const keys::Keys& keys, Header& header); Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation); Result VerifyFixedKey(const Header& header); +// helpers that parse an nca. +Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector& extended_header, std::vector& infos); +Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector* icon_out = nullptr); + auto GetKeyGenStr(u8 key_gen) -> const char*; } // namespace sphaira::nca diff --git a/sphaira/include/yati/yati.hpp b/sphaira/include/yati/yati.hpp index 3fccc29..4a92f28 100644 --- a/sphaira/include/yati/yati.hpp +++ b/sphaira/include/yati/yati.hpp @@ -10,7 +10,6 @@ #include "fs.hpp" #include "source/base.hpp" #include "container/base.hpp" -#include "nx/ncm.hpp" #include "ui/progress_box.hpp" #include #include @@ -136,7 +135,4 @@ Result InstallFromSource(ui::ProgressBox* pbox, std::shared_ptr so Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr container, const ConfigOverride& override = {}); Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr source, const container::Collections& collections, const ConfigOverride& override = {}); -Result ParseCnmtNca(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector& extended_header, std::vector& infos); -Result ParseControlNca(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector* icon_out = nullptr); - } // namespace sphaira::yati diff --git a/sphaira/source/ui/menus/game_menu.cpp b/sphaira/source/ui/menus/game_menu.cpp index 18b1295..d58956d 100644 --- a/sphaira/source/ui/menus/game_menu.cpp +++ b/sphaira/source/ui/menus/game_menu.cpp @@ -11,6 +11,7 @@ #include "defines.hpp" #include "i18n.hpp" #include "yati/nx/ncm.hpp" +#include "yati/nx/nca.hpp" #include #include @@ -19,20 +20,27 @@ namespace sphaira::ui::menu::game { namespace { -// thank you Shchmue ^^ -struct ApplicationOccupiedSizeEntry { - u8 storageId; - u8 padding[0x7]; - u64 sizeApplication; - u64 sizePatch; - u64 sizeAddOnContent; +constexpr NcmStorageId NCM_STORAGE_IDS[]{ + NcmStorageId_BuiltInUser, + NcmStorageId_SdCard, }; -struct ApplicationOccupiedSize { - ApplicationOccupiedSizeEntry entry[4]; -}; +NcmContentStorage ncm_cs[2]; +NcmContentMetaDatabase ncm_db[2]; -static_assert(sizeof(ApplicationOccupiedSize) == sizeof(NsApplicationOccupiedSize)); +auto& GetNcmCs(u8 storage_id) { + if (storage_id == NcmStorageId_SdCard) { + return ncm_cs[1]; + } + return ncm_cs[0]; +} + +auto& GetNcmDb(u8 storage_id) { + if (storage_id == NcmStorageId_SdCard) { + return ncm_db[1]; + } + return ncm_db[0]; +} using MetaEntries = std::vector; @@ -48,19 +56,23 @@ Result Notify(Result rc, const std::string& error_message) { return rc; } -Result GetMetaEntries(const Entry& e, MetaEntries& out) { +Result GetMetaEntries(u64 id, MetaEntries& out) { s32 count; - R_TRY(nsCountApplicationContentMeta(e.app_id, &count)); + R_TRY(nsCountApplicationContentMeta(id, &count)); out.resize(count); - R_TRY(nsListApplicationContentMetaStatus(e.app_id, 0, out.data(), out.size(), &count)); + R_TRY(nsListApplicationContentMetaStatus(id, 0, out.data(), out.size(), &count)); out.resize(count); R_SUCCEED(); } +Result GetMetaEntries(const Entry& e, MetaEntries& out) { + return GetMetaEntries(e.app_id, out); +} + // also sets the status to error. -void FakeNacpEntry(Entry& e) { +void FakeNacpEntry(ThreadResultData& e) { e.status = NacpLoadStatus::Error; // fake the nacp entry std::strcpy(e.lang.name, "Corrupted"); @@ -71,34 +83,113 @@ void FakeNacpEntry(Entry& e) { bool LoadControlImage(Entry& e) { if (!e.image && e.control) { + TimeStamp ts; const auto jpeg_size = e.control_size - sizeof(NacpStruct); e.image = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, jpeg_size); e.control.reset(); + log_write("\t\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); return true; } return false; } +Result LoadControlManual(u64 id, ThreadResultData& data) { + TimeStamp ts; + + MetaEntries entries; + R_TRY(GetMetaEntries(id, entries)); + R_UNLESS(!entries.empty(), 0x1); + + const auto& ee = entries.back(); + if (ee.storageID != NcmStorageId_SdCard && ee.storageID != NcmStorageId_BuiltInUser) { + return 0x1; + } + + auto& db = GetNcmDb(ee.storageID); + auto& cs = GetNcmCs(ee.storageID); + + NcmContentMetaKey key; + R_TRY(ncmContentMetaDatabaseGetLatestContentMetaKey(&db, &key, ee.application_id)); + + NcmContentId content_id; + R_TRY(ncmContentMetaDatabaseGetContentIdByType(&db, &content_id, &key, NcmContentType_Control)); + + u64 program_id; + R_TRY(ncmContentStorageGetProgramId(&cs, &program_id, &content_id, FsContentAttributes_All)); + + fs::FsPath path; + R_TRY(ncmContentStorageGetPath(&cs, path, sizeof(path), &content_id)); + + std::vector icon; + R_TRY(nca::ParseControl(path, program_id, &data.control->nacp, sizeof(data.control->nacp), &icon)); + std::memcpy(data.control->icon, icon.data(), icon.size()); + + data.control_size = sizeof(data.control->nacp) + icon.size(); + log_write("\t\t[manual control] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); + + R_SUCCEED(); +} + +auto LoadControlEntry(u64 id) -> ThreadResultData { + ThreadResultData data{}; + data.id = id; + data.control = std::make_shared(); + data.status = NacpLoadStatus::Error; + + bool manual_load = false; + if (hosversionBefore(20,0,0)) { + TimeStamp ts; + if (R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_CacheOnly, id, data.control.get(), sizeof(NsApplicationControlData), &data.control_size))) { + manual_load = false; + log_write("\t\t[ns control cache] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); + } + } + + if (manual_load) { + manual_load = R_SUCCEEDED(LoadControlManual(id, data)); + } + + Result rc{}; + if (!manual_load) { + TimeStamp ts; + rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, id, data.control.get(), sizeof(NsApplicationControlData), &data.control_size); + log_write("\t\t[ns control storage] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); + } + + if (R_SUCCEEDED(rc)) { + NacpLanguageEntry* lang{}; + if (R_SUCCEEDED(rc = nsGetApplicationDesiredLanguage(&data.control->nacp, &lang)) && lang) { + data.lang = *lang; + std::memcpy(data.display_version, data.control->nacp.display_version, sizeof(data.display_version)); + data.status = NacpLoadStatus::Loaded; + } + } + + if (R_FAILED(rc)) { + FakeNacpEntry(data); + } + + return data; +} + +void LoadResultIntoEntry(Entry& e, const ThreadResultData& result) { + e.status = result.status; + e.control = result.control; + e.control_size = result.control_size; + std::memcpy(e.display_version, result.display_version, sizeof(result.display_version)); + e.lang = result.lang; + e.status = result.status; +} + void LoadControlEntry(Entry& e, bool force_image_load = false) { if (e.status == NacpLoadStatus::None) { - e.control = std::make_unique(); - if (R_FAILED(nsGetApplicationControlData(NsApplicationControlSource_Storage, e.app_id, e.control.get(), sizeof(NsApplicationControlData), &e.control_size))) { - FakeNacpEntry(e); - } else { - NacpLanguageEntry* lang{}; - if (R_FAILED(nsGetApplicationDesiredLanguage(&e.control->nacp, &lang)) || !lang) { - FakeNacpEntry(e); - } else { - e.lang = *lang; - std::memcpy(e.display_version, e.control->nacp.display_version, sizeof(e.display_version)); - e.status = NacpLoadStatus::Loaded; + const auto result = LoadControlEntry(e.app_id); + LoadResultIntoEntry(e, result); + } - if (force_image_load) { - LoadControlImage(e); - } - } - } + if (force_image_load && e.status == NacpLoadStatus::Loaded) { + LoadControlImage(e); } } @@ -112,8 +203,91 @@ void LaunchEntry(const Entry& e) { Notify(rc, "Failed to launch application"); } +void ThreadFunc(void* user) { + auto data = static_cast(user); + + while (data->IsRunning()) { + data->Run(); + } +} + } // namespace +ThreadData::ThreadData() { + ueventCreate(&m_uevent, true); + mutexInit(&m_mutex_id); + mutexInit(&m_mutex_result); + m_running = true; +} + +auto ThreadData::IsRunning() const -> bool { + return m_running; +} + +void ThreadData::Run() { + while (IsRunning()) { + const auto waiter = waiterForUEvent(&m_uevent); + waitSingle(waiter, UINT64_MAX); + + if (!IsRunning()) { + return; + } + + std::vector ids; + { + mutexLock(&m_mutex_id); + ON_SCOPE_EXIT(mutexUnlock(&m_mutex_id)); + std::swap(ids, m_ids); + } + + for (u64 i = 0; i < std::size(ids); i++) { + if (!IsRunning()) { + return; + } + + // sleep after every other entry loaded. + if (i) { + svcSleepThread(1e+6*2); // 2ms + } + + const auto result = LoadControlEntry(ids[i]); + mutexLock(&m_mutex_result); + ON_SCOPE_EXIT(mutexUnlock(&m_mutex_result)); + m_result.emplace_back(result); + } + } +} + +void ThreadData::Close() { + m_running = false; + ueventSignal(&m_uevent); +} + +void ThreadData::Push(u64 id) { + mutexLock(&m_mutex_id); + ON_SCOPE_EXIT(mutexUnlock(&m_mutex_id)); + + const auto it = std::find(m_ids.begin(), m_ids.end(), id); + if (it == m_ids.end()) { + m_ids.emplace_back(id); + ueventSignal(&m_uevent); + } +} + +void ThreadData::Push(std::span entries) { + for (auto& e : entries) { + Push(e.app_id); + } +} + +void ThreadData::Pop(std::vector& out) { + mutexLock(&m_mutex_result); + ON_SCOPE_EXIT(mutexUnlock(&m_mutex_result)); + + std::swap(out, m_result); + m_result.clear(); +} + Menu::Menu() : grid::Menu{"Games"_i18n} { this->SetActions( std::make_pair(Button::B, Action{"Back"_i18n, [this](){ @@ -256,12 +430,30 @@ Menu::Menu() : grid::Menu{"Games"_i18n} { nsInitialize(); nsGetApplicationRecordUpdateSystemEvent(&m_event); + + for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) { + ncmOpenContentMetaDatabase(std::addressof(ncm_db[i]), NCM_STORAGE_IDS[i]); + ncmOpenContentStorage(std::addressof(ncm_cs[i]), NCM_STORAGE_IDS[i]); + } + + threadCreate(&m_thread, ThreadFunc, &m_thread_data, nullptr, 1024*32, 0x30, 1); + threadStart(&m_thread); } Menu::~Menu() { + m_thread_data.Close(); + + for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) { + ncmContentMetaDatabaseClose(std::addressof(ncm_db[i])); + ncmContentStorageClose(std::addressof(ncm_cs[i])); + } + FreeEntries(); eventClose(&m_event); nsExit(); + + threadWaitForExit(&m_thread); + threadClose(&m_thread); } void Menu::Update(Controller* controller, TouchInfo* touch) { @@ -293,12 +485,26 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { const int image_load_max = 2; int image_load_count = 0; + std::vector data; + m_thread_data.Pop(data); + + for (const auto& d : data) { + const auto it = std::find_if(m_entries.begin(), m_entries.end(), [&d](auto& e) { + return e.app_id == d.id; + }); + + if (it != m_entries.end()) { + LoadResultIntoEntry(*it, d); + } + } + m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) { // const auto& [x, y, w, h] = v; auto& e = m_entries[pos]; if (e.status == NacpLoadStatus::None) { - LoadControlEntry(e); + m_thread_data.Push(e.app_id); + e.status = NacpLoadStatus::Progress; } // lazy load image @@ -377,20 +583,7 @@ void Menu::ScanHomebrew() { continue; } - s64 size{}; - // code for sorting by size, it's too slow however... - #if 0 - ApplicationOccupiedSize occupied_size; - if (R_SUCCEEDED(nsCalculateApplicationOccupiedSize(e.application_id, (NsApplicationOccupiedSize*)&occupied_size))) { - for (auto& s : occupied_size.entry) { - size += s.sizeApplication; - size += s.sizePatch; - size += s.sizeAddOnContent; - } - } - #endif - - m_entries.emplace_back(e.application_id, size); + m_entries.emplace_back(e.application_id); } offset += record_count; @@ -401,6 +594,8 @@ void Menu::ScanHomebrew() { log_write("games found: %zu time_taken: %.2f seconds %zu ms %zu ns\n", m_entries.size(), ts.GetSecondsD(), ts.GetMs(), ts.GetNs()); this->Sort(); SetIndex(0); + + // m_thread_data.Push(m_entries); } void Menu::Sort() { diff --git a/sphaira/source/ui/menus/gc_menu.cpp b/sphaira/source/ui/menus/gc_menu.cpp index 48aef00..c2d2c63 100644 --- a/sphaira/source/ui/menus/gc_menu.cpp +++ b/sphaira/source/ui/menus/gc_menu.cpp @@ -320,7 +320,7 @@ Result Menu::GcMount() { std::vector extended_header; std::vector infos; const auto path = BuildGcPath(e.name, &m_handle); - R_TRY(yati::ParseCnmtNca(path, 0, header, extended_header, infos)); + R_TRY(nca::ParseCnmt(path, 0, header, extended_header, infos)); u8 key_gen; FsRightsId rights_id; @@ -511,7 +511,10 @@ void Menu::OnChangeIndex(s64 new_index) { fsGetProgramId(&program_id, path, FsContentAttributes_All); } - if (R_SUCCEEDED(yati::ParseControlNca(path, program_id, &nacp, sizeof(nacp), &icon))) { + TimeStamp ts; + if (R_SUCCEEDED(nca::ParseControl(path, program_id, &nacp, sizeof(nacp), &icon))) { + log_write("\t\tnca::ParseControl(): %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); + log_write("managed to parse control nca %s\n", path.s); NacpLanguageEntry* lang_entry{}; nacpGetLanguageEntry(&nacp, &lang_entry); diff --git a/sphaira/source/yati/nx/nca.cpp b/sphaira/source/yati/nx/nca.cpp index 143b4ab..5368ed3 100644 --- a/sphaira/source/yati/nx/nca.cpp +++ b/sphaira/source/yati/nx/nca.cpp @@ -151,6 +151,74 @@ Result VerifyFixedKey(const Header& header) { R_SUCCEED(); } +Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector& extended_header, std::vector& infos) { + FsFileSystem fs; + R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentMeta, path, FsContentAttributes_All)); + ON_SCOPE_EXIT(fsFsClose(std::addressof(fs))); + + FsDir dir; + R_TRY(fsFsOpenDirectory(std::addressof(fs), fs::FsPath{"/"}, FsDirOpenMode_ReadFiles, std::addressof(dir))); + ON_SCOPE_EXIT(fsDirClose(std::addressof(dir))); + + s64 total_entries; + FsDirectoryEntry buf; + R_TRY(fsDirRead(std::addressof(dir), std::addressof(total_entries), 1, std::addressof(buf))); + + FsFile file; + R_TRY(fsFsOpenFile(std::addressof(fs), fs::AppendPath("/", buf.name), FsOpenMode_Read, std::addressof(file))); + ON_SCOPE_EXIT(fsFileClose(std::addressof(file))); + + s64 offset{}; + u64 bytes_read; + R_TRY(fsFileRead(std::addressof(file), offset, std::addressof(header), sizeof(header), 0, std::addressof(bytes_read))); + offset += bytes_read; + + // read extended header + extended_header.resize(header.meta_header.extended_header_size); + R_TRY(fsFileRead(std::addressof(file), offset, extended_header.data(), extended_header.size(), 0, std::addressof(bytes_read))); + offset += bytes_read; + + // read infos. + infos.resize(header.meta_header.content_count); + R_TRY(fsFileRead(std::addressof(file), offset, infos.data(), infos.size() * sizeof(NcmPackagedContentInfo), 0, std::addressof(bytes_read))); + offset += bytes_read; + + R_SUCCEED(); +} + +Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out, s64 nacp_size, std::vector* icon_out) { + FsFileSystem fs; + R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentControl, path, FsContentAttributes_All)); + ON_SCOPE_EXIT(fsFsClose(std::addressof(fs))); + + // read nacp. + if (nacp_out) { + FsFile file; + R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/control.nacp"}, FsOpenMode_Read, std::addressof(file))); + ON_SCOPE_EXIT(fsFileClose(std::addressof(file))); + + u64 bytes_read; + R_TRY(fsFileRead(&file, 0, nacp_out, nacp_size, 0, &bytes_read)); + } + + // read icon. + if (icon_out) { + // todo: use matching icon based on the language version. + FsFile file; + R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/icon_AmericanEnglish.dat"}, FsOpenMode_Read, std::addressof(file))); + ON_SCOPE_EXIT(fsFileClose(std::addressof(file))); + + s64 size; + R_TRY(fsFileGetSize(std::addressof(file), std::addressof(size))); + icon_out->resize(size); + + u64 bytes_read; + R_TRY(fsFileRead(&file, 0, icon_out->data(), icon_out->size(), 0, &bytes_read)); + } + + R_SUCCEED(); +} + auto GetKeyGenStr(u8 key_gen) -> const char* { switch (key_gen) { case KeyGenerationOld_100: return "1.0.0"; diff --git a/sphaira/source/yati/yati.cpp b/sphaira/source/yati/yati.cpp index 89c73ea..c1690d9 100644 --- a/sphaira/source/yati/yati.cpp +++ b/sphaira/source/yati/yati.cpp @@ -929,7 +929,7 @@ Result Yati::InstallNca(std::span tickets, NcaCollection& nca) { } else if (nca.header.content_type == nca::ContentType_Control) { NacpLanguageEntry entry; std::vector icon; - R_TRY(yati::ParseControlNca(path, nca.header.program_id, &entry, sizeof(entry), &icon)); + R_TRY(nca::ParseControl(path, nca.header.program_id, &entry, sizeof(entry), &icon)); pbox->SetTitle(entry.name).SetImageData(icon); } @@ -949,7 +949,7 @@ Result Yati::InstallCnmtNca(std::span tickets, CnmtCollection& cn ncm::PackagedContentMeta header; std::vector infos; - R_TRY(ParseCnmtNca(path, cnmt.header.program_id, header, cnmt.extended_header, infos)); + R_TRY(nca::ParseCnmt(path, cnmt.header.program_id, header, cnmt.extended_header, infos)); for (const auto& packed_info : infos) { const auto& info = packed_info.info; @@ -1418,71 +1418,4 @@ Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr& extended_header, std::vector& infos) { - FsFileSystem fs; - R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentMeta, path, FsContentAttributes_All)); - ON_SCOPE_EXIT(fsFsClose(std::addressof(fs))); - - FsDir dir; - R_TRY(fsFsOpenDirectory(std::addressof(fs), fs::FsPath{"/"}, FsDirOpenMode_ReadFiles, std::addressof(dir))); - ON_SCOPE_EXIT(fsDirClose(std::addressof(dir))); - - s64 total_entries; - FsDirectoryEntry buf; - R_TRY(fsDirRead(std::addressof(dir), std::addressof(total_entries), 1, std::addressof(buf))); - - FsFile file; - R_TRY(fsFsOpenFile(std::addressof(fs), fs::AppendPath("/", buf.name), FsOpenMode_Read, std::addressof(file))); - ON_SCOPE_EXIT(fsFileClose(std::addressof(file))); - - s64 offset{}; - u64 bytes_read; - R_TRY(fsFileRead(std::addressof(file), offset, std::addressof(header), sizeof(header), 0, std::addressof(bytes_read))); - offset += bytes_read; - - // read extended header - extended_header.resize(header.meta_header.extended_header_size); - R_TRY(fsFileRead(std::addressof(file), offset, extended_header.data(), extended_header.size(), 0, std::addressof(bytes_read))); - offset += bytes_read; - - // read infos. - infos.resize(header.meta_header.content_count); - R_TRY(fsFileRead(std::addressof(file), offset, infos.data(), infos.size() * sizeof(NcmPackagedContentInfo), 0, std::addressof(bytes_read))); - offset += bytes_read; - - R_SUCCEED(); -} - -Result ParseControlNca(const fs::FsPath& path, u64 program_id, void* nacp_out, s64 nacp_size, std::vector* icon_out) { - FsFileSystem fs; - R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentControl, path, FsContentAttributes_All)); - ON_SCOPE_EXIT(fsFsClose(std::addressof(fs))); - - // read nacp. - if (nacp_out) { - FsFile file; - R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/control.nacp"}, FsOpenMode_Read, std::addressof(file))); - ON_SCOPE_EXIT(fsFileClose(std::addressof(file))); - - u64 bytes_read; - R_TRY(fsFileRead(&file, 0, nacp_out, nacp_size, 0, &bytes_read)); - } - - // read icon. - if (icon_out) { - FsFile file; - R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/icon_AmericanEnglish.dat"}, FsOpenMode_Read, std::addressof(file))); - ON_SCOPE_EXIT(fsFileClose(std::addressof(file))); - - s64 size; - R_TRY(fsFileGetSize(std::addressof(file), std::addressof(size))); - icon_out->resize(size); - - u64 bytes_read; - R_TRY(fsFileRead(&file, 0, icon_out->data(), icon_out->size(), 0, &bytes_read)); - } - - R_SUCCEED(); -} - } // namespace sphaira::yati