diff --git a/src/common/memory_patcher.cpp b/src/common/memory_patcher.cpp index ba97d97d0..73f4dafc1 100644 --- a/src/common/memory_patcher.cpp +++ b/src/common/memory_patcher.cpp @@ -9,6 +9,8 @@ namespace MemoryPatcher { uintptr_t g_eboot_address; +u64 g_eboot_image_size; +std::string g_game_serial; std::vector pending_patches; @@ -20,23 +22,41 @@ void ApplyPendingPatches() { for (size_t i = 0; i < pending_patches.size(); ++i) { patchInfo currentPatch = pending_patches[i]; + + if (currentPatch.gameSerial != g_game_serial) + continue; + 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 +78,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..2b1241c27 100644 --- a/src/common/memory_patcher.h +++ b/src/common/memory_patcher.h @@ -9,13 +9,24 @@ namespace MemoryPatcher { extern uintptr_t g_eboot_address; +extern u64 g_eboot_image_size; +extern std::string g_game_serial; + +enum PatchMask : uint8_t { + None, + Mask, + Mask_Jump32, +}; struct patchInfo { + std::string gameSerial; std::string modNameStr; std::string offsetStr; std::string valueStr; bool isOffset; bool littleEndian; + PatchMask patchMask; + int maskOffset; }; extern std::vector pending_patches; @@ -24,6 +35,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/emulator.cpp b/src/emulator.cpp index 836b989e5..17a6f4a2a 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -7,6 +7,7 @@ #include "common/debug.h" #include "common/logging/backend.h" #include "common/logging/log.h" +#include "common/memory_patcher.h" #include "common/ntapi.h" #include "common/path_util.h" #include "common/polyfill_thread.h" @@ -93,6 +94,7 @@ void Emulator::Run(const std::filesystem::path& file) { auto* param_sfo = Common::Singleton::Instance(); param_sfo->open(sce_sys_folder.string() + "/param.sfo", {}); id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); + MemoryPatcher::g_game_serial = id; title = param_sfo->GetString("TITLE"); LOG_INFO(Loader, "Game id: {} Title: {}", id, title); u32 fw_version = param_sfo->GetInteger("SYSTEM_VER"); diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index 7791dd095..7dc4802cd 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -752,6 +752,11 @@ void CheatsPatches::loadPatches(const QString& serial) { 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; while (!xmlReader.atEnd() && @@ -773,7 +778,7 @@ void CheatsPatches::loadPatches(const QString& serial) { } if (!patchName.isEmpty() && !patchLines.isEmpty()) { - addPatchToLayout(patchName, patchAuthor, patchNote, patchLines); + addPatchToLayout(patchName, patchAuthor, patchNote, patchLines, serial); patchName.clear(); patchAuthor.clear(); patchNote.clear(); @@ -786,7 +791,8 @@ void CheatsPatches::loadPatches(const QString& serial) { } void CheatsPatches::addPatchToLayout(const QString& name, const QString& author, - const QString& note, const QJsonArray& linesArray) { + const QString& note, const QJsonArray& linesArray, + const QString& serial) { QCheckBox* patchCheckBox = new QCheckBox(name); patchesGroupBoxLayout->addWidget(patchCheckBox); @@ -795,6 +801,7 @@ void CheatsPatches::addPatchToLayout(const QString& name, const QString& author, patchInfo.author = author; patchInfo.note = note; patchInfo.linesArray = linesArray; + patchInfo.serial = serial; m_patchInfos[name] = patchInfo; // Hook checkbox hover events @@ -873,33 +880,51 @@ 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.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); + patchValue.toStdString(), false, littleEndian, patchMask); } } } diff --git a/src/qt_gui/cheats_patches.h b/src/qt_gui/cheats_patches.h index d199e99da..b2d899c53 100644 --- a/src/qt_gui/cheats_patches.h +++ b/src/qt_gui/cheats_patches.h @@ -52,7 +52,7 @@ private: void addCheatsToLayout(const QJsonArray& modsArray); void addPatchToLayout(const QString& name, const QString& author, const QString& note, - const QJsonArray& linesArray); + const QJsonArray& linesArray, const QString& serial); void createFilesJson(); void uncheckAllCheatCheckBoxes(); @@ -85,6 +85,7 @@ private: QString author; QString note; QJsonArray linesArray; + QString serial; }; // Members