diff options
| author | PastaLaPate <pastalapate@proton.me> | 2026-11-02 20:29:43 +0100 |
|---|---|---|
| committer | PastaLaPate <pastalapate@proton.me> | 2026-11-02 20:29:43 +0100 |
| commit | 7d25dbcea1e08e09c952096671b5fe2196d7ac79 (patch) | |
| tree | f0b1ba079e2442daafb25812157100d3409bc564 /src/core/loader | |
| parent | 0d0e7788f8c86bcdbbef505654e9489808f38183 (diff) | |
| parent | 62214094d6c698b70f51016e5c12af415f025be7 (diff) | |
Merge remote-tracking branch 'upstream/nx2' into nx2
Diffstat (limited to 'src/core/loader')
| -rw-r--r-- | src/core/loader/deconstructed_rom_directory.cpp | 87 | ||||
| -rw-r--r-- | src/core/loader/deconstructed_rom_directory.h | 5 | ||||
| -rw-r--r-- | src/core/loader/loader.cpp | 35 | ||||
| -rw-r--r-- | src/core/loader/loader.h | 3 | ||||
| -rw-r--r-- | src/core/loader/nup.cpp | 280 | ||||
| -rw-r--r-- | src/core/loader/nup.h | 65 |
6 files changed, 451 insertions, 24 deletions
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index cda1645548..4b1168a59b 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -6,7 +6,11 @@ #include <algorithm> #include <cctype> +#include <chrono> #include <cstring> +#include <map> +#include <memory> +#include <mutex> #include <string_view> #include "common/logging.h" #include "common/random.h" @@ -90,34 +94,42 @@ FileSys::VirtualFile FindLooseRomFS(const FileSys::VirtualDir& exefs_dir) { } if (const auto parent = exefs_dir->GetParentDirectory()) { if (const auto romfs_dir = parent->GetSubdirectory("romfs")) { + // Building it walks every file and keeps an object for each (title 1: 206k files, ~150 + // MiB), so loaders of the same directory share one build while any of them holds it. + static std::mutex cache_mutex; + static std::map<std::string, std::weak_ptr<FileSys::VfsFile>> cache; + std::scoped_lock lk{cache_mutex}; + auto& cached = cache[romfs_dir->GetFullPath()]; + if (auto built = cached.lock()) { + return built; + } LOG_INFO(Loader, "Using RomFS directory next to the ExeFS"); - return FileSys::CreateRomFS(romfs_dir); + const auto started = std::chrono::steady_clock::now(); + auto built = FileSys::CreateRomFS(romfs_dir); + LOG_INFO(Loader, "Loose RomFS image ready: elapsed_ms={}", + std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::steady_clock::now() - started) + .count()); + cached = built; + return built; } } return nullptr; } -// The display version of a dump without control data, from its RomFS: Nintendo EPD titles name -// their resource size tables after it (System/Resource/ResourceSizeTable.Product.180.* in Mario -// Kart World 1.8.0). A hotfix can keep its minor version's tables (Product.110 in a 1.1.1). -std::string RomFSProductVersion(const FileSys::VirtualDir& romfs_dir) { - if (romfs_dir == nullptr) { - return {}; - } - const auto system_dir = romfs_dir->GetSubdirectory("System"); - const auto resource_dir = system_dir != nullptr ? system_dir->GetSubdirectory("Resource") : nullptr; - if (resource_dir == nullptr) { - return {}; - } - constexpr std::string_view Prefix = "ResourceSizeTable.Product."; +// The newest three-digit tag in a directory's file names of the form <prefix>NNN.<anything>. +std::string NewestProductTag(const FileSys::VirtualDir& dir, std::string_view prefix) { std::string best; - for (const auto& file : resource_dir->GetFiles()) { + if (dir == nullptr) { + return best; + } + for (const auto& file : dir->GetFiles()) { const std::string file_name = file->GetName(); - if (!file_name.starts_with(Prefix) || file_name.size() < Prefix.size() + 4) { + if (!file_name.starts_with(prefix) || file_name.size() < prefix.size() + 4) { continue; } - const std::string_view digits = std::string_view{file_name}.substr(Prefix.size(), 3); - if (file_name[Prefix.size() + 3] != '.' || + const std::string_view digits = std::string_view{file_name}.substr(prefix.size(), 3); + if (file_name[prefix.size() + 3] != '.' || !std::all_of(digits.begin(), digits.end(), [](char c) { return c >= '0' && c <= '9'; })) { continue; } @@ -125,6 +137,27 @@ std::string RomFSProductVersion(const FileSys::VirtualDir& romfs_dir) { best = version; } } + return best; +} + +// The display version of a dump without control data, from its RomFS: Nintendo EPD titles name +// their resource size tables after it (System/Resource/ResourceSizeTable.Product.180.* in Mario +// Kart World 1.8.0), and some name their game data list instead (GameData/GameDataList.Product.320.* +// in Donkey Kong Bananza 3.2.0). A hotfix can keep its minor version's tables (Product.110 in a +// 1.1.1). +std::string RomFSProductVersion(const FileSys::VirtualDir& romfs_dir) { + if (romfs_dir == nullptr) { + return {}; + } + const auto system_dir = romfs_dir->GetSubdirectory("System"); + std::string best = NewestProductTag( + system_dir != nullptr ? system_dir->GetSubdirectory("Resource") : nullptr, + "ResourceSizeTable.Product."); + if (const auto game_data = NewestProductTag(romfs_dir->GetSubdirectory("GameData"), + "GameDataList.Product."); + game_data > best) { + best = game_data; + } if (best.empty()) { return {}; } @@ -233,7 +266,7 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys name = control->GetApplicationName(); } - romfs = FindLooseRomFS(file_dir); + romfs_exefs_dir = file_dir; if (const auto title_dir = file_dir->GetParentDirectory()) { romfs_version = RomFSProductVersion(title_dir->GetSubdirectory("romfs")); } @@ -245,7 +278,17 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( , dir(std::move(directory)) , override_update(override_update_) { - romfs = FindLooseRomFS(dir); + romfs_exefs_dir = dir; +} + +// Loaders made only for metadata (the game list, title lookups) never need the RomFS, and several +// of them were alive with a whole copy each. +const FileSys::VirtualFile& AppLoader_DeconstructedRomDirectory::LooseRomFS() { + if (!romfs_resolved) { + romfs_resolved = true; + romfs = FindLooseRomFS(romfs_exefs_dir); + } + return romfs; } FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { @@ -304,7 +347,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect // Loose-ExeFS loads bypass the NCA/NSP loaders, which are the ones that normally apply // LayeredFS to the RomFS; apply it here so mods in user/load/<titleid>/ work too. - if (romfs != nullptr) { + if (LooseRomFS() != nullptr) { const FileSys::PatchManager patch_manager(metadata.GetTitleID(), system.GetFileSystemController(), system.GetContentProvider()); @@ -414,7 +457,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(FileSys::VirtualFile& out_dir) { - if (romfs == nullptr) { + if (LooseRomFS() == nullptr) { return ResultStatus::ErrorNoRomFS; } diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index b976b71ecb..db0120f4ad 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -58,8 +58,13 @@ public: ResultStatus ReadNSOModules(Modules& out_modules) override; private: + /// The loose RomFS next to the ExeFS, found (and built) on first use. + const FileSys::VirtualFile& LooseRomFS(); + FileSys::ProgramMetadata metadata; FileSys::VirtualFile romfs; + FileSys::VirtualDir romfs_exefs_dir; ///< Where LooseRomFS looks + bool romfs_resolved{}; FileSys::VirtualDir dir; std::vector<u8> icon_data; diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 33a93573cf..839bb4d80b 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -29,6 +29,7 @@ #include "core/loader/nro.h" #include "core/loader/nso.h" #include "core/loader/nsp.h" +#include "core/loader/nup.h" #include "core/loader/xci.h" namespace Loader { @@ -130,6 +131,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) { return *nsp_type; } else if (const auto xci_type = IdentifyFileLoader<AppLoader_XCI>(file)) { return *xci_type; + } else if (const auto nup_type = IdentifyFileLoader<AppLoader_NUP>(file)) { + return *nup_type; } else if (const auto nro_type = IdentifyFileLoader<AppLoader_NRO>(file)) { return *nro_type; } else if (const auto nca_type = IdentifyFileLoader<AppLoader_NCA>(file)) { @@ -150,7 +153,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { bool IsContainerType(FileType type) { return type == FileType::NSP || type == FileType::NSZ || type == FileType::XCI || - type == FileType::XCZ; + type == FileType::XCZ || type == FileType::NUP; } bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id, @@ -163,6 +166,24 @@ bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 progr type = IdentifyFile(file); } + if (type == FileType::NUP) { + FileSys::NupContainer nup{file}; + if (nup.GetStatus() != ResultStatus::Success) { + return false; + } + // A base package boots normally; a standalone update (no base) carries + // a complete patched tree and boots on its own (SPEC section 4.1.2). + if (const auto base = nup.FindBasePackage(); + base.has_value() && nup.GetExeFSDir(*base) != nullptr) { + return true; + } + if (const auto update = nup.FindBestUpdatePackage(); + update.has_value() && nup.GetExeFSDir(*update) != nullptr) { + return true; + } + return false; + } + if (!IsContainerType(type)) { return false; } @@ -190,6 +211,8 @@ FileType GuessFromFilename(const std::string& name) { return FileType::XCZ; else if (extension == "nsp") return FileType::NSP; + else if (extension == "nup") + return FileType::NUP; else if (extension == "nsz") return FileType::NSZ; else if (extension == "kip") @@ -215,6 +238,8 @@ std::string GetFileTypeString(FileType type) { return "NSP"; case FileType::NSZ: return "NSZ"; + case FileType::NUP: + return "NUP"; case FileType::KIP: return "KIP"; case FileType::DeconstructedRomDirectory: @@ -227,7 +252,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 68> RESULT_MESSAGES{ +constexpr std::array<const char*, 70> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -296,6 +321,8 @@ constexpr std::array<const char*, 68> RESULT_MESSAGES{ "The INI file contains more than the maximum allowable number of KIP files.", "Integrity verification could not be performed for this file.", "Integrity verification failed.", + "The NUP container is malformed or its TOC hash does not match.", + "The NUP container does not contain a loadable base package.", }; std::string GetResultStatusString(ResultStatus status) { @@ -380,6 +407,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V case FileType::KIP: return std::make_unique<AppLoader_KIP>(std::move(file)); + // NUP (NX Package) container format. + case FileType::NUP: + return std::make_unique<AppLoader_NUP>(std::move(file)); + // NX deconstructed ROM directory. case FileType::DeconstructedRomDirectory: return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 431aeace73..23be3597f5 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -47,6 +47,7 @@ enum class FileType { XCZ, NAX, KIP, + NUP, DeconstructedRomDirectory, }; @@ -157,6 +158,8 @@ enum class ResultStatus : u16 { ErrorINITooManyKIPs, ErrorIntegrityVerificationNotImplemented, ErrorIntegrityVerificationFailed, + ErrorNUPBadFormat, + ErrorNUPNoBasePackage, }; std::string GetResultStatusString(ResultStatus status); diff --git a/src/core/loader/nup.cpp b/src/core/loader/nup.cpp new file mode 100644 index 0000000000..7ddfb8947e --- /dev/null +++ b/src/core/loader/nup.cpp @@ -0,0 +1,280 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/loader/nup.h" + +#include <algorithm> +#include <cstring> + +#include "common/logging.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/nup_container.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/romfs_factory.h" +#include "core/file_sys/vfs/vfs_vector.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/deconstructed_rom_directory.h" + +namespace Loader { + +namespace { + +using FileSys::NupContainer; + +// NACP stores 16 uncompressed language entries; map BCP-47 tags onto them. +struct LanguageSlot { + std::string_view tag; + std::size_t index; +}; + +constexpr std::array<LanguageSlot, 18> LanguageSlots{{ + {"en-us", 0}, {"en-gb", 1}, {"ja", 2}, {"fr", 3}, {"de", 4}, + {"es-419", 5}, {"es", 6}, {"it", 7}, {"nl", 8}, {"fr-ca", 9}, + {"pt", 10}, {"ru", 11}, {"ko", 12}, {"zh-hant", 13}, {"zh-hans", 14}, + {"pt-br", 15}, {"pl", 16}, {"th", 17}, +}}; + +void CopyNameToEntry(FileSys::LanguageEntry& entry, std::string_view name) { + const std::size_t length = std::min(name.size(), entry.application_name.size() - 1); + std::memcpy(entry.application_name.data(), name.data(), length); + entry.application_name[length] = '\0'; +} + +std::unique_ptr<FileSys::NACP> BuildNacp(const NupContainer::PackageInfo& base, + const std::string& version) { + if (base.title_name.empty() && base.localized_titles.empty() && version.empty()) { + return nullptr; + } + auto nacp = std::make_unique<FileSys::NACP>(); + nacp->raw.supported_languages = static_cast<FileSys::SupportedLanguage>(0x3FFFF); + + for (const auto& [lang, name] : base.localized_titles) { + for (const auto& slot : LanguageSlots) { + // The uncompressed RawNACP only stores 16 entries; Polish/Thai exist + // only in the compressed format, so they fall back to the default. + if (slot.index < 16 && slot.tag == lang) { + CopyNameToEntry(nacp->raw.language_entries.language_entries[slot.index], name); + } + } + } + if (!base.title_name.empty()) { + CopyNameToEntry(nacp->raw.language_entries.language_entries[0], base.title_name); + } + if (!version.empty()) { + const std::size_t length = std::min(version.size(), nacp->raw.version_string.size() - 1); + std::memcpy(nacp->raw.version_string.data(), version.data(), length); + nacp->raw.version_string[length] = '\0'; + } + nacp->raw.save_data_owner_id = base.title_id; + + nacp->language_entries.resize(16); + std::memcpy(nacp->language_entries.data(), nacp->raw.language_entries.language_entries.data(), + sizeof(nacp->raw.language_entries.language_entries)); + return nacp; +} + +std::string DecodeVersion(u32 version) { + if (version == 0) { + return {}; + } + return fmt::format("{}.{}.{}", version >> 16, (version >> 8) & 0xFF, version & 0xFF); +} + +} // namespace + +AppLoader_NUP::AppLoader_NUP(FileSys::VirtualFile file_) + : AppLoader(std::move(file_)), nup(std::make_unique<NupContainer>(file)) { + if (nup->GetStatus() != ResultStatus::Success) { + return; + } + + const auto base_index = nup->FindBasePackage(); + const auto update_index = nup->FindBestUpdatePackage(); + + // The program comes from the base package; a standalone update container + // (SPEC §4.1.2) carries a complete patched tree and boots on its own. + std::optional<std::size_t> program_index; + std::optional<std::size_t> exefs_index; + if (base_index && nup->GetExeFSDir(*base_index) != nullptr) { + program_index = base_index; + exefs_index = update_index && nup->GetExeFSDir(*update_index) != nullptr + ? update_index + : base_index; + } else if (update_index && nup->GetExeFSDir(*update_index) != nullptr) { + LOG_INFO(Loader, + "NUP container is a standalone update; booting the update program directly"); + program_index = update_index; + exefs_index = update_index; + } + if (!program_index || !exefs_index) { + LOG_ERROR(Loader, "NUP container has no package with a usable ExeFS"); + return; + } + + const auto& program = nup->GetPackages()[*program_index]; + base_title_id = program.title_id; + has_program = true; + + const auto exefs = nup->GetExeFSDir(*exefs_index); + if (exefs == nullptr || exefs->GetFile("main") == nullptr || + exefs->GetFile("main.npdm") == nullptr) { + LOG_ERROR(Loader, "NUP package has no usable ExeFS"); + return; + } + secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); + + if (update_index) { + const auto& update = nup->GetPackages()[*update_index]; + display_version = update.display_version.empty() ? DecodeVersion(update.version) + : update.display_version; + } else { + display_version = program.display_version.empty() ? DecodeVersion(program.version) + : program.display_version; + } + nacp_file = BuildNacp(program, display_version); + if (nacp_file != nullptr && !program.icon.empty()) { + icon_file = std::make_shared<FileSys::VectorVfsFile>(program.icon, "icon.dat"); + } else if (nacp_file != nullptr) { + for (const auto& [lang, icon] : program.localized_icons) { + if (lang == "en-us") { + icon_file = std::make_shared<FileSys::VectorVfsFile>(icon, "icon_" + lang + ".dat"); + break; + } + } + if (icon_file == nullptr && !program.localized_icons.empty()) { + icon_file = std::make_shared<FileSys::VectorVfsFile>( + program.localized_icons.front().second, "icon.dat"); + } + } + + const auto addon_count = std::count_if(nup->GetPackages().begin(), nup->GetPackages().end(), + [](const auto& package) { + return package.kind == + NupContainer::PackageKind::Addon; + }); + if (addon_count > 0) { + LOG_WARNING(Loader, + "NUP container has {} add-on content package(s); AoC mounting is not " + "supported yet", + addon_count); + } +} + +AppLoader_NUP::~AppLoader_NUP() = default; + +FileType AppLoader_NUP::IdentifyType(const FileSys::VirtualFile& nup_file) { + return FileSys::NupContainer::IsNupFile(nup_file) ? FileType::NUP : FileType::Error; +} + +AppLoader::LoadResult AppLoader_NUP::Load(Kernel::KProcess& process, Core::System& system) { + if (is_loaded) { + return {ResultStatus::ErrorAlreadyLoaded, {}}; + } + if (nup->GetStatus() != ResultStatus::Success) { + return {nup->GetStatus(), {}}; + } + if (!has_program || secondary_loader == nullptr) { + return {ResultStatus::ErrorNUPNoBasePackage, {}}; + } + + const auto result = secondary_loader->Load(process, system); + if (result.first != ResultStatus::Success) { + return result; + } + + system.GetFileSystemController().RegisterProcess( + process.GetProcessId(), base_title_id, + std::make_shared<FileSys::RomFSFactory>(*this, system.GetContentProvider(), + system.GetFileSystemController())); + + is_loaded = true; + return result; +} + +ResultStatus AppLoader_NUP::VerifyIntegrity( + std::function<bool(size_t, size_t)> progress_callback) { + if (nup->GetStatus() != ResultStatus::Success) { + return nup->GetStatus(); + } + return nup->VerifyIntegrity(std::move(progress_callback)); +} + +ResultStatus AppLoader_NUP::ReadRomFS(FileSys::VirtualFile& out_file) { + const auto base_index = nup->FindBasePackage(); + const auto base_romfs = base_index ? nup->GetRomFSDir(*base_index) : FileSys::VirtualDir{}; + const auto update_index = nup->FindBestUpdatePackage(); + const auto update_romfs = + update_index ? nup->GetRomFSDir(*update_index) : FileSys::VirtualDir{}; + // A standalone update carries the complete merged tree; a base+update pair + // overlays the update onto the base (SPEC §4.1.4). + const auto merged = FileSys::NupContainer::MergeShadowDirs(base_romfs, update_romfs); + if (merged == nullptr) { + return ResultStatus::ErrorNoRomFS; + } + out_file = FileSys::CreateRomFS(merged); + if (out_file == nullptr) { + return ResultStatus::ErrorNoRomFS; + } + return ResultStatus::Success; +} + +bool AppLoader_NUP::IsRomFSUpdatable() const { + // The update is already merged into the exposed RomFS. + return false; +} + +ResultStatus AppLoader_NUP::ReadProgramId(u64& out_program_id) { + if (!has_program) { + return ResultStatus::ErrorNotInitialized; + } + out_program_id = base_title_id; + return ResultStatus::Success; +} + +ResultStatus AppLoader_NUP::ReadProgramIds(std::vector<u64>& out_program_ids) { + if (!has_program) { + return ResultStatus::ErrorNotInitialized; + } + out_program_ids.push_back(base_title_id); + return ResultStatus::Success; +} + +ResultStatus AppLoader_NUP::ReadIcon(std::vector<u8>& buffer) { + if (icon_file == nullptr) { + return ResultStatus::ErrorNoIcon; + } + buffer = icon_file->ReadAllBytes(); + return ResultStatus::Success; +} + +ResultStatus AppLoader_NUP::ReadTitle(std::string& title) { + if (nacp_file == nullptr) { + return ResultStatus::ErrorNoControl; + } + title = nacp_file->GetApplicationName(); + if (title.empty()) { + return ResultStatus::ErrorNoControl; + } + return ResultStatus::Success; +} + +ResultStatus AppLoader_NUP::ReadControlData(FileSys::NACP& control) { + if (nacp_file == nullptr) { + return ResultStatus::ErrorNoControl; + } + control = *nacp_file; + return ResultStatus::Success; +} + +ResultStatus AppLoader_NUP::ReadNSOModules(Modules& modules) { + if (secondary_loader == nullptr) { + return ResultStatus::ErrorNotInitialized; + } + return secondary_loader->ReadNSOModules(modules); +} + +} // namespace Loader diff --git a/src/core/loader/nup.h b/src/core/loader/nup.h new file mode 100644 index 0000000000..1f9dd5922b --- /dev/null +++ b/src/core/loader/nup.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <memory> +#include <optional> +#include <string> + +#include "core/file_sys/nup_container.h" +#include "core/loader/loader.h" + +namespace FileSys { +class NACP; +class NupContainer; +} // namespace FileSys + +namespace Loader { + +/// Loads a NUP (NX Package) container: loose ExeFS/RomFS trees for a base +/// program plus its updates in one file. The newest update package is layered +/// over the base; add-on content packages are recognized but not yet mounted. +class AppLoader_NUP final : public AppLoader { +public: + explicit AppLoader_NUP(FileSys::VirtualFile file_); + ~AppLoader_NUP() override; + + /** + * Identifies whether or not the given file is a NUP container. + * + * @param nup_file The file to identify. + * + * @return FileType::NUP, or FileType::Error if the file is not a NUP. + */ + static FileType IdentifyType(const FileSys::VirtualFile& nup_file); + + FileType GetFileType() const override { + return FileType::NUP; + } + + LoadResult Load(Kernel::KProcess& process, Core::System& system) override; + + ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; + + ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; + bool IsRomFSUpdatable() const override; + ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; + ResultStatus ReadIcon(std::vector<u8>& buffer) override; + ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadControlData(FileSys::NACP& control) override; + ResultStatus ReadNSOModules(Modules& modules) override; + +private: + std::unique_ptr<FileSys::NupContainer> nup; + std::unique_ptr<AppLoader> secondary_loader; + + std::unique_ptr<FileSys::NACP> nacp_file; + FileSys::VirtualFile icon_file; + u64 base_title_id{}; + std::string display_version; + bool has_program{}; +}; + +} // namespace Loader |
