From 07073734ed3785d1dee487f0c898a645fbd5f03c Mon Sep 17 00:00:00 2001 From: Feng Chen Date: Tue, 20 Jul 2021 13:10:05 +0800 Subject: [PATCH] file_sys: Support load game collection (#6582) Adds support for loading games with multiple programs embedded within such as the Dragon Quest 1+2+3 Collection --- src/core/core.cpp | 13 ++--- src/core/core.h | 2 +- src/core/file_sys/card_image.cpp | 18 ++++-- src/core/file_sys/card_image.h | 3 +- src/core/file_sys/submission_package.cpp | 73 ++++++++++++------------ src/core/file_sys/submission_package.h | 11 ++-- src/core/loader/loader.cpp | 13 +++-- src/core/loader/loader.h | 13 ++++- src/core/loader/nsp.cpp | 34 +++++------ src/core/loader/nsp.h | 4 +- src/core/loader/xci.cpp | 14 +++-- src/core/loader/xci.h | 3 +- src/yuzu/game_list.cpp | 8 ++- src/yuzu/game_list.h | 5 +- src/yuzu/game_list_worker.cpp | 44 +++++++++++--- src/yuzu/main.cpp | 17 +++--- src/yuzu/main.h | 6 +- 17 files changed, 172 insertions(+), 109 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 406320ed6..15226cf41 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -216,9 +216,9 @@ struct System::Impl { } ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, - std::size_t program_index) { + u64 program_id, std::size_t program_index) { app_loader = Loader::GetLoader(system, GetGameFileFromPath(virtual_filesystem, filepath), - program_index); + program_id, program_index); if (!app_loader) { LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); @@ -269,11 +269,10 @@ struct System::Impl { } } - u64 title_id{0}; - if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { + if (app_loader->ReadProgramId(program_id) != Loader::ResultStatus::Success) { LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", load_result); } - perf_stats = std::make_unique(title_id); + perf_stats = std::make_unique(program_id); // Reset counters and set time origin to current frame GetAndResetPerfStats(); perf_stats->BeginSystemFrame(); @@ -459,8 +458,8 @@ void System::Shutdown() { } System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath, - std::size_t program_index) { - return impl->Load(*this, emu_window, filepath, program_index); + u64 program_id, std::size_t program_index) { + return impl->Load(*this, emu_window, filepath, program_id, program_index); } bool System::IsPoweredOn() const { diff --git a/src/core/core.h b/src/core/core.h index 8b93ba998..b93c32e60 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -175,7 +175,7 @@ public: * @returns ResultStatus code, indicating if the operation succeeded. */ [[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath, - std::size_t program_index = 0); + u64 program_id = 0, std::size_t program_index = 0); /** * Indicates if the emulated system is powered on (all subsystems initialized and able to run an diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index db2f6a955..755d3303e 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -29,7 +29,7 @@ constexpr std::array partition_names{ "logo", }; -XCI::XCI(VirtualFile file_, std::size_t program_index) +XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index) : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, partitions(partition_names.size()), partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { @@ -63,12 +63,12 @@ XCI::XCI(VirtualFile file_, std::size_t program_index) secure_partition = std::make_shared( main_hfs.GetFile(partition_names[static_cast(XCIPartition::Secure)]), - program_index); + program_id, program_index); ncas = secure_partition->GetNCAsCollapsed(); program = secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program); - program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID()); + program_nca_status = secure_partition->GetProgramStatus(); if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) { program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; } @@ -174,6 +174,10 @@ u64 XCI::GetProgramTitleID() const { return secure_partition->GetProgramTitleID(); } +std::vector XCI::GetProgramTitleIDs() const { + return secure_partition->GetProgramTitleIDs(); +} + u32 XCI::GetSystemUpdateVersion() { const auto update = GetPartition(XCIPartition::Update); if (update == nullptr) { @@ -229,9 +233,11 @@ const std::vector>& XCI::GetNCAs() const { } std::shared_ptr XCI::GetNCAByType(NCAContentType type) const { - const auto iter = - std::find_if(ncas.begin(), ncas.end(), - [type](const std::shared_ptr& nca) { return nca->GetType() == type; }); + const auto program_id = secure_partition->GetProgramTitleID(); + const auto iter = std::find_if( + ncas.begin(), ncas.end(), [this, type, program_id](const std::shared_ptr& nca) { + return nca->GetType() == type && nca->GetTitleId() == program_id; + }); return iter == ncas.end() ? nullptr : *iter; } diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 4960e90fe..0fd9fa87c 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -78,7 +78,7 @@ enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; class XCI : public ReadOnlyVfsDirectory { public: - explicit XCI(VirtualFile file, std::size_t program_index = 0); + explicit XCI(VirtualFile file, u64 program_id = 0, size_t program_index = 0); ~XCI() override; Loader::ResultStatus GetStatus() const; @@ -104,6 +104,7 @@ public: VirtualFile GetLogoPartitionRaw() const; u64 GetProgramTitleID() const; + std::vector GetProgramTitleIDs() const; u32 GetSystemUpdateVersion(); u64 GetSystemUpdateTitleID() const; diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index d51d469e3..f192dffa5 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -20,8 +20,9 @@ namespace FileSys { -NSP::NSP(VirtualFile file_, std::size_t program_index_) - : file(std::move(file_)), program_index(program_index_), status{Loader::ResultStatus::Success}, +NSP::NSP(VirtualFile file_, u64 title_id_, std::size_t program_index_) + : file(std::move(file_)), expected_program_id(title_id_), + program_index(program_index_), status{Loader::ResultStatus::Success}, pfs(std::make_shared(file)), keys{Core::Crypto::KeyManager::Instance()} { if (pfs->GetStatus() != Loader::ResultStatus::Success) { status = pfs->GetStatus(); @@ -46,60 +47,59 @@ Loader::ResultStatus NSP::GetStatus() const { return status; } -Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { +Loader::ResultStatus NSP::GetProgramStatus() const { if (IsExtractedType() && GetExeFS() != nullptr && FileSys::IsDirectoryExeFS(GetExeFS())) { return Loader::ResultStatus::Success; } - const auto iter = program_status.find(title_id); + const auto iter = program_status.find(GetProgramTitleID()); if (iter == program_status.end()) return Loader::ResultStatus::ErrorNSPMissingProgramNCA; return iter->second; } -u64 NSP::GetFirstTitleID() const { - if (IsExtractedType()) { - return GetProgramTitleID(); - } - - if (program_status.empty()) - return 0; - return program_status.begin()->first; -} - u64 NSP::GetProgramTitleID() const { if (IsExtractedType()) { - if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) { - return 0; - } + return GetExtractedTitleID() + program_index; + } - ProgramMetadata meta; - if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) { - return meta.GetTitleID(); - } else { - return 0; + auto program_id = expected_program_id; + if (program_id == 0) { + if (!program_status.empty()) { + program_id = program_status.begin()->first; } } - const auto out = GetFirstTitleID(); - if ((out & 0x800) == 0) - return out; + program_id = program_id + program_index; + if (program_status.find(program_id) != program_status.end()) { + return program_id; + } - const auto ids = GetTitleIDs(); + const auto ids = GetProgramTitleIDs(); const auto iter = std::find_if(ids.begin(), ids.end(), [](u64 tid) { return (tid & 0x800) == 0; }); - return iter == ids.end() ? out : *iter; + return iter == ids.end() ? 0 : *iter; } -std::vector NSP::GetTitleIDs() const { - if (IsExtractedType()) { - return {GetProgramTitleID()}; +u64 NSP::GetExtractedTitleID() const { + if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) { + return 0; } - std::vector out; - out.reserve(ncas.size()); - for (const auto& kv : ncas) - out.push_back(kv.first); + ProgramMetadata meta; + if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) { + return meta.GetTitleID(); + } else { + return 0; + } +} + +std::vector NSP::GetProgramTitleIDs() const { + if (IsExtractedType()) { + return {GetExtractedTitleID()}; + } + + std::vector out{program_ids.cbegin(), program_ids.cend()}; return out; } @@ -146,7 +146,7 @@ std::shared_ptr NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType if (extracted) LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); - const auto title_id_iter = ncas.find(title_id + program_index); + const auto title_id_iter = ncas.find(title_id); if (title_id_iter == ncas.end()) return nullptr; @@ -160,7 +160,7 @@ std::shared_ptr NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType title_type) const { if (extracted) LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); - const auto nca = GetNCA(title_id, type); + const auto nca = GetNCA(title_id, type, title_type); if (nca != nullptr) return nca->GetBaseFile(); return nullptr; @@ -286,6 +286,7 @@ void NSP::ReadNCAs(const std::vector& files) { if (next_nca->GetType() == NCAContentType::Program) { program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); + program_ids.insert(next_nca->GetTitleId() & 0xFFFFFFFFFFFFF000); } if (next_nca->GetStatus() != Loader::ResultStatus::Success && diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index ecb3b6f15..030f36c09 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "common/common_types.h" #include "core/file_sys/vfs.h" @@ -27,15 +28,15 @@ enum class ContentRecordType : u8; class NSP : public ReadOnlyVfsDirectory { public: - explicit NSP(VirtualFile file_, std::size_t program_index_ = 0); + explicit NSP(VirtualFile file_, u64 title_id = 0, std::size_t program_index_ = 0); ~NSP() override; Loader::ResultStatus GetStatus() const; - Loader::ResultStatus GetProgramStatus(u64 title_id) const; + Loader::ResultStatus GetProgramStatus() const; // Should only be used when one title id can be assured. - u64 GetFirstTitleID() const; u64 GetProgramTitleID() const; - std::vector GetTitleIDs() const; + u64 GetExtractedTitleID() const; + std::vector GetProgramTitleIDs() const; bool IsExtractedType() const; @@ -69,6 +70,7 @@ private: VirtualFile file; + const u64 expected_program_id; const std::size_t program_index; bool extracted = false; @@ -78,6 +80,7 @@ private: std::shared_ptr pfs; // Map title id -> {map type -> NCA} std::map, std::shared_ptr>> ncas; + std::set program_ids; std::vector ticket_files; Core::Crypto::KeyManager& keys; diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 228dc6389..199e69e89 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -206,7 +206,8 @@ AppLoader::~AppLoader() = default; * @return std::unique_ptr a pointer to a loader object; nullptr for unsupported type */ static std::unique_ptr GetFileLoader(Core::System& system, FileSys::VirtualFile file, - FileType type, std::size_t program_index) { + FileType type, u64 program_id, + std::size_t program_index) { switch (type) { // Standard ELF file format. case FileType::ELF: @@ -227,7 +228,8 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileSys::V // NX XCI (nX Card Image) file format. case FileType::XCI: return std::make_unique(std::move(file), system.GetFileSystemController(), - system.GetContentProvider(), program_index); + system.GetContentProvider(), program_id, + program_index); // NX NAX (NintendoAesXts) file format. case FileType::NAX: @@ -236,7 +238,8 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileSys::V // NX NSP (Nintendo Submission Package) file format case FileType::NSP: return std::make_unique(std::move(file), system.GetFileSystemController(), - system.GetContentProvider(), program_index); + system.GetContentProvider(), program_id, + program_index); // NX KIP (Kernel Internal Process) file format case FileType::KIP: @@ -252,7 +255,7 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileSys::V } std::unique_ptr GetLoader(Core::System& system, FileSys::VirtualFile file, - std::size_t program_index) { + u64 program_id, std::size_t program_index) { FileType type = IdentifyFile(file); const FileType filename_type = GuessFromFilename(file->GetName()); @@ -266,7 +269,7 @@ std::unique_ptr GetLoader(Core::System& system, FileSys::VirtualFile LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type)); - return GetFileLoader(system, std::move(file), type, program_index); + return GetFileLoader(system, std::move(file), type, program_id, program_index); } } // namespace Loader diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index edc8bb257..7b1bac3f7 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -226,6 +226,17 @@ public: return ResultStatus::ErrorNotImplemented; } + /** + * Get the program ids of the application + * + * @param[out] out_program_ids Reference to store program ids into + * + * @return ResultStatus result of function + */ + virtual ResultStatus ReadProgramIds(std::vector& out_program_ids) { + return ResultStatus::ErrorNotImplemented; + } + /** * Get the RomFS of the application * Since the RomFS can be huge, we return a file reference instead of copying to a buffer @@ -324,6 +335,6 @@ protected: * @return the best loader for this file. */ std::unique_ptr GetLoader(Core::System& system, FileSys::VirtualFile file, - std::size_t program_index = 0); + u64 program_id = 0, std::size_t program_index = 0); } // namespace Loader diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index d815a7cd3..8b167ad3c 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -23,10 +23,9 @@ namespace Loader { AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_, const Service::FileSystem::FileSystemController& fsc, - const FileSys::ContentProvider& content_provider, + const FileSys::ContentProvider& content_provider, u64 program_id, std::size_t program_index) - : AppLoader(file_), nsp(std::make_unique(file_, program_index)), - title_id(nsp->GetProgramTitleID()) { + : AppLoader(file_), nsp(std::make_unique(file_, program_id, program_index)) { if (nsp->GetStatus() != ResultStatus::Success) { return; @@ -46,12 +45,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_, return pm.ParseControlNCA(*control_nca); }(); - if (title_id == 0) { - return; - } - secondary_loader = std::make_unique( - nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program)); + nsp->GetNCAFile(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Program)); } } @@ -68,10 +63,11 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) { } // Non-Extracted Type case + const auto program_id = nsp.GetProgramTitleID(); if (!nsp.IsExtractedType() && - nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr && - AppLoader_NCA::IdentifyType(nsp.GetNCAFile( - nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program)) == FileType::NCA) { + nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr && + AppLoader_NCA::IdentifyType( + nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) { return FileType::NSP; } } @@ -84,6 +80,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S return {ResultStatus::ErrorAlreadyLoaded, {}}; } + const auto title_id = nsp->GetProgramTitleID(); + if (!nsp->IsExtractedType() && title_id == 0) { return {ResultStatus::ErrorNSPMissingProgramNCA, {}}; } @@ -93,7 +91,7 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S return {nsp_status, {}}; } - const auto nsp_program_status = nsp->GetProgramStatus(title_id); + const auto nsp_program_status = nsp->GetProgramStatus(); if (nsp_program_status != ResultStatus::Success) { return {nsp_program_status, {}}; } @@ -134,8 +132,8 @@ ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { return ResultStatus::ErrorNoPackedUpdate; } - const auto read = - nsp->GetNCAFile(FileSys::GetUpdateTitleID(title_id), FileSys::ContentRecordType::Program); + const auto read = nsp->GetNCAFile(FileSys::GetUpdateTitleID(nsp->GetProgramTitleID()), + FileSys::ContentRecordType::Program); if (read == nullptr) { return ResultStatus::ErrorNoPackedUpdate; @@ -151,11 +149,15 @@ ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { } ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) { - if (title_id == 0) { + out_program_id = nsp->GetProgramTitleID(); + if (out_program_id == 0) { return ResultStatus::ErrorNotInitialized; } + return ResultStatus::Success; +} - out_program_id = title_id; +ResultStatus AppLoader_NSP::ReadProgramIds(std::vector& out_program_ids) { + out_program_ids = nsp->GetProgramTitleIDs(); return ResultStatus::Success; } diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 644c0ff58..50406a92e 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -28,7 +28,7 @@ class AppLoader_NSP final : public AppLoader { public: explicit AppLoader_NSP(FileSys::VirtualFile file_, const Service::FileSystem::FileSystemController& fsc, - const FileSys::ContentProvider& content_provider, + const FileSys::ContentProvider& content_provider, u64 program_id, std::size_t program_index); ~AppLoader_NSP() override; @@ -51,6 +51,7 @@ public: u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadProgramIds(std::vector& out_program_ids) override; ResultStatus ReadIcon(std::vector& buffer) override; ResultStatus ReadTitle(std::string& title) override; ResultStatus ReadControlData(FileSys::NACP& nacp) override; @@ -67,7 +68,6 @@ private: FileSys::VirtualFile icon_file; std::unique_ptr nacp_file; - u64 title_id; }; } // namespace Loader diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 635d6ae15..269603eef 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -22,9 +22,9 @@ namespace Loader { AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file_, const Service::FileSystem::FileSystemController& fsc, - const FileSys::ContentProvider& content_provider, + const FileSys::ContentProvider& content_provider, u64 program_id, std::size_t program_index) - : AppLoader(file_), xci(std::make_unique(file_, program_index)), + : AppLoader(file_), xci(std::make_unique(file_, program_id, program_index)), nca_loader(std::make_unique(xci->GetProgramNCAFile())) { if (xci->GetStatus() != ResultStatus::Success) { return; @@ -121,6 +121,11 @@ ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { return nca_loader->ReadProgramId(out_program_id); } +ResultStatus AppLoader_XCI::ReadProgramIds(std::vector& out_program_ids) { + out_program_ids = xci->GetProgramTitleIDs(); + return ResultStatus::Success; +} + ResultStatus AppLoader_XCI::ReadIcon(std::vector& buffer) { if (icon_file == nullptr) { return ResultStatus::ErrorNoControl; @@ -149,8 +154,9 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) { } ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& out_file) { - const auto nca = xci->GetSecurePartitionNSP()->GetNCA(xci->GetProgramTitleID(), - FileSys::ContentRecordType::HtmlDocument); + const auto nca = + xci->GetSecurePartitionNSP()->GetNCA(xci->GetSecurePartitionNSP()->GetProgramTitleID(), + FileSys::ContentRecordType::HtmlDocument); if (xci->GetStatus() != ResultStatus::Success || nca == nullptr) { return ResultStatus::ErrorXCIMissingPartition; } diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 708155c30..30caaf90e 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -28,7 +28,7 @@ class AppLoader_XCI final : public AppLoader { public: explicit AppLoader_XCI(FileSys::VirtualFile file_, const Service::FileSystem::FileSystemController& fsc, - const FileSys::ContentProvider& content_provider, + const FileSys::ContentProvider& content_provider, u64 program_id, std::size_t program_index); ~AppLoader_XCI() override; @@ -51,6 +51,7 @@ public: u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadProgramIds(std::vector& out_program_ids) override; ResultStatus ReadIcon(std::vector& buffer) override; ResultStatus ReadTitle(std::string& title) override; ResultStatus ReadControlData(FileSys::NACP& control) override; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 218b4782b..76c063c97 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -404,9 +404,11 @@ void GameList::ValidateEntry(const QModelIndex& item) { return; } + const auto title_id = selected.data(GameListItemPath::ProgramIdRole).toULongLong(); + // Users usually want to run a different game after closing one search_field->clear(); - emit GameChosen(file_path); + emit GameChosen(file_path, title_id); break; } case GameListItemType::AddDir: @@ -548,10 +550,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); }); connect(start_game, &QAction::triggered, [this, path]() { - emit BootGame(QString::fromStdString(path), 0, StartGameType::Normal); + emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal); }); connect(start_game_global, &QAction::triggered, [this, path]() { - emit BootGame(QString::fromStdString(path), 0, StartGameType::Global); + emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global); }); connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path); diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 50402da51..c9a9f4654 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -88,8 +88,9 @@ public: static const QStringList supported_file_extensions; signals: - void BootGame(const QString& game_path, std::size_t program_index, StartGameType type); - void GameChosen(const QString& game_path); + void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, + StartGameType type); + void GameChosen(const QString& game_path, const u64 title_id = 0); void ShouldCancelWorker(); void OpenFolderRequested(u64 program_id, GameListOpenTarget target, const std::string& game_path); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 33cc90d5a..2d5492157 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -336,18 +336,44 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa } } } else { - std::vector icon; - [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); + std::vector program_ids; + loader->ReadProgramIds(program_ids); - std::string name = " "; - [[maybe_unused]] const auto res3 = loader->ReadTitle(name); + if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + for (const auto id : program_ids) { + loader = Loader::GetLoader(system, file, id); + if (!loader) { + continue; + } - const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), - system.GetContentProvider()}; + std::vector icon; + [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); - emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, - compatibility_list, patch), - parent_dir); + std::string name = " "; + [[maybe_unused]] const auto res3 = loader->ReadTitle(name); + + const FileSys::PatchManager patch{id, system.GetFileSystemController(), + system.GetContentProvider()}; + + emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, id, + compatibility_list, patch), + parent_dir); + } + } else { + std::vector icon; + [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); + + std::string name = " "; + [[maybe_unused]] const auto res3 = loader->ReadTitle(name); + + const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), + system.GetContentProvider()}; + + emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, + program_id, compatibility_list, patch), + parent_dir); + } } } else if (is_dir) { watch_list.append(QString::fromStdString(physical_name)); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index b7fd33ae7..03a909d17 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1221,7 +1221,7 @@ void GMainWindow::AllowOSSleep() { #endif } -bool GMainWindow::LoadROM(const QString& filename, std::size_t program_index) { +bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) { // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) ShutdownGame(); @@ -1244,7 +1244,7 @@ bool GMainWindow::LoadROM(const QString& filename, std::size_t program_index) { }); const Core::System::ResultStatus result{ - system.Load(*render_window, filename.toStdString(), program_index)}; + system.Load(*render_window, filename.toStdString(), program_id, program_index)}; const auto drd_callout = (UISettings::values.callout_flags.GetValue() & static_cast(CalloutFlag::DRDDeprecation)) == 0; @@ -1331,7 +1331,8 @@ void GMainWindow::SelectAndSetCurrentUser() { Settings::values.current_user = dialog.GetIndex(); } -void GMainWindow::BootGame(const QString& filename, std::size_t program_index, StartGameType type) { +void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index, + StartGameType type) { LOG_INFO(Frontend, "yuzu starting..."); StoreRecentFile(filename); // Put the filename on top of the list @@ -1341,7 +1342,7 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index, S auto& system = Core::System::GetInstance(); const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); - const auto loader = Loader::GetLoader(system, v_file, program_index); + const auto loader = Loader::GetLoader(system, v_file, program_id, program_index); if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success && type == StartGameType::Normal) { @@ -1369,7 +1370,7 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index, S SelectAndSetCurrentUser(); } - if (!LoadROM(filename, program_index)) + if (!LoadROM(filename, program_id, program_index)) return; // Create and start the emulation thread @@ -1548,8 +1549,8 @@ void GMainWindow::UpdateRecentFiles() { ui.menu_recent_files->setEnabled(num_recent_files != 0); } -void GMainWindow::OnGameListLoadFile(QString game_path) { - BootGame(game_path); +void GMainWindow::OnGameListLoadFile(QString game_path, u64 program_id) { + BootGame(game_path, program_id); } void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, @@ -2450,7 +2451,7 @@ void GMainWindow::OnLoadComplete() { void GMainWindow::OnExecuteProgram(std::size_t program_index) { ShutdownGame(); - BootGame(last_filename_booted, program_index); + BootGame(last_filename_booted, 0, program_index); } void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 45c8310e1..a50e5b9fe 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -186,8 +186,8 @@ private: void PreventOSSleep(); void AllowOSSleep(); - bool LoadROM(const QString& filename, std::size_t program_index); - void BootGame(const QString& filename, std::size_t program_index = 0, + bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index); + void BootGame(const QString& filename, u64 program_id = 0, std::size_t program_index = 0, StartGameType with_config = StartGameType::Normal); void ShutdownGame(); @@ -238,7 +238,7 @@ private slots: void OnOpenQuickstartGuide(); void OnOpenFAQ(); /// Called whenever a user selects a game in the game list widget. - void OnGameListLoadFile(QString game_path); + void OnGameListLoadFile(QString game_path, u64 program_id); void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, const std::string& game_path); void OnTransferableShaderCacheOpenFile(u64 program_id);