Merge pull request #7 from CrazyBloo/Cheats_Patches

Load saved patches, fix issue with mask type patches not saving
This commit is contained in:
DanielSvoboda 2024-08-27 10:24:27 -03:00 committed by GitHub
commit 62408a0dbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 266 additions and 70 deletions

View file

@ -659,6 +659,7 @@ endif()
if (ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network)
add_definitions(-DENABLE_QT_GUI)
endif()
if (WIN32)

View file

@ -4,6 +4,16 @@
#include <algorithm>
#include <string>
#include "common/logging/log.h"
#include "common/path_util.h"
#ifdef ENABLE_QT_GUI
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QListView>
#include <QMessageBox>
#include <QXmlStreamReader>
#endif
#include "memory_patcher.h"
namespace MemoryPatcher {
@ -11,9 +21,224 @@ namespace MemoryPatcher {
uintptr_t g_eboot_address;
u64 g_eboot_image_size;
std::string g_game_serial;
std::vector<patchInfo> pending_patches;
#ifdef ENABLE_QT_GUI
QString toHex(unsigned long long value, size_t byteSize) {
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(byteSize * 2) << value;
return QString::fromStdString(ss.str());
}
QString convertValueToHex(const QString& type, const QString& valueStr) {
QString result;
std::string typeStr = type.toStdString();
std::string valueStrStd = valueStr.toStdString();
if (typeStr == "byte") {
unsigned int value = std::stoul(valueStrStd, nullptr, 16);
result = toHex(value, 1);
} else if (typeStr == "bytes16") {
unsigned int value = std::stoul(valueStrStd, nullptr, 16);
result = toHex(value, 2);
} else if (typeStr == "bytes32") {
unsigned long value = std::stoul(valueStrStd, nullptr, 16);
result = toHex(value, 4);
} else if (typeStr == "bytes64") {
unsigned long long value = std::stoull(valueStrStd, nullptr, 16);
result = toHex(value, 8);
} else if (typeStr == "float32") {
union {
float f;
uint32_t i;
} floatUnion;
floatUnion.f = std::stof(valueStrStd);
result = toHex(floatUnion.i, sizeof(floatUnion.i));
} else if (typeStr == "float64") {
union {
double d;
uint64_t i;
} doubleUnion;
doubleUnion.d = std::stod(valueStrStd);
result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
} else if (typeStr == "utf8") {
QByteArray byteArray = QString::fromStdString(valueStrStd).toUtf8();
byteArray.append('\0');
std::stringstream ss;
for (unsigned char c : byteArray) {
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c);
}
result = QString::fromStdString(ss.str());
} else if (typeStr == "utf16") {
QByteArray byteArray(
reinterpret_cast<const char*>(QString::fromStdString(valueStrStd).utf16()),
QString::fromStdString(valueStrStd).size() * 2);
byteArray.append('\0');
byteArray.append('\0');
std::stringstream ss;
for (unsigned char c : byteArray) {
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c);
}
result = QString::fromStdString(ss.str());
} else if (typeStr == "bytes") {
result = valueStr;
} else if (typeStr == "mask" || typeStr == "mask_jump32") {
result = valueStr;
} else {
LOG_INFO(Loader, "Error applying Patch, unknown type: {}", typeStr);
}
return result;
}
#endif
void OnGameLoaded() {
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
#ifdef ENABLE_QT_GUI
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)) {
LOG_ERROR(Loader, "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 = QString::fromStdString(g_game_serial);
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()) {
LOG_ERROR(Loader, "No patch file found for the current serial.");
return;
}
QString filePath = patchDir + "/" + selectedFileName;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_ERROR(Loader, "Unable to open the file for reading.");
return;
}
QByteArray xmlData = file.readAll();
file.close();
QString newXmlData;
QXmlStreamReader xmlReader(xmlData);
bool insideMetadata = false;
bool isEnabled = false;
while (!xmlReader.atEnd()) {
xmlReader.readNext();
if (xmlReader.isStartElement()) {
std::string currentPatchName;
QJsonArray patchLines;
if (xmlReader.name() == QStringLiteral("Metadata")) {
insideMetadata = true;
QString name = xmlReader.attributes().value("Name").toString();
currentPatchName = name.toStdString();
// Check and update the isEnabled attribute
for (const QXmlStreamAttribute& attr : xmlReader.attributes()) {
if (attr.name() == QStringLiteral("isEnabled")) {
if (attr.value().toString() == "true") {
isEnabled = true;
} else
isEnabled = false;
}
}
} 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 (isEnabled) {
foreach (const QJsonValue& value, patchLines) {
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 = 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;
MemoryPatcher::PatchMemory(currentPatchName, address.toStdString(),
patchValue.toStdString(), false, littleEndian,
patchMask);
}
}
}
}
}
if (xmlReader.hasError()) {
LOG_ERROR(Loader, "Failed to parse XML for {}", g_game_serial);
} else {
LOG_INFO(Loader, "Patches loaded successfully");
}
#endif
ApplyPendingPatches();
}
void AddPatchToQueue(patchInfo patchToAdd) {
pending_patches.push_back(patchToAdd);
}

View file

@ -5,6 +5,9 @@
#include <cstring>
#include <string>
#include <vector>
#ifdef ENABLE_QT_GUI
#include <QString>
#endif
namespace MemoryPatcher {
@ -31,6 +34,11 @@ struct patchInfo {
extern std::vector<patchInfo> pending_patches;
#ifdef ENABLE_QT_GUI
QString convertValueToHex(const QString& type, const QString& valueStr);
#endif
void OnGameLoaded();
void AddPatchToQueue(patchInfo patchToAdd);
void ApplyPendingPatches();

View file

@ -198,7 +198,7 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
if (name == "eboot") {
MemoryPatcher::g_eboot_address = base_virtual_addr;
MemoryPatcher::g_eboot_image_size = base_size;
MemoryPatcher::ApplyPendingPatches();
MemoryPatcher::OnGameLoaded();
}
}
}

View file

@ -300,6 +300,7 @@ void CheatsPatches::onSaveButtonClicked() {
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()) {
@ -309,10 +310,24 @@ void CheatsPatches::onSaveButtonClicked() {
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());
}
@ -323,9 +338,20 @@ void CheatsPatches::onSaveButtonClicked() {
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 {
@ -369,7 +395,7 @@ QCheckBox* CheatsPatches::findCheckBoxByName(const QString& name) {
QWidget* widget = item->widget();
QCheckBox* checkBox = qobject_cast<QCheckBox*>(widget);
if (checkBox) {
if (checkBox->text() == name) {
if (checkBox->text().toStdString().find(name.toStdString()) != std::string::npos) {
return checkBox;
}
}
@ -1030,6 +1056,8 @@ void CheatsPatches::applyCheat(const QString& modName, bool enabled) {
}
void CheatsPatches::applyPatch(const QString& patchName, bool enabled) {
if (!enabled)
return;
if (m_patchInfos.contains(patchName)) {
const PatchInfo& patchInfo = m_patchInfos[patchName];
@ -1040,7 +1068,7 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) {
QString patchValue = lineObject["Value"].toString();
QString maskOffsetStr = lineObject["Offset"].toString();
patchValue = convertValueToHex(type, patchValue);
patchValue = MemoryPatcher::convertValueToHex(type, patchValue);
bool littleEndian = false;
@ -1086,71 +1114,6 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) {
}
}
}
QString toHex(unsigned long long value, size_t byteSize) {
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(byteSize * 2) << value;
return QString::fromStdString(ss.str());
}
QString CheatsPatches::convertValueToHex(const QString& type, const QString& valueStr) {
QString result;
std::string typeStr = type.toStdString();
std::string valueStrStd = valueStr.toStdString();
if (typeStr == "byte") {
unsigned int value = std::stoul(valueStrStd, nullptr, 16);
result = toHex(value, 1);
} else if (typeStr == "bytes16") {
unsigned int value = std::stoul(valueStrStd, nullptr, 16);
result = toHex(value, 2);
} else if (typeStr == "bytes32") {
unsigned long value = std::stoul(valueStrStd, nullptr, 16);
result = toHex(value, 4);
} else if (typeStr == "bytes64") {
unsigned long long value = std::stoull(valueStrStd, nullptr, 16);
result = toHex(value, 8);
} else if (typeStr == "float32") {
union {
float f;
uint32_t i;
} floatUnion;
floatUnion.f = std::stof(valueStrStd);
result = toHex(floatUnion.i, sizeof(floatUnion.i));
} else if (typeStr == "float64") {
union {
double d;
uint64_t i;
} doubleUnion;
doubleUnion.d = std::stod(valueStrStd);
result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
} else if (typeStr == "utf8") {
QByteArray byteArray = QString::fromStdString(valueStrStd).toUtf8();
byteArray.append('\0');
std::stringstream ss;
for (unsigned char c : byteArray) {
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c);
}
result = QString::fromStdString(ss.str());
} else if (typeStr == "utf16") {
QByteArray byteArray(
reinterpret_cast<const char*>(QString::fromStdString(valueStrStd).utf16()),
QString::fromStdString(valueStrStd).size() * 2);
byteArray.append('\0');
byteArray.append('\0');
std::stringstream ss;
for (unsigned char c : byteArray) {
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c);
}
result = QString::fromStdString(ss.str());
} else if (typeStr == "bytes") {
result = valueStr;
} else if (typeStr == "mask" || typeStr == "mask_jump32") {
result = valueStr;
} else {
LOG_INFO(Loader, "Error applying Patch, unknown type: {}", typeStr);
}
return result;
}
bool CheatsPatches::eventFilter(QObject* obj, QEvent* event) {
if (event->type() == QEvent::HoverEnter || event->type() == QEvent::HoverLeave) {

View file

@ -58,7 +58,6 @@ private:
void applyCheat(const QString& modName, bool enabled);
void applyPatch(const QString& patchName, bool enabled);
QString convertValueToHex(const QString& type, const QString& valueStr);
// Event Filtering
bool eventFilter(QObject* obj, QEvent* event);