From aa3018a27de9f6b1ba6c2530816d53704084a876 Mon Sep 17 00:00:00 2001 From: Peace-Maker Date: Thu, 25 Jan 2018 17:18:30 +0100 Subject: [PATCH] Add support to define function signatures in gamedata A "Functions" section is parsed in gamedata files that allow you to define the signature of functions including metadata like the calling convention and specifying the register an argument is passed in. A new native DHookCreateFromConf can be used to setup a hook or detour from one of that function sections in the "Functions" section. --- AMBuildScript | 1 + extension.cpp | 5 + msvc10/sdk.vcxproj | 1 + msvc10/sdk.vcxproj.filters | 1 + natives.cpp | 88 +++- signatures.cpp | 562 +++++++++++++++++++++++++ signatures.h | 41 ++ sourcemod/gamedata/fbtools.games.txt | 77 ++++ sourcemod/scripting/dynhooks-test.sp | 7 +- sourcemod/scripting/include/dhooks.inc | 16 +- 10 files changed, 792 insertions(+), 7 deletions(-) create mode 100644 signatures.cpp create mode 100644 signatures.h create mode 100644 sourcemod/gamedata/fbtools.games.txt diff --git a/AMBuildScript b/AMBuildScript index ec14a05..1e8f8d4 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -255,6 +255,7 @@ program.sources += [ 'extension.cpp', 'listeners.cpp', 'natives.cpp', + 'signatures.cpp', 'vhook.cpp', 'util.cpp', 'dynhooks_sourcepawn.cpp', diff --git a/extension.cpp b/extension.cpp index 93d7a9b..0e905b3 100644 --- a/extension.cpp +++ b/extension.cpp @@ -1,6 +1,7 @@ #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); @@ -57,6 +58,7 @@ bool DHooks::SDK_OnLoad(char *error, size_t maxlength, bool late) sharesys->AddNatives(myself, g_Natives); g_pEntityListener = new DHooksEntityListener(); + g_pSignatures = new SignatureGameConfig(); return true; } @@ -84,6 +86,7 @@ void DHooks::SDK_OnAllLoaded() SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks); g_pSDKHooks->AddEntityListener(g_pEntityListener); + gameconfs->AddUserConfigHook("Functions", g_pSignatures); } void DHooks::SDK_OnUnload() @@ -101,6 +104,8 @@ void DHooks::SDK_OnUnload() 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) diff --git a/msvc10/sdk.vcxproj b/msvc10/sdk.vcxproj index e82f3b7..057532c 100644 --- a/msvc10/sdk.vcxproj +++ b/msvc10/sdk.vcxproj @@ -121,6 +121,7 @@ + diff --git a/msvc10/sdk.vcxproj.filters b/msvc10/sdk.vcxproj.filters index 1d7e1d7..8d8b328 100644 --- a/msvc10/sdk.vcxproj.filters +++ b/msvc10/sdk.vcxproj.filters @@ -37,6 +37,7 @@ DynamicHooks\conventions + diff --git a/natives.cpp b/natives.cpp index a61b143..b091f45 100644 --- a/natives.cpp +++ b/natives.cpp @@ -1,6 +1,7 @@ #include "natives.h" #include "util.h" #include "dynhooks_sourcepawn.h" +#include "signatures.h" // Must match same enum in sdktools.inc enum SDKFuncConfSource @@ -48,7 +49,7 @@ cell_t Native_CreateHook(IPluginContext *pContext, const cell_t *params) return hndl; } -//native Handle:DHookCreateDetour(Address:funcaddr, CallingConvention callConv, ReturnType:returntype, ThisPointerType:thistype); +//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]); @@ -64,6 +65,86 @@ cell_t Native_CreateDetour(IPluginContext *pContext, const cell_t *params) return hndl; } +// native Handle:DHookCreateFromConf(Handle:gameconf, const String:function[], DHookCallback:callback=INVALID_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); + } + + IPluginFunction *callback = nullptr; + if (params[3] != -1) + { + callback = pContext->GetFunctionById(params[3]); + if (!callback) + return pContext->ThrowNativeError("Failed to find callback function."); + } + + HookSetup *setup = nullptr; + // This is a virtual hook. + if (sig->offset.length() > 0) + { + int offset; + if (!conf->GetOffset(sig->offset.chars(), &offset)) + { + return BAD_HANDLE; + } + + setup = new HookSetup(sig->retType, PASSFLAG_BYVAL, sig->hookType, sig->thisType, offset, callback); + } + // This is a detour. + else + { + void *addr = nullptr;; + if (sig->signature.length() > 0) + { + if (!conf->GetMemSig(sig->signature.chars(), &addr) || !addr) + { + return BAD_HANDLE; + } + } + else + { + if (!conf->GetAddress(sig->address.chars(), &addr) || !addr) + { + return BAD_HANDLE; + } + } + + setup = new HookSetup(sig->retType, PASSFLAG_BYVAL, sig->callConv, sig->thisType, addr); + } + + // Push all the arguments. + auto args = sig->args.iter(); + while (!args.empty()) + { + ParamInfo info = args->value; + setup->params.push_back(info); + args.next(); + } + + // 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) @@ -1232,10 +1313,11 @@ sp_nativeinfo_t g_Natives[] = { {"DHookCreate", Native_CreateHook}, {"DHookCreateDetour", Native_CreateDetour}, - {"DHookSetFromConf", Native_SetFromConf }, + {"DHookCreateFromConf", Native_DHookCreateFromConf}, + {"DHookSetFromConf", Native_SetFromConf}, {"DHookAddParam", Native_AddParam}, {"DHookEnableDetour", Native_EnableDetour}, - //{"DHookDisableDetour", Native_DisableDetour}, + {"DHookDisableDetour", Native_DisableDetour}, {"DHookEntity", Native_HookEntity}, {"DHookGamerules", Native_HookGamerules}, {"DHookRaw", Native_HookRaw}, diff --git a/signatures.cpp b/signatures.cpp new file mode 100644 index 0000000..1229cbb --- /dev/null +++ b/signatures.cpp @@ -0,0 +1,562 @@ +#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; +ke::AString g_CurrentFunctionName; +ParamInfo g_CurrentArgumentInfo; +ke::AString g_CurrentArgumentName; + +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, 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, 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_CurrentArgumentName = name; + + auto arg = g_CurrentSignature->args.find(name); + // Continue changing that argument now. + if (arg.found()) + { + g_CurrentArgumentInfo = arg->value; + } + else + { + ParamInfo info; + memset(&info, 0, sizeof(info)); + info.flags = PASSFLAG_BYVAL; + g_CurrentArgumentInfo = info; + } + 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 + { + 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.type = GetHookParamTypeFromString(value); + if (g_CurrentArgumentInfo.type == HookParamType_Unknown) + { + smutils->LogError(myself, "Invalid argument type \"%s\" for argument \"%s\": line: %i col: %i", value, g_CurrentArgumentName.chars(), states->line, states->col); + return SMCResult_HaltFail; + } + } + else if (!strcmp(key, "size")) + { + g_CurrentArgumentInfo.size = atoi(value); + + if (g_CurrentArgumentInfo.size < 1) + { + smutils->LogError(myself, "Invalid argument size \"%s\" for argument \"%s\": line: %i col: %i", value, g_CurrentArgumentName.chars(), states->line, states->col); + return SMCResult_HaltFail; + } + } + else if (!strcmp(key, "flags")) + { + size_t flags; + if (strstr(value, "byval")) + flags |= PASSFLAG_BYVAL; + else if (strstr(value, "byref")) + flags |= PASSFLAG_BYREF; + else if (strstr(value, "byref")) + flags |= PASSFLAG_ODTOR; + else if (strstr(value, "octor")) + flags |= PASSFLAG_OCTOR; + else if (strstr(value, "oassignop")) + flags |= PASSFLAG_OASSIGNOP; + else if (strstr(value, "ocopyctor")) + flags |= PASSFLAG_OCOPYCTOR; + else if (strstr(value, "ounalign")) + flags |= PASSFLAG_OUNALIGN; + + g_CurrentArgumentInfo.flags = flags; + } + else if (!strcmp(key, "register")) + { + g_CurrentArgumentInfo.custom_register = GetCustomRegisterFromString(value); + + if (g_CurrentArgumentInfo.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--; + + // We were in a subsection of an ignored section. Keep ignoring. + if (g_IgnoreLevel > 0) + 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()) + { + smutils->LogError(myself, "Function \"%s\" doesn't have a \"signature\" nor \"address\" set: line: %i col: %i", g_CurrentFunctionName, states->line, states->col); + return SMCResult_HaltFail; + } + + // Save this function signature in our cache. + signatures_.insert(g_CurrentFunctionName.chars(), g_CurrentSignature); + g_CurrentFunctionName = nullptr; + g_CurrentSignature = nullptr; + break; + case PState_Arguments: + g_ParseState = PState_Function; + break; + case PState_Argument: + g_ParseState = PState_Arguments; + + if (g_CurrentArgumentInfo.type == HookParamType_Unknown) + { + smutils->LogError(myself, "Missing argument type for argument \"%s\": line: %i col: %i", g_CurrentArgumentName.chars(), 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.size) + { + if (g_CurrentArgumentInfo.type == HookParamType_Object) + { + smutils->LogError(myself, "Object param \"%s\" being set with no size: line: %i col: %i", g_CurrentArgumentName.chars(), states->line, states->col); + return SMCResult_HaltFail; + } + else + { + g_CurrentArgumentInfo.size = GetParamTypeSize(g_CurrentArgumentInfo.type); + } + } + + if (g_CurrentArgumentInfo.pass_type == SourceHook::PassInfo::PassType::PassType_Unknown) + g_CurrentArgumentInfo.pass_type = GetParamTypePassType(g_CurrentArgumentInfo.type); + g_CurrentSignature->args.insert(g_CurrentArgumentName.chars(), g_CurrentArgumentInfo); + + g_CurrentArgumentName = nullptr; + break; + } + + return SMCResult_Continue; +} + +void SignatureGameConfig::ReadSMC_ParseStart() +{ + g_ParseState = PState_Root; + g_IgnoreLevel = 0; + g_PlatformOnlyState = PState_None; + g_CurrentSignature = nullptr; + g_CurrentFunctionName = nullptr; + g_CurrentArgumentName = nullptr; +} + +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, "entity")) + 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, "entity")) + 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, "si")) + return SI; + else if (!strcmp(str, "di")) + return DI; + + + else if (!strcmp(str, "st0")) + return ST0; + + return Register_t::None; +} \ No newline at end of file diff --git a/signatures.h b/signatures.h new file mode 100644 index 0000000..bca7fa5 --- /dev/null +++ b/signatures.h @@ -0,0 +1,41 @@ +#ifndef _INCLUDE_SIGNATURES_H_ +#define _INCLUDE_SIGNATURES_H_ + +#include "extension.h" +#include "util.h" +#include +#include + +class SignatureWrapper { +public: + ke::AString signature; + ke::AString address; + ke::AString offset; + StringHashMap 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/sourcemod/gamedata/fbtools.games.txt b/sourcemod/gamedata/fbtools.games.txt new file mode 100644 index 0000000..6cae479 --- /dev/null +++ b/sourcemod/gamedata/fbtools.games.txt @@ -0,0 +1,77 @@ +"Games" +{ + "cstrike" + { + "Signatures" + { + "Deafen" + { + "library" "server" + "windows" "\x55\x8B\xEC\x83\xEC\x2A\x89\x4D\x2A\x8B\x45\x2A\x8B\x10\x8B\x4D\x2A\x8B\x82\x2A\x2A\x2A\x2A\xFF\xD0\x85\xC0\x74" + "linux" "@_ZN9CCSPlayer6DeafenEf" + } + "CFlashbangProjectile::Detonate" + { + "library" "server" + "windows" "\x53\x56\x8B\xF1\x57\x8B\x86\x2A\x2A\x2A\x2A\xC1\xE8\x2A\xA8\x2A\x74\x2A\xE8\x2A\x2A\x2A\x2A\xD9\x05\x2A\x2A\x2A\x2A\x6A\x40\x6A\x00\x51\x8B\xCE" + "linux" "@_ZN20CFlashbangProjectile8DetonateEv" + } + "PercentageOfFlashForPlayer" + { + "library" "server" + "windows" "\x53\x8B\xDC\x83\xEC\x08\x83\xE4\x2A\x83\xC4\x2A\x55\x8B\x6B\x2A\x89\x6C\x2A\x2A\x8B\xEC\x81\xEC\x2A\x2A\x2A\x2A\x56\x8B\x73\x08\x8D\x4D\xAC" + "linux" "@_Z26PercentageOfFlashForPlayerP11CBaseEntity6VectorS0_" + } + + } + } + "csgo" + { + "Signatures" + { + // "tr_player_flashbanged" is in RadiusFlash which calls CCSPlayer::Deafen and PercentageOfFlashForPlayer + "Deafen" + { + "library" "server" + "windows" "\x55\x8B\xEC\x83\xEC\x28\x56\x57\x8B\xF9\xF3\x0F\x11\x4D\xFC" + "linux" "\x55\x89\xE5\x83\xEC\x48\x89\x7D\xFC\x8B\x7D\x08\x89\x5D\xF4\x89\x75\xF8\x8B\x07" + } + // "flashbang_detonate" and "Scorch" + "CFlashbangProjectile::Detonate" + { + "library" "server" + "windows" "\x55\x8B\xEC\x83\xEC\x6C\x53\x56\x57\x8B\xF1" + "linux" "\x55\x89\xE5\x57\x56\x53\x81\xEC\x0C\x01\x00\x00\x8B\x5D\x08\x89\x1C\x24\xE8\x2A\x2A\x2A\x2A\x85\xC0\x89\xC6" + } + "PercentageOfFlashForPlayer" + { + "library" "server" + "windows" "\x53\x8B\xDC\x83\xEC\x08\x83\xE4\xF0\x83\xC4\x04\x55\x8B\x6B\x04\x89\x6C\x24\x04\x8B\xEC\x81\xEC\x08\x01\x00\x00\x56\x8B\xF1" + "linux" "\x55\x89\xE5\x57\x56\x53\x81\xEC\x9C\x01\x00\x00\x8B\x5D\x08\x8B\x03" + } + + } + + "Functions" + { + "Deafen" + { + "signature" "Deafen" + "callconv" "thiscall" + "return" "void" + "this" "entity" + "arguments" + { + "distance" + { + "type" "float" + "windows" + { + "register" "xmm1" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/sourcemod/scripting/dynhooks-test.sp b/sourcemod/scripting/dynhooks-test.sp index b9b7697..1dd8ab7 100644 --- a/sourcemod/scripting/dynhooks-test.sp +++ b/sourcemod/scripting/dynhooks-test.sp @@ -33,14 +33,17 @@ public void OnPluginStart() PrintToServer("CFlashbangProjectile::Detonate detoured!"); - hFlashbangDeafen = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_CBaseEntity); + hFlashbangDeafen = DHookCreateFromConf(temp, "Deafen"); + if (!hFlashbangDeafen) + SetFailState("Failed to setup detour for Deafen"); + /*hFlashbangDeafen = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_CBaseEntity); if (!hFlashbangDeafen) SetFailState("Failed to setup detour for Deafen"); if (!DHookSetFromConf(hFlashbangDeafen, temp, SDKConf_Signature, "Deafen")) SetFailState("Failed to load Deafen signature from gamedata"); - DHookAddParam(hFlashbangDeafen, HookParamType_Float, .custom_register=DHookRegister_XMM1); + DHookAddParam(hFlashbangDeafen, HookParamType_Float, .custom_register=DHookRegister_XMM1);*/ if (!DHookEnableDetour(hFlashbangDeafen, false, Detour_OnDeafen)) SetFailState("Failed to detour Deafen."); diff --git a/sourcemod/scripting/include/dhooks.inc b/sourcemod/scripting/include/dhooks.inc index f149705..95db9a6 100644 --- a/sourcemod/scripting/include/dhooks.inc +++ b/sourcemod/scripting/include/dhooks.inc @@ -219,7 +219,7 @@ native bool DHookRemoveEntityListener(ListenType type, ListenCB callback); * @param callback Callback function * * @return Returns setup handle for the hook. - * @error Failed to create hook setup handle. + * @error Failed to create hook setup handle or invalid callback function. */ native Handle DHookCreate(int offset, HookType hooktype, ReturnType returntype, ThisPointerType thistype, DHookCallback callback); @@ -237,6 +237,18 @@ native Handle DHookCreate(int offset, HookType hooktype, ReturnType returntype, */ native Handle 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. + * @param callback Callback function (only required for virtual hooks - ignored for detours). + * + * @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 Handle DHookCreateFromConf(Handle gameconf, const char[] name, DHookCallback callback=INVALID_FUNCTION); + /** * Load details for a vhook or detour from a gamedata file. * @@ -246,7 +258,7 @@ native Handle DHookCreateDetour(Address funcaddr, CallingConvention callConv, Re * @param name Name of the property to find. * * @return True on success, false if nothing was found. - * @error Invalid setup handle. + * @error Invalid setup or gamedata handle. */ native bool DHookSetFromConf(Handle setup, Handle gameconf, SDKFuncConfSource source, const char[] name);