From 957d3e38df03b58b6fe5f3df1e9b331c422b653e Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Thu, 22 Aug 2024 14:27:38 -0300 Subject: [PATCH] Patchs menu and fixes adds the possibility to download Patches, it does not modify the memory yet. and some other fixes --- src/common/path_util.cpp | 1 + src/common/path_util.h | 4 +- src/qt_gui/cheats_patches.cpp | 514 +++++++++++++++++++++++++++++----- src/qt_gui/cheats_patches.h | 50 +++- 4 files changed, 497 insertions(+), 72 deletions(-) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 86d1279c..b4b591c7 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -75,6 +75,7 @@ static auto UserPaths = [] { create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR); create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR); create_path(PathType::CheatsDir, user_dir / CHEATS_DIR); + create_path(PathType::PatchesDir, user_dir / PATCHES_DIR); return paths; }(); diff --git a/src/common/path_util.h b/src/common/path_util.h index a9a99f06..8922de9f 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -20,7 +20,8 @@ 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. + CheatsDir, // Where cheats are stored. + PatchesDir, // Where patches are stored. }; constexpr auto PORTABLE_DIR = "user"; @@ -37,6 +38,7 @@ constexpr auto SYSMODULES_DIR = "sys_modules"; constexpr auto DOWNLOAD_DIR = "download"; constexpr auto CAPTURES_DIR = "captures"; constexpr auto CHEATS_DIR = "cheats"; +constexpr auto PATCHES_DIR = "patches"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index 73c3da08..bc1b0c84 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -1,10 +1,13 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include +#include +#include #include #include #include +#include #include #include #include @@ -12,16 +15,20 @@ #include #include #include +#include #include #include #include #include #include +#include #include +#include #include #include "cheats_patches.h" #include "common/path_util.h" #include "core/module.h" +using namespace Common::FS; CheatsPatches::CheatsPatches(const QString& gameName, const QString& gameSerial, const QString& gameVersion, const QString& gameSize, @@ -29,28 +36,35 @@ CheatsPatches::CheatsPatches(const QString& gameName, const QString& gameSerial, : QWidget(parent), m_gameName(gameName), m_gameSerial(gameSerial), m_gameVersion(gameVersion), m_gameSize(gameSize), m_gameImage(gameImage) { setupUI(); - resize(850, 400); + resize(700, 400); setWindowTitle("Cheats / Patches"); } CheatsPatches::~CheatsPatches() {} +QString defaultTextEdit = + ("Cheat/Patches are experimental. Use with caution.\n" + "Select the repository from which you want to download cheats for this game and version and " + "click the button. Since we do not develop Cheat/Patches, if you encounter issues or want to " + "suggest new ones, please send your feedback to:\n" + "link not available yet :D"); + void CheatsPatches::setupUI() { - const auto& CHEATS_DIR = Common::FS::GetUserPath(Common::FS::PathType::CheatsDir); - QString CHEATS_DIR_QString = QString::fromStdString(CHEATS_DIR.string()); + QString CHEATS_DIR_QString = + QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::CheatsDir).string()); QString NameCheatJson = m_gameSerial + "_" + m_gameVersion + ".json"; m_cheatFilePath = CHEATS_DIR_QString + "/" + NameCheatJson; QHBoxLayout* mainLayout = new QHBoxLayout(this); // Create the game info group box - QGroupBox* gameInfoGroupBox = new QGroupBox("Game Information"); + QGroupBox* gameInfoGroupBox = new QGroupBox(); QVBoxLayout* gameInfoLayout = new QVBoxLayout(gameInfoGroupBox); gameInfoLayout->setAlignment(Qt::AlignTop); QLabel* gameImageLabel = new QLabel(); if (!m_gameImage.isNull()) { - gameImageLabel->setPixmap(m_gameImage.scaled(250, 250, Qt::KeepAspectRatio)); + gameImageLabel->setPixmap(m_gameImage.scaled(300, 300, Qt::KeepAspectRatio)); } else { gameImageLabel->setText("No Image Available"); } @@ -74,6 +88,13 @@ void CheatsPatches::setupUI() { gameSizeLabel->setAlignment(Qt::AlignLeft); gameInfoLayout->addWidget(gameSizeLabel); + // Add a text area for instructions + instructionsTextEdit = new QTextEdit(); + instructionsTextEdit->setText(defaultTextEdit); + instructionsTextEdit->setReadOnly(true); + instructionsTextEdit->setFixedHeight(130); + gameInfoLayout->addWidget(instructionsTextEdit); + // Create the tab widget QTabWidget* tabWidget = new QTabWidget(); QWidget* cheatsTab = new QWidget(); @@ -86,8 +107,6 @@ void CheatsPatches::setupUI() { // Setup the cheats tab QGroupBox* cheatsGroupBox = new QGroupBox(); rightLayout = new QVBoxLayout(cheatsGroupBox); - checkBoxStyle = "QCheckBox { font-size: 17px; }"; - buttonStyle = "QPushButton { font-size: 17px; }"; rightLayout->setAlignment(Qt::AlignTop); loadCheats(m_cheatFilePath); @@ -97,74 +116,259 @@ void CheatsPatches::setupUI() { scrollArea->setWidget(cheatsGroupBox); cheatsLayout->addWidget(scrollArea); - // Add a check for updates button - QHBoxLayout* buttonLayout = new QHBoxLayout(); - QPushButton* checkUpdateButton = new QPushButton("Download Cheats"); - checkUpdateButton->setStyleSheet(buttonStyle); - connect(checkUpdateButton, &QPushButton::clicked, [=]() { - if (QFile::exists(m_cheatFilePath)) { - QMessageBox::StandardButton reply; - reply = QMessageBox::question(this, "File Exists", - "File already exists. Do you want to replace it?", - QMessageBox::Yes | QMessageBox::No); - if (reply == QMessageBox::No) { - return; - } - } + QLabel* repositoryLabel = new QLabel("Repository:"); + repositoryLabel->setAlignment(Qt::AlignLeft); + repositoryLabel->setAlignment(Qt::AlignVCenter); - const QString url = - "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json/" + - m_gameSerial + "_" + m_gameVersion + ".json"; + // Add a combo box and a download button + QHBoxLayout* controlLayout = new QHBoxLayout(); + controlLayout->addWidget(repositoryLabel); + controlLayout->setAlignment(Qt::AlignLeft); + QComboBox* downloadComboBox = new QComboBox(); - QNetworkAccessManager* manager = new QNetworkAccessManager(this); - QNetworkRequest request(url); - QNetworkReply* reply = manager->get(request); + auto urlCheat_wolf2022 = "https://wolf2022.ir/trainer/" + NameCheatJson; + auto urlCheat_GoldHEN = + "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json/" + + NameCheatJson; + auto url_shadPs4 = + "https://raw.githubusercontent.com/DanielSvoboda/GoldHEN_Cheat_Repository/patch-1/json/" + + NameCheatJson; - connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error() == QNetworkReply::NoError) { - QByteArray jsonData = reply->readAll(); - QFile cheatFile(m_cheatFilePath); - if (cheatFile.open(QIODevice::WriteOnly)) { - cheatFile.write(jsonData); - cheatFile.close(); - loadCheats(m_cheatFilePath); - } - } else { - QMessageBox::warning(this, "Cheats not found", - "No Cheats found for this game in this version."); - } - reply->deleteLater(); - }); + downloadComboBox->addItem("wolf2022", urlCheat_wolf2022); + downloadComboBox->addItem("GoldHEN", urlCheat_GoldHEN); + downloadComboBox->addItem("shadPs4 test", url_shadPs4); + + controlLayout->addWidget(downloadComboBox); + + QPushButton* downloadButton = new QPushButton("Download Cheats"); + connect(downloadButton, &QPushButton::clicked, [=]() { + QString url = downloadComboBox->currentData().toString(); + downloadCheats(url); }); - buttonLayout->addWidget(checkUpdateButton); - cheatsLayout->addLayout(buttonLayout); + controlLayout->addWidget(downloadButton); + cheatsLayout->addLayout(controlLayout); cheatsTab->setLayout(cheatsLayout); + + // Setup the patches tab + QGroupBox* patchesGroupBox = new QGroupBox(); + patchesGroupBoxLayout = new QVBoxLayout(patchesGroupBox); + patchesGroupBoxLayout->setAlignment(Qt::AlignTop); + + QScrollArea* patchesScrollArea = new QScrollArea(); + patchesScrollArea->setWidgetResizable(true); + patchesScrollArea->setWidget(patchesGroupBox); + patchesLayout->addWidget(patchesScrollArea); + + QHBoxLayout* patchesControlLayout = new QHBoxLayout(); + QComboBox* patchesComboBox = new QComboBox(); + + QPushButton* patchesButton = new QPushButton("Download all available Patches"); + connect(patchesButton, &QPushButton::clicked, [=]() { + QString urlPatchGoldHEN = + "https://github.com/GoldHEN/GoldHEN_Patch_Repository/tree/main/patches/xml"; + downloadPatches(urlPatchGoldHEN); + }); + + patchesControlLayout->addWidget(patchesButton); + patchesLayout->addLayout(patchesControlLayout); patchesTab->setLayout(patchesLayout); - tabWidget->addTab(cheatsTab, "Cheats"); - tabWidget->addTab(patchesTab, "Patches"); + + tabWidget->addTab(cheatsTab, "CHEATS"); + tabWidget->addTab(patchesTab, "PATCHES"); + + // Connect the currentChanged signal to the onTabChanged slot + connect(tabWidget, &QTabWidget::currentChanged, this, &CheatsPatches::onTabChanged); + mainLayout->addWidget(gameInfoGroupBox, 1); mainLayout->addWidget(tabWidget, 3); setLayout(mainLayout); } -void CheatsPatches::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); +void CheatsPatches::onTabChanged(int index) { + if (index == 1) { + loadPatches(m_gameSerial); } } -void CheatsPatches::addMods(const QJsonArray& modsArray) { +void CheatsPatches::downloadCheats(const QString& url) { + if (QFile::exists(m_cheatFilePath)) { + QMessageBox::StandardButton reply; + reply = QMessageBox::question(this, "File Exists", + "File already exists. Do you want to replace it?", + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::No) { + return; + } + } + + QNetworkAccessManager* manager = new QNetworkAccessManager(this); + QNetworkRequest request(url); + QNetworkReply* reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, [=]() { + if (reply->error() == QNetworkReply::NoError) { + QByteArray jsonData = reply->readAll(); + QFile cheatFile(m_cheatFilePath); + if (cheatFile.open(QIODevice::WriteOnly)) { + cheatFile.write(jsonData); + cheatFile.close(); + loadCheats(m_cheatFilePath); + } + QMessageBox::information(this, "Cheats downloaded successfully", + "You have successfully downloaded the cheats for this version " + "of the game. (serial+version)"); + } else { + QMessageBox::warning(this, "Cheats not found", + "No Cheats found for this game in this version. (serial+version)"); + } + reply->deleteLater(); + }); +} + +void CheatsPatches::downloadPatches(const QString& url) { + QNetworkAccessManager* manager = new QNetworkAccessManager(this); + + QNetworkRequest request(url); + QNetworkReply* reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, [=]() { + if (reply->error() == QNetworkReply::NoError) { + QByteArray htmlData = reply->readAll(); + reply->deleteLater(); + + // Parsear HTML e extrair JSON usando QRegularExpression + QString htmlString = QString::fromUtf8(htmlData); + QRegularExpression jsonRegex( + R"()"); + QRegularExpressionMatch match = jsonRegex.match(htmlString); + + if (match.hasMatch()) { + QByteArray jsonData = match.captured(1).toUtf8(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + QJsonObject jsonObj = jsonDoc.object(); + QJsonArray itemsArray = + jsonObj["payload"].toObject()["tree"].toObject()["items"].toArray(); + + QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir)); + + if (!dir.exists()) { + std::filesystem::create_directory( + Common::FS::GetUserPath(Common::FS::PathType::PatchesDir)); + } + foreach (const QJsonValue& value, itemsArray) { + QJsonObject fileObj = value.toObject(); + QString fileName = fileObj["name"].toString(); + QString filePath = fileObj["path"].toString(); + + if (fileName.endsWith(".xml")) { + QString fileUrl = QString("https://raw.githubusercontent.com/GoldHEN/" + "GoldHEN_Patch_Repository/main/%1") + .arg(filePath); + QNetworkRequest fileRequest(fileUrl); + QNetworkReply* fileReply = manager->get(fileRequest); + + connect(fileReply, &QNetworkReply::finished, [=]() { + if (fileReply->error() == QNetworkReply::NoError) { + QByteArray fileData = fileReply->readAll(); + QFile localFile(dir.filePath(fileName)); + if (localFile.open(QIODevice::WriteOnly)) { + localFile.write(fileData); + localFile.close(); + } else { + QMessageBox::warning( + this, "File Error", + QString("Failed to save: %1").arg(fileName)); + } + } else { + QMessageBox::warning( + this, "Download Error", + QString("Failed to download: %1").arg(fileUrl)); + } + fileReply->deleteLater(); + }); + } + } + + // Create the files.json file with the identification of which file to open + createFilesJson(); + + QMessageBox::information( + this, "Download Complete", + QString( + "Patches Downloaded Successfully!" + "All Patches available for all games have been downloaded, there is no " + "need to download them individually for each game as happens in Cheats")); + + } else { + QMessageBox::warning(this, "Data Error", "Failed to parse JSON data from HTML."); + } + } else { + QMessageBox::warning(this, "Network Error", "Failed to retrieve HTML page."); + } + }); +} + +void CheatsPatches::createFilesJson() { + // Directory where XML files are located + QString patchesDir = + QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string()); + QDir dir(patchesDir); + + if (!dir.exists()) { + QString message = QString("Directory does not exist: %1").arg(patchesDir); + QMessageBox::warning(this, "ERRO", message); + return; + } + + QJsonObject filesObject; + QStringList xmlFiles = dir.entryList(QStringList() << "*.xml", QDir::Files); + + foreach (const QString& xmlFile, xmlFiles) { + QFile file(dir.filePath(xmlFile)); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QString message = QString("Failed to open file: %1").arg(xmlFile); + QMessageBox::warning(this, "ERRO", message); + continue; + } + + QXmlStreamReader xmlReader(&file); + QJsonArray titleIdsArray; + + while (!xmlReader.atEnd() && !xmlReader.hasError()) { + QXmlStreamReader::TokenType token = xmlReader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (xmlReader.name() == QStringLiteral("ID")) { + titleIdsArray.append(xmlReader.readElementText()); + } + } + } + + if (xmlReader.hasError()) { + QString message = QString("XML error: %1").arg(xmlReader.errorString()); + QMessageBox::warning(this, "ERRO", message); + } + filesObject[xmlFile] = titleIdsArray; + } + + QFile jsonFile(patchesDir + "/files.json"); + if (!jsonFile.open(QIODevice::WriteOnly)) { + QMessageBox::warning(this, "ERRO", "Failed to open files.json for writing"); + return; + } + + QJsonDocument jsonDoc(filesObject); + jsonFile.write(jsonDoc.toJson()); + jsonFile.close(); +} + +void CheatsPatches::addCheatsToLayout(const QJsonArray& modsArray) { QLayoutItem* item; while ((item = rightLayout->takeAt(0)) != nullptr) { delete item->widget(); delete item; } m_cheats.clear(); + m_cheatCheckBoxes.clear(); int maxWidthButton = 0; @@ -191,13 +395,12 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) { if (modType == "checkbox") { QCheckBox* cheatCheckBox = new QCheckBox(modName); - cheatCheckBox->setStyleSheet(checkBoxStyle); rightLayout->addWidget(cheatCheckBox); + m_cheatCheckBoxes.append(cheatCheckBox); connect(cheatCheckBox, &QCheckBox::toggled, [=](bool checked) { applyCheat(modName, checked); }); } else if (modType == "button") { QPushButton* cheatButton = new QPushButton(modName); - cheatButton->setStyleSheet(buttonStyle); cheatButton->adjustSize(); int buttonWidth = cheatButton->sizeHint().width(); if (buttonWidth > maxWidthButton) { @@ -210,7 +413,7 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) { buttonLayout->addWidget(cheatButton); buttonLayout->addStretch(); - rightLayout->addLayout(buttonLayout); // Add the layout to the main layout + rightLayout->addLayout(buttonLayout); connect(cheatButton, &QPushButton::clicked, [=]() { applyCheat(modName, true); }); } } @@ -223,7 +426,7 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) { QPushButton* button = qobject_cast(widget); if (button) { button->setMinimumWidth(maxWidthButton); - button->setFixedWidth(maxWidthButton); + button->setFixedWidth(maxWidthButton + 20); } } else { QLayout* layout = layoutItem->layout(); @@ -235,14 +438,13 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) { QPushButton* button = qobject_cast(innerWidget); if (button) { button->setMinimumWidth(maxWidthButton); - button->setFixedWidth(maxWidthButton); + button->setFixedWidth(maxWidthButton + 20); } } } } } } - QLabel* creditsLabel = new QLabel(); QString creditsText = "Author: "; QFile file(m_cheatFilePath); @@ -263,13 +465,173 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) { rightLayout->addWidget(creditsLabel); } +void CheatsPatches::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(); + addCheatsToLayout(modsArray); + } +} +void CheatsPatches::loadPatches(const QString& serial) { + + QLayoutItem* item; + while ((item = patchesGroupBoxLayout->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } + m_patchInfos.clear(); + + QString patchDir = + QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string()); + QString filesJsonPath = patchDir + "/files.json"; + + QFile jsonFile(filesJsonPath); + if (!jsonFile.open(QIODevice::ReadOnly)) { + QMessageBox::warning(this, "ERRO", "Failed to open files.json for reading."); + return; + } + + QByteArray jsonData = jsonFile.readAll(); + jsonFile.close(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + QJsonObject jsonObject = jsonDoc.object(); + + for (auto it = jsonObject.constBegin(); it != jsonObject.constEnd(); ++it) { + QString filePath = it.key(); + QFile file(patchDir + "/" + filePath); + QJsonArray idsArray = it.value().toArray(); + + if (idsArray.contains(QJsonValue(serial))) { + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::warning(this, "ERRO", + QString("Failed to open file:: %1").arg(file.fileName())); + continue; + } + + QXmlStreamReader xmlReader(&file); + QString patchName; + QString patchAuthor; + QString patchNote; + QJsonArray patchLines; + + while (!xmlReader.atEnd() && !xmlReader.hasError()) { + xmlReader.readNext(); + + if (xmlReader.tokenType() == QXmlStreamReader::StartElement) { + if (xmlReader.name() == QStringLiteral("Metadata")) { + QXmlStreamAttributes attributes = xmlReader.attributes(); + QString appVer = attributes.value("AppVer").toString(); + if (appVer == m_gameVersion) { + patchName = attributes.value("Name").toString(); + patchAuthor = attributes.value("Author").toString(); + patchNote = attributes.value("Note").toString(); + } + } else if (xmlReader.name() == QStringLiteral("PatchList")) { + QJsonArray linesArray; + while (!xmlReader.atEnd() && + !(xmlReader.tokenType() == QXmlStreamReader::EndElement && + xmlReader.name() == QStringLiteral("PatchList"))) { + xmlReader.readNext(); + if (xmlReader.tokenType() == QXmlStreamReader::StartElement && + xmlReader.name() == QStringLiteral("Line")) { + QXmlStreamAttributes attributes = xmlReader.attributes(); + QJsonObject lineObject; + lineObject["Type"] = attributes.value("Type").toString(); + lineObject["Address"] = attributes.value("Address").toString(); + lineObject["Value"] = attributes.value("Value").toString(); + linesArray.append(lineObject); + } + } + patchLines = linesArray; + } + } + + if (!patchName.isEmpty() && !patchLines.isEmpty()) { + addPatchToLayout(patchName, patchAuthor, patchNote, patchLines); + patchName.clear(); + patchAuthor.clear(); + patchNote.clear(); + patchLines = QJsonArray(); + } + } + file.close(); + } + } +} + +void CheatsPatches::addPatchToLayout(const QString& name, const QString& author, + const QString& note, const QJsonArray& linesArray) { + + QCheckBox* patchCheckBox = new QCheckBox(name); + patchesGroupBoxLayout->addWidget(patchCheckBox); + PatchInfo patchInfo; + patchInfo.name = name; + patchInfo.author = author; + patchInfo.note = note; + patchInfo.linesArray = linesArray; + m_patchInfos[name] = patchInfo; + + // Hook checkbox hover events + patchCheckBox->installEventFilter(this); +} + +void CheatsPatches::updateNoteTextEdit(const QString& patchName) { + if (m_patchInfos.contains(patchName)) { + const PatchInfo& patchInfo = m_patchInfos[patchName]; + QString text = QString("Name: %1\nAuthor: %2\n\n%3") + .arg(patchInfo.name) + .arg(patchInfo.author) + .arg(patchInfo.note); + + foreach (const QJsonValue& value, patchInfo.linesArray) { + QJsonObject lineObject = value.toObject(); + QString type = lineObject["Type"].toString(); + QString address = lineObject["Address"].toString(); + QString patchValue = lineObject["Value"].toString(); + + // add the values ​​to be modified in instructionsTextEdit + // text.append(QString("\nType: %1\nAddress: %2\n\nValue: %3") + // .arg(type) + // .arg(address) + // .arg(patchValue)); + + // implement + // modify the memory before starting using /\ "type,address,patchValue" + // before doing a value conversion depending on the 'type', as per the table + // https://github.com/GoldHEN/GoldHEN_Patch_Repository/tree/main?tab=readme-ov-file#patch-types + // Creates the applyPatches function ? + // start game button + } + + text.replace("\\n", "\n"); + instructionsTextEdit->setText(text); + } +} + +bool showErrorMessage = true; +void CheatsPatches::uncheckAllCheatCheckBoxes() { + for (auto& cheatCheckBox : m_cheatCheckBoxes) { + cheatCheckBox->setChecked(false); + } + showErrorMessage = true; +} + void CheatsPatches::applyCheat(const QString& modName, bool enabled) { if (!m_cheats.contains(modName)) return; if (Core::g_eboot_address == 0) { - QMessageBox::warning(this, "Cheats not found", - "Can't apply mod until a game has been started."); + if (showErrorMessage) { + QMessageBox::warning(this, "Game Not Started", + "Cheat cannot be applied until the game has started."); + showErrorMessage = false; + } + uncheckAllCheatCheckBoxes(); return; } @@ -299,3 +661,25 @@ void CheatsPatches::applyCheat(const QString& modName, bool enabled) { std::memcpy(cheatAddress, bytePatch.data(), bytePatch.size()); } } + +bool CheatsPatches::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::HoverEnter || event->type() == QEvent::HoverLeave) { + QCheckBox* checkBox = qobject_cast(obj); + if (checkBox) { + bool hovered = (event->type() == QEvent::HoverEnter); + onPatchCheckBoxHovered(checkBox, hovered); + return true; + } + } + // Pass the event on to base class + return QWidget::eventFilter(obj, event); +} + +void CheatsPatches::onPatchCheckBoxHovered(QCheckBox* checkBox, bool hovered) { + if (hovered) { + QString text = checkBox->text(); + updateNoteTextEdit(text); + } else { + instructionsTextEdit->setText(defaultTextEdit); + } +} \ No newline at end of file diff --git a/src/qt_gui/cheats_patches.h b/src/qt_gui/cheats_patches.h index bcd2679c..d9cb7227 100644 --- a/src/qt_gui/cheats_patches.h +++ b/src/qt_gui/cheats_patches.h @@ -5,13 +5,19 @@ #define CHEATS_PATCHES_H #include +#include +#include #include #include #include +#include #include #include #include +#include #include +#include +#include #include #include #include @@ -25,11 +31,31 @@ public: ~CheatsPatches(); private: + // UI Setup and Event Handlers void setupUI(); + void onTabChanged(int index); + void updateNoteTextEdit(const QString& patchName); + + // Cheat and Patch Management void loadCheats(const QString& filePath); - void addMods(const QJsonArray& modsArray); + void loadPatches(const QString& serial); + + void downloadCheats(const QString& url); + void downloadPatches(const QString& url); + + void addCheatsToLayout(const QJsonArray& modsArray); + void addPatchToLayout(const QString& name, const QString& author, const QString& note, + const QJsonArray& linesArray); + + void createFilesJson(); + void uncheckAllCheatCheckBoxes(); void applyCheat(const QString& modName, bool enabled); + // Event Filtering + bool eventFilter(QObject* obj, QEvent* event); + void onPatchCheckBoxHovered(QCheckBox* checkBox, bool hovered); + + // Patch Info Structures struct MemoryMod { QString offset; QString on; @@ -42,18 +68,30 @@ private: QVector memoryMods; }; + struct PatchInfo { + QString name; + QString author; + QString note; + QJsonArray linesArray; + }; + + // Members QString m_gameName; QString m_gameSerial; QString m_gameVersion; QString m_gameSize; QPixmap m_gameImage; QString m_cheatFilePath; - - QVBoxLayout* rightLayout; - QString checkBoxStyle; - QString buttonStyle; - QMap m_cheats; + QMap m_patchInfos; + QVector m_cheatCheckBoxes; + + // UI Elements + QVBoxLayout* rightLayout; + QVBoxLayout* patchesGroupBoxLayout; + QGroupBox* patchesGroupBox; + QVBoxLayout* patchesLayout; + QTextEdit* instructionsTextEdit; }; #endif // CHEATS_PATCHES_H