From 10485af9329af000a4f08c7f36a478e66ace65d4 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Tue, 20 Aug 2024 11:18:00 -0300 Subject: [PATCH] Cheats/Patches Adds the possibility of applying cheats/patches according to the specific game serial+version The logic for adding modifications has not yet been implemented! Interface based on issues/372 https://github.com/shadps4-emu/shadPS4/issues/372 [X]Front-end []Back-end Create a synchronized fork of the cheats/patches repository --- CMakeLists.txt | 4 +- src/common/path_util.cpp | 1 + src/common/path_util.h | 2 + src/qt_gui/gui_context_menus.h | 264 +++++++++++++++++++++++++++++++++ 4 files changed, 269 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 679325ec8..5f4146bb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ add_subdirectory(externals) include_directories(src) if(ENABLE_QT_GUI) - find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent) + find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent Network) qt_standard_project_setup() set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) @@ -639,7 +639,7 @@ else() endif() if (ENABLE_QT_GUI) - target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent) + target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network) endif() if (WIN32) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index c1e8a5c0a..86d1279c3 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -74,6 +74,7 @@ static auto UserPaths = [] { create_path(PathType::SysModuleDir, user_dir / SYSMODULES_DIR); create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR); create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR); + create_path(PathType::CheatsDir, user_dir / CHEATS_DIR); return paths; }(); diff --git a/src/common/path_util.h b/src/common/path_util.h index 263edd46e..a9a99f065 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -20,6 +20,7 @@ enum class PathType { SysModuleDir, // Where system modules are stored. DownloadDir, // Where downloads/temp files are stored. CapturesDir, // Where rdoc captures are stored. + CheatsDir, // Where cheats and patches are stored. }; constexpr auto PORTABLE_DIR = "user"; @@ -35,6 +36,7 @@ constexpr auto TEMPDATA_DIR = "temp"; constexpr auto SYSMODULES_DIR = "sys_modules"; constexpr auto DOWNLOAD_DIR = "download"; constexpr auto CAPTURES_DIR = "captures"; +constexpr auto CHEATS_DIR = "cheats"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 2f4b884cf..16f72192f 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -3,20 +3,26 @@ #pragma once +#include #include #include #include #include +#include #include #include #include #include #include +#include +#include #include #include #include #include #include +#include +#include #include "game_info.h" #include "trophy_viewer.h" @@ -28,6 +34,7 @@ #include #include #endif +#include "common/path_util.h" class GuiContextMenus : public QObject { Q_OBJECT @@ -46,11 +53,13 @@ public: QMenu menu(widget); QAction createShortcut("Create Shortcut", widget); QAction openFolder("Open Game Folder", widget); + QAction openCheats("Cheats/Patches", widget); QAction openSfoViewer("SFO Viewer", widget); QAction openTrophyViewer("Trophy Viewer", widget); menu.addAction(&openFolder); menu.addAction(&createShortcut); + menu.addAction(&openCheats); menu.addAction(&openSfoViewer); menu.addAction(&openTrophyViewer); @@ -129,6 +138,261 @@ public: } } +if (selected == &openCheats) { + const auto& CHEATS_DIR = Common::FS::GetUserPath(Common::FS::PathType::CheatsDir); + QString CHEATS_DIR_QString = QString::fromStdString(CHEATS_DIR.string()); + const QString NameCheatJson = QString::fromStdString(m_games[itemID].serial) + "_" + + QString::fromStdString(m_games[itemID].version) + ".json"; + const QString cheatFilePath = CHEATS_DIR_QString + "/" + NameCheatJson; + + QWidget* cheatWidget = new QWidget(); + cheatWidget->setAttribute(Qt::WA_DeleteOnClose); + cheatWidget->setWindowTitle("Cheats/Patches"); + cheatWidget->resize(700, 300); + + QVBoxLayout* mainLayout = new QVBoxLayout(cheatWidget); + QHBoxLayout* horizontalLayout = new QHBoxLayout(); + + // GroupBox for game information (Left side) + QGroupBox* gameInfoGroupBox = new QGroupBox(); + QVBoxLayout* leftLayout = new QVBoxLayout(gameInfoGroupBox); + leftLayout->setAlignment(Qt::AlignTop); + + // Game image + QLabel* gameImage = new QLabel(); + QPixmap pixmap(QString::fromStdString(m_games[itemID].icon_path)); + gameImage->setPixmap(pixmap.scaled(250, 250, Qt::KeepAspectRatio)); + gameImage->setAlignment(Qt::AlignCenter); + leftLayout->addWidget(gameImage, 0, Qt::AlignCenter); + + // Game name + QLabel* gameName = new QLabel(QString::fromStdString(m_games[itemID].name)); + gameName->setAlignment(Qt::AlignLeft); + gameName->setWordWrap(true); + leftLayout->addWidget(gameName); + + // Game serial + QLabel* gameSerial = + new QLabel("Serial: " + QString::fromStdString(m_games[itemID].serial)); + gameSerial->setAlignment(Qt::AlignLeft); + leftLayout->addWidget(gameSerial); + + // Game version + QLabel* gameVersion = + new QLabel("Version: " + QString::fromStdString(m_games[itemID].version)); + gameVersion->setAlignment(Qt::AlignLeft); + leftLayout->addWidget(gameVersion); + + // Game size + QLabel* gameSize = new QLabel("Size: " + QString::fromStdString(m_games[itemID].size)); + gameSize->setAlignment(Qt::AlignLeft); + leftLayout->addWidget(gameSize); + + // Check for credits and add QLabel if exists + QFile file(cheatFilePath); + if (file.open(QIODevice::ReadOnly)) { + QByteArray jsonData = file.readAll(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + QJsonObject jsonObject = jsonDoc.object(); + + if (jsonObject.contains("credits")) { + QJsonArray creditsArray = jsonObject["credits"].toArray(); + if (!creditsArray.isEmpty()) { + QStringList creditsList; + for (const QJsonValue& value : creditsArray) { + creditsList.append(value.toString()); + } + QString credits = creditsList.join(", "); + QLabel* creditsLabel = new QLabel("Author: " + credits); + creditsLabel->setAlignment(Qt::AlignLeft); + leftLayout->addWidget(creditsLabel); + } + } + } + + gameInfoGroupBox->setLayout(leftLayout); + horizontalLayout->addWidget(gameInfoGroupBox); + QScrollArea* scrollArea = new QScrollArea(); + scrollArea->setWidgetResizable(true); + + // Right + QGroupBox* cheatsGroupBox = new QGroupBox(); + QVBoxLayout* rightLayout = new QVBoxLayout(cheatsGroupBox); + QString checkBoxStyle = "QCheckBox { font-size: 19px; }"; + QString buttonStyle = "QPushButton { font-size: 19px; }"; + rightLayout->setAlignment(Qt::AlignTop); + + // Function to add checkboxes and buttons to the layout + auto addMods = [=](const QJsonArray& modsArray) { + // Limpar widgets existentes + QLayoutItem* item; + while ((item = rightLayout->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } + + for (const QJsonValue& modValue : modsArray) { + QJsonObject modObject = modValue.toObject(); + QString modName = modObject["name"].toString(); + QString modType = modObject["type"].toString(); + + if (modType == "checkbox") { + bool isEnabled = modObject.contains("is_enabled") + ? modObject["is_enabled"].toBool() + : false; + QCheckBox* cheatCheckBox = new QCheckBox(modName); + cheatCheckBox->setStyleSheet(checkBoxStyle); + cheatCheckBox->setChecked(isEnabled); + rightLayout->addWidget(cheatCheckBox); + + // Connect the toggled(bool) signal to handle state change + connect(cheatCheckBox, &QCheckBox::toggled, [=](bool checked) { + if (checked) { + // Implement action when checkbox is checked + } else { + // Implement action when checkbox is unchecked + } + }); + } else if (modType == "button") { + QPushButton* cheatButton = new QPushButton(modName); + cheatButton->setStyleSheet(buttonStyle); + connect(cheatButton, &QPushButton::clicked, [=]() { + // Implementar a ação do botão !!! + }); + rightLayout->addWidget(cheatButton); + } + } + }; + + QNetworkAccessManager* manager = new QNetworkAccessManager(cheatWidget); + + auto loadCheats = [=](const QString& filePath) { + QFile file(filePath); + if (file.open(QIODevice::ReadOnly)) { + QByteArray jsonData = file.readAll(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + QJsonObject jsonObject = jsonDoc.object(); + QJsonArray modsArray = jsonObject["mods"].toArray(); + addMods(modsArray); + } + }; + + loadCheats(cheatFilePath); + cheatsGroupBox->setLayout(rightLayout); + scrollArea->setWidget(cheatsGroupBox); + horizontalLayout->addWidget(scrollArea); + mainLayout->addLayout(horizontalLayout); + + QHBoxLayout* buttonLayout = new QHBoxLayout(); + QPushButton* checkUpdateButton = new QPushButton("Check Update"); + QPushButton* cancelButton = new QPushButton("Cancel"); + QPushButton* saveButton = new QPushButton("Save"); + + connect(checkUpdateButton, &QPushButton::clicked, [=]() { + if (QFile::exists(cheatFilePath)) { + QMessageBox::StandardButton reply; + reply = QMessageBox::question(cheatWidget, "File Exists", + "File already exists. Do you want to replace it?", + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::No) { + return; + } + } + + // Cheats repository URL, replace with a synced fork of the repository + const QString url = "https://raw.githubusercontent.com/GoldHEN/" + "GoldHEN_Cheat_Repository/main/json/" + + NameCheatJson; + + QNetworkRequest request(url); + QNetworkReply* reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, [=]() { + if (reply->error() == QNetworkReply::NoError) { + QByteArray jsonData = reply->readAll(); + + // Save the JSON file in the cheats folder + QFile cheatFile(cheatFilePath); + if (cheatFile.open(QIODevice::WriteOnly)) { + cheatFile.write(jsonData); + cheatFile.close(); + } + + // Reload and add new widgets + loadCheats(cheatFilePath); + } else { + QMessageBox::warning( + cheatWidget, "Cheats/Patches not found", + "No Cheats/Patches found for this game in this version."); + } + + reply->deleteLater(); + }); + }); + + connect(cancelButton, &QPushButton::clicked, [=]() { cheatWidget->close(); }); + + connect(saveButton, &QPushButton::clicked, [=]() { + QJsonDocument jsonDoc; + QFile file(cheatFilePath); + if (file.open(QIODevice::ReadOnly)) { + jsonDoc = QJsonDocument::fromJson(file.readAll()); + file.close(); + } + + QJsonObject json = jsonDoc.object(); + QJsonArray modsArray = json["mods"].toArray(); + + QMap modMap; + for (int i = 0; i < rightLayout->count(); ++i) { + QWidget* widget = rightLayout->itemAt(i)->widget(); + if (QCheckBox* checkBox = qobject_cast(widget)) { + modMap[checkBox->text()] = checkBox->isChecked(); + } + // Buttons don't need state saving, so we ignore them. + } + + for (auto it = modMap.begin(); it != modMap.end(); ++it) { + bool found = false; + for (int i = 0; i < modsArray.size(); ++i) { + QJsonObject mod = modsArray[i].toObject(); + if (mod["name"].toString() == it.key()) { + mod["is_enabled"] = it.value(); + modsArray[i] = mod; + found = true; + break; + } + } + if (!found) { + QJsonObject newMod; + newMod["name"] = it.key(); + newMod["is_enabled"] = it.value(); + modsArray.append(newMod); + } + } + + json["mods"] = modsArray; + jsonDoc.setObject(json); + + if (file.open(QIODevice::WriteOnly)) { + file.write(jsonDoc.toJson()); + file.close(); + QMessageBox::information(cheatWidget, "Save", "Settings saved."); + cheatWidget->close(); + } else { + QMessageBox::warning(cheatWidget, "Error", "Could not open file for writing."); + } + }); + + buttonLayout->addWidget(checkUpdateButton); + buttonLayout->addWidget(cancelButton); + buttonLayout->addWidget(saveButton); + mainLayout->addLayout(buttonLayout); + cheatWidget->setLayout(mainLayout); + cheatWidget->show(); + } + + if (selected == &openTrophyViewer) { QString trophyPath = QString::fromStdString(m_games[itemID].serial); QString gameTrpPath = QString::fromStdString(m_games[itemID].path);