From 0f5f1a814ed8cbb4f1edc8bd3a30ca063d08ec10 Mon Sep 17 00:00:00 2001 From: Peace-Maker Date: Tue, 2 Nov 2021 14:28:24 +0100 Subject: [PATCH] Import DHooks + Dynamic Detouring This is the latest DHooks version from https://github.com/peace-maker/DHooks2/tree/1314f2d1b4d870677a1b9f628c770f2799878f9b --- AMBuildScript | 1 + extensions/dhooks/AMBuilder | 63 + extensions/dhooks/DynamicHooks/convention.h | 290 ++++ .../DynamicHooks/conventions/x86GccCdecl.h | 48 + .../conventions/x86GccThiscall.cpp | 87 + .../DynamicHooks/conventions/x86GccThiscall.h | 59 + .../DynamicHooks/conventions/x86MsCdecl.cpp | 187 ++ .../DynamicHooks/conventions/x86MsCdecl.h | 88 + .../conventions/x86MsFastcall.cpp | 61 + .../DynamicHooks/conventions/x86MsFastcall.h | 72 + .../DynamicHooks/conventions/x86MsStdcall.cpp | 194 +++ .../DynamicHooks/conventions/x86MsStdcall.h | 88 + .../conventions/x86MsThiscall.cpp | 239 +++ .../DynamicHooks/conventions/x86MsThiscall.h | 93 + extensions/dhooks/DynamicHooks/hook.cpp | 503 ++++++ extensions/dhooks/DynamicHooks/hook.h | 217 +++ extensions/dhooks/DynamicHooks/manager.cpp | 107 ++ extensions/dhooks/DynamicHooks/manager.h | 86 + extensions/dhooks/DynamicHooks/registers.cpp | 509 ++++++ extensions/dhooks/DynamicHooks/registers.h | 462 +++++ extensions/dhooks/DynamicHooks/utilities.cpp | 73 + extensions/dhooks/DynamicHooks/utilities.h | 41 + extensions/dhooks/dynhooks_sourcepawn.cpp | 633 +++++++ extensions/dhooks/dynhooks_sourcepawn.h | 42 + extensions/dhooks/extension.cpp | 165 ++ extensions/dhooks/extension.h | 134 ++ extensions/dhooks/listeners.cpp | 138 ++ extensions/dhooks/listeners.h | 32 + extensions/dhooks/natives.cpp | 1513 +++++++++++++++++ extensions/dhooks/natives.h | 14 + extensions/dhooks/sdk-hacks.h | 75 + extensions/dhooks/signatures.cpp | 574 +++++++ extensions/dhooks/signatures.h | 53 + extensions/dhooks/smsdk_config.h | 82 + extensions/dhooks/util.cpp | 205 +++ extensions/dhooks/util.h | 54 + extensions/dhooks/version.rc | 45 + extensions/dhooks/vfunc_call.h | 324 ++++ extensions/dhooks/vhook.cpp | 1007 +++++++++++ extensions/dhooks/vhook.h | 274 +++ plugins/include/dhooks.inc | 1067 ++++++++++++ 41 files changed, 9999 insertions(+) create mode 100644 extensions/dhooks/AMBuilder create mode 100644 extensions/dhooks/DynamicHooks/convention.h create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86GccCdecl.h create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86GccThiscall.cpp create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86GccThiscall.h create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86MsCdecl.cpp create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86MsCdecl.h create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86MsFastcall.cpp create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86MsFastcall.h create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86MsStdcall.cpp create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86MsStdcall.h create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86MsThiscall.cpp create mode 100644 extensions/dhooks/DynamicHooks/conventions/x86MsThiscall.h create mode 100644 extensions/dhooks/DynamicHooks/hook.cpp create mode 100644 extensions/dhooks/DynamicHooks/hook.h create mode 100644 extensions/dhooks/DynamicHooks/manager.cpp create mode 100644 extensions/dhooks/DynamicHooks/manager.h create mode 100644 extensions/dhooks/DynamicHooks/registers.cpp create mode 100644 extensions/dhooks/DynamicHooks/registers.h create mode 100644 extensions/dhooks/DynamicHooks/utilities.cpp create mode 100644 extensions/dhooks/DynamicHooks/utilities.h create mode 100644 extensions/dhooks/dynhooks_sourcepawn.cpp create mode 100644 extensions/dhooks/dynhooks_sourcepawn.h create mode 100644 extensions/dhooks/extension.cpp create mode 100644 extensions/dhooks/extension.h create mode 100644 extensions/dhooks/listeners.cpp create mode 100644 extensions/dhooks/listeners.h create mode 100644 extensions/dhooks/natives.cpp create mode 100644 extensions/dhooks/natives.h create mode 100644 extensions/dhooks/sdk-hacks.h create mode 100644 extensions/dhooks/signatures.cpp create mode 100644 extensions/dhooks/signatures.h create mode 100644 extensions/dhooks/smsdk_config.h create mode 100644 extensions/dhooks/util.cpp create mode 100644 extensions/dhooks/util.h create mode 100644 extensions/dhooks/version.rc create mode 100644 extensions/dhooks/vfunc_call.h create mode 100644 extensions/dhooks/vhook.cpp create mode 100644 extensions/dhooks/vhook.h create mode 100644 plugins/include/dhooks.inc diff --git a/AMBuildScript b/AMBuildScript index 6e2fd936..9dd25828 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -804,6 +804,7 @@ else: 'extensions/clientprefs/AMBuilder', 'extensions/curl/AMBuilder', 'extensions/cstrike/AMBuilder', + 'extensions/dhooks/AMBuilder', 'extensions/geoip/AMBuilder', 'extensions/mysql/AMBuilder', 'extensions/pgsql/AMBuilder', diff --git a/extensions/dhooks/AMBuilder b/extensions/dhooks/AMBuilder new file mode 100644 index 00000000..a581be85 --- /dev/null +++ b/extensions/dhooks/AMBuilder @@ -0,0 +1,63 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os + +for cxx in builder.targets: + binary = SM.ExtLibrary(builder, cxx, 'dhooks.ext') + + # Only x86 on Linux and Windows is supported. + if binary.compiler.target.platform == 'mac' or binary.compiler.target.arch != 'x86': + continue + + binary.compiler.defines += [ + 'META_NO_HL2SDK', + 'HAVE_STRING_H', + ] + + if binary.compiler.like('gcc'): + binary.compiler.cflags += ['-Wno-invalid-offsetof'] + + binary.compiler.cxxincludes += [ + os.path.join(SM.mms_root, 'core'), + os.path.join(SM.mms_root, 'core', 'sourcehook'), + os.path.join(builder.sourcePath, 'sourcepawn', 'include'), + os.path.join(builder.sourcePath, 'sourcepawn', 'vm'), + os.path.join(builder.sourcePath, 'sourcepawn', 'vm', 'x86'), + os.path.join(builder.sourcePath, 'extensions', 'dhooks', 'DynamicHooks'), + ] + + binary.sources += [ + 'extension.cpp', + 'listeners.cpp', + 'natives.cpp', + 'signatures.cpp', + 'vhook.cpp', + 'util.cpp', + 'dynhooks_sourcepawn.cpp', + '../../public/smsdk_ext.cpp', + '../../public/asm/asm.c', + '../../public/libudis86/decode.c', + '../../public/libudis86/itab.c', + '../../public/libudis86/syn-att.c', + '../../public/libudis86/syn-intel.c', + '../../public/libudis86/syn.c', + '../../public/libudis86/udis86.c', + '../../sourcepawn/vm/x86/assembler-x86.cpp', + ] + + # DynamicHooks + binary.sources += [ + os.path.join('DynamicHooks', 'hook.cpp'), + os.path.join('DynamicHooks', 'manager.cpp'), + os.path.join('DynamicHooks', 'registers.cpp'), + os.path.join('DynamicHooks', 'utilities.cpp'), + os.path.join('DynamicHooks', 'conventions', 'x86MsCdecl.cpp'), + os.path.join('DynamicHooks', 'conventions', 'x86MsStdcall.cpp'), + os.path.join('DynamicHooks', 'conventions', 'x86MsFastcall.cpp'), + ] + + if binary.compiler.target.platform == 'windows': + binary.sources += [os.path.join('DynamicHooks', 'conventions', 'x86MsThiscall.cpp')] + else: + binary.sources += [os.path.join('DynamicHooks', 'conventions', 'x86GccThiscall.cpp')] + + SM.extensions += [builder.Add(binary)] diff --git a/extensions/dhooks/DynamicHooks/convention.h b/extensions/dhooks/DynamicHooks/convention.h new file mode 100644 index 00000000..ae4d6138 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/convention.h @@ -0,0 +1,290 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _CONVENTION_H +#define _CONVENTION_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "registers.h" +#include +#include +#include +#include +#include + +// ============================================================================ +// >> DataType_t +// ============================================================================ +enum DataType_t +{ + DATA_TYPE_VOID, + DATA_TYPE_BOOL, + DATA_TYPE_CHAR, + DATA_TYPE_UCHAR, + DATA_TYPE_SHORT, + DATA_TYPE_USHORT, + DATA_TYPE_INT, + DATA_TYPE_UINT, + DATA_TYPE_LONG, + DATA_TYPE_ULONG, + DATA_TYPE_LONG_LONG, + DATA_TYPE_ULONG_LONG, + DATA_TYPE_FLOAT, + DATA_TYPE_DOUBLE, + DATA_TYPE_POINTER, + DATA_TYPE_STRING, + DATA_TYPE_OBJECT +}; + +typedef struct DataTypeSized_s { + DataTypeSized_s() + { + type = DATA_TYPE_POINTER; + size = 0; + custom_register = None; + } + DataType_t type; + size_t size; + Register_t custom_register; +} DataTypeSized_t; + + +// ============================================================================ +// >> FUNCTIONS +// ============================================================================ +/* +Returns the size after applying alignment. + +@param : +The size that should be aligned. + +@param : +The alignment that should be used. +*/ +inline int Align(int size, int alignment) +{ + int unaligned = size % alignment; + if (unaligned == 0) + return size; + + return size + (alignment - unaligned); +} + +/* +Returns the size of a data type after applying alignment. + +@param : +The data type you would like to get the size of. + +@param : +The alignment that should be used. +*/ +inline int GetDataTypeSize(DataTypeSized_t type, int iAlignment=4) +{ + switch(type.type) + { + case DATA_TYPE_VOID: return 0; + case DATA_TYPE_BOOL: return Align(sizeof(bool), iAlignment); + case DATA_TYPE_CHAR: return Align(sizeof(char), iAlignment); + case DATA_TYPE_UCHAR: return Align(sizeof(unsigned char), iAlignment); + case DATA_TYPE_SHORT: return Align(sizeof(short), iAlignment); + case DATA_TYPE_USHORT: return Align(sizeof(unsigned short), iAlignment); + case DATA_TYPE_INT: return Align(sizeof(int), iAlignment); + case DATA_TYPE_UINT: return Align(sizeof(unsigned int), iAlignment); + case DATA_TYPE_LONG: return Align(sizeof(long), iAlignment); + case DATA_TYPE_ULONG: return Align(sizeof(unsigned long), iAlignment); + case DATA_TYPE_LONG_LONG: return Align(sizeof(long long), iAlignment); + case DATA_TYPE_ULONG_LONG: return Align(sizeof(unsigned long long), iAlignment); + case DATA_TYPE_FLOAT: return Align(sizeof(float), iAlignment); + case DATA_TYPE_DOUBLE: return Align(sizeof(double), iAlignment); + case DATA_TYPE_POINTER: return Align(sizeof(void *), iAlignment); + case DATA_TYPE_STRING: return Align(sizeof(char *), iAlignment); + case DATA_TYPE_OBJECT: return type.size; + default: puts("Unknown data type."); + } + return 0; +} + +// ============================================================================ +// >> CLASSES +// ============================================================================ +/* +This is the base class for every calling convention. Inherit from this class +to create your own calling convention. +*/ +class ICallingConvention +{ +public: + /* + Initializes the calling convention. + + @param : + A list of DataType_t objects, which define the arguments of the function. + + @param : + The return type of the function. + */ + ICallingConvention(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment=4) + { + m_vecArgTypes = std::move(vecArgTypes); + + for (size_t i=0; i < m_vecArgTypes.size(); i++) + { + DataTypeSized_t &type = m_vecArgTypes[i]; + if (!type.size) + type.size = GetDataTypeSize(type, iAlignment); + } + m_returnType = returnType; + if (!m_returnType.size) + m_returnType.size = GetDataTypeSize(m_returnType, iAlignment); + m_iAlignment = iAlignment; + } + + virtual ~ICallingConvention() + { + } + + /* + This should return a list of Register_t values. These registers will be + saved for later access. + */ + virtual std::vector GetRegisters() = 0; + + /* + Returns the number of bytes that should be added to the stack to clean up. + */ + virtual int GetPopSize() = 0; + + virtual int GetArgStackSize() = 0; + virtual void** GetStackArgumentPtr(CRegisters* pRegisters) = 0; + + /* + Returns the number of bytes for the buffer to store all the arguments that are passed in a register in. + */ + virtual int GetArgRegisterSize() = 0; + + /* + Returns a pointer to the argument at the given index. + + @param : + The index of the argument. + + @param : + A snapshot of all saved registers. + */ + virtual void* GetArgumentPtr(unsigned int iIndex, CRegisters* pRegisters) = 0; + + /* + */ + virtual void ArgumentPtrChanged(unsigned int iIndex, CRegisters* pRegisters, void* pArgumentPtr) = 0; + + /* + Returns a pointer to the return value. + + @param : + A snapshot of all saved registers. + */ + virtual void* GetReturnPtr(CRegisters* pRegisters) = 0; + + /* + */ + virtual void ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) = 0; + + /* + Save the return value in a seperate buffer, so we can restore it after calling the original function. + + @param : + A snapshot of all saved registers. + */ + virtual void SaveReturnValue(CRegisters* pRegisters) + { + std::unique_ptr pSavedReturnValue = std::make_unique(m_returnType.size); + memcpy(pSavedReturnValue.get(), GetReturnPtr(pRegisters), m_returnType.size); + m_pSavedReturnBuffers.push_back(std::move(pSavedReturnValue)); + } + + virtual void RestoreReturnValue(CRegisters* pRegisters) + { + uint8_t* pSavedReturnValue = m_pSavedReturnBuffers.back().get(); + memcpy(GetReturnPtr(pRegisters), pSavedReturnValue, m_returnType.size); + ReturnPtrChanged(pRegisters, pSavedReturnValue); + m_pSavedReturnBuffers.pop_back(); + } + + /* + Save the value of arguments in a seperate buffer for the post callback. + Compiler optimizations might cause the registers or stack space to be reused + and overwritten during function execution if the value isn't needed anymore + at some point. This leads to different values in the post hook. + + @param : + A snapshot of all saved registers. + */ + virtual void SaveCallArguments(CRegisters* pRegisters) + { + int size = GetArgStackSize() + GetArgRegisterSize(); + std::unique_ptr pSavedCallArguments = std::make_unique(size); + size_t offset = 0; + for (size_t i = 0; i < m_vecArgTypes.size(); i++) { + DataTypeSized_t &type = m_vecArgTypes[i]; + memcpy((void *)((unsigned long)pSavedCallArguments.get() + offset), GetArgumentPtr(i, pRegisters), type.size); + offset += type.size; + } + m_pSavedCallArguments.push_back(std::move(pSavedCallArguments)); + } + + virtual void RestoreCallArguments(CRegisters* pRegisters) + { + uint8_t *pSavedCallArguments = m_pSavedCallArguments.back().get(); + size_t offset = 0; + for (size_t i = 0; i < m_vecArgTypes.size(); i++) { + DataTypeSized_t &type = m_vecArgTypes[i]; + memcpy(GetArgumentPtr(i, pRegisters), (void *)((unsigned long)pSavedCallArguments + offset), type.size); + offset += type.size; + } + m_pSavedCallArguments.pop_back(); + } + +public: + std::vector m_vecArgTypes; + DataTypeSized_t m_returnType; + int m_iAlignment; + // Save the return in case we call the original function and want to override the return again. + std::vector> m_pSavedReturnBuffers; + // Save call arguments in case the function reuses the space and overwrites the values for the post hook. + std::vector> m_pSavedCallArguments; +}; + +#endif // _CONVENTION_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/conventions/x86GccCdecl.h b/extensions/dhooks/DynamicHooks/conventions/x86GccCdecl.h new file mode 100644 index 00000000..94c5b124 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86GccCdecl.h @@ -0,0 +1,48 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _X86_GCC_CDECL_H +#define _X86_GCC_CDECL_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86MsCdecl.h" + + +// ============================================================================ +// >> CLASSES +// ============================================================================ +typedef x86MsCdecl x86GccCdecl; + + +#endif // _X86_GCC_CDECL_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/conventions/x86GccThiscall.cpp b/extensions/dhooks/DynamicHooks/conventions/x86GccThiscall.cpp new file mode 100644 index 00000000..10f12503 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86GccThiscall.cpp @@ -0,0 +1,87 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86GccThiscall.h" + + +// ============================================================================ +// >> CLASSES +// ============================================================================ + +x86GccThiscall::x86GccThiscall(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : + x86GccCdecl(vecArgTypes, returnType, iAlignment) +{ + // Always add the |this| pointer. + DataTypeSized_t type; + type.type = DATA_TYPE_POINTER; + type.size = GetDataTypeSize(type, iAlignment); + type.custom_register = None; + m_vecArgTypes.insert(m_vecArgTypes.begin(), type); +} + +x86GccThiscall::~x86GccThiscall() +{ +} + +int x86GccThiscall::GetArgStackSize() +{ + // Remove the this pointer from the arguments size. + DataTypeSized_t type; + type.type = DATA_TYPE_POINTER; + return x86GccCdecl::GetArgStackSize() - GetDataTypeSize(type, m_iAlignment); +} + +void** x86GccThiscall::GetStackArgumentPtr(CRegisters* pRegisters) +{ + // Skip return address and this pointer. + DataTypeSized_t type; + type.type = DATA_TYPE_POINTER; + return (void **)(pRegisters->m_esp->GetValue() + 4 + GetDataTypeSize(type, m_iAlignment)); +} + +void x86GccThiscall::SaveCallArguments(CRegisters* pRegisters) +{ + // Count the this pointer. + int size = x86GccCdecl::GetArgStackSize() + GetArgRegisterSize(); + std::unique_ptr pSavedCallArguments = std::make_unique(size); + size_t offset = 0; + for (size_t i = 0; i < m_vecArgTypes.size(); i++) { + DataTypeSized_t &type = m_vecArgTypes[i]; + memcpy((void *)((unsigned long)pSavedCallArguments.get() + offset), GetArgumentPtr(i, pRegisters), type.size); + offset += type.size; + } + m_pSavedCallArguments.push_back(std::move(pSavedCallArguments)); +} diff --git a/extensions/dhooks/DynamicHooks/conventions/x86GccThiscall.h b/extensions/dhooks/DynamicHooks/conventions/x86GccThiscall.h new file mode 100644 index 00000000..c123796f --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86GccThiscall.h @@ -0,0 +1,59 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _X86_GCC_THISCALL_H +#define _X86_GCC_THISCALL_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86GccCdecl.h" + + +// ============================================================================ +// >> CLASSES +// ============================================================================ +// |this| pointer is always passed as implicit first argument on the stack. +class x86GccThiscall: public x86GccCdecl +{ +public: + x86GccThiscall(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment = 4); + virtual ~x86GccThiscall(); + + virtual int GetArgStackSize(); + virtual void** GetStackArgumentPtr(CRegisters* pRegisters); + + virtual void SaveCallArguments(CRegisters* pRegisters); +}; + +#endif // _X86_GCC_THISCALL_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/conventions/x86MsCdecl.cpp b/extensions/dhooks/DynamicHooks/conventions/x86MsCdecl.cpp new file mode 100644 index 00000000..58e1dbfd --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86MsCdecl.cpp @@ -0,0 +1,187 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86MsCdecl.h" +#include + + +// ============================================================================ +// >> x86MsCdecl +// ============================================================================ +x86MsCdecl::x86MsCdecl(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : + ICallingConvention(vecArgTypes, returnType, iAlignment) +{ + if (m_returnType.size > 4) + { + m_pReturnBuffer = malloc(m_returnType.size); + } + else + { + m_pReturnBuffer = NULL; + } +} + +x86MsCdecl::~x86MsCdecl() +{ + if (m_pReturnBuffer) + { + free(m_pReturnBuffer); + } +} + +std::vector x86MsCdecl::GetRegisters() +{ + std::vector registers; + + registers.push_back(ESP); + + if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + { + registers.push_back(ST0); + } + else + { + registers.push_back(EAX); + if (m_pReturnBuffer) + { + registers.push_back(EDX); + } + } + + // Save all the custom calling convention registers as well. + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register == None) + continue; + + // TODO: Make sure the list is unique? Set? + registers.push_back(m_vecArgTypes[i].custom_register); + } + + return registers; +} + +int x86MsCdecl::GetPopSize() +{ + return 0; +} + +int x86MsCdecl::GetArgStackSize() +{ + int iArgStackSize = 0; + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register == None) + iArgStackSize += m_vecArgTypes[i].size; + } + + return iArgStackSize; +} + +void** x86MsCdecl::GetStackArgumentPtr(CRegisters* pRegisters) +{ + return (void **)(pRegisters->m_esp->GetValue() + 4); +} + +int x86MsCdecl::GetArgRegisterSize() +{ + int iArgRegisterSize = 0; + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register != None) + iArgRegisterSize += m_vecArgTypes[i].size; + } + + return iArgRegisterSize; +} + +void* x86MsCdecl::GetArgumentPtr(unsigned int iIndex, CRegisters* pRegisters) +{ + if (iIndex >= m_vecArgTypes.size()) + return NULL; + + // Check if this argument was passed in a register. + if (m_vecArgTypes[iIndex].custom_register != None) + { + CRegister *pRegister = pRegisters->GetRegister(m_vecArgTypes[iIndex].custom_register); + if (!pRegister) + return NULL; + + return pRegister->m_pAddress; + } + + // Skip return address. + size_t iOffset = 4; + for(unsigned int i=0; i < iIndex; i++) + { + if (m_vecArgTypes[i].custom_register == None) + iOffset += m_vecArgTypes[i].size; + } + + return (void *) (pRegisters->m_esp->GetValue() + iOffset); +} + +void x86MsCdecl::ArgumentPtrChanged(unsigned int iIndex, CRegisters* pRegisters, void* pArgumentPtr) +{ +} + +void* x86MsCdecl::GetReturnPtr(CRegisters* pRegisters) +{ + if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + return pRegisters->m_st0->m_pAddress; + + if (m_pReturnBuffer) + { + // First half in eax, second half in edx + memcpy(m_pReturnBuffer, pRegisters->m_eax, 4); + memcpy((void *) ((unsigned long) m_pReturnBuffer + 4), pRegisters->m_edx, 4); + return m_pReturnBuffer; + } + + return pRegisters->m_eax->m_pAddress; +} + +void x86MsCdecl::ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) +{ + if (m_pReturnBuffer) + { + // First half in eax, second half in edx + memcpy(pRegisters->m_eax, m_pReturnBuffer, 4); + memcpy(pRegisters->m_edx, (void *) ((unsigned long) m_pReturnBuffer + 4), 4); + } +} \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/conventions/x86MsCdecl.h b/extensions/dhooks/DynamicHooks/conventions/x86MsCdecl.h new file mode 100644 index 00000000..37b8aba5 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86MsCdecl.h @@ -0,0 +1,88 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _X86_MS_CDECL_H +#define _X86_MS_CDECL_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "../convention.h" + + +// ============================================================================ +// >> CLASSES +// ============================================================================ +/* +Source: DynCall manual and Windows docs + +Registers: + - eax = return value + - edx = return value + - esp = stack pointer + - st0 = floating point return value + +Parameter passing: + - stack parameter order: right-to-left + - caller cleans up the stack + - all arguments are pushed onto the stack + - alignment: 4 bytes + +Return values: + - return values of pointer or intergral type (<= 32 bits) are returned via the eax register + - integers > 32 bits are returned via the eax and edx registers + - floating pointer types are returned via the st0 register +*/ +class x86MsCdecl: public ICallingConvention +{ +public: + x86MsCdecl(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment=4); + virtual ~x86MsCdecl(); + + virtual std::vector GetRegisters(); + virtual int GetPopSize(); + virtual int GetArgStackSize(); + virtual void** GetStackArgumentPtr(CRegisters* pRegisters); + virtual int GetArgRegisterSize(); + + virtual void* GetArgumentPtr(unsigned int iIndex, CRegisters* pRegisters); + virtual void ArgumentPtrChanged(unsigned int iIndex, CRegisters* pRegisters, void* pArgumentPtr); + + virtual void* GetReturnPtr(CRegisters* pRegisters); + virtual void ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr); + +private: + void* m_pReturnBuffer; +}; + +#endif // _X86_MS_CDECL_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/conventions/x86MsFastcall.cpp b/extensions/dhooks/DynamicHooks/conventions/x86MsFastcall.cpp new file mode 100644 index 00000000..ccb30392 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86MsFastcall.cpp @@ -0,0 +1,61 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86MsFastcall.h" + + +// ============================================================================ +// >> x86MsFastcall +// ============================================================================ +x86MsFastcall::x86MsFastcall(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : + x86MsStdcall(vecArgTypes, returnType, iAlignment) +{ + // First argument is passed in ecx. + if (m_vecArgTypes.size() > 0) { + DataTypeSized_t &type = m_vecArgTypes[0]; + // Don't force the register on the user. + // Why choose Fastcall if you set your own argument registers though.. + if (type.custom_register == None) + type.custom_register = ECX; + } + + // Second argument is passed in edx. + if (m_vecArgTypes.size() > 1) { + DataTypeSized_t &type = m_vecArgTypes[1]; + if (type.custom_register == None) + type.custom_register = EDX; + } +} diff --git a/extensions/dhooks/DynamicHooks/conventions/x86MsFastcall.h b/extensions/dhooks/DynamicHooks/conventions/x86MsFastcall.h new file mode 100644 index 00000000..4b074494 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86MsFastcall.h @@ -0,0 +1,72 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _X86_MS_FASTCALL_H +#define _X86_MS_FASTCALL_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86MsStdcall.h" + + +// ============================================================================ +// >> CLASSES +// ============================================================================ +/* +Source: DynCall manual and Windows docs + +Registers: + - eax = return value + - edx = return value + - esp = stack pointer + - st0 = floating point return value + +Parameter passing: + - first parameter in ecx, second parameter in edx, rest on the stack + - stack parameter order: right-to-left + - callee cleans up the stack + - alignment: 4 bytes + +Return values: + - return values of pointer or intergral type (<= 32 bits) are returned via the eax register + - integers > 32 bits are returned via the eax and edx registers + - floating pointer types are returned via the st0 register +*/ +class x86MsFastcall: public x86MsStdcall +{ +public: + x86MsFastcall(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment=4); +}; + +#endif // _X86_MS_FASTCALL_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/conventions/x86MsStdcall.cpp b/extensions/dhooks/DynamicHooks/conventions/x86MsStdcall.cpp new file mode 100644 index 00000000..e61cf83d --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86MsStdcall.cpp @@ -0,0 +1,194 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86MsStdcall.h" +#include + + +// ============================================================================ +// >> x86MsStdcall +// ============================================================================ +x86MsStdcall::x86MsStdcall(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : + ICallingConvention(vecArgTypes, returnType, iAlignment) +{ + if (m_returnType.size > 4) + { + m_pReturnBuffer = malloc(m_returnType.size); + } + else + { + m_pReturnBuffer = NULL; + } +} + +x86MsStdcall::~x86MsStdcall() +{ + if (m_pReturnBuffer) + { + free(m_pReturnBuffer); + } +} + +std::vector x86MsStdcall::GetRegisters() +{ + std::vector registers; + + registers.push_back(ESP); + + if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + { + registers.push_back(ST0); + } + else + { + registers.push_back(EAX); + if (m_pReturnBuffer) + { + registers.push_back(EDX); + } + } + + // Save all the custom calling convention registers as well. + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register == None) + continue; + + // TODO: Make sure the list is unique? Set? + registers.push_back(m_vecArgTypes[i].custom_register); + } + + return registers; +} + +int x86MsStdcall::GetPopSize() +{ + int iPopSize = 0; + + for(size_t i=0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register == None) + iPopSize += m_vecArgTypes[i].size; + } + + return iPopSize; +} + +int x86MsStdcall::GetArgStackSize() +{ + int iArgStackSize = 0; + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register == None) + iArgStackSize += m_vecArgTypes[i].size; + } + + return iArgStackSize; +} + +void** x86MsStdcall::GetStackArgumentPtr(CRegisters* pRegisters) +{ + return (void **)(pRegisters->m_esp->GetValue() + 4); +} + +int x86MsStdcall::GetArgRegisterSize() +{ + int iArgRegisterSize = 0; + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register != None) + iArgRegisterSize += m_vecArgTypes[i].size; + } + + return iArgRegisterSize; +} + +void* x86MsStdcall::GetArgumentPtr(unsigned int iIndex, CRegisters* pRegisters) +{ + if (iIndex >= m_vecArgTypes.size()) + return NULL; + + // Check if this argument was passed in a register. + if (m_vecArgTypes[iIndex].custom_register != None) + { + CRegister *pRegister = pRegisters->GetRegister(m_vecArgTypes[iIndex].custom_register); + if (!pRegister) + return NULL; + + return pRegister->m_pAddress; + } + + size_t iOffset = 4; + for (unsigned int i = 0; i < iIndex; i++) + { + if (m_vecArgTypes[i].custom_register == None) + iOffset += m_vecArgTypes[i].size; + } + + return (void *)(pRegisters->m_esp->GetValue() + iOffset); +} + +void x86MsStdcall::ArgumentPtrChanged(unsigned int iIndex, CRegisters* pRegisters, void* pArgumentPtr) +{ +} + +void* x86MsStdcall::GetReturnPtr(CRegisters* pRegisters) +{ + if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + return pRegisters->m_st0->m_pAddress; + + if (m_pReturnBuffer) + { + // First half in eax, second half in edx + memcpy(m_pReturnBuffer, pRegisters->m_eax, 4); + memcpy((void *) ((unsigned long) m_pReturnBuffer + 4), pRegisters->m_edx, 4); + return m_pReturnBuffer; + } + + return pRegisters->m_eax->m_pAddress; +} + +void x86MsStdcall::ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) +{ + if (m_pReturnBuffer) + { + // First half in eax, second half in edx + memcpy(pRegisters->m_eax, m_pReturnBuffer, 4); + memcpy(pRegisters->m_edx, (void *) ((unsigned long) m_pReturnBuffer + 4), 4); + } +} \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/conventions/x86MsStdcall.h b/extensions/dhooks/DynamicHooks/conventions/x86MsStdcall.h new file mode 100644 index 00000000..ed25b327 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86MsStdcall.h @@ -0,0 +1,88 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _X86_MS_STDCALL_H +#define _X86_MS_STDCALL_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "../convention.h" + + +// ============================================================================ +// >> CLASSES +// ============================================================================ +/* +Source: DynCall manual and Windows docs + +Registers: + - eax = return value + - edx = return value + - esp = stack pointer + - st0 = floating point return value + +Parameter passing: + - stack parameter order: right-to-left + - callee cleans up the stack + - all arguments are pushed onto the stack + - alignment: 4 bytes + +Return values: + - return values of pointer or intergral type (<= 32 bits) are returned via the eax register + - integers > 32 bits are returned via the eax and edx registers + - floating pointer types are returned via the st0 register +*/ +class x86MsStdcall: public ICallingConvention +{ +public: + x86MsStdcall(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment=4); + virtual ~x86MsStdcall(); + + virtual std::vector GetRegisters(); + virtual int GetPopSize(); + virtual int GetArgStackSize(); + virtual void** GetStackArgumentPtr(CRegisters* pRegisters); + virtual int GetArgRegisterSize(); + + virtual void* GetArgumentPtr(unsigned int iIndex, CRegisters* pRegisters); + virtual void ArgumentPtrChanged(unsigned int iIndex, CRegisters* pRegisters, void* pArgumentPtr); + + virtual void* GetReturnPtr(CRegisters* pRegisters); + virtual void ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr); + +private: + void* m_pReturnBuffer; +}; + +#endif // _X86_MS_STDCALL_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/conventions/x86MsThiscall.cpp b/extensions/dhooks/DynamicHooks/conventions/x86MsThiscall.cpp new file mode 100644 index 00000000..9c01b660 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86MsThiscall.cpp @@ -0,0 +1,239 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86MsThiscall.h" +#include + + +// ============================================================================ +// >> x86MsThiscall +// ============================================================================ +x86MsThiscall::x86MsThiscall(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : + ICallingConvention(vecArgTypes, returnType, iAlignment) +{ + if (m_returnType.size > 4) + { + m_pReturnBuffer = malloc(m_returnType.size); + } + else + { + m_pReturnBuffer = NULL; + } +} + +x86MsThiscall::~x86MsThiscall() +{ + if (m_pReturnBuffer) + { + free(m_pReturnBuffer); + } +} + +std::vector x86MsThiscall::GetRegisters() +{ + std::vector registers; + + registers.push_back(ESP); + // TODO: Allow custom this register. + registers.push_back(ECX); + + if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + { + registers.push_back(ST0); + } + else + { + registers.push_back(EAX); + if (m_pReturnBuffer) + { + registers.push_back(EDX); + } + } + + // Save all the custom calling convention registers as well. + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register == None) + continue; + + // TODO: Make sure the list is unique? Set? + registers.push_back(m_vecArgTypes[i].custom_register); + } + + return registers; +} + +int x86MsThiscall::GetPopSize() +{ + // This pointer. + // FIXME LINUX + //int iPopSize = GetDataTypeSize(DATA_TYPE_POINTER, m_iAlignment); + int iPopSize = 0; + + for(size_t i=0; i < m_vecArgTypes.size(); i++) + { + // Only pop arguments that are actually on the stack. + if (m_vecArgTypes[i].custom_register == None) + iPopSize += m_vecArgTypes[i].size; + } + + return iPopSize; +} + +int x86MsThiscall::GetArgStackSize() +{ + int iArgStackSize = 0; + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register == None) + iArgStackSize += m_vecArgTypes[i].size; + } + + return iArgStackSize; +} + +void** x86MsThiscall::GetStackArgumentPtr(CRegisters* pRegisters) +{ + return (void **)(pRegisters->m_esp->GetValue() + 4); +} + +int x86MsThiscall::GetArgRegisterSize() +{ + int iArgRegisterSize = 0; + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register != None) + iArgRegisterSize += m_vecArgTypes[i].size; + } + + return iArgRegisterSize; +} + +void* x86MsThiscall::GetArgumentPtr(unsigned int iIndex, CRegisters* pRegisters) +{ + if (iIndex == 0) + { + // TODO: Allow custom this register. + return pRegisters->m_ecx->m_pAddress; + } + + // The this pointer isn't explicitly defined as an argument. + iIndex--; + + if (iIndex >= m_vecArgTypes.size()) + return NULL; + + // Check if this argument was passed in a register. + if (m_vecArgTypes[iIndex].custom_register != None) + { + CRegister *pRegister = pRegisters->GetRegister(m_vecArgTypes[iIndex].custom_register); + if (!pRegister) + return NULL; + + return pRegister->m_pAddress; + } + + size_t iOffset = 4; + for(unsigned int i=0; i < iIndex; i++) + { + if (m_vecArgTypes[i].custom_register == None) + iOffset += m_vecArgTypes[i].size; + } + + return (void *) (pRegisters->m_esp->GetValue() + iOffset); +} + +void x86MsThiscall::ArgumentPtrChanged(unsigned int iIndex, CRegisters* pRegisters, void* pArgumentPtr) +{ +} + +void* x86MsThiscall::GetReturnPtr(CRegisters* pRegisters) +{ + if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + return pRegisters->m_st0->m_pAddress; + + if (m_pReturnBuffer) + { + // First half in eax, second half in edx + memcpy(m_pReturnBuffer, pRegisters->m_eax, 4); + memcpy((void *) ((unsigned long) m_pReturnBuffer + 4), pRegisters->m_edx, 4); + return m_pReturnBuffer; + } + + return pRegisters->m_eax->m_pAddress; +} + +void x86MsThiscall::ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) +{ + if (m_pReturnBuffer) + { + // First half in eax, second half in edx + memcpy(pRegisters->m_eax, m_pReturnBuffer, 4); + memcpy(pRegisters->m_edx, (void *) ((unsigned long) m_pReturnBuffer + 4), 4); + } +} + +void x86MsThiscall::SaveCallArguments(CRegisters* pRegisters) +{ + // Account for implicit this-pointer in ecx. + int size = GetArgStackSize() + GetArgRegisterSize() + sizeof(void *); + std::unique_ptr pSavedCallArguments = std::make_unique(size); + memcpy(pSavedCallArguments.get(), GetArgumentPtr(0, pRegisters), sizeof(void *)); + + size_t offset = sizeof(void *); + for (size_t i = 0; i < m_vecArgTypes.size(); i++) { + DataTypeSized_t &type = m_vecArgTypes[i]; + memcpy((void *)((unsigned long)pSavedCallArguments.get() + offset), GetArgumentPtr(i + 1, pRegisters), type.size); + offset += type.size; + } + m_pSavedCallArguments.push_back(std::move(pSavedCallArguments)); +} + +void x86MsThiscall::RestoreCallArguments(CRegisters* pRegisters) +{ + uint8_t* pSavedCallArguments = m_pSavedCallArguments.back().get(); + memcpy(GetArgumentPtr(0, pRegisters), pSavedCallArguments, sizeof(void *)); + + size_t offset = sizeof(void *); + for (size_t i = 0; i < m_vecArgTypes.size(); i++) { + DataTypeSized_t &type = m_vecArgTypes[i]; + memcpy(GetArgumentPtr(i + 1, pRegisters), (void *)((unsigned long)pSavedCallArguments + offset), type.size); + offset += type.size; + } + m_pSavedCallArguments.pop_back(); +} diff --git a/extensions/dhooks/DynamicHooks/conventions/x86MsThiscall.h b/extensions/dhooks/DynamicHooks/conventions/x86MsThiscall.h new file mode 100644 index 00000000..b28e061d --- /dev/null +++ b/extensions/dhooks/DynamicHooks/conventions/x86MsThiscall.h @@ -0,0 +1,93 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _X86_MS_THISCALL_H +#define _X86_MS_THISCALL_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "../convention.h" +#include + + +// ============================================================================ +// >> CLASSES +// ============================================================================ +/* +Source: DynCall manual and Windows docs + +Registers: + - eax = return value + - ecx = this pointer + - edx = return value + - esp = stack pointer + - st0 = floating point return value + +Parameter passing: + - stack parameter order: right-to-left + - callee cleans up the stack + - all other arguments are pushed onto the stack + - alignment: 4 bytes + +Return values: + - return values of pointer or intergral type (<= 32 bits) are returned via the eax register + - integers > 32 bits are returned via the eax and edx registers + - floating pointer types are returned via the st0 register +*/ +class x86MsThiscall: public ICallingConvention +{ +public: + x86MsThiscall(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment=4); + virtual ~x86MsThiscall(); + + virtual std::vector GetRegisters(); + virtual int GetPopSize(); + virtual int GetArgStackSize(); + virtual void** GetStackArgumentPtr(CRegisters* pRegisters); + virtual int GetArgRegisterSize(); + + virtual void* GetArgumentPtr(unsigned int iIndex, CRegisters* pRegisters); + virtual void ArgumentPtrChanged(unsigned int iIndex, CRegisters* pRegisters, void* pArgumentPtr); + + virtual void* GetReturnPtr(CRegisters* pRegisters); + virtual void ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr); + + virtual void SaveCallArguments(CRegisters* pRegisters); + virtual void RestoreCallArguments(CRegisters* pRegisters); + +private: + void* m_pReturnBuffer; +}; + +#endif // _X86_MS_THISCALL_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/hook.cpp b/extensions/dhooks/DynamicHooks/hook.cpp new file mode 100644 index 00000000..d9c7f4df --- /dev/null +++ b/extensions/dhooks/DynamicHooks/hook.cpp @@ -0,0 +1,503 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "hook.h" +#include "utilities.h" +#include +#include +#include "extension.h" + +using namespace sp; + +// ============================================================================ +// >> DEFINITIONS +// ============================================================================ +#define JMP_SIZE 6 + + +// ============================================================================ +// >> CHook +// ============================================================================ +CHook::CHook(void* pFunc, ICallingConvention* pConvention) +{ + m_pFunc = pFunc; + m_pRegisters = new CRegisters(pConvention->GetRegisters()); + m_pCallingConvention = pConvention; + + if (!m_hookHandler.init()) + return; + + if (!m_RetAddr.init()) + return; + + unsigned char* pTarget = (unsigned char *) pFunc; + + // Determine the number of bytes we need to copy + int iBytesToCopy = copy_bytes(pTarget, NULL, JMP_SIZE); + + // Create a buffer for the bytes to copy + a jump to the rest of the + // function. + unsigned char* pCopiedBytes = (unsigned char *) smutils->GetScriptingEngine()->AllocatePageMemory(iBytesToCopy + JMP_SIZE); + + // Fill the array with NOP instructions + memset(pCopiedBytes, 0x90, iBytesToCopy + JMP_SIZE); + + // Copy the required bytes to our array + copy_bytes(pTarget, pCopiedBytes, JMP_SIZE); + + // Write a jump after the copied bytes to the function/bridge + number of bytes to copy + WriteJMP(pCopiedBytes + iBytesToCopy, pTarget + iBytesToCopy); + + // Save the trampoline + m_pTrampoline = (void *) pCopiedBytes; + + // Create the bridge function + m_pBridge = CreateBridge(); + + // Write a jump to the bridge + WriteJMP((unsigned char *) pFunc, m_pBridge); +} + +CHook::~CHook() +{ + // Copy back the previously copied bytes + copy_bytes((unsigned char *) m_pTrampoline, (unsigned char *) m_pFunc, JMP_SIZE); + + // Free the trampoline buffer + smutils->GetScriptingEngine()->FreePageMemory(m_pTrampoline); + + // Free the asm bridge and new return address + smutils->GetScriptingEngine()->FreePageMemory(m_pBridge); + smutils->GetScriptingEngine()->FreePageMemory(m_pNewRetAddr); + + delete m_pRegisters; + delete m_pCallingConvention; +} + +void CHook::AddCallback(HookType_t eHookType, HookHandlerFn* pCallback) +{ + if (!pCallback) + return; + + HookTypeMap::Insert i = m_hookHandler.findForAdd(eHookType); + if (!i.found()) { + HookHandlerSet set; + set.init(); + m_hookHandler.add(i, eHookType, std::move(set)); + } + + i->value.add(pCallback); +} + +void CHook::RemoveCallback(HookType_t eHookType, HookHandlerFn* pCallback) +{ + HookTypeMap::Result r = m_hookHandler.find(eHookType); + if (!r.found()) + return; + + r->value.removeIfExists(pCallback); +} + +bool CHook::IsCallbackRegistered(HookType_t eHookType, HookHandlerFn* pCallback) +{ + HookTypeMap::Result r = m_hookHandler.find(eHookType); + if (!r.found()) + return false; + + return r->value.has(pCallback); +} + +bool CHook::AreCallbacksRegistered() +{ + HookTypeMap::Result r = m_hookHandler.find(HOOKTYPE_PRE); + if (r.found() && r->value.elements() > 0) + return true; + + r = m_hookHandler.find(HOOKTYPE_POST); + if (r.found() && r->value.elements() > 0) + return true; + + return false; +} + +ReturnAction_t CHook::HookHandler(HookType_t eHookType) +{ + if (eHookType == HOOKTYPE_POST) + { + ReturnAction_t lastPreReturnAction = m_LastPreReturnAction.back(); + m_LastPreReturnAction.pop_back(); + if (lastPreReturnAction == ReturnAction_Override) + m_pCallingConvention->RestoreReturnValue(m_pRegisters); + if (lastPreReturnAction < ReturnAction_Supercede) + m_pCallingConvention->RestoreCallArguments(m_pRegisters); + } + + ReturnAction_t returnAction = ReturnAction_Ignored; + HookTypeMap::Result r = m_hookHandler.find(eHookType); + if (!r.found()) + { + // Still save the arguments for the post hook even if there + // is no pre-handler registered. + if (eHookType == HOOKTYPE_PRE) + { + m_LastPreReturnAction.push_back(returnAction); + m_pCallingConvention->SaveCallArguments(m_pRegisters); + } + return returnAction; + } + + HookHandlerSet &callbacks = r->value; + for(HookHandlerSet::iterator it=callbacks.iter(); !it.empty(); it.next()) + { + ReturnAction_t result = ((HookHandlerFn) *it)(eHookType, this); + if (result > returnAction) + returnAction = result; + } + + if (eHookType == HOOKTYPE_PRE) + { + m_LastPreReturnAction.push_back(returnAction); + if (returnAction == ReturnAction_Override) + m_pCallingConvention->SaveReturnValue(m_pRegisters); + if (returnAction < ReturnAction_Supercede) + m_pCallingConvention->SaveCallArguments(m_pRegisters); + } + + return returnAction; +} + +void* __cdecl CHook::GetReturnAddress(void* pESP) +{ + ReturnAddressMap::Result r = m_RetAddr.find(pESP); + assert(r.found()); + if (!r.found()) + { + smutils->LogError(myself, "FATAL: Failed to find return address of original function. Check the arguments and return type of your detour setup."); + return NULL; + } + + void *pRetAddr = r->value.back(); + r->value.pop_back(); + + // Clear the stack address from the cache now that we ran the last post hook. + if (r->value.empty()) + m_RetAddr.remove(r); + + return pRetAddr; +} + +void __cdecl CHook::SetReturnAddress(void* pRetAddr, void* pESP) +{ + ReturnAddressMap::Insert i = m_RetAddr.findForAdd(pESP); + if (!i.found()) + m_RetAddr.add(i, pESP, std::vector()); + + i->value.push_back(pRetAddr); +} + +void* CHook::CreateBridge() +{ + sp::MacroAssembler masm; + Label label_supercede; + + // Write a redirect to the post-hook code + Write_ModifyReturnAddress(masm); + + // Call the pre-hook handler and jump to label_supercede if ReturnAction_Supercede was returned + Write_CallHandler(masm, HOOKTYPE_PRE); + masm.cmpb(r8_al, ReturnAction_Supercede); + + // Restore the previously saved registers, so any changes will be applied + Write_RestoreRegisters(masm, HOOKTYPE_PRE); + + masm.j(equal, &label_supercede); + + // Jump to the trampoline + masm.jmp(ExternalAddress(m_pTrampoline)); + + // This code will be executed if a pre-hook returns ReturnAction_Supercede + masm.bind(&label_supercede); + + // Finally, return to the caller + // This will still call post hooks, but will skip the original function. + masm.ret(m_pCallingConvention->GetPopSize()); + + void *base = smutils->GetScriptingEngine()->AllocatePageMemory(masm.length()); + masm.emitToExecutableMemory(base); + return base; +} + +void CHook::Write_ModifyReturnAddress(sp::MacroAssembler& masm) +{ + // Save scratch registers that are used by SetReturnAddress + static void* pEAX = NULL; + static void* pECX = NULL; + static void* pEDX = NULL; + + masm.movl(Operand(ExternalAddress(&pEAX)), eax); + masm.movl(Operand(ExternalAddress(&pECX)), ecx); + masm.movl(Operand(ExternalAddress(&pEDX)), edx); + + // Store the return address in eax + masm.movl(eax, Operand(esp, 0)); + + // Save the original return address by using the current esp as the key. + // This should be unique until we have returned to the original caller. + void (__cdecl CHook::*SetReturnAddress)(void*, void*) = &CHook::SetReturnAddress; + masm.push(esp); + masm.push(eax); + masm.push(intptr_t(this)); + masm.call(ExternalAddress((void *&)SetReturnAddress)); + masm.addl(esp, 12); + + // Restore scratch registers + masm.movl(eax, Operand(ExternalAddress(&pEAX))); + masm.movl(ecx, Operand(ExternalAddress(&pECX))); + masm.movl(edx, Operand(ExternalAddress(&pEDX))); + + // Override the return address. This is a redirect to our post-hook code + m_pNewRetAddr = CreatePostCallback(); + masm.movl(Operand(esp, 0), intptr_t(m_pNewRetAddr)); +} + +void* CHook::CreatePostCallback() +{ + sp::MacroAssembler masm; + + int iPopSize = m_pCallingConvention->GetPopSize(); + + // Subtract the previously added bytes (stack size + return address), so + // that we can access the arguments again + masm.subl(esp, iPopSize+4); + + // Call the post-hook handler + Write_CallHandler(masm, HOOKTYPE_POST); + + // Restore the previously saved registers, so any changes will be applied + Write_RestoreRegisters(masm, HOOKTYPE_POST); + + // Save scratch registers that are used by GetReturnAddress + static void* pEAX = NULL; + static void* pECX = NULL; + static void* pEDX = NULL; + masm.movl(Operand(ExternalAddress(&pEAX)), eax); + masm.movl(Operand(ExternalAddress(&pECX)), ecx); + masm.movl(Operand(ExternalAddress(&pEDX)), edx); + + // Get the original return address + void* (__cdecl CHook::*GetReturnAddress)(void*) = &CHook::GetReturnAddress; + masm.push(esp); + masm.push(intptr_t(this)); + masm.call(ExternalAddress((void *&)GetReturnAddress)); + masm.addl(esp, 8); + + // Save the original return address + static void* pRetAddr = NULL; + masm.movl(Operand(ExternalAddress(&pRetAddr)), eax); + + // Restore scratch registers + masm.movl(eax, Operand(ExternalAddress(&pEAX))); + masm.movl(ecx, Operand(ExternalAddress(&pECX))); + masm.movl(edx, Operand(ExternalAddress(&pEDX))); + + // Add the bytes again to the stack (stack size + return address), so we + // don't corrupt the stack. + masm.addl(esp, iPopSize+4); + + // Jump to the original return address + masm.jmp(Operand(ExternalAddress(&pRetAddr))); + + // Generate the code + void *base = smutils->GetScriptingEngine()->AllocatePageMemory(masm.length()); + masm.emitToExecutableMemory(base); + return base; +} + +void CHook::Write_CallHandler(sp::MacroAssembler& masm, HookType_t type) +{ + ReturnAction_t (__cdecl CHook::*HookHandler)(HookType_t) = &CHook::HookHandler; + + // Save the registers so that we can access them in our handlers + Write_SaveRegisters(masm, type); + + // Align the stack to 16 bytes. + masm.subl(esp, 8); + + // Call the global hook handler + masm.push(type); + masm.push(intptr_t(this)); + masm.call(ExternalAddress((void *&)HookHandler)); + masm.addl(esp, 16); +} + +void CHook::Write_SaveRegisters(sp::MacroAssembler& masm, HookType_t type) +{ + std::vector vecRegistersToSave = m_pCallingConvention->GetRegisters(); + for(size_t i = 0; i < vecRegistersToSave.size(); i++) + { + switch(vecRegistersToSave[i]) + { + // ======================================================================== + // >> 8-bit General purpose registers + // ======================================================================== + case AL: masm.movl(Operand(ExternalAddress(m_pRegisters->m_al->m_pAddress)), r8_al); break; + case CL: masm.movl(Operand(ExternalAddress(m_pRegisters->m_cl->m_pAddress)), r8_cl); break; + case DL: masm.movl(Operand(ExternalAddress(m_pRegisters->m_dl->m_pAddress)), r8_dl); break; + case BL: masm.movl(Operand(ExternalAddress(m_pRegisters->m_bl->m_pAddress)), r8_bl); break; + case AH: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ah->m_pAddress)), r8_ah); break; + case CH: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ch->m_pAddress)), r8_ch); break; + case DH: masm.movl(Operand(ExternalAddress(m_pRegisters->m_dh->m_pAddress)), r8_dh); break; + case BH: masm.movl(Operand(ExternalAddress(m_pRegisters->m_bh->m_pAddress)), r8_bh); break; + + // ======================================================================== + // >> 32-bit General purpose registers + // ======================================================================== + case EAX: masm.movl(Operand(ExternalAddress(m_pRegisters->m_eax->m_pAddress)), eax); break; + case ECX: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ecx->m_pAddress)), ecx); break; + case EDX: masm.movl(Operand(ExternalAddress(m_pRegisters->m_edx->m_pAddress)), edx); break; + case EBX: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ebx->m_pAddress)), ebx); break; + case ESP: masm.movl(Operand(ExternalAddress(m_pRegisters->m_esp->m_pAddress)), esp); break; + case EBP: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ebp->m_pAddress)), ebp); break; + case ESI: masm.movl(Operand(ExternalAddress(m_pRegisters->m_esi->m_pAddress)), esi); break; + case EDI: masm.movl(Operand(ExternalAddress(m_pRegisters->m_edi->m_pAddress)), edi); break; + + // ======================================================================== + // >> 128-bit XMM registers + // ======================================================================== + // TODO: Also provide movups? + case XMM0: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm0->m_pAddress)), xmm0); break; + case XMM1: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm1->m_pAddress)), xmm1); break; + case XMM2: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm2->m_pAddress)), xmm2); break; + case XMM3: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm3->m_pAddress)), xmm3); break; + case XMM4: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm4->m_pAddress)), xmm4); break; + case XMM5: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm5->m_pAddress)), xmm5); break; + case XMM6: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm6->m_pAddress)), xmm6); break; + case XMM7: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm7->m_pAddress)), xmm7); break; + + + // ======================================================================== + // >> 80-bit FPU registers + // ======================================================================== + case ST0: + // Don't mess with the FPU stack in a pre-hook. The float return is returned in st0, + // so only load it in a post hook to avoid writing back NaN. + if (type == HOOKTYPE_POST) + masm.fst32(Operand(ExternalAddress(m_pRegisters->m_st0->m_pAddress))); + break; + //case ST1: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st1->m_pAddress)), st1); break; + //case ST2: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st2->m_pAddress)), st2); break; + //case ST3: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st3->m_pAddress)), st3); break; + //case ST4: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st4->m_pAddress)), st4); break; + //case ST5: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st5->m_pAddress)), st5); break; + //case ST6: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st6->m_pAddress)), st6); break; + //case ST7: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st7->m_pAddress)), st7); break; + + default: puts("Unsupported register."); + } + } +} + +void CHook::Write_RestoreRegisters(sp::MacroAssembler& masm, HookType_t type) +{ + std::vector vecRegistersToSave = m_pCallingConvention->GetRegisters(); + for (size_t i = 0; i < vecRegistersToSave.size(); i++) + { + switch (vecRegistersToSave[i]) + { + // ======================================================================== + // >> 8-bit General purpose registers + // ======================================================================== + case AL: masm.movl(r8_al, Operand(ExternalAddress(m_pRegisters->m_al->m_pAddress))); break; + case CL: masm.movl(r8_cl, Operand(ExternalAddress(m_pRegisters->m_cl->m_pAddress))); break; + case DL: masm.movl(r8_dl, Operand(ExternalAddress(m_pRegisters->m_dl->m_pAddress))); break; + case BL: masm.movl(r8_bl, Operand(ExternalAddress(m_pRegisters->m_bl->m_pAddress))); break; + case AH: masm.movl(r8_ah, Operand(ExternalAddress(m_pRegisters->m_ah->m_pAddress))); break; + case CH: masm.movl(r8_ch, Operand(ExternalAddress(m_pRegisters->m_ch->m_pAddress))); break; + case DH: masm.movl(r8_dh, Operand(ExternalAddress(m_pRegisters->m_dh->m_pAddress))); break; + case BH: masm.movl(r8_bh, Operand(ExternalAddress(m_pRegisters->m_bh->m_pAddress))); break; + + // ======================================================================== + // >> 32-bit General purpose registers + // ======================================================================== + case EAX: masm.movl(eax, Operand(ExternalAddress(m_pRegisters->m_eax->m_pAddress))); break; + case ECX: masm.movl(ecx, Operand(ExternalAddress(m_pRegisters->m_ecx->m_pAddress))); break; + case EDX: masm.movl(edx, Operand(ExternalAddress(m_pRegisters->m_edx->m_pAddress))); break; + case EBX: masm.movl(ebx, Operand(ExternalAddress(m_pRegisters->m_ebx->m_pAddress))); break; + case ESP: masm.movl(esp, Operand(ExternalAddress(m_pRegisters->m_esp->m_pAddress))); break; + case EBP: masm.movl(ebp, Operand(ExternalAddress(m_pRegisters->m_ebp->m_pAddress))); break; + case ESI: masm.movl(esi, Operand(ExternalAddress(m_pRegisters->m_esi->m_pAddress))); break; + case EDI: masm.movl(edi, Operand(ExternalAddress(m_pRegisters->m_edi->m_pAddress))); break; + + // ======================================================================== + // >> 128-bit XMM registers + // ======================================================================== + // TODO: Also provide movups? + case XMM0: masm.movaps(xmm0, Operand(ExternalAddress(m_pRegisters->m_xmm0->m_pAddress))); break; + case XMM1: masm.movaps(xmm1, Operand(ExternalAddress(m_pRegisters->m_xmm1->m_pAddress))); break; + case XMM2: masm.movaps(xmm2, Operand(ExternalAddress(m_pRegisters->m_xmm2->m_pAddress))); break; + case XMM3: masm.movaps(xmm3, Operand(ExternalAddress(m_pRegisters->m_xmm3->m_pAddress))); break; + case XMM4: masm.movaps(xmm4, Operand(ExternalAddress(m_pRegisters->m_xmm4->m_pAddress))); break; + case XMM5: masm.movaps(xmm5, Operand(ExternalAddress(m_pRegisters->m_xmm5->m_pAddress))); break; + case XMM6: masm.movaps(xmm6, Operand(ExternalAddress(m_pRegisters->m_xmm6->m_pAddress))); break; + case XMM7: masm.movaps(xmm7, Operand(ExternalAddress(m_pRegisters->m_xmm7->m_pAddress))); break; + + + // ======================================================================== + // >> 80-bit FPU registers + // ======================================================================== + case ST0: + if (type == HOOKTYPE_POST) { + // Replace the top of the FPU stack. + // Copy st0 to st0 and pop -> just pop the FPU stack. + masm.fstp(st0); + // Push a value to the FPU stack. + // TODO: Only write back when changed? Save full 80bits for that case. + // Avoid truncation of the data if it's unchanged. + masm.fld32(Operand(ExternalAddress(m_pRegisters->m_st0->m_pAddress))); + } + break; + //case ST1: masm.movl(st1, tword_ptr_abs(Ptr(m_pRegisters->m_st1->m_pAddress))); break; + //case ST2: masm.movl(st2, tword_ptr_abs(Ptr(m_pRegisters->m_st2->m_pAddress))); break; + //case ST3: masm.movl(st3, tword_ptr_abs(Ptr(m_pRegisters->m_st3->m_pAddress))); break; + //case ST4: masm.movl(st4, tword_ptr_abs(Ptr(m_pRegisters->m_st4->m_pAddress))); break; + //case ST5: masm.movl(st5, tword_ptr_abs(Ptr(m_pRegisters->m_st5->m_pAddress))); break; + //case ST6: masm.movl(st6, tword_ptr_abs(Ptr(m_pRegisters->m_st6->m_pAddress))); break; + //case ST7: masm.movl(st7, tword_ptr_abs(Ptr(m_pRegisters->m_st7->m_pAddress))); break; + + default: puts("Unsupported register."); + } + } +} \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/hook.h b/extensions/dhooks/DynamicHooks/hook.h new file mode 100644 index 00000000..d7742d7a --- /dev/null +++ b/extensions/dhooks/DynamicHooks/hook.h @@ -0,0 +1,217 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _HOOK_H +#define _HOOK_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "registers.h" +#include "convention.h" +#include +#include + +// ============================================================================ +// >> HookType_t +// ============================================================================ +enum HookType_t +{ + // Callback will be executed before the original function. + HOOKTYPE_PRE, + + // Callback will be executed after the original function. + HOOKTYPE_POST +}; + +enum ReturnAction_t +{ + ReturnAction_Ignored, // handler didn't take any action + ReturnAction_Handled, // plugin did something, but real function should still be called + ReturnAction_Override, // call real function, but use my return value + ReturnAction_Supercede // skip real function; use my return value +}; + + +// ============================================================================ +// >> TYPEDEFS +// ============================================================================ +class CHook; +typedef ReturnAction_t (*HookHandlerFn)(HookType_t, CHook*); + +#ifdef __linux__ +#define __cdecl +#endif + +struct IntegerPolicy +{ + static inline uint32_t hash(size_t i) { + return ke::HashInteger(i); + } + static inline bool matches(size_t i1, size_t i2) { + return i1 == i2; + } +}; + +typedef ke::HashSet> HookHandlerSet; +typedef ke::HashMap HookTypeMap; +typedef ke::HashMap, ke::PointerPolicy> ReturnAddressMap; + +namespace sp +{ + class MacroAssembler; +} + + +// ============================================================================ +// >> CLASSES +// ============================================================================ + +class CHook +{ +private: + friend class CHookManager; + + /* + Creates a new function hook. + + @param : + The address of the function to hook + + @param : + The calling convention of . + */ + CHook(void* pFunc, ICallingConvention* pConvention); + ~CHook(); + +public: + /* + Adds a hook handler to the hook. + + @param type The hook type. + @param pFunc The hook handler that should be added. + */ + void AddCallback(HookType_t type, HookHandlerFn* pFunc); + + /* + Removes a hook handler to the hook. + + @param type The hook type. + @param pFunc The hook handler that should be removed. + */ + void RemoveCallback(HookType_t type, HookHandlerFn* pFunc); + + /* + Checks if a hook handler is already added. + + @param type The hook type. + @param pFunc The hook handler that should be checked. + */ + bool IsCallbackRegistered(HookType_t type, HookHandlerFn* pFunc); + + /* + Checks if there are any hook handlers added to this hook. + */ + bool AreCallbacksRegistered(); + + template + T GetArgument(int iIndex) + { + return *(T *) m_pCallingConvention->GetArgumentPtr(iIndex, m_pRegisters); + } + + template + void SetArgument(int iIndex, T value) + { + void* pPtr = m_pCallingConvention->GetArgumentPtr(iIndex, m_pRegisters); + *(T *) pPtr = value; + m_pCallingConvention->ArgumentPtrChanged(iIndex, m_pRegisters, pPtr); + } + + template + T GetReturnValue() + { + return *(T *) m_pCallingConvention->GetReturnPtr(m_pRegisters); + } + + template + void SetReturnValue(T value) + { + void* pPtr = m_pCallingConvention->GetReturnPtr(m_pRegisters); + *(T *) pPtr = value; + m_pCallingConvention->ReturnPtrChanged(m_pRegisters, pPtr); + } + +private: + void* CreateBridge(); + + void Write_ModifyReturnAddress(sp::MacroAssembler& masm); + void Write_CallHandler(sp::MacroAssembler& masm, HookType_t type); + void Write_SaveRegisters(sp::MacroAssembler& masm, HookType_t type); + void Write_RestoreRegisters(sp::MacroAssembler& masm, HookType_t type); + + void* CreatePostCallback(); + + ReturnAction_t __cdecl HookHandler(HookType_t type); + + void* __cdecl GetReturnAddress(void* pESP); + void __cdecl SetReturnAddress(void* pRetAddr, void* pESP); + +public: + + HookTypeMap m_hookHandler; + + // Address of the original function + void* m_pFunc; + + ICallingConvention* m_pCallingConvention; + + // Address of the bridge + void* m_pBridge; + + // Address of the trampoline + void* m_pTrampoline; + + // Register storage + CRegisters* m_pRegisters; + + // New return address + void* m_pNewRetAddr; + + ReturnAddressMap m_RetAddr; + + // Save the last return action of the pre HookHandler for use in the post handler. + std::vector m_LastPreReturnAction; +}; + +#endif // _HOOK_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/manager.cpp b/extensions/dhooks/DynamicHooks/manager.cpp new file mode 100644 index 00000000..d282a885 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/manager.cpp @@ -0,0 +1,107 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "manager.h" + + +// ============================================================================ +// >> CHookManager +// ============================================================================ +CHook* CHookManager::HookFunction(void* pFunc, ICallingConvention* pConvention) +{ + if (!pFunc) + return NULL; + + CHook* pHook = FindHook(pFunc); + if (pHook) + { + delete pConvention; + return pHook; + } + + pHook = new CHook(pFunc, pConvention); + m_Hooks.push_back(pHook); + return pHook; +} + +void CHookManager::UnhookFunction(void* pFunc) +{ + if (!pFunc) + return; + + for (size_t i = 0; i < m_Hooks.size(); i++) + { + CHook* pHook = m_Hooks[i]; + if (pHook->m_pFunc == pFunc) + { + m_Hooks.erase(m_Hooks.begin() + i); + delete pHook; + return; + } + } +} + +CHook* CHookManager::FindHook(void* pFunc) +{ + if (!pFunc) + return NULL; + + for(size_t i = 0; i < m_Hooks.size(); i++) + { + CHook* pHook = m_Hooks[i]; + if (pHook->m_pFunc == pFunc) + return pHook; + } + return NULL; +} + +void CHookManager::UnhookAllFunctions() +{ + for(size_t i = 0; i < m_Hooks.size(); i++) + delete m_Hooks[i]; + + m_Hooks.clear(); +} + + +// ============================================================================ +// >> GetHookManager +// ============================================================================ +CHookManager* GetHookManager() +{ + static CHookManager* s_pManager = new CHookManager; + return s_pManager; +} \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/manager.h b/extensions/dhooks/DynamicHooks/manager.h new file mode 100644 index 00000000..4c1d8ff5 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/manager.h @@ -0,0 +1,86 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _MANAGER_H +#define _MANAGER_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "hook.h" +#include "convention.h" +#include + + +// ============================================================================ +// >> CHookManager +// ============================================================================ +class CHookManager +{ +public: + /* + Hooks the given function and returns a new CHook instance. If the + function was already hooked, the existing CHook instance will be + returned. + */ + CHook* HookFunction(void* pFunc, ICallingConvention* pConvention); + + /* + Removes all callbacks and restores the original function. + */ + void UnhookFunction(void* pFunc); + + /* + Returns either NULL or the found CHook instance. + */ + CHook* FindHook(void* pFunc); + + /* + Removes all callbacks and restores all functions. + */ + void UnhookAllFunctions(); + +public: + std::vector m_Hooks; +}; + + +// ============================================================================ +// >> GetHookManager +// ============================================================================ +/* +Returns a pointer to a static CHookManager object. +*/ +CHookManager* GetHookManager(); + +#endif // _MANAGER_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/registers.cpp b/extensions/dhooks/DynamicHooks/registers.cpp new file mode 100644 index 00000000..f2200630 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/registers.cpp @@ -0,0 +1,509 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#include "registers.h" + +CRegisters::CRegisters(std::vector registers) +{ + // ======================================================================== + // >> 8-bit General purpose registers + // ======================================================================== + m_al = CreateRegister(registers, AL, 1); + m_cl = CreateRegister(registers, CL, 1); + m_dl = CreateRegister(registers, DL, 1); + m_bl = CreateRegister(registers, BL, 1); + + // 64-bit mode only + /* + m_spl = CreateRegister(registers, SPL, 1); + m_bpl = CreateRegister(registers, BPL, 1); + m_sil = CreateRegister(registers, SIL, 1); + m_dil = CreateRegister(registers, DIL, 1); + m_r8b = CreateRegister(registers, R8B, 1); + m_r9b = CreateRegister(registers, R9B, 1); + m_r10b = CreateRegister(registers, R10B, 1); + m_r11b = CreateRegister(registers, R11B, 1); + m_r12b = CreateRegister(registers, R12B, 1); + m_r13b = CreateRegister(registers, R13B, 1); + m_r14b = CreateRegister(registers, R14B, 1); + m_r15b = CreateRegister(registers, R15B, 1); + */ + + m_ah = CreateRegister(registers, AH, 1); + m_ch = CreateRegister(registers, CH, 1); + m_dh = CreateRegister(registers, DH, 1); + m_bh = CreateRegister(registers, BH, 1); + + // ======================================================================== + // >> 16-bit General purpose registers + // ======================================================================== + m_ax = CreateRegister(registers, AX, 2); + m_cx = CreateRegister(registers, CX, 2); + m_dx = CreateRegister(registers, DX, 2); + m_bx = CreateRegister(registers, BX, 2); + m_sp = CreateRegister(registers, SP, 2); + m_bp = CreateRegister(registers, BP, 2); + m_si = CreateRegister(registers, SI, 2); + m_di = CreateRegister(registers, DI, 2); + + // 64-bit mode only + /* + m_r8w = CreateRegister(registers, R8W, 2); + m_r9w = CreateRegister(registers, R9W, 2); + m_r10w = CreateRegister(registers, R10W, 2); + m_r11w = CreateRegister(registers, R11W, 2); + m_r12w = CreateRegister(registers, R12W, 2); + m_r13w = CreateRegister(registers, R13W, 2); + m_r14w = CreateRegister(registers, R14W, 2); + m_r15w = CreateRegister(registers, R14W, 2); + */ + + // ======================================================================== + // >> 32-bit General purpose registers + // ======================================================================== + m_eax = CreateRegister(registers, EAX, 4); + m_ecx = CreateRegister(registers, ECX, 4); + m_edx = CreateRegister(registers, EDX, 4); + m_ebx = CreateRegister(registers, EBX, 4); + m_esp = CreateRegister(registers, ESP, 4); + m_ebp = CreateRegister(registers, EBP, 4); + m_esi = CreateRegister(registers, ESI, 4); + m_edi = CreateRegister(registers, EDI, 4); + + // 64-bit mode only + /* + m_r8d = CreateRegister(registers, R8D, 4); + m_r9d = CreateRegister(registers, R9D, 4); + m_r10d = CreateRegister(registers, R10D, 4); + m_r11d = CreateRegister(registers, R11D, 4); + m_r12d = CreateRegister(registers, R12D, 4); + m_r13d = CreateRegister(registers, R13D, 4); + m_r14d = CreateRegister(registers, R14D, 4); + m_r15d = CreateRegister(registers, R15D, 4); + */ + + // ======================================================================== + // >> 64-bit General purpose registers + // ======================================================================== + // 64-bit mode only + /* + m_rax = CreateRegister(registers, RAX, 8); + m_rcx = CreateRegister(registers, RCX, 8); + m_rdx = CreateRegister(registers, RDX, 8); + m_rbx = CreateRegister(registers, RBX, 8); + m_rsp = CreateRegister(registers, RSP, 8); + m_rbp = CreateRegister(registers, RBP, 8); + m_rsi = CreateRegister(registers, RSI, 8); + m_rdi = CreateRegister(registers, RDI, 8); + */ + + // 64-bit mode only + /* + m_r8 = CreateRegister(registers, R8, 8); + m_r9 = CreateRegister(registers, R9, 8); + m_r10 = CreateRegister(registers, R10, 8); + m_r11 = CreateRegister(registers, R11, 8); + m_r12 = CreateRegister(registers, R12, 8); + m_r13 = CreateRegister(registers, R13, 8); + m_r14 = CreateRegister(registers, R14, 8); + m_r15 = CreateRegister(registers, R15, 8); + */ + + // ======================================================================== + // >> 64-bit MM (MMX) registers + // ======================================================================== + m_mm0 = CreateRegister(registers, MM0, 8); + m_mm1 = CreateRegister(registers, MM1, 8); + m_mm2 = CreateRegister(registers, MM2, 8); + m_mm3 = CreateRegister(registers, MM3, 8); + m_mm4 = CreateRegister(registers, MM4, 8); + m_mm5 = CreateRegister(registers, MM5, 8); + m_mm6 = CreateRegister(registers, MM6, 8); + m_mm7 = CreateRegister(registers, MM7, 8); + + // ======================================================================== + // >> 128-bit XMM registers + // ======================================================================== + // Copying data from xmm0-7 requires the memory address to be 16-byte aligned. + m_xmm0 = CreateRegister(registers, XMM0, 16, 16); + m_xmm1 = CreateRegister(registers, XMM1, 16, 16); + m_xmm2 = CreateRegister(registers, XMM2, 16, 16); + m_xmm3 = CreateRegister(registers, XMM3, 16, 16); + m_xmm4 = CreateRegister(registers, XMM4, 16, 16); + m_xmm5 = CreateRegister(registers, XMM5, 16, 16); + m_xmm6 = CreateRegister(registers, XMM6, 16, 16); + m_xmm7 = CreateRegister(registers, XMM7, 16, 16); + + // 64-bit mode only + /* + m_xmm8 = CreateRegister(registers, XMM8, 16); + m_xmm9 = CreateRegister(registers, XMM9, 16); + m_xmm10 = CreateRegister(registers, XMM10, 16); + m_xmm11 = CreateRegister(registers, XMM11, 16); + m_xmm12 = CreateRegister(registers, XMM12, 16); + m_xmm13 = CreateRegister(registers, XMM13, 16); + m_xmm14 = CreateRegister(registers, XMM14, 16); + m_xmm15 = CreateRegister(registers, XMM15, 16); + */ + + // ======================================================================== + // >> 16-bit Segment registers + // ======================================================================== + m_cs = CreateRegister(registers, CS, 2); + m_ss = CreateRegister(registers, SS, 2); + m_ds = CreateRegister(registers, DS, 2); + m_es = CreateRegister(registers, ES, 2); + m_fs = CreateRegister(registers, FS, 2); + m_gs = CreateRegister(registers, GS, 2); + + // ======================================================================== + // >> 80-bit FPU registers + // ======================================================================== + m_st0 = CreateRegister(registers, ST0, 10); + m_st1 = CreateRegister(registers, ST1, 10); + m_st2 = CreateRegister(registers, ST2, 10); + m_st3 = CreateRegister(registers, ST3, 10); + m_st4 = CreateRegister(registers, ST4, 10); + m_st5 = CreateRegister(registers, ST5, 10); + m_st6 = CreateRegister(registers, ST6, 10); + m_st7 = CreateRegister(registers, ST7, 10); +} + +CRegisters::~CRegisters() +{ + // ======================================================================== + // >> 8-bit General purpose registers + // ======================================================================== + DeleteRegister(m_al); + DeleteRegister(m_cl); + DeleteRegister(m_dl); + DeleteRegister(m_bl); + + // 64-bit mode only + /* + DeleteRegister(m_spl); + DeleteRegister(m_bpl); + DeleteRegister(m_sil); + DeleteRegister(m_dil); + DeleteRegister(m_r8b); + DeleteRegister(m_r9b); + DeleteRegister(m_r10b); + DeleteRegister(m_r11b); + DeleteRegister(m_r12b); + DeleteRegister(m_r13b); + DeleteRegister(m_r14b); + DeleteRegister(m_r15b); + */ + + DeleteRegister(m_ah); + DeleteRegister(m_ch); + DeleteRegister(m_dh); + DeleteRegister(m_bh); + + // ======================================================================== + // >> 16-bit General purpose registers + // ======================================================================== + DeleteRegister(m_ax); + DeleteRegister(m_cx); + DeleteRegister(m_dx); + DeleteRegister(m_bx); + DeleteRegister(m_sp); + DeleteRegister(m_bp); + DeleteRegister(m_si); + DeleteRegister(m_di); + + // 64-bit mode only + /* + DeleteRegister(m_r8w); + DeleteRegister(m_r9w); + DeleteRegister(m_r10w); + DeleteRegister(m_r11w); + DeleteRegister(m_r12w); + DeleteRegister(m_r13w); + DeleteRegister(m_r14w); + DeleteRegister(m_r15w); + */ + + // ======================================================================== + // >> 32-bit General purpose registers + // ======================================================================== + DeleteRegister(m_eax); + DeleteRegister(m_ecx); + DeleteRegister(m_edx); + DeleteRegister(m_ebx); + DeleteRegister(m_esp); + DeleteRegister(m_ebp); + DeleteRegister(m_esi); + DeleteRegister(m_edi); + + // 64-bit mode only + /* + DeleteRegister(m_r8d); + DeleteRegister(m_r9d); + DeleteRegister(m_r10d); + DeleteRegister(m_r11d); + DeleteRegister(m_r12d); + DeleteRegister(m_r13d); + DeleteRegister(m_r14d); + DeleteRegister(m_r15d); + */ + + // ======================================================================== + // >> 64-bit General purpose registers + // ======================================================================== + // 64-bit mode only + /* + DeleteRegister(m_rax); + DeleteRegister(m_rcx); + DeleteRegister(m_rdx); + DeleteRegister(m_rbx); + DeleteRegister(m_rsp); + DeleteRegister(m_rbp); + DeleteRegister(m_rsi); + DeleteRegister(m_rdi); + */ + + // 64-bit mode only + /* + DeleteRegister(m_r8); + DeleteRegister(m_r9); + DeleteRegister(m_r10); + DeleteRegister(m_r11); + DeleteRegister(m_r12); + DeleteRegister(m_r13); + DeleteRegister(m_r14); + DeleteRegister(m_r15); + */ + + // ======================================================================== + // >> 64-bit MM (MMX) registers + // ======================================================================== + DeleteRegister(m_mm0); + DeleteRegister(m_mm1); + DeleteRegister(m_mm2); + DeleteRegister(m_mm3); + DeleteRegister(m_mm4); + DeleteRegister(m_mm5); + DeleteRegister(m_mm6); + DeleteRegister(m_mm7); + + // ======================================================================== + // >> 128-bit XMM registers + // ======================================================================== + DeleteRegister(m_xmm0); + DeleteRegister(m_xmm1); + DeleteRegister(m_xmm2); + DeleteRegister(m_xmm3); + DeleteRegister(m_xmm4); + DeleteRegister(m_xmm5); + DeleteRegister(m_xmm6); + DeleteRegister(m_xmm7); + + // 64-bit mode only + /* + DeleteRegister(m_xmm8); + DeleteRegister(m_xmm9); + DeleteRegister(m_xmm10); + DeleteRegister(m_xmm11); + DeleteRegister(m_xmm12); + DeleteRegister(m_xmm13); + DeleteRegister(m_xmm14); + DeleteRegister(m_xmm15); + */ + + // ======================================================================== + // >> 2-bit Segment registers + // ======================================================================== + DeleteRegister(m_cs); + DeleteRegister(m_ss); + DeleteRegister(m_ds); + DeleteRegister(m_es); + DeleteRegister(m_fs); + DeleteRegister(m_gs); + + // ======================================================================== + // >> 80-bit FPU registers + // ======================================================================== + DeleteRegister(m_st0); + DeleteRegister(m_st1); + DeleteRegister(m_st2); + DeleteRegister(m_st3); + DeleteRegister(m_st4); + DeleteRegister(m_st5); + DeleteRegister(m_st6); + DeleteRegister(m_st7); +} + +CRegister* CRegisters::CreateRegister(std::vector& registers, Register_t reg, uint16_t iSize, uint16_t iAlignment) +{ + for(size_t i = 0; i < registers.size(); i++) + { + if (registers[i] == reg) + { + return new CRegister(iSize, iAlignment); + } + } + return NULL; +} + +void CRegisters::DeleteRegister(CRegister* pRegister) +{ + if (pRegister) + { + delete pRegister; + } +} + +CRegister* CRegisters::GetRegister(Register_t reg) +{ + switch (reg) + { + case AL: + return m_al; + case CL: + return m_cl; + case DL: + return m_dl; + case BL: + return m_bl; + case AH: + return m_ah; + case CH: + return m_ch; + case DH: + return m_dh; + case BH: + return m_bh; + + case AX: + return m_ax; + case CX: + return m_cx; + case DX: + return m_dx; + case BX: + return m_bx; + case SP: + return m_sp; + case BP: + return m_bp; + case SI: + return m_si; + case DI: + return m_di; + + case EAX: + return m_eax; + case ECX: + return m_ecx; + case EDX: + return m_edx; + case EBX: + return m_ebx; + case ESP: + return m_esp; + case EBP: + return m_ebp; + case ESI: + return m_esi; + case EDI: + return m_edi; + + case MM0: + return m_mm0; + case MM1: + return m_mm1; + case MM2: + return m_mm2; + case MM3: + return m_mm3; + case MM4: + return m_mm4; + case MM5: + return m_mm5; + case MM6: + return m_mm6; + case MM7: + return m_mm7; + + case XMM0: + return m_xmm0; + case XMM1: + return m_xmm1; + case XMM2: + return m_xmm2; + case XMM3: + return m_xmm3; + case XMM4: + return m_xmm4; + case XMM5: + return m_xmm5; + case XMM6: + return m_xmm6; + case XMM7: + return m_xmm7; + + case CS: + return m_cs; + case SS: + return m_ss; + case DS: + return m_ds; + case ES: + return m_es; + case FS: + return m_fs; + case GS: + return m_gs; + + case ST0: + return m_st0; + case ST1: + return m_st1; + case ST2: + return m_st2; + case ST3: + return m_st3; + case ST4: + return m_st4; + case ST5: + return m_st5; + case ST6: + return m_st6; + case ST7: + return m_st7; + + default: + return NULL; + } +} \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/registers.h b/extensions/dhooks/DynamicHooks/registers.h new file mode 100644 index 00000000..854c40f9 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/registers.h @@ -0,0 +1,462 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +* +* Adopted to provide similar features to SourceHook by AlliedModders LLC. +*/ + +#ifndef _REGISTERS_H +#define _REGISTERS_H + + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include +#include +#include +#include + + +// ============================================================================ +// >> Register_t +// ============================================================================ +enum Register_t +{ + // No register at all. + None, + + // ======================================================================== + // >> 8-bit General purpose registers + // ======================================================================== + AL, + CL, + DL, + BL, + + // 64-bit mode only + /* + SPL, + BPL, + SIL, + DIL, + R8B, + R9B, + R10B, + R11B, + R12B, + R13B, + R14B, + R15B, + */ + + AH, + CH, + DH, + BH, + + // ======================================================================== + // >> 16-bit General purpose registers + // ======================================================================== + AX, + CX, + DX, + BX, + SP, + BP, + SI, + DI, + + // 64-bit mode only + /* + R8W, + R9W, + R10W, + R11W, + R12W, + R13W, + R14W, + R15W, + */ + + // ======================================================================== + // >> 32-bit General purpose registers + // ======================================================================== + EAX, + ECX, + EDX, + EBX, + ESP, + EBP, + ESI, + EDI, + + // 64-bit mode only + /* + R8D, + R9D, + R10D, + R11D, + R12D, + R13D, + R14D, + R15D, + */ + + // ======================================================================== + // >> 64-bit General purpose registers + // ======================================================================== + // 64-bit mode only + /* + RAX, + RCX, + RDX, + RBX, + RSP, + RBP, + RSI, + RDI, + */ + + // 64-bit mode only + /* + R8, + R9, + R10, + R11, + R12, + R13, + R14, + R15, + */ + + // ======================================================================== + // >> 64-bit MM (MMX) registers + // ======================================================================== + MM0, + MM1, + MM2, + MM3, + MM4, + MM5, + MM6, + MM7, + + // ======================================================================== + // >> 128-bit XMM registers + // ======================================================================== + XMM0, + XMM1, + XMM2, + XMM3, + XMM4, + XMM5, + XMM6, + XMM7, + + // 64-bit mode only + /* + XMM8, + XMM9, + XMM10, + XMM11, + XMM12, + XMM13, + XMM14, + XMM15, + */ + + // ======================================================================== + // >> 16-bit Segment registers + // ======================================================================== + CS, + SS, + DS, + ES, + FS, + GS, + + // ======================================================================== + // >> 80-bit FPU registers + // ======================================================================== + ST0, + ST1, + ST2, + ST3, + ST4, + ST5, + ST6, + ST7, +}; + + +// ============================================================================ +// >> CRegister +// ============================================================================ +class CRegister +{ +public: + CRegister(uint16_t iSize, uint16_t iAlignment = 0) + { + m_iSize = iSize; + m_iAlignment = iAlignment; + if (iAlignment > 0) +#ifdef KE_WINDOWS + m_pAddress = _aligned_malloc(iSize, iAlignment); +#else + m_pAddress = aligned_alloc(iAlignment, iSize); +#endif + else + m_pAddress = malloc(iSize); + } + + ~CRegister() + { + +#ifdef KE_WINDOWS + if (m_iAlignment > 0) + _aligned_free(m_pAddress); + else + free(m_pAddress); +#else + free(m_pAddress); +#endif + } + + template + T GetValue() + { + return *(T *) m_pAddress; + } + + template + T GetPointerValue(int iOffset=0) + { + return *(T *) (GetValue() + iOffset); + } + + template + void SetValue(T value) + { + *(T *) m_pAddress = value; + } + + template + void SetPointerValue(T value, int iOffset=0) + { + *(T *) (GetValue() + iOffset) = value; + } + +public: + uint16_t m_iSize; + uint16_t m_iAlignment; + void* m_pAddress; +}; + + +// ============================================================================ +// >> CRegisters +// ============================================================================ +class CRegisters +{ +public: + CRegisters(std::vector registers); + ~CRegisters(); + + CRegister* GetRegister(Register_t reg); + +private: + CRegister* CreateRegister(std::vector& registers, Register_t reg, uint16_t iSize, uint16_t iAlignment = 0); + void DeleteRegister(CRegister* pRegister); + +public: + // ======================================================================== + // >> 8-bit General purpose registers + // ======================================================================== + CRegister* m_al; + CRegister* m_cl; + CRegister* m_dl; + CRegister* m_bl; + + // 64-bit mode only + /* + CRegister* m_spl; + CRegister* m_bpl; + CRegister* m_sil; + CRegister* m_dil; + CRegister* m_r8b; + CRegister* m_r9b; + CRegister* m_r10b; + CRegister* m_r11b; + CRegister* m_r12b; + CRegister* m_r13b; + CRegister* m_r14b; + CRegister* m_r15b; + */ + + CRegister* m_ah; + CRegister* m_ch; + CRegister* m_dh; + CRegister* m_bh; + + // ======================================================================== + // >> 16-bit General purpose registers + // ======================================================================== + CRegister* m_ax; + CRegister* m_cx; + CRegister* m_dx; + CRegister* m_bx; + CRegister* m_sp; + CRegister* m_bp; + CRegister* m_si; + CRegister* m_di; + + // 64-bit mode only + /* + CRegister* m_r8w; + CRegister* m_r9w; + CRegister* m_r10w; + CRegister* m_r11w; + CRegister* m_r12w; + CRegister* m_r13w; + CRegister* m_r14w; + CRegister* m_r15w; + */ + + // ======================================================================== + // >> 32-bit General purpose registers + // ======================================================================== + CRegister* m_eax; + CRegister* m_ecx; + CRegister* m_edx; + CRegister* m_ebx; + CRegister* m_esp; + CRegister* m_ebp; + CRegister* m_esi; + CRegister* m_edi; + + // 64-bit mode only + /* + CRegister* m_r8d; + CRegister* m_r9d; + CRegister* m_r10d; + CRegister* m_r11d; + CRegister* m_r12d; + CRegister* m_r13d; + CRegister* m_r14d; + CRegister* m_r15d; + */ + + // ======================================================================== + // >> 64-bit General purpose registers + // ======================================================================== + // 64-bit mode only + /* + CRegister* m_rax; + CRegister* m_rcx; + CRegister* m_rdx; + CRegister* m_rbx; + CRegister* m_rsp; + CRegister* m_rbp; + CRegister* m_rsi; + CRegister* m_rdi; + */ + + // 64-bit mode only + /* + CRegister* m_r8; + CRegister* m_r9; + CRegister* m_r10; + CRegister* m_r11; + CRegister* m_r12; + CRegister* m_r13; + CRegister* m_r14; + CRegister* m_r15; + */ + + // ======================================================================== + // >> 64-bit MM (MMX) registers + // ======================================================================== + CRegister* m_mm0; + CRegister* m_mm1; + CRegister* m_mm2; + CRegister* m_mm3; + CRegister* m_mm4; + CRegister* m_mm5; + CRegister* m_mm6; + CRegister* m_mm7; + + // ======================================================================== + // >> 128-bit XMM registers + // ======================================================================== + CRegister* m_xmm0; + CRegister* m_xmm1; + CRegister* m_xmm2; + CRegister* m_xmm3; + CRegister* m_xmm4; + CRegister* m_xmm5; + CRegister* m_xmm6; + CRegister* m_xmm7; + + // 64-bit mode only + /* + CRegister* m_xmm8; + CRegister* m_xmm9; + CRegister* m_xmm10; + CRegister* m_xmm11; + CRegister* m_xmm12; + CRegister* m_xmm13; + CRegister* m_xmm14; + CRegister* m_xmm15; + */ + + // ======================================================================== + // >> 16-bit Segment registers + // ======================================================================== + CRegister* m_cs; + CRegister* m_ss; + CRegister* m_ds; + CRegister* m_es; + CRegister* m_fs; + CRegister* m_gs; + + // ======================================================================== + // >> 80-bit FPU registers + // ======================================================================== + CRegister* m_st0; + CRegister* m_st1; + CRegister* m_st2; + CRegister* m_st3; + CRegister* m_st4; + CRegister* m_st5; + CRegister* m_st6; + CRegister* m_st7; +}; + +#endif // _REGISTERS_H \ No newline at end of file diff --git a/extensions/dhooks/DynamicHooks/utilities.cpp b/extensions/dhooks/DynamicHooks/utilities.cpp new file mode 100644 index 00000000..c48994cb --- /dev/null +++ b/extensions/dhooks/DynamicHooks/utilities.cpp @@ -0,0 +1,73 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#ifdef _WIN32 + #include +#endif + +#ifdef __linux__ + #include + #include + #include + #define PAGE_SIZE 4096 + #define ALIGN(ar) ((long)ar & ~(PAGE_SIZE-1)) + #define PAGE_EXECUTE_READWRITE PROT_READ|PROT_WRITE|PROT_EXEC +#endif + +#include + + +// ============================================================================ +// >> ParseParams +// ============================================================================ +void SetMemPatchable(void* pAddr, size_t size) +{ +#if defined __linux__ + if (mprotect((void *) ALIGN(pAddr), sysconf(_SC_PAGESIZE), PAGE_EXECUTE_READWRITE) == -1) + perror("mprotect"); +#elif defined _WIN32 + DWORD old_prot; + VirtualProtect(pAddr, size, PAGE_EXECUTE_READWRITE, &old_prot); +#endif +} + + +// ============================================================================ +// >> WriteJMP +// ============================================================================ +void WriteJMP(unsigned char* src, void* dest) +{ + SetMemPatchable(src, 20); + inject_jmp((void *)src, dest); +} diff --git a/extensions/dhooks/DynamicHooks/utilities.h b/extensions/dhooks/DynamicHooks/utilities.h new file mode 100644 index 00000000..008646c2 --- /dev/null +++ b/extensions/dhooks/DynamicHooks/utilities.h @@ -0,0 +1,41 @@ +/** +* ============================================================================= +* DynamicHooks +* Copyright (C) 2015 Robin Gohmert. All rights reserved. +* Copyright (C) 2018-2021 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This software is provided 'as-is', without any express or implied warranty. +* In no event will the authors be held liable for any damages arising from +* the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software in a +* product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc +* -fPIC thunks correctly +* +* Idea and trampoline code taken from DynDetours (thanks your-name-here). +*/ + +#ifndef _UTILITIES_H +#define _UTILITIES_H + +// ============================================================================ +// >> FUNCTIONS +// ============================================================================ +void SetMemPatchable(void* pAddr, size_t size); +void WriteJMP(unsigned char* src, void* dest); + +#endif // _UTILITIES_H \ No newline at end of file diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp new file mode 100644 index 00000000..9f568f20 --- /dev/null +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -0,0 +1,633 @@ +#include "dynhooks_sourcepawn.h" +#include "util.h" +#include + +#ifdef KE_WINDOWS +#include "conventions/x86MsCdecl.h" +#include "conventions/x86MsThiscall.h" +#include "conventions/x86MsStdcall.h" +#include "conventions/x86MsFastcall.h" +typedef x86MsCdecl x86DetourCdecl; +typedef x86MsThiscall x86DetourThisCall; +typedef x86MsStdcall x86DetourStdCall; +typedef x86MsFastcall x86DetourFastCall; +#elif defined KE_LINUX +#include "conventions/x86GccCdecl.h" +#include "conventions/x86GccThiscall.h" +#include "conventions/x86MsStdcall.h" +#include "conventions/x86MsFastcall.h" +typedef x86GccCdecl x86DetourCdecl; +typedef x86GccThiscall x86DetourThisCall; +// Uhm, stdcall on linux? +typedef x86MsStdcall x86DetourStdCall; +// Uhumm, fastcall on linux? +typedef x86MsFastcall x86DetourFastCall; +#else +#error "Unsupported platform." +#endif + +// Keep a map of detours and their registered plugin callbacks. +DetourMap g_pPreDetours; +DetourMap g_pPostDetours; + +void UnhookFunction(HookType_t hookType, CHook *pDetour) +{ + CHookManager *pDetourManager = GetHookManager(); + pDetour->RemoveCallback(hookType, (HookHandlerFn *)(void *)&HandleDetour); + // Only disable the detour if there are no more listeners. + if (!pDetour->AreCallbacksRegistered()) + pDetourManager->UnhookFunction(pDetour->m_pFunc); +} + +bool AddDetourPluginHook(HookType_t hookType, CHook *pDetour, HookSetup *setup, IPluginFunction *pCallback) +{ + DetourMap *map; + if (hookType == HOOKTYPE_PRE) + map = &g_pPreDetours; + else + map = &g_pPostDetours; + + // See if we already have this detour in our list. + PluginCallbackList *wrappers; + DetourMap::Insert f = map->findForAdd(pDetour); + if (f.found()) + { + wrappers = f->value; + } + else + { + // Create a vector to store all the plugin callbacks in. + wrappers = new PluginCallbackList; + if (!map->add(f, pDetour, wrappers)) + { + delete wrappers; + UnhookFunction(hookType, pDetour); + return false; + } + } + + // Add the plugin callback to the detour list. + CDynamicHooksSourcePawn *pWrapper = new CDynamicHooksSourcePawn(setup, pDetour, pCallback, hookType == HOOKTYPE_POST); + wrappers->push_back(pWrapper); + + return true; +} + +bool RemoveDetourPluginHook(HookType_t hookType, CHook *pDetour, IPluginFunction *pCallback) +{ + DetourMap *map; + if (hookType == HOOKTYPE_PRE) + map = &g_pPreDetours; + else + map = &g_pPostDetours; + + DetourMap::Result res = map->find(pDetour); + if (!res.found()) + return false; + + // Remove the plugin's callback + bool bRemoved = false; + PluginCallbackList *wrappers = res->value; + for (int i = wrappers->size()-1; i >= 0 ; i--) + { + CDynamicHooksSourcePawn *pWrapper = wrappers->at(i); + if (pWrapper->plugin_callback == pCallback) + { + bRemoved = true; + delete pWrapper; + wrappers->erase(wrappers->begin() + i); + } + } + + // No more plugin hooks on this callback. Free our structures. + if (wrappers->empty()) + { + delete wrappers; + UnhookFunction(hookType, pDetour); + map->remove(res); + } + + return bRemoved; +} + +void RemoveAllCallbacksForContext(HookType_t hookType, DetourMap *map, IPluginContext *pContext) +{ + PluginCallbackList *wrappers; + CDynamicHooksSourcePawn *pWrapper; + DetourMap::iterator it = map->iter(); + // Run through all active detours we added. + for (; !it.empty(); it.next()) + { + wrappers = it->value; + // See if there are callbacks of this plugin context registered + // and remove them. + for (int i = wrappers->size() - 1; i >= 0; i--) + { + pWrapper = wrappers->at(i); + if (pWrapper->plugin_callback->GetParentRuntime()->GetDefaultContext() != pContext) + continue; + + delete pWrapper; + wrappers->erase(wrappers->begin() + i); + } + + // No plugin interested in this hook anymore. unhook. + if (wrappers->empty()) + { + delete wrappers; + UnhookFunction(hookType, it->key); + it.erase(); + } + } +} + +void RemoveAllCallbacksForContext(IPluginContext *pContext) +{ + RemoveAllCallbacksForContext(HOOKTYPE_PRE, &g_pPreDetours, pContext); + RemoveAllCallbacksForContext(HOOKTYPE_POST, &g_pPostDetours, pContext); +} + +void CleanupDetours(HookType_t hookType, DetourMap *map) +{ + PluginCallbackList *wrappers; + CDynamicHooksSourcePawn *pWrapper; + DetourMap::iterator it = map->iter(); + // Run through all active detours we added. + for (; !it.empty(); it.next()) + { + wrappers = it->value; + // Remove all callbacks + for (int i = wrappers->size() - 1; i >= 0; i--) + { + pWrapper = wrappers->at(i); + delete pWrapper; + } + + // Unhook the function + delete wrappers; + UnhookFunction(hookType, it->key); + } + map->clear(); +} + +void CleanupDetours() +{ + CleanupDetours(HOOKTYPE_PRE, &g_pPreDetours); + CleanupDetours(HOOKTYPE_POST, &g_pPostDetours); +} + +ICallingConvention *ConstructCallingConvention(HookSetup *setup) +{ + // Convert function parameter types into DynamicHooks structures. + std::vector vecArgTypes; + for (size_t i = 0; i < setup->params.size(); i++) + { + ParamInfo &info = setup->params[i]; + DataTypeSized_t type; + type.type = DynamicHooks_ConvertParamTypeFrom(info.type); + type.size = info.size; + type.custom_register = info.custom_register; + vecArgTypes.push_back(type); + } + + DataTypeSized_t returnType; + returnType.type = DynamicHooks_ConvertReturnTypeFrom(setup->returnType); + returnType.size = 0; + // TODO: Add support for a custom return register. + returnType.custom_register = None; + + ICallingConvention *pCallConv = nullptr; + switch (setup->callConv) + { + case CallConv_CDECL: + pCallConv = new x86DetourCdecl(vecArgTypes, returnType); + break; + case CallConv_THISCALL: + pCallConv = new x86DetourThisCall(vecArgTypes, returnType); + break; + case CallConv_STDCALL: + pCallConv = new x86DetourStdCall(vecArgTypes, returnType); + break; + case CallConv_FASTCALL: + pCallConv = new x86DetourFastCall(vecArgTypes, returnType); + break; + default: + smutils->LogError(myself, "Unknown calling convention %d.", setup->callConv); + break; + } + + return pCallConv; +} + +// Some arguments might be optimized to be passed in registers instead of the stack. +bool UpdateRegisterArgumentSizes(CHook* pDetour, HookSetup *setup) +{ + // The registers the arguments are passed in might not be the same size as the actual parameter type. + // Update the type info to the size of the register that's now holding that argument, + // so we can copy the whole value. + ICallingConvention* callingConvention = pDetour->m_pCallingConvention; + std::vector &argTypes = callingConvention->m_vecArgTypes; + int numArgs = argTypes.size(); + + for (int i = 0; i < numArgs; i++) + { + // Ignore regular arguments on the stack. + if (argTypes[i].custom_register == None) + continue; + + CRegister *reg = pDetour->m_pRegisters->GetRegister(argTypes[i].custom_register); + // That register can't be handled yet. + if (!reg) + return false; + + argTypes[i].size = reg->m_iSize; + setup->params[i].size = reg->m_iSize; + } + + return true; +} + +// Central handler for all detours. Heart of the detour support. +ReturnAction_t HandleDetour(HookType_t hookType, CHook* pDetour) +{ + // Can't call into SourcePawn offthread. + if (g_MainThreadId != std::this_thread::get_id()) + return ReturnAction_Ignored; + + DetourMap *map; + if (hookType == HOOKTYPE_PRE) + map = &g_pPreDetours; + else + map = &g_pPostDetours; + + // Find the callback list for this detour. + DetourMap::Result r = map->find(pDetour); + if (!r.found()) + return ReturnAction_Ignored; + + // List of all callbacks. + PluginCallbackList *wrappers = r->value; + + HookReturnStruct *returnStruct = NULL; + Handle_t rHndl = BAD_HANDLE; + + HookParamsStruct *paramStruct = NULL; + Handle_t pHndl = BAD_HANDLE; + + int argNum = pDetour->m_pCallingConvention->m_vecArgTypes.size(); + // Keep a copy of the last return value if some plugin wants to override or supercede the function. + ReturnAction_t finalRet = ReturnAction_Ignored; + std::unique_ptr finalRetBuf = std::make_unique(pDetour->m_pCallingConvention->m_returnType.size); + + // Call all the plugin functions.. + for (size_t i = 0; i < wrappers->size(); i++) + { + CDynamicHooksSourcePawn *pWrapper = wrappers->at(i); + IPluginFunction *pCallback = pWrapper->plugin_callback; + + // Create a seperate buffer for changed return values for this plugin. + // We update the finalRet above if the tempRet is higher than the previous ones in the callback list. + ReturnAction_t tempRet = ReturnAction_Ignored; + uint8_t *tempRetBuf = nullptr; + + // Find the this pointer for thiscalls. + // Don't even try to load it if the plugin doesn't care and set it to be ignored. + if (pWrapper->callConv == CallConv_THISCALL && pWrapper->thisType != ThisPointer_Ignore) + { + // The this pointer is implicitly always the first argument. + void *thisPtr = pDetour->GetArgument(0); + cell_t thisAddr = GetThisPtr(thisPtr, pWrapper->thisType); + pCallback->PushCell(thisAddr); + } + + // Create the structure for plugins to change/get the return value if the function returns something. + if (pWrapper->returnType != ReturnType_Void) + { + // Create a handle for the return value to pass to the plugin callback. + returnStruct = pWrapper->GetReturnStruct(); + HandleError err; + rHndl = handlesys->CreateHandle(g_HookReturnHandle, returnStruct, pCallback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), &err); + if (!rHndl) + { + pCallback->Cancel(); + pCallback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(pCallback, "Error creating ReturnHandle in preparation to call hook callback. (error %d)", err); + + if (returnStruct) + delete returnStruct; + + // Don't call more callbacks. They will probably fail too. + break; + } + pCallback->PushCell(rHndl); + } + + // Create the structure for plugins to access the function arguments if it has some. + if (argNum > 0) + { + paramStruct = pWrapper->GetParamStruct(); + HandleError err; + pHndl = handlesys->CreateHandle(g_HookParamsHandle, paramStruct, pCallback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), &err); + if (!pHndl) + { + pCallback->Cancel(); + pCallback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(pCallback, "Error creating ThisHandle in preparation to call hook callback. (error %d)", err); + + // Don't leak our own handles here! Free the return struct if we fail during the argument marshalling. + if (rHndl) + { + HandleSecurity sec(pCallback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity()); + handlesys->FreeHandle(rHndl, &sec); + rHndl = BAD_HANDLE; + } + + if (paramStruct) + delete paramStruct; + + // Don't call more callbacks. They will probably fail too. + break; + } + pCallback->PushCell(pHndl); + } + + // Run the plugin callback. + cell_t result = (cell_t)MRES_Ignored; + pCallback->Execute(&result); + + switch ((MRESReturn)result) + { + case MRES_Handled: + tempRet = ReturnAction_Handled; + break; + case MRES_ChangedHandled: + tempRet = ReturnAction_Handled; + // Copy the changed parameter values from the plugin's parameter structure back into the actual detour arguments. + pWrapper->UpdateParamsFromStruct(paramStruct); + break; + case MRES_ChangedOverride: + case MRES_Override: + case MRES_Supercede: + // See if this function returns something we should override. + if (pWrapper->returnType != ReturnType_Void) + { + // Make sure the plugin provided a new return value. Could be an oversight if MRES_ChangedOverride + // is called without the return value actually being changed. + if (!returnStruct->isChanged) + { + //Throw an error if no override was set + tempRet = ReturnAction_Ignored; + pCallback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(pCallback, "Tried to override return value without return value being set"); + break; + } + + if (pWrapper->returnType == ReturnType_String || pWrapper->returnType == ReturnType_Int || pWrapper->returnType == ReturnType_Bool) + { + tempRetBuf = *(uint8_t **)returnStruct->newResult; + } + else if (pWrapper->returnType == ReturnType_Float) + { + *(float *)&tempRetBuf = *(float *)returnStruct->newResult; + } + else + { + tempRetBuf = (uint8_t *)returnStruct->newResult; + } + } + + // Store if the plugin wants the original function to be called. + if (result == MRES_Supercede) + tempRet = ReturnAction_Supercede; + else + tempRet = ReturnAction_Override; + + // Copy the changed parameter values from the plugin's parameter structure back into the actual detour arguments. + if (result == MRES_ChangedOverride) + pWrapper->UpdateParamsFromStruct(paramStruct); + break; + default: + tempRet = ReturnAction_Ignored; + break; + } + + // Prioritize the actions. + if (finalRet <= tempRet) + { + // Copy the action and return value. + finalRet = tempRet; + memcpy(finalRetBuf.get(), &tempRetBuf, pDetour->m_pCallingConvention->m_returnType.size); + } + + // Free the handles again. + HandleSecurity sec(pCallback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity()); + if (returnStruct) + { + handlesys->FreeHandle(rHndl, &sec); + } + if (paramStruct) + { + handlesys->FreeHandle(pHndl, &sec); + } + } + + // If we want to use our own return value, write it back. + if (finalRet >= ReturnAction_Override) + { + void* pPtr = pDetour->m_pCallingConvention->GetReturnPtr(pDetour->m_pRegisters); + memcpy(pPtr, finalRetBuf.get(), pDetour->m_pCallingConvention->m_returnType.size); + pDetour->m_pCallingConvention->ReturnPtrChanged(pDetour->m_pRegisters, pPtr); + } + + return finalRet; +} + +CDynamicHooksSourcePawn::CDynamicHooksSourcePawn(HookSetup *setup, CHook *pDetour, IPluginFunction *pCallback, bool post) +{ + this->params = setup->params; + this->offset = -1; + this->returnFlag = setup->returnFlag; + this->returnType = setup->returnType; + this->post = post; + this->plugin_callback = pCallback; + this->entity = -1; + this->thisType = setup->thisType; + this->hookType = setup->hookType; + this->m_pDetour = pDetour; + this->callConv = setup->callConv; +} + +HookReturnStruct *CDynamicHooksSourcePawn::GetReturnStruct() +{ + // Create buffers to store the return value of the function. + HookReturnStruct *res = new HookReturnStruct(); + res->isChanged = false; + res->type = this->returnType; + res->orgResult = NULL; + res->newResult = NULL; + + // Copy the actual function's return value too for post hooks. + if (this->post) + { + switch (this->returnType) + { + case ReturnType_String: + res->orgResult = malloc(sizeof(string_t)); + res->newResult = malloc(sizeof(string_t)); + *(string_t *)res->orgResult = m_pDetour->GetReturnValue(); + break; + case ReturnType_Int: + res->orgResult = malloc(sizeof(int)); + res->newResult = malloc(sizeof(int)); + *(int *)res->orgResult = m_pDetour->GetReturnValue(); + break; + case ReturnType_Bool: + res->orgResult = malloc(sizeof(bool)); + res->newResult = malloc(sizeof(bool)); + *(bool *)res->orgResult = m_pDetour->GetReturnValue(); + break; + case ReturnType_Float: + res->orgResult = malloc(sizeof(float)); + res->newResult = malloc(sizeof(float)); + *(float *)res->orgResult = m_pDetour->GetReturnValue(); + break; + case ReturnType_Vector: + { + res->orgResult = malloc(sizeof(SDKVector)); + res->newResult = malloc(sizeof(SDKVector)); + SDKVector vec = m_pDetour->GetReturnValue(); + *(SDKVector *)res->orgResult = vec; + break; + } + default: + res->orgResult = m_pDetour->GetReturnValue(); + break; + } + } + // Pre hooks don't have access to the return value yet - duh. + // Just create the buffers for overridden values. + // TODO: Strip orgResult malloc. + else + { + switch (this->returnType) + { + case ReturnType_String: + res->orgResult = malloc(sizeof(string_t)); + res->newResult = malloc(sizeof(string_t)); + *(string_t *)res->orgResult = NULL_STRING; + break; + case ReturnType_Vector: + res->orgResult = malloc(sizeof(SDKVector)); + res->newResult = malloc(sizeof(SDKVector)); + *(SDKVector *)res->orgResult = SDKVector(); + break; + case ReturnType_Int: + res->orgResult = malloc(sizeof(int)); + res->newResult = malloc(sizeof(int)); + *(int *)res->orgResult = 0; + break; + case ReturnType_Bool: + res->orgResult = malloc(sizeof(bool)); + res->newResult = malloc(sizeof(bool)); + *(bool *)res->orgResult = false; + break; + case ReturnType_Float: + res->orgResult = malloc(sizeof(float)); + res->newResult = malloc(sizeof(float)); + *(float *)res->orgResult = 0.0; + break; + } + } + + return res; +} + +HookParamsStruct *CDynamicHooksSourcePawn::GetParamStruct() +{ + // Save argument values of detoured function. + HookParamsStruct *params = new HookParamsStruct(); + params->dg = this; + + ICallingConvention* callingConvention = m_pDetour->m_pCallingConvention; + size_t stackSize = callingConvention->GetArgStackSize(); + size_t paramsSize = stackSize + callingConvention->GetArgRegisterSize(); + std::vector &argTypes = callingConvention->m_vecArgTypes; + size_t numArgs = argTypes.size(); + + // Create space for original parameters and changes plugins might do. + params->orgParams = (void **)malloc(paramsSize); + params->newParams = (void **)malloc(paramsSize); + params->isChanged = (bool *)malloc(numArgs * sizeof(bool)); + + // Save old stack parameters. + if (stackSize > 0) + { + void *pArgPtr = m_pDetour->m_pCallingConvention->GetStackArgumentPtr(m_pDetour->m_pRegisters); + memcpy(params->orgParams, pArgPtr, stackSize); + } + + memset(params->newParams, 0, paramsSize); + memset(params->isChanged, false, numArgs * sizeof(bool)); + + size_t firstArg = 0; + // TODO: Support custom register for this ptr. + if (callConv == CallConv_THISCALL) + firstArg = 1; + + // Save the old parameters passed in a register. + size_t offset = stackSize; + for (size_t i = 0; i < numArgs; i++) + { + // We already saved the stack arguments. + if (argTypes[i].custom_register == None) + continue; + + size_t size = argTypes[i].size; + // Register argument values are saved after all stack arguments in this buffer. + void *paramAddr = (void *)((intptr_t)params->orgParams + offset); + void *regAddr = callingConvention->GetArgumentPtr(i + firstArg, m_pDetour->m_pRegisters); + memcpy(paramAddr, regAddr, size); + offset += size; + } + + return params; +} + +void CDynamicHooksSourcePawn::UpdateParamsFromStruct(HookParamsStruct *params) +{ + // Function had no params to update now. + if (!params) + return; + + ICallingConvention* callingConvention = m_pDetour->m_pCallingConvention; + size_t stackSize = callingConvention->GetArgStackSize(); + std::vector &argTypes = callingConvention->m_vecArgTypes; + size_t numArgs = argTypes.size(); + + size_t firstArg = 0; + // TODO: Support custom register for this ptr. + if (callConv == CallConv_THISCALL) + firstArg = 1; + + size_t stackOffset = 0; + // Values of arguments stored in registers are saved after the stack arguments. + size_t registerOffset = stackSize; + size_t offset; + for (size_t i = 0; i < numArgs; i++) + { + size_t size = argTypes[i].size; + // Only have to copy something if the plugin changed this parameter. + if (params->isChanged[i]) + { + // Get the offset of this argument in the linear buffer. Register argument values are placed after all stack arguments. + offset = argTypes[i].custom_register == None ? stackOffset : registerOffset; + + void *paramAddr = (void *)((intptr_t)params->newParams + offset); + void *stackAddr = callingConvention->GetArgumentPtr(i + firstArg, m_pDetour->m_pRegisters); + memcpy(stackAddr, paramAddr, size); + } + + // Keep track of the seperate stack and register arguments. + if (argTypes[i].custom_register == None) + stackOffset += size; + else + registerOffset += size; + } +} diff --git a/extensions/dhooks/dynhooks_sourcepawn.h b/extensions/dhooks/dynhooks_sourcepawn.h new file mode 100644 index 00000000..be8a179d --- /dev/null +++ b/extensions/dhooks/dynhooks_sourcepawn.h @@ -0,0 +1,42 @@ +#ifndef _INCLUDE_DYNHOOKS_SP_H_ +#define _INCLUDE_DYNHOOKS_SP_H_ + +#include "manager.h" +#include "vhook.h" +#include + +class CDynamicHooksSourcePawn; +typedef ke::HashMap> CallbackMap; +typedef std::vector PluginCallbackList; +typedef ke::HashMap> DetourMap; + +//extern ke::Vector g_pDetours; +// Keep a list of plugin callback -> Hook wrapper for easily removing plugin hooks +//extern CallbackMap g_pPluginPreDetours; +//extern CallbackMap g_pPluginPostDetours; +// Keep a list of hook -> callbacks for calling in the detour handler +extern DetourMap g_pPreDetours; +extern DetourMap g_pPostDetours; + +class CDynamicHooksSourcePawn : public DHooksInfo { +public: + CDynamicHooksSourcePawn(HookSetup *setup, CHook *pDetour, IPluginFunction *pCallback, bool post); + + HookReturnStruct *GetReturnStruct(); + HookParamsStruct *GetParamStruct(); + void UpdateParamsFromStruct(HookParamsStruct *params); + +public: + CHook *m_pDetour; + CallingConvention callConv; +}; + +ICallingConvention *ConstructCallingConvention(HookSetup *setup); +bool UpdateRegisterArgumentSizes(CHook* pDetour, HookSetup *setup); +ReturnAction_t HandleDetour(HookType_t hookType, CHook* pDetour); +bool AddDetourPluginHook(HookType_t hookType, CHook *pDetour, HookSetup *setup, IPluginFunction *pCallback); +bool RemoveDetourPluginHook(HookType_t hookType, CHook *pDetour, IPluginFunction *pCallback); +void RemoveAllCallbacksForContext(IPluginContext *pContext); +void CleanupDetours(); + +#endif \ No newline at end of file diff --git a/extensions/dhooks/extension.cpp b/extensions/dhooks/extension.cpp new file mode 100644 index 00000000..a48ee7b8 --- /dev/null +++ b/extensions/dhooks/extension.cpp @@ -0,0 +1,165 @@ +#include "extension.h" +#include "listeners.h" +#include "dynhooks_sourcepawn.h" +#include "signatures.h" + +DHooks g_DHooksIface; /**< Global singleton for extension's main interface */ +SMEXT_LINK(&g_DHooksIface); + +IBinTools *g_pBinTools; +ISDKHooks *g_pSDKHooks; +ISDKTools *g_pSDKTools; +DHooksEntityListener *g_pEntityListener = NULL; + +HandleType_t g_HookSetupHandle = 0; +HandleType_t g_HookParamsHandle = 0; +HandleType_t g_HookReturnHandle = 0; + +std::thread::id g_MainThreadId; + +bool DHooks::SDK_OnLoad(char *error, size_t maxlength, bool late) +{ + HandleError err; + g_HookSetupHandle = handlesys->CreateType("HookSetup", this, 0, NULL, NULL, myself->GetIdentity(), &err); + if(g_HookSetupHandle == 0) + { + snprintf(error, maxlength, "Could not create hook setup handle type (err: %d)", err); + return false; + } + g_HookParamsHandle = handlesys->CreateType("HookParams", this, 0, NULL, NULL, myself->GetIdentity(), &err); + if(g_HookParamsHandle == 0) + { + snprintf(error, maxlength, "Could not create hook params handle type (err: %d)", err); + return false; + } + g_HookReturnHandle = handlesys->CreateType("HookReturn", this, 0, NULL, NULL, myself->GetIdentity(), &err); + if(g_HookReturnHandle == 0) + { + snprintf(error, maxlength, "Could not create hook return handle type (err: %d)", err); + return false; + } + + if (!g_pPreDetours.init()) + { + snprintf(error, maxlength, "Could not initialize pre hook detours hashmap."); + return false; + } + + if (!g_pPostDetours.init()) + { + snprintf(error, maxlength, "Could not initialize post hook detours hashmap."); + return false; + } + + sharesys->AddDependency(myself, "bintools.ext", true, true); + sharesys->AddDependency(myself, "sdktools.ext", true, true); + sharesys->AddDependency(myself, "sdkhooks.ext", true, true); + + sharesys->RegisterLibrary(myself, "dhooks"); + plsys->AddPluginsListener(this); + sharesys->AddNatives(myself, g_Natives); + + g_pEntityListener = new DHooksEntityListener(); + g_pSignatures = new SignatureGameConfig(); + g_MainThreadId = std::this_thread::get_id(); + + return true; +} + +void DHooks::OnHandleDestroy(HandleType_t type, void *object) +{ + if(type == g_HookSetupHandle) + { + delete (HookSetup *)object; + } + else if(type == g_HookParamsHandle) + { + delete (HookParamsStruct *)object; + } + else if(type == g_HookReturnHandle) + { + delete (HookReturnStruct *)object; + } +} + +void DHooks::SDK_OnAllLoaded() +{ + SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); + SM_GET_LATE_IFACE(BINTOOLS, g_pBinTools); + SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks); + + g_pSDKHooks->AddEntityListener(g_pEntityListener); + gameconfs->AddUserConfigHook("Functions", g_pSignatures); +} + +void DHooks::SDK_OnUnload() +{ + CleanupHooks(); + CleanupDetours(); + if(g_pEntityListener) + { + g_pEntityListener->CleanupListeners(); + g_pEntityListener->CleanupRemoveList(); + g_pSDKHooks->RemoveEntityListener(g_pEntityListener); + delete g_pEntityListener; + } + plsys->RemovePluginsListener(this); + + handlesys->RemoveType(g_HookSetupHandle, myself->GetIdentity()); + handlesys->RemoveType(g_HookParamsHandle, myself->GetIdentity()); + handlesys->RemoveType(g_HookReturnHandle, myself->GetIdentity()); + + gameconfs->RemoveUserConfigHook("Functions", g_pSignatures); +} + +bool DHooks::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late) +{ + if(!SetupHookManager(ismm)) + { + snprintf(error, maxlength, "Failed to get IHookManagerAutoGen iface"); + return false; + } + return true; +} + +void DHooks::OnPluginUnloaded(IPlugin *plugin) +{ + CleanupHooks(plugin->GetBaseContext()); + RemoveAllCallbacksForContext(plugin->GetBaseContext()); + if(g_pEntityListener) + { + g_pEntityListener->CleanupListeners(plugin->GetBaseContext()); + } +} +// The next 3 functions handle cleanup if our interfaces are going to be unloaded +bool DHooks::QueryRunning(char *error, size_t maxlength) +{ + SM_CHECK_IFACE(SDKTOOLS, g_pSDKTools); + SM_CHECK_IFACE(BINTOOLS, g_pBinTools); + SM_CHECK_IFACE(SDKHOOKS, g_pSDKHooks); + return true; +} +void DHooks::NotifyInterfaceDrop(SMInterface *pInterface) +{ + if(strcmp(pInterface->GetInterfaceName(), SMINTERFACE_SDKHOOKS_NAME) == 0) + { + if(g_pEntityListener) + { + // If this fails, remove this line and just delete the ent listener instead + g_pSDKHooks->RemoveEntityListener(g_pEntityListener); + + g_pEntityListener->CleanupListeners(); + delete g_pEntityListener; + g_pEntityListener = NULL; + } + g_pSDKHooks = NULL; + } + else if(strcmp(pInterface->GetInterfaceName(), SMINTERFACE_BINTOOLS_NAME) == 0) + { + g_pBinTools = NULL; + } + else if(strcmp(pInterface->GetInterfaceName(), SMINTERFACE_SDKTOOLS_NAME) == 0) + { + g_pSDKTools = NULL; + } +} diff --git a/extensions/dhooks/extension.h b/extensions/dhooks/extension.h new file mode 100644 index 00000000..003a05b5 --- /dev/null +++ b/extensions/dhooks/extension.h @@ -0,0 +1,134 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ + +/** + * @file extension.h + * @brief Sample extension code header. + */ + +#include "smsdk_ext.h" + +#include +#include +#include +#include "sdk-hacks.h" +#include + +/** + * @brief Sample implementation of the SDK Extension. + * Note: Uncomment one of the pre-defined virtual functions in order to use it. + */ + +class DHooks : public SDKExtension, public ISMEntityListener, public IPluginsListener, public IHandleTypeDispatch +{ +public: + virtual void OnHandleDestroy(HandleType_t type, void *object); +public: //IPluginsListener + virtual void OnPluginUnloaded(IPlugin *plugin); +public: + /** + * @brief This is called after the initial loading sequence has been processed. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @param late Whether or not the module was loaded after map load. + * @return True to succeed loading, false to fail. + */ + virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late); + + /** + * @brief This is called right before the extension is unloaded. + */ + virtual void SDK_OnUnload(); + + /** + * @brief This is called once all known extensions have been loaded. + * Note: It is is a good idea to add natives here, if any are provided. + */ + virtual void SDK_OnAllLoaded(); + + /** + * @brief Called when the pause state is changed. + */ + //virtual void SDK_OnPauseChange(bool paused); + + /** + * @brief this is called when Core wants to know if your extension is working. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @return True if working, false otherwise. + */ + virtual bool QueryRunning(char *error, size_t maxlength); + //virtual bool QueryInterfaceDrop(SMInterface *pInterface); + virtual void NotifyInterfaceDrop(SMInterface *pInterface); + virtual void OnCoreMapEnd(); +public: +#if defined SMEXT_CONF_METAMOD + /** + * @brief Called when Metamod is attached, before the extension version is called. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @param late Whether or not Metamod considers this a late load. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late); + + /** + * @brief Called when Metamod is detaching, after the extension version is called. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodUnload(char *error, size_t maxlength); + + /** + * @brief Called when Metamod's pause state is changing. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param paused Pause state being set. + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength); +#endif +}; +extern SourceHook::IHookManagerAutoGen *g_pHookManager; +extern sp_nativeinfo_t g_Natives[]; +extern std::thread::id g_MainThreadId; +#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/extensions/dhooks/listeners.cpp b/extensions/dhooks/listeners.cpp new file mode 100644 index 00000000..02d253cb --- /dev/null +++ b/extensions/dhooks/listeners.cpp @@ -0,0 +1,138 @@ +#include "listeners.h" +#include "vhook.h" + +using namespace SourceHook; + +std::vector g_EntityListeners; +std::vectorg_pRemoveList; + +void FrameCleanupHooks(void *data) +{ + for (int i = g_pRemoveList.size() - 1; i >= 0; i--) + { + DHooksManager *manager = g_pRemoveList.at(i); + delete manager; + g_pRemoveList.erase(g_pRemoveList.begin() + i); + } +} + +void DHooks::OnCoreMapEnd() +{ + for(int i = g_pHooks.size() -1; i >= 0; i--) + { + DHooksManager *manager = g_pHooks.at(i); + if(manager->callback->hookType == HookType_GameRules) + { + delete manager; + g_pHooks.erase(g_pHooks.begin() + i); + } + } +} + +void DHooksEntityListener::CleanupListeners(IPluginContext *pContext) +{ + for(int i = g_EntityListeners.size() -1; i >= 0; i--) + { + if(pContext == NULL || pContext == g_EntityListeners.at(i).callback->GetParentRuntime()->GetDefaultContext()) + { + g_EntityListeners.erase(g_EntityListeners.begin() + i); + } + } + for (int i = g_pRemoveList.size() -1; i >= 0; i--) + { + DHooksManager *manager = g_pRemoveList.at(i); + IPluginFunction *cb = manager->callback->plugin_callback; + if (pContext == NULL || (cb && pContext == cb->GetParentRuntime()->GetDefaultContext())) + { + manager->callback->plugin_callback = nullptr; + manager->remove_callback = nullptr; + } + } +} + +void DHooksEntityListener::CleanupRemoveList() +{ + for (int i = g_pRemoveList.size() - 1; i >= 0; i--) + { + DHooksManager *manager = g_pRemoveList.at(i); + delete manager; + } + g_pRemoveList.clear(); +} + +void DHooksEntityListener::OnEntityCreated(CBaseEntity *pEntity, const char *classname) +{ + int entity = gamehelpers->EntityToBCompatRef(pEntity); + + for(int i = g_EntityListeners.size() -1; i >= 0; i--) + { + EntityListener listerner = g_EntityListeners.at(i); + if(listerner.type == ListenType_Created) + { + IPluginFunction *callback = listerner.callback; + callback->PushCell(entity); + callback->PushString(classname); + callback->Execute(NULL); + } + } +} + +void DHooksEntityListener::OnEntityDestroyed(CBaseEntity *pEntity) +{ + int entity = gamehelpers->EntityToBCompatRef(pEntity); + + for(int i = g_EntityListeners.size() -1; i >= 0; i--) + { + EntityListener listerner = g_EntityListeners.at(i); + if(listerner.type == ListenType_Deleted) + { + IPluginFunction *callback = listerner.callback; + callback->PushCell(gamehelpers->EntityToBCompatRef(pEntity)); + callback->Execute(NULL); + } + } + + for(int i = g_pHooks.size() -1; i >= 0; i--) + { + DHooksManager *manager = g_pHooks.at(i); + if(manager->callback->hookType == HookType_Entity && manager->callback->entity == entity) + { + if(g_pRemoveList.size() == 0) + { + smutils->AddFrameAction(&FrameCleanupHooks, NULL); + } + + g_pRemoveList.push_back(manager); + g_pHooks.erase(g_pHooks.begin() + i); + } + } +} +bool DHooksEntityListener::AddPluginEntityListener(ListenType type, IPluginFunction *callback) +{ + for(int i = g_EntityListeners.size() -1; i >= 0; i--) + { + EntityListener listerner = g_EntityListeners.at(i); + if(listerner.callback == callback && listerner.type == type) + { + return true; + } + } + EntityListener listener; + listener.callback = callback; + listener.type = type; + g_EntityListeners.push_back(listener); + return true; +} +bool DHooksEntityListener::RemovePluginEntityListener(ListenType type, IPluginFunction *callback) +{ + for(int i = g_EntityListeners.size() -1; i >= 0; i--) + { + EntityListener listerner = g_EntityListeners.at(i); + if(listerner.callback == callback && listerner.type == type) + { + g_EntityListeners.erase(g_EntityListeners.begin() + i); + return true; + } + } + return false; +} diff --git a/extensions/dhooks/listeners.h b/extensions/dhooks/listeners.h new file mode 100644 index 00000000..b9484806 --- /dev/null +++ b/extensions/dhooks/listeners.h @@ -0,0 +1,32 @@ +#ifndef _INCLUDE_LISTENERS_H_ +#define _INCLUDE_LISTENERS_H_ + +#include "extension.h" +#include "vhook.h" + +enum ListenType +{ + ListenType_Created, + ListenType_Deleted +}; + +class DHooksEntityListener : public ISMEntityListener +{ +public: + virtual void OnEntityCreated(CBaseEntity *pEntity, const char *classname); + virtual void OnEntityDestroyed(CBaseEntity *pEntity); + void CleanupListeners(IPluginContext *func = NULL); + void CleanupRemoveList(); + bool AddPluginEntityListener(ListenType type, IPluginFunction *callback); + bool RemovePluginEntityListener(ListenType type, IPluginFunction *callback); +}; + + +struct EntityListener +{ + ListenType type; + IPluginFunction *callback; +}; + +extern std::vector g_pHooks; +#endif diff --git a/extensions/dhooks/natives.cpp b/extensions/dhooks/natives.cpp new file mode 100644 index 00000000..b447accf --- /dev/null +++ b/extensions/dhooks/natives.cpp @@ -0,0 +1,1513 @@ +#include "natives.h" +#include "util.h" +#include "dynhooks_sourcepawn.h" +#include "signatures.h" + +// Must match same enum in sdktools.inc +enum SDKFuncConfSource +{ + SDKConf_Virtual, + SDKConf_Signature, + SDKConf_Address +}; + +bool GetHandleIfValidOrError(HandleType_t type, void **object, IPluginContext *pContext, cell_t param) +{ + if(param == BAD_HANDLE) + { + return pContext->ThrowNativeError("Invalid Handle %i", BAD_HANDLE) != 0; + } + + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if((err = handlesys->ReadHandle(param, type, &sec, object)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", param, err) != 0; + } + return true; +} + +bool GetCallbackArgHandleIfValidOrError(HandleType_t type, HandleType_t otherType, void **object, IPluginContext *pContext, cell_t param) +{ + if (param == BAD_HANDLE) + { + return pContext->ThrowNativeError("Invalid Handle %i", BAD_HANDLE) != 0; + } + + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(param, type, &sec, object)) != HandleError_None) + { + // Check if the user mixed up the callback signature and tried to call + // e.g. DHookGetParam on a hReturn handle. Print a nicer error message in that case. + void *dummy; + if (handlesys->ReadHandle(param, otherType, &sec, &dummy) == HandleError_None) + return pContext->ThrowNativeError("Invalid Handle %x (error %d). It looks like you've chosen the wrong hook callback signature for your setup and you're trying to access the wrong handle.", param, err) != 0; + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", param, err) != 0; + } + return true; +} + +IPluginFunction *GetCallback(IPluginContext *pContext, HookSetup * setup, const cell_t *params, cell_t callback_index) +{ + IPluginFunction *ret = NULL; + + if (params[0] >= callback_index) + { + ret = pContext->GetFunctionById(params[callback_index]); + } + + if (!ret && setup->callback) + { + ret = setup->callback; + } + + return ret; +} + +//native Handle:DHookCreate(offset, HookType:hooktype, ReturnType:returntype, ThisPointerType:thistype, DHookCallback:callback = INVALID_FUNCTION); // Callback is now optional here. +cell_t Native_CreateHook(IPluginContext *pContext, const cell_t *params) +{ + IPluginFunction *callback = nullptr; + // The methodmap constructor doesn't have the callback parameter anymore. + if (params[0] >= 5) + callback = pContext->GetFunctionById(params[5]); + + HookSetup *setup = new HookSetup((ReturnType)params[3], PASSFLAG_BYVAL, (HookType)params[2], (ThisPointerType)params[4], params[1], callback); + + Handle_t hndl = handlesys->CreateHandle(g_HookSetupHandle, setup, pContext->GetIdentity(), myself->GetIdentity(), NULL); + + if(!hndl) + { + delete setup; + return pContext->ThrowNativeError("Failed to create hook"); + } + + return hndl; +} + +//native Handle:DHookCreateDetour(Address:funcaddr, CallingConvention:callConv, ReturnType:returntype, ThisPointerType:thistype); +cell_t Native_CreateDetour(IPluginContext *pContext, const cell_t *params) +{ + HookSetup *setup = new HookSetup((ReturnType)params[3], PASSFLAG_BYVAL, (CallingConvention)params[2], (ThisPointerType)params[4], (void *)params[1]); + + Handle_t hndl = handlesys->CreateHandle(g_HookSetupHandle, setup, pContext->GetIdentity(), myself->GetIdentity(), NULL); + + if (!hndl) + { + delete setup; + return pContext->ThrowNativeError("Failed to create hook"); + } + + return hndl; +} + +// native Handle:DHookCreateFromConf(Handle:gameconf, const String:function[]); +cell_t Native_DHookCreateFromConf(IPluginContext *pContext, const cell_t *params) +{ + IGameConfig *conf; + HandleError err; + if ((conf = gameconfs->ReadHandle(params[1], pContext->GetIdentity(), &err)) == nullptr) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", params[1], err); + } + + char *function; + pContext->LocalToString(params[2], &function); + + SignatureWrapper *sig = g_pSignatures->GetFunctionSignature(function); + if (!sig) + { + return pContext->ThrowNativeError("Function signature \"%s\" was not found.", function); + } + + HookSetup *setup = nullptr; + // This is a virtual hook. + if (sig->offset.length() > 0) + { + int offset; + if (!conf->GetOffset(sig->offset.c_str(), &offset)) + { + return BAD_HANDLE; + } + + setup = new HookSetup(sig->retType, PASSFLAG_BYVAL, sig->hookType, sig->thisType, offset, nullptr); + } + // This is a detour. + else + { + void *addr = nullptr;; + if (sig->signature.length() > 0) + { + if (!conf->GetMemSig(sig->signature.c_str(), &addr) || !addr) + { + return BAD_HANDLE; + } + } + else + { + if (!conf->GetAddress(sig->address.c_str(), &addr) || !addr) + { + return BAD_HANDLE; + } + } + + setup = new HookSetup(sig->retType, PASSFLAG_BYVAL, sig->callConv, sig->thisType, addr); + } + + // Push all the arguments. + for (ArgumentInfo &arg : sig->args) + { + ParamInfo info = arg.info; + setup->params.push_back(info); + } + + // Create the handle to hold this setup. + Handle_t hndl = handlesys->CreateHandle(g_HookSetupHandle, setup, pContext->GetIdentity(), myself->GetIdentity(), NULL); + if (!hndl) + { + delete setup; + return pContext->ThrowNativeError("Failed to create hook"); + } + + return hndl; +} + +//native bool:DHookSetFromConf(Handle:setup, Handle:gameconf, SDKFuncConfSource:source, const String:name[]); +cell_t Native_SetFromConf(IPluginContext *pContext, const cell_t *params) +{ + HookSetup *setup; + if (!GetHandleIfValidOrError(g_HookSetupHandle, (void **)&setup, pContext, params[1])) + { + return 0; + } + + IGameConfig *conf; + HandleError err; + if ((conf = gameconfs->ReadHandle(params[2], pContext->GetIdentity(), &err)) == nullptr) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", params[2], err); + } + + char *key; + pContext->LocalToString(params[4], &key); + + int offset = -1; + void *addr = nullptr;; + switch (params[3]) + { + case SDKConf_Virtual: + if (!conf->GetOffset(key, &offset)) + { + return 0; + } + break; + case SDKConf_Signature: + if (!conf->GetMemSig(key, &addr) || !addr) + { + return 0; + } + break; + case SDKConf_Address: + if (!conf->GetAddress(key, &addr) || !addr) + { + return 0; + } + break; + default: + return pContext->ThrowNativeError("Unknown SDKFuncConfSource: %d", params[3]); + } + + // Save the new info. This always invalidates the other option. + // Detour or vhook. + setup->funcAddr = addr; + setup->offset = offset; + + return 1; +} + +//native bool:DHookAddParam(Handle:setup, HookParamType:type, size=-1, DHookPassFlag:flag=DHookPass_ByVal, DHookRegister custom_register=DHookRegister_Default); +cell_t Native_AddParam(IPluginContext *pContext, const cell_t *params) +{ + HookSetup *setup; + + if(!GetHandleIfValidOrError(g_HookSetupHandle, (void **)&setup, pContext, params[1])) + { + return 0; + } + + ParamInfo info; + + info.type = (HookParamType)params[2]; + + if(params[0] >= 4) + { + info.flags = params[4]; + } + else + { + info.flags = PASSFLAG_BYVAL; + } + + if (params[0] >= 5) + { + PluginRegister custom_register = (PluginRegister)params[5]; + info.custom_register = DynamicHooks_ConvertRegisterFrom(custom_register); + + // Stay future proof. + if (info.custom_register == None && custom_register != DHookRegister_Default) + return pContext->ThrowNativeError("Unhandled DHookRegister %d", params[5]); + + if (info.custom_register != None && info.type == HookParamType_Object) + return pContext->ThrowNativeError("Can't pass an object in a register."); + } + else + { + info.custom_register = None; + } + + if(params[0] >= 3 && params[3] != -1) + { + info.size = params[3]; + } + else if(info.type == HookParamType_Object) + { + return pContext->ThrowNativeError("Object param being set with no size"); + } + else + { + info.size = GetParamTypeSize(info.type); + } + + info.pass_type = GetParamTypePassType(info.type); + setup->params.push_back(info); + + return 1; +} + + +// native bool:DHookEnableDetour(Handle:setup, bool:post, DHookCallback:callback); +cell_t Native_EnableDetour(IPluginContext *pContext, const cell_t *params) +{ + HookSetup *setup; + + if (!GetHandleIfValidOrError(g_HookSetupHandle, (void **)&setup, pContext, params[1])) + { + return 0; + } + + if (setup->funcAddr == nullptr) + { + return pContext->ThrowNativeError("Hook not setup for a detour."); + } + + IPluginFunction *callback = pContext->GetFunctionById(params[3]); + if (!callback) + { + return pContext->ThrowNativeError("Failed to retrieve function by id"); + } + + bool post = params[2] != 0; + HookType_t hookType = post ? HOOKTYPE_POST : HOOKTYPE_PRE; + + // Check if we already detoured that function. + CHookManager *pDetourManager = GetHookManager(); + CHook* pDetour = pDetourManager->FindHook(setup->funcAddr); + + // If there is no detour on this function yet, create it. + if (!pDetour) + { + ICallingConvention *pCallConv = ConstructCallingConvention(setup); + pDetour = pDetourManager->HookFunction(setup->funcAddr, pCallConv); + if (!UpdateRegisterArgumentSizes(pDetour, setup)) + return pContext->ThrowNativeError("A custom register for a parameter isn't supported."); + } + + // Register our pre/post handler. + pDetour->AddCallback(hookType, (HookHandlerFn *)&HandleDetour); + + // Add the plugin callback to the map. + return AddDetourPluginHook(hookType, pDetour, setup, callback); +} + +// native bool:DHookDisableDetour(Handle:setup, bool:post, DHookCallback:callback); +cell_t Native_DisableDetour(IPluginContext *pContext, const cell_t *params) +{ + HookSetup *setup; + + if (!GetHandleIfValidOrError(g_HookSetupHandle, (void **)&setup, pContext, params[1])) + { + return 0; + } + + if (setup->funcAddr == nullptr) + { + return pContext->ThrowNativeError("Hook not setup for a detour."); + } + + IPluginFunction *callback = pContext->GetFunctionById(params[3]); + if (!callback) + { + return pContext->ThrowNativeError("Failed to retrieve function by id"); + } + + bool post = params[2] != 0; + HookType_t hookType = post ? HOOKTYPE_POST : HOOKTYPE_PRE; + + // Check if we already detoured that function. + CHookManager *pDetourManager = GetHookManager(); + CHook* pDetour = pDetourManager->FindHook(setup->funcAddr); + + if (!pDetour || !pDetour->IsCallbackRegistered(hookType, (HookHandlerFn *)&HandleDetour)) + { + return pContext->ThrowNativeError("Function not detoured."); + } + + // Remove the callback from the hook. + return RemoveDetourPluginHook(hookType, pDetour, callback); +} + +cell_t HookEntityImpl(IPluginContext *pContext, const cell_t *params, uint32_t callbackIndex, uint32_t removalcbIndex) +{ + HookSetup *setup; + + if(!GetHandleIfValidOrError(g_HookSetupHandle, (void **)&setup, pContext, params[1])) + { + return 0; + } + + if (setup->offset == -1) + { + return pContext->ThrowNativeError("Hook not setup for a virtual hook."); + } + + if(setup->hookType != HookType_Entity) + { + return pContext->ThrowNativeError("Hook is not an entity hook"); + } + + IPluginFunction *cb = GetCallback(pContext, setup, params, callbackIndex); + if (!cb) + { + return pContext->ThrowNativeError("Failed to hook entity %i, no callback provided", params[3]); + } + + bool post = params[2] != 0; + IPluginFunction *removalcb = pContext->GetFunctionById(params[removalcbIndex]); + + for(int i = g_pHooks.size() -1; i >= 0; i--) + { + DHooksManager *manager = g_pHooks.at(i); + if(manager->callback->hookType == HookType_Entity && manager->callback->entity == gamehelpers->ReferenceToBCompatRef(params[3]) && manager->callback->offset == setup->offset && manager->callback->post == post && manager->remove_callback == removalcb && manager->callback->plugin_callback == cb) + { + return manager->hookid; + } + } + CBaseEntity *pEnt = gamehelpers->ReferenceToEntity(params[3]); + + if(!pEnt) + { + return pContext->ThrowNativeError("Invalid entity passed %i", params[3]); + } + + DHooksManager *manager = new DHooksManager(setup, pEnt, removalcb, cb, post); + + if(!manager->hookid) + { + delete manager; + return 0; + } + + g_pHooks.push_back(manager); + + return manager->hookid; +} + +// native DHookEntity(Handle:setup, bool:post, entity, DHookRemovalCB:removalcb, DHookCallback:callback = INVALID_FUNCTION); // Both callbacks are optional +cell_t Native_HookEntity(IPluginContext *pContext, const cell_t *params) +{ + return HookEntityImpl(pContext, params, 5, 4); +} +// public native int DynamicHook.HookEntity(HookMode mode, int entity, DHookCallback callback, DHookRemovalCB removalcb=INVALID_FUNCTION); +cell_t Native_HookEntity_Methodmap(IPluginContext *pContext, const cell_t *params) +{ + return HookEntityImpl(pContext, params, 4, 5); +} + +cell_t HookGamerulesImpl(IPluginContext *pContext, const cell_t *params, uint32_t callbackIndex, uint32_t removalcbIndex) +{ + HookSetup *setup; + + if(!GetHandleIfValidOrError(g_HookSetupHandle, (void **)&setup, pContext, params[1])) + { + return 0; + } + + if (setup->offset == -1) + { + return pContext->ThrowNativeError("Hook not setup for a virtual hook."); + } + + if(setup->hookType != HookType_GameRules) + { + return pContext->ThrowNativeError("Hook is not a gamerules hook"); + } + + IPluginFunction *cb = GetCallback(pContext, setup, params, callbackIndex); + if (!cb) + { + return pContext->ThrowNativeError("Failed to hook gamerules, no callback provided"); + } + + bool post = params[2] != 0; + IPluginFunction *removalcb = pContext->GetFunctionById(params[removalcbIndex]); + + for(int i = g_pHooks.size() -1; i >= 0; i--) + { + DHooksManager *manager = g_pHooks.at(i); + if(manager->callback->hookType == HookType_GameRules && manager->callback->offset == setup->offset && manager->callback->post == post && manager->remove_callback == removalcb && manager->callback->plugin_callback == cb) + { + return manager->hookid; + } + } + + void *rules = g_pSDKTools->GetGameRules(); + if(!rules) + { + return pContext->ThrowNativeError("Could not get gamerules pointer"); + } + + DHooksManager *manager = new DHooksManager(setup, rules, removalcb, cb, post); + + if(!manager->hookid) + { + delete manager; + return 0; + } + + g_pHooks.push_back(manager); + + return manager->hookid; +} + +// native DHookGamerules(Handle:setup, bool:post, DHookRemovalCB:removalcb, DHookCallback:callback = INVALID_FUNCTION); // Both callbacks are optional +cell_t Native_HookGamerules(IPluginContext *pContext, const cell_t *params) +{ + return HookGamerulesImpl(pContext, params, 4, 3); +} + +// public native int DynamicHook.HookGamerules(HookMode mode, DHookCallback callback, DHookRemovalCB removalcb=INVALID_FUNCTION); +cell_t Native_HookGamerules_Methodmap(IPluginContext *pContext, const cell_t *params) +{ + return HookGamerulesImpl(pContext, params, 3, 4); +} + +cell_t HookRawImpl(IPluginContext *pContext, const cell_t *params, int callbackIndex, int removalcbIndex) +{ + HookSetup *setup; + + if(!GetHandleIfValidOrError(g_HookSetupHandle, (void **)&setup, pContext, params[1])) + { + return 0; + } + + if (setup->offset == -1) + { + return pContext->ThrowNativeError("Hook not setup for a virtual hook."); + } + + if(setup->hookType != HookType_Raw) + { + return pContext->ThrowNativeError("Hook is not a raw hook"); + } + + IPluginFunction *cb = GetCallback(pContext, setup, params, callbackIndex); + if (!cb) + { + return pContext->ThrowNativeError("Failed to hook address, no callback provided"); + } + + bool post = params[2] != 0; + IPluginFunction *removalcb = nullptr; + if (removalcbIndex > 0) + removalcb = pContext->GetFunctionById(params[removalcbIndex]); + + void *iface = (void *)(params[3]); + + for(int i = g_pHooks.size() -1; i >= 0; i--) + { + DHooksManager *manager = g_pHooks.at(i); + if(manager->callback->hookType == HookType_Raw && manager->addr == (intptr_t)iface && manager->callback->offset == setup->offset && manager->callback->post == post && manager->remove_callback == removalcb && manager->callback->plugin_callback == cb) + { + return manager->hookid; + } + } + + if(!iface) + { + return pContext->ThrowNativeError("Invalid address passed"); + } + + DHooksManager *manager = new DHooksManager(setup, iface, removalcb, cb, post); + + if(!manager->hookid) + { + delete manager; + return 0; + } + + g_pHooks.push_back(manager); + + return manager->hookid; +} + +// DHookRaw(Handle:setup, bool:post, Address:addr, DHookRemovalCB:removalcb, DHookCallback:callback = INVALID_FUNCTION); // Both callbacks are optional +cell_t Native_HookRaw(IPluginContext *pContext, const cell_t *params) +{ + return HookRawImpl(pContext, params, 5, 4); +} + +// public native int DynamicHook.HookRaw(HookMode mode, Address addr, DHookCallback callback); +cell_t Native_HookRaw_Methodmap(IPluginContext *pContext, const cell_t *params) +{ + return HookRawImpl(pContext, params, 4, 0); +} + +// native bool:DHookRemoveHookID(hookid); +cell_t Native_RemoveHookID(IPluginContext *pContext, const cell_t *params) +{ + for(int i = g_pHooks.size() -1; i >= 0; i--) + { + DHooksManager *manager = g_pHooks.at(i); + if(manager->hookid == params[1] && manager->callback->plugin_callback->GetParentRuntime()->GetDefaultContext() == pContext) + { + delete manager; + g_pHooks.erase(g_pHooks.begin() + i); + return 1; + } + } + return 0; +} +// native any:DHookGetParam(Handle:hParams, num); +cell_t Native_GetParam(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] < 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + if(params[2] == 0) + { + return paramStruct->dg->params.size(); + } + + int index = params[2] - 1; + + size_t offset = GetParamOffset(paramStruct, index); + + void *addr = (void **)((intptr_t)paramStruct->orgParams + offset); + + if(*(void **)addr == NULL && (paramStruct->dg->params.at(index).type == HookParamType_CBaseEntity || paramStruct->dg->params.at(index).type == HookParamType_Edict)) + { + return pContext->ThrowNativeError("Trying to get value for null pointer."); + } + + switch(paramStruct->dg->params.at(index).type) + { + case HookParamType_Int: + return *(int *)addr; + case HookParamType_Bool: + return *(cell_t *)addr != 0; + case HookParamType_CBaseEntity: + return gamehelpers->EntityToBCompatRef(*(CBaseEntity **)addr); + case HookParamType_Edict: + return gamehelpers->IndexOfEdict(*(edict_t **)addr); + case HookParamType_Float: + return sp_ftoc(*(float *)addr); + default: + return pContext->ThrowNativeError("Invalid param type (%i) to get", paramStruct->dg->params.at(index).type); + } + + return 1; +} + +// native DHookSetParam(Handle:hParams, param, any:value) +cell_t Native_SetParam(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = (void **)((intptr_t)paramStruct->newParams + offset); + + switch(paramStruct->dg->params.at(index).type) + { + case HookParamType_Int: + *(int *)addr = params[3]; + break; + case HookParamType_Bool: + *(bool *)addr = (params[3] ? true : false); + break; + case HookParamType_CBaseEntity: + { + CBaseEntity *pEnt = gamehelpers->ReferenceToEntity(params[2]); + + if(!pEnt) + { + return pContext->ThrowNativeError("Invalid entity index passed for param value"); + } + + *(CBaseEntity **)addr = pEnt; + break; + } + case HookParamType_Edict: + { + edict_t *pEdict = gamehelpers->EdictOfIndex(params[2]); + + if(!pEdict || pEdict->IsFree()) + { + pContext->ThrowNativeError("Invalid entity index passed for param value"); + } + + *(edict_t **)addr = pEdict; + break; + } + case HookParamType_Float: + *(float *)addr = sp_ctof(params[3]); + break; + default: + return pContext->ThrowNativeError("Invalid param type (%i) to set", paramStruct->dg->params.at(index).type); + } + + paramStruct->isChanged[index] = true; + return 1; +} + +// native any:DHookGetReturn(Handle:hReturn); +cell_t Native_GetReturn(IPluginContext *pContext, const cell_t *params) +{ + HookReturnStruct *returnStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookReturnHandle, g_HookParamsHandle, (void **)&returnStruct, pContext, params[1])) + { + return 0; + } + + switch(returnStruct->type) + { + case ReturnType_Int: + return *(int *)returnStruct->orgResult; + case ReturnType_Bool: + return *(bool *)returnStruct->orgResult? 1 : 0; + case ReturnType_CBaseEntity: + return gamehelpers->EntityToBCompatRef((CBaseEntity *)returnStruct->orgResult); + case ReturnType_Edict: + return gamehelpers->IndexOfEdict((edict_t *)returnStruct->orgResult); + case ReturnType_Float: + return sp_ftoc(*(float *)returnStruct->orgResult); + default: + return pContext->ThrowNativeError("Invalid param type (%i) to get", returnStruct->type); + } + return 1; +} +// native DHookSetReturn(Handle:hReturn, any:value) +cell_t Native_SetReturn(IPluginContext *pContext, const cell_t *params) +{ + HookReturnStruct *returnStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookReturnHandle, g_HookParamsHandle, (void **)&returnStruct, pContext, params[1])) + { + return 0; + } + + switch(returnStruct->type) + { + case ReturnType_Int: + *(int *)returnStruct->newResult = params[2]; + break; + case ReturnType_Bool: + *(bool *)returnStruct->newResult = params[2] != 0; + break; + case ReturnType_CBaseEntity: + { + if(params[2] == -1) { + returnStruct->newResult = nullptr; + } else { + CBaseEntity *pEnt = gamehelpers->ReferenceToEntity(params[2]); + if(!pEnt) + { + return pContext->ThrowNativeError("Invalid entity index passed for return value"); + } + returnStruct->newResult = pEnt; + } + break; + } + case ReturnType_Edict: + { + edict_t *pEdict = gamehelpers->EdictOfIndex(params[2]); + if(!pEdict || pEdict->IsFree()) + { + pContext->ThrowNativeError("Invalid entity index passed for return value"); + } + returnStruct->newResult = pEdict; + break; + } + case ReturnType_Float: + *(float *)returnStruct->newResult = sp_ctof(params[2]); + break; + default: + return pContext->ThrowNativeError("Invalid param type (%i) to get",returnStruct->type); + } + returnStruct->isChanged = true; + return 1; +} +// native DHookGetParamVector(Handle:hParams, num, Float:vec[3]) +cell_t Native_GetParamVector(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = (void **)((intptr_t)paramStruct->orgParams + offset); + + if (*(void **)addr == NULL) + { + return pContext->ThrowNativeError("Trying to get value for null pointer."); + } + + switch(paramStruct->dg->params.at(index).type) + { + case HookParamType_VectorPtr: + { + cell_t *buffer; + pContext->LocalToPhysAddr(params[3], &buffer); + SDKVector *vec = *(SDKVector **)addr; + + buffer[0] = sp_ftoc(vec->x); + buffer[1] = sp_ftoc(vec->y); + buffer[2] = sp_ftoc(vec->z); + return 1; + } + } + + return pContext->ThrowNativeError("Invalid param type to get. Param is not a vector."); +} + +static void FreeChangedVector(void *pData) +{ + delete (SDKVector *)pData; +} + +// native DHookSetParamVector(Handle:hParams, num, Float:vec[3]) +cell_t Native_SetParamVector(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = (void **)((intptr_t)paramStruct->newParams + offset); + + switch(paramStruct->dg->params.at(index).type) + { + case HookParamType_VectorPtr: + { + cell_t *buffer; + pContext->LocalToPhysAddr(params[3], &buffer); + + *(SDKVector **)addr = new SDKVector(sp_ctof(buffer[0]), sp_ctof(buffer[1]), sp_ctof(buffer[2])); + paramStruct->isChanged[index] = true; + // Free it later (cheaply) after the function returned. + smutils->AddFrameAction(FreeChangedVector, *(SDKVector **)addr); + return 1; + } + } + return pContext->ThrowNativeError("Invalid param type to set. Param is not a vector."); +} + +// native DHookGetParamString(Handle:hParams, num, String:buffer[], size) +cell_t Native_GetParamString(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = (void **)((intptr_t)paramStruct->orgParams + offset); + + if(*(void **)addr == NULL) + { + return pContext->ThrowNativeError("Trying to get value for null pointer."); + } + + if(paramStruct->dg->params.at(index).type == HookParamType_CharPtr) + { + pContext->StringToLocal(params[3], params[4], *(const char **)addr); + } + + return 1; +} + +// native DHookGetReturnString(Handle:hReturn, String:buffer[], size) +cell_t Native_GetReturnString(IPluginContext *pContext, const cell_t *params) +{ + HookReturnStruct *returnStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookReturnHandle, g_HookParamsHandle, (void **)&returnStruct, pContext, params[1])) + { + return 0; + } + + switch(returnStruct->type) + { + case ReturnType_String: + pContext->StringToLocal(params[2], params[3], (*(string_t *)returnStruct->orgResult == NULL_STRING) ? "" : STRING(*(string_t *)returnStruct->orgResult)); + return 1; + case ReturnType_StringPtr: + pContext->StringToLocal(params[2], params[3], ((string_t *)returnStruct->orgResult == NULL) ? "" : ((string_t *)returnStruct->orgResult)->ToCStr()); + return 1; + case ReturnType_CharPtr: + pContext->StringToLocal(params[2], params[3], ((char *)returnStruct->orgResult == NULL) ? "" : (const char *)returnStruct->orgResult); + return 1; + default: + return pContext->ThrowNativeError("Invalid param type to get. Param is not a string."); + } +} + +static void FreeChangedCharPtr(void *pData) +{ + delete[](char *)pData; +} + +//native DHookSetReturnString(Handle:hReturn, String:value[]) +cell_t Native_SetReturnString(IPluginContext *pContext, const cell_t *params) +{ + HookReturnStruct *returnStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookReturnHandle, g_HookParamsHandle, (void **)&returnStruct, pContext, params[1])) + { + return 0; + } + + char *value; + pContext->LocalToString(params[2], &value); + + switch(returnStruct->type) + { + case ReturnType_CharPtr: + { + returnStruct->newResult = new char[strlen(value) + 1]; + strcpy((char *)returnStruct->newResult, value); + returnStruct->isChanged = true; + // Free it later (cheaply) after the function returned. + smutils->AddFrameAction(FreeChangedCharPtr, returnStruct->newResult); + return 1; + } + default: + return pContext->ThrowNativeError("Invalid param type to get. Param is not a char pointer."); + } +} + +//native DHookSetParamString(Handle:hParams, num, String:value[]) +cell_t Native_SetParamString(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + size_t offset = GetParamOffset(paramStruct, index); + void *addr = (void **)((intptr_t)paramStruct->newParams + offset); + + char *value; + pContext->LocalToString(params[3], &value); + + if(paramStruct->dg->params.at(index).type == HookParamType_CharPtr) + { + *(char **)addr = new char[strlen(value)+1]; + strcpy(*(char **)addr, value); + paramStruct->isChanged[index] = true; + // Free it later (cheaply) after the function returned. + smutils->AddFrameAction(FreeChangedCharPtr, *(char **)addr); + } + return 1; +} + +//native DHookAddEntityListener(ListenType:type, ListenCB:callback); +cell_t Native_AddEntityListener(IPluginContext *pContext, const cell_t *params) +{ + if(g_pEntityListener) + { + return g_pEntityListener->AddPluginEntityListener((ListenType)params[1], pContext->GetFunctionById(params[2]));; + } + return pContext->ThrowNativeError("Failed to get g_pEntityListener"); +} + +//native bool:DHookRemoveEntityListener(ListenType:type, ListenCB:callback); +cell_t Native_RemoveEntityListener(IPluginContext *pContext, const cell_t *params) +{ + if(g_pEntityListener) + { + return g_pEntityListener->RemovePluginEntityListener((ListenType)params[1], pContext->GetFunctionById(params[2]));; + } + return pContext->ThrowNativeError("Failed to get g_pEntityListener"); +} + +//native any:DHookGetParamObjectPtrVar(Handle:hParams, num, offset, ObjectValueType:type); +cell_t Native_GetParamObjectPtrVar(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + if(paramStruct->dg->params.at(index).type != HookParamType_ObjectPtr && paramStruct->dg->params.at(index).type != HookParamType_Object) + { + return pContext->ThrowNativeError("Invalid object value type %i", paramStruct->dg->params.at(index).type); + } + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = GetObjectAddr(paramStruct->dg->params.at(index).type, paramStruct->dg->params.at(index).flags, paramStruct->orgParams, offset); + + switch((ObjectValueType)params[4]) + { + case ObjectValueType_Int: + { + return *(int *)((intptr_t)addr + params[3]); + } + case ObjectValueType_Bool: + return (*(bool *)((intptr_t)addr + params[3])) ? 1 : 0; + case ObjectValueType_Ehandle: + case ObjectValueType_EhandlePtr: + { + edict_t *pEdict = gamehelpers->GetHandleEntity(*(CBaseHandle *)((intptr_t)addr +params[3])); + + if(!pEdict) + { + return -1; + } + + return gamehelpers->IndexOfEdict(pEdict); + } + case ObjectValueType_Float: + { + return sp_ftoc(*(float *)((intptr_t)addr + params[3])); + } + case ObjectValueType_CBaseEntityPtr: + return gamehelpers->EntityToBCompatRef(*(CBaseEntity **)((intptr_t)addr + params[3])); + case ObjectValueType_IntPtr: + { + int *ptr = *(int **)((intptr_t)addr + params[3]); + return *ptr; + } + case ObjectValueType_BoolPtr: + { + bool *ptr = *(bool **)((intptr_t)addr + params[3]); + return *ptr ? 1 : 0; + } + case ObjectValueType_FloatPtr: + { + float *ptr = *(float **)((intptr_t)addr + params[3]); + return sp_ftoc(*ptr); + } + default: + return pContext->ThrowNativeError("Invalid Object value type"); + } +} + +//native DHookSetParamObjectPtrVar(Handle:hParams, num, offset, ObjectValueType:type, value) +cell_t Native_SetParamObjectPtrVar(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + if(paramStruct->dg->params.at(index).type != HookParamType_ObjectPtr && paramStruct->dg->params.at(index).type != HookParamType_Object) + { + return pContext->ThrowNativeError("Invalid object value type %i", paramStruct->dg->params.at(index).type); + } + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = GetObjectAddr(paramStruct->dg->params.at(index).type, paramStruct->dg->params.at(index).flags, paramStruct->orgParams, offset); + + switch((ObjectValueType)params[4]) + { + case ObjectValueType_Int: + *(int *)((intptr_t)addr + params[3]) = params[5]; + break; + case ObjectValueType_Bool: + *(bool *)((intptr_t)addr + params[3]) = params[5] != 0; + break; + case ObjectValueType_Ehandle: + case ObjectValueType_EhandlePtr: + { + edict_t *pEdict = gamehelpers->EdictOfIndex(params[5]); + + if(!pEdict || pEdict->IsFree()) + { + return pContext->ThrowNativeError("Invalid edict passed"); + } + gamehelpers->SetHandleEntity(*(CBaseHandle *)((intptr_t)addr + params[3]), pEdict); + + break; + } + case ObjectValueType_Float: + *(float *)((intptr_t)addr + params[3]) = sp_ctof(params[5]); + break; + case ObjectValueType_CBaseEntityPtr: + { + CBaseEntity *pEnt = gamehelpers->ReferenceToEntity(params[5]); + + if(!pEnt) + { + return pContext->ThrowNativeError("Invalid entity passed"); + } + + *(CBaseEntity **)((intptr_t)addr + params[3]) = pEnt; + break; + } + case ObjectValueType_IntPtr: + { + int *ptr = *(int **)((intptr_t)addr + params[3]); + *ptr = params[5]; + break; + } + case ObjectValueType_BoolPtr: + { + bool *ptr = *(bool **)((intptr_t)addr + params[3]); + *ptr = params[5] != 0; + break; + } + case ObjectValueType_FloatPtr: + { + float *ptr = *(float **)((intptr_t)addr + params[3]); + *ptr = sp_ctof(params[5]); + break; + } + default: + return pContext->ThrowNativeError("Invalid Object value type"); + } + return 1; +} + +//native DHookGetParamObjectPtrVarVector(Handle:hParams, num, offset, ObjectValueType:type, Float:buffer[3]); +cell_t Native_GetParamObjectPtrVarVector(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + if(paramStruct->dg->params.at(index).type != HookParamType_ObjectPtr && paramStruct->dg->params.at(index).type != HookParamType_Object) + { + return pContext->ThrowNativeError("Invalid object value type %i", paramStruct->dg->params.at(index).type); + } + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = GetObjectAddr(paramStruct->dg->params.at(index).type, paramStruct->dg->params.at(index).flags, paramStruct->orgParams, offset); + + cell_t *buffer; + pContext->LocalToPhysAddr(params[5], &buffer); + + if((ObjectValueType)params[4] == ObjectValueType_VectorPtr || (ObjectValueType)params[4] == ObjectValueType_Vector) + { + SDKVector *vec; + + if((ObjectValueType)params[4] == ObjectValueType_VectorPtr) + { + vec = *(SDKVector **)((intptr_t)addr + params[3]); + if(vec == NULL) + { + return pContext->ThrowNativeError("Trying to get value for null pointer."); + } + } + else + { + vec = (SDKVector *)((intptr_t)addr + params[3]); + } + + buffer[0] = sp_ftoc(vec->x); + buffer[1] = sp_ftoc(vec->y); + buffer[2] = sp_ftoc(vec->z); + return 1; + } + + return pContext->ThrowNativeError("Invalid Object value type (not a type of vector)"); +} + +//native DHookSetParamObjectPtrVarVector(Handle:hParams, num, offset, ObjectValueType:type, Float:value[3]); +cell_t Native_SetParamObjectPtrVarVector(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + if(paramStruct->dg->params.at(index).type != HookParamType_ObjectPtr && paramStruct->dg->params.at(index).type != HookParamType_Object) + { + return pContext->ThrowNativeError("Invalid object value type %i", paramStruct->dg->params.at(index).type); + } + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = GetObjectAddr(paramStruct->dg->params.at(index).type, paramStruct->dg->params.at(index).flags, paramStruct->orgParams, offset); + + cell_t *buffer; + pContext->LocalToPhysAddr(params[5], &buffer); + + if((ObjectValueType)params[4] == ObjectValueType_VectorPtr || (ObjectValueType)params[4] == ObjectValueType_Vector) + { + SDKVector *vec; + + if((ObjectValueType)params[4] == ObjectValueType_VectorPtr) + { + vec = *(SDKVector **)((intptr_t)addr + params[3]); + if(vec == NULL) + { + return pContext->ThrowNativeError("Trying to set value for null pointer."); + } + } + else + { + vec = (SDKVector *)((intptr_t)addr + params[3]); + } + + vec->x = sp_ctof(buffer[0]); + vec->y = sp_ctof(buffer[1]); + vec->z = sp_ctof(buffer[2]); + return 1; + } + return pContext->ThrowNativeError("Invalid Object value type (not a type of vector)"); +} + +//native DHookGetParamObjectPtrString(Handle:hParams, num, offset, ObjectValueType:type, String:buffer[], size) +cell_t Native_GetParamObjectPtrString(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + if(paramStruct->dg->params.at(index).type != HookParamType_ObjectPtr && paramStruct->dg->params.at(index).type != HookParamType_Object) + { + return pContext->ThrowNativeError("Invalid object value type %i", paramStruct->dg->params.at(index).type); + } + + size_t offset = GetParamOffset(paramStruct, index); + void *addr = GetObjectAddr(paramStruct->dg->params.at(index).type, paramStruct->dg->params.at(index).flags, paramStruct->orgParams, offset); + + switch((ObjectValueType)params[4]) + { + case ObjectValueType_CharPtr: + { + char *ptr = *(char **)((intptr_t)addr + params[3]); + pContext->StringToLocal(params[5], params[6], ptr == NULL ? "" : (const char *)ptr); + break; + } + case ObjectValueType_String: + { + string_t string = *(string_t *)((intptr_t)addr + params[3]); + pContext->StringToLocal(params[5], params[6], string == NULL_STRING ? "" : STRING(string)); + break; + } + default: + return pContext->ThrowNativeError("Invalid Object value type (not a type of string)"); + } + return 1; +} + +// DHookGetReturnVector(Handle:hReturn, Float:vec[3]) +cell_t Native_GetReturnVector(IPluginContext *pContext, const cell_t *params) +{ + HookReturnStruct *returnStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookReturnHandle, g_HookParamsHandle, (void **)&returnStruct, pContext, params[1])) + { + return 0; + } + + cell_t *buffer; + pContext->LocalToPhysAddr(params[2], &buffer); + + if(returnStruct->type == ReturnType_Vector) + { + buffer[0] = sp_ftoc((*(SDKVector *)returnStruct->orgResult).x); + buffer[1] = sp_ftoc((*(SDKVector *)returnStruct->orgResult).y); + buffer[2] = sp_ftoc((*(SDKVector *)returnStruct->orgResult).z); + + return 1; + } + else if(returnStruct->type == ReturnType_VectorPtr) + { + buffer[0] = sp_ftoc(((SDKVector *)returnStruct->orgResult)->x); + buffer[1] = sp_ftoc(((SDKVector *)returnStruct->orgResult)->y); + buffer[2] = sp_ftoc(((SDKVector *)returnStruct->orgResult)->z); + + return 1; + } + return pContext->ThrowNativeError("Return type is not a vector type"); +} + +//DHookSetReturnVector(Handle:hReturn, Float:vec[3]) +cell_t Native_SetReturnVector(IPluginContext *pContext, const cell_t *params) +{ + HookReturnStruct *returnStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookReturnHandle, g_HookParamsHandle, (void **)&returnStruct, pContext, params[1])) + { + return 0; + } + + cell_t *buffer; + pContext->LocalToPhysAddr(params[2], &buffer); + + if(returnStruct->type == ReturnType_Vector) + { + *(SDKVector *)returnStruct->newResult = SDKVector(sp_ctof(buffer[0]), sp_ctof(buffer[1]), sp_ctof(buffer[2])); + returnStruct->isChanged = true; + + return 1; + } + else if(returnStruct->type == ReturnType_VectorPtr) + { + returnStruct->newResult = new SDKVector(sp_ctof(buffer[0]), sp_ctof(buffer[1]), sp_ctof(buffer[2])); + returnStruct->isChanged = true; + // Free it later (cheaply) after the function returned. + smutils->AddFrameAction(FreeChangedVector, returnStruct->newResult); + + return 1; + } + return pContext->ThrowNativeError("Return type is not a vector type"); +} + +//native bool:DHookIsNullParam(Handle:hParams, num); +cell_t Native_IsNullParam(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + size_t offset = GetParamOffset(paramStruct, index); + void *addr = (void **)((intptr_t)paramStruct->orgParams + offset); + + HookParamType type = paramStruct->dg->params.at(index).type; + + //Check that the type is ptr + if(type == HookParamType_StringPtr || type == HookParamType_CharPtr || type == HookParamType_VectorPtr || type == HookParamType_CBaseEntity || type == HookParamType_ObjectPtr || type == HookParamType_Edict || type == HookParamType_Unknown) + return *(void **)addr == NULL; + else + return pContext->ThrowNativeError("Param is not a pointer!"); +} + +//native Address:DHookGetParamAddress(Handle:hParams, num); +cell_t Native_GetParamAddress(IPluginContext *pContext, const cell_t *params) +{ + HookParamsStruct *paramStruct; + + if(!GetCallbackArgHandleIfValidOrError(g_HookParamsHandle, g_HookReturnHandle, (void **)¶mStruct, pContext, params[1])) + { + return 0; + } + + if(params[2] <= 0 || params[2] > (int)paramStruct->dg->params.size()) + { + return pContext->ThrowNativeError("Invalid param number %i max params is %i", params[2], paramStruct->dg->params.size()); + } + + int index = params[2] - 1; + + HookParamType type = paramStruct->dg->params.at(index).type; + if(type != HookParamType_StringPtr && type != HookParamType_CharPtr && type != HookParamType_VectorPtr && type != HookParamType_CBaseEntity && type != HookParamType_ObjectPtr && type != HookParamType_Edict && type != HookParamType_Unknown) + { + return pContext->ThrowNativeError("Param is not a pointer!"); + } + + size_t offset = GetParamOffset(paramStruct, index); + return *(cell_t *)((intptr_t)paramStruct->orgParams + offset); +} + +sp_nativeinfo_t g_Natives[] = +{ + {"DHookCreate", Native_CreateHook}, + {"DHookCreateDetour", Native_CreateDetour}, + {"DHookCreateFromConf", Native_DHookCreateFromConf}, + {"DHookSetFromConf", Native_SetFromConf}, + {"DHookAddParam", Native_AddParam}, + {"DHookEnableDetour", Native_EnableDetour}, + {"DHookDisableDetour", Native_DisableDetour}, + {"DHookEntity", Native_HookEntity}, + {"DHookGamerules", Native_HookGamerules}, + {"DHookRaw", Native_HookRaw}, + {"DHookRemoveHookID", Native_RemoveHookID}, + {"DHookGetParam", Native_GetParam}, + {"DHookGetReturn", Native_GetReturn}, + {"DHookSetReturn", Native_SetReturn}, + {"DHookSetParam", Native_SetParam}, + {"DHookGetParamVector", Native_GetParamVector}, + {"DHookGetReturnVector", Native_GetReturnVector}, + {"DHookSetReturnVector", Native_SetReturnVector}, + {"DHookSetParamVector", Native_SetParamVector}, + {"DHookGetParamString", Native_GetParamString}, + {"DHookGetReturnString", Native_GetReturnString}, + {"DHookSetReturnString", Native_SetReturnString}, + {"DHookSetParamString", Native_SetParamString}, + {"DHookAddEntityListener", Native_AddEntityListener}, + {"DHookRemoveEntityListener", Native_RemoveEntityListener}, + {"DHookGetParamObjectPtrVar", Native_GetParamObjectPtrVar}, + {"DHookSetParamObjectPtrVar", Native_SetParamObjectPtrVar}, + {"DHookGetParamObjectPtrVarVector", Native_GetParamObjectPtrVarVector}, + {"DHookSetParamObjectPtrVarVector", Native_SetParamObjectPtrVarVector}, + {"DHookGetParamObjectPtrString", Native_GetParamObjectPtrString}, + {"DHookIsNullParam", Native_IsNullParam}, + {"DHookGetParamAddress", Native_GetParamAddress}, + + // Methodmap API + {"DHookSetup.AddParam", Native_AddParam}, + {"DHookSetup.SetFromConf", Native_SetFromConf}, + + {"DynamicHook.DynamicHook", Native_CreateHook}, + {"DynamicHook.FromConf", Native_DHookCreateFromConf}, + {"DynamicHook.HookEntity", Native_HookEntity_Methodmap}, + {"DynamicHook.HookGamerules", Native_HookGamerules_Methodmap}, + {"DynamicHook.HookRaw", Native_HookRaw_Methodmap}, + {"DynamicHook.RemoveHook", Native_RemoveHookID}, + + {"DynamicDetour.DynamicDetour", Native_CreateDetour}, + {"DynamicDetour.FromConf", Native_DHookCreateFromConf}, + {"DynamicDetour.Enable", Native_EnableDetour}, + {"DynamicDetour.Disable", Native_DisableDetour}, + + {"DHookParam.Get", Native_GetParam}, + {"DHookParam.GetVector", Native_GetParamVector}, + {"DHookParam.GetString", Native_GetParamString}, + {"DHookParam.Set", Native_SetParam}, + {"DHookParam.SetVector", Native_SetParamVector}, + {"DHookParam.SetString", Native_SetParamString}, + {"DHookParam.GetObjectVar", Native_GetParamObjectPtrVar}, + {"DHookParam.GetObjectVarVector", Native_GetParamObjectPtrVarVector}, + {"DHookParam.GetObjectVarString", Native_GetParamObjectPtrString}, + {"DHookParam.SetObjectVar", Native_SetParamObjectPtrVar}, + {"DHookParam.SetObjectVarVector", Native_SetParamObjectPtrVarVector}, + {"DHookParam.IsNull", Native_IsNullParam}, + {"DHookParam.GetAddress", Native_GetParamAddress}, + + {"DHookReturn.Value.get", Native_GetReturn}, + {"DHookReturn.Value.set", Native_SetReturn}, + {"DHookReturn.GetVector", Native_GetReturnVector}, + {"DHookReturn.SetVector", Native_SetReturnVector}, + {"DHookReturn.GetString", Native_GetReturnString}, + {"DHookReturn.SetString", Native_SetReturnString}, + {NULL, NULL} +}; diff --git a/extensions/dhooks/natives.h b/extensions/dhooks/natives.h new file mode 100644 index 00000000..177865dd --- /dev/null +++ b/extensions/dhooks/natives.h @@ -0,0 +1,14 @@ +#ifndef _INCLUDE_NATIVES_H_ +#define _INCLUDE_NATIVES_H_ + +#include "extension.h" +#include "vhook.h" +#include "listeners.h" + +extern DHooksEntityListener *g_pEntityListener; +extern ISDKTools *g_pSDKTools; +extern HandleType_t g_HookSetupHandle; +extern HandleType_t g_HookParamsHandle; +extern HandleType_t g_HookReturnHandle; +extern std::vector g_pHooks; +#endif diff --git a/extensions/dhooks/sdk-hacks.h b/extensions/dhooks/sdk-hacks.h new file mode 100644 index 00000000..74880270 --- /dev/null +++ b/extensions/dhooks/sdk-hacks.h @@ -0,0 +1,75 @@ +#ifndef _INCLUDE_SDK_HACKS_H_ +#define _INCLUDE_SDK_HACKS_H_ + +class SDKVector +{ +public: + SDKVector(float x1, float y1, float z1) + { + this->x = x1; + this->y = y1; + this->z = z1; + } + SDKVector(void) + { + this->x = 0.0; + this->y = 0.0; + this->z = 0.0; + } + float x; + float y; + float z; +}; + +struct string_t +{ +public: + bool operator!() const { return ( pszValue == NULL ); } + bool operator==( const string_t &rhs ) const { return ( pszValue == rhs.pszValue ); } + bool operator!=( const string_t &rhs ) const { return ( pszValue != rhs.pszValue ); } + bool operator<( const string_t &rhs ) const { return ((void *)pszValue < (void *)rhs.pszValue ); } + + const char *ToCStr() const { return ( pszValue ) ? pszValue : ""; } + +protected: + const char *pszValue; +}; + +struct castable_string_t : public string_t // string_t is used in unions, hence, no constructor allowed +{ + castable_string_t() { pszValue = NULL; } + castable_string_t( const char *pszFrom ) { pszValue = (pszFrom && *pszFrom) ? pszFrom : 0; } +}; + +#define NULL_STRING castable_string_t() +#define STRING( string_t_obj ) (string_t_obj).ToCStr() +#define MAKE_STRING( c_str ) castable_string_t( c_str ) + +#define FL_EDICT_FREE (1<<1) + +struct edict_t +{ +public: + bool IsFree() + { + return (m_fStateFlags & FL_EDICT_FREE) != 0; + } +private: + int m_fStateFlags; +}; + +class CBaseHandle +{ +/*public: + bool IsValid() const {return m_Index != INVALID_EHANDLE_INDEX;} + int GetEntryIndex() const + { + if ( !IsValid() ) + return NUM_ENT_ENTRIES-1; + return m_Index & ENT_ENTRY_MASK; + }*/ +private: + unsigned long m_Index; +}; + +#endif \ No newline at end of file diff --git a/extensions/dhooks/signatures.cpp b/extensions/dhooks/signatures.cpp new file mode 100644 index 00000000..f3995e76 --- /dev/null +++ b/extensions/dhooks/signatures.cpp @@ -0,0 +1,574 @@ +#include + +SignatureGameConfig *g_pSignatures; + +enum ParseState +{ + PState_None, + PState_Root, + PState_Function, + PState_Arguments, + PState_Argument +}; + +ParseState g_ParseState; +unsigned int g_IgnoreLevel; +// The parent section type of a platform specific "windows" or "linux" section. +ParseState g_PlatformOnlyState; + +SignatureWrapper *g_CurrentSignature; +std::string g_CurrentFunctionName; +ArgumentInfo g_CurrentArgumentInfo; + +SignatureWrapper *SignatureGameConfig::GetFunctionSignature(const char *function) +{ + auto sig = signatures_.find(function); + if (!sig.found()) + return nullptr; + + return sig->value; +} + +/** + * Game config "Functions" section parsing. + */ +SMCResult SignatureGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *name) +{ + // We're ignoring the parent section. Ignore all child sections as well. + if (g_IgnoreLevel > 0) + { + g_IgnoreLevel++; + return SMCResult_Continue; + } + + // Handle platform specific sections first. +#if defined WIN32 + if (!strcmp(name, "windows")) +#elif defined _LINUX + if (!strcmp(name, "linux")) +#elif defined _OSX + if (!strcmp(name, "mac")) +#endif + { + // We're already in a section for a different OS that we're ignoring. Can't have a section for our OS in here. + if (g_IgnoreLevel > 0) + { + smutils->LogError(myself, "Unreachable platform specific section in \"%s\" Function: line: %i col: %i", g_CurrentFunctionName.c_str(), states->line, states->col); + return SMCResult_HaltFail; + } + + // We don't support nested (useless) sections of the same OS like "windows" { "windows" { "foo" "bar" } } + if (g_PlatformOnlyState != PState_None) + { + smutils->LogError(myself, "Duplicate platform specific section for \"%s\". Already parsing only for that OS: line: %i col: %i", name, states->line, states->col); + return SMCResult_HaltFail; + } + + // This is a specific block for us. + g_PlatformOnlyState = g_ParseState; + return SMCResult_Continue; + } +#if defined WIN32 + else if (!strcmp(name, "linux") || !strcmp(name, "mac")) +#elif defined _LINUX + else if (!strcmp(name, "windows") || !strcmp(name, "mac")) +#elif defined _OSX + else if (!strcmp(name, "windows") || !strcmp(name, "linux")) +#endif + { + if (g_PlatformOnlyState != PState_None) + { + smutils->LogError(myself, "Unreachable platform specific section in \"%s\" Function: line: %i col: %i", g_CurrentFunctionName.c_str(), states->line, states->col); + return SMCResult_HaltFail; + } + + // A specific block for a different platform. + g_IgnoreLevel++; + return SMCResult_Continue; + } + + switch (g_ParseState) + { + case PState_Root: + { + auto sig = signatures_.find(name); + if (sig.found()) + g_CurrentSignature = sig->value; + else + g_CurrentSignature = new SignatureWrapper(); + g_CurrentFunctionName = name; + g_ParseState = PState_Function; + break; + } + + case PState_Function: + { + if (!strcmp(name, "arguments")) + { + g_ParseState = PState_Arguments; + } + else + { + smutils->LogError(myself, "Unknown subsection \"%s\" (expected \"arguments\"): line: %i col: %i", name, states->line, states->col); + return SMCResult_HaltFail; + } + break; + } + case PState_Arguments: + { + g_ParseState = PState_Argument; + g_CurrentArgumentInfo.name = name; + + // Reset the parameter info. + ParamInfo info; + memset(&info, 0, sizeof(info)); + info.flags = PASSFLAG_BYVAL; + g_CurrentArgumentInfo.info = info; + + // See if we already have info about this argument. + for (auto &arg : g_CurrentSignature->args) { + if (!arg.name.compare(name)) { + // Continue changing that argument now. + g_CurrentArgumentInfo.info = arg.info; + break; + } + } + break; + } + default: + smutils->LogError(myself, "Unknown subsection \"%s\": line: %i col: %i", name, states->line, states->col); + return SMCResult_HaltFail; + } + + return SMCResult_Continue; +} + +SMCResult SignatureGameConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) +{ + // We don't care for anything in this section or subsections. + if (g_IgnoreLevel > 0) + return SMCResult_Continue; + + switch (g_ParseState) + { + case PState_Function: + + if (!strcmp(key, "signature")) + { + if (g_CurrentSignature->address.length() > 0 || g_CurrentSignature->offset.length() > 0) + { + smutils->LogError(myself, "Cannot have \"signature\", \"address\" or \"offset\" keys at the same time in one function: line: %i col: %i", states->line, states->col); + return SMCResult_HaltFail; + } + g_CurrentSignature->signature = value; + } + else if (!strcmp(key, "address")) + { + if (g_CurrentSignature->signature.length() > 0 || g_CurrentSignature->offset.length() > 0) + { + smutils->LogError(myself, "Cannot have \"signature\", \"address\" or \"offset\" keys at the same time in one function: line: %i col: %i", states->line, states->col); + return SMCResult_HaltFail; + } + g_CurrentSignature->address = value; + } + else if (!strcmp(key, "offset")) + { + if (g_CurrentSignature->address.length() > 0 || g_CurrentSignature->signature.length() > 0) + { + smutils->LogError(myself, "Cannot have \"signature\", \"address\" or \"offset\" keys at the same time in one function: line: %i col: %i", states->line, states->col); + return SMCResult_HaltFail; + } + g_CurrentSignature->offset = value; + } + else if (!strcmp(key, "callconv")) + { + CallingConvention callConv; + + if (!strcmp(value, "cdecl")) + callConv = CallConv_CDECL; + else if (!strcmp(value, "thiscall")) + callConv = CallConv_THISCALL; + else if (!strcmp(value, "stdcall")) + callConv = CallConv_STDCALL; + else if (!strcmp(value, "fastcall")) + callConv = CallConv_FASTCALL; + else + { + smutils->LogError(myself, "Invalid calling convention \"%s\": line: %i col: %i", value, states->line, states->col); + return SMCResult_HaltFail; + } + + g_CurrentSignature->callConv = callConv; + } + else if (!strcmp(key, "hooktype")) + { + HookType hookType; + + if (!strcmp(value, "entity")) + hookType = HookType_Entity; + else if (!strcmp(value, "gamerules")) + hookType = HookType_GameRules; + else if (!strcmp(value, "raw")) + hookType = HookType_Raw; + else + { + smutils->LogError(myself, "Invalid hook type \"%s\": line: %i col: %i", value, states->line, states->col); + return SMCResult_HaltFail; + } + + g_CurrentSignature->hookType = hookType; + } + else if (!strcmp(key, "return")) + { + g_CurrentSignature->retType = GetReturnTypeFromString(value); + + if (g_CurrentSignature->retType == ReturnType_Unknown) + { + smutils->LogError(myself, "Invalid return type \"%s\": line: %i col: %i", value, states->line, states->col); + return SMCResult_HaltFail; + } + } + else if (!strcmp(key, "this")) + { + if (!strcmp(value, "ignore")) + g_CurrentSignature->thisType = ThisPointer_Ignore; + else if (!strcmp(value, "entity")) + g_CurrentSignature->thisType = ThisPointer_CBaseEntity; + else if (!strcmp(value, "address")) + g_CurrentSignature->thisType = ThisPointer_Address; + else + { + smutils->LogError(myself, "Invalid this type \"%s\": line: %i col: %i", value, states->line, states->col); + return SMCResult_HaltFail; + } + } + else + { + smutils->LogError(myself, "Unknown key in Functions section \"%s\": line: %i col: %i", key, states->line, states->col); + return SMCResult_HaltFail; + } + break; + + case PState_Argument: + + if (!strcmp(key, "type")) + { + g_CurrentArgumentInfo.info.type = GetHookParamTypeFromString(value); + if (g_CurrentArgumentInfo.info.type == HookParamType_Unknown) + { + smutils->LogError(myself, "Invalid argument type \"%s\" for argument \"%s\": line: %i col: %i", value, g_CurrentArgumentInfo.name.c_str(), states->line, states->col); + return SMCResult_HaltFail; + } + } + else if (!strcmp(key, "size")) + { + g_CurrentArgumentInfo.info.size = static_cast(strtol(value, NULL, 0)); + + if (g_CurrentArgumentInfo.info.size < 1) + { + smutils->LogError(myself, "Invalid argument size \"%s\" for argument \"%s\": line: %i col: %i", value, g_CurrentArgumentInfo.name.c_str(), states->line, states->col); + return SMCResult_HaltFail; + } + } + else if (!strcmp(key, "flags")) + { + size_t flags = 0; + if (strstr(value, "byval")) + flags |= PASSFLAG_BYVAL; + if (strstr(value, "byref")) + flags |= PASSFLAG_BYREF; + if (strstr(value, "odtor")) + flags |= PASSFLAG_ODTOR; + if (strstr(value, "octor")) + flags |= PASSFLAG_OCTOR; + if (strstr(value, "oassignop")) + flags |= PASSFLAG_OASSIGNOP; +#ifdef PASSFLAG_OCOPYCTOR + if (strstr(value, "ocopyctor")) + flags |= PASSFLAG_OCOPYCTOR; +#endif +#ifdef PASSFLAG_OUNALIGN + if (strstr(value, "ounalign")) + flags |= PASSFLAG_OUNALIGN; +#endif + + g_CurrentArgumentInfo.info.flags = flags; + } + else if (!strcmp(key, "register")) + { + g_CurrentArgumentInfo.info.custom_register = GetCustomRegisterFromString(value); + + if (g_CurrentArgumentInfo.info.custom_register == Register_t::None) + { + smutils->LogError(myself, "Invalid register \"%s\": line: %i col: %i", value, states->line, states->col); + return SMCResult_HaltFail; + } + } + else + { + smutils->LogError(myself, "Unknown key in Functions section \"%s\": line: %i col: %i", key, states->line, states->col); + return SMCResult_HaltFail; + } + break; + + default: + smutils->LogError(myself, "Unknown key in Functions section \"%s\": line: %i col: %i", key, states->line, states->col); + return SMCResult_HaltFail; + } + return SMCResult_Continue; +} + +SMCResult SignatureGameConfig::ReadSMC_LeavingSection(const SMCStates *states) +{ + // We were ignoring this section. + if (g_IgnoreLevel > 0) + { + g_IgnoreLevel--; + return SMCResult_Continue; + } + + // We were in a section only for our OS. + if (g_PlatformOnlyState == g_ParseState) + { + g_PlatformOnlyState = PState_None; + return SMCResult_Continue; + } + + switch (g_ParseState) + { + case PState_Function: + g_ParseState = PState_Root; + + if (!g_CurrentSignature->address.length() && !g_CurrentSignature->signature.length() && !g_CurrentSignature->offset.length()) + { + smutils->LogError(myself, "Function \"%s\" doesn't have a \"signature\", \"offset\" nor \"address\" set: line: %i col: %i", g_CurrentFunctionName.c_str(), states->line, states->col); + return SMCResult_HaltFail; + } + + // Save this function signature in our cache. + signatures_.insert(g_CurrentFunctionName.c_str(), g_CurrentSignature); + g_CurrentFunctionName = ""; + g_CurrentSignature = nullptr; + break; + case PState_Arguments: + g_ParseState = PState_Function; + break; + case PState_Argument: + g_ParseState = PState_Arguments; + + if (g_CurrentArgumentInfo.info.type == HookParamType_Unknown) + { + smutils->LogError(myself, "Missing argument type for argument \"%s\": line: %i col: %i", g_CurrentArgumentInfo.name.c_str(), states->line, states->col); + return SMCResult_HaltFail; + } + + // The size wasn't set in the config. See if that's fine and we can guess it from the type. + if (!g_CurrentArgumentInfo.info.size) + { + if (g_CurrentArgumentInfo.info.type == HookParamType_Object) + { + smutils->LogError(myself, "Object param \"%s\" being set with no size: line: %i col: %i", g_CurrentArgumentInfo.name.c_str(), states->line, states->col); + return SMCResult_HaltFail; + } + else + { + g_CurrentArgumentInfo.info.size = GetParamTypeSize(g_CurrentArgumentInfo.info.type); + } + } + + if (g_CurrentArgumentInfo.info.pass_type == SourceHook::PassInfo::PassType::PassType_Unknown) + g_CurrentArgumentInfo.info.pass_type = GetParamTypePassType(g_CurrentArgumentInfo.info.type); + + // See if we were changing an existing argument. + bool changed = false; + for (auto &arg : g_CurrentSignature->args) + { + if (!arg.name.compare(g_CurrentArgumentInfo.name)) + { + arg.info = g_CurrentArgumentInfo.info; + changed = true; + break; + } + } + // This was a new argument. Add it to the end of the list. + if (!changed) + g_CurrentSignature->args.push_back(g_CurrentArgumentInfo); + + g_CurrentArgumentInfo.name = ""; + break; + } + + return SMCResult_Continue; +} + +void SignatureGameConfig::ReadSMC_ParseStart() +{ + g_ParseState = PState_Root; + g_IgnoreLevel = 0; + g_PlatformOnlyState = PState_None; + g_CurrentSignature = nullptr; + g_CurrentFunctionName = ""; + g_CurrentArgumentInfo.name = ""; +} + +ReturnType SignatureGameConfig::GetReturnTypeFromString(const char *str) +{ + if (!strcmp(str, "void")) + return ReturnType_Void; + else if (!strcmp(str, "int")) + return ReturnType_Int; + else if (!strcmp(str, "bool")) + return ReturnType_Bool; + else if (!strcmp(str, "float")) + return ReturnType_Float; + else if (!strcmp(str, "string")) + return ReturnType_String; + else if (!strcmp(str, "stringptr")) + return ReturnType_StringPtr; + else if (!strcmp(str, "charptr")) + return ReturnType_CharPtr; + else if (!strcmp(str, "vector")) + return ReturnType_Vector; + else if (!strcmp(str, "vectorptr")) + return ReturnType_VectorPtr; + else if (!strcmp(str, "cbaseentity")) + return ReturnType_CBaseEntity; + else if (!strcmp(str, "edict")) + return ReturnType_Edict; + + return ReturnType_Unknown; +} + +HookParamType SignatureGameConfig::GetHookParamTypeFromString(const char *str) +{ + if (!strcmp(str, "int")) + return HookParamType_Int; + else if (!strcmp(str, "bool")) + return HookParamType_Bool; + else if (!strcmp(str, "float")) + return HookParamType_Float; + else if (!strcmp(str, "string")) + return HookParamType_String; + else if (!strcmp(str, "stringptr")) + return HookParamType_StringPtr; + else if (!strcmp(str, "charptr")) + return HookParamType_CharPtr; + else if (!strcmp(str, "vectorptr")) + return HookParamType_VectorPtr; + else if (!strcmp(str, "cbaseentity")) + return HookParamType_CBaseEntity; + else if (!strcmp(str, "objectptr")) + return HookParamType_ObjectPtr; + else if (!strcmp(str, "edict")) + return HookParamType_Edict; + else if (!strcmp(str, "object")) + return HookParamType_Object; + + return HookParamType_Unknown; +} + +Register_t SignatureGameConfig::GetCustomRegisterFromString(const char *str) +{ + if (!strcmp(str, "al")) + return AL; + else if (!strcmp(str, "cl")) + return CL; + else if (!strcmp(str, "dl")) + return DL; + else if (!strcmp(str, "bl")) + return BL; + else if (!strcmp(str, "ah")) + return AH; + else if (!strcmp(str, "ch")) + return CH; + else if (!strcmp(str, "dh")) + return DH; + else if (!strcmp(str, "bh")) + return BH; + + else if (!strcmp(str, "ax")) + return AX; + else if (!strcmp(str, "cx")) + return CX; + else if (!strcmp(str, "dx")) + return DX; + else if (!strcmp(str, "bx")) + return BX; + else if (!strcmp(str, "sp")) + return SP; + else if (!strcmp(str, "bp")) + return BP; + else if (!strcmp(str, "si")) + return SI; + else if (!strcmp(str, "di")) + return DI; + + else if (!strcmp(str, "eax")) + return EAX; + else if (!strcmp(str, "ecx")) + return ECX; + else if (!strcmp(str, "edx")) + return EDX; + else if (!strcmp(str, "ebx")) + return EBX; + else if (!strcmp(str, "esp")) + return ESP; + else if (!strcmp(str, "ebp")) + return EBP; + else if (!strcmp(str, "esi")) + return ESI; + else if (!strcmp(str, "edi")) + return EDI; + + else if (!strcmp(str, "mm0")) + return MM0; + else if (!strcmp(str, "mm1")) + return MM1; + else if (!strcmp(str, "mm2")) + return MM2; + else if (!strcmp(str, "mm3")) + return MM3; + else if (!strcmp(str, "mm4")) + return MM4; + else if (!strcmp(str, "mm5")) + return MM5; + else if (!strcmp(str, "mm6")) + return MM6; + else if (!strcmp(str, "mm7")) + return MM7; + + else if (!strcmp(str, "xmm0")) + return XMM0; + else if (!strcmp(str, "xmm1")) + return XMM1; + else if (!strcmp(str, "xmm2")) + return XMM2; + else if (!strcmp(str, "xmm3")) + return XMM3; + else if (!strcmp(str, "xmm4")) + return XMM4; + else if (!strcmp(str, "xmm5")) + return XMM5; + else if (!strcmp(str, "xmm6")) + return XMM6; + else if (!strcmp(str, "xmm7")) + return XMM7; + + else if (!strcmp(str, "cs")) + return CS; + else if (!strcmp(str, "ss")) + return SS; + else if (!strcmp(str, "ds")) + return DS; + else if (!strcmp(str, "es")) + return ES; + else if (!strcmp(str, "fs")) + return FS; + else if (!strcmp(str, "gs")) + return GS; + + else if (!strcmp(str, "st0")) + return ST0; + + return Register_t::None; +} \ No newline at end of file diff --git a/extensions/dhooks/signatures.h b/extensions/dhooks/signatures.h new file mode 100644 index 00000000..1668d231 --- /dev/null +++ b/extensions/dhooks/signatures.h @@ -0,0 +1,53 @@ +#ifndef _INCLUDE_SIGNATURES_H_ +#define _INCLUDE_SIGNATURES_H_ + +#include "extension.h" +#include "util.h" +#include +#include +#include + +struct ArgumentInfo { + ArgumentInfo() : name() + { } + + ArgumentInfo(std::string name, ParamInfo info) : name(name), info(info) + { } + + std::string name; + ParamInfo info; +}; + +class SignatureWrapper { +public: + std::string signature; + std::string address; + std::string offset; + std::vector args; + CallingConvention callConv; + HookType hookType; + ReturnType retType; + ThisPointerType thisType; +}; + +class SignatureGameConfig : public ITextListener_SMC { +public: + SignatureWrapper *GetFunctionSignature(const char *function); +public: + //ITextListener_SMC + SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name); + SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value); + SMCResult ReadSMC_LeavingSection(const SMCStates *states); + void ReadSMC_ParseStart(); + +private: + ReturnType GetReturnTypeFromString(const char *str); + HookParamType GetHookParamTypeFromString(const char *str); + Register_t GetCustomRegisterFromString(const char *str); + +private: + StringHashMap signatures_; +}; + +extern SignatureGameConfig *g_pSignatures; +#endif diff --git a/extensions/dhooks/smsdk_config.h b/extensions/dhooks/smsdk_config.h new file mode 100644 index 00000000..661a1579 --- /dev/null +++ b/extensions/dhooks/smsdk_config.h @@ -0,0 +1,82 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ + +/** + * @file smsdk_config.h + * @brief Contains macros for configuring basic extension information. + */ + +/* Basic information exposed publicly */ +#define SMEXT_CONF_NAME "DHooks" +#define SMEXT_CONF_DESCRIPTION "Dynamic Hooks" +#define SMEXT_CONF_VERSION "2.2.0-detours17" +#define SMEXT_CONF_AUTHOR "Dr!fter and Peace-Maker" +#define SMEXT_CONF_URL "http://www.sourcemod.net/" +#define SMEXT_CONF_LOGTAG "DHOOKS" +#define SMEXT_CONF_LICENSE "GPL" +#define SMEXT_CONF_DATESTRING __DATE__ + +/** + * @brief Exposes plugin's main interface. + */ +#define SMEXT_LINK(name) SDKExtension *g_pExtensionIface = name; + +/** + * @brief Sets whether or not this plugin required Metamod. + * NOTE: Uncomment to enable, comment to disable. + */ +#define SMEXT_CONF_METAMOD + +/** Enable interfaces you want to use here by uncommenting lines */ +//#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_TIMERSYS +//#define SMEXT_ENABLE_THREADER +//#define SMEXT_ENABLE_LIBSYS +//#define SMEXT_ENABLE_MENUS +//#define SMEXT_ENABLE_ADTFACTORY +#define SMEXT_ENABLE_PLUGINSYS +//#define SMEXT_ENABLE_ADMINSYS +//#define SMEXT_ENABLE_TEXTPARSERS +//#define SMEXT_ENABLE_USERMSGS +//#define SMEXT_ENABLE_TRANSLATOR +//#define SMEXT_ENABLE_NINVOKE +//#define SMEXT_ENABLE_ROOTCONSOLEMENU + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/extensions/dhooks/util.cpp b/extensions/dhooks/util.cpp new file mode 100644 index 00000000..f1facf1f --- /dev/null +++ b/extensions/dhooks/util.cpp @@ -0,0 +1,205 @@ +#include "util.h" + +void * GetObjectAddr(HookParamType type, unsigned int flags, void **params, size_t offset) +{ +#ifdef WIN32 + if (type == HookParamType_Object) + return (void *)((intptr_t)params + offset); +#elif POSIX + if (type == HookParamType_Object && !(flags & PASSFLAG_ODTOR)) //Objects are passed by rrefrence if they contain destructors. + return (void *)((intptr_t)params + offset); +#endif + return *(void **)((intptr_t)params + offset); + +} + +size_t GetStackParamOffset(HookParamsStruct *paramStruct, unsigned int index) +{ + assert(paramStruct->dg->params[index].custom_register == None); + + size_t offset = 0; + for (unsigned int i = 0; i < index; i++) + { + // Only care for arguments on the stack before us. + if (paramStruct->dg->params[i].custom_register != None) + continue; + +#ifndef WIN32 + if (paramStruct->dg->params[i].type == HookParamType_Object && (paramStruct->dg->params[i].flags & PASSFLAG_ODTOR)) //Passed by refrence + { + offset += sizeof(void *); + continue; + } +#endif + offset += paramStruct->dg->params[i].size; + } + return offset; +} + +size_t GetRegisterParamOffset(HookParamsStruct *paramStruct, unsigned int index) +{ + // TODO: Fix this up and get a pointer to the CDetour + assert(paramStruct->dg->params[index].custom_register != None); + + // Need to get the size of the stack arguments first. Register arguments are stored after them in the buffer. + size_t stackSize = 0; + for (int i = paramStruct->dg->params.size() - 1; i >= 0; i--) + { + if (paramStruct->dg->params[i].custom_register == None) + stackSize += paramStruct->dg->params[i].size; + } + + size_t offset = stackSize; + for (unsigned int i = 0; i < index; i++) + { + // Only care for arguments passed through a register as well before us. + if (paramStruct->dg->params[i].custom_register == None) + continue; + + offset += paramStruct->dg->params[i].size; + } + return offset; +} + +size_t GetParamOffset(HookParamsStruct *paramStruct, unsigned int index) +{ + if (paramStruct->dg->params[index].custom_register == None) + return GetStackParamOffset(paramStruct, index); + else + return GetRegisterParamOffset(paramStruct, index); +} + +size_t GetParamTypeSize(HookParamType type) +{ + return sizeof(void *); +} + +size_t GetParamsSize(DHooksCallback *dg)//Get the full size, this is for creating the STACK. +{ + size_t res = 0; + + for (int i = dg->params.size() - 1; i >= 0; i--) + { + res += dg->params.at(i).size; + } + + return res; +} + +DataType_t DynamicHooks_ConvertParamTypeFrom(HookParamType type) +{ + switch (type) + { + case HookParamType_Int: + return DATA_TYPE_INT; + case HookParamType_Bool: + return DATA_TYPE_BOOL; + case HookParamType_Float: + return DATA_TYPE_FLOAT; + case HookParamType_StringPtr: + case HookParamType_CharPtr: + case HookParamType_VectorPtr: + case HookParamType_CBaseEntity: + case HookParamType_ObjectPtr: + case HookParamType_Edict: + return DATA_TYPE_POINTER; + case HookParamType_Object: + return DATA_TYPE_OBJECT; + default: + smutils->LogError(myself, "Unhandled parameter type %d!", type); + } + + return DATA_TYPE_POINTER; +} + +DataType_t DynamicHooks_ConvertReturnTypeFrom(ReturnType type) +{ + switch (type) + { + case ReturnType_Void: + return DATA_TYPE_VOID; + case ReturnType_Int: + return DATA_TYPE_INT; + case ReturnType_Bool: + return DATA_TYPE_BOOL; + case ReturnType_Float: + return DATA_TYPE_FLOAT; + case ReturnType_StringPtr: + case ReturnType_CharPtr: + case ReturnType_VectorPtr: + case ReturnType_CBaseEntity: + case ReturnType_Edict: + return DATA_TYPE_POINTER; + case ReturnType_Vector: + return DATA_TYPE_OBJECT; + default: + smutils->LogError(myself, "Unhandled return type %d!", type); + } + + return DATA_TYPE_VOID; +} + +Register_t DynamicHooks_ConvertRegisterFrom(PluginRegister reg) +{ + switch (reg) + { + case DHookRegister_Default: + return None; + + case DHookRegister_AL: + return AL; + case DHookRegister_CL: + return CL; + case DHookRegister_DL: + return DL; + case DHookRegister_BL: + return BL; + case DHookRegister_AH: + return AH; + case DHookRegister_CH: + return CH; + case DHookRegister_DH: + return DH; + case DHookRegister_BH: + return BH; + + case DHookRegister_EAX: + return EAX; + case DHookRegister_ECX: + return ECX; + case DHookRegister_EDX: + return EDX; + case DHookRegister_EBX: + return EBX; + case DHookRegister_ESP: + return ESP; + case DHookRegister_EBP: + return EBP; + case DHookRegister_ESI: + return ESI; + case DHookRegister_EDI: + return EDI; + + case DHookRegister_XMM0: + return XMM0; + case DHookRegister_XMM1: + return XMM1; + case DHookRegister_XMM2: + return XMM2; + case DHookRegister_XMM3: + return XMM3; + case DHookRegister_XMM4: + return XMM4; + case DHookRegister_XMM5: + return XMM5; + case DHookRegister_XMM6: + return XMM6; + case DHookRegister_XMM7: + return XMM7; + + case DHookRegister_ST0: + return ST0; + } + + return None; +} diff --git a/extensions/dhooks/util.h b/extensions/dhooks/util.h new file mode 100644 index 00000000..8b1e068e --- /dev/null +++ b/extensions/dhooks/util.h @@ -0,0 +1,54 @@ +#ifndef _INCLUDE_UTIL_FUNCTIONS_H_ +#define _INCLUDE_UTIL_FUNCTIONS_H_ + +#include "vhook.h" +#include "convention.h" + +enum PluginRegister +{ + // Don't change the register and use the default for the calling convention. + DHookRegister_Default, + + // 8-bit general purpose registers + DHookRegister_AL, + DHookRegister_CL, + DHookRegister_DL, + DHookRegister_BL, + DHookRegister_AH, + DHookRegister_CH, + DHookRegister_DH, + DHookRegister_BH, + + // 32-bit general purpose registers + DHookRegister_EAX, + DHookRegister_ECX, + DHookRegister_EDX, + DHookRegister_EBX, + DHookRegister_ESP, + DHookRegister_EBP, + DHookRegister_ESI, + DHookRegister_EDI, + + // 128-bit XMM registers + DHookRegister_XMM0, + DHookRegister_XMM1, + DHookRegister_XMM2, + DHookRegister_XMM3, + DHookRegister_XMM4, + DHookRegister_XMM5, + DHookRegister_XMM6, + DHookRegister_XMM7, + + // 80-bit FPU registers + DHookRegister_ST0 +}; + +size_t GetParamOffset(HookParamsStruct *params, unsigned int index); +void * GetObjectAddr(HookParamType type, unsigned int flags, void **params, size_t offset); +size_t GetParamTypeSize(HookParamType type); +size_t GetParamsSize(DHooksCallback *dg); + +DataType_t DynamicHooks_ConvertParamTypeFrom(HookParamType type); +DataType_t DynamicHooks_ConvertReturnTypeFrom(ReturnType type); +Register_t DynamicHooks_ConvertRegisterFrom(PluginRegister reg); +#endif diff --git a/extensions/dhooks/version.rc b/extensions/dhooks/version.rc new file mode 100644 index 00000000..076f9728 --- /dev/null +++ b/extensions/dhooks/version.rc @@ -0,0 +1,45 @@ +#include "winres.h" + +#include + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif + +#ifndef SM_GENERATED_BUILD +#define BINARY_NAME "dhooks.ext.dll\0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION SM_VERSION_FILE + PRODUCTVERSION SM_VERSION_FILE + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "DHooks Extension" + VALUE "FileDescription", "SourceMod Dynamic Hooks Extension" + VALUE "FileVersion", SM_VERSION_STRING + VALUE "InternalName", "SourceMod DHooks Extension" + VALUE "LegalCopyright", "Copyright (c) 2021, AlliedModders LLC" + VALUE "OriginalFilename", BINARY_NAME + VALUE "ProductName", "SourceMod DHooks Extension" + VALUE "ProductVersion", SM_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 0x04B0 + END +END diff --git a/extensions/dhooks/vfunc_call.h b/extensions/dhooks/vfunc_call.h new file mode 100644 index 00000000..09cb6849 --- /dev/null +++ b/extensions/dhooks/vfunc_call.h @@ -0,0 +1,324 @@ +#ifndef _INCLUDE_VFUNC_CALL_H_ +#define _INCLUDE_VFUNC_CALL_H_ + +#include "vhook.h" +#include "extension.h" +#include "util.h" + +#define PARAMINFO_SWITCH(passType) \ + paramInfo[i].flags = dg->params.at(i).flags; \ + paramInfo[i].size = dg->params.at(i).size; \ + paramInfo[i].type = passType; + +#define VSTK_PARAM_SWITCH(paramType) \ + if(paramStruct->isChanged[i]) \ + { \ + *(paramType *)vptr = *(paramType *)newAddr; \ + } \ + else \ + { \ + *(paramType *)vptr = *(paramType *)orgAddr; \ + } \ + if(i + 1 != dg->params.size()) \ + { \ + vptr += dg->params.at(i).size; \ + } \ + break; + +#define VSTK_PARAM_SWITCH_OBJECT() \ + memcpy(vptr, objAddr, dg->params.at(i).size); \ + if(i + 1 != dg->params.size()) \ + { \ + vptr += dg->params.at(i).size; \ + } \ + break; + +template +T CallVFunction(DHooksCallback *dg, HookParamsStruct *paramStruct, void *iface) +{ + SourceMod::PassInfo *paramInfo = NULL; + SourceMod::PassInfo returnInfo; + + if(dg->returnType != ReturnType_Void) + { + returnInfo.flags = dg->returnFlag; + returnInfo.size = sizeof(T); + if( dg->returnType != ReturnType_Vector) + { + returnInfo.type = PassType_Basic; + } + else + { + returnInfo.type = PassType_Object; + } + } + + ICallWrapper *pCall; + + size_t size = GetParamsSize(dg); + + unsigned char *vstk = (unsigned char *)malloc(sizeof(void *) + size); + unsigned char *vptr = vstk; + + *(void **)vptr = iface; + + if(paramStruct) + { + vptr += sizeof(void *); + paramInfo = (SourceMod::PassInfo *)malloc(sizeof(SourceMod::PassInfo) * dg->params.size()); + + for(int i = 0; i < (int)dg->params.size(); i++) + { + size_t offset = GetParamOffset(paramStruct, i); + + void *orgAddr = (void **)((intptr_t)paramStruct->orgParams + offset); + void *newAddr = (void **)((intptr_t)paramStruct->newParams + offset); + + switch(dg->params.at(i).type) + { + case HookParamType_Int: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(int); + case HookParamType_Bool: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(cell_t); + case HookParamType_Float: + PARAMINFO_SWITCH(PassType_Float); + VSTK_PARAM_SWITCH(float); + case HookParamType_String: + PARAMINFO_SWITCH(PassType_Object); + VSTK_PARAM_SWITCH(string_t); + case HookParamType_StringPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(string_t *); + case HookParamType_CharPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(char *); + case HookParamType_VectorPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(SDKVector *); + case HookParamType_CBaseEntity: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(CBaseEntity *); + case HookParamType_Edict: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(edict_t *); + case HookParamType_Object: + { + void *objAddr = GetObjectAddr(HookParamType_Object, paramStruct->dg->params.at(i).flags, paramStruct->orgParams, offset); + PARAMINFO_SWITCH(PassType_Object); + VSTK_PARAM_SWITCH_OBJECT(); + } + default: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(void *); + } + } + } + + T ret = 0; + + if(dg->returnType == ReturnType_Void) + { + pCall = g_pBinTools->CreateVCall(dg->offset, 0, 0, NULL, paramInfo, dg->params.size()); + pCall->Execute(vstk, NULL); + } + else + { + pCall = g_pBinTools->CreateVCall(dg->offset, 0, 0, &returnInfo, paramInfo, dg->params.size()); + pCall->Execute(vstk, &ret); + } + + pCall->Destroy(); + free(vstk); + + if(paramInfo != NULL) + { + free(paramInfo); + } + + return ret; +} +template <> +SDKVector CallVFunction(DHooksCallback *dg, HookParamsStruct *paramStruct, void *iface) +{ + SourceMod::PassInfo *paramInfo = NULL; + SourceMod::PassInfo returnInfo; + + if(dg->returnType != ReturnType_Void) + { + returnInfo.flags = dg->returnFlag; + returnInfo.size = sizeof(SDKVector); + returnInfo.type = PassType_Object; + } + + ICallWrapper *pCall; + + size_t size = GetParamsSize(dg); + + unsigned char *vstk = (unsigned char *)malloc(sizeof(void *) + size); + unsigned char *vptr = vstk; + + *(void **)vptr = iface; + + if(paramStruct) + { + vptr += sizeof(void *); + paramInfo = (SourceMod::PassInfo *)malloc(sizeof(SourceMod::PassInfo) * dg->params.size()); + for(int i = 0; i < (int)dg->params.size(); i++) + { + size_t offset = GetParamOffset(paramStruct, i); + + void *orgAddr = *(void **)((intptr_t)paramStruct->orgParams + offset); + void *newAddr = *(void **)((intptr_t)paramStruct->newParams + offset); + + switch (dg->params.at(i).type) + { + case HookParamType_Int: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(int); + case HookParamType_Bool: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(cell_t); + case HookParamType_Float: + PARAMINFO_SWITCH(PassType_Float); + VSTK_PARAM_SWITCH(float); + case HookParamType_String: + PARAMINFO_SWITCH(PassType_Object); + VSTK_PARAM_SWITCH(string_t); + case HookParamType_StringPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(string_t *); + case HookParamType_CharPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(char *); + case HookParamType_VectorPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(SDKVector *); + case HookParamType_CBaseEntity: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(CBaseEntity *); + case HookParamType_Edict: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(edict_t *); + case HookParamType_Object: + { + void *objAddr = GetObjectAddr(HookParamType_Object, paramStruct->dg->params.at(i).flags, paramStruct->orgParams, offset); + PARAMINFO_SWITCH(PassType_Object); + VSTK_PARAM_SWITCH_OBJECT(); + } + default: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(void *); + } + } + } + + SDKVector ret; + + pCall = g_pBinTools->CreateVCall(dg->offset, 0, 0, &returnInfo, paramInfo, dg->params.size()); + pCall->Execute(vstk, &ret); + + pCall->Destroy(); + free(vstk); + + if(paramInfo != NULL) + { + free(paramInfo); + } + + return ret; +} +#ifndef WIN32 +template <> +string_t CallVFunction(DHooksCallback *dg, HookParamsStruct *paramStruct, void *iface) +{ + SourceMod::PassInfo *paramInfo = NULL; + SourceMod::PassInfo returnInfo; + + if(dg->returnType != ReturnType_Void) + { + returnInfo.flags = dg->returnFlag; + returnInfo.size = sizeof(string_t); + returnInfo.type = PassType_Object; + } + + ICallWrapper *pCall; + + size_t size = GetParamsSize(dg); + + unsigned char *vstk = (unsigned char *)malloc(sizeof(void *) + size); + unsigned char *vptr = vstk; + + *(void **)vptr = iface; + + if(paramStruct) + { + vptr += sizeof(void *); + paramInfo = (SourceMod::PassInfo *)malloc(sizeof(SourceMod::PassInfo) * dg->params.size()); + for(int i = 0; i < dg->params.size(); i++) + { + size_t offset = GetParamOffset(paramStruct, i); + + void *orgAddr = *(void **)((intptr_t)paramStruct->orgParams + offset); + void *newAddr = *(void **)((intptr_t)paramStruct->newParams + offset); + + switch (dg->params.at(i).type) + { + case HookParamType_Int: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(int); + case HookParamType_Bool: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(cell_t); + case HookParamType_Float: + PARAMINFO_SWITCH(PassType_Float); + VSTK_PARAM_SWITCH(float); + case HookParamType_String: + PARAMINFO_SWITCH(PassType_Object); + VSTK_PARAM_SWITCH(string_t); + case HookParamType_StringPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(string_t *); + case HookParamType_CharPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(char *); + case HookParamType_VectorPtr: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(SDKVector *); + case HookParamType_CBaseEntity: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(CBaseEntity *); + case HookParamType_Edict: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(edict_t *); + case HookParamType_Object: + { + void *objAddr = GetObjectAddr(HookParamType_Object, paramStruct->dg->params.at(i).flags, paramStruct->orgParams, offset); + PARAMINFO_SWITCH(PassType_Object); + VSTK_PARAM_SWITCH_OBJECT(); + } + default: + PARAMINFO_SWITCH(PassType_Basic); + VSTK_PARAM_SWITCH(void *); + } + } + } + + string_t ret; + + pCall = g_pBinTools->CreateVCall(dg->offset, 0, 0, &returnInfo, paramInfo, dg->params.size()); + pCall->Execute(vstk, &ret); + + pCall->Destroy(); + free(vstk); + + if(paramInfo != NULL) + { + free(paramInfo); + } + + return ret; +} +#endif +#endif diff --git a/extensions/dhooks/vhook.cpp b/extensions/dhooks/vhook.cpp new file mode 100644 index 00000000..7eec21db --- /dev/null +++ b/extensions/dhooks/vhook.cpp @@ -0,0 +1,1007 @@ +#include "vhook.h" +#include "vfunc_call.h" +#include "util.h" +#include + +SourceHook::IHookManagerAutoGen *g_pHookManager = NULL; + +std::vector g_pHooks; + +using namespace SourceHook; +using namespace sp; + +#ifdef WIN32 +#define OBJECT_OFFSET sizeof(void *) +#else +#define OBJECT_OFFSET (sizeof(void *)*2) +#endif + +#ifndef WIN32 +void *GenerateThunk(ReturnType type) +{ + sp::MacroAssembler masm; + static const size_t kStackNeeded = (2) * 4; // 2 args max + static const size_t kReserve = ke::Align(kStackNeeded + 8, 16) - 8; + + masm.push(ebp); + masm.movl(ebp, esp); + masm.subl(esp, kReserve); + if (type != ReturnType_String && type != ReturnType_Vector) + { + masm.lea(eax, Operand(ebp, 12)); // grab the incoming caller argument vector + masm.movl(Operand(esp, 1 * 4), eax); // set that as the 2nd argument + masm.movl(eax, Operand(ebp, 8)); // grab the |this| + masm.movl(Operand(esp, 0 * 4), eax); // set |this| as the 1st argument*/ + } + else + { + masm.lea(eax, Operand(ebp, 8)); // grab the incoming caller argument vector + masm.movl(Operand(esp, 1 * 4), eax); // set that as the 2nd argument + masm.movl(eax, Operand(ebp, 12)); // grab the |this| + masm.movl(Operand(esp, 0 * 4), eax); // set |this| as the 1st argument*/ + } + if (type == ReturnType_Float) + { + masm.call(ExternalAddress((void *)Callback_float)); + } + else if (type == ReturnType_Vector) + { + masm.call(ExternalAddress((void *)Callback_vector)); + } + else if (type == ReturnType_String) + { + masm.call(ExternalAddress((void *)Callback_stringt)); + } + else + { + masm.call(ExternalAddress((void *)Callback)); + } + masm.addl(esp, kReserve); + masm.pop(ebp); // restore ebp + masm.ret(); + + void *base = g_pSM->GetScriptingEngine()->AllocatePageMemory(masm.length()); + masm.emitToExecutableMemory(base); + return base; +} +#else +// HUGE THANKS TO BAILOPAN (dvander)! +void *GenerateThunk(ReturnType type) +{ + sp::MacroAssembler masm; + static const size_t kStackNeeded = (3 + 1) * 4; // 3 args max, 1 locals max + static const size_t kReserve = ke::Align(kStackNeeded + 8, 16) - 8; + + masm.push(ebp); + masm.movl(ebp, esp); + masm.subl(esp, kReserve); + masm.lea(eax, Operand(esp, 3 * 4)); // ptr to 2nd var after argument space + masm.movl(Operand(esp, 2 * 4), eax); // set the ptr as the third argument + masm.lea(eax, Operand(ebp, 8)); // grab the incoming caller argument vector + masm.movl(Operand(esp, 1 * 4), eax); // set that as the 2nd argument + masm.movl(Operand(esp, 0 * 4), ecx); // set |this| as the 1st argument + if (type == ReturnType_Float) + { + masm.call(ExternalAddress(Callback_float)); + } + else if (type == ReturnType_Vector) + { + masm.call(ExternalAddress(Callback_vector)); + } + else + { + masm.call(ExternalAddress(Callback)); + } + masm.movl(ecx, Operand(esp, 3 * 4)); + masm.addl(esp, kReserve); + masm.pop(ebp); // restore ebp + masm.pop(edx); // grab return address in edx + masm.addl(esp, ecx); // remove arguments + masm.jmp(edx); // return to caller + + void *base = g_pSM->GetScriptingEngine()->AllocatePageMemory(masm.length()); + masm.emitToExecutableMemory(base); + return base; +} +#endif + +DHooksManager::DHooksManager(HookSetup *setup, void *iface, IPluginFunction *remove_callback, IPluginFunction *plugincb, bool post) +{ + this->callback = MakeHandler(setup->returnType); + this->hookid = 0; + this->remove_callback = remove_callback; + this->callback->offset = setup->offset; + this->callback->plugin_callback = plugincb; + this->callback->returnFlag = setup->returnFlag; + this->callback->thisType = setup->thisType; + this->callback->post = post; + this->callback->hookType = setup->hookType; + this->callback->params = setup->params; + + this->addr = 0; + + if(this->callback->hookType == HookType_Entity) + { + this->callback->entity = gamehelpers->EntityToBCompatRef((CBaseEntity *)iface); + } + else + { + if(this->callback->hookType == HookType_Raw) + { + this->addr = (intptr_t)iface; + } + this->callback->entity = -1; + } + + CProtoInfoBuilder protoInfo(ProtoInfo::CallConv_ThisCall); + + for(int i = this->callback->params.size() -1; i >= 0; i--) + { + protoInfo.AddParam(this->callback->params.at(i).size, this->callback->params.at(i).pass_type, PASSFLAG_BYVAL, NULL, NULL, NULL, NULL);//This seems like we need to do something about it at some point... + } + + if(this->callback->returnType == ReturnType_Void) + { + protoInfo.SetReturnType(0, SourceHook::PassInfo::PassType_Unknown, 0, NULL, NULL, NULL, NULL); + } + else if(this->callback->returnType == ReturnType_Float) + { + protoInfo.SetReturnType(sizeof(float), SourceHook::PassInfo::PassType_Float, setup->returnFlag, NULL, NULL, NULL, NULL); + } + else if(this->callback->returnType == ReturnType_String) + { + protoInfo.SetReturnType(sizeof(string_t), SourceHook::PassInfo::PassType_Object, setup->returnFlag, NULL, NULL, NULL, NULL);//We have to be 4 really... or else RIP + } + else if(this->callback->returnType == ReturnType_Vector) + { + protoInfo.SetReturnType(sizeof(SDKVector), SourceHook::PassInfo::PassType_Object, setup->returnFlag, NULL, NULL, NULL, NULL); + } + else + { + protoInfo.SetReturnType(sizeof(void *), SourceHook::PassInfo::PassType_Basic, setup->returnFlag, NULL, NULL, NULL, NULL); + } + this->pManager = g_pHookManager->MakeHookMan(protoInfo, 0, this->callback->offset); + + this->hookid = g_SHPtr->AddHook(g_PLID,ISourceHook::Hook_Normal, iface, 0, this->pManager, this->callback, this->callback->post); +} + +void CleanupHooks(IPluginContext *pContext) +{ + for(int i = g_pHooks.size() -1; i >= 0; i--) + { + DHooksManager *manager = g_pHooks.at(i); + + if(pContext == NULL || pContext == manager->callback->plugin_callback->GetParentRuntime()->GetDefaultContext()) + { + delete manager; + g_pHooks.erase(g_pHooks.begin() + i); + } + } +} + +bool SetupHookManager(ISmmAPI *ismm) +{ + g_pHookManager = static_cast(ismm->MetaFactory(MMIFACE_SH_HOOKMANAUTOGEN, NULL, NULL)); + + return g_pHookManager != NULL; +} + +SourceHook::PassInfo::PassType GetParamTypePassType(HookParamType type) +{ + switch(type) + { + case HookParamType_Float: + return SourceHook::PassInfo::PassType_Float; + case HookParamType_Object: + return SourceHook::PassInfo::PassType_Object; + } + return SourceHook::PassInfo::PassType_Basic; +} + +size_t GetStackArgsSize(DHooksCallback *dg) +{ + size_t res = GetParamsSize(dg); + #ifdef WIN32 + if(dg->returnType == ReturnType_Vector)//Account for result vector ptr. + #else + if(dg->returnType == ReturnType_Vector || dg->returnType == ReturnType_String) + #endif + { + res += OBJECT_OFFSET; + } + return res; +} + +HookReturnStruct::~HookReturnStruct() +{ + if (this->type == ReturnType_String || this->type == ReturnType_Int || this->type == ReturnType_Bool || this->type == ReturnType_Float || this->type == ReturnType_Vector) + { + free(this->newResult); + free(this->orgResult); + } +} + +HookParamsStruct::~HookParamsStruct() +{ + if (this->orgParams != NULL) + { + free(this->orgParams); + } + if (this->isChanged != NULL) + { + free(this->isChanged); + } + if (this->newParams != NULL) + { + free(this->newParams); + } +} + +HookParamsStruct *GetParamStruct(DHooksCallback *dg, void **argStack, size_t argStackSize) +{ + HookParamsStruct *params = new HookParamsStruct(); + params->dg = dg; + #ifdef WIN32 + if(dg->returnType != ReturnType_Vector) + #else + if(dg->returnType != ReturnType_Vector && dg->returnType != ReturnType_String) + #endif + { + params->orgParams = (void **)malloc(argStackSize); + memcpy(params->orgParams, argStack, argStackSize); + } + else //Offset result ptr + { + params->orgParams = (void **)malloc(argStackSize-OBJECT_OFFSET); + memcpy(params->orgParams, argStack+OBJECT_OFFSET, argStackSize-OBJECT_OFFSET); + } + size_t paramsSize = GetParamsSize(dg); + + params->newParams = (void **)malloc(paramsSize); + params->isChanged = (bool *)malloc(dg->params.size() * sizeof(bool)); + + for (unsigned int i = 0; i < dg->params.size(); i++) + { + *(void **)((intptr_t)params->newParams + GetParamOffset(params, i)) = NULL; + params->isChanged[i] = false; + } + + return params; +} + +HookReturnStruct *GetReturnStruct(DHooksCallback *dg) +{ + HookReturnStruct *res = new HookReturnStruct(); + res->isChanged = false; + res->type = dg->returnType; + res->orgResult = NULL; + res->newResult = NULL; + + if(g_SHPtr->GetOrigRet() && dg->post) + { + switch(dg->returnType) + { + case ReturnType_String: + res->orgResult = malloc(sizeof(string_t)); + res->newResult = malloc(sizeof(string_t)); + *(string_t *)res->orgResult = META_RESULT_ORIG_RET(string_t); + break; + case ReturnType_Int: + res->orgResult = malloc(sizeof(int)); + res->newResult = malloc(sizeof(int)); + *(int *)res->orgResult = META_RESULT_ORIG_RET(int); + break; + case ReturnType_Bool: + res->orgResult = malloc(sizeof(bool)); + res->newResult = malloc(sizeof(bool)); + *(bool *)res->orgResult = META_RESULT_ORIG_RET(bool); + break; + case ReturnType_Float: + res->orgResult = malloc(sizeof(float)); + res->newResult = malloc(sizeof(float)); + *(float *)res->orgResult = META_RESULT_ORIG_RET(float); + break; + case ReturnType_Vector: + { + res->orgResult = malloc(sizeof(SDKVector)); + res->newResult = malloc(sizeof(SDKVector)); + SDKVector vec = META_RESULT_ORIG_RET(SDKVector); + *(SDKVector *)res->orgResult = vec; + break; + } + default: + res->orgResult = META_RESULT_ORIG_RET(void *); + break; + } + } + else + { + switch(dg->returnType) + { + case ReturnType_String: + res->orgResult = malloc(sizeof(string_t)); + res->newResult = malloc(sizeof(string_t)); + *(string_t *)res->orgResult = NULL_STRING; + break; + case ReturnType_Vector: + res->orgResult = malloc(sizeof(SDKVector)); + res->newResult = malloc(sizeof(SDKVector)); + *(SDKVector *)res->orgResult = SDKVector(); + break; + case ReturnType_Int: + res->orgResult = malloc(sizeof(int)); + res->newResult = malloc(sizeof(int)); + *(int *)res->orgResult = 0; + break; + case ReturnType_Bool: + res->orgResult = malloc(sizeof(bool)); + res->newResult = malloc(sizeof(bool)); + *(bool *)res->orgResult = false; + break; + case ReturnType_Float: + res->orgResult = malloc(sizeof(float)); + res->newResult = malloc(sizeof(float)); + *(float *)res->orgResult = 0.0; + break; + } + } + + return res; +} + +cell_t GetThisPtr(void *iface, ThisPointerType type) +{ + if(type == ThisPointer_CBaseEntity) + { + if (!iface) + return -1; + return gamehelpers->EntityToBCompatRef((CBaseEntity *)iface); + } + + return (cell_t)iface; +} + +#ifdef WIN32 +void *Callback(DHooksCallback *dg, void **argStack, size_t *argsizep) +#else +void *Callback(DHooksCallback *dg, void **argStack) +#endif +{ + HookReturnStruct *returnStruct = NULL; + HookParamsStruct *paramStruct = NULL; + Handle_t rHndl; + Handle_t pHndl; + + #ifdef WIN32 + *argsizep = GetStackArgsSize(dg); + #else + size_t argsize = GetStackArgsSize(dg); + #endif + + if(dg->thisType == ThisPointer_CBaseEntity || dg->thisType == ThisPointer_Address) + { + dg->plugin_callback->PushCell(GetThisPtr(g_SHPtr->GetIfacePtr(), dg->thisType)); + } + if(dg->returnType != ReturnType_Void) + { + returnStruct = GetReturnStruct(dg); + rHndl = handlesys->CreateHandle(g_HookReturnHandle, returnStruct, dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), NULL); + if(!rHndl) + { + dg->plugin_callback->Cancel(); + if(returnStruct) + { + delete returnStruct; + } + g_SHPtr->SetRes(MRES_IGNORED); + return NULL; + } + dg->plugin_callback->PushCell(rHndl); + } + + #ifdef WIN32 + if(*argsizep > 0) + { + paramStruct = GetParamStruct(dg, argStack, *argsizep); + #else + if(argsize > 0) + { + paramStruct = GetParamStruct(dg, argStack, argsize); + #endif + pHndl = handlesys->CreateHandle(g_HookParamsHandle, paramStruct, dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), NULL); + if(!pHndl) + { + dg->plugin_callback->Cancel(); + if(returnStruct) + { + HandleSecurity sec(dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity()); + handlesys->FreeHandle(rHndl, &sec); + } + if(paramStruct) + { + delete paramStruct; + } + g_SHPtr->SetRes(MRES_IGNORED); + return NULL; + } + dg->plugin_callback->PushCell(pHndl); + } + cell_t result = (cell_t)MRES_Ignored; + META_RES mres = MRES_IGNORED; + + dg->plugin_callback->Execute(&result); + + void *ret = g_SHPtr->GetOverrideRetPtr(); + switch((MRESReturn)result) + { + case MRES_Handled: + case MRES_ChangedHandled: + g_SHPtr->DoRecall(); + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + ret = CallVFunction(dg, paramStruct, g_SHPtr->GetIfacePtr()); + break; + case MRES_ChangedOverride: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + if(dg->returnType == ReturnType_String || dg->returnType == ReturnType_Int || dg->returnType == ReturnType_Bool) + { + ret = *(void **)returnStruct->newResult; + } + else + { + ret = returnStruct->newResult; + } + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + break; + } + } + g_SHPtr->DoRecall(); + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + CallVFunction(dg, paramStruct, g_SHPtr->GetIfacePtr()); + break; + case MRES_Override: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + g_SHPtr->SetRes(MRES_OVERRIDE); + mres = MRES_OVERRIDE; + if(dg->returnType == ReturnType_String || dg->returnType == ReturnType_Int || dg->returnType == ReturnType_Bool) + { + ret = *(void **)returnStruct->newResult; + } + else + { + ret = returnStruct->newResult; + } + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + } + } + break; + case MRES_Supercede: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + if(dg->returnType == ReturnType_String || dg->returnType == ReturnType_Int || dg->returnType == ReturnType_Bool) + { + ret = *(void **)returnStruct->newResult; + } + else + { + ret = returnStruct->newResult; + } + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + } + } + else + { + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + } + break; + default: + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + break; + } + + HandleSecurity sec(dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity()); + + if(returnStruct) + { + handlesys->FreeHandle(rHndl, &sec); + } + if(paramStruct) + { + handlesys->FreeHandle(pHndl, &sec); + } + + if(dg->returnType == ReturnType_Void || mres <= MRES_HANDLED) + { + return NULL; + } + return ret; +} +#ifdef WIN32 +float Callback_float(DHooksCallback *dg, void **argStack, size_t *argsizep) +#else +float Callback_float(DHooksCallback *dg, void **argStack) +#endif +{ + HookReturnStruct *returnStruct = NULL; + HookParamsStruct *paramStruct = NULL; + Handle_t rHndl; + Handle_t pHndl; + + #ifdef WIN32 + *argsizep = GetStackArgsSize(dg); + #else + size_t argsize = GetStackArgsSize(dg); + #endif + + if(dg->thisType == ThisPointer_CBaseEntity || dg->thisType == ThisPointer_Address) + { + dg->plugin_callback->PushCell(GetThisPtr(g_SHPtr->GetIfacePtr(), dg->thisType)); + } + + returnStruct = GetReturnStruct(dg); + rHndl = handlesys->CreateHandle(g_HookReturnHandle, returnStruct, dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), NULL); + + if(!rHndl) + { + dg->plugin_callback->Cancel(); + if(returnStruct) + { + delete returnStruct; + } + g_SHPtr->SetRes(MRES_IGNORED); + return 0.0; + } + dg->plugin_callback->PushCell(rHndl); + + #ifdef WIN32 + if(*argsizep > 0) + { + paramStruct = GetParamStruct(dg, argStack, *argsizep); + #else + if(argsize > 0) + { + paramStruct = GetParamStruct(dg, argStack, argsize); + #endif + pHndl = handlesys->CreateHandle(g_HookParamsHandle, paramStruct, dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), NULL); + if(!pHndl) + { + dg->plugin_callback->Cancel(); + if(returnStruct) + { + delete returnStruct; + } + if(paramStruct) + { + delete paramStruct; + } + g_SHPtr->SetRes(MRES_IGNORED); + return 0.0; + } + dg->plugin_callback->PushCell(pHndl); + } + cell_t result = (cell_t)MRES_Ignored; + META_RES mres = MRES_IGNORED; + dg->plugin_callback->Execute(&result); + + void *ret = g_SHPtr->GetOverrideRetPtr(); + switch((MRESReturn)result) + { + case MRES_Handled: + case MRES_ChangedHandled: + g_SHPtr->DoRecall(); + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + *(float *)ret = CallVFunction(dg, paramStruct, g_SHPtr->GetIfacePtr()); + break; + case MRES_ChangedOverride: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + *(float *)ret = *(float *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + break; + } + } + g_SHPtr->DoRecall(); + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + CallVFunction(dg, paramStruct, g_SHPtr->GetIfacePtr()); + break; + case MRES_Override: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + g_SHPtr->SetRes(MRES_OVERRIDE); + mres = MRES_OVERRIDE; + *(float *)ret = *(float *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + } + } + break; + case MRES_Supercede: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + *(float *)ret = *(float *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + } + } + break; + default: + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + break; + } + + HandleSecurity sec(dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity()); + + if(returnStruct) + { + handlesys->FreeHandle(rHndl, &sec); + } + if(paramStruct) + { + handlesys->FreeHandle(pHndl, &sec); + } + + if(dg->returnType == ReturnType_Void || mres <= MRES_HANDLED) + { + return 0.0; + } + return *(float *)ret; +} +#ifdef WIN32 +SDKVector *Callback_vector(DHooksCallback *dg, void **argStack, size_t *argsizep) +#else +SDKVector *Callback_vector(DHooksCallback *dg, void **argStack) +#endif +{ + SDKVector *vec_result = (SDKVector *)argStack[0]; // Save the result + + HookReturnStruct *returnStruct = NULL; + HookParamsStruct *paramStruct = NULL; + Handle_t rHndl; + Handle_t pHndl; + + #ifdef WIN32 + *argsizep = GetStackArgsSize(dg); + #else + size_t argsize = GetStackArgsSize(dg); + #endif + + if(dg->thisType == ThisPointer_CBaseEntity || dg->thisType == ThisPointer_Address) + { + dg->plugin_callback->PushCell(GetThisPtr(g_SHPtr->GetIfacePtr(), dg->thisType)); + } + + returnStruct = GetReturnStruct(dg); + rHndl = handlesys->CreateHandle(g_HookReturnHandle, returnStruct, dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), NULL); + + if(!rHndl) + { + dg->plugin_callback->Cancel(); + if(returnStruct) + { + delete returnStruct; + } + g_SHPtr->SetRes(MRES_IGNORED); + return NULL; + } + dg->plugin_callback->PushCell(rHndl); + + #ifdef WIN32 + if(*argsizep > 0) + { + paramStruct = GetParamStruct(dg, argStack, *argsizep); + #else + if(argsize > 0) + { + paramStruct = GetParamStruct(dg, argStack, argsize); + #endif + pHndl = handlesys->CreateHandle(g_HookParamsHandle, paramStruct, dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), NULL); + if(!pHndl) + { + dg->plugin_callback->Cancel(); + if(returnStruct) + { + delete returnStruct; + } + if(paramStruct) + { + delete paramStruct; + } + g_SHPtr->SetRes(MRES_IGNORED); + return NULL; + } + dg->plugin_callback->PushCell(pHndl); + } + cell_t result = (cell_t)MRES_Ignored; + META_RES mres = MRES_IGNORED; + dg->plugin_callback->Execute(&result); + + void *ret = g_SHPtr->GetOverrideRetPtr(); + ret = vec_result; + switch((MRESReturn)result) + { + case MRES_Handled: + case MRES_ChangedHandled: + g_SHPtr->DoRecall(); + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + *vec_result = CallVFunction(dg, paramStruct, g_SHPtr->GetIfacePtr()); + break; + case MRES_ChangedOverride: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + *vec_result = *(SDKVector *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + break; + } + } + g_SHPtr->DoRecall(); + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + CallVFunction(dg, paramStruct, g_SHPtr->GetIfacePtr()); + break; + case MRES_Override: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + g_SHPtr->SetRes(MRES_OVERRIDE); + mres = MRES_OVERRIDE; + *vec_result = *(SDKVector *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + } + } + break; + case MRES_Supercede: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + *vec_result = *(SDKVector *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + } + } + break; + default: + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + break; + } + + HandleSecurity sec(dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity()); + + if(returnStruct) + { + handlesys->FreeHandle(rHndl, &sec); + } + if(paramStruct) + { + handlesys->FreeHandle(pHndl, &sec); + } + + if(dg->returnType == ReturnType_Void || mres <= MRES_HANDLED) + { + vec_result->x = 0; + vec_result->y = 0; + vec_result->z = 0; + return vec_result; + } + return vec_result; +} + +#ifndef WIN32 +string_t *Callback_stringt(DHooksCallback *dg, void **argStack) +{ + string_t *string_result = (string_t *)argStack[0]; // Save the result + + HookReturnStruct *returnStruct = NULL; + HookParamsStruct *paramStruct = NULL; + Handle_t rHndl; + Handle_t pHndl; + + size_t argsize = GetStackArgsSize(dg); + + if(dg->thisType == ThisPointer_CBaseEntity || dg->thisType == ThisPointer_Address) + { + dg->plugin_callback->PushCell(GetThisPtr(g_SHPtr->GetIfacePtr(), dg->thisType)); + } + + returnStruct = GetReturnStruct(dg); + rHndl = handlesys->CreateHandle(g_HookReturnHandle, returnStruct, dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), NULL); + + if(!rHndl) + { + dg->plugin_callback->Cancel(); + if(returnStruct) + { + delete returnStruct; + } + g_SHPtr->SetRes(MRES_IGNORED); + return NULL; + } + dg->plugin_callback->PushCell(rHndl); + + if(argsize > 0) + { + paramStruct = GetParamStruct(dg, argStack, argsize); + pHndl = handlesys->CreateHandle(g_HookParamsHandle, paramStruct, dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), NULL); + if(!pHndl) + { + dg->plugin_callback->Cancel(); + if(returnStruct) + { + delete returnStruct; + } + if(paramStruct) + { + delete paramStruct; + } + g_SHPtr->SetRes(MRES_IGNORED); + return NULL; + } + dg->plugin_callback->PushCell(pHndl); + } + cell_t result = (cell_t)MRES_Ignored; + META_RES mres = MRES_IGNORED; + dg->plugin_callback->Execute(&result); + + void *ret = g_SHPtr->GetOverrideRetPtr(); + ret = string_result; + switch((MRESReturn)result) + { + case MRES_Handled: + case MRES_ChangedHandled: + g_SHPtr->DoRecall(); + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + *string_result = CallVFunction(dg, paramStruct, g_SHPtr->GetIfacePtr()); + break; + case MRES_ChangedOverride: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + *string_result = *(string_t *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + break; + } + } + g_SHPtr->DoRecall(); + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + CallVFunction(dg, paramStruct, g_SHPtr->GetIfacePtr()); + break; + case MRES_Override: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + g_SHPtr->SetRes(MRES_OVERRIDE); + mres = MRES_OVERRIDE; + *string_result = *(string_t *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + } + } + break; + case MRES_Supercede: + if(dg->returnType != ReturnType_Void) + { + if(returnStruct->isChanged) + { + g_SHPtr->SetRes(MRES_SUPERCEDE); + mres = MRES_SUPERCEDE; + *string_result = *(string_t *)returnStruct->newResult; + } + else //Throw an error if no override was set + { + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(dg->plugin_callback, "Tried to override return value without return value being set"); + } + } + break; + default: + g_SHPtr->SetRes(MRES_IGNORED); + mres = MRES_IGNORED; + break; + } + + HandleSecurity sec(dg->plugin_callback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity()); + + if(returnStruct) + { + handlesys->FreeHandle(rHndl, &sec); + } + if(paramStruct) + { + handlesys->FreeHandle(pHndl, &sec); + } + + if(dg->returnType == ReturnType_Void || mres <= MRES_HANDLED) + { + *string_result = NULL_STRING; + return string_result; + } + return string_result; +} +#endif diff --git a/extensions/dhooks/vhook.h b/extensions/dhooks/vhook.h new file mode 100644 index 00000000..44a76d1b --- /dev/null +++ b/extensions/dhooks/vhook.h @@ -0,0 +1,274 @@ +#ifndef _INCLUDE_VHOOK_H_ +#define _INCLUDE_VHOOK_H_ + +#include "extension.h" +#include +#include +#include +#include +#include + +enum CallingConvention +{ + CallConv_CDECL, + CallConv_THISCALL, + CallConv_STDCALL, + CallConv_FASTCALL, +}; + +enum MRESReturn +{ + MRES_ChangedHandled = -2, // Use changed values and return MRES_Handled + MRES_ChangedOverride, // Use changed values and return MRES_Override + MRES_Ignored, // plugin didn't take any action + MRES_Handled, // plugin did something, but real function should still be called + MRES_Override, // call real function, but use my return value + MRES_Supercede // skip real function; use my return value +}; + +enum ObjectValueType +{ + ObjectValueType_Int = 0, + ObjectValueType_Bool, + ObjectValueType_Ehandle, + ObjectValueType_Float, + ObjectValueType_CBaseEntityPtr, + ObjectValueType_IntPtr, + ObjectValueType_BoolPtr, + ObjectValueType_EhandlePtr, + ObjectValueType_FloatPtr, + ObjectValueType_Vector, + ObjectValueType_VectorPtr, + ObjectValueType_CharPtr, + ObjectValueType_String +}; + +enum HookParamType +{ + HookParamType_Unknown, + HookParamType_Int, + HookParamType_Bool, + HookParamType_Float, + HookParamType_String, + HookParamType_StringPtr, + HookParamType_CharPtr, + HookParamType_VectorPtr, + HookParamType_CBaseEntity, + HookParamType_ObjectPtr, + HookParamType_Edict, + HookParamType_Object +}; + +enum ReturnType +{ + ReturnType_Unknown, + ReturnType_Void, + ReturnType_Int, + ReturnType_Bool, + ReturnType_Float, + ReturnType_String, + ReturnType_StringPtr, + ReturnType_CharPtr, + ReturnType_Vector, + ReturnType_VectorPtr, + ReturnType_CBaseEntity, + ReturnType_Edict +}; + +enum ThisPointerType +{ + ThisPointer_Ignore, + ThisPointer_CBaseEntity, + ThisPointer_Address +}; + +enum HookType +{ + HookType_Entity, + HookType_GameRules, + HookType_Raw +}; + +struct ParamInfo +{ + HookParamType type; + size_t size; + unsigned int flags; + SourceHook::PassInfo::PassType pass_type; + Register_t custom_register; +}; + +#ifdef WIN32 +#define OBJECT_OFFSET sizeof(void *) +#else +#define OBJECT_OFFSET (sizeof(void *)*2) +#endif + +class HookReturnStruct +{ +public: + ~HookReturnStruct(); +public: + ReturnType type; + bool isChanged; + void *orgResult; + void *newResult; +}; + +class DHooksInfo +{ +public: + SourceHook::CVector params; + int offset; + unsigned int returnFlag; + ReturnType returnType; + bool post; + IPluginFunction *plugin_callback; + int entity; + ThisPointerType thisType; + HookType hookType; +}; + +class DHooksCallback : public SourceHook::ISHDelegate, public DHooksInfo +{ +public: + virtual bool IsEqual(ISHDelegate *pOtherDeleg){return false;}; + virtual void DeleteThis() + { + *(void ***)this = this->oldvtable; + g_pSM->GetScriptingEngine()->FreePageMemory(this->newvtable[2]); + delete this->newvtable; + delete this; + }; + virtual void Call() {}; +public: + void **newvtable; + void **oldvtable; +}; + +#ifdef WIN32 +void *Callback(DHooksCallback *dg, void **stack, size_t *argsizep); +float Callback_float(DHooksCallback *dg, void **stack, size_t *argsizep); +SDKVector *Callback_vector(DHooksCallback *dg, void **stack, size_t *argsizep); +#else +void *Callback(DHooksCallback *dg, void **stack); +float Callback_float(DHooksCallback *dg, void **stack); +SDKVector *Callback_vector(DHooksCallback *dg, void **stack); +string_t *Callback_stringt(DHooksCallback *dg, void **stack); +#endif + +bool SetupHookManager(ISmmAPI *ismm); +void CleanupHooks(IPluginContext *pContext = NULL); +size_t GetParamTypeSize(HookParamType type); +SourceHook::PassInfo::PassType GetParamTypePassType(HookParamType type); +void *GenerateThunk(ReturnType type); + +static DHooksCallback *MakeHandler(ReturnType type) +{ + DHooksCallback *dg = new DHooksCallback(); + dg->returnType = type; + dg->oldvtable = *(void ***)dg; + dg->newvtable = new void *[3]; + dg->newvtable[0] = dg->oldvtable[0]; + dg->newvtable[1] = dg->oldvtable[1]; + dg->newvtable[2] = GenerateThunk(type); + *(void ***)dg = dg->newvtable; + return dg; +} + +class HookParamsStruct +{ +public: + HookParamsStruct() + { + this->orgParams = NULL; + this->newParams = NULL; + this->dg = NULL; + this->isChanged = NULL; + } + ~HookParamsStruct(); +public: + void **orgParams; + void **newParams; + bool *isChanged; + DHooksInfo *dg; +}; + +class HookSetup +{ +public: + HookSetup(ReturnType returnType, unsigned int returnFlag, HookType hookType, ThisPointerType thisType, int offset, IPluginFunction *callback) + { + this->returnType = returnType; + this->returnFlag = returnFlag; + this->hookType = hookType; + this->callConv = CallConv_THISCALL; + this->thisType = thisType; + this->offset = offset; + this->funcAddr = nullptr; + this->callback = callback; + }; + HookSetup(ReturnType returnType, unsigned int returnFlag, CallingConvention callConv, ThisPointerType thisType, void *funcAddr) + { + this->returnType = returnType; + this->returnFlag = returnFlag; + this->hookType = HookType_Raw; + this->callConv = callConv; + this->thisType = thisType; + this->offset = -1; + this->funcAddr = funcAddr; + this->callback = nullptr; + }; + ~HookSetup(){}; + + bool IsVirtual() + { + return this->offset != -1; + } +public: + unsigned int returnFlag; + ReturnType returnType; + HookType hookType; + CallingConvention callConv; + ThisPointerType thisType; + SourceHook::CVector params; + int offset; + void *funcAddr; + IPluginFunction *callback; +}; + +class DHooksManager +{ +public: + DHooksManager(HookSetup *setup, void *iface, IPluginFunction *remove_callback, IPluginFunction *plugincb, bool post); + ~DHooksManager() + { + if(this->hookid) + { + g_SHPtr->RemoveHookByID(this->hookid); + if(this->remove_callback) + { + this->remove_callback->PushCell(this->hookid); + this->remove_callback->Execute(NULL); + } + if(this->pManager) + { + g_pHookManager->ReleaseHookMan(this->pManager); + } + } + } +public: + intptr_t addr; + int hookid; + DHooksCallback *callback; + IPluginFunction *remove_callback; + SourceHook::HookManagerPubFunc pManager; +}; + +size_t GetStackArgsSize(DHooksCallback *dg); +cell_t GetThisPtr(void *iface, ThisPointerType type); + +extern IBinTools *g_pBinTools; +extern HandleType_t g_HookParamsHandle; +extern HandleType_t g_HookReturnHandle; +#endif diff --git a/plugins/include/dhooks.inc b/plugins/include/dhooks.inc new file mode 100644 index 00000000..51bb3bb6 --- /dev/null +++ b/plugins/include/dhooks.inc @@ -0,0 +1,1067 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod (C)2021 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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$ + */ + +#if defined _dhooks_included +#endinput +#endif +#define _dhooks_included + +// Needed for the SDKFuncConfSource enum. +#include + +#define INVALID_HOOK_ID 0 + +enum ObjectValueType +{ + ObjectValueType_Int = 0, + ObjectValueType_Bool, + ObjectValueType_Ehandle, + ObjectValueType_Float, + ObjectValueType_CBaseEntityPtr, + ObjectValueType_IntPtr, + ObjectValueType_BoolPtr, + ObjectValueType_EhandlePtr, + ObjectValueType_FloatPtr, + ObjectValueType_Vector, + ObjectValueType_VectorPtr, + ObjectValueType_CharPtr, + ObjectValueType_String +}; + +enum ListenType +{ + ListenType_Created, + ListenType_Deleted +}; + +enum ReturnType +{ + ReturnType_Unknown, + ReturnType_Void, + ReturnType_Int, + ReturnType_Bool, + ReturnType_Float, + ReturnType_String, //Note this is a string_t + ReturnType_StringPtr, //Note this is a string_t * + ReturnType_CharPtr, + ReturnType_Vector, + ReturnType_VectorPtr, + ReturnType_CBaseEntity, + ReturnType_Edict +}; + +enum HookParamType +{ + HookParamType_Unknown, + HookParamType_Int, + HookParamType_Bool, + HookParamType_Float, + HookParamType_String, //Note this is a string_t + HookParamType_StringPtr, //Note this is a string_t * + HookParamType_CharPtr, + HookParamType_VectorPtr, + HookParamType_CBaseEntity, + HookParamType_ObjectPtr, + HookParamType_Edict, + HookParamType_Object +}; + +enum ThisPointerType +{ + ThisPointer_Ignore, + ThisPointer_CBaseEntity, + ThisPointer_Address +}; + +enum HookType +{ + HookType_Entity, + HookType_GameRules, + HookType_Raw +}; + +enum CallingConvention +{ + CallConv_CDECL, + CallConv_THISCALL, + CallConv_STDCALL, + CallConv_FASTCALL, +}; + +enum HookMode +{ + Hook_Pre, // Callback will be executed BEFORE the original function. + Hook_Post // Callback will be executed AFTER the original function. +}; + +enum MRESReturn +{ + MRES_ChangedHandled = -2, // Use changed values and return MRES_Handled + MRES_ChangedOverride, // Use changed values and return MRES_Override + MRES_Ignored, // plugin didn't take any action + MRES_Handled, // plugin did something, but real function should still be called + MRES_Override, // call real function, but use my return value + MRES_Supercede // skip real function; use my return value +}; + +enum DHookPassFlag +{ + DHookPass_ByVal = (1<<0), /**< Passing by value */ + DHookPass_ByRef = (1<<1), /**< Passing by reference */ + DHookPass_ODTOR = (1<<2), /**< Object has a destructor */ + DHookPass_OCTOR = (1<<3), /**< Object has a constructor */ + DHookPass_OASSIGNOP = (1<<4), /**< Object has an assignment operator */ +}; + +enum DHookRegister +{ + // Don't change the register and use the default for the calling convention. + DHookRegister_Default, + + // 8-bit general purpose registers + DHookRegister_AL, + DHookRegister_CL, + DHookRegister_DL, + DHookRegister_BL, + DHookRegister_AH, + DHookRegister_CH, + DHookRegister_DH, + DHookRegister_BH, + + // 32-bit general purpose registers + DHookRegister_EAX, + DHookRegister_ECX, + DHookRegister_EDX, + DHookRegister_EBX, + DHookRegister_ESP, + DHookRegister_EBP, + DHookRegister_ESI, + DHookRegister_EDI, + + // 128-bit XMM registers + DHookRegister_XMM0, + DHookRegister_XMM1, + DHookRegister_XMM2, + DHookRegister_XMM3, + DHookRegister_XMM4, + DHookRegister_XMM5, + DHookRegister_XMM6, + DHookRegister_XMM7, + + // 80-bit FPU registers + DHookRegister_ST0 +}; + +typeset ListenCB +{ + //Deleted + function void (int entity); + + //Created + function void (int entity, const char[] classname); +}; + +typeset DHookRemovalCB +{ + function void (int hookid); +}; +typeset DHookCallback +{ + //Function Example: void Ham::Test() with this pointer ignore + function MRESReturn (); + + //Function Example: void Ham::Test() with this pointer passed + function MRESReturn (int pThis); + + //Function Example: void Ham::Test(int cake) with this pointer ignore + function MRESReturn (DHookParam hParams); + + //Function Example: void Ham::Test(int cake) with this pointer passed + function MRESReturn (int pThis, DHookParam hParams); + + //Function Example: int Ham::Test() with this pointer ignore + function MRESReturn (DHookReturn hReturn); + + //Function Example: int Ham::Test() with this pointer passed + function MRESReturn (int pThis, DHookReturn hReturn); + + //Function Example: int Ham::Test(int cake) with this pointer ignore + function MRESReturn (DHookReturn hReturn, DHookParam hParams); + + //Function Example: int Ham::Test(int cake) with this pointer passed + function MRESReturn (int pThis, DHookReturn hReturn, DHookParam hParams); + + //Address NOW + + //Function Example: void Ham::Test() with this pointer passed + function MRESReturn (Address pThis); + + //Function Example: void Ham::Test(int cake) with this pointer passed + function MRESReturn (Address pThis, DHookParam hParams); + + //Function Example: int Ham::Test() with this pointer passed + function MRESReturn (Address pThis, DHookReturn hReturn); + + //Function Example: int Ham::Test(int cake) with this pointer passed + function MRESReturn (Address pThis, DHookReturn hReturn, DHookParam hParams); + +}; + +// Represents the parameters of the hooked function. +methodmap DHookParam < Handle +{ + // Get the value of a parameter. + // Use only for: int, entity, edict, bool or float parameter types. + // + // @param num Parameter number to get, starting at 1. Parameter number 0 returns + // the number of parameters. + // + // @return Value if num greater than 0. If 0 returns parameter count. + // If CBaseEntity returns entity index. + // @error Invalid handle, invalid param number or invalid param type. + public native any Get(int num); + + // Get the value of a vector parameter. + // Use only for: vector or vectorptr parameter types. + // + // @param num Parameter number to get, starting at 1. + // @param vec Vector buffer to store result. + // + // @error Invalid handle, invalid param number or invalid param type. + public native void GetVector(int num, float vec[3]); + + // Get the value of a string parameter. + // Use only for: string, stringptr or charptr parameter types. + // + // @param num Parameter number to get, starting at 1. + // @param buffer String buffer to store result. + // @param size Buffer size. + // + // @error Invalid handle, invalid param number or invalid param type. + public native void GetString(int num, char[] buffer, int size); + + // Set the value of a parameter. + // Use only for: int, entity, edict, bool or float parameter types. + // + // The changes are only applied when MRES_ChangedHandled or MRES_ChangedOverride + // is returned in the callback. + // + // @param num Parameter number to set starting at 1. + // @param value Value to set it as (only pass int, bool, float or entity index). + // + // @error Invalid handle, invalid param number or invalid param type. + public native void Set(int num, any value); + + // Set the value of a vector parameter. + // Use only for: vector or vectorptr parameter types. + // + // The changes are only applied when MRES_ChangedHandled or MRES_ChangedOverride + // is returned in the callback. + // + // @param num Parameter number to set, starting at 1. + // @param vec Value to set vector as. + // + // @error Invalid handle, invalid param number or invalid param type. + public native void SetVector(int num, const float vec[3]); + + // Set the value of a string parameter. + // Use only for: string, stringptr or charptr parameter types. + // + // The changes are only applied when MRES_ChangedHandled or MRES_ChangedOverride + // is returned in the callback. + // + // @param num Parameter number to set, starting at 1. + // @param value Value to set string as. + // + // @error Invalid handle, invalid param number or invalid param type. + public native void SetString(int num, const char[] value); + + // Gets an object's variable value. + // + // @param num Parameter number to get, starting at 1. + // @param offset Byte offset within the object to the var to get. + // @param type Type of var it is. + // + // @return Value of the objects var. If EHANDLE type or entity returns entity index. + // @error Invalid handle, invalid param number, invalid param type or invalid Object type. + public native any GetObjectVar(int num, int offset, ObjectValueType type); + + // Gets an object's vector variable value. + // + // @param num Parameter number to get, starting at 1. + // @param offset Byte offset within the object to the var to get. + // @param type Type of var it is. + // @param vec Buffer to store the result vector. + // + // @error Invalid handle, invalid param number, invalid param type or invalid Object type. + public native void GetObjectVarVector(int num, int offset, ObjectValueType type, float vec[3]); + + // Gets an object's string variable value. + // + // @param num Parameter number to get, starting at 1. + // @param offset Byte offset within the object to the var to get. + // @param type Type of var it is. + // @param buffer Buffer to store the result string. + // @param size Size of the buffer. + // + // @error Invalid handle, invalid param number, invalid param type or invalid Object type. + public native void GetObjectVarString(int num, int offset, ObjectValueType type, char[] buffer, int size); + + // Sets an object's variable value. + // + // The changes are only applied when MRES_ChangedHandled or MRES_ChangedOverride + // is returned in the callback. + // + // @param num Parameter number to set, starting at 1. + // @param offset Byte offset within the object to the var to set. + // @param type Type of var it is. + // @param value The value to set the var to. + // + // @error Invalid handle, invalid param number, invalid param type or invalid Object type. + public native void SetObjectVar(int num, int offset, ObjectValueType type, any value); + + // Sets an object's vector variable value. + // + // The changes are only applied when MRES_ChangedHandled or MRES_ChangedOverride + // is returned in the callback. + // + // @param num Parameter number to set, starting at 1. + // @param offset Byte offset within the object to the var to set. + // @param type Type of var it is. + // @param vec The value to set the vector var to. + // + // @error Invalid handle, invalid param number, invalid param type or invalid Object type. + public native void SetObjectVarVector(int num, int offset, ObjectValueType type, const float vec[3]); + + // No setter for object strings yet. Open an issue if you really need it. + + // Checks if a pointer parameter is null. + // + // @param num Parameter number to check, starting at 1. + // + // @return True if null, false otherwise. + // @error Non-pointer parameter. + public native bool IsNull(int num); + + // Get param address (Use only for ptr param types) + // + // @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1.) + // + // @error Invalid handle. Invalid param number. Invalid param type. + // @return address of the parameter. + public native Address GetAddress(int num); +}; + + +// Represents the return value of the hooked function. +methodmap DHookReturn < Handle +{ + // Retrieves or sets the return value. + // Use only for: int, entity, edict, bool or float return types. + // + // The return value is only readable in a post hook. + // The value is only applied when MRES_Override or MRES_Supercede is returned + // in the callback. + property any Value { + public native get(); + public native set(any value); + } + + // Get return vector value. + // Use only for: vector or vectorptr return types. + // + // Only useful in post hooks. + // + // @param vec Vector buffer to store result in. + // + // @error Invalid Handle or invalid type. + public native void GetVector(float vec[3]); + + // Get return string value. + // Use only for: string, stringptr or charptr return types. + // + // Only useful in post hooks. + // + // @param buffer String buffer to store result in. + // @param size String buffer size. + // + // @error Invalid Handle or invalid type. + public native void GetString(char[] buffer, int size); + + // Set return vector value. + // Use only for: vector or vectorptr return types. + // + // The value is only applied when MRES_Override or MRES_Supercede is returned + // in the callback. + // + // @param vec Value to set return vector to. + // + // @error Invalid Handle or invalid type. + public native void SetVector(const float vec[3]); + + // Set return string value. + // Use only for: string, stringptr or charptr return types. + // + // The value is only applied when MRES_Override or MRES_Supercede is returned + // in the callback. + // + // @param buffer Value to set return string to. + // + // @error Invalid Handle or invalid type. + public native void SetString(const char[] buffer); +}; + +// Base method map for common functions between virtual hooks and detours. +methodmap DHookSetup < Handle +{ + // Load address or offset for a vtable hook or detour from a gamedata file. + // + // @param gameconf GameData handle. + // @param source Whether to look in Offsets, Signatures, or Addresses. + // @param name Name of the property to find. + // + // @return True on success, false if nothing was found. + // @error Invalid setup or gamedata handle. + public native bool SetFromConf(Handle gameconf, SDKFuncConfSource source, const char[] name); + + // Adds a parameter to a hook setup. + // + // @param type Parameter type. + // @param size Used for Objects (not Object ptr) to define the size of the object. + // @param flag Used to change the pass type (ignored by detours). + // @param custom_register The register this argument is passed in instead of the stack (ignored by vhooks). + // + // @error Invalid setup handle or too many params added (request upping the max in thread). + public native void AddParam(HookParamType type, int size=-1, DHookPassFlag flag=DHookPass_ByVal, DHookRegister custom_register=DHookRegister_Default); +}; + +// A DynamicHook allows to hook a virtual function on any C++ object. +// Currently CBaseEntity and CGameRules have a convenience API for easy entity hooking, +// but it's possible to provide a raw this-pointer to hook any object in memory too. +// +// Internally this intercepts function calls by replacing the function pointer +// in the virtual table of the object with our own function. +methodmap DynamicHook < DHookSetup +{ + // Creates a vtable hook. + // + // @param offset Virtual table offset of function to hook. + // @param hooktype Type of hook. + // @param returntype Type of return value. + // @param thistype Type of this pointer or ignore (ignore can be used if not needed). + // + // @error Failed to create hook setup handle or invalid callback function. + public native DynamicHook(int offset, HookType hooktype, ReturnType returntype, ThisPointerType thistype); + + // Setup a vtable hook for a function as described in a "Functions" section in gamedata. + // The "Functions" section is parsed once the gamedata file is loaded and cached globally. + // + // @param gameconf GameData handle to use for address lookup. + // Doesn't have to be the same as the one with the "Functions" section. + // @param name Name of the function in a "Functions" section to load. + // + // @return Setup handle for the detour or null if offset wasn't found. + // @error Failed to create detour setup handle, invalid gamedata handle, + // invalid callback function or failed to find function in cached "Functions" sections. + public static native DynamicHook FromConf(Handle gameconf, const char[] name); + + // Hook an entity. + // + // Entity hooks are auto-removed when the entity is destroyed. + // If you need to read the return value of the function, choose a post hook. + // + // @param mode The desired hook mode - pre or post. + // A pre hook calls your callback BEFORE the original function is called. + // You can access the parameters, set the return value, and skip the original function. + // A post hook calls your callback AFTER the original function executed. + // You can access the parameters and get/set the return value. + // @param entity Entity index to hook on. + // @param callback Callback function. + // @param removalcb Optional callback for when the hook is removed. + // + // @return A hookid on success, INVALID_HOOK_ID otherwise. + // @error Invalid setup handle, invalid address, invalid hook type or invalid callback. + public native int HookEntity(HookMode mode, int entity, DHookCallback callback, DHookRemovalCB removalcb=INVALID_FUNCTION); + + // Hook gamerules object. + // + // Game rules hooks are auto-removed on map end. + // If you need to read the return value of the function, choose a post hook. + // + // @param mode The desired hook mode - pre or post. + // A pre hook calls your callback BEFORE the original function is called. + // You can access the parameters, set the return value, and skip the original function. + // A post hook calls your callback AFTER the original function executed. + // You can access the parameters and get/set the return value. + // @param callback Callback function. + // @param removalcb Optional callback for when the hook is removed. + // + // @return A hookid on success, INVALID_HOOK_ID otherwise. + // @error Invalid setup handle, invalid address, invalid hook type or invalid callback. + public native int HookGamerules(HookMode mode, DHookCallback callback, DHookRemovalCB removalcb=INVALID_FUNCTION); + + // Hook a raw this-pointer. + // If you need to read the return value of the function, choose a post hook. + // + // @param mode The desired hook mode - pre or post. + // A pre hook calls your callback BEFORE the original function is called. + // You can access the parameters, set the return value, and skip the original function. + // A post hook calls your callback AFTER the original function executed. + // You can access the parameters and get/set the return value. + // @param addr This pointer address. + // @param callback Callback function. + // + // @return A hookid on success, INVALID_HOOK_ID otherwise. + // @error Invalid setup handle, invalid address, invalid hook type or invalid callback. + public native int HookRaw(HookMode mode, Address addr, DHookCallback callback); + + // Remove hook by hook id: + // This will NOT fire the removal callback! + // + // @param hookid Hook id to remove. + // + // @return True on success, false otherwise + public static native bool RemoveHook(int hookid); +}; + +// A DynamicDetour is a way to hook and block any function in memory. +// Given the address of a function, it can call a callback in your script whenever +// the function gets called. The callback has access to all parameters of the function +// as well as the return value. +// +// Internally this works by replacing the first instructions of the function +// with a jump to our own code. This means that the signature used to find +// the function address in the first place might not match anymore after a detour. +// If you need to detour the same function in different plugins make sure to +// wildcard \x2a the first 6 bytes of the signature to accommodate for the patched +// jump introduced by the detour. +methodmap DynamicDetour < DHookSetup +{ + // Creates a detour. + // + // @param funcaddr The address of the function to detour. + // Can be Address_Null if you want to load the address from gamedata using DHookSetFromConf. + // @param callConv Calling convention of the function. + // @param returnType Type of the return value. + // @param thisType Type of this pointer or ignore (ignore can be used if not needed). + // Only used for thiscall detours. + // + // @error Failed to create detour setup handle. + public native DynamicDetour(Address funcaddr, CallingConvention callConv, ReturnType returntype, ThisPointerType thisType=ThisPointer_Ignore); + + // Setup a detour for a function as described in a "Functions" section in gamedata. + // The "Functions" section is parsed once the gamedata file is loaded and cached globally. + // + // @param gameconf GameData handle to use for address lookup. + // Doesn't have to be the same as the one with the "Functions" section. + // @param name Name of the function in a "Functions" section to load. + // + // @return Setup handle for the detour or null if offset wasn't found. + // @error Failed to create detour setup handle, invalid gamedata handle, + // invalid callback function or failed to find function in cached "Functions" sections. + public static native DynamicDetour FromConf(Handle gameconf, const char[] name); + + // Enable the detour of the function described in this detour setup. + // If you need to read the return value of the function, choose a post hook. + // + // @param mode The desired hook mode - pre or post. + // A pre hook calls your callback BEFORE the original function is called. + // You can access the parameters, set the return value, and skip the original function. + // A post hook calls your callback AFTER the original function executed. + // You can access the parameters and get/set the return value. + // @param callback Callback function. + // + // @return True if detour was enabled, false otherwise. + // @error Hook handle is not setup for a detour. + public native bool Enable(HookMode mode, DHookCallback callback); + + // Disable the detour of the function described in this detour setup. + // + // @param mode The hook mode to disable - pre or post. + // @param callback Callback function. + // + // @return True if detour was disabled, false otherwise. + // @error Hook handle is not setup for a detour or function is not detoured. + public native bool Disable(HookMode mode, DHookCallback callback); +}; + +/* Adds an entity listener hook + * + * @param type Type of listener to add + * @param callback Callback to use + * + * @noreturn +*/ +native void DHookAddEntityListener(ListenType type, ListenCB callback); + +/* Removes an entity listener hook + * + * @param type Type of listener to remove + * @param callback Callback this listener was using + * + * @return True if one was removed false otherwise. +*/ +native bool DHookRemoveEntityListener(ListenType type, ListenCB callback); + +/* Creates a hook + * + * @param offset vtable offset of function to hook + * @param hooktype Type of hook + * @param returntype Type of return value + * @param thistype Type of this pointer or ignore (ignore can be used if not needed) + * @param callback Optional callback function, if not set here must be set when hooking. + * + * @return Returns setup handle for the hook. + * @error Failed to create hook setup handle or invalid callback function. +*/ +native DynamicHook DHookCreate(int offset, HookType hooktype, ReturnType returntype, ThisPointerType thistype, DHookCallback callback=INVALID_FUNCTION); + +/** + * Creates a detour + * + * @param funcaddr The address of the function to detour. + * Can be Address_Null if you want to load the address from gamedata using DHookSetFromConf. + * @param callConv Calling convention of the function. + * @param returnType Type of the return value. + * @param thisType Type of this pointer or ignore (ignore can be used if not needed) + * + * @return Setup handle for the detour. + * @error Failed to create detour setup handle. + */ +native DynamicDetour DHookCreateDetour(Address funcaddr, CallingConvention callConv, ReturnType returntype, ThisPointerType thisType); + +/** + * Setup a detour or hook for a function as described in a "Functions" section in gamedata. + * + * @param gameconf GameConfig handle + * @param name Name of the function in the gamedata to load. + * + * @return Setup handle for the detour or INVALID_HANDLE if offset/signature/address wasn't found. + * @error Failed to create detour setup handle, invalid gamedata handle, invalid callback function or failed to find function in gamedata. + */ +native DHookSetup DHookCreateFromConf(Handle gameconf, const char[] name); + +/** + * Load details for a vhook or detour from a gamedata file. + * + * @param setup Hook setup handle to set the offset or address on. + * @param gameconf GameConfig handle + * @param source Whether to look in Offsets or Signatures. + * @param name Name of the property to find. + * + * @return True on success, false if nothing was found. + * @error Invalid setup or gamedata handle. + */ +native bool DHookSetFromConf(Handle setup, Handle gameconf, SDKFuncConfSource source, const char[] name); + +/** + * Enable the detour of the function described in the hook setup handle. + * + * @param setup Hook setup handle + * @param post True to make the hook a post hook. (If you need to change the retunr value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) + * @param callback Callback function + * + * @return True if detour was enabled, false otherwise. + * @error Hook handle is not setup for a detour. + */ +native bool DHookEnableDetour(Handle setup, bool post, DHookCallback callback); + +/** + * Disable the detour of the function described in the hook setup handle. + * + * @param setup Hook setup handle + * @param post True to disable a post hook. + * @param callback Callback function + * + * @return True if detour was disabled, false otherwise. + * @error Hook handle is not setup for a detour or function is not detoured. + */ +native bool DHookDisableDetour(Handle setup, bool post, DHookCallback callback); + +/* Adds param to a hook setup + * + * @param setup Setup handle to add the param to. + * @param type Param type + * @param size Used for Objects (not Object ptr) to define the size of the object. + * @param flag Used to change the pass type. + * @param custom_register The register this argument is passed in instead of the stack. + * + * @error Invalid setup handle or too many params added (request upping the max in thread) + * @noreturn +*/ +native void DHookAddParam(Handle setup, HookParamType type, int size=-1, DHookPassFlag flag=DHookPass_ByVal, DHookRegister custom_register=DHookRegister_Default); + +/* Hook entity + * + * @param setup Setup handle to use to add the hook. + * @param post True to make the hook a post hook. (If you need to change the return value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) + * @param entity Entity index to hook on. + * @param removalcb Callback for when the hook is removed (Entity hooks are auto-removed on entity destroyed and will call this callback) + * @param callback Optional callback function, if not set here must be set when creating the hook. + * + * @error Invalid setup handle, invalid address, invalid hook type or invalid callback. + * @return INVALID_HOOK_ID on fail a hookid on success +*/ +native int DHookEntity(Handle setup, bool post, int entity, DHookRemovalCB removalcb=INVALID_FUNCTION, DHookCallback callback=INVALID_FUNCTION); + +/* Hook gamerules + * + * @param setup Setup handle to use to add the hook. + * @param post True to make the hook a post hook. (If you need to change the return value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) + * @param removalcb Callback for when the hook is removed (Game rules hooks are auto-removed on map end and will call this callback) + * @param callback Optional callback function, if not set here must be set when creating the hook. + * + * @error Invalid setup handle, invalid address, invalid hook type or invalid callback. + * @return INVALID_HOOK_ID on fail a hookid on success +*/ +native int DHookGamerules(Handle setup, bool post, DHookRemovalCB removalcb=INVALID_FUNCTION, DHookCallback callback=INVALID_FUNCTION); + +/* Hook a raw pointer + * + * @param setup Setup handle to use to add the hook. + * @param post True to make the hook a post hook. (If you need to change the return value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) + * @param addr This pointer address. + * @param removalcb Callback for when the hook is removed (Entity hooks are auto-removed on entity destroyed and will call this callback) + * @param callback Optional callback function, if not set here must be set when creating the hook. + * + * @error Invalid setup handle, invalid address, invalid hook type or invalid callback. + * @return INVALID_HOOK_ID on fail a hookid on success +*/ +native int DHookRaw(Handle setup, bool post, Address addr, DHookRemovalCB removalcb=INVALID_FUNCTION, DHookCallback callback=INVALID_FUNCTION); + +/* Remove hook by hook id + * + * @param hookid Hook id to remove + * + * @return true on success false otherwise + * @note This will not fire the removal callback! +*/ +native bool DHookRemoveHookID(int hookid); + +/* Get param value (Use only for: int, entity, bool or float param types) + * + * @param hParams Handle to params structure + * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1. 0 Will return the number of params stored) + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @return value if num greater than 0. If 0 returns paramcount. +*/ +native any DHookGetParam(Handle hParams, int num); + +/* Get vector param value + * + * @param hParams Handle to params structure + * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1.) + * @param vec Vector buffer to store result. + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookGetParamVector(Handle hParams, int num, float vec[3]); + +/* Get string param value + * + * @param hParams Handle to params structure + * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1.) + * @param buffer String buffer to store result + * @param size Buffer size + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookGetParamString(Handle hParams, int num, char[] buffer, int size); + +/* Set param value (Use only for: int, entity, bool or float param types) + * + * @param hParams Handle to params structure + * @param num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) + * @param value Value to set it as (only pass int, bool, float or entity index) + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookSetParam(Handle hParams, int num, any value); + +/* Set vector param value + * + * @param hParams Handle to params structure + * @param num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) + * @param vec Value to set vector as. + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookSetParamVector(Handle hParams, int num, float vec[3]); + +/* Set string param value + * + * @param hParams Handle to params structure + * @param num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) + * @param value Value to set string as. + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookSetParamString(Handle hParams, int num, char[] value); + +/* Get return value (Use only for: int, entity, bool or float return types) + * + * @param hReturn Handle to return structure + * + * @error Invalid Handle, invalid type. + * @return Returns default value if prehook returns actual value if post hook. +*/ +native any DHookGetReturn(Handle hReturn); + +/* Get return vector value + * + * @param hReturn Handle to return structure + * @param vec Vector buffer to store result in. (In pre hooks will be default value (0.0,0.0,0.0)) + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookGetReturnVector(Handle hReturn, float vec[3]); + +/* Get return string value + * + * @param hReturn Handle to return structure + * @param buffer String buffer to store result in. (In pre hooks will be default value "") + * @param size String buffer size + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookGetReturnString(Handle hReturn, char[] buffer, int size); + +/* Set return value (Use only for: int, entity, bool or float return types) + * + * @param hReturn Handle to return structure + * @param value Value to set return as + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookSetReturn(Handle hReturn, any value); + +/* Set return vector value + * + * @param hReturn Handle to return structure + * @param vec Value to set return vector as + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookSetReturnVector(Handle hReturn, float vec[3]); + +/* Set return string value + * + * @param hReturn Handle to return structure + * @param value Value to set return string as + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookSetReturnString(Handle hReturn, char[] value); + +//WE SHOULD WRAP THESE AROUND STOCKS FOR NON PTR AS WE SUPPORT BOTH WITH THESE NATIVE'S + +/* Gets an objects variable value + * + * @param hParams Handle to params structure + * @param num Param number to get. + * @param offset Offset within the object to the var to get. + * @param type Type of var it is + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @return Value of the objects var. If EHANDLE type or entity returns entity index. +*/ +native any DHookGetParamObjectPtrVar(Handle hParams, int num, int offset, ObjectValueType type); + +/* Sets an objects variable value + * + * @param hParams Handle to params structure + * @param num Param number to set. + * @param offset Offset within the object to the var to set. + * @param type Type of var it is + * @param value The value to set the var to. + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @noreturn +*/ +native void DHookSetParamObjectPtrVar(Handle hParams, int num, int offset, ObjectValueType type, any value); + +/* Gets an objects vector variable value + * + * @param hParams Handle to params structure + * @param num Param number to get. + * @param offset Offset within the object to the var to get. + * @param type Type of var it is + * @param buffer Buffer to store the result vector + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @noreturn +*/ +native void DHookGetParamObjectPtrVarVector(Handle hParams, int num, int offset, ObjectValueType type, float buffer[3]); + +/* Sets an objects vector variable value + * + * @param hParams Handle to params structure + * @param num Param number to set. + * @param offset Offset within the object to the var to set. + * @param type Type of var it is + * @param value The value to set the vector var to. + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @noreturn +*/ +native void DHookSetParamObjectPtrVarVector(Handle hParams, int num, int offset, ObjectValueType type, float value[3]); + +/* Gets an objects string variable value + * + * @param hParams Handle to params structure + * @param num Param number to get. + * @param offset Offset within the object to the var to get. + * @param type Type of var it is + * @param buffer Buffer to store the result vector + * @param size Size of the buffer + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @noreturn +*/ +native void DHookGetParamObjectPtrString(Handle hParams, int num, int offset, ObjectValueType type, char[] buffer, int size); + +/* Checks if a pointer param is null + * + * @param hParams Handle to params structure + * @param num Param number to check. + * + * @error Non pointer param + * @return True if null false otherwise. +*/ +native bool DHookIsNullParam(Handle hParams, int num); + +/* Get param address (Use only for ptr param types) + * + * @param hParams Handle to params structure + * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1.) + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @return address of the parameter. +*/ +native Address DHookGetParamAddress(Handle hParams, int num); + +public Extension __ext_dhooks = +{ + name = "dhooks", + file = "dhooks.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_dhooks_SetNTVOptional() +{ + MarkNativeAsOptional("DHookAddEntityListener"); + MarkNativeAsOptional("DHookRemoveEntityListener"); + MarkNativeAsOptional("DHookCreate"); + MarkNativeAsOptional("DHookCreateDetour"); + MarkNativeAsOptional("DHookCreateFromConf"); + MarkNativeAsOptional("DHookSetFromConf"); + MarkNativeAsOptional("DHookEnableDetour"); + MarkNativeAsOptional("DHookDisableDetour"); + MarkNativeAsOptional("DHookAddParam"); + MarkNativeAsOptional("DHookEntity"); + MarkNativeAsOptional("DHookGamerules"); + MarkNativeAsOptional("DHookRaw"); + MarkNativeAsOptional("DHookRemoveHookID"); + MarkNativeAsOptional("DHookGetParam"); + MarkNativeAsOptional("DHookGetParamVector"); + MarkNativeAsOptional("DHookGetParamString"); + MarkNativeAsOptional("DHookSetParam"); + MarkNativeAsOptional("DHookSetParamVector"); + MarkNativeAsOptional("DHookSetParamString"); + MarkNativeAsOptional("DHookGetReturn"); + MarkNativeAsOptional("DHookGetReturnVector"); + MarkNativeAsOptional("DHookGetReturnString"); + MarkNativeAsOptional("DHookSetReturn"); + MarkNativeAsOptional("DHookSetReturnVector"); + MarkNativeAsOptional("DHookSetReturnString"); + MarkNativeAsOptional("DHookGetParamObjectPtrVar"); + MarkNativeAsOptional("DHookSetParamObjectPtrVar"); + MarkNativeAsOptional("DHookGetParamObjectPtrVarVector"); + MarkNativeAsOptional("DHookSetParamObjectPtrVarVector"); + MarkNativeAsOptional("DHookIsNullParam"); + MarkNativeAsOptional("DHookGetParamObjectPtrString"); + MarkNativeAsOptional("DHookGetParamAddress"); + + MarkNativeAsOptional("DHookParam.IsNull"); + MarkNativeAsOptional("DHookParam.Get"); + MarkNativeAsOptional("DHookParam.GetVector"); + MarkNativeAsOptional("DHookParam.GetString"); + MarkNativeAsOptional("DHookParam.Set"); + MarkNativeAsOptional("DHookParam.SetVector"); + MarkNativeAsOptional("DHookParam.SetString"); + MarkNativeAsOptional("DHookParam.GetObjectVar"); + MarkNativeAsOptional("DHookParam.GetObjectVarVector"); + MarkNativeAsOptional("DHookParam.GetObjectVarString"); + MarkNativeAsOptional("DHookParam.SetObjectVar"); + MarkNativeAsOptional("DHookParam.SetObjectVarVector"); + MarkNativeAsOptional("DHookParam.GetAddress"); + MarkNativeAsOptional("DHookReturn.Value.get"); + MarkNativeAsOptional("DHookReturn.Value.set"); + MarkNativeAsOptional("DHookReturn.GetVector"); + MarkNativeAsOptional("DHookReturn.GetString"); + MarkNativeAsOptional("DHookReturn.SetVector"); + MarkNativeAsOptional("DHookReturn.SetString"); + MarkNativeAsOptional("DHookSetup.SetFromConf"); + MarkNativeAsOptional("DHookSetup.AddParam"); + MarkNativeAsOptional("DynamicHook.DynamicHook"); + MarkNativeAsOptional("DynamicHook.FromConf"); + MarkNativeAsOptional("DynamicHook.HookEntity"); + MarkNativeAsOptional("DynamicHook.HookGamerules"); + MarkNativeAsOptional("DynamicHook.HookRaw"); + MarkNativeAsOptional("DynamicHook.RemoveHook"); + MarkNativeAsOptional("DynamicDetour.DynamicDetour"); + MarkNativeAsOptional("DynamicDetour.FromConf"); + MarkNativeAsOptional("DynamicDetour.Enable"); + MarkNativeAsOptional("DynamicDetour.Disable"); +} +#endif