Management |save checkbox | shadps4 repository

MENU - Cheats/Patches Management (implementing Patches)
save patches checkbox
add shadps4 repository
This commit is contained in:
DanielSvoboda 2024-08-26 12:00:15 -03:00
parent 9107c6e8c0
commit 3ac48b2d61
9 changed files with 697 additions and 184 deletions

View file

@ -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

View file

@ -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<QStringListModel*>(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<QCheckBox*>(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);
}
}
}

View file

@ -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
#endif // CHEATS_PATCHES_H

View file

@ -0,0 +1,287 @@
#include <QApplication>
#include <QCheckBox>
#include <QDir>
#include <QFile>
#include <QFileInfoList>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QItemSelectionModel>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QListView>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QStringListModel>
#include <QTabWidget>
#include <QTextEdit>
#include <QVBoxLayout>
#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<QStringListModel*>(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<QPair<QString, QString>>& 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<QString, QString>& 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);
}
}

View file

@ -0,0 +1,35 @@
#ifndef CHEATS_PATCHES_MANAGEMENT_H
#define CHEATS_PATCHES_MANAGEMENT_H
#include <QList>
#include <QListView>
#include <QString>
#include <QVBoxLayout>
#include <QWidget>
class CheatsPatchesManagement : public QObject {
Q_OBJECT
public:
explicit CheatsPatchesManagement(QObject* parent = nullptr);
void setupCheatsManagementWidget(QWidget* parent);
void setGameInfo(const QList<QPair<QString, QString>>& gameInfoPairs);
private:
void populateFileListCheats();
void populateFileListPatches();
void loadCheats(const QString& filePath);
void addMods(const QJsonArray& modsArray);
void onCheckUpdateButtonClicked();
void onCheckPatchesUpdateButtonClicked();
QList<QPair<QString, QString>> m_gameInfoPairs;
QListView* listView_selectFile;
QVBoxLayout* rightLayout;
QListView* patchesListView;
signals:
void downloadFinished();
};
#endif // CHEATS_PATCHES_MANAGEMENT_H

View file

@ -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);

View file

@ -10,6 +10,7 @@
#include <QtConcurrent>
#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<QPair<QString, QString>> 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");

View file

@ -11,6 +11,7 @@
#include <QScopedPointer>
#include <fmt/core.h>
#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<GameInfoClass> m_game_info = std::make_shared<GameInfoClass>();
// Cheats/Patches Management.
QVBoxLayout* patchesRightLayout;
protected:
void dragEnterEvent(QDragEnterEvent* event1) override {

View file

@ -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));