From 4267169006fc4ed43f6d810e08426fb8058de9c0 Mon Sep 17 00:00:00 2001 From: kraibse <33869366+kraibse@users.noreply.github.com> Date: Mon, 2 Nov 2026 17:23:47 +0100 Subject: file_sys: let the NUP packer override and probe the name, icon and version PackOptions::metadata overrides the title name, icon and display version found in the dump. A version override is written to the base and to the newest update, since the NUP loader reads the version from the newest update first. ProbeMetadata reads what a dump would be packed with, the version coming from the newest update that reports one, else the base. Assisted-by: Claude Sonnet 5.5 (wrote the metadata overrides, ProbeMetadata and their test) --- src/core/file_sys/nup_writer.cpp | 99 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 3 deletions(-) (limited to 'src/core/file_sys/nup_writer.cpp') diff --git a/src/core/file_sys/nup_writer.cpp b/src/core/file_sys/nup_writer.cpp index 8c7728ed01..4161969f98 100644 --- a/src/core/file_sys/nup_writer.cpp +++ b/src/core/file_sys/nup_writer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -516,8 +517,9 @@ std::unique_ptr OpenLoader( class Discovery { public: - Discovery(const fs::path& dump_root_, std::vector excluded_) - : dump_root{dump_root_}, excluded{std::move(excluded_)} {} + Discovery(const fs::path& dump_root_, std::vector excluded_, + const Metadata& overrides_) + : dump_root{dump_root_}, excluded{std::move(excluded_)}, overrides{overrides_} {} Plan Run() { const auto base = FindDirectory(dump_root, "base"); @@ -678,6 +680,34 @@ private: tlvs.emplace_back(TlvIcon, std::move(icon)); } } + + if (!overrides.title_name.empty()) { + SetTlv(tlvs, TlvTitleName, StringBytes(overrides.title_name)); + } + if (!overrides.icon.empty()) { + SetTlv(tlvs, TlvIcon, overrides.icon); + } + if (!overrides.display_version.empty()) { + // The loader reports the newest update's version when there is one. + SetTlv(tlvs, TlvDisplayVersion, StringBytes(overrides.display_version)); + std::optional newest; + for (u32 i = 0; i < plan.packages.size(); ++i) { + if (plan.packages[i].kind == KindUpdate && + (!newest || plan.packages[i].version >= plan.packages[*newest].version)) { + newest = i; + } + } + if (newest && *newest != target) { + SetTlv(package_tlvs[*newest], TlvDisplayVersion, + StringBytes(overrides.display_version)); + } + } + } + + static void SetTlv(std::vector>>& tlvs, u16 type, + std::vector payload) { + std::erase_if(tlvs, [type](const auto& tlv) { return tlv.first == type; }); + tlvs.emplace_back(type, std::move(payload)); } bool IsExcluded(const fs::path& path) const { @@ -722,6 +752,7 @@ private: fs::path dump_root; std::vector excluded; + Metadata overrides; RealVfsFilesystem vfs; Plan plan; std::vector>>> package_tlvs; @@ -838,7 +869,7 @@ public: for (const auto& path : {archive, part, journal}) { excluded.push_back(fs::absolute(path).lexically_normal()); } - plan = Discovery{dump_root, std::move(excluded)}.Run(); + plan = Discovery{dump_root, std::move(excluded), options.metadata}.Run(); layout = BuildLayout(plan); OpenNew(); } @@ -1421,6 +1452,68 @@ std::optional DumpRootFromMain(const fs::path& main) { return root; } +Metadata ProbeMetadata(const fs::path& dump_root) { + RealVfsFilesystem vfs; + const auto base = FindDirectory(dump_root, "base"); + const auto updates = FindDirectory(dump_root, "updates"); + + // Newest update first, then the base (or a bare dump's root). + std::vector> update_dirs; + std::error_code ec; + if (!updates.empty()) { + for (const auto& entry : fs::directory_iterator(updates, ec)) { + const auto version = ParseDecimal(ToUtf8(entry.path().filename())); + if (version && entry.is_directory(ec)) { + update_dirs.emplace_back(*version, entry.path()); + } + } + } + std::ranges::sort(update_dirs, std::greater<>{}); + const fs::path base_dir = !base.empty() ? base : (updates.empty() ? dump_root : fs::path{}); + + Metadata result; + const auto probe = [&](const fs::path& dir, const std::string& label, bool names) { + const auto loader = OpenLoader(vfs, dir); + if (!loader) { + return; + } + if (result.display_version.empty()) { + std::string version; + if (loader->ReadDisplayVersion(version) == Loader::ResultStatus::Success && + !version.empty()) { + result.display_version = version; + result.version_source = label; + } + } + if (names) { + NACP control; + if (result.title_name.empty() && + loader->ReadControlData(control) == Loader::ResultStatus::Success) { + result.title_name = control.GetApplicationName(); + } + std::vector icon; + if (result.icon.empty() && + loader->ReadIcon(icon) == Loader::ResultStatus::Success) { + result.icon = std::move(icon); + } + } + }; + for (const auto& [version, dir] : update_dirs) { + probe(dir, "update " + std::to_string(version), false); + } + if (!base_dir.empty()) { + probe(base_dir, "base", true); + } + // Names and icons fall back to the updates when the base has none. + for (const auto& [version, dir] : update_dirs) { + if (!result.title_name.empty() && !result.icon.empty()) { + break; + } + probe(dir, "update " + std::to_string(version), true); + } + return result; +} + fs::path DefaultArchivePath(const fs::path& dump_root) { auto root = dump_root; if (!root.has_filename()) { -- cgit v1.2.3