From d8273c38578780e1e8880648d07d292f6d2461a6 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 17 Oct 2018 09:03:56 -0400 Subject: [PATCH 1/4] patch_manager: Add support for using LayeredFS with Data --- src/core/file_sys/patch_manager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 0117cb0bf7..1f49285620 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -168,7 +168,8 @@ bool PatchManager::HasNSOPatch(const std::array& build_id_) const { static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); - if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) { + if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || + load_dir == nullptr || load_dir->GetSize() <= 0) { return; } @@ -218,7 +219,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content title_id, static_cast(type)) .c_str(); - if (type == ContentRecordType::Program) + if (type == ContentRecordType::Program || type == ContentRecordType::Data) LOG_INFO(Loader, log_string); else LOG_DEBUG(Loader, log_string); From 780c21ab2d48aeb5aee921943b14c8de76e86910 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 17 Oct 2018 09:04:20 -0400 Subject: [PATCH 2/4] fsp_srv: Apply patches to Data storage in OpenDataStorageByDataId --- src/core/hle/service/filesystem/fsp_srv.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index d5dced429a..c87721c39c 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -17,6 +17,7 @@ #include "core/file_sys/errors.h" #include "core/file_sys/mode.h" #include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" @@ -630,6 +631,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { static_cast(storage_id), unknown, title_id); auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); + if (data.Failed()) { // TODO(DarkLordZach): Find the right error code to use here LOG_ERROR(Service_FS, @@ -640,7 +642,9 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { return; } - IStorage storage(std::move(data.Unwrap())); + FileSys::PatchManager pm{title_id}; + + IStorage storage(pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); From 59044862a902913700c3e7062ac8cfa43811c420 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 17 Oct 2018 14:04:18 -0400 Subject: [PATCH 3/4] registered_cache: Deduplicate results of ListEntry and ListEntryFilter Prevents a Entry from appearing in the list twice if the user has it installed in two places (e.g. User NAND and SDMC) --- src/core/file_sys/registered_cache.cpp | 11 +++++++++++ src/core/file_sys/registered_cache.h | 7 +++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 1febb398e1..d1dea5e826 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include "common/assert.h" @@ -30,6 +31,10 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type); } +bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { + return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type); +} + static bool FollowsTwoDigitDirFormat(std::string_view name) { static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript | std::regex_constants::icase); @@ -593,6 +598,9 @@ std::vector RegisteredCacheUnion::ListEntries() const { }, [](const CNMT& c, const ContentRecord& r) { return true; }); } + + std::sort(out.begin(), out.end()); + out.erase(std::unique(out.begin(), out.end()), out.end()); return out; } @@ -616,6 +624,9 @@ std::vector RegisteredCacheUnion::ListEntriesFilter( return true; }); } + + std::sort(out.begin(), out.end()); + out.erase(std::unique(out.begin(), out.end()), out.end()); return out; } } // namespace FileSys diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 5ddacba475..aeb1c69bac 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -50,6 +50,9 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) { // boost flat_map requires operator< for O(log(n)) lookups. bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); +// std unique requires operator== to identify duplicates. +bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); + /* * A class that catalogues NCAs in the registered directory structure. * Nintendo's registered format follows this structure: @@ -60,8 +63,8 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) * | 00 * | 01 <- Actual content split along 4GB boundaries. (optional) * - * (This impl also supports substituting the nca dir for an nca file, as that's more convenient when - * 4GB splitting can be ignored.) + * (This impl also supports substituting the nca dir for an nca file, as that's more convenient + * when 4GB splitting can be ignored.) */ class RegisteredCache { friend class RegisteredCacheUnion; From 9d0fb0f81506429fa83923b04878a5ee2e8ff420 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 17 Oct 2018 18:27:23 -0400 Subject: [PATCH 4/4] qt: Add support for dumping a DLC Data RomFS --- src/core/file_sys/registered_cache.cpp | 4 ++ src/core/file_sys/registered_cache.h | 1 + src/yuzu/main.cpp | 73 ++++++++++++++++++++++---- src/yuzu/main.h | 6 ++- 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index d1dea5e826..29b1004141 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -35,6 +35,10 @@ bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type); } +bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { + return !operator==(lhs, rhs); +} + static bool FollowsTwoDigitDirFormat(std::string_view name) { static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript | std::regex_constants::icase); diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index aeb1c69bac..5beceffb3e 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -52,6 +52,7 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) // std unique requires operator== to identify duplicates. bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); +bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); /* * A class that catalogues NCAs in the registered directory structure. diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index bef9df00da..36c702195c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -100,6 +100,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif +constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; + /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there * is a bitfield "callout_flags" options, used to track if a message has already been shown to the @@ -823,14 +825,10 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src } void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { - const auto path = fmt::format("{}{:016X}/romfs", - FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id); - - const auto failed = [this, &path] { + const auto failed = [this] { QMessageBox::warning(this, tr("RomFS Extraction Failed!"), tr("There was an error copying the RomFS files or the user " "cancelled the operation.")); - vfs->DeleteDirectory(path); }; const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read)); @@ -845,10 +843,24 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - const auto romfs = - loader->IsRomFSUpdatable() - ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset()) - : file; + const auto installed = Service::FileSystem::GetUnionContents(); + auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id); + + if (!romfs_title_id) { + failed(); + return; + } + + const auto path = fmt::format( + "{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id); + + FileSys::VirtualFile romfs; + + if (*romfs_title_id == program_id) { + romfs = file; + } else { + romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); + } const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); if (extracted == nullptr) { @@ -860,6 +872,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa if (out == nullptr) { failed(); + vfs->DeleteDirectory(path); return; } @@ -870,8 +883,11 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa "files into the new directory while
skeleton will only create the directory " "structure."), {"Full", "Skeleton"}, 0, false, &ok); - if (!ok) + if (!ok) { failed(); + vfs->DeleteDirectory(path); + return; + } const auto full = res == "Full"; const auto entry_size = CalculateRomFSEntrySize(extracted, full); @@ -888,6 +904,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa } else { progress.close(); failed(); + vfs->DeleteDirectory(path); } } @@ -1459,6 +1476,42 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { } } +boost::optional GMainWindow::SelectRomFSDumpTarget( + const FileSys::RegisteredCacheUnion& installed, u64 program_id) { + const auto dlc_entries = + installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + std::vector dlc_match; + dlc_match.reserve(dlc_entries.size()); + std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), + [&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) { + return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id && + installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; + }); + + std::vector romfs_tids; + romfs_tids.push_back(program_id); + for (const auto& entry : dlc_match) + romfs_tids.push_back(entry.title_id); + + if (romfs_tids.size() > 1) { + QStringList list{"Base"}; + for (std::size_t i = 1; i < romfs_tids.size(); ++i) + list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); + + bool ok; + const auto res = QInputDialog::getItem( + this, tr("Select RomFS Dump Target"), + tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); + if (!ok) { + return boost::none; + } + + return romfs_tids[list.indexOf(res)]; + } + + return program_id; +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 3663d6aeda..c8cbc0ba8d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -10,6 +10,7 @@ #include #include +#include #include "common/common_types.h" #include "core/core.h" #include "ui_main.h" @@ -29,8 +30,9 @@ class WaitTreeWidget; enum class GameListOpenTarget; namespace FileSys { +class RegisteredCacheUnion; class VfsFilesystem; -} +} // namespace FileSys namespace Tegra { class DebugContext; @@ -175,6 +177,8 @@ private slots: void OnReinitializeKeys(ReinitializeKeyBehavior behavior); private: + boost::optional SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, + u64 program_id); void UpdateStatusBar(); Ui::MainWindow ui;