// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include "about_dialog.h" #include "cheats_patches.h" #include "check_update.h" #include "common/io_file.h" #include "common/path_util.h" #include "common/scm_rev.h" #include "common/string_util.h" #include "common/version.h" #include "core/file_format/pkg.h" #include "core/loader.h" #include "game_install_dialog.h" #include "main_window.h" #include "settings_dialog.h" #include "video_core/renderer_vulkan/vk_instance.h" MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); installEventFilter(this); setAttribute(Qt::WA_DeleteOnClose); } MainWindow::~MainWindow() { SaveWindowState(); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::save(config_dir / "config.toml"); } bool MainWindow::Init() { auto start = std::chrono::steady_clock::now(); // setup ui AddUiWidgets(); CreateActions(); CreateRecentGameActions(); ConfigureGuiFromSettings(); LoadTranslation(); CreateDockWindows(); CreateConnects(); SetLastUsedTheme(); SetLastIconSizeBullet(); GetPhysicalDevices(); // show ui setMinimumSize(350, minimumSizeHint().height()); std::string window_title = ""; if (Common::isRelease) { window_title = fmt::format("shadPS4 v{}", Common::VERSION); } else { window_title = fmt::format("shadPS4 v{} {} {}", Common::VERSION, Common::g_scm_branch, Common::g_scm_desc); } setWindowTitle(QString::fromStdString(window_title)); this->show(); // load game list LoadGameLists(); CheckUpdateMain(true); auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); statusBar.reset(new QStatusBar); this->setStatusBar(statusBar.data()); // Update status bar int numGames = m_game_info->m_games.size(); QString statusMessage = "Games: " + QString::number(numGames) + " (" + QString::number(duration.count()) + "ms)"; statusBar->showMessage(statusMessage); return true; } void MainWindow::CreateActions() { // create action group for icon size m_icon_size_act_group = new QActionGroup(this); m_icon_size_act_group->addAction(ui->setIconSizeTinyAct); m_icon_size_act_group->addAction(ui->setIconSizeSmallAct); m_icon_size_act_group->addAction(ui->setIconSizeMediumAct); m_icon_size_act_group->addAction(ui->setIconSizeLargeAct); // create action group for list mode m_list_mode_act_group = new QActionGroup(this); m_list_mode_act_group->addAction(ui->setlistModeListAct); m_list_mode_act_group->addAction(ui->setlistModeGridAct); // create action group for themes m_theme_act_group = new QActionGroup(this); m_theme_act_group->addAction(ui->setThemeDark); m_theme_act_group->addAction(ui->setThemeLight); m_theme_act_group->addAction(ui->setThemeGreen); m_theme_act_group->addAction(ui->setThemeBlue); m_theme_act_group->addAction(ui->setThemeViolet); } void MainWindow::AddUiWidgets() { // add toolbar widgets QApplication::setStyle("Fusion"); ui->toolBar->setObjectName("mw_toolbar"); ui->toolBar->addWidget(ui->playButton); ui->toolBar->addWidget(ui->pauseButton); ui->toolBar->addWidget(ui->stopButton); ui->toolBar->addWidget(ui->refreshButton); ui->toolBar->addWidget(ui->settingsButton); ui->toolBar->addWidget(ui->controllerButton); QFrame* line = new QFrame(this); line->setFrameShape(QFrame::StyledPanel); line->setFrameShadow(QFrame::Sunken); ui->toolBar->addWidget(line); ui->toolBar->addWidget(ui->sizeSliderContainer); ui->toolBar->addWidget(ui->mw_searchbar); } void MainWindow::CreateDockWindows() { // place holder widget is needed for good health they say :) QWidget* phCentralWidget = new QWidget(this); setCentralWidget(phCentralWidget); m_dock_widget.reset(new QDockWidget(tr("Game List"), this)); m_game_list_frame.reset(new GameListFrame(m_game_info, this)); m_game_list_frame->setObjectName("gamelist"); m_game_grid_frame.reset(new GameGridFrame(m_game_info, this)); m_game_grid_frame->setObjectName("gamegridlist"); m_elf_viewer.reset(new ElfViewer(this)); m_elf_viewer->setObjectName("elflist"); int table_mode = Config::getTableMode(); int slider_pos = 0; if (table_mode == 0) { // List m_game_grid_frame->hide(); m_elf_viewer->hide(); m_game_list_frame->show(); m_dock_widget->setWidget(m_game_list_frame.data()); slider_pos = Config::getSliderPosition(); ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start; isTableList = true; } else if (table_mode == 1) { // Grid m_game_list_frame->hide(); m_elf_viewer->hide(); m_game_grid_frame->show(); m_dock_widget->setWidget(m_game_grid_frame.data()); slider_pos = Config::getSliderPositionGrid(); ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start; isTableList = false; } else { m_game_list_frame->hide(); m_game_grid_frame->hide(); m_elf_viewer->show(); m_dock_widget->setWidget(m_elf_viewer.data()); isTableList = false; } m_dock_widget->setAllowedAreas(Qt::AllDockWidgetAreas); m_dock_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_dock_widget->resize(this->width(), this->height()); addDockWidget(Qt::LeftDockWidgetArea, m_dock_widget.data()); this->setDockNestingEnabled(true); // handle resize like this for now, we deal with it when we add more docks connect(this, &MainWindow::WindowResized, this, [&]() { this->resizeDocks({m_dock_widget.data()}, {this->width()}, Qt::Orientation::Horizontal); }); } void MainWindow::LoadGameLists() { // Get game info from game folders. m_game_info->GetGameInfo(this); if (isTableList) { m_game_list_frame->PopulateGameList(); } else { m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } } void MainWindow::CheckUpdateMain(bool checkSave) { if (checkSave) { if (!Config::autoUpdate()) { return; } } auto checkUpdate = new CheckUpdate(false); checkUpdate->exec(); } void MainWindow::GetPhysicalDevices() { Vulkan::Instance instance(false, false); auto physical_devices = instance.GetPhysicalDevices(); for (const vk::PhysicalDevice physical_device : physical_devices) { auto prop = physical_device.getProperties(); QString name = QString::fromUtf8(prop.deviceName, -1); if (prop.apiVersion < Vulkan::TargetVulkanApiVersion) { name += tr(" * Unsupported Vulkan Version"); } m_physical_devices.push_back(name); } } void MainWindow::CreateConnects() { connect(this, &MainWindow::WindowResized, this, &MainWindow::HandleResize); connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable); connect(ui->exitAct, &QAction::triggered, this, &QWidget::close); connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable); connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshGameTable); connect(ui->showGameListAct, &QAction::triggered, this, &MainWindow::ShowGameList); connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable); connect(ui->sizeSlider, &QSlider::valueChanged, this, [this](int value) { if (isTableList) { m_game_list_frame->icon_size = 36 + value; // 36 is the minimum icon size to use due to text disappearing. m_game_list_frame->ResizeIcons(36 + value); Config::setIconSize(36 + value); Config::setSliderPosition(value); } else { m_game_grid_frame->icon_size = 69 + value; m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); Config::setIconSizeGrid(69 + value); Config::setSliderPositionGrid(value); } }); connect(ui->playButton, &QPushButton::clicked, this, &MainWindow::StartGame); connect(m_game_grid_frame.get(), &QTableWidget::cellDoubleClicked, this, &MainWindow::StartGame); connect(m_game_list_frame.get(), &QTableWidget::cellDoubleClicked, this, &MainWindow::StartGame); connect(ui->configureAct, &QAction::triggered, this, [this]() { auto settingsDialog = new SettingsDialog(m_physical_devices, this); connect(settingsDialog, &SettingsDialog::LanguageChanged, this, &MainWindow::OnLanguageChanged); settingsDialog->exec(); }); connect(ui->settingsButton, &QPushButton::clicked, this, [this]() { auto settingsDialog = new SettingsDialog(m_physical_devices, this); connect(settingsDialog, &SettingsDialog::LanguageChanged, this, &MainWindow::OnLanguageChanged); settingsDialog->exec(); }); connect(ui->updaterAct, &QAction::triggered, this, [this]() { auto checkUpdate = new CheckUpdate(true); checkUpdate->exec(); }); connect(ui->aboutAct, &QAction::triggered, this, [this]() { auto aboutDialog = new AboutDialog(this); aboutDialog->exec(); }); connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = 36; // 36 is the minimum icon size to use due to text disappearing. ui->sizeSlider->setValue(0); // icone_size - 36 Config::setIconSize(36); Config::setSliderPosition(0); } else { ui->sizeSlider->setValue(0); // icone_size - 36 Config::setIconSizeGrid(69); Config::setSliderPositionGrid(0); } }); connect(ui->setIconSizeSmallAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = 64; ui->sizeSlider->setValue(28); Config::setIconSize(64); Config::setSliderPosition(28); } else { ui->sizeSlider->setValue(28); Config::setIconSizeGrid(97); Config::setSliderPositionGrid(28); } }); connect(ui->setIconSizeMediumAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = 128; ui->sizeSlider->setValue(92); Config::setIconSize(128); Config::setSliderPosition(92); } else { ui->sizeSlider->setValue(92); Config::setIconSizeGrid(160); Config::setSliderPositionGrid(91); } }); connect(ui->setIconSizeLargeAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = 256; ui->sizeSlider->setValue(220); Config::setIconSize(256); Config::setSliderPosition(220); } else { ui->sizeSlider->setValue(220); Config::setIconSizeGrid(256); Config::setSliderPositionGrid(220); } }); // List connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget.data(), [this]() { BackgroundMusicPlayer::getInstance().stopMusic(); m_dock_widget->setWidget(m_game_list_frame.data()); m_game_grid_frame->hide(); m_elf_viewer->hide(); m_game_list_frame->show(); if (m_game_list_frame->item(0, 0) == nullptr) { m_game_list_frame->clearContents(); m_game_list_frame->PopulateGameList(); } isTableList = true; Config::setTableMode(0); int slider_pos = Config::getSliderPosition(); ui->sizeSlider->setEnabled(true); ui->sizeSlider->setSliderPosition(slider_pos); }); // Grid connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget.data(), [this]() { BackgroundMusicPlayer::getInstance().stopMusic(); m_dock_widget->setWidget(m_game_grid_frame.data()); m_game_grid_frame->show(); m_game_list_frame->hide(); m_elf_viewer->hide(); if (m_game_grid_frame->item(0, 0) == nullptr) { m_game_grid_frame->clearContents(); m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } isTableList = false; Config::setTableMode(1); int slider_pos_grid = Config::getSliderPositionGrid(); ui->sizeSlider->setEnabled(true); ui->sizeSlider->setSliderPosition(slider_pos_grid); }); // Elf connect(ui->setlistElfAct, &QAction::triggered, m_dock_widget.data(), [this]() { BackgroundMusicPlayer::getInstance().stopMusic(); m_dock_widget->setWidget(m_elf_viewer.data()); m_game_grid_frame->hide(); m_game_list_frame->hide(); m_elf_viewer->show(); isTableList = false; ui->sizeSlider->setDisabled(true); Config::setTableMode(2); }); // Cheats/Patches Download. connect(ui->downloadCheatsPatchesAct, &QAction::triggered, this, [this]() { QDialog* panelDialog = new QDialog(this); QVBoxLayout* layout = new QVBoxLayout(panelDialog); QPushButton* downloadAllCheatsButton = new QPushButton(tr("Download Cheats For All Installed Games"), panelDialog); QPushButton* downloadAllPatchesButton = new QPushButton(tr("Download Patches For All Games"), panelDialog); layout->addWidget(downloadAllCheatsButton); layout->addWidget(downloadAllPatchesButton); panelDialog->setLayout(layout); connect(downloadAllCheatsButton, &QPushButton::clicked, this, [this, panelDialog]() { QEventLoop eventLoop; int pendingDownloads = 0; auto onDownloadFinished = [&]() { if (--pendingDownloads <= 0) { eventLoop.quit(); } }; for (const GameInfo& game : m_game_info->m_games) { QString empty = ""; QString gameSerial = QString::fromStdString(game.serial); QString gameVersion = QString::fromStdString(game.version); CheatsPatches* cheatsPatches = new CheatsPatches(empty, empty, empty, empty, empty, nullptr); connect(cheatsPatches, &CheatsPatches::downloadFinished, onDownloadFinished); pendingDownloads += 3; cheatsPatches->downloadCheats("wolf2022", gameSerial, gameVersion, false); cheatsPatches->downloadCheats("GoldHEN", gameSerial, gameVersion, false); cheatsPatches->downloadCheats("shadPS4", gameSerial, gameVersion, false); } eventLoop.exec(); QMessageBox::information( nullptr, tr("Download Complete"), tr("You have downloaded cheats for all the games you have installed.")); panelDialog->accept(); }); connect(downloadAllPatchesButton, &QPushButton::clicked, [panelDialog]() { QEventLoop eventLoop; int pendingDownloads = 0; auto onDownloadFinished = [&]() { if (--pendingDownloads <= 0) { eventLoop.quit(); } }; QString empty = ""; CheatsPatches* cheatsPatches = new CheatsPatches(empty, empty, empty, empty, empty, nullptr); connect(cheatsPatches, &CheatsPatches::downloadFinished, onDownloadFinished); pendingDownloads += 2; cheatsPatches->downloadPatches("GoldHEN", false); cheatsPatches->downloadPatches("shadPS4", false); eventLoop.exec(); QMessageBox::information( nullptr, tr("Download Complete"), QString(tr("Patches Downloaded Successfully!") + "\n" + tr("All Patches available for all games have been downloaded."))); cheatsPatches->createFilesJson("GoldHEN"); cheatsPatches->createFilesJson("shadPS4"); panelDialog->accept(); }); panelDialog->exec(); }); // Dump game list. connect(ui->dumpGameListAct, &QAction::triggered, this, [&] { QString filePath = qApp->applicationDirPath().append("/GameList.txt"); QFile file(filePath); QTextStream out(&file); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "Failed to open file for writing:" << file.errorString(); return; } out << QString("%1 %2 %3 %4 %5\n") .arg(" NAME", -50) .arg(" ID", -10) .arg("FW", -4) .arg(" APP VERSION", -11) .arg(" Path"); for (const GameInfo& game : m_game_info->m_games) { QString game_path; Common::FS::PathToQString(game_path, game.path); out << QString("%1 %2 %3 %4 %5\n") .arg(QString::fromStdString(game.name), -50) .arg(QString::fromStdString(game.serial), -10) .arg(QString::fromStdString(game.fw), -4) .arg(QString::fromStdString(game.version), -11) .arg(game_path); } }); // Package install. connect(ui->bootInstallPkgAct, &QAction::triggered, this, &MainWindow::InstallPkg); connect(ui->bootGameAct, &QAction::triggered, this, &MainWindow::BootGame); connect(ui->gameInstallPathAct, &QAction::triggered, this, &MainWindow::InstallDirectory); // elf viewer connect(ui->addElfFolderAct, &QAction::triggered, m_elf_viewer.data(), &ElfViewer::OpenElfFolder); // Package Viewer. connect(ui->pkgViewerAct, &QAction::triggered, this, [this]() { PKGViewer* pkgViewer = new PKGViewer( m_game_info, this, [this](std::filesystem::path file, int pkgNum, int nPkg) { this->InstallDragDropPkg(file, pkgNum, nPkg); }); pkgViewer->show(); }); // Themes connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar); Config::setMainWindowTheme(static_cast(Theme::Dark)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; } }); connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar); Config::setMainWindowTheme(static_cast(Theme::Light)); if (!isIconBlack) { SetUiIcons(true); isIconBlack = true; } }); connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar); Config::setMainWindowTheme(static_cast(Theme::Green)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; } }); connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar); Config::setMainWindowTheme(static_cast(Theme::Blue)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; } }); connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar); Config::setMainWindowTheme(static_cast(Theme::Violet)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; } }); } void MainWindow::StartGame() { isGameRunning = true; BackgroundMusicPlayer::getInstance().stopMusic(); QString gamePath = ""; int table_mode = Config::getTableMode(); if (table_mode == 0) { if (m_game_list_frame->currentItem()) { int itemID = m_game_list_frame->currentItem()->row(); Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin"); } } else if (table_mode == 1) { if (m_game_grid_frame->cellClicked) { int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) + m_game_grid_frame->crtColumn; Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin"); } } else { if (m_elf_viewer->currentItem()) { int itemID = m_elf_viewer->currentItem()->row(); gamePath = m_elf_viewer->m_elf_list[itemID]; } } if (gamePath != "") { AddRecentFiles(gamePath); Core::Emulator emulator; const auto path = Common::FS::PathFromQString(gamePath); if (!std::filesystem::exists(path)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } emulator.Run(path); } } void MainWindow::SearchGameTable(const QString& text) { if (isTableList) { for (int row = 0; row < m_game_list_frame->rowCount(); row++) { QString game_name = QString::fromStdString(m_game_info->m_games[row].name); bool match = (game_name.contains(text, Qt::CaseInsensitive)); // Check only in column 1 m_game_list_frame->setRowHidden(row, !match); } } else { QVector filteredGames; for (const auto& gameInfo : m_game_info->m_games) { QString game_name = QString::fromStdString(gameInfo.name); if (game_name.contains(text, Qt::CaseInsensitive)) { filteredGames.push_back(gameInfo); } } std::sort(filteredGames.begin(), filteredGames.end(), m_game_info->CompareStrings); m_game_grid_frame->PopulateGameGrid(filteredGames, true); } } void MainWindow::ShowGameList() { if (ui->showGameListAct->isChecked()) { RefreshGameTable(); } else { m_game_grid_frame->clearContents(); m_game_list_frame->clearContents(); } }; void MainWindow::RefreshGameTable() { // m_game_info->m_games.clear(); m_game_info->GetGameInfo(this); m_game_list_frame->clearContents(); m_game_list_frame->PopulateGameList(); m_game_grid_frame->clearContents(); m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); statusBar->clearMessage(); int numGames = m_game_info->m_games.size(); QString statusMessage = tr("Games: ") + QString::number(numGames); statusBar->showMessage(statusMessage); } void MainWindow::ConfigureGuiFromSettings() { setGeometry(Config::getMainWindowGeometryX(), Config::getMainWindowGeometryY(), Config::getMainWindowGeometryW(), Config::getMainWindowGeometryH()); ui->showGameListAct->setChecked(true); if (isTableList) { ui->setlistModeListAct->setChecked(true); } else { ui->setlistModeGridAct->setChecked(true); } BackgroundMusicPlayer::getInstance().setVolume(Config::getBGMvolume()); } void MainWindow::SaveWindowState() const { Config::setMainWindowWidth(this->width()); Config::setMainWindowHeight(this->height()); Config::setMainWindowGeometry(this->geometry().x(), this->geometry().y(), this->geometry().width(), this->geometry().height()); } void MainWindow::InstallPkg() { QFileDialog dialog; dialog.setFileMode(QFileDialog::ExistingFiles); dialog.setNameFilter(tr("PKG File (*.PKG *.pkg)")); if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); int nPkg = fileNames.size(); int pkgNum = 0; for (const QString& file : fileNames) { ++pkgNum; std::filesystem::path path = Common::FS::PathFromQString(file); MainWindow::InstallDragDropPkg(path, pkgNum, nPkg); } } } void MainWindow::BootGame() { QFileDialog dialog; dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilter(tr("ELF files (*.bin *.elf *.oelf)")); if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); int nFiles = fileNames.size(); if (nFiles > 1) { QMessageBox::critical(nullptr, tr("Game Boot"), QString(tr("Only one file can be selected!"))); } else { std::filesystem::path path = Common::FS::PathFromQString(fileNames[0]); Core::Emulator emulator; if (!std::filesystem::exists(path)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } emulator.Run(path); } } } void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg) { if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) { std::string failreason; pkg = PKG(); if (!pkg.Open(file, failreason)) { QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); return; } auto extract_path = Config::getGameInstallDir() / pkg.GetTitleID(); QString pkgType = QString::fromStdString(pkg.GetPkgFlags()); QString gameDirPath; Common::FS::PathToQString(gameDirPath, extract_path); QDir game_dir(gameDirPath); if (game_dir.exists()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("PKG Extraction")); if (!psf.Open(pkg.sfo)) { QMessageBox::critical(this, tr("PKG ERROR"), "Could not read SFO. Check log for details"); return; } std::string content_id; if (auto value = psf.GetString("CONTENT_ID"); value.has_value()) { content_id = std::string{*value}; } else { QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no CONTENT_ID"); return; } std::string entitlement_label = Common::SplitString(content_id, '-')[2]; auto addon_extract_path = Config::getAddonInstallDir() / pkg.GetTitleID() / entitlement_label; QString addonDirPath; Common::FS::PathToQString(addonDirPath, addon_extract_path); QDir addon_dir(addonDirPath); auto category = psf.GetString("CATEGORY"); if (pkgType.contains("PATCH")) { QString pkg_app_version; if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { pkg_app_version = QString::fromStdString(std::string{*app_ver}); } else { QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); return; } psf.Open(extract_path / "sce_sys" / "param.sfo"); 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}); } else { QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); return; } double appD = game_app_version.toDouble(); double pkgD = pkg_app_version.toDouble(); if (pkgD == appD) { msgBox.setText(QString(tr("Patch detected!") + "\n" + tr("PKG and Game versions match: ") + pkg_app_version + "\n" + tr("Would you like to overwrite?"))); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); } else if (pkgD < appD) { msgBox.setText(QString(tr("Patch detected!") + "\n" + tr("PKG Version %1 is older than installed version: ") .arg(pkg_app_version) + game_app_version + "\n" + tr("Would you like to overwrite?"))); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); } else { msgBox.setText(QString(tr("Patch detected!") + "\n" + tr("Game is installed: ") + game_app_version + "\n" + tr("Would you like to install Patch: ") + pkg_app_version + " ?")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); } int result = msgBox.exec(); if (result == QMessageBox::Yes) { // Do nothing. } else { return; } } else if (category == "ac") { if (!addon_dir.exists()) { QMessageBox addonMsgBox; addonMsgBox.setWindowTitle(tr("DLC Installation")); addonMsgBox.setText(QString(tr("Would you like to install DLC: %1?")) .arg(QString::fromStdString(entitlement_label))); addonMsgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); addonMsgBox.setDefaultButton(QMessageBox::No); int result = addonMsgBox.exec(); if (result == QMessageBox::Yes) { extract_path = addon_extract_path; } else { return; } } else { msgBox.setText(QString(tr("DLC already installed:") + "\n" + addonDirPath + "\n\n" + tr("Would you like to overwrite?"))); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); int result = msgBox.exec(); if (result == QMessageBox::Yes) { extract_path = addon_extract_path; } else { return; } } } else { msgBox.setText(QString(tr("Game already installed") + "\n" + addonDirPath + "\n" + tr("Would you like to overwrite?"))); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); int result = msgBox.exec(); if (result == QMessageBox::Yes) { // Do nothing. } else { return; } } } else { // Do nothing; if (pkgType.contains("PATCH")) { QMessageBox::information(this, tr("PKG Extraction"), tr("PKG is a patch, please install the game first!")); return; } // what else? } if (!pkg.Extract(file, extract_path, failreason)) { QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); } else { int nfiles = pkg.GetNumberOfFiles(); if (nfiles > 0) { QVector indices; for (int i = 0; i < nfiles; i++) { indices.append(i); } QProgressDialog dialog; dialog.setWindowTitle(tr("PKG Extraction")); dialog.setWindowModality(Qt::WindowModal); QString extractmsg = QString(tr("Extracting PKG %1/%2")).arg(pkgNum).arg(nPkg); dialog.setLabelText(extractmsg); dialog.setAutoClose(true); dialog.setRange(0, nfiles); QFutureWatcher futureWatcher; connect(&futureWatcher, &QFutureWatcher::finished, this, [=, this]() { if (pkgNum == nPkg) { QString path; Common::FS::PathToQString(path, Config::getGameInstallDir()); QMessageBox extractMsgBox(this); extractMsgBox.setWindowTitle(tr("Extraction Finished")); extractMsgBox.setText( QString(tr("Game successfully installed at %1")).arg(path)); extractMsgBox.addButton(QMessageBox::Ok); extractMsgBox.setDefaultButton(QMessageBox::Ok); connect(&extractMsgBox, &QMessageBox::buttonClicked, this, [&](QAbstractButton* button) { if (extractMsgBox.button(QMessageBox::Ok) == button) { extractMsgBox.close(); emit ExtractionFinished(); } }); extractMsgBox.exec(); } }); connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); }); connect(&futureWatcher, &QFutureWatcher::progressValueChanged, &dialog, &QProgressDialog::setValue); futureWatcher.setFuture( QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); })); dialog.exec(); } } } else { QMessageBox::critical(this, tr("PKG ERROR"), tr("File doesn't appear to be a valid PKG file")); } } void MainWindow::InstallDirectory() { GameInstallDialog dlg; dlg.exec(); RefreshGameTable(); } void MainWindow::SetLastUsedTheme() { Theme lastTheme = static_cast(Config::getMainWindowTheme()); m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar); switch (lastTheme) { case Theme::Light: ui->setThemeLight->setChecked(true); isIconBlack = true; break; case Theme::Dark: ui->setThemeDark->setChecked(true); isIconBlack = false; SetUiIcons(false); break; case Theme::Green: ui->setThemeGreen->setChecked(true); isIconBlack = false; SetUiIcons(false); break; case Theme::Blue: ui->setThemeBlue->setChecked(true); isIconBlack = false; SetUiIcons(false); break; case Theme::Violet: ui->setThemeViolet->setChecked(true); isIconBlack = false; SetUiIcons(false); break; } } void MainWindow::SetLastIconSizeBullet() { // set QAction bullet point if applicable int lastSize = Config::getIconSize(); switch (lastSize) { case 36: ui->setIconSizeTinyAct->setChecked(true); break; case 64: ui->setIconSizeSmallAct->setChecked(true); break; case 128: ui->setIconSizeMediumAct->setChecked(true); break; case 256: ui->setIconSizeLargeAct->setChecked(true); break; } } QIcon MainWindow::RecolorIcon(const QIcon& icon, bool isWhite) { QPixmap pixmap(icon.pixmap(icon.actualSize(QSize(120, 120)))); QColor clr(isWhite ? Qt::white : Qt::black); QBitmap mask = pixmap.createMaskFromColor(clr, Qt::MaskOutColor); pixmap.fill(QColor(isWhite ? Qt::black : Qt::white)); pixmap.setMask(mask); return QIcon(pixmap); } void MainWindow::SetUiIcons(bool isWhite) { ui->bootInstallPkgAct->setIcon(RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite)); ui->bootGameAct->setIcon(RecolorIcon(ui->bootGameAct->icon(), isWhite)); ui->exitAct->setIcon(RecolorIcon(ui->exitAct->icon(), isWhite)); ui->updaterAct->setIcon(RecolorIcon(ui->updaterAct->icon(), isWhite)); ui->downloadCheatsPatchesAct->setIcon( RecolorIcon(ui->downloadCheatsPatchesAct->icon(), isWhite)); ui->dumpGameListAct->setIcon(RecolorIcon(ui->dumpGameListAct->icon(), isWhite)); ui->aboutAct->setIcon(RecolorIcon(ui->aboutAct->icon(), isWhite)); ui->setlistModeListAct->setIcon(RecolorIcon(ui->setlistModeListAct->icon(), isWhite)); ui->setlistModeGridAct->setIcon(RecolorIcon(ui->setlistModeGridAct->icon(), isWhite)); ui->gameInstallPathAct->setIcon(RecolorIcon(ui->gameInstallPathAct->icon(), isWhite)); ui->menuThemes->setIcon(RecolorIcon(ui->menuThemes->icon(), isWhite)); ui->menuGame_List_Icons->setIcon(RecolorIcon(ui->menuGame_List_Icons->icon(), isWhite)); ui->playButton->setIcon(RecolorIcon(ui->playButton->icon(), isWhite)); ui->pauseButton->setIcon(RecolorIcon(ui->pauseButton->icon(), isWhite)); ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite)); ui->refreshButton->setIcon(RecolorIcon(ui->refreshButton->icon(), isWhite)); ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite)); ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite)); ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite)); ui->menuGame_List_Mode->setIcon(RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite)); ui->pkgViewerAct->setIcon(RecolorIcon(ui->pkgViewerAct->icon(), isWhite)); ui->configureAct->setIcon(RecolorIcon(ui->configureAct->icon(), isWhite)); ui->addElfFolderAct->setIcon(RecolorIcon(ui->addElfFolderAct->icon(), isWhite)); } void MainWindow::resizeEvent(QResizeEvent* event) { emit WindowResized(event); QMainWindow::resizeEvent(event); } void MainWindow::HandleResize(QResizeEvent* event) { if (isTableList) { m_game_list_frame->RefreshListBackgroundImage(); } else { m_game_grid_frame->windowWidth = this->width(); m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); m_game_grid_frame->RefreshGridBackgroundImage(); } } void MainWindow::AddRecentFiles(QString filePath) { std::vector vec = Config::getRecentFiles(); if (!vec.empty()) { if (filePath.toStdString() == vec.at(0)) { return; } auto it = std::find(vec.begin(), vec.end(), filePath.toStdString()); if (it != vec.end()) { vec.erase(it); } } vec.insert(vec.begin(), filePath.toStdString()); if (vec.size() > 6) { vec.pop_back(); } Config::setRecentFiles(vec); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::save(config_dir / "config.toml"); CreateRecentGameActions(); // Refresh the QActions. } void MainWindow::CreateRecentGameActions() { m_recent_files_group = new QActionGroup(this); ui->menuRecent->clear(); std::vector vec = Config::getRecentFiles(); for (int i = 0; i < vec.size(); i++) { QAction* recentFileAct = new QAction(this); recentFileAct->setText(QString::fromStdString(vec.at(i))); ui->menuRecent->addAction(recentFileAct); m_recent_files_group->addAction(recentFileAct); } connect(m_recent_files_group, &QActionGroup::triggered, this, [this](QAction* action) { auto gamePath = Common::FS::PathFromQString(action->text()); AddRecentFiles(action->text()); // Update the list. Core::Emulator emulator; if (!std::filesystem::exists(gamePath)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } emulator.Run(gamePath); }); } void MainWindow::LoadTranslation() { auto language = QString::fromStdString(Config::getEmulatorLanguage()); const QString base_dir = QStringLiteral(":/translations"); QString base_path = QStringLiteral("%1/%2.qm").arg(base_dir).arg(language); if (QFile::exists(base_path)) { if (translator != nullptr) { qApp->removeTranslator(translator); } translator = new QTranslator(qApp); if (!translator->load(base_path)) { QMessageBox::warning( nullptr, QStringLiteral("Translation Error"), QStringLiteral("Failed to find load translation file for '%1':\n%2") .arg(language) .arg(base_path)); delete translator; } else { qApp->installTranslator(translator); ui->retranslateUi(this); } } } void MainWindow::OnLanguageChanged(const std::string& locale) { Config::setEmulatorLanguage(locale); LoadTranslation(); } bool MainWindow::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { auto tblMode = Config::getTableMode(); if (tblMode != 2 && (tblMode != 1 || m_game_grid_frame->IsValidCellSelected())) { StartGame(); return true; } } } return QMainWindow::eventFilter(obj, event); }