From e0f670499ccf4547ac1822da93636108b0753a90 Mon Sep 17 00:00:00 2001 From: Drifer Date: Sun, 26 Jun 2011 01:25:42 -0700 Subject: [PATCH] Add a number of useful forwards and natives to the cstrike extension (bug 4732, r=fyren). --- extensions/cstrike/AMBuilder | 5 +- extensions/cstrike/CDetour/detourhelpers.h | 98 +++++ extensions/cstrike/CDetour/detours.cpp | 192 ++++++++++ extensions/cstrike/CDetour/detours.h | 241 ++++++++++++ extensions/cstrike/Makefile | 6 +- extensions/cstrike/asm/asm.c | 421 +++++++++++++++++++++ extensions/cstrike/asm/asm.h | 40 ++ extensions/cstrike/extension.cpp | 78 ++++ extensions/cstrike/extension.h | 18 +- extensions/cstrike/forwards.cpp | 234 ++++++++++++ extensions/cstrike/forwards.h | 17 + extensions/cstrike/msvc9/cstrike.vcproj | 38 +- extensions/cstrike/natives.cpp | 213 ++++++++++- extensions/cstrike/sdk/smsdk_config.h | 5 +- extensions/cstrike/sdk/smsdk_ext.h | 7 + gamedata/sm-cstrike.games.txt | 45 +++ plugins/include/cstrike.inc | 104 +++++ 17 files changed, 1753 insertions(+), 9 deletions(-) create mode 100644 extensions/cstrike/CDetour/detourhelpers.h create mode 100644 extensions/cstrike/CDetour/detours.cpp create mode 100644 extensions/cstrike/CDetour/detours.h create mode 100644 extensions/cstrike/asm/asm.c create mode 100644 extensions/cstrike/asm/asm.h create mode 100644 extensions/cstrike/forwards.cpp create mode 100644 extensions/cstrike/forwards.h diff --git a/extensions/cstrike/AMBuilder b/extensions/cstrike/AMBuilder index b147589d..6ff16fb3 100644 --- a/extensions/cstrike/AMBuilder +++ b/extensions/cstrike/AMBuilder @@ -15,7 +15,10 @@ if AMBuild.target['platform'] in sdk['platform']: 'natives.cpp', 'RegNatives.cpp', 'timeleft.cpp', - 'sdk/smsdk_ext.cpp' + 'forwards.cpp', + 'sdk/smsdk_ext.cpp', + 'CDetour/detours.cpp', + 'asm/asm.c' ]) SM.PostSetupHL2Job(extension, binary, 'ep2v') SM.AutoVersion('extensions/cstrike', binary) diff --git a/extensions/cstrike/CDetour/detourhelpers.h b/extensions/cstrike/CDetour/detourhelpers.h new file mode 100644 index 00000000..85cda0ea --- /dev/null +++ b/extensions/cstrike/CDetour/detourhelpers.h @@ -0,0 +1,98 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2007 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 PLATFORM_POSIX +#include +#define PAGE_SIZE 4096 +#define ALIGN(ar) ((long)ar & ~(PAGE_SIZE-1)) +#define PAGE_EXECUTE_READWRITE PROT_READ|PROT_WRITE|PROT_EXEC +#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 PLATFORM_POSIX + void *addr2 = (void *)ALIGN(addr); + mprotect(addr2, sysconf(_SC_PAGESIZE), prot); +#elif defined PLATFORM_WINDOWS + DWORD old_prot; + VirtualProtect(addr, length, prot, &old_prot); +#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/extensions/cstrike/CDetour/detours.cpp b/extensions/cstrike/CDetour/detours.cpp new file mode 100644 index 00000000..71aba447 --- /dev/null +++ b/extensions/cstrike/CDetour/detours.cpp @@ -0,0 +1,192 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* 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: 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/cstrike/CDetour/detours.h b/extensions/cstrike/CDetour/detours.h new file mode 100644 index 00000000..e43b3ce0 --- /dev/null +++ b/extensions/cstrike/CDetour/detours.h @@ -0,0 +1,241 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* 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: 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/cstrike/Makefile b/extensions/cstrike/Makefile index 1ace2413..69ee7f77 100644 --- a/extensions/cstrike/Makefile +++ b/extensions/cstrike/Makefile @@ -18,7 +18,7 @@ PROJECT = game.cstrike #Uncomment for Metamod: Source enabled extension USEMETA = true -OBJECTS = sdk/smsdk_ext.cpp extension.cpp natives.cpp RegNatives.cpp timeleft.cpp +OBJECTS = sdk/smsdk_ext.cpp extension.cpp natives.cpp RegNatives.cpp timeleft.cpp forwards.cpp CDetour/detours.cpp asm/asm.c ############################################## ### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### @@ -156,7 +156,9 @@ $(BIN_DIR)/%.o: %.cpp $(CPP) $(INCLUDE) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< all: check - mkdir -p $(BIN_DIR)/sdk + mkdir -p $(BIN_DIR)/sdk + mkdir -p $(BIN_DIR)/CDetour + mkdir -p $(BIN_DIR)/asm if [ "$(USEMETA)" = "true" ]; then \ ln -sf $(HL2LIB)/$(LIB_PREFIX)vstdlib$(LIB_SUFFIX); \ ln -sf $(HL2LIB)/$(LIB_PREFIX)tier0$(LIB_SUFFIX); \ diff --git a/extensions/cstrike/asm/asm.c b/extensions/cstrike/asm/asm.c new file mode 100644 index 00000000..2facf8d9 --- /dev/null +++ b/extensions/cstrike/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/cstrike/asm/asm.h b/extensions/cstrike/asm/asm.h new file mode 100644 index 00000000..60862323 --- /dev/null +++ b/extensions/cstrike/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/cstrike/extension.cpp b/extensions/cstrike/extension.cpp index fd82865d..a418e829 100644 --- a/extensions/cstrike/extension.cpp +++ b/extensions/cstrike/extension.cpp @@ -34,6 +34,8 @@ #include "RegNatives.h" #include "timeleft.h" #include "iplayerinfo.h" +#include "ISDKTools.h" +#include "forwards.h" /** * @file extension.cpp @@ -53,6 +55,8 @@ SMEXT_LINK(&g_CStrike); extern sp_nativeinfo_t g_CSNatives[]; +ISDKTools *g_pSDKTools = NULL; + bool CStrike::SDK_OnLoad(char *error, size_t maxlength, bool late) { if (strcmp(g_pSM->GetGameFolderName(), "cstrike") != 0) @@ -75,9 +79,23 @@ bool CStrike::SDK_OnLoad(char *error, size_t maxlength, bool late) sharesys->AddNatives(myself, g_CSNatives); sharesys->RegisterLibrary(myself, "cstrike"); + plsys->AddPluginsListener(this); playerhelpers->RegisterCommandTargetProcessor(this); + CDetourManager::Init(g_pSM->GetScriptingEngine(), g_pGameConf); + + g_pHandleBuyForward = forwards->CreateForward("CS_OnBuyCommand", ET_Event, 2, NULL, Param_Cell, Param_String); + g_pPriceForward = forwards->CreateForward("CS_OnGetWeaponPrice", ET_Event, 3, NULL, Param_Cell, Param_String, Param_CellByRef); + g_pTerminateRoundForward = forwards->CreateForward("CS_OnTerminateRound", ET_Event, 2, NULL, Param_FloatByRef, Param_CellByRef); + g_pCSWeaponDropForward = forwards->CreateForward("CS_OnCSWeaponDrop", ET_Event, 2, NULL, Param_Cell, Param_Cell); + + + m_TerminateRoundDetourEnabled = false; + m_WeaponPriceDetourEnabled = false; + m_HandleBuyDetourEnabled = false; + m_CSWeaponDetourEnabled = false; + return true; } @@ -99,11 +117,26 @@ void CStrike::SDK_OnUnload() } g_RegNatives.UnregisterAll(); gameconfs->CloseGameConfigFile(g_pGameConf); + plsys->RemovePluginsListener(this); playerhelpers->UnregisterCommandTargetProcessor(this); + forwards->ReleaseForward(g_pHandleBuyForward); + forwards->ReleaseForward(g_pPriceForward); + forwards->ReleaseForward(g_pTerminateRoundForward); + forwards->ReleaseForward(g_pCSWeaponDropForward); } void CStrike::SDK_OnAllLoaded() { + SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); + if (g_pSDKTools == NULL) + { + smutils->LogError(myself, "SDKTools interface not found. TerminateRound native disabled."); + } + else if (g_pSDKTools->GetInterfaceVersion() < 2) + { + // THIS ISN'T DA LIMBO STICK. LOW IS BAD + smutils->LogError(myself, "SDKTools interface is outdated. TerminateRound native disabled."); + } gameevents->AddListener(&g_TimeLeftEvents, "round_start", true); gameevents->AddListener(&g_TimeLeftEvents, "round_end", true); SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelInit, gamedll, &g_TimeLeftEvents, &TimeLeftEvents::LevelInit, true); @@ -257,3 +290,48 @@ const char *CStrike::GetExtensionDateString() return SM_BUILD_TIMESTAMP; } +void CStrike::OnPluginLoaded(IPlugin *plugin) +{ + if (!m_WeaponPriceDetourEnabled && g_pPriceForward->GetFunctionCount()) + { + m_WeaponPriceDetourEnabled = CreateWeaponPriceDetour(); + if (m_WeaponPriceDetourEnabled) + m_HandleBuyDetourEnabled = true; + } + if (!m_TerminateRoundDetourEnabled && g_pTerminateRoundForward->GetFunctionCount()) + { + m_TerminateRoundDetourEnabled = CreateTerminateRoundDetour(); + } + if (!m_HandleBuyDetourEnabled && g_pHandleBuyForward->GetFunctionCount()) + { + m_HandleBuyDetourEnabled = CreateHandleBuyDetour(); + } + if (!m_CSWeaponDetourEnabled && g_pCSWeaponDropForward->GetFunctionCount()) + { + m_CSWeaponDetourEnabled = CreateCSWeaponDropDetour(); + } +} + +void CStrike::OnPluginUnloaded(IPlugin *plugin) +{ + if (m_WeaponPriceDetourEnabled && !g_pPriceForward->GetFunctionCount()) + { + RemoveWeaponPriceDetour(); + m_WeaponPriceDetourEnabled = false; + } + if (m_TerminateRoundDetourEnabled && !g_pTerminateRoundForward->GetFunctionCount()) + { + RemoveTerminateRoundDetour(); + m_TerminateRoundDetourEnabled = false; + } + if (m_HandleBuyDetourEnabled && !g_pHandleBuyForward->GetFunctionCount()) + { + RemoveHandleBuyDetour(); + m_HandleBuyDetourEnabled = false; + } + if (m_CSWeaponDetourEnabled && !g_pCSWeaponDropForward->GetFunctionCount()) + { + RemoveCSWeaponDropDetour(); + m_CSWeaponDetourEnabled = false;; + } +} diff --git a/extensions/cstrike/extension.h b/extensions/cstrike/extension.h index 5b459866..778d0d58 100644 --- a/extensions/cstrike/extension.h +++ b/extensions/cstrike/extension.h @@ -38,7 +38,9 @@ */ #include "smsdk_ext.h" +#include "CDetour/detours.h" #include +#include /** * @brief Sample implementation of the SDK Extension. @@ -46,7 +48,8 @@ */ class CStrike : public SDKExtension, - public ICommandTargetProcessor + public ICommandTargetProcessor, + public IPluginsListener { public: /** @@ -91,6 +94,9 @@ public: const char *GetExtensionDateString(); public: bool ProcessCommandTarget(cmd_target_info_t *info); +public: //IPluginsListener + void OnPluginLoaded(IPlugin *plugin); + void OnPluginUnloaded(IPlugin *plugin); public: #if defined SMEXT_CONF_METAMOD /** @@ -124,11 +130,21 @@ public: */ //virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength); #endif +private: + bool m_WeaponPriceDetourEnabled; + bool m_TerminateRoundDetourEnabled; + bool m_HandleBuyDetourEnabled; + bool m_CSWeaponDetourEnabled; }; /* Interfaces from SourceMod */ extern IBinTools *g_pBinTools; extern IGameConfig *g_pGameConf; +extern ISDKTools *g_pSDKTools; extern int g_msgHintText; +extern bool g_pIgnoreTerminateDetour; +extern bool g_pIgnoreCSWeaponDropDetour; +extern bool g_pTerminateRoundDetoured; +extern bool g_pCSWeaponDropDetoured; #endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/extensions/cstrike/forwards.cpp b/extensions/cstrike/forwards.cpp new file mode 100644 index 00000000..21597669 --- /dev/null +++ b/extensions/cstrike/forwards.cpp @@ -0,0 +1,234 @@ +#include "extension.h" +#include "forwards.h" + +bool g_pTerminateRoundDetoured = false; +bool g_pCSWeaponDropDetoured = false; +bool g_pIgnoreTerminateDetour = false; +bool g_pIgnoreCSWeaponDropDetour = false; +bool g_PriceDetoured = false; +bool g_HandleBuyDetoured = false; +int lastclient = -1; + +IForward *g_pHandleBuyForward = NULL; +IForward *g_pPriceForward = NULL; +IForward *g_pTerminateRoundForward = NULL; +IForward *g_pCSWeaponDropForward = NULL; +CDetour *DHandleBuy = NULL; +CDetour *DWeaponPrice = NULL; +CDetour *DTerminateRound = NULL; +CDetour *DCSWeaponDrop = NULL; + +int weaponNameOffset = -1; + +DETOUR_DECL_MEMBER1(DetourHandleBuy, int, const char *, weapon) +{ + int client = engine->IndexOfEdict(engine->PEntityOfEntIndex(gamehelpers->EntityToBCompatRef(reinterpret_cast(this)))); + + lastclient = client; + + cell_t result = Pl_Continue; + + g_pHandleBuyForward->PushCell(client); + g_pHandleBuyForward->PushString(weapon); + g_pHandleBuyForward->Execute(&result); + + if (result != Pl_Continue) + { + lastclient = -1; + return 0; + } + int val = DETOUR_MEMBER_CALL(DetourHandleBuy)(weapon); + lastclient = -1; + return val; +} + +DETOUR_DECL_MEMBER0(DetourWeaponPrice, int) +{ + int price = DETOUR_MEMBER_CALL(DetourWeaponPrice)(); + + if (lastclient == -1) + return price; + + const char *weapon_name = reinterpret_cast(this+weaponNameOffset); + + int original = price; + + cell_t result = Pl_Continue; + + g_pPriceForward->PushCell(lastclient); + g_pPriceForward->PushString(weapon_name); + g_pPriceForward->PushCellByRef(&price); + g_pPriceForward->Execute(&result); + + if (result == Pl_Continue) + return original; + + return price; +} + +DETOUR_DECL_MEMBER2(DetourTerminateRound, void, float, delay, int, reason) +{ + if (g_pIgnoreTerminateDetour) + { + g_pIgnoreTerminateDetour = false; + DETOUR_MEMBER_CALL(DetourTerminateRound)(delay, reason); + return; + } + float orgdelay = delay; + int orgreason = reason; + + cell_t result = Pl_Continue; + + g_pTerminateRoundForward->PushFloatByRef(&delay); + g_pTerminateRoundForward->PushCellByRef(&reason); + g_pTerminateRoundForward->Execute(&result); + + if (result >= Pl_Handled) + return; + + if (result == Pl_Changed) + return DETOUR_MEMBER_CALL(DetourTerminateRound)(delay, reason); + + return DETOUR_MEMBER_CALL(DetourTerminateRound)(orgdelay, orgreason); +} +DETOUR_DECL_MEMBER3(DetourCSWeaponDrop, void, CBaseEntity *, weapon, bool, something, bool, toss) +{ + if (g_pIgnoreCSWeaponDropDetour) + { + g_pIgnoreCSWeaponDropDetour = false; + DETOUR_MEMBER_CALL(DetourCSWeaponDrop)(weapon, something, toss); + return; + } + + int client = engine->IndexOfEdict(engine->PEntityOfEntIndex(gamehelpers->EntityToBCompatRef(reinterpret_cast(this)))); + int weaponIndex = engine->IndexOfEdict(engine->PEntityOfEntIndex(gamehelpers->EntityToBCompatRef(weapon))); + + cell_t result = Pl_Continue; + g_pCSWeaponDropForward->PushCell(client); + g_pCSWeaponDropForward->PushCell(weaponIndex); + g_pCSWeaponDropForward->Execute(&result); + + + if (result == Pl_Continue) + DETOUR_MEMBER_CALL(DetourCSWeaponDrop)(weapon, something, toss); + + return; +} +bool CreateWeaponPriceDetour() +{ + if (weaponNameOffset == -1) + { + if (!g_pGameConf->GetOffset("WeaponName", &weaponNameOffset)) + { + smutils->LogError(myself, "Could not find WeaponName offset - Disabled OnGetWeaponPrice forward"); + return false; + } + } + DWeaponPrice = DETOUR_CREATE_MEMBER(DetourWeaponPrice, "GetWeaponPrice"); + + if (DWeaponPrice != NULL) + { + if (!CreateHandleBuyDetour()) + { + g_pSM->LogError(myself, "GetWeaponPrice detour could not be initialized - HandleCommand_Buy_Internal failed to detour, disabled OnGetWeaponPrice forward."); + return false; + } + DWeaponPrice->EnableDetour(); + g_PriceDetoured = true; + return true; + } + + g_pSM->LogError(myself, "GetWeaponPrice detour could not be initialized - Disabled OnGetWeaponPrice forward."); + + return false; +} + +bool CreateTerminateRoundDetour() +{ + DTerminateRound = DETOUR_CREATE_MEMBER(DetourTerminateRound, "TerminateRound"); + + if (DTerminateRound != NULL) + { + DTerminateRound->EnableDetour(); + g_pTerminateRoundDetoured = true; + return true; + } + g_pSM->LogError(myself, "TerminateRound detour could not be initialized - Disabled OnTerminateRound forward"); + return false; +} + +bool CreateHandleBuyDetour() +{ + if (g_HandleBuyDetoured) + return true; + + DHandleBuy = DETOUR_CREATE_MEMBER(DetourHandleBuy, "HandleCommand_Buy_Internal"); + + if (DHandleBuy != NULL) + { + DHandleBuy->EnableDetour(); + g_HandleBuyDetoured = true; + return true; + } + g_pSM->LogError(myself, "HandleCommand_Buy_Internal detour could not be initialized - Disabled OnBuyCommand forward"); + return false; +} + +bool CreateCSWeaponDropDetour() +{ + DCSWeaponDrop = DETOUR_CREATE_MEMBER(DetourCSWeaponDrop, "CSWeaponDrop"); + + if (DCSWeaponDrop != NULL) + { + DCSWeaponDrop->EnableDetour(); + g_pCSWeaponDropDetoured = true; + return true; + } + + g_pSM->LogError(myself, "CSWeaponDrop detour could not be initialized - Disabled OnCSWeaponDrop forward"); + return false; +} + +void RemoveWeaponPriceDetour() +{ + if (DWeaponPrice != NULL) + { + DWeaponPrice->Destroy(); + DWeaponPrice = NULL; + } + g_PriceDetoured = false; + +} + +void RemoveHandleBuyDetour() +{ + if (g_PriceDetoured) + return; + + if (DHandleBuy != NULL) + { + DHandleBuy->Destroy(); + DHandleBuy = NULL; + } + g_HandleBuyDetoured = false; +} + +void RemoveTerminateRoundDetour() +{ + if (DTerminateRound != NULL) + { + DTerminateRound->Destroy(); + DTerminateRound = NULL; + } + g_pTerminateRoundDetoured = false; +} + +void RemoveCSWeaponDropDetour() +{ + if (DCSWeaponDrop != NULL) + { + DCSWeaponDrop->Destroy(); + DCSWeaponDrop = NULL; + } + g_pCSWeaponDropDetoured = false; +} diff --git a/extensions/cstrike/forwards.h b/extensions/cstrike/forwards.h new file mode 100644 index 00000000..785c3224 --- /dev/null +++ b/extensions/cstrike/forwards.h @@ -0,0 +1,17 @@ +#ifndef _INCLUDE_CSTRIKE_FORWARDS_H_ +#define _INCLUDE_CSTRIKE_FORWARDS_H_ +bool CreateWeaponPriceDetour(); +bool CreateHandleBuyDetour(); +bool CreateTerminateRoundDetour(); +bool CreateCSWeaponDropDetour(); +void RemoveWeaponPriceDetour(); +void RemoveHandleBuyDetour(); +void RemoveTerminateRoundDetour(); +void RemoveCSWeaponDropDetour(); + +extern IServerGameEnts *gameents; +extern IForward *g_pHandleBuyForward; +extern IForward *g_pPriceForward; +extern IForward *g_pTerminateRoundForward; +extern IForward *g_pCSWeaponDropForward; +#endif //_INCLUDE_CSTRIKE_FORWARDS_H_ diff --git a/extensions/cstrike/msvc9/cstrike.vcproj b/extensions/cstrike/msvc9/cstrike.vcproj index 3e5e5005..0ff2f522 100644 --- a/extensions/cstrike/msvc9/cstrike.vcproj +++ b/extensions/cstrike/msvc9/cstrike.vcproj @@ -505,7 +505,7 @@ Name="VCPostBuildEventTool" /> - + + @@ -703,6 +707,10 @@ RelativePath="..\extension.h" > + + @@ -739,6 +747,34 @@ > + + + + + + + + + + + + + + diff --git a/extensions/cstrike/natives.cpp b/extensions/cstrike/natives.cpp index ed2e5ceb..813c94ac 100644 --- a/extensions/cstrike/natives.cpp +++ b/extensions/cstrike/natives.cpp @@ -31,10 +31,11 @@ #include "extension.h" #include "RegNatives.h" +#include #define REGISTER_NATIVE_ADDR(name, code) \ void *addr; \ - if (!g_pGameConf->GetMemSig(name, &addr)) \ + if (!g_pGameConf->GetMemSig(name, &addr) || !addr) \ { \ return pContext->ThrowNativeError("Failed to locate function"); \ } \ @@ -71,6 +72,59 @@ inline CBaseEntity *GetCBaseEntity(int num, bool isplayer) return pUnk->GetBaseEntity(); } +bool UTIL_FindDataTable(SendTable *pTable, const char *name, sm_sendprop_info_t *info, unsigned int offset) +{ + const char *pname; + int props = pTable->GetNumProps(); + SendProp *prop; + SendTable *table; + + for (int i=0; iGetProp(i); + + if ((table = prop->GetDataTable()) != NULL) + { + pname = table->GetName(); + if (pname && strcmp(name, pname) == 0) + { + info->prop = prop; + info->actual_offset = offset + info->prop->GetOffset(); + return true; + } + + if (UTIL_FindDataTable(table, + name, + info, + offset + prop->GetOffset()) + ) + { + return true; + } + } + } + + return false; +} + +//Taken From Sourcemod Core +unsigned int strncopy(char *dest, const char *src, size_t count) +{ + if (!count) + { + return 0; + } + + char *start = dest; + while ((*src) && (--count)) + { + *dest++ = *src++; + } + *dest = '\0'; + + return (dest - start); +} + static cell_t CS_RespawnPlayer(IPluginContext *pContext, const cell_t *params) { static ICallWrapper *pWrapper = NULL; @@ -120,10 +174,165 @@ static cell_t CS_SwitchTeam(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t CS_DropWeapon(IPluginContext *pContext, const cell_t *params) +{ + static ICallWrapper *pWrapper = NULL; + if (!pWrapper) + { + REGISTER_NATIVE_ADDR("CSWeaponDrop", + PassInfo pass[3]; \ + pass[0].flags = PASSFLAG_BYVAL; \ + pass[0].type = PassType_Basic; \ + pass[0].size = sizeof(CBaseEntity *); \ + pass[1].flags = PASSFLAG_BYVAL; \ + pass[1].type = PassType_Basic; \ + pass[1].size = sizeof(bool); \ + pass[2].flags = PASSFLAG_BYVAL; \ + pass[2].type = PassType_Basic; \ + pass[2].size = sizeof(bool); \ + pWrapper = g_pBinTools->CreateCall(addr, CallConv_ThisCall, NULL, pass, 3)) + } + + CBaseEntity *pEntity; + if (!(pEntity = GetCBaseEntity(params[1], true))) + { + return pContext->ThrowNativeError("Client index %d is not valid", params[1]); + } + + CBaseEntity *pWeapon; + if (!(pWeapon = GetCBaseEntity(params[2], false))) + { + return pContext->ThrowNativeError("Weapon index %d is not valid", params[2]); + } + + //Psychonic is awesome for this + sm_sendprop_info_t *spi = new sm_sendprop_info_t; + IServerUnknown *pUnk = (IServerUnknown *)pWeapon; + IServerNetworkable *pNet = pUnk->GetNetworkable(); + + if (!UTIL_FindDataTable(pNet->GetServerClass()->m_pTable, "DT_WeaponCSBase", spi, 0)) + return pContext->ThrowNativeError("Entity index %d is not a weapon", params[2]); + + if (!gamehelpers->FindSendPropInfo("CBaseCombatWeapon", "m_hOwnerEntity", spi)) + return pContext->ThrowNativeError("Invalid entity index %d for weapon", params[2]); + + CBaseHandle &hndl = *(CBaseHandle *)((uint8_t *)pWeapon + spi->actual_offset); + if (params[1] != hndl.GetEntryIndex()) + return pContext->ThrowNativeError("Weapon %d is not owned by client %d", params[2], params[1]); + + if (params[4] == 1 && g_pCSWeaponDropDetoured) + g_pIgnoreCSWeaponDropDetour = true; + + unsigned char vstk[sizeof(CBaseEntity *) * 2 + sizeof(bool) * 2]; + unsigned char *vptr = vstk; + + // first one is always false. second is true to toss, false to just drop + *(CBaseEntity **)vptr = pEntity; + vptr += sizeof(CBaseEntity *); + *(CBaseEntity **)vptr = pWeapon; + vptr += sizeof(CBaseEntity *); + *(bool *)vptr = false; + vptr += sizeof(bool); + *(bool *)vptr = (params[3]) ? true : false; + + pWrapper->Execute(vstk, NULL); + + return 1; +} + +static cell_t CS_TerminateRound(IPluginContext *pContext, const cell_t *params) +{ + if (g_pSDKTools == NULL) + { + smutils->LogError(myself, "SDKTools interface not found. TerminateRound native disabled."); + } + else if (g_pSDKTools->GetInterfaceVersion() < 2) + { + // THIS ISN'T DA LIMBO STICK. LOW IS BAD + return pContext->ThrowNativeError("SDKTools interface is outdated. TerminateRound native disabled."); + } + + static ICallWrapper *pWrapper = NULL; + + if (!pWrapper) + { + REGISTER_NATIVE_ADDR("TerminateRound", + PassInfo pass[2]; \ + pass[0].flags = PASSFLAG_BYVAL; \ + pass[0].type = PassType_Basic; \ + pass[0].size = sizeof(float); \ + pass[1].flags = PASSFLAG_BYVAL; \ + pass[1].type = PassType_Basic; \ + pass[1].size = sizeof(int); \ + pWrapper = g_pBinTools->CreateCall(addr, CallConv_ThisCall, NULL, pass, 2)) + } + + void *gamerules = g_pSDKTools->GetGameRules(); + if (gamerules == NULL) + { + return pContext->ThrowNativeError("GameRules not available. TerminateRound native disabled."); + } + + if (params[3] == 1 && g_pTerminateRoundDetoured) + g_pIgnoreTerminateDetour = true; + + unsigned char vstk[sizeof(void *) + sizeof(float)+ sizeof(int)]; + unsigned char *vptr = vstk; + + *(void **)vptr = gamerules; + vptr += sizeof(void *); + *(float *)vptr = sp_ctof(params[1]); + vptr += sizeof(float); + *(int*)vptr = params[2]; + + pWrapper->Execute(vstk, NULL); + + return 1; +} + +static cell_t CS_GetTranslatedWeaponAlias(IPluginContext *pContext, const cell_t *params) +{ + static ICallWrapper *pWrapper = NULL; + + if (!pWrapper) + { + REGISTER_NATIVE_ADDR("GetTranslatedWeaponAlias", + PassInfo pass[1]; \ + PassInfo retpass[1]; \ + pass[0].flags = PASSFLAG_BYVAL; \ + pass[0].type = PassType_Basic; \ + pass[0].size = sizeof(char *); \ + retpass[0].flags = PASSFLAG_BYVAL; \ + retpass[0].type = PassType_Basic; \ + retpass[0].size = sizeof(char *); \ + pWrapper = g_pBinTools->CreateCall(addr, CallConv_Cdecl, &retpass[0], pass, 1)) + } + char *dest, *weapon; + + pContext->LocalToString(params[2], &dest); + pContext->LocalToString(params[1], &weapon); + + char *ret = new char[128]; + + unsigned char vstk[sizeof(char *)]; + unsigned char *vptr = vstk; + + *(char **)vptr = weapon; + + pWrapper->Execute(vstk, &ret); + + strncopy(dest, ret, params[3]); + + return 1; +} + sp_nativeinfo_t g_CSNatives[] = { {"CS_RespawnPlayer", CS_RespawnPlayer}, - {"CS_SwitchTeam", CS_SwitchTeam}, + {"CS_SwitchTeam", CS_SwitchTeam}, + {"CS_DropWeapon", CS_DropWeapon}, + {"CS_TerminateRound", CS_TerminateRound}, + {"CS_GetTranslatedWeaponAlias", CS_GetTranslatedWeaponAlias}, {NULL, NULL} }; diff --git a/extensions/cstrike/sdk/smsdk_config.h b/extensions/cstrike/sdk/smsdk_config.h index a522b074..f56dbfb5 100644 --- a/extensions/cstrike/sdk/smsdk_config.h +++ b/extensions/cstrike/sdk/smsdk_config.h @@ -59,16 +59,17 @@ #define SMEXT_CONF_METAMOD /** Enable interfaces you want to use here by uncommenting lines */ -//#define SMEXT_ENABLE_FORWARDSYS +#define SMEXT_ENABLE_FORWARDSYS //#define SMEXT_ENABLE_HANDLESYS #define SMEXT_ENABLE_PLAYERHELPERS //#define SMEXT_ENABLE_DBMANAGER #define SMEXT_ENABLE_GAMECONF //#define SMEXT_ENABLE_MEMUTILS -//#define SMEXT_ENABLE_GAMEHELPERS +#define SMEXT_ENABLE_GAMEHELPERS #define SMEXT_ENABLE_TIMERSYS //#define SMEXT_ENABLE_THREADER //#define SMEXT_ENABLE_LIBSYS #define SMEXT_ENABLE_USERMSGS +#define SMEXT_ENABLE_PLUGINSYS #endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/extensions/cstrike/sdk/smsdk_ext.h b/extensions/cstrike/sdk/smsdk_ext.h index 3d5a229d..a31b65b0 100644 --- a/extensions/cstrike/sdk/smsdk_ext.h +++ b/extensions/cstrike/sdk/smsdk_ext.h @@ -76,6 +76,9 @@ #if defined SMEXT_ENABLE_USERMSGS #include #endif +#if defined SMEXT_ENABLE_PLUGINSYS +#include +#endif #if defined SMEXT_CONF_METAMOD #include @@ -266,6 +269,10 @@ extern ILibrarySys *libsys; #if defined SMEXT_ENABLE_USERMSGS extern IUserMessages *usermsgs; #endif +#if defined SMEXT_ENABLE_PLUGINSYS +extern IPluginManager *plsys; +#endif + #if defined SMEXT_CONF_METAMOD PLUGIN_GLOBALVARS(); diff --git a/gamedata/sm-cstrike.games.txt b/gamedata/sm-cstrike.games.txt index 2064b08d..a4262280 100644 --- a/gamedata/sm-cstrike.games.txt +++ b/gamedata/sm-cstrike.games.txt @@ -13,6 +13,16 @@ { "cstrike" { + "Offsets" + { + //Offset of szClassName in CCSWeaponInfo + "WeaponName" + { + "windows" "6" + "linux" "6" + "mac" "6" + } + } "Signatures" { "RoundRespawn" @@ -29,6 +39,41 @@ "linux" "@_ZN9CCSPlayer10SwitchTeamEi" "mac" "@_ZN9CCSPlayer10SwitchTeamEi" } + "HandleCommand_Buy_Internal" + { + "library" "server" + "windows" "\x55\x8B\xEC\x81\xEC\x2A\x01\x00\x00\x89\x8D\x58" + "linux" "@_ZN9CCSPlayer26HandleCommand_Buy_InternalEPKc" + "mac" "@_ZN9CCSPlayer26HandleCommand_Buy_InternalEPKc" + } + "GetWeaponPrice" + { + "library" "server" + "windows" "\x8B\x81\xB0\x08\x00\x00\xC3" + "linux" "@_ZNK13CCSWeaponInfo14GetWeaponPriceEv" + "mac" "@_ZNK13CCSWeaponInfo14GetWeaponPriceEv" + } + "CSWeaponDrop"//Wildcard first 6 bytes for CS:S DM + { + "library" "server" + "windows" "\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x89\x8D\x5C\x2A\x2A\x2A\xC6\x45\x2A\x2A\x8B\x8D\x5C\x2A\x2A\x2A\xE8" + "linux" "@_ZN9CCSPlayer12CSWeaponDropEP17CBaseCombatWeaponbb" + "mac" "@_ZN9CCSPlayer12CSWeaponDropEP17CBaseCombatWeaponbb" + } + "TerminateRound" + { + "library" "server" + "windows" "\x83\xEC\x2A\x53\x8B\x5C\x2A\x2A\x55\x56\x57\x33\xF6\x8B\xE9\x33" + "linux" "@_ZN12CCSGameRules14TerminateRoundEfi" + "mac" "@_ZN12CCSGameRules14TerminateRoundEfi" + } + "GetTranslatedWeaponAlias" + { + "library" "server" + "windows" "\xA1\x2A\x2A\x2A\x2A\x80\x2A\x00\x53" + "linux" "@_Z24GetTranslatedWeaponAliasPKc" + "mac" "@_Z24GetTranslatedWeaponAliasPKc" + } } } } diff --git a/plugins/include/cstrike.inc b/plugins/include/cstrike.inc index b7568c45..298d1615 100644 --- a/plugins/include/cstrike.inc +++ b/plugins/include/cstrike.inc @@ -45,6 +45,70 @@ #define CS_SLOT_GRENADE 3 /**< Grenade slot (will only return one grenade). */ #define CS_SLOT_C4 4 /**< C4 slot. */ +enum CSRoundEndReason +{ + CSRoundEnd_TargetBombed = 0, // Target Successfully Bombed! + CSRoundEnd_VIPEscaped, // The VIP has escaped! + CSRoundEnd_VIPKilled, // VIP has been assassinated! + CSRoundEnd_TerroristsEscaped, // The terrorists have escaped! + CSRoundEnd_CTStoppedEscape, // The CTs have prevented most of the terrorists from escaping! + CSRoundEnd_TerroristsStopped, // Escaping terrorists have all been neutralized! + CSRoundEnd_BombDefused, // The bomb has been defused! + CSRoundEnd_CTWin, // Counter-Terrorists Win! + CSRoundEnd_TerroristWin, // Terrorists Win! + CSRoundEnd_Draw, // Round Draw! + CSRoundEnd_HostagesRescued, // All Hostages have been rescued! + CSRoundEnd_TargetSaved, // Target has been saved! + CSRoundEnd_HostagesNotRescued, // Hostages have not been rescued! + CSRoundEnd_TerroristsNotEscaped, // Terrorists have not escaped! + CSRoundEnd_VIPNotEscaped, // VIP has not escaped! + CSRoundEnd_GameStart // Game Commencing! +}; + +/** + * Called when a player attempts to purchase an item. + * Return Plugin_Continue to allow the purchase or return a + * higher action to deny. + * + * @param client Client index + * @param weapon User input for weapon name + */ +forward Action:CS_OnBuyCommand(client, const String:weapon[]); + +/** + * Called when CSWeaponDrop is called + * Return Plugin_Continue to allow the call or return a + * higher action to deny. + * + * @param client Client index + * @param weapon Weapon index + */ +forward Action:CS_OnCSWeaponDrop(client, weaponIndex); + +/** + * Called when game retrieves a weapon's price for a player. + * Return Plugin_Continue to use default value or return a higher + * action to use a newly-set price. + * + * @note This can be called multiple times per weapon purchase + * + * @param client Client index + * @param weapon Weapon classname + * @param price Buffer param for the price of the weapon + */ +forward Action:CS_OnGetWeaponPrice(client, const String:weapon[], &price); + +/** + * Called when TerminateRound is called. + * Return Plugin_Continue to ignore, return Plugin_Changed to continue, + * using the given delay and reason, or return Plugin_Handled or a higher + * action to block TerminateRound from firing. + * + * @param delay Time (in seconds) until new round starts + * @param reason Reason for round end + */ +forward Action:CS_OnTerminateRound(&Float:delay, &CSRoundEndReason:reason); + /** * Respawns a player. * @@ -64,6 +128,42 @@ native CS_RespawnPlayer(client); */ native CS_SwitchTeam(client, team); +/** + * Forces a player to drop or toss their weapon + * + * @param client Player's index. + * @param weaponIndex Index of weapon to drop. + * @param toss True to toss weapon (with velocity) or false to just drop weapon + * @param blockhook Set to true to stop the corresponding CS_OnCSWeaponDrop + * + * @noreturn + * @error Invalid client index, client not in game, or invalid weapon index. + */ +native CS_DropWeapon(client, weaponIndex, bool:toss, bool:blockhook = false); + +/** + * Forces round to end with a reason + * + * @param delay Time (in seconds) to delay before new round starts + * @param reason Reason for the round ending + * @param blockhook Set to true to stop the corresponding CS_OnTerminateRound + * forward from being called. + * @noreturn + */ + native CS_TerminateRound(Float:delay, CSRoundEndReason:reason, bool:blockhook = false); + + /** + * Gets a weapon name from a weapon alias + * + * @param alias Weapons alias to get weapon name for. + * @param weapon Buffer to store weapons name + * @param size Size of buffer to store the weapons name. + * @noreturn + * + * @note Will set the buffer to the original alias if it is not an alias to a weapon. + */ + native CS_GetTranslatedWeaponAlias(const String:alias[], String:weapon[], size); + /** * Do not edit below this line! */ @@ -84,5 +184,9 @@ public __ext_cstrike_SetNTVOptional() { MarkNativeAsOptional("CS_RespawnPlayer"); MarkNativeAsOptional("CS_SwitchTeam"); + MarkNativeAsOptional("CS_DropWeapon"); + MarkNativeAsOptional("CS_TerminateRound"); + MarkNativeAsOptional("CS_GetTranslatedWeaponAlias"); } #endif +