enderalse/source/fs.dll/skse64/skse64_common/BranchTrampoline.cpp

255 lines
4.9 KiB
C++

#include "BranchTrampoline.h"
#include "SafeWrite.h"
#include <climits>
BranchTrampoline g_branchTrampoline;
BranchTrampoline g_localTrampoline;
BranchTrampoline::BranchTrampoline()
:m_base(nullptr)
, m_len(0)
, m_allocated(0)
, m_curAlloc(nullptr)
{
//
}
BranchTrampoline::~BranchTrampoline()
{
Destroy();
}
bool BranchTrampoline::Create(size_t len, void * module)
{
if (!module) module = GetModuleHandle(NULL);
// search backwards from module base
uintptr_t moduleBase = uintptr_t(module);
uintptr_t addr = moduleBase;
uintptr_t lowestOKAddress = moduleBase - 0x80000000 + (1024 * 1024 * 128); // largest 32-bit displacement with 128MB scratch space
addr--;
while (!m_base)
{
MEMORY_BASIC_INFORMATION info;
if (!VirtualQuery((void *)addr, &info, sizeof(info)))
{
_ERROR("VirtualQuery failed: %08X", GetLastError());
break;
}
if (info.State == MEM_FREE)
{
// free block, big enough?
if (info.RegionSize >= len)
{
// try to allocate it
addr = ((uintptr_t)info.BaseAddress) + info.RegionSize - len;
m_base = (void *)VirtualAlloc((void *)addr, len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (m_base)
{
m_len = len;
m_allocated = 0;
}
else
{
_WARNING("trampoline alloc %016I64Xx%016I64X failed (%08X)", addr, len, GetLastError());
}
}
}
// move back and try again
if (!m_base)
{
addr = ((uintptr_t)info.BaseAddress) - 1;
}
if (addr < lowestOKAddress)
{
_ERROR("couldn't allocate trampoline, no free space before image");
break;
}
}
return m_base != nullptr;
}
void BranchTrampoline::Destroy()
{
if (m_base)
{
VirtualFree(m_base, 0, MEM_RELEASE);
m_base = nullptr;
}
}
void BranchTrampoline::SetBase(size_t len, void * base)
{
ASSERT(!m_base);
m_base = base;
m_len = len;
m_allocated = 0;
m_curAlloc = nullptr;
}
void * BranchTrampoline::StartAlloc()
{
ASSERT(m_base);
ASSERT(!m_curAlloc);
m_curAlloc = ((UInt8 *)m_base) + m_allocated;
return m_curAlloc;
}
void BranchTrampoline::EndAlloc(const void * end)
{
ASSERT(m_base);
ASSERT(m_curAlloc);
size_t len = uintptr_t(end) - uintptr_t(m_curAlloc);
ASSERT(len <= Remain());
m_allocated += len;
m_curAlloc = nullptr;
}
void * BranchTrampoline::Allocate(size_t size)
{
ASSERT(m_base);
void * result = nullptr;
if (size <= Remain())
{
result = ((UInt8 *)m_base) + m_allocated;
m_allocated += size;
}
return result;
}
bool BranchTrampoline::Write6Branch(uintptr_t src, uintptr_t dst)
{
return Write6Branch_Internal(src, dst, 0x25);
}
bool BranchTrampoline::Write6Call(uintptr_t src, uintptr_t dst)
{
return Write6Branch_Internal(src, dst, 0x15);
}
bool BranchTrampoline::Write5Branch(uintptr_t src, uintptr_t dst)
{
return Write5Branch_Internal(src, dst, 0xE9);
}
bool BranchTrampoline::Write5Call(uintptr_t src, uintptr_t dst)
{
return Write5Branch_Internal(src, dst, 0xE8);
}
bool BranchTrampoline::Write6Branch_Internal(uintptr_t src, uintptr_t dst, UInt8 op)
{
bool result = false;
uintptr_t * trampoline = (uintptr_t *)Allocate();
if (trampoline)
{
uintptr_t trampolineAddr = (uintptr_t)trampoline;
uintptr_t nextInstr = src + 6;
ptrdiff_t trampolineDispl = trampolineAddr - nextInstr;
if ((trampolineDispl >= _I32_MIN) && (trampolineDispl <= _I32_MAX))
{
UInt8 code[6];
// jmp [rip+imm32]
code[0] = 0xFF;
code[1] = op;
*((SInt32 *)&code[2]) = (SInt32)trampolineDispl;
SafeWriteBuf(src, code, sizeof(code));
*trampoline = dst;
result = true;
}
}
// do this for now so it's obvious when something goes wrong
ASSERT(result);
return result;
}
bool BranchTrampoline::Write5Branch_Internal(uintptr_t src, uintptr_t dst, UInt8 op)
{
bool result = false;
#pragma pack(push, 1)
// code placed in trampoline
struct TrampolineCode
{
// jmp [rip]
UInt8 escape; // FF
UInt8 modrm; // 25
UInt32 displ; // 00000000
// rip points here
UInt64 dst; // target
void Init(uintptr_t _dst)
{
escape = 0xFF;
modrm = 0x25;
displ = 0;
dst = _dst;
}
};
struct HookCode
{
// jmp disp32
UInt8 op; // E9 for jmp, E8 for call
SInt32 displ; //
void Init(SInt32 _displ, UInt8 _op)
{
op = _op;
displ = _displ;
}
};
#pragma pack(pop)
STATIC_ASSERT(sizeof(TrampolineCode) == 14);
STATIC_ASSERT(sizeof(HookCode) == 5);
TrampolineCode * trampolineCode = (TrampolineCode *)Allocate(sizeof(TrampolineCode));
if (trampolineCode)
{
trampolineCode->Init(dst);
HookCode hookCode;
uintptr_t trampolineAddr = uintptr_t(trampolineCode);
uintptr_t nextInstr = src + sizeof(hookCode);
ptrdiff_t trampolineDispl = trampolineAddr - nextInstr;
// should never fail because we're branching in to the trampoline
ASSERT((trampolineDispl >= _I32_MIN) && (trampolineDispl <= _I32_MAX));
hookCode.Init(trampolineDispl, op);
SafeWriteBuf(src, &hookCode, sizeof(hookCode));
result = true;
}
// do this for now so it's obvious when something goes wrong
ASSERT(result);
return result;
}