Patchs menu and fixes
adds the possibility to download Patches, it does not modify the memory yet. and some other fixes
This commit is contained in:
parent
0e4888723e
commit
957d3e38df
|
@ -75,6 +75,7 @@ static auto UserPaths = [] {
|
||||||
create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR);
|
create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR);
|
||||||
create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR);
|
create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR);
|
||||||
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
|
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
|
||||||
|
create_path(PathType::PatchesDir, user_dir / PATCHES_DIR);
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
}();
|
}();
|
||||||
|
|
|
@ -20,7 +20,8 @@ enum class PathType {
|
||||||
SysModuleDir, // Where system modules are stored.
|
SysModuleDir, // Where system modules are stored.
|
||||||
DownloadDir, // Where downloads/temp files are stored.
|
DownloadDir, // Where downloads/temp files are stored.
|
||||||
CapturesDir, // Where rdoc captures 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";
|
constexpr auto PORTABLE_DIR = "user";
|
||||||
|
@ -37,6 +38,7 @@ constexpr auto SYSMODULES_DIR = "sys_modules";
|
||||||
constexpr auto DOWNLOAD_DIR = "download";
|
constexpr auto DOWNLOAD_DIR = "download";
|
||||||
constexpr auto CAPTURES_DIR = "captures";
|
constexpr auto CAPTURES_DIR = "captures";
|
||||||
constexpr auto CHEATS_DIR = "cheats";
|
constexpr auto CHEATS_DIR = "cheats";
|
||||||
|
constexpr auto PATCHES_DIR = "patches";
|
||||||
|
|
||||||
// Filenames
|
// Filenames
|
||||||
constexpr auto LOG_FILE = "shad_log.txt";
|
constexpr auto LOG_FILE = "shad_log.txt";
|
||||||
|
|
|
@ -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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QComboBox>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QEvent>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
#include <QHoverEvent>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
@ -12,16 +15,20 @@
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QNetworkRequest>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
|
#include <QTextEdit>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
#include <common/logging/log.h>
|
#include <common/logging/log.h>
|
||||||
#include "cheats_patches.h"
|
#include "cheats_patches.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "core/module.h"
|
#include "core/module.h"
|
||||||
|
using namespace Common::FS;
|
||||||
|
|
||||||
CheatsPatches::CheatsPatches(const QString& gameName, const QString& gameSerial,
|
CheatsPatches::CheatsPatches(const QString& gameName, const QString& gameSerial,
|
||||||
const QString& gameVersion, const QString& gameSize,
|
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),
|
: QWidget(parent), m_gameName(gameName), m_gameSerial(gameSerial), m_gameVersion(gameVersion),
|
||||||
m_gameSize(gameSize), m_gameImage(gameImage) {
|
m_gameSize(gameSize), m_gameImage(gameImage) {
|
||||||
setupUI();
|
setupUI();
|
||||||
resize(850, 400);
|
resize(700, 400);
|
||||||
setWindowTitle("Cheats / Patches");
|
setWindowTitle("Cheats / Patches");
|
||||||
}
|
}
|
||||||
|
|
||||||
CheatsPatches::~CheatsPatches() {}
|
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() {
|
void CheatsPatches::setupUI() {
|
||||||
const auto& CHEATS_DIR = Common::FS::GetUserPath(Common::FS::PathType::CheatsDir);
|
QString CHEATS_DIR_QString =
|
||||||
QString CHEATS_DIR_QString = QString::fromStdString(CHEATS_DIR.string());
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::CheatsDir).string());
|
||||||
QString NameCheatJson = m_gameSerial + "_" + m_gameVersion + ".json";
|
QString NameCheatJson = m_gameSerial + "_" + m_gameVersion + ".json";
|
||||||
m_cheatFilePath = CHEATS_DIR_QString + "/" + NameCheatJson;
|
m_cheatFilePath = CHEATS_DIR_QString + "/" + NameCheatJson;
|
||||||
|
|
||||||
QHBoxLayout* mainLayout = new QHBoxLayout(this);
|
QHBoxLayout* mainLayout = new QHBoxLayout(this);
|
||||||
|
|
||||||
// Create the game info group box
|
// Create the game info group box
|
||||||
QGroupBox* gameInfoGroupBox = new QGroupBox("Game Information");
|
QGroupBox* gameInfoGroupBox = new QGroupBox();
|
||||||
QVBoxLayout* gameInfoLayout = new QVBoxLayout(gameInfoGroupBox);
|
QVBoxLayout* gameInfoLayout = new QVBoxLayout(gameInfoGroupBox);
|
||||||
gameInfoLayout->setAlignment(Qt::AlignTop);
|
gameInfoLayout->setAlignment(Qt::AlignTop);
|
||||||
|
|
||||||
QLabel* gameImageLabel = new QLabel();
|
QLabel* gameImageLabel = new QLabel();
|
||||||
if (!m_gameImage.isNull()) {
|
if (!m_gameImage.isNull()) {
|
||||||
gameImageLabel->setPixmap(m_gameImage.scaled(250, 250, Qt::KeepAspectRatio));
|
gameImageLabel->setPixmap(m_gameImage.scaled(300, 300, Qt::KeepAspectRatio));
|
||||||
} else {
|
} else {
|
||||||
gameImageLabel->setText("No Image Available");
|
gameImageLabel->setText("No Image Available");
|
||||||
}
|
}
|
||||||
|
@ -74,6 +88,13 @@ void CheatsPatches::setupUI() {
|
||||||
gameSizeLabel->setAlignment(Qt::AlignLeft);
|
gameSizeLabel->setAlignment(Qt::AlignLeft);
|
||||||
gameInfoLayout->addWidget(gameSizeLabel);
|
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
|
// Create the tab widget
|
||||||
QTabWidget* tabWidget = new QTabWidget();
|
QTabWidget* tabWidget = new QTabWidget();
|
||||||
QWidget* cheatsTab = new QWidget();
|
QWidget* cheatsTab = new QWidget();
|
||||||
|
@ -86,8 +107,6 @@ void CheatsPatches::setupUI() {
|
||||||
// Setup the cheats tab
|
// Setup the cheats tab
|
||||||
QGroupBox* cheatsGroupBox = new QGroupBox();
|
QGroupBox* cheatsGroupBox = new QGroupBox();
|
||||||
rightLayout = new QVBoxLayout(cheatsGroupBox);
|
rightLayout = new QVBoxLayout(cheatsGroupBox);
|
||||||
checkBoxStyle = "QCheckBox { font-size: 17px; }";
|
|
||||||
buttonStyle = "QPushButton { font-size: 17px; }";
|
|
||||||
rightLayout->setAlignment(Qt::AlignTop);
|
rightLayout->setAlignment(Qt::AlignTop);
|
||||||
|
|
||||||
loadCheats(m_cheatFilePath);
|
loadCheats(m_cheatFilePath);
|
||||||
|
@ -97,74 +116,259 @@ void CheatsPatches::setupUI() {
|
||||||
scrollArea->setWidget(cheatsGroupBox);
|
scrollArea->setWidget(cheatsGroupBox);
|
||||||
cheatsLayout->addWidget(scrollArea);
|
cheatsLayout->addWidget(scrollArea);
|
||||||
|
|
||||||
// Add a check for updates button
|
QLabel* repositoryLabel = new QLabel("Repository:");
|
||||||
QHBoxLayout* buttonLayout = new QHBoxLayout();
|
repositoryLabel->setAlignment(Qt::AlignLeft);
|
||||||
QPushButton* checkUpdateButton = new QPushButton("Download Cheats");
|
repositoryLabel->setAlignment(Qt::AlignVCenter);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString url =
|
// Add a combo box and a download button
|
||||||
"https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json/" +
|
QHBoxLayout* controlLayout = new QHBoxLayout();
|
||||||
m_gameSerial + "_" + m_gameVersion + ".json";
|
controlLayout->addWidget(repositoryLabel);
|
||||||
|
controlLayout->setAlignment(Qt::AlignLeft);
|
||||||
|
QComboBox* downloadComboBox = new QComboBox();
|
||||||
|
|
||||||
QNetworkAccessManager* manager = new QNetworkAccessManager(this);
|
auto urlCheat_wolf2022 = "https://wolf2022.ir/trainer/" + NameCheatJson;
|
||||||
QNetworkRequest request(url);
|
auto urlCheat_GoldHEN =
|
||||||
QNetworkReply* reply = manager->get(request);
|
"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, [=]() {
|
downloadComboBox->addItem("wolf2022", urlCheat_wolf2022);
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
downloadComboBox->addItem("GoldHEN", urlCheat_GoldHEN);
|
||||||
QByteArray jsonData = reply->readAll();
|
downloadComboBox->addItem("shadPs4 test", url_shadPs4);
|
||||||
QFile cheatFile(m_cheatFilePath);
|
|
||||||
if (cheatFile.open(QIODevice::WriteOnly)) {
|
controlLayout->addWidget(downloadComboBox);
|
||||||
cheatFile.write(jsonData);
|
|
||||||
cheatFile.close();
|
QPushButton* downloadButton = new QPushButton("Download Cheats");
|
||||||
loadCheats(m_cheatFilePath);
|
connect(downloadButton, &QPushButton::clicked, [=]() {
|
||||||
}
|
QString url = downloadComboBox->currentData().toString();
|
||||||
} else {
|
downloadCheats(url);
|
||||||
QMessageBox::warning(this, "Cheats not found",
|
|
||||||
"No Cheats found for this game in this version.");
|
|
||||||
}
|
|
||||||
reply->deleteLater();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
buttonLayout->addWidget(checkUpdateButton);
|
controlLayout->addWidget(downloadButton);
|
||||||
cheatsLayout->addLayout(buttonLayout);
|
cheatsLayout->addLayout(controlLayout);
|
||||||
cheatsTab->setLayout(cheatsLayout);
|
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);
|
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(gameInfoGroupBox, 1);
|
||||||
mainLayout->addWidget(tabWidget, 3);
|
mainLayout->addWidget(tabWidget, 3);
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheatsPatches::loadCheats(const QString& filePath) {
|
void CheatsPatches::onTabChanged(int index) {
|
||||||
QFile file(filePath);
|
if (index == 1) {
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
loadPatches(m_gameSerial);
|
||||||
QByteArray jsonData = file.readAll();
|
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
|
|
||||||
QJsonObject jsonObject = jsonDoc.object();
|
|
||||||
QJsonArray modsArray = jsonObject["mods"].toArray();
|
|
||||||
addMods(modsArray);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"(<script type="application/json" data-target="react-app.embeddedData">(.+?)</script>)");
|
||||||
|
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;
|
QLayoutItem* item;
|
||||||
while ((item = rightLayout->takeAt(0)) != nullptr) {
|
while ((item = rightLayout->takeAt(0)) != nullptr) {
|
||||||
delete item->widget();
|
delete item->widget();
|
||||||
delete item;
|
delete item;
|
||||||
}
|
}
|
||||||
m_cheats.clear();
|
m_cheats.clear();
|
||||||
|
m_cheatCheckBoxes.clear();
|
||||||
|
|
||||||
int maxWidthButton = 0;
|
int maxWidthButton = 0;
|
||||||
|
|
||||||
|
@ -191,13 +395,12 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) {
|
||||||
|
|
||||||
if (modType == "checkbox") {
|
if (modType == "checkbox") {
|
||||||
QCheckBox* cheatCheckBox = new QCheckBox(modName);
|
QCheckBox* cheatCheckBox = new QCheckBox(modName);
|
||||||
cheatCheckBox->setStyleSheet(checkBoxStyle);
|
|
||||||
rightLayout->addWidget(cheatCheckBox);
|
rightLayout->addWidget(cheatCheckBox);
|
||||||
|
m_cheatCheckBoxes.append(cheatCheckBox);
|
||||||
connect(cheatCheckBox, &QCheckBox::toggled,
|
connect(cheatCheckBox, &QCheckBox::toggled,
|
||||||
[=](bool checked) { applyCheat(modName, checked); });
|
[=](bool checked) { applyCheat(modName, checked); });
|
||||||
} else if (modType == "button") {
|
} else if (modType == "button") {
|
||||||
QPushButton* cheatButton = new QPushButton(modName);
|
QPushButton* cheatButton = new QPushButton(modName);
|
||||||
cheatButton->setStyleSheet(buttonStyle);
|
|
||||||
cheatButton->adjustSize();
|
cheatButton->adjustSize();
|
||||||
int buttonWidth = cheatButton->sizeHint().width();
|
int buttonWidth = cheatButton->sizeHint().width();
|
||||||
if (buttonWidth > maxWidthButton) {
|
if (buttonWidth > maxWidthButton) {
|
||||||
|
@ -210,7 +413,7 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) {
|
||||||
buttonLayout->addWidget(cheatButton);
|
buttonLayout->addWidget(cheatButton);
|
||||||
buttonLayout->addStretch();
|
buttonLayout->addStretch();
|
||||||
|
|
||||||
rightLayout->addLayout(buttonLayout); // Add the layout to the main layout
|
rightLayout->addLayout(buttonLayout);
|
||||||
connect(cheatButton, &QPushButton::clicked, [=]() { applyCheat(modName, true); });
|
connect(cheatButton, &QPushButton::clicked, [=]() { applyCheat(modName, true); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,7 +426,7 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) {
|
||||||
QPushButton* button = qobject_cast<QPushButton*>(widget);
|
QPushButton* button = qobject_cast<QPushButton*>(widget);
|
||||||
if (button) {
|
if (button) {
|
||||||
button->setMinimumWidth(maxWidthButton);
|
button->setMinimumWidth(maxWidthButton);
|
||||||
button->setFixedWidth(maxWidthButton);
|
button->setFixedWidth(maxWidthButton + 20);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QLayout* layout = layoutItem->layout();
|
QLayout* layout = layoutItem->layout();
|
||||||
|
@ -235,14 +438,13 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) {
|
||||||
QPushButton* button = qobject_cast<QPushButton*>(innerWidget);
|
QPushButton* button = qobject_cast<QPushButton*>(innerWidget);
|
||||||
if (button) {
|
if (button) {
|
||||||
button->setMinimumWidth(maxWidthButton);
|
button->setMinimumWidth(maxWidthButton);
|
||||||
button->setFixedWidth(maxWidthButton);
|
button->setFixedWidth(maxWidthButton + 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QLabel* creditsLabel = new QLabel();
|
QLabel* creditsLabel = new QLabel();
|
||||||
QString creditsText = "Author: ";
|
QString creditsText = "Author: ";
|
||||||
QFile file(m_cheatFilePath);
|
QFile file(m_cheatFilePath);
|
||||||
|
@ -263,13 +465,173 @@ void CheatsPatches::addMods(const QJsonArray& modsArray) {
|
||||||
rightLayout->addWidget(creditsLabel);
|
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) {
|
void CheatsPatches::applyCheat(const QString& modName, bool enabled) {
|
||||||
if (!m_cheats.contains(modName))
|
if (!m_cheats.contains(modName))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Core::g_eboot_address == 0) {
|
if (Core::g_eboot_address == 0) {
|
||||||
QMessageBox::warning(this, "Cheats not found",
|
if (showErrorMessage) {
|
||||||
"Can't apply mod until a game has been started.");
|
QMessageBox::warning(this, "Game Not Started",
|
||||||
|
"Cheat cannot be applied until the game has started.");
|
||||||
|
showErrorMessage = false;
|
||||||
|
}
|
||||||
|
uncheckAllCheatCheckBoxes();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,3 +661,25 @@ void CheatsPatches::applyCheat(const QString& modName, bool enabled) {
|
||||||
std::memcpy(cheatAddress, bytePatch.data(), bytePatch.size());
|
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<QCheckBox*>(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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,13 +5,19 @@
|
||||||
#define CHEATS_PATCHES_H
|
#define CHEATS_PATCHES_H
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QGroupBox>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QLabel>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QScrollArea>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QTabWidget>
|
||||||
|
#include <QTextEdit>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
@ -25,11 +31,31 @@ public:
|
||||||
~CheatsPatches();
|
~CheatsPatches();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// UI Setup and Event Handlers
|
||||||
void setupUI();
|
void setupUI();
|
||||||
|
void onTabChanged(int index);
|
||||||
|
void updateNoteTextEdit(const QString& patchName);
|
||||||
|
|
||||||
|
// Cheat and Patch Management
|
||||||
void loadCheats(const QString& filePath);
|
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);
|
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 {
|
struct MemoryMod {
|
||||||
QString offset;
|
QString offset;
|
||||||
QString on;
|
QString on;
|
||||||
|
@ -42,18 +68,30 @@ private:
|
||||||
QVector<MemoryMod> memoryMods;
|
QVector<MemoryMod> memoryMods;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PatchInfo {
|
||||||
|
QString name;
|
||||||
|
QString author;
|
||||||
|
QString note;
|
||||||
|
QJsonArray linesArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Members
|
||||||
QString m_gameName;
|
QString m_gameName;
|
||||||
QString m_gameSerial;
|
QString m_gameSerial;
|
||||||
QString m_gameVersion;
|
QString m_gameVersion;
|
||||||
QString m_gameSize;
|
QString m_gameSize;
|
||||||
QPixmap m_gameImage;
|
QPixmap m_gameImage;
|
||||||
QString m_cheatFilePath;
|
QString m_cheatFilePath;
|
||||||
|
|
||||||
QVBoxLayout* rightLayout;
|
|
||||||
QString checkBoxStyle;
|
|
||||||
QString buttonStyle;
|
|
||||||
|
|
||||||
QMap<QString, Cheat> m_cheats;
|
QMap<QString, Cheat> m_cheats;
|
||||||
|
QMap<QString, PatchInfo> m_patchInfos;
|
||||||
|
QVector<QCheckBox*> m_cheatCheckBoxes;
|
||||||
|
|
||||||
|
// UI Elements
|
||||||
|
QVBoxLayout* rightLayout;
|
||||||
|
QVBoxLayout* patchesGroupBoxLayout;
|
||||||
|
QGroupBox* patchesGroupBox;
|
||||||
|
QVBoxLayout* patchesLayout;
|
||||||
|
QTextEdit* instructionsTextEdit;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CHEATS_PATCHES_H
|
#endif // CHEATS_PATCHES_H
|
||||||
|
|
Loading…
Reference in New Issue