diff --git a/CMakeLists.txt b/CMakeLists.txt index 3781b5bea..805d33709 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -548,6 +548,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/about_dialog.ui src/qt_gui/cheats_patches.cpp src/qt_gui/cheats_patches.h + src/qt_gui/cheats_patches_management.cpp + src/qt_gui/cheats_patches_management.h src/qt_gui/main_window_ui.h src/qt_gui/main_window.cpp src/qt_gui/main_window.h diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index 6e5d80065..63213ae18 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -40,18 +40,18 @@ 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), manager(new QNetworkAccessManager(this)) { setupUI(); - resize(700, 400); + resize(700, 350); 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"); + ("Cheat/Patches are experimental. Use with caution.\n\n" + "Download the cheats individually by selecting the repository and clicking the download " + "button.\nIn the Patch tab you can download all Patches at once, choose which one you want " + "to use and save the option.\n\n" + "As we do not develop the Cheat/Patches,\nplease report issues related to the cheat author."); void CheatsPatches::setupUI() { QString CHEATS_DIR_QString = @@ -68,7 +68,7 @@ void CheatsPatches::setupUI() { QLabel* gameImageLabel = new QLabel(); if (!m_gameImage.isNull()) { - gameImageLabel->setPixmap(m_gameImage.scaled(300, 300, Qt::KeepAspectRatio)); + gameImageLabel->setPixmap(m_gameImage.scaled(275, 275, Qt::KeepAspectRatio)); } else { gameImageLabel->setText("No Image Available"); } @@ -96,7 +96,7 @@ void CheatsPatches::setupUI() { instructionsTextEdit = new QTextEdit(); instructionsTextEdit->setText(defaultTextEdit); instructionsTextEdit->setReadOnly(true); - instructionsTextEdit->setFixedHeight(130); + instructionsTextEdit->setFixedHeight(170); gameInfoLayout->addWidget(instructionsTextEdit); // Create the tab widget @@ -145,25 +145,20 @@ void CheatsPatches::setupUI() { controlLayout->setAlignment(Qt::AlignLeft); QComboBox* downloadComboBox = new QComboBox(); - auto urlCheat_wolf2022 = "https://wolf2022.ir/trainer/" + NameCheatJson; - - downloadComboBox->addItem("wolf2022", urlCheat_wolf2022); + downloadComboBox->addItem("wolf2022", "wolf2022"); downloadComboBox->addItem("GoldHEN", "GoldHEN"); + downloadComboBox->addItem("shadPS4", "shadPS4"); controlLayout->addWidget(downloadComboBox); QPushButton* downloadButton = new QPushButton("Download Cheats"); connect(downloadButton, &QPushButton::clicked, [=]() { - QString url = downloadComboBox->currentData().toString(); - if (url == "GoldHEN") { - downloadFilesGoldHEN(); - } else { - downloadCheats(url); - } + QString source = downloadComboBox->currentData().toString(); + downloadCheats(source, m_gameSerial, m_gameVersion, true); }); - QPushButton* removeCheatButton = new QPushButton("Delete File"); - connect(removeCheatButton, &QPushButton::clicked, [=]() { + QPushButton* deleteCheatButton = new QPushButton("Delete File"); + connect(deleteCheatButton, &QPushButton::clicked, [=]() { QStringListModel* model = qobject_cast(listView_selectFile->model()); if (!model) { return; @@ -196,7 +191,7 @@ void CheatsPatches::setupUI() { }); controlLayout->addWidget(downloadButton); - controlLayout->addWidget(removeCheatButton); + controlLayout->addWidget(deleteCheatButton); cheatsLayout->addLayout(controlLayout); cheatsTab->setLayout(cheatsLayout); @@ -205,6 +200,7 @@ void CheatsPatches::setupUI() { QGroupBox* patchesGroupBox = new QGroupBox(); patchesGroupBoxLayout = new QVBoxLayout(patchesGroupBox); patchesGroupBoxLayout->setAlignment(Qt::AlignTop); + patchesGroupBox->setLayout(patchesGroupBoxLayout); QScrollArea* patchesScrollArea = new QScrollArea(); patchesScrollArea->setWidgetResizable(true); @@ -216,8 +212,13 @@ void CheatsPatches::setupUI() { QPushButton* patchesButton = new QPushButton("Download All Available Patches"); connect(patchesButton, &QPushButton::clicked, [=]() { downloadPatches(); }); - patchesControlLayout->addWidget(patchesButton); + + QPushButton* saveButton = new QPushButton("Save"); + connect(saveButton, &QPushButton::clicked, this, &CheatsPatches::onSaveButtonClicked); + + patchesControlLayout->addWidget(saveButton); + patchesLayout->addLayout(patchesControlLayout); patchesTab->setLayout(patchesLayout); @@ -235,9 +236,167 @@ void CheatsPatches::setupUI() { setLayout(mainLayout); } -void CheatsPatches::downloadFilesGoldHEN() { - QString url = - "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json.txt"; +void CheatsPatches::onSaveButtonClicked() { + 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::critical(this, "Error", "Unable to open files.json for reading."); + return; + } + + QByteArray jsonData = jsonFile.readAll(); + jsonFile.close(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + QJsonObject jsonObject = jsonDoc.object(); + + QString selectedFileName; + QString serial = m_gameSerial; + + for (auto it = jsonObject.constBegin(); it != jsonObject.constEnd(); ++it) { + QString filePath = it.key(); + QJsonArray idsArray = it.value().toArray(); + + if (idsArray.contains(QJsonValue(serial))) { + selectedFileName = filePath; + break; + } + } + + if (selectedFileName.isEmpty()) { + QMessageBox::critical(this, "Error", "No patch file found for the current serial."); + return; + } + + QString filePath = patchDir + "/" + selectedFileName; + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(this, "Error", "Unable to open the file for reading."); + return; + } + + QByteArray xmlData = file.readAll(); + file.close(); + + QString newXmlData; + QXmlStreamWriter xmlWriter(&newXmlData); + xmlWriter.setAutoFormatting(true); + xmlWriter.writeStartDocument(); + + QXmlStreamReader xmlReader(xmlData); + bool insideMetadata = false; + + while (!xmlReader.atEnd()) { + xmlReader.readNext(); + + if (xmlReader.isStartElement()) { + if (xmlReader.name() == QStringLiteral("Metadata")) { + insideMetadata = true; + xmlWriter.writeStartElement(xmlReader.name().toString()); + + QString name = xmlReader.attributes().value("Name").toString(); + bool isEnabled = false; + bool hasIsEnabled = false; + + // Check and update the isEnabled attribute + for (const QXmlStreamAttribute& attr : xmlReader.attributes()) { + if (attr.name() == QStringLiteral("isEnabled")) { + hasIsEnabled = true; + auto it = m_patchInfos.find(name); + if (it != m_patchInfos.end()) { + QCheckBox* checkBox = findCheckBoxByName(it->name); + if (checkBox) { + isEnabled = checkBox->isChecked(); + xmlWriter.writeAttribute("isEnabled", isEnabled ? "true" : "false"); + } + } + } else { + xmlWriter.writeAttribute(attr.name().toString(), attr.value().toString()); + } + } + + if (!hasIsEnabled) { + auto it = m_patchInfos.find(name); + if (it != m_patchInfos.end()) { + QCheckBox* checkBox = findCheckBoxByName(it->name); + if (checkBox) { + isEnabled = checkBox->isChecked(); + } + } + xmlWriter.writeAttribute("isEnabled", isEnabled ? "true" : "false"); + } + } else { + xmlWriter.writeStartElement(xmlReader.name().toString()); + for (const QXmlStreamAttribute& attr : xmlReader.attributes()) { + xmlWriter.writeAttribute(attr.name().toString(), attr.value().toString()); + } + } + } else if (xmlReader.isEndElement()) { + if (xmlReader.name() == QStringLiteral("Metadata")) { + insideMetadata = false; + } + xmlWriter.writeEndElement(); + } else if (xmlReader.isCharacters() && !xmlReader.isWhitespace()) { + xmlWriter.writeCharacters(xmlReader.text().toString()); + } + } + + xmlWriter.writeEndDocument(); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(this, "Error", "Unable to open the file for writing."); + return; + } + + QTextStream textStream(&file); + textStream << newXmlData; + file.close(); + + if (xmlReader.hasError()) { + QMessageBox::critical(this, "Error", "Failed to parse XML: " + xmlReader.errorString()); + } else { + QMessageBox::information(this, "Success", "Options saved successfully."); + } +} + +QCheckBox* CheatsPatches::findCheckBoxByName(const QString& name) { + for (int i = 0; i < patchesGroupBoxLayout->count(); ++i) { + QLayoutItem* item = patchesGroupBoxLayout->itemAt(i); + if (item) { + QWidget* widget = item->widget(); + QCheckBox* checkBox = qobject_cast(widget); + if (checkBox) { + if (checkBox->text() == name) { + return checkBox; + } + } + } + } + return nullptr; +} + +void CheatsPatches::downloadCheats(const QString& source, const QString& m_gameSerial, + const QString& m_gameVersion, const bool showMessageBox) { + QDir dir(Common::FS::GetUserPath(Common::FS::PathType::CheatsDir)); + if (!dir.exists()) { + dir.mkpath("."); + } + + QString url; + if (source == "GoldHEN") { + url = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json.txt"; + } else if (source == "wolf2022") { + url = "https://wolf2022.ir/trainer/" + m_gameSerial + "_" + m_gameVersion + ".json"; + } else if (source == "shadPS4") { + url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/" + "CHEATS_JSON.txt"; + } else { + QMessageBox::warning(this, "Invalid Source", "The selected source is invalid."); + return; + } QNetworkRequest request(url); QNetworkReply* reply = manager->get(request); @@ -245,93 +404,146 @@ void CheatsPatches::downloadFilesGoldHEN() { connect(reply, &QNetworkReply::finished, [=]() { if (reply->error() == QNetworkReply::NoError) { QByteArray jsonData = reply->readAll(); - processJsonContent(jsonData); - } else { - QMessageBox::warning( - this, "Download Error", - QString("Unable to query the json.txt file from the GoldHEN repository.\nError: %1") - .arg(reply->errorString())); - } - reply->deleteLater(); - }); + bool foundFiles = false; - connect(reply, &QNetworkReply::errorOccurred, [=](QNetworkReply::NetworkError code) { - QMessageBox::warning(this, "Download Error", - QString("Error in response: %1").arg(reply->errorString())); - }); -} + if (source == "GoldHEN" || source == "shadPS4") { + QString textContent(jsonData); + QRegularExpression regex( + QString("%1_%2[^=]*\.json").arg(m_gameSerial).arg(m_gameVersion)); + QRegularExpressionMatchIterator matches = regex.globalMatch(textContent); + QString baseUrl; -void CheatsPatches::processJsonContent(const QByteArray& jsonData) { - bool foundFiles = false; - QString textContent(jsonData); - QRegularExpression regex(QString("%1_%2[^=]*\.json").arg(m_gameSerial).arg(m_gameVersion)); - QRegularExpressionMatchIterator matches = regex.globalMatch(textContent); - QString baseUrl = - "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json/"; + if (source == "GoldHEN") { + baseUrl = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/" + "main/json/"; + } else { + baseUrl = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/" + "main/CHEATS/"; + } - QDir dir(Common::FS::GetUserPath(Common::FS::PathType::CheatsDir)); - if (!dir.exists()) { - dir.mkpath("."); - } + while (matches.hasNext()) { + QRegularExpressionMatch match = matches.next(); + QString fileName = match.captured(0); - while (matches.hasNext()) { - QRegularExpressionMatch match = matches.next(); - QString fileName = match.captured(0); + if (!fileName.isEmpty()) { + QString newFileName = fileName; + int dotIndex = newFileName.lastIndexOf('.'); + if (dotIndex != -1) { - if (!fileName.isEmpty()) { - QString newFileName = fileName; - int dotIndex = newFileName.lastIndexOf('.'); - if (dotIndex != -1) { - newFileName.insert(dotIndex, "_GoldHEN"); + if (source == "GoldHEN") { + newFileName.insert(dotIndex, "_GoldHEN"); + } else { + newFileName.insert(dotIndex, "_shadPS4"); + } + } + QString fileUrl = baseUrl + fileName; + QString localFilePath = dir.filePath(newFileName); + + if (QFile::exists(localFilePath) && showMessageBox) { + 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) { + continue; + } + } + QNetworkRequest fileRequest(fileUrl); + QNetworkReply* fileReply = manager->get(fileRequest); + + connect(fileReply, &QNetworkReply::finished, [=]() { + if (fileReply->error() == QNetworkReply::NoError) { + QByteArray fileData = fileReply->readAll(); + QFile localFile(localFilePath); + if (localFile.open(QIODevice::WriteOnly)) { + localFile.write(fileData); + localFile.close(); + } else { + QMessageBox::warning( + this, "Error Saving", + QString("Failed to save file: \n %1").arg(localFilePath)); + } + } else { + QMessageBox::warning( + this, "Failed to Download", + QString("Failed to download file: %1\n\nError: %2") + .arg(fileUrl) + .arg(fileReply->errorString())); + } + fileReply->deleteLater(); + }); + + foundFiles = true; + } + } + if (!foundFiles && showMessageBox) { + QMessageBox::warning( + this, "Cheats Not Found1", + "No Cheats found for this game in this version of the selected " + "repository,\n" + "try another repository or a different version of the game."); + } + } else if (source == "wolf2022") { + QString fileName = QFileInfo(QUrl(url).path()).fileName(); + QString baseFileName = fileName; + int dotIndex = baseFileName.lastIndexOf('.'); + if (dotIndex != -1) { + baseFileName.insert(dotIndex, "_wolf2022"); + } + QString filePath = + QString::fromStdString( + Common::FS::GetUserPath(Common::FS::PathType::CheatsDir).string()) + + "/" + baseFileName; + if (QFile::exists(filePath) && showMessageBox) { + QMessageBox::StandardButton reply2; + reply2 = QMessageBox::question( + this, "File Exists", "File already exists. Do you want to replace it?", + QMessageBox::Yes | QMessageBox::No); + if (reply2 == QMessageBox::No) { + reply->deleteLater(); + return; + } + } + QFile cheatFile(filePath); + if (cheatFile.open(QIODevice::WriteOnly)) { + cheatFile.write(jsonData); + cheatFile.close(); + foundFiles = true; + populateFileListCheats(); + } else { + QMessageBox::warning(this, "Error Saving", + QString("Failed to save file:\n %1").arg(filePath)); + } + } + if (foundFiles && showMessageBox) { + QMessageBox::information( + this, "Cheats Downloaded Successfully", + "You have successfully downloaded the cheats\n" + "for this version of the game from the selected repository.\n\n" + "You can try downloading from another repository, if it is available " + "it will also be possible to use it by selecting the file from the list."); + populateFileListCheats(); } - QString fileUrl = baseUrl + fileName; - QString localFilePath = dir.filePath(newFileName); - QNetworkRequest fileRequest(fileUrl); - QNetworkReply* fileReply = manager->get(fileRequest); - - connect(fileReply, &QNetworkReply::finished, [=]() { - if (fileReply->error() == QNetworkReply::NoError) { - QByteArray fileData = fileReply->readAll(); - QFile localFile(localFilePath); - if (localFile.open(QIODevice::WriteOnly)) { - localFile.write(fileData); - localFile.close(); - } else { - QMessageBox::warning( - this, "Error Saving", - QString("Failed to save file:\n %1").arg(localFilePath)); - } - } else { - QMessageBox::warning(this, "Failed to Download", - QString("Failed to download file: %1\n\nError: %2") - .arg(fileUrl) - .arg(fileReply->errorString())); - } - fileReply->deleteLater(); - }); - - // Marks that at least one file was found - foundFiles = true; + } else { + if (showMessageBox) { + QMessageBox::warning( + this, "Cheats Not Found", + "No Cheats found for this game in this version of the selected " + "repository,\n" + "try another repository or a different version of the game."); + } } - } + reply->deleteLater(); + emit downloadFinished(); + }); - if (!foundFiles) { - QMessageBox::warning( - this, "Cheats Not Found", - "No Cheats found for this game in this version of the selected repository,\n" - "try another repository or a different version of the game."); - } else { - - QMessageBox::information( - this, "Cheats Downloaded Successfully", - "You have successfully downloaded the cheats\n" - "for this version of the game from the 'GoldHEN' repository.\n\n" - "You can try downloading from another repository, if it is available it will also " - "be possible to use it by selecting the file from the list."); - ; - populateFileListCheats(); - } + // connect(reply, &QNetworkReply::errorOccurred, [=](QNetworkReply::NetworkError code) { + // if (showMessageBox) + // QMessageBox::warning(this, "Download Error", + // QString("Error in response: %1").arg(reply->errorString())); + // }); } void CheatsPatches::onTabChanged(int index) { @@ -340,61 +552,6 @@ void CheatsPatches::onTabChanged(int index) { } } -void CheatsPatches::downloadCheats(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 jsonData = reply->readAll(); - QString fileName = QFileInfo(QUrl(url).path()).fileName(); - QString baseFileName = fileName; - int dotIndex = baseFileName.lastIndexOf('.'); - if (dotIndex != -1) { - baseFileName.insert(dotIndex, "_wolf2022"); - } - - QString filePath = - QString::fromStdString( - Common::FS::GetUserPath(Common::FS::PathType::CheatsDir).string()) + - "/" + baseFileName; - - if (QFile::exists(filePath)) { - 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; - } - } - - QFile cheatFile(filePath); - if (cheatFile.open(QIODevice::WriteOnly)) { - cheatFile.write(jsonData); - cheatFile.close(); - populateFileListCheats(); - } else { - QMessageBox::warning(this, "Error saving", - QString("Failed to save file:\n %1").arg(filePath)); - } - QMessageBox::information( - this, "Cheats Downloaded Successfully", - "You have successfully downloaded the cheats\n" - "for this version of the game from the 'wolf2022' repository.\n\n" - "You can try downloading from another repository, if it is available it will also " - "be possible to use it by selecting the file from the list."); - } else { - QMessageBox::warning( - this, "Cheats Not Found", - "No Cheats found for this game in this version of the selected repository,\n" - "try another repository or a different version of the game."); - } - reply->deleteLater(); - }); -} - void CheatsPatches::downloadPatches() { QString url = "https://github.com/GoldHEN/GoldHEN_Patch_Repository/tree/main/" "patches/xml"; @@ -482,7 +639,6 @@ void CheatsPatches::downloadPatches() { } void CheatsPatches::createFilesJson() { - // Directory where XML files are located QString patchesDir = QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string()); QDir dir(patchesDir); @@ -680,7 +836,6 @@ void CheatsPatches::onFileSelected(const QModelIndex& index) { } void CheatsPatches::loadCheats(const QString& filePath) { - QFile file(filePath); if (file.open(QIODevice::ReadOnly)) { QByteArray jsonData = file.readAll(); @@ -691,8 +846,8 @@ void CheatsPatches::loadCheats(const QString& filePath) { addCheatsToLayout(modsArray, creditsArray); } } -void CheatsPatches::loadPatches(const QString& serial) { +void CheatsPatches::loadPatches(const QString& serial) { QLayoutItem* item; while ((item = patchesGroupBoxLayout->takeAt(0)) != nullptr) { delete item->widget(); @@ -733,6 +888,7 @@ void CheatsPatches::loadPatches(const QString& serial) { QString patchAuthor; QString patchNote; QJsonArray patchLines; + bool isEnabled = false; while (!xmlReader.atEnd() && !xmlReader.hasError()) { xmlReader.readNext(); @@ -745,11 +901,15 @@ void CheatsPatches::loadPatches(const QString& serial) { patchName = attributes.value("Name").toString(); patchAuthor = attributes.value("Author").toString(); patchNote = attributes.value("Note").toString(); + isEnabled = isEnabled = + attributes.value("isEnabled").toString() == QStringLiteral("true"); } if (appVer == "mask") { - patchName = attributes.value("Name").toString(); + patchName = attributes.value("Name").toString() + " (any version)"; patchAuthor = attributes.value("Author").toString(); patchNote = attributes.value("Note").toString(); + isEnabled = isEnabled = + attributes.value("isEnabled").toString() == QStringLiteral("true"); } } else if (xmlReader.name() == QStringLiteral("PatchList")) { QJsonArray linesArray; @@ -772,7 +932,8 @@ void CheatsPatches::loadPatches(const QString& serial) { } if (!patchName.isEmpty() && !patchLines.isEmpty()) { - addPatchToLayout(patchName, patchAuthor, patchNote, patchLines, serial); + addPatchToLayout(patchName, patchAuthor, patchNote, patchLines, serial, + isEnabled); patchName.clear(); patchAuthor.clear(); patchNote.clear(); @@ -786,10 +947,12 @@ void CheatsPatches::loadPatches(const QString& serial) { void CheatsPatches::addPatchToLayout(const QString& name, const QString& author, const QString& note, const QJsonArray& linesArray, - const QString& serial) { - + const QString& serial, bool isEnabled) { QCheckBox* patchCheckBox = new QCheckBox(name); + patchCheckBox->setChecked( + isEnabled); // Configura o estado do checkbox com base no valor isEnabled patchesGroupBoxLayout->addWidget(patchCheckBox); + PatchInfo patchInfo; patchInfo.name = name; patchInfo.author = author; @@ -798,7 +961,6 @@ void CheatsPatches::addPatchToLayout(const QString& name, const QString& author, patchInfo.serial = serial; m_patchInfos[name] = patchInfo; - // Hook checkbox hover events patchCheckBox->installEventFilter(this); connect(patchCheckBox, &QCheckBox::toggled, [=](bool checked) { applyPatch(name, checked); }); @@ -850,9 +1012,6 @@ void CheatsPatches::applyCheat(const QString& modName, bool enabled) { std::string offsetStr = memoryMod.offset.toStdString(); std::string valueStr = value.toStdString(); - // Determine if the hint field is present - bool isHintPresent = m_cheats[modName].hasHint; - if (MemoryPatcher::g_eboot_address == 0) { MemoryPatcher::patchInfo addingPatch; addingPatch.modNameStr = modNameStr; @@ -864,8 +1023,9 @@ void CheatsPatches::applyCheat(const QString& modName, bool enabled) { continue; } - bool isOffset = !isHintPresent; - MemoryPatcher::PatchMemory(modNameStr, offsetStr, valueStr, isOffset, false); + // Determine if the hint field is present + bool isHintPresent = m_cheats[modName].hasHint; + MemoryPatcher::PatchMemory(modNameStr, offsetStr, valueStr, !isHintPresent, false); } } @@ -1012,4 +1172,4 @@ void CheatsPatches::onPatchCheckBoxHovered(QCheckBox* checkBox, bool hovered) { } 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 7f1c744b9..46f364301 100644 --- a/src/qt_gui/cheats_patches.h +++ b/src/qt_gui/cheats_patches.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - #ifndef CHEATS_PATCHES_H #define CHEATS_PATCHES_H @@ -31,31 +28,34 @@ public: CheatsPatches(const QString& gameName, const QString& gameSerial, const QString& gameVersion, const QString& gameSize, const QPixmap& gameImage, QWidget* parent = nullptr); ~CheatsPatches(); + void downloadCheats(const QString& source, const QString& m_gameSerial, + const QString& m_gameVersion, const bool showMessageBox); + void downloadPatches(); + +signals: + void downloadFinished(); private: // UI Setup and Event Handlers void setupUI(); - void downloadFilesGoldHEN(); - void processJsonContent(const QByteArray& jsonData); + void onSaveButtonClicked(); + QCheckBox* findCheckBoxByName(const QString& name); void onTabChanged(int index); void updateNoteTextEdit(const QString& patchName); // Cheat and Patch Management void populateFileListCheats(); void onFileSelected(const QModelIndex& index); + void createFilesJson(); + void uncheckAllCheatCheckBoxes(); void loadCheats(const QString& filePath); void loadPatches(const QString& serial); - void downloadCheats(const QString& url); - void downloadPatches(); - void addCheatsToLayout(const QJsonArray& modsArray, const QJsonArray& creditsArray); void addPatchToLayout(const QString& name, const QString& author, const QString& note, - const QJsonArray& linesArray, const QString& serial); + const QJsonArray& linesArray, const QString& serial, bool isEnabled); - void createFilesJson(); - void uncheckAllCheatCheckBoxes(); void applyCheat(const QString& modName, bool enabled); void applyPatch(const QString& patchName, bool enabled); QString convertValueToHex(const QString& type, const QString& valueStr); @@ -110,4 +110,4 @@ private: QItemSelectionModel* selectionModel; }; -#endif // CHEATS_PATCHES_H \ No newline at end of file +#endif // CHEATS_PATCHES_H diff --git a/src/qt_gui/cheats_patches_management.cpp b/src/qt_gui/cheats_patches_management.cpp new file mode 100644 index 000000000..b044adbab --- /dev/null +++ b/src/qt_gui/cheats_patches_management.cpp @@ -0,0 +1,287 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cheats_patches.h" +#include "cheats_patches_management.h" +#include "common/path_util.h" + +using namespace Common::FS; + +CheatsPatchesManagement::CheatsPatchesManagement(QObject* parent) : QObject(parent) {} + +void CheatsPatchesManagement::setupCheatsManagementWidget(QWidget* parent) { + const auto& CHEATS_DIR = Common::FS::GetUserPath(Common::FS::PathType::CheatsDir); + QString CHEATS_DIR_QString = QString::fromStdString(CHEATS_DIR.string()); + + QWidget* cheatWidget = new QWidget(parent, Qt::Window); + cheatWidget->setAttribute(Qt::WA_DeleteOnClose); + cheatWidget->setWindowTitle("Cheats/Patches Management"); + cheatWidget->resize(800, 800); + + QVBoxLayout* mainLayout = new QVBoxLayout(cheatWidget); + + QTabWidget* tabWidget = new QTabWidget(); + + QWidget* cheatsTab = new QWidget(); + QVBoxLayout* cheatsLayout = new QVBoxLayout(cheatsTab); + + listView_selectFile = new QListView(); + listView_selectFile->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + listView_selectFile->setSelectionMode(QAbstractItemView::SingleSelection); + listView_selectFile->setEditTriggers(QAbstractItemView::NoEditTriggers); + + QVBoxLayout* fileListLayout = new QVBoxLayout(); + fileListLayout->addWidget(new QLabel("Select Cheat File:")); + fileListLayout->addWidget(listView_selectFile); + + QPushButton* deleteCheatButton = new QPushButton("Delete File"); + fileListLayout->addWidget(deleteCheatButton); + + QString defaultTextEdit = + "With the button below 'Download' you can download Cheats for all games that are " + "installed on the emulator, they will be downloaded from all available repositories, " + "GoldHEN, wolf2022, shadPS4.\n" + "With the button 'Delete File' you can delete the chosen Cheat file from the list " + "above.\n\nIn the main menu, by right-clicking, you can open each game individually."; + + QTextEdit* infoTextEdit = new QTextEdit(); + infoTextEdit->setText(defaultTextEdit); + infoTextEdit->setReadOnly(true); + infoTextEdit->setFixedHeight(90); + + fileListLayout->addWidget(infoTextEdit); + fileListLayout->setAlignment(Qt::AlignTop); + + QWidget* leftWidget = new QWidget(); + leftWidget->setLayout(fileListLayout); + leftWidget->setMinimumWidth(600); + + QScrollArea* scrollArea = new QScrollArea(); + scrollArea->setWidgetResizable(true); + + QGroupBox* cheatsGroupBox = new QGroupBox(); + rightLayout = new QVBoxLayout(cheatsGroupBox); + rightLayout->setAlignment(Qt::AlignTop); + cheatsGroupBox->setLayout(rightLayout); + + scrollArea->setWidget(cheatsGroupBox); + + QWidget* rightWidget = new QWidget(); + QVBoxLayout* rightWidgetLayout = new QVBoxLayout(rightWidget); + rightWidgetLayout->addWidget(scrollArea); + rightWidget->setMinimumWidth(400); + + QHBoxLayout* topLayout = new QHBoxLayout(); + topLayout->addWidget(leftWidget); + topLayout->addWidget(rightWidget); + + cheatsLayout->addLayout(topLayout); + + QHBoxLayout* cheatsButtonLayout = new QHBoxLayout(); + QPushButton* checkUpdateButton = new QPushButton("Download Cheats For All Installed Games"); + + connect(checkUpdateButton, &QPushButton::clicked, [this]() { onCheckUpdateButtonClicked(); }); + + connect(deleteCheatButton, &QPushButton::clicked, [=]() { + QStringListModel* model = qobject_cast(listView_selectFile->model()); + if (!model) { + return; + } + QItemSelectionModel* selectionModel = listView_selectFile->selectionModel(); + QModelIndexList selectedIndexes = selectionModel->selectedIndexes(); + + if (!selectedIndexes.isEmpty()) { + int row = selectedIndexes.first().row(); + QString selectedFileName = model->stringList().at(row); + QString filePath = + CHEATS_DIR_QString + "/" + selectedFileName.split("|").first().trimmed(); + + if (QFile::remove(filePath)) { + QMessageBox::information(cheatWidget, "File Deleted", + "File has been successfully deleted."); + populateFileListCheats(); + } else { + QMessageBox::critical(cheatWidget, "File Deletion Failed", + "Failed to delete the selected file."); + } + } + }); + + cheatsButtonLayout->addWidget(checkUpdateButton); + cheatsButtonLayout->setAlignment(Qt::AlignBottom); + + cheatsLayout->addLayout(cheatsButtonLayout); + + tabWidget->addTab(cheatsTab, "CHEATS"); + + QWidget* patchesTab = new QWidget(); + QVBoxLayout* patchesLayout = new QVBoxLayout(patchesTab); + + QString default2TextEdit = "implementing :)"; + QTextEdit* infoTextEdit2 = new QTextEdit(); + infoTextEdit2->setText(default2TextEdit); + infoTextEdit2->setReadOnly(true); + infoTextEdit2->setFixedHeight(200); + + patchesLayout->addWidget(infoTextEdit2); + + tabWidget->addTab(patchesTab, "PATCHES"); + + mainLayout->addWidget(tabWidget); + + cheatWidget->setLayout(mainLayout); + populateFileListCheats(); + cheatWidget->show(); +} + +void CheatsPatchesManagement::setGameInfo(const QList>& gameInfoPairs) { + m_gameInfoPairs = gameInfoPairs; +} + +void CheatsPatchesManagement::onCheckUpdateButtonClicked() { + QEventLoop eventLoop; + int pendingDownloads = 0; + + auto onDownloadFinished = [&]() { + if (--pendingDownloads <= 0) { + eventLoop.quit(); + // Exit loop when all downloads are complete + } + }; + + for (const QPair& gameInfo : m_gameInfoPairs) { + QString gameSerial = gameInfo.first; + QString gameVersion = gameInfo.second; + QString empty = ""; + CheatsPatches* cheatsPatches = + new CheatsPatches(empty, empty, empty, empty, empty, nullptr); + connect(cheatsPatches, &CheatsPatches::downloadFinished, onDownloadFinished); + + // Count how many downloads are in progress, 3 downloads for each game + pendingDownloads += 3; + + cheatsPatches->downloadCheats("wolf2022", gameSerial, gameVersion, false); + cheatsPatches->downloadCheats("GoldHEN", gameSerial, gameVersion, false); + cheatsPatches->downloadCheats("shadPS4", gameSerial, gameVersion, false); + } + // Block until all downloads are complete + eventLoop.exec(); + QMessageBox::information(nullptr, "Cheats Downloaded Successfully", + "You have downloaded cheats for all the games you have installed.\n\n" + "If you want, you can use the button to delete a file."); + populateFileListCheats(); +} + +void CheatsPatchesManagement::populateFileListCheats() { + const auto& CHEATS_DIR = Common::FS::GetUserPath(Common::FS::PathType::CheatsDir); + QString CHEATS_DIR_QString = QString::fromStdString(CHEATS_DIR.string()); + + QDir dir(CHEATS_DIR_QString); + QStringList filters; + filters << "*.json"; + dir.setNameFilters(filters); + + QFileInfoList fileList = dir.entryInfoList(QDir::Files); + QStringList fileNames; + QStringList filePaths; + + for (const QFileInfo& fileInfo : fileList) { + fileNames << fileInfo.fileName(); + filePaths << fileInfo.absoluteFilePath(); + } + + QStringList formattedFileNames; + for (int i = 0; i < fileNames.size(); ++i) { + QFile file(filePaths[i]); + if (file.open(QIODevice::ReadOnly)) { + QByteArray jsonData = file.readAll(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + QJsonObject jsonObject = jsonDoc.object(); + QString gameName = jsonObject["name"].toString(); + formattedFileNames << QString("%1 | %2").arg(fileNames[i]).arg(gameName); + file.close(); + } + } + + QStringListModel* newModel = new QStringListModel(formattedFileNames, nullptr); + listView_selectFile->setModel(newModel); + + connect(listView_selectFile->selectionModel(), &QItemSelectionModel::selectionChanged, + [this, filePaths, newModel]() { + QModelIndexList selectedIndexes = + listView_selectFile->selectionModel()->selectedIndexes(); + if (!selectedIndexes.isEmpty()) { + QString selectedFilePath = filePaths[selectedIndexes.first().row()]; + loadCheats(selectedFilePath); + } else { + addMods(QJsonArray()); + } + }); + + if (!formattedFileNames.isEmpty()) { + QModelIndex firstIndex = newModel->index(0, 0); + listView_selectFile->selectionModel()->select(firstIndex, QItemSelectionModel::Select | + QItemSelectionModel::Rows); + listView_selectFile->setCurrentIndex(firstIndex); + QString selectedFilePath = filePaths.first(); + loadCheats(selectedFilePath); + } else { + listView_selectFile->selectionModel()->clearSelection(); + addMods(QJsonArray()); + } +} + +void CheatsPatchesManagement::addMods(const QJsonArray& modsArray) { + if (!rightLayout) + return; + + 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") { + QCheckBox* cheatCheckBox = new QCheckBox(modName); + cheatCheckBox->setChecked(false); + cheatCheckBox->setEnabled(false); + rightLayout->addWidget(cheatCheckBox); + } else if (modType == "button") { + QPushButton* cheatButton = new QPushButton(modName); + cheatButton->setEnabled(false); + rightLayout->addWidget(cheatButton); + } + } +} + +void CheatsPatchesManagement::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); + } +} diff --git a/src/qt_gui/cheats_patches_management.h b/src/qt_gui/cheats_patches_management.h new file mode 100644 index 000000000..aa195f434 --- /dev/null +++ b/src/qt_gui/cheats_patches_management.h @@ -0,0 +1,35 @@ +#ifndef CHEATS_PATCHES_MANAGEMENT_H +#define CHEATS_PATCHES_MANAGEMENT_H + +#include +#include +#include +#include +#include + +class CheatsPatchesManagement : public QObject { + Q_OBJECT + +public: + explicit CheatsPatchesManagement(QObject* parent = nullptr); + void setupCheatsManagementWidget(QWidget* parent); + void setGameInfo(const QList>& gameInfoPairs); + +private: + void populateFileListCheats(); + void populateFileListPatches(); + void loadCheats(const QString& filePath); + void addMods(const QJsonArray& modsArray); + void onCheckUpdateButtonClicked(); + void onCheckPatchesUpdateButtonClicked(); + QList> m_gameInfoPairs; + + QListView* listView_selectFile; + QVBoxLayout* rightLayout; + QListView* patchesListView; + +signals: + void downloadFinished(); +}; + +#endif // CHEATS_PATCHES_MANAGEMENT_H diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index f217b1c79..757643ca6 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -44,6 +44,11 @@ public: itemID = widget->currentRow() * widget->columnCount() + widget->currentColumn(); } + // Do not show the menu if an item is selected + if (itemID == -1) { + return; + } + // Setup menu. QMenu menu(widget); QAction createShortcut("Create Shortcut", widget); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 34ef0d868..f392097cc 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -10,6 +10,7 @@ #include #include "about_dialog.h" +#include "cheats_patches.h" #include "common/io_file.h" #include "common/version.h" #include "core/file_format/pkg.h" @@ -313,6 +314,21 @@ void MainWindow::CreateConnects() { Config::setTableMode(2); }); + // Cheats Management. + connect(ui->cheatsManagementAct, &QAction::triggered, this, [this]() { + QList> gameInfoPairs; + + for (const GameInfo& game : m_game_info->m_games) { + QString gameSerial = QString::fromStdString(game.serial); + QString gameVersion = QString::fromStdString(game.version); + gameInfoPairs.append(qMakePair(gameSerial, gameVersion)); + } + + CheatsPatchesManagement* manager = new CheatsPatchesManagement(this); + manager->setGameInfo(gameInfoPairs); + manager->setupCheatsManagementWidget(this); + }); + // Dump game list. connect(ui->dumpGameListAct, &QAction::triggered, this, [&] { QString filePath = qApp->applicationDirPath().append("/GameList.txt"); diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 24de15b92..5c9d30a74 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -11,6 +11,7 @@ #include #include +#include "cheats_patches_management.h" #include "common/config.h" #include "common/path_util.h" #include "core/file_format/psf.h" @@ -88,6 +89,8 @@ private: PSF psf; std::shared_ptr m_game_info = std::make_shared(); + // Cheats/Patches Management. + QVBoxLayout* patchesRightLayout; protected: void dragEnterEvent(QDragEnterEvent* event1) override { diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 7d0c58dd2..c1d9e9b99 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -43,6 +43,7 @@ public: QAction* setlistModeGridAct; QAction* setlistElfAct; QAction* gameInstallPathAct; + QAction* cheatsManagementAct; QAction* dumpGameListAct; QAction* pkgViewerAct; QAction* aboutAct; @@ -138,11 +139,12 @@ public: gameInstallPathAct = new QAction(MainWindow); gameInstallPathAct->setObjectName("gameInstallPathAct"); gameInstallPathAct->setIcon(QIcon(":images/folder_icon.png")); + cheatsManagementAct = new QAction(MainWindow); + cheatsManagementAct->setObjectName("cheatsManagementAct"); dumpGameListAct = new QAction(MainWindow); dumpGameListAct->setObjectName("dumpGameList"); pkgViewerAct = new QAction(MainWindow); pkgViewerAct->setObjectName("pkgViewer"); - pkgViewerAct->setObjectName("pkgViewer"); pkgViewerAct->setIcon(QIcon(":images/file_icon.png")); aboutAct = new QAction(MainWindow); aboutAct->setObjectName("aboutAct"); @@ -295,6 +297,7 @@ public: menuSettings->addAction(configureAct); menuSettings->addAction(gameInstallPathAct); menuSettings->addAction(menuUtils->menuAction()); + menuUtils->addAction(cheatsManagementAct); menuUtils->addAction(dumpGameListAct); menuUtils->addAction(pkgViewerAct); menuAbout->addAction(aboutAct); @@ -341,6 +344,8 @@ public: setlistElfAct->setText(QCoreApplication::translate("MainWindow", "Elf Viewer", nullptr)); gameInstallPathAct->setText( QCoreApplication::translate("MainWindow", "Game Install Directory", nullptr)); + cheatsManagementAct->setText( + QCoreApplication::translate("MainWindow", "Cheats Management", nullptr)); dumpGameListAct->setText( QCoreApplication::translate("MainWindow", "Dump Game List", nullptr)); pkgViewerAct->setText(QCoreApplication::translate("MainWindow", "PKG Viewer", nullptr));