From 6ae30c9b15fcc200c7b642016e7adbfdf9b979f4 Mon Sep 17 00:00:00 2001 From: BobTheBob9 Date: Tue, 12 Jul 2022 14:05:02 +0100 Subject: move exploit prevention and limits code out of serverauthentication, and have actual defs for CBasePlayer --- NorthstarDLL/ExploitFixes.cpp | 387 -------------------------- NorthstarDLL/ExploitFixes_UTF8Parser.h | 175 ------------ NorthstarDLL/NorthstarDLL.vcxproj | 7 +- NorthstarDLL/NorthstarDLL.vcxproj.filters | 428 ++++++++++++++--------------- NorthstarDLL/audio.cpp | 3 +- NorthstarDLL/bansystem.cpp | 19 +- NorthstarDLL/bansystem.h | 2 +- NorthstarDLL/buildainfile.cpp | 14 + NorthstarDLL/clientauthhooks.cpp | 6 +- NorthstarDLL/clientchathooks.cpp | 32 +-- NorthstarDLL/dedicated.cpp | 4 +- NorthstarDLL/exploitfixes.cpp | 435 ++++++++++++++++++++++++++++++ NorthstarDLL/exploitfixes_utf8parser.cpp | 190 +++++++++++++ NorthstarDLL/hoststate.cpp | 16 +- NorthstarDLL/limits.cpp | 185 +++++++++++++ NorthstarDLL/limits.h | 44 +++ NorthstarDLL/localchatwriter.cpp | 69 ++--- NorthstarDLL/localchatwriter.h | 19 +- NorthstarDLL/masterserver.cpp | 26 +- NorthstarDLL/masterserver.h | 2 +- NorthstarDLL/misccommands.cpp | 8 +- NorthstarDLL/miscserverfixes.cpp | 4 - NorthstarDLL/miscserverscript.cpp | 31 +-- NorthstarDLL/miscserverscript.h | 2 - NorthstarDLL/modmanager.cpp | 2 +- NorthstarDLL/playlist.cpp | 6 +- NorthstarDLL/plugins.cpp | 2 +- NorthstarDLL/printmaps.cpp | 2 +- NorthstarDLL/r2engine.cpp | 4 + NorthstarDLL/r2engine.h | 41 +++ NorthstarDLL/r2server.cpp | 6 +- NorthstarDLL/r2server.h | 42 ++- NorthstarDLL/scriptmainmenupromos.cpp | 32 +-- NorthstarDLL/scriptserverbrowser.cpp | 114 ++++---- NorthstarDLL/serverauthentication.cpp | 403 ++++++--------------------- NorthstarDLL/serverauthentication.h | 96 ++----- NorthstarDLL/serverchathooks.cpp | 25 +- 37 files changed, 1484 insertions(+), 1399 deletions(-) delete mode 100644 NorthstarDLL/ExploitFixes.cpp delete mode 100644 NorthstarDLL/ExploitFixes_UTF8Parser.h create mode 100644 NorthstarDLL/exploitfixes.cpp create mode 100644 NorthstarDLL/exploitfixes_utf8parser.cpp create mode 100644 NorthstarDLL/limits.cpp create mode 100644 NorthstarDLL/limits.h delete mode 100644 NorthstarDLL/miscserverscript.h (limited to 'NorthstarDLL') diff --git a/NorthstarDLL/ExploitFixes.cpp b/NorthstarDLL/ExploitFixes.cpp deleted file mode 100644 index b347310c..00000000 --- a/NorthstarDLL/ExploitFixes.cpp +++ /dev/null @@ -1,387 +0,0 @@ -#include "pch.h" - -#include "ExploitFixes_UTF8Parser.h" -#include "NSMem.h" -#include "cvar.h" -#include "tier0.h" - -AUTOHOOK_INIT() - -ConVar* ns_exploitfixes_log; -#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0) -#define BLOCKED_INFO(s) \ - ( \ - [=]() -> bool \ - { \ - if (SHOULD_LOG) \ - { \ - std::stringstream stream; \ - stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \ - spdlog::error(stream.str()); \ - } \ - return false; \ - }()) - -// 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); -} - -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); - } -}; - -struct Angle -{ - float pitch, yaw, roll; - - Angle(float pitch = 0, float yaw = 0, float roll = 0) : pitch(pitch), yaw(yaw), roll(roll) {} - - bool IsInvalid() - { - return !ValidateFloats(pitch, yaw, roll); - - if (!ValidateFloats(pitch, yaw, roll)) - return false; - - return (pitch > 90 || pitch < -90) || (yaw > 180 || yaw < -180) || (roll > 180 || roll < -180); - } -}; - -// block bad netmessages -// Servers can literally request a screenshot from any client, yeah no -AUTOHOOK(CLC_Screenshot_WriteToBuffer, engine.dll + 0x22AF20, -bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 57 48 83 EC 20 8B 42 10 -{ - return false; -} - -AUTOHOOK(CLC_Screenshot_ReadFromBuffer, engine.dll + 0x221F00, -bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38 -{ - return false; -} - -// This is unused ingame and a big client=>client exploit vector -AUTOHOOK(Base_CmdKeyValues_ReadFromBuffer, engine.dll + 0x220040, -bool, __fastcall, (void* thisptr, void* buffer)) // 40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70 -{ - return false; -} - -AUTOHOOK(CClient_ProcessSetConVar, engine.dll + 0x75CF0, -bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10 -{ - - 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; - bool bIsServerFrame = Tier0::ThreadInServerFrameThread(); - - std::string BLOCK_PREFIX = std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: "; - - if (bIsServerFrame) - { - constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69; - if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT) - { - return BLOCKED_INFO("Invalid m_ConVars_count (" << msg->m_ConVars_count << ")"); - } - } - - 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 BLOCKED_INFO("Missing null terminators"); - - auto realVar = R2::g_pCVar->FindVar(entry->name); - - if (realVar) - memcpy( - entry->name, - realVar->m_ConCommandBase.m_pszName, - strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case - - bool isValidFlags = true; - if (bIsServerFrame) - { - if (realVar) - isValidFlags = realVar->IsFlagSet(FCVAR_USERINFO); // ConVar MUST be userinfo var - } - else - { - // TODO: Should probably have some sanity checks, but can't find any that are consistent - } - - if (!isValidFlags) - { - if (!realVar) - { - return BLOCKED_INFO("Invalid flags on nonexistant cvar (how tho???)"); - } - else - { - return BLOCKED_INFO( - "Invalid flags (" << std::hex << "0x" << realVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name); - } - } - } - else - { - return BLOCKED_INFO("Unreadable memory at " << (void*)entry); // Not risking that one, they all gotta be readable - } - } - - return CClient_ProcessSetConVar(msg); -} - -// prevent invalid user CMDs -AUTOHOOK(CClient_ProcessUsercmds, engine.dll + 0x1040F0, -bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58 -{ - struct CLC_Move - { - BYTE gap0[24]; - void* m_pMessageHandler; - int m_nBackupCommands; - int m_nNewCommands; - int m_nLength; - // bf_read m_DataIn; - // bf_write m_DataOut; - }; - - auto msg = (CLC_Move*)pMsg; - - const char* BLOCK_PREFIX = "ProcessUserCmds: "; - - if (msg->m_nBackupCommands < 0) - { - return BLOCKED_INFO("Invalid m_nBackupCommands (" << msg->m_nBackupCommands << ")"); - } - - if (msg->m_nNewCommands < 0) - { - return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")"); - } - - // removing, as vanilla already limits num usercmds per frame - /*constexpr int NUMCMD_SANITY_LIMIT = 16; - if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT) - { - return BLOCKED_INFO("Command count is too high (new: " << msg->m_nNewCommands << ", backup: " << msg->m_nBackupCommands << ")"); - - }*/ - - if (msg->m_nLength <= 0) - return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")"); - - return CClient_ProcessUsercmds(thisptr, pMsg); -} - -AUTOHOOK(ReadUsercmd, server.dll + 0x2603F0, -void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24 ? 53 55 56 57 -{ - // Let normal usercmd read happen first, it's safe - ReadUsercmd(buf, pCmd_move, pCmd_from); - - // Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing) - struct __declspec(align(4)) SV_CUserCmd - { - DWORD command_number; - DWORD tick_count; - float command_time; - Angle worldViewAngles; - BYTE gap18[4]; - Angle localViewAngles; - Angle attackangles; - Vector move; - DWORD buttons; - BYTE impulse; - short weaponselect; - DWORD meleetarget; - BYTE gap4C[24]; - char headoffset; - BYTE gap65[11]; - Vector cameraPos; - Angle cameraAngles; - BYTE gap88[4]; - int tickSomething; - DWORD dword90; - DWORD predictedServerEventAck; - DWORD dword98; - float frameTime; - }; - - auto cmd = (SV_CUserCmd*)pCmd_move; - auto fromCmd = (SV_CUserCmd*)pCmd_from; - - std::string BLOCK_PREFIX = - "ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): "; - - if (cmd->worldViewAngles.IsInvalid()) - { - BLOCKED_INFO("CMD has invalid worldViewAngles"); - goto INVALID_CMD; - } - - if (cmd->attackangles.IsInvalid()) - { - BLOCKED_INFO("CMD has invalid attackangles"); - goto INVALID_CMD; - } - - if (cmd->localViewAngles.IsInvalid()) - { - BLOCKED_INFO("CMD has invalid localViewAngles"); - goto INVALID_CMD; - } - - if (cmd->cameraAngles.IsInvalid()) - { - BLOCKED_INFO("CMD has invalid cameraAngles"); - goto INVALID_CMD; - } - - if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0) - { - BLOCKED_INFO( - "Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime - << ", commandTime : " << cmd->command_time << ")"); - goto INVALID_CMD; // No simulation of bogus-timed cmds - } - - if (!cmd->move.IsValid()) - { - BLOCKED_INFO("Invalid move vector"); - goto INVALID_CMD; - } - - if (!cmd->cameraPos.IsValid()) - { - BLOCKED_INFO("Invalid cameraPos"); // IIRC this can crash spectating clients or anyone watching replays - goto INVALID_CMD; - } - - return; - -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->tick_count = cmd->frameTime = 0; - cmd->move = cmd->cameraPos = Vector(0, 0, 0); - cmd->buttons = 0; - cmd->meleetarget = 0; -} - -// ensure that GetLocalBaseClient().m_bRestrictServerCommands is set correctly, which the return value of this function controls -// this is IsValveMod in source, but we're making it IsRespawnMod now since valve didn't make this one -AUTOHOOK(IsRespawnMod, engine.dll + 0x1C6360, -bool, __fastcall, (const char* pModName)) // 48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63 -{ - return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) - && !Tier0::CommandLine()->CheckParm("-norestrictservercommands"); -} - -// prevent utf8 parser from crashing when provided bad data, which can be sent through user-controlled openinvites -AUTOHOOK(Rson_ParseUTF8, engine.dll + 0xEF670, -bool, __fastcall, (INT64 * a1, DWORD* a2, char* strData)) // 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 -{ - static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16"); - - // only call if we're parsing utf8 data from the network (i.e. communities), otherwise we get perf issues - if (_ReturnAddress() == targetRetAddr) - { - if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData)) - { - const char* BLOCK_PREFIX = "ParseUTF8 Hook: "; - BLOCKED_INFO("Ignoring potentially-crashing utf8 string"); - return false; - } - } - - return Rson_ParseUTF8(a1, a2, strData); -} - -ON_DLL_LOAD("engine.dll", EngineExploitFixes, (HMODULE baseAddress)) -{ - AUTOHOOK_DISPATCH_MODULE(engine.dll) - - // allow client/ui to run clientcommands despite restricting servercommands - NSMem::BytePatch((uintptr_t)baseAddress + 0x4FB65, "EB 11"); - NSMem::BytePatch((uintptr_t)baseAddress + 0x4FBAC, "EB 16"); -} - -ON_DLL_LOAD_RELIESON("server.dll", ServerExploitFixes, ConVar, (HMODULE baseAddress)) -{ - AUTOHOOK_DISPATCH_MODULE(server.dll) - - // Dumb ANTITAMPER patches (they negatively impact performance and security) - constexpr const char* ANTITAMPER_EXPORTS[] = { - "ANTITAMPER_SPOTCHECK_CODEMARKER", - "ANTITAMPER_TESTVALUE_CODEMARKER", - "ANTITAMPER_TRIGGER_CODEMARKER", - }; - - // Prevent these from actually doing anything - for (auto exportName : ANTITAMPER_EXPORTS) - { - uintptr_t address = (uintptr_t)GetProcAddress(baseAddress, 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, "C3"); - spdlog::info("Patched AntiTamper function export \"{}\"", exportName); - } - } - - ns_exploitfixes_log = - new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something"); -} \ No newline at end of file diff --git a/NorthstarDLL/ExploitFixes_UTF8Parser.h b/NorthstarDLL/ExploitFixes_UTF8Parser.h deleted file mode 100644 index 77014a35..00000000 --- a/NorthstarDLL/ExploitFixes_UTF8Parser.h +++ /dev/null @@ -1,175 +0,0 @@ -// Reimplementation of a exploitable UTF decoding function in titanfall - -#include "NSMem.h" - -#pragma once - -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"); - - 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); - v5 = 0; - _strData = strData; - v7 = &v4[*((UINT16*)a2 + 2)]; - if (v3 >= 2) - { - ++v4; - --v7; - if (v3 != 2) - { - while (1) - { - - if (!NSMem::IsMemoryReadable(v4, 1)) - return false; // INVALID - - v11 = *v4++; // crash potential - if (v11 != 92) - goto LABEL_6; - v11 = *v4++; - if (v11 == 110) - break; - switch (v11) - { - case 't': - v11 = 9; - goto LABEL_6; - case 'r': - v11 = 13; - goto LABEL_6; - case 'b': - v11 = 8; - goto LABEL_6; - case 'f': - v11 = 12; - goto LABEL_6; - } - if (v11 != 117) - goto LABEL_6; - v12 = *v4 | 0x20; - v13 = v4[1] | 0x20; - v14 = v4[2] | 0x20; - v15 = v4[3] | 0x20; - v16 = 87; - if (v12 <= 0x39) - v16 = 48; - v17 = v12 - v16; - v18 = 87; - v19 = v17 << 12; - if (v13 <= 0x39) - v18 = 48; - v20 = v13 - v18; - v21 = 87; - v22 = (v20 << 8) | v19; - if (v14 <= 0x39) - v21 = 48; - v23 = v14 - v21; - v24 = 87; - v25 = (16 * v23) | v22; - if (v15 <= 0x39) - v24 = 48; - v4 += 4; - v26 = (v15 - v24) | v25; - if (v26 - 55296 <= 0x7FF) - { - if (v26 >= 0xDC00) - return true; - if (*v4 != 92 || v4[1] != 117) - return true; - - v27 = v4[2] | 0x20; - v28 = v4[3] | 0x20; - v29 = v4[4] | 0x20; - v30 = v4[5] | 0x20; - v31 = 87; - if (v27 <= 0x39) - v31 = 48; - v32 = v27 - v31; - v33 = 87; - v34 = v32 << 12; - if (v28 <= 0x39) - v33 = 48; - v35 = v28 - v33; - v36 = 87; - v37 = (v35 << 8) | v34; - if (v29 <= 0x39) - v36 = 48; - v38 = v29 - v36; - v39 = 87; - v40 = (16 * v38) | v37; - if (v30 <= 0x39) - v39 = 48; - v4 += 6; - v41 = ((v30 - v39) | v40) - 56320; - if (v41 > 0x3FF) - return true; - v26 = v41 | ((v26 - 55296) << 10); - } - _strData += (DWORD)sub_F1320(v26, _strData); - LABEL_7: - if (v4 == v7) - goto LABEL_48; - } - v11 = 10; - LABEL_6: - v5 |= v11; - *_strData++ = v11; - goto LABEL_7; - } - } - LABEL_48: - return true; - } -} // namespace ExploitFixes_UTF8Parser \ No newline at end of file diff --git a/NorthstarDLL/NorthstarDLL.vcxproj b/NorthstarDLL/NorthstarDLL.vcxproj index 4f880642..43e1062a 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj +++ b/NorthstarDLL/NorthstarDLL.vcxproj @@ -120,6 +120,7 @@ + @@ -545,7 +546,6 @@ - @@ -553,7 +553,7 @@ - + @@ -569,6 +569,7 @@ + @@ -621,7 +622,7 @@ - + diff --git a/NorthstarDLL/NorthstarDLL.vcxproj.filters b/NorthstarDLL/NorthstarDLL.vcxproj.filters index 1b7603c3..514800c1 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj.filters +++ b/NorthstarDLL/NorthstarDLL.vcxproj.filters @@ -16,27 +16,12 @@ {d4199e4b-10d2-43ce-af9c-e1fa79e1e64e} - - {4d322431-dcaa-4f75-aee0-3b6371cf52a6} - - - {94259c8c-5411-48bf-af4f-46ca32b7d0bb} - - - {4f525372-34a8-40b3-8a95-81d77cdfcf7f} - {b6f79919-9735-476d-8798-067a75cbeca0} {ca657be5-c2d8-4322-a689-1154aaafe57b} - - {a18afb37-5fdd-4340-a6b4-a6541593e398} - - - {9751b551-5886-45d4-a039-cbd10445263d} - {8596cc1c-0492-4467-91e3-1f03b7e19f77} @@ -55,12 +40,6 @@ {74567974-c66b-45ef-ab28-97b7154ca224} - - {3e892d07-2239-44da-9cf3-c288a34cf9a2} - - - {6bbce8a5-38b4-4763-a7cb-4e98012ec245} - {4ca5392e-7d3d-4066-833f-f534cd5787c3} @@ -73,12 +52,6 @@ {85aacdee-0f92-4ec4-b20c-0739c1175055} - - {4db0d1e9-9035-457f-87f1-5dc3f13b6b9e} - - - {d1f93d1e-0ecb-44fe-a277-d3e75aec2570} - {3d41d3fc-8a3b-4358-b3e8-4f06dc96abfe} @@ -91,12 +64,6 @@ {24fd0855-9288-4129-93ba-c6cafdc98d1b} - - {2cbddb28-0b17-4881-847d-8773da52b268} - - - {0c93d909-e0d6-4c35-a8a4-a13f681a1012} - {4cb0dd89-5f16-4549-a864-34ca3075352a} @@ -112,45 +79,75 @@ {ea1e17a6-40b7-4e1b-8edb-e9ae704ce604} - - {59b0f68f-daa7-4641-b6fa-8464b56da2bb} - - - {44a83740-9d70-480d-9a7a-43b81f8eab9e} - - - {4a8a695a-a103-4b1f-b314-0ec19a253119} - - - {14fc0931-acad-46ec-a55e-94f4469d4235} - {51910ba0-2ff8-461d-9f67-8d7907b57d22} - - {04fd662a-6e70-494c-b720-c694a5cc2fb1} - {325e0d7d-6832-496d-8d8e-968fdfa5dd40} {802d0771-62f1-4733-89f9-57a4d8864b8d} - - {0f1ba4c4-78ee-4b05-afa5-6f598063f5c1} + + {04fd662a-6e70-494c-b720-c694a5cc2fb1} - - {bf0769d8-40fd-4701-85e9-7ed94aab2283} + + {a18afb37-5fdd-4340-a6b4-a6541593e398} - + + {4a8a695a-a103-4b1f-b314-0ec19a253119} + + {d8a83b5e-9a23-4124-824f-eab37880cb08} - - {ee3ba13a-3061-41d7-981d-328ac2596fd2} + + {2cbddb28-0b17-4881-847d-8773da52b268} - + + {4db0d1e9-9035-457f-87f1-5dc3f13b6b9e} + + + {59b0f68f-daa7-4641-b6fa-8464b56da2bb} + + + {3e892d07-2239-44da-9cf3-c288a34cf9a2} + + + {14fc0931-acad-46ec-a55e-94f4469d4235} + + {947835db-67d6-42c0-870d-62743f85231f} + + {bf0769d8-40fd-4701-85e9-7ed94aab2283} + + + {9751b551-5886-45d4-a039-cbd10445263d} + + + {96101d42-72af-4fd1-8559-8d1d1ff66240} + + + {ee3ba13a-3061-41d7-981d-328ac2596fd2} + + + {0c93d909-e0d6-4c35-a8a4-a13f681a1012} + + + {94259c8c-5411-48bf-af4f-46ca32b7d0bb} + + + {44a83740-9d70-480d-9a7a-43b81f8eab9e} + + + {6bbce8a5-38b4-4763-a7cb-4e98012ec245} + + + {826d5193-3ad0-434b-ba7c-dd24ed4bbd0c} + + + {0f1ba4c4-78ee-4b05-afa5-6f598063f5c1} + @@ -159,30 +156,12 @@ Header Files\include - - Header Files\Shared\Hooks - Header Files Header Files\Client - - Header Files\Shared - - - Header Files\Shared\Hooks - - - Header Files\Shared - - - Header Files\Shared - - - Header Files\Shared\Convar - Header Files\include\spdlog @@ -453,12 +432,6 @@ Header Files\include\spdlog\details - - Header Files\Shared\Convar - - - Header Files\Shared\Mods - Header Files\include\rapidjson @@ -564,27 +537,12 @@ Header Files\include\rapidjson\msinttypes - - Header Files\Shared\Mods\Compiled - Header Files\Server\Authentication Header Files\include - - Header Files\Shared - - - Header Files\Shared\Convar - - - Header Files\Shared\Mods\Compiled - - - Header Files\Shared - Header Files\include\openssl\openssl @@ -1401,18 +1359,6 @@ Header Files\Client - - Header Files\Shared - - - Header Files\Shared\Convar - - - Header Files\Shared\Math - - - Header Files\Shared\Math - Header Files\Client @@ -1422,106 +1368,136 @@ Header Files - - Source Files\Shared\Exploit Fixes - Header Files Header Files - - Header Files\Shared + + Header Files\Server\Scripted - - Header Files\Shared\Game Functions + + Header Files\Dedicated Server + + + Header Files + + + Source Files\Exploit Fixes + + + Header Files - Header Files\Shared + Header Files - - Header Files\Server\Scripted + + Header Files - - Header Files\Server\Scripted + + Header Files - - Header Files\Server\Dedicated + + Header Files + + + Header Files + + + Header Files + + + Header Files\Math + + + Header Files\Math + + + Header Files\Convar + + + Header Files\Convar + + + Header Files\Convar + + + Header Files\Filesystem + + + Header Files\Hooks + + + Header Files\Exploit Fixes + + + Header Files\Console + + + Header Files\Convar + + + Header Files\Mods + + + Header Files\Mods\Compiled Assets - Header Files\Shared\Console + Header Files\Console - Header Files\Shared\Console + Header Files\Console + + + Header Files\Game Functions - Header Files\Shared\Game Functions + Header Files\Game Functions - Header Files\Shared\Game Functions + Header Files\Game Functions - - Header Files\Shared\Game Functions + + Header Files\Mods\Compiled Assets - - Header Files\Shared + + Header Files\Game Functions - - Header Files\Shared\Filesystem + + Header Files\Hooks - Header Files\Shared\Filesystem + Header Files\Filesystem - - Header Files + + Header Files\Math - - Source Files\Shared\Exploit Fixes + + Header Files\Hooks Source Files - - Source Files\Shared\Hooks - - Source Files\Server\Dedicated Server + Source Files\Dedicated Server Source Files\Client - - Source Files\Shared - - - Source Files\Shared\Hooks - - - Source Files\Shared - - - Source Files\Shared\Convar - - - Source Files\Shared\Convar - - Source Files\Shared\Mods + Source Files\Mods - Source Files\Shared\Mods\Compiled Assets + Source Files\Mods\Compiled Assets Source Files\Server\Authentication - Source Files\Shared\Mods\Compiled Assets - - - Source Files\Shared + Source Files\Mods\Compiled Assets Source Files\Client @@ -1530,26 +1506,17 @@ Source Files\Client - Source Files\Server\Dedicated Server - - - Source Files\Shared\Convar + Source Files\Dedicated Server - Source Files\Shared\Mods\Compiled Assets + Source Files\Mods\Compiled Assets Source Files\Client - - Source Files\Shared - Source Files\Client - - Source Files\Shared - Source Files\Server @@ -1568,12 +1535,6 @@ Source Files\Server - - Source Files\Shared\Convar - - - Source Files\Shared\Math - Source Files\Client @@ -1586,27 +1547,12 @@ Source Files\Client - - Source Files\Shared\Exploit Fixes - Source Files Source Files\Client - - Source Files\Shared - - - Source Files\Shared\Game Functions - - - Source Files\Shared - - - Source Files\Shared - Source Files\Client\Scripted @@ -1631,42 +1577,96 @@ Source Files\Server\Scripted - - Source Files\Shared\Console - Source Files\Client - - Source Files\Shared\Console - - - Source Files\Shared\Game Functions + + Source Files - - Source Files\Shared\Game Functions + + Source Files - - Source Files\Shared\Game Functions + + Source Files - Source Files\Shared + Source Files - - Source Files\Shared\Console + + Source Files - - Source Files\Shared\Filesystem + + Source Files - - Source Files\Shared\Filesystem + + Source Files - + Source Files - + Source Files + + Source Files + + + Source Files + + + Source Files\Game Functions + + + Source Files\Game Functions + + + Source Files\Game Functions + + + Source Files\Filesystem + + + Source Files\Filesystem + + + Source Files\Exploit Fixes + + + Source Files\Exploit Fixes + + + Source Files\Hooks + + + Source Files\Hooks + + + Source Files\Math + + + Source Files\Convar + + + Source Files\Convar + + + Source Files\Console + + + Source Files\Console + + + Source Files\Convar + + + Source Files\Convar + + + Source Files\Game Functions + + + Source Files\Console + diff --git a/NorthstarDLL/audio.cpp b/NorthstarDLL/audio.cpp index 629d9179..0aa5c9f0 100644 --- a/NorthstarDLL/audio.cpp +++ b/NorthstarDLL/audio.cpp @@ -315,6 +315,7 @@ void CustomAudioManager::ClearAudioOverrides() { // stop all miles sounds beforehand // miles_stop_all + MilesStopAll(); // this is cancer but it works @@ -498,5 +499,5 @@ ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (HMODULE baseAddre AUTOHOOK_DISPATCH() Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, ""); - MilesStopAll = (MilesStopAll_Type)((char*)GetModuleHandleA("mileswin64.dll") + 0x580850); + MilesStopAll = (MilesStopAll_Type)((char*)baseAddress + 0x580850); } \ No newline at end of file diff --git a/NorthstarDLL/bansystem.cpp b/NorthstarDLL/bansystem.cpp index 2a277874..d1b58102 100644 --- a/NorthstarDLL/bansystem.cpp +++ b/NorthstarDLL/bansystem.cpp @@ -3,14 +3,15 @@ #include "bansystem.h" #include "serverauthentication.h" #include "concommand.h" -#include "miscserverscript.h" +#include "r2server.h" +#include "r2engine.h" #include "nsprefix.h" #include const char* BANLIST_PATH_SUFFIX = "/banlist.txt"; -ServerBanSystem* g_ServerBanSystem; +ServerBanSystem* g_pServerBanSystem; void ServerBanSystem::OpenBanlist() { @@ -71,11 +72,11 @@ void ConCommand_ban(const CCommand& args) // assuming maxplayers 32 for (int i = 0; i < 32; i++) { - void* player = GetPlayerByIndex(i); + R2::CBasePlayer* player = R2::UTIL_PlayerByIndex(i); - if (!strcmp((char*)player + 0x16, args.Arg(1)) || !strcmp((char*)player + 0xF500, args.Arg(1))) + if (!strcmp(player->m_Name, args.Arg(1)) || !strcmp(player->m_UID, args.Arg(1))) { - g_ServerBanSystem->BanUID(strtoll((char*)player + 0xF500, nullptr, 10)); + g_pServerBanSystem->BanUID(strtoll((char*)player + 0xF500, nullptr, 10)); R2::CBaseClient__Disconnect(player, 1, "Banned from server"); break; } @@ -88,18 +89,18 @@ void ConCommand_unban(const CCommand& args) return; // assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything - g_ServerBanSystem->UnbanUID(strtoll(args.Arg(1), nullptr, 10)); + g_pServerBanSystem->UnbanUID(strtoll(args.Arg(1), nullptr, 10)); } void ConCommand_clearbanlist(const CCommand& args) { - g_ServerBanSystem->ClearBanlist(); + g_pServerBanSystem->ClearBanlist(); } ON_DLL_LOAD_RELIESON("engine.dll", BanSystem, ConCommand, (HMODULE baseAddress)) { - g_ServerBanSystem = new ServerBanSystem; - g_ServerBanSystem->OpenBanlist(); + g_pServerBanSystem = new ServerBanSystem; + g_pServerBanSystem->OpenBanlist(); RegisterConCommand("ban", ConCommand_ban, "bans a given player by uid or name", FCVAR_GAMEDLL); RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_NONE); diff --git a/NorthstarDLL/bansystem.h b/NorthstarDLL/bansystem.h index b4238982..0d21aaa5 100644 --- a/NorthstarDLL/bansystem.h +++ b/NorthstarDLL/bansystem.h @@ -15,4 +15,4 @@ class ServerBanSystem bool IsUIDAllowed(uint64_t uid); }; -extern ServerBanSystem* g_ServerBanSystem; +extern ServerBanSystem* g_pServerBanSystem; diff --git a/NorthstarDLL/buildainfile.cpp b/NorthstarDLL/buildainfile.cpp index 6659677e..1ac73b43 100644 --- a/NorthstarDLL/buildainfile.cpp +++ b/NorthstarDLL/buildainfile.cpp @@ -16,6 +16,7 @@ const int AINET_SCRIPT_VERSION_NUMBER = 21; const int PLACEHOLDER_CRC = 0; const int MAX_HULLS = 5; +#pragma pack(push, 1) struct CAI_NodeLink { short srcId; @@ -26,6 +27,7 @@ struct CAI_NodeLink char unk2[5]; int64_t flags; }; +#pragma pack(pop) #pragma pack(push, 1) struct CAI_NodeLinkDisk @@ -35,7 +37,9 @@ struct CAI_NodeLinkDisk char unk0; bool hulls[MAX_HULLS]; }; +#pragma pack(pop) +#pragma pack(push, 1) struct CAI_Node { int index; // not present on disk @@ -64,6 +68,7 @@ struct CAI_Node char unk9[8]; // padding until next bit char unk10[8]; // should match up to unk6 on disk }; +#pragma pack(pop) // the way CAI_Nodes are represented in on-disk ain files #pragma pack(push, 1) @@ -83,7 +88,9 @@ struct CAI_NodeDisk short unk5; char unk6[8]; }; // total size of 68 bytes +#pragma pack(pop) +#pragma pack(push, 1) struct UnkNodeStruct0 { int index; @@ -108,10 +115,12 @@ struct UnkNodeStruct0 char pad4[132]; char unk5; }; +#pragma pack(pop) int* pUnkStruct0Count; UnkNodeStruct0*** pppUnkNodeStruct0s; +#pragma pack(push, 1) struct UnkLinkStruct1 { short unk0; @@ -121,10 +130,12 @@ struct UnkLinkStruct1 char unk4; char unk5; }; +#pragma pack(pop) int* pUnkLinkStruct1Count; UnkLinkStruct1*** pppUnkStruct1s; +#pragma pack(push, 1) struct CAI_ScriptNode { float x; @@ -132,7 +143,9 @@ struct CAI_ScriptNode float z; uint64_t scriptdata; }; +#pragma pack(pop) +#pragma pack(push, 1) struct CAI_Network { // +0 @@ -162,6 +175,7 @@ struct CAI_Network // +84176 CAI_Node** nodes; }; +#pragma pack(pop) char** pUnkServerMapversionGlobal; diff --git a/NorthstarDLL/clientauthhooks.cpp b/NorthstarDLL/clientauthhooks.cpp index 2ddee855..3ba66d6f 100644 --- a/NorthstarDLL/clientauthhooks.cpp +++ b/NorthstarDLL/clientauthhooks.cpp @@ -17,12 +17,12 @@ void,, (void* a1)) { // game will call this forever, until it gets a valid auth key // so, we need to manually invalidate our key until we're authed with northstar, then we'll allow game to auth with stryder - if (!g_MasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN) + if (!g_pMasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN) { // if player has agreed to send token and we aren't already authing, try to auth if (Cvar_ns_has_agreed_to_send_token->GetInt() == AGREED_TO_SEND_TOKEN && - !g_MasterServerManager->m_bOriginAuthWithMasterServerInProgress) - g_MasterServerManager->AuthenticateOriginWithMasterServer(R2::g_pLocalPlayerUserID, R2::g_pLocalPlayerOriginToken); + !g_pMasterServerManager->m_bOriginAuthWithMasterServerInProgress) + g_pMasterServerManager->AuthenticateOriginWithMasterServer(R2::g_pLocalPlayerUserID, R2::g_pLocalPlayerOriginToken); // invalidate key so auth will fail *R2::g_pLocalPlayerOriginToken = 0; diff --git a/NorthstarDLL/clientchathooks.cpp b/NorthstarDLL/clientchathooks.cpp index 8e1f7c77..ca783f88 100644 --- a/NorthstarDLL/clientchathooks.cpp +++ b/NorthstarDLL/clientchathooks.cpp @@ -12,9 +12,7 @@ void,, (void* self, const char* message, int inboxId, bool isTeam, bool isDead)) { // This hook is called for each HUD, but we only want our logic to run once. if (self != *CHudChat::allHuds) - { return; - } if (g_pClientSquirrel->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR) { @@ -31,7 +29,7 @@ void,, (void* self, const char* message, int inboxId, bool isTeam, bool isDead)) payload = message + 1; } - g_pClientSquirrel->pushinteger(g_pClientSquirrel->sqvm2, (int) senderId - 1); + g_pClientSquirrel->pushinteger(g_pClientSquirrel->sqvm2, (int)senderId - 1); g_pClientSquirrel->pushstring(g_pClientSquirrel->sqvm2, payload); g_pClientSquirrel->pushbool(g_pClientSquirrel->sqvm2, isTeam); g_pClientSquirrel->pushbool(g_pClientSquirrel->sqvm2, isDead); @@ -39,42 +37,38 @@ void,, (void* self, const char* message, int inboxId, bool isTeam, bool isDead)) g_pClientSquirrel->call(g_pClientSquirrel->sqvm2, 5); } else - { for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next) - { CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead); - } - } } // void NSChatWrite( int context, string str ) -static SQRESULT SQ_ChatWrite(void* sqvm) +SQRESULT SQ_ChatWrite(void* sqvm) { - int context = g_pClientSquirrel->getinteger(g_pClientSquirrel->sqvm2, 1); - const char* str = g_pClientSquirrel->getstring(g_pClientSquirrel->sqvm2, 2); + int context = g_pClientSquirrel->getinteger(sqvm, 1); + const char* str = g_pClientSquirrel->getstring(sqvm, 2); LocalChatWriter((LocalChatWriter::Context)context).Write(str); - return SQRESULT_NOTNULL; + return SQRESULT_NULL; } // void NSChatWriteRaw( int context, string str ) -static SQRESULT SQ_ChatWriteRaw(void* sqvm) +SQRESULT SQ_ChatWriteRaw(void* sqvm) { - int context = g_pClientSquirrel->getinteger(g_pClientSquirrel->sqvm2, 1); - const char* str = g_pClientSquirrel->getstring(g_pClientSquirrel->sqvm2, 2); + int context = g_pClientSquirrel->getinteger(sqvm, 1); + const char* str = g_pClientSquirrel->getstring(sqvm, 2); LocalChatWriter((LocalChatWriter::Context)context).InsertText(str); - return SQRESULT_NOTNULL; + return SQRESULT_NULL; } // void NSChatWriteLine( int context, string str ) -static SQRESULT SQ_ChatWriteLine(void* sqvm) +SQRESULT SQ_ChatWriteLine(void* sqvm) { - int context = g_pClientSquirrel->getinteger(g_pClientSquirrel->sqvm2, 1); - const char* str = g_pClientSquirrel->getstring(g_pClientSquirrel->sqvm2, 2); + int context = g_pClientSquirrel->getinteger(sqvm, 1); + const char* str = g_pClientSquirrel->getstring(sqvm, 2); LocalChatWriter((LocalChatWriter::Context)context).WriteLine(str); - return SQRESULT_NOTNULL; + return SQRESULT_NULL; } ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientChatHooks, ClientSquirrel, (HMODULE baseAddress)) diff --git a/NorthstarDLL/dedicated.cpp b/NorthstarDLL/dedicated.cpp index cf0d68f1..cbaed442 100644 --- a/NorthstarDLL/dedicated.cpp +++ b/NorthstarDLL/dedicated.cpp @@ -81,9 +81,9 @@ void RunServer(CDedicatedExports* dedicated) SetConsoleTitleA(fmt::format( "{} - {} {}/{} players ({})", - g_MasterServerManager->m_sUnicodeServerName, + g_pMasterServerManager->m_sUnicodeServerName, g_pHostState->m_levelName, - g_pServerAuthenticationManager->m_additionalPlayerData.size(), + g_pServerAuthentication->m_PlayerAuthenticationData.size(), maxPlayers, GetCurrentPlaylistName()) .c_str()); diff --git a/NorthstarDLL/exploitfixes.cpp b/NorthstarDLL/exploitfixes.cpp new file mode 100644 index 00000000..fbe91e3b --- /dev/null +++ b/NorthstarDLL/exploitfixes.cpp @@ -0,0 +1,435 @@ +#include "pch.h" +#include "NSMem.h" +#include "cvar.h" +#include "limits.h" +#include "dedicated.h" +#include "tier0.h" +#include "r2engine.h" +#include "r2client.h" + +AUTOHOOK_INIT() + +ConVar* ns_exploitfixes_log; +#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0) +#define BLOCKED_INFO(s) \ + ( \ + [=]() -> bool \ + { \ + if (SHOULD_LOG) \ + { \ + std::stringstream stream; \ + stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \ + spdlog::error(stream.str()); \ + } \ + return false; \ + }()) + +// 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); +} + +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); + } +}; + +struct Angle +{ + float pitch, yaw, roll; + + Angle(float pitch = 0, float yaw = 0, float roll = 0) : pitch(pitch), yaw(yaw), roll(roll) {} + + bool IsInvalid() + { + return !ValidateFloats(pitch, yaw, roll); + + if (!ValidateFloats(pitch, yaw, roll)) + return false; + + return (pitch > 90 || pitch < -90) || (yaw > 180 || yaw < -180) || (roll > 180 || roll < -180); + } +}; + +// block bad netmessages +// Servers can literally request a screenshot from any client, yeah no +AUTOHOOK(CLC_Screenshot_WriteToBuffer, engine.dll + 0x22AF20, +bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 57 48 83 EC 20 8B 42 10 +{ + return false; +} + +AUTOHOOK(CLC_Screenshot_ReadFromBuffer, engine.dll + 0x221F00, +bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38 +{ + return false; +} + +// This is unused ingame and a big client=>server=>client exploit vector +AUTOHOOK(Base_CmdKeyValues_ReadFromBuffer, engine.dll + 0x220040, +bool, __fastcall, (void* thisptr, void* buffer)) // 40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70 +{ + return false; +} + +AUTOHOOK(CClient_ProcessSetConVar, engine.dll + 0x75CF0, +bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10 +{ + + 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; + bool bIsServerFrame = Tier0::ThreadInServerFrameThread(); + + std::string BLOCK_PREFIX = std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: "; + + if (bIsServerFrame) + { + constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69; + if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT) + { + return BLOCKED_INFO("Invalid m_ConVars_count (" << msg->m_ConVars_count << ")"); + } + } + + 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 BLOCKED_INFO("Missing null terminators"); + + auto realVar = R2::g_pCVar->FindVar(entry->name); + + if (realVar) + memcpy( + entry->name, + realVar->m_ConCommandBase.m_pszName, + strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case + + bool isValidFlags = true; + if (bIsServerFrame) + { + if (realVar) + isValidFlags = realVar->IsFlagSet(FCVAR_USERINFO); // ConVar MUST be userinfo var + } + else + { + // TODO: Should probably have some sanity checks, but can't find any that are consistent + } + + if (!isValidFlags) + { + if (!realVar) + { + return BLOCKED_INFO("Invalid flags on nonexistant cvar (how tho???)"); + } + else + { + return BLOCKED_INFO( + "Invalid flags (" << std::hex << "0x" << realVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name); + } + } + } + else + { + return BLOCKED_INFO("Unreadable memory at " << (void*)entry); // Not risking that one, they all gotta be readable + } + } + + return CClient_ProcessSetConVar(msg); +} + +// prevent invalid user CMDs +AUTOHOOK(CClient_ProcessUsercmds, engine.dll + 0x1040F0, +bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58 +{ + struct CLC_Move + { + BYTE gap0[24]; + void* m_pMessageHandler; + int m_nBackupCommands; + int m_nNewCommands; + int m_nLength; + // bf_read m_DataIn; + // bf_write m_DataOut; + }; + + auto msg = (CLC_Move*)pMsg; + + const char* BLOCK_PREFIX = "ProcessUserCmds: "; + + if (msg->m_nBackupCommands < 0) + { + return BLOCKED_INFO("Invalid m_nBackupCommands (" << msg->m_nBackupCommands << ")"); + } + + if (msg->m_nNewCommands < 0) + { + return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")"); + } + + // removing, as vanilla already limits num usercmds per frame + /*constexpr int NUMCMD_SANITY_LIMIT = 16; + if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT) + { + return BLOCKED_INFO("Command count is too high (new: " << msg->m_nNewCommands << ", backup: " << msg->m_nBackupCommands << ")"); + + }*/ + + if (msg->m_nLength <= 0) + return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")"); + + return CClient_ProcessUsercmds(thisptr, pMsg); +} + +AUTOHOOK(ReadUsercmd, server.dll + 0x2603F0, +void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24 ? 53 55 56 57 +{ + // Let normal usercmd read happen first, it's safe + ReadUsercmd(buf, pCmd_move, pCmd_from); + + // Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing) + struct __declspec(align(4)) SV_CUserCmd + { + DWORD command_number; + DWORD tick_count; + float command_time; + Angle worldViewAngles; + BYTE gap18[4]; + Angle localViewAngles; + Angle attackangles; + Vector move; + DWORD buttons; + BYTE impulse; + short weaponselect; + DWORD meleetarget; + BYTE gap4C[24]; + char headoffset; + BYTE gap65[11]; + Vector cameraPos; + Angle cameraAngles; + BYTE gap88[4]; + int tickSomething; + DWORD dword90; + DWORD predictedServerEventAck; + DWORD dword98; + float frameTime; + }; + + auto cmd = (SV_CUserCmd*)pCmd_move; + auto fromCmd = (SV_CUserCmd*)pCmd_from; + + std::string BLOCK_PREFIX = + "ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): "; + + if (cmd->worldViewAngles.IsInvalid()) + { + BLOCKED_INFO("CMD has invalid worldViewAngles"); + goto INVALID_CMD; + } + + if (cmd->attackangles.IsInvalid()) + { + BLOCKED_INFO("CMD has invalid attackangles"); + goto INVALID_CMD; + } + + if (cmd->localViewAngles.IsInvalid()) + { + BLOCKED_INFO("CMD has invalid localViewAngles"); + goto INVALID_CMD; + } + + if (cmd->cameraAngles.IsInvalid()) + { + BLOCKED_INFO("CMD has invalid cameraAngles"); + goto INVALID_CMD; + } + + if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0) + { + BLOCKED_INFO( + "Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime + << ", commandTime : " << cmd->command_time << ")"); + goto INVALID_CMD; // No simulation of bogus-timed cmds + } + + if (!cmd->move.IsValid()) + { + BLOCKED_INFO("Invalid move vector"); + goto INVALID_CMD; + } + + if (!cmd->cameraPos.IsValid()) + { + BLOCKED_INFO("Invalid cameraPos"); // IIRC this can crash spectating clients or anyone watching replays + goto INVALID_CMD; + } + + return; + +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->tick_count = cmd->frameTime = 0; + cmd->move = cmd->cameraPos = Vector(0, 0, 0); + cmd->buttons = 0; + cmd->meleetarget = 0; +} + +// ensure that GetLocalBaseClient().m_bRestrictServerCommands is set correctly, which the return value of this function controls +// this is IsValveMod in source, but we're making it IsRespawnMod now since valve didn't make this one +AUTOHOOK(IsRespawnMod, engine.dll + 0x1C6360, +bool, __fastcall, (const char* pModName)) // 48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63 +{ + return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) + && !Tier0::CommandLine()->CheckParm("-norestrictservercommands"); +} + +// ratelimit stringcmds, and prevent remote clients from calling non-FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS commands +bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, R2::cmd_source_t commandSource); +AUTOHOOK(CGameClient__ExecuteStringCommand, +engine.dll + 0x1022E0, bool, , (R2::CBasePlayer* self, uint32_t unknown, const char* pCommandString)) +{ + if (!g_pServerLimits->CheckStringCommandLimits(self)) + { + R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands"); + return false; + } + + // verify the command we're trying to execute is FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, if it's a concommand + char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor + memset(commandBuf, 0, sizeof(commandBuf)); + CCommand tempCommand = *(CCommand*)&commandBuf; + + if (!CCommand__Tokenize(tempCommand, pCommandString, R2::cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC()) + return false; + + ConCommand* command = R2::g_pCVar->FindCommand(tempCommand.Arg(0)); + + // if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff + if (command && !command->IsFlagSet(FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS)) + { + // ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients + if (IsDedicatedServer()) + return false; + + if (strcmp(self->m_UID, R2::g_pLocalPlayerUserID)) + return false; + } + + return CGameClient__ExecuteStringCommand(self, unknown, pCommandString); +} + +// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines +bool bWasWritingStringTableSuccessful; +AUTOHOOK(CBaseClient__SendServerInfo, +engine.dll + 0x104FB0, void, , (void* self)) +{ + bWasWritingStringTableSuccessful = true; + CBaseClient__SendServerInfo(self); + if (!bWasWritingStringTableSuccessful) + R2::CBaseClient__Disconnect( + self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting"); +} + +ON_DLL_LOAD("engine.dll", EngineExploitFixes, (HMODULE baseAddress)) +{ + AUTOHOOK_DISPATCH_MODULE(engine.dll) + + CCommand__Tokenize = (bool(*)(CCommand&, const char*, R2::cmd_source_t))((char*)baseAddress + 0x418380); + + // allow client/ui to run clientcommands despite restricting servercommands + NSMem::BytePatch((uintptr_t)baseAddress + 0x4FB65, "EB 11"); + NSMem::BytePatch((uintptr_t)baseAddress + 0x4FBAC, "EB 16"); + + // patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails + { + uintptr_t writeAddress = (uintptr_t)(&bWasWritingStringTableSuccessful - ((uintptr_t)baseAddress + 0x234EDC)); + + auto addr = (uintptr_t)baseAddress + 0x234ED2; + NSMem::BytePatch(addr, "C7 05"); + NSMem::BytePatch(addr + 2, (BYTE*)&writeAddress, sizeof(writeAddress)); + + NSMem::BytePatch(addr + 6, "00 00 00 00"); + + NSMem::NOP(addr + 10, 5); + } +} + +ON_DLL_LOAD_RELIESON("server.dll", ServerExploitFixes, ConVar, (HMODULE baseAddress)) +{ + AUTOHOOK_DISPATCH_MODULE(server.dll) + + // ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue) + // this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches + NSMem::BytePatch((uintptr_t)baseAddress + 0x153920, "C3"); + + // Dumb ANTITAMPER patches (they negatively impact performance and security) + constexpr const char* ANTITAMPER_EXPORTS[] = { + "ANTITAMPER_SPOTCHECK_CODEMARKER", + "ANTITAMPER_TESTVALUE_CODEMARKER", + "ANTITAMPER_TRIGGER_CODEMARKER", + }; + + // Prevent these from actually doing anything + for (auto exportName : ANTITAMPER_EXPORTS) + { + uintptr_t address = (uintptr_t)GetProcAddress(baseAddress, 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, "C3"); + spdlog::info("Patched AntiTamper function export \"{}\"", exportName); + } + } + + ns_exploitfixes_log = + new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something"); +} \ No newline at end of file diff --git a/NorthstarDLL/exploitfixes_utf8parser.cpp b/NorthstarDLL/exploitfixes_utf8parser.cpp new file mode 100644 index 00000000..5627c3e3 --- /dev/null +++ b/NorthstarDLL/exploitfixes_utf8parser.cpp @@ -0,0 +1,190 @@ +#include "pch.h" +#include "NSMem.h" + +AUTOHOOK_INIT() + +// Reimplementation of an exploitable UTF decoding function in titanfall +bool __fastcall CheckUTF8Valid(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"); + + 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); + v5 = 0; + _strData = strData; + v7 = &v4[*((UINT16*)a2 + 2)]; + if (v3 >= 2) + { + ++v4; + --v7; + if (v3 != 2) + { + while (1) + { + + if (!NSMem::IsMemoryReadable(v4, 1)) + return false; // INVALID + + v11 = *v4++; // crash potential + if (v11 != 92) + goto LABEL_6; + v11 = *v4++; + if (v11 == 110) + break; + switch (v11) + { + case 't': + v11 = 9; + goto LABEL_6; + case 'r': + v11 = 13; + goto LABEL_6; + case 'b': + v11 = 8; + goto LABEL_6; + case 'f': + v11 = 12; + goto LABEL_6; + } + if (v11 != 117) + goto LABEL_6; + v12 = *v4 | 0x20; + v13 = v4[1] | 0x20; + v14 = v4[2] | 0x20; + v15 = v4[3] | 0x20; + v16 = 87; + if (v12 <= 0x39) + v16 = 48; + v17 = v12 - v16; + v18 = 87; + v19 = v17 << 12; + if (v13 <= 0x39) + v18 = 48; + v20 = v13 - v18; + v21 = 87; + v22 = (v20 << 8) | v19; + if (v14 <= 0x39) + v21 = 48; + v23 = v14 - v21; + v24 = 87; + v25 = (16 * v23) | v22; + if (v15 <= 0x39) + v24 = 48; + v4 += 4; + v26 = (v15 - v24) | v25; + if (v26 - 55296 <= 0x7FF) + { + if (v26 >= 0xDC00) + return true; + if (*v4 != 92 || v4[1] != 117) + return true; + + v27 = v4[2] | 0x20; + v28 = v4[3] | 0x20; + v29 = v4[4] | 0x20; + v30 = v4[5] | 0x20; + v31 = 87; + if (v27 <= 0x39) + v31 = 48; + v32 = v27 - v31; + v33 = 87; + v34 = v32 << 12; + if (v28 <= 0x39) + v33 = 48; + v35 = v28 - v33; + v36 = 87; + v37 = (v35 << 8) | v34; + if (v29 <= 0x39) + v36 = 48; + v38 = v29 - v36; + v39 = 87; + v40 = (16 * v38) | v37; + if (v30 <= 0x39) + v39 = 48; + v4 += 6; + v41 = ((v30 - v39) | v40) - 56320; + if (v41 > 0x3FF) + return true; + v26 = v41 | ((v26 - 55296) << 10); + } + _strData += (DWORD)sub_F1320(v26, _strData); + LABEL_7: + if (v4 == v7) + goto LABEL_48; + } + v11 = 10; + LABEL_6: + v5 |= v11; + *_strData++ = v11; + goto LABEL_7; + } + } +LABEL_48: + return true; +} + +// prevent utf8 parser from crashing when provided bad data, which can be sent through user-controlled openinvites +AUTOHOOK(Rson_ParseUTF8, engine.dll + 0xEF670, +bool, __fastcall, (INT64 * a1, DWORD* a2, char* strData)) // 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 +{ + static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16"); + + // only call if we're parsing utf8 data from the network (i.e. communities), otherwise we get perf issues + if (_ReturnAddress() == targetRetAddr && !CheckUTF8Valid(a1, a2, strData)) + return false; + + return Rson_ParseUTF8(a1, a2, strData); +} + +ON_DLL_LOAD("engine.dll", EngineExploitFixes_UTF8Parser, (HMODULE baseAddress)) +{ + AUTOHOOK_DISPATCH() +} \ No newline at end of file diff --git a/NorthstarDLL/hoststate.cpp b/NorthstarDLL/hoststate.cpp index 248f7259..aabfbe7b 100644 --- a/NorthstarDLL/hoststate.cpp +++ b/NorthstarDLL/hoststate.cpp @@ -25,7 +25,7 @@ void,, (CHostState* hostState)) Cbuf_Execute(); // need to do this to ensure we don't go to private match - if (g_pServerAuthenticationManager->m_bNeedLocalAuthForNewgame) + if (g_pServerAuthentication->m_bNeedLocalAuthForNewgame) SetCurrentPlaylist("tdm"); // net_data_block_enabled is required for sp, force it if we're on an sp map @@ -45,17 +45,17 @@ void,, (CHostState* hostState)) // Copy new server name cvar to source Cvar_hostname->SetValue(Cvar_ns_server_name->GetString()); - g_MasterServerManager->AddSelfToServerList( + g_pMasterServerManager->AddSelfToServerList( Cvar_hostport->GetInt(), - Cvar_ns_player_auth_port->GetInt(), + g_pServerAuthentication->Cvar_ns_player_auth_port->GetInt(), Cvar_ns_server_name->GetString(), Cvar_ns_server_desc->GetString(), hostState->m_levelName, GetCurrentPlaylistName(), maxPlayers, Cvar_ns_server_password->GetString()); - g_pServerAuthenticationManager->StartPlayerAuthServer(); - g_pServerAuthenticationManager->m_bNeedLocalAuthForNewgame = false; + g_pServerAuthentication->StartPlayerAuthServer(); + g_pServerAuthentication->m_bNeedLocalAuthForNewgame = false; } AUTOHOOK(CHostState__State_ChangeLevelMP, engine.dll + 0x16E520, @@ -73,7 +73,7 @@ void,, (CHostState* hostState)) if (!strncmp(g_pHostState->m_levelName, "sp_", 3)) g_pCVar->FindVar("net_data_block_enabled")->SetValue(true); - g_MasterServerManager->UpdateServerMapAndPlaylist(hostState->m_levelName, GetCurrentPlaylistName(), maxPlayers); + g_pMasterServerManager->UpdateServerMapAndPlaylist(hostState->m_levelName, GetCurrentPlaylistName(), maxPlayers); double dStartTime = Tier0::Plat_FloatTime(); CHostState__State_ChangeLevelMP(hostState); @@ -85,8 +85,8 @@ void,, (CHostState* hostState)) { spdlog::info("HostState: GameShutdown"); - g_MasterServerManager->RemoveSelfFromServerList(); - g_pServerAuthenticationManager->StopPlayerAuthServer(); + g_pMasterServerManager->RemoveSelfFromServerList(); + g_pServerAuthentication->StopPlayerAuthServer(); CHostState__State_GameShutdown(hostState); } diff --git a/NorthstarDLL/limits.cpp b/NorthstarDLL/limits.cpp new file mode 100644 index 00000000..051ed8f8 --- /dev/null +++ b/NorthstarDLL/limits.cpp @@ -0,0 +1,185 @@ +#include "pch.h" +#include "limits.h" +#include "hoststate.h" +#include "r2client.h" +#include "r2engine.h" +#include "tier0.h" +#include "serverauthentication.h" + +AUTOHOOK_INIT() + +ServerLimitsManager* g_pServerLimits; + +ConVar* Cvar_net_datablock_enabled; + +void ServerLimitsManager::AddPlayer(R2::CBasePlayer* player) +{ + PlayerLimitData limitData; + m_PlayerLimitData.insert(std::make_pair(player, limitData)); +} + +bool ServerLimitsManager::CheckStringCommandLimits(R2::CBasePlayer* player) +{ + if (CVar_sv_quota_stringcmdspersecond->GetInt() != -1) + { + // note: this isn't super perfect, legit clients can trigger it in lobby if they try, mostly good enough tho imo + if (Tier0::Plat_FloatTime() - m_PlayerLimitData[player].lastClientCommandQuotaStart >= 1.0) + { + // reset quota + m_PlayerLimitData[player].lastClientCommandQuotaStart = Tier0::Plat_FloatTime(); + m_PlayerLimitData[player].numClientCommandsInQuota = 0; + } + + m_PlayerLimitData[player].numClientCommandsInQuota++; + if (m_PlayerLimitData[player].numClientCommandsInQuota > + CVar_sv_quota_stringcmdspersecond->GetInt()) + { + // too many stringcmds, dc player + return false; + } + } + + return true; +} + +bool ServerLimitsManager::CheckChatLimits(R2::CBasePlayer* player) +{ + if (Tier0::Plat_FloatTime() - m_PlayerLimitData[player].lastSayTextLimitStart >= 1.0) + { + m_PlayerLimitData[player].lastSayTextLimitStart = Tier0::Plat_FloatTime(); + m_PlayerLimitData[player].sayTextLimitCount = 0; + } + + if (m_PlayerLimitData[player].sayTextLimitCount >= Cvar_sv_max_chat_messages_per_sec->GetInt()) + return false; + + m_PlayerLimitData[player].sayTextLimitCount++; + return true; +} + +AUTOHOOK(CNetChan__ProcessMessages, engine.dll + 0x2140A0, char, __fastcall, (void* self, void* buf)) +{ + enum eNetChanLimitMode + { + NETCHANLIMIT_WARN, + NETCHANLIMIT_KICK + }; + + double startTime = Tier0::Plat_FloatTime(); + char ret = CNetChan__ProcessMessages(self, buf); + + // check processing limits, unless we're in a level transition + if (R2::g_pHostState->m_iCurrentState == R2::HostState_t::HS_RUN && Tier0::ThreadInServerFrameThread()) + { + // player that sent the message + R2::CBasePlayer* sender = *(R2::CBasePlayer**)((char*)self + 368); + + // if no sender, return + // relatively certain this is fine? + if (!sender || !g_pServerLimits->m_PlayerLimitData.count(sender)) + return ret; + + // reset every second + if (startTime - g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart >= 1.0 || + g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart == -1.0) + { + g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart = startTime; + g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime = 0.0; + } + g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime += + (Tier0::Plat_FloatTime() * 1000) - (startTime * 1000); + + if (g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime >= + g_pServerLimits->Cvar_net_chan_limit_msec_per_sec->GetInt()) + { + spdlog::warn( + "Client {} hit netchan processing limit with {}ms of processing time this second (max is {})", + (char*)sender + 0x16, + g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime, + g_pServerLimits->Cvar_net_chan_limit_msec_per_sec->GetInt()); + + // never kick local player + if (g_pServerLimits->Cvar_net_chan_limit_mode->GetInt() != NETCHANLIMIT_WARN && + strcmp(R2::g_pLocalPlayerUserID, sender->m_UID)) + { + R2::CBaseClient__Disconnect(sender, 1, "Exceeded net channel processing limit"); + return false; + } + } + } + + return ret; +} + +AUTOHOOK(ProcessConnectionlessPacket, engine.dll + 0x117800, bool, , (void* a1, R2::netpacket_t* packet)) +{ + if (packet->adr.type == R2::NA_IP && + (!(packet->data[4] == 'N' && Cvar_net_datablock_enabled->GetBool()) || !Cvar_net_datablock_enabled->GetBool())) + { + // bad lookup: optimise later tm + UnconnectedPlayerLimitData* sendData = nullptr; + for (UnconnectedPlayerLimitData& foundSendData : g_pServerLimits->m_UnconnectedPlayerLimitData) + { + if (!memcmp(packet->adr.ip, foundSendData.ip, 16)) + { + sendData = &foundSendData; + break; + } + } + + if (!sendData) + { + sendData = &g_pServerLimits->m_UnconnectedPlayerLimitData.emplace_back(); + memcpy(sendData->ip, packet->adr.ip, 16); + } + + if (Tier0::Plat_FloatTime() < sendData->timeoutEnd) + return false; + + if (Tier0::Plat_FloatTime() - sendData->lastQuotaStart >= 1.0) + { + sendData->lastQuotaStart = Tier0::Plat_FloatTime(); + sendData->packetCount = 0; + } + + sendData->packetCount++; + + if (sendData->packetCount >= g_pServerLimits->Cvar_sv_querylimit_per_sec->GetInt()) + { + spdlog::warn( + "Client went over connectionless ratelimit of {} per sec with packet of type {}", + g_pServerLimits->Cvar_sv_querylimit_per_sec->GetInt(), + packet->data[4]); + + // timeout for a minute + sendData->timeoutEnd = Tier0::Plat_FloatTime() + 60.0; + return false; + } + } + + return ProcessConnectionlessPacket(a1, packet); +} + +ON_DLL_LOAD_RELIESON("engine.dll", ServerLimits, ConVar, (HMODULE baseAddress)) +{ + AUTOHOOK_DISPATCH() + + g_pServerLimits = new ServerLimitsManager; + + g_pServerLimits->CVar_sv_quota_stringcmdspersecond = new ConVar( + "sv_quota_stringcmdspersecond", + "60", + FCVAR_GAMEDLL, + "How many string commands per second clients are allowed to submit, 0 to disallow all string commands, -1 to disable"); + g_pServerLimits->Cvar_net_chan_limit_mode = + new ConVar("net_chan_limit_mode", "0", FCVAR_GAMEDLL, "The mode for netchan processing limits: 0 = warn, 1 = kick"); + g_pServerLimits->Cvar_net_chan_limit_msec_per_sec = new ConVar( + "net_chan_limit_msec_per_sec", + "0", + FCVAR_GAMEDLL, + "Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget"); + g_pServerLimits->Cvar_sv_querylimit_per_sec = new ConVar("sv_querylimit_per_sec", "15", FCVAR_GAMEDLL, ""); + g_pServerLimits->Cvar_sv_max_chat_messages_per_sec = new ConVar("sv_max_chat_messages_per_sec", "5", FCVAR_GAMEDLL, ""); + + Cvar_net_datablock_enabled = R2::g_pCVar->FindVar("net_datablock_enabled"); +} \ No newline at end of file diff --git a/NorthstarDLL/limits.h b/NorthstarDLL/limits.h new file mode 100644 index 00000000..2303ba5e --- /dev/null +++ b/NorthstarDLL/limits.h @@ -0,0 +1,44 @@ +#pragma once +#include "r2server.h" +#include "convar.h" +#include + +struct PlayerLimitData +{ + double lastClientCommandQuotaStart = -1.0; + int numClientCommandsInQuota = 0; + + double lastNetChanProcessingLimitStart = -1.0; + double netChanProcessingLimitTime = 0.0; + + double lastSayTextLimitStart = -1.0; + int sayTextLimitCount = 0; +}; + +struct UnconnectedPlayerLimitData +{ + char ip[16]; + double lastQuotaStart = 0.0; + int packetCount = 0; + double timeoutEnd = -1.0; +}; + +class ServerLimitsManager +{ + public: + ConVar* CVar_sv_quota_stringcmdspersecond; + ConVar* Cvar_net_chan_limit_mode; + ConVar* Cvar_net_chan_limit_msec_per_sec; + ConVar* Cvar_sv_querylimit_per_sec; + ConVar* Cvar_sv_max_chat_messages_per_sec; + + std::unordered_map m_PlayerLimitData; + std::vector m_UnconnectedPlayerLimitData; + + public: + void AddPlayer(R2::CBasePlayer* player); + bool CheckStringCommandLimits(R2::CBasePlayer* player); + bool CheckChatLimits(R2::CBasePlayer* player); +}; + +extern ServerLimitsManager* g_pServerLimits; diff --git a/NorthstarDLL/localchatwriter.cpp b/NorthstarDLL/localchatwriter.cpp index 42c72373..7f5e2a0a 100644 --- a/NorthstarDLL/localchatwriter.cpp +++ b/NorthstarDLL/localchatwriter.cpp @@ -37,12 +37,12 @@ class vgui_BaseRichText_vtable void(__fastcall* SetVerticalScrollbar)(vgui_BaseRichText* self, bool state); void(__fastcall* SetMaximumCharCount)(vgui_BaseRichText* self, int maxChars); - void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, vgui_Color col); + void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, Color col); void(__fastcall* InsertIndentChange)(vgui_BaseRichText* self, int pixelsIndent); void(__fastcall* InsertClickableTextStart)(vgui_BaseRichText* self, const char* pchClickAction); void(__fastcall* InsertClickableTextEnd)(vgui_BaseRichText* self); void(__fastcall* InsertPossibleURLString)( - vgui_BaseRichText* self, const char* text, vgui_Color URLTextColor, vgui_Color normalTextColor); + vgui_BaseRichText* self, const char* text, Color URLTextColor, Color normalTextColor); void(__fastcall* InsertFade)(vgui_BaseRichText* self, float flSustain, float flLength); void(__fastcall* ResetAllFades)(vgui_BaseRichText* self, bool bHold, bool bOnlyExpired, float flNewSustain); void(__fastcall* SetToFullHeight)(vgui_BaseRichText* self); @@ -81,25 +81,25 @@ LocalChatWriter::SwatchColor swatchColors[4] = { LocalChatWriter::NetworkNameColor, }; -vgui_Color darkColors[8] = { - vgui_Color {0, 0, 0, 255}, - vgui_Color {205, 49, 49, 255}, - vgui_Color {13, 188, 121, 255}, - vgui_Color {229, 229, 16, 255}, - vgui_Color {36, 114, 200, 255}, - vgui_Color {188, 63, 188, 255}, - vgui_Color {17, 168, 205, 255}, - vgui_Color {229, 229, 229, 255}}; - -vgui_Color lightColors[8] = { - vgui_Color {102, 102, 102, 255}, - vgui_Color {241, 76, 76, 255}, - vgui_Color {35, 209, 139, 255}, - vgui_Color {245, 245, 67, 255}, - vgui_Color {59, 142, 234, 255}, - vgui_Color {214, 112, 214, 255}, - vgui_Color {41, 184, 219, 255}, - vgui_Color {255, 255, 255, 255}}; +Color darkColors[8] = { + Color {0, 0, 0, 255}, + Color {205, 49, 49, 255}, + Color {13, 188, 121, 255}, + Color {229, 229, 16, 255}, + Color {36, 114, 200, 255}, + Color {188, 63, 188, 255}, + Color {17, 168, 205, 255}, + Color {229, 229, 229, 255}}; + +Color lightColors[8] = { + Color {102, 102, 102, 255}, + Color {241, 76, 76, 255}, + Color {35, 209, 139, 255}, + Color {245, 245, 67, 255}, + Color {59, 142, 234, 255}, + Color {214, 112, 214, 255}, + Color {41, 184, 219, 255}, + Color {255, 255, 255, 255}}; class AnsiEscapeParser { @@ -144,7 +144,7 @@ class AnsiEscapeParser LocalChatWriter* m_writer; Next m_next = Next::ControlType; - vgui_Color m_expandedColor {0, 0, 0, 0}; + Color m_expandedColor {0, 0, 0, 0}; Next HandleControlType(unsigned long val) { @@ -190,7 +190,7 @@ class AnsiEscapeParser // Next values are r,g,b if (val == 2) { - m_expandedColor = {0, 0, 0, 255}; + m_expandedColor.SetColor(0, 0, 0, 255); return Next::ForegroundR; } // Next value is 8-bit swatch color @@ -220,12 +220,12 @@ class AnsiEscapeParser unsigned char green = ((code - blue) / 6) % 6; unsigned char red = (code - blue - (green * 6)) / 36; m_writer->InsertColorChange( - vgui_Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255}); + Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255}); } else if (val < UCHAR_MAX) { unsigned char brightness = (val - 232) * 10 + 8; - m_writer->InsertColorChange(vgui_Color {brightness, brightness, brightness, 255}); + m_writer->InsertColorChange(Color {brightness, brightness, brightness, 255}); } return Next::ControlType; @@ -236,7 +236,7 @@ class AnsiEscapeParser if (val >= UCHAR_MAX) return Next::ControlType; - m_expandedColor.r = (unsigned char)val; + m_expandedColor[0] = (unsigned char)val; return Next::ForegroundG; } @@ -245,7 +245,7 @@ class AnsiEscapeParser if (val >= UCHAR_MAX) return Next::ControlType; - m_expandedColor.g = (unsigned char)val; + m_expandedColor[1] = (unsigned char)val; return Next::ForegroundB; } @@ -254,7 +254,7 @@ class AnsiEscapeParser if (val >= UCHAR_MAX) return Next::ControlType; - m_expandedColor.b = (unsigned char)val; + m_expandedColor[2] = (unsigned char)val; m_writer->InsertColorChange(m_expandedColor); return Next::ControlType; } @@ -320,6 +320,8 @@ void LocalChatWriter::InsertChar(wchar_t ch) void LocalChatWriter::InsertText(const char* str) { + spdlog::info(str); + WCHAR messageUnicode[288]; ConvertANSIToUnicode(str, -1, messageUnicode, 274); @@ -347,7 +349,7 @@ void LocalChatWriter::InsertText(const wchar_t* str) InsertDefaultFade(); } -void LocalChatWriter::InsertColorChange(vgui_Color color) +void LocalChatWriter::InsertColorChange(Color color) { for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next) { @@ -358,20 +360,25 @@ void LocalChatWriter::InsertColorChange(vgui_Color color) } } -static vgui_Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor) +static Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor) { switch (swatchColor) { case LocalChatWriter::MainTextColor: return hud->m_mainTextColor; + case LocalChatWriter::SameTeamNameColor: return hud->m_sameTeamColor; + case LocalChatWriter::EnemyTeamNameColor: return hud->m_enemyTeamColor; + case LocalChatWriter::NetworkNameColor: return hud->m_networkNameColor; + } - return vgui_Color {0, 0, 0, 0}; + + return Color(0, 0, 0, 0); } void LocalChatWriter::InsertSwatchColorChange(SwatchColor swatchColor) diff --git a/NorthstarDLL/localchatwriter.h b/NorthstarDLL/localchatwriter.h index e52e4c80..b9183f63 100644 --- a/NorthstarDLL/localchatwriter.h +++ b/NorthstarDLL/localchatwriter.h @@ -1,13 +1,6 @@ #pragma once #include "pch.h" - -struct vgui_Color -{ - unsigned char r; - unsigned char g; - unsigned char b; - unsigned char a; -}; +#include "color.h" class vgui_BaseRichText; @@ -18,10 +11,10 @@ class CHudChat char unknown1[720]; - vgui_Color m_sameTeamColor; - vgui_Color m_enemyTeamColor; - vgui_Color m_mainTextColor; - vgui_Color m_networkNameColor; + Color m_sameTeamColor; + Color m_enemyTeamColor; + Color m_mainTextColor; + Color m_networkNameColor; char unknown2[12]; @@ -61,7 +54,7 @@ class LocalChatWriter void InsertChar(wchar_t ch); void InsertText(const char* str); void InsertText(const wchar_t* str); - void InsertColorChange(vgui_Color color); + void InsertColorChange(Color color); void InsertSwatchColorChange(SwatchColor color); private: diff --git a/NorthstarDLL/masterserver.cpp b/NorthstarDLL/masterserver.cpp index 37a67c21..4d5f371b 100644 --- a/NorthstarDLL/masterserver.cpp +++ b/NorthstarDLL/masterserver.cpp @@ -32,7 +32,7 @@ ConVar* Cvar_ns_curl_log_enable; ConVar* Cvar_hostname; ConVar* Cvar_hostport; -MasterServerManager* g_MasterServerManager; +MasterServerManager* g_pMasterServerManager; // Convert a hex digit char to integer. inline int hctod(char c) @@ -578,7 +578,7 @@ void MasterServerManager::AuthenticateWithOwnServer(const char* uid, const char* goto REQUEST_END_CLEANUP; } - AuthData newAuthData {}; + RemoteAuthData newAuthData {}; strncpy(newAuthData.uid, authInfoJson["id"].GetString(), sizeof(newAuthData.uid)); newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; @@ -600,9 +600,9 @@ void MasterServerManager::AuthenticateWithOwnServer(const char* uid, const char* newAuthData.pdata[i++] = static_cast(byte.GetUint()); } - std::lock_guard guard(g_pServerAuthenticationManager->m_authDataMutex); - g_pServerAuthenticationManager->m_authData.clear(); - g_pServerAuthenticationManager->m_authData.insert(std::make_pair(authInfoJson["authToken"].GetString(), newAuthData)); + std::lock_guard guard(g_pServerAuthentication->m_AuthDataMutex); + g_pServerAuthentication->m_RemoteAuthenticationData.clear(); + g_pServerAuthentication->m_RemoteAuthenticationData.insert(std::make_pair(authInfoJson["authToken"].GetString(), newAuthData)); m_bSuccessfullyAuthenticatedWithGameServer = true; } @@ -908,8 +908,8 @@ void MasterServerManager::AddSelfToServerList( // send all registration info so we have all necessary info to reregister our server if masterserver goes down, // without a restart this isn't threadsafe :terror: { - char* escapedNameNew = curl_easy_escape(curl, g_MasterServerManager->m_sUnicodeServerName.c_str(), NULL); - char* escapedDescNew = curl_easy_escape(curl, g_MasterServerManager->m_sUnicodeServerDesc.c_str(), NULL); + char* escapedNameNew = curl_easy_escape(curl, g_pMasterServerManager->m_sUnicodeServerName.c_str(), NULL); + char* escapedDescNew = curl_easy_escape(curl, g_pMasterServerManager->m_sUnicodeServerDesc.c_str(), NULL); char* escapedMapNew = curl_easy_escape(curl, R2::g_pHostState->m_levelName, NULL); char* escapedPlaylistNew = curl_easy_escape(curl, R2::GetCurrentPlaylistName(), NULL); char* escapedPasswordNew = curl_easy_escape(curl, Cvar_ns_server_password->GetString(), NULL); @@ -929,12 +929,12 @@ void MasterServerManager::AddSelfToServerList( Cvar_ns_masterserver_hostname->GetString(), m_sOwnServerId, Cvar_hostport->GetInt(), - Cvar_ns_player_auth_port->GetInt(), + g_pServerAuthentication->Cvar_ns_player_auth_port->GetInt(), escapedNameNew, escapedDescNew, escapedMapNew, escapedPlaylistNew, - g_pServerAuthenticationManager->m_additionalPlayerData.size(), + g_pServerAuthentication->m_PlayerAuthenticationData.size(), maxPlayers, escapedPasswordNew) .c_str()); @@ -1194,22 +1194,22 @@ void MasterServerManager::RemoveSelfFromServerList() void ConCommand_ns_fetchservers(const CCommand& args) { - g_MasterServerManager->RequestServerList(); + g_pMasterServerManager->RequestServerList(); } MasterServerManager::MasterServerManager() : m_pendingConnectionInfo {}, m_sOwnServerId {""}, m_sOwnClientAuthToken {""} {} ON_DLL_LOAD_RELIESON("engine.dll", MasterServer, ConCommand, (HMODULE baseAddress)) { - g_MasterServerManager = new MasterServerManager; + g_pMasterServerManager = new MasterServerManager; Cvar_ns_masterserver_hostname = new ConVar("ns_masterserver_hostname", "127.0.0.1", FCVAR_NONE, ""); Cvar_ns_server_name = new ConVar("ns_server_name", "Unnamed Northstar Server", FCVAR_GAMEDLL, "This server's description", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { - g_MasterServerManager->m_sUnicodeServerName = unescape_unicode(Cvar_ns_server_name->GetString()); + g_pMasterServerManager->m_sUnicodeServerName = unescape_unicode(Cvar_ns_server_name->GetString()); }); Cvar_ns_server_desc = new ConVar("ns_server_desc", "Default server description", FCVAR_GAMEDLL, "This server's name", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { - g_MasterServerManager->m_sUnicodeServerName = unescape_unicode(Cvar_ns_server_desc->GetString()); + g_pMasterServerManager->m_sUnicodeServerDesc = unescape_unicode(Cvar_ns_server_desc->GetString()); }); Cvar_ns_server_password = new ConVar("ns_server_password", "", FCVAR_GAMEDLL, "This server's password"); diff --git a/NorthstarDLL/masterserver.h b/NorthstarDLL/masterserver.h index bb2348ca..ae5167df 100644 --- a/NorthstarDLL/masterserver.h +++ b/NorthstarDLL/masterserver.h @@ -148,6 +148,6 @@ class MasterServerManager }; std::string unescape_unicode(const std::string& str); -extern MasterServerManager* g_MasterServerManager; +extern MasterServerManager* g_pMasterServerManager; extern ConVar* Cvar_ns_masterserver_hostname; extern ConVar* Cvar_ns_server_password; diff --git a/NorthstarDLL/misccommands.cpp b/NorthstarDLL/misccommands.cpp index 55c0a147..dd1b9d65 100644 --- a/NorthstarDLL/misccommands.cpp +++ b/NorthstarDLL/misccommands.cpp @@ -21,22 +21,22 @@ void ConCommand_force_newgame(const CCommand& arg) void ConCommand_ns_start_reauth_and_leave_to_lobby(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(R2::g_pLocalPlayerUserID, g_MasterServerManager->m_sOwnClientAuthToken); + g_pMasterServerManager->m_bNewgameAfterSelfAuth = true; + g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken); } void ConCommand_ns_end_reauth_and_leave_to_lobby(const CCommand& arg) { R2::Cbuf_AddText( R2::Cbuf_GetCurrentPlayer(), - fmt::format("serverfilter {}", g_pServerAuthenticationManager->m_authData.begin()->first).c_str(), + fmt::format("serverfilter {}", g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first).c_str(), R2::cmd_source_t::kCommandSrcCode); R2::Cbuf_Execute(); // weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this if (g_pClientSquirrel->sqvm) { - g_pServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true; + g_pServerAuthentication->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? diff --git a/NorthstarDLL/miscserverfixes.cpp b/NorthstarDLL/miscserverfixes.cpp index b5eebac8..926779b7 100644 --- a/NorthstarDLL/miscserverfixes.cpp +++ b/NorthstarDLL/miscserverfixes.cpp @@ -8,8 +8,4 @@ ON_DLL_LOAD("server.dll", MiscServerFixes, (HMODULE baseAddress)) // nop out call to VGUI shutdown since it crashes the game when quitting from the console NSMem::NOP(ba + 0x154A96, 5); - - // ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue) - // this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches - NSMem::BytePatch(ba + 0x153920, "C3"); } \ No newline at end of file diff --git a/NorthstarDLL/miscserverscript.cpp b/NorthstarDLL/miscserverscript.cpp index 25a0cd1a..12ab5708 100644 --- a/NorthstarDLL/miscserverscript.cpp +++ b/NorthstarDLL/miscserverscript.cpp @@ -1,44 +1,33 @@ #include "pch.h" -#include "miscserverscript.h" #include "squirrel.h" #include "masterserver.h" #include "serverauthentication.h" #include "r2client.h" +#include "r2server.h" -// annoying helper function because i can't figure out getting players or entities from sqvm rn -// wish i didn't have to do it like this, but here we are -void* GetPlayerByIndex(int playerIndex) -{ - const int PLAYER_ARRAY_OFFSET = 0x12A53F90; - const int PLAYER_SIZE = 0x2D728; - - void* playerArrayBase = (char*)GetModuleHandleA("engine.dll") + PLAYER_ARRAY_OFFSET; - void* player = (char*)playerArrayBase + (playerIndex * PLAYER_SIZE); - - return player; -} +#include // void function NSEarlyWritePlayerIndexPersistenceForLeave( int playerIndex ) SQRESULT SQ_EarlyWritePlayerIndexPersistenceForLeave(void* sqvm) { int playerIndex = g_pServerSquirrel->getinteger(sqvm, 1); - void* player = GetPlayerByIndex(playerIndex); + R2::CBasePlayer* player = R2::UTIL_PlayerByIndex(playerIndex); - if (!g_pServerAuthenticationManager->m_additionalPlayerData.count(player)) + if (!g_pServerAuthentication->m_PlayerAuthenticationData.count(player)) { g_pServerSquirrel->raiseerror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str()); return SQRESULT_ERROR; } - g_pServerAuthenticationManager->m_additionalPlayerData[player].needPersistenceWriteOnLeave = false; - g_pServerAuthenticationManager->WritePersistentData(player); + g_pServerAuthentication->m_PlayerAuthenticationData[player].needPersistenceWriteOnLeave = false; + g_pServerAuthentication->WritePersistentData(player); return SQRESULT_NULL; } // bool function NSIsWritingPlayerPersistence() SQRESULT SQ_IsWritingPlayerPersistence(void* sqvm) { - g_pServerSquirrel->pushbool(sqvm, g_MasterServerManager->m_bSavingPersistentData); + g_pServerSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bSavingPersistentData); return SQRESULT_NOTNULL; } @@ -46,14 +35,14 @@ SQRESULT SQ_IsWritingPlayerPersistence(void* sqvm) SQRESULT SQ_IsPlayerIndexLocalPlayer(void* sqvm) { int playerIndex = g_pServerSquirrel->getinteger(sqvm, 1); - void* player = GetPlayerByIndex(playerIndex); - if (!g_pServerAuthenticationManager->m_additionalPlayerData.count(player)) + R2::CBasePlayer* player = R2::UTIL_PlayerByIndex(playerIndex); + if (!g_pServerAuthentication->m_PlayerAuthenticationData.count(player)) { g_pServerSquirrel->raiseerror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str()); return SQRESULT_ERROR; } - g_pServerSquirrel->pushbool(sqvm, !strcmp(R2::g_pLocalPlayerUserID, (char*)player + 0xF500)); + g_pServerSquirrel->pushbool(sqvm, !strcmp(R2::g_pLocalPlayerUserID, player->m_UID)); return SQRESULT_NOTNULL; } diff --git a/NorthstarDLL/miscserverscript.h b/NorthstarDLL/miscserverscript.h deleted file mode 100644 index 00b6a0d1..00000000 --- a/NorthstarDLL/miscserverscript.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -void* GetPlayerByIndex(int playerIndex); diff --git a/NorthstarDLL/modmanager.cpp b/NorthstarDLL/modmanager.cpp index 3fa6eb14..9ed3ea6f 100644 --- a/NorthstarDLL/modmanager.cpp +++ b/NorthstarDLL/modmanager.cpp @@ -539,7 +539,7 @@ void ModManager::LoadMods() buffer.Clear(); rapidjson::Writer writer(buffer); modinfoDoc.Accept(writer); - g_MasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString()); + g_pMasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString()); m_bHasLoadedMods = true; } diff --git a/NorthstarDLL/playlist.cpp b/NorthstarDLL/playlist.cpp index c6bee74d..0eb0e1d2 100644 --- a/NorthstarDLL/playlist.cpp +++ b/NorthstarDLL/playlist.cpp @@ -4,6 +4,7 @@ #include "concommand.h" #include "convar.h" #include "squirrel.h" +#include "hoststate.h" AUTOHOOK_INIT() @@ -21,9 +22,8 @@ ConVar* Cvar_ns_use_clc_SetPlaylistVarOverride; AUTOHOOK(clc_SetPlaylistVarOverride__Process, engine.dll + 0x222180, char,, (void* a1, void* a2)) { - // the private_match playlist is the only situation where there should be any legitimate sending of this netmessage - // todo: check map == mp_lobby here too - if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(R2::GetCurrentPlaylistName(), "private_match")) + // the private_match playlist on mp_lobby is the only situation where there should be any legitimate sending of this netmessage + if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(R2::GetCurrentPlaylistName(), "private_match") || strcmp(R2::g_pHostState->m_levelName, "mp_lobby")) return 1; return clc_SetPlaylistVarOverride__Process(a1, a2); diff --git a/NorthstarDLL/plugins.cpp b/NorthstarDLL/plugins.cpp index 2d52d4ab..a6087efe 100644 --- a/NorthstarDLL/plugins.cpp +++ b/NorthstarDLL/plugins.cpp @@ -193,7 +193,7 @@ SQRESULT SQ_SetConnected(void* sqvm) SQRESULT SQ_UpdateListenServer(void* sqvm) { AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = g_MasterServerManager->m_sOwnServerId; + serverInfo.id = g_pMasterServerManager->m_sOwnServerId; serverInfo.password = Cvar_ns_server_password->GetString(); ReleaseSRWLockExclusive(&serverInfoLock); return SQRESULT_NOTNULL; diff --git a/NorthstarDLL/printmaps.cpp b/NorthstarDLL/printmaps.cpp index af3ea275..bd89946b 100644 --- a/NorthstarDLL/printmaps.cpp +++ b/NorthstarDLL/printmaps.cpp @@ -128,7 +128,7 @@ int, __fastcall, (const char const* cmdname, const char const* partial, char com const int queryLength = strlen(query); int numMaps = 0; - for (int i = 0; i < COMMAND_COMPLETION_MAXITEMS && i < vMapList.size(); i++) + for (int i = 0; i < vMapList.size() && numMaps < COMMAND_COMPLETION_MAXITEMS; i++) { if (!strncmp(query, vMapList[i].name.c_str(), queryLength)) { diff --git a/NorthstarDLL/r2engine.cpp b/NorthstarDLL/r2engine.cpp index 8d4390e6..5d063141 100644 --- a/NorthstarDLL/r2engine.cpp +++ b/NorthstarDLL/r2engine.cpp @@ -11,6 +11,8 @@ namespace R2 Cbuf_ExecuteType Cbuf_Execute; CEngine* g_pEngine; + + void (*CBaseClient__Disconnect)(void* self, uint32_t unknownButAlways1, const char* reason, ...); } // namespace R2 ON_DLL_LOAD("engine.dll", R2Engine, (HMODULE baseAddress)) @@ -20,4 +22,6 @@ ON_DLL_LOAD("engine.dll", R2Engine, (HMODULE baseAddress)) Cbuf_Execute = (Cbuf_ExecuteType)((char*)baseAddress + 0x1204B0); g_pEngine = *(CEngine**)((char*)baseAddress + 0x7D70C8); + + CBaseClient__Disconnect = (void (*)(void*, uint32_t, const char*, ...))((char*)baseAddress + 0x1012C0); } \ No newline at end of file diff --git a/NorthstarDLL/r2engine.h b/NorthstarDLL/r2engine.h index c31adfd1..b3d118de 100644 --- a/NorthstarDLL/r2engine.h +++ b/NorthstarDLL/r2engine.h @@ -99,4 +99,45 @@ namespace R2 }; extern CEngine* g_pEngine; + + extern void (*CBaseClient__Disconnect)(void* self, uint32_t unknownButAlways1, const char* reason, ...); + + #pragma once + typedef enum + { + NA_NULL = 0, + NA_LOOPBACK, + NA_IP, + } netadrtype_t; + + #pragma pack(push, 1) + typedef struct netadr_s + { + netadrtype_t type; + unsigned char ip[16]; // IPv6 + // IPv4's 127.0.0.1 is [::ffff:127.0.0.1], that is: + // 00 00 00 00 00 00 00 00 00 00 FF FF 7F 00 00 01 + unsigned short port; + } netadr_t; + #pragma pack(pop) + + #pragma pack(push, 1) + typedef struct netpacket_s + { + netadr_t adr; // sender address + // int source; // received source + char unk[10]; + double received_time; + unsigned char* data; // pointer to raw packet data + void* message; // easy bitbuf data access // 'inpacket.message' etc etc (pointer) + char unk2[16]; + int size; + + // bf_read message; // easy bitbuf data access // 'inpacket.message' etc etc (pointer) + // int size; // size in bytes + // int wiresize; // size in bytes before decompression + // bool stream; // was send as stream + // struct netpacket_s* pNext; // for internal use, should be NULL in public + } netpacket_t; + #pragma pack(pop) } // namespace R2 diff --git a/NorthstarDLL/r2server.cpp b/NorthstarDLL/r2server.cpp index 7a6ebe90..848104d4 100644 --- a/NorthstarDLL/r2server.cpp +++ b/NorthstarDLL/r2server.cpp @@ -7,7 +7,8 @@ using namespace R2; namespace R2 { server_state_t* g_pServerState; - Server_GetEntityByIndexType Server_GetEntityByIndex; + void* (*Server_GetEntityByIndex)(int index); + CBasePlayer*(__fastcall* UTIL_PlayerByIndex)(int playerIndex); } // namespace R2 ON_DLL_LOAD("engine.dll", R2EngineServer, (HMODULE baseAddress)) @@ -17,5 +18,6 @@ ON_DLL_LOAD("engine.dll", R2EngineServer, (HMODULE baseAddress)) ON_DLL_LOAD("server.dll", R2GameServer, (HMODULE baseAddress)) { - Server_GetEntityByIndex = (Server_GetEntityByIndexType)((char*)baseAddress + 0xFB820); + Server_GetEntityByIndex = (void*(*)(int))((char*)baseAddress + 0xFB820); + UTIL_PlayerByIndex = (CBasePlayer*(__fastcall*)(int))((char*)baseAddress + 0x26AA10); } \ No newline at end of file diff --git a/NorthstarDLL/r2server.h b/NorthstarDLL/r2server.h index 816adc82..c032d722 100644 --- a/NorthstarDLL/r2server.h +++ b/NorthstarDLL/r2server.h @@ -14,6 +14,44 @@ namespace R2 extern server_state_t* g_pServerState; // server entity stuff - typedef void* (*Server_GetEntityByIndexType)(int index); - extern Server_GetEntityByIndexType Server_GetEntityByIndex; + extern void* (*Server_GetEntityByIndex)(int index); + + const int PERSISTENCE_MAX_SIZE = 0xD000; + + enum class ePersistenceReady : char + { + NOT_READY, + READY = 3, + READY_LOCAL = 3, + READY_REMOTE + }; + + #pragma pack(push, 1) + struct CBasePlayer + { + char pad0[0x16]; + + // +0x16 + char m_Name[64]; + // +0x56 + + char pad1[0x44A]; + + // +0x4A0 + ePersistenceReady m_iPersistenceReady; + // +0x4A1 + + char pad2[0x59]; + + // +0x4FA + char m_PersistenceBuffer[PERSISTENCE_MAX_SIZE]; + + char pad3[0x2006]; + + // +0xF500 + char m_UID[32]; + }; + #pragma pack(pop) + + extern CBasePlayer*(__fastcall* UTIL_PlayerByIndex)(int playerIndex); } // namespace R2 \ No newline at end of file diff --git a/NorthstarDLL/scriptmainmenupromos.cpp b/NorthstarDLL/scriptmainmenupromos.cpp index c0fc42bb..5d86c603 100644 --- a/NorthstarDLL/scriptmainmenupromos.cpp +++ b/NorthstarDLL/scriptmainmenupromos.cpp @@ -26,100 +26,100 @@ enum eMainMenuPromoDataProperty // void function NSRequestCustomMainMenuPromos() SQRESULT SQ_RequestCustomMainMenuPromos(void* sqvm) { - g_MasterServerManager->RequestMainMenuPromos(); + g_pMasterServerManager->RequestMainMenuPromos(); return SQRESULT_NULL; } // bool function NSHasCustomMainMenuPromoData() SQRESULT SQ_HasCustomMainMenuPromoData(void* sqvm) { - g_pUISquirrel->pushbool(sqvm, g_MasterServerManager->m_bHasMainMenuPromoData); + g_pUISquirrel->pushbool(sqvm, g_pMasterServerManager->m_bHasMainMenuPromoData); return SQRESULT_NOTNULL; } // var function NSGetCustomMainMenuPromoData( int promoDataKey ) SQRESULT SQ_GetCustomMainMenuPromoData(void* sqvm) { - if (!g_MasterServerManager->m_bHasMainMenuPromoData) + if (!g_pMasterServerManager->m_bHasMainMenuPromoData) return SQRESULT_NULL; switch (g_pUISquirrel->getinteger(sqvm, 1)) { case eMainMenuPromoDataProperty::newInfoTitle1: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.newInfoTitle1.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle1.c_str()); break; } case eMainMenuPromoDataProperty::newInfoTitle2: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.newInfoTitle2.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle2.c_str()); break; } case eMainMenuPromoDataProperty::newInfoTitle3: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.newInfoTitle3.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle3.c_str()); break; } case eMainMenuPromoDataProperty::largeButtonTitle: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.largeButtonTitle.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonTitle.c_str()); break; } case eMainMenuPromoDataProperty::largeButtonText: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.largeButtonText.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonText.c_str()); break; } case eMainMenuPromoDataProperty::largeButtonUrl: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.largeButtonUrl.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonUrl.c_str()); break; } case eMainMenuPromoDataProperty::largeButtonImageIndex: { - g_pUISquirrel->pushinteger(sqvm, g_MasterServerManager->m_sMainMenuPromoData.largeButtonImageIndex); + g_pUISquirrel->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonImageIndex); break; } case eMainMenuPromoDataProperty::smallButton1Title: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton1Title.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1Title.c_str()); break; } case eMainMenuPromoDataProperty::smallButton1Url: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton1Url.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1Url.c_str()); break; } case eMainMenuPromoDataProperty::smallButton1ImageIndex: { - g_pUISquirrel->pushinteger(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton1ImageIndex); + g_pUISquirrel->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1ImageIndex); break; } case eMainMenuPromoDataProperty::smallButton2Title: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton2Title.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2Title.c_str()); break; } case eMainMenuPromoDataProperty::smallButton2Url: { - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton2Url.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2Url.c_str()); break; } case eMainMenuPromoDataProperty::smallButton2ImageIndex: { - g_pUISquirrel->pushinteger(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton2ImageIndex); + g_pUISquirrel->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2ImageIndex); break; } } diff --git a/NorthstarDLL/scriptserverbrowser.cpp b/NorthstarDLL/scriptserverbrowser.cpp index a42e9b14..13f4b7e6 100644 --- a/NorthstarDLL/scriptserverbrowser.cpp +++ b/NorthstarDLL/scriptserverbrowser.cpp @@ -10,35 +10,35 @@ // bool function NSIsMasterServerAuthenticated() SQRESULT SQ_IsMasterServerAuthenticated(void* sqvm) { - g_pUISquirrel->pushbool(sqvm, g_MasterServerManager->m_bOriginAuthWithMasterServerDone); + g_pUISquirrel->pushbool(sqvm, g_pMasterServerManager->m_bOriginAuthWithMasterServerDone); return SQRESULT_NOTNULL; } // void function NSRequestServerList() SQRESULT SQ_RequestServerList(void* sqvm) { - g_MasterServerManager->RequestServerList(); + g_pMasterServerManager->RequestServerList(); return SQRESULT_NULL; } // bool function NSIsRequestingServerList() SQRESULT SQ_IsRequestingServerList(void* sqvm) { - g_pUISquirrel->pushbool(sqvm, g_MasterServerManager->m_bScriptRequestingServerList); + g_pUISquirrel->pushbool(sqvm, g_pMasterServerManager->m_bScriptRequestingServerList); return SQRESULT_NOTNULL; } // bool function NSMasterServerConnectionSuccessful() SQRESULT SQ_MasterServerConnectionSuccessful(void* sqvm) { - g_pUISquirrel->pushbool(sqvm, g_MasterServerManager->m_bSuccessfullyConnected); + g_pUISquirrel->pushbool(sqvm, g_pMasterServerManager->m_bSuccessfullyConnected); return SQRESULT_NOTNULL; } // int function NSGetServerCount() SQRESULT SQ_GetServerCount(void* sqvm) { - g_pUISquirrel->pushinteger(sqvm, g_MasterServerManager->m_vRemoteServers.size()); + g_pUISquirrel->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers.size()); return SQRESULT_NOTNULL; } @@ -47,19 +47,19 @@ SQRESULT SQ_GetServerName(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get name of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].name); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].name); return SQRESULT_NOTNULL; } @@ -68,19 +68,19 @@ SQRESULT SQ_GetServerDescription(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get description of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].description.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].description.c_str()); return SQRESULT_NOTNULL; } @@ -89,19 +89,19 @@ SQRESULT SQ_GetServerMap(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get map of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].map); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].map); return SQRESULT_NOTNULL; } @@ -110,19 +110,19 @@ SQRESULT SQ_GetServerPlaylist(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get playlist of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].playlist); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].playlist); return SQRESULT_NOTNULL; } @@ -131,19 +131,19 @@ SQRESULT SQ_GetServerPlayerCount(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get playercount of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushinteger(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].playerCount); + g_pUISquirrel->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].playerCount); return SQRESULT_NOTNULL; } @@ -152,19 +152,19 @@ SQRESULT SQ_GetServerMaxPlayerCount(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get max playercount of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushinteger(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].maxPlayers); + g_pUISquirrel->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].maxPlayers); return SQRESULT_NOTNULL; } @@ -173,19 +173,19 @@ SQRESULT SQ_GetServerID(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get id of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].id); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].id); return SQRESULT_NOTNULL; } @@ -194,19 +194,19 @@ SQRESULT SQ_ServerRequiresPassword(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get hasPassword of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushbool(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].requiresPassword); + g_pUISquirrel->pushbool(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiresPassword); return SQRESULT_NOTNULL; } @@ -215,19 +215,19 @@ SQRESULT SQ_GetServerRequiredModsCount(void* sqvm) { SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get required mods count of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushinteger(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()); + g_pUISquirrel->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()); return SQRESULT_NOTNULL; } @@ -237,31 +237,31 @@ SQRESULT SQ_GetServerRequiredModName(void* sqvm) SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); SQInteger modIndex = g_pUISquirrel->getinteger(sqvm, 2); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get hasPassword of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - if (modIndex >= g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) + if (modIndex >= g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get required mod name of mod index {} when only {} mod are available", modIndex, - g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) + g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Name.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Name.c_str()); return SQRESULT_NOTNULL; } @@ -271,38 +271,38 @@ SQRESULT SQ_GetServerRequiredModVersion(void* sqvm) SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); SQInteger modIndex = g_pUISquirrel->getinteger(sqvm, 2); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get required mod version of server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } - if (modIndex >= g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) + if (modIndex >= g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to get required mod version of mod index {} when only {} mod are available", modIndex, - g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) + g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) .c_str()); return SQRESULT_ERROR; } - g_pUISquirrel->pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Version.c_str()); + g_pUISquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Version.c_str()); return SQRESULT_NOTNULL; } // void function NSClearRecievedServerList() SQRESULT SQ_ClearRecievedServerList(void* sqvm) { - g_MasterServerManager->ClearServerList(); + g_pMasterServerManager->ClearServerList(); return SQRESULT_NULL; } @@ -314,28 +314,28 @@ SQRESULT SQ_TryAuthWithServer(void* sqvm) SQInteger serverIndex = g_pUISquirrel->getinteger(sqvm, 1); const SQChar* password = g_pUISquirrel->getstring(sqvm, 2); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { g_pUISquirrel->raiseerror( sqvm, fmt::format( "Tried to auth with server index {} when only {} servers are available", serverIndex, - g_MasterServerManager->m_vRemoteServers.size()) + g_pMasterServerManager->m_vRemoteServers.size()) .c_str()); return SQRESULT_ERROR; } // send off persistent data first, don't worry about server/client stuff, since m_additionalPlayerData should only have entries when // we're a local server note: this seems like it could create a race condition, test later - for (auto& pair : g_pServerAuthenticationManager->m_additionalPlayerData) - g_pServerAuthenticationManager->WritePersistentData(pair.first); + for (auto& pair : g_pServerAuthentication->m_PlayerAuthenticationData) + g_pServerAuthentication->WritePersistentData(pair.first); // do auth - g_MasterServerManager->AuthenticateWithServer( + g_pMasterServerManager->AuthenticateWithServer( R2::g_pLocalPlayerUserID, - g_MasterServerManager->m_sOwnClientAuthToken, - g_MasterServerManager->m_vRemoteServers[serverIndex].id, + g_pMasterServerManager->m_sOwnClientAuthToken, + g_pMasterServerManager->m_vRemoteServers[serverIndex].id, (char*)password); return SQRESULT_NULL; @@ -344,28 +344,28 @@ SQRESULT SQ_TryAuthWithServer(void* sqvm) // bool function NSIsAuthenticatingWithServer() SQRESULT SQ_IsAuthComplete(void* sqvm) { - g_pUISquirrel->pushbool(sqvm, g_MasterServerManager->m_bScriptAuthenticatingWithGameServer); + g_pUISquirrel->pushbool(sqvm, g_pMasterServerManager->m_bScriptAuthenticatingWithGameServer); return SQRESULT_NOTNULL; } // bool function NSWasAuthSuccessful() SQRESULT SQ_WasAuthSuccessful(void* sqvm) { - g_pUISquirrel->pushbool(sqvm, g_MasterServerManager->m_bSuccessfullyAuthenticatedWithGameServer); + g_pUISquirrel->pushbool(sqvm, g_pMasterServerManager->m_bSuccessfullyAuthenticatedWithGameServer); return SQRESULT_NOTNULL; } // void function NSConnectToAuthedServer() SQRESULT SQ_ConnectToAuthedServer(void* sqvm) { - if (!g_MasterServerManager->m_bHasPendingConnectionInfo) + if (!g_pMasterServerManager->m_bHasPendingConnectionInfo) { g_pUISquirrel->raiseerror( sqvm, fmt::format("Tried to connect to authed server before any pending connection info was available").c_str()); return SQRESULT_ERROR; } - RemoteServerConnectionInfo info = g_MasterServerManager->m_pendingConnectionInfo; + RemoteServerConnectionInfo info = g_pMasterServerManager->m_pendingConnectionInfo; // set auth token, then try to connect // i'm honestly not entirely sure how silentconnect works regarding ports and encryption so using connect for now @@ -383,7 +383,7 @@ SQRESULT SQ_ConnectToAuthedServer(void* sqvm) .c_str(), R2::cmd_source_t::kCommandSrcCode); - g_MasterServerManager->m_bHasPendingConnectionInfo = false; + g_pMasterServerManager->m_bHasPendingConnectionInfo = false; return SQRESULT_NULL; } @@ -391,7 +391,7 @@ SQRESULT SQ_ConnectToAuthedServer(void* sqvm) SQRESULT SQ_TryAuthWithLocalServer(void* sqvm) { // do auth request - g_MasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_MasterServerManager->m_sOwnClientAuthToken); + g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken); return SQRESULT_NULL; } @@ -403,7 +403,7 @@ SQRESULT SQ_CompleteAuthWithLocalServer(void* sqvm) // note: this assumes we have no authdata other than our own R2::Cbuf_AddText( R2::Cbuf_GetCurrentPlayer(), - fmt::format("serverfilter {}", g_pServerAuthenticationManager->m_authData.begin()->first).c_str(), + fmt::format("serverfilter {}", g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first).c_str(), R2::cmd_source_t::kCommandSrcCode); return SQRESULT_NULL; diff --git a/NorthstarDLL/serverauthentication.cpp b/NorthstarDLL/serverauthentication.cpp index 7dc840a1..350999fb 100644 --- a/NorthstarDLL/serverauthentication.cpp +++ b/NorthstarDLL/serverauthentication.cpp @@ -1,11 +1,11 @@ #include "pch.h" #include "serverauthentication.h" +#include "limits.h" #include "cvar.h" #include "convar.h" #include "masterserver.h" #include "hoststate.h" #include "bansystem.h" -#include "miscserverscript.h" #include "concommand.h" #include "dedicated.h" #include "nsprefix.h" @@ -26,35 +26,17 @@ AUTOHOOK_INIT() const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; // global vars -ServerAuthenticationManager* g_pServerAuthenticationManager; - -ConVar* Cvar_ns_player_auth_port; -ConVar* Cvar_ns_erase_auth_info; -ConVar* CVar_ns_auth_allow_insecure; -ConVar* CVar_ns_auth_allow_insecure_write; -ConVar* CVar_sv_quota_stringcmdspersecond; -ConVar* Cvar_net_chan_limit_mode; -ConVar* Cvar_net_chan_limit_msec_per_sec; -ConVar* Cvar_sv_querylimit_per_sec; -ConVar* Cvar_sv_max_chat_messages_per_sec; - -ConVar* Cvar_net_datablock_enabled; - -// use the R2 namespace for game funcs -namespace R2 -{ - void (*CBaseClient__Disconnect)(void* self, uint32_t unknownButAlways1, const char* reason, ...); -} // namespace R2 +ServerAuthenticationManager* g_pServerAuthentication; void ServerAuthenticationManager::StartPlayerAuthServer() { - if (m_runningPlayerAuthThread) + if (m_bRunningPlayerAuthThread) { - spdlog::warn("ServerAuthenticationManager::StartPlayerAuthServer was called while m_runningPlayerAuthThread is true"); + spdlog::warn("ServerAuthenticationManager::StartPlayerAuthServer was called while m_bRunningPlayerAuthThread is true"); return; } - m_runningPlayerAuthThread = true; + m_bRunningPlayerAuthThread = true; // listen is a blocking call so thread this std::thread serverThread( @@ -62,19 +44,19 @@ void ServerAuthenticationManager::StartPlayerAuthServer() { // this is just a super basic way to verify that servers have ports open, masterserver will try to read this before ensuring // server is legit - m_playerAuthServer.Get( + m_PlayerAuthServer.Get( "/verify", [](const httplib::Request& request, httplib::Response& response) { response.set_content(AUTHSERVER_VERIFY_STRING, "text/plain"); }); - m_playerAuthServer.Post( + m_PlayerAuthServer.Post( "/authenticate_incoming_player", [this](const httplib::Request& request, httplib::Response& response) { - if (!request.has_param("id") || !request.has_param("authToken") || request.body.size() >= 65335 || + if (!request.has_param("id") || !request.has_param("authToken") || request.body.size() >= R2::PERSISTENCE_MAX_SIZE || !request.has_param("serverAuthToken") || strcmp( - g_MasterServerManager->m_sOwnServerAuthToken, + g_pMasterServerManager->m_sOwnServerAuthToken, request.get_param_value("serverAuthToken") .c_str())) { @@ -82,7 +64,7 @@ void ServerAuthenticationManager::StartPlayerAuthServer() return; } - AuthData newAuthData {}; + RemoteAuthData newAuthData {}; strncpy(newAuthData.uid, request.get_param_value("id").c_str(), sizeof(newAuthData.uid)); newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; @@ -93,13 +75,13 @@ void ServerAuthenticationManager::StartPlayerAuthServer() newAuthData.pdata = new char[newAuthData.pdataSize]; memcpy(newAuthData.pdata, request.body.c_str(), newAuthData.pdataSize); - std::lock_guard guard(m_authDataMutex); - m_authData.insert(std::make_pair(request.get_param_value("authToken"), newAuthData)); + std::lock_guard guard(m_AuthDataMutex); + m_RemoteAuthenticationData.insert(std::make_pair(request.get_param_value("authToken"), newAuthData)); response.set_content("{\"success\":true}", "application/json"); }); - m_playerAuthServer.listen("0.0.0.0", Cvar_ns_player_auth_port->GetInt()); + m_PlayerAuthServer.listen("0.0.0.0", Cvar_ns_player_auth_port->GetInt()); }); serverThread.detach(); @@ -107,27 +89,36 @@ void ServerAuthenticationManager::StartPlayerAuthServer() void ServerAuthenticationManager::StopPlayerAuthServer() { - if (!m_runningPlayerAuthThread) + if (!m_bRunningPlayerAuthThread) { - spdlog::warn("ServerAuthenticationManager::StopPlayerAuthServer was called while m_runningPlayerAuthThread is false"); + spdlog::warn("ServerAuthenticationManager::StopPlayerAuthServer was called while m_bRunningPlayerAuthThread is false"); return; } - m_runningPlayerAuthThread = false; - m_playerAuthServer.stop(); + m_bRunningPlayerAuthThread = false; + m_PlayerAuthServer.stop(); +} + +void ServerAuthenticationManager::AddPlayerData(R2::CBasePlayer* player, const char* pToken) +{ + PlayerAuthenticationData additionalData; + additionalData.pdataSize = m_RemoteAuthenticationData[pToken].pdataSize; + additionalData.usingLocalPdata = player->m_iPersistenceReady == R2::ePersistenceReady::READY_LOCAL; + + m_PlayerAuthenticationData.insert(std::make_pair(player, additionalData)); } -char* ServerAuthenticationManager::VerifyPlayerName(void* player, char* authToken, char* name) +void ServerAuthenticationManager::VerifyPlayerName(R2::CBasePlayer* player, char* authToken, char* name) { - std::lock_guard guard(m_authDataMutex); + std::lock_guard guard(m_AuthDataMutex); - if (!m_authData.empty() && m_authData.count(std::string(authToken))) + if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) { - AuthData authData = m_authData[authToken]; + RemoteAuthData authData = m_RemoteAuthenticationData[authToken]; bool nameAccepted = (!*authData.username || !strcmp(name, authData.username)); - if (!nameAccepted && g_MasterServerManager->m_bRequireClientAuth && !CVar_ns_auth_allow_insecure->GetInt()) + if (!nameAccepted && g_pMasterServerManager->m_bRequireClientAuth && !CVar_ns_auth_allow_insecure->GetInt()) { // limit name length to 64 characters just in case something changes, this technically shouldn't be needed given the master // server gets usernames from origin but we have it just in case @@ -135,25 +126,24 @@ char* ServerAuthenticationManager::VerifyPlayerName(void* player, char* authToke name[63] = 0; } } - return name; } -bool ServerAuthenticationManager::AuthenticatePlayer(void* player, int64_t uid, char* authToken) +bool ServerAuthenticationManager::AuthenticatePlayer(R2::CBasePlayer* player, uint64_t uid, char* authToken) { std::string strUid = std::to_string(uid); - std::lock_guard guard(m_authDataMutex); + std::lock_guard guard(m_AuthDataMutex); bool authFail = true; - if (!m_authData.empty() && m_authData.count(std::string(authToken))) + if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) { // use stored auth data - AuthData authData = m_authData[authToken]; + RemoteAuthData authData = m_RemoteAuthenticationData[authToken]; if (!strcmp(strUid.c_str(), authData.uid)) // connecting client's uid is the same as auth's uid { authFail = false; // uuid - strcpy((char*)player + 0xF500, strUid.c_str()); + strcpy(player->m_UID, strUid.c_str()); // reset from disk if we're doing that if (m_bForceReadLocalPlayerPersistenceFromDisk && !strcmp(authData.uid, R2::g_pLocalPlayerUserID)) @@ -168,33 +158,35 @@ bool ServerAuthenticationManager::AuthenticatePlayer(void* player, int64_t uid, pdataStream.seekg(0, pdataStream.beg); // copy pdata into buffer - pdataStream.read((char*)player + 0x4FA, length); + pdataStream.read(player->m_PersistenceBuffer, length); } else // fallback to remote pdata if no local default - memcpy((char*)player + 0x4FA, authData.pdata, authData.pdataSize); + memcpy(player->m_PersistenceBuffer, authData.pdata, authData.pdataSize); } else { // copy pdata into buffer - memcpy((char*)player + 0x4FA, authData.pdata, authData.pdataSize); + memcpy(player->m_PersistenceBuffer, authData.pdata, authData.pdataSize); } - // set persistent data as ready, we use 0x4 internally to mark the client as using remote persistence - *((char*)player + 0x4a0) = (char)0x4; + // set persistent data as ready + player->m_iPersistenceReady = R2::ePersistenceReady::READY_REMOTE; } } if (authFail) { - // set persistent data as ready, we use 0x3 internally to mark the client as using local persistence - *((char*)player + 0x4a0) = (char)0x3; + // set persistent data as ready + player->m_iPersistenceReady = R2::ePersistenceReady::READY_LOCAL; + return CVar_ns_auth_allow_insecure->GetBool(); + - if (!CVar_ns_auth_allow_insecure->GetBool()) // no auth data and insecure connections aren't allowed, so dc the client - return false; + // logic needs an overhaul as it is bad and shit + // todo: if need default pdata, we should populate in script using InitPersistentData() and just null it out here // insecure connections are allowed, try reading from disk // uuid - strcpy((char*)player + 0xF500, strUid.c_str()); + /* strcpy((char*)player + 0xF500, strUid.c_str()); // try reading pdata file for player std::string pdataPath = GetNorthstarPrefix() + "/playerdata_"; @@ -213,32 +205,32 @@ bool ServerAuthenticationManager::AuthenticatePlayer(void* player, int64_t uid, // copy pdata into buffer pdataStream.read((char*)player + 0x4FA, length); - pdataStream.close(); + pdataStream.close();*/ } return true; // auth successful, client stays on } -bool ServerAuthenticationManager::RemovePlayerAuthData(void* player) +bool ServerAuthenticationManager::RemovePlayerAuthData(R2::CBasePlayer* player) { - if (!Cvar_ns_erase_auth_info->GetBool()) + if (!Cvar_ns_erase_auth_info->GetBool()) // keep auth data forever return false; // hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect - if (m_bNeedLocalAuthForNewgame && !strcmp((char*)player + 0xF500, R2::g_pLocalPlayerUserID)) + if (m_bNeedLocalAuthForNewgame && !strcmp(player->m_UID, R2::g_pLocalPlayerUserID)) return false; // we don't have our auth token at this point, so lookup authdata by uid - for (auto& auth : m_authData) + for (auto& auth : m_RemoteAuthenticationData) { - if (!strcmp((char*)player + 0xF500, auth.second.uid)) + if (!strcmp(player->m_UID, auth.second.uid)) { // pretty sure this is fine, since we don't iterate after the erase // i think if we iterated after it'd be undefined behaviour tho - std::lock_guard guard(m_authDataMutex); + std::lock_guard guard(m_AuthDataMutex); delete[] auth.second.pdata; - m_authData.erase(auth.first); + m_RemoteAuthenticationData.erase(auth.first); return true; } } @@ -246,13 +238,12 @@ bool ServerAuthenticationManager::RemovePlayerAuthData(void* player) return false; } -void ServerAuthenticationManager::WritePersistentData(void* player) +void ServerAuthenticationManager::WritePersistentData(R2::CBasePlayer* player) { - // we use 0x4 internally to mark clients as using remote persistence - if (*((char*)player + 0x4A0) == (char)0x4) + if (player->m_iPersistenceReady == R2::ePersistenceReady::READY_REMOTE) { - g_MasterServerManager->WritePlayerPersistentData( - (char*)player + 0xF500, (char*)player + 0x4FA, m_additionalPlayerData[player].pdataSize); + g_pMasterServerManager->WritePlayerPersistentData( + player->m_UID, (const char*)player->m_PersistenceBuffer, m_PlayerAuthenticationData[player].pdataSize); } else if (CVar_ns_auth_allow_insecure_write->GetBool()) { @@ -260,25 +251,10 @@ void ServerAuthenticationManager::WritePersistentData(void* player) } } -bool ServerAuthenticationManager::CheckPlayerChatRatelimit(void* player) -{ - if (Tier0::Plat_FloatTime() - m_additionalPlayerData[player].lastSayTextLimitStart >= 1.0) - { - m_additionalPlayerData[player].lastSayTextLimitStart = Tier0::Plat_FloatTime(); - m_additionalPlayerData[player].sayTextLimitCount = 0; - } - - if (m_additionalPlayerData[player].sayTextLimitCount >= Cvar_sv_max_chat_messages_per_sec->GetInt()) - return false; - - m_additionalPlayerData[player].sayTextLimitCount++; - return true; -} - // auth hooks // store these in vars so we can use them in CBaseClient::Connect -// this is fine because ptrs won't decay by the time we use this, just don't use it outside of cbaseclient::connect +// this is fine because ptrs won't decay by the time we use this, just don't use it outside of calls from cbaseclient::connectclient char* pNextPlayerToken; uint64_t iNextPlayerUid; @@ -310,19 +286,19 @@ void*,, ( } AUTOHOOK(CBaseClient__Connect, engine.dll + 0x101740, -bool,, (void* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, void* a7)) +bool,, (R2::CBasePlayer* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, void* a7)) { // try changing name before all else - name = g_pServerAuthenticationManager->VerifyPlayerName(self, pNextPlayerToken, name); + g_pServerAuthentication->VerifyPlayerName(self, pNextPlayerToken, name); // try to auth player, dc if it fails - // we connect irregardless of auth, because returning bad from this function can fuck client state p bad + // we connect regardless of auth, because returning bad from this function can fuck client state p bad bool ret = CBaseClient__Connect(self, name, netchan_ptr_arg, b_fake_player_arg, a5, Buffer, a7); if (!ret) return ret; - if (!g_ServerBanSystem->IsUIDAllowed(iNextPlayerUid)) + if (!g_pServerBanSystem->IsUIDAllowed(iNextPlayerUid)) { R2::CBaseClient__Disconnect(self, 1, "Banned from server"); return ret; @@ -331,40 +307,34 @@ bool,, (void* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, if (strlen(name) >= 64) // fix for name overflow bug R2::CBaseClient__Disconnect(self, 1, "Invalid name"); else if ( - !g_pServerAuthenticationManager->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken) && - g_MasterServerManager->m_bRequireClientAuth) + !g_pServerAuthentication->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken) && + g_pMasterServerManager->m_bRequireClientAuth) R2::CBaseClient__Disconnect(self, 1, "Authentication Failed"); - if (!g_pServerAuthenticationManager->m_additionalPlayerData.count(self)) - { - AdditionalPlayerData additionalData; - additionalData.pdataSize = g_pServerAuthenticationManager->m_authData[pNextPlayerToken].pdataSize; - additionalData.usingLocalPdata = *((char*)self + 0x4a0) == (char)0x3; - - g_pServerAuthenticationManager->m_additionalPlayerData.insert(std::make_pair(self, additionalData)); - } + g_pServerAuthentication->AddPlayerData(self, pNextPlayerToken); + g_pServerLimits->AddPlayer(self); return ret; } AUTOHOOK(CBaseClient__ActivatePlayer, engine.dll + 0x100F80, -void,, (void* self)) +void,, (R2::CBasePlayer* self)) { // if we're authed, write our persistent data // RemovePlayerAuthData returns true if it removed successfully, i.e. on first call only, and we only want to write on >= second call // (since this func is called on map loads) - if (*((char*)self + 0x4A0) >= (char)0x3 && !g_pServerAuthenticationManager->RemovePlayerAuthData(self)) + if (self->m_iPersistenceReady >= R2::ePersistenceReady::READY && !g_pServerAuthentication->RemovePlayerAuthData(self)) { - g_pServerAuthenticationManager->m_bForceReadLocalPlayerPersistenceFromDisk = false; - g_pServerAuthenticationManager->WritePersistentData(self); - g_MasterServerManager->UpdateServerPlayerCount(g_pServerAuthenticationManager->m_additionalPlayerData.size()); + g_pServerAuthentication->m_bForceReadLocalPlayerPersistenceFromDisk = false; + g_pServerAuthentication->WritePersistentData(self); + g_pMasterServerManager->UpdateServerPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); } CBaseClient__ActivatePlayer(self); } AUTOHOOK(_CBaseClient__Disconnect, engine.dll + 0x1012C0, -void,, (void* self, uint32_t unknownButAlways1, const char* pReason, ...)) +void,, (R2::CBasePlayer* self, uint32_t unknownButAlways1, const char* pReason, ...)) { // have to manually format message because can't pass varargs to original func char buf[1024]; @@ -377,184 +347,23 @@ void,, (void* self, uint32_t unknownButAlways1, const char* pReason, ...)) // this reason is used while connecting to a local server, hacky, but just ignore it if (strcmp(pReason, "Connection closing")) { - spdlog::info("Player {} disconnected: \"{}\"", (char*)self + 0x16, buf); + spdlog::info("Player {} disconnected: \"{}\"", self->m_Name, buf); // dcing, write persistent data - if (g_pServerAuthenticationManager->m_additionalPlayerData[self].needPersistenceWriteOnLeave) - g_pServerAuthenticationManager->WritePersistentData(self); - g_pServerAuthenticationManager->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case + if (g_pServerAuthentication->m_PlayerAuthenticationData[self].needPersistenceWriteOnLeave) + g_pServerAuthentication->WritePersistentData(self); + g_pServerAuthentication->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case } - if (g_pServerAuthenticationManager->m_additionalPlayerData.count(self)) + if (g_pServerAuthentication->m_PlayerAuthenticationData.count(self)) { - g_pServerAuthenticationManager->m_additionalPlayerData.erase(self); - g_MasterServerManager->UpdateServerPlayerCount(g_pServerAuthenticationManager->m_additionalPlayerData.size()); + g_pServerAuthentication->m_PlayerAuthenticationData.erase(self); + g_pMasterServerManager->UpdateServerPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); } _CBaseClient__Disconnect(self, unknownButAlways1, buf); } -// maybe this should be done outside of auth code, but effort to refactor rn and it sorta fits -bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, R2::cmd_source_t commandSource); - -AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0, -char,, (void* self, uint32_t unknown, const char* pCommandString)) -{ - if (CVar_sv_quota_stringcmdspersecond->GetInt() != -1) - { - // note: this isn't super perfect, legit clients can trigger it in lobby, mostly good enough tho imo - // https://github.com/perilouswithadollarsign/cstrike15_src/blob/f82112a2388b841d72cb62ca48ab1846dfcc11c8/engine/sv_client.cpp#L1513 - if (Tier0::Plat_FloatTime() - g_pServerAuthenticationManager->m_additionalPlayerData[self].lastClientCommandQuotaStart >= 1.0) - { - // reset quota - g_pServerAuthenticationManager->m_additionalPlayerData[self].lastClientCommandQuotaStart = Tier0::Plat_FloatTime(); - g_pServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota = 0; - } - - g_pServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota++; - if (g_pServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota > - CVar_sv_quota_stringcmdspersecond->GetInt()) - { - // too many stringcmds, dc player - R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands"); - return false; - } - } - - // verify the command we're trying to execute is FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, if it's a concommand - char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor - memset(commandBuf, 0, sizeof(commandBuf)); - CCommand tempCommand = *(CCommand*)&commandBuf; - - if (!CCommand__Tokenize(tempCommand, pCommandString, R2::cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC()) - return false; - - ConCommand* command = R2::g_pCVar->FindCommand(tempCommand.Arg(0)); - - // if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff - if (command && !command->IsFlagSet(FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS)) - { - // ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients - if (IsDedicatedServer()) - return false; - - if (strcmp((char*)self + 0xF500, R2::g_pLocalPlayerUserID)) - return false; - } - - return CGameClient__ExecuteStringCommand(self, unknown, pCommandString); -} - -bool bWasWritingStringTableSuccessful; - -AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0, -void,, (void* self)) -{ - bWasWritingStringTableSuccessful = true; - CBaseClient__SendServerInfo(self); - if (!bWasWritingStringTableSuccessful) - R2::CBaseClient__Disconnect( - self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting"); -} - -AUTOHOOK(CNetChan___ProcessMessages, engine.dll + 0x2140A0, -char, __fastcall, (void* self, void* buf)) -{ - double startTime = Tier0::Plat_FloatTime(); - char ret = CNetChan___ProcessMessages(self, buf); - - // check processing limits, unless we're in a level transition - if (R2::g_pHostState->m_iCurrentState == R2::HostState_t::HS_RUN && Tier0::ThreadInServerFrameThread()) - { - // player that sent the message - void* sender = *(void**)((char*)self + 368); - - // if no sender, return - // relatively certain this is fine? - if (!sender || !g_pServerAuthenticationManager->m_additionalPlayerData.count(sender)) - return ret; - - // reset every second - if (startTime - g_pServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart >= 1.0 || - g_pServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart == -1.0) - { - g_pServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart = startTime; - g_pServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime = 0.0; - } - g_pServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime += - (Tier0::Plat_FloatTime() * 1000) - (startTime * 1000); - - if (g_pServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime >= - Cvar_net_chan_limit_msec_per_sec->GetInt()) - { - spdlog::warn( - "Client {} hit netchan processing limit with {}ms of processing time this second (max is {})", - (char*)sender + 0x16, - g_pServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime, - Cvar_net_chan_limit_msec_per_sec->GetInt()); - - // nonzero = kick, 0 = warn, but never kick local player - if (Cvar_net_chan_limit_mode->GetInt() && strcmp(R2::g_pLocalPlayerUserID, (char*)sender + 0xF500)) - { - R2::CBaseClient__Disconnect(sender, 1, "Exceeded net channel processing limit"); - return false; - } - } - } - - return ret; -} - -AUTOHOOK(ProcessConnectionlessPacket, engine.dll + 0x117800, -bool,, (void* a1, netpacket_t* packet)) -{ - if (packet->adr.type == NA_IP && - (!(packet->data[4] == 'N' && Cvar_net_datablock_enabled->GetBool()) || !Cvar_net_datablock_enabled->GetBool())) - { - // bad lookup: optimise later tm - UnconnectedPlayerSendData* sendData = nullptr; - for (UnconnectedPlayerSendData& foundSendData : g_pServerAuthenticationManager->m_unconnectedPlayerSendData) - { - if (!memcmp(packet->adr.ip, foundSendData.ip, 16)) - { - sendData = &foundSendData; - break; - } - } - - if (!sendData) - { - sendData = &g_pServerAuthenticationManager->m_unconnectedPlayerSendData.emplace_back(); - memcpy(sendData->ip, packet->adr.ip, 16); - } - - if (Tier0::Plat_FloatTime() < sendData->timeoutEnd) - return false; - - if (Tier0::Plat_FloatTime() - sendData->lastQuotaStart >= 1.0) - { - sendData->lastQuotaStart = Tier0::Plat_FloatTime(); - sendData->packetCount = 0; - } - - sendData->packetCount++; - - if (sendData->packetCount >= Cvar_sv_querylimit_per_sec->GetInt()) - { - spdlog::warn( - "Client went over connectionless ratelimit of {} per sec with packet of type {}", - Cvar_sv_querylimit_per_sec->GetInt(), - packet->data[4]); - - // timeout for a minute - sendData->timeoutEnd = Tier0::Plat_FloatTime() + 60.0; - return false; - } - } - - return ProcessConnectionlessPacket(a1, packet); -} - void ConCommand_ns_resetpersistence(const CCommand& args) { if (*R2::g_pServerState == R2::server_state_t::ss_active) @@ -564,49 +373,28 @@ void ConCommand_ns_resetpersistence(const CCommand& args) } spdlog::info("resetting persistence on next lobby load..."); - g_pServerAuthenticationManager->m_bForceReadLocalPlayerPersistenceFromDisk = true; + g_pServerAuthentication->m_bForceReadLocalPlayerPersistenceFromDisk = true; } ON_DLL_LOAD_RELIESON("engine.dll", ServerAuthentication, ConCommand, (HMODULE baseAddress)) { AUTOHOOK_DISPATCH() - g_pServerAuthenticationManager = new ServerAuthenticationManager; + g_pServerAuthentication = new ServerAuthenticationManager; - Cvar_ns_erase_auth_info = + g_pServerAuthentication->Cvar_ns_player_auth_port = new ConVar("ns_player_auth_port", "8081", FCVAR_GAMEDLL, ""); + g_pServerAuthentication->Cvar_ns_erase_auth_info = new ConVar("ns_erase_auth_info", "1", FCVAR_GAMEDLL, "Whether auth info should be erased from this server on disconnect or crash"); - CVar_ns_auth_allow_insecure = + g_pServerAuthentication->CVar_ns_auth_allow_insecure = new ConVar("ns_auth_allow_insecure", "0", FCVAR_GAMEDLL, "Whether this server will allow unauthenicated players to connect"); - CVar_ns_auth_allow_insecure_write = new ConVar( + g_pServerAuthentication->CVar_ns_auth_allow_insecure_write = new ConVar( "ns_auth_allow_insecure_write", "0", FCVAR_GAMEDLL, - "Whether the pdata of unauthenticated clients will be written to disk when changed"); - // literally just stolen from a fix valve used in csgo - CVar_sv_quota_stringcmdspersecond = new ConVar( - "sv_quota_stringcmdspersecond", - "60", - FCVAR_GAMEDLL, - "How many string commands per second clients are allowed to submit, 0 to disallow all string commands"); - // https://blog.counter-strike.net/index.php/2019/07/24922/ but different because idk how to check what current tick number is - Cvar_net_chan_limit_mode = - new ConVar("net_chan_limit_mode", "0", FCVAR_GAMEDLL, "The mode for netchan processing limits: 0 = log, 1 = kick"); - Cvar_net_chan_limit_msec_per_sec = new ConVar( - "net_chan_limit_msec_per_sec", - "0", - FCVAR_GAMEDLL, - "Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget"); - Cvar_ns_player_auth_port = new ConVar("ns_player_auth_port", "8081", FCVAR_GAMEDLL, ""); - Cvar_sv_querylimit_per_sec = new ConVar("sv_querylimit_per_sec", "15", FCVAR_GAMEDLL, ""); - Cvar_sv_max_chat_messages_per_sec = new ConVar("sv_max_chat_messages_per_sec", "5", FCVAR_GAMEDLL, ""); - - Cvar_net_datablock_enabled = R2::g_pCVar->FindVar("net_datablock_enabled"); + "Whether the pdata of unauthenticated clients will be written to disk when changed"); RegisterConCommand( "ns_resetpersistence", ConCommand_ns_resetpersistence, "resets your pdata when you next enter the lobby", FCVAR_NONE); - - R2::CBaseClient__Disconnect = (void(*)(void*, uint32_t, const char*, ...))((char*)baseAddress + 0x1012C0); - CCommand__Tokenize = (bool(*)(CCommand&, const char*, R2::cmd_source_t))((char*)baseAddress + 0x418380); uintptr_t ba = (uintptr_t)baseAddress; @@ -627,17 +415,4 @@ ON_DLL_LOAD_RELIESON("engine.dll", ServerAuthentication, ConCommand, (HMODULE ba ba + 0x114510, "EB" // jz => jmp ); - - // patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails - { - uintptr_t writeAddress = (uintptr_t)(&bWasWritingStringTableSuccessful - (ba + 0x234EDC)); - - auto addr = ba + 0x234ED2; - NSMem::BytePatch(addr, "C7 05"); - NSMem::BytePatch(addr + 2, (BYTE*)&writeAddress, sizeof(writeAddress)); - - NSMem::BytePatch(addr + 6, "00 00 00 00"); - - NSMem::NOP(addr + 10, 5); - } } \ No newline at end of file diff --git a/NorthstarDLL/serverauthentication.h b/NorthstarDLL/serverauthentication.h index b70ba2cf..86b4594b 100644 --- a/NorthstarDLL/serverauthentication.h +++ b/NorthstarDLL/serverauthentication.h @@ -1,10 +1,11 @@ #pragma once #include "convar.h" #include "httplib.h" +#include "r2server.h" #include #include -struct AuthData +struct RemoteAuthData { char uid[33]; char username[64]; @@ -14,98 +15,39 @@ struct AuthData size_t pdataSize; }; -struct AdditionalPlayerData +struct PlayerAuthenticationData { bool usingLocalPdata; size_t pdataSize; bool needPersistenceWriteOnLeave = true; - - double lastClientCommandQuotaStart = -1.0; - int numClientCommandsInQuota = 0; - - double lastNetChanProcessingLimitStart = -1.0; - double netChanProcessingLimitTime = 0.0; - - double lastSayTextLimitStart = -1.0; - int sayTextLimitCount = 0; -}; - -#pragma once -typedef enum -{ - NA_NULL = 0, - NA_LOOPBACK, - NA_IP, -} netadrtype_t; - -#pragma pack(push, 1) -typedef struct netadr_s -{ - netadrtype_t type; - unsigned char ip[16]; // IPv6 - // IPv4's 127.0.0.1 is [::ffff:127.0.0.1], that is: - // 00 00 00 00 00 00 00 00 00 00 FF FF 7F 00 00 01 - unsigned short port; -} netadr_t; -#pragma pack(pop) - -#pragma pack(push, 1) -typedef struct netpacket_s -{ - netadr_t adr; // sender address - // int source; // received source - char unk[10]; - double received_time; - unsigned char* data; // pointer to raw packet data - void* message; // easy bitbuf data access // 'inpacket.message' etc etc (pointer) - char unk2[16]; - int size; - - // bf_read message; // easy bitbuf data access // 'inpacket.message' etc etc (pointer) - // int size; // size in bytes - // int wiresize; // size in bytes before decompression - // bool stream; // was send as stream - // struct netpacket_s* pNext; // for internal use, should be NULL in public -} netpacket_t; -#pragma pack(pop) - -struct UnconnectedPlayerSendData -{ - char ip[16]; - double lastQuotaStart = 0.0; - int packetCount = 0; - double timeoutEnd = -1.0; }; class ServerAuthenticationManager { private: - httplib::Server m_playerAuthServer; + httplib::Server m_PlayerAuthServer; public: - std::mutex m_authDataMutex; - std::unordered_map m_authData; - std::unordered_map m_additionalPlayerData; - std::vector m_unconnectedPlayerSendData; - bool m_runningPlayerAuthThread = false; + ConVar* Cvar_ns_player_auth_port; + ConVar* Cvar_ns_erase_auth_info; + ConVar* CVar_ns_auth_allow_insecure; + ConVar* CVar_ns_auth_allow_insecure_write; + + std::mutex m_AuthDataMutex; + std::unordered_map m_RemoteAuthenticationData; + std::unordered_map m_PlayerAuthenticationData; + bool m_bRunningPlayerAuthThread = false; bool m_bNeedLocalAuthForNewgame = false; bool m_bForceReadLocalPlayerPersistenceFromDisk = false; public: void StartPlayerAuthServer(); void StopPlayerAuthServer(); - bool AuthenticatePlayer(void* player, int64_t uid, char* authToken); - char* VerifyPlayerName(void* player, char* authToken, char* name); - bool RemovePlayerAuthData(void* player); - void WritePersistentData(void* player); - bool CheckPlayerChatRatelimit(void* player); + void AddPlayerData(R2::CBasePlayer* player, const char* pToken); + bool AuthenticatePlayer(R2::CBasePlayer* player, uint64_t uid, char* authToken); + void VerifyPlayerName(R2::CBasePlayer* player, char* authToken, char* name); + bool RemovePlayerAuthData(R2::CBasePlayer* player); + void WritePersistentData(R2::CBasePlayer* player); }; -// use the R2 namespace for game funcs -namespace R2 -{ - extern void (*CBaseClient__Disconnect)(void* self, uint32_t unknownButAlways1, const char* reason, ...); -} // namespace R2 - -extern ServerAuthenticationManager* g_pServerAuthenticationManager; -extern ConVar* Cvar_ns_player_auth_port; +extern ServerAuthenticationManager* g_pServerAuthentication; diff --git a/NorthstarDLL/serverchathooks.cpp b/NorthstarDLL/serverchathooks.cpp index 8340948a..000c6eea 100644 --- a/NorthstarDLL/serverchathooks.cpp +++ b/NorthstarDLL/serverchathooks.cpp @@ -1,8 +1,8 @@ #include "pch.h" #include "serverchathooks.h" -#include "serverauthentication.h" +#include "limits.h" #include "squirrel.h" -#include "miscserverscript.h" +#include "r2server.h" #include #include @@ -11,7 +11,6 @@ AUTOHOOK_INIT() class CServerGameDLL; -class CBasePlayer; class CRecipientFilter { @@ -23,11 +22,10 @@ CServerGameDLL* g_pServerGameDLL; void(__fastcall* CServerGameDLL__OnReceivedSayTextMessage)( CServerGameDLL* self, unsigned int senderPlayerId, const char* text, int channelId); -CBasePlayer*(__fastcall* UTIL_PlayerByIndex)(int playerIndex); void(__fastcall* CRecipientFilter__Construct)(CRecipientFilter* self); void(__fastcall* CRecipientFilter__Destruct)(CRecipientFilter* self); void(__fastcall* CRecipientFilter__AddAllPlayers)(CRecipientFilter* self); -void(__fastcall* CRecipientFilter__AddRecipient)(CRecipientFilter* self, const CBasePlayer* player); +void(__fastcall* CRecipientFilter__AddRecipient)(CRecipientFilter* self, const R2::CBasePlayer* player); void(__fastcall* CRecipientFilter__MakeReliable)(CRecipientFilter* self); void(__fastcall* UserMessageBegin)(CRecipientFilter* filter, const char* messagename); @@ -38,7 +36,7 @@ void(__fastcall* MessageWriteBool)(bool bValue); bool bShouldCallSayTextHook = false; -AUTOHOOK(_CServerGameDLL__OnReceivedSayTextMessageHook, server.dll + 0x1595C0, +AUTOHOOK(_CServerGameDLL__OnReceivedSayTextMessage, server.dll + 0x1595C0, void,, (CServerGameDLL* self, unsigned int senderPlayerId, const char* text, bool isTeam)) { // MiniHook doesn't allow calling the base function outside of anywhere but the hook function. @@ -46,14 +44,14 @@ void,, (CServerGameDLL* self, unsigned int senderPlayerId, const char* text, boo if (bShouldCallSayTextHook) { bShouldCallSayTextHook = false; - _CServerGameDLL__OnReceivedSayTextMessageHook(self, senderPlayerId, text, isTeam); + _CServerGameDLL__OnReceivedSayTextMessage(self, senderPlayerId, text, isTeam); return; } - void* sender = GetPlayerByIndex(senderPlayerId - 1); + R2::CBasePlayer* sender = R2::UTIL_PlayerByIndex(senderPlayerId - 1); // check chat ratelimits - if (!g_pServerAuthenticationManager->CheckPlayerChatRatelimit(sender)) + if (!g_pServerLimits->CheckChatLimits(sender)) return; if (g_pServerSquirrel->setupfunc("CServerGameDLL_ProcessMessageStartThread") != SQRESULT_ERROR) @@ -64,7 +62,7 @@ void,, (CServerGameDLL* self, unsigned int senderPlayerId, const char* text, boo g_pServerSquirrel->call(g_pServerSquirrel->sqvm2, 3); } else - _CServerGameDLL__OnReceivedSayTextMessageHook(self, senderPlayerId, text, isTeam); + _CServerGameDLL__OnReceivedSayTextMessage(self, senderPlayerId, text, isTeam); } void ChatSendMessage(unsigned int playerIndex, const char* text, bool isteam) @@ -80,10 +78,10 @@ void ChatSendMessage(unsigned int playerIndex, const char* text, bool isteam) void ChatBroadcastMessage(int fromPlayerIndex, int toPlayerIndex, const char* text, bool isTeam, bool isDead, CustomMessageType messageType) { - CBasePlayer* toPlayer = NULL; + R2::CBasePlayer* toPlayer = NULL; if (toPlayerIndex >= 0) { - toPlayer = UTIL_PlayerByIndex(toPlayerIndex + 1); + toPlayer = R2::UTIL_PlayerByIndex(toPlayerIndex + 1); if (toPlayer == NULL) return; } @@ -162,11 +160,10 @@ ON_DLL_LOAD_RELIESON("server.dll", ServerChatHooks, ServerSquirrel, (HMODULE bas AUTOHOOK_DISPATCH_MODULE(server.dll) CServerGameDLL__OnReceivedSayTextMessage = (void(__fastcall*)(CServerGameDLL*, unsigned int, const char*, int))((char*)baseAddress + 0x1595C0); - UTIL_PlayerByIndex = (CBasePlayer*(__fastcall*)(int))((char*)baseAddress + 0x26AA10); CRecipientFilter__Construct = (void(__fastcall*)(CRecipientFilter*))((char*)baseAddress + 0x1E9440); CRecipientFilter__Destruct = (void(__fastcall*)(CRecipientFilter*))((char*)baseAddress + 0x1E9700); CRecipientFilter__AddAllPlayers = (void(__fastcall*)(CRecipientFilter*))((char*)baseAddress + 0x1E9940); - CRecipientFilter__AddRecipient = (void(__fastcall*)(CRecipientFilter*, const CBasePlayer*))((char*)baseAddress + 0x1E9b30); + CRecipientFilter__AddRecipient = (void(__fastcall*)(CRecipientFilter*, const R2::CBasePlayer*))((char*)baseAddress + 0x1E9b30); CRecipientFilter__MakeReliable = (void(__fastcall*)(CRecipientFilter*))((char*)baseAddress + 0x1EA4E0); UserMessageBegin = (void(__fastcall*)(CRecipientFilter*, const char*))((char*)baseAddress + 0x15C520); -- cgit v1.2.3