From 5377ae9c37f2b41b3d781602afd349693fbfa493 Mon Sep 17 00:00:00 2001 From: CrazyBloo Date: Sat, 24 Aug 2024 07:11:12 -0400 Subject: [PATCH] Implement pattern scanning for mask type patches --- src/common/memory_patcher.cpp | 77 +++++++++++++++++++++++++++++++---- src/common/memory_patcher.h | 14 ++++++- src/core/module.cpp | 1 + src/qt_gui/cheats_patches.cpp | 36 ++++++++++++---- 4 files changed, 113 insertions(+), 15 deletions(-) diff --git a/src/common/memory_patcher.cpp b/src/common/memory_patcher.cpp index ba97d97d0..7998e7f2f 100644 --- a/src/common/memory_patcher.cpp +++ b/src/common/memory_patcher.cpp @@ -9,6 +9,7 @@ namespace MemoryPatcher { uintptr_t g_eboot_address; +u64 g_eboot_image_size; std::vector pending_patches; @@ -18,25 +19,42 @@ void AddPatchToQueue(patchInfo patchToAdd) { void ApplyPendingPatches() { + //TODO: need to verify that the patch is actually for the game we open, + //if we enable patches but open a different game they will still attempt to load + for (size_t i = 0; i < pending_patches.size(); ++i) { patchInfo currentPatch = pending_patches[i]; PatchMemory(currentPatch.modNameStr, currentPatch.offsetStr, currentPatch.valueStr, - currentPatch.isOffset, currentPatch.littleEndian); + currentPatch.isOffset, currentPatch.littleEndian, currentPatch.patchMask, + currentPatch.maskOffset); } pending_patches.clear(); } void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valueStr, bool isOffset, - bool littleEndian) { + bool littleEndian, PatchMask patchMask, int maskOffset) { // Send a request to modify the process memory. void* cheatAddress = nullptr; - if (isOffset) { - cheatAddress = reinterpret_cast(g_eboot_address + std::stoi(offsetStr, 0, 16)); - } else { - cheatAddress = - reinterpret_cast(g_eboot_address + (std::stoi(offsetStr, 0, 16) - 0x400000)); + if (patchMask == PatchMask::None) { + if (isOffset) { + cheatAddress = reinterpret_cast(g_eboot_address + std::stoi(offsetStr, 0, 16)); + } else { + cheatAddress = + reinterpret_cast(g_eboot_address + (std::stoi(offsetStr, 0, 16) - 0x400000)); + } + } + + if (patchMask == PatchMask::Mask) { + cheatAddress = reinterpret_cast(PatternScan(offsetStr) + maskOffset); + } + + //TODO: implement mask_jump32 + + if (cheatAddress == nullptr) { + LOG_ERROR(Loader, "Failed to get address for patch {}", modNameStr); + return; } std::vector bytePatch; @@ -58,4 +76,49 @@ void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valu valueStr); } +static std::vector PatternToByte(const std::string& pattern) { + std::vector bytes; + const char* start = pattern.data(); + const char* end = start + pattern.size(); + + for (const char* current = start; current < end; ++current) { + if (*current == '?') { + ++current; + if (*current == '?') + ++current; + bytes.push_back(-1); + } else { + bytes.push_back(strtoul(current, const_cast(¤t), 16)); + } + } + + return bytes; +} + +uintptr_t PatternScan(const std::string& signature) { + std::vector patternBytes = PatternToByte(signature); + const auto scanBytes = static_cast((void*)g_eboot_address); + + const int32_t* sigPtr = patternBytes.data(); + const size_t sigSize = patternBytes.size(); + + uint32_t foundResults = 0; + for (uint32_t i = 0; i < g_eboot_image_size - sigSize; ++i) { + bool found = true; + for (uint32_t j = 0; j < sigSize; ++j) { + if (scanBytes[i + j] != sigPtr[j] && sigPtr[j] != -1) { + found = false; + break; + } + } + + if (found) { + foundResults++; + return reinterpret_cast(&scanBytes[i]); + } + } + + return 0; +} + } // namespace MemoryPatcher \ No newline at end of file diff --git a/src/common/memory_patcher.h b/src/common/memory_patcher.h index e6e220a17..e60621fbe 100644 --- a/src/common/memory_patcher.h +++ b/src/common/memory_patcher.h @@ -9,6 +9,13 @@ namespace MemoryPatcher { extern uintptr_t g_eboot_address; +extern u64 g_eboot_image_size; + +enum PatchMask : uint8_t { + None, + Mask, + Mask_Jump32, +}; struct patchInfo { std::string modNameStr; @@ -16,6 +23,8 @@ struct patchInfo { std::string valueStr; bool isOffset; bool littleEndian; + PatchMask patchMask; + int maskOffset; }; extern std::vector pending_patches; @@ -24,6 +33,9 @@ void AddPatchToQueue(patchInfo patchToAdd); void ApplyPendingPatches(); void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valueStr, bool isOffset, - bool littleEndian); + bool littleEndian, PatchMask patchMask = PatchMask::None, int maskOffset = 0); + +static std::vector PatternToByte(const std::string& pattern); +uintptr_t PatternScan(const std::string& signature); } // namespace MemoryPatcher \ No newline at end of file diff --git a/src/core/module.cpp b/src/core/module.cpp index 2f1212448..83f645109 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -197,6 +197,7 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { if (MemoryPatcher::g_eboot_address == 0) { if (name == "eboot") { MemoryPatcher::g_eboot_address = base_virtual_addr; + MemoryPatcher::g_eboot_image_size = base_size; MemoryPatcher::ApplyPendingPatches(); } } diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index 7791dd095..d9351e78a 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -748,9 +748,14 @@ void CheatsPatches::loadPatches(const QString& serial) { 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(); + patchName = attributes.value("Name").toString(); + patchAuthor = attributes.value("Author").toString(); + patchNote = attributes.value("Note").toString(); + } + if (appVer == "mask") { + patchName = attributes.value("Name").toString(); + patchAuthor = attributes.value("Author").toString(); + patchNote = attributes.value("Note").toString(); } } else if (xmlReader.name() == QStringLiteral("PatchList")) { QJsonArray linesArray; @@ -873,19 +878,34 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) { 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.toStdString() == "bytes16") { + if (type == "bytes16") { littleEndian = true; - } else if (type.toStdString() == "bytes32") { + } else if (type == "bytes32") { littleEndian = true; - } else if (type.toStdString() == "bytes64") { + } 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.modNameStr = patchName.toStdString(); @@ -893,13 +913,15 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) { 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); + patchValue.toStdString(), false, littleEndian, patchMask); } } }