From de6b852dc21900372ae721d7e4899485dcccaf0b Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 17 Jul 2020 05:20:27 -0400 Subject: [PATCH 1/9] game_list: Add "Remove" context menu Adds the following actions: - Remove Installed Update - Remove All Installed DLC - Remove Shader Cache - Remove Custom Configuration - Remove All Installed Contents --- src/yuzu/game_list.cpp | 26 ++++++++++++++++++++++++-- src/yuzu/game_list.h | 15 +++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index ab7fc7a24..20073f3c8 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -474,10 +474,17 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) { QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); - QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); + QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); QAction* open_transferable_shader_cache = context_menu.addAction(tr("Open Transferable Shader Cache")); context_menu.addSeparator(); + QMenu* remove_menu = context_menu.addMenu(tr("Remove")); + QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); + QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); + QAction* remove_shader_cache = remove_menu->addAction(tr("Remove Shader Cache")); + QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); + remove_menu->addSeparator(); + QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents")); QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); @@ -491,11 +498,26 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string pat connect(open_save_location, &QAction::triggered, [this, program_id, path]() { emit OpenFolderRequested(GameListOpenTarget::SaveData, path); }); - connect(open_lfs_location, &QAction::triggered, [this, program_id, path]() { + connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { emit OpenFolderRequested(GameListOpenTarget::ModData, path); }); connect(open_transferable_shader_cache, &QAction::triggered, [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); + connect(remove_all_content, &QAction::triggered, [this, program_id]() { + emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Game); + }); + connect(remove_update, &QAction::triggered, [this, program_id]() { + emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Update); + }); + connect(remove_dlc, &QAction::triggered, [this, program_id]() { + emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::AddOnContent); + }); + connect(remove_shader_cache, &QAction::triggered, [this, program_id]() { + emit RemoveFileRequested(program_id, GameListRemoveTarget::ShaderCache); + }); + connect(remove_custom_config, &QAction::triggered, [this, program_id]() { + emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration); + }); connect(dump_romfs, &QAction::triggered, [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); 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 { ModData, }; +enum class GameListRemoveTarget { + ShaderCache, + CustomConfiguration, +}; + +enum class InstalledEntryType { + Game, + Update, + AddOnContent, +}; + class GameList : public QWidget { Q_OBJECT @@ -75,6 +86,8 @@ signals: void ShouldCancelWorker(); void OpenFolderRequested(GameListOpenTarget target, const std::string& game_path); void OpenTransferableShaderCacheRequested(u64 program_id); + void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); + void RemoveFileRequested(u64 program_id, GameListRemoveTarget target); void DumpRomFSRequested(u64 program_id, const std::string& game_path); void CopyTIDRequested(u64 program_id); void NavigateToGamedbEntryRequested(u64 program_id, @@ -117,8 +130,6 @@ private: friend class GameListSearchField; }; -Q_DECLARE_METATYPE(GameListOpenTarget); - class GameListPlaceholder : public QWidget { Q_OBJECT public: From 85e1facfe618967e4a4b865e0b4f6fcb95a786a5 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 17 Jul 2020 05:21:26 -0400 Subject: [PATCH 2/9] main: Connect game list remove signals to removal functions --- src/yuzu/main.cpp | 168 ++++++++++++++++++++++++++++++++++++++++++++-- src/yuzu/main.h | 4 ++ 2 files changed, 167 insertions(+), 5 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 31a635176..e94785101 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -847,6 +847,9 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, &GMainWindow::OnTransferableShaderCacheOpenFile); + connect(game_list, &GameList::RemoveInstalledEntryRequested, this, + &GMainWindow::OnGameListRemoveInstalledEntry); + connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, @@ -1322,14 +1325,12 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str } void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { - ASSERT(program_id != 0); - const QString shader_dir = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); - const QString tranferable_shader_cache_folder_path = + const QString transferable_shader_cache_folder_path = shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); const QString transferable_shader_cache_file_path = - tranferable_shader_cache_folder_path + QDir::separator() + + transferable_shader_cache_folder_path + QDir::separator() + QString::fromStdString(fmt::format("{:016X}.bin", program_id)); if (!QFile::exists(transferable_shader_cache_file_path)) { @@ -1350,7 +1351,7 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { param << QDir::toNativeSeparators(transferable_shader_cache_file_path); QProcess::startDetached(explorer, param); #else - QDesktopServices::openUrl(QUrl::fromLocalFile(tranferable_shader_cache_folder_path)); + QDesktopServices::openUrl(QUrl::fromLocalFile(transferable_shader_cache_folder_path)); #endif } @@ -1394,6 +1395,163 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src return true; } +void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { + QString entry_type; + + switch (type) { + case InstalledEntryType::Game: + entry_type = tr("Contents"); + break; + case InstalledEntryType::Update: + entry_type = tr("Update"); + break; + case InstalledEntryType::AddOnContent: + entry_type = tr("DLC"); + break; + } + + if (QMessageBox::question( + this, tr("Remove Entry"), tr("Remove Installed Game %1?").arg(entry_type), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { + return; + } + + bool res; + + switch (type) { + case InstalledEntryType::Game: + res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->RemoveExistingEntry(program_id); + + if (res) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the installed base game.")); + } else { + QMessageBox::warning( + this, tr("Error Removing %1").arg(entry_type), + tr("The base game is not installed in the NAND and cannot be removed.")); + } + [[fallthrough]]; + case InstalledEntryType::Update: + res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->RemoveExistingEntry(program_id | 0x800); + + if (res) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the installed update.")); + } else { + QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), + tr("There is no update installed for this title.")); + } + + if (type == InstalledEntryType::Game) { + [[fallthrough]]; + } else { + break; + } + case InstalledEntryType::AddOnContent: + u32 count{}; + const auto dlc_entries = Core::System::GetInstance().GetContentProvider().ListEntriesFilter( + FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + + for (const auto& entry : dlc_entries) { + if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { + res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->RemoveExistingEntry(entry.title_id); + if (res) { + ++count; + } + } + } + + if (count == 0) { + QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), + tr("There are no DLC installed for this title.")); + break; + } + + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed %1 installed DLC.").arg(count)); + break; + } + game_list->PopulateAsync(UISettings::values.game_dirs); + FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + + "game_list"); +} + +void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target) { + QString question; + + switch (target) { + case GameListRemoveTarget::ShaderCache: + question = tr("Delete Transferable Shader Cache?"); + break; + case GameListRemoveTarget::CustomConfiguration: + question = tr("Remove Custom Game Configuration?"); + break; + } + + if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) != QMessageBox::Yes) { + return; + } + + switch (target) { + case GameListRemoveTarget::ShaderCache: { + const QString shader_dir = + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); + const QString transferable_shader_cache_folder_path = + shader_dir + QStringLiteral("opengl") + QDir::separator() + + QStringLiteral("transferable"); + const QString transferable_shader_cache_file_path = + transferable_shader_cache_folder_path + QDir::separator() + + QString::fromStdString(fmt::format("{:016X}.bin", program_id)); + + if (!QFile::exists(transferable_shader_cache_file_path)) { + QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), + tr("A shader cache for this title does not exist.")); + break; + } + + if (QFile::remove(transferable_shader_cache_file_path)) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the transferable shader cache.")); + } else { + QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), + tr("Failed to remove the transferable shader cache.")); + } + break; + } + case GameListRemoveTarget::CustomConfiguration: { + const QString config_dir = + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir)); + const QString custom_config_file_path = + config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id)); + + if (!QFile::exists(custom_config_file_path)) { + QMessageBox::warning(this, tr("Error Removing Custom Configuration"), + tr("A custom configuration for this title does not exist.")); + break; + } + + if (QFile::remove(custom_config_file_path)) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the custom game configuration.")); + } else { + QMessageBox::warning(this, tr("Error Removing Custom Configuration"), + tr("Failed to remove the custom game configuration.")); + } + break; + } + } +} + void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { const auto failed = [this] { QMessageBox::warning(this, tr("RomFS Extraction Failed!"), diff --git a/src/yuzu/main.h b/src/yuzu/main.h index db573d606..bbdf7ae51 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -32,6 +32,8 @@ class QPushButton; class QProgressDialog; class WaitTreeWidget; enum class GameListOpenTarget; +enum class GameListRemoveTarget; +enum class InstalledEntryType; class GameListPlaceholder; namespace Core::Frontend { @@ -198,6 +200,8 @@ private slots: void OnGameListLoadFile(QString game_path); void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path); void OnTransferableShaderCacheOpenFile(u64 program_id); + void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); + void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target); void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, From ef02370816924156b6c3c3fd70e7e2595be19216 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 17 Jul 2020 06:06:56 -0400 Subject: [PATCH 3/9] main: Split removal cases into their individual functions and address feedback --- src/yuzu/main.cpp | 243 ++++++++++++++++++++++++---------------------- src/yuzu/main.h | 5 + 2 files changed, 133 insertions(+), 115 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e94785101..49c10459b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1396,19 +1396,18 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src } void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { - QString entry_type; - - switch (type) { - case InstalledEntryType::Game: - entry_type = tr("Contents"); - break; - case InstalledEntryType::Update: - entry_type = tr("Update"); - break; - case InstalledEntryType::AddOnContent: - entry_type = tr("DLC"); - break; - } + const QString entry_type = [this, type] { + switch (type) { + case InstalledEntryType::Game: + return tr("Contents"); + case InstalledEntryType::Update: + return tr("Update"); + case InstalledEntryType::AddOnContent: + return tr("DLC"); + default: + return QString{}; + } + }(); if (QMessageBox::question( this, tr("Remove Entry"), tr("Remove Installed Game %1?").arg(entry_type), @@ -1416,68 +1415,19 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT return; } - bool res; - switch (type) { case InstalledEntryType::Game: - res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->RemoveExistingEntry(program_id); - - if (res) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the installed base game.")); - } else { - QMessageBox::warning( - this, tr("Error Removing %1").arg(entry_type), - tr("The base game is not installed in the NAND and cannot be removed.")); - } + RemoveBaseContent(program_id, entry_type); [[fallthrough]]; case InstalledEntryType::Update: - res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->RemoveExistingEntry(program_id | 0x800); - - if (res) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the installed update.")); - } else { - QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), - tr("There is no update installed for this title.")); - } - + RemoveUpdateContent(program_id, entry_type); if (type == InstalledEntryType::Game) { [[fallthrough]]; } else { break; } case InstalledEntryType::AddOnContent: - u32 count{}; - const auto dlc_entries = Core::System::GetInstance().GetContentProvider().ListEntriesFilter( - FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); - - for (const auto& entry : dlc_entries) { - if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { - res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->RemoveExistingEntry(entry.title_id); - if (res) { - ++count; - } - } - } - - if (count == 0) { - QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), - tr("There are no DLC installed for this title.")); - break; - } - - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed %1 installed DLC.").arg(count)); + RemoveAddOnContent(program_id, entry_type); break; } game_list->PopulateAsync(UISettings::values.game_dirs); @@ -1485,17 +1435,75 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT "game_list"); } -void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target) { - QString question; +void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) { + const auto res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->RemoveExistingEntry(program_id); - switch (target) { - case GameListRemoveTarget::ShaderCache: - question = tr("Delete Transferable Shader Cache?"); - break; - case GameListRemoveTarget::CustomConfiguration: - question = tr("Remove Custom Game Configuration?"); - break; + if (res) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the installed base game.")); + } else { + QMessageBox::warning( + this, tr("Error Removing %1").arg(entry_type), + tr("The base game is not installed in the NAND and cannot be removed.")); } +} + +void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) { + const auto res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->RemoveExistingEntry(program_id | 0x800); + + if (res) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the installed update.")); + } else { + QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), + tr("There is no update installed for this title.")); + } +} + +void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) { + u32 count{}; + const auto dlc_entries = Core::System::GetInstance().GetContentProvider().ListEntriesFilter( + FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + + for (const auto& entry : dlc_entries) { + if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { + const auto res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->RemoveExistingEntry(entry.title_id); + if (res) { + ++count; + } + } + } + + if (count == 0) { + QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), + tr("There are no DLC installed for this title.")); + return; + } + + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed %1 installed DLC.").arg(count)); +} + +void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target) { + const QString question = [this, target] { + switch (target) { + case GameListRemoveTarget::ShaderCache: + return tr("Delete Transferable Shader Cache?"); + case GameListRemoveTarget::CustomConfiguration: + return tr("Remove Custom Game Configuration?"); + default: + return QString{}; + } + }(); if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { @@ -1503,52 +1511,57 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ } switch (target) { - case GameListRemoveTarget::ShaderCache: { - const QString shader_dir = - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); - const QString transferable_shader_cache_folder_path = - shader_dir + QStringLiteral("opengl") + QDir::separator() + - QStringLiteral("transferable"); - const QString transferable_shader_cache_file_path = - transferable_shader_cache_folder_path + QDir::separator() + - QString::fromStdString(fmt::format("{:016X}.bin", program_id)); - - if (!QFile::exists(transferable_shader_cache_file_path)) { - QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), - tr("A shader cache for this title does not exist.")); - break; - } - - if (QFile::remove(transferable_shader_cache_file_path)) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the transferable shader cache.")); - } else { - QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), - tr("Failed to remove the transferable shader cache.")); - } + case GameListRemoveTarget::ShaderCache: + RemoveTransferableShaderCache(program_id); + break; + case GameListRemoveTarget::CustomConfiguration: + RemoveCustomConfiguration(program_id); break; } - case GameListRemoveTarget::CustomConfiguration: { - const QString config_dir = - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir)); - const QString custom_config_file_path = - config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id)); +} - if (!QFile::exists(custom_config_file_path)) { - QMessageBox::warning(this, tr("Error Removing Custom Configuration"), - tr("A custom configuration for this title does not exist.")); - break; - } +void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { + const QString shader_dir = + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); + const QString transferable_shader_cache_folder_path = + shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); + const QString transferable_shader_cache_file_path = + transferable_shader_cache_folder_path + QDir::separator() + + QString::fromStdString(fmt::format("{:016X}.bin", program_id)); - if (QFile::remove(custom_config_file_path)) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the custom game configuration.")); - } else { - QMessageBox::warning(this, tr("Error Removing Custom Configuration"), - tr("Failed to remove the custom game configuration.")); - } - break; + if (!QFile::exists(transferable_shader_cache_file_path)) { + QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), + tr("A shader cache for this title does not exist.")); + return; } + + if (QFile::remove(transferable_shader_cache_file_path)) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the transferable shader cache.")); + } else { + QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), + tr("Failed to remove the transferable shader cache.")); + } +} + +void GMainWindow::RemoveCustomConfiguration(u64 program_id) { + const QString config_dir = + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir)); + const QString custom_config_file_path = + config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id)); + + if (!QFile::exists(custom_config_file_path)) { + QMessageBox::warning(this, tr("Error Removing Custom Configuration"), + tr("A custom configuration for this title does not exist.")); + return; + } + + if (QFile::remove(custom_config_file_path)) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the custom game configuration.")); + } else { + QMessageBox::warning(this, tr("Error Removing Custom Configuration"), + tr("Failed to remove the custom game configuration.")); } } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index bbdf7ae51..73a44a3bf 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -233,6 +233,11 @@ private slots: void OnLanguageChanged(const QString& locale); private: + void RemoveBaseContent(u64 program_id, const QString& entry_type); + void RemoveUpdateContent(u64 program_id, const QString& entry_type); + void RemoveAddOnContent(u64 program_id, const QString& entry_type); + void RemoveTransferableShaderCache(u64 program_id); + void RemoveCustomConfiguration(u64 program_id); std::optional SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); InstallResult InstallNSPXCI(const QString& filename); InstallResult InstallNCA(const QString& filename); From f78e44762a109c9dac0e41735aa8af8616cbadfa Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 17 Jul 2020 06:14:13 -0400 Subject: [PATCH 4/9] main: Silence [[fallthrough]] warning --- src/yuzu/main.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 49c10459b..eeca0acb2 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1421,11 +1421,10 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT [[fallthrough]]; case InstalledEntryType::Update: RemoveUpdateContent(program_id, entry_type); - if (type == InstalledEntryType::Game) { - [[fallthrough]]; - } else { + if (type != InstalledEntryType::Game) { break; } + [[fallthrough]]; case InstalledEntryType::AddOnContent: RemoveAddOnContent(program_id, entry_type); break; From cd814bfdfee631bca465db1810df648267f439b8 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 17 Jul 2020 13:04:49 -0400 Subject: [PATCH 5/9] main: Remove assert for opening savedata when program_id = 0 --- src/yuzu/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index eeca0acb2..08aac9f45 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1260,7 +1260,6 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str case GameListOpenTarget::SaveData: { open_target = tr("Save Data"); const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); - ASSERT(program_id != 0); if (has_user_save) { // User save data From b317942131ba48577e8303c475e9720fd93e54c7 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 17 Jul 2020 13:04:07 -0400 Subject: [PATCH 6/9] game_list: Limit context menu options for homebrew Hides the following options when the title id is 0: - Open Save Location - Open Mod Data Location - Open Transferable Shader Cache - All removal options except Remove Custom Configuration --- src/yuzu/game_list.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 20073f3c8..62acc3720 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -491,7 +491,13 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string pat context_menu.addSeparator(); QAction* properties = context_menu.addAction(tr("Properties")); - open_save_location->setEnabled(program_id != 0); + open_save_location->setVisible(program_id != 0); + open_mod_location->setVisible(program_id != 0); + open_transferable_shader_cache->setVisible(program_id != 0); + remove_update->setVisible(program_id != 0); + remove_dlc->setVisible(program_id != 0); + remove_shader_cache->setVisible(program_id != 0); + remove_all_content->setVisible(program_id != 0); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); From 2ec852dd9f38ab6376f78ade15b8c62d86ab518a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:28:44 -0400 Subject: [PATCH 7/9] registered_cache: Add support for removing folder ncas --- src/core/file_sys/registered_cache.cpp | 101 +++++++++++++------------ src/core/file_sys/registered_cache.h | 6 +- 2 files changed, 54 insertions(+), 53 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 return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy); } -bool RegisteredCache::RemoveExistingEntry(u64 title_id) { - const auto delete_nca = [this](const NcaID& id) { - const auto path = GetRelativePathFromNcaID(id, false, true, false); - - if (dir->GetFileRelative(path) == nullptr) { - return false; - } - - Core::Crypto::SHA256Hash hash{}; - mbedtls_sha256_ret(id.data(), id.size(), hash.data(), 0); - const auto dirname = fmt::format("000000{:02X}", hash[0]); - - const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname); - - const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false))); - - return res; - }; - - // If an entry exists in the registered cache, remove it - if (HasEntry(title_id, ContentRecordType::Meta)) { - LOG_INFO(Loader, - "Previously installed entry (v{}) for title_id={:016X} detected! " - "Attempting to remove...", - GetEntryVersion(title_id).value_or(0), title_id); - // Get all the ncas associated with the current CNMT and delete them - const auto meta_old_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); - const auto program_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{}); - const auto data_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{}); - const auto control_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{}); - const auto html_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{}); - const auto legal_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{}); - - delete_nca(meta_old_id); - delete_nca(program_id); - delete_nca(data_id); - delete_nca(control_id); - delete_nca(html_id); - delete_nca(legal_id); - return true; - } - return false; -} - InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_exists, const VfsCopyFunction& copy) { const auto ncas = nsp.GetNCAsCollapsed(); @@ -692,6 +642,57 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); } +bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { + const auto delete_nca = [this](const NcaID& id) { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + const bool isFile = dir->GetFileRelative(path) != nullptr; + const bool isDir = dir->GetDirectoryRelative(path) != nullptr; + + if (isFile) { + return dir->DeleteFile(path); + } else if (isDir) { + return dir->DeleteSubdirectoryRecursive(path); + } + + return false; + }; + + // If an entry exists in the registered cache, remove it + if (HasEntry(title_id, ContentRecordType::Meta)) { + LOG_INFO(Loader, + "Previously installed entry (v{}) for title_id={:016X} detected! " + "Attempting to remove...", + GetEntryVersion(title_id).value_or(0), title_id); + + // Get all the ncas associated with the current CNMT and delete them + const auto meta_old_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); + const auto program_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{}); + const auto data_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{}); + const auto control_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{}); + const auto html_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{}); + const auto legal_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{}); + + const auto deleted_meta = delete_nca(meta_old_id); + const auto deleted_program = delete_nca(program_id); + const auto deleted_data = delete_nca(data_id); + const auto deleted_control = delete_nca(control_id); + const auto deleted_html = delete_nca(html_id); + const auto deleted_legal = delete_nca(legal_id); + + return deleted_meta && (deleted_meta || deleted_program || deleted_data || + deleted_control || deleted_html || deleted_legal); + } + + return false; +} + InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, bool overwrite_if_exists, std::optional 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: std::optional title_type = {}, std::optional record_type = {}, std::optional title_id = {}) const override; - // Removes an existing entry based on title id - bool RemoveExistingEntry(u64 title_id); - // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure // there is a meta NCA and all of them are accessible. InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false, @@ -172,6 +169,9 @@ public: InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, const VfsCopyFunction& copy = &VfsRawCopy); + // Removes an existing entry based on title id + bool RemoveExistingEntry(u64 title_id) const; + private: template void IterateAllMetadata(std::vector& out, From ed46f3c62a7649cfaada61c8cdb9dfd91e54a9db Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:30:25 -0400 Subject: [PATCH 8/9] xts_archive: Check if the file is nullptr prior to parsing Fixes an access violation where the file no longer exists at the specified path while being parsed. --- src/core/file_sys/xts_archive.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 nca_id) NAX::~NAX() = default; Loader::ResultStatus NAX::Parse(std::string_view path) { - if (file->ReadObject(header.get()) != sizeof(NAXHeader)) + if (file == nullptr) { + return Loader::ResultStatus::ErrorNullFile; + } + if (file->ReadObject(header.get()) != sizeof(NAXHeader)) { return Loader::ResultStatus::ErrorBadNAXHeader; - - if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) + } + if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) { return Loader::ResultStatus::ErrorBadNAXHeader; - - if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) + } + if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) { return Loader::ResultStatus::ErrorIncorrectNAXFileSize; + } keys.DeriveSDSeedLazy(); std::array sd_keys{}; From e59d17167d72a96068b7fd929e11a1cf26ecb809 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:30:56 -0400 Subject: [PATCH 9/9] main: Add support for removing SDMC installed titles --- src/yuzu/main.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 08aac9f45..276658c9e 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1428,16 +1428,15 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT RemoveAddOnContent(program_id, entry_type); break; } - game_list->PopulateAsync(UISettings::values.game_dirs); FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list"); + game_list->PopulateAsync(UISettings::values.game_dirs); } void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) { - const auto res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->RemoveExistingEntry(program_id); + const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); + const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) || + fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id); if (res) { QMessageBox::information(this, tr("Successfully Removed"), @@ -1450,10 +1449,10 @@ void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) { } void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) { - const auto res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->RemoveExistingEntry(program_id | 0x800); + const auto update_id = program_id | 0x800; + const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); + const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || + fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); if (res) { QMessageBox::information(this, tr("Successfully Removed"), @@ -1466,15 +1465,15 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) { u32 count{}; + const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); const auto dlc_entries = Core::System::GetInstance().GetContentProvider().ListEntriesFilter( FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); for (const auto& entry : dlc_entries) { if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { - const auto res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->RemoveExistingEntry(entry.title_id); + const auto res = + fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) || + fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id); if (res) { ++count; } @@ -1883,9 +1882,9 @@ void GMainWindow::OnMenuInstallToNAND() { : tr("%n file(s) failed to install\n", "", failed_files.size())); QMessageBox::information(this, tr("Install Results"), install_results); - game_list->PopulateAsync(UISettings::values.game_dirs); FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list"); + game_list->PopulateAsync(UISettings::values.game_dirs); ui.action_Install_File_NAND->setEnabled(true); }