diff options
| author | PastaLaPate <pastalapate@proton.me> | 2026-11-02 20:34:15 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-11-02 20:34:15 +0100 |
| commit | 6f002badce45fa2fdb414043544c829e834a7e27 (patch) | |
| tree | 51c21a851fbc8218ebe7febb69fb710a891cc4fc /src/core/file_sys | |
| parent | 396f011ea875dfe4fdb1b0f489651ab74a505b74 (diff) | |
| parent | 046646e3514ca4950622d75609e68498f889989e (diff) | |
Merge branch 'nx2' into ci/qt-6.12.0
Diffstat (limited to 'src/core/file_sys')
| -rw-r--r-- | src/core/file_sys/fsmitm_romfsbuild.cpp | 27 | ||||
| -rw-r--r-- | src/core/file_sys/nup_writer.cpp | 99 | ||||
| -rw-r--r-- | src/core/file_sys/nup_writer.h | 17 | ||||
| -rw-r--r-- | src/core/file_sys/system_archive/system_archive.cpp | 19 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_real.cpp | 37 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_real.h | 8 |
6 files changed, 173 insertions, 34 deletions
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp index 8807bbd0f7..d34e24f177 100644 --- a/src/core/file_sys/fsmitm_romfsbuild.cpp +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -55,15 +55,18 @@ static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size struct RomFSBuildFileContext; +// The context's directories and files own the nodes; the links between them don't, or every +// parent and child would own each other and no build would ever be freed (title 1's loose RomFS: +// 206k files, ~50 MiB per build). struct RomFSBuildDirectoryContext { std::string path; u32 cur_path_ofs = 0; u32 path_len = 0; u32 entry_offset = 0; - std::shared_ptr<RomFSBuildDirectoryContext> parent; - std::shared_ptr<RomFSBuildDirectoryContext> child; - std::shared_ptr<RomFSBuildDirectoryContext> sibling; - std::shared_ptr<RomFSBuildFileContext> file; + RomFSBuildDirectoryContext* parent = nullptr; + RomFSBuildDirectoryContext* child = nullptr; + RomFSBuildDirectoryContext* sibling = nullptr; + RomFSBuildFileContext* file = nullptr; }; struct RomFSBuildFileContext { @@ -73,8 +76,8 @@ struct RomFSBuildFileContext { u32 entry_offset = 0; u64 offset = 0; u64 size = 0; - std::shared_ptr<RomFSBuildDirectoryContext> parent; - std::shared_ptr<RomFSBuildFileContext> sibling; + RomFSBuildDirectoryContext* parent = nullptr; + RomFSBuildFileContext* sibling = nullptr; VirtualFile source; }; @@ -168,7 +171,7 @@ bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> num_dirs++; dir_table_size += sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4); - dir_ctx->parent = std::move(parent_dir_ctx); + dir_ctx->parent = parent_dir_ctx.get(); directories.emplace_back(std::move(dir_ctx)); return true; @@ -180,7 +183,7 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare num_files++; file_table_size += sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4); - file_ctx->parent = std::move(parent_dir_ctx); + file_ctx->parent = parent_dir_ctx.get(); files.emplace_back(std::move(file_ctx)); return true; @@ -245,11 +248,11 @@ std::vector<std::pair<u64, VirtualFile>> RomFSBuildContext::Build() { Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4)); prev_file = cur_file; } - // Assign deferred parent/sibling ownership. + // Assign deferred parent/sibling links. for (auto it = files.rbegin(); it != files.rend(); ++it) { auto& cur_file = *it; cur_file->sibling = cur_file->parent->file; - cur_file->parent->file = cur_file; + cur_file->parent->file = cur_file.get(); } // Determine directory offsets. @@ -260,11 +263,11 @@ std::vector<std::pair<u64, VirtualFile>> RomFSBuildContext::Build() { static_cast<u32>(sizeof(RomFSDirectoryEntry) + Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4)); } - // Assign deferred parent/sibling ownership. + // Assign deferred parent/sibling links. for (auto it = directories.rbegin(); (*it) != root; ++it) { auto& cur_dir = *it; cur_dir->sibling = cur_dir->parent->child; - cur_dir->parent->child = cur_dir; + cur_dir->parent->child = cur_dir.get(); } // Create output map. 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 <cctype> #include <charconv> #include <cstring> +#include <functional> #include <limits> #include <map> #include <memory> @@ -516,8 +517,9 @@ std::unique_ptr<Loader::AppLoader_DeconstructedRomDirectory> OpenLoader( class Discovery { public: - Discovery(const fs::path& dump_root_, std::vector<fs::path> excluded_) - : dump_root{dump_root_}, excluded{std::move(excluded_)} {} + Discovery(const fs::path& dump_root_, std::vector<fs::path> 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<u32> 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<std::pair<u16, std::vector<u8>>>& tlvs, u16 type, + std::vector<u8> 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<fs::path> excluded; + Metadata overrides; RealVfsFilesystem vfs; Plan plan; std::vector<std::vector<std::pair<u16, std::vector<u8>>>> 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<fs::path> 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<std::pair<u64, fs::path>> 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<u8> 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()) { diff --git a/src/core/file_sys/nup_writer.h b/src/core/file_sys/nup_writer.h index 231387b156..d2423dd1f4 100644 --- a/src/core/file_sys/nup_writer.h +++ b/src/core/file_sys/nup_writer.h @@ -8,6 +8,7 @@ #include <functional> #include <optional> #include <string> +#include <vector> #include "common/common_types.h" @@ -30,6 +31,17 @@ enum class PackResult { Success, Cancelled, InvalidSource, IoError }; // that one file does not evict everything else from the cache. constexpr u64 DefaultRawThreshold = 256ULL * 1024 * 1024; +// Application metadata written to the base package (or the first package when there is no base). +// In PackOptions an empty field keeps what the dump supplies; ProbeMetadata fills all of them. +struct Metadata { + std::string title_name; + // JPEG or PNG bytes; 256x256 JPEG as distributed on-console. + std::vector<u8> icon; + std::string display_version; + // Where ProbeMetadata found display_version, e.g. "update 458752" or "base". + std::string version_source; +}; + struct PackOptions { // Low-storage mode: delete each source file once its extent is synced and journaled. bool delete_sources = false; @@ -37,6 +49,8 @@ struct PackOptions { int zstd_level = 3; // Files larger than this are always stored raw. u64 raw_threshold = DefaultRawThreshold; + // Overrides for the name, icon and display version found in the dump. + Metadata metadata; }; // A loose dump holds exefs/main and main.npdm, directly or under base/ or updates/<version>/, @@ -45,6 +59,9 @@ bool IsLooseDump(const std::filesystem::path& dir); // The dump folder a game list entry's exefs/main (or exefs folder) belongs to, if it is a loose // dump. std::optional<std::filesystem::path> DumpRootFromMain(const std::filesystem::path& main); +// Reads the name, icon and display version a dump would be packed with. The version comes from +// the newest update that reports one, else the base. +Metadata ProbeMetadata(const std::filesystem::path& dump_root); std::filesystem::path DefaultArchivePath(const std::filesystem::path& dump_root); // Writes <archive>.part and renames it when done; an existing archive is never overwritten. A diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index 1743d01b12..c3931b6f62 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <cstdlib> + #include "common/logging.h" #include "core/file_sys/romfs.h" #include "core/file_sys/system_archive/mii_model.h" @@ -12,6 +14,7 @@ #include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/system_archive/system_version.h" #include "core/file_sys/system_archive/time_zone_binary.h" +#include "core/file_sys/vfs/vfs_vector.h" namespace FileSys::SystemArchive { @@ -76,6 +79,22 @@ constexpr inline SystemArchiveDescriptor GetSystemArchive(u64 title_id) { VirtualFile SynthesizeSystemArchive(const u64 title_id) { auto const desc = GetSystemArchive(title_id); + if (desc.name == nullptr) { + // Not a system archive Eden knows (fmt throws on a null string, ending the process). + // Switch 2 system data (ids 04...) is not installed with Switch 1 firmware, and titles + // abort when it cannot be mounted (Nintendo Switch Sports Resort, 0400000000000849): + // serve an empty archive. EDEN_NX2_EMPTY_SYSTEM_DATA=0 fails the request instead. + static const bool empty_nx2 = [] { + const char* setting = std::getenv("EDEN_NX2_EMPTY_SYSTEM_DATA"); + return setting == nullptr || setting[0] != '0'; + }(); + if (empty_nx2 && (title_id >> 56) == 0x04) { + LOG_WARNING(Service_FS, "Empty archive for Switch 2 system data {:#016x}", title_id); + return CreateRomFS(std::make_shared<VectorVfsDirectory>()); + } + LOG_WARNING(Service_FS, "No system archive {:#016x} to synthesize", title_id); + return nullptr; + } LOG_INFO(Service_FS, "Synthesizing system archive '{}' ({:#016x}).", desc.name, title_id); if (desc.supplier != nullptr) { if (auto const dir = desc.supplier(); dir != nullptr) { diff --git a/src/core/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp index 5b5b54017c..7cc5315a70 100644 --- a/src/core/file_sys/vfs/vfs_real.cpp +++ b/src/core/file_sys/vfs/vfs_real.cpp @@ -114,7 +114,6 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { } VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional<u64> size, - std::optional<std::string> parent_path, OpenMode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); std::scoped_lock lk{list_lock}; @@ -132,14 +131,14 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op auto reference = std::make_unique<FileReference>(); this->InsertReferenceIntoListLocked(*reference); - auto file = std::make_shared<RealVfsFile>(*this, std::move(reference), path, perms, size, std::move(parent_path)); + auto file = std::make_shared<RealVfsFile>(*this, std::move(reference), path, perms, size); cache[path] = file; return file; } VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) { - return OpenFileFromEntry(path_, {}, {}, perms); + return OpenFileFromEntry(path_, {}, perms); } VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) { @@ -257,7 +256,8 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri return lk; } -void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) { +void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference, + const std::string& path) { if (in_dtor) return; @@ -266,6 +266,11 @@ void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference // Remove from list. this->RemoveReferenceFromListLocked(*reference); + // Forget the path, unless it was opened again since this file's last reference went away. + if (const auto it = cache.find(path); it != cache.end() && it->second.expired()) { + cache.erase(it); + } + // Close the file. if (reference->file) { reference->file.reset(); @@ -309,14 +314,11 @@ void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) } RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, - const std::string& path_, OpenMode perms_, std::optional<u64> size_, - std::optional<std::string> parent_path_) - : base(base_), reference(std::move(reference_)), path(path_), - parent_path(parent_path_ ? std::move(*parent_path_) : FS::GetParentPath(path_)), - path_components(FS::SplitPathComponentsCopy(path_)), size(size_), perms(perms_) {} + const std::string& path_, OpenMode perms_, std::optional<u64> size_) + : base(base_), reference(std::move(reference_)), path(path_), size(size_), perms(perms_) {} RealVfsFile::~RealVfsFile() { - base.DropReference(std::move(reference)); + base.DropReference(std::move(reference), path); } std::string RealVfsFile::GetName() const { @@ -325,7 +327,14 @@ std::string RealVfsFile::GetName() const { return FS::Android::GetFilename(path); } #endif - return path_components.empty() ? "" : std::string(path_components.back()); + // The last path component. + const auto end = path.find_last_not_of("\\/"); + if (end == std::string::npos) { + return ""; + } + const auto separator = path.find_last_of("\\/", end); + const auto begin = separator == std::string::npos ? 0 : separator + 1; + return path.substr(begin, end + 1 - begin); } std::size_t RealVfsFile::GetSize() const { @@ -343,7 +352,7 @@ bool RealVfsFile::Resize(std::size_t new_size) { } VirtualDir RealVfsFile::GetContainingDirectory() const { - return base.OpenDirectory(parent_path, perms); + return base.OpenDirectory(FS::GetParentPath(path), perms); } bool RealVfsFile::IsWritable() const { @@ -372,7 +381,7 @@ std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t o } bool RealVfsFile::Rename(std::string_view name) { - return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; + return base.MoveFile(path, FS::GetParentPath(path) + '/' + std::string(name)) != nullptr; } // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if @@ -390,7 +399,7 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>( &out](const std::filesystem::directory_entry& entry) { const auto full_path_string = FS::PathToUTF8String(entry.path()); - out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), path, perms)); + out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms)); return true; }; diff --git a/src/core/file_sys/vfs/vfs_real.h b/src/core/file_sys/vfs/vfs_real.h index a97f7d32ea..2df125b7eb 100644 --- a/src/core/file_sys/vfs/vfs_real.h +++ b/src/core/file_sys/vfs/vfs_real.h @@ -62,12 +62,11 @@ private: friend class RealVfsFile; std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms, FileReference& reference); - void DropReference(std::unique_ptr<FileReference>&& reference); + void DropReference(std::unique_ptr<FileReference>&& reference, const std::string& path); private: friend class RealVfsDirectory; VirtualFile OpenFileFromEntry(std::string_view path, std::optional<u64> size, - std::optional<std::string> parent_path, OpenMode perms = OpenMode::Read); private: @@ -84,7 +83,7 @@ class RealVfsFile : public VfsFile { public: RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference, const std::string& path, OpenMode perms = OpenMode::Read, - std::optional<u64> size = {}, std::optional<std::string> parent_path = {}); + std::optional<u64> size = {}); ~RealVfsFile() override; std::string GetName() const override; @@ -101,9 +100,8 @@ private: RealVfsFilesystem& base; std::unique_ptr<FileReference> reference; + // Only the path: a loose RomFS keeps one of these per file (title 1: 206k). std::string path; - std::string parent_path; - std::vector<std::string> path_components; std::optional<u64> size; OpenMode perms; }; |
