From ab201398b2d6dd781527db3d411aef71750dc975 Mon Sep 17 00:00:00 2001 From: CrazyBloo Date: Fri, 13 Sep 2024 00:44:20 -0400 Subject: [PATCH] Enable patches on cli builds (#897) * patch support for cli * fix mac build * format --- CMakeLists.txt | 4 +- src/{qt_gui => common}/memory_patcher.cpp | 173 +++++++++++++++++----- src/{qt_gui => common}/memory_patcher.h | 6 +- src/core/module.cpp | 6 +- src/emulator.cpp | 2 +- src/main.cpp | 10 ++ src/qt_gui/cheats_patches.cpp | 5 +- src/qt_gui/main.cpp | 8 + 8 files changed, 164 insertions(+), 50 deletions(-) rename src/{qt_gui => common}/memory_patcher.cpp (66%) rename src/{qt_gui => common}/memory_patcher.h (85%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cbfe962..ee719f68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/qt_gui/memory_patcher.cpp b/src/common/memory_patcher.cpp similarity index 66% rename from src/qt_gui/memory_patcher.cpp rename to src/common/memory_patcher.cpp index d5ffa1c9..85a25167 100644 --- a/src/qt_gui/memory_patcher.cpp +++ b/src/common/memory_patcher.cpp @@ -2,14 +2,20 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include +#include +#ifdef ENABLE_QT_GUI #include #include #include #include #include #include +#include #include +#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 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 byteArray = + std::vector(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(c); } - result = QString::fromStdString(ss.str()); - } else if (typeStr == "utf16") { - QByteArray byteArray( - reinterpret_cast(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(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(wc)); + } else { + wc -= 0x10000; + valueStringU16.push_back(static_cast(0xD800 | (wc >> 10))); + valueStringU16.push_back(static_cast(0xDC00 | (wc & 0x3FF))); + } } - result = QString::fromStdString(ss.str()); - } else if (typeStr == "bytes") { + + std::vector byteArray; + // convert to little endian + for (char16_t ch : valueStringU16) { + unsigned char low_byte = static_cast(ch & 0x00FF); + unsigned char high_byte = static_cast((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(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) { diff --git a/src/qt_gui/memory_patcher.h b/src/common/memory_patcher.h similarity index 85% rename from src/qt_gui/memory_patcher.h rename to src/common/memory_patcher.h index 821184ad..899ffccb 100644 --- a/src/qt_gui/memory_patcher.h +++ b/src/common/memory_patcher.h @@ -5,13 +5,13 @@ #include #include #include -#include 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 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); diff --git a/src/core/module.cpp b/src/core/module.cpp index c28ac106..575f9974 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -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() { diff --git a/src/emulator.cpp b/src/emulator.cpp index 5112ffd6..cc9cbbd9 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -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" diff --git a/src/main.cpp b/src/main.cpp index 9df14f13..cc64d9f7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#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; } diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index 0b3dc3ce..a58106cb 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -28,9 +28,9 @@ #include #include #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; diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index abbf6dcb..68a674f5 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -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]); }