Merge pull request #6 from CrazyBloo/Cheats_Patches
pattern scanning for 'mask' type patches + prevent patches from applying to the wrong game
This commit is contained in:
commit
c78752ef88
|
@ -9,6 +9,8 @@
|
||||||
namespace MemoryPatcher {
|
namespace MemoryPatcher {
|
||||||
|
|
||||||
uintptr_t g_eboot_address;
|
uintptr_t g_eboot_address;
|
||||||
|
u64 g_eboot_image_size;
|
||||||
|
std::string g_game_serial;
|
||||||
|
|
||||||
std::vector<patchInfo> pending_patches;
|
std::vector<patchInfo> pending_patches;
|
||||||
|
|
||||||
|
@ -20,23 +22,41 @@ void ApplyPendingPatches() {
|
||||||
|
|
||||||
for (size_t i = 0; i < pending_patches.size(); ++i) {
|
for (size_t i = 0; i < pending_patches.size(); ++i) {
|
||||||
patchInfo currentPatch = pending_patches[i];
|
patchInfo currentPatch = pending_patches[i];
|
||||||
|
|
||||||
|
if (currentPatch.gameSerial != g_game_serial)
|
||||||
|
continue;
|
||||||
|
|
||||||
PatchMemory(currentPatch.modNameStr, currentPatch.offsetStr, currentPatch.valueStr,
|
PatchMemory(currentPatch.modNameStr, currentPatch.offsetStr, currentPatch.valueStr,
|
||||||
currentPatch.isOffset, currentPatch.littleEndian);
|
currentPatch.isOffset, currentPatch.littleEndian, currentPatch.patchMask,
|
||||||
|
currentPatch.maskOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
pending_patches.clear();
|
pending_patches.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valueStr, bool isOffset,
|
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.
|
// Send a request to modify the process memory.
|
||||||
void* cheatAddress = nullptr;
|
void* cheatAddress = nullptr;
|
||||||
|
|
||||||
if (isOffset) {
|
if (patchMask == PatchMask::None) {
|
||||||
cheatAddress = reinterpret_cast<void*>(g_eboot_address + std::stoi(offsetStr, 0, 16));
|
if (isOffset) {
|
||||||
} else {
|
cheatAddress = reinterpret_cast<void*>(g_eboot_address + std::stoi(offsetStr, 0, 16));
|
||||||
cheatAddress =
|
} else {
|
||||||
reinterpret_cast<void*>(g_eboot_address + (std::stoi(offsetStr, 0, 16) - 0x400000));
|
cheatAddress =
|
||||||
|
reinterpret_cast<void*>(g_eboot_address + (std::stoi(offsetStr, 0, 16) - 0x400000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchMask == PatchMask::Mask) {
|
||||||
|
cheatAddress = reinterpret_cast<void*>(PatternScan(offsetStr) + maskOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement mask_jump32
|
||||||
|
|
||||||
|
if (cheatAddress == nullptr) {
|
||||||
|
LOG_ERROR(Loader, "Failed to get address for patch {}", modNameStr);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> bytePatch;
|
std::vector<unsigned char> bytePatch;
|
||||||
|
@ -58,4 +78,49 @@ void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valu
|
||||||
valueStr);
|
valueStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<int32_t> PatternToByte(const std::string& pattern) {
|
||||||
|
std::vector<int32_t> 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<char**>(¤t), 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t PatternScan(const std::string& signature) {
|
||||||
|
std::vector<int32_t> patternBytes = PatternToByte(signature);
|
||||||
|
const auto scanBytes = static_cast<uint8_t*>((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<uintptr_t>(&scanBytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace MemoryPatcher
|
} // namespace MemoryPatcher
|
|
@ -9,13 +9,24 @@
|
||||||
namespace MemoryPatcher {
|
namespace MemoryPatcher {
|
||||||
|
|
||||||
extern uintptr_t g_eboot_address;
|
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 {
|
struct patchInfo {
|
||||||
|
std::string gameSerial;
|
||||||
std::string modNameStr;
|
std::string modNameStr;
|
||||||
std::string offsetStr;
|
std::string offsetStr;
|
||||||
std::string valueStr;
|
std::string valueStr;
|
||||||
bool isOffset;
|
bool isOffset;
|
||||||
bool littleEndian;
|
bool littleEndian;
|
||||||
|
PatchMask patchMask;
|
||||||
|
int maskOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern std::vector<patchInfo> pending_patches;
|
extern std::vector<patchInfo> pending_patches;
|
||||||
|
@ -24,6 +35,9 @@ void AddPatchToQueue(patchInfo patchToAdd);
|
||||||
void ApplyPendingPatches();
|
void ApplyPendingPatches();
|
||||||
|
|
||||||
void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valueStr, bool isOffset,
|
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<int32_t> PatternToByte(const std::string& pattern);
|
||||||
|
uintptr_t PatternScan(const std::string& signature);
|
||||||
|
|
||||||
} // namespace MemoryPatcher
|
} // namespace MemoryPatcher
|
|
@ -197,6 +197,7 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
|
||||||
if (MemoryPatcher::g_eboot_address == 0) {
|
if (MemoryPatcher::g_eboot_address == 0) {
|
||||||
if (name == "eboot") {
|
if (name == "eboot") {
|
||||||
MemoryPatcher::g_eboot_address = base_virtual_addr;
|
MemoryPatcher::g_eboot_address = base_virtual_addr;
|
||||||
|
MemoryPatcher::g_eboot_image_size = base_size;
|
||||||
MemoryPatcher::ApplyPendingPatches();
|
MemoryPatcher::ApplyPendingPatches();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/memory_patcher.h"
|
||||||
#include "common/ntapi.h"
|
#include "common/ntapi.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "common/polyfill_thread.h"
|
#include "common/polyfill_thread.h"
|
||||||
|
@ -93,6 +94,7 @@ void Emulator::Run(const std::filesystem::path& file) {
|
||||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||||
param_sfo->open(sce_sys_folder.string() + "/param.sfo", {});
|
param_sfo->open(sce_sys_folder.string() + "/param.sfo", {});
|
||||||
id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9);
|
id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||||
|
MemoryPatcher::g_game_serial = id;
|
||||||
title = param_sfo->GetString("TITLE");
|
title = param_sfo->GetString("TITLE");
|
||||||
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
||||||
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER");
|
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER");
|
||||||
|
|
|
@ -752,6 +752,11 @@ void CheatsPatches::loadPatches(const QString& serial) {
|
||||||
patchAuthor = attributes.value("Author").toString();
|
patchAuthor = attributes.value("Author").toString();
|
||||||
patchNote = attributes.value("Note").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")) {
|
} else if (xmlReader.name() == QStringLiteral("PatchList")) {
|
||||||
QJsonArray linesArray;
|
QJsonArray linesArray;
|
||||||
while (!xmlReader.atEnd() &&
|
while (!xmlReader.atEnd() &&
|
||||||
|
@ -773,7 +778,7 @@ void CheatsPatches::loadPatches(const QString& serial) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!patchName.isEmpty() && !patchLines.isEmpty()) {
|
if (!patchName.isEmpty() && !patchLines.isEmpty()) {
|
||||||
addPatchToLayout(patchName, patchAuthor, patchNote, patchLines);
|
addPatchToLayout(patchName, patchAuthor, patchNote, patchLines, serial);
|
||||||
patchName.clear();
|
patchName.clear();
|
||||||
patchAuthor.clear();
|
patchAuthor.clear();
|
||||||
patchNote.clear();
|
patchNote.clear();
|
||||||
|
@ -786,7 +791,8 @@ void CheatsPatches::loadPatches(const QString& serial) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheatsPatches::addPatchToLayout(const QString& name, const QString& author,
|
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);
|
QCheckBox* patchCheckBox = new QCheckBox(name);
|
||||||
patchesGroupBoxLayout->addWidget(patchCheckBox);
|
patchesGroupBoxLayout->addWidget(patchCheckBox);
|
||||||
|
@ -795,6 +801,7 @@ void CheatsPatches::addPatchToLayout(const QString& name, const QString& author,
|
||||||
patchInfo.author = author;
|
patchInfo.author = author;
|
||||||
patchInfo.note = note;
|
patchInfo.note = note;
|
||||||
patchInfo.linesArray = linesArray;
|
patchInfo.linesArray = linesArray;
|
||||||
|
patchInfo.serial = serial;
|
||||||
m_patchInfos[name] = patchInfo;
|
m_patchInfos[name] = patchInfo;
|
||||||
|
|
||||||
// Hook checkbox hover events
|
// Hook checkbox hover events
|
||||||
|
@ -873,33 +880,51 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) {
|
||||||
QString type = lineObject["Type"].toString();
|
QString type = lineObject["Type"].toString();
|
||||||
QString address = lineObject["Address"].toString();
|
QString address = lineObject["Address"].toString();
|
||||||
QString patchValue = lineObject["Value"].toString();
|
QString patchValue = lineObject["Value"].toString();
|
||||||
|
QString maskOffsetStr = lineObject["Offset"].toString();
|
||||||
|
|
||||||
patchValue = convertValueToHex(type, patchValue);
|
patchValue = convertValueToHex(type, patchValue);
|
||||||
|
|
||||||
bool littleEndian = false;
|
bool littleEndian = false;
|
||||||
|
|
||||||
if (type.toStdString() == "bytes16") {
|
if (type == "bytes16") {
|
||||||
littleEndian = true;
|
littleEndian = true;
|
||||||
} else if (type.toStdString() == "bytes32") {
|
} else if (type == "bytes32") {
|
||||||
littleEndian = true;
|
littleEndian = true;
|
||||||
} else if (type.toStdString() == "bytes64") {
|
} else if (type == "bytes64") {
|
||||||
littleEndian = true;
|
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) {
|
if (MemoryPatcher::g_eboot_address == 0) {
|
||||||
MemoryPatcher::patchInfo addingPatch;
|
MemoryPatcher::patchInfo addingPatch;
|
||||||
|
addingPatch.gameSerial = patchInfo.serial.toStdString();
|
||||||
addingPatch.modNameStr = patchName.toStdString();
|
addingPatch.modNameStr = patchName.toStdString();
|
||||||
addingPatch.offsetStr = address.toStdString();
|
addingPatch.offsetStr = address.toStdString();
|
||||||
addingPatch.valueStr = patchValue.toStdString();
|
addingPatch.valueStr = patchValue.toStdString();
|
||||||
addingPatch.isOffset = false;
|
addingPatch.isOffset = false;
|
||||||
addingPatch.littleEndian = littleEndian;
|
addingPatch.littleEndian = littleEndian;
|
||||||
|
addingPatch.patchMask = patchMask;
|
||||||
|
addingPatch.maskOffset = maskOffsetValue;
|
||||||
|
|
||||||
MemoryPatcher::AddPatchToQueue(addingPatch);
|
MemoryPatcher::AddPatchToQueue(addingPatch);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryPatcher::PatchMemory(patchName.toStdString(), address.toStdString(),
|
MemoryPatcher::PatchMemory(patchName.toStdString(), address.toStdString(),
|
||||||
patchValue.toStdString(), false, littleEndian);
|
patchValue.toStdString(), false, littleEndian, patchMask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ private:
|
||||||
|
|
||||||
void addCheatsToLayout(const QJsonArray& modsArray);
|
void addCheatsToLayout(const QJsonArray& modsArray);
|
||||||
void addPatchToLayout(const QString& name, const QString& author, const QString& note,
|
void addPatchToLayout(const QString& name, const QString& author, const QString& note,
|
||||||
const QJsonArray& linesArray);
|
const QJsonArray& linesArray, const QString& serial);
|
||||||
|
|
||||||
void createFilesJson();
|
void createFilesJson();
|
||||||
void uncheckAllCheatCheckBoxes();
|
void uncheckAllCheatCheckBoxes();
|
||||||
|
@ -85,6 +85,7 @@ private:
|
||||||
QString author;
|
QString author;
|
||||||
QString note;
|
QString note;
|
||||||
QJsonArray linesArray;
|
QJsonArray linesArray;
|
||||||
|
QString serial;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Members
|
// Members
|
||||||
|
|
Loading…
Reference in New Issue