Cstrike: Import some memory tools from SourceMod
This commit is contained in:
419
public/memtools/CDetour/asm/asm.c
Normal file
419
public/memtools/CDetour/asm/asm.c
Normal file
@ -0,0 +1,419 @@
|
||||
#include "asm.h"
|
||||
|
||||
#ifndef WIN32
|
||||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
|
||||
#define REG_EAX 0
|
||||
#define REG_ECX 1
|
||||
#define REG_EDX 2
|
||||
#define REG_EBX 3
|
||||
|
||||
#define IA32_MOV_REG_IMM 0xB8 // encoding is +r <imm32>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Checks if a call to a fpic thunk has just been written into dest.
|
||||
* If found replaces it with a direct mov that sets the required register to the value of pc.
|
||||
*
|
||||
* @param dest Destination buffer where a call opcode + addr (5 bytes) has just been written.
|
||||
* @param pc The program counter value that needs to be set (usually the next address from the source).
|
||||
* @noreturn
|
||||
*/
|
||||
void check_thunks(unsigned char *dest, unsigned char *pc)
|
||||
{
|
||||
#if defined WIN32
|
||||
return;
|
||||
#else
|
||||
/* Step write address back 4 to the start of the function address */
|
||||
unsigned char *writeaddr = dest - 4;
|
||||
unsigned char *calloffset = *(unsigned char **)writeaddr;
|
||||
unsigned char *calladdr = (unsigned char *)(dest + (unsigned int)calloffset);
|
||||
|
||||
/* Lookup name of function being called */
|
||||
if ((*calladdr == 0x8B) && (*(calladdr+2) == 0x24) && (*(calladdr+3) == 0xC3))
|
||||
{
|
||||
//a thunk maybe?
|
||||
char movByte = IA32_MOV_REG_IMM;
|
||||
|
||||
/* Calculate the correct mov opcode */
|
||||
switch (*(calladdr+1))
|
||||
{
|
||||
case 0x04:
|
||||
{
|
||||
movByte += REG_EAX;
|
||||
break;
|
||||
}
|
||||
case 0x1C:
|
||||
{
|
||||
movByte += REG_EBX;
|
||||
break;
|
||||
}
|
||||
case 0x0C:
|
||||
{
|
||||
movByte += REG_ECX;
|
||||
break;
|
||||
}
|
||||
case 0x14:
|
||||
{
|
||||
movByte += REG_EDX;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
//Msg("Unknown thunk: %c\n", *(calladdr+1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Move our write address back one to where the call opcode was */
|
||||
writeaddr--;
|
||||
|
||||
|
||||
/* Write our mov */
|
||||
*writeaddr = movByte;
|
||||
writeaddr++;
|
||||
|
||||
/* Write the value - The provided program counter value */
|
||||
*(void **)writeaddr = (void *)pc;
|
||||
writeaddr += 4;
|
||||
}
|
||||
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
//if dest is NULL, returns minimum number of bytes needed to be copied
|
||||
//if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs
|
||||
//http://www.devmaster.net/forums/showthread.php?t=2311
|
||||
int copy_bytes(unsigned char *func, unsigned char* dest, int required_len) {
|
||||
int bytecount = 0;
|
||||
|
||||
while(bytecount < required_len && *func != 0xCC)
|
||||
{
|
||||
// prefixes F0h, F2h, F3h, 66h, 67h, D8h-DFh, 2Eh, 36h, 3Eh, 26h, 64h and 65h
|
||||
int operandSize = 4;
|
||||
int FPU = 0;
|
||||
int twoByte = 0;
|
||||
unsigned char opcode = 0x90;
|
||||
unsigned char modRM = 0xFF;
|
||||
while(*func == 0xF0 ||
|
||||
*func == 0xF2 ||
|
||||
*func == 0xF3 ||
|
||||
(*func & 0xFC) == 0x64 ||
|
||||
(*func & 0xF8) == 0xD8 ||
|
||||
(*func & 0x7E) == 0x62)
|
||||
{
|
||||
if(*func == 0x66)
|
||||
{
|
||||
operandSize = 2;
|
||||
}
|
||||
else if((*func & 0xF8) == 0xD8)
|
||||
{
|
||||
FPU = *func;
|
||||
if (dest)
|
||||
*dest++ = *func++;
|
||||
else
|
||||
func++;
|
||||
bytecount++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (dest)
|
||||
*dest++ = *func++;
|
||||
else
|
||||
func++;
|
||||
bytecount++;
|
||||
}
|
||||
|
||||
// two-byte opcode byte
|
||||
if(*func == 0x0F)
|
||||
{
|
||||
twoByte = 1;
|
||||
if (dest)
|
||||
*dest++ = *func++;
|
||||
else
|
||||
func++;
|
||||
bytecount++;
|
||||
}
|
||||
|
||||
// opcode byte
|
||||
opcode = *func++;
|
||||
if (dest) *dest++ = opcode;
|
||||
bytecount++;
|
||||
|
||||
// mod R/M byte
|
||||
modRM = 0xFF;
|
||||
if(FPU)
|
||||
{
|
||||
if((opcode & 0xC0) != 0xC0)
|
||||
{
|
||||
modRM = opcode;
|
||||
}
|
||||
}
|
||||
else if(!twoByte)
|
||||
{
|
||||
if((opcode & 0xC4) == 0x00 ||
|
||||
((opcode & 0xF4) == 0x60 && ((opcode & 0x0A) == 0x02 || (opcode & 0x09) == 0x09)) ||
|
||||
(opcode & 0xF0) == 0x80 ||
|
||||
((opcode & 0xF8) == 0xC0 && (opcode & 0x0E) != 0x02) ||
|
||||
(opcode & 0xFC) == 0xD0 ||
|
||||
(opcode & 0xF6) == 0xF6)
|
||||
{
|
||||
modRM = *func++;
|
||||
if (dest) *dest++ = modRM;
|
||||
bytecount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(((opcode & 0xF0) == 0x00 && (opcode & 0x0F) >= 0x04 && (opcode & 0x0D) != 0x0D) ||
|
||||
(opcode & 0xF0) == 0x30 ||
|
||||
opcode == 0x77 ||
|
||||
(opcode & 0xF0) == 0x80 ||
|
||||
((opcode & 0xF0) == 0xA0 && (opcode & 0x07) <= 0x02) ||
|
||||
(opcode & 0xF8) == 0xC8)
|
||||
{
|
||||
// No mod R/M byte
|
||||
}
|
||||
else
|
||||
{
|
||||
modRM = *func++;
|
||||
if (dest) *dest++ = modRM;
|
||||
bytecount++;
|
||||
}
|
||||
}
|
||||
|
||||
// SIB
|
||||
if((modRM & 0x07) == 0x04 &&
|
||||
(modRM & 0xC0) != 0xC0)
|
||||
{
|
||||
if (dest)
|
||||
*dest++ = *func++; //SIB
|
||||
else
|
||||
func++;
|
||||
bytecount++;
|
||||
}
|
||||
|
||||
// mod R/M displacement
|
||||
|
||||
// Dword displacement, no base
|
||||
if((modRM & 0xC5) == 0x05) {
|
||||
if (dest) {
|
||||
*(unsigned int*)dest = *(unsigned int*)func;
|
||||
dest += 4;
|
||||
}
|
||||
func += 4;
|
||||
bytecount += 4;
|
||||
}
|
||||
|
||||
// Byte displacement
|
||||
if((modRM & 0xC0) == 0x40) {
|
||||
if (dest)
|
||||
*dest++ = *func++;
|
||||
else
|
||||
func++;
|
||||
bytecount++;
|
||||
}
|
||||
|
||||
// Dword displacement
|
||||
if((modRM & 0xC0) == 0x80) {
|
||||
if (dest) {
|
||||
*(unsigned int*)dest = *(unsigned int*)func;
|
||||
dest += 4;
|
||||
}
|
||||
func += 4;
|
||||
bytecount += 4;
|
||||
}
|
||||
|
||||
// immediate
|
||||
if(FPU)
|
||||
{
|
||||
// Can't have immediate operand
|
||||
}
|
||||
else if(!twoByte)
|
||||
{
|
||||
if((opcode & 0xC7) == 0x04 ||
|
||||
(opcode & 0xFE) == 0x6A || // PUSH/POP/IMUL
|
||||
(opcode & 0xF0) == 0x70 || // Jcc
|
||||
opcode == 0x80 ||
|
||||
opcode == 0x83 ||
|
||||
(opcode & 0xFD) == 0xA0 || // MOV
|
||||
opcode == 0xA8 || // TEST
|
||||
(opcode & 0xF8) == 0xB0 || // MOV
|
||||
(opcode & 0xFE) == 0xC0 || // RCL
|
||||
opcode == 0xC6 || // MOV
|
||||
opcode == 0xCD || // INT
|
||||
(opcode & 0xFE) == 0xD4 || // AAD/AAM
|
||||
(opcode & 0xF8) == 0xE0 || // LOOP/JCXZ
|
||||
opcode == 0xEB ||
|
||||
(opcode == 0xF6 && (modRM & 0x30) == 0x00)) // TEST
|
||||
{
|
||||
if (dest)
|
||||
*dest++ = *func++;
|
||||
else
|
||||
func++;
|
||||
bytecount++;
|
||||
}
|
||||
else if((opcode & 0xF7) == 0xC2) // RET
|
||||
{
|
||||
if (dest) {
|
||||
*(unsigned short*)dest = *(unsigned short*)func;
|
||||
dest += 2;
|
||||
}
|
||||
func += 2;
|
||||
bytecount += 2;
|
||||
}
|
||||
else if((opcode & 0xFC) == 0x80 ||
|
||||
(opcode & 0xC7) == 0x05 ||
|
||||
(opcode & 0xF8) == 0xB8 ||
|
||||
(opcode & 0xFE) == 0xE8 || // CALL/Jcc
|
||||
(opcode & 0xFE) == 0x68 ||
|
||||
(opcode & 0xFC) == 0xA0 ||
|
||||
(opcode & 0xEE) == 0xA8 ||
|
||||
opcode == 0xC7 ||
|
||||
(opcode == 0xF7 && (modRM & 0x30) == 0x00))
|
||||
{
|
||||
if (dest) {
|
||||
//Fix CALL/JMP offset
|
||||
if ((opcode & 0xFE) == 0xE8) {
|
||||
if (operandSize == 4)
|
||||
{
|
||||
*(long*)dest = ((func + *(long*)func) - dest);
|
||||
|
||||
//pRED* edit. func is the current address of the call address, +4 is the next instruction, so the value of $pc
|
||||
check_thunks(dest+4, func+4);
|
||||
}
|
||||
else
|
||||
*(short*)dest = ((func + *(short*)func) - dest);
|
||||
|
||||
} else {
|
||||
if (operandSize == 4)
|
||||
*(unsigned long*)dest = *(unsigned long*)func;
|
||||
else
|
||||
*(unsigned short*)dest = *(unsigned short*)func;
|
||||
}
|
||||
dest += operandSize;
|
||||
}
|
||||
func += operandSize;
|
||||
bytecount += operandSize;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(opcode == 0xBA || // BT
|
||||
opcode == 0x0F || // 3DNow!
|
||||
(opcode & 0xFC) == 0x70 || // PSLLW
|
||||
(opcode & 0xF7) == 0xA4 || // SHLD
|
||||
opcode == 0xC2 ||
|
||||
opcode == 0xC4 ||
|
||||
opcode == 0xC5 ||
|
||||
opcode == 0xC6)
|
||||
{
|
||||
if (dest)
|
||||
*dest++ = *func++;
|
||||
else
|
||||
func++;
|
||||
}
|
||||
else if((opcode & 0xF0) == 0x80) // Jcc -i
|
||||
{
|
||||
if (dest) {
|
||||
if (operandSize == 4)
|
||||
*(unsigned long*)dest = *(unsigned long*)func;
|
||||
else
|
||||
*(unsigned short*)dest = *(unsigned short*)func;
|
||||
|
||||
dest += operandSize;
|
||||
}
|
||||
func += operandSize;
|
||||
bytecount += operandSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bytecount;
|
||||
}
|
||||
|
||||
//insert a specific JMP instruction at the given location
|
||||
void inject_jmp(void* src, void* dest) {
|
||||
*(unsigned char*)src = OP_JMP;
|
||||
*(long*)((unsigned char*)src+1) = (long)((unsigned char*)dest - ((unsigned char*)src + OP_JMP_SIZE));
|
||||
}
|
||||
|
||||
//fill a given block with NOPs
|
||||
void fill_nop(void* src, unsigned int len) {
|
||||
unsigned char* src2 = (unsigned char*)src;
|
||||
while (len) {
|
||||
*src2++ = OP_NOP;
|
||||
--len;
|
||||
}
|
||||
}
|
||||
|
||||
void* eval_jump(void* src) {
|
||||
unsigned char* addr = (unsigned char*)src;
|
||||
|
||||
if (!addr) return 0;
|
||||
|
||||
//import table jump
|
||||
if (addr[0] == OP_PREFIX && addr[1] == OP_JMP_SEG) {
|
||||
addr += 2;
|
||||
addr = *(unsigned char**)addr;
|
||||
//TODO: if addr points into the IAT
|
||||
return *(void**)addr;
|
||||
}
|
||||
|
||||
//8bit offset
|
||||
else if (addr[0] == OP_JMP_BYTE) {
|
||||
addr = &addr[OP_JMP_BYTE_SIZE] + *(char*)&addr[1];
|
||||
//mangled 32bit jump?
|
||||
if (addr[0] == OP_JMP) {
|
||||
addr = addr + *(int*)&addr[1];
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
/*
|
||||
//32bit offset
|
||||
else if (addr[0] == OP_JMP) {
|
||||
addr = &addr[OP_JMP_SIZE] + *(int*)&addr[1];
|
||||
}
|
||||
*/
|
||||
|
||||
return addr;
|
||||
}
|
||||
/*
|
||||
from ms detours package
|
||||
static bool detour_is_imported(PBYTE pbCode, PBYTE pbAddress)
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
VirtualQuery((PVOID)pbCode, &mbi, sizeof(mbi));
|
||||
__try {
|
||||
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase;
|
||||
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDosHeader +
|
||||
pDosHeader->e_lfanew);
|
||||
if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pbAddress >= ((PBYTE)pDosHeader +
|
||||
pNtHeader->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) &&
|
||||
pbAddress < ((PBYTE)pDosHeader +
|
||||
pNtHeader->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress +
|
||||
pNtHeader->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
__except(EXCEPTION_EXECUTE_HANDLER) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*/
|
40
public/memtools/CDetour/asm/asm.h
Normal file
40
public/memtools/CDetour/asm/asm.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef __ASM_H__
|
||||
#define __ASM_H__
|
||||
|
||||
#define OP_JMP 0xE9
|
||||
#define OP_JMP_SIZE 5
|
||||
|
||||
#define OP_NOP 0x90
|
||||
#define OP_NOP_SIZE 1
|
||||
|
||||
#define OP_PREFIX 0xFF
|
||||
#define OP_JMP_SEG 0x25
|
||||
|
||||
#define OP_JMP_BYTE 0xEB
|
||||
#define OP_JMP_BYTE_SIZE 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void check_thunks(unsigned char *dest, unsigned char *pc);
|
||||
|
||||
//if dest is NULL, returns minimum number of bytes needed to be copied
|
||||
//if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs
|
||||
//http://www.devmaster.net/forums/showthread.php?t=2311
|
||||
int copy_bytes(unsigned char *func, unsigned char* dest, int required_len);
|
||||
|
||||
//insert a specific JMP instruction at the given location
|
||||
void inject_jmp(void* src, void* dest);
|
||||
|
||||
//fill a given block with NOPs
|
||||
void fill_nop(void* src, unsigned int len);
|
||||
|
||||
//evaluate a JMP at the target
|
||||
void* eval_jump(void* src);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //__ASM_H__
|
129
public/memtools/CDetour/detourhelpers.h
Normal file
129
public/memtools/CDetour/detourhelpers.h
Normal file
@ -0,0 +1,129 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id: detourhelpers.h 248 2008-08-27 00:56:22Z pred $
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_SOURCEMOD_DETOURHELPERS_H_
|
||||
#define _INCLUDE_SOURCEMOD_DETOURHELPERS_H_
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#include <sys/mman.h>
|
||||
#ifndef PAGE_SIZE
|
||||
#define PAGE_SIZE 4096
|
||||
#endif
|
||||
#define ALIGN(ar) ((long)ar & ~(PAGE_SIZE-1))
|
||||
#define PAGE_EXECUTE_READWRITE PROT_READ|PROT_WRITE|PROT_EXEC
|
||||
#if defined(__linux)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
struct patch_t
|
||||
{
|
||||
patch_t()
|
||||
{
|
||||
patch[0] = 0;
|
||||
bytes = 0;
|
||||
}
|
||||
unsigned char patch[20];
|
||||
size_t bytes;
|
||||
};
|
||||
|
||||
inline void ProtectMemory(void *addr, int length, int prot)
|
||||
{
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
void *addr2 = (void *)ALIGN(addr);
|
||||
mprotect(addr2, sysconf(_SC_PAGESIZE), prot);
|
||||
#elif defined(WIN32)
|
||||
DWORD old_prot;
|
||||
VirtualProtect(addr, length, prot, &old_prot);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline unsigned char *AllocatePageMemory(size_t size)
|
||||
{
|
||||
#if defined WIN32
|
||||
return (unsigned char *)VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
#elif defined __GNUC__
|
||||
#if defined __APPLE__
|
||||
unsigned char *addr = (unsigned char *)valloc(size);
|
||||
#else
|
||||
unsigned char *addr = (unsigned char *)memalign(sysconf(_SC_PAGESIZE), size);
|
||||
#endif
|
||||
mprotect(addr, size, PROT_READ | PROT_WRITE | PROT_EXEC);
|
||||
return addr;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void FreePageMemory(void *addr)
|
||||
{
|
||||
#if defined(WIN32)
|
||||
VirtualFree(addr, 0, MEM_RELEASE);
|
||||
#else
|
||||
free(addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void SetMemPatchable(void *address, size_t size)
|
||||
{
|
||||
ProtectMemory(address, (int)size, PAGE_EXECUTE_READWRITE);
|
||||
}
|
||||
|
||||
inline void DoGatePatch(unsigned char *target, void *callback)
|
||||
{
|
||||
SetMemPatchable(target, 20);
|
||||
|
||||
target[0] = 0xFF; /* JMP */
|
||||
target[1] = 0x25; /* MEM32 */
|
||||
*(void **)(&target[2]) = callback;
|
||||
}
|
||||
|
||||
inline void ApplyPatch(void *address, int offset, const patch_t *patch, patch_t *restore)
|
||||
{
|
||||
ProtectMemory(address, 20, PAGE_EXECUTE_READWRITE);
|
||||
|
||||
unsigned char *addr = (unsigned char *)address + offset;
|
||||
if (restore)
|
||||
{
|
||||
for (size_t i=0; i<patch->bytes; i++)
|
||||
{
|
||||
restore->patch[i] = addr[i];
|
||||
}
|
||||
restore->bytes = patch->bytes;
|
||||
}
|
||||
|
||||
for (size_t i=0; i<patch->bytes; i++)
|
||||
{
|
||||
addr[i] = patch->patch[i];
|
||||
}
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_SOURCEMOD_DETOURHELPERS_H_
|
231
public/memtools/CDetour/detours.cpp
Normal file
231
public/memtools/CDetour/detours.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id: detours.cpp 248 2008-08-27 00:56:22Z pred $
|
||||
*/
|
||||
|
||||
#include "detours.h"
|
||||
#include "asm/asm.h"
|
||||
|
||||
//ISourcePawnEngine *CDetourManager::spengine = NULL;
|
||||
//IGameConfig *CDetourManager::gameconf = NULL;
|
||||
|
||||
void CDetourManager::Init(/*ISourcePawnEngine *spengine, IGameConfig *gameconf*/)
|
||||
{
|
||||
//CDetourManager::spengine = spengine;
|
||||
//CDetourManager::gameconf = gameconf;
|
||||
}
|
||||
|
||||
CDetour *CDetourManager::CreateDetour(void *callbackfunction, void **trampoline, const char *signame)
|
||||
{
|
||||
CDetour *detour = new CDetour(callbackfunction, trampoline, signame);
|
||||
if (detour)
|
||||
{
|
||||
if (!detour->Init(/*spengine, gameconf*/))
|
||||
{
|
||||
delete detour;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return detour;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CDetour *CDetourManager::CreateDetour(void *callbackfunction, void **trampoline, void *address)
|
||||
{
|
||||
CDetour *detour = new CDetour(callbackfunction, trampoline, address);
|
||||
if (detour)
|
||||
{
|
||||
if (!detour->Init(/*spengine, gameconf*/))
|
||||
{
|
||||
delete detour;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return detour;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
CDetour::CDetour(void *callbackfunction, void **trampoline, const char *signame)
|
||||
{
|
||||
enabled = false;
|
||||
detoured = false;
|
||||
detour_address = NULL;
|
||||
detour_trampoline = NULL;
|
||||
this->signame = signame;
|
||||
this->detour_callback = callbackfunction;
|
||||
this->address = NULL;
|
||||
//spengine = NULL;
|
||||
//gameconf = NULL;
|
||||
this->trampoline = trampoline;
|
||||
}
|
||||
|
||||
CDetour::CDetour(void *callbackfunction, void **trampoline, void *address)
|
||||
{
|
||||
enabled = false;
|
||||
detoured = false;
|
||||
detour_address = address;
|
||||
detour_trampoline = NULL;
|
||||
this->signame = NULL;
|
||||
this->detour_callback = callbackfunction;
|
||||
this->address = address;
|
||||
//spengine = NULL;
|
||||
//gameconf = NULL;
|
||||
this->trampoline = trampoline;
|
||||
}
|
||||
|
||||
|
||||
bool CDetour::Init(/*ISourcePawnEngine *spengine, IGameConfig *gameconf*/)
|
||||
{
|
||||
//this->spengine = spengine;
|
||||
//this->gameconf = gameconf;
|
||||
|
||||
if (!CreateDetour())
|
||||
{
|
||||
enabled = false;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
enabled = true;
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void CDetour::Destroy()
|
||||
{
|
||||
DeleteDetour();
|
||||
delete this;
|
||||
}
|
||||
|
||||
bool CDetour::IsEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool CDetour::CreateDetour()
|
||||
{
|
||||
/*if (signame != NULL && !gameconf->GetMemSig(signame, &detour_address))
|
||||
{
|
||||
g_pSM->LogError(myself, "Could not locate %s - Disabling detour", signame);
|
||||
return false;
|
||||
}*/
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
detour_address = address;
|
||||
}
|
||||
|
||||
if (!detour_address)
|
||||
{
|
||||
//g_pSM->LogError(myself, "Sigscan for %s failed - Disabling detour to prevent crashes", signame);
|
||||
return false;
|
||||
}
|
||||
|
||||
detour_restore.bytes = copy_bytes((unsigned char *)detour_address, NULL, OP_JMP_SIZE+1);
|
||||
|
||||
/* First, save restore bits */
|
||||
for (size_t i=0; i<detour_restore.bytes; i++)
|
||||
{
|
||||
detour_restore.patch[i] = ((unsigned char *)detour_address)[i];
|
||||
}
|
||||
|
||||
JitWriter wr;
|
||||
JitWriter *jit = ≀
|
||||
unsigned int CodeSize = 0;
|
||||
|
||||
wr.outbase = NULL;
|
||||
wr.outptr = NULL;
|
||||
|
||||
jit_rewind:
|
||||
|
||||
/* Patch old bytes in */
|
||||
if (wr.outbase != NULL)
|
||||
{
|
||||
copy_bytes((unsigned char *)detour_address, (unsigned char*)wr.outptr, detour_restore.bytes);
|
||||
}
|
||||
wr.outptr += detour_restore.bytes;
|
||||
|
||||
/* Return to the original function */
|
||||
unsigned int call = IA32_Jump_Imm32(jit, 0);
|
||||
IA32_Write_Jump32_Abs(jit, call, (unsigned char *)detour_address + detour_restore.bytes);
|
||||
|
||||
if (wr.outbase == NULL)
|
||||
{
|
||||
CodeSize = wr.get_outputpos();
|
||||
wr.outbase = (char *)AllocatePageMemory(CodeSize);
|
||||
//spengine->SetReadWrite(wr.outbase);
|
||||
wr.outptr = wr.outbase;
|
||||
detour_trampoline = wr.outbase;
|
||||
goto jit_rewind;
|
||||
}
|
||||
|
||||
//spengine->SetReadExecute(wr.outbase);
|
||||
|
||||
*trampoline = detour_trampoline;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDetour::DeleteDetour()
|
||||
{
|
||||
if (detoured)
|
||||
{
|
||||
DisableDetour();
|
||||
}
|
||||
|
||||
if (detour_trampoline)
|
||||
{
|
||||
/* Free the allocated trampoline memory */
|
||||
FreePageMemory(detour_trampoline);
|
||||
detour_trampoline = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CDetour::EnableDetour()
|
||||
{
|
||||
if (!detoured)
|
||||
{
|
||||
DoGatePatch((unsigned char *)detour_address, &detour_callback);
|
||||
detoured = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CDetour::DisableDetour()
|
||||
{
|
||||
if (detoured)
|
||||
{
|
||||
/* Remove the patch */
|
||||
ApplyPatch(detour_address, 0, &detour_restore, NULL);
|
||||
detoured = false;
|
||||
}
|
||||
}
|
361
public/memtools/CDetour/detours.h
Normal file
361
public/memtools/CDetour/detours.h
Normal file
@ -0,0 +1,361 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id: detours.h 257 2008-09-23 03:12:13Z pred $
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_SOURCEMOD_DETOURS_H_
|
||||
#define _INCLUDE_SOURCEMOD_DETOURS_H_
|
||||
|
||||
#include "amxxmodule.h"
|
||||
//#include <jit/jit_helpers.h>
|
||||
//#include <jit/x86/x86_macros.h>
|
||||
#include "detourhelpers.h"
|
||||
|
||||
/**
|
||||
* CDetours class for SourceMod Extensions by pRED*
|
||||
* detourhelpers.h entirely stolen from CSS:DM and were written by BAILOPAN (I assume).
|
||||
* asm.h/c from devmaster.net (thanks cybermind) edited by pRED* to handle gcc -fPIC thunks correctly
|
||||
* Concept by Nephyrin Zey (http://www.doublezen.net/) and Windows Detour Library (http://research.microsoft.com/sn/detours/)
|
||||
* Member function pointer ideas by Don Clugston (http://www.codeproject.com/cpp/FastDelegate.asp)
|
||||
*/
|
||||
|
||||
#define DETOUR_MEMBER_CALL(name) (this->*name##_Actual)
|
||||
#define DETOUR_STATIC_CALL(name) (name##_Actual)
|
||||
|
||||
#define DETOUR_DECL_STATIC0(name, ret) \
|
||||
ret (*name##_Actual)(void) = NULL; \
|
||||
ret name(void)
|
||||
|
||||
#define DETOUR_DECL_STATIC1(name, ret, p1type, p1name) \
|
||||
ret (*name##_Actual)(p1type) = NULL; \
|
||||
ret name(p1type p1name)
|
||||
|
||||
#define DETOUR_DECL_STATIC4(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \
|
||||
ret (*name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \
|
||||
ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name)
|
||||
|
||||
#define DETOUR_DECL_MEMBER0(name, ret) \
|
||||
class name##Class \
|
||||
{ \
|
||||
public: \
|
||||
ret name(); \
|
||||
static ret (name##Class::* name##_Actual)(void); \
|
||||
}; \
|
||||
ret (name##Class::* name##Class::name##_Actual)(void) = NULL; \
|
||||
ret name##Class::name()
|
||||
|
||||
#define DETOUR_DECL_MEMBER1(name, ret, p1type, p1name) \
|
||||
class name##Class \
|
||||
{ \
|
||||
public: \
|
||||
ret name(p1type p1name); \
|
||||
static ret (name##Class::* name##_Actual)(p1type); \
|
||||
}; \
|
||||
ret (name##Class::* name##Class::name##_Actual)(p1type) = NULL; \
|
||||
ret name##Class::name(p1type p1name)
|
||||
|
||||
#define DETOUR_DECL_MEMBER2(name, ret, p1type, p1name, p2type, p2name) \
|
||||
class name##Class \
|
||||
{ \
|
||||
public: \
|
||||
ret name(p1type p1name, p2type p2name); \
|
||||
static ret (name##Class::* name##_Actual)(p1type, p2type); \
|
||||
}; \
|
||||
ret (name##Class::* name##Class::name##_Actual)(p1type, p2type) = NULL; \
|
||||
ret name##Class::name(p1type p1name, p2type p2name)
|
||||
|
||||
#define DETOUR_DECL_MEMBER3(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \
|
||||
class name##Class \
|
||||
{ \
|
||||
public: \
|
||||
ret name(p1type p1name, p2type p2name, p3type p3name); \
|
||||
static ret (name##Class::* name##_Actual)(p1type, p2type, p3type); \
|
||||
}; \
|
||||
ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type) = NULL; \
|
||||
ret name##Class::name(p1type p1name, p2type p2name, p3type p3name)
|
||||
|
||||
#define DETOUR_DECL_MEMBER4(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \
|
||||
class name##Class \
|
||||
{ \
|
||||
public: \
|
||||
ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name); \
|
||||
static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type); \
|
||||
}; \
|
||||
ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \
|
||||
ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name)
|
||||
|
||||
#define DETOUR_DECL_MEMBER8(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name) \
|
||||
class name##Class \
|
||||
{ \
|
||||
public: \
|
||||
ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name); \
|
||||
static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type); \
|
||||
}; \
|
||||
ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type) = NULL; \
|
||||
ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name)
|
||||
|
||||
|
||||
#define GET_MEMBER_CALLBACK(name) (void *)GetCodeAddress(&name##Class::name)
|
||||
#define GET_MEMBER_TRAMPOLINE(name) (void **)(&name##Class::name##_Actual)
|
||||
|
||||
#define GET_STATIC_CALLBACK(name) (void *)&name
|
||||
#define GET_STATIC_TRAMPOLINE(name) (void **)&name##_Actual
|
||||
|
||||
#define DETOUR_CREATE_MEMBER(name, gamedata, target) CDetourManager::CreateDetour(GET_MEMBER_CALLBACK(name), GET_MEMBER_TRAMPOLINE(name), gamedata, target);
|
||||
#define DETOUR_CREATE_STATIC(name, gamedata, target) CDetourManager::CreateDetour(GET_STATIC_CALLBACK(name), GET_STATIC_TRAMPOLINE(name), gamedata, target);
|
||||
#define DETOUR_CREATE_STATIC_FIXED(name, address) CDetourManager::CreateDetour(GET_STATIC_CALLBACK(name), GET_STATIC_TRAMPOLINE(name), address);
|
||||
|
||||
class GenericClass {};
|
||||
typedef void (GenericClass::*VoidFunc)();
|
||||
|
||||
inline void *GetCodeAddr(VoidFunc mfp)
|
||||
{
|
||||
return *(void **)&mfp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a member function pointer to a void pointer.
|
||||
* This relies on the assumption that the code address lies at mfp+0
|
||||
* This is the case for both g++ and later MSVC versions on non virtual functions but may be different for other compilers
|
||||
* Based on research by Don Clugston : http://www.codeproject.com/cpp/FastDelegate.asp
|
||||
*/
|
||||
#define GetCodeAddress(mfp) GetCodeAddr(reinterpret_cast<VoidFunc>(mfp))
|
||||
|
||||
class CDetourManager;
|
||||
|
||||
class CDetour
|
||||
{
|
||||
public:
|
||||
|
||||
bool IsEnabled();
|
||||
|
||||
/**
|
||||
* These would be somewhat self-explanatory I hope
|
||||
*/
|
||||
void EnableDetour();
|
||||
void DisableDetour();
|
||||
|
||||
void Destroy();
|
||||
|
||||
friend class CDetourManager;
|
||||
|
||||
protected:
|
||||
CDetour(void *callbackfunction, void **trampoline, const char *signame);
|
||||
CDetour(void *callbackfunction, void **trampoline, void *address);
|
||||
|
||||
bool Init();
|
||||
private:
|
||||
|
||||
/* These create/delete the allocated memory */
|
||||
bool CreateDetour();
|
||||
void DeleteDetour();
|
||||
|
||||
bool enabled;
|
||||
bool detoured;
|
||||
|
||||
patch_t detour_restore;
|
||||
/* Address of the detoured function */
|
||||
void *detour_address;
|
||||
/* Address of the allocated trampoline function */
|
||||
void *detour_trampoline;
|
||||
/* Address of the callback handler */
|
||||
void *detour_callback;
|
||||
/* The function pointer used to call our trampoline */
|
||||
void **trampoline;
|
||||
|
||||
const char *signame;
|
||||
void *address;
|
||||
};
|
||||
|
||||
class CDetourManager
|
||||
{
|
||||
public:
|
||||
|
||||
static void Init();
|
||||
|
||||
/**
|
||||
* Creates a new detour
|
||||
*
|
||||
* @param callbackfunction Void pointer to your detour callback function.
|
||||
* @param trampoline Address of the trampoline pointer
|
||||
* @param signame Section name containing a signature to fetch from the gamedata file.
|
||||
* @return A new CDetour pointer to control your detour.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* CBaseServer::ConnectClient(netadr_s &, int, int, int, char const*, char const*, char const*, int)
|
||||
*
|
||||
* Define a new class with the required function and a member function pointer to the same type:
|
||||
*
|
||||
* class CBaseServerDetour
|
||||
* {
|
||||
* public:
|
||||
* bool ConnectClient(void *netaddr_s, int, int, int, char const*, char const*, char const*, int);
|
||||
* static bool (CBaseServerDetour::* ConnectClient_Actual)(void *netaddr_s, int, int, int, char const*, char const*, char const*, int);
|
||||
* }
|
||||
*
|
||||
* void *callbackfunc = GetCodeAddress(&CBaseServerDetour::ConnectClient);
|
||||
* void **trampoline = (void **)(&CBaseServerDetour::ConnectClient_Actual);
|
||||
*
|
||||
* Creation:
|
||||
* CDetourManager::CreateDetour(callbackfunc, trampoline, "ConnectClient");
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* CBaseServerDetour::ConnectClient(void *netaddr_s, int, int, int, char const*, char const*, char const*, int)
|
||||
* {
|
||||
* //pre hook code
|
||||
* bool result = (this->*ConnectClient_Actual)(netaddr_s, rest of params);
|
||||
* //post hook code
|
||||
* return result;
|
||||
* }
|
||||
*
|
||||
* Note we changed the netadr_s reference into a void* to avoid needing to define the type
|
||||
*/
|
||||
static CDetour *CreateDetour(void *callbackfunction, void **trampoline, const char *signame);
|
||||
static CDetour *CreateDetour(void *callbackfunction, void **trampoline, void *address);
|
||||
|
||||
friend class CBlocker;
|
||||
friend class CDetour;
|
||||
|
||||
/*private:
|
||||
static ISourcePawnEngine *spengine;
|
||||
static IGameConfig *gameconf;*/
|
||||
};
|
||||
|
||||
|
||||
// Helpers from jit_helpers.h/x86_macros.h.
|
||||
|
||||
class JitWriter
|
||||
{
|
||||
public:
|
||||
inline cell read_cell()
|
||||
{
|
||||
cell val = *(inptr);
|
||||
inptr++;
|
||||
return val;
|
||||
}
|
||||
inline cell peek_cell()
|
||||
{
|
||||
return *inptr;
|
||||
}
|
||||
inline cell *read_cellptr()
|
||||
{
|
||||
cell *val = *(cell **)(inptr);
|
||||
inptr++;
|
||||
return val;
|
||||
}
|
||||
inline void write_ubyte(unsigned char c)
|
||||
{
|
||||
if (outbase)
|
||||
{
|
||||
*outptr = c;
|
||||
}
|
||||
outptr++;
|
||||
}
|
||||
inline void write_ushort(unsigned short c)
|
||||
{
|
||||
if (outbase)
|
||||
{
|
||||
*(unsigned short *)outptr = c;
|
||||
}
|
||||
outptr += sizeof(unsigned short);
|
||||
}
|
||||
inline void write_byte(char c)
|
||||
{
|
||||
if (outbase)
|
||||
{
|
||||
*outptr = c;
|
||||
}
|
||||
outptr++;
|
||||
}
|
||||
inline void write_int32(int c)
|
||||
{
|
||||
if (outbase)
|
||||
{
|
||||
*(int *)outptr = c;
|
||||
}
|
||||
outptr += sizeof(int);
|
||||
}
|
||||
inline void write_uint32(unsigned int c)
|
||||
{
|
||||
if (outbase)
|
||||
{
|
||||
*(unsigned int *)outptr = c;
|
||||
}
|
||||
outptr += sizeof(unsigned int);
|
||||
}
|
||||
inline unsigned int get_outputpos()
|
||||
{
|
||||
return (outptr - outbase);
|
||||
}
|
||||
inline void set_outputpos(unsigned int offs)
|
||||
{
|
||||
outptr = outbase + offs;
|
||||
}
|
||||
inline unsigned int get_inputpos()
|
||||
{
|
||||
return (unsigned int)((char *)inptr - (char *)inbase);
|
||||
}
|
||||
public:
|
||||
cell *inptr; /* input pointer */
|
||||
cell *inbase; /* input base */
|
||||
char *outbase; /* output pointer */
|
||||
char *outptr; /* output base */
|
||||
};
|
||||
|
||||
#define IA32_JMP_IMM32 0xE9 // encoding is imm32
|
||||
|
||||
inline unsigned int IA32_Jump_Imm32(JitWriter *jit, int disp)
|
||||
{
|
||||
unsigned int ptr;
|
||||
jit->write_ubyte(IA32_JMP_IMM32);
|
||||
ptr = jit->get_outputpos();
|
||||
jit->write_int32(disp);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrects a jump using an absolute offset, not a relative one.
|
||||
*/
|
||||
inline void IA32_Write_Jump32_Abs(JitWriter *jit, unsigned int jmp, void *target)
|
||||
{
|
||||
//save old ptr
|
||||
char *oldptr = jit->outptr;
|
||||
//get relative difference
|
||||
long diff = ((long)target - ((long)jit->outbase + jmp + 4));
|
||||
//overwrite old value
|
||||
jit->outptr = jit->outbase + jmp;
|
||||
jit->write_int32(diff);
|
||||
//restore old ptr
|
||||
jit->outptr = oldptr;
|
||||
}
|
||||
|
||||
|
||||
#endif // _INCLUDE_SOURCEMOD_DETOURS_H_
|
Reference in New Issue
Block a user