mirror of
https://github.com/geode-sdk/geode.git
synced 2025-04-01 07:40:18 -04:00
commit
ccfa6edf53
12 changed files with 219 additions and 29 deletions
|
@ -243,7 +243,7 @@ if (DEFINED GEODE_TULIPHOOK_REPO_PATH)
|
|||
message(STATUS "Using ${GEODE_TULIPHOOK_REPO_PATH} for TulipHook")
|
||||
add_subdirectory(${GEODE_TULIPHOOK_REPO_PATH} ${GEODE_TULIPHOOK_REPO_PATH}/build)
|
||||
else()
|
||||
CPMAddPackage("gh:geode-sdk/TulipHook#58dc814")
|
||||
CPMAddPackage("gh:geode-sdk/TulipHook#fd1e02e")
|
||||
endif()
|
||||
set(CMAKE_WARN_DEPRECATED ON CACHE BOOL "" FORCE)
|
||||
|
||||
|
|
|
@ -9,18 +9,30 @@
|
|||
|
||||
// Set dllexport/dllimport to geode classes & functions
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) && !defined(__CYGWIN__)
|
||||
#define GEODE_WINDOWS(...) __VA_ARGS__
|
||||
#define GEODE_IS_WINDOWS
|
||||
#define GEODE_IS_DESKTOP
|
||||
#define GEODE_PLATFORM_NAME "Windows"
|
||||
#define GEODE_CALL __stdcall
|
||||
#define GEODE_CDECL_CALL __cdecl
|
||||
#define GEODE_PLATFORM_EXTENSION ".dll"
|
||||
#define GEODE_PLATFORM_SHORT_IDENTIFIER "win"
|
||||
#define CC_TARGET_OS_WIN32
|
||||
|
||||
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64) && !defined(__CYGWIN__)
|
||||
#define GEODE_IS_WINDOWS64
|
||||
#define GEODE_WINDOWS64(...) __VA_ARGS__
|
||||
#define GEODE_WINDOWS32(...)
|
||||
#define GEODE_CALL
|
||||
#else
|
||||
#define GEODE_IS_WINDOWS32
|
||||
#define GEODE_WINDOWS32(...) __VA_ARGS__
|
||||
#define GEODE_WINDOWS64(...)
|
||||
#define GEODE_CALL __stdcall
|
||||
#endif
|
||||
#else
|
||||
#define GEODE_WINDOWS(...)
|
||||
#define GEODE_WINDOWS32(...)
|
||||
#define GEODE_WINDOWS64(...)
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
@ -45,7 +57,6 @@
|
|||
#define CC_TARGET_OS_MAC
|
||||
#endif
|
||||
#define GEODE_CALL
|
||||
#define GEODE_CDECL_CALL
|
||||
#else
|
||||
#define GEODE_MACOS(...)
|
||||
#define GEODE_IOS(...)
|
||||
|
@ -57,7 +68,6 @@
|
|||
#define GEODE_IS_ANDROID
|
||||
#define GEODE_IS_MOBILE
|
||||
#define GEODE_CALL
|
||||
#define GEODE_CDECL_CALL
|
||||
#define CC_TARGET_OS_ANDROID
|
||||
|
||||
#if defined(__arm__)
|
||||
|
|
|
@ -27,7 +27,13 @@
|
|||
#define GEODE_API extern "C" __declspec(dllexport)
|
||||
#define GEODE_EXPORT __declspec(dllexport)
|
||||
|
||||
static_assert(sizeof(void*) == 4, "Geode must be compiled in 32-bit for Windows!");
|
||||
#if defined(GEODE_IS_WINDOWS64)
|
||||
#define GEODE_IS_X64
|
||||
#define GEODE_CDECL_CALL
|
||||
#else
|
||||
#define GEODE_IS_X86
|
||||
#define GEODE_CDECL_CALL __cdecl
|
||||
#endif
|
||||
|
||||
#include "windows.hpp"
|
||||
|
||||
|
@ -47,6 +53,9 @@
|
|||
#define GEODE_API extern "C" __attribute__((visibility("default")))
|
||||
#define GEODE_EXPORT __attribute__((visibility("default")))
|
||||
|
||||
#define GEODE_IS_X64
|
||||
#define GEODE_CDECL_CALL
|
||||
|
||||
#include "macos.hpp"
|
||||
|
||||
#elif defined(GEODE_IS_IOS)
|
||||
|
@ -65,6 +74,9 @@
|
|||
#define GEODE_API extern "C" __attribute__((visibility("default")))
|
||||
#define GEODE_EXPORT __attribute__((visibility("default")))
|
||||
|
||||
#define GEODE_IS_X64
|
||||
#define GEODE_CDECL_CALL
|
||||
|
||||
#include "ios.hpp"
|
||||
|
||||
#elif defined(GEODE_IS_ANDROID)
|
||||
|
@ -83,6 +95,13 @@
|
|||
#define GEODE_API extern "C" __attribute__((visibility("default")))
|
||||
#define GEODE_EXPORT __attribute__((visibility("default")))
|
||||
|
||||
#if defined(GEODE_IS_ANDROID64)
|
||||
#define GEODE_IS_X64
|
||||
#else
|
||||
#define GEODE_IS_X86
|
||||
#endif
|
||||
#define GEODE_CDECL_CALL
|
||||
|
||||
#include "android.hpp"
|
||||
|
||||
#else
|
||||
|
|
|
@ -27,36 +27,53 @@ namespace geode::base {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
namespace geode::cast {
|
||||
template <class Type>
|
||||
struct ShrunkPointer {
|
||||
uint32_t m_ptrOffset;
|
||||
|
||||
Type* into(uintptr_t base) {
|
||||
return reinterpret_cast<Type*>(base + m_ptrOffset);
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeDescriptorType {
|
||||
void* m_typeinfoTable;
|
||||
int32_t m_spare;
|
||||
intptr_t m_spare;
|
||||
char m_typeDescriptorName[0x100];
|
||||
};
|
||||
|
||||
struct ClassDescriptorType;
|
||||
|
||||
struct BaseClassDescriptorType {
|
||||
TypeDescriptorType* m_typeDescriptor;
|
||||
ShrunkPointer<TypeDescriptorType> m_typeDescriptor;
|
||||
int32_t m_numContainedBases;
|
||||
int32_t m_memberDisplacement[3];
|
||||
int32_t m_attributes;
|
||||
ClassDescriptorType* m_classDescriptor;
|
||||
ShrunkPointer<ClassDescriptorType> m_classDescriptor;
|
||||
};
|
||||
|
||||
struct BaseClassArrayType {
|
||||
ShrunkPointer<BaseClassDescriptorType> m_descriptorEntries[0x100];
|
||||
};
|
||||
|
||||
struct ClassDescriptorType {
|
||||
int32_t m_signature;
|
||||
int32_t m_attributes;
|
||||
int32_t m_numBaseClasses;
|
||||
BaseClassDescriptorType** m_baseClassArray;
|
||||
ShrunkPointer<BaseClassArrayType> m_baseClassArray;
|
||||
};
|
||||
|
||||
struct CompleteLocatorType {
|
||||
int32_t m_signature;
|
||||
int32_t m_offset;
|
||||
int32_t m_cdOffset;
|
||||
TypeDescriptorType* m_typeDescriptor;
|
||||
ClassDescriptorType* m_classDescriptor;
|
||||
ShrunkPointer<TypeDescriptorType> m_typeDescriptor;
|
||||
ShrunkPointer<ClassDescriptorType> m_classDescriptor;
|
||||
#ifdef GEODE_IS_X64
|
||||
int32_t m_locatorOffset;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct MetaPointerType {
|
||||
|
@ -89,12 +106,20 @@ namespace geode::cast {
|
|||
|
||||
auto afterIdent = static_cast<char const*>(afterDesc->m_typeDescriptorName);
|
||||
|
||||
auto classDesc = metaPtr->m_completeLocator->m_classDescriptor;
|
||||
#ifdef GEODE_IS_X64
|
||||
auto locatorOffset = metaPtr->m_completeLocator->m_locatorOffset;
|
||||
auto base = reinterpret_cast<uintptr_t>(metaPtr->m_completeLocator) - locatorOffset;
|
||||
#else
|
||||
auto base = 0;
|
||||
#endif
|
||||
|
||||
auto classDesc = metaPtr->m_completeLocator->m_classDescriptor.into(base);
|
||||
for (int32_t i = 0; i < classDesc->m_numBaseClasses; ++i) {
|
||||
auto entry = classDesc->m_baseClassArray.into(base)->m_descriptorEntries[i].into(base);
|
||||
auto optionIdent = static_cast<char const*>(
|
||||
classDesc->m_baseClassArray[i]->m_typeDescriptor->m_typeDescriptorName
|
||||
entry->m_typeDescriptor.into(base)->m_typeDescriptorName
|
||||
);
|
||||
auto optionOffset = classDesc->m_baseClassArray[i]->m_memberDisplacement[0];
|
||||
auto optionOffset = entry->m_memberDisplacement[0];
|
||||
|
||||
if (std::strcmp(afterIdent, optionIdent) == 0) {
|
||||
auto afterPtr = reinterpret_cast<std::byte*>(basePtr) + optionOffset;
|
||||
|
|
|
@ -42,10 +42,19 @@ DWORD WINAPI xinputGetDSoundAudioDeviceGuids(DWORD dwUserIndex, GUID* pDSoundRen
|
|||
return getDSoundAudioDeviceGuids(dwUserIndex, pDSoundRenderGuid, pDSoundCaptureGuid);
|
||||
}
|
||||
|
||||
#pragma comment(linker, "/export:XInputGetState=_xinputGetState@8")
|
||||
#pragma comment(linker, "/export:XInputSetState=_xinputSetState@8")
|
||||
#pragma comment(linker, "/export:XInputGetCapabilities=_xinputGetCapabilities@12")
|
||||
#pragma comment(linker, "/export:XInputGetDSoundAudioDeviceGuids=_xinputGetDSoundAudioDeviceGuids@12")
|
||||
// https://github.com/mrexodia/perfect-dll-proxy
|
||||
#if defined(_WIN64)
|
||||
#define PROXY_PATH(export) \
|
||||
"/export:" #export "=\\\\.\\GLOBALROOT\\SystemRoot\\System32\\XInput9_1_0.dll." #export
|
||||
#else
|
||||
#define PROXY_PATH(export) \
|
||||
"/export:" #export "=\\\\.\\GLOBALROOT\\SystemRoot\\SysWOW64\\XInput9_1_0.dll." #export
|
||||
#endif
|
||||
|
||||
#pragma comment(linker, PROXY_PATH(XInputGetState))
|
||||
#pragma comment(linker, PROXY_PATH(XInputSetState))
|
||||
#pragma comment(linker, PROXY_PATH(XInputGetCapabilities))
|
||||
#pragma comment(linker, PROXY_PATH(XInputGetDSoundAudioDeviceGuids))
|
||||
|
||||
BOOL fileExists(char const* path) {
|
||||
DWORD attrib = GetFileAttributesA(path);
|
||||
|
|
|
@ -383,7 +383,7 @@ int ZipUtils::ccInflateCCZFile(char const* path, unsigned char** out) {
|
|||
}
|
||||
|
||||
unsigned long destlen = len;
|
||||
unsigned long source = (unsigned long)compressed + sizeof(*header);
|
||||
uintptr_t source = (uintptr_t)compressed + sizeof(*header);
|
||||
int ret = uncompress(*out, &destlen, (Bytef*)source, fileLen - sizeof(*header));
|
||||
|
||||
delete[] compressed;
|
||||
|
|
|
@ -28,14 +28,14 @@ $execute {
|
|||
reinterpret_cast<void*>(geode::addresser::getNonVirtual(&FMOD::System::init)),
|
||||
&FMOD_System_init_hook,
|
||||
"FMOD::System::init"
|
||||
GEODE_WINDOWS(, tulip::hook::TulipConvention::Stdcall)
|
||||
GEODE_WINDOWS32(, tulip::hook::TulipConvention::Stdcall)
|
||||
);
|
||||
|
||||
(void)geode::Mod::get()->hook(
|
||||
reinterpret_cast<void*>(geode::addresser::getNonVirtual(&FMOD::ChannelControl::setVolume)),
|
||||
&FMOD_ChannelControl_setVolume_hook,
|
||||
"FMOD::ChannelControl::setVolume"
|
||||
GEODE_WINDOWS(, tulip::hook::TulipConvention::Stdcall)
|
||||
GEODE_WINDOWS32(, tulip::hook::TulipConvention::Stdcall)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ $execute {
|
|||
// geode::base::getCocos() + 0x1225DC = MessageBoxW in .idata
|
||||
if (LoaderImpl::get()->isForwardCompatMode()) return;
|
||||
|
||||
#if GEODE_COMP_GD_VERSION == 22040
|
||||
const uint32_t importedMessageBoxA = geode::base::getCocos() + 0x122600;
|
||||
|
||||
ByteVector p = {
|
||||
|
@ -48,6 +49,9 @@ $execute {
|
|||
|
||||
(void)Mod::get()->patch(reinterpret_cast<void*>(geode::base::getCocos() + 0xC75F9), p);
|
||||
(void)Mod::get()->patch(reinterpret_cast<void*>(geode::base::getCocos() + 0xc7651), p);
|
||||
#else
|
||||
#pragma message("Unsupported GD version!")
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -38,11 +38,15 @@ $execute {
|
|||
if (LoaderImpl::get()->isForwardCompatMode()) return;
|
||||
|
||||
// BitmapDC::~BitmapDC
|
||||
#if GEODE_COMP_GD_VERSION == 22040
|
||||
patchCall(0xC9A56, (uintptr_t)&RemoveFontResourceWHook);
|
||||
|
||||
// BitmapDC::setFont
|
||||
patchCall(0xCB5BC, (uintptr_t)&RemoveFontResourceWHook);
|
||||
patchCall(0xCB642, (uintptr_t)&AddFontResourceWHook);
|
||||
#else
|
||||
#pragma message("Unsupported GD version!")
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -151,11 +151,18 @@ static std::string getStacktrace(PCONTEXT context) {
|
|||
|
||||
auto process = GetCurrentProcess();
|
||||
auto thread = GetCurrentThread();
|
||||
#ifdef GEODE_IS_X86
|
||||
stack.AddrPC.Offset = context->Eip;
|
||||
stack.AddrPC.Mode = AddrModeFlat;
|
||||
stack.AddrStack.Offset = context->Esp;
|
||||
stack.AddrStack.Mode = AddrModeFlat;
|
||||
stack.AddrFrame.Offset = context->Ebp;
|
||||
#else
|
||||
stack.AddrPC.Offset = context->Rip;
|
||||
stack.AddrStack.Offset = context->Rsp;
|
||||
stack.AddrFrame.Offset = context->Rbp;
|
||||
#endif
|
||||
|
||||
stack.AddrPC.Mode = AddrModeFlat;
|
||||
stack.AddrStack.Mode = AddrModeFlat;
|
||||
stack.AddrFrame.Mode = AddrModeFlat;
|
||||
|
||||
// size_t frame = 0;
|
||||
|
@ -174,6 +181,7 @@ static std::string getStacktrace(PCONTEXT context) {
|
|||
}
|
||||
|
||||
static std::string getRegisters(PCONTEXT context) {
|
||||
#ifdef GEODE_IS_X86
|
||||
return fmt::format(
|
||||
"EAX: {:08x}\n"
|
||||
"EBX: {:08x}\n"
|
||||
|
@ -194,6 +202,44 @@ static std::string getRegisters(PCONTEXT context) {
|
|||
context->Esi,
|
||||
context->Eip
|
||||
);
|
||||
#else
|
||||
return fmt::format(
|
||||
"RAX: {:016x}\n"
|
||||
"RBX: {:016x}\n"
|
||||
"RCX: {:016x}\n"
|
||||
"RDX: {:016x}\n"
|
||||
"RBP: {:016x}\n"
|
||||
"RSP: {:016x}\n"
|
||||
"RDI: {:016x}\n"
|
||||
"RSI: {:016x}\n"
|
||||
"RIP: {:016x}\n"
|
||||
"R8: {:016x}\n"
|
||||
"R9: {:016x}\n"
|
||||
"R10: {:016x}\n"
|
||||
"R11: {:016x}\n"
|
||||
"R12: {:016x}\n"
|
||||
"R13: {:016x}\n"
|
||||
"R14: {:016x}\n"
|
||||
"R15: {:016x}\n",
|
||||
context->Rax,
|
||||
context->Rbx,
|
||||
context->Rcx,
|
||||
context->Rdx,
|
||||
context->Rbp,
|
||||
context->Rsp,
|
||||
context->Rdi,
|
||||
context->Rsi,
|
||||
context->Rip,
|
||||
context->R8,
|
||||
context->R9,
|
||||
context->R10,
|
||||
context->R11,
|
||||
context->R12,
|
||||
context->R13,
|
||||
context->R14,
|
||||
context->R15
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
|
|
|
@ -65,6 +65,8 @@ std::string loadGeode() {
|
|||
|
||||
gdTimestamp = ntHeader->FileHeader.TimeDateStamp;
|
||||
|
||||
#ifdef GEODE_IS_WINDOWS32
|
||||
|
||||
constexpr size_t trampolineSize = 12;
|
||||
mainTrampolineAddr = VirtualAlloc(
|
||||
nullptr, trampolineSize,
|
||||
|
@ -128,6 +130,9 @@ std::string loadGeode() {
|
|||
return "Geode could not hook the main function, not loading Geode.";
|
||||
std::memcpy(reinterpret_cast<void*>(patchAddr), patchBytes, patchSize);
|
||||
VirtualProtectEx(process, reinterpret_cast<void*>(patchAddr), patchSize, oldProtect, &oldProtect);
|
||||
#else
|
||||
#pragma message("64-bit entry is not implemented yet.")
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -69,12 +69,26 @@ Addresser::MultipleInheritance* Addresser::instance() {
|
|||
#ifdef GEODE_IS_WINDOWS
|
||||
#include <delayimp.h>
|
||||
extern "C" FARPROC WINAPI __delayLoadHelper2(PCImgDelayDescr pidd, FARPROC* ppfnIATEntry); // NOLINT(*-reserved-identifier)
|
||||
|
||||
FARPROC WINAPI delayLoadHook(unsigned dliNotify, PDelayLoadInfo pdli) {
|
||||
switch (dliNotify) {
|
||||
case dliFailLoadLib:
|
||||
case dliFailGetProc:
|
||||
// incase the delayload helper fails at all (missing symbol, or library entirely),
|
||||
// return -1, so we can more easily handle it below
|
||||
return (FARPROC)(-1);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" const PfnDliHook __pfnDliFailureHook2 = delayLoadHook;
|
||||
#endif
|
||||
|
||||
intptr_t Addresser::followThunkFunction(intptr_t address) {
|
||||
#ifdef GEODE_IS_WINDOWS
|
||||
#ifdef GEODE_IS_WINDOWS32
|
||||
// if theres a jmp at the start
|
||||
if (*reinterpret_cast<uint8_t*>(address) == 0xE9) {
|
||||
if (address && *reinterpret_cast<uint8_t*>(address) == 0xE9) {
|
||||
auto relative = *reinterpret_cast<uint32_t*>(address + 1);
|
||||
auto newAddress = address + relative + 5;
|
||||
// and if that jmp leads to a jmp dword ptr, only then follow it,
|
||||
|
@ -87,7 +101,7 @@ intptr_t Addresser::followThunkFunction(intptr_t address) {
|
|||
}
|
||||
|
||||
// check if first instruction is a jmp dword ptr [....], i.e. if the func is a thunk
|
||||
if (*reinterpret_cast<uint8_t*>(address) == 0xFF && *reinterpret_cast<uint8_t*>(address + 1) == 0x25) {
|
||||
if (address && *reinterpret_cast<uint8_t*>(address) == 0xFF && *reinterpret_cast<uint8_t*>(address + 1) == 0x25) {
|
||||
// read where the jmp reads from
|
||||
address = *reinterpret_cast<uint32_t*>(address + 2);
|
||||
// that then contains the actual address of the func
|
||||
|
@ -95,7 +109,7 @@ intptr_t Addresser::followThunkFunction(intptr_t address) {
|
|||
}
|
||||
|
||||
// if it starts with mov eax,..., it's a delay loaded func
|
||||
if (*reinterpret_cast<uint8_t*>(address) == 0xB8) {
|
||||
if (address && *reinterpret_cast<uint8_t*>(address) == 0xB8) {
|
||||
// follow the jmp to the tailMerge func and grab the ImgDelayDescr pointer from there
|
||||
// do it this way instead of grabbing it from the NT header ourselves because
|
||||
// we don't know the dll name
|
||||
|
@ -110,6 +124,60 @@ intptr_t Addresser::followThunkFunction(intptr_t address) {
|
|||
|
||||
// get the address of the function, loading the library if needed
|
||||
address = reinterpret_cast<intptr_t>(__delayLoadHelper2(idd, imp));
|
||||
|
||||
// if the helper failed, it will return -1, so we can handle it here
|
||||
if (address == -1) {
|
||||
address = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef GEODE_IS_WINDOWS64
|
||||
static constexpr auto checkByteSequence = [](uintptr_t address, const std::initializer_list<uint8_t>& bytes) {
|
||||
for (auto byte : bytes) {
|
||||
if (*reinterpret_cast<uint8_t*>(address++) != byte) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// check if first instruction is a jmp qword ptr [rip + ...], i.e. if the func is a thunk
|
||||
// FF 25 xxxxxxxx
|
||||
if (address && checkByteSequence(address, {0xFF, 0x25})) {
|
||||
const auto offset = *reinterpret_cast<int32_t*>(address + 2);
|
||||
// rip is at address + 6 (size of the instruction)
|
||||
address = *reinterpret_cast<uintptr_t*>(address + 6 + offset);
|
||||
}
|
||||
|
||||
// if it starts with lea eax,..., it's a delay loaded func
|
||||
// 48 8D 05 xxxxxxxx
|
||||
if (address && checkByteSequence(address, {0x48, 0x8d, 0x05})) {
|
||||
// follow the jmp to the tailMerge func and grab the ImgDelayDescr pointer from there
|
||||
// do it this way instead of grabbing it from the NT header ourselves because
|
||||
// we don't know the dll name
|
||||
auto leaAddress = address + 7 + *reinterpret_cast<int32_t*>(address + 3);
|
||||
|
||||
auto jmpOffset = *reinterpret_cast<int32_t*>(address + 7 + 1);
|
||||
auto tailMergeAddr = address + 7 + jmpOffset + 5;
|
||||
// this is quite a scary offset, but lets hope it works
|
||||
auto leaAddr = tailMergeAddr + 51;
|
||||
// make sure leaAddr is pointing to a lea rcx, [rip + ...]
|
||||
if (leaAddr && checkByteSequence(leaAddr, {0x48, 0x8d, 0x0d})) {
|
||||
auto offset = *reinterpret_cast<int32_t*>(leaAddr + 3);
|
||||
auto did = reinterpret_cast<PCImgDelayDescr>(leaAddr + 7 + offset);
|
||||
address = reinterpret_cast<intptr_t>(__delayLoadHelper2(did, reinterpret_cast<FARPROC*>(leaAddress)));
|
||||
|
||||
if (address == -1) {
|
||||
address = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if theres a jmp at the start
|
||||
if (address && *reinterpret_cast<uint8_t*>(address) == 0xE9) {
|
||||
auto relative = *reinterpret_cast<uint32_t*>(address + 1);
|
||||
auto newAddress = address + relative + 5;
|
||||
address = newAddress;
|
||||
}
|
||||
#endif
|
||||
return address;
|
||||
|
|
Loading…
Add table
Reference in a new issue