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