Memory Patch Fixes (#4619)

* Fix bytesN types to format hex

```
Possible prefixes == (s == `0x` || s == `#` || s == `$`)
```

* use PatchInfo struct for PatchMemory
This commit is contained in:
NotAnEnergyDrinkAddict
2026-06-25 21:18:53 +12:00
committed by GitHub
parent aff387e8a9
commit a52b4c0dae
3 changed files with 81 additions and 64 deletions

View File

@@ -30,20 +30,34 @@ std::string toHex(u64 value, size_t byteSize) {
return ss.str(); return ss.str();
} }
static bool isHexSym(const std::string& s) {
return (s.size() >= 2 && (s[0] == '$' || s[0] == '#'));
}
static bool isHex0x(const std::string& s) {
return (s.size() >= 3 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'));
}
// possible prefixes == (s == `0x` || s == `#` || s == `$`) will be interpet as hex value, else is
// decimal
static int convertNumBase(const std::string& s) {
return (isHex0x(s) || isHexSym(s)) ? 16 : 10;
}
std::string convertValueToHex(const std::string type, const std::string valueStr) { std::string convertValueToHex(const std::string type, const std::string valueStr) {
std::string result; std::string result;
if (type == "byte") { if (type == "byte") {
const u32 value = std::stoul(valueStr, nullptr, 16); const u32 value = std::stoul(valueStr, nullptr, convertNumBase(valueStr));
result = toHex(value, 1); result = toHex(value, 1);
} else if (type == "bytes16") { } else if (type == "bytes16") {
const u32 value = std::stoul(valueStr, nullptr, 16); const u32 value = std::stoul(valueStr, nullptr, convertNumBase(valueStr));
result = toHex(value, 2); result = toHex(value, 2);
} else if (type == "bytes32") { } else if (type == "bytes32") {
const u32 value = std::stoul(valueStr, nullptr, 16); const u32 value = std::stoul(valueStr, nullptr, convertNumBase(valueStr));
result = toHex(value, 4); result = toHex(value, 4);
} else if (type == "bytes64") { } else if (type == "bytes64") {
const u64 value = std::stoull(valueStr, nullptr, 16); const u64 value = std::stoull(valueStr, nullptr, convertNumBase(valueStr));
result = toHex(value, 8); result = toHex(value, 8);
} else if (type == "float32") { } else if (type == "float32") {
union { union {
@@ -170,9 +184,18 @@ void ApplyPatchesFromXML(std::filesystem::path path) {
maskOffsetValue = std::stoi(maskOffsetStr, 0, 10); maskOffsetValue = std::stoi(maskOffsetStr, 0, 10);
} }
MemoryPatcher::PatchMemory(currentPatchName, address, patchValue, targetStr, const patchInfo patch = {
sizeStr, false, littleEndian, patchMask, .gameSerial = "*",
maskOffsetValue); .modNameStr = currentPatchName,
.offsetStr = address,
.valueStr = patchValue,
.targetStr = targetStr,
.sizeStr = sizeStr,
.littleEndian = littleEndian,
.patchMask = patchMask,
.maskOffset = maskOffsetValue,
};
MemoryPatcher::PatchMemory(patch);
} }
} }
} }
@@ -214,11 +237,9 @@ void OnGameLoaded() {
ApplyPendingPatches(); ApplyPendingPatches();
} }
void AddPatchToQueue(patchInfo patchToAdd) { void AddPatchToQueue(const patchInfo& patchToAdd) {
if (patches_applied) { if (patches_applied) {
PatchMemory(patchToAdd.modNameStr, patchToAdd.offsetStr, patchToAdd.valueStr, PatchMemory(patchToAdd);
patchToAdd.targetStr, patchToAdd.sizeStr, patchToAdd.isOffset,
patchToAdd.littleEndian, patchToAdd.patchMask, patchToAdd.maskOffset);
return; return;
} }
pending_patches.push_back(patchToAdd); pending_patches.push_back(patchToAdd);
@@ -232,35 +253,32 @@ void ApplyPendingPatches() {
if (currentPatch.gameSerial != "*" && currentPatch.gameSerial != g_game_serial) if (currentPatch.gameSerial != "*" && currentPatch.gameSerial != g_game_serial)
continue; continue;
PatchMemory(currentPatch.modNameStr, currentPatch.offsetStr, currentPatch.valueStr, PatchMemory(currentPatch);
currentPatch.targetStr, currentPatch.sizeStr, 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, void PatchMemory(const patchInfo& patch) {
std::string targetStr, std::string sizeStr, bool isOffset, 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 (patchMask == PatchMask::None) { if (patch.patchMask == PatchMask::None) {
if (isOffset) { if (patch.isOffset) {
cheatAddress = reinterpret_cast<void*>(g_eboot_address + std::stoi(offsetStr, 0, 16));
} else {
cheatAddress = cheatAddress =
reinterpret_cast<void*>(g_eboot_address + (std::stoi(offsetStr, 0, 16) - 0x400000)); reinterpret_cast<void*>(g_eboot_address + std::stoi(patch.offsetStr, 0, 16));
} else {
cheatAddress = reinterpret_cast<void*>(g_eboot_address +
(std::stoi(patch.offsetStr, 0, 16) - 0x400000));
} }
} }
if (patchMask == PatchMask::Mask) { if (patch.patchMask == PatchMask::Mask) {
cheatAddress = reinterpret_cast<void*>(PatternScan(offsetStr) + maskOffset); cheatAddress = reinterpret_cast<void*>(PatternScan(patch.offsetStr) + patch.maskOffset);
} }
if (patchMask == PatchMask::Mask_Jump32) { if (patch.patchMask == PatchMask::Mask_Jump32) {
int jumpSize = std::stoi(sizeStr); int jumpSize = std::stoi(patch.sizeStr);
constexpr int MAX_PATTERN_LENGTH = 256; constexpr int MAX_PATTERN_LENGTH = 256;
if (jumpSize < 5) { if (jumpSize < 5) {
@@ -273,35 +291,35 @@ void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valu
} }
// Find the base address using "Address" // Find the base address using "Address"
uintptr_t baseAddress = PatternScan(offsetStr); uintptr_t baseAddress = PatternScan(patch.offsetStr);
if (baseAddress == 0) { if (baseAddress == 0) {
LOG_ERROR(Loader, "PatternScan failed for mask_jump32 with pattern: {}", offsetStr); LOG_ERROR(Loader, "PatternScan failed for mask_jump32 with pattern: {}",
patch.offsetStr);
return; return;
} }
uintptr_t patchAddress = baseAddress + maskOffset; uintptr_t patchAddress = baseAddress + patch.maskOffset;
// Fills the original region (jumpSize bytes) with NOPs // Fills the original region (jumpSize bytes) with NOPs
std::vector<u8> nopBytes(jumpSize, 0x90); std::vector<u8> nopBytes(jumpSize, 0x90);
std::memcpy(reinterpret_cast<void*>(patchAddress), nopBytes.data(), nopBytes.size()); std::memcpy(reinterpret_cast<void*>(patchAddress), nopBytes.data(), nopBytes.size());
// Use "Target" to locate the start of the code cave // Use "Target" to locate the start of the code cave
uintptr_t jump_target = PatternScan(targetStr); uintptr_t jump_target = PatternScan(patch.targetStr);
if (jump_target == 0) { if (jump_target == 0) {
LOG_ERROR(Loader, "PatternScan failed to Target with pattern: {}", targetStr); LOG_ERROR(Loader, "PatternScan failed to Target with pattern: {}", patch.targetStr);
return; return;
} }
// Converts the Value attribute to a byte array (payload) // Converts the Value attribute to a byte array (payload)
std::vector<u8> payload; std::vector<u8> payload;
for (size_t i = 0; i < valueStr.length(); i += 2) { for (size_t i = 0; i < patch.valueStr.length(); i += 2) {
std::string tempStr = patch.valueStr.substr(i, 2);
std::string tempStr = valueStr.substr(i, 2);
const char* byteStr = tempStr.c_str(); const char* byteStr = tempStr.c_str();
char* endPtr; char* endPtr;
unsigned int byteVal = std::strtoul(byteStr, &endPtr, 16); unsigned int byteVal = std::strtoul(byteStr, &endPtr, 16);
if (endPtr != byteStr + 2) { if (endPtr != byteStr + 2) {
LOG_ERROR(Loader, "Invalid byte in Value: {}", valueStr.substr(i, 2)); LOG_ERROR(Loader, "Invalid byte in Value: {}", patch.valueStr.substr(i, 2));
return; return;
} }
payload.push_back(static_cast<u8>(byteVal)); payload.push_back(static_cast<u8>(byteVal));
@@ -333,32 +351,32 @@ void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valu
LOG_INFO(Loader, LOG_INFO(Loader,
"Applied Patch mask_jump32: {}, PatchAddress: {:#x}, JumpTarget: {:#x}, " "Applied Patch mask_jump32: {}, PatchAddress: {:#x}, JumpTarget: {:#x}, "
"CodeCaveEnd: {:#x}, JumpSize: {}", "CodeCaveEnd: {:#x}, JumpSize: {}",
modNameStr, patchAddress, jump_target, code_cave_end, jumpSize); patch.modNameStr, patchAddress, jump_target, code_cave_end, jumpSize);
return; return;
} }
if (cheatAddress == nullptr) { if (cheatAddress == nullptr) {
LOG_ERROR(Loader, "Failed to get address for patch {}", modNameStr); LOG_ERROR(Loader, "Failed to get address for patch {}", patch.modNameStr);
return; return;
} }
std::vector<unsigned char> bytePatch; std::vector<unsigned char> bytePatch;
for (size_t i = 0; i < valueStr.length(); i += 2) { for (size_t i = 0; i < patch.valueStr.length(); i += 2) {
unsigned char byte = unsigned char byte = static_cast<unsigned char>(
static_cast<unsigned char>(std::strtol(valueStr.substr(i, 2).c_str(), nullptr, 16)); std::strtol(patch.valueStr.substr(i, 2).c_str(), nullptr, 16));
bytePatch.push_back(byte); bytePatch.push_back(byte);
} }
if (littleEndian) { if (patch.littleEndian) {
std::reverse(bytePatch.begin(), bytePatch.end()); std::reverse(bytePatch.begin(), bytePatch.end());
} }
std::memcpy(cheatAddress, bytePatch.data(), bytePatch.size()); std::memcpy(cheatAddress, bytePatch.data(), bytePatch.size());
LOG_INFO(Loader, "Applied patch: {}, Offset: {}, Value: {}", modNameStr, LOG_INFO(Loader, "Applied patch: {}, Offset: {:#x}, Value: {}", patch.modNameStr,
(uintptr_t)cheatAddress, valueStr); (uintptr_t)cheatAddress, patch.valueStr);
} }
static std::vector<int32_t> PatternToByte(const std::string& pattern) { static std::vector<int32_t> PatternToByte(const std::string& pattern) {

View File

@@ -26,12 +26,12 @@ enum PatchMask : uint8_t {
}; };
struct patchInfo { struct patchInfo {
std::string gameSerial; const std::string& gameSerial;
std::string modNameStr; const std::string& modNameStr;
std::string offsetStr; const std::string& offsetStr;
std::string valueStr; const std::string& valueStr;
std::string targetStr; const std::string& targetStr;
std::string sizeStr; const std::string& sizeStr;
bool isOffset; bool isOffset;
bool littleEndian; bool littleEndian;
PatchMask patchMask; PatchMask patchMask;
@@ -41,11 +41,9 @@ struct patchInfo {
std::string convertValueToHex(const std::string type, const std::string valueStr); std::string convertValueToHex(const std::string type, const std::string valueStr);
void OnGameLoaded(); void OnGameLoaded();
void AddPatchToQueue(patchInfo patchToAdd); void AddPatchToQueue(const patchInfo& patchToAdd);
void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valueStr, void PatchMemory(const patchInfo& patch);
std::string targetStr, std::string sizeStr, bool isOffset, bool littleEndian,
PatchMask patchMask = PatchMask::None, int maskOffset = 0);
static std::vector<int32_t> PatternToByte(const std::string& pattern); static std::vector<int32_t> PatternToByte(const std::string& pattern);
uintptr_t PatternScan(const std::string& signature); uintptr_t PatternScan(const std::string& signature);

View File

@@ -124,17 +124,18 @@ void IPC::InputLoop() {
} else if (cmd == "START") { } else if (cmd == "START") {
start_semaphore.release(); start_semaphore.release();
} else if (cmd == "PATCH_MEMORY") { } else if (cmd == "PATCH_MEMORY") {
MemoryPatcher::patchInfo entry; const MemoryPatcher::patchInfo entry = {
entry.gameSerial = "*"; .gameSerial = "*",
entry.modNameStr = next_str(); .modNameStr = next_str(),
entry.offsetStr = next_str(); .offsetStr = next_str(),
entry.valueStr = next_str(); .valueStr = next_str(),
entry.targetStr = next_str(); .targetStr = next_str(),
entry.sizeStr = next_str(); .sizeStr = next_str(),
entry.isOffset = next_u64() != 0; .isOffset = next_u64() != 0,
entry.littleEndian = next_u64() != 0; .littleEndian = next_u64() != 0,
entry.patchMask = static_cast<MemoryPatcher::PatchMask>(next_u64()); .patchMask = static_cast<MemoryPatcher::PatchMask>(next_u64()),
entry.maskOffset = static_cast<int>(next_u64()); .maskOffset = static_cast<int>(next_u64()),
};
MemoryPatcher::AddPatchToQueue(entry); MemoryPatcher::AddPatchToQueue(entry);
} else if (cmd == "PAUSE") { } else if (cmd == "PAUSE") {
DebugState.PauseGuestThreads(); DebugState.PauseGuestThreads();