diff options
| -rw-r--r-- | src/core/file_sys/registered_cache.cpp | 101 | ||||
| -rw-r--r-- | src/core/file_sys/registered_cache.h | 6 | ||||
| -rw-r--r-- | src/core/file_sys/xts_archive.cpp | 14 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 34 | ||||
| -rw-r--r-- | src/yuzu/game_list.h | 15 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 182 | ||||
| -rw-r--r-- | src/yuzu/main.h | 9 |
7 files changed, 291 insertions, 70 deletions
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 37351c561..e94eed3b6 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -547,56 +547,6 @@ InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_ex | |||
| 547 | return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy); | 547 | return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy); |
| 548 | } | 548 | } |
| 549 | 549 | ||
| 550 | bool RegisteredCache::RemoveExistingEntry(u64 title_id) { | ||
| 551 | const auto delete_nca = [this](const NcaID& id) { | ||
| 552 | const auto path = GetRelativePathFromNcaID(id, false, true, false); | ||
| 553 | |||
| 554 | if (dir->GetFileRelative(path) == nullptr) { | ||
| 555 | return false; | ||
| 556 | } | ||
| 557 | |||
| 558 | Core::Crypto::SHA256Hash hash{}; | ||
| 559 | mbedtls_sha256_ret(id.data(), id.size(), hash.data(), 0); | ||
| 560 | const auto dirname = fmt::format("000000{:02X}", hash[0]); | ||
| 561 | |||
| 562 | const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname); | ||
| 563 | |||
| 564 | const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false))); | ||
| 565 | |||
| 566 | return res; | ||
| 567 | }; | ||
| 568 | |||
| 569 | // If an entry exists in the registered cache, remove it | ||
| 570 | if (HasEntry(title_id, ContentRecordType::Meta)) { | ||
| 571 | LOG_INFO(Loader, | ||
| 572 | "Previously installed entry (v{}) for title_id={:016X} detected! " | ||
| 573 | "Attempting to remove...", | ||
| 574 | GetEntryVersion(title_id).value_or(0), title_id); | ||
| 575 | // Get all the ncas associated with the current CNMT and delete them | ||
| 576 | const auto meta_old_id = | ||
| 577 | GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); | ||
| 578 | const auto program_id = | ||
| 579 | GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{}); | ||
| 580 | const auto data_id = | ||
| 581 | GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{}); | ||
| 582 | const auto control_id = | ||
| 583 | GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{}); | ||
| 584 | const auto html_id = | ||
| 585 | GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{}); | ||
| 586 | const auto legal_id = | ||
| 587 | GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{}); | ||
| 588 | |||
| 589 | delete_nca(meta_old_id); | ||
| 590 | delete_nca(program_id); | ||
| 591 | delete_nca(data_id); | ||
| 592 | delete_nca(control_id); | ||
| 593 | delete_nca(html_id); | ||
| 594 | delete_nca(legal_id); | ||
| 595 | return true; | ||
| 596 | } | ||
| 597 | return false; | ||
| 598 | } | ||
| 599 | |||
| 600 | InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_exists, | 550 | InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_exists, |
| 601 | const VfsCopyFunction& copy) { | 551 | const VfsCopyFunction& copy) { |
| 602 | const auto ncas = nsp.GetNCAsCollapsed(); | 552 | const auto ncas = nsp.GetNCAsCollapsed(); |
| @@ -692,6 +642,57 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, | |||
| 692 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); | 642 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); |
| 693 | } | 643 | } |
| 694 | 644 | ||
| 645 | bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { | ||
| 646 | const auto delete_nca = [this](const NcaID& id) { | ||
| 647 | const auto path = GetRelativePathFromNcaID(id, false, true, false); | ||
| 648 | |||
| 649 | const bool isFile = dir->GetFileRelative(path) != nullptr; | ||
| 650 | const bool isDir = dir->GetDirectoryRelative(path) != nullptr; | ||
| 651 | |||
| 652 | if (isFile) { | ||
| 653 | return dir->DeleteFile(path); | ||
| 654 | } else if (isDir) { | ||
| 655 | return dir->DeleteSubdirectoryRecursive(path); | ||
| 656 | } | ||
| 657 | |||
| 658 | return false; | ||
| 659 | }; | ||
| 660 | |||
| 661 | // If an entry exists in the registered cache, remove it | ||
| 662 | if (HasEntry(title_id, ContentRecordType::Meta)) { | ||
| 663 | LOG_INFO(Loader, | ||
| 664 | "Previously installed entry (v{}) for title_id={:016X} detected! " | ||
| 665 | "Attempting to remove...", | ||
| 666 | GetEntryVersion(title_id).value_or(0), title_id); | ||
| 667 | |||
| 668 | // Get all the ncas associated with the current CNMT and delete them | ||
| 669 | const auto meta_old_id = | ||
| 670 | GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); | ||
| 671 | const auto program_id = | ||
| 672 | GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{}); | ||
| 673 | const auto data_id = | ||
| 674 | GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{}); | ||
| 675 | const auto control_id = | ||
| 676 | GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{}); | ||
| 677 | const auto html_id = | ||
| 678 | GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{}); | ||
| 679 | const auto legal_id = | ||
| 680 | GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{}); | ||
| 681 | |||
| 682 | const auto deleted_meta = delete_nca(meta_old_id); | ||
| 683 | const auto deleted_program = delete_nca(program_id); | ||
| 684 | const auto deleted_data = delete_nca(data_id); | ||
| 685 | const auto deleted_control = delete_nca(control_id); | ||
| 686 | const auto deleted_html = delete_nca(html_id); | ||
| 687 | const auto deleted_legal = delete_nca(legal_id); | ||
| 688 | |||
| 689 | return deleted_meta && (deleted_meta || deleted_program || deleted_data || | ||
| 690 | deleted_control || deleted_html || deleted_legal); | ||
| 691 | } | ||
| 692 | |||
| 693 | return false; | ||
| 694 | } | ||
| 695 | |||
| 695 | InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, | 696 | InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, |
| 696 | bool overwrite_if_exists, | 697 | bool overwrite_if_exists, |
| 697 | std::optional<NcaID> override_id) { | 698 | std::optional<NcaID> override_id) { |
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 29cf0d40c..ec1d54f27 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h | |||
| @@ -155,9 +155,6 @@ public: | |||
| 155 | std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {}, | 155 | std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {}, |
| 156 | std::optional<u64> title_id = {}) const override; | 156 | std::optional<u64> title_id = {}) const override; |
| 157 | 157 | ||
| 158 | // Removes an existing entry based on title id | ||
| 159 | bool RemoveExistingEntry(u64 title_id); | ||
| 160 | |||
| 161 | // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure | 158 | // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure |
| 162 | // there is a meta NCA and all of them are accessible. | 159 | // there is a meta NCA and all of them are accessible. |
| 163 | InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false, | 160 | InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false, |
| @@ -172,6 +169,9 @@ public: | |||
| 172 | InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, | 169 | InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, |
| 173 | const VfsCopyFunction& copy = &VfsRawCopy); | 170 | const VfsCopyFunction& copy = &VfsRawCopy); |
| 174 | 171 | ||
| 172 | // Removes an existing entry based on title id | ||
| 173 | bool RemoveExistingEntry(u64 title_id) const; | ||
| 174 | |||
| 175 | private: | 175 | private: |
| 176 | template <typename T> | 176 | template <typename T> |
| 177 | void IterateAllMetadata(std::vector<T>& out, | 177 | void IterateAllMetadata(std::vector<T>& out, |
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 86e06ccb9..81413c684 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp | |||
| @@ -70,14 +70,18 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) | |||
| 70 | NAX::~NAX() = default; | 70 | NAX::~NAX() = default; |
| 71 | 71 | ||
| 72 | Loader::ResultStatus NAX::Parse(std::string_view path) { | 72 | Loader::ResultStatus NAX::Parse(std::string_view path) { |
| 73 | if (file->ReadObject(header.get()) != sizeof(NAXHeader)) | 73 | if (file == nullptr) { |
| 74 | return Loader::ResultStatus::ErrorNullFile; | ||
| 75 | } | ||
| 76 | if (file->ReadObject(header.get()) != sizeof(NAXHeader)) { | ||
| 74 | return Loader::ResultStatus::ErrorBadNAXHeader; | 77 | return Loader::ResultStatus::ErrorBadNAXHeader; |
| 75 | 78 | } | |
| 76 | if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) | 79 | if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) { |
| 77 | return Loader::ResultStatus::ErrorBadNAXHeader; | 80 | return Loader::ResultStatus::ErrorBadNAXHeader; |
| 78 | 81 | } | |
| 79 | if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) | 82 | if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) { |
| 80 | return Loader::ResultStatus::ErrorIncorrectNAXFileSize; | 83 | return Loader::ResultStatus::ErrorIncorrectNAXFileSize; |
| 84 | } | ||
| 81 | 85 | ||
| 82 | keys.DeriveSDSeedLazy(); | 86 | keys.DeriveSDSeedLazy(); |
| 83 | std::array<Core::Crypto::Key256, 2> sd_keys{}; | 87 | std::array<Core::Crypto::Key256, 2> sd_keys{}; |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index ab7fc7a24..62acc3720 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -474,28 +474,56 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
| 474 | 474 | ||
| 475 | void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) { | 475 | void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) { |
| 476 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); | 476 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); |
| 477 | QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); | 477 | QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); |
| 478 | QAction* open_transferable_shader_cache = | 478 | QAction* open_transferable_shader_cache = |
| 479 | context_menu.addAction(tr("Open Transferable Shader Cache")); | 479 | context_menu.addAction(tr("Open Transferable Shader Cache")); |
| 480 | context_menu.addSeparator(); | 480 | context_menu.addSeparator(); |
| 481 | QMenu* remove_menu = context_menu.addMenu(tr("Remove")); | ||
| 482 | QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); | ||
| 483 | QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); | ||
| 484 | QAction* remove_shader_cache = remove_menu->addAction(tr("Remove Shader Cache")); | ||
| 485 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); | ||
| 486 | remove_menu->addSeparator(); | ||
| 487 | QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents")); | ||
| 481 | QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); | 488 | QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); |
| 482 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | 489 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); |
| 483 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | 490 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |
| 484 | context_menu.addSeparator(); | 491 | context_menu.addSeparator(); |
| 485 | QAction* properties = context_menu.addAction(tr("Properties")); | 492 | QAction* properties = context_menu.addAction(tr("Properties")); |
| 486 | 493 | ||
| 487 | open_save_location->setEnabled(program_id != 0); | 494 | open_save_location->setVisible(program_id != 0); |
| 495 | open_mod_location->setVisible(program_id != 0); | ||
| 496 | open_transferable_shader_cache->setVisible(program_id != 0); | ||
| 497 | remove_update->setVisible(program_id != 0); | ||
| 498 | remove_dlc->setVisible(program_id != 0); | ||
| 499 | remove_shader_cache->setVisible(program_id != 0); | ||
| 500 | remove_all_content->setVisible(program_id != 0); | ||
| 488 | auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | 501 | auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); |
| 489 | navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); | 502 | navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); |
| 490 | 503 | ||
| 491 | connect(open_save_location, &QAction::triggered, [this, program_id, path]() { | 504 | connect(open_save_location, &QAction::triggered, [this, program_id, path]() { |
| 492 | emit OpenFolderRequested(GameListOpenTarget::SaveData, path); | 505 | emit OpenFolderRequested(GameListOpenTarget::SaveData, path); |
| 493 | }); | 506 | }); |
| 494 | connect(open_lfs_location, &QAction::triggered, [this, program_id, path]() { | 507 | connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { |
| 495 | emit OpenFolderRequested(GameListOpenTarget::ModData, path); | 508 | emit OpenFolderRequested(GameListOpenTarget::ModData, path); |
| 496 | }); | 509 | }); |
| 497 | connect(open_transferable_shader_cache, &QAction::triggered, | 510 | connect(open_transferable_shader_cache, &QAction::triggered, |
| 498 | [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); | 511 | [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); |
| 512 | connect(remove_all_content, &QAction::triggered, [this, program_id]() { | ||
| 513 | emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Game); | ||
| 514 | }); | ||
| 515 | connect(remove_update, &QAction::triggered, [this, program_id]() { | ||
| 516 | emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Update); | ||
| 517 | }); | ||
| 518 | connect(remove_dlc, &QAction::triggered, [this, program_id]() { | ||
| 519 | emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::AddOnContent); | ||
| 520 | }); | ||
| 521 | connect(remove_shader_cache, &QAction::triggered, [this, program_id]() { | ||
| 522 | emit RemoveFileRequested(program_id, GameListRemoveTarget::ShaderCache); | ||
| 523 | }); | ||
| 524 | connect(remove_custom_config, &QAction::triggered, [this, program_id]() { | ||
| 525 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration); | ||
| 526 | }); | ||
| 499 | connect(dump_romfs, &QAction::triggered, | 527 | connect(dump_romfs, &QAction::triggered, |
| 500 | [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); | 528 | [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); |
| 501 | connect(copy_tid, &QAction::triggered, | 529 | connect(copy_tid, &QAction::triggered, |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index a38cb2fc3..483835cce 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -39,6 +39,17 @@ enum class GameListOpenTarget { | |||
| 39 | ModData, | 39 | ModData, |
| 40 | }; | 40 | }; |
| 41 | 41 | ||
| 42 | enum class GameListRemoveTarget { | ||
| 43 | ShaderCache, | ||
| 44 | CustomConfiguration, | ||
| 45 | }; | ||
| 46 | |||
| 47 | enum class InstalledEntryType { | ||
| 48 | Game, | ||
| 49 | Update, | ||
| 50 | AddOnContent, | ||
| 51 | }; | ||
| 52 | |||
| 42 | class GameList : public QWidget { | 53 | class GameList : public QWidget { |
| 43 | Q_OBJECT | 54 | Q_OBJECT |
| 44 | 55 | ||
| @@ -75,6 +86,8 @@ signals: | |||
| 75 | void ShouldCancelWorker(); | 86 | void ShouldCancelWorker(); |
| 76 | void OpenFolderRequested(GameListOpenTarget target, const std::string& game_path); | 87 | void OpenFolderRequested(GameListOpenTarget target, const std::string& game_path); |
| 77 | void OpenTransferableShaderCacheRequested(u64 program_id); | 88 | void OpenTransferableShaderCacheRequested(u64 program_id); |
| 89 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); | ||
| 90 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target); | ||
| 78 | void DumpRomFSRequested(u64 program_id, const std::string& game_path); | 91 | void DumpRomFSRequested(u64 program_id, const std::string& game_path); |
| 79 | void CopyTIDRequested(u64 program_id); | 92 | void CopyTIDRequested(u64 program_id); |
| 80 | void NavigateToGamedbEntryRequested(u64 program_id, | 93 | void NavigateToGamedbEntryRequested(u64 program_id, |
| @@ -117,8 +130,6 @@ private: | |||
| 117 | friend class GameListSearchField; | 130 | friend class GameListSearchField; |
| 118 | }; | 131 | }; |
| 119 | 132 | ||
| 120 | Q_DECLARE_METATYPE(GameListOpenTarget); | ||
| 121 | |||
| 122 | class GameListPlaceholder : public QWidget { | 133 | class GameListPlaceholder : public QWidget { |
| 123 | Q_OBJECT | 134 | Q_OBJECT |
| 124 | public: | 135 | public: |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 31a635176..276658c9e 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -847,6 +847,9 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 847 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); | 847 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); |
| 848 | connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, | 848 | connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, |
| 849 | &GMainWindow::OnTransferableShaderCacheOpenFile); | 849 | &GMainWindow::OnTransferableShaderCacheOpenFile); |
| 850 | connect(game_list, &GameList::RemoveInstalledEntryRequested, this, | ||
| 851 | &GMainWindow::OnGameListRemoveInstalledEntry); | ||
| 852 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); | ||
| 850 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | 853 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); |
| 851 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | 854 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); |
| 852 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 855 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| @@ -1257,7 +1260,6 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str | |||
| 1257 | case GameListOpenTarget::SaveData: { | 1260 | case GameListOpenTarget::SaveData: { |
| 1258 | open_target = tr("Save Data"); | 1261 | open_target = tr("Save Data"); |
| 1259 | const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); | 1262 | const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); |
| 1260 | ASSERT(program_id != 0); | ||
| 1261 | 1263 | ||
| 1262 | if (has_user_save) { | 1264 | if (has_user_save) { |
| 1263 | // User save data | 1265 | // User save data |
| @@ -1322,14 +1324,12 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str | |||
| 1322 | } | 1324 | } |
| 1323 | 1325 | ||
| 1324 | void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { | 1326 | void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { |
| 1325 | ASSERT(program_id != 0); | ||
| 1326 | |||
| 1327 | const QString shader_dir = | 1327 | const QString shader_dir = |
| 1328 | QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); | 1328 | QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); |
| 1329 | const QString tranferable_shader_cache_folder_path = | 1329 | const QString transferable_shader_cache_folder_path = |
| 1330 | shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); | 1330 | shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); |
| 1331 | const QString transferable_shader_cache_file_path = | 1331 | const QString transferable_shader_cache_file_path = |
| 1332 | tranferable_shader_cache_folder_path + QDir::separator() + | 1332 | transferable_shader_cache_folder_path + QDir::separator() + |
| 1333 | QString::fromStdString(fmt::format("{:016X}.bin", program_id)); | 1333 | QString::fromStdString(fmt::format("{:016X}.bin", program_id)); |
| 1334 | 1334 | ||
| 1335 | if (!QFile::exists(transferable_shader_cache_file_path)) { | 1335 | if (!QFile::exists(transferable_shader_cache_file_path)) { |
| @@ -1350,7 +1350,7 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { | |||
| 1350 | param << QDir::toNativeSeparators(transferable_shader_cache_file_path); | 1350 | param << QDir::toNativeSeparators(transferable_shader_cache_file_path); |
| 1351 | QProcess::startDetached(explorer, param); | 1351 | QProcess::startDetached(explorer, param); |
| 1352 | #else | 1352 | #else |
| 1353 | QDesktopServices::openUrl(QUrl::fromLocalFile(tranferable_shader_cache_folder_path)); | 1353 | QDesktopServices::openUrl(QUrl::fromLocalFile(transferable_shader_cache_folder_path)); |
| 1354 | #endif | 1354 | #endif |
| 1355 | } | 1355 | } |
| 1356 | 1356 | ||
| @@ -1394,6 +1394,174 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src | |||
| 1394 | return true; | 1394 | return true; |
| 1395 | } | 1395 | } |
| 1396 | 1396 | ||
| 1397 | void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { | ||
| 1398 | const QString entry_type = [this, type] { | ||
| 1399 | switch (type) { | ||
| 1400 | case InstalledEntryType::Game: | ||
| 1401 | return tr("Contents"); | ||
| 1402 | case InstalledEntryType::Update: | ||
| 1403 | return tr("Update"); | ||
| 1404 | case InstalledEntryType::AddOnContent: | ||
| 1405 | return tr("DLC"); | ||
| 1406 | default: | ||
| 1407 | return QString{}; | ||
| 1408 | } | ||
| 1409 | }(); | ||
| 1410 | |||
| 1411 | if (QMessageBox::question( | ||
| 1412 | this, tr("Remove Entry"), tr("Remove Installed Game %1?").arg(entry_type), | ||
| 1413 | QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { | ||
| 1414 | return; | ||
| 1415 | } | ||
| 1416 | |||
| 1417 | switch (type) { | ||
| 1418 | case InstalledEntryType::Game: | ||
| 1419 | RemoveBaseContent(program_id, entry_type); | ||
| 1420 | [[fallthrough]]; | ||
| 1421 | case InstalledEntryType::Update: | ||
| 1422 | RemoveUpdateContent(program_id, entry_type); | ||
| 1423 | if (type != InstalledEntryType::Game) { | ||
| 1424 | break; | ||
| 1425 | } | ||
| 1426 | [[fallthrough]]; | ||
| 1427 | case InstalledEntryType::AddOnContent: | ||
| 1428 | RemoveAddOnContent(program_id, entry_type); | ||
| 1429 | break; | ||
| 1430 | } | ||
| 1431 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + | ||
| 1432 | "game_list"); | ||
| 1433 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 1434 | } | ||
| 1435 | |||
| 1436 | void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) { | ||
| 1437 | const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); | ||
| 1438 | const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) || | ||
| 1439 | fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id); | ||
| 1440 | |||
| 1441 | if (res) { | ||
| 1442 | QMessageBox::information(this, tr("Successfully Removed"), | ||
| 1443 | tr("Successfully removed the installed base game.")); | ||
| 1444 | } else { | ||
| 1445 | QMessageBox::warning( | ||
| 1446 | this, tr("Error Removing %1").arg(entry_type), | ||
| 1447 | tr("The base game is not installed in the NAND and cannot be removed.")); | ||
| 1448 | } | ||
| 1449 | } | ||
| 1450 | |||
| 1451 | void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) { | ||
| 1452 | const auto update_id = program_id | 0x800; | ||
| 1453 | const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); | ||
| 1454 | const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || | ||
| 1455 | fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); | ||
| 1456 | |||
| 1457 | if (res) { | ||
| 1458 | QMessageBox::information(this, tr("Successfully Removed"), | ||
| 1459 | tr("Successfully removed the installed update.")); | ||
| 1460 | } else { | ||
| 1461 | QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), | ||
| 1462 | tr("There is no update installed for this title.")); | ||
| 1463 | } | ||
| 1464 | } | ||
| 1465 | |||
| 1466 | void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) { | ||
| 1467 | u32 count{}; | ||
| 1468 | const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); | ||
| 1469 | const auto dlc_entries = Core::System::GetInstance().GetContentProvider().ListEntriesFilter( | ||
| 1470 | FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); | ||
| 1471 | |||
| 1472 | for (const auto& entry : dlc_entries) { | ||
| 1473 | if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { | ||
| 1474 | const auto res = | ||
| 1475 | fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) || | ||
| 1476 | fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id); | ||
| 1477 | if (res) { | ||
| 1478 | ++count; | ||
| 1479 | } | ||
| 1480 | } | ||
| 1481 | } | ||
| 1482 | |||
| 1483 | if (count == 0) { | ||
| 1484 | QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), | ||
| 1485 | tr("There are no DLC installed for this title.")); | ||
| 1486 | return; | ||
| 1487 | } | ||
| 1488 | |||
| 1489 | QMessageBox::information(this, tr("Successfully Removed"), | ||
| 1490 | tr("Successfully removed %1 installed DLC.").arg(count)); | ||
| 1491 | } | ||
| 1492 | |||
| 1493 | void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target) { | ||
| 1494 | const QString question = [this, target] { | ||
| 1495 | switch (target) { | ||
| 1496 | case GameListRemoveTarget::ShaderCache: | ||
| 1497 | return tr("Delete Transferable Shader Cache?"); | ||
| 1498 | case GameListRemoveTarget::CustomConfiguration: | ||
| 1499 | return tr("Remove Custom Game Configuration?"); | ||
| 1500 | default: | ||
| 1501 | return QString{}; | ||
| 1502 | } | ||
| 1503 | }(); | ||
| 1504 | |||
| 1505 | if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, | ||
| 1506 | QMessageBox::No) != QMessageBox::Yes) { | ||
| 1507 | return; | ||
| 1508 | } | ||
| 1509 | |||
| 1510 | switch (target) { | ||
| 1511 | case GameListRemoveTarget::ShaderCache: | ||
| 1512 | RemoveTransferableShaderCache(program_id); | ||
| 1513 | break; | ||
| 1514 | case GameListRemoveTarget::CustomConfiguration: | ||
| 1515 | RemoveCustomConfiguration(program_id); | ||
| 1516 | break; | ||
| 1517 | } | ||
| 1518 | } | ||
| 1519 | |||
| 1520 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { | ||
| 1521 | const QString shader_dir = | ||
| 1522 | QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); | ||
| 1523 | const QString transferable_shader_cache_folder_path = | ||
| 1524 | shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); | ||
| 1525 | const QString transferable_shader_cache_file_path = | ||
| 1526 | transferable_shader_cache_folder_path + QDir::separator() + | ||
| 1527 | QString::fromStdString(fmt::format("{:016X}.bin", program_id)); | ||
| 1528 | |||
| 1529 | if (!QFile::exists(transferable_shader_cache_file_path)) { | ||
| 1530 | QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), | ||
| 1531 | tr("A shader cache for this title does not exist.")); | ||
| 1532 | return; | ||
| 1533 | } | ||
| 1534 | |||
| 1535 | if (QFile::remove(transferable_shader_cache_file_path)) { | ||
| 1536 | QMessageBox::information(this, tr("Successfully Removed"), | ||
| 1537 | tr("Successfully removed the transferable shader cache.")); | ||
| 1538 | } else { | ||
| 1539 | QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), | ||
| 1540 | tr("Failed to remove the transferable shader cache.")); | ||
| 1541 | } | ||
| 1542 | } | ||
| 1543 | |||
| 1544 | void GMainWindow::RemoveCustomConfiguration(u64 program_id) { | ||
| 1545 | const QString config_dir = | ||
| 1546 | QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir)); | ||
| 1547 | const QString custom_config_file_path = | ||
| 1548 | config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id)); | ||
| 1549 | |||
| 1550 | if (!QFile::exists(custom_config_file_path)) { | ||
| 1551 | QMessageBox::warning(this, tr("Error Removing Custom Configuration"), | ||
| 1552 | tr("A custom configuration for this title does not exist.")); | ||
| 1553 | return; | ||
| 1554 | } | ||
| 1555 | |||
| 1556 | if (QFile::remove(custom_config_file_path)) { | ||
| 1557 | QMessageBox::information(this, tr("Successfully Removed"), | ||
| 1558 | tr("Successfully removed the custom game configuration.")); | ||
| 1559 | } else { | ||
| 1560 | QMessageBox::warning(this, tr("Error Removing Custom Configuration"), | ||
| 1561 | tr("Failed to remove the custom game configuration.")); | ||
| 1562 | } | ||
| 1563 | } | ||
| 1564 | |||
| 1397 | void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { | 1565 | void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { |
| 1398 | const auto failed = [this] { | 1566 | const auto failed = [this] { |
| 1399 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), | 1567 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), |
| @@ -1714,9 +1882,9 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 1714 | : tr("%n file(s) failed to install\n", "", failed_files.size())); | 1882 | : tr("%n file(s) failed to install\n", "", failed_files.size())); |
| 1715 | 1883 | ||
| 1716 | QMessageBox::information(this, tr("Install Results"), install_results); | 1884 | QMessageBox::information(this, tr("Install Results"), install_results); |
| 1717 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 1718 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + | 1885 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + |
| 1719 | "game_list"); | 1886 | "game_list"); |
| 1887 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 1720 | ui.action_Install_File_NAND->setEnabled(true); | 1888 | ui.action_Install_File_NAND->setEnabled(true); |
| 1721 | } | 1889 | } |
| 1722 | 1890 | ||
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index db573d606..73a44a3bf 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -32,6 +32,8 @@ class QPushButton; | |||
| 32 | class QProgressDialog; | 32 | class QProgressDialog; |
| 33 | class WaitTreeWidget; | 33 | class WaitTreeWidget; |
| 34 | enum class GameListOpenTarget; | 34 | enum class GameListOpenTarget; |
| 35 | enum class GameListRemoveTarget; | ||
| 36 | enum class InstalledEntryType; | ||
| 35 | class GameListPlaceholder; | 37 | class GameListPlaceholder; |
| 36 | 38 | ||
| 37 | namespace Core::Frontend { | 39 | namespace Core::Frontend { |
| @@ -198,6 +200,8 @@ private slots: | |||
| 198 | void OnGameListLoadFile(QString game_path); | 200 | void OnGameListLoadFile(QString game_path); |
| 199 | void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path); | 201 | void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path); |
| 200 | void OnTransferableShaderCacheOpenFile(u64 program_id); | 202 | void OnTransferableShaderCacheOpenFile(u64 program_id); |
| 203 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); | ||
| 204 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target); | ||
| 201 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); | 205 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); |
| 202 | void OnGameListCopyTID(u64 program_id); | 206 | void OnGameListCopyTID(u64 program_id); |
| 203 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 207 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| @@ -229,6 +233,11 @@ private slots: | |||
| 229 | void OnLanguageChanged(const QString& locale); | 233 | void OnLanguageChanged(const QString& locale); |
| 230 | 234 | ||
| 231 | private: | 235 | private: |
| 236 | void RemoveBaseContent(u64 program_id, const QString& entry_type); | ||
| 237 | void RemoveUpdateContent(u64 program_id, const QString& entry_type); | ||
| 238 | void RemoveAddOnContent(u64 program_id, const QString& entry_type); | ||
| 239 | void RemoveTransferableShaderCache(u64 program_id); | ||
| 240 | void RemoveCustomConfiguration(u64 program_id); | ||
| 232 | std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); | 241 | std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); |
| 233 | InstallResult InstallNSPXCI(const QString& filename); | 242 | InstallResult InstallNSPXCI(const QString& filename); |
| 234 | InstallResult InstallNCA(const QString& filename); | 243 | InstallResult InstallNCA(const QString& filename); |