#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; 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; }