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
+