From c3e20d5d121898cda0c0bcd8061c76a9abeb33e5 Mon Sep 17 00:00:00 2001 From: Fyren Date: Wed, 21 Jul 2010 22:09:03 -0500 Subject: [PATCH] FireOutput detour in SDKTools now uses CDetour (bug 4416, r=pred). --- extensions/sdktools/AMBuilder | 4 +- .../{detours.h => CDetour/detourhelpers.h} | 12 +- extensions/sdktools/CDetour/detours.cpp | 192 ++++++++ extensions/sdktools/CDetour/detours.h | 241 ++++++++++ extensions/sdktools/Makefile | 10 +- extensions/sdktools/asm/asm.c | 421 ++++++++++++++++++ extensions/sdktools/asm/asm.h | 40 ++ extensions/sdktools/extension.cpp | 3 +- extensions/sdktools/output.cpp | 136 +----- extensions/sdktools/output.h | 5 +- gamedata/sdktools.games/engine.darkm.txt | 7 - gamedata/sdktools.games/engine.ep1.txt | 8 - gamedata/sdktools.games/engine.ep2.txt | 8 - gamedata/sdktools.games/engine.ep2valve.txt | 9 - gamedata/sdktools.games/engine.l4d.txt | 8 - gamedata/sdktools.games/engine.l4d2.txt | 8 - 16 files changed, 928 insertions(+), 184 deletions(-) rename extensions/sdktools/{detours.h => CDetour/detourhelpers.h} (87%) create mode 100644 extensions/sdktools/CDetour/detours.cpp create mode 100644 extensions/sdktools/CDetour/detours.h create mode 100644 extensions/sdktools/asm/asm.c create mode 100644 extensions/sdktools/asm/asm.h diff --git a/extensions/sdktools/AMBuilder b/extensions/sdktools/AMBuilder index e2c520b6..ef9adab9 100644 --- a/extensions/sdktools/AMBuilder +++ b/extensions/sdktools/AMBuilder @@ -36,7 +36,9 @@ for i in SM.sdkInfo: 'vsound.cpp', 'hooks.cpp', 'vstringtable.cpp', - 'sdk/smsdk_ext.cpp' + 'CDetour/detours.cpp', + 'sdk/smsdk_ext.cpp', + 'asm/asm.c' ]) SM.PostSetupHL2Job(extension, binary, i) SM.AutoVersion('extensions/sdktools', binary) diff --git a/extensions/sdktools/detours.h b/extensions/sdktools/CDetour/detourhelpers.h similarity index 87% rename from extensions/sdktools/detours.h rename to extensions/sdktools/CDetour/detourhelpers.h index 938d85cd..4df2b69f 100644 --- a/extensions/sdktools/detours.h +++ b/extensions/sdktools/CDetour/detourhelpers.h @@ -1,8 +1,8 @@ /** * vim: set ts=4 : * ============================================================================= - * SourceMod SDKTools Extension - * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * SourceMod + * Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under @@ -26,11 +26,11 @@ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), * or . * - * Version: $Id$ + * Version: $Id: detourhelpers.h 248 2008-08-27 00:56:22Z pred $ */ -#ifndef _INCLUDE_SOURCEMOD_DETOURS_H_ -#define _INCLUDE_SOURCEMOD_DETOURS_H_ +#ifndef _INCLUDE_SOURCEMOD_DETOURHELPERS_H_ +#define _INCLUDE_SOURCEMOD_DETOURHELPERS_H_ #if defined PLATFORM_POSIX #include @@ -95,4 +95,4 @@ inline void ApplyPatch(void *address, int offset, const patch_t *patch, patch_t } } -#endif //_INCLUDE_SOURCEMOD_DETOURS_H_ +#endif //_INCLUDE_SOURCEMOD_DETOURHELPERS_H_ diff --git a/extensions/sdktools/CDetour/detours.cpp b/extensions/sdktools/CDetour/detours.cpp new file mode 100644 index 00000000..f31c8c84 --- /dev/null +++ b/extensions/sdktools/CDetour/detours.cpp @@ -0,0 +1,192 @@ +/** +* 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 + +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::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; + 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 (!gameconf->GetMemSig(signame, &detour_address)) + { + g_pSM->LogError(myself, "Could not locate %s - Disabling detour", signame); + return false; + } + + 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; iAllocatePageMemory(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 */ + spengine->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/extensions/sdktools/CDetour/detours.h b/extensions/sdktools/CDetour/detours.h new file mode 100644 index 00000000..e518502c --- /dev/null +++ b/extensions/sdktools/CDetour/detours.h @@ -0,0 +1,241 @@ +/** +* 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 "extension.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 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) CDetourManager::CreateDetour(GET_MEMBER_CALLBACK(name), GET_MEMBER_TRAMPOLINE(name), gamedata); +#define DETOUR_CREATE_STATIC(name, gamedata) CDetourManager::CreateDetour(GET_STATIC_CALLBACK(name), GET_STATIC_TRAMPOLINE(name), gamedata); + + +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); + + bool Init(ISourcePawnEngine *spengine, IGameConfig *gameconf); +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; + ISourcePawnEngine *spengine; + IGameConfig *gameconf; +}; + +class CDetourManager +{ +public: + + static void Init(ISourcePawnEngine *spengine, IGameConfig *gameconf); + + /** + * 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); + + friend class CBlocker; + friend class CDetour; + +private: + static ISourcePawnEngine *spengine; + static IGameConfig *gameconf; +}; + +#endif // _INCLUDE_SOURCEMOD_DETOURS_H_ diff --git a/extensions/sdktools/Makefile b/extensions/sdktools/Makefile index 77105f1e..a6c87085 100644 --- a/extensions/sdktools/Makefile +++ b/extensions/sdktools/Makefile @@ -22,7 +22,7 @@ USEMETA = true OBJECTS = sdk/smsdk_ext.cpp extension.cpp vdecoder.cpp vcallbuilder.cpp vcaller.cpp \ vnatives.cpp vsound.cpp tenatives.cpp trnatives.cpp tempents.cpp vstringtable.cpp \ vhelpers.cpp vglobals.cpp voice.cpp inputnatives.cpp teamnatives.cpp output.cpp \ - outputnatives.cpp hooks.cpp + outputnatives.cpp hooks.cpp CDetour/detours.cpp asm/asm.c ############################################## ### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### @@ -91,7 +91,7 @@ ifeq "$(ENGINE)" "left4dead2" override ENGSET = true endif -ifeq "$(ENGINE)" "left4dead2" +ifeq ($(ENGINE),$(filter $(ENGINE), left4dead2 orangeboxvalve)) LINK_HL2 = $(HL2LIB)/tier1_i486.a $(HL2LIB)/mathlib_i486.a libvstdlib.so libtier0.so else LINK_HL2 = $(HL2LIB)/tier1_i486.a $(HL2LIB)/mathlib_i486.a vstdlib_i486.so tier0_i486.so @@ -149,7 +149,9 @@ $(BIN_DIR)/%.o: %.cpp all: check mkdir -p $(BIN_DIR)/sdk -ifeq "$(ENGINE)" "left4dead2" + mkdir -p $(BIN_DIR)/CDetour + mkdir -p $(BIN_DIR)/asm +ifeq ($(ENGINE),$(filter $(ENGINE), left4dead2 orangeboxvalve)) ln -sf $(SRCDS)/bin/libvstdlib.so libvstdlib.so; ln -sf $(SRCDS)/bin/libtier0.so libtier0.so; else @@ -175,4 +177,6 @@ default: all clean: check rm -rf $(BIN_DIR)/*.o rm -rf $(BIN_DIR)/sdk/*.o + rm -rf $(BIN_DIR)/CDetour/*.o + rm -rf $(BIN_DIR)/asm/*.o rm -rf $(BIN_DIR)/$(BINARY) diff --git a/extensions/sdktools/asm/asm.c b/extensions/sdktools/asm/asm.c new file mode 100644 index 00000000..b984fefe --- /dev/null +++ b/extensions/sdktools/asm/asm.c @@ -0,0 +1,421 @@ +#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 + +extern void Msg( const char *, ... ); + +/** +* 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/extensions/sdktools/asm/asm.h b/extensions/sdktools/asm/asm.h new file mode 100644 index 00000000..60862323 --- /dev/null +++ b/extensions/sdktools/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/extensions/sdktools/extension.cpp b/extensions/sdktools/extension.cpp index 59d31d2b..ba175808 100644 --- a/extensions/sdktools/extension.cpp +++ b/extensions/sdktools/extension.cpp @@ -2,7 +2,7 @@ * vim: set ts=4 : * ============================================================================= * SourceMod SDKTools Extension - * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under @@ -142,6 +142,7 @@ bool SDKTools::SDK_OnLoad(char *error, size_t maxlength, bool late) plsys->AddPluginsListener(&g_OutputManager); + CDetourManager::Init(g_pSM->GetScriptingEngine(), g_pGameConf); g_OutputManager.Init(); VoiceInit(); diff --git a/extensions/sdktools/output.cpp b/extensions/sdktools/output.cpp index e6289847..cb453d5e 100644 --- a/extensions/sdktools/output.cpp +++ b/extensions/sdktools/output.cpp @@ -2,7 +2,7 @@ * vim: set ts=4 : * ============================================================================= * SourceMod SDKTools Extension - * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under @@ -34,13 +34,13 @@ ISourcePawnEngine *spengine = NULL; EntityOutputManager g_OutputManager; +CDetour *fireOutputDetour = NULL; EntityOutputManager::EntityOutputManager() { info_address = NULL; info_callback = NULL; HookCount = 0; - is_detoured = false; enabled = false; } @@ -53,7 +53,7 @@ EntityOutputManager::~EntityOutputManager() EntityOutputs->Destroy(); ClassNames->Destroy(); - ShutdownFireEventDetour(); + fireOutputDetour->Destroy(); } void EntityOutputManager::Init() @@ -74,127 +74,19 @@ bool EntityOutputManager::IsEnabled() return enabled; } +DETOUR_DECL_MEMBER4(FireOutput, void, void *, variant_t, CBaseEntity *, pActivator, CBaseEntity *, pCaller, float, fDelay) +{ + g_OutputManager.FireEventDetour((void *)this, pActivator, pCaller, fDelay); + DETOUR_MEMBER_CALL(FireOutput)(variant_t, pActivator, pCaller, fDelay); +} + bool EntityOutputManager::CreateFireEventDetour() { - if (!g_pGameConf->GetMemSig("FireOutput", &info_address)) - { - return false; - } + fireOutputDetour = DETOUR_CREATE_MEMBER(FireOutput, "FireOutput"); - if (!info_address) - { - g_pSM->LogError(myself, "Could not locate FireOutput - Disabling Entity Outputs"); - return false; - } + if (fireOutputDetour) return true; - if (!g_pGameConf->GetOffset("FireOutputBackup", (int *)&(info_restore.bytes))) - { - return false; - } - - /* First, save restore bits */ - for (size_t i=0; iExecAlloc(100); - JitWriter wr; - JitWriter *jit = ≀ - wr.outbase = (jitcode_t)info_callback; - wr.outptr = wr.outbase; - - /* Function we are detouring into is - * - * void FireEventDetour(CBaseEntityOutput(void *) *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay = 0 ) - */ - - /* push fDelay [esp+20h] - * push pCaller [esp+1Ch] - * push pActivator [esp+18h] - * push pOutput [ecx] - */ - - -#if defined PLATFORM_WINDOWS - - IA32_Push_Rm_Disp8_ESP(jit, 32); - IA32_Push_Rm_Disp8_ESP(jit, 32); - IA32_Push_Rm_Disp8_ESP(jit, 32); - - IA32_Push_Reg(jit, REG_ECX); - -#elif defined PLATFORM_LINUX || defined PLATFORM_APPLE - IA32_Push_Rm_Disp8_ESP(jit, 20); - IA32_Push_Rm_Disp8_ESP(jit, 20); - IA32_Push_Rm_Disp8_ESP(jit, 20); - - IA32_Push_Rm_Disp8_ESP(jit, 16); -#endif - - jitoffs_t call = IA32_Call_Imm32(jit, 0); - IA32_Write_Jump32_Abs(jit, call, (void *)TempDetour); - - -#if defined PLATFORM_LINUX || defined PLATFORM_APPLE - IA32_Add_Rm_Imm8(jit, REG_ESP, 4, MOD_REG); //add esp, 4 -#elif defined PLATFORM_WINDOWS - IA32_Pop_Reg(jit, REG_ECX); -#endif - - IA32_Add_Rm_Imm8(jit, REG_ESP, 12, MOD_REG); //add esp, 12 (0Ch) - - - /* Patch old bytes in */ - for (size_t i=0; iwrite_ubyte(info_restore.patch[i]); - } - - /* Return to the original function */ - call = IA32_Jump_Imm32(jit, 0); - IA32_Write_Jump32_Abs(jit, call, (unsigned char *)info_address + info_restore.bytes); - - return true; -} - -void EntityOutputManager::InitFireEventDetour() -{ - if (!is_detoured) - { - DoGatePatch((unsigned char *)info_address, &info_callback); - is_detoured = true; - } -} - -void EntityOutputManager::DeleteFireEventDetour() -{ - if (is_detoured) - { - ShutdownFireEventDetour(); - } - - if (info_callback) - { - /* Free the gate */ - spengine->ExecFree(info_callback); - info_callback = NULL; - } -} - -void TempDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay) -{ - g_OutputManager.FireEventDetour(pOutput, pActivator, pCaller, fDelay); -} - -void EntityOutputManager::ShutdownFireEventDetour() -{ - if (info_callback) - { - /* Remove the patch */ - ApplyPatch(info_address, 0, &info_restore, NULL); - is_detoured = false; - } + return false; } void EntityOutputManager::FireEventDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay) @@ -321,7 +213,7 @@ void EntityOutputManager::OnHookAdded() if (HookCount == 1) { // This is the first hook created - InitFireEventDetour(); + fireOutputDetour->EnableDetour(); } } @@ -331,7 +223,7 @@ void EntityOutputManager::OnHookRemoved() if (HookCount == 0) { - ShutdownFireEventDetour(); + fireOutputDetour->DisableDetour(); } } diff --git a/extensions/sdktools/output.h b/extensions/sdktools/output.h index afaf40d9..89abc4dc 100644 --- a/extensions/sdktools/output.h +++ b/extensions/sdktools/output.h @@ -2,7 +2,7 @@ * vim: set ts=4 : * ============================================================================= * SourceMod SDKTools Extension - * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under @@ -37,7 +37,7 @@ #include "sh_list.h" #include "sh_stack.h" #include "sm_trie_tpl.h" -#include "detours.h" +#include "CDetour/detours.h" extern ISourcePawnEngine *spengine; @@ -117,7 +117,6 @@ private: // Patch/unpatch the server dll void InitFireEventDetour(); void ShutdownFireEventDetour(); - bool is_detoured; //These create/delete the allocated memory and write into it bool CreateFireEventDetour(); diff --git a/gamedata/sdktools.games/engine.darkm.txt b/gamedata/sdktools.games/engine.darkm.txt index a0969526..01ce287b 100644 --- a/gamedata/sdktools.games/engine.darkm.txt +++ b/gamedata/sdktools.games/engine.darkm.txt @@ -207,13 +207,6 @@ "linux" "@_ZN17CBaseEntityOutput10FireOutputE9variant_tP11CBaseEntityS2_f" } } - "Offsets" - { - "FireOutputBackup" - { - "windows" "6" - } - } } /* SetUserInfo data */ diff --git a/gamedata/sdktools.games/engine.ep1.txt b/gamedata/sdktools.games/engine.ep1.txt index be2a7e61..9226b8c5 100644 --- a/gamedata/sdktools.games/engine.ep1.txt +++ b/gamedata/sdktools.games/engine.ep1.txt @@ -241,14 +241,6 @@ "linux" "@_ZN17CBaseEntityOutput10FireOutputE9variant_tP11CBaseEntityS2_f" } } - "Offsets" - { - "FireOutputBackup" - { - "windows" "6" - "linux" "6" - } - } } /* SetUserInfo data */ diff --git a/gamedata/sdktools.games/engine.ep2.txt b/gamedata/sdktools.games/engine.ep2.txt index db621aca..56c95edf 100644 --- a/gamedata/sdktools.games/engine.ep2.txt +++ b/gamedata/sdktools.games/engine.ep2.txt @@ -227,14 +227,6 @@ "linux" "@_ZN17CBaseEntityOutput10FireOutputE9variant_tP11CBaseEntityS2_f" } } - "Offsets" - { - "FireOutputBackup" - { - "windows" "6" - "linux" "10" - } - } } /* SetUserInfo data */ diff --git a/gamedata/sdktools.games/engine.ep2valve.txt b/gamedata/sdktools.games/engine.ep2valve.txt index b546abf7..ef733127 100644 --- a/gamedata/sdktools.games/engine.ep2valve.txt +++ b/gamedata/sdktools.games/engine.ep2valve.txt @@ -232,15 +232,6 @@ "mac" "@_ZN17CBaseEntityOutput10FireOutputE9variant_tP11CBaseEntityS2_f" } } - "Offsets" - { - "FireOutputBackup" - { - "windows" "6" - "linux" "12" - "mac" "6" - } - } } /* SetUserInfo data */ diff --git a/gamedata/sdktools.games/engine.l4d.txt b/gamedata/sdktools.games/engine.l4d.txt index b25ba36b..f37cb85c 100644 --- a/gamedata/sdktools.games/engine.l4d.txt +++ b/gamedata/sdktools.games/engine.l4d.txt @@ -220,14 +220,6 @@ "linux" "@_ZN17CBaseEntityOutput10FireOutputE9variant_tP11CBaseEntityS2_f" } } - "Offsets" - { - "FireOutputBackup" - { - "windows" "6" - "linux" "10" - } - } } /* SetUserInfo data */ diff --git a/gamedata/sdktools.games/engine.l4d2.txt b/gamedata/sdktools.games/engine.l4d2.txt index d00bc003..c61cee57 100644 --- a/gamedata/sdktools.games/engine.l4d2.txt +++ b/gamedata/sdktools.games/engine.l4d2.txt @@ -220,14 +220,6 @@ "linux" "@_ZN17CBaseEntityOutput10FireOutputE9variant_tP11CBaseEntityS2_f" } } - "Offsets" - { - "FireOutputBackup" - { - "windows" "6" - "linux" "12" - } - } } /* SetUserInfo data */