From d7b3b47aa47038bcfd71ae6fc2639cf1d0efc00a Mon Sep 17 00:00:00 2001 From: Arkshine Date: Wed, 28 May 2014 00:06:51 +0200 Subject: [PATCH] Cstrike: Import some memory tools from SourceMod --- AMBuildScript | 3 + dlls/cstrike/cstrike/AMBuilder | 3 + dlls/cstrike/cstrike/Makefile | 6 +- dlls/cstrike/cstrike/msvc10/cstrike.vcxproj | 11 +- .../cstrike/msvc10/cstrike.vcxproj.filters | 30 + public/memtools/CDetour/asm/asm.c | 419 +++++++++++ public/memtools/CDetour/asm/asm.h | 40 ++ public/memtools/CDetour/detourhelpers.h | 129 ++++ public/memtools/CDetour/detours.cpp | 231 ++++++ public/memtools/CDetour/detours.h | 361 ++++++++++ public/memtools/MemoryUtils.cpp | 657 ++++++++++++++++++ public/memtools/MemoryUtils.h | 89 +++ public/sm_memtable.h | 170 +++++ public/sm_symtable.h | 231 ++++++ 14 files changed, 2376 insertions(+), 4 deletions(-) create mode 100644 public/memtools/CDetour/asm/asm.c create mode 100644 public/memtools/CDetour/asm/asm.h create mode 100644 public/memtools/CDetour/detourhelpers.h create mode 100644 public/memtools/CDetour/detours.cpp create mode 100644 public/memtools/CDetour/detours.h create mode 100644 public/memtools/MemoryUtils.cpp create mode 100644 public/memtools/MemoryUtils.h create mode 100644 public/sm_memtable.h create mode 100644 public/sm_symtable.h diff --git a/AMBuildScript b/AMBuildScript index 038ac32b..506c5b43 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -225,6 +225,7 @@ class AMXXConfig(object): '-arch', 'i386', '-lstdc++', '-stdlib=libstdc++', + '-framework', 'CoreServices', ] cfg.cxxflags += ['-stdlib=libstdc++'] elif builder.target_platform == 'windows': @@ -238,7 +239,9 @@ class AMXXConfig(object): ] cfg.includes += [os.path.join(builder.buildPath, 'includes')] + cfg.includes += [os.path.join(builder.sourcePath, 'public')] cfg.includes += [os.path.join(builder.sourcePath, 'public', 'amtl')] + cfg.includes += [os.path.join(builder.sourcePath, 'public', 'memtools')] return # diff --git a/dlls/cstrike/cstrike/AMBuilder b/dlls/cstrike/cstrike/AMBuilder index 904ab6d6..5efeb1d0 100644 --- a/dlls/cstrike/cstrike/AMBuilder +++ b/dlls/cstrike/cstrike/AMBuilder @@ -9,6 +9,9 @@ binary.sources = [ 'CstrikePlayer.cpp', 'CstrikeNatives.cpp', 'CstrikeHacks.cpp', + '../../../public/memtools/MemoryUtils.cpp', + '../../../public/memtools/CDetour/detours.cpp', + '../../../public/memtools/CDetour/asm/asm.c', ] AMXX.modules += [builder.Add(binary)] diff --git a/dlls/cstrike/cstrike/Makefile b/dlls/cstrike/cstrike/Makefile index 61140da5..dae7b5ac 100755 --- a/dlls/cstrike/cstrike/Makefile +++ b/dlls/cstrike/cstrike/Makefile @@ -14,7 +14,9 @@ MM_ROOT = ../../../../metamod/metamod PROJECT = cstrike -OBJECTS = sdk/amxxmodule.cpp amxx_api.cpp CstrikePlayer.cpp CstrikeNatives.cpp CstrikeHacks.cpp +OBJECTS = sdk/amxxmodule.cpp amxx_api.cpp CstrikePlayer.cpp CstrikeNatives.cpp CstrikeHacks.cpp \ + ../../../public/memtools/MemoryUtils.cpp ../../../public/memtools/CDetour/detours.cpp \ + ../../../public/memtools/CDetour/asm/asm.c ############################################## ### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### @@ -29,7 +31,7 @@ CPP_OSX = clang LINK = -INCLUDE = -I. -I$(HLSDK) -I$(HLSDK)/common -I$(HLSDK)/dlls -I$(HLSDK)/engine -I$(HLSDK)/game_shared \ +INCLUDE = -I. -I../../../public -I../../../public/amtl -I$(HLSDK) -I$(HLSDK)/common -I$(HLSDK)/dlls -I$(HLSDK)/engine -I$(HLSDK)/game_shared \ -I$(HLSDK)/public -I$(MM_ROOT) -Isdk ################################################ diff --git a/dlls/cstrike/cstrike/msvc10/cstrike.vcxproj b/dlls/cstrike/cstrike/msvc10/cstrike.vcxproj index 33702db6..355ccb75 100644 --- a/dlls/cstrike/cstrike/msvc10/cstrike.vcxproj +++ b/dlls/cstrike/cstrike/msvc10/cstrike.vcxproj @@ -62,7 +62,7 @@ Disabled - ..\;..\sdk;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) + ..\;..\..\..\..\public; ..\..\..\..\public\amtl;..\..\..\..\public\memtools;..\sdk;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;CSTRIKE_EXPORTS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebug @@ -105,7 +105,7 @@ MaxSpeed OnlyExplicitInline - ..\;..\sdk;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) + ..\;..\..\..\..\public; ..\..\..\..\public\amtl;..\..\..\..\public\memtools;..\sdk;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;CSTRIKE_EXPORTS;%(PreprocessorDefinitions) true MultiThreaded @@ -135,6 +135,9 @@ + + + @@ -142,6 +145,10 @@ + + + + diff --git a/dlls/cstrike/cstrike/msvc10/cstrike.vcxproj.filters b/dlls/cstrike/cstrike/msvc10/cstrike.vcxproj.filters index f0ca883d..c73b5ac0 100644 --- a/dlls/cstrike/cstrike/msvc10/cstrike.vcxproj.filters +++ b/dlls/cstrike/cstrike/msvc10/cstrike.vcxproj.filters @@ -18,6 +18,15 @@ {7be12e0f-47b1-4d7e-880c-83e5d1c378d1} + + {bbcd1d67-4670-423d-912f-695737e76bad} + + + {074d4da1-d1be-40ba-8417-1fc388d85568} + + + {4f3c4a13-065a-49b1-83a1-f646a3ec3678} + @@ -35,6 +44,15 @@ Source Files + + Memtools + + + Memtools\CDetour + + + Memtools\CDetour\asm + @@ -55,6 +73,18 @@ Header Files + + Memtools + + + Memtools\CDetour + + + Memtools\CDetour + + + Memtools\CDetour\asm + diff --git a/public/memtools/CDetour/asm/asm.c b/public/memtools/CDetour/asm/asm.c new file mode 100644 index 00000000..f415f21b --- /dev/null +++ b/public/memtools/CDetour/asm/asm.c @@ -0,0 +1,419 @@ +#include "asm.h" + +#ifndef WIN32 +#define _GNU_SOURCE +#include +#include + +#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 +#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; + } +} +*/ diff --git a/public/memtools/CDetour/asm/asm.h b/public/memtools/CDetour/asm/asm.h new file mode 100644 index 00000000..838e6916 --- /dev/null +++ b/public/memtools/CDetour/asm/asm.h @@ -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__ diff --git a/public/memtools/CDetour/detourhelpers.h b/public/memtools/CDetour/detourhelpers.h new file mode 100644 index 00000000..291d8df2 --- /dev/null +++ b/public/memtools/CDetour/detourhelpers.h @@ -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 . + * + * 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 . + * + * 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 + #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 + #endif +#elif defined(WIN32) + #include +#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; ibytes; i++) + { + restore->patch[i] = addr[i]; + } + restore->bytes = patch->bytes; + } + + for (size_t i=0; ibytes; i++) + { + addr[i] = patch->patch[i]; + } +} + +#endif //_INCLUDE_SOURCEMOD_DETOURHELPERS_H_ diff --git a/public/memtools/CDetour/detours.cpp b/public/memtools/CDetour/detours.cpp new file mode 100644 index 00000000..2bd1fcb4 --- /dev/null +++ b/public/memtools/CDetour/detours.cpp @@ -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 . +* +* 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 . +* +* 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; iSetReadWrite(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; + } +} diff --git a/public/memtools/CDetour/detours.h b/public/memtools/CDetour/detours.h new file mode 100644 index 00000000..797cb1c8 --- /dev/null +++ b/public/memtools/CDetour/detours.h @@ -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 . +* +* 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 . +* +* 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 +//#include +#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(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_ diff --git a/public/memtools/MemoryUtils.cpp b/public/memtools/MemoryUtils.cpp new file mode 100644 index 00000000..843d0f6f --- /dev/null +++ b/public/memtools/MemoryUtils.cpp @@ -0,0 +1,657 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2011 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 . + * + * 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 . + */ + +#include "MemoryUtils.h" +#include + +#if defined(__linux__) + #include + #include + #include + #include + #include + #include + + #define PAGE_SIZE 4096 + #define PAGE_ALIGN_UP(x) ((x + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)) +#endif + +#if defined(__APPLE__) + #include + #include + #include + #include + #include + #include + + /* Define things from 10.6 SDK for older SDKs */ + #ifndef MAC_OS_X_VERSION_10_6 + struct task_dyld_info + { + mach_vm_address_t all_image_info_addr; + mach_vm_size_t all_image_info_size; + }; + typedef struct task_dyld_info task_dyld_info_data_t; + #define TASK_DYLD_INFO 17 + #define TASK_DYLD_INFO_COUNT (sizeof(task_dyld_info_data_t) / sizeof(natural_t)) + #endif // MAC_OS_X_VERSION_10_6 +#endif // __APPLE__ + + + +MemoryUtils g_MemUtils; + +MemoryUtils::MemoryUtils() +{ +#if defined(__APPLE__) + Gestalt(gestaltSystemVersionMajor, &m_OSXMajor); + Gestalt(gestaltSystemVersionMinor, &m_OSXMinor); + + /* Get pointer to struct that describes all loaded mach-o images in process */ + if ((m_OSXMajor == 10 && m_OSXMinor >= 6) || m_OSXMajor > 10) + { + task_dyld_info_data_t dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); + m_ImageList = (struct dyld_all_image_infos *)dyld_info.all_image_info_addr; + } + else + { + struct nlist list[2]; + memset(list, 0, sizeof(list)); + list[0].n_un.n_name = (char *)"_dyld_all_image_infos"; + nlist("/usr/lib/dyld", list); + m_ImageList = (struct dyld_all_image_infos *)list[0].n_value; + } +#endif +} + +MemoryUtils::~MemoryUtils() +{ +#if defined(__linux__) || defined(__APPLE__) + for (size_t i = 0; i < m_SymTables.length(); i++) + { + delete m_SymTables[i]; + } + m_SymTables.clear(); +#endif +} + +void *MemoryUtils::FindPattern(const void *libPtr, const char *pattern, size_t len) +{ + DynLibInfo lib; + bool found; + char *ptr, *end; + + memset(&lib, 0, sizeof(DynLibInfo)); + + if (!GetLibraryInfo(libPtr, lib)) + { + return NULL; + } + + ptr = reinterpret_cast(lib.baseAddress); + end = ptr + lib.memorySize - len; + + while (ptr < end) + { + found = true; + for (register size_t i = 0; i < len; i++) + { + if (pattern[i] != '\x2A' && pattern[i] != ptr[i]) + { + found = false; + break; + } + } + + if (found) + return ptr; + + ptr++; + } + + return NULL; +} + +void *MemoryUtils::ResolveSymbol(void *handle, const char *symbol) +{ +#if defined(WIN32) + + return GetProcAddress((HMODULE)handle, symbol); + +#elif defined(__linux__) + + struct link_map *dlmap; + struct stat dlstat; + int dlfile; + uintptr_t map_base; + Elf32_Ehdr *file_hdr; + Elf32_Shdr *sections, *shstrtab_hdr, *symtab_hdr, *strtab_hdr; + Elf32_Sym *symtab; + const char *shstrtab, *strtab; + uint16_t section_count; + uint32_t symbol_count; + LibSymbolTable *libtable; + SymbolTable *table; + Symbol *symbol_entry; + + dlmap = (struct link_map *)handle; + symtab_hdr = NULL; + strtab_hdr = NULL; + table = NULL; + + /* See if we already have a symbol table for this library */ + for (size_t i = 0; i < m_SymTables.length(); i++) + { + libtable = m_SymTables[i]; + if (libtable->lib_base == dlmap->l_addr) + { + table = &libtable->table; + break; + } + } + + /* If we don't have a symbol table for this library, then create one */ + if (table == NULL) + { + libtable = new LibSymbolTable(); + libtable->table.Initialize(); + libtable->lib_base = dlmap->l_addr; + libtable->last_pos = 0; + table = &libtable->table; + m_SymTables.append(libtable); + } + + /* See if the symbol is already cached in our table */ + symbol_entry = table->FindSymbol(symbol, strlen(symbol)); + if (symbol_entry != NULL) + { + return symbol_entry->address; + } + + /* If symbol isn't in our table, then we have open the actual library */ + dlfile = open(dlmap->l_name, O_RDONLY); + if (dlfile == -1 || fstat(dlfile, &dlstat) == -1) + { + close(dlfile); + return NULL; + } + + /* Map library file into memory */ + file_hdr = (Elf32_Ehdr *)mmap(NULL, dlstat.st_size, PROT_READ, MAP_PRIVATE, dlfile, 0); + map_base = (uintptr_t)file_hdr; + if (file_hdr == MAP_FAILED) + { + close(dlfile); + return NULL; + } + close(dlfile); + + if (file_hdr->e_shoff == 0 || file_hdr->e_shstrndx == SHN_UNDEF) + { + munmap(file_hdr, dlstat.st_size); + return NULL; + } + + sections = (Elf32_Shdr *)(map_base + file_hdr->e_shoff); + section_count = file_hdr->e_shnum; + /* Get ELF section header string table */ + shstrtab_hdr = §ions[file_hdr->e_shstrndx]; + shstrtab = (const char *)(map_base + shstrtab_hdr->sh_offset); + + /* Iterate sections while looking for ELF symbol table and string table */ + for (uint16_t i = 0; i < section_count; i++) + { + Elf32_Shdr &hdr = sections[i]; + const char *section_name = shstrtab + hdr.sh_name; + + if (strcmp(section_name, ".symtab") == 0) + { + symtab_hdr = &hdr; + } + else if (strcmp(section_name, ".strtab") == 0) + { + strtab_hdr = &hdr; + } + } + + /* Uh oh, we don't have a symbol table or a string table */ + if (symtab_hdr == NULL || strtab_hdr == NULL) + { + munmap(file_hdr, dlstat.st_size); + return NULL; + } + + symtab = (Elf32_Sym *)(map_base + symtab_hdr->sh_offset); + strtab = (const char *)(map_base + strtab_hdr->sh_offset); + symbol_count = symtab_hdr->sh_size / symtab_hdr->sh_entsize; + + /* Iterate symbol table starting from the position we were at last time */ + for (uint32_t i = libtable->last_pos; i < symbol_count; i++) + { + Elf32_Sym &sym = symtab[i]; + unsigned char sym_type = ELF32_ST_TYPE(sym.st_info); + const char *sym_name = strtab + sym.st_name; + Symbol *cur_sym; + + /* Skip symbols that are undefined or do not refer to functions or objects */ + if (sym.st_shndx == SHN_UNDEF || (sym_type != STT_FUNC && sym_type != STT_OBJECT)) + { + continue; + } + + /* Caching symbols as we go along */ + cur_sym = table->InternSymbol(sym_name, strlen(sym_name), (void *)(dlmap->l_addr + sym.st_value)); + if (strcmp(symbol, sym_name) == 0) + { + symbol_entry = cur_sym; + libtable->last_pos = ++i; + break; + } + } + + munmap(file_hdr, dlstat.st_size); + return symbol_entry ? symbol_entry->address : NULL; + +#elif defined(__APPLE__) + + uintptr_t dlbase, linkedit_addr; + uint32_t image_count; + struct mach_header *file_hdr; + struct load_command *loadcmds; + struct segment_command *linkedit_hdr; + struct symtab_command *symtab_hdr; + struct nlist *symtab; + const char *strtab; + uint32_t loadcmd_count; + uint32_t symbol_count; + LibSymbolTable *libtable; + SymbolTable *table; + Symbol *symbol_entry; + + dlbase = 0; + image_count = m_ImageList->infoArrayCount; + linkedit_hdr = NULL; + symtab_hdr = NULL; + table = NULL; + + /* Loop through mach-o images in process. + * We can skip index 0 since that is just the executable. + */ + for (uint32_t i = 1; i < image_count; i++) + { + const struct dyld_image_info &info = m_ImageList->infoArray[i]; + + /* "Load" each one until we get a matching handle */ + void *h = dlopen(info.imageFilePath, RTLD_NOLOAD); + if (h == handle) + { + dlbase = (uintptr_t)info.imageLoadAddress; + dlclose(h); + break; + } + + dlclose(h); + } + + if (!dlbase) + { + /* Uh oh, we couldn't find a matching handle */ + return NULL; + } + + /* See if we already have a symbol table for this library */ + for (size_t i = 0; i < m_SymTables.length(); i++) + { + libtable = m_SymTables[i]; + if (libtable->lib_base == dlbase) + { + table = &libtable->table; + break; + } + } + + /* If we don't have a symbol table for this library, then create one */ + if (table == NULL) + { + libtable = new LibSymbolTable(); + libtable->table.Initialize(); + libtable->lib_base = dlbase; + libtable->last_pos = 0; + table = &libtable->table; + m_SymTables.append(libtable); + } + + /* See if the symbol is already cached in our table */ + symbol_entry = table->FindSymbol(symbol, strlen(symbol)); + if (symbol_entry != NULL) + { + return symbol_entry->address; + } + + /* If symbol isn't in our table, then we have to locate it in memory */ + + file_hdr = (struct mach_header *)dlbase; + loadcmds = (struct load_command *)(dlbase + sizeof(struct mach_header)); + loadcmd_count = file_hdr->ncmds; + + /* Loop through load commands until we find the ones for the symbol table */ + for (uint32_t i = 0; i < loadcmd_count; i++) + { + if (loadcmds->cmd == LC_SEGMENT && !linkedit_hdr) + { + struct segment_command *seg = (struct segment_command *)loadcmds; + if (strcmp(seg->segname, "__LINKEDIT") == 0) + { + linkedit_hdr = seg; + if (symtab_hdr) + { + break; + } + } + } + else if (loadcmds->cmd == LC_SYMTAB) + { + symtab_hdr = (struct symtab_command *)loadcmds; + if (linkedit_hdr) + { + break; + } + } + + /* Load commands are not of a fixed size which is why we add the size */ + loadcmds = (struct load_command *)((uintptr_t)loadcmds + loadcmds->cmdsize); + } + + if (!linkedit_hdr || !symtab_hdr || !symtab_hdr->symoff || !symtab_hdr->stroff) + { + /* Uh oh, no symbol table */ + return NULL; + } + + linkedit_addr = dlbase + linkedit_hdr->vmaddr; + symtab = (struct nlist *)(linkedit_addr + symtab_hdr->symoff - linkedit_hdr->fileoff); + strtab = (const char *)(linkedit_addr + symtab_hdr->stroff - linkedit_hdr->fileoff); + symbol_count = symtab_hdr->nsyms; + + /* Iterate symbol table starting from the position we were at last time */ + for (uint32_t i = libtable->last_pos; i < symbol_count; i++) + { + struct nlist &sym = symtab[i]; + /* Ignore the prepended underscore on all symbols, so +1 here */ + const char *sym_name = strtab + sym.n_un.n_strx + 1; + Symbol *cur_sym; + + /* Skip symbols that are undefined */ + if (sym.n_sect == NO_SECT) + { + continue; + } + + /* Caching symbols as we go along */ + cur_sym = table->InternSymbol(sym_name, strlen(sym_name), (void *)(dlbase + sym.n_value)); + if (strcmp(symbol, sym_name) == 0) + { + symbol_entry = cur_sym; + libtable->last_pos = ++i; + break; + } + } + + return symbol_entry ? symbol_entry->address : NULL; + +#endif +} + +bool MemoryUtils::GetLibraryInfo(const void *libPtr, DynLibInfo &lib) +{ + uintptr_t baseAddr; + + if (libPtr == NULL) + { + return false; + } + +#if defined(WIN32) + + MEMORY_BASIC_INFORMATION info; + IMAGE_DOS_HEADER *dos; + IMAGE_NT_HEADERS *pe; + IMAGE_FILE_HEADER *file; + IMAGE_OPTIONAL_HEADER *opt; + + if (!VirtualQuery(libPtr, &info, sizeof(MEMORY_BASIC_INFORMATION))) + { + return false; + } + + baseAddr = reinterpret_cast(info.AllocationBase); + + /* All this is for our insane sanity checks :o */ + dos = reinterpret_cast(baseAddr); + pe = reinterpret_cast(baseAddr + dos->e_lfanew); + file = &pe->FileHeader; + opt = &pe->OptionalHeader; + + /* Check PE magic and signature */ + if (dos->e_magic != IMAGE_DOS_SIGNATURE || pe->Signature != IMAGE_NT_SIGNATURE || opt->Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + return false; + } + + /* Check architecture, which is 32-bit/x86 right now + * Should change this for 64-bit if Valve gets their act together + */ + if (file->Machine != IMAGE_FILE_MACHINE_I386) + { + return false; + } + + /* For our purposes, this must be a dynamic library */ + if ((file->Characteristics & IMAGE_FILE_DLL) == 0) + { + return false; + } + + /* Finally, we can do this */ + lib.memorySize = opt->SizeOfImage; + +#elif defined(__linux__) + + Dl_info info; + Elf32_Ehdr *file; + Elf32_Phdr *phdr; + uint16_t phdrCount; + + if (!dladdr(libPtr, &info)) + { + return false; + } + + if (!info.dli_fbase || !info.dli_fname) + { + return false; + } + + /* This is for our insane sanity checks :o */ + baseAddr = reinterpret_cast(info.dli_fbase); + file = reinterpret_cast(baseAddr); + + /* Check ELF magic */ + if (memcmp(ELFMAG, file->e_ident, SELFMAG) != 0) + { + return false; + } + + /* Check ELF version */ + if (file->e_ident[EI_VERSION] != EV_CURRENT) + { + return false; + } + + /* Check ELF architecture, which is 32-bit/x86 right now + * Should change this for 64-bit if Valve gets their act together + */ + if (file->e_ident[EI_CLASS] != ELFCLASS32 || file->e_machine != EM_386 || file->e_ident[EI_DATA] != ELFDATA2LSB) + { + return false; + } + + /* For our purposes, this must be a dynamic library/shared object */ + if (file->e_type != ET_DYN) + { + return false; + } + + phdrCount = file->e_phnum; + phdr = reinterpret_cast(baseAddr + file->e_phoff); + + for (uint16_t i = 0; i < phdrCount; i++) + { + Elf32_Phdr &hdr = phdr[i]; + + /* We only really care about the segment with executable code */ + if (hdr.p_type == PT_LOAD && hdr.p_flags == (PF_X|PF_R)) + { + /* From glibc, elf/dl-load.c: + * c->mapend = ((ph->p_vaddr + ph->p_filesz + GLRO(dl_pagesize) - 1) + * & ~(GLRO(dl_pagesize) - 1)); + * + * In glibc, the segment file size is aligned up to the nearest page size and + * added to the virtual address of the segment. We just want the size here. + */ + lib.memorySize = PAGE_ALIGN_UP(hdr.p_filesz); + break; + } + } + +#elif defined(__APPLE__) + + Dl_info info; + struct mach_header *file; + struct segment_command *seg; + uint32_t cmd_count; + + if (!dladdr(libPtr, &info)) + { + return false; + } + + if (!info.dli_fbase || !info.dli_fname) + { + return false; + } + + /* This is for our insane sanity checks :o */ + baseAddr = (uintptr_t)info.dli_fbase; + file = (struct mach_header *)baseAddr; + + /* Check Mach-O magic */ + if (file->magic != MH_MAGIC) + { + return false; + } + + /* Check architecture (32-bit/x86) */ + if (file->cputype != CPU_TYPE_I386 || file->cpusubtype != CPU_SUBTYPE_I386_ALL) + { + return false; + } + + /* For our purposes, this must be a dynamic library */ + if (file->filetype != MH_DYLIB) + { + return false; + } + + cmd_count = file->ncmds; + seg = (struct segment_command *)(baseAddr + sizeof(struct mach_header)); + + /* Add up memory sizes of mapped segments */ + for (uint32_t i = 0; i < cmd_count; i++) + { + if (seg->cmd == LC_SEGMENT) + { + lib.memorySize += seg->vmsize; + } + + seg = (struct segment_command *)((uintptr_t)seg + seg->cmdsize); + } + +#endif + + lib.baseAddress = reinterpret_cast(baseAddr); + + return true; +} + +bool MemoryUtils::GetLibraryOfAddress(const void *libPtr, char *buffer, size_t maxlength, uintptr_t *base) +{ +#if defined(__linux__) || defined(__APPLE__) + + Dl_info info; + if (!dladdr(libPtr, &info)) + { + return false; + } + if (!info.dli_fbase || !info.dli_fname) + { + return false; + } + const char *dllpath = info.dli_fname; + snprintf(buffer, maxlength, "%s", dllpath); + if (base) + { + *base = (uintptr_t)info.dli_fbase; + } + +#else + + MEMORY_BASIC_INFORMATION mem; + if (!VirtualQuery(libPtr, &mem, sizeof(mem))) + { + return false; + } + if (mem.AllocationBase == NULL) + { + return false; + } + HMODULE dll = (HMODULE)mem.AllocationBase; + GetModuleFileName(dll, (LPTSTR)buffer, maxlength); + if (base) + { + *base = (uintptr_t)mem.AllocationBase; + } + +#endif + + return true; +} + diff --git a/public/memtools/MemoryUtils.h b/public/memtools/MemoryUtils.h new file mode 100644 index 00000000..1e7a5fdc --- /dev/null +++ b/public/memtools/MemoryUtils.h @@ -0,0 +1,89 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2011 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 . + * + * 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 . + */ + +#ifndef _INCLUDE_SOURCEMOD_MEMORYUTILS_H_ +#define _INCLUDE_SOURCEMOD_MEMORYUTILS_H_ + +#if defined(__linux__) || defined(__APPLE__) + #include + #include + #include +#endif + +#if defined(__APPLE__) + #include +#endif + +#if defined(WIN32) + #include +#endif + +struct DynLibInfo +{ + void *baseAddress; + size_t memorySize; +}; + +#if defined(__linux__) || defined(__APPLE__) + struct LibSymbolTable + { + SymbolTable table; + uintptr_t lib_base; + uint32_t last_pos; + }; +#endif + +class MemoryUtils +{ + public: + MemoryUtils(); + ~MemoryUtils(); + + public: + void *FindPattern(const void *libPtr, const char *pattern, size_t len); + void *ResolveSymbol(void *handle, const char *symbol); + + public: + bool GetLibraryInfo(const void *libPtr, DynLibInfo &lib); + bool GetLibraryOfAddress(const void *libPtr, char *buffer, size_t maxlength, uintptr_t *base); + +#if defined(__linux__) || defined(__APPLE__) + private: + ke::Vector m_SymTables; + #if defined(__APPLE__) + struct dyld_all_image_infos *m_ImageList; + SInt32 m_OSXMajor; + SInt32 m_OSXMinor; + #endif // __APPLE__ +#endif +}; + +extern MemoryUtils g_MemUtils; + +#endif // _INCLUDE_SOURCEMOD_MEMORYUTILS_H_ diff --git a/public/sm_memtable.h b/public/sm_memtable.h new file mode 100644 index 00000000..d7e2d90d --- /dev/null +++ b/public/sm_memtable.h @@ -0,0 +1,170 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 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 . + * + * 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 . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ +#define _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ + +#include +#include + +class BaseMemTable +{ +public: + BaseMemTable(unsigned int init_size) + { + membase = (unsigned char *)malloc(init_size); + size = init_size; + tail = 0; + } + ~BaseMemTable() + { + free(membase); + membase = NULL; + } +public: + /** + * Allocates 'size' bytes of memory. + * Optionally outputs the address through 'addr'. + * Returns an index >= 0 on success, < 0 on failure. + */ + int CreateMem(unsigned int addsize, void **addr) + { + int idx = (int)tail; + + while (tail + addsize >= size) { + size *= 2; + membase = (unsigned char *)realloc(membase, size); + } + + tail += addsize; + if (addr) + *addr = (void *)&membase[idx]; + + return idx; + } + + /** + * Given an index into the memory table, returns its address. + * Returns NULL if invalid. + */ + void *GetAddress(int index) + { + if (index < 0 || (unsigned int)index >= tail) + return NULL; + return &membase[index]; + } + + /** + * Scraps the memory table. For caching purposes, the memory + * is not freed, however subsequent calls to CreateMem() will + * begin at the first index again. + */ + void Reset() + { + tail = 0; + } + + inline unsigned int GetMemUsage() + { + return size; + } + + inline unsigned int GetActualMemUsed() + { + return tail; + } + +private: + unsigned char *membase; + unsigned int size; + unsigned int tail; +}; + +class BaseStringTable +{ +public: + BaseStringTable(unsigned int init_size) : m_table(init_size) + { + } +public: + /** + * Adds a string to the string table and returns its index. + */ + int AddString(const char *string) + { + return AddString(string, strlen(string)); + } + + /** + * Adds a string to the string table and returns its index. + */ + int AddString(const char *string, size_t length) + { + size_t len = length + 1; + int idx; + char *addr; + + idx = m_table.CreateMem(len, (void **)&addr); + memcpy(addr, string, length + 1); + return idx; + } + + /** + * Given an index into the string table, returns the associated string. + */ + inline const char *GetString(int str) + { + return (const char *)m_table.GetAddress(str); + } + + /** + * Scraps the string table. For caching purposes, the memory + * is not freed, however subsequent calls to AddString() will + * begin at the first index again. + */ + void Reset() + { + m_table.Reset(); + } + + /** + * Returns the parent BaseMemTable that this string table uses. + */ + inline BaseMemTable *GetMemTable() + { + return &m_table; + } +private: + BaseMemTable m_table; +}; + +#endif //_INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ + diff --git a/public/sm_symtable.h b/public/sm_symtable.h new file mode 100644 index 00000000..0f1d391f --- /dev/null +++ b/public/sm_symtable.h @@ -0,0 +1,231 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2009 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 . + * + * 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 . + */ + +#ifndef _INCLUDE_SOURCEMOD_CORE_SYMBOLTABLE_H_ +#define _INCLUDE_SOURCEMOD_CORE_SYMBOLTABLE_H_ + +#include +#include +#include + +#define KESTRING_TABLE_START_SIZE 65536 + +struct Symbol +{ + size_t length; + uint32_t hash; + void *address; + Symbol *tbl_next; + + inline char *buffer() + { + return reinterpret_cast(this + 1); + } +}; + +class SymbolTable +{ +public: + ~SymbolTable() + { + for (uint32_t i = 0; i < nbuckets; i++) + { + Symbol *sym = buckets[i]; + while (sym != NULL) + { + Symbol *next = sym->tbl_next; + free(sym); + sym = next; + } + } + free(buckets); + } + + bool Initialize() + { + buckets = (Symbol **)malloc(sizeof(Symbol *) * KESTRING_TABLE_START_SIZE); + if (buckets == NULL) + { + return false; + } + memset(buckets, 0, sizeof(Symbol *) * KESTRING_TABLE_START_SIZE); + + nbuckets = KESTRING_TABLE_START_SIZE; + nused = 0; + bucketmask = KESTRING_TABLE_START_SIZE - 1; + return true; + } + + static inline uint32_t HashString(const char *data, size_t len) + { + #undef get16bits + #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) + #define get16bits(d) (*((const uint16_t *) (d))) + #endif + #if !defined (get16bits) + #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) + #endif + uint32_t hash = len, tmp; + int rem; + + if (len <= 0 || data == NULL) + { + return 0; + } + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (;len > 0; len--) { + hash += get16bits (data); + tmp = (get16bits (data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2 * sizeof (uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: hash += get16bits (data); + hash ^= hash << 16; + hash ^= data[sizeof (uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: hash += get16bits (data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += *data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; + + #undef get16bits + } + + Symbol **FindSymbolBucket(const char *str, size_t len, uint32_t hash) + { + uint32_t bucket = hash & bucketmask; + Symbol **pkvs = &buckets[bucket]; + + Symbol *kvs = *pkvs; + while (kvs != NULL) + { + if (len == kvs->length && memcmp(str, kvs->buffer(), len * sizeof(char)) == 0) + { + return pkvs; + } + pkvs = &kvs->tbl_next; + kvs = *pkvs; + } + + return pkvs; + } + + void ResizeSymbolTable() + { + uint32_t xnbuckets = nbuckets * 2; + Symbol **xbuckets = (Symbol **)malloc(sizeof(Symbol *) * xnbuckets); + if (xbuckets == NULL) + { + return; + } + memset(xbuckets, 0, sizeof(Symbol *) * xnbuckets); + uint32_t xbucketmask = xnbuckets - 1; + for (uint32_t i = 0; i < nbuckets; i++) + { + Symbol *sym = buckets[i]; + while (sym != NULL) + { + Symbol *next = sym->tbl_next; + uint32_t bucket = sym->hash & xbucketmask; + sym->tbl_next = xbuckets[bucket]; + xbuckets[bucket] = sym; + sym = next; + } + } + free(buckets); + buckets = xbuckets; + nbuckets = xnbuckets; + bucketmask = xbucketmask; + } + + Symbol *FindSymbol(const char *str, size_t len) + { + uint32_t hash = HashString(str, len); + Symbol **pkvs = FindSymbolBucket(str, len, hash); + return *pkvs; + } + + Symbol *InternSymbol(const char* str, size_t len, void *address) + { + uint32_t hash = HashString(str, len); + Symbol **pkvs = FindSymbolBucket(str, len, hash); + if (*pkvs != NULL) + { + return *pkvs; + } + + Symbol *kvs = (Symbol *)malloc(sizeof(Symbol) + sizeof(char) * (len + 1)); + kvs->length = len; + kvs->hash = hash; + kvs->address = address; + kvs->tbl_next = NULL; + memcpy(kvs + 1, str, sizeof(char) * (len + 1)); + *pkvs = kvs; + nused++; + + if (nused > nbuckets && nbuckets <= INT_MAX / 2) + { + ResizeSymbolTable(); + } + + return kvs; + } +private: + uint32_t nbuckets; + uint32_t nused; + uint32_t bucketmask; + Symbol **buckets; +}; + +#endif //_INCLUDE_SOURCEMOD_CORE_SYMBOLTABLE_H_