LiteLoaderBDS-1.16.40/LiteLoader/third-party/include/ModUtils/ModUtils.h
2023-03-01 23:21:09 -08:00

474 lines
17 KiB
C++

#pragma once
#include <Windows.h>
#include <string>
#include <cstdarg>
#include <fileapi.h>
#include <Psapi.h>
#include <iostream>
#include <vector>
#include <xinput.h>
#include <sstream>
#include <map>
#include <chrono>
//#include "ini.h"
#define INRANGE(x, a, b) (x >= a && x <= b)
#define GET_BYTE(x) (GET_BITS(x[0]) << 4 | GET_BITS(x[1]))
#define GET_BITS(x) (INRANGE((x & (~0x20)), 'A', 'F') ? ((x & (~0x20)) - 'A' + 0xa) : (INRANGE(x, '0', '9') ? x - '0' : 0))
namespace ModUtils {
static std::string muModuleName = "";
static HWND muWindow = NULL;
static FILE* muLogFile = nullptr;
static bool muLogOpened = false;
static constexpr int MASKED = 0xffff;
static constexpr unsigned char HK_NONE = 0x07;
class Timer {
public:
Timer(unsigned int millis) {
m_interval = millis;
}
bool Check() {
auto now = std::chrono::system_clock::now();
if (m_resetOnNextCheck) {
m_lastExecutionTime = now;
m_resetOnNextCheck = false;
return false;
}
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastExecutionTime);
if (diff.count() >= m_interval) {
m_lastExecutionTime = now;
return true;
}
return false;
}
void Reset() {
m_resetOnNextCheck = true;
}
private:
unsigned int m_interval = 0;
bool m_resetOnNextCheck = true;
std::chrono::system_clock::time_point m_lastExecutionTime;
};
// Gets the name of the .dll which the mod code is running in
inline std::string GetModuleName(bool thisModule = true) {
static char dummy = 'x';
HMODULE module = NULL;
if (thisModule) {
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, &dummy, &module);
}
char lpFilename[MAX_PATH];
GetModuleFileNameA(module, lpFilename, sizeof(lpFilename));
std::string moduleName = strrchr(lpFilename, '\\');
moduleName = moduleName.substr(1, moduleName.length());
if (thisModule) {
moduleName.erase(moduleName.find(".dll"), moduleName.length());
}
return moduleName;
}
// Gets the path to the current mod relative to the game root folder
inline std::string GetModuleFolderPath() {
return std::string("mods\\" + GetModuleName(true));
}
// Logs both to std::out and to a log file simultaneously
inline void Log(std::string msg, ...) {
if (muModuleName == "") {
muModuleName = GetModuleName(true);
}
if (muLogFile == nullptr && !muLogOpened) {
CreateDirectoryA(std::string("mods\\" + muModuleName).c_str(), NULL);
fopen_s(&muLogFile, std::string("mods\\" + muModuleName + "\\log.txt").c_str(), "w");
muLogOpened = true;
}
va_list args;
va_start(args, msg);
vprintf(std::string(muModuleName + " > " + msg + "\n").c_str(), args);
if (muLogFile != nullptr) {
vfprintf(muLogFile, std::string(muModuleName + " > " + msg + "\n").c_str(), args);
fflush(muLogFile);
}
va_end(args);
}
// The log should preferably be closed when code execution is finished.
inline void CloseLog() {
if (muLogFile != nullptr) {
fclose(muLogFile);
muLogFile = nullptr;
}
}
// Shows a popup with a warning and logs that same warning.
inline void RaiseError(std::string error) {
if (muModuleName == "") {
muModuleName = GetModuleName(true);
}
// Log("Raised error: %s", error.c_str());
// MessageBox(NULL, error.c_str(), muModuleName.c_str(), MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
}
// Gets the base address of the game's memory.
inline DWORD_PTR GetProcessBaseAddress(DWORD processId) {
DWORD_PTR baseAddress = 0;
HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
HMODULE* moduleArray = nullptr;
LPBYTE moduleArrayBytes = 0;
DWORD bytesRequired = 0;
if (processHandle) {
if (EnumProcessModules(processHandle, NULL, 0, &bytesRequired)) {
if (bytesRequired) {
moduleArrayBytes = (LPBYTE)LocalAlloc(LPTR, bytesRequired);
if (moduleArrayBytes) {
unsigned int moduleCount;
moduleCount = bytesRequired / sizeof(HMODULE);
moduleArray = (HMODULE*)moduleArrayBytes;
if (EnumProcessModules(processHandle, moduleArray, bytesRequired, &bytesRequired)) {
baseAddress = (DWORD_PTR)moduleArray[0];
}
LocalFree(moduleArrayBytes);
}
}
}
CloseHandle(processHandle);
}
return baseAddress;
}
// Disables or enables the memory protection in a given region. Remembers and restores the original memory protection type of the given addresses.
inline void ToggleMemoryProtection(bool protectionEnabled, uintptr_t address, size_t size) {
static std::map<uintptr_t, DWORD> protectionHistory;
if (protectionEnabled && protectionHistory.find(address) != protectionHistory.end()) {
VirtualProtect((void*)address, size, protectionHistory[address], &protectionHistory[address]);
protectionHistory.erase(address);
} else if (!protectionEnabled && protectionHistory.find(address) == protectionHistory.end()) {
DWORD oldProtection = 0;
VirtualProtect((void*)address, size, PAGE_EXECUTE_READWRITE, &oldProtection);
protectionHistory[address] = oldProtection;
}
}
// Copies memory after changing the permissions at both the source and destination so we don't get an access violation.
inline void MemCopy(uintptr_t destination, uintptr_t source, size_t numBytes) {
ToggleMemoryProtection(false, destination, numBytes);
ToggleMemoryProtection(false, source, numBytes);
memcpy((void*)destination, (void*)source, numBytes);
ToggleMemoryProtection(true, source, numBytes);
ToggleMemoryProtection(true, destination, numBytes);
}
// Simple wrapper around memset
inline void MemSet(uintptr_t address, unsigned char byte, size_t numBytes) {
ToggleMemoryProtection(false, address, numBytes);
memset((void*)address, byte, numBytes);
ToggleMemoryProtection(true, address, numBytes);
}
inline std::vector<std::string> split(std::string str, std::string pattern) {
std::string::size_type pos;
std::vector<std::string> result;
str += pattern;
size_t size = str.size();
for (size_t i = 0; i < size; i++) {
pos = str.find(pattern, i);
if (pos < size) {
std::string s = str.substr(i, pos - i);
result.push_back(s);
i = pos + pattern.size() - 1;
}
}
return result;
}
inline uintptr_t FindSig(const char* szSignature) {
const char* pattern = szSignature;
uintptr_t firstMatch = 0;
DWORD processId = GetCurrentProcessId();
static const uintptr_t rangeStart = GetProcessBaseAddress(processId);
static MODULEINFO miModInfo;
static bool init = false;
if (!init) {
init = true;
GetModuleInformation(GetCurrentProcess(), (HMODULE)rangeStart, &miModInfo, sizeof(MODULEINFO));
}
static const uintptr_t rangeEnd = rangeStart + miModInfo.SizeOfImage;
BYTE patByte = GET_BYTE(pattern);
const char* oldPat = pattern;
for (uintptr_t pCur = rangeStart; pCur < rangeEnd; pCur++) {
if (!*pattern)
return firstMatch;
while (*(PBYTE)pattern == ' ')
pattern++;
if (!*pattern)
return firstMatch;
if (oldPat != pattern) {
oldPat = pattern;
if (*(PBYTE)pattern != '\?')
patByte = GET_BYTE(pattern);
}
if (*(PBYTE)pattern == '\?' || *(BYTE*)pCur == patByte) {
if (!firstMatch)
firstMatch = pCur;
if (!pattern[2] || !pattern[1])
return firstMatch;
pattern += 2;
} else {
pattern = szSignature;
firstMatch = 0;
}
}
return 0;
}
// Scans the whole memory of the main process module for the given signature.
inline uintptr_t SigScan(std::vector<uint16_t> pattern) {
DWORD processId = GetCurrentProcessId();
uintptr_t regionStart = GetProcessBaseAddress(processId);
// Log("Process name: %s", GetModuleName(false).c_str());
// Log("Process ID: %i", processId);
// Log("Process base address: 0x%llX", regionStart);
std::string patternString = "";
for (auto bytes : pattern) {
std::stringstream stream;
std::string byte = "";
if (bytes == MASKED) {
byte = "?";
} else {
stream << "0x" << std::hex << bytes;
byte = stream.str();
}
patternString.append(byte + " ");
}
// Log("Pattern: %s", patternString.c_str());
size_t numRegionsChecked = 0;
uintptr_t currentAddress = 0;
while (numRegionsChecked < 1000000000000000) {
MEMORY_BASIC_INFORMATION memoryInfo = {0};
if (VirtualQuery((void*)regionStart, &memoryInfo, sizeof(MEMORY_BASIC_INFORMATION)) == 0) {
DWORD error = GetLastError();
if (error == ERROR_INVALID_PARAMETER) {
// Log("Reached end of scannable memory.");
} else {
// Log("VirtualQuery failed, error code: %i.", error);
}
break;
}
regionStart = (uintptr_t)memoryInfo.BaseAddress;
uintptr_t regionSize = (uintptr_t)memoryInfo.RegionSize;
uintptr_t regionEnd = regionStart + regionSize;
uintptr_t protection = (uintptr_t)memoryInfo.Protect;
uintptr_t state = (uintptr_t)memoryInfo.State;
bool readableMemory = (protection == PAGE_EXECUTE_READWRITE ||
protection == PAGE_READWRITE ||
protection == PAGE_READONLY ||
protection == PAGE_WRITECOPY ||
protection == PAGE_EXECUTE_WRITECOPY ||
protection == PAGE_EXECUTE_READ ||
protection == PAGE_WRITECOMBINE) &&
state == MEM_COMMIT;
if (readableMemory) {
// Log("Checking region: %p", regionStart);
currentAddress = regionStart;
while (currentAddress < regionEnd - pattern.size()) {
for (size_t i = 0; i < pattern.size(); i++) {
if (pattern[i] == MASKED) {
currentAddress++;
continue;
} else if (*(unsigned char*)currentAddress != (unsigned char)pattern[i]) {
currentAddress++;
break;
} else if (i == pattern.size() - 1) {
uintptr_t signature = currentAddress - pattern.size() + 1;
// Log(std::to_string(protection));
// Log("Found signature at %p", signature);
return signature;
}
currentAddress++;
}
}
} else {
// Log("Skipped region: %p", regionStart);
}
numRegionsChecked++;
regionStart += memoryInfo.RegionSize;
}
// Log("Stopped at: %p, num regions checked: %i", currentAddress, numRegionsChecked);
RaiseError("Could not find signature!");
return 0;
}
inline uintptr_t SigScan(std::vector<uint16_t> pattern, uintptr_t regionStart, int32_t size) {
DWORD processId = GetCurrentProcessId();
// Log("Process name: %s", GetModuleName(false).c_str());
// Log("Process ID: %i", processId);
// Log("Process base address: 0x%llX", regionStart);
std::string patternString = "";
for (auto bytes : pattern) {
std::stringstream stream;
std::string byte = "";
if (bytes == MASKED) {
byte = "?";
} else {
stream << "0x" << std::hex << bytes;
byte = stream.str();
}
patternString.append(byte + " ");
}
// Log("Pattern: %s", patternString.c_str());
size_t numRegionsChecked = 0;
uintptr_t currentAddress = 0;
while (numRegionsChecked < 1000000000000000) {
MEMORY_BASIC_INFORMATION memoryInfo = {0};
if (VirtualQuery((void*)regionStart, &memoryInfo, sizeof(MEMORY_BASIC_INFORMATION)) == 0) {
DWORD error = GetLastError();
if (error == ERROR_INVALID_PARAMETER) {
// Log("Reached end of scannable memory.");
} else {
// Log("VirtualQuery failed, error code: %i.", error);
}
break;
}
uintptr_t regionSize = (uintptr_t)memoryInfo.RegionSize;
uintptr_t protection = (uintptr_t)memoryInfo.Protect;
uintptr_t state = (uintptr_t)memoryInfo.State;
bool readableMemory = (protection == PAGE_EXECUTE_READWRITE ||
protection == PAGE_READWRITE ||
protection == PAGE_READONLY ||
protection == PAGE_WRITECOPY ||
protection == PAGE_EXECUTE_WRITECOPY ||
protection == PAGE_EXECUTE_READ ||
protection == PAGE_WRITECOMBINE) &&
state == MEM_COMMIT;
uintptr_t regionEnd = regionStart + size;
if (readableMemory) {
// Log("Checking region: %p", regionStart);
currentAddress = regionStart;
while (currentAddress < regionEnd) {
for (size_t i = 0; i < pattern.size(); i++) {
if (pattern[i] == MASKED) {
currentAddress++;
continue;
} else if (*(unsigned char*)currentAddress != (unsigned char)pattern[i]) {
currentAddress++;
break;
} else if (i == pattern.size() - 1) {
uintptr_t signature = currentAddress - pattern.size() + 1;
// Log(std::to_string(protection));
// Log("Found signature at %p", signature);
return signature;
}
currentAddress++;
}
}
} else {
// Log("Skipped region: %p", regionStart);
}
numRegionsChecked++;
regionStart += memoryInfo.RegionSize;
}
// Log("Stopped at: %p, num regions checked: %i", currentAddress, numRegionsChecked);
RaiseError("Could not find signature!");
return 0;
}
// Replaces the memory at a given address with newBytes. Performs memory comparison with originalBytes to stop unintended memory modification.
inline bool Replace(uintptr_t address, std::vector<uint16_t> originalBytes, std::vector<uint8_t> newBytes) {
std::vector<uint8_t> truncatedOriginalBytes;
for (auto byte : originalBytes) {
truncatedOriginalBytes.push_back((uint8_t)byte);
}
std::string bufferString = "";
std::vector<uint8_t> buffer(originalBytes.size());
MemCopy((uintptr_t)&buffer[0], (uintptr_t)(void*)address, buffer.size());
for (size_t i = 0; i < buffer.size(); i++) {
std::stringstream stream;
stream << "0x" << std::hex << (unsigned int)buffer[i];
std::string byte = stream.str();
bufferString.append(byte);
if (originalBytes[i] == MASKED) {
bufferString.append("?");
buffer[i] = 0xff;
}
bufferString.append(" ");
}
// Log("Bytes at address: %s", bufferString.c_str());
std::string newBytesString = "";
for (size_t i = 0; i < newBytes.size(); i++) {
std::stringstream stream;
stream << "0x" << std::hex << (unsigned int)newBytes[i];
std::string byte = stream.str();
newBytesString.append(byte + " ");
}
// Log("New bytes: %s", newBytesString.c_str());
if (memcmp(&buffer[0], &truncatedOriginalBytes[0], buffer.size()) == 0) {
// Log("Bytes match");
MemCopy((uintptr_t)(void*)address, (uintptr_t)&newBytes[0], newBytes.size());
// Log("Patch applied");
return true;
} else {
RaiseError("Bytes do not match!");
}
return false;
}
// Takes a 4-byte relative address and converts it to an absolute 8-byte address.
inline uintptr_t RelativeToAbsoluteAddress(uintptr_t relativeAddressLocation) {
uintptr_t absoluteAddress = 0;
intptr_t relativeAddress = 0;
MemCopy((uintptr_t)&relativeAddress, relativeAddressLocation, 4);
absoluteAddress = relativeAddressLocation + 4 + relativeAddress;
return absoluteAddress;
}
// Places a 14-byte absolutely addressed jump from A to B. Add extra clearance when the jump doesn't fit cleanly.
inline void Hook(uintptr_t address, uintptr_t destination, size_t extraClearance = 0) {
size_t clearance = 14 + extraClearance;
MemSet(address, 0x90, clearance);
*(uintptr_t*)address = 0x0000000025ff;
MemCopy((address + 6), (uintptr_t)&destination, 8);
// Log("Created jump from %p to %p with a clearance of %i", address, destination, clearance);
}
} // namespace ModUtils