Enable patches on cli builds (#897)
Some checks are pending
Reuse / reuse (push) Waiting to run
Clang Format / clang-format (push) Waiting to run
Linux-Qt / build (push) Waiting to run
Linux / build (push) Waiting to run
macOS-Qt / build (push) Waiting to run
macOS / build (push) Waiting to run
Windows-Qt / build (push) Waiting to run
Windows / build (push) Waiting to run

* patch support for cli

* fix mac build

* format
This commit is contained in:
CrazyBloo 2024-09-13 00:44:20 -04:00 committed by GitHub
parent de183d3b80
commit ab201398b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 164 additions and 50 deletions

View file

@ -376,6 +376,8 @@ set(COMMON src/common/logging/backend.cpp
src/common/version.h
src/common/ntapi.h
src/common/ntapi.cpp
src/common/memory_patcher.h
src/common/memory_patcher.cpp
src/common/scm_rev.cpp
src/common/scm_rev.h
)
@ -628,8 +630,6 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
src/qt_gui/about_dialog.ui
src/qt_gui/cheats_patches.cpp
src/qt_gui/cheats_patches.h
src/qt_gui/memory_patcher.cpp
src/qt_gui/memory_patcher.h
src/qt_gui/main_window_ui.h
src/qt_gui/main_window.cpp
src/qt_gui/main_window.h

View file

@ -2,14 +2,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <codecvt>
#include <sstream>
#include <string>
#include <pugixml.hpp>
#ifdef ENABLE_QT_GUI
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QListView>
#include <QMessageBox>
#include <QString>
#include <QXmlStreamReader>
#endif
#include "common/logging/log.h"
#include "common/path_util.h"
#include "memory_patcher.h"
@ -17,78 +23,169 @@
namespace MemoryPatcher {
uintptr_t g_eboot_address;
u64 g_eboot_image_size;
uint64_t g_eboot_image_size;
std::string g_game_serial;
std::string patchFile;
std::vector<patchInfo> pending_patches;
QString toHex(unsigned long long value, size_t byteSize) {
std::string 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());
return ss.str();
}
QString convertValueToHex(const QString& type, const QString& valueStr) {
QString result;
std::string typeStr = type.toStdString();
std::string valueStrStd = valueStr.toStdString();
std::string convertValueToHex(const std::string type, const std::string valueStr) {
std::string result;
if (typeStr == "byte") {
unsigned int value = std::stoul(valueStrStd, nullptr, 16);
if (type == "byte") {
unsigned int value = std::stoul(valueStr, nullptr, 16);
result = toHex(value, 1);
} else if (typeStr == "bytes16") {
unsigned int value = std::stoul(valueStrStd, nullptr, 16);
} else if (type == "bytes16") {
unsigned int value = std::stoul(valueStr, nullptr, 16);
result = toHex(value, 2);
} else if (typeStr == "bytes32") {
unsigned long value = std::stoul(valueStrStd, nullptr, 16);
} else if (type == "bytes32") {
unsigned long value = std::stoul(valueStr, nullptr, 16);
result = toHex(value, 4);
} else if (typeStr == "bytes64") {
unsigned long long value = std::stoull(valueStrStd, nullptr, 16);
} else if (type == "bytes64") {
unsigned long long value = std::stoull(valueStr, nullptr, 16);
result = toHex(value, 8);
} else if (typeStr == "float32") {
} else if (type == "float32") {
union {
float f;
uint32_t i;
} floatUnion;
floatUnion.f = std::stof(valueStrStd);
floatUnion.f = std::stof(valueStr);
result = toHex(floatUnion.i, sizeof(floatUnion.i));
} else if (typeStr == "float64") {
} else if (type == "float64") {
union {
double d;
uint64_t i;
} doubleUnion;
doubleUnion.d = std::stod(valueStrStd);
doubleUnion.d = std::stod(valueStr);
result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
} else if (typeStr == "utf8") {
QByteArray byteArray = QString::fromStdString(valueStrStd).toUtf8();
byteArray.append('\0');
} else if (type == "utf8") {
std::vector<unsigned char> byteArray =
std::vector<unsigned char>(valueStr.begin(), valueStr.end());
byteArray.push_back('\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 = ss.str();
} else if (type == "utf16") {
std::wstring wide_str(valueStr.size(), L'\0');
std::mbstowcs(&wide_str[0], valueStr.c_str(), valueStr.size());
wide_str.resize(std::wcslen(wide_str.c_str()));
std::u16string valueStringU16;
for (wchar_t wc : wide_str) {
if (wc <= 0xFFFF) {
valueStringU16.push_back(static_cast<char16_t>(wc));
} else {
wc -= 0x10000;
valueStringU16.push_back(static_cast<char16_t>(0xD800 | (wc >> 10)));
valueStringU16.push_back(static_cast<char16_t>(0xDC00 | (wc & 0x3FF)));
}
result = QString::fromStdString(ss.str());
} else if (typeStr == "bytes") {
}
std::vector<unsigned char> byteArray;
// convert to little endian
for (char16_t ch : valueStringU16) {
unsigned char low_byte = static_cast<unsigned char>(ch & 0x00FF);
unsigned char high_byte = static_cast<unsigned char>((ch >> 8) & 0x00FF);
byteArray.push_back(low_byte);
byteArray.push_back(high_byte);
}
byteArray.push_back('\0');
byteArray.push_back('\0');
std::stringstream ss;
for (unsigned char ch : byteArray) {
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(ch);
}
result = ss.str();
} else if (type == "bytes") {
result = valueStr;
} else if (typeStr == "mask" || typeStr == "mask_jump32") {
} else if (type == "mask" || type == "mask_jump32") {
result = valueStr;
} else {
LOG_INFO(Loader, "Error applying Patch, unknown type: {}", typeStr);
LOG_INFO(Loader, "Error applying Patch, unknown type: {}", type);
}
return result;
}
void OnGameLoaded() {
if (!patchFile.empty()) {
std::string patchDir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string();
std::string filePath = patchDir + "/" + patchFile;
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(filePath.c_str());
if (result) {
auto patchXML = doc.child("Patch");
for (pugi::xml_node_iterator it = patchXML.children().begin();
it != patchXML.children().end(); ++it) {
if (std::string(it->name()) == "Metadata") {
if (std::string(it->attribute("isEnabled").value()) == "true") {
auto patchList = it->first_child();
std::string currentPatchName = it->attribute("Name").value();
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
patchLineIt != patchList.children().end(); ++patchLineIt) {
std::string type = patchLineIt->attribute("Type").value();
std::string address = patchLineIt->attribute("Address").value();
std::string patchValue = patchLineIt->attribute("Value").value();
std::string maskOffsetStr = patchLineIt->attribute("type").value();
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.empty())
maskOffsetValue = std::stoi(maskOffsetStr, 0, 10);
}
if (type == "mask_jump32")
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
MemoryPatcher::PatchMemory(currentPatchName, address, patchValue, false,
littleEndian, patchMask);
}
}
}
}
} else
LOG_ERROR(Loader, "couldnt patch parse xml : {}", result.description());
ApplyPendingPatches();
return;
}
#ifdef ENABLE_QT_GUI
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
QString patchDir =
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string());
@ -190,7 +287,8 @@ void OnGameLoaded() {
QString patchValue = lineObject["Value"].toString();
QString maskOffsetStr = lineObject["Offset"].toString();
patchValue = convertValueToHex(type, patchValue);
patchValue = QString::fromStdString(
convertValueToHex(type.toStdString(), patchValue.toStdString()));
bool littleEndian = false;
@ -233,6 +331,7 @@ void OnGameLoaded() {
}
ApplyPendingPatches();
}
#endif
}
void AddPatchToQueue(patchInfo patchToAdd) {

View file

@ -5,13 +5,13 @@
#include <cstring>
#include <string>
#include <vector>
#include <QString>
namespace MemoryPatcher {
extern uintptr_t g_eboot_address;
extern u64 g_eboot_image_size;
extern uint64_t g_eboot_image_size;
extern std::string g_game_serial;
extern std::string patchFile;
enum PatchMask : uint8_t {
None,
@ -32,7 +32,7 @@ struct patchInfo {
extern std::vector<patchInfo> pending_patches;
QString convertValueToHex(const QString& type, const QString& valueStr);
std::string convertValueToHex(const std::string type, const std::string valueStr);
void OnGameLoaded();
void AddPatchToQueue(patchInfo patchToAdd);

View file

@ -6,9 +6,7 @@
#include "common/arch.h"
#include "common/assert.h"
#include "common/logging/log.h"
#ifdef ENABLE_QT_GUI
#include "qt_gui/memory_patcher.h"
#endif
#include "common/memory_patcher.h"
#include "common/string_util.h"
#include "core/aerolib/aerolib.h"
#include "core/cpu_patches.h"
@ -199,7 +197,6 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
const VAddr entry_addr = base_virtual_addr + elf.GetElfEntry();
LOG_INFO(Core_Linker, "program entry addr ..........: {:#018x}", entry_addr);
#ifdef ENABLE_QT_GUI
if (MemoryPatcher::g_eboot_address == 0) {
if (name == "eboot") {
MemoryPatcher::g_eboot_address = base_virtual_addr;
@ -207,7 +204,6 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
MemoryPatcher::OnGameLoaded();
}
}
#endif
}
void Module::LoadDynamicInfo() {

View file

@ -8,7 +8,7 @@
#include "common/logging/backend.h"
#include "common/logging/log.h"
#ifdef ENABLE_QT_GUI
#include "qt_gui/memory_patcher.h"
#include "common/memory_patcher.h"
#endif
#include "common/ntapi.h"
#include "common/path_util.h"

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/core.h>
#include "common/memory_patcher.h"
#include "emulator.h"
int main(int argc, char* argv[]) {
@ -10,7 +11,16 @@ int main(int argc, char* argv[]) {
return -1;
}
for (int i = 0; i < argc; i++) {
std::string curArg = argv[i];
if (curArg == "-p") {
std::string patchFile = argv[i + 1];
MemoryPatcher::patchFile = patchFile;
}
}
Core::Emulator emulator;
emulator.Run(argv[1]);
return 0;
}

View file

@ -28,9 +28,9 @@
#include <QXmlStreamReader>
#include <common/logging/log.h>
#include "cheats_patches.h"
#include "common/memory_patcher.h"
#include "common/path_util.h"
#include "core/module.h"
#include "qt_gui/memory_patcher.h"
using namespace Common::FS;
@ -1178,7 +1178,8 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) {
QString patchValue = lineObject["Value"].toString();
QString maskOffsetStr = lineObject["Offset"].toString();
patchValue = MemoryPatcher::convertValueToHex(type, patchValue);
patchValue = QString::fromStdString(
MemoryPatcher::convertValueToHex(type.toStdString(), patchValue.toStdString()));
bool littleEndian = false;

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
#include "common/memory_patcher.h"
#include "core/file_sys/fs.h"
#include "emulator.h"
#include "game_install_dialog.h"
@ -36,6 +37,13 @@ int main(int argc, char* argv[]) {
// Check for command line arguments
if (has_command_line_argument) {
Core::Emulator emulator;
for (int i = 0; i < argc; i++) {
std::string curArg = argv[i];
if (curArg == "-p") {
std::string patchFile = argv[i + 1];
MemoryPatcher::patchFile = patchFile;
}
}
emulator.Run(argv[1]);
}