mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-28 13:55:39 -06:00
* Cheats/Patches Adds the possibility of applying cheats/patches according to the specific game serial+version The logic for adding modifications has not yet been implemented! Interface based on issues/372 https://github.com/shadps4-emu/shadPS4/issues/372 [X]Front-end []Back-end Create a synchronized fork of the cheats/patches repository * Clang Format * separate files The code has been separated into separate files as suggested by georgemoralis. Added the Patch tab, which has not been implemented yet. Added the 'applyCheat' area to apply the modification, not implemented yet... And added LOG_INFO. * reuse * initial implementation of cheat functionality * Update cheats_patches.cpp sets all added buttons to the size of the largest button. and fixes some aesthetic issues. * move eboot_address to module.h fixes the non-qt builds and makes more sense to be there anyway * Patchs menu and fixes adds the possibility to download Patches, it does not modify the memory yet. and some other fixes * MemoryPatcher namespace, activate cheats on start * format * initial patch implementation * format * format again... * convertValueToHex * Fixes Choosing which cheat file to use. And some other fixes * fix bytes16, bytes32, bytes64 type patches If a patch is any of these types we convert it from little endian to big endian * format * format again :( * Implement pattern scanning for mask type patches * add check to stop patches applying to wrong game previously if you added a patch to a game, but closed the window and opened a different game it would still try to apply the patch, this is now fixed * format * Fix 'Hint' 0x400000 | and Author * Management |save checkbox | shadps4 repository MENU - Cheats/Patches Management (implementing Patches) save patches checkbox add shadps4 repository * Load saved patches, miscellaneous fixes * Fix an issue with mask patches not being saved * format + remove debug log * multiple patches | TR translation for cheats/patches * clang * ENABLE_QT_GUI * OK * move memory_patcher to qt_gui * clang * add cheats hu_HU * fix log * Remove the item from the patchesListView if no patches were added (the game has patches, but not for the current version) --------- Co-authored-by: CrazyBloo <CrazyBloo@users.noreply.github.com>
1264 lines
50 KiB
C++
1264 lines
50 KiB
C++
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
||
#include <QComboBox>
|
||
#include <QDir>
|
||
#include <QEvent>
|
||
#include <QFile>
|
||
#include <QGroupBox>
|
||
#include <QHBoxLayout>
|
||
#include <QHoverEvent>
|
||
#include <QJsonArray>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QLabel>
|
||
#include <QListView>
|
||
#include <QMessageBox>
|
||
#include <QNetworkAccessManager>
|
||
#include <QNetworkReply>
|
||
#include <QNetworkRequest>
|
||
#include <QPixmap>
|
||
#include <QPushButton>
|
||
#include <QScrollArea>
|
||
#include <QString>
|
||
#include <QStringListModel>
|
||
#include <QTabWidget>
|
||
#include <QTextEdit>
|
||
#include <QVBoxLayout>
|
||
#include <QXmlStreamReader>
|
||
#include <common/logging/log.h>
|
||
#include "cheats_patches.h"
|
||
#include "common/path_util.h"
|
||
#include "core/module.h"
|
||
#include "qt_gui/memory_patcher.h"
|
||
|
||
using namespace Common::FS;
|
||
|
||
CheatsPatches::CheatsPatches(const QString& gameName, const QString& gameSerial,
|
||
const QString& gameVersion, const QString& gameSize,
|
||
const QPixmap& gameImage, QWidget* parent)
|
||
: QWidget(parent), m_gameName(gameName), m_gameSerial(gameSerial), m_gameVersion(gameVersion),
|
||
m_gameSize(gameSize), m_gameImage(gameImage), manager(new QNetworkAccessManager(this)) {
|
||
setupUI();
|
||
resize(500, 400);
|
||
setWindowTitle(tr("Cheats / Patches"));
|
||
}
|
||
|
||
CheatsPatches::~CheatsPatches() {}
|
||
|
||
void CheatsPatches::setupUI() {
|
||
defaultTextEdit = tr("defaultTextEdit_MSG");
|
||
defaultTextEdit.replace("\\n", "\n");
|
||
|
||
QString CHEATS_DIR_QString =
|
||
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::CheatsDir).string());
|
||
QString NameCheatJson = m_gameSerial + "_" + m_gameVersion + ".json";
|
||
m_cheatFilePath = CHEATS_DIR_QString + "/" + NameCheatJson;
|
||
|
||
QHBoxLayout* mainLayout = new QHBoxLayout(this);
|
||
|
||
// Create the game info group box
|
||
QGroupBox* gameInfoGroupBox = new QGroupBox();
|
||
QVBoxLayout* gameInfoLayout = new QVBoxLayout(gameInfoGroupBox);
|
||
gameInfoLayout->setAlignment(Qt::AlignTop);
|
||
|
||
QLabel* gameImageLabel = new QLabel();
|
||
if (!m_gameImage.isNull()) {
|
||
gameImageLabel->setPixmap(m_gameImage.scaled(275, 275, Qt::KeepAspectRatio));
|
||
} else {
|
||
gameImageLabel->setText(tr("No Image Available"));
|
||
}
|
||
gameImageLabel->setAlignment(Qt::AlignCenter);
|
||
gameInfoLayout->addWidget(gameImageLabel, 0, Qt::AlignCenter);
|
||
|
||
QLabel* gameNameLabel = new QLabel(m_gameName);
|
||
gameNameLabel->setAlignment(Qt::AlignLeft);
|
||
gameNameLabel->setWordWrap(true);
|
||
gameInfoLayout->addWidget(gameNameLabel);
|
||
|
||
QLabel* gameSerialLabel = new QLabel(tr("Serial: ") + m_gameSerial);
|
||
gameSerialLabel->setAlignment(Qt::AlignLeft);
|
||
gameInfoLayout->addWidget(gameSerialLabel);
|
||
|
||
QLabel* gameVersionLabel = new QLabel(tr("Version: ") + m_gameVersion);
|
||
gameVersionLabel->setAlignment(Qt::AlignLeft);
|
||
gameInfoLayout->addWidget(gameVersionLabel);
|
||
|
||
QLabel* gameSizeLabel = new QLabel(tr("Size: ") + m_gameSize);
|
||
gameSizeLabel->setAlignment(Qt::AlignLeft);
|
||
gameInfoLayout->addWidget(gameSizeLabel);
|
||
|
||
// Add a text area for instructions and 'Patch' descriptions
|
||
instructionsTextEdit = new QTextEdit();
|
||
instructionsTextEdit->setText(defaultTextEdit);
|
||
instructionsTextEdit->setReadOnly(true);
|
||
instructionsTextEdit->setFixedHeight(290);
|
||
gameInfoLayout->addWidget(instructionsTextEdit);
|
||
|
||
// Create the tab widget
|
||
QTabWidget* tabWidget = new QTabWidget();
|
||
QWidget* cheatsTab = new QWidget();
|
||
QWidget* patchesTab = new QWidget();
|
||
|
||
// Layouts for the tabs
|
||
QVBoxLayout* cheatsLayout = new QVBoxLayout();
|
||
QVBoxLayout* patchesLayout = new QVBoxLayout();
|
||
|
||
// Setup the cheats tab
|
||
QGroupBox* cheatsGroupBox = new QGroupBox();
|
||
rightLayout = new QVBoxLayout(cheatsGroupBox);
|
||
rightLayout->setAlignment(Qt::AlignTop);
|
||
|
||
cheatsGroupBox->setLayout(rightLayout);
|
||
QScrollArea* scrollArea = new QScrollArea();
|
||
scrollArea->setWidgetResizable(true);
|
||
scrollArea->setWidget(cheatsGroupBox);
|
||
scrollArea->setMinimumHeight(490);
|
||
cheatsLayout->addWidget(scrollArea);
|
||
|
||
// QListView
|
||
listView_selectFile = new QListView();
|
||
listView_selectFile->setSelectionMode(QAbstractItemView::SingleSelection);
|
||
listView_selectFile->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||
|
||
// Add QListView to layout
|
||
QVBoxLayout* fileListLayout = new QVBoxLayout();
|
||
fileListLayout->addWidget(new QLabel(tr("Select Cheat File:")));
|
||
fileListLayout->addWidget(listView_selectFile);
|
||
cheatsLayout->addLayout(fileListLayout, 2);
|
||
|
||
// Call the method to fill the list of cheat files
|
||
populateFileListCheats();
|
||
|
||
QLabel* repositoryLabel = new QLabel("Repository:");
|
||
repositoryLabel->setAlignment(Qt::AlignLeft);
|
||
repositoryLabel->setAlignment(Qt::AlignVCenter);
|
||
|
||
// Add a combo box and a download button
|
||
QHBoxLayout* controlLayout = new QHBoxLayout();
|
||
controlLayout->addWidget(repositoryLabel);
|
||
controlLayout->setAlignment(Qt::AlignLeft);
|
||
QComboBox* downloadComboBox = new QComboBox();
|
||
|
||
downloadComboBox->addItem("wolf2022", "wolf2022");
|
||
downloadComboBox->addItem("GoldHEN", "GoldHEN");
|
||
downloadComboBox->addItem("shadPS4", "shadPS4");
|
||
|
||
controlLayout->addWidget(downloadComboBox);
|
||
|
||
QPushButton* downloadButton = new QPushButton(tr("Download Cheats"));
|
||
connect(downloadButton, &QPushButton::clicked, [=]() {
|
||
QString source = downloadComboBox->currentData().toString();
|
||
downloadCheats(source, m_gameSerial, m_gameVersion, true);
|
||
});
|
||
|
||
QPushButton* deleteCheatButton = new QPushButton(tr("Delete File"));
|
||
connect(deleteCheatButton, &QPushButton::clicked, [=]() {
|
||
QStringListModel* model = qobject_cast<QStringListModel*>(listView_selectFile->model());
|
||
if (!model) {
|
||
return;
|
||
}
|
||
QItemSelectionModel* selectionModel = listView_selectFile->selectionModel();
|
||
if (!selectionModel) {
|
||
return;
|
||
}
|
||
QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
|
||
if (selectedIndexes.isEmpty()) {
|
||
QMessageBox::warning(
|
||
this, tr("Delete File"),
|
||
tr("No files selected.") + "\n" +
|
||
tr("You can delete the cheats you don't want after downloading them."));
|
||
return;
|
||
}
|
||
QModelIndex selectedIndex = selectedIndexes.first();
|
||
QString selectedFileName = model->data(selectedIndex).toString();
|
||
|
||
int ret = QMessageBox::warning(
|
||
this, tr("Delete File"),
|
||
QString(tr("Do you want to delete the selected file?\n%1")).arg(selectedFileName),
|
||
QMessageBox::Yes | QMessageBox::No);
|
||
|
||
if (ret == QMessageBox::Yes) {
|
||
QString filePath = CHEATS_DIR_QString + "/" + selectedFileName;
|
||
QFile::remove(filePath);
|
||
populateFileListCheats();
|
||
}
|
||
});
|
||
|
||
controlLayout->addWidget(downloadButton);
|
||
controlLayout->addWidget(deleteCheatButton);
|
||
|
||
cheatsLayout->addLayout(controlLayout);
|
||
cheatsTab->setLayout(cheatsLayout);
|
||
|
||
// Setup the patches tab
|
||
QGroupBox* patchesGroupBox = new QGroupBox();
|
||
patchesGroupBoxLayout = new QVBoxLayout(patchesGroupBox);
|
||
patchesGroupBoxLayout->setAlignment(Qt::AlignTop);
|
||
patchesGroupBox->setLayout(patchesGroupBoxLayout);
|
||
|
||
QScrollArea* patchesScrollArea = new QScrollArea();
|
||
patchesScrollArea->setWidgetResizable(true);
|
||
patchesScrollArea->setWidget(patchesGroupBox);
|
||
patchesScrollArea->setMinimumHeight(490);
|
||
patchesLayout->addWidget(patchesScrollArea);
|
||
|
||
// List of files in patchesListView
|
||
patchesListView = new QListView();
|
||
patchesListView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||
patchesListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||
|
||
// Add new label "Select Patch File:" above the QListView
|
||
QVBoxLayout* patchFileListLayout = new QVBoxLayout();
|
||
patchFileListLayout->addWidget(new QLabel(tr("Select Patch File:")));
|
||
patchFileListLayout->addWidget(patchesListView);
|
||
patchesLayout->addLayout(patchFileListLayout, 2);
|
||
|
||
QStringListModel* patchesModel = new QStringListModel();
|
||
patchesListView->setModel(patchesModel);
|
||
|
||
QHBoxLayout* patchesControlLayout = new QHBoxLayout();
|
||
|
||
QLabel* patchesRepositoryLabel = new QLabel(tr("Repository:"));
|
||
patchesRepositoryLabel->setAlignment(Qt::AlignLeft);
|
||
patchesRepositoryLabel->setAlignment(Qt::AlignVCenter);
|
||
patchesControlLayout->addWidget(patchesRepositoryLabel);
|
||
|
||
// Add the combo box with options
|
||
patchesComboBox = new QComboBox();
|
||
patchesComboBox->addItem("GoldHEN", "GoldHEN");
|
||
patchesComboBox->addItem("shadPS4", "shadPS4");
|
||
patchesControlLayout->addWidget(patchesComboBox);
|
||
|
||
QPushButton* patchesButton = new QPushButton(tr("Download Patches"));
|
||
connect(patchesButton, &QPushButton::clicked, [=]() {
|
||
QString selectedOption = patchesComboBox->currentData().toString();
|
||
downloadPatches(selectedOption, true);
|
||
});
|
||
patchesControlLayout->addWidget(patchesButton);
|
||
|
||
QPushButton* saveButton = new QPushButton(tr("Save"));
|
||
connect(saveButton, &QPushButton::clicked, this, &CheatsPatches::onSaveButtonClicked);
|
||
|
||
patchesControlLayout->addWidget(saveButton);
|
||
|
||
patchesLayout->addLayout(patchesControlLayout);
|
||
patchesTab->setLayout(patchesLayout);
|
||
|
||
tabWidget->addTab(cheatsTab, tr("Cheats"));
|
||
tabWidget->addTab(patchesTab, tr("Patches"));
|
||
|
||
connect(tabWidget, &QTabWidget::currentChanged, this, [this](int index) {
|
||
if (index == 1) {
|
||
populateFileListPatches();
|
||
}
|
||
});
|
||
|
||
mainLayout->addWidget(gameInfoGroupBox, 1);
|
||
mainLayout->addWidget(tabWidget, 3);
|
||
|
||
manager = new QNetworkAccessManager(this);
|
||
|
||
setLayout(mainLayout);
|
||
}
|
||
|
||
void CheatsPatches::onSaveButtonClicked() {
|
||
// Get the name of the selected folder in the patchesListView
|
||
QString selectedPatchName;
|
||
QModelIndexList selectedIndexes = patchesListView->selectionModel()->selectedIndexes();
|
||
if (selectedIndexes.isEmpty()) {
|
||
QMessageBox::warning(this, tr("Error"), tr("No patch selected."));
|
||
return;
|
||
}
|
||
selectedPatchName = patchesListView->model()->data(selectedIndexes.first()).toString();
|
||
int separatorIndex = selectedPatchName.indexOf(" | ");
|
||
selectedPatchName = selectedPatchName.mid(separatorIndex + 3);
|
||
|
||
QString patchDir =
|
||
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string()) +
|
||
"/" + selectedPatchName;
|
||
|
||
QString filesJsonPath = patchDir + "/files.json";
|
||
QFile jsonFile(filesJsonPath);
|
||
if (!jsonFile.open(QIODevice::ReadOnly)) {
|
||
QMessageBox::critical(this, tr("Error"), tr("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, tr("Error"), tr("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, tr("Error"), tr("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;
|
||
bool foundPatchInfo = 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) {
|
||
foundPatchInfo = true;
|
||
isEnabled = checkBox->isChecked();
|
||
xmlWriter.writeAttribute("isEnabled", isEnabled ? "true" : "false");
|
||
}
|
||
}
|
||
if (!foundPatchInfo) {
|
||
auto maskIt = m_patchInfos.find(name + " (any version)");
|
||
if (maskIt != m_patchInfos.end()) {
|
||
QCheckBox* checkBox = findCheckBoxByName(maskIt->name);
|
||
if (checkBox) {
|
||
foundPatchInfo = true;
|
||
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) {
|
||
foundPatchInfo = true;
|
||
isEnabled = checkBox->isChecked();
|
||
}
|
||
}
|
||
if (!foundPatchInfo) {
|
||
auto maskIt = m_patchInfos.find(name + " (any version)");
|
||
if (maskIt != m_patchInfos.end()) {
|
||
QCheckBox* checkBox = findCheckBoxByName(maskIt->name);
|
||
if (checkBox) {
|
||
foundPatchInfo = true;
|
||
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, tr("Error"), tr("Unable to open the file for writing."));
|
||
return;
|
||
}
|
||
|
||
QTextStream textStream(&file);
|
||
textStream << newXmlData;
|
||
file.close();
|
||
|
||
if (xmlReader.hasError()) {
|
||
QMessageBox::critical(this, tr("Error"),
|
||
tr("Failed to parse XML: ") + "\n" + xmlReader.errorString());
|
||
} else {
|
||
QMessageBox::information(this, tr("Success"), tr("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().toStdString().find(name.toStdString()) != std::string::npos) {
|
||
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, tr("Invalid Source"),
|
||
QString(tr("The selected source is invalid.") + "\n%1").arg(source));
|
||
return;
|
||
}
|
||
|
||
QNetworkRequest request(url);
|
||
QNetworkReply* reply = manager->get(request);
|
||
|
||
connect(reply, &QNetworkReply::finished, [=]() {
|
||
if (reply->error() == QNetworkReply::NoError) {
|
||
QByteArray jsonData = reply->readAll();
|
||
bool foundFiles = false;
|
||
|
||
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;
|
||
|
||
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/";
|
||
}
|
||
|
||
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 (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, tr("File Exists"),
|
||
tr("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, tr("Error"),
|
||
QString(tr("Failed to save file:") + "\n%1")
|
||
.arg(localFilePath));
|
||
}
|
||
} else {
|
||
QMessageBox::warning(this, tr("Error"),
|
||
QString(tr("Failed to download file:") +
|
||
"%1\n\n" + tr("Error:") + "%2")
|
||
.arg(fileUrl)
|
||
.arg(fileReply->errorString()));
|
||
}
|
||
fileReply->deleteLater();
|
||
});
|
||
|
||
foundFiles = true;
|
||
}
|
||
}
|
||
if (!foundFiles && showMessageBox) {
|
||
QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
|
||
}
|
||
} 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, tr("File Exists"),
|
||
tr("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, tr("Error"),
|
||
QString(tr("Failed to save file:") + "\n%1").arg(filePath));
|
||
}
|
||
}
|
||
if (foundFiles && showMessageBox) {
|
||
QMessageBox::information(this, tr("Cheats Downloaded Successfully"),
|
||
tr("CheatsDownloadedSuccessfully_MSG"));
|
||
populateFileListCheats();
|
||
}
|
||
|
||
} else {
|
||
if (showMessageBox) {
|
||
QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
|
||
}
|
||
}
|
||
reply->deleteLater();
|
||
emit downloadFinished();
|
||
});
|
||
|
||
// connect(reply, &QNetworkReply::errorOccurred, [=](QNetworkReply::NetworkError code) {
|
||
// if (showMessageBox)
|
||
// QMessageBox::warning(this, "Download Error",
|
||
// QString("Error in response: %1").arg(reply->errorString()));
|
||
// });
|
||
}
|
||
|
||
void CheatsPatches::populateFileListPatches() {
|
||
QLayoutItem* item;
|
||
while ((item = patchesGroupBoxLayout->takeAt(0)) != nullptr) {
|
||
delete item->widget();
|
||
delete item;
|
||
}
|
||
m_patchInfos.clear();
|
||
|
||
QString patchesDir =
|
||
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string());
|
||
QDir dir(patchesDir);
|
||
|
||
QStringList folders = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||
QStringList matchingFiles;
|
||
|
||
foreach (const QString& folder, folders) {
|
||
QString folderPath = dir.filePath(folder);
|
||
QDir subDir(folderPath);
|
||
|
||
QString filesJsonPath = subDir.filePath("files.json");
|
||
QFile file(filesJsonPath);
|
||
|
||
if (file.open(QIODevice::ReadOnly)) {
|
||
QByteArray fileData = file.readAll();
|
||
file.close();
|
||
|
||
QJsonDocument jsonDoc(QJsonDocument::fromJson(fileData));
|
||
QJsonObject jsonObj = jsonDoc.object();
|
||
|
||
for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it) {
|
||
QString fileName = it.key();
|
||
QJsonArray serials = it.value().toArray();
|
||
|
||
if (serials.contains(QJsonValue(m_gameSerial))) {
|
||
QString fileEntry = fileName + " | " + folder;
|
||
if (!matchingFiles.contains(fileEntry)) {
|
||
matchingFiles << fileEntry;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
QStringListModel* model = new QStringListModel(matchingFiles, this);
|
||
patchesListView->setModel(model);
|
||
|
||
connect(
|
||
patchesListView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this]() {
|
||
QModelIndexList selectedIndexes = patchesListView->selectionModel()->selectedIndexes();
|
||
if (!selectedIndexes.isEmpty()) {
|
||
QString selectedText = selectedIndexes.first().data().toString();
|
||
addPatchesToLayout(selectedText);
|
||
}
|
||
});
|
||
|
||
if (!matchingFiles.isEmpty()) {
|
||
QModelIndex firstIndex = model->index(0, 0);
|
||
patchesListView->selectionModel()->select(firstIndex, QItemSelectionModel::Select |
|
||
QItemSelectionModel::Rows);
|
||
patchesListView->setCurrentIndex(firstIndex);
|
||
}
|
||
}
|
||
|
||
void CheatsPatches::downloadPatches(const QString repository, const bool showMessageBox) {
|
||
QString url;
|
||
if (repository == "GoldHEN") {
|
||
url = "https://github.com/GoldHEN/GoldHEN_Patch_Repository/tree/main/"
|
||
"patches/xml";
|
||
}
|
||
if (repository == "shadPS4") {
|
||
url = "https://github.com/shadps4-emu/ps4_cheats/tree/main/"
|
||
"PATCHES";
|
||
}
|
||
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));
|
||
QString fullPath = dir.filePath(repository);
|
||
if (!dir.exists(fullPath)) {
|
||
dir.mkpath(fullPath);
|
||
}
|
||
dir.setPath(fullPath);
|
||
|
||
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;
|
||
if (repository == "GoldHEN") {
|
||
fileUrl = QString("https://raw.githubusercontent.com/GoldHEN/"
|
||
"GoldHEN_Patch_Repository/main/%1")
|
||
.arg(filePath);
|
||
}
|
||
if (repository == "shadPS4") {
|
||
fileUrl = QString("https://raw.githubusercontent.com/shadps4-emu/"
|
||
"ps4_cheats/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 {
|
||
if (showMessageBox) {
|
||
QMessageBox::warning(
|
||
this, tr("Error"),
|
||
QString(tr("Failed to save:") + "\n%1").arg(fileName));
|
||
}
|
||
}
|
||
} else {
|
||
if (showMessageBox) {
|
||
QMessageBox::warning(
|
||
this, tr("Error"),
|
||
QString(tr("Failed to download:") + "\n%1").arg(fileUrl));
|
||
}
|
||
}
|
||
fileReply->deleteLater();
|
||
});
|
||
}
|
||
}
|
||
if (showMessageBox) {
|
||
QMessageBox::information(this, tr("Download Complete"),
|
||
QString(tr("DownloadComplete_MSG")));
|
||
}
|
||
|
||
// Create the files.json file with the identification of which file to open
|
||
createFilesJson(repository);
|
||
populateFileListPatches();
|
||
|
||
} else {
|
||
if (showMessageBox) {
|
||
QMessageBox::warning(this, tr("Error"),
|
||
tr("Failed to parse JSON data from HTML."));
|
||
}
|
||
}
|
||
} else {
|
||
if (showMessageBox) {
|
||
QMessageBox::warning(this, tr("Error"), tr("Failed to retrieve HTML page."));
|
||
}
|
||
}
|
||
emit downloadFinished();
|
||
});
|
||
}
|
||
|
||
void CheatsPatches::createFilesJson(const QString& repository) {
|
||
|
||
QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir));
|
||
QString fullPath = dir.filePath(repository);
|
||
if (!dir.exists(fullPath)) {
|
||
dir.mkpath(fullPath);
|
||
}
|
||
dir.setPath(fullPath);
|
||
|
||
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)) {
|
||
QMessageBox::warning(this, tr("ERROR"),
|
||
QString(tr("Failed to open file:") + "\n%1").arg(xmlFile));
|
||
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()) {
|
||
QMessageBox::warning(this, tr("ERROR"),
|
||
QString(tr("XML ERROR:") + "\n%1").arg(xmlReader.errorString()));
|
||
}
|
||
filesObject[xmlFile] = titleIdsArray;
|
||
}
|
||
|
||
QFile jsonFile(dir.absolutePath() + "/files.json");
|
||
if (!jsonFile.open(QIODevice::WriteOnly)) {
|
||
QMessageBox::warning(this, tr("ERROR"), tr("Failed to open files.json for writing"));
|
||
return;
|
||
}
|
||
|
||
QJsonDocument jsonDoc(filesObject);
|
||
jsonFile.write(jsonDoc.toJson());
|
||
jsonFile.close();
|
||
}
|
||
|
||
void CheatsPatches::addCheatsToLayout(const QJsonArray& modsArray, const QJsonArray& creditsArray) {
|
||
QLayoutItem* item;
|
||
while ((item = rightLayout->takeAt(0)) != nullptr) {
|
||
delete item->widget();
|
||
delete item;
|
||
}
|
||
m_cheats.clear();
|
||
m_cheatCheckBoxes.clear();
|
||
|
||
int maxWidthButton = 0;
|
||
|
||
for (const QJsonValue& modValue : modsArray) {
|
||
QJsonObject modObject = modValue.toObject();
|
||
QString modName = modObject["name"].toString();
|
||
QString modType = modObject["type"].toString();
|
||
|
||
Cheat cheat;
|
||
cheat.name = modName;
|
||
cheat.type = modType;
|
||
|
||
QJsonArray memoryArray = modObject["memory"].toArray();
|
||
for (const QJsonValue& memoryValue : memoryArray) {
|
||
QJsonObject memoryObject = memoryValue.toObject();
|
||
MemoryMod memoryMod;
|
||
memoryMod.offset = memoryObject["offset"].toString();
|
||
memoryMod.on = memoryObject["on"].toString();
|
||
memoryMod.off = memoryObject["off"].toString();
|
||
cheat.memoryMods.append(memoryMod);
|
||
}
|
||
|
||
// Check for the presence of 'hint' field
|
||
cheat.hasHint = modObject.contains("hint");
|
||
|
||
m_cheats[modName] = cheat;
|
||
|
||
if (modType == "checkbox") {
|
||
QCheckBox* cheatCheckBox = new QCheckBox(modName);
|
||
rightLayout->addWidget(cheatCheckBox);
|
||
m_cheatCheckBoxes.append(cheatCheckBox);
|
||
connect(cheatCheckBox, &QCheckBox::toggled,
|
||
[=](bool checked) { applyCheat(modName, checked); });
|
||
} else if (modType == "button") {
|
||
QPushButton* cheatButton = new QPushButton(modName);
|
||
cheatButton->adjustSize();
|
||
int buttonWidth = cheatButton->sizeHint().width();
|
||
if (buttonWidth > maxWidthButton) {
|
||
maxWidthButton = buttonWidth;
|
||
}
|
||
|
||
// Create a horizontal layout for buttons
|
||
QHBoxLayout* buttonLayout = new QHBoxLayout();
|
||
buttonLayout->setContentsMargins(0, 0, 0, 0);
|
||
buttonLayout->addWidget(cheatButton);
|
||
buttonLayout->addStretch();
|
||
|
||
rightLayout->addLayout(buttonLayout);
|
||
connect(cheatButton, &QPushButton::clicked, [=]() { applyCheat(modName, true); });
|
||
}
|
||
}
|
||
|
||
// Set minimum and fixed size for all buttons + 20
|
||
for (int i = 0; i < rightLayout->count(); ++i) {
|
||
QLayoutItem* layoutItem = rightLayout->itemAt(i);
|
||
QWidget* widget = layoutItem->widget();
|
||
if (widget) {
|
||
QPushButton* button = qobject_cast<QPushButton*>(widget);
|
||
if (button) {
|
||
button->setMinimumWidth(maxWidthButton);
|
||
button->setFixedWidth(maxWidthButton + 20);
|
||
}
|
||
} else {
|
||
QLayout* layout = layoutItem->layout();
|
||
if (layout) {
|
||
for (int j = 0; j < layout->count(); ++j) {
|
||
QLayoutItem* innerItem = layout->itemAt(j);
|
||
QWidget* innerWidget = innerItem->widget();
|
||
if (innerWidget) {
|
||
QPushButton* button = qobject_cast<QPushButton*>(innerWidget);
|
||
if (button) {
|
||
button->setMinimumWidth(maxWidthButton);
|
||
button->setFixedWidth(maxWidthButton + 20);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Set credits label
|
||
QLabel* creditsLabel = new QLabel();
|
||
QString creditsText = tr("Author: ");
|
||
if (!creditsArray.isEmpty()) {
|
||
creditsText += creditsArray[0].toString();
|
||
}
|
||
creditsLabel->setText(creditsText);
|
||
creditsLabel->setAlignment(Qt::AlignLeft);
|
||
rightLayout->addWidget(creditsLabel);
|
||
}
|
||
|
||
void CheatsPatches::populateFileListCheats() {
|
||
QString cheatsDir =
|
||
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::CheatsDir).string());
|
||
QString pattern = m_gameSerial + "_" + m_gameVersion + "*.json";
|
||
|
||
QDir dir(cheatsDir);
|
||
QStringList filters;
|
||
filters << pattern;
|
||
dir.setNameFilters(filters);
|
||
|
||
QFileInfoList fileList = dir.entryInfoList(QDir::Files);
|
||
QStringList fileNames;
|
||
|
||
for (const QFileInfo& fileInfo : fileList) {
|
||
fileNames << fileInfo.fileName();
|
||
}
|
||
|
||
QStringListModel* model = new QStringListModel(fileNames, this);
|
||
listView_selectFile->setModel(model);
|
||
|
||
connect(listView_selectFile->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||
[this]() {
|
||
QModelIndexList selectedIndexes =
|
||
listView_selectFile->selectionModel()->selectedIndexes();
|
||
if (!selectedIndexes.isEmpty()) {
|
||
|
||
QString selectedFileName = selectedIndexes.first().data().toString();
|
||
QString cheatsDir = QString::fromStdString(
|
||
Common::FS::GetUserPath(Common::FS::PathType::CheatsDir).string());
|
||
|
||
QFile file(cheatsDir + "/" + selectedFileName);
|
||
if (file.open(QIODevice::ReadOnly)) {
|
||
QByteArray jsonData = file.readAll();
|
||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
|
||
QJsonObject jsonObject = jsonDoc.object();
|
||
QJsonArray modsArray = jsonObject["mods"].toArray();
|
||
QJsonArray creditsArray = jsonObject["credits"].toArray();
|
||
addCheatsToLayout(modsArray, creditsArray);
|
||
}
|
||
}
|
||
});
|
||
|
||
if (!fileNames.isEmpty()) {
|
||
QModelIndex firstIndex = model->index(0, 0);
|
||
listView_selectFile->selectionModel()->select(firstIndex, QItemSelectionModel::Select |
|
||
QItemSelectionModel::Rows);
|
||
listView_selectFile->setCurrentIndex(firstIndex);
|
||
}
|
||
}
|
||
|
||
void CheatsPatches::addPatchesToLayout(const QString& filePath) {
|
||
if (filePath == "") {
|
||
return;
|
||
}
|
||
QString folderPath = filePath.section(" | ", 1, 1);
|
||
|
||
// Clear existing layout items
|
||
QLayoutItem* item;
|
||
while ((item = patchesGroupBoxLayout->takeAt(0)) != nullptr) {
|
||
delete item->widget();
|
||
delete item;
|
||
}
|
||
m_patchInfos.clear();
|
||
|
||
QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir));
|
||
QString fullPath = dir.filePath(folderPath);
|
||
|
||
if (!dir.exists(fullPath)) {
|
||
QMessageBox::warning(this, tr("ERROR"),
|
||
QString(tr("Directory does not exist:") + "\n%1").arg(fullPath));
|
||
return;
|
||
}
|
||
dir.setPath(fullPath);
|
||
|
||
QString filesJsonPath = dir.filePath("files.json");
|
||
|
||
QFile jsonFile(filesJsonPath);
|
||
if (!jsonFile.open(QIODevice::ReadOnly)) {
|
||
QMessageBox::warning(this, tr("ERROR"), tr("Failed to open files.json for reading."));
|
||
return;
|
||
}
|
||
|
||
QByteArray jsonData = jsonFile.readAll();
|
||
jsonFile.close();
|
||
|
||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
|
||
QJsonObject jsonObject = jsonDoc.object();
|
||
|
||
bool patchAdded = false;
|
||
|
||
// Iterate over each entry in the JSON file
|
||
for (auto it = jsonObject.constBegin(); it != jsonObject.constEnd(); ++it) {
|
||
QString xmlFileName = it.key();
|
||
QJsonArray idsArray = it.value().toArray();
|
||
|
||
// Check if the serial is in the ID list
|
||
if (idsArray.contains(QJsonValue(m_gameSerial))) {
|
||
QString xmlFilePath = dir.filePath(xmlFileName);
|
||
QFile xmlFile(xmlFilePath);
|
||
|
||
if (!xmlFile.open(QIODevice::ReadOnly)) {
|
||
QMessageBox::warning(
|
||
this, tr("ERROR"),
|
||
QString(tr("Failed to open file:") + "\n%1").arg(xmlFile.fileName()));
|
||
continue;
|
||
}
|
||
QXmlStreamReader xmlReader(&xmlFile);
|
||
QString patchName;
|
||
QString patchAuthor;
|
||
QString patchNote;
|
||
QJsonArray patchLines;
|
||
bool isEnabled = false;
|
||
|
||
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();
|
||
isEnabled =
|
||
attributes.value("isEnabled").toString() == QStringLiteral("true");
|
||
}
|
||
if (appVer == "mask") {
|
||
patchName = attributes.value("Name").toString() + " (any version)";
|
||
patchAuthor = attributes.value("Author").toString();
|
||
patchNote = attributes.value("Note").toString();
|
||
isEnabled =
|
||
attributes.value("isEnabled").toString() == QStringLiteral("true");
|
||
}
|
||
} 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()) {
|
||
QCheckBox* patchCheckBox = new QCheckBox(patchName);
|
||
patchCheckBox->setChecked(isEnabled);
|
||
patchesGroupBoxLayout->addWidget(patchCheckBox);
|
||
|
||
PatchInfo patchInfo;
|
||
patchInfo.name = patchName;
|
||
patchInfo.author = patchAuthor;
|
||
patchInfo.note = patchNote;
|
||
patchInfo.linesArray = patchLines;
|
||
patchInfo.serial = m_gameSerial;
|
||
m_patchInfos[patchName] = patchInfo;
|
||
|
||
patchCheckBox->installEventFilter(this);
|
||
|
||
connect(patchCheckBox, &QCheckBox::toggled,
|
||
[=](bool checked) { applyPatch(patchName, checked); });
|
||
|
||
patchName.clear();
|
||
patchAuthor.clear();
|
||
patchNote.clear();
|
||
patchLines = QJsonArray();
|
||
patchAdded = true;
|
||
}
|
||
}
|
||
xmlFile.close();
|
||
}
|
||
}
|
||
|
||
// Remove the item from the list view if no patches were added (the game has patches, but not
|
||
// for the current version)
|
||
if (!patchAdded) {
|
||
QStringListModel* model = qobject_cast<QStringListModel*>(patchesListView->model());
|
||
if (model) {
|
||
QStringList items = model->stringList();
|
||
int index = items.indexOf(filePath);
|
||
if (index != -1) {
|
||
items.removeAt(index);
|
||
model->setStringList(items);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void CheatsPatches::updateNoteTextEdit(const QString& patchName) {
|
||
if (m_patchInfos.contains(patchName)) {
|
||
const PatchInfo& patchInfo = m_patchInfos[patchName];
|
||
QString text = QString(tr("Name:") + " %1\n" + tr("Author:") + " %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));
|
||
}
|
||
text.replace("\\n", "\n");
|
||
instructionsTextEdit->setText(text);
|
||
}
|
||
}
|
||
|
||
bool showErrorMessage = true;
|
||
void CheatsPatches::uncheckAllCheatCheckBoxes() {
|
||
for (auto& cheatCheckBox : m_cheatCheckBoxes) {
|
||
cheatCheckBox->setChecked(false);
|
||
}
|
||
showErrorMessage = true;
|
||
}
|
||
|
||
void CheatsPatches::applyCheat(const QString& modName, bool enabled) {
|
||
if (!m_cheats.contains(modName))
|
||
return;
|
||
|
||
Cheat cheat = m_cheats[modName];
|
||
|
||
for (const MemoryMod& memoryMod : cheat.memoryMods) {
|
||
QString value = enabled ? memoryMod.on : memoryMod.off;
|
||
|
||
std::string modNameStr = modName.toStdString();
|
||
std::string offsetStr = memoryMod.offset.toStdString();
|
||
std::string valueStr = value.toStdString();
|
||
|
||
if (MemoryPatcher::g_eboot_address == 0) {
|
||
MemoryPatcher::patchInfo addingPatch;
|
||
addingPatch.modNameStr = modNameStr;
|
||
addingPatch.offsetStr = offsetStr;
|
||
addingPatch.valueStr = valueStr;
|
||
addingPatch.isOffset = true;
|
||
|
||
MemoryPatcher::AddPatchToQueue(addingPatch);
|
||
continue;
|
||
}
|
||
// Determine if the hint field is present
|
||
bool isHintPresent = m_cheats[modName].hasHint;
|
||
MemoryPatcher::PatchMemory(modNameStr, offsetStr, valueStr, !isHintPresent, false);
|
||
}
|
||
}
|
||
|
||
void CheatsPatches::applyPatch(const QString& patchName, bool enabled) {
|
||
if (!enabled)
|
||
return;
|
||
if (m_patchInfos.contains(patchName)) {
|
||
const PatchInfo& patchInfo = m_patchInfos[patchName];
|
||
|
||
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();
|
||
QString maskOffsetStr = lineObject["Offset"].toString();
|
||
|
||
patchValue = MemoryPatcher::convertValueToHex(type, patchValue);
|
||
|
||
bool littleEndian = false;
|
||
|
||
if (type == "bytes16") {
|
||
littleEndian = true;
|
||
} else if (type == "bytes32") {
|
||
littleEndian = true;
|
||
} else if (type == "bytes64") {
|
||
littleEndian = true;
|
||
}
|
||
|
||
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
|
||
int maskOffsetValue = 0;
|
||
|
||
if (type == "mask") {
|
||
patchMask = MemoryPatcher::PatchMask::Mask;
|
||
|
||
// im not sure if this works, there is no games to test the mask offset on yet
|
||
if (!maskOffsetStr.toStdString().empty())
|
||
maskOffsetValue = std::stoi(maskOffsetStr.toStdString(), 0, 10);
|
||
}
|
||
|
||
if (type == "mask_jump32")
|
||
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
|
||
|
||
if (MemoryPatcher::g_eboot_address == 0) {
|
||
MemoryPatcher::patchInfo addingPatch;
|
||
addingPatch.gameSerial = patchInfo.serial.toStdString();
|
||
addingPatch.modNameStr = patchName.toStdString();
|
||
addingPatch.offsetStr = address.toStdString();
|
||
addingPatch.valueStr = patchValue.toStdString();
|
||
addingPatch.isOffset = false;
|
||
addingPatch.littleEndian = littleEndian;
|
||
addingPatch.patchMask = patchMask;
|
||
addingPatch.maskOffset = maskOffsetValue;
|
||
|
||
MemoryPatcher::AddPatchToQueue(addingPatch);
|
||
continue;
|
||
}
|
||
MemoryPatcher::PatchMemory(patchName.toStdString(), address.toStdString(),
|
||
patchValue.toStdString(), false, littleEndian, patchMask);
|
||
}
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
} |