sm-ext-dhooks2/signatures.cpp
Peace-Maker 891fa5352e Add support for the "fastcall" calling convention
Callee cleans the stack and first two arguments are passed in registers ecx and edx.
You could emulate this by choosing stdcall and setting the custom_registers on the arguments manually, but this is easier.
2019-06-20 02:14:07 +02:00

577 lines
16 KiB
C++

#include <signatures.h>
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;
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.chars(), 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.chars(), 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.chars(), states->line, states->col);
return SMCResult_HaltFail;
}
}
else if (!strcmp(key, "size"))
{
g_CurrentArgumentInfo.info.size = atoi(value);
if (g_CurrentArgumentInfo.info.size < 1)
{
smutils->LogError(myself, "Invalid argument size \"%s\" for argument \"%s\": line: %i col: %i", value, g_CurrentArgumentInfo.name.chars(), states->line, states->col);
return SMCResult_HaltFail;
}
}
else if (!strcmp(key, "flags"))
{
size_t flags = 0;
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;
#ifdef PASSFLAG_OCOPYCTOR
else if (strstr(value, "ocopyctor"))
flags |= PASSFLAG_OCOPYCTOR;
#endif
#ifdef PASSFLAG_OUNALIGN
else 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--;
// 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() && !g_CurrentSignature->offset.length())
{
smutils->LogError(myself, "Function \"%s\" doesn't have a \"signature\", \"offset\" nor \"address\" set: line: %i col: %i", g_CurrentFunctionName.chars(), 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.info.type == HookParamType_Unknown)
{
smutils->LogError(myself, "Missing argument type for argument \"%s\": line: %i col: %i", g_CurrentArgumentInfo.name.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.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.chars(), 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.append(g_CurrentArgumentInfo);
g_CurrentArgumentInfo.name = 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_CurrentArgumentInfo.name = 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, "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;
}