From a588bc5da85cc8413782148cca06d3d0c83d480a Mon Sep 17 00:00:00 2001 From: ElBread3 <92335081+ElBread3@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:49:42 -0500 Subject: [PATCH] Separate Updates from Game Folder (#1026) * separate updates implementation * clang format clang format clang format clang format clang hates me work please * hotfix: check for sfo file instead of the folder * tiny change * refactor * forgot to change this over * add review changes * use operator --- src/common/config.cpp | 11 ++++++ src/common/config.h | 2 + src/core/file_sys/fs.cpp | 10 ++++- src/emulator.cpp | 5 ++- src/qt_gui/game_info.cpp | 2 +- src/qt_gui/game_info.h | 8 +++- src/qt_gui/gui_context_menus.h | 69 +++++++++++++++++++++++++++++++++- src/qt_gui/main_window.cpp | 26 +++++++++---- src/qt_gui/settings_dialog.cpp | 7 ++++ src/qt_gui/settings_dialog.ui | 7 ++++ 10 files changed, 135 insertions(+), 12 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 17862e6aa07..1dde7223c40 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -60,6 +60,7 @@ static bool vkMarkers = false; static bool vkCrashDiagnostic = false; static s16 cursorState = HideCursorState::Idle; static int cursorHideTimeout = 5; // 5 seconds (default) +static bool separateupdatefolder = false; // Gui std::vector settings_install_dirs = {}; @@ -207,6 +208,10 @@ bool vkCrashDiagnosticEnabled() { return vkCrashDiagnostic; } +bool getSeparateUpdateEnabled() { + return separateupdatefolder; +} + void setGpuId(s32 selectedGpuId) { gpuId = selectedGpuId; } @@ -319,6 +324,10 @@ void setSpecialPadClass(int type) { specialPadClass = type; } +void setSeparateUpdateEnabled(bool use) { + separateupdatefolder = use; +} + void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_x = x; main_window_geometry_y = y; @@ -483,6 +492,7 @@ void load(const std::filesystem::path& path) { } isShowSplash = toml::find_or(general, "showSplash", true); isAutoUpdate = toml::find_or(general, "autoUpdate", false); + separateupdatefolder = toml::find_or(general, "separateUpdateEnabled", false); } if (data.contains("Input")) { @@ -597,6 +607,7 @@ void save(const std::filesystem::path& path) { data["General"]["updateChannel"] = updateChannel; data["General"]["showSplash"] = isShowSplash; data["General"]["autoUpdate"] = isAutoUpdate; + data["General"]["separateUpdateEnabled"] = separateupdatefolder; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["backButtonBehavior"] = backButtonBehavior; diff --git a/src/common/config.h b/src/common/config.h index 591d6dced0e..9c71c96a8e6 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -19,6 +19,7 @@ bool isFullscreenMode(); bool getPlayBGM(); int getBGMvolume(); bool getEnableDiscordRPC(); +bool getSeparateUpdateEnabled(); std::string getLogFilter(); std::string getLogType(); @@ -62,6 +63,7 @@ void setLanguage(u32 language); void setNeoMode(bool enable); void setUserName(const std::string& type); void setUpdateChannel(const std::string& type); +void setSeparateUpdateEnabled(bool use); void setCursorState(s16 cursorState); void setCursorHideTimeout(int newcursorHideTimeout); diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 3b060dd8327..8e6d7462254 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/config.h" #include "common/string_util.h" #include "core/file_sys/fs.h" @@ -53,7 +54,14 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory, b // Remove device (e.g /app0) from path to retrieve relative path. pos = mount->mount.size() + 1; const auto rel_path = std::string_view(corrected_path).substr(pos); - const auto host_path = mount->host_path / rel_path; + std::filesystem::path host_path = mount->host_path / rel_path; + + std::filesystem::path patch_path = mount->host_path; + patch_path += "-UPDATE"; + if (corrected_path.starts_with("/app0/") && std::filesystem::exists(patch_path / rel_path)) { + host_path = patch_path / rel_path; + } + if (!NeedsCaseInsensitiveSearch) { return host_path; } diff --git a/src/emulator.cpp b/src/emulator.cpp index 67aaa049270..46bcfea3770 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -114,7 +114,10 @@ void Emulator::Run(const std::filesystem::path& file) { std::string app_version; u32 fw_version; - std::filesystem::path sce_sys_folder = file.parent_path() / "sce_sys"; + std::filesystem::path game_patch_folder = file.parent_path().concat("-UPDATE"); + bool use_game_patch = std::filesystem::exists(game_patch_folder / "sce_sys"); + std::filesystem::path sce_sys_folder = + use_game_patch ? game_patch_folder / "sce_sys" : file.parent_path() / "sce_sys"; if (std::filesystem::is_directory(sce_sys_folder)) { for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) { if (entry.path().filename() == "param.sfo") { diff --git a/src/qt_gui/game_info.cpp b/src/qt_gui/game_info.cpp index d82f43f2011..48643f8edf4 100644 --- a/src/qt_gui/game_info.cpp +++ b/src/qt_gui/game_info.cpp @@ -17,7 +17,7 @@ void GameInfoClass::GetGameInfo(QWidget* parent) { QDir parentFolder(installDir); QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const auto& fileInfo : fileList) { - if (fileInfo.isDir()) { + if (fileInfo.isDir() && !fileInfo.filePath().endsWith("-UPDATE")) { filePaths.append(fileInfo.absoluteFilePath()); } } diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index 8f65803bf48..640b25e49ff 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -25,9 +25,15 @@ class GameInfoClass : public QObject { static GameInfo readGameInfo(const std::filesystem::path& filePath) { GameInfo game; game.path = filePath; + std::filesystem::path sce_folder_path = filePath / "sce_sys" / "param.sfo"; + std::filesystem::path game_update_path = + std::filesystem::path(filePath.string() + "-UPDATE"); + if (std::filesystem::exists(game_update_path / "sce_sys" / "param.sfo")) { + sce_folder_path = game_update_path / "sce_sys" / "param.sfo"; + } PSF psf; - if (psf.Open(game.path / "sce_sys" / "param.sfo")) { + if (psf.Open(sce_folder_path)) { game.icon_path = game.path / "sce_sys" / "icon0.png"; QString iconpath; Common::FS::PathToQString(iconpath, game.icon_path); diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 4eb65757217..fba8616af20 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -68,6 +68,18 @@ class GuiContextMenus : public QObject { menu.addMenu(copyMenu); + // "Delete..." submenu. + QMenu* deleteMenu = new QMenu(tr("Delete..."), widget); + QAction* deleteGame = new QAction(tr("Delete Game"), widget); + QAction* deleteUpdate = new QAction(tr("Delete Update"), widget); + QAction* deleteDLC = new QAction(tr("Delete DLC"), widget); + + deleteMenu->addAction(deleteGame); + deleteMenu->addAction(deleteUpdate); + deleteMenu->addAction(deleteDLC); + + menu.addMenu(deleteMenu); + // Show menu. auto selected = menu.exec(global_pos); if (!selected) { @@ -82,7 +94,13 @@ class GuiContextMenus : public QObject { if (selected == &openSfoViewer) { PSF psf; - if (psf.Open(std::filesystem::path(m_games[itemID].path) / "sce_sys" / "param.sfo")) { + QString game_update_path; + Common::FS::PathToQString(game_update_path, m_games[itemID].path.concat("-UPDATE")); + std::filesystem::path game_folder_path = m_games[itemID].path; + if (std::filesystem::exists(Common::FS::PathFromQString(game_update_path))) { + game_folder_path = Common::FS::PathFromQString(game_update_path); + } + if (psf.Open(game_folder_path / "sce_sys" / "param.sfo")) { int rows = psf.GetEntries().size(); QTableWidget* tableWidget = new QTableWidget(rows, 2); tableWidget->setAttribute(Qt::WA_DeleteOnClose); @@ -269,6 +287,55 @@ class GuiContextMenus : public QObject { .arg(QString::fromStdString(m_games[itemID].size)); clipboard->setText(combinedText); } + + if (selected == deleteGame || selected == deleteUpdate || selected == deleteDLC) { + bool error = false; + QString folder_path, game_update_path; + Common::FS::PathToQString(folder_path, m_games[itemID].path); + Common::FS::PathToQString(game_update_path, m_games[itemID].path.concat("-UPDATE")); + QString message_type = tr("Game"); + if (selected == deleteUpdate) { + if (!Config::getSeparateUpdateEnabled()) { + QMessageBox::critical( + nullptr, tr("Error"), + QString(tr("This feature requires the 'Enable Separate Update Folder' " + "config option " + "to work. If you want to use this feature, please enable it."))); + error = true; + } else if (!std::filesystem::exists(m_games[itemID].path.concat("-UPDATE"))) { + QMessageBox::critical(nullptr, tr("Error"), + QString(tr("This game has no update to delete!"))); + error = true; + } else { + folder_path = game_update_path; + message_type = tr("Update"); + } + } else if (selected == deleteDLC) { + std::filesystem::path addon_path = + Config::getAddonInstallDir() / + Common::FS::PathFromQString(folder_path).parent_path().filename(); + if (!std::filesystem::exists(addon_path)) { + QMessageBox::critical(nullptr, tr("Error"), + QString(tr("This game has no DLC to delete!"))); + error = true; + } else { + folder_path = QString::fromStdString(addon_path.string()); + message_type = tr("DLC"); + } + } + if (!error) { + QString gameName = QString::fromStdString(m_games[itemID].name); + QDir dir(folder_path); + QMessageBox::StandardButton reply = QMessageBox::question( + nullptr, QString(tr("Delete %1")).arg(message_type), + QString(tr("Are you sure you want to delete %1's %2 directory?")) + .arg(gameName, message_type), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) { + dir.removeRecursively(); + } + } + } } int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) { diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 025749dd4e8..d80102ff4e6 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -676,10 +676,17 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int InstallDirSelect ids; ids.exec(); auto game_install_dir = ids.getSelectedDirectory(); - auto extract_path = game_install_dir / pkg.GetTitleID(); + auto game_folder_path = game_install_dir / pkg.GetTitleID(); QString pkgType = QString::fromStdString(pkg.GetPkgFlags()); + bool use_game_update = pkgType.contains("Patch") && Config::getSeparateUpdateEnabled(); + auto game_update_path = use_game_update + ? game_install_dir / (std::string(pkg.GetTitleID()) + "-UPDATE") + : game_folder_path; + if (!std::filesystem::exists(game_update_path)) { + std::filesystem::create_directory(game_update_path); + } QString gameDirPath; - Common::FS::PathToQString(gameDirPath, extract_path); + Common::FS::PathToQString(gameDirPath, game_folder_path); QDir game_dir(gameDirPath); if (game_dir.exists()) { QMessageBox msgBox; @@ -715,7 +722,11 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); return; } - psf.Open(extract_path / "sce_sys" / "param.sfo"); + std::filesystem::path sce_folder_path = + std::filesystem::exists(game_update_path / "sce_sys" / "param.sfo") + ? game_update_path / "sce_sys" / "param.sfo" + : game_folder_path / "sce_sys" / "param.sfo"; + psf.Open(sce_folder_path); QString game_app_version; if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { game_app_version = QString::fromStdString(std::string{*app_ver}); @@ -764,7 +775,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int addonMsgBox.setDefaultButton(QMessageBox::No); int result = addonMsgBox.exec(); if (result == QMessageBox::Yes) { - extract_path = addon_extract_path; + game_update_path = addon_extract_path; } else { return; } @@ -775,12 +786,14 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int msgBox.setDefaultButton(QMessageBox::No); int result = msgBox.exec(); if (result == QMessageBox::Yes) { - extract_path = addon_extract_path; + game_update_path = addon_extract_path; } else { return; } } } else { + QString gameDirPath; + Common::FS::PathToQString(gameDirPath, game_folder_path); msgBox.setText(QString(tr("Game already installed") + "\n" + gameDirPath + "\n" + tr("Would you like to overwrite?"))); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); @@ -801,8 +814,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int } // what else? } - - if (!pkg.Extract(file, extract_path, failreason)) { + if (!pkg.Extract(file, game_update_path, failreason)) { QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); } else { int nfiles = pkg.GetNumberOfFiles(); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index b63f14c051e..77701f22132 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -133,6 +133,9 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge connect(ui->ps4proCheckBox, &QCheckBox::stateChanged, this, [](int val) { Config::setNeoMode(val); }); + connect(ui->separateUpdatesCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setSeparateUpdateEnabled(val); }); + connect(ui->logTypeComboBox, &QComboBox::currentTextChanged, this, [](const QString& text) { Config::setLogType(text.toStdString()); }); @@ -270,6 +273,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->showSplashCheckBox->installEventFilter(this); ui->ps4proCheckBox->installEventFilter(this); ui->discordRPCCheckbox->installEventFilter(this); + ui->separateUpdatesCheckBox->installEventFilter(this); ui->userName->installEventFilter(this); ui->logTypeGroupBox->installEventFilter(this); ui->logFilter->installEventFilter(this); @@ -328,6 +332,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->logTypeComboBox->setCurrentText(QString::fromStdString(Config::getLogType())); ui->logFilterLineEdit->setText(QString::fromStdString(Config::getLogFilter())); ui->userNameLineEdit->setText(QString::fromStdString(Config::getUserName())); + ui->separateUpdatesCheckBox->setChecked(Config::getSeparateUpdateEnabled()); ui->debugDump->setChecked(Config::debugDump()); ui->vkValidationCheckBox->setChecked(Config::vkValidationEnabled()); @@ -437,6 +442,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("ps4proCheckBox"); } else if (elementName == "discordRPCCheckbox") { text = tr("discordRPCCheckbox"); + } else if (elementName == "separateUpdatesCheckBox") { + text = tr("separateUpdatesCheckBox"); } else if (elementName == "userName") { text = tr("userName"); } else if (elementName == "logTypeGroupBox") { diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index b98fe228de8..5d1225a1e9f 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -134,6 +134,13 @@ + + + + Enable Separate Update Folder + + +