diff --git a/extensions/dhooks/AMBuilder b/extensions/dhooks/AMBuilder index 3ea7987166..5dc23a9f6b 100644 --- a/extensions/dhooks/AMBuilder +++ b/extensions/dhooks/AMBuilder @@ -7,9 +7,6 @@ for cxx in builder.targets: # mac isn't supported if binary.compiler.target.platform == 'mac': continue - # Presently only x86_64 on Windows is supported - if binary.compiler.target.arch == 'x86_64' and binary.compiler.target.platform != 'windows': - continue binary.compiler.defines += [ 'META_NO_HL2SDK', diff --git a/extensions/dhooks/DynamicHooks/convention.h b/extensions/dhooks/DynamicHooks/convention.h index e9a63ed479..1253ca89a6 100644 --- a/extensions/dhooks/DynamicHooks/convention.h +++ b/extensions/dhooks/DynamicHooks/convention.h @@ -74,10 +74,12 @@ typedef struct DataTypeSized_s { type = DATA_TYPE_POINTER; size = 0; custom_register = None; + custom_register2 = None; } DataType_t type; size_t size; Register_t custom_register; + Register_t custom_register2; // Currently only used for Vector returns on Linux64 } DataTypeSized_t; @@ -191,7 +193,7 @@ class ICallingConvention 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. + Returns the number of bytes for the buffer to store all the arguments that are passed in a register. */ virtual int GetArgRegisterSize() = 0; diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp index 10b909f814..917f284b96 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp @@ -22,4 +22,307 @@ * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. -*/ \ No newline at end of file +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86_64SystemVDefault.h" +#include +#include + +// ============================================================================ +// >> CLASSES +// ============================================================================ +x86_64SystemVDefault::x86_64SystemVDefault(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : + ICallingConvention(vecArgTypes, returnType, iAlignment) +{ + const Register_t params_reg[] = { RDI, RSI, RDX, RCX, R8, R9 }; + const Register_t params_floatreg[] = { XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7 }; + //const char* regNames[] = { "RCX or XMM0", "RDX or XMM1", "R8 or XMM2", "R9 or XMM3"}; + const std::uint8_t num_reg = sizeof(params_reg) / sizeof(Register_t); + const std::uint8_t num_floatreg = sizeof(params_floatreg) / sizeof(Register_t); + + bool used_reg[] = { false, false, false, false, false, false }; + bool used_floatreg[] = { false, false, false, false, false, false, false, false }; + + // Figure out if any register has been used + auto retreg = m_returnType.custom_register; + for (int i = 0; i < num_reg; ++i) { + if (retreg == params_reg[i]) { + used_reg[i] = true; + break; + } + } + for (int i = 0; i < num_floatreg; ++i) { + if (retreg == params_floatreg[i]) { + used_floatreg[i] = true; + break; + } + } + + // Let's pad our objects up since the stack must be aligned by 8 (and potentially 16 or 32). + for (auto& arg : m_vecArgTypes) { + if (arg.type == DATA_TYPE_OBJECT) { + arg.size = Align(arg.size, 8); + } + } + + for (const auto& arg : m_vecArgTypes) { + bool found_as_int_or_ptr = false; + + for (int i = 0; i < num_reg; ++i) { + if (arg.custom_register == params_reg[i]) { + if (used_reg[i]) { + puts("Argument (int/ptr) register is used twice, or shared with return"); + return; + } + used_reg[i] = true; + found_as_int_or_ptr = true; + break; + } + } + + if (found_as_int_or_ptr) continue; + + for (int i = 0; i < num_floatreg; ++i) { + if (arg.custom_register == params_floatreg[i]) { + if (used_floatreg[i]) { + puts("Argument (float/double) register is used twice, or shared with return"); + return; + } + used_floatreg[i] = true; + break; + } + } + } + + // TODO: Object return-type register is RDI, even when thiscall. + // inRDI = out-object-address & outRAX = inRDI + // thiscall: inRDI = out-object-address & inRSI = this + // + // Currently not handling object return types other than Vector, which would fit into XMM0 & XMM1. + // + // It'd be great if we had PassInfo here... + if (m_returnType.custom_register != XMM0 && m_returnType.custom_register2 != XMM1 && m_returnType.type == DATA_TYPE_OBJECT) { + puts("Return type is an OBJECT but not a Vector. We don't support this right now."); + return; + } + + for (auto& arg : m_vecArgTypes) { + if (arg.custom_register != None) continue; + + if (arg.type == DATA_TYPE_FLOAT || arg.type == DATA_TYPE_DOUBLE) { + for (int i = 0; i < num_reg && arg.custom_register == None; ++i) { + // Register is unused. Assign it. + if (!used_floatreg[i]) { + arg.custom_register = params_floatreg[i]; + used_floatreg[i] = true; + } + } + } else { + for (int i = 0; i < num_reg && arg.custom_register == None; ++i) { + // Register is unused. Assign it. + if (!used_reg[i]) { + arg.custom_register = params_reg[i]; + used_reg[i] = true; + } + } + } + } +} + +std::vector x86_64SystemVDefault::GetRegisters() +{ + std::vector registers; + + registers.push_back(RSP); + + if (m_returnType.custom_register != None) + { + registers.push_back(m_returnType.custom_register); + + if (m_returnType.custom_register2 != None) + { + registers.push_back(m_returnType.custom_register2); + } + } + else if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + { + registers.push_back(XMM0); + } + else + { + registers.push_back(RAX); + } + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + auto reg = m_vecArgTypes[i].custom_register; + if (reg == None) + { + continue; + } + registers.push_back(m_vecArgTypes[i].custom_register); + } + + // Always carry every argument register through the hook, declared or not. A member + // function declared without its `this` (fine on x86, where it stays on the stack) or + // any undeclared argument would otherwise reach the original function clobbered by + // the callbacks. + const Register_t argRegisters[] = { RDI, RSI, RDX, RCX, R8, R9, XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7 }; + for (Register_t reg : argRegisters) + { + if (std::find(registers.begin(), registers.end(), reg) == registers.end()) + { + registers.push_back(reg); + } + } + + return registers; +} + +int x86_64SystemVDefault::GetPopSize() +{ + // Clean-up is caller handled + return 0; +} + +int x86_64SystemVDefault::GetArgStackSize() +{ + size_t stackSize = 0; + for (const auto& arg : m_vecArgTypes) + { + if (arg.custom_register == None) + { + stackSize += arg.size; + } + } + return stackSize; +} + +void** x86_64SystemVDefault::GetStackArgumentPtr(CRegisters* registers) +{ + // Skip return address + return (void **)(registers->m_rsp->GetValue() + 8); +} + +int x86_64SystemVDefault::GetArgRegisterSize() +{ + int argRegisterSize = 0; + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register != None) + { + // It doesn't matter, it's always 8 bytes or less + argRegisterSize += 8; + } + } + + return argRegisterSize; +} + +void* x86_64SystemVDefault::GetArgumentPtr(unsigned int index, CRegisters* registers) +{ + //g_pSM->LogMessage(myself, "Retrieving argument %d (max args %d) registers %p", index, m_vecArgTypes.size(), registers); + if (index >= m_vecArgTypes.size()) + { + //g_pSM->LogMessage(myself, "Not enough arguments"); + return nullptr; + } + + // Check if this argument was passed in a register. + if (m_vecArgTypes[index].custom_register != None) + { + CRegister* reg = registers->GetRegister(m_vecArgTypes[index].custom_register); + if (!reg) + { + //g_pSM->LogMessage(myself, "Register does not exit"); + return nullptr; + } + //g_pSM->LogMessage(myself, "Register arg %d", m_vecArgTypes[index].custom_register); + return reg->m_pAddress; + } + + // Return address + size_t offset = 8; + for (unsigned int i = 0; i < index; i++) + { + if (m_vecArgTypes[i].custom_register == None) + { + // "Regular" types will have a size of 8. + // An object's alignment depends on its contents, which we don't know, so we have to assume the user passed in a size that includes the alignment. + // We at least align object sizes to 8 here though. + // (And dhooks will have already aligned it but maybe this isn't coming from DHooks?) + offset += Align(m_vecArgTypes[i].size, m_iAlignment); + } + } + return (void *) (registers->m_rsp->GetValue() + offset); +} + +void x86_64SystemVDefault::ArgumentPtrChanged(unsigned int index, CRegisters* registers, void* argumentPtr) +{ +} + +void* x86_64SystemVDefault::GetReturnPtr(CRegisters* registers) +{ + // Custom return value register + if (m_returnType.custom_register != None) + { + return registers->GetRegister(m_returnType.custom_register)->m_pAddress; + } + + if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + { + // Floating point register + return registers->m_xmm0->m_pAddress; + } + return registers->m_rax->m_pAddress; +} + +void x86_64SystemVDefault::ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) +{ +} + +void x86_64SystemVDefault::SaveReturnValue(CRegisters* registers) +{ + // Return value for Objects can be a bit complicated but we just have to handle it for Vector right now. + // This means it's either RAX or XMM0&XMM1. + std::unique_ptr savedReturn = std::make_unique(m_returnType.size); + memcpy(savedReturn.get(), GetReturnPtr(registers), m_returnType.size); + m_pSavedReturnBuffers.push_back(std::move(savedReturn)); +} + +void x86_64SystemVDefault::RestoreReturnValue(CRegisters* registers) +{ + uint8_t* savedReturn = m_pSavedReturnBuffers.back().get(); + memcpy(GetReturnPtr(registers), savedReturn, m_returnType.size); + ReturnPtrChanged(registers, savedReturn); + m_pSavedReturnBuffers.pop_back(); +} + +void x86_64SystemVDefault::SaveCallArguments(CRegisters* registers) +{ + int size = GetArgStackSize() + GetArgRegisterSize(); + std::unique_ptr savedCallArguments = std::make_unique(size); + size_t offset = 0; + for (unsigned int i = 0; i < m_vecArgTypes.size(); i++) { + // Doesn't matter the type, it will always be within 8 bytes + memcpy((void *)((uintptr_t)savedCallArguments.get() + offset), GetArgumentPtr(i, registers), 8); + offset += 8; + } + m_pSavedCallArguments.push_back(std::move(savedCallArguments)); +} + +void x86_64SystemVDefault::RestoreCallArguments(CRegisters* registers) +{ + uint8_t *savedCallArguments = m_pSavedCallArguments.back().get(); + size_t offset = 0; + for (size_t i = 0; i < m_vecArgTypes.size(); i++) { + // Doesn't matter the type, it will always be within 8 bytes + memcpy(GetArgumentPtr((unsigned int)i, registers), (void *)((uintptr_t)savedCallArguments + offset), 8); + offset += 8; + } + m_pSavedCallArguments.pop_back(); +} diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h index 10b909f814..c84b4fb611 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h @@ -22,4 +22,46 @@ * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. -*/ \ No newline at end of file +*/ + +#ifndef _X86_64_SYSTEMV_DEFAULT_H +#define _X86_64_SYSTEMV_DEFAULT_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "../convention.h" + +// ============================================================================ +// >> CLASSES +// ============================================================================ +class x86_64SystemVDefault : public ICallingConvention +{ +public: + x86_64SystemVDefault(std::vector& vecArgTypes, DataTypeSized_t returnType, int iAlignment = 8); + virtual ~x86_64SystemVDefault() = default; + + virtual std::vector GetRegisters() override; + virtual int GetPopSize() override; + + virtual int GetArgStackSize() override; + virtual void** GetStackArgumentPtr(CRegisters* registers) override; + + virtual int GetArgRegisterSize() override; + + virtual void* GetArgumentPtr(unsigned int index, CRegisters* registers) override; + virtual void ArgumentPtrChanged(unsigned int index, CRegisters* registers, void* argumentPtr) override; + + virtual void* GetReturnPtr(CRegisters* registers) override; + virtual void ReturnPtrChanged(CRegisters* registers, void* returnPtr) override; + + virtual void SaveReturnValue(CRegisters* registers) override; + virtual void RestoreReturnValue(CRegisters* registers) override; + + virtual void SaveCallArguments(CRegisters* pRegisters) override; + virtual void RestoreCallArguments(CRegisters* pRegisters) override; + +protected: +}; + +#endif // _X86_64_SYSTEMV_DEFAULT_H diff --git a/extensions/dhooks/DynamicHooks/hook.cpp b/extensions/dhooks/DynamicHooks/hook.cpp index 55e4f8aa50..08d1c11827 100644 --- a/extensions/dhooks/DynamicHooks/hook.cpp +++ b/extensions/dhooks/DynamicHooks/hook.cpp @@ -296,6 +296,11 @@ void CHook::CreateBridge() { auto& jit = m_bridge; + // Generate the post-hook code first. Both buffers come from the same page allocator, + // and a GenBuffer that grows marks its old block (so the whole page) read/execute: + // growing one while the other is half written makes the next write fault. + CreatePostCallback(); + //jit.breakpoint(); PrintRegisters(jit); @@ -364,6 +369,8 @@ void CHook::Write_ModifyReturnAddress(x64JitWriter& jit) // Shadow space 32 bytes + 8 bytes to keep it aligned on 16 bytes MSVC_ONLY(jit.sub(rsp, 40)); + // We need to keep it aligned to 16 bytes on Linux too... + GCC_ONLY(jit.sub(rsp, 8)); // 1st param (this) GCC_ONLY(jit.mov(rdi, reinterpret_cast(this))); @@ -381,11 +388,13 @@ void CHook::Write_ModifyReturnAddress(x64JitWriter& jit) jit.mov(rax, func.address); jit.call(rax); + // Free Linux stack alignemnt + GCC_ONLY(jit.add(rsp, 8)); // Free shadow space MSVC_ONLY(jit.add(rsp, 40)); // Override the return address. This is a redirect to our post-hook code - CreatePostCallback(); + // (generated by CreateBridge beforehand). jit.mov(rax, reinterpret_cast(&m_pNewRetAddr)); jit.mov(rax, rax()); jit.mov(rsp(), rax); @@ -420,12 +429,14 @@ void CHook::CreatePostCallback() // Shadow space 32 bytes + 8 bytes to keep it aligned on 16 bytes MSVC_ONLY(jit.sub(rsp, 40)); + // We need to keep it aligned to 16 bytes on Linux too... + GCC_ONLY(jit.sub(rsp, 8)); // 1st param (this) GCC_ONLY(jit.mov(rdi, reinterpret_cast(this))); MSVC_ONLY(jit.mov(rcx, reinterpret_cast(this))); - // 2n parameter (rsp) + // 2nd parameter (rsp) GCC_ONLY(jit.lea(rsi, rsp())); MSVC_ONLY(jit.lea(rdx, rsp(40))); @@ -433,6 +444,8 @@ void CHook::CreatePostCallback() jit.mov(rax, func.address); jit.call(rax); + // Free Linux stack alignemnt + GCC_ONLY(jit.add(rsp, 8)); // Free shadow space MSVC_ONLY(jit.add(rsp, 40)); @@ -453,6 +466,8 @@ void CHook::Write_CallHandler(x64JitWriter& jit, HookType_t type) // Shadow space 32 bytes + 8 bytes to keep it aligned on 16 bytes MSVC_ONLY(jit.sub(rsp, 40)); + // We need to keep it aligned to 16 bytes on Linux too... + GCC_ONLY(jit.sub(rsp, 8)); // Call the global hook handler @@ -467,6 +482,8 @@ void CHook::Write_CallHandler(x64JitWriter& jit, HookType_t type) jit.mov(rax, func.address); jit.call(rax); + // Free Linux stack alignemnt + GCC_ONLY(jit.add(rsp, 8)); // Free shadow space MSVC_ONLY(jit.add(rsp, 40)); } diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index 9f7057c3e5..73184325eb 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -49,6 +49,8 @@ typedef x86MsFastcall x86DetourFastCall; #endif #elif defined KE_LINUX #ifdef DYNAMICHOOKS_x86_64 +#include "conventions/x86_64SystemVDefault.h" +typedef x86_64SystemVDefault x86_64DetourCall; #else #include "conventions/x86GccCdecl.h" #include "conventions/x86GccThiscall.h" @@ -253,17 +255,28 @@ ICallingConvention *ConstructCallingConvention(HookSetup *setup) returnType.size = 0; // TODO: Add support for a custom return register. returnType.custom_register = None; + returnType.custom_register2 = None; + +#if defined(DYNAMICHOOKS_x86_64) && defined(PLATFORM_LINUX) + if (setup->returnType == ReturnType_Vector) { + returnType.size = 12; + returnType.custom_register = XMM0; + returnType.custom_register2 = XMM1; + } +#endif #ifdef DYNAMICHOOKS_x86_64 -#ifdef WIN32 if (setup->callConv == CallConv_THISCALL) { DataTypeSized_t type; type.type = DATA_TYPE_POINTER; type.size = GetDataTypeSize(type, sizeof(void*)); +#ifdef PLATFORM_WINDOWS type.custom_register = RCX; +#else + type.custom_register = RDI; +#endif vecArgTypes.insert(vecArgTypes.begin(), type); } -#endif #endif ICallingConvention *pCallConv = nullptr; @@ -308,19 +321,27 @@ bool UpdateRegisterArgumentSizes(CHook* pDetour, HookSetup *setup) ICallingConvention* callingConvention = pDetour->m_pCallingConvention; std::vector &argTypes = callingConvention->m_vecArgTypes; int numArgs = argTypes.size(); + int argTypesOffset = 0; + +#ifdef DYNAMICHOOKS_x86_64 + if (setup->callConv == CallConv_THISCALL) { + argTypesOffset = 1; + --numArgs; + } +#endif for (int i = 0; i < numArgs; i++) { // Ignore regular arguments on the stack. - if (argTypes[i].custom_register == None) + if (argTypes[argTypesOffset+i].custom_register == None) continue; - CRegister *reg = pDetour->m_pRegisters->GetRegister(argTypes[i].custom_register); + CRegister *reg = pDetour->m_pRegisters->GetRegister(argTypes[argTypesOffset+i].custom_register); // That register can't be handled yet. if (!reg) return false; - argTypes[i].size = reg->m_iSize; + argTypes[argTypesOffset+i].size = reg->m_iSize; setup->params[i].size = reg->m_iSize; } @@ -375,6 +396,7 @@ ReturnAction_t HandleDetour(HookType_t hookType, CHook* pDetour) if (pWrapper->callConv == CallConv_THISCALL && pWrapper->thisType != ThisPointer_Ignore) { // The this pointer is implicitly always the first argument. + // TODO: Linux64 with an object return value can mean `this` is the second argument, but not relevant for now. auto thisAddr = pDetour->GetArgument(0); if (pWrapper->thisType == ThisPointer_CBaseEntity) { if (thisAddr == nullptr) { @@ -387,7 +409,7 @@ ReturnAction_t HandleDetour(HookType_t hookType, CHook* pDetour) std::int64_t addr = reinterpret_cast(thisAddr); pWrapper->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - pWrapper->plugin_callback->PushCell((cell_t)thisAddr); + pWrapper->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } @@ -696,6 +718,7 @@ void CDynamicHooksSourcePawn::UpdateParamsFromStruct(HookParamsStruct *params) size_t numArgs = argTypes.size(); size_t firstArg = 0; + // TODO: Linux64 will use RDI for retmem even if thiscall // TODO: Support custom register for this ptr. if (callConv == CallConv_THISCALL) firstArg = 1; @@ -719,7 +742,7 @@ void CDynamicHooksSourcePawn::UpdateParamsFromStruct(HookParamsStruct *params) // Keep track of the seperate stack and register arguments. if (argTypes[i].custom_register == None) { -#ifdef DYNAMICHOOKS_x86_64 +#if defined(DYNAMICHOOKS_x86_64) && defined(PLATFORM_WINDOWS) stackOffset += 8; #else stackOffset += size; diff --git a/extensions/dhooks/natives.cpp b/extensions/dhooks/natives.cpp index 92254ba618..65f101834e 100644 --- a/extensions/dhooks/natives.cpp +++ b/extensions/dhooks/natives.cpp @@ -174,7 +174,7 @@ cell_t Native_CreateHook(IPluginContext *pContext, const cell_t *params) //native Handle:DHookCreateDetour(Address:funcaddr, CallingConvention:callConv, ReturnType:returntype, ThisPointerType:thistype); cell_t Native_CreateDetour(IPluginContext *pContext, const cell_t *params) { - void *addr = reinterpret_cast(params[1]); + void *addr = LegacyAddressToPtr(params[1]); if (pContext->GetRuntime()->FindPubvarByName("__Int64_Address__", nullptr) == SP_ERROR_NONE) { cell_t* sp_addr; if (int err = pContext->LocalToPhysAddr(params[1], &sp_addr); err != SP_ERROR_NONE) { @@ -353,6 +353,7 @@ cell_t Native_AddParam(IPluginContext *pContext, const cell_t *params) info.flags = PASSFLAG_BYVAL; } + // TODO: why? // DynamicDetours doesn't expose the passflags concept like SourceHook. // See if we're trying to set some invalid flags on detour arguments. if(setup->hookMethod == Detour && (info.flags & ~PASSFLAG_BYVAL) > 0) @@ -651,7 +652,7 @@ cell_t HookRawImpl(IPluginContext *pContext, const cell_t *params, int callbackI if (removalcbIndex > 0) removalcb = pContext->GetFunctionById(params[removalcbIndex]); - void *iface = reinterpret_cast(params[3]); + void *iface = LegacyAddressToPtr(params[3]); if (pContext->GetRuntime()->FindPubvarByName("__Int64_Address__", nullptr) == SP_ERROR_NONE) { cell_t* sp_addr; if (int err = pContext->LocalToPhysAddr(params[3], &sp_addr); err != SP_ERROR_NONE) { @@ -1514,8 +1515,9 @@ cell_t Native_GetParamAddress(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeErrorEx(err, "Could not read argument"); } *reinterpret_cast(sp_addr) = reinterpret_cast(return_ptr); + return 0; } - return reinterpret_cast(return_ptr); + return PtrToLegacyAddress(return_ptr); } sp_nativeinfo_t g_Natives[] = diff --git a/extensions/dhooks/util.cpp b/extensions/dhooks/util.cpp index ac9b678a10..02f48c089e 100644 --- a/extensions/dhooks/util.cpp +++ b/extensions/dhooks/util.cpp @@ -37,7 +37,7 @@ void * GetObjectAddr(HookParamType type, unsigned int flags, void **params, size 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. + if (type == HookParamType_Object && !(flags & PASSFLAG_ODTOR)) //Objects are passed by reference if they contain destructors. return (void *)((intptr_t)params + offset); #endif return *(void **)((intptr_t)params + offset); @@ -62,7 +62,7 @@ size_t GetStackParamOffset(HookParamsStruct *paramStruct, unsigned int index) continue; } #endif -#ifdef KE_ARCH_X64 +#if defined(KE_ARCH_X64) && defined(WIN64) offset += 8; #else offset += paramStruct->dg->params[i].size; diff --git a/extensions/dhooks/util.h b/extensions/dhooks/util.h index a168c6babe..9b1bbcda7a 100644 --- a/extensions/dhooks/util.h +++ b/extensions/dhooks/util.h @@ -74,6 +74,19 @@ enum PluginRegister DHookRegister_ST0 }; +// Plugins built before `Address` became int64 pass 32-bit addresses. Like the core +// natives (LoadFromAddress, ...), convert them as-is: lossless on x86, but on x86_64 +// such plugins can't hold a pointer and must be rebuilt with SourceMod 1.13+. +inline cell_t PtrToLegacyAddress(void *ptr) +{ + return static_cast(reinterpret_cast(ptr)); +} + +inline void *LegacyAddressToPtr(cell_t addr) +{ + return reinterpret_cast(static_cast(addr)); +} + 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); diff --git a/extensions/dhooks/vhook.cpp b/extensions/dhooks/vhook.cpp index 21b99e5093..d9da5c99be 100644 --- a/extensions/dhooks/vhook.cpp +++ b/extensions/dhooks/vhook.cpp @@ -56,6 +56,7 @@ using namespace sp; #ifdef KE_ARCH_X64 using namespace SourceHook::Asm; +#ifdef WIN32 SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) { auto masm = new x64JitWriter(); @@ -132,6 +133,147 @@ SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) masm->SetRE(); return masm; } +#else +// linux64 thunker +SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) +{ + auto masm = new x64JitWriter(); + auto type = hook->returnType; + + // Save our frame pointer. + // This also realigns the stack to 16 bytes. + masm->push(rbp); + masm->mov(rbp, rsp); + + // SourceHook calls us as ISHDelegate::Call(this, params...): rdi is the delegate, + // integer params follow in rsi, rdx, rcx, r8, r9, floats in xmm0-xmm7, the rest on the stack. + // Rebuild the arg stack Callback() expects, at the offsets GetStackParamOffset() computes: + // 8 bytes per param, objects passed by value take their own size. + static x86_64_Reg arg_reg[] = { rsi, rdx, rcx, r8, r9 }; + static x86_64_FloatReg arg_reg_float[] = { xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 }; + + // Vector/string_t callbacks take a result pointer in argStack[0] and read params after OBJECT_OFFSET. + bool resultInStack = (type == ReturnType_Vector || type == ReturnType_String); + int32_t argsOffset = resultInStack ? OBJECT_OFFSET : 0; + int32_t argsSize = 0; + for (size_t i = 0; i < hook->params.size(); i++) + { + bool byRefObject = hook->params[i].type == HookParamType_Object && (hook->params[i].flags & PASSFLAG_ODTOR); + argsSize += byRefObject ? sizeof(void *) : hook->params[i].size; + } + // + 8 bytes of slack: by-value objects are copied 8 bytes at a time + int32_t resultOffset = argsOffset + argsSize + 8; + int32_t fakeStackSize = ke::Align(resultOffset + (resultInStack ? (int32_t)sizeof(SDKVector) + 4 : 0), 16); + masm->sub(rsp, fakeStackSize); + + if (resultInStack) + { + masm->lea(rax, rsp(resultOffset)); + masm->mov(rsp(0), rax); + } + + int reg_index = 0; + int float_index = 0; + int32_t callerStack = 16; // return address + saved rbp + int32_t offset = argsOffset; + for (size_t i = 0; i < hook->params.size(); i++) + { + const ParamInfo ¶m = hook->params[i]; + bool byRefObject = param.type == HookParamType_Object && (param.flags & PASSFLAG_ODTOR); + if (param.type == HookParamType_Float && param.flags == PASSFLAG_BYVAL) + { + if (float_index < 8) + { + masm->movsd(rsp(offset), arg_reg_float[float_index++]); + } + else + { + masm->mov(rax, rbp(callerStack)); + masm->mov(rsp(offset), rax); + callerStack += 8; + } + offset += param.size; + } + else if (param.type == HookParamType_Object && !byRefObject) + { + // Trivially copyable objects: System V passes those over 16 bytes inline on the stack. + // Smaller ones are split over registers by field class, which we can't know: assume integer fields. + int32_t qwords = ke::Align((int32_t)param.size, 8) / 8; + for (int32_t q = 0; q < qwords; q++) + { + if (param.size > 16 || reg_index + qwords > 5) + { + masm->mov(rax, rbp(callerStack + q * 8)); + masm->mov(rsp(offset + q * 8), rax); + } + else + { + masm->mov(rsp(offset + q * 8), arg_reg[reg_index + q]); + } + } + if (param.size > 16 || reg_index + qwords > 5) + callerStack += qwords * 8; + else + reg_index += qwords; + offset += param.size; + } + else + { + if (reg_index < 5) + { + masm->mov(rsp(offset), arg_reg[reg_index++]); + } + else + { + masm->mov(rax, rbp(callerStack)); + masm->mov(rsp(offset), rax); + callerStack += 8; + } + offset += byRefObject ? sizeof(void *) : param.size; + } + } + + // Callback(dg, argStack): rdi still holds the delegate. + masm->mov(rsi, rsp); + if (type == ReturnType_Float) + { + masm->mov(rax, (uintptr_t)Callback_float); + } + else if (type == ReturnType_Vector) + { + masm->mov(rax, (uintptr_t)Callback_vector); + } + else if (type == ReturnType_String) + { + masm->mov(rax, (uintptr_t)Callback_stringt); + } + else + { + masm->mov(rax, (uintptr_t)Callback); + } + masm->call(rax); + + if (type == ReturnType_Vector) + { + // Vector is returned by value in xmm0 (x, y) and xmm1 (z). + masm->movsd(xmm0, rax()); + masm->movsd(xmm1, rax(8)); + } + else if (type == ReturnType_String) + { + masm->mov(rax, rax()); + } + + // Restore RSP and RBP + // (same as `mov rsp, rbp` + `pop rbp`) + masm->leave(); + + masm->retn(); + + masm->SetRE(); + return masm; +} +#endif #elif !defined( WIN32 ) void *GenerateThunk(HookSetup* hook) { @@ -514,7 +656,7 @@ void *Callback(DHooksCallback *dg, void **argStack) std::int64_t addr = reinterpret_cast(thisAddr); dg->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - dg->plugin_callback->PushCell((cell_t)thisAddr); + dg->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } @@ -712,7 +854,7 @@ float Callback_float(DHooksCallback *dg, void **argStack) std::int64_t addr = reinterpret_cast(thisAddr); dg->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - dg->plugin_callback->PushCell((cell_t)thisAddr); + dg->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } @@ -883,7 +1025,7 @@ SDKVector *Callback_vector(DHooksCallback *dg, void **argStack) std::int64_t addr = reinterpret_cast(thisAddr); dg->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - dg->plugin_callback->PushCell((cell_t)thisAddr); + dg->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } @@ -1051,7 +1193,7 @@ string_t *Callback_stringt(DHooksCallback *dg, void **argStack) std::int64_t addr = reinterpret_cast(thisAddr); dg->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - dg->plugin_callback->PushCell((cell_t)thisAddr); + dg->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } diff --git a/extensions/dhooks/vhook.h b/extensions/dhooks/vhook.h index fc095047ad..9ccd1010f7 100644 --- a/extensions/dhooks/vhook.h +++ b/extensions/dhooks/vhook.h @@ -133,12 +133,6 @@ struct ParamInfo Register_t custom_register; }; -#ifdef WIN32 -#define OBJECT_OFFSET sizeof(void *) -#else -#define OBJECT_OFFSET (sizeof(void *)*2) -#endif - class HookReturnStruct { public: diff --git a/plugins/testsuite/dhooks/dhooks-css-test.games.txt b/plugins/testsuite/dhooks/dhooks-css-test.games.txt new file mode 100644 index 0000000000..88b4a4290c --- /dev/null +++ b/plugins/testsuite/dhooks/dhooks-css-test.games.txt @@ -0,0 +1,67 @@ +"Games" +{ + "cstrike" + { + "Offsets" + { + "Teleport" + { + "linux" "112" + "linux64" "112" + } + "OnTakeDamage" + { + "linux" "65" + "linux64" "65" + } + "AcceptInput" + { + "linux" "39" + "linux64" "39" + } + // After the value union (12 bytes on x86, 16 on x86_64 where string_t is a pointer) and the EHANDLE. + "variant_t::fieldType" + { + "linux" "16" + "linux64" "20" + } + "Weapon_ShootPosition" + { + "linux" "273" + "linux64" "273" + } + "GetPlayerMaxSpeed" + { + "linux" "447" + "linux64" "447" + } + } + "Signatures" + { + "RoundRespawn" + { + "library" "server" + "linux" "@_ZN9CCSPlayer12RoundRespawnEv" + "linux64" "@_ZN9CCSPlayer12RoundRespawnEv" + } + "SwitchTeam" + { + "library" "server" + "linux" "@_ZN9CCSPlayer10SwitchTeamEi" + "linux64" "@_ZN9CCSPlayer10SwitchTeamEi" + } + "SetHibernating" + { + "library" "engine" + "linux" "@_ZN11CGameServer14SetHibernatingEb" + "linux64" "@_ZN11CGameServer14SetHibernatingEb" + } + "TerminateRound" + { + "library" "server" + "linux" "@_ZN12CCSGameRules14TerminateRoundEfi" + "linux64" "@_ZN12CCSGameRules14TerminateRoundEfi" + } + } + } +} diff --git a/plugins/testsuite/dhooks/dhooks-css-test.sp b/plugins/testsuite/dhooks/dhooks-css-test.sp new file mode 100644 index 0000000000..b99bcb5a2f --- /dev/null +++ b/plugins/testsuite/dhooks/dhooks-css-test.sp @@ -0,0 +1,283 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include +#include +#include + +// Runtime check of DHooks on CS:S (x86 and x86_64): virtual hooks + detours, +// int/float/bool/vector/object params, param changes and return values. +// +// Install dhooks-css-test.games.txt into gamedata/. Run `dhx_test` with at least one +// alive bot, or start the server with -dhx_auto to run it unattended and quit. +// Every check logs a "DHX:" line; compare the values with the ones the test passes. +DynamicHook g_hTeleport; +DynamicHook g_hOnTakeDamage; +DynamicHook g_hAcceptInput; +DynamicHook g_hShootPosition; +DynamicHook g_hMaxSpeed; +Handle g_hCallShootPosition; +Handle g_hCallMaxSpeed; +int g_iVariantFieldType; +DynamicDetour g_dRoundRespawn; +DynamicDetour g_dSwitchTeam; +DynamicDetour g_dTerminateRound; +DynamicDetour g_dSetHibernating; + +public void OnPluginStart() +{ + GameData gd = new GameData("dhooks-css-test.games"); + if (gd == null) + SetFailState("gamedata missing"); + + g_hTeleport = new DynamicHook(gd.GetOffset("Teleport"), HookType_Entity, ReturnType_Void, ThisPointer_CBaseEntity); + g_hTeleport.AddParam(HookParamType_VectorPtr); + g_hTeleport.AddParam(HookParamType_ObjectPtr); + g_hTeleport.AddParam(HookParamType_VectorPtr); + + g_hOnTakeDamage = new DynamicHook(gd.GetOffset("OnTakeDamage"), HookType_Entity, ReturnType_Int, ThisPointer_CBaseEntity); + g_hOnTakeDamage.AddParam(HookParamType_ObjectPtr); + + // variant_t is passed by hidden reference (CHandle has a copy constructor), hence ODTOR. + g_hAcceptInput = new DynamicHook(gd.GetOffset("AcceptInput"), HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity); + g_hAcceptInput.AddParam(HookParamType_CharPtr); + g_hAcceptInput.AddParam(HookParamType_CBaseEntity); + g_hAcceptInput.AddParam(HookParamType_CBaseEntity); + g_hAcceptInput.AddParam(HookParamType_Object, 20, DHookPass_ByVal|DHookPass_ODTOR|DHookPass_OCTOR|DHookPass_OASSIGNOP); + g_hAcceptInput.AddParam(HookParamType_Int); + + g_iVariantFieldType = gd.GetOffset("variant_t::fieldType"); + + g_hShootPosition = new DynamicHook(gd.GetOffset("Weapon_ShootPosition"), HookType_Entity, ReturnType_Vector, ThisPointer_CBaseEntity); + g_hMaxSpeed = new DynamicHook(gd.GetOffset("GetPlayerMaxSpeed"), HookType_Entity, ReturnType_Float, ThisPointer_CBaseEntity); + + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(gd, SDKConf_Virtual, "Weapon_ShootPosition"); + PrepSDKCall_SetReturnInfo(SDKType_Vector, SDKPass_ByValue); + g_hCallShootPosition = EndPrepSDKCall(); + + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(gd, SDKConf_Virtual, "GetPlayerMaxSpeed"); + PrepSDKCall_SetReturnInfo(SDKType_Float, SDKPass_Plain); + g_hCallMaxSpeed = EndPrepSDKCall(); + + g_dRoundRespawn = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_CBaseEntity); + CheckDetour(g_dRoundRespawn.SetFromConf(gd, SDKConf_Signature, "RoundRespawn"), "RoundRespawn"); + CheckDetour(g_dRoundRespawn.Enable(Hook_Pre, Detour_RoundRespawn), "RoundRespawn enable"); + + g_dSwitchTeam = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_CBaseEntity); + g_dSwitchTeam.AddParam(HookParamType_Int); + CheckDetour(g_dSwitchTeam.SetFromConf(gd, SDKConf_Signature, "SwitchTeam"), "SwitchTeam"); + CheckDetour(g_dSwitchTeam.Enable(Hook_Pre, Detour_SwitchTeam), "SwitchTeam enable"); + + g_dTerminateRound = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_Address); + g_dTerminateRound.AddParam(HookParamType_Float); + g_dTerminateRound.AddParam(HookParamType_Int); + CheckDetour(g_dTerminateRound.SetFromConf(gd, SDKConf_Signature, "TerminateRound"), "TerminateRound"); + CheckDetour(g_dTerminateRound.Enable(Hook_Pre, Detour_TerminateRoundPre), "TerminateRound enable"); + CheckDetour(g_dTerminateRound.Enable(Hook_Post, Detour_TerminateRoundPost), "TerminateRound enable post"); + + // Keep the server awake so bots can join (no cvar for it on CS:S). + g_dSetHibernating = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_Address); + g_dSetHibernating.AddParam(HookParamType_Bool); + CheckDetour(g_dSetHibernating.SetFromConf(gd, SDKConf_Signature, "SetHibernating"), "SetHibernating"); + CheckDetour(g_dSetHibernating.Enable(Hook_Pre, Detour_SetHibernating), "SetHibernating enable"); + + delete gd; + if (FindCommandLineParam("-dhx_auto")) + CreateConVar("dhx_auto", "1"); + RegServerCmd("dhx_test", Command_Test); + HookEvent("round_freeze_end", Event_RoundFreezeEnd, EventHookMode_PostNoCopy); + LogMessage("DHX: loaded, hooks set up"); +} + +// Unattended mode (no console input under gdb): run once a round is live, then quit. +public void Event_RoundFreezeEnd(Event hEvent, const char[] sName, bool bDontBroadcast) +{ + static bool bDone; + if (bDone || FindConVar("dhx_auto") == null) + return; + bDone = true; + CreateTimer(2.0, Timer_AutoTest); +} + +public Action Timer_AutoTest(Handle hTimer) +{ + Command_Test(0); + CreateTimer(3.0, Timer_Quit); + return Plugin_Stop; +} + +public Action Timer_Quit(Handle hTimer) +{ + LogMessage("DHX: auto quit"); + ServerCommand("quit"); + return Plugin_Stop; +} + +void CheckDetour(bool bOk, const char[] sWhat) +{ + if (!bOk) + SetFailState("DHX: %s failed", sWhat); +} + +public Action Command_Test(int iArgs) +{ + int iBot = -1; + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && IsFakeClient(i) && IsPlayerAlive(i) && !IsClientSourceTV(i)) + { + iBot = i; + break; + } + } + if (iBot == -1) + { + LogMessage("DHX: no alive bot"); + return Plugin_Handled; + } + + LogMessage("DHX: testing on bot %d (%N)", iBot, iBot); + g_hTeleport.HookEntity(Hook_Pre, iBot, Hook_Teleport); + g_hOnTakeDamage.HookEntity(Hook_Pre, iBot, Hook_OnTakeDamagePre); + g_hOnTakeDamage.HookEntity(Hook_Post, iBot, Hook_OnTakeDamagePost); + + float fPos[3] = {123.5, -456.25, 64.0}; + TeleportEntity(iBot, fPos, NULL_VECTOR, NULL_VECTOR); + float fNow[3]; + GetClientAbsOrigin(iBot, fNow); + LogMessage("DHX: bot origin after TeleportEntity: %.2f %.2f %.2f", fNow[0], fNow[1], fNow[2]); + // SDKHooks_TakeDamage bypasses hooks (SH_MCALL), so hurt through a real point_hurt. + int iHealth = GetClientHealth(iBot); + DispatchKeyValue(iBot, "targetname", "dhx_victim"); + int iHurt = CreateEntityByName("point_hurt"); + DispatchKeyValue(iHurt, "DamageTarget", "dhx_victim"); + DispatchKeyValue(iHurt, "Damage", "20"); + DispatchSpawn(iHurt); + AcceptEntityInput(iHurt, "Hurt"); + RemoveEntity(iHurt); + LogMessage("DHX: health %d -> %d (pre-hook halves 20 damage: expect -10)", iHealth, GetClientHealth(iBot)); + + float fEye[3], fShoot[3]; + GetClientEyePosition(iBot, fEye); + SDKCall(g_hCallShootPosition, iBot, fShoot); + LogMessage("DHX: Weapon_ShootPosition (no hook) = %.2f %.2f %.2f", fShoot[0], fShoot[1], fShoot[2]); + g_hShootPosition.HookEntity(Hook_Post, iBot, Hook_ShootPositionPost); + SDKCall(g_hCallShootPosition, iBot, fShoot); + LogMessage("DHX: Weapon_ShootPosition (post hook, unchanged) = %.2f %.2f %.2f, eye = %.2f %.2f %.2f", fShoot[0], fShoot[1], fShoot[2], fEye[0], fEye[1], fEye[2]); + g_hShootPosition.HookEntity(Hook_Pre, iBot, Hook_ShootPositionPre); + SDKCall(g_hCallShootPosition, iBot, fShoot); + LogMessage("DHX: Weapon_ShootPosition (pre hook supercede) = %.2f %.2f %.2f (expect 1.50 -2.25 3.00)", fShoot[0], fShoot[1], fShoot[2]); + + float fSpeed = SDKCall(g_hCallMaxSpeed, iBot); + g_hMaxSpeed.HookEntity(Hook_Pre, iBot, Hook_MaxSpeedPre); + LogMessage("DHX: GetPlayerMaxSpeed %.2f -> %.2f (expect 123.50)", fSpeed, view_as(SDKCall(g_hCallMaxSpeed, iBot))); + + g_hAcceptInput.HookEntity(Hook_Pre, iBot, Hook_AcceptInput); + SetVariantString("255 0 0"); + AcceptEntityInput(iBot, "Color", iBot, iBot); + + int iTeam = GetClientTeam(iBot); + CS_SwitchTeam(iBot, iTeam == CS_TEAM_T ? CS_TEAM_CT : CS_TEAM_T); + CS_RespawnPlayer(iBot); + CS_TerminateRound(3.0, CSRoundEnd_Draw); + LogMessage("DHX: test done"); + return Plugin_Handled; +} + +MRESReturn Hook_Teleport(int iEntity, DHookParam hParams) +{ + float fPos[3]; + hParams.GetVector(1, fPos); + LogMessage("DHX: [vhook] Teleport ent=%d origin=%.2f %.2f %.2f angNull=%d", iEntity, fPos[0], fPos[1], fPos[2], hParams.IsNull(2)); + return MRES_Ignored; +} + +MRESReturn Hook_OnTakeDamagePre(int iEntity, DHookReturn hReturn, DHookParam hParams) +{ + // CTakeDamageInfo: Vector m_vecDamageForce, m_vecDamagePosition, m_vecReportedPosition, EHANDLE x3, float m_flDamage + float fDamage = hParams.GetObjectVar(1, 48, ObjectValueType_Float); + hParams.SetObjectVar(1, 48, ObjectValueType_Float, fDamage / 2.0); + LogMessage("DHX: [vhook] OnTakeDamage pre ent=%d damage=%.2f -> %.2f", iEntity, fDamage, fDamage / 2.0); + return MRES_ChangedHandled; +} + +MRESReturn Hook_OnTakeDamagePost(int iEntity, DHookReturn hReturn, DHookParam hParams) +{ + LogMessage("DHX: [vhook] OnTakeDamage post ent=%d return=%d", iEntity, hReturn.Value); + return MRES_Ignored; +} + +MRESReturn Detour_RoundRespawn(int iClient) +{ + LogMessage("DHX: [detour] RoundRespawn client=%d", iClient); + return MRES_Ignored; +} + +MRESReturn Detour_SwitchTeam(int iClient, DHookParam hParams) +{ + LogMessage("DHX: [detour] SwitchTeam client=%d team=%d", iClient, hParams.Get(1)); + return MRES_Ignored; +} + +MRESReturn Detour_TerminateRoundPre(Address pGameRules, DHookParam hParams) +{ + float fDelay = hParams.Get(1); + int iReason = hParams.Get(2); + LogMessage("DHX: [detour] TerminateRound pre this=%s delay=%.2f reason=%d -> delay 1.5", pGameRules == Address_Null ? "null" : "ok", fDelay, iReason); + hParams.Set(1, 1.5); + return MRES_ChangedHandled; +} + +MRESReturn Detour_TerminateRoundPost(Address pGameRules, DHookParam hParams) +{ + LogMessage("DHX: [detour] TerminateRound post delay=%.2f reason=%d", view_as(hParams.Get(1)), hParams.Get(2)); + return MRES_Ignored; +} + +MRESReturn Detour_SetHibernating(Address pServer, DHookParam hParams) +{ + static bool bLogged; + if (!bLogged) + { + bLogged = true; + LogMessage("DHX: [detour engine] SetHibernating(%d) -> 0", hParams.Get(1)); + } + hParams.Set(1, false); + return MRES_ChangedHandled; +} + +MRESReturn Hook_ShootPositionPost(int iEntity, DHookReturn hReturn) +{ + float fVec[3]; + hReturn.GetVector(fVec); + LogMessage("DHX: [vhook] Weapon_ShootPosition post ent=%d return=%.2f %.2f %.2f", iEntity, fVec[0], fVec[1], fVec[2]); + return MRES_Ignored; +} + +MRESReturn Hook_ShootPositionPre(int iEntity, DHookReturn hReturn) +{ + float fVec[3] = {1.5, -2.25, 3.0}; + hReturn.SetVector(fVec); + return MRES_Supercede; +} + +MRESReturn Hook_MaxSpeedPre(int iEntity, DHookReturn hReturn) +{ + hReturn.Value = 123.5; + return MRES_Supercede; +} + +MRESReturn Hook_AcceptInput(int iEntity, DHookReturn hReturn, DHookParam hParams) +{ + char sInput[32], sValue[32]; + hParams.GetString(1, sInput, sizeof(sInput)); + int iActivator = hParams.IsNull(2) ? -1 : hParams.Get(2); + int iType = hParams.GetObjectVar(4, g_iVariantFieldType, ObjectValueType_Int); + hParams.GetObjectVarString(4, 0, ObjectValueType_String, sValue, sizeof(sValue)); + LogMessage("DHX: [vhook] AcceptInput ent=%d input=%s activator=%d variant type=%d value=\"%s\" outputid=%d", iEntity, sInput, iActivator, iType, sValue, hParams.Get(5)); + return MRES_Ignored; +}