From 379cbc8bc251307777a14b901e5617e834398485 Mon Sep 17 00:00:00 2001 From: KittenPopo Date: Sat, 26 Mar 2022 16:20:09 -0700 Subject: Major exploit fixes and some minor bug fixes/improvements (#117) * Added main exploit fixes * Fixed typo in sigscanning.cpp * Fully implemented * Added proper includes for new files * Update README.md * typo * spare me my sanity (fixed ridiculous code) * Added rest of KittenMemUtils * Rename KittenMemUtils * Removed all messy memory edits, implemented NSMem instead * Update NorthstarDedicatedTest.vcxproj * [1] Move everything from securitypatches to ExploitFixes * [2] Move everything from securitypatches to ExploitFixes * Fixed module offsets in stack trace * Fixed UTF8 Parsing (Multiplayer Crash) * Implemented UT8 fix * Update NorthstarDedicatedTest.vcxproj * Update hookutils.cpp * Small fixes * all my homies hate clang-format * Temporarily restore README.md * Added string hash macro * Added convenient vtfunc macro * Made lil ConCommand creation macro * Fixed multiple NET_SetConVar exploits * Quick fixerino * Fix convar struct (and other things) * Revive clang-format (but good, i think) * Update .clang-format * Reformatted code to meet .clang-format requirements * Minor formatting fixes * Fixed Northstar "crashing" when console is closed * Update .clang-format --- NorthstarDedicatedTest/ExploitFixes.cpp | 177 ++++++++++++++++++----- NorthstarDedicatedTest/ExploitFixes.h | 5 +- NorthstarDedicatedTest/ExploitFixes_UTF8Parser.h | 102 ++++++------- NorthstarDedicatedTest/NSMem.h | 95 ++++++++---- NorthstarDedicatedTest/concommand.h | 5 +- NorthstarDedicatedTest/convar.cpp | 13 +- NorthstarDedicatedTest/convar.h | 5 +- NorthstarDedicatedTest/cvar.cpp | 40 +---- NorthstarDedicatedTest/cvar.h | 13 +- NorthstarDedicatedTest/dllmain.cpp | 2 + NorthstarDedicatedTest/hooks.cpp | 2 +- NorthstarDedicatedTest/keyvalues.cpp | 2 +- NorthstarDedicatedTest/languagehooks.cpp | 4 +- NorthstarDedicatedTest/latencyflex.cpp | 4 +- NorthstarDedicatedTest/logging.cpp | 66 ++++++--- NorthstarDedicatedTest/misccommands.cpp | 73 +++++----- NorthstarDedicatedTest/miscserverfixes.cpp | 5 +- NorthstarDedicatedTest/modmanager.cpp | 15 +- NorthstarDedicatedTest/pch.h | 14 ++ 19 files changed, 385 insertions(+), 257 deletions(-) (limited to 'NorthstarDedicatedTest') diff --git a/NorthstarDedicatedTest/ExploitFixes.cpp b/NorthstarDedicatedTest/ExploitFixes.cpp index d36b4175..36bd36f4 100644 --- a/NorthstarDedicatedTest/ExploitFixes.cpp +++ b/NorthstarDedicatedTest/ExploitFixes.cpp @@ -3,39 +3,36 @@ #include "ExploitFixes.h" #include "ExploitFixes_UTF8Parser.h" #include "NSMem.h" +#include "cvar.h" // Make sure 3 or less floats are valid -bool ValidateFloats(float a, float b = 0, float c = 0) { - return !isnan(a) && !isnan(b) && !isnan(c); -} +bool ValidateFloats(float a, float b = 0, float c = 0) { return !isnan(a) && !isnan(b) && !isnan(c); } -struct Vector { +struct Vector +{ float x, y, z; Vector(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z) {} - bool IsValid() { - return ValidateFloats(x, y, z); - } + bool IsValid() { return ValidateFloats(x, y, z); } }; -struct Angle { +struct Angle +{ float pitch, yaw, roll; Angle(float pitch = 0, float yaw = 0, float roll = 0) : pitch(pitch), yaw(yaw), roll(roll) {} - bool IsInvalid() { + bool IsInvalid() + { if (!ValidateFloats(pitch, yaw, roll)) return false; - return - (pitch > 90 || pitch < -90) - || (yaw > 180 || yaw < -180) - || (roll > 180 || roll < -180); + return (pitch > 90 || pitch < -90) || (yaw > 180 || yaw < -180) || (roll > 180 || roll < -180); } }; -#define BLOCK_NETMSG_FUNC(name, pattern) \ +#define BLOCK_NETMSG_FUNC(name, pattern) \ KHOOK(name, ("engine.dll", pattern), bool, __fastcall, (void* thisptr, void* buffer)) { return false; } // Servers can literally request a screenshot from any client, yeah no @@ -45,9 +42,83 @@ BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48 // This is unused ingame and a big exploit vector BLOCK_NETMSG_FUNC(Base_CmdKeyValues_ReadFromBuffer, "40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70"); +KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10"), bool, __fastcall, (void* pMsg)) +{ + + constexpr int ENTRY_STR_LEN = 260; + struct SetConVarEntry + { + char name[ENTRY_STR_LEN]; + char val[ENTRY_STR_LEN]; + }; + + struct NET_SetConVar + { + void* vtable; + void* unk1; + void* unk2; + void* m_pMessageHandler; + SetConVarEntry* m_ConVars; // convar entry array + void* unk5; // these 2 unks are just vector capacity or whatever + void* unk6; + int m_ConVars_count; // amount of cvar entries in array (this will not be out of bounds) + }; + + auto msg = (NET_SetConVar*)pMsg; + + constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 20; + if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT) + return false; // Nope + + for (int i = 0; i < msg->m_ConVars_count; i++) + { + auto entry = msg->m_ConVars + i; + + // Safety check for memory access + if (NSMem::IsMemoryReadable(entry, sizeof(*entry))) + { + + // Find null terminators + bool nameValid = false, valValid = false; + for (int i = 0; i < ENTRY_STR_LEN; i++) + { + if (!entry->name[i]) + nameValid = true; + if (!entry->val[i]) + valValid = true; + } + + if (!nameValid || !valValid) + return false; // Missing null terminators + + auto realVar = g_pCVar->FindVar(entry->name); + + if (!realVar) + // Not an actual cvar, no thanks + return false; + + // Force name to match case + memcpy(entry->name, realVar->m_ConCommandBase.m_pszName, strlen(realVar->m_ConCommandBase.m_pszName) + 1); + + if (!ConVar::IsFlagSet(realVar, FCVAR_USERINFO) || ConVar::IsFlagSet(realVar, FCVAR_CHEAT)) + { + return false; + } + } + else + { + return false; // Not risking that one, they all gotta be readable + } + } + + return oCClient_ProcessSetConVar(msg); +} + // Purpose: prevent invalid user CMDs -KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg)) { - struct __declspec(align(8)) CLC_Move { +KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg)) +{ + struct CLC_Move + { BYTE gap0[24]; void* m_pMessageHandler; int m_nBackupCommands; @@ -72,7 +143,8 @@ KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __f return oCClient_ProcessUsercmds(thisptr, pMsg); } -KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) { +KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) +{ // Let normal usercmd read happen first, it's safe oReadUsercmd(buf, pCmd_move, pCmd_from); @@ -105,20 +177,18 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall }; auto cmd = (SV_CUserCmd*)pCmd_move; - if ( - cmd->worldViewAngles.IsInvalid() || - cmd->localViewAngles.IsInvalid() || - cmd->attackangles.IsInvalid() || - cmd->cameraAngles.IsInvalid()) { + if (cmd->worldViewAngles.IsInvalid() || cmd->localViewAngles.IsInvalid() || cmd->attackangles.IsInvalid() || + cmd->cameraAngles.IsInvalid()) + { goto INVALID_CMD; } if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0) goto INVALID_CMD; // No simulation of bogus-timed cmds - if (!cmd->move.IsValid() || // Prevent player freeze (and even server crash) exploit + if (!cmd->move.IsValid() || // Prevent player freeze (and even server crash) exploit !cmd->cameraPos.IsValid()) // IIRC this can crash spectating clients or anyone watching replays - goto INVALID_CMD; + goto INVALID_CMD; if (!ValidateFloats(cmd->cameraPos.x, cmd->cameraPos.y, cmd->cameraPos.z)) goto INVALID_CMD; // IIRC this can crash spectating clients or anyone watching replays @@ -128,7 +198,7 @@ INVALID_CMD: // Fix any gameplay-affecting cmd properties // NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems - cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = Angle(0,0,0); + cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = Angle(0, 0, 0); cmd->tick_count = cmd->frameTime = 0; cmd->move = cmd->cameraPos = Vector(0, 0, 0); cmd->buttons = 0; @@ -139,19 +209,24 @@ INVALID_CMD: // this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients // especially since we have script commands this could theoretically be awful #include "gameutils.h" -KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ()) { +KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ()) +{ return !CommandLine()->CheckParm("-norestrictservercommands"); } // Fix respawn's crappy UTF8 parser so it doesn't crash -_- // This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome -KHOOK(CrashFunc_ParseUTF8, ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"), - bool, __fastcall, (INT64* a1, DWORD* a2, char* strData)) { +KHOOK( + CrashFunc_ParseUTF8, ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"), bool, + __fastcall, (INT64 * a1, DWORD* a2, char* strData)) +{ static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16"); - if (_ReturnAddress() == targetRetAddr) { - if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData)) { + if (_ReturnAddress() == targetRetAddr) + { + if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData)) + { spdlog::warn("ParseUTF8 Hook: Ignoring potentially-crashing utf8 string"); return false; } @@ -162,8 +237,10 @@ KHOOK(CrashFunc_ParseUTF8, ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 ////////////////////////////////////////////////// -void DoBytePatches() { +void DoBytePatches() +{ uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll"); + uintptr_t serverBase = (uintptr_t)GetModuleHandleA("server.dll"); // patches to make commands run from client/ui script still work // note: this is likely preventable in a nicer way? test prolly @@ -176,17 +253,49 @@ void DoBytePatches() { int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE; NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int)); } + + { // Dumb ANTITAMPER patches (they negatively impact performance and security) + + constexpr const char* ANTITAMPER_EXPORTS[] = { + "ANTITAMPER_SPOTCHECK_CODEMARKER", + "ANTITAMPER_TESTVALUE_CODEMARKER", + "ANTITAMPER_TRIGGER_CODEMARKER", + }; + + // Prevent thesefrom actually doing anything + for (auto exportName : ANTITAMPER_EXPORTS) + { + + auto address = (uintptr_t)GetProcAddress(NULL, exportName); + if (!address) + { + spdlog::warn("Failed to find AntiTamper function export \"{}\"", exportName); + } + else + { + + // Just return, none of them have any args or are userpurge + NSMem::BytePatch(address, {0xC3}); + + spdlog::info("Patched AntiTamper function export \"{}\"", exportName); + } + } + } } -void ExploitFixes::LoadCallback(HMODULE unused) { +void ExploitFixes::LoadCallback(HMODULE unused) +{ spdlog::info("ExploitFixes::LoadCallback ..."); spdlog::info("\tByte patching..."); DoBytePatches(); - if (KHook::InitAllHooks()) { + if (KHook::InitAllHooks()) + { spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " exploit-patch hooks."); - } else { + } + else + { spdlog::critical("\tFAILED to initialize all exploit patches."); // Force exit? diff --git a/NorthstarDedicatedTest/ExploitFixes.h b/NorthstarDedicatedTest/ExploitFixes.h index 49928640..7a407a3d 100644 --- a/NorthstarDedicatedTest/ExploitFixes.h +++ b/NorthstarDedicatedTest/ExploitFixes.h @@ -3,6 +3,7 @@ #pragma once #include "pch.h" -namespace ExploitFixes { +namespace ExploitFixes +{ void LoadCallback(HMODULE unused); -} +} \ No newline at end of file diff --git a/NorthstarDedicatedTest/ExploitFixes_UTF8Parser.h b/NorthstarDedicatedTest/ExploitFixes_UTF8Parser.h index 6b767a0c..b06d442b 100644 --- a/NorthstarDedicatedTest/ExploitFixes_UTF8Parser.h +++ b/NorthstarDedicatedTest/ExploitFixes_UTF8Parser.h @@ -8,55 +8,55 @@ namespace ExploitFixes_UTF8Parser { bool __fastcall CheckValid(INT64* a1, DWORD* a2, char* strData) { - static auto sub_F1320 = (INT64(__fastcall*)(DWORD a1, char* a2)) NSMem::PatternScan("engine.dll", "83 F9 7F 77 08 88 0A"); + static auto sub_F1320 = (INT64(__fastcall*)(DWORD a1, char* a2))NSMem::PatternScan("engine.dll", "83 F9 7F 77 08 88 0A"); - DWORD v3; // eax - char* v4; // rbx - char v5; // si - char* _strData; // rdi - char* v7; // rbp - char v11; // al - DWORD v12; // er9 - DWORD v13; // ecx - DWORD v14; // edx - DWORD v15; // er8 - int v16; // eax - DWORD v17; // er9 - int v18; // eax - DWORD v19; // er9 - DWORD v20; // ecx - int v21; // eax - int v22; // er9 - DWORD v23; // edx - int v24; // eax - int v25; // er9 - DWORD v26; // er9 - DWORD v27; // er10 - DWORD v28; // ecx - DWORD v29; // edx - DWORD v30; // er8 - int v31; // eax - DWORD v32; // er10 - int v33; // eax - DWORD v34; // er10 - DWORD v35; // ecx - int v36; // eax - int v37; // er10 - DWORD v38; // edx - int v39; // eax - int v40; // er10 - DWORD v41; // er10 - INT64 v43; // r8 - INT64 v44; // rdx - INT64 v45; // rcx - INT64 v46; // rax - INT64 v47; // rax - char v48; // al - INT64 v49; // r8 - INT64 v50; // rdx - INT64 v51; // rcx - INT64 v52; // rax - INT64 v53; // rax + DWORD v3; // eax + char* v4; // rbx + char v5; // si + char* _strData; // rdi + char* v7; // rbp + char v11; // al + DWORD v12; // er9 + DWORD v13; // ecx + DWORD v14; // edx + DWORD v15; // er8 + int v16; // eax + DWORD v17; // er9 + int v18; // eax + DWORD v19; // er9 + DWORD v20; // ecx + int v21; // eax + int v22; // er9 + DWORD v23; // edx + int v24; // eax + int v25; // er9 + DWORD v26; // er9 + DWORD v27; // er10 + DWORD v28; // ecx + DWORD v29; // edx + DWORD v30; // er8 + int v31; // eax + DWORD v32; // er10 + int v33; // eax + DWORD v34; // er10 + DWORD v35; // ecx + int v36; // eax + int v37; // er10 + DWORD v38; // edx + int v39; // eax + int v40; // er10 + DWORD v41; // er10 + INT64 v43; // r8 + INT64 v44; // rdx + INT64 v45; // rcx + INT64 v46; // rax + INT64 v47; // rax + char v48; // al + INT64 v49; // r8 + INT64 v50; // rdx + INT64 v51; // rcx + INT64 v52; // rax + INT64 v53; // rax v3 = a2[2]; v4 = (char*)(a1[1] + *a2); @@ -71,7 +71,7 @@ namespace ExploitFixes_UTF8Parser { while (1) { - + if (!NSMem::IsMemoryReadable(v4, 1)) return false; // INVALID @@ -128,7 +128,7 @@ namespace ExploitFixes_UTF8Parser return true; if (*v4 != 92 || v4[1] != 117) return true; - + v27 = v4[2] | 0x20; v28 = v4[3] | 0x20; v29 = v4[4] | 0x20; @@ -172,4 +172,4 @@ namespace ExploitFixes_UTF8Parser LABEL_48: return true; } -} \ No newline at end of file +} // namespace ExploitFixes_UTF8Parser \ No newline at end of file diff --git a/NorthstarDedicatedTest/NSMem.h b/NorthstarDedicatedTest/NSMem.h index a6ddf033..a5bbd42f 100644 --- a/NorthstarDedicatedTest/NSMem.h +++ b/NorthstarDedicatedTest/NSMem.h @@ -4,8 +4,10 @@ // KittenPopo's memory stuff, made for northstar (because I really can't handle working with northstar's original memory stuff tbh) #pragma region Pattern Scanning -namespace NSMem { - inline void* PatternScan(void* module, const int* pattern, int patternSize, int offset) { +namespace NSMem +{ + inline void* PatternScan(void* module, const int* pattern, int patternSize, int offset) + { if (!module) return NULL; @@ -16,16 +18,20 @@ namespace NSMem { auto scanBytes = (BYTE*)module; - for (auto i = 0; i < sizeOfImage - patternSize; ++i) { + for (auto i = 0; i < sizeOfImage - patternSize; ++i) + { bool found = true; - for (auto j = 0; j < patternSize; ++j) { - if (scanBytes[i + j] != pattern[j] && pattern[j] != -1) { + for (auto j = 0; j < patternSize; ++j) + { + if (scanBytes[i + j] != pattern[j] && pattern[j] != -1) + { found = false; break; } } - if (found) { + if (found) + { uintptr_t addressInt = (uintptr_t)(&scanBytes[i]) + offset; return (uint8_t*)addressInt; } @@ -34,31 +40,42 @@ namespace NSMem { return nullptr; } - inline void* PatternScan(const char* moduleName, const char* pattern, int offset = 0) { + inline void* PatternScan(const char* moduleName, const char* pattern, int offset = 0) + { std::vector patternNums; bool lastChar = 0; int size = strlen(pattern); - for (int i = 0; i < size; i++) { + for (int i = 0; i < size; i++) + { char c = pattern[i]; // If this is a space character, ignore it if (c == ' ' || c == '\t') continue; - if (c == '?') { + if (c == '?') + { // Add a wildcard (-1) patternNums.push_back(-1); - } else if (i < size - 1) { + } + else if (i < size - 1) + { BYTE result = 0; - for (int j = 0; j < 2; j++) { + for (int j = 0; j < 2; j++) + { int val = 0; char c = (pattern + i + j)[0]; - if (c >= 'a') { + if (c >= 'a') + { val = c - 'a' + 0xA; - } else if (c >= 'A') { + } + else if (c >= 'A') + { val = c - 'A' + 0xA; - } else { + } + else + { val = c - '0'; } @@ -72,24 +89,28 @@ namespace NSMem { return PatternScan(GetModuleHandleA(moduleName), &patternNums[0], patternNums.size(), offset); } - inline void BytePatch(uintptr_t address, const BYTE* vals, int size) { + inline void BytePatch(uintptr_t address, const BYTE* vals, int size) + { WriteProcessMemory(GetCurrentProcess(), (LPVOID)address, vals, size, NULL); } - inline void BytePatch(uintptr_t address, std::initializer_list vals) { + inline void BytePatch(uintptr_t address, std::initializer_list vals) + { std::vector bytes = vals; if (!bytes.empty()) BytePatch(address, &bytes[0], bytes.size()); } - inline void NOP(uintptr_t address, int size) { + inline void NOP(uintptr_t address, int size) + { BYTE* buf = (BYTE*)malloc(size); memset(buf, 0x90, size); BytePatch(address, buf, size); free(buf); } - inline bool IsMemoryReadable(void* ptr, size_t size) { + inline bool IsMemoryReadable(void* ptr, size_t size) + { BYTE* buffer = (BYTE*)malloc(size); size_t numWritten = 0; @@ -98,18 +119,21 @@ namespace NSMem { return numWritten == size; } -} +} // namespace NSMem #pragma region KHOOK -struct KHookPatternInfo { - const char* moduleName, *pattern; +struct KHookPatternInfo +{ + const char *moduleName, *pattern; int offset = 0; - KHookPatternInfo(const char* moduleName, const char* pattern, int offset = 0) - : moduleName(moduleName), pattern(pattern), offset(offset) {} + KHookPatternInfo(const char* moduleName, const char* pattern, int offset = 0) : moduleName(moduleName), pattern(pattern), offset(offset) + { + } }; -struct KHook { +struct KHook +{ KHookPatternInfo targetFunc; void* targetFuncAddr; void* hookFunc; @@ -117,13 +141,15 @@ struct KHook { static inline std::vector _allHooks; - KHook(KHookPatternInfo targetFunc, void* hookFunc, void** original) : targetFunc(targetFunc) { + KHook(KHookPatternInfo targetFunc, void* hookFunc, void** original) : targetFunc(targetFunc) + { this->hookFunc = hookFunc; this->original = original; _allHooks.push_back(this); } - bool Setup() { + bool Setup() + { targetFuncAddr = NSMem::PatternScan(targetFunc.moduleName, targetFunc.pattern, targetFunc.offset); if (!targetFuncAddr) return false; @@ -132,11 +158,16 @@ struct KHook { } // Returns true if succeeded - static bool InitAllHooks() { - for (KHook* hook : _allHooks) { - if (hook->Setup()) { + static bool InitAllHooks() + { + for (KHook* hook : _allHooks) + { + if (hook->Setup()) + { spdlog::info("KHook hooked at {}", hook->targetFuncAddr); - } else { + } + else + { return false; } } @@ -144,9 +175,9 @@ struct KHook { return MH_EnableHook(MH_ALL_HOOKS) == MH_OK; } }; -#define KHOOK(name, funcPatternInfo, returnType, convention, args) \ +#define KHOOK(name, funcPatternInfo, returnType, convention, args) \ returnType convention hk##name args; \ auto o##name = (returnType(convention*) args)0; \ - KHook k##name = KHook(KHookPatternInfo funcPatternInfo, &hk##name, (void**)&o##name); \ + KHook k##name = KHook(KHookPatternInfo funcPatternInfo, &hk##name, (void**)&o##name); \ returnType convention hk##name args #pragma endregion \ No newline at end of file diff --git a/NorthstarDedicatedTest/concommand.h b/NorthstarDedicatedTest/concommand.h index b1342163..eb812c32 100644 --- a/NorthstarDedicatedTest/concommand.h +++ b/NorthstarDedicatedTest/concommand.h @@ -104,4 +104,7 @@ class ConCommand : public ConCommandBase }; // Size: 0x0060 void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags); -void InitialiseConCommands(HMODULE baseAddress); \ No newline at end of file +void InitialiseConCommands(HMODULE baseAddress); + +#define MAKE_CONCMD(name, helpStr, flags, fn) \ +RegisterConCommand(name, fn, helpStr, flags); \ No newline at end of file diff --git a/NorthstarDedicatedTest/convar.cpp b/NorthstarDedicatedTest/convar.cpp index 7023e13c..3f2728d0 100644 --- a/NorthstarDedicatedTest/convar.cpp +++ b/NorthstarDedicatedTest/convar.cpp @@ -6,10 +6,6 @@ #include "gameutils.h" #include "sourceinterface.h" -// should this be in modmanager? -std::unordered_map - g_CustomConvars; // this is used in modloading code to determine whether we've registered a mod convar already - typedef void (*ConVarRegisterType)( ConVar* pConVar, const char* pszName, const char* pszDefaultValue, int nFlags, const char* pszHelpString, bool bMin, float fMin, bool bMax, float fMax, void* pCallback); @@ -40,6 +36,8 @@ void InitialiseConVars(HMODULE baseAddress) HookEnabler hook; ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x417FA0, &ConVar::IsFlagSet, reinterpret_cast(&CvarIsFlagSet)); + + } //----------------------------------------------------------------------------- @@ -54,8 +52,6 @@ ConVar::ConVar(const char* pszName, const char* pszDefaultValue, int nFlags, con conVarMalloc(&this->m_pMalloc, 0, 0); // Allocate new memory for ConVar. conVarRegister(this, pszName, pszDefaultValue, nFlags, pszHelpString, 0, 0, 0, 0, 0); - - g_CustomConvars.emplace(pszName, this); } //----------------------------------------------------------------------------- @@ -72,8 +68,6 @@ ConVar::ConVar( conVarMalloc(&this->m_pMalloc, 0, 0); // Allocate new memory for ConVar. conVarRegister(this, pszName, pszDefaultValue, nFlags, pszHelpString, bMin, fMin, bMax, fMax, pCallback); - - g_CustomConvars.emplace(pszName, this); } //----------------------------------------------------------------------------- @@ -82,10 +76,7 @@ ConVar::ConVar( ConVar::~ConVar(void) { if (m_Value.m_pszString) - { delete[] m_Value.m_pszString; - m_Value.m_pszString = NULL; - } } //----------------------------------------------------------------------------- diff --git a/NorthstarDedicatedTest/convar.h b/NorthstarDedicatedTest/convar.h index a00c7f0e..fc5b0eb9 100644 --- a/NorthstarDedicatedTest/convar.h +++ b/NorthstarDedicatedTest/convar.h @@ -129,7 +129,7 @@ class ConVar }; ConCommandBase m_ConCommandBase{}; // 0x0000 - ConVar* m_pParent{}; // 0x0040 + const char* defaultVal{}; // 0x0040 CVValue_t m_Value{}; // 0x0048 bool m_bHasMin{}; // 0x005C float m_fMinVal{}; // 0x0060 @@ -139,5 +139,4 @@ class ConVar char m_pPad80[10]{}; // 0x0080 }; // Size: 0x0080 -void InitialiseConVars(HMODULE baseAddress); -extern std::unordered_map g_CustomConvars; \ No newline at end of file +void InitialiseConVars(HMODULE baseAddress); \ No newline at end of file diff --git a/NorthstarDedicatedTest/cvar.cpp b/NorthstarDedicatedTest/cvar.cpp index 7f8c35d7..23d767fd 100644 --- a/NorthstarDedicatedTest/cvar.cpp +++ b/NorthstarDedicatedTest/cvar.cpp @@ -3,45 +3,6 @@ #include "convar.h" #include "concommand.h" -//----------------------------------------------------------------------------- -// Purpose: finds base commands. -// Input : *pszCommandName - -//----------------------------------------------------------------------------- -ConCommandBase* CCvar::FindCommandBase(const char* pszCommandName) -{ - static int index = 14; - return CallVFunc(index, this, pszCommandName); -} - -//----------------------------------------------------------------------------- -// Purpose: finds ConVars. -// Input : *pszVarName - -//----------------------------------------------------------------------------- -ConVar* CCvar::FindVar(const char* pszVarName) -{ - static int index = 16; - return CallVFunc(index, this, pszVarName); -} - -//----------------------------------------------------------------------------- -// Purpose: finds ConCommands. -// Input : *pszCommandName - -//----------------------------------------------------------------------------- -ConCommand* CCvar::FindCommand(const char* pszCommandName) -{ - static int index = 18; - return CallVFunc(index, this, pszCommandName); -} - -//----------------------------------------------------------------------------- -// Purpose: iterates over all ConVars -//----------------------------------------------------------------------------- -CCVarIteratorInternal* CCvar::FactoryInternalIterator() -{ - static int index = 41; - return CallVFunc(index, this); -} - //----------------------------------------------------------------------------- // Purpose: returns all ConVars //----------------------------------------------------------------------------- @@ -61,5 +22,6 @@ std::unordered_map CCvar::DumpToMap() return allConVars; } + SourceInterface* g_pCVarInterface; CCvar* g_pCVar; \ No newline at end of file diff --git a/NorthstarDedicatedTest/cvar.h b/NorthstarDedicatedTest/cvar.h index 43b13bfb..e254af4e 100644 --- a/NorthstarDedicatedTest/cvar.h +++ b/NorthstarDedicatedTest/cvar.h @@ -1,5 +1,6 @@ #pragma once #include "convar.h" +#include "pch.h" //----------------------------------------------------------------------------- // Forward declarations @@ -25,11 +26,13 @@ class CCVarIteratorInternal // Fully reversed table, just look at the virtual fu //----------------------------------------------------------------------------- class CCvar { - public: - ConCommandBase* FindCommandBase(const char* pszCommandName); - ConVar* FindVar(const char* pszVarName); - ConCommand* FindCommand(const char* pszCommandName); - CCVarIteratorInternal* FactoryInternalIterator(); + public: + + M_VMETHOD(ConCommandBase*, FindCommandBase, 14, (const char* pszCommandName), (this, pszCommandName)); + M_VMETHOD(ConVar*, FindVar, 16, (const char* pszVarName), (this, pszVarName)); + M_VMETHOD(ConCommand*, FindCommand, 18, (const char* pszCommandName), (this, pszCommandName)); + M_VMETHOD(CCVarIteratorInternal*, FactoryInternalIterator, 41, (), (this)); + std::unordered_map DumpToMap(); }; diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp index 524235cf..22b9a673 100644 --- a/NorthstarDedicatedTest/dllmain.cpp +++ b/NorthstarDedicatedTest/dllmain.cpp @@ -197,7 +197,9 @@ bool InitialiseNorthstar() parseConfigurables(); InitialiseVersion(); + // Fix some users' failure to connect to respawn datacenters SetEnvironmentVariableA("OPENSSL_ia32cap", "~0x200000200000000"); + curl_global_init_mem(CURL_GLOBAL_DEFAULT, _malloc_base, _free_base, _realloc_base, _strdup_base, _calloc_base); InitialiseLogging(); diff --git a/NorthstarDedicatedTest/hooks.cpp b/NorthstarDedicatedTest/hooks.cpp index 4c403872..055fe4df 100644 --- a/NorthstarDedicatedTest/hooks.cpp +++ b/NorthstarDedicatedTest/hooks.cpp @@ -36,7 +36,7 @@ LoadLibraryWType LoadLibraryWOriginal; void InstallInitialHooks() { if (MH_Initialize() != MH_OK) - spdlog::error("MH_Initialize failed"); + spdlog::error("MH_Initialize (minhook initialization) failed"); HookEnabler hook; ENABLER_CREATEHOOK(hook, &GetCommandLineA, &GetCommandLineAHook, reinterpret_cast(&GetCommandLineAOriginal)); diff --git a/NorthstarDedicatedTest/keyvalues.cpp b/NorthstarDedicatedTest/keyvalues.cpp index 08c85dc1..2063be62 100644 --- a/NorthstarDedicatedTest/keyvalues.cpp +++ b/NorthstarDedicatedTest/keyvalues.cpp @@ -59,7 +59,7 @@ void ModManager::TryBuildKeyValues(const char* filename) if (!m_loadedMods[i].Enabled) continue; - size_t fileHash = std::hash{}(normalisedPath); + size_t fileHash = STR_HASH(normalisedPath); auto modKv = m_loadedMods[i].KeyValues.find(fileHash); if (modKv != m_loadedMods[i].KeyValues.end()) { diff --git a/NorthstarDedicatedTest/languagehooks.cpp b/NorthstarDedicatedTest/languagehooks.cpp index 95638ef2..8d60ca22 100644 --- a/NorthstarDedicatedTest/languagehooks.cpp +++ b/NorthstarDedicatedTest/languagehooks.cpp @@ -15,7 +15,7 @@ GetGameLanguageType GetGameLanguageOriginal; bool CheckLangAudioExists(char* lang) { - std::string path{"r2\\sound\\general_"}; + std::string path {"r2\\sound\\general_"}; path += lang; path += ".mstr"; return fs::exists(path); @@ -31,7 +31,7 @@ std::vector file_list(fs::path dir, std::regex ext_pattern) using iterator = fs::directory_iterator; const iterator end; - for (iterator iter{dir}; iter != end; ++iter) + for (iterator iter {dir}; iter != end; ++iter) { const std::string filename = iter->path().filename().string(); std::smatch matches; diff --git a/NorthstarDedicatedTest/latencyflex.cpp b/NorthstarDedicatedTest/latencyflex.cpp index 623ac06b..4ac1c760 100644 --- a/NorthstarDedicatedTest/latencyflex.cpp +++ b/NorthstarDedicatedTest/latencyflex.cpp @@ -8,9 +8,9 @@ OnRenderStartType OnRenderStart; ConVar* Cvar_r_latencyflex; -HMODULE m_lfxModule{}; +HMODULE m_lfxModule {}; typedef void (*PFN_winelfx_WaitAndBeginFrame)(); -PFN_winelfx_WaitAndBeginFrame m_winelfx_WaitAndBeginFrame{}; +PFN_winelfx_WaitAndBeginFrame m_winelfx_WaitAndBeginFrame {}; void OnRenderStartHook() { diff --git a/NorthstarDedicatedTest/logging.cpp b/NorthstarDedicatedTest/logging.cpp index 9b2a0925..98e80592 100644 --- a/NorthstarDedicatedTest/logging.cpp +++ b/NorthstarDedicatedTest/logging.cpp @@ -52,74 +52,77 @@ long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) return EXCEPTION_CONTINUE_SEARCH; std::stringstream exceptionCause; + exceptionCause << "Cause: "; switch (exceptionCode) { case EXCEPTION_ACCESS_VIOLATION: case EXCEPTION_IN_PAGE_ERROR: { - exceptionCause << "Cause: Access Violation" << std::endl; + exceptionCause << "Access Violation" << std::endl; auto exceptionInfo0 = exceptionInfo->ExceptionRecord->ExceptionInformation[0]; auto exceptionInfo1 = exceptionInfo->ExceptionRecord->ExceptionInformation[1]; if (!exceptionInfo0) - exceptionCause << "Attempted to read from: 0x" << std::setw(8) << std::setfill('0') << std::hex << exceptionInfo1; + exceptionCause << "Attempted to read from: 0x" << (void*)exceptionInfo1; else if (exceptionInfo0 == 1) - exceptionCause << "Attempted to write to: 0x" << std::setw(8) << std::setfill('0') << std::hex << exceptionInfo1; + exceptionCause << "Attempted to write to: 0x" << (void*)exceptionInfo1; else if (exceptionInfo0 == 8) - exceptionCause << "Data Execution Prevention (DEP) at: 0x" << std::setw(8) << std::setfill('0') << std::hex + exceptionCause << "Data Execution Prevention (DEP) at: 0x" << (void*)std::hex << exceptionInfo1; else - exceptionCause << "Unknown access violation at: 0x" << std::setw(8) << std::setfill('0') << std::hex << exceptionInfo1; + exceptionCause << "Unknown access violation at: 0x" << (void*)exceptionInfo1; break; } case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: - exceptionCause << "Cause: Array bounds exceeded"; + exceptionCause << "Array bounds exceeded"; break; case EXCEPTION_DATATYPE_MISALIGNMENT: - exceptionCause << "Cause: Datatype misalignment"; + exceptionCause << "Datatype misalignment"; break; case EXCEPTION_FLT_DENORMAL_OPERAND: - exceptionCause << "Cause: Denormal operand"; + exceptionCause << "Denormal operand"; break; case EXCEPTION_FLT_DIVIDE_BY_ZERO: + exceptionCause << "Divide by zero (float)"; + break; case EXCEPTION_INT_DIVIDE_BY_ZERO: - exceptionCause << "Cause: Divide by zero"; + exceptionCause << "Divide by zero (int)"; break; case EXCEPTION_FLT_INEXACT_RESULT: - exceptionCause << "Cause: Inexact result"; + exceptionCause << "Inexact result"; break; case EXCEPTION_FLT_INVALID_OPERATION: - exceptionCause << "Cause: invalid operation"; + exceptionCause << "Invalid operation"; break; case EXCEPTION_FLT_OVERFLOW: case EXCEPTION_INT_OVERFLOW: - exceptionCause << "Cause: Numeric overflow"; + exceptionCause << "Numeric overflow"; break; case EXCEPTION_FLT_UNDERFLOW: - exceptionCause << "Cause: Numeric underflow"; + exceptionCause << "Numeric underflow"; break; case EXCEPTION_FLT_STACK_CHECK: - exceptionCause << "Cause: Stack check"; + exceptionCause << "Stack check"; break; case EXCEPTION_ILLEGAL_INSTRUCTION: - exceptionCause << "Cause: Illegal instruction"; + exceptionCause << "Illegal instruction"; break; case EXCEPTION_INVALID_DISPOSITION: - exceptionCause << "Cause: Invalid disposition"; + exceptionCause << "Invalid disposition"; break; case EXCEPTION_NONCONTINUABLE_EXCEPTION: - exceptionCause << "Cause: Noncontinuable exception"; + exceptionCause << "Noncontinuable exception"; break; case EXCEPTION_PRIV_INSTRUCTION: - exceptionCause << "Cause: Priv instruction"; + exceptionCause << "Priviledged instruction"; break; case EXCEPTION_STACK_OVERFLOW: - exceptionCause << "Cause: Stack overflow"; + exceptionCause << "Stack overflow"; break; default: - exceptionCause << "Cause: Unknown"; + exceptionCause << "Unknown"; break; } @@ -206,14 +209,33 @@ long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) return EXCEPTION_EXECUTE_HANDLER; } +HANDLE hExceptionFilter; + +BOOL WINAPI ConsoleHandlerRoutine(DWORD eventCode) +{ + switch (eventCode) + { + case CTRL_CLOSE_EVENT: + // User closed console, shut everything down + spdlog::info("Exiting due to console close..."); + RemoveVectoredExceptionHandler(hExceptionFilter); + exit(EXIT_SUCCESS); + return FALSE; + } + + return TRUE; +} + void InitialiseLogging() { - AddVectoredExceptionHandler(TRUE, ExceptionFilter); + hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter); AllocConsole(); freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v"); + + SetConsoleCtrlHandler(ConsoleHandlerRoutine, true); } ConVar* Cvar_spewlog_enable; @@ -221,10 +243,12 @@ ConVar* Cvar_spewlog_enable; enum SpewType_t { SPEW_MESSAGE = 0, + SPEW_WARNING, SPEW_ASSERT, SPEW_ERROR, SPEW_LOG, + SPEW_TYPE_COUNT }; diff --git a/NorthstarDedicatedTest/misccommands.cpp b/NorthstarDedicatedTest/misccommands.cpp index 4a556fad..dca87947 100644 --- a/NorthstarDedicatedTest/misccommands.cpp +++ b/NorthstarDedicatedTest/misccommands.cpp @@ -6,50 +6,43 @@ #include "serverauthentication.h" #include "squirrel.h" -void ForceLoadMapCommand(const CCommand& arg) +void AddMiscConCommands() { - if (arg.ArgC() < 2) - return; - - g_pHostState->m_iNextState = HS_NEW_GAME; - strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName)); -} + MAKE_CONCMD( + "force_newgame", "forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME", FCVAR_NONE, + [](const CCommand& arg) { + if (arg.ArgC() < 2) + return; -void SelfAuthAndLeaveToLobbyCommand(const CCommand& arg) -{ - // hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect + g_pHostState->m_iNextState = HS_NEW_GAME; + strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName)); + }); - g_MasterServerManager->m_bNewgameAfterSelfAuth = true; - g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, g_MasterServerManager->m_ownClientAuthToken); -} + MAKE_CONCMD( + "ns_start_reauth_and_leave_to_lobby", "called by the server, used to reauth and return the player to lobby when leaving a game", + FCVAR_SERVER_CAN_EXECUTE, [](const CCommand& arg) { + // hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect + g_MasterServerManager->m_bNewgameAfterSelfAuth = true; + g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, g_MasterServerManager->m_ownClientAuthToken); + }); -void EndSelfAuthAndLeaveToLobbyCommand(const CCommand& arg) -{ - Cbuf_AddText( - Cbuf_GetCurrentPlayer(), fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(), - cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); + // this is a concommand because we make a deferred call to it from another thread + MAKE_CONCMD("ns_end_reauth_and_leave_to_lobby", "", FCVAR_NONE, [](const CCommand& arg) { + Cbuf_AddText( + Cbuf_GetCurrentPlayer(), fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(), + cmd_source_t::kCommandSrcCode); + Cbuf_Execute(); - // weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this - if (g_ClientSquirrelManager->sqvm) - { - g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true; - // this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta fucks - // things should maybe set this in HostState_NewGame? - SetCurrentPlaylist("tdm"); - strcpy(g_pHostState->m_levelName, "mp_lobby"); - g_pHostState->m_iNextState = HS_NEW_GAME; - } -} + // weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this + if (g_ClientSquirrelManager->sqvm) + { + g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true; -void AddMiscConCommands() -{ - RegisterConCommand( - "force_newgame", ForceLoadMapCommand, "forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME", - FCVAR_NONE); - RegisterConCommand( - "ns_start_reauth_and_leave_to_lobby", SelfAuthAndLeaveToLobbyCommand, - "called by the server, used to reauth and return the player to lobby when leaving a game", FCVAR_SERVER_CAN_EXECUTE); - // this is a concommand because we make a deferred call to it from another thread - RegisterConCommand("ns_end_reauth_and_leave_to_lobby", EndSelfAuthAndLeaveToLobbyCommand, "", FCVAR_NONE); + // this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta fucks + // things should maybe set this in HostState_NewGame? + SetCurrentPlaylist("tdm"); + strcpy(g_pHostState->m_levelName, "mp_lobby"); + g_pHostState->m_iNextState = HS_NEW_GAME; + } + }); } \ No newline at end of file diff --git a/NorthstarDedicatedTest/miscserverfixes.cpp b/NorthstarDedicatedTest/miscserverfixes.cpp index 0b9a12db..7d977d64 100644 --- a/NorthstarDedicatedTest/miscserverfixes.cpp +++ b/NorthstarDedicatedTest/miscserverfixes.cpp @@ -25,7 +25,4 @@ void InitialiseMiscServerFixes(HMODULE baseAddress) } } -void InitialiseMiscEngineServerFixes(HMODULE baseAddress) -{ - -} \ No newline at end of file +void InitialiseMiscEngineServerFixes(HMODULE baseAddress) {} \ No newline at end of file diff --git a/NorthstarDedicatedTest/modmanager.cpp b/NorthstarDedicatedTest/modmanager.cpp index 79529c99..99bb5fdb 100644 --- a/NorthstarDedicatedTest/modmanager.cpp +++ b/NorthstarDedicatedTest/modmanager.cpp @@ -191,9 +191,10 @@ ModManager::ModManager() { // precaculated string hashes // note: use backslashes for these, since we use lexically_normal for file paths which uses them - m_hScriptsRsonHash = std::hash{}("scripts\\vscripts\\scripts.rson"); - m_hPdefHash = std::hash{}( - "cfg\\server\\persistent_player_data_version_231.pdef"); // this can have multiple versions, but we use 231 so that's what we hash + m_hScriptsRsonHash = STR_HASH("scripts\\vscripts\\scripts.rson"); + m_hPdefHash = STR_HASH( + "cfg\\server\\persistent_player_data_version_231.pdef" // this can have multiple versions, but we use 231 so that's what we hash + ); LoadMods(); } @@ -282,9 +283,7 @@ void ModManager::LoadMods() // preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this // causes us to leak memory on reload, but not much, potentially find a way to not do this at some point for (ModConVar* convar : mod.ConVars) - if (g_CustomConvars.find(convar->Name) == - g_CustomConvars.end()) // make sure convar isn't registered yet, unsure if necessary but idk what behaviour is for defining - // same convar multiple times + if (!g_pCVar->FindVar(convar->Name.c_str())) // make sure convar isn't registered yet, unsure if necessary but idk what behaviour is for defining same convar multiple times new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str()); // read vpk paths @@ -396,7 +395,7 @@ void ModManager::LoadMods() if (fs::is_regular_file(file)) { std::string kvStr = file.path().lexically_relative(mod.ModDirectory / "keyvalues").lexically_normal().string(); - mod.KeyValues.emplace(std::hash{}(kvStr), kvStr); + mod.KeyValues.emplace(STR_HASH(kvStr), kvStr); } } } @@ -537,7 +536,7 @@ void ModManager::UnloadMods() void ModManager::CompileAssetsForFile(const char* filename) { - size_t fileHash = std::hash{}(fs::path(filename).lexically_normal().string()); + size_t fileHash = STR_HASH(fs::path(filename).lexically_normal().string()); if (fileHash == m_hScriptsRsonHash) BuildScriptsRson(); diff --git a/NorthstarDedicatedTest/pch.h b/NorthstarDedicatedTest/pch.h index 29d1fe8d..cc44c107 100644 --- a/NorthstarDedicatedTest/pch.h +++ b/NorthstarDedicatedTest/pch.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -30,4 +31,17 @@ template ReturnType CallVFunc(int index, return (*reinterpret_cast(thisPtr))[index](thisPtr, args...); } +template constexpr T CallVFunc_Alt(void* classBase, Args... args) noexcept { + return ((*(T(__thiscall***)(void*, Args...))(classBase))[index])(classBase, args...); +} + + +#define STR_HASH(s) (std::hash()(s)) + +// Example usage: M_VMETHOD(int, GetEntityIndex, 8, (CBaseEntity* ent), (this, ent)) +#define M_VMETHOD(returnType, name, index, args, argsRaw) \ +FORCEINLINE returnType name args noexcept { \ + return CallVFunc_Alt argsRaw; \ +} + #endif \ No newline at end of file -- cgit v1.2.3