From 841881af9ea6ec73b1d505d5a8f7c1f766273724 Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:26:07 +0100 Subject: big refactor (#171) * use in-file macros rather than global funcs for registering dll load callbacks * move more things to macros * fix debug crashes * move sqvm funcs to sq managers * get rid of context file * refactor some squirrel stuff and ingame compilation error message * move tier0 and playlist funcs to namespaces * uiscript_reset concommand: don't loop forever if compilation fails * improve showing console for ui script compile errors * standardise concommand func naming in c++ * use lambdas for dll load callbacks so intellisense shits itself less * use cvar change callbacks for unescaping ns_server_name and ns_server_desc * add proper helpstrings to masterserver cvars * add cvar help and find * allow parsing of convar flags from string * normalise mod fs paths to be lowercase * move hoststate to its own file and add host_init hooks * better IsFlagSet def * replace files in ReadFromCache * rename g_ModManager to g_pModManager * formatting changes * make cvar print work on dedi, move demo fix stuff, add findflags * add proper map autocompletes and maps command * formatting changes * separate gameutils into multiple r2 headers * Update keyvalues.cpp * move sqvm funcs into wrappers in the manager class * remove unnecessary header files * lots of cleanup and starting moving to new hooking macros * update more stuff to new hook macros * rename project folder (:tf: commit log) * fix up postbuild commands to use relative dir * almost fully replaced hooking lib * completely remove old hooking * add nsprefix because i forgot to include it * move exploit prevention and limits code out of serverauthentication, and have actual defs for CBasePlayer * use modular ServerPresence system for registering servers * add new memory lib * accidentally pushed broke code oops * lots of stuff idk * implement some more prs * improve rpakfilesystem * fix line endings on vcxproj * Revert "fix line endings on vcxproj" This reverts commit 4ff7d022d2602c2dba37beba8b8df735cf5cd7d9. * add more prs * i swear i committed these how are they not there * Add ability to load Datatables from files (#238) * first version of kinda working custom datatables * Fix copy error * Finish custom datatables * Fix Merge * Fix line endings * Add fallback to rpak when ns_prefere_datatable_from_disk is true * fix typo * Bug fixess * Fix Function Registration hook * Set convar value * Fix Client and Ui VM * enable server auth with ms agian * Add Filters * FIx unused import * Merge remote-tracking branch 'upsteam/bobs-big-refactor-pr' into datatables Co-authored-by: RoyalBlue1 * Add some changes from main to refactor (#243) * Add PR template * Update CI folder location * Delete startup args txt files * Fix line endings (hopefully) (#244) * Fix line endings (hopefully) * Fix more line endings * Update refactor (#250) * Add PR template * Update CI folder location * Delete startup args txt files * Add editorconfig file (#246) * Add editorconfig file It's a cross-editor compatible config file that defines certain editor behaviour (e.g. adding/removing newline at end of file) It is supported by major editors like Visual Studio (Code) and by version control providers like GitHub. Should end the constant adding/removing of final newline in PRs * More settings - unicode by default - trim newlines - use tabs for indentation (ugh) * Ignore folder rename (#245) * Hot reload banlist on player join (#233) * added banlist hotreload * fix formatting * didnt append, cleared whole file oopsie * unfuckedunban not rewriting file * fixed not checking for new line Co-authored-by: ScureX <47725553+ScureX@users.noreply.github.com> * Refactor cleanup (#256) * Fix indentation * Fix path in clang-format command in readme * Refactor cleanup (some formatting fixes) (#257) * Fix some formatting * More formatting fixes * add scriptdatatable.cpp rewrite * Some formatting fixes (#260) * More formatting stuff (#261) * various formatting changes and fixes * Fix changed icon (#264) * clang format, fix issues with server registration and rpak loading * fix more formatting * update postbuild step * set launcher directory and error on fail creating log files * change some stuff in exploitfixes * only unrestrict dev commands when commandline flag is present * fix issues with cvar flag commit * fixup command flags better and reformat * bring up to date with main * fixup formatting * improve cvar flag fixup and remove temp thing from findflags * set serverfilter better * avoid ptr decay when setting auth token * add more entity functions * Fix the MS server registration issues. (#285) * Port ms presence reporter to std::async * Fix crash due to std::optional being assigned nullptr. * Fix formatting. * Wait 20 seconds if MS returns DUPLICATE_SERVER. * Change PERSISTENCE_MAX_SIZE to fix player authentication (#287) The size check added in the refactor was incorrect: - 56306: expected pdata size based on the pdef - 512: allowance for trailing junk (r2 adds 137 bytes of trailing junk) - 100: for some wiggle room Co-Authored-By: pg9182 <96569817+pg9182@users.noreply.github.com> * change miscserverscript to use actual entity arguments rather than player index jank * Fix token clearing hook (#290) A certain someone forgot to put an `0x` in front of their hex number, meaning the offset is wrong. This would cause token to be leaked again Co-authored-by: Maya Co-authored-by: RoyalBlue1 Co-authored-by: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Co-authored-by: ScureX <47725553+ScureX@users.noreply.github.com> Co-authored-by: Erlite Co-authored-by: Emma Miler Co-authored-by: pg9182 <96569817+pg9182@users.noreply.github.com> --- NorthstarDLL/NorthstarDLL.vcxproj | 1343 ++++---- NorthstarDLL/NorthstarDLL.vcxproj.filters | 3582 ++++++++++---------- NorthstarDLL/audio.cpp | 84 +- NorthstarDLL/audio.h | 4 - NorthstarDLL/bansystem.cpp | 45 +- NorthstarDLL/bansystem.h | 4 +- NorthstarDLL/buildainfile.cpp | 72 +- NorthstarDLL/buildainfile.h | 3 - NorthstarDLL/chatcommand.cpp | 8 +- NorthstarDLL/chatcommand.h | 3 - NorthstarDLL/clientauthhooks.cpp | 49 +- NorthstarDLL/clientauthhooks.h | 2 - NorthstarDLL/clientchathooks.cpp | 72 +- NorthstarDLL/clientchathooks.h | 5 - NorthstarDLL/clientruihooks.cpp | 18 +- NorthstarDLL/clientruihooks.h | 2 - NorthstarDLL/clientvideooverrides.cpp | 29 +- NorthstarDLL/clientvideooverrides.h | 2 - NorthstarDLL/concommand.cpp | 53 +- NorthstarDLL/concommand.h | 23 +- NorthstarDLL/context.cpp | 14 - NorthstarDLL/context.h | 11 - NorthstarDLL/convar.cpp | 38 +- NorthstarDLL/convar.h | 51 +- NorthstarDLL/crashhandler.cpp | 216 ++ NorthstarDLL/crashhandler.h | 4 + NorthstarDLL/cvar.cpp | 8 +- NorthstarDLL/cvar.h | 8 +- NorthstarDLL/debugoverlay.cpp | 62 +- NorthstarDLL/debugoverlay.h | 3 - NorthstarDLL/dedicated.cpp | 286 +- NorthstarDLL/dedicated.h | 4 - NorthstarDLL/dedicatedmaterialsystem.cpp | 107 +- NorthstarDLL/dedicatedmaterialsystem.h | 3 - NorthstarDLL/demofixes.cpp | 26 + NorthstarDLL/dllmain.cpp | 158 +- NorthstarDLL/exploitfixes.cpp | 543 ++- NorthstarDLL/exploitfixes.h | 12 - NorthstarDLL/exploitfixes_lzss.cpp | 79 + NorthstarDLL/exploitfixes_utf8parser.cpp | 200 ++ NorthstarDLL/exploitfixes_utf8parser.h | 175 - NorthstarDLL/filesystem.cpp | 215 +- NorthstarDLL/filesystem.h | 14 +- NorthstarDLL/gameutils.cpp | 119 - NorthstarDLL/gameutils.h | 249 -- NorthstarDLL/hooks.cpp | 390 ++- NorthstarDLL/hooks.h | 308 +- NorthstarDLL/hookutils.cpp | 44 - NorthstarDLL/hookutils.h | 23 - NorthstarDLL/host.cpp | 37 + NorthstarDLL/hoststate.cpp | 116 + NorthstarDLL/hoststate.h | 45 + NorthstarDLL/include/MinHook.x64.lib | Bin 32400 -> 32401 bytes NorthstarDLL/include/libcrypto_static.lib | Bin 35832382 -> 35832383 bytes NorthstarDLL/include/libcurl/lib/libcurl_a.lib | Bin 2140640 -> 2140641 bytes NorthstarDLL/include/libssl_static.lib | Bin 4851208 -> 4851209 bytes NorthstarDLL/keyvalues.cpp | 81 +- NorthstarDLL/keyvalues.h | 3 - NorthstarDLL/languagehooks.cpp | 27 +- NorthstarDLL/languagehooks.h | 3 - NorthstarDLL/latencyflex.cpp | 74 +- NorthstarDLL/latencyflex.h | 2 - NorthstarDLL/limits.cpp | 299 ++ NorthstarDLL/limits.h | 51 + NorthstarDLL/localchatwriter.cpp | 87 +- NorthstarDLL/localchatwriter.h | 21 +- NorthstarDLL/logging.cpp | 449 +-- NorthstarDLL/logging.h | 3 - NorthstarDLL/masterserver.cpp | 1048 +++--- NorthstarDLL/masterserver.h | 94 +- NorthstarDLL/maxplayers.cpp | 439 ++- NorthstarDLL/maxplayers.h | 9 +- NorthstarDLL/memalloc.cpp | 20 +- NorthstarDLL/memory.cpp | 348 ++ NorthstarDLL/memory.h | 90 + NorthstarDLL/miscclientfixes.cpp | 49 - NorthstarDLL/miscclientfixes.h | 2 - NorthstarDLL/misccommands.cpp | 180 +- NorthstarDLL/misccommands.h | 1 + NorthstarDLL/miscserverfixes.cpp | 23 +- NorthstarDLL/miscserverfixes.h | 1 - NorthstarDLL/miscserverscript.cpp | 81 +- NorthstarDLL/miscserverscript.h | 2 - NorthstarDLL/modlocalisation.cpp | 32 +- NorthstarDLL/modlocalisation.h | 3 - NorthstarDLL/modmanager.cpp | 236 +- NorthstarDLL/modmanager.h | 51 +- NorthstarDLL/nsmem.h | 193 -- NorthstarDLL/nsprefix.cpp | 4 +- NorthstarDLL/nsprefix.h | 2 +- NorthstarDLL/pch.h | 9 +- NorthstarDLL/pdef.cpp | 18 +- NorthstarDLL/playlist.cpp | 138 +- NorthstarDLL/playlist.h | 10 +- NorthstarDLL/plugins.cpp | 96 +- NorthstarDLL/plugins.h | 2 - NorthstarDLL/printcommand.h | 6 + NorthstarDLL/printcommands.cpp | 174 + NorthstarDLL/printmaps.cpp | 168 + NorthstarDLL/printmaps.h | 2 + NorthstarDLL/r2client.cpp | 20 + NorthstarDLL/r2client.h | 11 + NorthstarDLL/r2engine.cpp | 36 + NorthstarDLL/r2engine.h | 206 ++ NorthstarDLL/r2server.cpp | 17 + NorthstarDLL/r2server.h | 23 + NorthstarDLL/rpakfilesystem.cpp | 276 +- NorthstarDLL/rpakfilesystem.h | 43 +- NorthstarDLL/runframe.cpp | 20 + NorthstarDLL/scriptbrowserhooks.cpp | 22 +- NorthstarDLL/scriptbrowserhooks.h | 3 - NorthstarDLL/scriptdatatables.cpp | 940 +++++ NorthstarDLL/scriptjson.cpp | 601 +--- NorthstarDLL/scriptjson.h | 2 - NorthstarDLL/scriptmainmenupromos.cpp | 50 +- NorthstarDLL/scriptmainmenupromos.h | 3 - NorthstarDLL/scriptmodmenu.cpp | 108 +- NorthstarDLL/scriptmodmenu.h | 3 - NorthstarDLL/scriptserverbrowser.cpp | 311 +- NorthstarDLL/scriptserverbrowser.h | 4 - NorthstarDLL/scriptservertoclientstringcommand.cpp | 13 +- NorthstarDLL/scriptservertoclientstringcommand.h | 3 - NorthstarDLL/scriptsrson.cpp | 21 +- NorthstarDLL/scriptutility.cpp | 30 +- NorthstarDLL/scriptutility.h | 4 - NorthstarDLL/serverauthentication.cpp | 691 +--- NorthstarDLL/serverauthentication.h | 103 +- NorthstarDLL/serverchathooks.cpp | 176 +- NorthstarDLL/serverchathooks.h | 4 - NorthstarDLL/serverpresence.cpp | 237 ++ NorthstarDLL/serverpresence.h | 92 + NorthstarDLL/sigscanning.cpp | 41 - NorthstarDLL/sigscanning.h | 7 - NorthstarDLL/sourceconsole.cpp | 89 +- NorthstarDLL/sourceconsole.h | 15 +- NorthstarDLL/sourceinterface.cpp | 83 +- NorthstarDLL/sourceinterface.h | 3 - NorthstarDLL/squirrel.cpp | 925 ++--- NorthstarDLL/squirrel.h | 931 ++--- NorthstarDLL/squirreldatatypes.h | 484 +++ NorthstarDLL/tier0.cpp | 37 + NorthstarDLL/tier0.h | 68 + NorthstarDLL/vector.h | 52 + NorthstarLauncher/NorthstarLauncher.vcxproj | 212 +- .../NorthstarLauncher.vcxproj.filters | 2 +- R2Northstar.sln | 2 +- loader_launcher_proxy/Memory.cpp | 89 - loader_launcher_proxy/Memory.h | 24 - .../loader_launcher_proxy.vcxproj.filters | 2 +- loader_wsock32_proxy/hookutils.cpp | 61 - loader_wsock32_proxy/include/MinHook.h | 186 + loader_wsock32_proxy/include/MinHook.x64.lib | Bin 0 -> 32401 bytes loader_wsock32_proxy/loader.cpp | 14 +- loader_wsock32_proxy/loader_wsock32_proxy.vcxproj | 10 +- .../loader_wsock32_proxy.vcxproj.filters | 13 +- 155 files changed, 11332 insertions(+), 9693 deletions(-) delete mode 100644 NorthstarDLL/buildainfile.h delete mode 100644 NorthstarDLL/chatcommand.h delete mode 100644 NorthstarDLL/clientauthhooks.h delete mode 100644 NorthstarDLL/clientchathooks.h delete mode 100644 NorthstarDLL/clientruihooks.h delete mode 100644 NorthstarDLL/clientvideooverrides.h delete mode 100644 NorthstarDLL/context.cpp delete mode 100644 NorthstarDLL/context.h create mode 100644 NorthstarDLL/crashhandler.cpp create mode 100644 NorthstarDLL/crashhandler.h delete mode 100644 NorthstarDLL/debugoverlay.h delete mode 100644 NorthstarDLL/dedicatedmaterialsystem.h create mode 100644 NorthstarDLL/demofixes.cpp delete mode 100644 NorthstarDLL/exploitfixes.h create mode 100644 NorthstarDLL/exploitfixes_lzss.cpp create mode 100644 NorthstarDLL/exploitfixes_utf8parser.cpp delete mode 100644 NorthstarDLL/exploitfixes_utf8parser.h delete mode 100644 NorthstarDLL/gameutils.cpp delete mode 100644 NorthstarDLL/gameutils.h delete mode 100644 NorthstarDLL/hookutils.cpp delete mode 100644 NorthstarDLL/hookutils.h create mode 100644 NorthstarDLL/host.cpp create mode 100644 NorthstarDLL/hoststate.cpp create mode 100644 NorthstarDLL/hoststate.h delete mode 100644 NorthstarDLL/keyvalues.h delete mode 100644 NorthstarDLL/languagehooks.h delete mode 100644 NorthstarDLL/latencyflex.h create mode 100644 NorthstarDLL/limits.cpp create mode 100644 NorthstarDLL/limits.h create mode 100644 NorthstarDLL/memory.cpp create mode 100644 NorthstarDLL/memory.h delete mode 100644 NorthstarDLL/miscclientfixes.cpp delete mode 100644 NorthstarDLL/miscclientfixes.h delete mode 100644 NorthstarDLL/miscserverfixes.h delete mode 100644 NorthstarDLL/miscserverscript.h delete mode 100644 NorthstarDLL/modlocalisation.h delete mode 100644 NorthstarDLL/nsmem.h create mode 100644 NorthstarDLL/printcommand.h create mode 100644 NorthstarDLL/printcommands.cpp create mode 100644 NorthstarDLL/printmaps.cpp create mode 100644 NorthstarDLL/printmaps.h create mode 100644 NorthstarDLL/r2client.cpp create mode 100644 NorthstarDLL/r2client.h create mode 100644 NorthstarDLL/r2engine.cpp create mode 100644 NorthstarDLL/r2engine.h create mode 100644 NorthstarDLL/r2server.cpp create mode 100644 NorthstarDLL/r2server.h create mode 100644 NorthstarDLL/runframe.cpp delete mode 100644 NorthstarDLL/scriptbrowserhooks.h create mode 100644 NorthstarDLL/scriptdatatables.cpp delete mode 100644 NorthstarDLL/scriptjson.h delete mode 100644 NorthstarDLL/scriptmainmenupromos.h delete mode 100644 NorthstarDLL/scriptmodmenu.h delete mode 100644 NorthstarDLL/scriptserverbrowser.h delete mode 100644 NorthstarDLL/scriptservertoclientstringcommand.h delete mode 100644 NorthstarDLL/scriptutility.h create mode 100644 NorthstarDLL/serverpresence.cpp create mode 100644 NorthstarDLL/serverpresence.h delete mode 100644 NorthstarDLL/sigscanning.cpp delete mode 100644 NorthstarDLL/sigscanning.h create mode 100644 NorthstarDLL/squirreldatatypes.h create mode 100644 NorthstarDLL/tier0.cpp create mode 100644 NorthstarDLL/tier0.h create mode 100644 NorthstarDLL/vector.h delete mode 100644 loader_launcher_proxy/Memory.cpp delete mode 100644 loader_launcher_proxy/Memory.h delete mode 100644 loader_wsock32_proxy/hookutils.cpp create mode 100644 loader_wsock32_proxy/include/MinHook.h create mode 100644 loader_wsock32_proxy/include/MinHook.x64.lib diff --git a/NorthstarDLL/NorthstarDLL.vcxproj b/NorthstarDLL/NorthstarDLL.vcxproj index 19444e8d..9ce5da3c 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj +++ b/NorthstarDLL/NorthstarDLL.vcxproj @@ -1,671 +1,674 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {cfad2623-064f-453c-8196-79ee10292e32} - NorthstarDLL - 10.0 - Northstar - - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - - - - - - - - - - - - - - - - true - - - false - - - - Level3 - true - _DEBUG;NORTHSTARDEDICATEDTEST_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);CURL_STATICLIB - true - Use - pch.h - stdcpp20 - $(ProjectDir)include;%(AdditionalIncludeDirectories) - - - Windows - true - false - $(ProjectDir)include\MinHook.x64.lib;$(ProjectDir)include\libcurl\lib\libcurl_a.lib;dbghelp.lib;Wldap32.lib;Normaliz.lib;version.lib;%(AdditionalDependencies) - - - %(AdditionalLibraryDirectories) - - - - - - - - - Level3 - true - true - true - NDEBUG;NORTHSTARDEDICATEDTEST_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);CURL_STATICLIB - true - Use - pch.h - stdcpp20 - $(ProjectDir)include;%(AdditionalIncludeDirectories) - MultiThreadedDLL - - - Windows - true - true - true - false - $(ProjectDir)include\MinHook.x64.lib;$(ProjectDir)include\libcurl\lib\libcurl_a.lib;dbghelp.lib;Wldap32.lib;Normaliz.lib;version.lib;%(AdditionalDependencies) - - - %(AdditionalLibraryDirectories) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Create - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {cfad2623-064f-453c-8196-79ee10292e32} + NorthstarDedicatedTest + 10.0 + NorthstarDLL + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + true + Northstar + + + false + Northstar + + + + Level3 + true + _DEBUG;NORTHSTARDEDICATEDTEST_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);CURL_STATICLIB + true + Use + pch.h + stdcpp20 + $(ProjectDir)include;%(AdditionalIncludeDirectories) + + + Windows + true + false + $(ProjectDir)include\MinHook.x64.lib;$(ProjectDir)include\libcurl\lib\libcurl_a.lib;dbghelp.lib;Wldap32.lib;Normaliz.lib;version.lib;%(AdditionalDependencies) + + + %(AdditionalLibraryDirectories) + + + + + + + copy /Y "$(TargetPath)" "$(SolutionDir)..\..\" + + + + + Level3 + true + true + true + NDEBUG;NORTHSTARDEDICATEDTEST_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);CURL_STATICLIB + true + Use + pch.h + stdcpp20 + $(ProjectDir)include;%(AdditionalIncludeDirectories) + MultiThreadedDLL + + + Windows + true + true + true + false + $(ProjectDir)include\MinHook.x64.lib;$(ProjectDir)include\libcurl\lib\libcurl_a.lib;dbghelp.lib;Wldap32.lib;Normaliz.lib;version.lib;%(AdditionalDependencies) + + + %(AdditionalLibraryDirectories) + + + + + + + IF EXIST "$(SolutionDir)..\..\Titanfall2.exe" del "$(SolutionDir)..\..\Northstar.dll" && copy /Y "$(TargetPath)" "$(SolutionDir)..\..\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NorthstarDLL/NorthstarDLL.vcxproj.filters b/NorthstarDLL/NorthstarDLL.vcxproj.filters index ebbe4fa4..9b5837d2 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj.filters +++ b/NorthstarDLL/NorthstarDLL.vcxproj.filters @@ -1,1789 +1,1795 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {d4199e4b-10d2-43ce-af9c-e1fa79e1e64e} - - - {4d322431-dcaa-4f75-aee0-3b6371cf52a6} - - - {94259c8c-5411-48bf-af4f-46ca32b7d0bb} - - - {4f525372-34a8-40b3-8a95-81d77cdfcf7f} - - - {8b8ed12a-9269-4dc3-b932-0daefdf6a388} - - - {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} - - - {11eaa578-6336-456e-9c7c-8bd202470945} - - - {7ecd75d2-7eee-41c4-87b6-3b7c2213f34e} - - - {8afc70f1-639c-49ef-9348-ef6dcece114e} - - - {398efed5-0a92-4d32-b5ba-b4a725b2a70a} - - - {74567974-c66b-45ef-ab28-97b7154ca224} - - - {3e892d07-2239-44da-9cf3-c288a34cf9a2} - - - {6bbce8a5-38b4-4763-a7cb-4e98012ec245} - - - {4ca5392e-7d3d-4066-833f-f534cd5787c3} - - - {94b15898-ef33-41c7-995a-31791fccb7e2} - - - {6495657f-ea55-4552-8aa7-b54eb8e86a99} - - - {85aacdee-0f92-4ec4-b20c-0739c1175055} - - - {4db0d1e9-9035-457f-87f1-5dc3f13b6b9e} - - - {d1f93d1e-0ecb-44fe-a277-d3e75aec2570} - - - {14fc0931-acad-46ec-a55e-94f4469d4235} - - - {3d41d3fc-8a3b-4358-b3e8-4f06dc96abfe} - - - {d69760a9-d5ec-4f3e-8f43-f74041654d44} - - - {365e5c1f-4b2f-4d8b-a1d8-cdef401ca689} - - - {24fd0855-9288-4129-93ba-c6cafdc98d1b} - - - {2cbddb28-0b17-4881-847d-8773da52b268} - - - {0c93d909-e0d6-4c35-a8a4-a13f681a1012} - - - {4cb0dd89-5f16-4549-a864-34ca3075352a} - - - {914d8b8f-6b19-4f23-b746-f40062d72906} - - - {09516029-fac7-4235-ad61-402977534a0b} - - - {8cc1ae44-9dbf-4719-91a2-82e00b8d78e2} - - - {ea1e17a6-40b7-4e1b-8edb-e9ae704ce604} - - - {59b0f68f-daa7-4641-b6fa-8464b56da2bb} - - - {44a83740-9d70-480d-9a7a-43b81f8eab9e} - - - {4a8a695a-a103-4b1f-b314-0ec19a253119} - - - {b30e08b1-b962-4264-8cbb-a0a31924b93e} - - - {947835db-67d6-42c0-870d-62743f85231f} - - - {7f609cee-d2c0-46a2-b06e-83b9f0511915} - - - - - Header Files - - - Header Files\include - - - Header Files\Shared\Hooks - - - Header Files\Shared\Hooks - - - Header Files - - - Header Files\Dedicated - - - Header Files\Client - - - Header Files\Shared - - - Header Files\Shared\Hooks - - - Header Files\Shared - - - Header Files\Shared - - - Header Files\Shared - - - Header Files\Shared\Convar - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog - - - Header Files\include\spdlog\cfg - - - Header Files\include\spdlog\cfg - - - Header Files\include\spdlog\cfg - - - Header Files\include\spdlog\cfg - - - Header Files\include\spdlog\fmt - - - Header Files\include\spdlog\fmt - - - Header Files\include\spdlog\fmt - - - Header Files\include\spdlog\fmt - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\sinks - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\include\spdlog\details - - - Header Files\Shared\Convar - - - Header Files\Shared\Mods - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson - - - Header Files\include\rapidjson\error - - - Header Files\include\rapidjson\error - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\internal - - - Header Files\include\rapidjson\msinttypes - - - Header Files\include\rapidjson\msinttypes - - - Header Files\Shared - - - Header Files\Shared\Mods\Compiled - - - Header Files\Server\Authentication - - - Header Files\Client - - - Header Files\Client - - - Header Files\Shared\Mods\Compiled - - - Header Files\include - - - Header Files\Shared - - - Header Files\Client - - - Header Files\Client - - - Header Files\Server - - - Header Files\Dedicated - - - Header Files\Shared\Convar - - - Header Files\Server - - - Header Files\Shared\Mods\Compiled - - - Header Files\Client - - - Header Files\Client - - - Header Files\Shared\Game Functions - - - Header Files\Shared - - - Header Files\Client - - - Header Files\Client - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\include\openssl\internal - - - Header Files\Server - - - Header Files\Shared - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\include\libcurl - - - Header Files\Shared - - - Header Files\Server\Authentication - - - Header Files\Client - - - Header Files\Client - - - Header Files\Client - - - Header Files\Server - - - Header Files\Shared - - - Header Files\Client - - - Header Files\Shared\Convar - - - Header Files\Shared\Math - - - Header Files\Shared\Math - - - Header Files\Server - - - Header Files\Client - - - Header Files\Client - - - Header Files\Client - - - Header Files - - - Header Files - - - Header Files - - - Header Files\Client - - - Header Files\Client - - - Source Files\Shared\Exploit Fixes\UTF8Parser - - - Header Files - - - Header Files\Client - - - Header Files - - - Header Files\Shared\ExploitFixes - - - Header Files\Shared\ExploitFixes - - - Header Files\Shared - - - Header Files\Shared - - - - - Source Files - - - Source Files - - - Source Files\Shared\Hooks - - - Source Files\Shared\Hooks - - - Source Files\Server\Dedicated - - - Source Files\Client - - - Source Files\Shared - - - Source Files\Shared\Hooks - - - Source Files\Shared - - - Source Files\Shared - - - Source Files\Shared - - - Source Files\Shared\Convar - - - Source Files\Shared\Convar - - - Source Files\Shared\Mods - - - Source Files\Shared - - - Source Files\Shared\Mods\Compiled - - - Source Files\Server\Authentication - - - Source Files\Client - - - Source Files\Client - - - Source Files\Shared\Mods\Compiled - - - Source Files\Shared - - - Source Files\Shared\Game Functions - - - Source Files\Client - - - Source Files\Client - - - Source Files\Server - - - Source Files\Server\Dedicated - - - Source Files\Shared\Convar - - - Source Files\Server - - - Source Files\Shared\Mods\Compiled - - - Source Files\Client - - - Source Files\Client - - - Source Files\Shared - - - Source Files\Client - - - Source Files\Client - - - Source Files\Shared - - - Source Files\Server - - - Source Files\Shared - - - Source Files\Server\Authentication - - - Source Files\Client - - - Source Files\Client - - - Source Files\Client - - - Source Files\Server - - - Source Files\Client - - - Source Files\Shared\Convar - - - Source Files\Shared\Math - - - Source Files\Server - - - Source Files\Client - - - Source Files\Client - - - Source Files\Client - - - Source Files - - - Source Files\Client - - - Source Files\Client - - - Source Files\Shared\Exploit Fixes - - - Source Files - - - Source Files\Client - - - Source Files\Shared - - - Source Files\Shared - - - - - Source Files\Client - - - - - Header Files\include\spdlog\fmt\bundled - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\openssl - - - Header Files\include\openssl\crypto - - - Header Files\include\openssl\crypto - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {d4199e4b-10d2-43ce-af9c-e1fa79e1e64e} + + + {b6f79919-9735-476d-8798-067a75cbeca0} + + + {ca657be5-c2d8-4322-a689-1154aaafe57b} + + + {8596cc1c-0492-4467-91e3-1f03b7e19f77} + + + {11eaa578-6336-456e-9c7c-8bd202470945} + + + {7ecd75d2-7eee-41c4-87b6-3b7c2213f34e} + + + {8afc70f1-639c-49ef-9348-ef6dcece114e} + + + {398efed5-0a92-4d32-b5ba-b4a725b2a70a} + + + {74567974-c66b-45ef-ab28-97b7154ca224} + + + {4ca5392e-7d3d-4066-833f-f534cd5787c3} + + + {94b15898-ef33-41c7-995a-31791fccb7e2} + + + {6495657f-ea55-4552-8aa7-b54eb8e86a99} + + + {85aacdee-0f92-4ec4-b20c-0739c1175055} + + + {3d41d3fc-8a3b-4358-b3e8-4f06dc96abfe} + + + {d69760a9-d5ec-4f3e-8f43-f74041654d44} + + + {365e5c1f-4b2f-4d8b-a1d8-cdef401ca689} + + + {24fd0855-9288-4129-93ba-c6cafdc98d1b} + + + {4cb0dd89-5f16-4549-a864-34ca3075352a} + + + {914d8b8f-6b19-4f23-b746-f40062d72906} + + + {09516029-fac7-4235-ad61-402977534a0b} + + + {8cc1ae44-9dbf-4719-91a2-82e00b8d78e2} + + + {ea1e17a6-40b7-4e1b-8edb-e9ae704ce604} + + + {51910ba0-2ff8-461d-9f67-8d7907b57d22} + + + {325e0d7d-6832-496d-8d8e-968fdfa5dd40} + + + {802d0771-62f1-4733-89f9-57a4d8864b8d} + + + {04fd662a-6e70-494c-b720-c694a5cc2fb1} + + + {a18afb37-5fdd-4340-a6b4-a6541593e398} + + + {4a8a695a-a103-4b1f-b314-0ec19a253119} + + + {d8a83b5e-9a23-4124-824f-eab37880cb08} + + + {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} + + + {ca669b16-b8bb-4654-993f-fffa44c914f1} + + + {26365f16-ff52-4e80-a01b-2ca020376c93} + + + {7263403a-7550-4aa2-a724-f622ab200eed} + + + + + Header Files + + + Header Files\include + + + Header Files + + + Header Files\Client + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog + + + Header Files\include\spdlog\cfg + + + Header Files\include\spdlog\cfg + + + Header Files\include\spdlog\cfg + + + Header Files\include\spdlog\cfg + + + Header Files\include\spdlog\fmt + + + Header Files\include\spdlog\fmt + + + Header Files\include\spdlog\fmt + + + Header Files\include\spdlog\fmt + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\sinks + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\spdlog\details + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson + + + Header Files\include\rapidjson\error + + + Header Files\include\rapidjson\error + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\internal + + + Header Files\include\rapidjson\msinttypes + + + Header Files\include\rapidjson\msinttypes + + + Header Files\Server\Authentication + + + Header Files\include + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\openssl\internal + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\include\libcurl + + + Header Files\Server\Authentication + + + Header Files\Client + + + Header Files\Client + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\Server\Scripted + + + Header Files\Dedicated Server + + + Header Files + + + Source Files\Exploit Fixes + + + Header Files + + + Header Files + + + Header Files + + + 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\Console + + + Header Files\Console + + + Header Files\Game Functions + + + Header Files\Game Functions + + + Header Files\Game Functions + + + Header Files\Mods\Compiled Assets + + + Header Files\Game Functions + + + Header Files\Filesystem + + + Header Files\Math + + + Header Files\Server + + + Header Files\Hooks + + + Header Files + + + Header Files\Squirrel + + + Header Files\Squirrel + + + Header Files\Math + + + + + Source Files + + + Source Files\Dedicated Server + + + Source Files\Client + + + Source Files\Mods + + + Source Files\Mods\Compiled Assets + + + Source Files\Server\Authentication + + + Source Files\Mods\Compiled Assets + + + Source Files\Client + + + Source Files\Client + + + Source Files\Dedicated Server + + + Source Files\Mods\Compiled Assets + + + Source Files\Client + + + Source Files\Server + + + Source Files\Server\Authentication + + + Source Files\Client + + + Source Files\Client + + + Source Files\Client + + + Source Files\Server + + + Source Files\Client + + + Source Files + + + Source Files\Client + + + Source Files\Client + + + Source Files + + + Source Files\Client + + + Source Files\Client\Scripted + + + Source Files\Client\Scripted + + + Source Files\Client\Scripted + + + Source Files\Client\Scripted + + + Source Files\Client\Scripted + + + Source Files\Client\Scripted + + + Source Files\Server\Scripted + + + Source Files\Server\Scripted + + + Source Files\Client + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + 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\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 + + + Source Files\Server + + + Source Files + + + Source Files\Hooks + + + Source Files\Exploit Fixes + + + Source Files\Scripted + + + Source Files\Scripted + + + Source Files\Squirrel + + + Source Files\Scripted + + + + + Source Files\Client + + + + + Header Files\include\spdlog\fmt\bundled + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\openssl + + + Header Files\include\openssl\crypto + + + Header Files\include\openssl\crypto + + \ No newline at end of file diff --git a/NorthstarDLL/audio.cpp b/NorthstarDLL/audio.cpp index 6d52a071..b1592b6f 100644 --- a/NorthstarDLL/audio.cpp +++ b/NorthstarDLL/audio.cpp @@ -1,13 +1,15 @@ #include "pch.h" #include "audio.h" #include "dedicated.h" +#include "convar.h" #include "rapidjson/error/en.h" #include #include #include #include -#include "convar.h" + +AUTOHOOK_INIT() extern "C" { @@ -229,10 +231,8 @@ EventOverrideData::EventOverrideData(const std::string& data, const fs::path& pa } // read from after the header first to preserve the empty header, then read the header last - wavStream.seekg(sizeof(EMPTY_WAVE), std::ios::beg); - wavStream.read(reinterpret_cast(&data[sizeof(EMPTY_WAVE)]), fileSize - sizeof(EMPTY_WAVE)); wavStream.seekg(0, std::ios::beg); - wavStream.read(reinterpret_cast(data), sizeof(EMPTY_WAVE)); + wavStream.read(reinterpret_cast(data), fileSize); wavStream.close(); spdlog::info("Finished async read of audio sample {}", pathString); @@ -315,6 +315,7 @@ void CustomAudioManager::ClearAudioOverrides() { // stop all miles sounds beforehand // miles_stop_all + MilesStopAll(); // this is cancer but it works @@ -323,15 +324,12 @@ void CustomAudioManager::ClearAudioOverrides() // slightly (very) bad // wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them - std::unique_lock lock(g_CustomAudioManager.m_loadingMutex); + std::unique_lock lock(m_loadingMutex); m_loadedAudioOverrides.clear(); m_loadedAudioOverridesRegex.clear(); } -typedef bool (*LoadSampleMetadata_Type)(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType); -LoadSampleMetadata_Type LoadSampleMetadata_Original; - template Iter select_randomly(Iter start, Iter end, RandomGenerator& g) { std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1); @@ -368,6 +366,26 @@ bool ShouldPlayAudioEvent(const char* eventName, const std::shared_ptrsecond; if (!ShouldPlayAudioEvent(eventName, overrideData)) - return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType); + return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); void* data = 0; unsigned int dataLength = 0; @@ -454,7 +472,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal( if (!data) { spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName); - return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType); + return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); } audioBuffer = data; @@ -465,51 +483,25 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal( *(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength; // 64 - Auto-detect sample type - bool res = LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, 64); + bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64); if (!res) spdlog::error("LoadSampleMetadata failed! The game will crash :("); return res; } -// DO NOT TOUCH THIS FUNCTION -// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer. -bool __fastcall LoadSampleMetadata_Hook(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType) -{ - uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent(); - - // Raw source, used for voice data only - if (audioType == 0) - return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType); - - return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType); -} - -typedef bool (*MilesLog_Type)(int level, const char* string); -MilesLog_Type MilesLog_Original; - -void __fastcall MilesLog_Hook(int level, const char* string) +// clang-format off +AUTOHOOK(MilesLog, client.dll + 0x57DAD0, +void, __fastcall, (int level, const char* string)) +// clang-format on { spdlog::info("[MSS] {} - {}", level, string); } -void InitialiseMilesAudioHooks(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module)) { - Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, ""); - - if (IsDedicatedServer()) - return; - - uintptr_t milesAudioBase = (uintptr_t)GetModuleHandleA("mileswin64.dll"); + AUTOHOOK_DISPATCH() - if (!milesAudioBase) - return spdlog::error("miles audio not found :terror:"); - - HookEnabler hook; - - ENABLER_CREATEHOOK( - hook, (char*)milesAudioBase + 0xF110, &LoadSampleMetadata_Hook, reinterpret_cast(&LoadSampleMetadata_Original)); - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x57DAD0, &MilesLog_Hook, reinterpret_cast(&MilesLog_Original)); - - MilesStopAll = (MilesStopAll_Type)((char*)baseAddress + 0x580850); + Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, ""); + MilesStopAll = module.Offset(0x580850).As(); } diff --git a/NorthstarDLL/audio.h b/NorthstarDLL/audio.h index bf3f0d87..26cda205 100644 --- a/NorthstarDLL/audio.h +++ b/NorthstarDLL/audio.h @@ -5,8 +5,6 @@ #include #include -namespace fs = std::filesystem; - enum class AudioSelectionStrategy { INVALID = -1, @@ -46,5 +44,3 @@ class CustomAudioManager }; extern CustomAudioManager g_CustomAudioManager; - -void InitialiseMilesAudioHooks(HMODULE baseAddress); diff --git a/NorthstarDLL/bansystem.cpp b/NorthstarDLL/bansystem.cpp index c15cf115..25c0e6bf 100644 --- a/NorthstarDLL/bansystem.cpp +++ b/NorthstarDLL/bansystem.cpp @@ -1,26 +1,28 @@ +#pragma once #include "pch.h" #include "bansystem.h" #include "serverauthentication.h" +#include "maxplayers.h" #include "concommand.h" -#include "miscserverscript.h" -#include +#include "r2server.h" +#include "r2engine.h" #include "nsprefix.h" -#include + +#include const char* BANLIST_PATH_SUFFIX = "/banlist.txt"; const char BANLIST_COMMENT_CHAR = '#'; -ServerBanSystem* g_ServerBanSystem; +ServerBanSystem* g_pBanSystem; void ServerBanSystem::OpenBanlist() { - std::ifstream enabledModsStream(GetNorthstarPrefix() + "/banlist.txt"); - std::stringstream enabledModsStringStream; + std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt"); - if (!enabledModsStream.fail()) + if (!banlistStream.fail()) { std::string line; - while (std::getline(enabledModsStream, line)) + while (std::getline(banlistStream, line)) { // ignore line if first char is # or line is empty if (line == "" || line.front() == BANLIST_COMMENT_CHAR) @@ -41,7 +43,7 @@ void ServerBanSystem::OpenBanlist() m_vBannedUids.push_back(strtoull(uid.c_str(), nullptr, 10)); } - enabledModsStream.close(); + banlistStream.close(); } // open write stream for banlist // dont do this to allow for all time access @@ -182,15 +184,14 @@ void ConCommand_ban(const CCommand& args) if (args.ArgC() < 2) return; - // assuming maxplayers 32 - for (int i = 0; i < 32; i++) + for (int i = 0; i < R2::GetMaxPlayers(); i++) { - void* player = GetPlayerByIndex(i); + R2::CBaseClient* player = &R2::g_pClientArray[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(strtoull((char*)player + 0xF500, nullptr, 10)); - CBaseClient__Disconnect(player, 1, "Banned from server"); + g_pBanSystem->BanUID(strtoull(player->m_UID, nullptr, 10)); + R2::CBaseClient__Disconnect(player, 1, "Banned from server"); break; } } @@ -202,20 +203,20 @@ 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(strtoull(args.Arg(1), nullptr, 10)); + g_pBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10)); } void ConCommand_clearbanlist(const CCommand& args) { - g_ServerBanSystem->ClearBanlist(); + g_pBanSystem->ClearBanlist(); } -void InitialiseBanSystem(HMODULE baseAddress) +ON_DLL_LOAD_RELIESON("engine.dll", BanSystem, ConCommand, (CModule module)) { - g_ServerBanSystem = new ServerBanSystem; - g_ServerBanSystem->OpenBanlist(); + g_pBanSystem = new ServerBanSystem; + g_pBanSystem->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); - RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_NONE); + RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_GAMEDLL); + RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_GAMEDLL); } diff --git a/NorthstarDLL/bansystem.h b/NorthstarDLL/bansystem.h index 3b6ae587..6f180126 100644 --- a/NorthstarDLL/bansystem.h +++ b/NorthstarDLL/bansystem.h @@ -16,6 +16,4 @@ class ServerBanSystem bool IsUIDAllowed(uint64_t uid); }; -extern ServerBanSystem* g_ServerBanSystem; - -void InitialiseBanSystem(HMODULE baseAddress); +extern ServerBanSystem* g_pBanSystem; diff --git a/NorthstarDLL/buildainfile.cpp b/NorthstarDLL/buildainfile.cpp index 24a16f74..8190adba 100644 --- a/NorthstarDLL/buildainfile.cpp +++ b/NorthstarDLL/buildainfile.cpp @@ -1,19 +1,19 @@ #include "pch.h" -#include "buildainfile.h" #include "convar.h" -#include "hookutils.h" +#include "hoststate.h" +#include "r2engine.h" + #include #include -#include "nsmem.h" -namespace fs = std::filesystem; +AUTOHOOK_INIT() const int AINET_VERSION_NUMBER = 57; const int AINET_SCRIPT_VERSION_NUMBER = 21; -const int MAP_VERSION_TEMP = 30; const int PLACEHOLDER_CRC = 0; const int MAX_HULLS = 5; +#pragma pack(push, 1) struct CAI_NodeLink { short srcId; @@ -24,6 +24,7 @@ struct CAI_NodeLink char unk2[5]; int64_t flags; }; +#pragma pack(pop) #pragma pack(push, 1) struct CAI_NodeLinkDisk @@ -33,7 +34,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 @@ -62,6 +65,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) @@ -81,7 +85,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; @@ -106,10 +112,12 @@ struct UnkNodeStruct0 char pad4[132]; char unk5; }; +#pragma pack(pop) int* pUnkStruct0Count; UnkNodeStruct0*** pppUnkNodeStruct0s; +#pragma pack(push, 1) struct UnkLinkStruct1 { short unk0; @@ -119,10 +127,12 @@ struct UnkLinkStruct1 char unk4; char unk5; }; +#pragma pack(pop) int* pUnkLinkStruct1Count; UnkLinkStruct1*** pppUnkStruct1s; +#pragma pack(push, 1) struct CAI_ScriptNode { float x; @@ -130,7 +140,9 @@ struct CAI_ScriptNode float z; uint64_t scriptdata; }; +#pragma pack(pop) +#pragma pack(push, 1) struct CAI_Network { // +0 @@ -160,16 +172,16 @@ struct CAI_Network // +84176 CAI_Node** nodes; }; +#pragma pack(pop) char** pUnkServerMapversionGlobal; -char* pMapName; ConVar* Cvar_ns_ai_dumpAINfileFromLoad; void DumpAINInfo(CAI_Network* aiNetwork) { - fs::path writePath("r2/maps/graphs"); - writePath /= pMapName; + fs::path writePath(fmt::format("{}/maps/graphs", R2::g_pModName)); + writePath /= R2::g_pHostState->m_levelName; writePath += ".ain"; // dump from memory @@ -349,20 +361,20 @@ void DumpAINInfo(CAI_Network* aiNetwork) writeStream.close(); } -typedef void (*CAI_NetworkBuilder__BuildType)(void* builder, CAI_Network* aiNetwork, void* unknown); -CAI_NetworkBuilder__BuildType CAI_NetworkBuilder__Build; - -void CAI_NetworkBuilder__BuildHook(void* builder, CAI_Network* aiNetwork, void* unknown) +// clang-format off +AUTOHOOK(CAI_NetworkBuilder__Build, server.dll + 0x385E20, +void, __fastcall, (void* builder, CAI_Network* aiNetwork, void* unknown)) +// clang-format on { CAI_NetworkBuilder__Build(builder, aiNetwork, unknown); DumpAINInfo(aiNetwork); } -typedef void (*LoadAINFileType)(void* aimanager, void* buf, const char* filename); -LoadAINFileType LoadAINFile; - -void LoadAINFileHook(void* aimanager, void* buf, const char* filename) +// clang-format off +AUTOHOOK(LoadAINFile, server.dll + 0x3933A0, +void, __fastcall, (void* aimanager, void* buf, const char* filename)) +// clang-format on { LoadAINFile(aimanager, buf, filename); @@ -373,28 +385,16 @@ void LoadAINFileHook(void* aimanager, void* buf, const char* filename) } } -void InitialiseBuildAINFileHooks(HMODULE baseAddress) +ON_DLL_LOAD("server.dll", BuildAINFile, (CModule module)) { + AUTOHOOK_DISPATCH() + Cvar_ns_ai_dumpAINfileFromLoad = new ConVar( "ns_ai_dumpAINfileFromLoad", "0", FCVAR_NONE, "For debugging: whether we should dump ain data for ains loaded from disk"); - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x385E20, &CAI_NetworkBuilder__BuildHook, reinterpret_cast(&CAI_NetworkBuilder__Build)); - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x3933A0, &LoadAINFileHook, reinterpret_cast(&LoadAINFile)); - - pUnkStruct0Count = (int*)((char*)baseAddress + 0x1063BF8); - pppUnkNodeStruct0s = (UnkNodeStruct0***)((char*)baseAddress + 0x1063BE0); - - pUnkLinkStruct1Count = (int*)((char*)baseAddress + 0x1063AA8); - pppUnkStruct1s = (UnkLinkStruct1***)((char*)baseAddress + 0x1063A90); - pUnkServerMapversionGlobal = (char**)((char*)baseAddress + 0xBFBE08); - pMapName = (char*)baseAddress + 0x1053370; - - uintptr_t base = (uintptr_t)baseAddress; - - // remove a check that prevents a logging function in link generation from working - // due to the sheer amount of logging this is a massive perf hit to generation, but spewlog_enable 0 exists so whatever - NSMem::NOP(base + 0x3889B6, 6); - NSMem::NOP(base + 0x3889BF, 6); + pUnkStruct0Count = module.Offset(0x1063BF8).As(); + pppUnkNodeStruct0s = module.Offset(0x1063BE0).As(); + pUnkLinkStruct1Count = module.Offset(0x1063AA8).As(); + pppUnkStruct1s = module.Offset(0x1063A90).As(); + pUnkServerMapversionGlobal = module.Offset(0xBFBE08).As(); } diff --git a/NorthstarDLL/buildainfile.h b/NorthstarDLL/buildainfile.h deleted file mode 100644 index 9ec01f18..00000000 --- a/NorthstarDLL/buildainfile.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseBuildAINFileHooks(HMODULE baseAddress); diff --git a/NorthstarDLL/chatcommand.cpp b/NorthstarDLL/chatcommand.cpp index 2aa51737..37c438f3 100644 --- a/NorthstarDLL/chatcommand.cpp +++ b/NorthstarDLL/chatcommand.cpp @@ -1,13 +1,11 @@ #include "pch.h" #include "convar.h" #include "concommand.h" -#include "chatcommand.h" #include "localchatwriter.h" // note: isIngameChat is an int64 because the whole register the arg is stored in needs to be 0'd out to work // if isIngameChat is false, we use network chat instead -typedef void(__fastcall* ClientSayTextType)(void* a1, const char* message, __int64 isIngameChat, bool isTeamChat); -ClientSayTextType ClientSayText; +void(__fastcall* ClientSayText)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat); void ConCommand_say(const CCommand& args) { @@ -29,9 +27,9 @@ void ConCommand_log(const CCommand& args) } } -void InitialiseChatCommands(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientChatCommand, ConCommand, (CModule module)) { - ClientSayText = (ClientSayTextType)((char*)baseAddress + 0x54780); + ClientSayText = module.Offset(0x54780).As(); RegisterConCommand("say", ConCommand_say, "Enters a message in public chat", FCVAR_CLIENTDLL); RegisterConCommand("say_team", ConCommand_say_team, "Enters a message in team chat", FCVAR_CLIENTDLL); RegisterConCommand("log", ConCommand_log, "Log a message to the local chat window", FCVAR_CLIENTDLL); diff --git a/NorthstarDLL/chatcommand.h b/NorthstarDLL/chatcommand.h deleted file mode 100644 index 546d0126..00000000 --- a/NorthstarDLL/chatcommand.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseChatCommands(HMODULE baseAddress); diff --git a/NorthstarDLL/clientauthhooks.cpp b/NorthstarDLL/clientauthhooks.cpp index 3235e6cd..cd01ad91 100644 --- a/NorthstarDLL/clientauthhooks.cpp +++ b/NorthstarDLL/clientauthhooks.cpp @@ -1,12 +1,9 @@ #include "pch.h" -#include "clientauthhooks.h" -#include "hookutils.h" -#include "gameutils.h" #include "masterserver.h" #include "convar.h" +#include "r2client.h" -typedef void (*AuthWithStryderType)(void* a1); -AuthWithStryderType AuthWithStryder; +AUTOHOOK_INIT() ConVar* Cvar_ns_has_agreed_to_send_token; @@ -15,51 +12,53 @@ const int NOT_DECIDED_TO_SEND_TOKEN = 0; const int AGREED_TO_SEND_TOKEN = 1; const int DISAGREED_TO_SEND_TOKEN = 2; -typedef char* (*Auth3PTokenType)(); -Auth3PTokenType Auth3PToken; - -char* token_location = 0x0; - -void AuthWithStryderHook(void* a1) +// clang-format off +AUTOHOOK(AuthWithStryder, engine.dll + 0x1843A0, +void, __fastcall, (void* a1)) +// clang-format on { // 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(g_LocalPlayerUserID, g_LocalPlayerOriginToken); + !g_pMasterServerManager->m_bOriginAuthWithMasterServerInProgress) + g_pMasterServerManager->AuthenticateOriginWithMasterServer(R2::g_pLocalPlayerUserID, R2::g_pLocalPlayerOriginToken); // invalidate key so auth will fail - *g_LocalPlayerOriginToken = 0; + *R2::g_pLocalPlayerOriginToken = 0; } AuthWithStryder(a1); } -char* Auth3PTokenHook() +char* p3PToken; + +// clang-format off +AUTOHOOK(Auth3PToken, engine.dll + 0x183760, +char*, __fastcall, ()) +// clang-format on { - if (g_MasterServerManager->m_sOwnClientAuthToken[0] != 0) + if (g_pMasterServerManager->m_sOwnClientAuthToken[0]) { - memset(token_location, 0x0, 1024); - strcpy(token_location, "Protocol 3: Protect the Pilot"); + memset(p3PToken, 0x0, 1024); + strcpy(p3PToken, "Protocol 3: Protect the Pilot"); } return Auth3PToken(); } -void InitialiseClientAuthHooks(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientAuthHooks, ConVar, (CModule module)) { + AUTOHOOK_DISPATCH() + + p3PToken = module.Offset(0x13979D80).As(); + // this cvar will save to cfg once initially agreed with Cvar_ns_has_agreed_to_send_token = new ConVar( "ns_has_agreed_to_send_token", "0", FCVAR_ARCHIVE_PLAYERPROFILE, "whether the user has agreed to send their origin token to the northstar masterserver"); - - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1843A0, &AuthWithStryderHook, reinterpret_cast(&AuthWithStryder)); - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x183760, &Auth3PTokenHook, reinterpret_cast(&Auth3PToken)); - token_location = (char*)baseAddress + 0x13979D80; } diff --git a/NorthstarDLL/clientauthhooks.h b/NorthstarDLL/clientauthhooks.h deleted file mode 100644 index 4d7e7ccf..00000000 --- a/NorthstarDLL/clientauthhooks.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -void InitialiseClientAuthHooks(HMODULE baseAddress); diff --git a/NorthstarDLL/clientchathooks.cpp b/NorthstarDLL/clientchathooks.cpp index 74418c06..a7a42689 100644 --- a/NorthstarDLL/clientchathooks.cpp +++ b/NorthstarDLL/clientchathooks.cpp @@ -1,29 +1,22 @@ #include "pch.h" -#include "clientchathooks.h" -#include #include "squirrel.h" #include "serverchathooks.h" #include "localchatwriter.h" -typedef void(__fastcall* CHudChat__AddGameLineType)(void* self, const char* message, int fromPlayerId, bool isteam, bool isdead); -CHudChat__AddGameLineType CHudChat__AddGameLine; +#include -struct ChatTags -{ - bool whisper; - bool team; - bool dead; -}; +AUTOHOOK_INIT() -static void CHudChat__AddGameLineHook(void* self, const char* message, int inboxId, bool isTeam, bool isDead) +// clang-format off +AUTOHOOK(CHudChat__AddGameLine, client.dll + 0x22E580, +void, __fastcall, (void* self, const char* message, int inboxId, bool isTeam, bool isDead)) +// clang-format on { // This hook is called for each HUD, but we only want our logic to run once. if (self != *CHudChat::allHuds) - { return; - } - if (g_ClientSquirrelManager->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR) + if (g_pSquirrel->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR) { int senderId = inboxId & CUSTOM_MESSAGE_INDEX_MASK; bool isAnonymous = senderId == 0; @@ -38,58 +31,53 @@ static void CHudChat__AddGameLineHook(void* self, const char* message, int inbox payload = message + 1; } - g_ClientSquirrelManager->pusharg((int)senderId - 1); - g_ClientSquirrelManager->pusharg(payload); - g_ClientSquirrelManager->pusharg(isTeam); - g_ClientSquirrelManager->pusharg(isDead); - g_ClientSquirrelManager->pusharg(type); - g_ClientSquirrelManager->call(5); + g_pSquirrel->pushinteger(g_pSquirrel->m_pSQVM->sqvm, (int)senderId - 1); + g_pSquirrel->pushstring(g_pSquirrel->m_pSQVM->sqvm, payload); + g_pSquirrel->pushbool(g_pSquirrel->m_pSQVM->sqvm, isTeam); + g_pSquirrel->pushbool(g_pSquirrel->m_pSQVM->sqvm, isDead); + g_pSquirrel->pushinteger(g_pSquirrel->m_pSQVM->sqvm, type); + g_pSquirrel->call(g_pSquirrel->m_pSQVM->sqvm, 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(HSquirrelVM* sqvm) { - int context = ClientSq_getinteger(sqvm, 1); - const char* str = ClientSq_getstring(sqvm, 2); + int context = g_pSquirrel->getinteger(sqvm, 1); + const char* str = g_pSquirrel->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(HSquirrelVM* sqvm) { - int context = ClientSq_getinteger(sqvm, 1); - const char* str = ClientSq_getstring(sqvm, 2); + int context = g_pSquirrel->getinteger(sqvm, 1); + const char* str = g_pSquirrel->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(HSquirrelVM* sqvm) { - int context = ClientSq_getinteger(sqvm, 1); - const char* str = ClientSq_getstring(sqvm, 2); + int context = g_pSquirrel->getinteger(sqvm, 1); + const char* str = g_pSquirrel->getstring(sqvm, 2); LocalChatWriter((LocalChatWriter::Context)context).WriteLine(str); - return SQRESULT_NOTNULL; + return SQRESULT_NULL; } -void InitialiseClientChatHooks(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientChatHooks, ClientSquirrel, (CModule module)) { - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x22E580, &CHudChat__AddGameLineHook, reinterpret_cast(&CHudChat__AddGameLine)); + AUTOHOOK_DISPATCH() - g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite); - g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw); - g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine); + g_pSquirrel->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite); + g_pSquirrel->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw); + g_pSquirrel->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine); } diff --git a/NorthstarDLL/clientchathooks.h b/NorthstarDLL/clientchathooks.h deleted file mode 100644 index 79a1b3e2..00000000 --- a/NorthstarDLL/clientchathooks.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include "pch.h" -#include "serverchathooks.h" - -void InitialiseClientChatHooks(HMODULE baseAddress); diff --git a/NorthstarDLL/clientruihooks.cpp b/NorthstarDLL/clientruihooks.cpp index a05ef217..3cb08368 100644 --- a/NorthstarDLL/clientruihooks.cpp +++ b/NorthstarDLL/clientruihooks.cpp @@ -1,13 +1,14 @@ #include "pch.h" -#include "clientruihooks.h" #include "convar.h" -ConVar* Cvar_rui_drawEnable; +AUTOHOOK_INIT() -typedef char (*DrawRUIFuncType)(void* a1, float* a2); -DrawRUIFuncType DrawRUIFunc; +ConVar* Cvar_rui_drawEnable; -char DrawRUIFuncHook(void* a1, float* a2) +// clang-format off +AUTOHOOK(DrawRUIFunc, engine.dll + 0xFC500, +bool, __fastcall, (void* a1, float* a2)) +// clang-format on { if (!Cvar_rui_drawEnable->GetBool()) return 0; @@ -15,10 +16,9 @@ char DrawRUIFuncHook(void* a1, float* a2) return DrawRUIFunc(a1, a2); } -void InitialiseEngineClientRUIHooks(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", RUI, ConVar, (CModule module)) { - Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn"); + AUTOHOOK_DISPATCH() - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xFC500, &DrawRUIFuncHook, reinterpret_cast(&DrawRUIFunc)); + Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn"); } diff --git a/NorthstarDLL/clientruihooks.h b/NorthstarDLL/clientruihooks.h deleted file mode 100644 index 57b79d9b..00000000 --- a/NorthstarDLL/clientruihooks.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -void InitialiseEngineClientRUIHooks(HMODULE baseAddress); diff --git a/NorthstarDLL/clientvideooverrides.cpp b/NorthstarDLL/clientvideooverrides.cpp index 659bf23c..b9954bf2 100644 --- a/NorthstarDLL/clientvideooverrides.cpp +++ b/NorthstarDLL/clientvideooverrides.cpp @@ -1,21 +1,21 @@ #include "pch.h" -#include "clientvideooverrides.h" #include "modmanager.h" -#include "nsmem.h" -typedef void* (*BinkOpenType)(const char* path, uint32_t flags); -BinkOpenType BinkOpen; +AUTOHOOK_INIT() -void* BinkOpenHook(const char* path, uint32_t flags) +// clang-format off +AUTOHOOK_PROCADDRESS(BinkOpen, bink2w64.dll, BinkOpen, +void*, __fastcall, (const char* path, uint32_t flags)) +// clang-format on { std::string filename(fs::path(path).filename().string()); spdlog::info("BinkOpen {}", filename); // figure out which mod is handling the bink Mod* fileOwner = nullptr; - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { - if (!mod.Enabled) + if (!mod.m_bEnabled) continue; if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end()) @@ -25,23 +25,18 @@ void* BinkOpenHook(const char* path, uint32_t flags) if (fileOwner) { // create new path - fs::path binkPath(fileOwner->ModDirectory / "media" / filename); + fs::path binkPath(fileOwner->m_ModDirectory / "media" / filename); return BinkOpen(binkPath.string().c_str(), flags); } else return BinkOpen(path, flags); } -void InitialiseEngineClientVideoOverrides(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT("client.dll", BinkVideo, (CModule module)) { + AUTOHOOK_DISPATCH() + // remove engine check for whether the bik we're trying to load exists in r2/media, as this will fail for biks in mods // note: the check in engine is actually unnecessary, so it's just useless in practice and we lose nothing by removing it - NSMem::NOP((uintptr_t)baseAddress + 0x459AD, 6); - - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, - reinterpret_cast(GetProcAddress(GetModuleHandleA("bink2w64.dll"), "BinkOpen")), - &BinkOpenHook, - reinterpret_cast(&BinkOpen)); + module.Offset(0x459AD).NOP(6); } diff --git a/NorthstarDLL/clientvideooverrides.h b/NorthstarDLL/clientvideooverrides.h deleted file mode 100644 index 8fdbba3f..00000000 --- a/NorthstarDLL/clientvideooverrides.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -void InitialiseEngineClientVideoOverrides(HMODULE baseAddress); diff --git a/NorthstarDLL/concommand.cpp b/NorthstarDLL/concommand.cpp index 1bdda91b..6d77e137 100644 --- a/NorthstarDLL/concommand.cpp +++ b/NorthstarDLL/concommand.cpp @@ -1,27 +1,8 @@ #include "pch.h" #include "concommand.h" -#include "gameutils.h" #include "misccommands.h" -#include - -typedef void (*ConCommandConstructorType)( - ConCommand* newCommand, const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, void* parent); -ConCommandConstructorType conCommandConstructor; - -void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags) -{ - spdlog::info("Registering ConCommand {}", name); - - // no need to free this ever really, it should exist as long as game does - ConCommand* newCommand = new ConCommand; - conCommandConstructor(newCommand, name, callback, helpString, flags, nullptr); -} -void InitialiseConCommands(HMODULE baseAddress) -{ - conCommandConstructor = (ConCommandConstructorType)((char*)baseAddress + 0x415F60); - AddMiscConCommands(); -} +#include //----------------------------------------------------------------------------- // Purpose: Returns true if this is a command @@ -57,7 +38,7 @@ bool ConCommandBase::IsRegistered(void) const //----------------------------------------------------------------------------- bool ConCommandBase::IsFlagSet(int nFlags) const { - return false; // !TODO: Returning false on every query? (original implementation in Northstar before ConCommandBase refactor) + return m_nFlags & nFlags; } //----------------------------------------------------------------------------- @@ -138,3 +119,33 @@ char* ConCommandBase::CopyString(const char* szFrom) const } return szTo; } + +typedef void (*ConCommandConstructorType)( + ConCommand* newCommand, const char* name, FnCommandCallback_t callback, const char* helpString, int flags, void* parent); +ConCommandConstructorType ConCommandConstructor; + +void RegisterConCommand(const char* name, FnCommandCallback_t callback, const char* helpString, int flags) +{ + spdlog::info("Registering ConCommand {}", name); + + // no need to free this ever really, it should exist as long as game does + ConCommand* newCommand = new ConCommand; + ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr); +} + +void RegisterConCommand( + const char* name, FnCommandCallback_t callback, const char* helpString, int flags, FnCommandCompletionCallback completionCallback) +{ + spdlog::info("Registering ConCommand {}", name); + + // no need to free this ever really, it should exist as long as game does + ConCommand* newCommand = new ConCommand; + ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr); + newCommand->m_pCompletionCallback = completionCallback; +} + +ON_DLL_LOAD("engine.dll", ConCommand, (CModule module)) +{ + ConCommandConstructor = module.Offset(0x415F60).As(); + AddMiscConCommands(); +} diff --git a/NorthstarDLL/concommand.h b/NorthstarDLL/concommand.h index 15e289d8..89363bc7 100644 --- a/NorthstarDLL/concommand.h +++ b/NorthstarDLL/concommand.h @@ -72,6 +72,20 @@ inline const char* CCommand::operator[](int nIndex) const return Arg(nIndex); } +//----------------------------------------------------------------------------- +// Called when a ConCommand needs to execute +//----------------------------------------------------------------------------- +typedef void (*FnCommandCallback_t)(const CCommand& command); + +#define COMMAND_COMPLETION_MAXITEMS 64 +#define COMMAND_COMPLETION_ITEM_LENGTH 128 + +//----------------------------------------------------------------------------- +// Returns 0 to COMMAND_COMPLETION_MAXITEMS worth of completion strings +//----------------------------------------------------------------------------- +typedef int (*__fastcall FnCommandCompletionCallback)( + const char* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]); + // From r5reloaded class ConCommandBase { @@ -113,8 +127,8 @@ class ConCommand : public ConCommandBase void Init(void); bool IsCommand(void) const; - void* m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase. - void* m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax'). + FnCommandCallback_t m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase. + FnCommandCompletionCallback m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax'). int m_nCallbackFlags {}; // 0x0050 char pad_0054[4]; // 0x0054 int unk0; // 0x0058 @@ -122,6 +136,5 @@ class ConCommand : public ConCommandBase }; // Size: 0x0060 void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags); -void InitialiseConCommands(HMODULE baseAddress); - -#define MAKE_CONCMD(name, helpStr, flags, fn) RegisterConCommand(name, fn, helpStr, flags); +void RegisterConCommand( + const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, FnCommandCompletionCallback completionCallback); diff --git a/NorthstarDLL/context.cpp b/NorthstarDLL/context.cpp deleted file mode 100644 index 19ee85a3..00000000 --- a/NorthstarDLL/context.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "pch.h" -#include "context.h" - -const char* GetContextName(ScriptContext context) -{ - if (context == ScriptContext::CLIENT) - return "CLIENT"; - else if (context == ScriptContext::SERVER) - return "SERVER"; - else if (context == ScriptContext::UI) - return "UI"; - - return ""; -} diff --git a/NorthstarDLL/context.h b/NorthstarDLL/context.h deleted file mode 100644 index d872f738..00000000 --- a/NorthstarDLL/context.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -enum class ScriptContext : int -{ - SERVER, - CLIENT, - UI, - NONE -}; - -const char* GetContextName(ScriptContext context); diff --git a/NorthstarDLL/convar.cpp b/NorthstarDLL/convar.cpp index bf8edbf9..c8f63922 100644 --- a/NorthstarDLL/convar.cpp +++ b/NorthstarDLL/convar.cpp @@ -1,13 +1,11 @@ -#include - #include "pch.h" #include "bits.h" #include "cvar.h" #include "convar.h" -#include "hookutils.h" -#include "gameutils.h" #include "sourceinterface.h" +#include + typedef void (*ConVarRegisterType)( ConVar* pConVar, const char* pszName, @@ -27,25 +25,19 @@ ConVarMallocType conVarMalloc; void* g_pConVar_Vtable = nullptr; void* g_pIConVar_Vtable = nullptr; -typedef bool (*CvarIsFlagSetType)(ConVar* self, int flags); -CvarIsFlagSetType CvarIsFlagSet; - //----------------------------------------------------------------------------- // Purpose: ConVar interface initialization //----------------------------------------------------------------------------- -void InitialiseConVars(HMODULE baseAddress) +ON_DLL_LOAD("engine.dll", ConVar, (CModule module)) { - conVarMalloc = (ConVarMallocType)((char*)baseAddress + 0x415C20); - conVarRegister = (ConVarRegisterType)((char*)baseAddress + 0x417230); - - g_pConVar_Vtable = (char*)baseAddress + 0x67FD28; - g_pIConVar_Vtable = (char*)baseAddress + 0x67FDC8; + conVarMalloc = module.Offset(0x415C20).As(); + conVarRegister = module.Offset(0x417230).As(); - g_pCVarInterface = new SourceInterface("vstdlib.dll", "VEngineCvar007"); - g_pCVar = *g_pCVarInterface; + g_pConVar_Vtable = module.Offset(0x67FD28); + g_pIConVar_Vtable = module.Offset(0x67FDC8); - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x417FA0, &ConVar::IsFlagSet, reinterpret_cast(&CvarIsFlagSet)); + R2::g_pCVarInterface = new SourceInterface("vstdlib.dll", "VEngineCvar007"); + R2::g_pCVar = *R2::g_pCVarInterface; } //----------------------------------------------------------------------------- @@ -74,7 +66,7 @@ ConVar::ConVar( float fMin, bool bMax, float fMax, - void* pCallback) + FnChangeCallback_t pCallback) { spdlog::info("Registering Convar {}", pszName); @@ -476,16 +468,12 @@ bool ConVar::IsCommand(void) const //----------------------------------------------------------------------------- // Purpose: Test each ConVar query before setting the value. -// Input : *pConVar - nFlags +// Input : nFlags // Output : False if change is permitted, true if not. //----------------------------------------------------------------------------- -bool ConVar::IsFlagSet(ConVar* pConVar, int nFlags) +bool ConVar::IsFlagSet(int nFlags) const { - // unrestrict FCVAR_DEVELOPMENTONLY and FCVAR_HIDDEN - if (pConVar && (nFlags == FCVAR_DEVELOPMENTONLY || nFlags == FCVAR_HIDDEN)) - return false; - - return CvarIsFlagSet(pConVar, nFlags); + return m_ConCommandBase.m_nFlags & nFlags; } //----------------------------------------------------------------------------- diff --git a/NorthstarDLL/convar.h b/NorthstarDLL/convar.h index 15f1f562..176d0d72 100644 --- a/NorthstarDLL/convar.h +++ b/NorthstarDLL/convar.h @@ -58,15 +58,58 @@ // ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd. #define FCVAR_SERVER_CANNOT_QUERY \ (1 << 29) // If this is set, then the server is not allowed to query this cvar's value (via IServerPluginHelpers::StartQueryCvarValue). + +// !!!NOTE!!! : this is likely incorrect, there are multiple concommands that the vanilla game registers with this flag that 100% should not +// be remotely executable i.e. multiple commands that only exist on client (screenshot, joystick_initialize) we now use +// FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS in all places this flag was previously used #define FCVAR_CLIENTCMD_CAN_EXECUTE \ (1 << 30) // IVEngineClient::ClientCmd is allowed to execute this command. // Note: IVEngineClient::ClientCmd_Unrestricted can run any client command. #define FCVAR_ACCESSIBLE_FROM_THREADS (1 << 25) // used as a debugging tool necessary to check material system thread convars + +// TODO: could be cool to repurpose these for northstar use somehow? // #define FCVAR_AVAILABLE (1<<26) // #define FCVAR_AVAILABLE (1<<27) // #define FCVAR_AVAILABLE (1<<31) +// flag => string stuff +const std::multimap g_PrintCommandFlags = { + {FCVAR_UNREGISTERED, "UNREGISTERED"}, + {FCVAR_DEVELOPMENTONLY, "DEVELOPMENTONLY"}, + {FCVAR_GAMEDLL, "GAMEDLL"}, + {FCVAR_CLIENTDLL, "CLIENTDLL"}, + {FCVAR_HIDDEN, "HIDDEN"}, + {FCVAR_PROTECTED, "PROTECTED"}, + {FCVAR_SPONLY, "SPONLY"}, + {FCVAR_ARCHIVE, "ARCHIVE"}, + {FCVAR_NOTIFY, "NOTIFY"}, + {FCVAR_USERINFO, "USERINFO"}, + + // TODO: PRINTABLEONLY and GAMEDLL_FOR_REMOTE_CLIENTS are both 1<<10, one is for vars and one is for commands + // this fucking sucks i think + {FCVAR_PRINTABLEONLY, "PRINTABLEONLY"}, + {FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, "GAMEDLL_FOR_REMOTE_CLIENTS"}, + + {FCVAR_UNLOGGED, "UNLOGGED"}, + {FCVAR_NEVER_AS_STRING, "NEVER_AS_STRING"}, + {FCVAR_REPLICATED, "REPLICATED"}, + {FCVAR_CHEAT, "CHEAT"}, + {FCVAR_SS, "SS"}, + {FCVAR_DEMO, "DEMO"}, + {FCVAR_DONTRECORD, "DONTRECORD"}, + {FCVAR_SS_ADDED, "SS_ADDED"}, + {FCVAR_RELEASE, "RELEASE"}, + {FCVAR_RELOAD_MATERIALS, "RELOAD_MATERIALS"}, + {FCVAR_RELOAD_TEXTURES, "RELOAD_TEXTURES"}, + {FCVAR_NOT_CONNECTED, "NOT_CONNECTED"}, + {FCVAR_MATERIAL_SYSTEM_THREAD, "MATERIAL_SYSTEM_THREAD"}, + {FCVAR_ARCHIVE_PLAYERPROFILE, "ARCHIVE_PLAYERPROFILE"}, + {FCVAR_SERVER_CAN_EXECUTE, "SERVER_CAN_EXECUTE"}, + {FCVAR_SERVER_CANNOT_QUERY, "SERVER_CANNOT_QUERY"}, + {FCVAR_CLIENTCMD_CAN_EXECUTE, "UNKNOWN"}, + {FCVAR_ACCESSIBLE_FROM_THREADS, "ACCESSIBLE_FROM_THREADS"}}; + //----------------------------------------------------------------------------- // Forward declarations //----------------------------------------------------------------------------- @@ -74,6 +117,8 @@ class ConCommandBase; class ConCommand; class ConVar; +typedef void (*FnChangeCallback_t)(ConVar* var, const char* pOldValue, float flOldValue); + //----------------------------------------------------------------------------- // Purpose: A console variable //----------------------------------------------------------------------------- @@ -91,7 +136,7 @@ class ConVar float fMin, bool bMax, float fMax, - void* pCallback); + FnChangeCallback_t pCallback); ~ConVar(void); const char* GetBaseName(void) const; @@ -125,7 +170,7 @@ class ConVar bool IsRegistered(void) const; bool IsCommand(void) const; - static bool IsFlagSet(ConVar* pConVar, int nFlags); + bool IsFlagSet(int nFlags) const; struct CVValue_t { @@ -145,5 +190,3 @@ class ConVar void* m_pMalloc {}; // 0x0070 char m_pPad80[10] {}; // 0x0080 }; // Size: 0x0080 - -void InitialiseConVars(HMODULE baseAddress); diff --git a/NorthstarDLL/crashhandler.cpp b/NorthstarDLL/crashhandler.cpp new file mode 100644 index 00000000..8e083078 --- /dev/null +++ b/NorthstarDLL/crashhandler.cpp @@ -0,0 +1,216 @@ +#include "pch.h" +#include "crashhandler.h" +#include "dedicated.h" +#include "nsprefix.h" + +#include + +HANDLE hExceptionFilter; + +long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) +{ + static bool logged = false; + if (logged) + return EXCEPTION_CONTINUE_SEARCH; + + if (!IsDebuggerPresent()) + { + const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode; + if (exceptionCode != EXCEPTION_ACCESS_VIOLATION && exceptionCode != EXCEPTION_ARRAY_BOUNDS_EXCEEDED && + exceptionCode != EXCEPTION_DATATYPE_MISALIGNMENT && exceptionCode != EXCEPTION_FLT_DENORMAL_OPERAND && + exceptionCode != EXCEPTION_FLT_DIVIDE_BY_ZERO && exceptionCode != EXCEPTION_FLT_INEXACT_RESULT && + exceptionCode != EXCEPTION_FLT_INVALID_OPERATION && exceptionCode != EXCEPTION_FLT_OVERFLOW && + exceptionCode != EXCEPTION_FLT_STACK_CHECK && exceptionCode != EXCEPTION_FLT_UNDERFLOW && + exceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION && exceptionCode != EXCEPTION_IN_PAGE_ERROR && + exceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO && exceptionCode != EXCEPTION_INT_OVERFLOW && + exceptionCode != EXCEPTION_INVALID_DISPOSITION && exceptionCode != EXCEPTION_NONCONTINUABLE_EXCEPTION && + exceptionCode != EXCEPTION_PRIV_INSTRUCTION && exceptionCode != EXCEPTION_STACK_OVERFLOW) + return EXCEPTION_CONTINUE_SEARCH; + + std::stringstream exceptionCause; + exceptionCause << "Cause: "; + switch (exceptionCode) + { + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_IN_PAGE_ERROR: + { + exceptionCause << "Access Violation" << std::endl; + + auto exceptionInfo0 = exceptionInfo->ExceptionRecord->ExceptionInformation[0]; + auto exceptionInfo1 = exceptionInfo->ExceptionRecord->ExceptionInformation[1]; + + if (!exceptionInfo0) + exceptionCause << "Attempted to read from: 0x" << (void*)exceptionInfo1; + else if (exceptionInfo0 == 1) + exceptionCause << "Attempted to write to: 0x" << (void*)exceptionInfo1; + else if (exceptionInfo0 == 8) + exceptionCause << "Data Execution Prevention (DEP) at: 0x" << (void*)std::hex << exceptionInfo1; + else + exceptionCause << "Unknown access violation at: 0x" << (void*)exceptionInfo1; + + break; + } + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + exceptionCause << "Array bounds exceeded"; + break; + case EXCEPTION_DATATYPE_MISALIGNMENT: + exceptionCause << "Datatype misalignment"; + break; + case EXCEPTION_FLT_DENORMAL_OPERAND: + exceptionCause << "Denormal operand"; + break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + exceptionCause << "Divide by zero (float)"; + break; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + exceptionCause << "Divide by zero (int)"; + break; + case EXCEPTION_FLT_INEXACT_RESULT: + exceptionCause << "Inexact result"; + break; + case EXCEPTION_FLT_INVALID_OPERATION: + exceptionCause << "Invalid operation"; + break; + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_INT_OVERFLOW: + exceptionCause << "Numeric overflow"; + break; + case EXCEPTION_FLT_UNDERFLOW: + exceptionCause << "Numeric underflow"; + break; + case EXCEPTION_FLT_STACK_CHECK: + exceptionCause << "Stack check"; + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + exceptionCause << "Illegal instruction"; + break; + case EXCEPTION_INVALID_DISPOSITION: + exceptionCause << "Invalid disposition"; + break; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + exceptionCause << "Noncontinuable exception"; + break; + case EXCEPTION_PRIV_INSTRUCTION: + exceptionCause << "Priviledged instruction"; + break; + case EXCEPTION_STACK_OVERFLOW: + exceptionCause << "Stack overflow"; + break; + default: + exceptionCause << "Unknown"; + break; + } + + void* exceptionAddress = exceptionInfo->ExceptionRecord->ExceptionAddress; + + HMODULE crashedModuleHandle; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast(exceptionAddress), &crashedModuleHandle); + + MODULEINFO crashedModuleInfo; + GetModuleInformation(GetCurrentProcess(), crashedModuleHandle, &crashedModuleInfo, sizeof(crashedModuleInfo)); + + char crashedModuleFullName[MAX_PATH]; + GetModuleFileNameExA(GetCurrentProcess(), crashedModuleHandle, crashedModuleFullName, MAX_PATH); + char* crashedModuleName = strrchr(crashedModuleFullName, '\\') + 1; + + DWORD64 crashedModuleOffset = ((DWORD64)exceptionAddress) - ((DWORD64)crashedModuleInfo.lpBaseOfDll); + CONTEXT* exceptionContext = exceptionInfo->ContextRecord; + + spdlog::error("Northstar has crashed! a minidump has been written and exception info is available below:"); + spdlog::error(exceptionCause.str()); + spdlog::error("At: {} + {}", crashedModuleName, (void*)crashedModuleOffset); + + PVOID framesToCapture[62]; + int frames = RtlCaptureStackBackTrace(0, 62, framesToCapture, NULL); + for (int i = 0; i < frames; i++) + { + HMODULE backtraceModuleHandle; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast(framesToCapture[i]), &backtraceModuleHandle); + + char backtraceModuleFullName[MAX_PATH]; + GetModuleFileNameExA(GetCurrentProcess(), backtraceModuleHandle, backtraceModuleFullName, MAX_PATH); + char* backtraceModuleName = strrchr(backtraceModuleFullName, '\\') + 1; + + void* actualAddress = (void*)framesToCapture[i]; + void* relativeAddress = (void*)(uintptr_t(actualAddress) - uintptr_t(backtraceModuleHandle)); + + spdlog::error(" {} + {} ({})", backtraceModuleName, relativeAddress, actualAddress); + } + + spdlog::error("RAX: 0x{0:x}", exceptionContext->Rax); + spdlog::error("RBX: 0x{0:x}", exceptionContext->Rbx); + spdlog::error("RCX: 0x{0:x}", exceptionContext->Rcx); + spdlog::error("RDX: 0x{0:x}", exceptionContext->Rdx); + spdlog::error("RSI: 0x{0:x}", exceptionContext->Rsi); + spdlog::error("RDI: 0x{0:x}", exceptionContext->Rdi); + spdlog::error("RBP: 0x{0:x}", exceptionContext->Rbp); + spdlog::error("RSP: 0x{0:x}", exceptionContext->Rsp); + spdlog::error("R8: 0x{0:x}", exceptionContext->R8); + spdlog::error("R9: 0x{0:x}", exceptionContext->R9); + spdlog::error("R10: 0x{0:x}", exceptionContext->R10); + spdlog::error("R11: 0x{0:x}", exceptionContext->R11); + spdlog::error("R12: 0x{0:x}", exceptionContext->R12); + spdlog::error("R13: 0x{0:x}", exceptionContext->R13); + spdlog::error("R14: 0x{0:x}", exceptionContext->R14); + spdlog::error("R15: 0x{0:x}", exceptionContext->R15); + + time_t time = std::time(nullptr); + tm currentTime = *std::localtime(&time); + std::stringstream stream; + stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str()); + + auto hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (hMinidumpFile) + { + MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo; + dumpExceptionInfo.ThreadId = GetCurrentThreadId(); + dumpExceptionInfo.ExceptionPointers = exceptionInfo; + dumpExceptionInfo.ClientPointers = false; + + MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + hMinidumpFile, + MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory), + &dumpExceptionInfo, + nullptr, + nullptr); + CloseHandle(hMinidumpFile); + } + else + spdlog::error("Failed to write minidump file {}!", stream.str()); + + if (!IsDedicatedServer()) + MessageBoxA( + 0, "Northstar has crashed! Crash info can be found in R2Northstar/logs", "Northstar has crashed!", MB_ICONERROR | MB_OK); + } + + logged = true; + return EXCEPTION_EXECUTE_HANDLER; +} + +BOOL WINAPI ConsoleHandlerRoutine(DWORD eventCode) +{ + switch (eventCode) + { + case CTRL_CLOSE_EVENT: + // User closed console, shut everything down + spdlog::info("Exiting due to console close..."); + RemoveCrashHandler(); + exit(EXIT_SUCCESS); + return FALSE; + } + + return TRUE; +} + +void InitialiseCrashHandler() +{ + hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter); + SetConsoleCtrlHandler(ConsoleHandlerRoutine, true); +} + +void RemoveCrashHandler() +{ + RemoveVectoredExceptionHandler(hExceptionFilter); +} diff --git a/NorthstarDLL/crashhandler.h b/NorthstarDLL/crashhandler.h new file mode 100644 index 00000000..e0dc6906 --- /dev/null +++ b/NorthstarDLL/crashhandler.h @@ -0,0 +1,4 @@ +#pragma once + +void InitialiseCrashHandler(); +void RemoveCrashHandler(); diff --git a/NorthstarDLL/cvar.cpp b/NorthstarDLL/cvar.cpp index 04594b8f..787790be 100644 --- a/NorthstarDLL/cvar.cpp +++ b/NorthstarDLL/cvar.cpp @@ -23,5 +23,9 @@ std::unordered_map CCvar::DumpToMap() return allConVars; } -SourceInterface* g_pCVarInterface; -CCvar* g_pCVar; +// use the R2 namespace for game funcs +namespace R2 +{ + SourceInterface* g_pCVarInterface; + CCvar* g_pCVar; +} // namespace R2 diff --git a/NorthstarDLL/cvar.h b/NorthstarDLL/cvar.h index a39df387..e65e5145 100644 --- a/NorthstarDLL/cvar.h +++ b/NorthstarDLL/cvar.h @@ -35,5 +35,9 @@ class CCvar std::unordered_map DumpToMap(); }; -extern SourceInterface* g_pCVarInterface; -extern CCvar* g_pCVar; +// use the R2 namespace for game funcs +namespace R2 +{ + extern SourceInterface* g_pCVarInterface; + extern CCvar* g_pCVar; +} // namespace R2 diff --git a/NorthstarDLL/debugoverlay.cpp b/NorthstarDLL/debugoverlay.cpp index 96da2f38..ef456867 100644 --- a/NorthstarDLL/debugoverlay.cpp +++ b/NorthstarDLL/debugoverlay.cpp @@ -1,17 +1,9 @@ #include "pch.h" -#include "debugoverlay.h" #include "dedicated.h" #include "cvar.h" +#include "vector.h" -struct Vector3 -{ - float x, y, z; -}; - -struct QAngle -{ - float x, y, z, w; -}; +AUTOHOOK_INIT() enum OverlayType_t { @@ -40,7 +32,7 @@ struct OverlayBase_t int m_nServerCount; // Latch server count, too float m_flEndTime; // When does this box go away OverlayBase_t* m_pNextOverlay; - __int64 m_pUnk; + void* m_pUnk; }; struct OverlayLine_t : public OverlayBase_t @@ -76,37 +68,18 @@ struct OverlayBox_t : public OverlayBase_t int a; }; -// this is in cvar.h, don't need it here -/*class Color -{ - public: - Color(int r, int g, int b, int a) - { - _color[0] = (unsigned char)r; - _color[1] = (unsigned char)g; - _color[2] = (unsigned char)b; - _color[3] = (unsigned char)a; - } - - private: - unsigned char _color[4]; -};*/ - static HMODULE sEngineModule; -typedef void (*DrawOverlayType)(OverlayBase_t* a1); -DrawOverlayType DrawOverlay; - typedef void (*RenderLineType)(Vector3 v1, Vector3 v2, Color c, bool bZBuffer); static RenderLineType RenderLine; - typedef void (*RenderBoxType)(Vector3 vOrigin, QAngle angles, Vector3 vMins, Vector3 vMaxs, Color c, bool bZBuffer, bool bInsideOut); static RenderBoxType RenderBox; - static RenderBoxType RenderWireframeBox; -// engine.dll+0xABCB0 -void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay) +// clang-format off +AUTOHOOK(DrawOverlay, engine.dll + 0xABCB0, +void, __fastcall, (OverlayBase_t * pOverlay)) +// clang-format on { EnterCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); // s_OverlayMutex @@ -151,24 +124,17 @@ void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay) LeaveCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); } -void InitialiseDebugOverlay(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", DebugOverlay, ConVar, (CModule module)) { - if (IsDedicatedServer()) - return; - - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xABCB0, &DrawOverlayHook, reinterpret_cast(&DrawOverlay)); - - RenderLine = reinterpret_cast((char*)baseAddress + 0x192A70); - - RenderBox = reinterpret_cast((char*)baseAddress + 0x192520); - - RenderWireframeBox = reinterpret_cast((char*)baseAddress + 0x193DA0); + AUTOHOOK_DISPATCH() - sEngineModule = baseAddress; + RenderLine = module.Offset(0x192A70).As(); + RenderBox = module.Offset(0x192520).As(); + RenderWireframeBox = module.Offset(0x193DA0).As(); + sEngineModule = reinterpret_cast(module.m_nAddress); // not in g_pCVar->FindVar by this point for whatever reason, so have to get from memory - ConVar* Cvar_enable_debug_overlays = (ConVar*)((char*)baseAddress + 0x10DB0990); + ConVar* Cvar_enable_debug_overlays = module.Offset(0x10DB0990).As(); Cvar_enable_debug_overlays->SetValue(false); Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0"; Cvar_enable_debug_overlays->AddFlags(FCVAR_CHEAT); diff --git a/NorthstarDLL/debugoverlay.h b/NorthstarDLL/debugoverlay.h deleted file mode 100644 index 40b752f3..00000000 --- a/NorthstarDLL/debugoverlay.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseDebugOverlay(HMODULE baseAddress); diff --git a/NorthstarDLL/dedicated.cpp b/NorthstarDLL/dedicated.cpp index e6394aee..251e2437 100644 --- a/NorthstarDLL/dedicated.cpp +++ b/NorthstarDLL/dedicated.cpp @@ -1,9 +1,16 @@ #include "pch.h" #include "dedicated.h" -#include "hookutils.h" -#include "gameutils.h" +#include "tier0.h" +#include "playlist.h" +#include "r2engine.h" +#include "hoststate.h" #include "serverauthentication.h" #include "masterserver.h" +#include "printcommand.h" + +AUTOHOOK_INIT() + +using namespace R2; bool IsDedicatedServer() { @@ -33,32 +40,23 @@ void Sys_Printf(CDedicatedExports* dedicated, const char* msg) spdlog::info("[DEDICATED SERVER] {}", msg); } -typedef void (*CHostState__InitType)(CHostState* self); - void RunServer(CDedicatedExports* dedicated) { spdlog::info("CDedicatedExports::RunServer(): starting"); - spdlog::info(CommandLine()->GetCmdLine()); + spdlog::info(Tier0::CommandLine()->GetCmdLine()); // initialise engine g_pEngine->Frame(); // add +map if not present // don't manually execute this from cbuf as users may have it in their startup args anyway, easier just to run from stuffcmds if present - if (!CommandLine()->CheckParm("+map")) - CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString()); + if (!Tier0::CommandLine()->CheckParm("+map")) + Tier0::CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString()); - // run server autoexec and re-run commandline - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode); + // re-run commandline Cbuf_AddText(Cbuf_GetCurrentPlayer(), "stuffcmds", cmd_source_t::kCommandSrcCode); Cbuf_Execute(); - // ensure playlist initialises right, if we've not explicitly called setplaylist - SetCurrentPlaylist(GetCurrentPlaylistName()); - - // note: we no longer manually set map and hoststate to start server in g_pHostState, we just use +map which seems to initialise stuff - // better - // get tickinterval ConVar* Cvar_base_tickinterval_mp = g_pCVar->FindVar("base_tickinterval_mp"); @@ -66,43 +64,31 @@ void RunServer(CDedicatedExports* dedicated) double frameTitle = 0; while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING) { - double frameStart = Plat_FloatTime(); + double frameStart = Tier0::Plat_FloatTime(); g_pEngine->Frame(); - // only update the title after at least 500ms since the last update - if ((frameStart - frameTitle) > 0.5) - { - frameTitle = frameStart; - - // this way of getting playercount/maxplayers honestly really sucks, but not got any other methods of doing it rn - const char* maxPlayers = GetCurrentPlaylistVar("max_players", false); - if (!maxPlayers) - maxPlayers = "6"; - - SetConsoleTitleA(fmt::format( - "{} - {} {}/{} players ({})", - g_MasterServerManager->m_sUnicodeServerName, - g_pHostState->m_levelName, - g_ServerAuthenticationManager->m_additionalPlayerData.size(), - maxPlayers, - GetCurrentPlaylistName()) - .c_str()); - } - std::this_thread::sleep_for(std::chrono::duration>( - Cvar_base_tickinterval_mp->GetFloat() - fmin(Plat_FloatTime() - frameStart, 0.25))); + Cvar_base_tickinterval_mp->GetFloat() - fmin(Tier0::Plat_FloatTime() - frameStart, 0.25))); } } -typedef bool (*IsGameActiveWindowType)(); -IsGameActiveWindowType IsGameActiveWindow; -bool IsGameActiveWindowHook() +// use server presence to update window title +class DedicatedConsoleServerPresence : public ServerPresenceReporter { - return true; -} + void ReportPresence(const ServerPresence* pServerPresence) override + { + SetConsoleTitleA(fmt::format( + "{} - {} {}/{} players ({})", + pServerPresence->m_sServerName, + pServerPresence->m_MapName, + pServerPresence->m_iPlayerCount, + pServerPresence->m_iMaxPlayers, + pServerPresence->m_PlaylistName) + .c_str()); + } +}; HANDLE consoleInputThreadHandle = NULL; - DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter) { while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN) @@ -121,139 +107,106 @@ DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter) { input += "\n"; Cbuf_AddText(Cbuf_GetCurrentPlayer(), input.c_str(), cmd_source_t::kCommandSrcCode); + TryPrintCvarHelpForCommand(input.c_str()); // this needs to be done on main thread, unstable in this one } } return 0; } -#include "nsmem.h" -void InitialiseDedicated(HMODULE engineAddress) +// clang-format off +AUTOHOOK(IsGameActiveWindow, engine.dll + 0x1CDC80, +bool,, ()) +// clang-format on +{ + return true; +} + +ON_DLL_LOAD_DEDI_RELIESON("engine.dll", DedicatedServer, ServerPresence, (CModule module)) { spdlog::info("InitialiseDedicated"); - uintptr_t ea = (uintptr_t)engineAddress; + AUTOHOOK_DISPATCH_MODULE("engine.dll") - { - // Host_Init - // prevent a particle init that relies on client dll - NSMem::NOP(ea + 0x156799, 5); - } + // Host_Init + // prevent a particle init that relies on client dll + module.Offset(0x156799).NOP(5); + + // Host_Init + // don't call Key_Init to avoid loading some extra rsons from rpak (will be necessary to boot if we ever wanna disable rpaks entirely on + // dedi) + module.Offset(0x1565B0).NOP(5); { // CModAppSystemGroup::Create // force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment - auto ptr = ea + 0x1C4EBD; + MemoryAddress base = module.Offset(0x1C4EBD); // cmp => mov - NSMem::BytePatch(ptr + 1, "C6 87"); + base.Offset(1).Patch("C6 87"); // 00 => 01 - NSMem::BytePatch(ptr + 7, "01"); + base.Offset(7).Patch("01"); } - { - // Some init that i'm not sure of that crashes - // nop the call to it - NSMem::NOP(ea + 0x156A63, 5); - } - - { - // runframeserver - // nop some access violations - NSMem::NOP(ea + 0x159819, 17); - } - - { - NSMem::NOP(ea + 0x156B4C, 7); + // Some init that i'm not sure of that crashes + // nop the call to it + module.Offset(0x156A63).NOP(5); - // previously patched these, took me a couple weeks to figure out they were the issue - // removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address - // so uhh, don't do that - // NSMem::NOP(ea + 0x156B4C + 7, 8); + // runframeserver + // nop some access violations + module.Offset(0x159819).NOP(17); - NSMem::NOP(ea + 0x156B4C + 15, 9); - } + module.Offset(0x156B4C).NOP(7); - { - // HostState_State_NewGame - // nop an access violation - NSMem::NOP(ea + 0xB934C, 9); - } + // previously patched these, took me a couple weeks to figure out they were the issue + // removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address + // so uhh, don't do that + // NSMem::NOP(ea + 0x156B4C + 7, 8); + module.Offset(0x156B4C).Offset(15).NOP(9); - { - // CEngineAPI::Connect - // remove call to Shader_Connect - NSMem::NOP(ea + 0x1C4D7D, 5); - } + // HostState_State_NewGame + // nop an access violation + module.Offset(0xB934C).NOP(9); - // currently does not work, crashes stuff, likely gotta keep this here - //{ - // // CEngineAPI::Connect - // // remove calls to register ui rpak asset types - // NSMem::NOP(ea + 0x1C4E07, 5); - //} + // CEngineAPI::Connect + // remove call to Shader_Connect + module.Offset(0x1C4D7D).NOP(5); - { - // Host_Init - // remove call to ui loading stuff - NSMem::NOP(ea + 0x156595, 5); - } + // Host_Init + // remove call to ui loading stuff + module.Offset(0x156595).NOP(5); - { - // some function that gets called from RunFrameServer - // nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway - NSMem::NOP(ea + 0x15A0BB, 5); - } + // some function that gets called from RunFrameServer + // nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway + module.Offset(0x15A0BB).NOP(5); - { - // RunFrameServer - // nop a function that access violations - NSMem::NOP(ea + 0x159BF3, 5); - } + // RunFrameServer + // nop a function that access violations + module.Offset(0x159BF3).NOP(5); - { - // func that checks if origin is inited - // always return 1 - NSMem::BytePatch( - ea + 0x183B70, - { - 0xB0, - 0x01, // mov al,01 - 0xC3 // ret - }); - } + // func that checks if origin is inited + // always return 1 + module.Offset(0x183B70).Patch("B0 01 C3"); // mov al,01 ret - { - // HostState_State_ChangeLevel - // nop clientinterface call - NSMem::NOP(ea + 0x1552ED, 16); - } + // HostState_State_ChangeLevel + // nop clientinterface call + module.Offset(0x1552ED).NOP(16); - { - // HostState_State_ChangeLevel - // nop clientinterface call - NSMem::NOP(ea + 0x155363, 16); - } + // HostState_State_ChangeLevel + // nop clientinterface call + module.Offset(0x155363).NOP(16); - // note: previously had DisableDedicatedWindowCreation patches here, but removing those rn since they're all shit and unstable and bad - // and such check commit history if any are needed for reimplementation - { - // IVideoMode::CreateGameWindow - // nop call to ShowWindow - NSMem::NOP(ea + 0x1CD146, 5); - } + // IVideoMode::CreateGameWindow + // nop call to ShowWindow + module.Offset(0x1CD146).NOP(5); CDedicatedExports* dedicatedExports = new CDedicatedExports; dedicatedExports->vtable = dedicatedExports; dedicatedExports->Sys_Printf = Sys_Printf; dedicatedExports->RunServer = RunServer; - CDedicatedExports** exports = (CDedicatedExports**)((char*)engineAddress + 0x13F0B668); - *exports = dedicatedExports; - - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)engineAddress + 0x1CDC80, &IsGameActiveWindowHook, reinterpret_cast(&IsGameActiveWindow)); + *module.Offset(0x13F0B668).As() = dedicatedExports; // extra potential patches: // nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow @@ -265,15 +218,19 @@ void InitialiseDedicated(HMODULE engineAddress) // make sure it still gets registered // add cmdline args that are good for dedi - CommandLine()->AppendParm("-nomenuvid", 0); - CommandLine()->AppendParm("-nosound", 0); - CommandLine()->AppendParm("-windowed", 0); - CommandLine()->AppendParm("-nomessagebox", 0); - CommandLine()->AppendParm("+host_preload_shaders", "0"); - CommandLine()->AppendParm("+net_usesocketsforloopback", "1"); + Tier0::CommandLine()->AppendParm("-nomenuvid", 0); + Tier0::CommandLine()->AppendParm("-nosound", 0); + Tier0::CommandLine()->AppendParm("-windowed", 0); + Tier0::CommandLine()->AppendParm("-nomessagebox", 0); + Tier0::CommandLine()->AppendParm("+host_preload_shaders", "0"); + Tier0::CommandLine()->AppendParm("+net_usesocketsforloopback", "1"); + + // use presence reporter for console title + DedicatedConsoleServerPresence* presenceReporter = new DedicatedConsoleServerPresence; + g_pServerPresence->AddPresenceReporter(presenceReporter); // Disable Quick Edit mode to reduce chance of user unintentionally hanging their server by selecting something. - if (!CommandLine()->CheckParm("-bringbackquickedit")) + if (!Tier0::CommandLine()->CheckParm("-bringbackquickedit")) { HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE); DWORD mode = 0; @@ -295,35 +252,42 @@ void InitialiseDedicated(HMODULE engineAddress) spdlog::info("Quick Edit enabled by user request"); // create console input thread - if (!CommandLine()->CheckParm("-noconsoleinput")) + if (!Tier0::CommandLine()->CheckParm("-noconsoleinput")) consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL); else spdlog::info("Console input disabled by user request"); } -void InitialiseDedicatedOrigin(HMODULE baseAddress) +ON_DLL_LOAD_DEDI("tier0.dll", DedicatedServerOrigin, (CModule module)) { // disable origin on dedicated // for any big ea lawyers, this can't be used to play the game without origin, game will throw a fit if you try to do anything without // an origin id as a client for dedi it's fine though, game doesn't care if origin is disabled as long as there's only a server - - NSMem::BytePatch( - (uintptr_t)GetProcAddress(GetModuleHandleA("tier0.dll"), "Tier0_InitOrigin"), - { - 0xC3 // ret - }); + module.GetExport("Tier0_InitOrigin").Patch("C3"); } -typedef void (*PrintFatalSquirrelErrorType)(void* sqvm); -PrintFatalSquirrelErrorType PrintFatalSquirrelError; -void PrintFatalSquirrelErrorHook(void* sqvm) +// clang-format off +AUTOHOOK(PrintSquirrelError, server.dll + 0x794D0, +void, __fastcall, (void* sqvm)) +// clang-format on { - PrintFatalSquirrelError(sqvm); - g_pEngine->m_nQuitting = EngineQuitState::QUIT_TODESKTOP; + PrintSquirrelError(sqvm); + + // close dedicated server if a fatal error is hit + static ConVar* Cvar_fatal_script_errors = g_pCVar->FindVar("fatal_script_errors"); + if (Cvar_fatal_script_errors->GetBool()) + abort(); } -void InitialiseDedicatedServerGameDLL(HMODULE baseAddress) +ON_DLL_LOAD_DEDI("server.dll", DedicatedServerGameDLL, (CModule module)) { - HookEnabler hook; - ENABLER_CREATEHOOK(hook, baseAddress + 0x794D0, &PrintFatalSquirrelErrorHook, reinterpret_cast(&PrintFatalSquirrelError)); + AUTOHOOK_DISPATCH_MODULE(server.dll) + + if (Tier0::CommandLine()->CheckParm("-nopakdedi")) + { + module.Offset(0x6BA350).Patch("C3"); // dont load skins.rson from rpak if we don't have rpaks, as loading it will cause a crash + module.Offset(0x6BA300).Patch( + "B8 C8 00 00 00 C3"); // return 200 as the number of skins from server.dll + 6BA300, this is the normal value read from + // skins.rson and should be updated when we need it more modular + } } diff --git a/NorthstarDLL/dedicated.h b/NorthstarDLL/dedicated.h index 0a2d247e..82806763 100644 --- a/NorthstarDLL/dedicated.h +++ b/NorthstarDLL/dedicated.h @@ -1,7 +1,3 @@ #pragma once bool IsDedicatedServer(); - -void InitialiseDedicated(HMODULE moduleAddress); -void InitialiseDedicatedOrigin(HMODULE baseAddress); -void InitialiseDedicatedServerGameDLL(HMODULE baseAddress); diff --git a/NorthstarDLL/dedicatedmaterialsystem.cpp b/NorthstarDLL/dedicatedmaterialsystem.cpp index 47440b7a..28ee9b76 100644 --- a/NorthstarDLL/dedicatedmaterialsystem.cpp +++ b/NorthstarDLL/dedicatedmaterialsystem.cpp @@ -1,24 +1,12 @@ #include "pch.h" #include "dedicated.h" -#include "dedicatedmaterialsystem.h" -#include "hookutils.h" -#include "gameutils.h" -#include "nsmem.h" +#include "tier0.h" -typedef HRESULT (*__stdcall D3D11CreateDeviceType)( - void* pAdapter, - int DriverType, - HMODULE Software, - UINT Flags, - int* pFeatureLevels, - UINT FeatureLevels, - UINT SDKVersion, - void** ppDevice, - int* pFeatureLevel, - void** ppImmediateContext); -D3D11CreateDeviceType D3D11CreateDevice; +AUTOHOOK_INIT() -HRESULT __stdcall D3D11CreateDeviceHook( +// clang-format off +AUTOHOOK(D3D11CreateDevice, materialsystem_dx11.dll + 0xD9A0E, +HRESULT, __stdcall, ( void* pAdapter, int DriverType, HMODULE Software, @@ -28,95 +16,26 @@ HRESULT __stdcall D3D11CreateDeviceHook( UINT SDKVersion, void** ppDevice, int* pFeatureLevel, - void** ppImmediateContext) + void** ppImmediateContext)) +// clang-format on { // note: this is super duper temp pretty much just messing around with it // does run surprisingly well on dedi for a software driver tho if you ignore the +1gb ram usage at times, seems like dedi doesn't // really call gpu much even with renderthread still being a thing will be using this hook for actual d3d stubbing and stuff later - // atm, i think the play might be to run d3d in software, and then just stub out any calls that allocate memory/use alot of resources - // (e.g. createtexture and that sorta thing) // note: this has been succeeded by the d3d11 and gfsdk stubs, and is only being kept around for posterity and as a fallback option - if (CommandLine()->CheckParm("-softwared3d11")) + if (Tier0::CommandLine()->CheckParm("-softwared3d11")) DriverType = 5; // D3D_DRIVER_TYPE_WARP return D3D11CreateDevice( pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext); } -void InitialiseDedicatedMaterialSystem(HMODULE baseAddress) -{ - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xD9A0E, &D3D11CreateDeviceHook, reinterpret_cast(&D3D11CreateDevice)); - - // not using these for now since they're related to nopping renderthread/gamewindow i.e. very hard - //{ - // // function that launches renderthread - // char* ptr = (char*)baseAddress + 0x87047; - // TempReadWrite rw(ptr); - // - // // make it not launch renderthread - // *ptr = (char)0x90; - // *(ptr + 1) = (char)0x90; - // *(ptr + 2) = (char)0x90; - // *(ptr + 3) = (char)0x90; - // *(ptr + 4) = (char)0x90; - // *(ptr + 5) = (char)0x90; - //} - // - //{ - // // some function that waits on renderthread job - // char* ptr = (char*)baseAddress + 0x87d00; - // TempReadWrite rw(ptr); - // - // // return immediately - // *ptr = (char)0xC3; - //} - - { - // CMaterialSystem::FindMaterial - // make the game always use the error material - NSMem::BytePatch((uintptr_t)baseAddress + 0x5F0F1, {0xE9, 0x34, 0x03, 0x00}); - } - - // previously had DisableDedicatedWindowCreation stuff here, removing for now since shit and unstable - // check commit history if needed -} - -typedef void* (*PakLoadAPI__LoadRpakType)(char* filename, void* unknown, int flags); -PakLoadAPI__LoadRpakType PakLoadAPI__LoadRpak; - -void* PakLoadAPI__LoadRpakHook(char* filename, void* unknown, int flags) -{ - spdlog::info("PakLoadAPI__LoadRpakHook {}", filename); - - // on dedi, don't load any paks that aren't required - if (strncmp(filename, "common", 6)) - return 0; - - return PakLoadAPI__LoadRpak(filename, unknown, flags); -} - -typedef void* (*PakLoadAPI__LoadRpak2Type)(char* filename, void* unknown, int flags, void* callback, void* callback2); -PakLoadAPI__LoadRpak2Type PakLoadAPI__LoadRpak2; - -void* PakLoadAPI__LoadRpak2Hook(char* filename, void* unknown, int flags, void* callback, void* callback2) -{ - spdlog::info("PakLoadAPI__LoadRpak2Hook {}", filename); - - // on dedi, don't load any paks that aren't required - if (strncmp(filename, "common", 6)) - return 0; - - return PakLoadAPI__LoadRpak2(filename, unknown, flags, callback, callback2); -} - -void InitialiseDedicatedRtechGame(HMODULE baseAddress) +ON_DLL_LOAD_DEDI("materialsystem_dx11.dll", DedicatedServerMaterialSystem, (CModule module)) { - baseAddress = GetModuleHandleA("rtech_game.dll"); + AUTOHOOK_DISPATCH() - HookEnabler hook; - // unfortunately this is unstable, seems to freeze when changing maps - // ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB0F0, &PakLoadAPI__LoadRpakHook, reinterpret_cast(&PakLoadAPI__LoadRpak)); - // ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB170, &PakLoadAPI__LoadRpak2Hook, reinterpret_cast(&PakLoadAPI__LoadRpak2)); + // CMaterialSystem::FindMaterial + // make the game always use the error material + module.Offset(0x5F0F1).Patch("E9 34 03 00"); } diff --git a/NorthstarDLL/dedicatedmaterialsystem.h b/NorthstarDLL/dedicatedmaterialsystem.h deleted file mode 100644 index 189001e6..00000000 --- a/NorthstarDLL/dedicatedmaterialsystem.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -void InitialiseDedicatedMaterialSystem(HMODULE baseAddress); -void InitialiseDedicatedRtechGame(HMODULE baseAddress); diff --git a/NorthstarDLL/demofixes.cpp b/NorthstarDLL/demofixes.cpp new file mode 100644 index 00000000..ab092484 --- /dev/null +++ b/NorthstarDLL/demofixes.cpp @@ -0,0 +1,26 @@ +#include "pch.h" +#include "convar.h" + +ON_DLL_LOAD_CLIENT("engine.dll", EngineDemoFixes, (CModule module)) +{ + // allow demo recording on loopback + module.Offset(0x8E1B1).NOP(2); + module.Offset(0x56CC3).NOP(2); +} + +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientDemoFixes, ConVar, (CModule module)) +{ + // change default values of demo cvars to enable them by default, but not autorecord + // this is before Host_Init, the setvalue calls here will get overwritten by custom cfgs/launch options + ConVar* Cvar_demo_enableDemos = R2::g_pCVar->FindVar("demo_enabledemos"); + Cvar_demo_enableDemos->m_pszDefaultValue = "1"; + Cvar_demo_enableDemos->SetValue(true); + + ConVar* Cvar_demo_writeLocalFile = R2::g_pCVar->FindVar("demo_writeLocalFile"); + Cvar_demo_writeLocalFile->m_pszDefaultValue = "1"; + Cvar_demo_writeLocalFile->SetValue(true); + + ConVar* Cvar_demo_autoRecord = R2::g_pCVar->FindVar("demo_autoRecord"); + Cvar_demo_autoRecord->m_pszDefaultValue = "0"; + Cvar_demo_autoRecord->SetValue(false); +} diff --git a/NorthstarDLL/dllmain.cpp b/NorthstarDLL/dllmain.cpp index 020ab1cd..8d78ee2c 100644 --- a/NorthstarDLL/dllmain.cpp +++ b/NorthstarDLL/dllmain.cpp @@ -1,62 +1,23 @@ #include "pch.h" -#include "hooks.h" #include "main.h" -#include "squirrel.h" -#include "dedicated.h" -#include "dedicatedmaterialsystem.h" -#include "sourceconsole.h" #include "logging.h" -#include "concommand.h" -#include "modmanager.h" -#include "filesystem.h" -#include "serverauthentication.h" -#include "scriptmodmenu.h" -#include "scriptserverbrowser.h" -#include "keyvalues.h" -#include "masterserver.h" -#include "gameutils.h" -#include "chatcommand.h" -#include "modlocalisation.h" -#include "playlist.h" -#include "miscserverscript.h" -#include "clientauthhooks.h" -#include "latencyflex.h" -#include "scriptbrowserhooks.h" -#include "scriptmainmenupromos.h" -#include "miscclientfixes.h" -#include "miscserverfixes.h" -#include "rpakfilesystem.h" -#include "bansystem.h" +#include "crashhandler.h" #include "memalloc.h" -#include "maxplayers.h" -#include "languagehooks.h" -#include "audio.h" -#include "buildainfile.h" #include "nsprefix.h" -#include "serverchathooks.h" -#include "clientchathooks.h" -#include "localchatwriter.h" -#include "scriptservertoclientstringcommand.h" #include "plugin_abi.h" #include "plugins.h" -#include "debugoverlay.h" -#include "clientvideooverrides.h" -#include "clientruihooks.h" -#include #include "version.h" #include "pch.h" -#include "scriptutility.h" #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" #include "rapidjson/error/en.h" -#include "exploitfixes.h" -#include "scriptjson.h" -typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject)); +#include +#include -bool initialised = false; +typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject)); BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { @@ -72,18 +33,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv return TRUE; } -void WaitForDebugger(HMODULE baseAddress) -{ - // earlier waitfordebugger call than is in vanilla, just so we can debug stuff a little easier - if (CommandLine()->CheckParm("-waitfordebugger")) - { - spdlog::info("waiting for debugger..."); - - while (!IsDebuggerPresent()) - Sleep(100); - } -} - void freeLibrary(HMODULE hLib) { if (!FreeLibrary(hLib)) @@ -190,15 +139,13 @@ bool LoadPlugins() bool InitialiseNorthstar() { - if (initialised) - { - // spdlog::warn("Called InitialiseNorthstar more than once!"); // it's actually 100% fine for that to happen + static bool bInitialised = false; + if (bInitialised) return false; - } - initialised = true; + bInitialised = true; - parseConfigurables(); + InitialiseNorthstarPrefix(); InitialiseVersion(); // Fix some users' failure to connect to respawn datacenters @@ -206,6 +153,7 @@ bool InitialiseNorthstar() curl_global_init_mem(CURL_GLOBAL_DEFAULT, _malloc_base, _free_base, _realloc_base, _strdup_base, _calloc_base); + InitialiseCrashHandler(); InitialiseLogging(); InstallInitialHooks(); CreateLogFiles(); @@ -213,94 +161,6 @@ bool InitialiseNorthstar() // Write launcher version to log spdlog::info("NorthstarLauncher version: {}", version); - InitialiseInterfaceCreationHooks(); - - AddDllLoadCallback("tier0.dll", InitialiseTier0GameUtilFunctions); - AddDllLoadCallback("engine.dll", WaitForDebugger); - AddDllLoadCallback("engine.dll", InitialiseEngineGameUtilFunctions); - AddDllLoadCallback("server.dll", InitialiseServerGameUtilFunctions); - - // dedi patches - { - AddDllLoadCallbackForDedicatedServer("tier0.dll", InitialiseDedicatedOrigin); - AddDllLoadCallbackForDedicatedServer("engine.dll", InitialiseDedicated); - AddDllLoadCallbackForDedicatedServer("server.dll", InitialiseDedicatedServerGameDLL); - AddDllLoadCallbackForDedicatedServer("materialsystem_dx11.dll", InitialiseDedicatedMaterialSystem); - // this fucking sucks, but seemingly we somehow load after rtech_game???? unsure how, but because of this we have to apply patches - // here, not on rtech_game load - AddDllLoadCallbackForDedicatedServer("engine.dll", InitialiseDedicatedRtechGame); - } - - AddDllLoadCallback("engine.dll", InitialiseConVars); - AddDllLoadCallback("engine.dll", InitialiseConCommands); - - // client-exclusive patches - { - - AddDllLoadCallbackForClient("tier0.dll", InitialiseTier0LanguageHooks); - AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrel); - AddDllLoadCallbackForClient("client.dll", InitialiseSourceConsole); - AddDllLoadCallbackForClient("engine.dll", InitialiseChatCommands); - AddDllLoadCallbackForClient("client.dll", InitialiseScriptModMenu); - AddDllLoadCallbackForClient("client.dll", InitialiseScriptServerBrowser); - AddDllLoadCallbackForClient("localize.dll", InitialiseModLocalisation); - AddDllLoadCallbackForClient("engine.dll", InitialiseClientAuthHooks); - AddDllLoadCallbackForClient("client.dll", InitialiseLatencyFleX); - AddDllLoadCallbackForClient("engine.dll", InitialiseScriptExternalBrowserHooks); - AddDllLoadCallbackForClient("client.dll", InitialiseScriptMainMenuPromos); - AddDllLoadCallbackForClient("client.dll", InitialiseMiscClientFixes); - AddDllLoadCallbackForClient("client.dll", InitialiseClientPrintHooks); - AddDllLoadCallbackForClient("client.dll", InitialisePluginCommands); - AddDllLoadCallbackForClient("client.dll", InitialiseClientChatHooks); - AddDllLoadCallbackForClient("client.dll", InitialiseLocalChatWriter); - AddDllLoadCallbackForClient("client.dll", InitialiseScriptServerToClientStringCommands); - AddDllLoadCallbackForClient("engine.dll", InitialiseEngineClientVideoOverrides); - AddDllLoadCallbackForClient("engine.dll", InitialiseEngineClientRUIHooks); - AddDllLoadCallbackForClient("engine.dll", InitialiseDebugOverlay); - AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrelJson); - AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrelUtilityFunctions); - // audio hooks - AddDllLoadCallbackForClient("client.dll", InitialiseMilesAudioHooks); - } - - AddDllLoadCallback("engine.dll", InitialiseEngineSpewFuncHooks); - AddDllLoadCallback("server.dll", InitialiseServerSquirrel); - AddDllLoadCallback("engine.dll", InitialiseBanSystem); - AddDllLoadCallback("engine.dll", InitialiseServerAuthentication); - AddDllLoadCallback("engine.dll", InitialiseSharedMasterServer); - AddDllLoadCallback("server.dll", InitialiseMiscServerScriptCommand); - AddDllLoadCallback("server.dll", InitialiseMiscServerFixes); - AddDllLoadCallback("server.dll", InitialiseBuildAINFileHooks); - AddDllLoadCallback("server.dll", InitialiseServerSquirrelUtilityFunctions); - AddDllLoadCallback("server.dll", InitialiseServerSquirrelJson); - - AddDllLoadCallback("engine.dll", InitialisePlaylistHooks); - - AddDllLoadCallback("filesystem_stdio.dll", InitialiseFilesystem); - AddDllLoadCallback("engine.dll", InitialiseEngineRpakFilesystem); - AddDllLoadCallback("engine.dll", InitialiseKeyValues); - - AddDllLoadCallback("engine.dll", InitialiseServerChatHooks_Engine); - AddDllLoadCallback("server.dll", InitialiseServerChatHooks_Server); - - // maxplayers increase - AddDllLoadCallback("engine.dll", InitialiseMaxPlayersOverride_Engine); - AddDllLoadCallback("client.dll", InitialiseMaxPlayersOverride_Client); - AddDllLoadCallback("server.dll", InitialiseMaxPlayersOverride_Server); - - // mod manager after everything else - AddDllLoadCallback("engine.dll", InitialiseModManager); - - { - // activate multi-module exploitfixes callbacks - constexpr const char* EXPLOITFIXES_MULTICALLBACK_MODS[] = {"client.dll", "engine.dll", "server.dll"}; - for (const char* mod : EXPLOITFIXES_MULTICALLBACK_MODS) - AddDllLoadCallback(mod, ExploitFixes::LoadCallback_MultiModule); - - // activate exploit fixes later - AddDllLoadCallback("server.dll", ExploitFixes::LoadCallback_Full); - } - // run callbacks for any libraries that are already loaded by now CallAllPendingDLLLoadCallbacks(); diff --git a/NorthstarDLL/exploitfixes.cpp b/NorthstarDLL/exploitfixes.cpp index 0aa0a3bf..240c352c 100644 --- a/NorthstarDLL/exploitfixes.cpp +++ b/NorthstarDLL/exploitfixes.cpp @@ -1,52 +1,63 @@ #include "pch.h" - -#include "exploitfixes.h" -#include "exploitfixes_utf8parser.h" -#include "nsmem.h" #include "cvar.h" -#include "gameutils.h" +#include "limits.h" +#include "dedicated.h" +#include "tier0.h" +#include "r2engine.h" +#include "r2client.h" +#include "vector.h" + +AUTOHOOK_INIT() + +ConVar* Cvar_ns_exploitfixes_log; +ConVar* Cvar_ns_should_log_all_clientcommands; + +ConVar* Cvar_sv_cheats; -ConVar* ns_exploitfixes_log; -#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0) #define BLOCKED_INFO(s) \ ( \ [=]() -> bool \ { \ - if (SHOULD_LOG) \ + if (Cvar_ns_exploitfixes_log->GetBool()) \ { \ std::stringstream stream; \ - stream << "exploitfixes.cpp: " << BLOCK_PREFIX << s; \ + stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \ spdlog::error(stream.str()); \ } \ return false; \ }()) -struct Float3 +// block bad netmessages +// Servers can literally request a screenshot from any client, yeah no +// clang-format off +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 +// clang-format on { - float vals[3]; - - void MakeValid() - { - for (auto& val : vals) - if (isnan(val)) - val = 0; - } -}; - -#define BLOCK_NETMSG_FUNC(name, pattern) \ - KHOOK(name, ("engine.dll", pattern), bool, __fastcall, (void* thisptr, void* buffer)) \ - { \ - return false; \ - } + return false; +} -// Servers can literally request a screenshot from any client, yeah no -BLOCK_NETMSG_FUNC(CLC_Screenshot_WriteToBuffer, "48 89 5C 24 ? 57 48 83 EC 20 8B 42 10"); -BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38"); +// clang-format off +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 +// clang-format on +{ + return false; +} -// This is unused ingame and a big exploit vector -BLOCK_NETMSG_FUNC(Base_CmdKeyValues_ReadFromBuffer, "40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70"); +// This is unused ingame and a big client=>server=>client exploit vector +// clang-format off +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 +// clang-format on +{ + return false; +} -KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10"), bool, __fastcall, (void* pMsg)) +// clang-format off +AUTOHOOK(CClient_ProcessSetConVar, engine.dll + 0x75CF0, +bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10 +// clang-format on { constexpr int ENTRY_STR_LEN = 260; @@ -69,25 +80,12 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 }; auto msg = (NET_SetConVar*)pMsg; + bool bIsServerFrame = Tier0::ThreadInServerFrameThread(); - bool areWeServer; - - { - // Figure out of we are the client or the server - // To do this, we utilize the msg's m_pMessageHandler pointer - // m_pMessageHandler points to a virtual class that handles all net messages - // The first virtual table function of our m_pMessageHandler will differ if it is IServerMessageHandler or IClientMessageHandler - void* msgHandlerVTableFirstFunc = **(void****)(msg->m_pMessageHandler); - static auto engineBaseAddress = (uintptr_t)GetModuleHandleA("engine.dll"); - auto offset = uintptr_t(msgHandlerVTableFirstFunc) - engineBaseAddress; - - constexpr uintptr_t CLIENTSTATE_FIRST_VFUNC_OFFSET = 0x8A15C; - areWeServer = offset != CLIENTSTATE_FIRST_VFUNC_OFFSET; - } - - std::string BLOCK_PREFIX = std::string {"NET_SetConVar ("} + (areWeServer ? "server" : "client") + "): Blocked dangerous/invalid msg: "; + std::string BLOCK_PREFIX = + std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: "; - if (areWeServer) + if (bIsServerFrame) { constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69; if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT) @@ -101,9 +99,8 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 auto entry = msg->m_ConVars + i; // Safety check for memory access - if (NSMem::IsMemoryReadable(entry, sizeof(*entry))) + if (MemoryAddress(entry).IsMemoryReadable(sizeof(*entry))) { - // Find null terminators bool nameValid = false, valValid = false; for (int i = 0; i < ENTRY_STR_LEN; i++) @@ -117,36 +114,19 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 if (!nameValid || !valValid) return BLOCKED_INFO("Missing null terminators"); - auto realVar = g_pCVar->FindVar(entry->name); + ConVar* pVar = R2::g_pCVar->FindVar(entry->name); - if (realVar) + if (pVar) + { memcpy( entry->name, - realVar->m_ConCommandBase.m_pszName, - strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case - - bool isValidFlags = true; - if (areWeServer) - { - if (realVar) - isValidFlags = ConVar::IsFlagSet(realVar, FCVAR_USERINFO); // ConVar MUST be userinfo var - } - else - { - // TODO: Should probably have some sanity checks, but can't find any that are consistent - } + pVar->m_ConCommandBase.m_pszName, + strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case - if (!isValidFlags) - { - if (!realVar) - { - return BLOCKED_INFO("Invalid flags on nonexistant cvar (how tho???)"); - } - else - { + int iFlags = bIsServerFrame ? FCVAR_USERINFO : FCVAR_REPLICATED; + if (!pVar->IsFlagSet(iFlags)) return BLOCKED_INFO( - "Invalid flags (" << std::hex << "0x" << realVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name); - } + "Invalid flags (" << std::hex << "0x" << pVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name); } } else @@ -155,11 +135,14 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 } } - return oCClient_ProcessSetConVar(msg); + return CClient_ProcessSetConVar(msg); } -// Purpose: prevent invalid user CMDs -KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg)) +// prevent invalid user CMDs +// clang-format off +AUTOHOOK(CClient_ProcessUsercmds, engine.dll + 0x1040F0, +bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58 +// clang-format on { struct CLC_Move { @@ -186,22 +169,19 @@ KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __f return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")"); } - 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 oCClient_ProcessUsercmds(thisptr, pMsg); + return CClient_ProcessUsercmds(thisptr, pMsg); } -KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) +// clang-format off +AUTOHOOK(ReadUsercmd, server.dll + 0x2603F0, +void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24 ? 53 55 56 57 +// clang-format on { // Let normal usercmd read happen first, it's safe - oReadUsercmd(buf, pCmd_move, pCmd_from); + 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 alignas(4) SV_CUserCmd @@ -209,11 +189,11 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall DWORD command_number; DWORD tick_count; float command_time; - Float3 worldViewAngles; + Vector3 worldViewAngles; BYTE gap18[4]; - Float3 localViewAngles; - Float3 attackangles; - Float3 move; + Vector3 localViewAngles; + Vector3 attackangles; + Vector3 move; DWORD buttons; BYTE impulse; short weaponselect; @@ -221,8 +201,8 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall BYTE gap4C[24]; char headoffset; BYTE gap65[11]; - Float3 cameraPos; - Float3 cameraAngles; + Vector3 cameraPos; + Vector3 cameraAngles; BYTE gap88[4]; int tickSomething; DWORD dword90; @@ -237,7 +217,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall std::string BLOCK_PREFIX = "ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): "; - // Fix invalid player angles + // fix invalid player angles cmd->worldViewAngles.MakeValid(); cmd->attackangles.MakeValid(); cmd->localViewAngles.MakeValid(); @@ -249,7 +229,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall // Fix invaid movement vector cmd->move.MakeValid(); - if (cmd->tick_count == 0 || cmd->command_time <= 0) + 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 @@ -260,6 +240,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall 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 = {0, 0, 0}; @@ -269,287 +250,209 @@ INVALID_CMD: cmd->meleetarget = 0; } -// basically: by default r2 isn't set as a valve mod, meaning that m_bRestrictServerCommands is false -// this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients -// especially since we have script commands this could theoretically be awful -KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ()) -{ - bool result = !CommandLine()->CheckParm("-norestrictservercommands"); - spdlog::info("ExploitFixes: Overriding IsValveMod to {}...", result); - return result; -} - -// Fix respawn's crappy UTF8 parser so it doesn't crash -_- -// This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome -KHOOK( - CrashFunc_ParseUTF8, - ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"), - bool, - __fastcall, - (INT64 * a1, DWORD* a2, char* strData)) +// 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 +// clang-format off +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 +// clang-format on { + // somewhat temp, store the modname here, since we don't have a proper ptr in engine to it rn + int iSize = strlen(pModName); + R2::g_pModName = new char[iSize + 1]; + strcpy(R2::g_pModName, pModName); - static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16"); - -#ifdef _MSC_VER - if (_ReturnAddress() == targetRetAddr) -#else - if (__builtin_return_address(0) == targetRetAddr) -#endif - { - if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData)) - { - const char* BLOCK_PREFIX = "ParseUTF8 Hook: "; - BLOCKED_INFO("Ignoring potentially-crashing utf8 string"); - return false; - } - } - - return oCrashFunc_ParseUTF8(a1, a2, strData); + return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) && !Tier0::CommandLine()->CheckParm("-norestrictservercommands"); } -// GetEntByIndex (called by ScriptGetEntByIndex) doesn't check for the index being out of bounds when it's -// above the max entity count. This allows it to be used to crash servers. -typedef void*(__fastcall* GetEntByIndexType)(int idx); -GetEntByIndexType GetEntByIndex; - -static void* GetEntByIndexHook(int idx) -{ - if (idx >= 0x4000) - { - spdlog::info("GetEntByIndex {} is out of bounds", idx); - return nullptr; - } - return GetEntByIndex(idx); -} +// ratelimit stringcmds, and prevent remote clients from calling commands that they shouldn't +bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, R2::cmd_source_t commandSource); -// RELOCATED FROM https://github.com/R2Northstar/NorthstarLauncher/commit/25dbf729cfc75107a0fcf0186924b58ecc05214b -// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read -// out of the bounds of the output buffer. -KHOOK( - LZSS_SafeUncompress, - ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 33 ED 41 8B F9"), - uint32_t, - __fastcall, - (void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize)) +// clang-format off +AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0, +bool, __fastcall, (R2::CBaseClient* self, uint32_t unknown, const char* pCommandString)) +// clang-format on { - static constexpr int LZSS_LOOKSHIFT = 4; + if (Cvar_ns_should_log_all_clientcommands->GetBool()) + spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString); - uint32_t totalBytes = 0; - int getCmdByte = 0, cmdByte = 0; - - struct lzss_header_t + if (!g_pServerLimits->CheckStringCommandLimits(self)) { - uint32_t id, actualSize; - }; + R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands"); + return false; + } - lzss_header_t header = *(lzss_header_t*)pInput; + // 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 (pInput == NULL || header.id != 'SSZL' || header.actualSize == 0 || header.actualSize > unBufSize) - return 0; + if (!CCommand__Tokenize(tempCommand, pCommandString, R2::cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC()) + return false; - pInput += sizeof(lzss_header_t); + ConCommand* command = R2::g_pCVar->FindCommand(tempCommand.Arg(0)); - for (;;) + // 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)) { - if (!getCmdByte) - cmdByte = *pInput++; + // ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients + if (IsDedicatedServer()) + return false; - getCmdByte = (getCmdByte + 1) & 0x07; + if (strcmp(self->m_UID, R2::g_pLocalPlayerUserID)) + return false; + } - if (cmdByte & 0x01) - { - int position = *pInput++ << LZSS_LOOKSHIFT; - position |= (*pInput >> LZSS_LOOKSHIFT); - position += 1; + // check for and block abusable legacy portal 2 commands + // these aren't actually concommands weirdly enough, they seem to just be hardcoded + if (!Cvar_sv_cheats->GetBool()) + { + constexpr const char* blockedCommands[] = { + "emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something) - int count = (*pInput++ & 0x0F) + 1; - if (count == 1) - break; + // These both execute a command for every single entity for some reason, nice one valve + "pre_go_to_hub", + "pre_go_to_calibration", - // Ensure reference chunk exists entirely within our buffer - if (position > totalBytes) - return 0; + "end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed + "load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer() + }; - totalBytes += count; - if (totalBytes > unBufSize) - return 0; + int iCmdLength = strlen(tempCommand.Arg(0)); - unsigned char* pSource = pOutput - position; - for (int i = 0; i < count; i++) - *pOutput++ = *pSource++; - } - else + bool bIsBadCommand = false; + for (auto& blockedCommand : blockedCommands) { - totalBytes++; - if (totalBytes > unBufSize) - return 0; + if (iCmdLength != strlen(blockedCommand)) + continue; + + for (int i = 0; tempCommand.Arg(0)[i]; i++) + if (tolower(tempCommand.Arg(0)[i]) != blockedCommand[i]) + goto NEXT_COMMAND; // break out of this loop, then go to next command - *pOutput++ = *pInput++; + // this is a command we need to block + return false; + NEXT_COMMAND:; } - cmdByte = cmdByte >> 1; } - if (totalBytes == header.actualSize) - { - return totalBytes; - } - else - { - return 0; - } + return CGameClient__ExecuteStringCommand(self, unknown, pCommandString); } -////////////////////////////////////////////////// +// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines +bool bWasWritingStringTableSuccessful; -void DoBytePatches() +// clang-format off +AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0, +void, __fastcall, (void* self)) +// clang-format on { - uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll"); - uintptr_t serverBase = (uintptr_t)GetModuleHandleA("server.dll"); + bWasWritingStringTableSuccessful = true; + CBaseClient__SendServerInfo(self); + if (!bWasWritingStringTableSuccessful) + R2::CBaseClient__Disconnect( + self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting"); +} - // patches to make commands run from client/ui script still work - // note: this is likely preventable in a nicer way? test prolly - NSMem::BytePatch(engineBase + 0x4FB65, "EB 11"); - NSMem::BytePatch(engineBase + 0x4FBAC, "EB 16"); +// return null when GetEntByIndex is passed an index >= 0x4000 +// this is called from exactly 1 script clientcommand that can be given an arbitrary index, and going above 0x4000 crashes +// clang-format off +AUTOHOOK(GetEntByIndex, server.dll + 0x2A8A50, +void*, __fastcall, (int i)) +// clang-format on +{ + const int MAX_ENT_IDX = 0x4000; - // disconnect concommand + if (i >= MAX_ENT_IDX) { - uintptr_t addr = engineBase + 0x5ADA2D; - int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE; - NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int)); + spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX); + return nullptr; } - { // Dumb ANTITAMPER patches (they negatively impact performance and security) - - constexpr const char* ANTITAMPER_EXPORTS[] = { - "ANTITAMPER_SPOTCHECK_CODEMARKER", - "ANTITAMPER_TESTVALUE_CODEMARKER", - "ANTITAMPER_TRIGGER_CODEMARKER", - }; - - // Prevent thesefrom actually doing anything - for (auto exportName : ANTITAMPER_EXPORTS) - { - - auto address = (uintptr_t)GetProcAddress(GetModuleHandleA("server.dll"), 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); - } - } - } + return GetEntByIndex(i); } - -KHOOK( - SpecialClientCommand, - ("server.dll", "48 89 5C 24 ? 48 89 74 24 ? 55 57 41 56 48 8D 6C 24 ? 48 81 EC ? ? ? ? 83 3A 00"), - bool, - __fastcall, - (void* player, CCommand* command)) +// clang-format off +AUTOHOOK(CL_CopyExistingEntity, engine.dll + 0x6F940, +bool, __fastcall, (void* a1)) +// clang-format on { - - static ConVar* sv_cheats = g_pCVar->FindVar("sv_cheats"); - - if (sv_cheats->GetBool()) - return oSpecialClientCommand(player, command); // Don't block anything if sv_cheats is on - - // These are mostly from Portal 2 (sigh) - constexpr const char* blockedCommands[] = { - "emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something) - - // These both execute a command for every single entity for some reason, nice one valve - "pre_go_to_hub", - "pre_go_to_calibration", - - "end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed - "load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer() + struct CEntityReadInfo + { + BYTE gap[40]; + int nNewEntity; }; - if (command->ArgC() > 0) + CEntityReadInfo* pReadInfo = (CEntityReadInfo*)a1; + if (pReadInfo->nNewEntity >= 0x1000 || pReadInfo->nNewEntity < 0) { - std::string cmdStr = command->Arg(0); - for (char& c : cmdStr) - c = tolower(c); - - for (const char* blockedCommand : blockedCommands) - { - if (cmdStr.find(blockedCommand) != std::string::npos) - { - // Block this command - spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr); - return true; - } - } + // Value isn't sanitized in release builds for + // every game powered by the Source Engine 1 + // causing read/write outside of array bounds. + // This defect has let to the achievement of a + // full-chain RCE exploit. We hook and perform + // sanity checks for the value of m_nNewEntity + // here to prevent this behavior from happening. + return false; } - return oSpecialClientCommand(player, command); + return CL_CopyExistingEntity(a1); } -void SetupKHook(KHook* hook) +ON_DLL_LOAD("engine.dll", EngineExploitFixes, (CModule module)) { - if (hook->Setup()) - { - spdlog::debug("KHook::Setup(): Hooked at {}", hook->targetFuncAddr); - } - else - { - spdlog::critical("\tFAILED to initialize all exploit patches."); + AUTOHOOK_DISPATCH_MODULE(engine.dll) - // Force exit - MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR); - exit(0); - } -} - -void ExploitFixes::LoadCallback_MultiModule(HMODULE baseAddress) -{ + CCommand__Tokenize = module.Offset(0x418380).As(); - spdlog::info("ExploitFixes::LoadCallback_MultiModule({}) ...", (void*)baseAddress); + // allow client/ui to run clientcommands despite restricting servercommands + module.Offset(0x4FB65).Patch("EB 11"); + module.Offset(0x4FBAC).Patch("EB 16"); - int hooksEnabled = 0; - for (auto itr = KHook::_allHooks.begin(); itr != KHook::_allHooks.end(); itr++) + // patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails { - auto curHook = *itr; - if (GetModuleHandleA(curHook->targetFunc.moduleName) == baseAddress) - { - SetupKHook(curHook); - itr = KHook::_allHooks.erase(itr); // Prevent repeated initialization + MemoryAddress writeAddress(&bWasWritingStringTableSuccessful - module.Offset(0x234EDC).m_nAddress); - hooksEnabled++; + MemoryAddress addr = module.Offset(0x234ED2); + addr.Patch("C7 05"); + addr.Offset(2).Patch((BYTE*)&writeAddress, sizeof(writeAddress)); - if (itr == KHook::_allHooks.end()) - break; - } - } + addr.Offset(6).Patch("00 00 00 00"); - spdlog::info("\tEnabled {} hooks.", hooksEnabled); + addr.Offset(10).NOP(5); + } } -void ExploitFixes::LoadCallback_Full(HMODULE baseAddress) +ON_DLL_LOAD_RELIESON("server.dll", ServerExploitFixes, ConVar, (CModule module)) { - spdlog::info("ExploitFixes::LoadCallback_Full ..."); + AUTOHOOK_DISPATCH_MODULE(server.dll) - spdlog::info("\tByte patching..."); - DoBytePatches(); + // 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 + module.Offset(0x153920).Patch("C3"); - for (KHook* hook : KHook::_allHooks) - SetupKHook(hook); - - spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " late exploit-patch hooks."); - KHook::_allHooks.clear(); + // Dumb ANTITAMPER patches (they negatively impact performance and security) + constexpr const char* ANTITAMPER_EXPORTS[] = { + "ANTITAMPER_SPOTCHECK_CODEMARKER", + "ANTITAMPER_TESTVALUE_CODEMARKER", + "ANTITAMPER_TRIGGER_CODEMARKER", + }; - ns_exploitfixes_log = - new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever exploitfixes.cpp blocks/corrects something"); + // Prevent these from actually doing anything + for (auto exportName : ANTITAMPER_EXPORTS) + { + MemoryAddress exportAddr = module.GetExport(exportName); + if (exportAddr) + { + // Just return, none of them have any args or are userpurge + exportAddr.Patch("C3"); + spdlog::info("Patched AntiTamper function export \"{}\"", exportName); + } + } - g_pCVar->FindCommand("migrateme")->m_nFlags &= ~FCVAR_SERVER_CAN_EXECUTE; + Cvar_ns_exploitfixes_log = + new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something"); + Cvar_ns_should_log_all_clientcommands = + new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands"); - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x2a8a50, &GetEntByIndexHook, reinterpret_cast(&GetEntByIndex)); + Cvar_sv_cheats = R2::g_pCVar->FindVar("sv_cheats"); } diff --git a/NorthstarDLL/exploitfixes.h b/NorthstarDLL/exploitfixes.h deleted file mode 100644 index d0754d72..00000000 --- a/NorthstarDLL/exploitfixes.h +++ /dev/null @@ -1,12 +0,0 @@ -// KittenPopo's exploit fix hooks, feel free to add more here - -#pragma once -#include "pch.h" - -namespace ExploitFixes -{ - // This callback will be ran muliple times on multiple module loads - void LoadCallback_MultiModule(HMODULE baseAddress); - - void LoadCallback_Full(HMODULE unused); -} // namespace ExploitFixes diff --git a/NorthstarDLL/exploitfixes_lzss.cpp b/NorthstarDLL/exploitfixes_lzss.cpp new file mode 100644 index 00000000..4205133a --- /dev/null +++ b/NorthstarDLL/exploitfixes_lzss.cpp @@ -0,0 +1,79 @@ +#include "pch.h" + +AUTOHOOK_INIT() + +static constexpr int LZSS_LOOKSHIFT = 4; + +struct lzss_header_t +{ + unsigned int id; + unsigned int actualSize; +}; + +// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read +// out of the bounds of the output buffer. +// clang-format off +AUTOHOOK(CLZSS__SafeDecompress, engine.dll + 0x432A10, +unsigned int, __fastcall, (void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize)) +// clang-format on +{ + unsigned int totalBytes = 0; + int getCmdByte = 0; + int cmdByte = 0; + + lzss_header_t header = *(lzss_header_t*)pInput; + + if (!pInput || !header.actualSize || header.id != 0x53535A4C || header.actualSize > unBufSize) + return 0; + + pInput += sizeof(lzss_header_t); + + for (;;) + { + if (!getCmdByte) + cmdByte = *pInput++; + + getCmdByte = (getCmdByte + 1) & 0x07; + + if (cmdByte & 0x01) + { + int position = *pInput++ << LZSS_LOOKSHIFT; + position |= (*pInput >> LZSS_LOOKSHIFT); + position += 1; + int count = (*pInput++ & 0x0F) + 1; + if (count == 1) + break; + + // Ensure reference chunk exists entirely within our buffer + if (position > totalBytes) + return 0; + + totalBytes += count; + if (totalBytes > unBufSize) + return 0; + + unsigned char* pSource = pOutput - position; + for (int i = 0; i < count; i++) + *pOutput++ = *pSource++; + } + else + { + totalBytes++; + if (totalBytes > unBufSize) + return 0; + + *pOutput++ = *pInput++; + } + cmdByte = cmdByte >> 1; + } + + if (totalBytes != header.actualSize) + return 0; + + return totalBytes; +} + +ON_DLL_LOAD("engine.dll", ExploitFixes_LZSS, (CModule module)) +{ + AUTOHOOK_DISPATCH() +} diff --git a/NorthstarDLL/exploitfixes_utf8parser.cpp b/NorthstarDLL/exploitfixes_utf8parser.cpp new file mode 100644 index 00000000..e2510765 --- /dev/null +++ b/NorthstarDLL/exploitfixes_utf8parser.cpp @@ -0,0 +1,200 @@ +#include "pch.h" + +AUTOHOOK_INIT() + +INT64(__fastcall* sub_F1320)(DWORD a1, char* a2); + +// Reimplementation of an exploitable UTF decoding function in titanfall +bool __fastcall CheckUTF8Valid(INT64* a1, DWORD* a2, char* strData) +{ + 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 (!MemoryAddress(v4).IsMemoryReadable(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 +// clang-format off +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 +// clang-format on +{ + static void* targetRetAddr = CModule("engine.dll").FindPattern("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 + void* pReturnAddress = +#ifdef _MSC_VER + _ReturnAddress() +#else + __builtin_return_address(0) +#endif + ; + + if (pReturnAddress == targetRetAddr && !CheckUTF8Valid(a1, a2, strData)) + return false; + + return Rson_ParseUTF8(a1, a2, strData); +} + +ON_DLL_LOAD("engine.dll", EngineExploitFixes_UTF8Parser, (CModule module)) +{ + AUTOHOOK_DISPATCH() + + sub_F1320 = module.FindPattern("83 F9 7F 77 08 88 0A").As(); +} diff --git a/NorthstarDLL/exploitfixes_utf8parser.h b/NorthstarDLL/exploitfixes_utf8parser.h deleted file mode 100644 index 24545ea3..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 diff --git a/NorthstarDLL/filesystem.cpp b/NorthstarDLL/filesystem.cpp index c17d813f..e6c6f49a 100644 --- a/NorthstarDLL/filesystem.cpp +++ b/NorthstarDLL/filesystem.cpp @@ -1,87 +1,69 @@ #include "pch.h" #include "filesystem.h" -#include "hooks.h" -#include "hookutils.h" #include "sourceinterface.h" #include "modmanager.h" #include #include -// hook forward declares -typedef FileHandle_t (*ReadFileFromVPKType)(VPKData* vpkInfo, __int64* b, char* filename); -ReadFileFromVPKType readFileFromVPK; -FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename); +AUTOHOOK_INIT() -typedef bool (*ReadFromCacheType)(IFileSystem* filesystem, char* path, void* result); -ReadFromCacheType readFromCache; -bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result); +using namespace R2; -typedef void (*AddSearchPathType)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType); -AddSearchPathType addSearchPathOriginal; -void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType); +bool bReadingOriginalFile = false; +std::string sCurrentModPath; -typedef FileHandle_t (*ReadFileFromFilesystemType)( - IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5); -ReadFileFromFilesystemType readFileFromFilesystem; -FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5); +ConVar* Cvar_ns_fs_log_reads; -typedef VPKData* (*MountVPKType)(IFileSystem* fileSystem, const char* vpkPath); -MountVPKType mountVPK; -VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath); +// use the R2 namespace for game funcs +namespace R2 +{ + SourceInterface* g_pFilesystem; -bool readingOriginalFile; -std::string currentModPath; -SourceInterface* g_Filesystem; + std::string ReadVPKFile(const char* path) + { + // read scripts.rson file, todo: check if this can be overwritten + FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0); -void InitialiseFilesystem(HMODULE baseAddress) -{ - g_Filesystem = new SourceInterface("filesystem_stdio.dll", "VFileSystem017"); - - // create hooks - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x5CBA0, &ReadFileFromVPKHook, reinterpret_cast(&readFileFromVPK)); - ENABLER_CREATEHOOK( - hook, - reinterpret_cast((*g_Filesystem)->m_vtable->ReadFromCache), - &ReadFromCacheHook, - reinterpret_cast(&readFromCache)); - ENABLER_CREATEHOOK( - hook, - reinterpret_cast((*g_Filesystem)->m_vtable->AddSearchPath), - &AddSearchPathHook, - reinterpret_cast(&addSearchPathOriginal)); - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15F20, &ReadFileFromFilesystemHook, reinterpret_cast(&readFileFromFilesystem)); - ENABLER_CREATEHOOK( - hook, reinterpret_cast((*g_Filesystem)->m_vtable->MountVPK), &MountVPKHook, reinterpret_cast(&mountVPK)); -} + std::stringstream fileStream; + int bytesRead = 0; + char data[4096]; + do + { + bytesRead = (*g_pFilesystem)->m_vtable2->Read(&(*g_pFilesystem)->m_vtable2, data, (int)std::size(data), fileHandle); + fileStream.write(data, bytesRead); + } while (bytesRead == std::size(data)); -std::string ReadVPKFile(const char* path) -{ - // read scripts.rson file, todo: check if this can be overwritten - FileHandle_t fileHandle = (*g_Filesystem)->m_vtable2->Open(&(*g_Filesystem)->m_vtable2, path, "rb", "GAME", 0); + (*g_pFilesystem)->m_vtable2->Close(*g_pFilesystem, fileHandle); + + return fileStream.str(); + } - std::stringstream fileStream; - int bytesRead = 0; - char data[4096]; - do + std::string ReadVPKOriginalFile(const char* path) { - bytesRead = (*g_Filesystem)->m_vtable2->Read(&(*g_Filesystem)->m_vtable2, data, (int)std::size(data), fileHandle); - fileStream.write(data, bytesRead); - } while (bytesRead == std::size(data)); + // todo: should probably set search path to be g_pModName here also - (*g_Filesystem)->m_vtable2->Close(*g_Filesystem, fileHandle); + bReadingOriginalFile = true; + std::string ret = ReadVPKFile(path); + bReadingOriginalFile = false; - return fileStream.str(); -} + return ret; + } +} // namespace R2 -std::string ReadVPKOriginalFile(const char* path) +// clang-format off +HOOK(AddSearchPathHook, AddSearchPath, +void, __fastcall, (IFileSystem * fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)) +// clang-format on { - readingOriginalFile = true; - std::string ret = ReadVPKFile(path); - readingOriginalFile = false; + AddSearchPath(fileSystem, pPath, pathID, addType); - return ret; + // make sure current mod paths are at head + if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD) + { + AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD); + AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD); + } } void SetNewModSearchPaths(Mod* mod) @@ -90,96 +72,84 @@ void SetNewModSearchPaths(Mod* mod) // in the future we could also determine whether the file we're setting paths for needs a mod dir, or compiled assets if (mod != nullptr) { - if ((fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().compare(currentModPath)) + if ((fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().compare(sCurrentModPath)) { - spdlog::info("changing mod search path from {} to {}", currentModPath, mod->ModDirectory.string()); + spdlog::info("changing mod search path from {} to {}", sCurrentModPath, mod->m_ModDirectory.string()); - addSearchPathOriginal( - &*(*g_Filesystem), (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD); - currentModPath = (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string(); + AddSearchPath( + &*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD); + sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string(); } } else // push compiled to head - addSearchPathOriginal(&*(*g_Filesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD); + AddSearchPath(&*(*g_pFilesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD); } -bool TryReplaceFile(char* path, bool shouldCompile) +bool TryReplaceFile(const char* pPath, bool shouldCompile) { - if (readingOriginalFile) + if (bReadingOriginalFile) return false; if (shouldCompile) - (*g_ModManager).CompileAssetsForFile(path); + g_pModManager->CompileAssetsForFile(pPath); // idk how efficient the lexically normal check is // can't just set all /s in path to \, since some paths aren't in writeable memory - auto file = g_ModManager->m_modFiles.find(fs::path(path).lexically_normal().string()); - if (file != g_ModManager->m_modFiles.end()) + auto file = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path(pPath))); + if (file != g_pModManager->m_ModFiles.end()) { - SetNewModSearchPaths(file->second.owningMod); + SetNewModSearchPaths(file->second.m_pOwningMod); return true; } return false; } -FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename) +// force modded files to be read from mods, not cache +// clang-format off +HOOK(ReadFromCacheHook, ReadFromCache, +bool, __fastcall, (IFileSystem * filesystem, char* pPath, void* result)) +// clang-format off { - // move this to a convar at some point when we can read them in native - // spdlog::info("ReadFileFromVPKHook {} {}", filename, vpkInfo->path); - - // there is literally never any reason to compile here, since we'll always compile in ReadFileFromFilesystemHook in the same codepath - // this is called - if (TryReplaceFile(filename, false)) - { - *b = -1; - return b; - } - - return readFileFromVPK(vpkInfo, b, filename); -} - -bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result) -{ - // move this to a convar at some point when we can read them in native - // spdlog::info("ReadFromCacheHook {}", path); - - if (TryReplaceFile(path, true)) + if (TryReplaceFile(pPath, true)) return false; - return readFromCache(filesystem, path, result); + return ReadFromCache(filesystem, pPath, result); } -void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType) +// force modded files to be read from mods, not vpk +// clang-format off +AUTOHOOK(ReadFileFromVPK, filesystem_stdio.dll + 0x5CBA0, +FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename)) +// clang-format on { - addSearchPathOriginal(fileSystem, pPath, pathID, addType); - - // make sure current mod paths are at head - if (!strcmp(pathID, "GAME") && currentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD) + // don't compile here because this is only ever called from OpenEx, which already compiles + if (TryReplaceFile(filename, false)) { - addSearchPathOriginal(fileSystem, currentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD); - addSearchPathOriginal(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD); + *b = -1; + return b; } + + return ReadFileFromVPK(vpkInfo, b, filename); } -FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5) +// clang-format off +AUTOHOOK(CBaseFileSystem__OpenEx, filesystem_stdio.dll + 0x15F50, +FileHandle_t, __fastcall, (IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char **ppszResolvedFilename)) +// clang-format on { - // this isn't super efficient, but it's necessary, since calling addsearchpath in readfilefromvpk doesn't work, possibly refactor later - // it also might be possible to hook functions that are called later, idk look into callstack for ReadFileFromVPK - if (!readingOriginalFile) - TryReplaceFile((char*)pPath, true); - - return readFileFromFilesystem(filesystem, pPath, pOptions, a4, a5); + TryReplaceFile(pPath, true); + return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename); } -VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath) +HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath)) { - spdlog::info("MountVPK {}", vpkPath); - VPKData* ret = mountVPK(fileSystem, vpkPath); + spdlog::info("MountVPK {}", pVpkPath); + VPKData* ret = MountVPK(fileSystem, pVpkPath); - for (Mod mod : g_ModManager->m_loadedMods) + for (Mod mod : g_pModManager->m_LoadedMods) { - if (!mod.Enabled) + if (!mod.m_bEnabled) continue; for (ModVPKEntry& vpkEntry : mod.Vpks) @@ -189,13 +159,13 @@ VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath) { // resolve vpk name and try to load one with the same name // todo: we should be unloading these on map unload manually - std::string mapName(fs::path(vpkPath).filename().string()); + std::string mapName(fs::path(pVpkPath).filename().string()); std::string modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string()); if (mapName.compare(modMapName)) continue; } - VPKData* loaded = mountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str()); + VPKData* loaded = MountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str()); if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here ret = loaded; } @@ -203,3 +173,14 @@ VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath) return ret; } + +ON_DLL_LOAD("filesystem_stdio.dll", Filesystem, (CModule module)) +{ + AUTOHOOK_DISPATCH() + + R2::g_pFilesystem = new SourceInterface("filesystem_stdio.dll", "VFileSystem017"); + + AddSearchPathHook.Dispatch((*g_pFilesystem)->m_vtable->AddSearchPath); + ReadFromCacheHook.Dispatch((*g_pFilesystem)->m_vtable->ReadFromCache); + MountVPKHook.Dispatch((*g_pFilesystem)->m_vtable->MountVPK); +} diff --git a/NorthstarDLL/filesystem.h b/NorthstarDLL/filesystem.h index 6015c6a5..c326b419 100644 --- a/NorthstarDLL/filesystem.h +++ b/NorthstarDLL/filesystem.h @@ -59,7 +59,8 @@ class IFileSystem FileHandle_t (*Open)( IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pOptions, const char* pathID, int64_t unknown); void (*Close)(IFileSystem* fileSystem, FileHandle_t file); - void* unknown2[6]; + long long (*Seek)(IFileSystem::VTable2** fileSystem, FileHandle_t file, long long offset, long long whence); + void* unknown2[5]; bool (*FileExists)(IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pPathID); }; @@ -67,8 +68,11 @@ class IFileSystem VTable2* m_vtable2; }; -std::string ReadVPKFile(const char* path); -std::string ReadVPKOriginalFile(const char* path); +// use the R2 namespace for game funcs +namespace R2 +{ + extern SourceInterface* g_pFilesystem; -void InitialiseFilesystem(HMODULE baseAddress); -extern SourceInterface* g_Filesystem; + std::string ReadVPKFile(const char* path); + std::string ReadVPKOriginalFile(const char* path); +} // namespace R2 diff --git a/NorthstarDLL/gameutils.cpp b/NorthstarDLL/gameutils.cpp deleted file mode 100644 index 405624ca..00000000 --- a/NorthstarDLL/gameutils.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "pch.h" -#include "convar.h" -#include "gameutils.h" - -// memory -IMemAlloc* g_pMemAllocSingleton; -typedef IMemAlloc* (*CreateGlobalMemAllocType)(); -CreateGlobalMemAllocType CreateGlobalMemAlloc; - -// cmd.h -Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; -Cbuf_AddTextType Cbuf_AddText; -Cbuf_ExecuteType Cbuf_Execute; - -// hoststate stuff -CHostState* g_pHostState; - -// cengine stuff -CEngine* g_pEngine; -server_state_t* sv_m_State; - -// network stuff -ConVar* Cvar_hostport; - -// playlist stuff -GetCurrentPlaylistType GetCurrentPlaylistName; -SetCurrentPlaylistType SetCurrentPlaylist; -SetPlaylistVarOverrideType SetPlaylistVarOverride; -GetCurrentPlaylistVarType GetCurrentPlaylistVar; - -// server entity stuff -Server_GetEntityByIndexType Server_GetEntityByIndex; - -// auth -char* g_LocalPlayerUserID; -char* g_LocalPlayerOriginToken; - -// misc stuff -ErrorType Error; -CommandLineType CommandLine; -Plat_FloatTimeType Plat_FloatTime; -ThreadInServerFrameThreadType ThreadInServerFrameThread; -GetBaseLocalClientType GetBaseLocalClient; - -void InitialiseEngineGameUtilFunctions(HMODULE baseAddress) -{ - Cbuf_GetCurrentPlayer = (Cbuf_GetCurrentPlayerType)((char*)baseAddress + 0x120630); - Cbuf_AddText = (Cbuf_AddTextType)((char*)baseAddress + 0x1203B0); - Cbuf_Execute = (Cbuf_ExecuteType)((char*)baseAddress + 0x1204B0); - - g_pHostState = (CHostState*)((char*)baseAddress + 0x7CF180); - g_pEngine = *(CEngine**)((char*)baseAddress + 0x7D70C8); - sv_m_State = (server_state_t*)((char*)baseAddress + 0x12A53D48); - - GetCurrentPlaylistName = (GetCurrentPlaylistType)((char*)baseAddress + 0x18C640); - SetCurrentPlaylist = (SetCurrentPlaylistType)((char*)baseAddress + 0x18EB20); - SetPlaylistVarOverride = (SetPlaylistVarOverrideType)((char*)baseAddress + 0x18ED00); - GetCurrentPlaylistVar = (GetCurrentPlaylistVarType)((char*)baseAddress + 0x18C680); - - g_LocalPlayerUserID = (char*)baseAddress + 0x13F8E688; - g_LocalPlayerOriginToken = (char*)baseAddress + 0x13979C80; - - GetBaseLocalClient = (GetBaseLocalClientType)((char*)baseAddress + 0x78200); - - /* NOTE: - g_pCVar->FindVar("convar_name") now works. These are no longer needed. - You can also itterate over every ConVar using CCVarIteratorInternal - dump the pointers to a vector and access them from there. - Example: - std::vector g_pAllConVars; - for (auto& map : g_pCVar->DumpToMap()) - { - ConVar* pConVar = g_pCVar->FindVar(map.first.c_str()); - if (pConVar) - { - g_pAllConVars.push_back(pConVar); - } - }*/ - - Cvar_hostport = (ConVar*)((char*)baseAddress + 0x13FA6070); -} - -void InitialiseServerGameUtilFunctions(HMODULE baseAddress) -{ - Server_GetEntityByIndex = (Server_GetEntityByIndexType)((char*)baseAddress + 0xFB820); -} - -void InitialiseTier0GameUtilFunctions(HMODULE baseAddress) -{ - if (!baseAddress) - { - spdlog::critical("tier0 base address is null, but it should be already loaded"); - throw "tier0 base address is null, but it should be already loaded"; - } - if (g_pMemAllocSingleton) - return; // seems this function was already called - CreateGlobalMemAlloc = reinterpret_cast(GetProcAddress(baseAddress, "CreateGlobalMemAlloc")); - IMemAlloc** ppMemAllocSingleton = reinterpret_cast(GetProcAddress(baseAddress, "g_pMemAllocSingleton")); - if (!ppMemAllocSingleton) - { - spdlog::critical("Address of g_pMemAllocSingleton is a null pointer, this should never happen"); - throw "Address of g_pMemAllocSingleton is a null pointer, this should never happen"; - } - if (!*ppMemAllocSingleton) - { - g_pMemAllocSingleton = CreateGlobalMemAlloc(); - *ppMemAllocSingleton = g_pMemAllocSingleton; - spdlog::info("Created new g_pMemAllocSingleton"); - } - else - { - g_pMemAllocSingleton = *ppMemAllocSingleton; - } - - Error = reinterpret_cast(GetProcAddress(baseAddress, "Error")); - CommandLine = reinterpret_cast(GetProcAddress(baseAddress, "CommandLine")); - Plat_FloatTime = reinterpret_cast(GetProcAddress(baseAddress, "Plat_FloatTime")); - ThreadInServerFrameThread = reinterpret_cast(GetProcAddress(baseAddress, "ThreadInServerFrameThread")); -} diff --git a/NorthstarDLL/gameutils.h b/NorthstarDLL/gameutils.h deleted file mode 100644 index e86fa38c..00000000 --- a/NorthstarDLL/gameutils.h +++ /dev/null @@ -1,249 +0,0 @@ -#pragma once -#include "convar.h" - -// memory -class IMemAlloc -{ - public: - struct VTable - { - void* unknown[1]; // alloc debug - void* (*Alloc)(IMemAlloc* memAlloc, size_t nSize); - void* unknown2[1]; // realloc debug - void* (*Realloc)(IMemAlloc* memAlloc, void* pMem, size_t nSize); - void* unknown3[1]; // free #1 - void (*Free)(IMemAlloc* memAlloc, void* pMem); - void* unknown4[2]; // nullsubs, maybe CrtSetDbgFlag - size_t (*GetSize)(IMemAlloc* memAlloc, void* pMem); - void* unknown5[9]; // they all do literally nothing - void (*DumpStats)(IMemAlloc* memAlloc); - void (*DumpStatsFileBase)(IMemAlloc* memAlloc, const char* pchFileBase); - void* unknown6[4]; - int (*heapchk)(IMemAlloc* memAlloc); - }; - - VTable* m_vtable; -}; - -extern IMemAlloc* g_pMemAllocSingleton; -typedef IMemAlloc* (*CreateGlobalMemAllocType)(); -extern CreateGlobalMemAllocType CreateGlobalMemAlloc; - -// cmd.h -enum class ECommandTarget_t -{ - CBUF_FIRST_PLAYER = 0, - CBUF_LAST_PLAYER = 1, // MAX_SPLITSCREEN_CLIENTS - 1, MAX_SPLITSCREEN_CLIENTS = 2 - CBUF_SERVER = CBUF_LAST_PLAYER + 1, - - CBUF_COUNT, -}; - -enum class cmd_source_t -{ - // Added to the console buffer by gameplay code. Generally unrestricted. - kCommandSrcCode, - - // Sent from code via engine->ClientCmd, which is restricted to commands visible - // via FCVAR_CLIENTCMD_CAN_EXECUTE. - kCommandSrcClientCmd, - - // Typed in at the console or via a user key-bind. Generally unrestricted, although - // the client will throttle commands sent to the server this way to 16 per second. - kCommandSrcUserInput, - - // Came in over a net connection as a clc_stringcmd - // host_client will be valid during this state. - // - // Restricted to FCVAR_GAMEDLL commands (but not convars) and special non-ConCommand - // server commands hardcoded into gameplay code (e.g. "joingame") - kCommandSrcNetClient, - - // Received from the server as the client - // - // Restricted to commands with FCVAR_SERVER_CAN_EXECUTE - kCommandSrcNetServer, - - // Being played back from a demo file - // - // Not currently restricted by convar flag, but some commands manually ignore calls - // from this source. FIXME: Should be heavily restricted as demo commands can come - // from untrusted sources. - kCommandSrcDemoFile, - - // Invalid value used when cleared - kCommandSrcInvalid = -1 -}; - -typedef ECommandTarget_t (*Cbuf_GetCurrentPlayerType)(); -extern Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; - -// compared to the defs i've seen, this is missing an arg, it could be nTickInterval or source, not sure, guessing it's source -typedef void (*Cbuf_AddTextType)(ECommandTarget_t eTarget, const char* text, cmd_source_t source); -extern Cbuf_AddTextType Cbuf_AddText; - -typedef void (*Cbuf_ExecuteType)(); -extern Cbuf_ExecuteType Cbuf_Execute; - -// commandline stuff -class CCommandLine -{ - public: - // based on the defs in the 2013 source sdk, but for some reason has an extra function (may be another CreateCmdLine overload?) - // these seem to line up with what they should be though - virtual void CreateCmdLine(const char* commandline) {} - virtual void CreateCmdLine(int argc, char** argv) {} - virtual void unknown() {} - virtual const char* GetCmdLine(void) const {} - - virtual const char* CheckParm(const char* psz, const char** ppszValue = 0) const {} - virtual void RemoveParm() const {} - virtual void AppendParm(const char* pszParm, const char* pszValues) {} - - virtual const char* ParmValue(const char* psz, const char* pDefaultVal = 0) const {} - virtual int ParmValue(const char* psz, int nDefaultVal) const {} - virtual float ParmValue(const char* psz, float flDefaultVal) const {} - - virtual int ParmCount() const {} - virtual int FindParm(const char* psz) const {} - virtual const char* GetParm(int nIndex) const {} - virtual void SetParm(int nIndex, char const* pParm) {} - - // virtual const char** GetParms() const {} -}; - -// hoststate stuff -enum HostState_t -{ - HS_NEW_GAME = 0, - HS_LOAD_GAME, - HS_CHANGE_LEVEL_SP, - HS_CHANGE_LEVEL_MP, - HS_RUN, - HS_GAME_SHUTDOWN, - HS_SHUTDOWN, - HS_RESTART, -}; - -struct CHostState -{ - public: - HostState_t m_iCurrentState; - HostState_t m_iNextState; - - float m_vecLocation[3]; - float m_angLocation[3]; - - char m_levelName[32]; - char m_mapGroupName[32]; - char m_landmarkName[32]; - char m_saveName[32]; - float m_flShortFrameTime; // run a few one-tick frames to avoid large timesteps while loading assets - - bool m_activeGame; - bool m_bRememberLocation; - bool m_bBackgroundLevel; - bool m_bWaitingForConnection; - bool m_bLetToolsOverrideLoadGameEnts; // During a load game, this tells Foundry to override ents that are selected in Hammer. - bool m_bSplitScreenConnect; - bool m_bGameHasShutDownAndFlushedMemory; // This is false once we load a map into memory, and set to true once the map is unloaded and - // all memory flushed - bool m_bWorkshopMapDownloadPending; -}; - -extern CHostState* g_pHostState; - -// cengine stuff -enum EngineQuitState -{ - QUIT_NOTQUITTING = 0, - QUIT_TODESKTOP, - QUIT_RESTART -}; - -enum EngineState_t -{ - DLL_INACTIVE = 0, // no dll - DLL_ACTIVE, // engine is focused - DLL_CLOSE, // closing down dll - DLL_RESTART, // engine is shutting down but will restart right away - DLL_PAUSED, // engine is paused, can become active from this state -}; - -class CEngine -{ - public: - virtual void unknown() {} // unsure if this is where - virtual bool Load(bool dedicated, const char* baseDir) {} - virtual void Unload() {} - virtual void SetNextState(EngineState_t iNextState) {} - virtual EngineState_t GetState() {} - virtual void Frame() {} - virtual double GetFrameTime() {} - virtual float GetCurTime() {} - - EngineQuitState m_nQuitting; - EngineState_t m_nDllState; - EngineState_t m_nNextDllState; - double m_flCurrentTime; - float m_flFrameTime; - double m_flPreviousTime; - float m_flFilteredTime; - float m_flMinFrameTime; // Expected duration of a frame, or zero if it is unlimited. -}; - -extern CEngine* g_pEngine; - -enum server_state_t -{ - ss_dead = 0, // Dead - ss_loading, // Spawning - ss_active, // Running - ss_paused, // Running, but paused -}; - -extern server_state_t* sv_m_State; - -// network stuff -extern ConVar* Cvar_hostport; - -// playlist stuff -typedef const char* (*GetCurrentPlaylistType)(); -extern GetCurrentPlaylistType GetCurrentPlaylistName; - -typedef void (*SetCurrentPlaylistType)(const char* playlistName); -extern SetCurrentPlaylistType SetCurrentPlaylist; - -typedef void (*SetPlaylistVarOverrideType)(const char* varName, const char* value); -extern SetPlaylistVarOverrideType SetPlaylistVarOverride; - -typedef char* (*GetCurrentPlaylistVarType)(const char* varName, bool useOverrides); -extern GetCurrentPlaylistVarType GetCurrentPlaylistVar; - -// server entity stuff -typedef void* (*Server_GetEntityByIndexType)(int index); -extern Server_GetEntityByIndexType Server_GetEntityByIndex; - -// auth -extern char* g_LocalPlayerUserID; -extern char* g_LocalPlayerOriginToken; - -// misc stuff -typedef void (*ErrorType)(const char* fmt, ...); -extern ErrorType Error; - -typedef CCommandLine* (*CommandLineType)(); -extern CommandLineType CommandLine; - -typedef double (*Plat_FloatTimeType)(); -extern Plat_FloatTimeType Plat_FloatTime; - -typedef bool (*ThreadInServerFrameThreadType)(); -extern ThreadInServerFrameThreadType ThreadInServerFrameThread; - -typedef void* (*GetBaseLocalClientType)(); -extern GetBaseLocalClientType GetBaseLocalClient; - -void InitialiseEngineGameUtilFunctions(HMODULE baseAddress); -void InitialiseServerGameUtilFunctions(HMODULE baseAddress); -void InitialiseTier0GameUtilFunctions(HMODULE baseAddress); diff --git a/NorthstarDLL/hooks.cpp b/NorthstarDLL/hooks.cpp index 72ae727a..cca1d986 100644 --- a/NorthstarDLL/hooks.cpp +++ b/NorthstarDLL/hooks.cpp @@ -1,61 +1,193 @@ #include "pch.h" -#include "hooks.h" -#include "hookutils.h" -#include "sigscanning.h" #include "dedicated.h" +#include #include #include #include #include #include #include +#include -typedef LPSTR (*GetCommandLineAType)(); -LPSTR GetCommandLineAHook(); +AUTOHOOK_INIT() -typedef HMODULE (*LoadLibraryExAType)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); -HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); +// called from the ON_DLL_LOAD macros +__dllLoadCallback::__dllLoadCallback( + eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn) +{ + // parse reliesOn array from string + std::vector reliesOnArray; -typedef HMODULE (*LoadLibraryAType)(LPCSTR lpLibFileName); -HMODULE LoadLibraryAHook(LPCSTR lpLibFileName); + if (reliesOn.length() && reliesOn[0] != '(') + { + reliesOnArray.push_back(reliesOn); + } + else + { + // follows the format (tag, tag, tag) + std::string sCurrentTag; + for (int i = 1; i < reliesOn.length(); i++) + { + if (!isspace(reliesOn[i])) + { + if (reliesOn[i] == ',' || reliesOn[i] == ')') + { + reliesOnArray.push_back(sCurrentTag); + sCurrentTag = ""; + } + else + sCurrentTag += reliesOn[i]; + } + } + } -typedef HMODULE (*LoadLibraryExWType)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); -HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); + switch (side) + { + case eDllLoadCallbackSide::UNSIDED: + { + AddDllLoadCallback(dllName, callback, uniqueStr, reliesOnArray); + break; + } -typedef HMODULE (*LoadLibraryWType)(LPCWSTR lpLibFileName); -HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName); + case eDllLoadCallbackSide::CLIENT: + { + AddDllLoadCallbackForClient(dllName, callback, uniqueStr, reliesOnArray); + break; + } -GetCommandLineAType GetCommandLineAOriginal; -LoadLibraryExAType LoadLibraryExAOriginal; -LoadLibraryAType LoadLibraryAOriginal; -LoadLibraryExWType LoadLibraryExWOriginal; -LoadLibraryWType LoadLibraryWOriginal; + case eDllLoadCallbackSide::DEDICATED_SERVER: + { + AddDllLoadCallbackForDedicatedServer(dllName, callback, uniqueStr, reliesOnArray); + break; + } + } +} -void InstallInitialHooks() +void __fileAutohook::Dispatch() { - if (MH_Initialize() != MH_OK) - spdlog::error("MH_Initialize (minhook initialization) failed"); + for (__autohook* hook : hooks) + hook->Dispatch(); +} + +void __fileAutohook::DispatchForModule(const char* pModuleName) +{ + const int iModuleNameLen = strlen(pModuleName); + + for (__autohook* hook : hooks) + if ((hook->iAddressResolutionMode == __autohook::OFFSET_STRING && !strncmp(pModuleName, hook->pAddrString, iModuleNameLen)) || + (hook->iAddressResolutionMode == __autohook::PROCADDRESS && !strcmp(pModuleName, hook->pModuleName))) + hook->Dispatch(); +} - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, reinterpret_cast(&GetCommandLineA), &GetCommandLineAHook, reinterpret_cast(&GetCommandLineAOriginal)); - ENABLER_CREATEHOOK( - hook, reinterpret_cast(&LoadLibraryExA), &LoadLibraryExAHook, reinterpret_cast(&LoadLibraryExAOriginal)); - ENABLER_CREATEHOOK(hook, reinterpret_cast(&LoadLibraryA), &LoadLibraryAHook, reinterpret_cast(&LoadLibraryAOriginal)); - ENABLER_CREATEHOOK( - hook, reinterpret_cast(&LoadLibraryExW), &LoadLibraryExWHook, reinterpret_cast(&LoadLibraryExWOriginal)); - ENABLER_CREATEHOOK(hook, reinterpret_cast(&LoadLibraryW), &LoadLibraryWHook, reinterpret_cast(&LoadLibraryWOriginal)); +ManualHook::ManualHook(const char* funcName, LPVOID func) : pHookFunc(func), ppOrigFunc(nullptr) +{ + const int iFuncNameStrlen = strlen(funcName); + pFuncName = new char[iFuncNameStrlen]; + memcpy(pFuncName, funcName, iFuncNameStrlen); } -LPSTR GetCommandLineAHook() +ManualHook::ManualHook(const char* funcName, LPVOID* orig, LPVOID func) : pHookFunc(func), ppOrigFunc(orig) +{ + const int iFuncNameStrlen = strlen(funcName); + pFuncName = new char[iFuncNameStrlen]; + memcpy(pFuncName, funcName, iFuncNameStrlen); +} + +bool ManualHook::Dispatch(LPVOID addr, LPVOID* orig) +{ + if (orig) + ppOrigFunc = orig; + + if (MH_CreateHook(addr, pHookFunc, ppOrigFunc) == MH_OK) + { + if (MH_EnableHook(addr) == MH_OK) + { + spdlog::info("Enabling hook {}", pFuncName); + return true; + } + else + spdlog::error("MH_EnableHook failed for function {}", pFuncName); + } + else + spdlog::error("MH_CreateHook failed for function {}", pFuncName); + + return false; +} + +// dll load callback stuff +// this allows for code to register callbacks to be run as soon as a dll is loaded, mainly to allow for patches to be made on dll load +struct DllLoadCallback +{ + std::string dll; + DllLoadCallbackFuncType callback; + std::string tag; + std::vector reliesOn; + bool called; +}; + +// HACK: declaring and initialising this vector at file scope crashes on debug builds due to static initialisation order +// using a static var like this ensures that the vector is initialised lazily when it's used +std::vector& GetDllLoadCallbacks() +{ + static std::vector vec = std::vector(); + return vec; +} + +void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector reliesOn) +{ + DllLoadCallback& callbackStruct = GetDllLoadCallbacks().emplace_back(); + + callbackStruct.dll = dll; + callbackStruct.callback = callback; + callbackStruct.tag = tag; + callbackStruct.reliesOn = reliesOn; + callbackStruct.called = false; +} + +void AddDllLoadCallbackForDedicatedServer( + std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector reliesOn) +{ + if (!IsDedicatedServer()) + return; + + AddDllLoadCallback(dll, callback, tag, reliesOn); +} + +void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector reliesOn) +{ + if (IsDedicatedServer()) + return; + + AddDllLoadCallback(dll, callback, tag, reliesOn); +} + +void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFuncName) +{ + char* pStrippedFuncName = (char*)pFuncName; + // strip & char from funcname + if (*pStrippedFuncName == '&') + pStrippedFuncName++; + + if (MH_CreateHook(pTarget, pDetour, (LPVOID*)ppOriginal) == MH_OK) + { + if (MH_EnableHook(pTarget) == MH_OK) + spdlog::info("Enabling hook {}", pStrippedFuncName); + else + spdlog::error("MH_EnableHook failed for function {}", pStrippedFuncName); + } + else + spdlog::error("MH_CreateHook failed for function {}", pStrippedFuncName); +} + +AUTOHOOK_ABSOLUTEADDR(_GetCommandLineA, GetCommandLineA, LPSTR, WINAPI, ()) { static char* cmdlineModified; static char* cmdlineOrg; if (cmdlineOrg == nullptr || cmdlineModified == nullptr) { - cmdlineOrg = GetCommandLineAOriginal(); + cmdlineOrg = _GetCommandLineA(); bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs"); @@ -111,77 +243,86 @@ LPSTR GetCommandLineAHook() return cmdlineModified; } -// dll load callback stuff -// this allows for code to register callbacks to be run as soon as a dll is loaded, mainly to allow for patches to be made on dll load -struct DllLoadCallback -{ - std::string dll; - DllLoadCallbackFuncType callback; - bool called; -}; - -std::vector dllLoadCallbacks; - -void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback) -{ - DllLoadCallback* callbackStruct = new DllLoadCallback; - callbackStruct->dll = dll; - callbackStruct->callback = callback; - callbackStruct->called = false; - - dllLoadCallbacks.push_back(callbackStruct); -} - -void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback) -{ - if (!IsDedicatedServer()) - return; - - DllLoadCallback* callbackStruct = new DllLoadCallback; - callbackStruct->dll = dll; - callbackStruct->callback = callback; - callbackStruct->called = false; - - dllLoadCallbacks.push_back(callbackStruct); -} - -void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback) -{ - if (IsDedicatedServer()) - return; - - DllLoadCallback* callbackStruct = new DllLoadCallback; - callbackStruct->dll = dll; - callbackStruct->callback = callback; - callbackStruct->called = false; - - dllLoadCallbacks.push_back(callbackStruct); -} - +std::vector calledTags; void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress) { - for (auto& callbackStruct : dllLoadCallbacks) + CModule cModule(moduleAddress); + + while (true) { - if (!callbackStruct->called && - strstr(lpLibFileName + (strlen(lpLibFileName) - callbackStruct->dll.length()), callbackStruct->dll.c_str()) != nullptr) + bool bDoneCalling = true; + + for (auto& callbackStruct : GetDllLoadCallbacks()) { - callbackStruct->callback(moduleAddress); - callbackStruct->called = true; + if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename()) + { + bool bShouldContinue = false; + + if (!callbackStruct.reliesOn.empty()) + { + for (std::string tag : callbackStruct.reliesOn) + { + if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end()) + { + bDoneCalling = false; + bShouldContinue = true; + break; + } + } + } + + if (bShouldContinue) + continue; + + callbackStruct.callback(moduleAddress); + calledTags.push_back(callbackStruct.tag); + callbackStruct.called = true; + } } + + if (bDoneCalling) + break; } } void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress) { - for (auto& callbackStruct : dllLoadCallbacks) + CModule cModule(moduleAddress); + + while (true) { - std::wstring wcharStrDll = std::wstring(callbackStruct->dll.begin(), callbackStruct->dll.end()); - const wchar_t* callbackDll = wcharStrDll.c_str(); - if (!callbackStruct->called && wcsstr(lpLibFileName + (wcslen(lpLibFileName) - wcharStrDll.length()), callbackDll) != nullptr) + bool bDoneCalling = true; + + for (auto& callbackStruct : GetDllLoadCallbacks()) { - callbackStruct->callback(moduleAddress); - callbackStruct->called = true; + if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename()) + { + bool bShouldContinue = false; + + if (!callbackStruct.reliesOn.empty()) + { + for (std::string tag : callbackStruct.reliesOn) + { + if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end()) + { + bDoneCalling = false; + bShouldContinue = true; + break; + } + } + } + + if (bShouldContinue) + continue; + + callbackStruct.callback(moduleAddress); + calledTags.push_back(callbackStruct.tag); + callbackStruct.called = true; + } } + + if (bDoneCalling) + break; } } @@ -208,65 +349,78 @@ void CallAllPendingDLLLoadCallbacks() } } -HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) +// clang-format off +AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, LoadLibraryExA, +HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) +// clang-format on { + HMODULE moduleAddress; + + // replace xinput dll with one that has ASLR if (!strncmp(lpLibFileName, "XInput1_3.dll", 14)) { - HMODULE moduleAddress = LoadLibraryExAOriginal("XInput9_1_0.dll", hFile, dwFlags); - if (moduleAddress) - { - CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - } - else + moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags); + + if (!moduleAddress) { MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR); exit(-1); + + return nullptr; } - return moduleAddress; } else - { - HMODULE moduleAddress = LoadLibraryExAOriginal(lpLibFileName, hFile, dwFlags); - if (moduleAddress) - { - CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - } - return moduleAddress; - } + moduleAddress = _LoadLibraryExA(lpLibFileName, hFile, dwFlags); + + if (moduleAddress) + CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); + + return moduleAddress; } -HMODULE LoadLibraryAHook(LPCSTR lpLibFileName) +// clang-format off +AUTOHOOK_ABSOLUTEADDR(_LoadLibraryA, LoadLibraryA, +HMODULE, WINAPI, (LPCSTR lpLibFileName)) +// clang-format on { - HMODULE moduleAddress = LoadLibraryAOriginal(lpLibFileName); + HMODULE moduleAddress = _LoadLibraryA(lpLibFileName); if (moduleAddress) - { CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - } return moduleAddress; } -HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) +// clang-format off +AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExW, LoadLibraryExW, +HMODULE, WINAPI, (LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) +// clang-format on { - HMODULE moduleAddress = LoadLibraryExWOriginal(lpLibFileName, hFile, dwFlags); + HMODULE moduleAddress = _LoadLibraryExW(lpLibFileName, hFile, dwFlags); if (moduleAddress) - { CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - } return moduleAddress; } -HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName) +// clang-format off +AUTOHOOK_ABSOLUTEADDR(_LoadLibraryW, LoadLibraryW, +HMODULE, WINAPI, (LPCWSTR lpLibFileName)) +// clang-format on { - HMODULE moduleAddress = LoadLibraryWOriginal(lpLibFileName); + HMODULE moduleAddress = _LoadLibraryW(lpLibFileName); if (moduleAddress) - { CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - } return moduleAddress; } + +void InstallInitialHooks() +{ + if (MH_Initialize() != MH_OK) + spdlog::error("MH_Initialize (minhook initialization) failed"); + + AUTOHOOK_DISPATCH() +} diff --git a/NorthstarDLL/hooks.h b/NorthstarDLL/hooks.h index aca66491..f47791fb 100644 --- a/NorthstarDLL/hooks.h +++ b/NorthstarDLL/hooks.h @@ -1,11 +1,311 @@ #pragma once +#include "memory.h" + #include +#include void InstallInitialHooks(); -typedef void (*DllLoadCallbackFuncType)(HMODULE moduleAddress); -void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback); -void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback); -void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback); +typedef void (*DllLoadCallbackFuncType)(CModule moduleAddress); +void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector reliesOn = {}); +void AddDllLoadCallbackForDedicatedServer( + std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector reliesOn = {}); +void AddDllLoadCallbackForClient( + std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector reliesOn = {}); void CallAllPendingDLLLoadCallbacks(); + +// new dll load callback stuff +enum class eDllLoadCallbackSide +{ + UNSIDED, + CLIENT, + DEDICATED_SERVER +}; + +class __dllLoadCallback +{ + public: + __dllLoadCallback() = delete; + __dllLoadCallback( + eDllLoadCallbackSide side, + const std::string dllName, + DllLoadCallbackFuncType callback, + std::string uniqueStr, + std::string reliesOn); +}; + +#define __CONCAT3(x, y, z) x##y##z +#define CONCAT3(x, y, z) __CONCAT3(x, y, z) +#define __CONCAT2(x, y) x##y +#define CONCAT2(x, y) __CONCAT2(x, y) +#define __STR(s) #s + +// adds a callback to be called when a given dll is loaded, for creating hooks and such +#define __ON_DLL_LOAD(dllName, side, uniquestr, reliesOn, args) \ + void CONCAT2(__dllLoadCallback, uniquestr) args; \ + namespace \ + { \ + __dllLoadCallback CONCAT2(__dllLoadCallbackInstance, __LINE__)( \ + side, dllName, CONCAT2(__dllLoadCallback, uniquestr), __STR(uniquestr), reliesOn); \ + } \ + void CONCAT2(__dllLoadCallback, uniquestr) args + +#define ON_DLL_LOAD(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, "", args) +#define ON_DLL_LOAD_RELIESON(dllName, uniquestr, reliesOn, args) \ + __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, __STR(reliesOn), args) +#define ON_DLL_LOAD_CLIENT(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, "", args) +#define ON_DLL_LOAD_CLIENT_RELIESON(dllName, uniquestr, reliesOn, args) \ + __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, __STR(reliesOn), args) +#define ON_DLL_LOAD_DEDI(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, "", args) +#define ON_DLL_LOAD_DEDI_RELIESON(dllName, uniquestr, reliesOn, args) \ + __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, __STR(reliesOn), args) + +// new macro hook stuff +class __autohook; + +class __fileAutohook +{ + public: + std::vector<__autohook*> hooks; + + void Dispatch(); + void DispatchForModule(const char* pModuleName); +}; + +// initialise autohooks for this file +#define AUTOHOOK_INIT() \ + namespace \ + { \ + __fileAutohook __FILEAUTOHOOK; \ + } + +// dispatch all autohooks in this file +#define AUTOHOOK_DISPATCH() __FILEAUTOHOOK.Dispatch(); + +#define AUTOHOOK_DISPATCH_MODULE(moduleName) __FILEAUTOHOOK.DispatchForModule(__STR(moduleName)); + +class __autohook +{ + public: + enum AddressResolutionMode + { + OFFSET_STRING, // we're using a string that of the format dllname.dll + offset + ABSOLUTE_ADDR, // we're using an absolute address, we don't need to process it at all + PROCADDRESS // resolve using GetModuleHandle and GetProcAddress + }; + + char* pFuncName; + + LPVOID pHookFunc; + LPVOID* ppOrigFunc; + + // address resolution props + AddressResolutionMode iAddressResolutionMode; + char* pAddrString = nullptr; // for OFFSET_STRING + LPVOID iAbsoluteAddress = nullptr; // for ABSOLUTE_ADDR + char* pModuleName; // for PROCADDRESS + char* pProcName; // for PROCADDRESS + + public: + __autohook() = delete; + + __autohook(__fileAutohook* autohook, const char* funcName, LPVOID absoluteAddress, LPVOID* orig, LPVOID func) + : pHookFunc(func), ppOrigFunc(orig), iAbsoluteAddress(absoluteAddress) + { + iAddressResolutionMode = ABSOLUTE_ADDR; + + const int iFuncNameStrlen = strlen(funcName) + 1; + pFuncName = new char[iFuncNameStrlen]; + memcpy(pFuncName, funcName, iFuncNameStrlen); + + autohook->hooks.push_back(this); + } + + __autohook(__fileAutohook* autohook, const char* funcName, const char* addrString, LPVOID* orig, LPVOID func) + : pHookFunc(func), ppOrigFunc(orig) + { + iAddressResolutionMode = OFFSET_STRING; + + const int iFuncNameStrlen = strlen(funcName) + 1; + pFuncName = new char[iFuncNameStrlen]; + memcpy(pFuncName, funcName, iFuncNameStrlen); + + const int iAddrStrlen = strlen(addrString) + 1; + pAddrString = new char[iAddrStrlen]; + memcpy(pAddrString, addrString, iAddrStrlen); + + autohook->hooks.push_back(this); + } + + __autohook(__fileAutohook* autohook, const char* funcName, const char* moduleName, const char* procName, LPVOID* orig, LPVOID func) + : pHookFunc(func), ppOrigFunc(orig) + { + iAddressResolutionMode = PROCADDRESS; + + const int iFuncNameStrlen = strlen(funcName) + 1; + pFuncName = new char[iFuncNameStrlen]; + memcpy(pFuncName, funcName, iFuncNameStrlen); + + const int iModuleNameStrlen = strlen(moduleName) + 1; + pModuleName = new char[iModuleNameStrlen]; + memcpy(pModuleName, moduleName, iModuleNameStrlen); + + const int iProcNameStrlen = strlen(procName) + 1; + pProcName = new char[iProcNameStrlen]; + memcpy(pProcName, procName, iProcNameStrlen); + + autohook->hooks.push_back(this); + } + + ~__autohook() + { + delete[] pFuncName; + + if (pAddrString) + delete[] pAddrString; + + if (pModuleName) + delete[] pModuleName; + + if (pProcName) + delete[] pProcName; + } + + void Dispatch() + { + LPVOID targetAddr = nullptr; + + // determine the address of the function we're hooking + switch (iAddressResolutionMode) + { + case ABSOLUTE_ADDR: + { + targetAddr = iAbsoluteAddress; + break; + } + + case OFFSET_STRING: + { + // in the format server.dll + 0xDEADBEEF + int iDllNameEnd = 0; + for (; !isspace(pAddrString[iDllNameEnd]) && pAddrString[iDllNameEnd] != '+'; iDllNameEnd++) + ; + + char* pModuleName = new char[iDllNameEnd + 1]; + memcpy(pModuleName, pAddrString, iDllNameEnd); + pModuleName[iDllNameEnd] = '\0'; + + // get the module address + const HMODULE pModuleAddr = GetModuleHandleA(pModuleName); + + if (!pModuleAddr) + break; + + // get the offset string + uintptr_t iOffset = 0; + + int iOffsetBegin = iDllNameEnd; + int iOffsetEnd = strlen(pAddrString); + + // seek until we hit the start of the number offset + for (; !(pAddrString[iOffsetBegin] >= '0' && pAddrString[iOffsetBegin] <= '9') && pAddrString[iOffsetBegin]; iOffsetBegin++) + ; + + bool bIsHex = + pAddrString[iOffsetBegin] == '0' && (pAddrString[iOffsetBegin + 1] == 'X' || pAddrString[iOffsetBegin + 1] == 'x'); + if (bIsHex) + iOffset = std::stoi(pAddrString + iOffsetBegin + 2, 0, 16); + else + iOffset = std::stoi(pAddrString + iOffsetBegin); + + targetAddr = (LPVOID)((uintptr_t)pModuleAddr + iOffset); + break; + } + + case PROCADDRESS: + { + targetAddr = GetProcAddress(GetModuleHandleA(pModuleName), pProcName); + break; + } + } + + if (MH_CreateHook(targetAddr, pHookFunc, ppOrigFunc) == MH_OK) + { + if (MH_EnableHook(targetAddr) == MH_OK) + spdlog::info("Enabling hook {}", pFuncName); + else + spdlog::error("MH_EnableHook failed for function {}", pFuncName); + } + else + spdlog::error("MH_CreateHook failed for function {}", pFuncName); + } +}; + +// hook a function at a given offset from a dll to be dispatched with AUTOHOOK_DISPATCH() +#define AUTOHOOK(name, addrString, type, callingConvention, args) \ + type callingConvention CONCAT2(__autohookfunc, name) args; \ + namespace \ + { \ + type(*callingConvention name) args; \ + __autohook CONCAT2(__autohook, __LINE__)( \ + &__FILEAUTOHOOK, __STR(name), __STR(addrString), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ + } \ + type callingConvention CONCAT2(__autohookfunc, name) args + +// hook a function at a given absolute constant address to be dispatched with AUTOHOOK_DISPATCH() +#define AUTOHOOK_ABSOLUTEADDR(name, addr, type, callingConvention, args) \ + type callingConvention CONCAT2(__autohookfunc, name) args; \ + namespace \ + { \ + type(*callingConvention name) args; \ + __autohook \ + CONCAT2(__autohook, __LINE__)(&__FILEAUTOHOOK, __STR(name), addr, (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ + } \ + type callingConvention CONCAT2(__autohookfunc, name) args + +// hook a function at a given module and exported function to be dispatched with AUTOHOOK_DISPATCH() +#define AUTOHOOK_PROCADDRESS(name, moduleName, procName, type, callingConvention, args) \ + type callingConvention CONCAT2(__autohookfunc, name) args; \ + namespace \ + { \ + type(*callingConvention name) args; \ + __autohook CONCAT2(__autohook, __LINE__)( \ + &__FILEAUTOHOOK, __STR(name), __STR(moduleName), __STR(procName), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ + } \ + type callingConvention CONCAT2(__autohookfunc, name) \ + args + +class ManualHook +{ + public: + char* pFuncName; + + LPVOID pHookFunc; + LPVOID* ppOrigFunc; + + public: + ManualHook() = delete; + ManualHook(const char* funcName, LPVOID func); + ManualHook(const char* funcName, LPVOID* orig, LPVOID func); + bool Dispatch(LPVOID addr, LPVOID* orig = nullptr); +}; + +// hook a function to be dispatched manually later +#define HOOK(varName, originalFunc, type, callingConvention, args) \ + namespace \ + { \ + type(*callingConvention originalFunc) args; \ + } \ + type callingConvention CONCAT2(__manualhookfunc, varName) args; \ + ManualHook varName = ManualHook(__STR(varName), (LPVOID*)&originalFunc, (LPVOID)CONCAT2(__manualhookfunc, varName)); \ + type callingConvention CONCAT2(__manualhookfunc, varName) args + +#define HOOK_NOORIG(varName, type, callingConvention, args) \ + type callingConvention CONCAT2(__manualhookfunc, varName) args; \ + ManualHook varName = ManualHook(__STR(varName), (LPVOID)CONCAT2(__manualhookfunc, varName)); \ + type callingConvention CONCAT2(__manualhookfunc, varName) \ + args + +void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFuncName = ""); +#define MAKEHOOK(pTarget, pDetour, ppOriginal) MakeHook(pTarget, pDetour, ppOriginal, __STR(pDetour)) diff --git a/NorthstarDLL/hookutils.cpp b/NorthstarDLL/hookutils.cpp deleted file mode 100644 index dbb45f51..00000000 --- a/NorthstarDLL/hookutils.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "pch.h" -#include "hookutils.h" - -#include - -void HookEnabler::CreateHook(LPVOID ppTarget, LPVOID ppDetour, LPVOID* ppOriginal, const char* targetName) -{ - // the macro for this uses ppTarget's name as targetName, and this typically starts with & - // targetname is used for debug stuff and debug output is nicer if we don't have this - if (*targetName == '&') - targetName++; - - if (MH_CreateHook(ppTarget, ppDetour, ppOriginal) == MH_OK) - { - HookTarget* target = new HookTarget; - target->targetAddress = ppTarget; - target->targetName = (char*)targetName; - - m_hookTargets.push_back(target); - } - else - { - if (targetName != nullptr) - spdlog::error("MH_CreateHook failed for function {}", targetName); - else - spdlog::error("MH_CreateHook failed for unknown function"); - } -} - -HookEnabler::~HookEnabler() -{ - for (auto& hook : m_hookTargets) - { - if (MH_EnableHook(hook->targetAddress) != MH_OK) - { - if (hook->targetName != nullptr) - spdlog::error("MH_EnableHook failed for function {}", hook->targetName); - else - spdlog::error("MH_EnableHook failed for unknown function"); - } - else - spdlog::info("Enabling hook {}", hook->targetName); - } -} diff --git a/NorthstarDLL/hookutils.h b/NorthstarDLL/hookutils.h deleted file mode 100644 index d0473d69..00000000 --- a/NorthstarDLL/hookutils.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include - -// Enables all hooks created with the HookEnabler object when it goes out of scope and handles hook errors -class HookEnabler -{ - private: - struct HookTarget - { - char* targetName; - LPVOID targetAddress; - }; - - std::vector m_hookTargets; - - public: - void CreateHook(LPVOID ppTarget, LPVOID ppDetour, LPVOID* ppOriginal, const char* targetName = nullptr); - ~HookEnabler(); -}; - -// macro to call HookEnabler::CreateHook with the hook's name -#define ENABLER_CREATEHOOK(enabler, ppTarget, ppDetour, ppOriginal) \ - enabler.CreateHook(ppTarget, reinterpret_cast(ppDetour), ppOriginal, #ppDetour) diff --git a/NorthstarDLL/host.cpp b/NorthstarDLL/host.cpp new file mode 100644 index 00000000..87b1ce4e --- /dev/null +++ b/NorthstarDLL/host.cpp @@ -0,0 +1,37 @@ +#include "pch.h" +#include "convar.h" +#include "modmanager.h" +#include "printcommand.h" +#include "printmaps.h" +#include "misccommands.h" +#include "r2engine.h" +#include "tier0.h" + +AUTOHOOK_INIT() + +// clang-format off +AUTOHOOK(Host_Init, engine.dll + 0x155EA0, +void, __fastcall, (bool bDedicated)) +// clang-format on +{ + spdlog::info("Host_Init()"); + Host_Init(bDedicated); + + FixupCvarFlags(); + + // need to initialise these after host_init since they do stuff to preexisting concommands/convars without being client/server specific + InitialiseCommandPrint(); + InitialiseMapsPrint(); + + // client/server autoexecs on necessary platforms + // dedi needs autoexec_ns_server on boot, while non-dedi will run it on on listen server start + if (bDedicated) + R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", R2::cmd_source_t::kCommandSrcCode); + else + R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "exec autoexec_ns_client", R2::cmd_source_t::kCommandSrcCode); +} + +ON_DLL_LOAD("engine.dll", Host_Init, (CModule module)) +{ + AUTOHOOK_DISPATCH() +} diff --git a/NorthstarDLL/hoststate.cpp b/NorthstarDLL/hoststate.cpp new file mode 100644 index 00000000..adbcde3b --- /dev/null +++ b/NorthstarDLL/hoststate.cpp @@ -0,0 +1,116 @@ +#include "pch.h" +#include "hoststate.h" +#include "masterserver.h" +#include "serverauthentication.h" +#include "serverpresence.h" +#include "playlist.h" +#include "tier0.h" +#include "r2engine.h" +#include "limits.h" + +AUTOHOOK_INIT() + +using namespace R2; + +// use the R2 namespace for game funcs +namespace R2 +{ + CHostState* g_pHostState; +} // namespace R2 + +ConVar* Cvar_hostport; + +void ServerStartingOrChangingMap() +{ + // net_data_block_enabled is required for sp, force it if we're on an sp map + // sucks for security but just how it be + if (!strncmp(g_pHostState->m_levelName, "sp_", 3)) + g_pCVar->FindVar("net_data_block_enabled")->SetValue(true); +} + +// clang-format off +AUTOHOOK(CHostState__State_NewGame, engine.dll + 0x16E7D0, +void, __fastcall, (CHostState* self)) +// clang-format on +{ + spdlog::info("HostState: NewGame"); + + Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode); + Cbuf_Execute(); + + // need to do this to ensure we don't go to private match + if (g_pServerAuthentication->m_bNeedLocalAuthForNewgame) + SetCurrentPlaylist("tdm"); + + // don't require authentication on singleplayer startup + g_pServerAuthentication->m_bRequireClientAuth = strncmp(g_pHostState->m_levelName, "sp_", 3); + + ServerStartingOrChangingMap(); + + double dStartTime = Tier0::Plat_FloatTime(); + CHostState__State_NewGame(self); + spdlog::info("loading took {}s", Tier0::Plat_FloatTime() - dStartTime); + + // setup server presence + g_pServerPresence->CreatePresence(); + g_pServerPresence->SetMap(g_pHostState->m_levelName, true); + g_pServerPresence->SetPlaylist(GetCurrentPlaylistName()); + g_pServerPresence->SetPort(Cvar_hostport->GetInt()); + + g_pServerAuthentication->StartPlayerAuthServer(); + g_pServerAuthentication->m_bNeedLocalAuthForNewgame = false; +} + +// clang-format off +AUTOHOOK(CHostState__State_ChangeLevelMP, engine.dll + 0x16E520, +void, __fastcall, (CHostState* self)) +// clang-format on +{ + spdlog::info("HostState: ChangeLevelMP"); + + ServerStartingOrChangingMap(); + + double dStartTime = Tier0::Plat_FloatTime(); + CHostState__State_ChangeLevelMP(self); + spdlog::info("loading took {}s", Tier0::Plat_FloatTime() - dStartTime); + + g_pServerPresence->SetMap(g_pHostState->m_levelName); +} + +// clang-format off +AUTOHOOK(CHostState__State_GameShutdown, engine.dll + 0x16E640, +void, __fastcall, (CHostState* self)) +// clang-format on +{ + spdlog::info("HostState: GameShutdown"); + + g_pServerPresence->DestroyPresence(); + g_pServerAuthentication->StopPlayerAuthServer(); + + CHostState__State_GameShutdown(self); +} + +// clang-format off +AUTOHOOK(CHostState__FrameUpdate, engine.dll + 0x16DB00, +void, __fastcall, (CHostState* self, double flCurrentTime, float flFrameTime)) +// clang-format on +{ + CHostState__FrameUpdate(self, flCurrentTime, flFrameTime); + + if (*R2::g_pServerState == R2::server_state_t::ss_active) + { + // update server presence + g_pServerPresence->RunFrame(flCurrentTime); + + // update limits for frame + g_pServerLimits->RunFrame(flCurrentTime, flFrameTime); + } +} + +ON_DLL_LOAD_RELIESON("engine.dll", HostState, ConVar, (CModule module)) +{ + AUTOHOOK_DISPATCH() + + g_pHostState = module.Offset(0x7CF180).As(); + Cvar_hostport = module.Offset(0x13FA6070).As(); +} diff --git a/NorthstarDLL/hoststate.h b/NorthstarDLL/hoststate.h new file mode 100644 index 00000000..a77385ef --- /dev/null +++ b/NorthstarDLL/hoststate.h @@ -0,0 +1,45 @@ +#pragma once + +// use the R2 namespace for game funxcs +namespace R2 +{ + enum class HostState_t + { + HS_NEW_GAME = 0, + HS_LOAD_GAME, + HS_CHANGE_LEVEL_SP, + HS_CHANGE_LEVEL_MP, + HS_RUN, + HS_GAME_SHUTDOWN, + HS_SHUTDOWN, + HS_RESTART, + }; + + struct CHostState + { + public: + HostState_t m_iCurrentState; + HostState_t m_iNextState; + + float m_vecLocation[3]; + float m_angLocation[3]; + + char m_levelName[32]; + char m_mapGroupName[32]; + char m_landmarkName[32]; + char m_saveName[32]; + float m_flShortFrameTime; // run a few one-tick frames to avoid large timesteps while loading assets + + bool m_activeGame; + bool m_bRememberLocation; + bool m_bBackgroundLevel; + bool m_bWaitingForConnection; + bool m_bLetToolsOverrideLoadGameEnts; // During a load game, this tells Foundry to override ents that are selected in Hammer. + bool m_bSplitScreenConnect; + bool m_bGameHasShutDownAndFlushedMemory; // This is false once we load a map into memory, and set to true once the map is unloaded + // and all memory flushed + bool m_bWorkshopMapDownloadPending; + }; + + extern CHostState* g_pHostState; +} // namespace R2 diff --git a/NorthstarDLL/include/MinHook.x64.lib b/NorthstarDLL/include/MinHook.x64.lib index 213df08f..a346f386 100644 Binary files a/NorthstarDLL/include/MinHook.x64.lib and b/NorthstarDLL/include/MinHook.x64.lib differ diff --git a/NorthstarDLL/include/libcrypto_static.lib b/NorthstarDLL/include/libcrypto_static.lib index 26c68964..0ddd830c 100644 Binary files a/NorthstarDLL/include/libcrypto_static.lib and b/NorthstarDLL/include/libcrypto_static.lib differ diff --git a/NorthstarDLL/include/libcurl/lib/libcurl_a.lib b/NorthstarDLL/include/libcurl/lib/libcurl_a.lib index f0fa49f2..32b917c9 100644 Binary files a/NorthstarDLL/include/libcurl/lib/libcurl_a.lib and b/NorthstarDLL/include/libcurl/lib/libcurl_a.lib differ diff --git a/NorthstarDLL/include/libssl_static.lib b/NorthstarDLL/include/libssl_static.lib index 3411cb57..f7916421 100644 Binary files a/NorthstarDLL/include/libssl_static.lib and b/NorthstarDLL/include/libssl_static.lib differ diff --git a/NorthstarDLL/keyvalues.cpp b/NorthstarDLL/keyvalues.cpp index 0d829de9..98a9ce66 100644 --- a/NorthstarDLL/keyvalues.cpp +++ b/NorthstarDLL/keyvalues.cpp @@ -1,45 +1,16 @@ #include "pch.h" -#include "keyvalues.h" #include "modmanager.h" #include "filesystem.h" -#include "hookutils.h" #include -// hook forward defs -typedef char (*KeyValues__LoadFromBufferType)( - void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7); -KeyValues__LoadFromBufferType KeyValues__LoadFromBuffer; -char KeyValues__LoadFromBufferHook( - void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7); - -void InitialiseKeyValues(HMODULE baseAddress) -{ - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x426C30, &KeyValues__LoadFromBufferHook, reinterpret_cast(&KeyValues__LoadFromBuffer)); -} - -void* savedFilesystemPtr; - -char KeyValues__LoadFromBufferHook(void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7) -{ - // this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of - // LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it - // from a function call that always happens before playlists is loaded - if (pFileSystem != nullptr) - savedFilesystemPtr = pFileSystem; - if (!pFileSystem && !strcmp(resourceName, "playlists")) - pFileSystem = savedFilesystemPtr; - - return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7); -} +AUTOHOOK_INIT() void ModManager::TryBuildKeyValues(const char* filename) { spdlog::info("Building KeyValues for file {}", filename); - std::string normalisedPath = fs::path(filename).lexically_normal().string(); + std::string normalisedPath = g_pModManager->NormaliseModFilePath(fs::path(filename)); fs::path compiledPath = GetCompiledAssetsPath() / filename; fs::path compiledDir = compiledPath.parent_path(); fs::create_directories(compiledDir); @@ -54,14 +25,14 @@ void ModManager::TryBuildKeyValues(const char* filename) // copy over patch kv files, and add #bases to new file, last mods' patches should be applied first // note: #include should be identical but it's actually just broken, thanks respawn - for (int64_t i = m_loadedMods.size() - 1; i > -1; i--) + for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--) { - if (!m_loadedMods[i].Enabled) + if (!m_LoadedMods[i].m_bEnabled) continue; size_t fileHash = STR_HASH(normalisedPath); - auto modKv = m_loadedMods[i].KeyValues.find(fileHash); - if (modKv != m_loadedMods[i].KeyValues.end()) + auto modKv = m_LoadedMods[i].KeyValues.find(fileHash); + if (modKv != m_LoadedMods[i].KeyValues.end()) { // should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt" @@ -76,7 +47,7 @@ void ModManager::TryBuildKeyValues(const char* filename) fs::remove(compiledDir / patchFilePath); - fs::copy_file(m_loadedMods[i].ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath); + fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath); } } @@ -86,7 +57,7 @@ void ModManager::TryBuildKeyValues(const char* filename) newKvs += "\"\n"; // load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons) - std::string originalFile = ReadVPKOriginalFile(filename); + std::string originalFile = R2::ReadVPKOriginalFile(filename); if (!originalFile.length()) { @@ -96,7 +67,6 @@ void ModManager::TryBuildKeyValues(const char* filename) char rootName[64]; memset(rootName, 0, sizeof(rootName)); - rootName[63] = '\0'; // iterate until we hit an ascii char that isn't in a # command or comment to get root obj name int i = 0; @@ -127,11 +97,36 @@ void ModManager::TryBuildKeyValues(const char* filename) writeStream.close(); ModOverrideFile overrideFile; - overrideFile.owningMod = nullptr; - overrideFile.path = normalisedPath; + overrideFile.m_pOwningMod = nullptr; + overrideFile.m_Path = normalisedPath; - if (m_modFiles.find(normalisedPath) == m_modFiles.end()) - m_modFiles.insert(std::make_pair(normalisedPath, overrideFile)); + if (m_ModFiles.find(normalisedPath) == m_ModFiles.end()) + m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile)); else - m_modFiles[normalisedPath] = overrideFile; + m_ModFiles[normalisedPath] = overrideFile; +} + +// clang-format off +AUTOHOOK(KeyValues__LoadFromBuffer, engine.dll + 0x426C30, +char, __fastcall, (void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7)) +// clang-format on +{ + static void* pSavedFilesystemPtr = nullptr; + + // this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of + // LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it + // from a function call that always happens before playlists is loaded + + // note: would be better if we could serialize this to disk for playlists, as this method breaks saving playlists in demos + if (pFileSystem != nullptr) + pSavedFilesystemPtr = pFileSystem; + if (!pFileSystem && !strcmp(resourceName, "playlists")) + pFileSystem = pSavedFilesystemPtr; + + return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7); +} + +ON_DLL_LOAD("engine.dll", KeyValues, (CModule module)) +{ + AUTOHOOK_DISPATCH() } diff --git a/NorthstarDLL/keyvalues.h b/NorthstarDLL/keyvalues.h deleted file mode 100644 index 8f931e3f..00000000 --- a/NorthstarDLL/keyvalues.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseKeyValues(HMODULE baseAddress); diff --git a/NorthstarDLL/languagehooks.cpp b/NorthstarDLL/languagehooks.cpp index 38435f93..d00beb68 100644 --- a/NorthstarDLL/languagehooks.cpp +++ b/NorthstarDLL/languagehooks.cpp @@ -1,18 +1,13 @@ #include "pch.h" -#include "languagehooks.h" -#include "gameutils.h" +#include "tier0.h" + #include #include -namespace fs = std::filesystem; - -typedef char* (*GetGameLanguageType)(); -char* GetGameLanguage(); +AUTOHOOK_INIT() typedef LANGID (*Tier0_DetectDefaultLanguageType)(); -GetGameLanguageType GetGameLanguageOriginal; - bool CheckLangAudioExists(char* lang) { std::string path {"r2\\sound\\general_"}; @@ -52,7 +47,10 @@ std::string GetAnyInstalledAudioLanguage() return "NO LANGUAGE DETECTED"; } -char* GetGameLanguageHook() +// clang-format off +AUTOHOOK(GetGameLanguage, tier0.dll + 0xF560, +char*, __fastcall, ()) +// clang-format on { auto tier0Handle = GetModuleHandleA("tier0.dll"); auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage"); @@ -60,7 +58,7 @@ char* GetGameLanguageHook() bool& canOriginDictateLang = *(bool*)((char*)tier0Handle + 0xA9A90); const char* forcedLanguage; - if (CommandLine()->CheckParm("-language", &forcedLanguage)) + if (Tier0::CommandLine()->CheckParm("-language", &forcedLanguage)) { if (!CheckLangAudioExists((char*)forcedLanguage)) { @@ -79,7 +77,7 @@ char* GetGameLanguageHook() canOriginDictateLang = true; // let it try { - auto lang = GetGameLanguageOriginal(); + auto lang = GetGameLanguage(); if (!CheckLangAudioExists(lang)) { if (strcmp(lang, "russian") != @@ -97,7 +95,7 @@ char* GetGameLanguageHook() Tier0_DetectDefaultLanguageType(); // force the global in tier0 to be populated with language inferred from user's system rather than // defaulting to Russian canOriginDictateLang = false; // Origin has no say anymore, we will fallback to user's system setup language - auto lang = GetGameLanguageOriginal(); + auto lang = GetGameLanguage(); spdlog::info("Detected system language: {}", lang); if (!CheckLangAudioExists(lang)) { @@ -112,8 +110,7 @@ char* GetGameLanguageHook() return lang; } -void InitialiseTier0LanguageHooks(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT("tier0.dll", LanguageHooks, (CModule module)) { - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xF560, &GetGameLanguageHook, reinterpret_cast(&GetGameLanguageOriginal)); + AUTOHOOK_DISPATCH() } diff --git a/NorthstarDLL/languagehooks.h b/NorthstarDLL/languagehooks.h deleted file mode 100644 index 55b591e0..00000000 --- a/NorthstarDLL/languagehooks.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseTier0LanguageHooks(HMODULE baseAddress); diff --git a/NorthstarDLL/latencyflex.cpp b/NorthstarDLL/latencyflex.cpp index bb274dab..620e031a 100644 --- a/NorthstarDLL/latencyflex.cpp +++ b/NorthstarDLL/latencyflex.cpp @@ -1,76 +1,44 @@ #include "pch.h" -#include "latencyflex.h" -#include "hookutils.h" #include "convar.h" -typedef void (*OnRenderStartType)(); -OnRenderStartType OnRenderStart; +AUTOHOOK_INIT() ConVar* Cvar_r_latencyflex; -HMODULE m_lfxModule {}; -typedef void (*PFN_lfx_WaitAndBeginFrame)(); -PFN_lfx_WaitAndBeginFrame m_lfx_WaitAndBeginFrame {}; +void (*m_winelfx_WaitAndBeginFrame)(); -void OnRenderStartHook() +// clang-format off +AUTOHOOK(OnRenderStart, client.dll + 0x1952C0, +void, __fastcall, ()) +// clang-format on { - // Sleep before next frame as needed to reduce latency. - if (Cvar_r_latencyflex->GetInt()) - { - if (m_lfx_WaitAndBeginFrame) - { - m_lfx_WaitAndBeginFrame(); - } - } + if (Cvar_r_latencyflex->GetBool() && m_winelfx_WaitAndBeginFrame) + m_winelfx_WaitAndBeginFrame(); OnRenderStart(); } -void InitialiseLatencyFleX(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module)) { // Connect to the LatencyFleX service // LatencyFleX is an open source vendor agnostic replacement for Nvidia Reflex input latency reduction technology. // https://ishitatsuyuki.github.io/post/latencyflex/ - const auto lfxModuleName = "latencyflex_layer.dll"; - const auto lfxModuleNameFallback = "latencyflex_wine.dll"; - auto useFallbackEntrypoints = false; - - // Load LatencyFleX library. - m_lfxModule = ::LoadLibraryA(lfxModuleName); - if (m_lfxModule == nullptr && ::GetLastError() == ERROR_MOD_NOT_FOUND) - { - spdlog::info("LFX: Primary LatencyFleX library not found, trying fallback."); - - m_lfxModule = ::LoadLibraryA(lfxModuleNameFallback); - if (m_lfxModule == nullptr) - { - if (::GetLastError() == ERROR_MOD_NOT_FOUND) - { - spdlog::info("LFX: Fallback LatencyFleX library not found."); - } - else - { - spdlog::info("LFX: Error loading fallback LatencyFleX library - Code: {}", ::GetLastError()); - } - - return; - } - - useFallbackEntrypoints = true; - } - else if (m_lfxModule == nullptr) + HMODULE pLfxModule; + + if (pLfxModule = LoadLibraryA("latencyflex_layer.dll")) + m_winelfx_WaitAndBeginFrame = + reinterpret_cast(reinterpret_cast(GetProcAddress(pLfxModule, "lfx_WaitAndBeginFrame"))); + else if (pLfxModule = LoadLibraryA("latencyflex_wine.dll")) + m_winelfx_WaitAndBeginFrame = + reinterpret_cast(reinterpret_cast(GetProcAddress(pLfxModule, "winelfx_WaitAndBeginFrame"))); + else { - spdlog::info("LFX: Error loading primary LatencyFleX library - Code: {}", ::GetLastError()); + spdlog::info("Unable to load LatencyFleX library, LatencyFleX disabled."); return; } - m_lfx_WaitAndBeginFrame = reinterpret_cast(reinterpret_cast( - GetProcAddress(m_lfxModule, !useFallbackEntrypoints ? "lfx_WaitAndBeginFrame" : "winelfx_WaitAndBeginFrame"))); - - spdlog::info("LFX: Initialized."); + AUTOHOOK_DISPATCH() + spdlog::info("LatencyFleX initialized."); Cvar_r_latencyflex = new ConVar("r_latencyflex", "1", FCVAR_ARCHIVE, "Whether or not to use LatencyFleX input latency reduction."); - - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1952C0, &OnRenderStartHook, reinterpret_cast(&OnRenderStart)); } diff --git a/NorthstarDLL/latencyflex.h b/NorthstarDLL/latencyflex.h deleted file mode 100644 index 663d9ec6..00000000 --- a/NorthstarDLL/latencyflex.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -void InitialiseLatencyFleX(HMODULE baseAddress); diff --git a/NorthstarDLL/limits.cpp b/NorthstarDLL/limits.cpp new file mode 100644 index 00000000..fd635136 --- /dev/null +++ b/NorthstarDLL/limits.cpp @@ -0,0 +1,299 @@ +#include "pch.h" +#include "limits.h" +#include "hoststate.h" +#include "r2client.h" +#include "r2engine.h" +#include "r2server.h" +#include "maxplayers.h" +#include "tier0.h" +#include "vector.h" +#include "serverauthentication.h" + +AUTOHOOK_INIT() + +ServerLimitsManager* g_pServerLimits; + +ConVar* Cvar_net_datablock_enabled; + +// todo: make this work on higher timescales, also possibly disable when sv_cheats is set +void ServerLimitsManager::RunFrame(double flCurrentTime, float flFrameTime) +{ + if (Cvar_sv_antispeedhack_enable->GetBool()) + { + // for each player, set their usercmd processing budget for the frame to the last frametime for the server + for (int i = 0; i < R2::GetMaxPlayers(); i++) + { + R2::CBaseClient* player = &R2::g_pClientArray[i]; + + if (m_PlayerLimitData.find(player) != m_PlayerLimitData.end()) + { + PlayerLimitData* pLimitData = &g_pServerLimits->m_PlayerLimitData[player]; + if (pLimitData->flFrameUserCmdBudget < 0.016666667 * Cvar_sv_antispeedhack_maxtickbudget->GetFloat()) + pLimitData->flFrameUserCmdBudget += + fmax(flFrameTime, 0.016666667) * g_pServerLimits->Cvar_sv_antispeedhack_budgetincreasemultiplier->GetFloat(); + } + } + } +} + +void ServerLimitsManager::AddPlayer(R2::CBaseClient* player) +{ + PlayerLimitData limitData; + limitData.flFrameUserCmdBudget = 0.016666667 * Cvar_sv_antispeedhack_maxtickbudget->GetFloat(); + + m_PlayerLimitData.insert(std::make_pair(player, limitData)); +} + +void ServerLimitsManager::RemovePlayer(R2::CBaseClient* player) +{ + if (m_PlayerLimitData.find(player) != m_PlayerLimitData.end()) + m_PlayerLimitData.erase(player); +} + +bool ServerLimitsManager::CheckStringCommandLimits(R2::CBaseClient* 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::CBaseClient* 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; +} + +// clang-format off +AUTOHOOK(CNetChan__ProcessMessages, engine.dll + 0x2140A0, +char, __fastcall, (void* self, void* buf)) +// clang-format on +{ + 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::CBaseClient* sender = *(R2::CBaseClient**)((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; +} + +// clang-format off +AUTOHOOK(ProcessConnectionlessPacket, engine.dll + 0x117800, +bool, , (void* a1, R2::netpacket_t* packet)) +// clang-format on +{ + 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); +} + +// this is weird and i'm not sure if it's correct, so not using for now +/*AUTOHOOK(CBasePlayer__PhysicsSimulate, server.dll + 0x5A6E50, bool, __fastcall, (void* self, int a2, char a3)) +{ + spdlog::info("CBasePlayer::PhysicsSimulate"); + return CBasePlayer__PhysicsSimulate(self, a2, a3); +}*/ + +struct alignas(4) SV_CUserCmd +{ + DWORD command_number; + DWORD tick_count; + float command_time; + Vector3 worldViewAngles; + BYTE gap18[4]; + Vector3 localViewAngles; + Vector3 attackangles; + Vector3 move; + DWORD buttons; + BYTE impulse; + short weaponselect; + DWORD meleetarget; + BYTE gap4C[24]; + char headoffset; + BYTE gap65[11]; + Vector3 cameraPos; + Vector3 cameraAngles; + BYTE gap88[4]; + int tickSomething; + DWORD dword90; + DWORD predictedServerEventAck; + DWORD dword98; + float frameTime; +}; + +// clang-format off +AUTOHOOK(CPlayerMove__RunCommand, server.dll + 0x5B8100, +void, __fastcall, (void* self, R2::CBasePlayer* player, SV_CUserCmd* pUserCmd, uint64_t a4)) +// clang-format on +{ + if (g_pServerLimits->Cvar_sv_antispeedhack_enable->GetBool()) + { + R2::CBaseClient* pClient = &R2::g_pClientArray[player->m_nPlayerIndex - 1]; + + if (g_pServerLimits->m_PlayerLimitData.find(pClient) != g_pServerLimits->m_PlayerLimitData.end()) + { + PlayerLimitData* pLimitData = &g_pServerLimits->m_PlayerLimitData[pClient]; + + pLimitData->flFrameUserCmdBudget = fmax(0.0, pLimitData->flFrameUserCmdBudget - pUserCmd->frameTime); + + if (pLimitData->flFrameUserCmdBudget <= 0.0) + { + spdlog::warn("player {} went over usercmd budget ({})", pClient->m_Name, pLimitData->flFrameUserCmdBudget); + return; + } + // else + // spdlog::info("{}: {}", pClient->m_Name, pLimitData->flFrameUserCmdBudget); + } + } + + CPlayerMove__RunCommand(self, player, pUserCmd, a4); +} + +ON_DLL_LOAD_RELIESON("engine.dll", ServerLimits, ConVar, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(engine.dll) + + 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", + "100", + 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, ""); + g_pServerLimits->Cvar_sv_antispeedhack_enable = + new ConVar("sv_antispeedhack_enable", "0", FCVAR_NONE, "whether to enable antispeedhack protections"); + g_pServerLimits->Cvar_sv_antispeedhack_maxtickbudget = new ConVar( + "sv_antispeedhack_maxtickbudget", + "64", + FCVAR_GAMEDLL, + "Maximum number of client-issued usercmd ticks that can be replayed in packet loss conditions, 0 to allow no restrictions"); + g_pServerLimits->Cvar_sv_antispeedhack_budgetincreasemultiplier = new ConVar( + "sv_antispeedhack_budgetincreasemultiplier", + "1.2", + FCVAR_GAMEDLL, + "Increase usercmd processing budget by tickinterval * value per tick"); + + Cvar_net_datablock_enabled = R2::g_pCVar->FindVar("net_datablock_enabled"); +} + +ON_DLL_LOAD("server.dll", ServerLimitsServer, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(server.dll) +} diff --git a/NorthstarDLL/limits.h b/NorthstarDLL/limits.h new file mode 100644 index 00000000..068f91c9 --- /dev/null +++ b/NorthstarDLL/limits.h @@ -0,0 +1,51 @@ +#pragma once +#include "r2engine.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; + + float flFrameUserCmdBudget = 0.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; + ConVar* Cvar_sv_antispeedhack_enable; + ConVar* Cvar_sv_antispeedhack_maxtickbudget; + ConVar* Cvar_sv_antispeedhack_budgetincreasemultiplier; + + std::unordered_map m_PlayerLimitData; + std::vector m_UnconnectedPlayerLimitData; + + public: + void RunFrame(double flCurrentTime, float flFrameTime); + void AddPlayer(R2::CBaseClient* player); + void RemovePlayer(R2::CBaseClient* player); + bool CheckStringCommandLimits(R2::CBaseClient* player); + bool CheckChatLimits(R2::CBaseClient* player); +}; + +extern ServerLimitsManager* g_pServerLimits; diff --git a/NorthstarDLL/localchatwriter.cpp b/NorthstarDLL/localchatwriter.cpp index 72a5afa7..efa7eeee 100644 --- a/NorthstarDLL/localchatwriter.cpp +++ b/NorthstarDLL/localchatwriter.cpp @@ -37,12 +37,11 @@ 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); + void(__fastcall* InsertPossibleURLString)(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 +80,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 +143,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 +189,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 @@ -219,13 +218,12 @@ class AnsiEscapeParser unsigned char blue = code % 6; 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}); + m_writer->InsertColorChange(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 +234,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 +243,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 +252,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; } @@ -280,12 +278,11 @@ void LocalChatWriter::Write(const char* str) if (startOfEscape != str) { // There is some text before the escape sequence, just print that - size_t copyChars = startOfEscape - str; if (copyChars > 255) copyChars = 255; - strncpy(writeBuffer, str, copyChars); - writeBuffer[copyChars] = 0; + + strncpy_s(writeBuffer, copyChars + 1, str, copyChars); InsertText(writeBuffer); } @@ -320,6 +317,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 +346,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 +357,24 @@ 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) @@ -436,12 +439,12 @@ void LocalChatWriter::InsertDefaultFade() } } -void InitialiseLocalChatWriter(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT("client.dll", LocalChatWriter, (CModule module)) { - gGameSettings = (CGameSettings**)((char*)baseAddress + 0x11BAA48); - gChatFadeLength = (CGameFloatVar**)((char*)baseAddress + 0x11BAB78); - gChatFadeSustain = (CGameFloatVar**)((char*)baseAddress + 0x11BAC08); - CHudChat::allHuds = (CHudChat**)((char*)baseAddress + 0x11BA9E8); + gGameSettings = module.Offset(0x11BAA48).As(); + gChatFadeLength = module.Offset(0x11BAB78).As(); + gChatFadeSustain = module.Offset(0x11BAC08).As(); + CHudChat::allHuds = module.Offset(0x11BA9E8).As(); - ConvertANSIToUnicode = (ConvertANSIToUnicodeType)((char*)baseAddress + 0x7339A0); + ConvertANSIToUnicode = module.Offset(0x7339A0).As(); } diff --git a/NorthstarDLL/localchatwriter.h b/NorthstarDLL/localchatwriter.h index 8048e084..0df0cac8 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: @@ -70,5 +63,3 @@ class LocalChatWriter const char* ApplyAnsiEscape(const char* escape); void InsertDefaultFade(); }; - -void InitialiseLocalChatWriter(HMODULE baseAddress); diff --git a/NorthstarDLL/logging.cpp b/NorthstarDLL/logging.cpp index 4d277767..99179d26 100644 --- a/NorthstarDLL/logging.cpp +++ b/NorthstarDLL/logging.cpp @@ -1,258 +1,20 @@ #include "pch.h" #include "logging.h" -#include "sourceconsole.h" -#include "spdlog/sinks/basic_file_sink.h" -#include "hookutils.h" -#include "dedicated.h" #include "convar.h" -#include -#include +#include "concommand.h" #include "nsprefix.h" -#include - -// This needs to be called after hooks are loaded so we can access the command line args -void CreateLogFiles() -{ - if (strstr(GetCommandLineA(), "-disablelogs")) - { - spdlog::default_logger()->set_level(spdlog::level::off); - } - else - { - try - { - // todo: might be good to delete logs that are too old - time_t time = std::time(nullptr); - tm currentTime = *std::localtime(&time); - std::stringstream stream; - - stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str()); - spdlog::default_logger()->sinks().push_back(std::make_shared(stream.str(), false)); - spdlog::flush_on(spdlog::level::info); - } - catch (...) - { - spdlog::error("Failed creating log file"); - MessageBoxA( - 0, "Failed creating log file! Make sure the profile directory is writable.", "Northstar Warning", MB_ICONWARNING | MB_OK); - } - } -} - -long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) -{ - static bool logged = false; - if (logged) - return EXCEPTION_CONTINUE_SEARCH; - - if (!IsDebuggerPresent()) - { - const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode; - if (exceptionCode != EXCEPTION_ACCESS_VIOLATION && exceptionCode != EXCEPTION_ARRAY_BOUNDS_EXCEEDED && - exceptionCode != EXCEPTION_DATATYPE_MISALIGNMENT && exceptionCode != EXCEPTION_FLT_DENORMAL_OPERAND && - exceptionCode != EXCEPTION_FLT_DIVIDE_BY_ZERO && exceptionCode != EXCEPTION_FLT_INEXACT_RESULT && - exceptionCode != EXCEPTION_FLT_INVALID_OPERATION && exceptionCode != EXCEPTION_FLT_OVERFLOW && - exceptionCode != EXCEPTION_FLT_STACK_CHECK && exceptionCode != EXCEPTION_FLT_UNDERFLOW && - exceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION && exceptionCode != EXCEPTION_IN_PAGE_ERROR && - exceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO && exceptionCode != EXCEPTION_INT_OVERFLOW && - exceptionCode != EXCEPTION_INVALID_DISPOSITION && exceptionCode != EXCEPTION_NONCONTINUABLE_EXCEPTION && - exceptionCode != EXCEPTION_PRIV_INSTRUCTION && exceptionCode != EXCEPTION_STACK_OVERFLOW) - return EXCEPTION_CONTINUE_SEARCH; - - std::stringstream exceptionCause; - exceptionCause << "Cause: "; - switch (exceptionCode) - { - case EXCEPTION_ACCESS_VIOLATION: - case EXCEPTION_IN_PAGE_ERROR: - { - exceptionCause << "Access Violation" << std::endl; - - auto exceptionInfo0 = exceptionInfo->ExceptionRecord->ExceptionInformation[0]; - auto exceptionInfo1 = exceptionInfo->ExceptionRecord->ExceptionInformation[1]; - - if (!exceptionInfo0) - exceptionCause << "Attempted to read from: 0x" << (void*)exceptionInfo1; - else if (exceptionInfo0 == 1) - exceptionCause << "Attempted to write to: 0x" << (void*)exceptionInfo1; - else if (exceptionInfo0 == 8) - exceptionCause << "Data Execution Prevention (DEP) at: 0x" << (void*)std::hex << exceptionInfo1; - else - exceptionCause << "Unknown access violation at: 0x" << (void*)exceptionInfo1; - - break; - } - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: - exceptionCause << "Array bounds exceeded"; - break; - case EXCEPTION_DATATYPE_MISALIGNMENT: - exceptionCause << "Datatype misalignment"; - break; - case EXCEPTION_FLT_DENORMAL_OPERAND: - exceptionCause << "Denormal operand"; - break; - case EXCEPTION_FLT_DIVIDE_BY_ZERO: - exceptionCause << "Divide by zero (float)"; - break; - case EXCEPTION_INT_DIVIDE_BY_ZERO: - exceptionCause << "Divide by zero (int)"; - break; - case EXCEPTION_FLT_INEXACT_RESULT: - exceptionCause << "Inexact result"; - break; - case EXCEPTION_FLT_INVALID_OPERATION: - exceptionCause << "Invalid operation"; - break; - case EXCEPTION_FLT_OVERFLOW: - case EXCEPTION_INT_OVERFLOW: - exceptionCause << "Numeric overflow"; - break; - case EXCEPTION_FLT_UNDERFLOW: - exceptionCause << "Numeric underflow"; - break; - case EXCEPTION_FLT_STACK_CHECK: - exceptionCause << "Stack check"; - break; - case EXCEPTION_ILLEGAL_INSTRUCTION: - exceptionCause << "Illegal instruction"; - break; - case EXCEPTION_INVALID_DISPOSITION: - exceptionCause << "Invalid disposition"; - break; - case EXCEPTION_NONCONTINUABLE_EXCEPTION: - exceptionCause << "Noncontinuable exception"; - break; - case EXCEPTION_PRIV_INSTRUCTION: - exceptionCause << "Priviledged instruction"; - break; - case EXCEPTION_STACK_OVERFLOW: - exceptionCause << "Stack overflow"; - break; - default: - exceptionCause << "Unknown"; - break; - } - - void* exceptionAddress = exceptionInfo->ExceptionRecord->ExceptionAddress; - - HMODULE crashedModuleHandle; - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast(exceptionAddress), &crashedModuleHandle); - - MODULEINFO crashedModuleInfo; - GetModuleInformation(GetCurrentProcess(), crashedModuleHandle, &crashedModuleInfo, sizeof(crashedModuleInfo)); - - char crashedModuleFullName[MAX_PATH]; - GetModuleFileNameExA(GetCurrentProcess(), crashedModuleHandle, crashedModuleFullName, MAX_PATH); - char* crashedModuleName = strrchr(crashedModuleFullName, '\\') + 1; - - DWORD64 crashedModuleOffset = ((DWORD64)exceptionAddress) - ((DWORD64)crashedModuleInfo.lpBaseOfDll); - CONTEXT* exceptionContext = exceptionInfo->ContextRecord; - - spdlog::error("Northstar has crashed! a minidump has been written and exception info is available below:"); - spdlog::error(exceptionCause.str()); - spdlog::error("At: {} + {}", crashedModuleName, (void*)crashedModuleOffset); - - PVOID framesToCapture[62]; - int frames = RtlCaptureStackBackTrace(0, 62, framesToCapture, NULL); - for (int i = 0; i < frames; i++) - { - HMODULE backtraceModuleHandle; - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast(framesToCapture[i]), &backtraceModuleHandle); - - char backtraceModuleFullName[MAX_PATH]; - GetModuleFileNameExA(GetCurrentProcess(), backtraceModuleHandle, backtraceModuleFullName, MAX_PATH); - char* backtraceModuleName = strrchr(backtraceModuleFullName, '\\') + 1; - - void* actualAddress = (void*)framesToCapture[i]; - void* relativeAddress = (void*)(uintptr_t(actualAddress) - uintptr_t(backtraceModuleHandle)); - - spdlog::error(" {} + {} ({})", backtraceModuleName, relativeAddress, actualAddress); - } - - spdlog::error("RAX: 0x{0:x}", exceptionContext->Rax); - spdlog::error("RBX: 0x{0:x}", exceptionContext->Rbx); - spdlog::error("RCX: 0x{0:x}", exceptionContext->Rcx); - spdlog::error("RDX: 0x{0:x}", exceptionContext->Rdx); - spdlog::error("RSI: 0x{0:x}", exceptionContext->Rsi); - spdlog::error("RDI: 0x{0:x}", exceptionContext->Rdi); - spdlog::error("RBP: 0x{0:x}", exceptionContext->Rbp); - spdlog::error("RSP: 0x{0:x}", exceptionContext->Rsp); - spdlog::error("R8: 0x{0:x}", exceptionContext->R8); - spdlog::error("R9: 0x{0:x}", exceptionContext->R9); - spdlog::error("R10: 0x{0:x}", exceptionContext->R10); - spdlog::error("R11: 0x{0:x}", exceptionContext->R11); - spdlog::error("R12: 0x{0:x}", exceptionContext->R12); - spdlog::error("R13: 0x{0:x}", exceptionContext->R13); - spdlog::error("R14: 0x{0:x}", exceptionContext->R14); - spdlog::error("R15: 0x{0:x}", exceptionContext->R15); - - time_t time = std::time(nullptr); - tm currentTime = *std::localtime(&time); - std::stringstream stream; - stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str()); - - auto hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - if (hMinidumpFile) - { - MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo; - dumpExceptionInfo.ThreadId = GetCurrentThreadId(); - dumpExceptionInfo.ExceptionPointers = exceptionInfo; - dumpExceptionInfo.ClientPointers = false; - - MiniDumpWriteDump( - GetCurrentProcess(), - GetCurrentProcessId(), - hMinidumpFile, - MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory), - &dumpExceptionInfo, - nullptr, - nullptr); - CloseHandle(hMinidumpFile); - } - else - spdlog::error("Failed to write minidump file {}!", stream.str()); - - if (!IsDedicatedServer()) - MessageBoxA( - 0, "Northstar has crashed! Crash info can be found in R2Northstar/logs", "Northstar has crashed!", MB_ICONERROR | MB_OK); - } - - logged = true; - return EXCEPTION_EXECUTE_HANDLER; -} - -HANDLE hExceptionFilter; - -BOOL WINAPI ConsoleHandlerRoutine(DWORD eventCode) -{ - switch (eventCode) - { - case CTRL_CLOSE_EVENT: - // User closed console, shut everything down - spdlog::info("Exiting due to console close..."); - RemoveVectoredExceptionHandler(hExceptionFilter); - exit(EXIT_SUCCESS); - return FALSE; - } - - return TRUE; -} - -void InitialiseLogging() -{ - hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter); +#include "bitbuf.h" +#include "tier0.h" +#include "spdlog/sinks/basic_file_sink.h" - AllocConsole(); - freopen("CONOUT$", "w", stdout); - freopen("CONOUT$", "w", stderr); - spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v"); +#include +#include - SetConsoleCtrlHandler(ConsoleHandlerRoutine, true); -} +AUTOHOOK_INIT() ConVar* Cvar_spewlog_enable; -enum SpewType_t +enum class SpewType_t { SPEW_MESSAGE = 0, @@ -264,56 +26,24 @@ enum SpewType_t SPEW_TYPE_COUNT }; -typedef void (*EngineSpewFuncType)(); -EngineSpewFuncType EngineSpewFunc; - -void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format, va_list args) +const std::unordered_map PrintSpewTypes = { + {SpewType_t::SPEW_MESSAGE, "SPEW_MESSAGE"}, + {SpewType_t::SPEW_WARNING, "SPEW_WARNING"}, + {SpewType_t::SPEW_ASSERT, "SPEW_ASSERT"}, + {SpewType_t::SPEW_ERROR, "SPEW_ERROR"}, + {SpewType_t::SPEW_LOG, "SPEW_LOG"}}; + +// clang-format off +AUTOHOOK(EngineSpewFunc, engine.dll + 0x11CA80, +void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_list args)) +// clang-format on { if (!Cvar_spewlog_enable->GetBool()) return; - const char* typeStr; - switch (type) - { - case SPEW_MESSAGE: - { - typeStr = "SPEW_MESSAGE"; - break; - } - - case SPEW_WARNING: - { - typeStr = "SPEW_WARNING"; - break; - } - - case SPEW_ASSERT: - { - typeStr = "SPEW_ASSERT"; - break; - } - - case SPEW_ERROR: - { - typeStr = "SPEW_ERROR"; - break; - } - - case SPEW_LOG: - { - typeStr = "SPEW_LOG"; - break; - } - - default: - { - typeStr = "SPEW_UNKNOWN"; - break; - } - } - + const char* typeStr = PrintSpewTypes.at(type); char formatted[2048] = {0}; - bool shouldFormat = true; + bool bShouldFormat = true; // because titanfall 2 is quite possibly the worst thing to yet exist, it sometimes gives invalid specifiers which will crash // ttf2sdk had a way to prevent them from crashing but it doesnt work in debug builds @@ -360,19 +90,17 @@ void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format, default: { - shouldFormat = false; + bShouldFormat = false; break; } } } } - if (shouldFormat) + if (bShouldFormat) vsnprintf(formatted, sizeof(formatted), format, args); else - { spdlog::warn("Failed to format {} \"{}\"", typeStr, format); - } auto endpos = strlen(formatted); if (formatted[endpos - 1] == '\n') @@ -381,10 +109,11 @@ void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format, spdlog::info("[SERVER {}] {}", typeStr, formatted); } -typedef void (*Status_ConMsg_Type)(const char* text, ...); -Status_ConMsg_Type Status_ConMsg_Original; - -void Status_ConMsg_Hook(const char* text, ...) +// used for printing the output of status +// clang-format off +AUTOHOOK(Status_ConMsg, engine.dll + 0x15ABD0, +void,, (const char* text, ...)) +// clang-format on { char formatted[2048]; va_list list; @@ -400,10 +129,10 @@ void Status_ConMsg_Hook(const char* text, ...) spdlog::info(formatted); } -typedef bool (*CClientState_ProcessPrint_Type)(__int64 thisptr, __int64 msg); -CClientState_ProcessPrint_Type CClientState_ProcessPrint_Original; - -bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg) +// clang-format off +AUTOHOOK(CClientState_ProcessPrint, engine.dll + 0x1A1530, +bool,, (void* thisptr, uintptr_t msg)) +// clang-format on { char* text = *(char**)(msg + 0x20); @@ -415,32 +144,8 @@ bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg) return true; } -void InitialiseEngineSpewFuncHooks(HMODULE baseAddress) -{ - HookEnabler hook; - - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x11CA80, EngineSpewFuncHook, reinterpret_cast(&EngineSpewFunc)); - - // Hook print function that status concmd uses to actually print data - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15ABD0, Status_ConMsg_Hook, reinterpret_cast(&Status_ConMsg_Original)); - - // Hook CClientState::ProcessPrint - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x1A1530, - CClientState_ProcessPrint_Hook, - reinterpret_cast(&CClientState_ProcessPrint_Original)); - - Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged"); -} - -#include "bitbuf.h" - ConVar* Cvar_cl_showtextmsg; -typedef void (*TextMsg_Type)(__int64); -TextMsg_Type TextMsg_Original; - class ICenterPrint { public: @@ -453,11 +158,22 @@ class ICenterPrint virtual void SetTextColor(int r, int g, int b, int a) = 0; }; -ICenterPrint* internalCenterPrint = NULL; +ICenterPrint* pInternalCenterPrint = NULL; -void TextMsgHook(BFRead* msg) +enum class TextMsgPrintType_t { - int msg_dest = msg->ReadByte(); + HUD_PRINTNOTIFY = 1, + HUD_PRINTCONSOLE, + HUD_PRINTTALK, + HUD_PRINTCENTER +}; + +// clang-format off +AUTOHOOK(TextMsg, client.dll + 0x198710, +void,, (BFRead* msg)) +// clang-format on +{ + TextMsgPrintType_t msg_dest = (TextMsgPrintType_t)msg->ReadByte(); char text[256]; msg->ReadString(text, sizeof(text)); @@ -467,29 +183,86 @@ void TextMsgHook(BFRead* msg) switch (msg_dest) { - case 4: // HUD_PRINTCENTER - internalCenterPrint->Print(text); + case TextMsgPrintType_t::HUD_PRINTCENTER: + pInternalCenterPrint->Print(text); break; + default: spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest); [[fallthrough]]; - case 2: // HUD_PRINTCONSOLE + + case TextMsgPrintType_t::HUD_PRINTCONSOLE: auto endpos = strlen(text); if (text[endpos - 1] == '\n') text[endpos - 1] = '\0'; // cut off repeated newline + spdlog::info(text); break; } } -void InitialiseClientPrintHooks(HMODULE baseAddress) +// clang-format off +AUTOHOOK(ConCommand_echo, engine.dll + 0x123680, +void,, (const CCommand& arg)) +// clang-format on +{ + if (arg.ArgC() >= 2) + spdlog::info("[echo] {}", arg.ArgS()); +} + +// This needs to be called after hooks are loaded so we can access the command line args +void CreateLogFiles() +{ + if (strstr(GetCommandLineA(), "-disablelogs")) + { + spdlog::default_logger()->set_level(spdlog::level::off); + } + else + { + try + { + // todo: might be good to delete logs that are too old + time_t time = std::time(nullptr); + tm currentTime = *std::localtime(&time); + std::stringstream stream; + + stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str()); + spdlog::default_logger()->sinks().push_back(std::make_shared(stream.str(), false)); + spdlog::flush_on(spdlog::level::info); + } + catch (...) + { + spdlog::error("Failed creating log file!"); + MessageBoxA( + 0, "Failed creating log file! Make sure the profile directory is writable.", "Northstar Warning", MB_ICONWARNING | MB_OK); + } + } +} + +void InitialiseLogging() +{ + AllocConsole(); + + // Bind stdout to receive console output. + // these two lines are responsible for stuff to not show up in the console sometimes, from talking about it on discord + // apparently they were meant to make logging work when using -northstar, however from testing it seems that it doesnt + // work regardless of these two lines + // freopen("CONOUT$", "w", stdout); + // freopen("CONOUT$", "w", stderr); + spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v"); +} + +ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", EngineSpewFuncHooks, ConVar, (CModule module)) { - HookEnabler hook; + AUTOHOOK_DISPATCH_MODULE(engine.dll) - internalCenterPrint = (ICenterPrint*)((char*)baseAddress + 0x216E940); + Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged"); +} - // "TextMsg" usermessage - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x198710, TextMsgHook, reinterpret_cast(&TextMsg_Original)); +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientPrintHooks, ConVar, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(client.dll) Cvar_cl_showtextmsg = new ConVar("cl_showtextmsg", "1", FCVAR_NONE, "Enable/disable text messages printing on the screen."); + pInternalCenterPrint = module.Offset(0x216E940).As(); } diff --git a/NorthstarDLL/logging.h b/NorthstarDLL/logging.h index 59537e01..83fd0c12 100644 --- a/NorthstarDLL/logging.h +++ b/NorthstarDLL/logging.h @@ -1,7 +1,4 @@ #pragma once -#include "context.h" void CreateLogFiles(); void InitialiseLogging(); -void InitialiseEngineSpewFuncHooks(HMODULE baseAddress); -void InitialiseClientPrintHooks(HMODULE baseAddress); diff --git a/NorthstarDLL/masterserver.cpp b/NorthstarDLL/masterserver.cpp index 62d037be..fc747e5d 100644 --- a/NorthstarDLL/masterserver.cpp +++ b/NorthstarDLL/masterserver.cpp @@ -1,151 +1,29 @@ #include "pch.h" #include "masterserver.h" #include "concommand.h" -#include "gameutils.h" -#include "hookutils.h" +#include "playlist.h" #include "serverauthentication.h" -#include "gameutils.h" +#include "tier0.h" +#include "r2engine.h" +#include "modmanager.h" +#include "misccommands.h" +#include "version.h" + #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" #include "rapidjson/error/en.h" -#include "modmanager.h" -#include "misccommands.h" + #include #include -#include "version.h" -#include -// NOTE for anyone reading this: we used to use httplib for requests here, but it had issues, so we're moving to curl now for masterserver -// requests so httplib is used exclusively for server stuff now -ConVar* Cvar_ns_masterserver_hostname; -ConVar* Cvar_ns_report_server_to_masterserver; -ConVar* Cvar_ns_report_sp_server_to_masterserver; +using namespace std::chrono_literals; -ConVar* Cvar_ns_server_name; -ConVar* Cvar_ns_server_desc; -ConVar* Cvar_ns_server_password; +MasterServerManager* g_pMasterServerManager; +ConVar* Cvar_ns_masterserver_hostname; ConVar* Cvar_ns_curl_log_enable; -// Source ConVar -ConVar* Cvar_hostname; - -MasterServerManager* g_MasterServerManager; - -typedef void (*CHostState__State_NewGameType)(CHostState* hostState); -CHostState__State_NewGameType CHostState__State_NewGame; - -typedef void (*CHostState__State_ChangeLevelMPType)(CHostState* hostState); -CHostState__State_ChangeLevelMPType CHostState__State_ChangeLevelMP; - -typedef void (*CHostState__State_ChangeLevelSPType)(CHostState* hostState); -CHostState__State_ChangeLevelSPType CHostState__State_ChangeLevelSP; - -typedef void (*CHostState__State_GameShutdownType)(CHostState* hostState); -CHostState__State_GameShutdownType CHostState__State_GameShutdown; - -// Convert a hex digit char to integer. -inline int hctod(char c) -{ - if (c >= 'A' && c <= 'F') - { - return c - 'A' + 10; - } - else if (c >= 'a' && c <= 'f') - { - return c - 'a' + 10; - } - else - { - return c - '0'; - } -} - -// This function interprets all 4-hexadecimal-digit unicode codepoint characters like \u4E2D to UTF-8 encoding. -std::string unescape_unicode(const std::string& str) -{ - std::string result; - std::regex r("\\\\u([a-f\\d]{4})", std::regex::icase); - auto matches_begin = std::sregex_iterator(str.begin(), str.end(), r); - auto matches_end = std::sregex_iterator(); - std::smatch last_match; - for (std::sregex_iterator i = matches_begin; i != matches_end; ++i) - { - last_match = *i; - result.append(last_match.prefix()); - unsigned int cp = 0; - for (int i = 2; i <= 5; ++i) - { - cp *= 16; - cp += hctod(last_match.str()[i]); - } - if (cp <= 0x7F) - { - result.push_back(cp); - } - else if (cp <= 0x7FF) - { - result.push_back((cp >> 6) | 0b11000000 & (~(1 << 5))); - result.push_back(cp & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - } - else if (cp <= 0xFFFF) - { - result.push_back((cp >> 12) | 0b11100000 & (~(1 << 4))); - result.push_back((cp >> 6) & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - result.push_back(cp & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - } - } - if (!last_match.ready()) - { - return str; - } - else - { - result.append(last_match.suffix()); - } - return result; -} - -void UpdateServerInfoFromUnicodeToUTF8() -{ - g_MasterServerManager->m_sUnicodeServerName = unescape_unicode(Cvar_ns_server_name->GetString()); - g_MasterServerManager->m_sUnicodeServerDesc = unescape_unicode(Cvar_ns_server_desc->GetString()); -} - -const char* HttplibErrorToString(httplib::Error error) -{ - switch (error) - { - case httplib::Error::Success: - return "httplib::Error::Success"; - case httplib::Error::Unknown: - return "httplib::Error::Unknown"; - case httplib::Error::Connection: - return "httplib::Error::Connection"; - case httplib::Error::BindIPAddress: - return "httplib::Error::BindIPAddress"; - case httplib::Error::Read: - return "httplib::Error::Read"; - case httplib::Error::Write: - return "httplib::Error::Write"; - case httplib::Error::ExceedRedirectCount: - return "httplib::Error::ExceedRedirectCount"; - case httplib::Error::Canceled: - return "httplib::Error::Canceled"; - case httplib::Error::SSLConnection: - return "httplib::Error::SSLConnection"; - case httplib::Error::SSLLoadingCerts: - return "httplib::Error::SSLLoadingCerts"; - case httplib::Error::SSLServerVerification: - return "httplib::Error::SSLServerVerification"; - case httplib::Error::UnsupportedMultipartBoundaryChars: - return "httplib::Error::UnsupportedMultipartBoundaryChars"; - } - - return ""; -} - RemoteServerInfo::RemoteServerInfo( const char* newId, const char* newName, @@ -159,29 +37,28 @@ RemoteServerInfo::RemoteServerInfo( // passworded servers don't have public ips requiresPassword = newRequiresPassword; - strncpy((char*)id, newId, sizeof(id)); - id[sizeof(id) - 1] = 0; - strncpy((char*)name, newName, sizeof(name)); - name[sizeof(name) - 1] = 0; + strncpy_s((char*)id, sizeof(id), newId, sizeof(id) - 1); + strncpy_s((char*)name, sizeof(name), newName, sizeof(name) - 1); description = std::string(newDescription); - strncpy((char*)map, newMap, sizeof(map)); - map[sizeof(map) - 1] = 0; - strncpy((char*)playlist, newPlaylist, sizeof(playlist)); - playlist[sizeof(playlist) - 1] = 0; + strncpy_s((char*)map, sizeof(map), newMap, sizeof(map) - 1); + strncpy_s((char*)playlist, sizeof(playlist), newPlaylist, sizeof(playlist) - 1); playerCount = newPlayerCount; maxPlayers = newMaxPlayers; } -void MasterServerManager::SetCommonHttpClientOptions(CURL* curl) +void SetCommonHttpClientOptions(CURL* curl) { curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_easy_setopt(curl, CURLOPT_VERBOSE, Cvar_ns_curl_log_enable->GetBool()); curl_easy_setopt(curl, CURLOPT_USERAGENT, &NSUserAgent); + // Timeout since the MS has fucky async functions without await, making curl hang due to a successful connection but no response for ~90 + // seconds. + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // curl_easy_setopt(curl, CURLOPT_STDERR, stdout); - if (CommandLine()->FindParm("-msinsecure")) // TODO: this check doesn't seem to work + if (Tier0::CommandLine()->FindParm("-msinsecure")) // TODO: this check doesn't seem to work { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); @@ -204,7 +81,7 @@ size_t CurlWriteToStringBufferCallback(char* contents, size_t size, size_t nmemb return size * nmemb; } -void MasterServerManager::AuthenticateOriginWithMasterServer(char* uid, char* originToken) +void MasterServerManager::AuthenticateOriginWithMasterServer(const char* uid, const char* originToken) { if (m_bOriginAuthWithMasterServerInProgress) return; @@ -255,8 +132,11 @@ void MasterServerManager::AuthenticateOriginWithMasterServer(char* uid, char* or if (originAuthInfo["success"].IsTrue() && originAuthInfo.HasMember("token") && originAuthInfo["token"].IsString()) { - strncpy(m_sOwnClientAuthToken, originAuthInfo["token"].GetString(), sizeof(m_sOwnClientAuthToken)); - m_sOwnClientAuthToken[sizeof(m_sOwnClientAuthToken) - 1] = 0; + strncpy_s( + m_sOwnClientAuthToken, + sizeof(m_sOwnClientAuthToken), + originAuthInfo["token"].GetString(), + sizeof(m_sOwnClientAuthToken) - 1); spdlog::info("Northstar origin authentication completed successfully!"); } else @@ -347,7 +227,7 @@ void MasterServerManager::RequestServerList() goto REQUEST_END_CLEANUP; } - // todo: verify json props are fine before adding to m_vRemoteServers + // todo: verify json props are fine before adding to m_remoteServers if (!serverObj.HasMember("id") || !serverObj["id"].IsString() || !serverObj.HasMember("name") || !serverObj["name"].IsString() || !serverObj.HasMember("description") || !serverObj["description"].IsString() || !serverObj.HasMember("map") || !serverObj["map"].IsString() || !serverObj.HasMember("playlist") || @@ -491,10 +371,6 @@ void MasterServerManager::RequestMainMenuPromos() { spdlog::error("Failed reading masterserver response: got fastify error response"); spdlog::error(readBuffer); - if (mainMenuPromoJson["error"].HasMember("enum")) - s_authfail_reason = std::string(mainMenuPromoJson["error"]["enum"].GetString()); - else - s_authfail_reason = std::string("No error message provided"); goto REQUEST_END_CLEANUP; } @@ -559,7 +435,7 @@ void MasterServerManager::RequestMainMenuPromos() requestThread.detach(); } -void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken) +void MasterServerManager::AuthenticateWithOwnServer(const char* uid, const char* playerToken) { // dont wait, just stop if we're trying to do 2 auth requests at once if (m_bAuthenticatingWithGameServer) @@ -615,10 +491,12 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken { spdlog::error("Failed reading masterserver response: got fastify error response"); spdlog::error(readBuffer); + if (authInfoJson["error"].HasMember("enum")) - s_authfail_reason = std::string(authInfoJson["error"]["enum"].GetString()); + m_sAuthFailureReason = authInfoJson["error"]["enum"].GetString(); else - s_authfail_reason = std::string("No error message provided"); + m_sAuthFailureReason = "No error message provided"; + goto REQUEST_END_CLEANUP; } @@ -636,9 +514,8 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken goto REQUEST_END_CLEANUP; } - AuthData newAuthData {}; - strncpy(newAuthData.uid, authInfoJson["id"].GetString(), sizeof(newAuthData.uid)); - newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; + RemoteAuthData newAuthData {}; + strncpy_s(newAuthData.uid, sizeof(newAuthData.uid), authInfoJson["id"].GetString(), sizeof(newAuthData.uid) - 1); newAuthData.pdataSize = authInfoJson["persistentData"].GetArray().Size(); newAuthData.pdata = new char[newAuthData.pdataSize]; @@ -658,9 +535,10 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken newAuthData.pdata[i++] = static_cast(byte.GetUint()); } - std::lock_guard guard(g_ServerAuthenticationManager->m_authDataMutex); - g_ServerAuthenticationManager->m_authData.clear(); - g_ServerAuthenticationManager->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; } @@ -679,7 +557,7 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken if (m_bNewgameAfterSelfAuth) { // pretty sure this is threadsafe? - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "ns_end_reauth_and_leave_to_lobby", cmd_source_t::kCommandSrcCode); + R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "ns_end_reauth_and_leave_to_lobby", R2::cmd_source_t::kCommandSrcCode); m_bNewgameAfterSelfAuth = false; } @@ -689,7 +567,7 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken requestThread.detach(); } -void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password) +void MasterServerManager::AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password) { // dont wait, just stop if we're trying to do 2 auth requests at once if (m_bAuthenticatingWithGameServer) @@ -766,10 +644,12 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c { spdlog::error("Failed reading masterserver response: got fastify error response"); spdlog::error(readBuffer); + if (connectionInfoJson["error"].HasMember("enum")) - s_authfail_reason = std::string(connectionInfoJson["error"]["enum"].GetString()); + m_sAuthFailureReason = connectionInfoJson["error"]["enum"].GetString(); else - s_authfail_reason = std::string("No error message provided"); + m_sAuthFailureReason = "No error message provided"; + goto REQUEST_END_CLEANUP; } @@ -791,8 +671,11 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c m_pendingConnectionInfo.ip.S_un.S_addr = inet_addr(connectionInfoJson["ip"].GetString()); m_pendingConnectionInfo.port = (unsigned short)connectionInfoJson["port"].GetUint(); - strncpy(m_pendingConnectionInfo.authToken, connectionInfoJson["authToken"].GetString(), 31); - m_pendingConnectionInfo.authToken[31] = 0; + strncpy_s( + m_pendingConnectionInfo.authToken, + sizeof(m_pendingConnectionInfo.authToken), + connectionInfoJson["authToken"].GetString(), + sizeof(m_pendingConnectionInfo.authToken) - 1); m_bHasPendingConnectionInfo = true; m_bSuccessfullyAuthenticatedWithGameServer = true; @@ -814,36 +697,267 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c requestThread.detach(); } -void MasterServerManager::AddSelfToServerList( - int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password) +void MasterServerManager::WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize) { - if (!Cvar_ns_report_server_to_masterserver->GetBool()) + // still call this if we don't have a server id, since lobbies that aren't port forwarded need to be able to call it + m_bSavingPersistentData = true; + if (!pdataSize) + { + spdlog::warn("attempted to write pdata of size 0!"); return; + } + + std::string strPlayerId(playerId); + std::string strPdata(pdata, pdataSize); + + std::thread requestThread( + [this, strPlayerId, strPdata, pdataSize] + { + CURL* curl = curl_easy_init(); + SetCommonHttpClientOptions(curl); + + std::string readBuffer; + curl_easy_setopt( + curl, + CURLOPT_URL, + fmt::format( + "{}/accounts/write_persistence?id={}&serverId={}", + Cvar_ns_masterserver_hostname->GetString(), + strPlayerId, + m_sOwnServerId) + .c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + + curl_mime* mime = curl_mime_init(curl); + curl_mimepart* part = curl_mime_addpart(mime); + + curl_mime_data(part, strPdata.c_str(), pdataSize); + curl_mime_name(part, "pdata"); + curl_mime_filename(part, "file.pdata"); + curl_mime_type(part, "application/octet-stream"); + + curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); + + CURLcode result = curl_easy_perform(curl); + + if (result == CURLcode::CURLE_OK) + m_bSuccessfullyConnected = true; + else + m_bSuccessfullyConnected = false; - if (!Cvar_ns_report_sp_server_to_masterserver->GetBool() && !strncmp(map, "sp_", 3)) + curl_easy_cleanup(curl); + + m_bSavingPersistentData = false; + }); + + requestThread.detach(); +} + +void ConCommand_ns_fetchservers(const CCommand& args) +{ + g_pMasterServerManager->RequestServerList(); +} + +MasterServerManager::MasterServerManager() : m_pendingConnectionInfo {}, m_sOwnServerId {""}, m_sOwnClientAuthToken {""} {} + +ON_DLL_LOAD_RELIESON("engine.dll", MasterServer, (ConCommand, ServerPresence), (CModule module)) +{ + g_pMasterServerManager = new MasterServerManager; + + Cvar_ns_masterserver_hostname = new ConVar("ns_masterserver_hostname", "127.0.0.1", FCVAR_NONE, ""); + Cvar_ns_curl_log_enable = new ConVar("ns_curl_log_enable", "0", FCVAR_NONE, "Whether curl should log to the console"); + + RegisterConCommand("ns_fetchservers", ConCommand_ns_fetchservers, "Fetch all servers from the masterserver", FCVAR_CLIENTDLL); + + MasterServerPresenceReporter* presenceReporter = new MasterServerPresenceReporter; + g_pServerPresence->AddPresenceReporter(presenceReporter); +} + +void MasterServerPresenceReporter::CreatePresence(const ServerPresence* pServerPresence) +{ + m_nNumRegistrationAttempts = 0; +} + +void MasterServerPresenceReporter::ReportPresence(const ServerPresence* pServerPresence) +{ + // make a copy of presence for multithreading purposes + ServerPresence threadedPresence(pServerPresence); + + if (!*g_pMasterServerManager->m_sOwnServerId) { - m_bRequireClientAuth = false; - return; + // Don't try if we've reached the max registration attempts. + // In the future, we should probably allow servers to re-authenticate after a while if the MS was down. + if (m_nNumRegistrationAttempts >= MAX_REGISTRATION_ATTEMPTS) + { + return; + } + + // Make sure to wait til the cooldown is over for DUPLICATE_SERVER failures. + if (Tier0::Plat_FloatTime() < m_fNextAddServerAttemptTime) + { + return; + } + + // If we're not running any InternalAddServer() attempt in the background. + if (!addServerFuture.valid()) + { + // Launch an attempt to add the local server to the master server. + InternalAddServer(pServerPresence); + } + } + else + { + // If we're not running any InternalUpdateServer() attempt in the background. + if (!updateServerFuture.valid()) + { + // Launch an attempt to update the local server on the master server. + InternalUpdateServer(pServerPresence); + } } +} - m_bRequireClientAuth = true; +void MasterServerPresenceReporter::DestroyPresence(const ServerPresence* pServerPresence) +{ + // Don't call this if we don't have a server id. + if (!*g_pMasterServerManager->m_sOwnServerId) + { + return; + } - std::string strName(name); - std::string strDescription(description); - std::string strMap(map); - std::string strPlaylist(playlist); - std::string strPassword(password); + // Not bothering with better thread safety in this case since DestroyPresence() is called when the game is shutting down. + *g_pMasterServerManager->m_sOwnServerId = 0; std::thread requestThread( - [this, port, authPort, strName, strDescription, strMap, strPlaylist, maxPlayers, strPassword] + [this] + { + CURL* curl = curl_easy_init(); + SetCommonHttpClientOptions(curl); + + std::string readBuffer; + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt( + curl, + CURLOPT_URL, + fmt::format( + "{}/server/remove_server?id={}", Cvar_ns_masterserver_hostname->GetString(), g_pMasterServerManager->m_sOwnServerId) + .c_str()); + + CURLcode result = curl_easy_perform(curl); + curl_easy_cleanup(curl); + }); + + requestThread.detach(); +} + +void MasterServerPresenceReporter::RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) +{ + // Check if we're already running an InternalAddServer() call in the background. + // If so, grab the result if it's ready. + if (addServerFuture.valid()) + { + std::future_status status = addServerFuture.wait_for(0ms); + if (status != std::future_status::ready) + { + // Still running, no need to do anything. + return; + } + + // Check the result. + auto resultData = addServerFuture.get(); + + g_pMasterServerManager->m_bSuccessfullyConnected = resultData.result != MasterServerReportPresenceResult::FailedNoConnect; + + switch (resultData.result) + { + case MasterServerReportPresenceResult::Success: + // Copy over the server id and auth token granted by the MS. + strncpy_s( + g_pMasterServerManager->m_sOwnServerId, + sizeof(g_pMasterServerManager->m_sOwnServerId), + resultData.id.value().c_str(), + sizeof(g_pMasterServerManager->m_sOwnServerId) - 1); + strncpy_s( + g_pMasterServerManager->m_sOwnServerAuthToken, + sizeof(g_pMasterServerManager->m_sOwnServerAuthToken), + resultData.serverAuthToken.value().c_str(), + sizeof(g_pMasterServerManager->m_sOwnServerAuthToken) - 1); + break; + case MasterServerReportPresenceResult::FailedNoRetry: + case MasterServerReportPresenceResult::FailedNoConnect: + // If we failed to connect to the master server, or failed with no retry, stop trying. + m_nNumRegistrationAttempts = MAX_REGISTRATION_ATTEMPTS; + break; + case MasterServerReportPresenceResult::Failed: + ++m_nNumRegistrationAttempts; + break; + case MasterServerReportPresenceResult::FailedDuplicateServer: + ++m_nNumRegistrationAttempts; + // Wait at least twenty seconds until we re-attempt to add the server. + m_fNextAddServerAttemptTime = Tier0::Plat_FloatTime() + 20.0f; + break; + } + + if (m_nNumRegistrationAttempts >= MAX_REGISTRATION_ATTEMPTS) + { + spdlog::error("Reached max ms server registration attempts."); + } + } + else if (updateServerFuture.valid()) + { + // Check if the InternalUpdateServer() call completed. + std::future_status status = updateServerFuture.wait_for(0ms); + if (status != std::future_status::ready) { - // Keep track of attempted connects in case of DUPLICATE_SERVER response - int retry_counter = 0; + // Still running, no need to do anything. + return; + } + + auto resultData = updateServerFuture.get(); + if (resultData.result == MasterServerReportPresenceResult::Success) + { + if (resultData.id) + { + strncpy_s( + g_pMasterServerManager->m_sOwnServerId, + sizeof(g_pMasterServerManager->m_sOwnServerId), + resultData.id.value().c_str(), + sizeof(g_pMasterServerManager->m_sOwnServerId) - 1); + } + + if (resultData.serverAuthToken) + { + strncpy_s( + g_pMasterServerManager->m_sOwnServerAuthToken, + sizeof(g_pMasterServerManager->m_sOwnServerAuthToken), + resultData.serverAuthToken.value().c_str(), + sizeof(g_pMasterServerManager->m_sOwnServerAuthToken) - 1); + } + } + } +} + +void MasterServerPresenceReporter::InternalAddServer(const ServerPresence* pServerPresence) +{ + const ServerPresence threadedPresence(pServerPresence); + // Never call this with an ongoing InternalAddServer() call. + assert(!addServerFuture.valid()); + + g_pMasterServerManager->m_sOwnServerId[0] = 0; + g_pMasterServerManager->m_sOwnServerAuthToken[0] = 0; - START_REQUEST: - m_sOwnServerId[0] = 0; - m_sOwnServerAuthToken[0] = 0; + std::string modInfo = g_pMasterServerManager->m_sOwnModInfoJson; + std::string hostname = Cvar_ns_masterserver_hostname->GetString(); + spdlog::info("Attempting to register the local server to the master server."); + + addServerFuture = std::async( + std::launch::async, + [threadedPresence, modInfo, hostname] + { CURL* curl = curl_easy_init(); SetCommonHttpClientOptions(curl); @@ -855,7 +969,22 @@ void MasterServerManager::AddSelfToServerList( curl_mime* mime = curl_mime_init(curl); curl_mimepart* part = curl_mime_addpart(mime); - curl_mime_data(part, m_sOwnModInfoJson.c_str(), m_sOwnModInfoJson.size()); + // Lambda to quickly cleanup resources and return a value. + auto ReturnCleanup = + [curl, mime](MasterServerReportPresenceResult result, const char* id = "", const char* serverAuthToken = "") + { + curl_easy_cleanup(curl); + curl_mime_free(mime); + + MasterServerPresenceReporter::ReportPresenceResultData data; + data.result = result; + data.id = id; + data.serverAuthToken = serverAuthToken; + + return data; + }; + + curl_mime_data(part, modInfo.c_str(), modInfo.size()); curl_mime_name(part, "modinfo"); curl_mime_filename(part, "modinfo.json"); curl_mime_type(part, "application/json"); @@ -864,25 +993,26 @@ void MasterServerManager::AddSelfToServerList( // format every paramter because computers hate me { - char* nameEscaped = curl_easy_escape(curl, strName.c_str(), strName.length()); - char* descEscaped = curl_easy_escape(curl, strDescription.c_str(), strDescription.length()); - char* mapEscaped = curl_easy_escape(curl, strMap.c_str(), strMap.length()); - char* playlistEscaped = curl_easy_escape(curl, strPlaylist.c_str(), strPlaylist.length()); - char* passwordEscaped = curl_easy_escape(curl, strPassword.c_str(), strPassword.length()); + char* nameEscaped = curl_easy_escape(curl, threadedPresence.m_sServerName.c_str(), NULL); + char* descEscaped = curl_easy_escape(curl, threadedPresence.m_sServerDesc.c_str(), NULL); + char* mapEscaped = curl_easy_escape(curl, threadedPresence.m_MapName, NULL); + char* playlistEscaped = curl_easy_escape(curl, threadedPresence.m_PlaylistName, NULL); + char* passwordEscaped = curl_easy_escape(curl, threadedPresence.m_Password, NULL); curl_easy_setopt( curl, CURLOPT_URL, fmt::format( - "{}/server/add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", - Cvar_ns_masterserver_hostname->GetString(), - port, - authPort, + "{}/server/" + "add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", + hostname.c_str(), + threadedPresence.m_iPort, + threadedPresence.m_iAuthPort, nameEscaped, descEscaped, mapEscaped, playlistEscaped, - maxPlayers, + threadedPresence.m_iMaxPlayers, passwordEscaped) .c_str()); @@ -897,23 +1027,23 @@ void MasterServerManager::AddSelfToServerList( if (result == CURLcode::CURLE_OK) { - m_bSuccessfullyConnected = true; - rapidjson_document serverAddedJson; serverAddedJson.Parse(readBuffer.c_str()); + // If we could not parse the JSON or it isn't an object, assume the MS is either wrong or we're completely out of date. + // No retry. if (serverAddedJson.HasParseError()) { spdlog::error( "Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(serverAddedJson.GetParseError())); - goto REQUEST_END_CLEANUP; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); } if (!serverAddedJson.IsObject()) { spdlog::error("Failed reading masterserver authentication response: root object is not an object"); - goto REQUEST_END_CLEANUP; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); } if (serverAddedJson.HasMember("error")) @@ -921,488 +1051,168 @@ void MasterServerManager::AddSelfToServerList( spdlog::error("Failed reading masterserver response: got fastify error response"); spdlog::error(readBuffer); - // Check for enum member in JSON - if (serverAddedJson["error"].HasMember("enum")) + // If this is DUPLICATE_SERVER, we'll retry adding the server every 20 seconds. + // The master server will only update its internal server list and clean up dead servers on certain events. + // And then again, only if a player requests the server list after the cooldown (1 second by default), or a server is + // added/updated/removed. In any case this needs to be fixed in the master server rewrite. + if (serverAddedJson["error"].HasMember("enum") && + strcmp(serverAddedJson["error"]["enum"].GetString(), "DUPLICATE_SERVER") == 0) { - // Check for DUPLICATE_SERVER error response, stop if we tried 10 times - if (strncmp(serverAddedJson["error"]["enum"].GetString(), "DUPLICATE_SERVER", 17) == 0 && retry_counter < 10) - { - - spdlog::info("Retrying request in 10 seconds."); - // Incremement retry counter - retry_counter++; - - // Sleep for 10 seconds - std::this_thread::sleep_for(std::chrono::seconds(10)); - - // curl cleanup to retry request - curl_easy_cleanup(curl); - curl_mime_free(mime); - - // go to beginning and retry - goto START_REQUEST; - } + spdlog::error("Cooling down while the master server cleans the dead server entry, if any."); + return ReturnCleanup(MasterServerReportPresenceResult::FailedDuplicateServer); } - goto REQUEST_END_CLEANUP; + + // Retry until we reach max retries. + return ReturnCleanup(MasterServerReportPresenceResult::Failed); } if (!serverAddedJson["success"].IsTrue()) { spdlog::error("Adding server to masterserver failed: \"success\" is not true"); - goto REQUEST_END_CLEANUP; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); } if (!serverAddedJson.HasMember("id") || !serverAddedJson["id"].IsString() || !serverAddedJson.HasMember("serverAuthToken") || !serverAddedJson["serverAuthToken"].IsString()) { spdlog::error("Failed reading masterserver response: malformed json object"); - goto REQUEST_END_CLEANUP; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); } - strncpy(m_sOwnServerId, serverAddedJson["id"].GetString(), sizeof(m_sOwnServerId)); - m_sOwnServerId[sizeof(m_sOwnServerId) - 1] = 0; - - strncpy(m_sOwnServerAuthToken, serverAddedJson["serverAuthToken"].GetString(), sizeof(m_sOwnServerAuthToken)); - m_sOwnServerAuthToken[sizeof(m_sOwnServerAuthToken) - 1] = 0; - - spdlog::info("Succesfully added server to the master server."); - - // heartbeat thread - // ideally this should actually be done in main thread, rather than on it's own thread, so it'd stop if server freezes - std::thread heartbeatThread( - [this] - { - Sleep(5000); - - // defensive check, as m_ownServer could be set to null during the Sleep(5000) above - if (!*m_sOwnServerId) - return; - - do - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); - - // 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* escapedMapNew = curl_easy_escape(curl, g_pHostState->m_levelName, NULL); - char* escapedPlaylistNew = curl_easy_escape(curl, GetCurrentPlaylistName(), NULL); - char* escapedPasswordNew = curl_easy_escape(curl, Cvar_ns_server_password->GetString(), NULL); - - int maxPlayers = 6; - char* maxPlayersVar = GetCurrentPlaylistVar("max_players", false); - if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this - maxPlayers = std::stoi(maxPlayersVar); - - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/server/" - "update_values?id={}&port={}&authPort={}&name={}&description={}&map={}&playlist={}&playerCount={}&" - "maxPlayers={}&password={}", - Cvar_ns_masterserver_hostname->GetString(), - m_sOwnServerId, - Cvar_hostport->GetInt(), - Cvar_ns_player_auth_port->GetInt(), - escapedNameNew, - escapedDescNew, - escapedMapNew, - escapedPlaylistNew, - g_ServerAuthenticationManager->m_additionalPlayerData.size(), - maxPlayers, - escapedPasswordNew) - .c_str()); - - curl_free(escapedNameNew); - curl_free(escapedDescNew); - curl_free(escapedMapNew); - curl_free(escapedPlaylistNew); - curl_free(escapedPasswordNew); - } - - curl_mime* mime = curl_mime_init(curl); - curl_mimepart* part = curl_mime_addpart(mime); - - curl_mime_data(part, m_sOwnModInfoJson.c_str(), m_sOwnModInfoJson.size()); - curl_mime_name(part, "modinfo"); - curl_mime_filename(part, "modinfo.json"); - curl_mime_type(part, "application/json"); - - curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); - - CURLcode result = curl_easy_perform(curl); - - // defensive check, as m_sOwnServerId could be set to null before this request gets processed - if (!*m_sOwnServerId) - return; - - if (result == CURLcode::CURLE_OK) - { - rapidjson_document serverAddedJson; - serverAddedJson.Parse(readBuffer.c_str()); - - if (!serverAddedJson.HasParseError() && serverAddedJson.IsObject()) - { - if (serverAddedJson.HasMember("id") && serverAddedJson["id"].IsString()) - { - strncpy(m_sOwnServerId, serverAddedJson["id"].GetString(), sizeof(m_sOwnServerId)); - m_sOwnServerId[sizeof(m_sOwnServerId) - 1] = 0; - } - - if (serverAddedJson.HasMember("serverAuthToken") && serverAddedJson["serverAuthToken"].IsString()) - { - strncpy( - m_sOwnServerAuthToken, - serverAddedJson["serverAuthToken"].GetString(), - sizeof(m_sOwnServerAuthToken)); - m_sOwnServerAuthToken[sizeof(m_sOwnServerAuthToken) - 1] = 0; - } - } - } - else - spdlog::warn("Heartbeat failed with error {}", curl_easy_strerror(result)); - - curl_easy_cleanup(curl); - Sleep(10000); - } while (*m_sOwnServerId); - }); - - heartbeatThread.detach(); + spdlog::info("Successfully registered the local server to the master server."); + return ReturnCleanup( + MasterServerReportPresenceResult::Success, + serverAddedJson["id"].GetString(), + serverAddedJson["serverAuthToken"].GetString()); } else { spdlog::error("Failed adding self to server list: error {}", curl_easy_strerror(result)); - m_bSuccessfullyConnected = false; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoConnect); } - - REQUEST_END_CLEANUP: - curl_easy_cleanup(curl); - curl_mime_free(mime); }); - - requestThread.detach(); } -void MasterServerManager::UpdateServerMapAndPlaylist(char* map, char* playlist, int maxPlayers) +void MasterServerPresenceReporter::InternalUpdateServer(const ServerPresence* pServerPresence) { - // dont call this if we don't have a server id - if (!*m_sOwnServerId) - return; + const ServerPresence threadedPresence(pServerPresence); - std::string strMap(map); - std::string strPlaylist(playlist); + // Never call this with an ongoing InternalUpdateServer() call. + assert(!updateServerFuture.valid()); - std::thread requestThread( - [this, strMap, strPlaylist, maxPlayers] + const std::string serverId = g_pMasterServerManager->m_sOwnServerId; + const std::string hostname = Cvar_ns_masterserver_hostname->GetString(); + const std::string modinfo = g_pMasterServerManager->m_sOwnModInfoJson; + + updateServerFuture = std::async( + std::launch::async, + [threadedPresence, serverId, hostname, modinfo] { CURL* curl = curl_easy_init(); SetCommonHttpClientOptions(curl); + // Lambda to quickly cleanup resources and return a value. + auto ReturnCleanup = [curl](MasterServerReportPresenceResult result, const char* id = "", const char* serverAuthToken = "") + { + curl_easy_cleanup(curl); + + MasterServerPresenceReporter::ReportPresenceResultData data; + data.result = result; + + if (id != nullptr) + { + data.id = id; + } + + if (serverAuthToken != nullptr) + { + data.serverAuthToken = serverAuthToken; + } + + return data; + }; + std::string readBuffer; curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); - // escape params + // 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* mapEscaped = curl_easy_escape(curl, strMap.c_str(), strMap.length()); - char* playlistEscaped = curl_easy_escape(curl, strPlaylist.c_str(), strPlaylist.length()); + char* nameEscaped = curl_easy_escape(curl, threadedPresence.m_sServerName.c_str(), NULL); + char* descEscaped = curl_easy_escape(curl, threadedPresence.m_sServerDesc.c_str(), NULL); + char* mapEscaped = curl_easy_escape(curl, threadedPresence.m_MapName, NULL); + char* playlistEscaped = curl_easy_escape(curl, threadedPresence.m_PlaylistName, NULL); + char* passwordEscaped = curl_easy_escape(curl, threadedPresence.m_Password, NULL); curl_easy_setopt( curl, CURLOPT_URL, fmt::format( - "{}/server/update_values?id={}&map={}&playlist={}&maxPlayers={}", - Cvar_ns_masterserver_hostname->GetString(), - m_sOwnServerId, + "{}/server/" + "update_values?id={}&port={}&authPort={}&name={}&description={}&map={}&playlist={}&playerCount={}&" + "maxPlayers={}&password={}", + hostname.c_str(), + serverId.c_str(), + threadedPresence.m_iPort, + threadedPresence.m_iAuthPort, + nameEscaped, + descEscaped, mapEscaped, playlistEscaped, - maxPlayers) + threadedPresence.m_iPlayerCount, + threadedPresence.m_iMaxPlayers, + passwordEscaped) .c_str()); + curl_free(nameEscaped); + curl_free(descEscaped); curl_free(mapEscaped); curl_free(playlistEscaped); + curl_free(passwordEscaped); } - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; - else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::UpdateServerPlayerCount(int playerCount) -{ - // dont call this if we don't have a server id - if (!*m_sOwnServerId) - return; - - std::thread requestThread( - [this, playerCount] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/server/update_values?id={}&playerCount={}", Cvar_ns_masterserver_hostname->GetString(), m_sOwnServerId, playerCount) - .c_str()); - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; - else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::WritePlayerPersistentData(char* playerId, char* pdata, size_t pdataSize) -{ - // still call this if we don't have a server id, since lobbies that aren't port forwarded need to be able to call it - m_bSavingPersistentData = true; - if (!pdataSize) - { - spdlog::warn("attempted to write pdata of size 0!"); - return; - } - - std::string strPlayerId(playerId); - std::string strPdata(pdata, pdataSize); - - std::thread requestThread( - [this, strPlayerId, strPdata, pdataSize] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/accounts/write_persistence?id={}&serverId={}", - Cvar_ns_masterserver_hostname->GetString(), - strPlayerId, - m_sOwnServerId) - .c_str()); - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_mime* mime = curl_mime_init(curl); curl_mimepart* part = curl_mime_addpart(mime); - curl_mime_data(part, strPdata.c_str(), pdataSize); - curl_mime_name(part, "pdata"); - curl_mime_filename(part, "file.pdata"); - curl_mime_type(part, "application/octet-stream"); + curl_mime_data(part, modinfo.c_str(), modinfo.size()); + curl_mime_name(part, "modinfo"); + curl_mime_filename(part, "modinfo.json"); + curl_mime_type(part, "application/json"); curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); CURLcode result = curl_easy_perform(curl); if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; - else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); - - m_bSavingPersistentData = false; - }); - - requestThread.detach(); -} - -void MasterServerManager::RemoveSelfFromServerList() -{ - // dont call this if we don't have a server id - if (!*m_sOwnServerId || !Cvar_ns_report_server_to_masterserver->GetBool()) - return; + { + rapidjson_document serverAddedJson; + serverAddedJson.Parse(readBuffer.c_str()); - std::thread requestThread( - [this] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); + const char* updatedId = nullptr; + const char* updatedAuthToken = nullptr; - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format("{}/server/remove_server?id={}", Cvar_ns_masterserver_hostname->GetString(), m_sOwnServerId).c_str()); + if (!serverAddedJson.HasParseError() && serverAddedJson.IsObject()) + { + if (serverAddedJson.HasMember("id") && serverAddedJson["id"].IsString()) + { + updatedId = serverAddedJson["id"].GetString(); + } - *m_sOwnServerId = 0; - CURLcode result = curl_easy_perform(curl); + if (serverAddedJson.HasMember("serverAuthToken") && serverAddedJson["serverAuthToken"].IsString()) + { + updatedAuthToken = serverAddedJson["serverAuthToken"].GetString(); + } + } - if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; + return ReturnCleanup(MasterServerReportPresenceResult::Success, updatedId, updatedAuthToken); + } else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); + { + spdlog::warn("Heartbeat failed with error {}", curl_easy_strerror(result)); + return ReturnCleanup(MasterServerReportPresenceResult::Failed); + } }); - - requestThread.detach(); -} - -void ConCommand_ns_fetchservers(const CCommand& args) -{ - g_MasterServerManager->RequestServerList(); -} - -void CHostState__State_NewGameHook(CHostState* hostState) -{ - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - - // need to do this to ensure we don't go to private match - if (g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame) - SetCurrentPlaylist("tdm"); - - // net_data_block_enabled is required for sp, force it if we're on an sp map - // sucks for security but just how it be - if (!strncmp(g_pHostState->m_levelName, "sp_", 3)) - { - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "net_data_block_enabled 1", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - } - - CHostState__State_NewGame(hostState); - - int maxPlayers = 6; - char* maxPlayersVar = GetCurrentPlaylistVar("max_players", false); - if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this - maxPlayers = std::stoi(maxPlayersVar); - - // Copy new server name cvar to source - Cvar_hostname->SetValue(Cvar_ns_server_name->GetString()); - // This calls the function that converts unicode strings from servername and serverdesc to UTF-8 - UpdateServerInfoFromUnicodeToUTF8(); - - g_MasterServerManager->AddSelfToServerList( - Cvar_hostport->GetInt(), - Cvar_ns_player_auth_port->GetInt(), - (char*)Cvar_ns_server_name->GetString(), - (char*)Cvar_ns_server_desc->GetString(), - hostState->m_levelName, - (char*)GetCurrentPlaylistName(), - maxPlayers, - (char*)Cvar_ns_server_password->GetString()); - g_ServerAuthenticationManager->StartPlayerAuthServer(); - g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = false; -} - -void CHostState__State_ChangeLevelMPHook(CHostState* hostState) -{ - int maxPlayers = 6; - char* maxPlayersVar = GetCurrentPlaylistVar("max_players", false); - if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this - maxPlayers = std::stoi(maxPlayersVar); - - // net_data_block_enabled is required for sp, force it if we're on an sp map - // sucks for security but just how it be - if (!strncmp(g_pHostState->m_levelName, "sp_", 3)) - { - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "net_data_block_enabled 1", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - } - - g_MasterServerManager->UpdateServerMapAndPlaylist(hostState->m_levelName, (char*)GetCurrentPlaylistName(), maxPlayers); - CHostState__State_ChangeLevelMP(hostState); -} - -void CHostState__State_ChangeLevelSPHook(CHostState* hostState) -{ - // is this even called? genuinely i don't think so - // from what i can tell, it's not called on mp=>sp change or sp=>sp change - // so idk it's fucked - - int maxPlayers = 6; - char* maxPlayersVar = GetCurrentPlaylistVar("max_players", false); - if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this - maxPlayers = std::stoi(maxPlayersVar); - - g_MasterServerManager->UpdateServerMapAndPlaylist(hostState->m_levelName, (char*)GetCurrentPlaylistName(), maxPlayers); - CHostState__State_ChangeLevelSP(hostState); -} - -void CHostState__State_GameShutdownHook(CHostState* hostState) -{ - g_MasterServerManager->RemoveSelfFromServerList(); - g_ServerAuthenticationManager->StopPlayerAuthServer(); - - CHostState__State_GameShutdown(hostState); -} - -MasterServerManager::MasterServerManager() : m_pendingConnectionInfo {}, m_sOwnServerId {""}, m_sOwnClientAuthToken {""} {} - -void InitialiseSharedMasterServer(HMODULE baseAddress) -{ - Cvar_ns_masterserver_hostname = new ConVar("ns_masterserver_hostname", "127.0.0.1", FCVAR_NONE, ""); - // unfortunately lib doesn't let us specify a port and still have https work - // Cvar_ns_masterserver_port = new ConVar("ns_masterserver_port", "8080", FCVAR_NONE, ""); - - Cvar_ns_server_name = new ConVar("ns_server_name", "Unnamed Northstar Server", FCVAR_GAMEDLL, ""); - Cvar_ns_server_desc = new ConVar("ns_server_desc", "Default server description", FCVAR_GAMEDLL, ""); - Cvar_ns_server_password = new ConVar("ns_server_password", "", FCVAR_GAMEDLL, ""); - Cvar_ns_report_server_to_masterserver = new ConVar("ns_report_server_to_masterserver", "1", FCVAR_GAMEDLL, ""); - Cvar_ns_report_sp_server_to_masterserver = new ConVar("ns_report_sp_server_to_masterserver", "0", FCVAR_GAMEDLL, ""); - - Cvar_ns_curl_log_enable = new ConVar("ns_curl_log_enable", "0", FCVAR_NONE, ""); - - Cvar_hostname = *(ConVar**)((char*)baseAddress + 0x1315bae8); - - g_MasterServerManager = new MasterServerManager; - - RegisterConCommand("ns_fetchservers", ConCommand_ns_fetchservers, "", FCVAR_CLIENTDLL); - - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x16E7D0, CHostState__State_NewGameHook, reinterpret_cast(&CHostState__State_NewGame)); - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x16E520, - CHostState__State_ChangeLevelMPHook, - reinterpret_cast(&CHostState__State_ChangeLevelMP)); - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x16E5D0, - CHostState__State_ChangeLevelSPHook, - reinterpret_cast(&CHostState__State_ChangeLevelSP)); - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x16E640, - CHostState__State_GameShutdownHook, - reinterpret_cast(&CHostState__State_GameShutdown)); } diff --git a/NorthstarDLL/masterserver.h b/NorthstarDLL/masterserver.h index 3d5f716c..0bbf7c76 100644 --- a/NorthstarDLL/masterserver.h +++ b/NorthstarDLL/masterserver.h @@ -1,8 +1,15 @@ #pragma once + #include "convar.h" +#include "serverpresence.h" #include #include #include +#include + +extern ConVar* Cvar_ns_masterserver_hostname; +extern ConVar* Cvar_ns_curl_log_enable; + struct RemoteModInfo { public: @@ -82,8 +89,6 @@ class MasterServerManager char m_sOwnClientAuthToken[33]; std::string m_sOwnModInfoJson; - std::string m_sUnicodeServerName; // Unicode unescaped version of Cvar_ns_auth_servername for support in cjk characters - std::string m_sUnicodeServerDesc; // Unicode unescaped version of Cvar_ns_auth_serverdesc for support in cjk characters bool m_bOriginAuthWithMasterServerDone = false; bool m_bOriginAuthWithMasterServerInProgress = false; @@ -97,8 +102,7 @@ class MasterServerManager bool m_bNewgameAfterSelfAuth = false; bool m_bScriptAuthenticatingWithGameServer = false; bool m_bSuccessfullyAuthenticatedWithGameServer = false; - - std::string s_authfail_reason {}; + std::string m_sAuthFailureReason {}; bool m_bHasPendingConnectionInfo = false; RemoteServerConnectionInfo m_pendingConnectionInfo; @@ -108,28 +112,76 @@ class MasterServerManager bool m_bHasMainMenuPromoData = false; MainMenuPromoData m_sMainMenuPromoData; - private: - void SetCommonHttpClientOptions(CURL* curl); - public: MasterServerManager(); + void ClearServerList(); void RequestServerList(); void RequestMainMenuPromos(); - void AuthenticateOriginWithMasterServer(char* uid, char* originToken); - void AuthenticateWithOwnServer(char* uid, char* playerToken); - void AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password); - void - AddSelfToServerList(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password); - void UpdateServerMapAndPlaylist(char* map, char* playlist, int playerCount); - void UpdateServerPlayerCount(int playerCount); - void WritePlayerPersistentData(char* playerId, char* pdata, size_t pdataSize); - void RemoveSelfFromServerList(); + void AuthenticateOriginWithMasterServer(const char* uid, const char* originToken); + void AuthenticateWithOwnServer(const char* uid, const char* playerToken); + void AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password); + void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize); }; -std::string unescape_unicode(const std::string& str); -void UpdateServerInfoFromUnicodeToUTF8(); -void InitialiseSharedMasterServer(HMODULE baseAddress); -extern MasterServerManager* g_MasterServerManager; +extern MasterServerManager* g_pMasterServerManager; extern ConVar* Cvar_ns_masterserver_hostname; -extern ConVar* Cvar_ns_server_password; + +/** Result returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */ +enum class MasterServerReportPresenceResult +{ + // Adding this server to the MS was successful. + Success, + // We failed to add this server to the MS and should retry. + Failed, + // We failed to add this server to the MS and shouldn't retry. + FailedNoRetry, + // We failed to even reach the MS. + FailedNoConnect, + // We failed to add the server because an existing server with the same ip:port exists. + FailedDuplicateServer, +}; + +class MasterServerPresenceReporter : public ServerPresenceReporter +{ + public: + /** Full data returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */ + struct ReportPresenceResultData + { + MasterServerReportPresenceResult result; + + std::optional id; + std::optional serverAuthToken; + }; + + const int MAX_REGISTRATION_ATTEMPTS = 5; + + // Called to initialise the master server presence reporter's state. + void CreatePresence(const ServerPresence* pServerPresence) override; + + // Run on an internal to either add the server to the MS or update it. + void ReportPresence(const ServerPresence* pServerPresence) override; + + // Called when we need to remove the server from the master server. + void DestroyPresence(const ServerPresence* pServerPresence) override; + + // Called every frame. + void RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) override; + + protected: + // Contains the async logic to add the server to the MS. + void InternalAddServer(const ServerPresence* pServerPresence); + + // Contains the async logic to update the server on the MS. + void InternalUpdateServer(const ServerPresence* pServerPresence); + + // The future used for InternalAddServer() calls. + std::future addServerFuture; + + // The future used for InternalAddServer() calls. + std::future updateServerFuture; + + int m_nNumRegistrationAttempts; + + double m_fNextAddServerAttemptTime; +}; diff --git a/NorthstarDLL/maxplayers.cpp b/NorthstarDLL/maxplayers.cpp index 2d513c71..ece8d14b 100644 --- a/NorthstarDLL/maxplayers.cpp +++ b/NorthstarDLL/maxplayers.cpp @@ -1,6 +1,8 @@ #include "pch.h" +#include "tier0.h" #include "maxplayers.h" -#include "gameutils.h" + +AUTOHOOK_INIT() // never set this to anything below 32 #define NEW_MAX_PLAYERS 64 @@ -45,50 +47,33 @@ constexpr int Team_PlayerArray_AddedLength = NEW_MAX_PLAYERS - 32; constexpr int Team_PlayerArray_AddedSize = PAD_NUMBER(Team_PlayerArray_AddedLength * 8, 4); constexpr int Team_AddedSize = Team_PlayerArray_AddedSize; -#include "nsmem.h" -template void ChangeOffset(void* addr, unsigned int offset) -{ - NSMem::BytePatch((uintptr_t)addr, (BYTE*)&offset, sizeof(T)); -} - -/* -typedef bool(*MatchRecvPropsToSendProps_R_Type)(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable); -MatchRecvPropsToSendProps_R_Type MatchRecvPropsToSendProps_R_Original; - -bool MatchRecvPropsToSendProps_R_Hook(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable) +bool MaxPlayersIncreaseEnabled() { - const char* tableName = *(const char**)(sendTable + 0x118); - - spdlog::info("MatchRecvPropsToSendProps_R table name {}", tableName); - - bool orig = MatchRecvPropsToSendProps_R_Original(lookup, tableNameBroken, sendTable, recvTable); - return orig; + static bool bMaxPlayersIncreaseEnabled = Tier0::CommandLine()->CheckParm("-experimentalmaxplayersincrease"); + return bMaxPlayersIncreaseEnabled; } -typedef bool(*DataTable_SetupReceiveTableFromSendTable_Type)(__int64 sendTable, bool needsDecoder); -DataTable_SetupReceiveTableFromSendTable_Type DataTable_SetupReceiveTableFromSendTable_Original; - -bool DataTable_SetupReceiveTableFromSendTable_Hook(__int64 sendTable, bool needsDecoder) +// should we use R2 for this? not sure +namespace R2 // use R2 namespace for game funcs { - const char* tableName = *(const char**)(sendTable + 0x118); + int GetMaxPlayers() + { + if (MaxPlayersIncreaseEnabled()) + return NEW_MAX_PLAYERS; - spdlog::info("DataTable_SetupReceiveTableFromSendTable table name {}", tableName); - if (!strcmp(tableName, "m_bConnected")) { - char f[64]; - sprintf_s(f, "%p", sendTable); - MessageBoxA(0, f, "DataTable_SetupReceiveTableFromSendTable", 0); + return 32; } +} // namespace R2 - return DataTable_SetupReceiveTableFromSendTable_Original(sendTable, needsDecoder); +template void ChangeOffset(MemoryAddress addr, unsigned int offset) +{ + addr.Patch((BYTE*)&offset, sizeof(T)); } -*/ -typedef void* (*StringTables_CreateStringTable_Type)( - __int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags); -StringTables_CreateStringTable_Type StringTables_CreateStringTable_Original; - -void* StringTables_CreateStringTable_Hook( - __int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags) +// clang-format off +AUTOHOOK(StringTables_CreateStringTable, engine.dll + 0x22E220, +void*,, (void* thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags)) +// clang-format on { // Change the amount of entries to account for a bigger player amount if (!strcmp(name, "userinfo")) @@ -100,36 +85,33 @@ void* StringTables_CreateStringTable_Hook( maxentries = maxPlayersPowerOf2; } - return StringTables_CreateStringTable_Original(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags); + return StringTables_CreateStringTable(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags); } -bool MaxPlayersIncreaseEnabled() -{ - return CommandLine() && CommandLine()->CheckParm("-experimentalmaxplayersincrease"); -} - -void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress) +ON_DLL_LOAD("engine.dll", MaxPlayersOverride_Engine, (CModule module)) { if (!MaxPlayersIncreaseEnabled()) return; + AUTOHOOK_DISPATCH_MODULE(engine.dll) + // patch GetPlayerLimits to ignore the boundary limit - ChangeOffset((char*)baseAddress + 0x116458, 0xEB); // jle => jmp + module.Offset(0x116458).Patch("0xEB"); // jle => jmp // patch ED_Alloc to change nFirstIndex - ChangeOffset((char*)baseAddress + 0x18F46C + 1, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1) + ChangeOffset(module.Offset(0x18F46C + 1), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1) // patch CGameServer::SpawnServer to change GetMaxClients inline - ChangeOffset((char*)baseAddress + 0x119543 + 2, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1) + ChangeOffset(module.Offset(0x119543 + 2), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1) // patch CGameServer::SpawnServer to change for loop - ChangeOffset((char*)baseAddress + 0x11957F + 2, NEW_MAX_PLAYERS); // original: 32 + ChangeOffset(module.Offset(0x11957F + 2), NEW_MAX_PLAYERS); // original: 32 // patch CGameServer::SpawnServer to change for loop (there are two) - ChangeOffset((char*)baseAddress + 0x119586 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1) + ChangeOffset(module.Offset(0x119586 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1) // patch max players somewhere in CClientState - ChangeOffset((char*)baseAddress + 0x1A162C + 2, NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1) + ChangeOffset(module.Offset(0x1A162C + 2), NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1) // patch max players in userinfo stringtable creation /*{ @@ -142,22 +124,10 @@ void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress) // proper fix below // patch max players in userinfo stringtable creation loop - ChangeOffset((char*)baseAddress + 0x114C48 + 2, NEW_MAX_PLAYERS); // original: 32 + ChangeOffset(module.Offset(0x114C48 + 2), NEW_MAX_PLAYERS); // original: 32 // do not load prebaked SendTable message list - ChangeOffset((char*)baseAddress + 0x75859, 0xEB); // jnz -> jmp - - HookEnabler hook; - - // ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x209000, &MatchRecvPropsToSendProps_R_Hook, - // reinterpret_cast(&MatchRecvPropsToSendProps_R_Original)); ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1FACD0, - // &DataTable_SetupReceiveTableFromSendTable_Hook, reinterpret_cast(&DataTable_SetupReceiveTableFromSendTable_Original)); - - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x22E220, - &StringTables_CreateStringTable_Hook, - reinterpret_cast(&StringTables_CreateStringTable_Original)); + module.Offset(0x75859).Patch("EB"); // jnz -> jmp } typedef void (*RunUserCmds_Type)(bool a1, float a2); @@ -167,7 +137,10 @@ HMODULE serverBase = 0; auto RandomIntZeroMax = (__int64(__fastcall*)())0; // lazy rebuild -void RunUserCmds_Hook(bool a1, float a2) +// clang-format off +AUTOHOOK(RunUserCmds, server.dll + 0x483D10, +void,, (bool a1, float a2)) +// clang-format on { unsigned char v3; // bl int v5; // er14 @@ -308,162 +281,160 @@ void RunUserCmds_Hook(bool a1, float a2) } } -typedef __int64 (*SendPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1); -SendPropArray2_Type SendPropArray2_Original; - -__int64 __fastcall SendPropArray2_Hook(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1) +// clang-format off +AUTOHOOK(SendPropArray2, server.dll + 0x12B130, +__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1)) +// clang-format on { // Change the amount of elements to account for a bigger player amount if (!strcmp(name, "\"player_array\"")) elements = NEW_MAX_PLAYERS; - return SendPropArray2_Original(recvProp, elements, flags, name, proxyFn, unk1); + return SendPropArray2(recvProp, elements, flags, name, proxyFn, unk1); } -void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress) +ON_DLL_LOAD("server.dll", MaxPlayersOverride_Server, (CModule module)) { if (!MaxPlayersIncreaseEnabled()) return; + AUTOHOOK_DISPATCH_MODULE(server.dll) + // get required data - serverBase = GetModuleHandleA("server.dll"); + serverBase = (HMODULE)module.m_nAddress; RandomIntZeroMax = (decltype(RandomIntZeroMax))(GetProcAddress(GetModuleHandleA("vstdlib.dll"), "RandomIntZeroMax")); // patch max players amount - ChangeOffset((char*)baseAddress + 0x9A44D + 3, NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128) + ChangeOffset(module.Offset(0x9A44D + 3), NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128) // patch SpawnGlobalNonRewinding to change forced edict index - ChangeOffset((char*)baseAddress + 0x2BC403 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1) + ChangeOffset(module.Offset(0x2BC403 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1) constexpr int CPlayerResource_OriginalSize = 4776; constexpr int CPlayerResource_AddedSize = PlayerResource_TotalSize; constexpr int CPlayerResource_ModifiedSize = CPlayerResource_OriginalSize + CPlayerResource_AddedSize; // CPlayerResource class allocation function - allocate a bigger amount to fit all new max player data - ChangeOffset((char*)baseAddress + 0x5C560A + 1, CPlayerResource_ModifiedSize); + ChangeOffset(module.Offset(0x5C560A + 1), CPlayerResource_ModifiedSize); // DT_PlayerResource::m_iPing SendProp - ChangeOffset((char*)baseAddress + 0x5C5059 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset((char*)baseAddress + 0x5C50A8 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset((char*)baseAddress + 0x5C50E2 + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x5C5059 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); + ChangeOffset(module.Offset(0x5C50A8 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); + ChangeOffset(module.Offset(0x5C50E2 + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_iPing DataMap - ChangeOffset((char*)baseAddress + 0xB94598, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset((char*)baseAddress + 0xB9459C, NEW_MAX_PLAYERS + 1); - ChangeOffset((char*)baseAddress + 0xB945C0, PlayerResource_Ping_Size); + ChangeOffset(module.Offset(0xB94598), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); + ChangeOffset(module.Offset(0xB9459C), NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xB945C0), PlayerResource_Ping_Size); // DT_PlayerResource::m_iTeam SendProp - ChangeOffset((char*)baseAddress + 0x5C5110 + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset((char*)baseAddress + 0x5C519C + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset((char*)baseAddress + 0x5C517E + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x5C5110 + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start); + ChangeOffset(module.Offset(0x5C519C + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start); + ChangeOffset(module.Offset(0x5C517E + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_iTeam DataMap - ChangeOffset((char*)baseAddress + 0xB94600, CPlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset((char*)baseAddress + 0xB94604, NEW_MAX_PLAYERS + 1); - ChangeOffset((char*)baseAddress + 0xB94628, PlayerResource_Team_Size); + ChangeOffset(module.Offset(0xB94600), CPlayerResource_OriginalSize + PlayerResource_Team_Start); + ChangeOffset(module.Offset(0xB94604), NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xB94628), PlayerResource_Team_Size); // DT_PlayerResource::m_iPRHealth SendProp - ChangeOffset((char*)baseAddress + 0x5C51C0 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset((char*)baseAddress + 0x5C5204 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset((char*)baseAddress + 0x5C523E + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x5C51C0 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); + ChangeOffset(module.Offset(0x5C5204 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); + ChangeOffset(module.Offset(0x5C523E + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_iPRHealth DataMap - ChangeOffset((char*)baseAddress + 0xB94668, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset((char*)baseAddress + 0xB9466C, NEW_MAX_PLAYERS + 1); - ChangeOffset((char*)baseAddress + 0xB94690, PlayerResource_PRHealth_Size); + ChangeOffset(module.Offset(0xB94668), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); + ChangeOffset(module.Offset(0xB9466C), NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xB94690), PlayerResource_PRHealth_Size); // DT_PlayerResource::m_bConnected SendProp - ChangeOffset((char*)baseAddress + 0x5C526C + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset((char*)baseAddress + 0x5C52B4 + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset((char*)baseAddress + 0x5C52EE + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x5C526C + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x5C52B4 + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x5C52EE + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_bConnected DataMap - ChangeOffset((char*)baseAddress + 0xB946D0, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset((char*)baseAddress + 0xB946D4, NEW_MAX_PLAYERS + 1); - ChangeOffset((char*)baseAddress + 0xB946F8, PlayerResource_Connected_Size); + ChangeOffset(module.Offset(0xB946D0), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0xB946D4), NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xB946F8), PlayerResource_Connected_Size); // DT_PlayerResource::m_bAlive SendProp - ChangeOffset((char*)baseAddress + 0x5C5321 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset((char*)baseAddress + 0x5C5364 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset((char*)baseAddress + 0x5C539E + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x5C5321 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); + ChangeOffset(module.Offset(0x5C5364 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); + ChangeOffset(module.Offset(0x5C539E + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_bAlive DataMap - ChangeOffset((char*)baseAddress + 0xB94738, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset((char*)baseAddress + 0xB9473C, NEW_MAX_PLAYERS + 1); - ChangeOffset((char*)baseAddress + 0xB94760, PlayerResource_Alive_Size); + ChangeOffset(module.Offset(0xB94738), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); + ChangeOffset(module.Offset(0xB9473C), NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xB94760), PlayerResource_Alive_Size); // DT_PlayerResource::m_boolStats SendProp - ChangeOffset((char*)baseAddress + 0x5C53CC + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset((char*)baseAddress + 0x5C5414 + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset((char*)baseAddress + 0x5C544E + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x5C53CC + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); + ChangeOffset(module.Offset(0x5C5414 + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); + ChangeOffset(module.Offset(0x5C544E + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_boolStats DataMap - ChangeOffset((char*)baseAddress + 0xB947A0, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset((char*)baseAddress + 0xB947A4, NEW_MAX_PLAYERS + 1); - ChangeOffset((char*)baseAddress + 0xB947C8, PlayerResource_BoolStats_Size); + ChangeOffset(module.Offset(0xB947A0), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); + ChangeOffset(module.Offset(0xB947A4), NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xB947C8), PlayerResource_BoolStats_Size); // DT_PlayerResource::m_killStats SendProp - ChangeOffset((char*)baseAddress + 0x5C547C + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset((char*)baseAddress + 0x5C54E2 + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset((char*)baseAddress + 0x5C54FE + 4, PlayerResource_KillStats_Length); + ChangeOffset(module.Offset(0x5C547C + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); + ChangeOffset(module.Offset(0x5C54E2 + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); + ChangeOffset(module.Offset(0x5C54FE + 4), PlayerResource_KillStats_Length); // DT_PlayerResource::m_killStats DataMap - ChangeOffset((char*)baseAddress + 0xB94808, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset((char*)baseAddress + 0xB9480C, PlayerResource_KillStats_Length); - ChangeOffset((char*)baseAddress + 0xB94830, PlayerResource_KillStats_Size); + ChangeOffset(module.Offset(0xB94808), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); + ChangeOffset(module.Offset(0xB9480C), PlayerResource_KillStats_Length); + ChangeOffset(module.Offset(0xB94830), PlayerResource_KillStats_Size); // DT_PlayerResource::m_scoreStats SendProp - ChangeOffset((char*)baseAddress + 0x5C5528 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset((char*)baseAddress + 0x5C5576 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset((char*)baseAddress + 0x5C5584 + 4, PlayerResource_ScoreStats_Length); + ChangeOffset(module.Offset(0x5C5528 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); + ChangeOffset(module.Offset(0x5C5576 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); + ChangeOffset(module.Offset(0x5C5584 + 4), PlayerResource_ScoreStats_Length); // DT_PlayerResource::m_scoreStats DataMap - ChangeOffset((char*)baseAddress + 0xB94870, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset((char*)baseAddress + 0xB94874, PlayerResource_ScoreStats_Length); - ChangeOffset((char*)baseAddress + 0xB94898, PlayerResource_ScoreStats_Size); + ChangeOffset(module.Offset(0xB94870), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); + ChangeOffset(module.Offset(0xB94874), PlayerResource_ScoreStats_Length); + ChangeOffset(module.Offset(0xB94898), PlayerResource_ScoreStats_Size); // CPlayerResource::UpdatePlayerData - m_bConnected - ChangeOffset((char*)baseAddress + 0x5C66EE + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset((char*)baseAddress + 0x5C672E + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x5C66EE + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x5C672E + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); // CPlayerResource::UpdatePlayerData - m_iPing - ChangeOffset((char*)baseAddress + 0x5C6394 + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset((char*)baseAddress + 0x5C63DB + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); + ChangeOffset(module.Offset(0x5C6394 + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); + ChangeOffset(module.Offset(0x5C63DB + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start); // CPlayerResource::UpdatePlayerData - m_iTeam - ChangeOffset((char*)baseAddress + 0x5C63FD + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset((char*)baseAddress + 0x5C6442 + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start); + ChangeOffset(module.Offset(0x5C63FD + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start); + ChangeOffset(module.Offset(0x5C6442 + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start); // CPlayerResource::UpdatePlayerData - m_iPRHealth - ChangeOffset((char*)baseAddress + 0x5C645B + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset((char*)baseAddress + 0x5C64A0 + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); + ChangeOffset(module.Offset(0x5C645B + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); + ChangeOffset(module.Offset(0x5C64A0 + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); // CPlayerResource::UpdatePlayerData - m_bConnected - ChangeOffset((char*)baseAddress + 0x5C64AA + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset((char*)baseAddress + 0x5C64F0 + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x5C64AA + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x5C64F0 + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start); // CPlayerResource::UpdatePlayerData - m_bAlive - ChangeOffset((char*)baseAddress + 0x5C650A + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset((char*)baseAddress + 0x5C654F + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); + ChangeOffset(module.Offset(0x5C650A + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); + ChangeOffset(module.Offset(0x5C654F + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start); // CPlayerResource::UpdatePlayerData - m_boolStats - ChangeOffset((char*)baseAddress + 0x5C6557 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset((char*)baseAddress + 0x5C65A5 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); + ChangeOffset(module.Offset(0x5C6557 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); + ChangeOffset(module.Offset(0x5C65A5 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); // CPlayerResource::UpdatePlayerData - m_scoreStats - ChangeOffset((char*)baseAddress + 0x5C65C2 + 3, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset((char*)baseAddress + 0x5C65E3 + 4, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); + ChangeOffset(module.Offset(0x5C65C2 + 3), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); + ChangeOffset(module.Offset(0x5C65E3 + 4), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); // CPlayerResource::UpdatePlayerData - m_killStats - ChangeOffset((char*)baseAddress + 0x5C6654 + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset((char*)baseAddress + 0x5C665B + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - - // GameLoop::RunUserCmds - rebuild - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x483D10, &RunUserCmds_Hook, reinterpret_cast(&RunUserCmds_Original)); + ChangeOffset(module.Offset(0x5C6654 + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); + ChangeOffset(module.Offset(0x5C665B + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); - *(DWORD*)((char*)baseAddress + 0x14E7390) = 0; - auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x5C4FE0); + *module.Offset(0x14E7390).As() = 0; + auto DT_PlayerResource_Construct = module.Offset(0x5C4FE0).As<__int64(__fastcall*)()>(); DT_PlayerResource_Construct(); constexpr int CTeam_OriginalSize = 3336; @@ -471,191 +442,188 @@ void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress) constexpr int CTeam_ModifiedSize = CTeam_OriginalSize + CTeam_AddedSize; // CTeam class allocation function - allocate a bigger amount to fit all new team player data - ChangeOffset((char*)baseAddress + 0x23924A + 1, CTeam_ModifiedSize); + ChangeOffset(module.Offset(0x23924A + 1), CTeam_ModifiedSize); // CTeam::CTeam - increase memset length to clean newly allocated data - ChangeOffset((char*)baseAddress + 0x2395AE + 2, 256 + CTeam_AddedSize); - - // hook required to change the size of DT_Team::"player_array" - HookEnabler hook2; - ENABLER_CREATEHOOK(hook2, (char*)baseAddress + 0x12B130, &SendPropArray2_Hook, reinterpret_cast(&SendPropArray2_Original)); - hook2.~HookEnabler(); // force hook before calling construct function + ChangeOffset(module.Offset(0x2395AE + 2), 256 + CTeam_AddedSize); - *(DWORD*)((char*)baseAddress + 0xC945A0) = 0; - auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x238F50); + *module.Offset(0xC945A0).As() = 0; + auto DT_Team_Construct = module.Offset(0x238F50).As<__int64(__fastcall*)()>(); DT_Team_Construct(); } -typedef __int64 (*RecvPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn); -RecvPropArray2_Type RecvPropArray2_Original; - -__int64 __fastcall RecvPropArray2_Hook(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn) +// clang-format off +AUTOHOOK(RecvPropArray2, client.dll + 0x1CEDA0, +__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn)) +// clang-format on { // Change the amount of elements to account for a bigger player amount if (!strcmp(name, "\"player_array\"")) elements = NEW_MAX_PLAYERS; - return RecvPropArray2_Original(recvProp, elements, flags, name, proxyFn); + return RecvPropArray2(recvProp, elements, flags, name, proxyFn); } -void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress) +ON_DLL_LOAD("client.dll", MaxPlayersOverride_Client, (CModule module)) { if (!MaxPlayersIncreaseEnabled()) return; + AUTOHOOK_DISPATCH_MODULE(client.dll) + constexpr int C_PlayerResource_OriginalSize = 5768; constexpr int C_PlayerResource_AddedSize = PlayerResource_TotalSize; constexpr int C_PlayerResource_ModifiedSize = C_PlayerResource_OriginalSize + C_PlayerResource_AddedSize; // C_PlayerResource class allocation function - allocate a bigger amount to fit all new max player data - ChangeOffset((char*)baseAddress + 0x164C41 + 1, C_PlayerResource_ModifiedSize); + ChangeOffset(module.Offset(0x164C41 + 1), C_PlayerResource_ModifiedSize); // C_PlayerResource::C_PlayerResource - change loop end value - ChangeOffset((char*)baseAddress + 0x1640C4 + 2, NEW_MAX_PLAYERS - 32); + ChangeOffset(module.Offset(0x1640C4 + 2), NEW_MAX_PLAYERS - 32); // C_PlayerResource::C_PlayerResource - change m_szName address ChangeOffset( - (char*)baseAddress + 0x1640D0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class + module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class // C_PlayerResource::C_PlayerResource - change m_szName address ChangeOffset( - (char*)baseAddress + 0x1640D0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class + module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class // C_PlayerResource::C_PlayerResource - increase memset length to clean newly allocated data - ChangeOffset((char*)baseAddress + 0x1640D0 + 3, 2244 + C_PlayerResource_AddedSize); + ChangeOffset(module.Offset(0x1640D0 + 3), 2244 + C_PlayerResource_AddedSize); // C_PlayerResource::UpdatePlayerName - change m_szName address - ChangeOffset((char*)baseAddress + 0x16431F + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x16431F + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName - change m_szName address 1 - ChangeOffset((char*)baseAddress + 0x1645B1 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x1645B1 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName - change m_szName address 2 - ChangeOffset((char*)baseAddress + 0x1645C0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x1645C0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName - change m_szName address 3 - ChangeOffset((char*)baseAddress + 0x1645DD + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x1645DD + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName internal func - change m_szName address 1 - ChangeOffset((char*)baseAddress + 0x164B71 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x164B71 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName internal func - change m_szName address 2 - ChangeOffset((char*)baseAddress + 0x164B9B + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x164B9B + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName2 (?) - change m_szName address 1 - ChangeOffset((char*)baseAddress + 0x164641 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x164641 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName2 (?) - change m_szName address 2 - ChangeOffset((char*)baseAddress + 0x164650 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x164650 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName2 (?) - change m_szName address 3 - ChangeOffset((char*)baseAddress + 0x16466D + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x16466D + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 1 - ChangeOffset((char*)baseAddress + 0x164BA3 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x164BA3 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 2 - ChangeOffset((char*)baseAddress + 0x164BCE + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x164BCE + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 3 - ChangeOffset((char*)baseAddress + 0x164BE7 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0x164BE7 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // C_PlayerResource::m_szName - ChangeOffset((char*)baseAddress + 0xc350f8, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); - ChangeOffset((char*)baseAddress + 0xc350f8 + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xc350f8), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); + ChangeOffset(module.Offset(0xc350f8 + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource size - ChangeOffset((char*)baseAddress + 0x163415 + 6, C_PlayerResource_ModifiedSize); + ChangeOffset(module.Offset(0x163415 + 6), C_PlayerResource_ModifiedSize); // DT_PlayerResource::m_iPing RecvProp - ChangeOffset((char*)baseAddress + 0x163492 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset((char*)baseAddress + 0x1634D6 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset((char*)baseAddress + 0x163515 + 5, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x163492 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); + ChangeOffset(module.Offset(0x1634D6 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); + ChangeOffset(module.Offset(0x163515 + 5), NEW_MAX_PLAYERS + 1); // C_PlayerResource::m_iPing - ChangeOffset((char*)baseAddress + 0xc35170, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); - ChangeOffset((char*)baseAddress + 0xc35170 + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xc35170), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); + ChangeOffset(module.Offset(0xc35170 + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_iTeam RecvProp - ChangeOffset((char*)baseAddress + 0x163549 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset((char*)baseAddress + 0x1635C8 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset((char*)baseAddress + 0x1635AD + 5, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x163549 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start); + ChangeOffset(module.Offset(0x1635C8 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start); + ChangeOffset(module.Offset(0x1635AD + 5), NEW_MAX_PLAYERS + 1); // C_PlayerResource::m_iTeam - ChangeOffset((char*)baseAddress + 0xc351e8, C_PlayerResource_OriginalSize + PlayerResource_Team_Start); - ChangeOffset((char*)baseAddress + 0xc351e8 + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xc351e8), C_PlayerResource_OriginalSize + PlayerResource_Team_Start); + ChangeOffset(module.Offset(0xc351e8 + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_iPRHealth RecvProp - ChangeOffset((char*)baseAddress + 0x1635F9 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset((char*)baseAddress + 0x163625 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset((char*)baseAddress + 0x163675 + 5, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x1635F9 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); + ChangeOffset(module.Offset(0x163625 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); + ChangeOffset(module.Offset(0x163675 + 5), NEW_MAX_PLAYERS + 1); // C_PlayerResource::m_iPRHealth - ChangeOffset((char*)baseAddress + 0xc35260, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); - ChangeOffset((char*)baseAddress + 0xc35260 + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xc35260), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); + ChangeOffset(module.Offset(0xc35260 + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_bConnected RecvProp - ChangeOffset((char*)baseAddress + 0x1636A9 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset((char*)baseAddress + 0x1636D5 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset((char*)baseAddress + 0x163725 + 5, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x1636A9 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x1636D5 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x163725 + 5), NEW_MAX_PLAYERS + 1); // C_PlayerResource::m_bConnected - ChangeOffset((char*)baseAddress + 0xc352d8, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - ChangeOffset((char*)baseAddress + 0xc352d8 + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xc352d8), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0xc352d8 + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_bAlive RecvProp - ChangeOffset((char*)baseAddress + 0x163759 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset((char*)baseAddress + 0x163785 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset((char*)baseAddress + 0x1637D5 + 5, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x163759 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); + ChangeOffset(module.Offset(0x163785 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); + ChangeOffset(module.Offset(0x1637D5 + 5), NEW_MAX_PLAYERS + 1); // C_PlayerResource::m_bAlive - ChangeOffset((char*)baseAddress + 0xc35350, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); - ChangeOffset((char*)baseAddress + 0xc35350 + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xc35350), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); + ChangeOffset(module.Offset(0xc35350 + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_boolStats RecvProp - ChangeOffset((char*)baseAddress + 0x163809 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset((char*)baseAddress + 0x163835 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset((char*)baseAddress + 0x163885 + 5, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0x163809 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); + ChangeOffset(module.Offset(0x163835 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); + ChangeOffset(module.Offset(0x163885 + 5), NEW_MAX_PLAYERS + 1); // C_PlayerResource::m_boolStats - ChangeOffset((char*)baseAddress + 0xc353c8, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); - ChangeOffset((char*)baseAddress + 0xc353c8 + 4, NEW_MAX_PLAYERS + 1); + ChangeOffset(module.Offset(0xc353c8), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); + ChangeOffset(module.Offset(0xc353c8 + 4), NEW_MAX_PLAYERS + 1); // DT_PlayerResource::m_killStats RecvProp - ChangeOffset((char*)baseAddress + 0x1638B3 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset((char*)baseAddress + 0x1638E5 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset((char*)baseAddress + 0x163935 + 5, PlayerResource_KillStats_Length); + ChangeOffset(module.Offset(0x1638B3 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); + ChangeOffset(module.Offset(0x1638E5 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); + ChangeOffset(module.Offset(0x163935 + 5), PlayerResource_KillStats_Length); // C_PlayerResource::m_killStats - ChangeOffset((char*)baseAddress + 0xc35440, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); - ChangeOffset((char*)baseAddress + 0xc35440 + 4, PlayerResource_KillStats_Length); + ChangeOffset(module.Offset(0xc35440), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); + ChangeOffset(module.Offset(0xc35440 + 4), PlayerResource_KillStats_Length); // DT_PlayerResource::m_scoreStats RecvProp - ChangeOffset((char*)baseAddress + 0x163969 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset((char*)baseAddress + 0x163995 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset((char*)baseAddress + 0x1639E5 + 5, PlayerResource_ScoreStats_Length); + ChangeOffset(module.Offset(0x163969 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); + ChangeOffset(module.Offset(0x163995 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); + ChangeOffset(module.Offset(0x1639E5 + 5), PlayerResource_ScoreStats_Length); // C_PlayerResource::m_scoreStats - ChangeOffset((char*)baseAddress + 0xc354b8, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); - ChangeOffset((char*)baseAddress + 0xc354b8 + 4, PlayerResource_ScoreStats_Length); + ChangeOffset(module.Offset(0xc354b8), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); + ChangeOffset(module.Offset(0xc354b8 + 4), PlayerResource_ScoreStats_Length); // C_PlayerResource::GetPlayerName - change m_bConnected address - ChangeOffset((char*)baseAddress + 0x164599 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x164599 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); // C_PlayerResource::GetPlayerName2 (?) - change m_bConnected address - ChangeOffset((char*)baseAddress + 0x164629 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x164629 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); // C_PlayerResource::GetPlayerName internal func - change m_bConnected address - ChangeOffset((char*)baseAddress + 0x164B13 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x164B13 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); // Some other get name func (that seems to be unused) - change m_bConnected address - ChangeOffset((char*)baseAddress + 0x164860 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x164860 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); // Some other get name func 2 (that seems to be unused too) - change m_bConnected address - ChangeOffset((char*)baseAddress + 0x164834 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); + ChangeOffset(module.Offset(0x164834 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); - *(DWORD*)((char*)baseAddress + 0xC35068) = 0; - auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x163400); + *module.Offset(0xC35068).As() = 0; + auto DT_PlayerResource_Construct = module.Offset(0x163400).As<__int64(__fastcall*)()>(); DT_PlayerResource_Construct(); constexpr int C_Team_OriginalSize = 3200; @@ -663,20 +631,15 @@ void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress) constexpr int C_Team_ModifiedSize = C_Team_OriginalSize + C_Team_AddedSize; // C_Team class allocation function - allocate a bigger amount to fit all new team player data - ChangeOffset((char*)baseAddress + 0x182321 + 1, C_Team_ModifiedSize); + ChangeOffset(module.Offset(0x182321 + 1), C_Team_ModifiedSize); // C_Team::C_Team - increase memset length to clean newly allocated data - ChangeOffset((char*)baseAddress + 0x1804A2 + 2, 256 + C_Team_AddedSize); + ChangeOffset(module.Offset(0x1804A2 + 2), 256 + C_Team_AddedSize); // DT_Team size - ChangeOffset((char*)baseAddress + 0xC3AA0C, C_Team_ModifiedSize); - - // hook required to change the size of DT_Team::"player_array" - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1CEDA0, &RecvPropArray2_Hook, reinterpret_cast(&RecvPropArray2_Original)); - hook.~HookEnabler(); // force hook before calling construct function + ChangeOffset(module.Offset(0xC3AA0C), C_Team_ModifiedSize); - *(DWORD*)((char*)baseAddress + 0xC3AFF8) = 0; - auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x17F950); + *module.Offset(0xC3AFF8).As() = 0; + auto DT_Team_Construct = module.Offset(0x17F950).As<__int64(__fastcall*)()>(); DT_Team_Construct(); } diff --git a/NorthstarDLL/maxplayers.h b/NorthstarDLL/maxplayers.h index 5ce8403a..b251f6a6 100644 --- a/NorthstarDLL/maxplayers.h +++ b/NorthstarDLL/maxplayers.h @@ -1,4 +1,7 @@ #pragma once -void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress); -void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress); -void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress); + +// should we use R2 for this? not sure +namespace R2 // use R2 namespace for game funcs +{ + int GetMaxPlayers(); +} // namespace R2 diff --git a/NorthstarDLL/memalloc.cpp b/NorthstarDLL/memalloc.cpp index e2240269..8c0fafc8 100644 --- a/NorthstarDLL/memalloc.cpp +++ b/NorthstarDLL/memalloc.cpp @@ -1,6 +1,8 @@ #include "pch.h" #include "memalloc.h" -#include "gameutils.h" +#include "tier0.h" + +using namespace Tier0; // TODO: rename to malloc and free after removing statically compiled .libs @@ -8,9 +10,8 @@ extern "C" void* _malloc_base(size_t n) { // allocate into static buffer if g_pMemAllocSingleton isn't initialised if (!g_pMemAllocSingleton) - { - InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll")); - } + TryCreateGlobalMemAlloc(); + return g_pMemAllocSingleton->m_vtable->Alloc(g_pMemAllocSingleton, n); } @@ -22,19 +23,16 @@ extern "C" void* _malloc_base(size_t n) extern "C" void _free_base(void* p) { if (!g_pMemAllocSingleton) - { - spdlog::warn("Trying to free something before g_pMemAllocSingleton was ready, this should never happen"); - InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll")); - } + TryCreateGlobalMemAlloc(); + g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p); } extern "C" void* _realloc_base(void* oldPtr, size_t size) { if (!g_pMemAllocSingleton) - { - InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll")); - } + TryCreateGlobalMemAlloc(); + return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size); } diff --git a/NorthstarDLL/memory.cpp b/NorthstarDLL/memory.cpp new file mode 100644 index 00000000..2e994fb2 --- /dev/null +++ b/NorthstarDLL/memory.cpp @@ -0,0 +1,348 @@ +#include "pch.h" +#include "memory.h" + +MemoryAddress::MemoryAddress() : m_nAddress(0) {} +MemoryAddress::MemoryAddress(const uintptr_t nAddress) : m_nAddress(nAddress) {} +MemoryAddress::MemoryAddress(const void* pAddress) : m_nAddress(reinterpret_cast(pAddress)) {} + +// operators +MemoryAddress::operator uintptr_t() const +{ + return m_nAddress; +} + +MemoryAddress::operator void*() const +{ + return reinterpret_cast(m_nAddress); +} + +MemoryAddress::operator bool() const +{ + return m_nAddress != 0; +} + +bool MemoryAddress::operator==(const MemoryAddress& other) const +{ + return m_nAddress == other.m_nAddress; +} + +bool MemoryAddress::operator!=(const MemoryAddress& other) const +{ + return m_nAddress != other.m_nAddress; +} + +bool MemoryAddress::operator==(const uintptr_t& addr) const +{ + return m_nAddress == addr; +} + +bool MemoryAddress::operator!=(const uintptr_t& addr) const +{ + return m_nAddress != addr; +} + +MemoryAddress MemoryAddress::operator+(const MemoryAddress& other) const +{ + return Offset(other.m_nAddress); +} + +MemoryAddress MemoryAddress::operator-(const MemoryAddress& other) const +{ + return MemoryAddress(m_nAddress - other.m_nAddress); +} + +MemoryAddress MemoryAddress::operator+(const uintptr_t& addr) const +{ + return Offset(addr); +} + +MemoryAddress MemoryAddress::operator-(const uintptr_t& addr) const +{ + return MemoryAddress(m_nAddress - addr); +} + +MemoryAddress MemoryAddress::operator*() const +{ + return Deref(); +} + +// traversal +MemoryAddress MemoryAddress::Offset(const uintptr_t nOffset) const +{ + return MemoryAddress(m_nAddress + nOffset); +} + +MemoryAddress MemoryAddress::Deref(const int nNumDerefs) const +{ + uintptr_t ret = m_nAddress; + for (int i = 0; i < nNumDerefs; i++) + ret = *reinterpret_cast(ret); + + return MemoryAddress(ret); +} + +// patching +void MemoryAddress::Patch(const uint8_t* pBytes, const size_t nSize) +{ + if (nSize) + WriteProcessMemory(GetCurrentProcess(), reinterpret_cast(m_nAddress), pBytes, nSize, NULL); +} + +void MemoryAddress::Patch(const std::initializer_list bytes) +{ + uint8_t* pBytes = new uint8_t[bytes.size()]; + + int i = 0; + for (const uint8_t& byte : bytes) + pBytes[i++] = byte; + + Patch(pBytes, bytes.size()); + delete[] pBytes; +} + +inline std::vector HexBytesToString(const char* pHexString) +{ + std::vector ret; + + int size = strlen(pHexString); + for (int i = 0; i < size; i++) + { + // If this is a space character, ignore it + if (isspace(pHexString[i])) + continue; + + if (i < size - 1) + { + BYTE result = 0; + for (int j = 0; j < 2; j++) + { + int val = 0; + char c = *(pHexString + i + j); + if (c >= 'a') + { + val = c - 'a' + 0xA; + } + else if (c >= 'A') + { + val = c - 'A' + 0xA; + } + else if (isdigit(c)) + { + val = c - '0'; + } + else + { + assert(false, "Failed to parse invalid hex string."); + val = -1; + } + + result += (j == 0) ? val * 16 : val; + } + ret.push_back(result); + } + + i++; + } + + return ret; +} + +void MemoryAddress::Patch(const char* pBytes) +{ + std::vector vBytes = HexBytesToString(pBytes); + Patch(vBytes.data(), vBytes.size()); +} + +void MemoryAddress::NOP(const size_t nSize) +{ + uint8_t* pBytes = new uint8_t[nSize]; + + memset(pBytes, 0x90, nSize); + Patch(pBytes, nSize); + + delete[] pBytes; +} + +bool MemoryAddress::IsMemoryReadable(const size_t nSize) +{ + static SYSTEM_INFO sysInfo; + if (!sysInfo.dwPageSize) + GetSystemInfo(&sysInfo); + + MEMORY_BASIC_INFORMATION memInfo; + if (!VirtualQuery(reinterpret_cast(m_nAddress), &memInfo, sizeof(memInfo))) + return false; + + return memInfo.RegionSize >= nSize && memInfo.State & MEM_COMMIT && !(memInfo.Protect & PAGE_NOACCESS); +} + +CModule::CModule(const HMODULE pModule) +{ + MODULEINFO mInfo {0}; + + if (pModule && pModule != INVALID_HANDLE_VALUE) + GetModuleInformation(GetCurrentProcess(), pModule, &mInfo, sizeof(MODULEINFO)); + + m_nModuleSize = static_cast(mInfo.SizeOfImage); + m_pModuleBase = reinterpret_cast(mInfo.lpBaseOfDll); + m_nAddress = m_pModuleBase; + + if (!m_nModuleSize || !m_pModuleBase) + return; + + m_pDOSHeader = reinterpret_cast(m_pModuleBase); + m_pNTHeaders = reinterpret_cast(m_pModuleBase + m_pDOSHeader->e_lfanew); + + const IMAGE_SECTION_HEADER* hSection = IMAGE_FIRST_SECTION(m_pNTHeaders); // Get first image section. + + for (WORD i = 0; i < m_pNTHeaders->FileHeader.NumberOfSections; i++) // Loop through the sections. + { + const IMAGE_SECTION_HEADER& hCurrentSection = hSection[i]; // Get current section. + + ModuleSections_t moduleSection = ModuleSections_t( + std::string(reinterpret_cast(hCurrentSection.Name)), + static_cast(m_pModuleBase + hCurrentSection.VirtualAddress), + hCurrentSection.SizeOfRawData); + + if (!strcmp((const char*)hCurrentSection.Name, ".text")) + m_ExecutableCode = moduleSection; + else if (!strcmp((const char*)hCurrentSection.Name, ".pdata")) + m_ExceptionTable = moduleSection; + else if (!strcmp((const char*)hCurrentSection.Name, ".data")) + m_RunTimeData = moduleSection; + else if (!strcmp((const char*)hCurrentSection.Name, ".rdata")) + m_ReadOnlyData = moduleSection; + + m_vModuleSections.push_back(moduleSection); // Push back a struct with the section data. + } +} + +CModule::CModule(const char* pModuleName) : CModule(GetModuleHandleA(pModuleName)) {} + +MemoryAddress CModule::GetExport(const char* pExportName) +{ + return MemoryAddress(reinterpret_cast(GetProcAddress(reinterpret_cast(m_nAddress), pExportName))); +} + +MemoryAddress CModule::FindPattern(const uint8_t* pPattern, const char* pMask) +{ + if (!m_ExecutableCode.IsSectionValid()) + return MemoryAddress(); + + uint64_t nBase = static_cast(m_ExecutableCode.m_pSectionBase); + uint64_t nSize = static_cast(m_ExecutableCode.m_nSectionSize); + + const uint8_t* pData = reinterpret_cast(nBase); + const uint8_t* pEnd = pData + static_cast(nSize) - strlen(pMask); + + int nMasks[64]; // 64*16 = enough masks for 1024 bytes. + int iNumMasks = static_cast(ceil(static_cast(strlen(pMask)) / 16.f)); + + memset(nMasks, '\0', iNumMasks * sizeof(int)); + for (intptr_t i = 0; i < iNumMasks; ++i) + { + for (intptr_t j = strnlen(pMask + i * 16, 16) - 1; j >= 0; --j) + { + if (pMask[i * 16 + j] == 'x') + { + _bittestandset(reinterpret_cast(&nMasks[i]), j); + } + } + } + __m128i xmm1 = _mm_loadu_si128(reinterpret_cast(pPattern)); + __m128i xmm2, xmm3, msks; + for (; pData != pEnd; _mm_prefetch(reinterpret_cast(++pData + 64), _MM_HINT_NTA)) + { + if (pPattern[0] == pData[0]) + { + xmm2 = _mm_loadu_si128(reinterpret_cast(pData)); + msks = _mm_cmpeq_epi8(xmm1, xmm2); + if ((_mm_movemask_epi8(msks) & nMasks[0]) == nMasks[0]) + { + for (uintptr_t i = 1; i < static_cast(iNumMasks); ++i) + { + xmm2 = _mm_loadu_si128(reinterpret_cast((pData + i * 16))); + xmm3 = _mm_loadu_si128(reinterpret_cast((pPattern + i * 16))); + msks = _mm_cmpeq_epi8(xmm2, xmm3); + if ((_mm_movemask_epi8(msks) & nMasks[i]) == nMasks[i]) + { + if ((i + 1) == iNumMasks) + { + return MemoryAddress(const_cast(pData)); + } + } + else + goto CONTINUE; + } + + return MemoryAddress((&*(const_cast(pData)))); + } + } + + CONTINUE:; + } + + return MemoryAddress(); +} + +inline std::pair, std::string> MaskedBytesFromPattern(const char* pPatternString) +{ + std::vector vRet; + std::string sMask; + + int size = strlen(pPatternString); + for (int i = 0; i < size; i++) + { + // If this is a space character, ignore it + if (isspace(pPatternString[i])) + continue; + + if (pPatternString[i] == '?') + { + // Add a wildcard + vRet.push_back(0); + sMask.append("?"); + } + else if (i < size - 1) + { + BYTE result = 0; + for (int j = 0; j < 2; j++) + { + int val = 0; + char c = *(pPatternString + i + j); + if (c >= 'a') + { + val = c - 'a' + 0xA; + } + else if (c >= 'A') + { + val = c - 'A' + 0xA; + } + else if (isdigit(c)) + { + val = c - '0'; + } + else + { + assert(false, "Failed to parse invalid pattern string."); + val = -1; + } + + result += (j == 0) ? val * 16 : val; + } + + vRet.push_back(result); + sMask.append("x"); + } + + i++; + } + + return std::make_pair(vRet, sMask); +} + +MemoryAddress CModule::FindPattern(const char* pPattern) +{ + const auto pattern = MaskedBytesFromPattern(pPattern); + return FindPattern(pattern.first.data(), pattern.second.c_str()); +} diff --git a/NorthstarDLL/memory.h b/NorthstarDLL/memory.h new file mode 100644 index 00000000..38c76cb3 --- /dev/null +++ b/NorthstarDLL/memory.h @@ -0,0 +1,90 @@ +#pragma once + +class MemoryAddress +{ + public: + uintptr_t m_nAddress; + + public: + MemoryAddress(); + MemoryAddress(const uintptr_t nAddress); + MemoryAddress(const void* pAddress); + + // operators + operator uintptr_t() const; + operator void*() const; + operator bool() const; + + bool operator==(const MemoryAddress& other) const; + bool operator!=(const MemoryAddress& other) const; + bool operator==(const uintptr_t& addr) const; + bool operator!=(const uintptr_t& addr) const; + + MemoryAddress operator+(const MemoryAddress& other) const; + MemoryAddress operator-(const MemoryAddress& other) const; + MemoryAddress operator+(const uintptr_t& other) const; + MemoryAddress operator-(const uintptr_t& other) const; + MemoryAddress operator*() const; + + template T As() + { + return reinterpret_cast(m_nAddress); + } + + // traversal + MemoryAddress Offset(const uintptr_t nOffset) const; + MemoryAddress Deref(const int nNumDerefs = 1) const; + + // patching + void Patch(const uint8_t* pBytes, const size_t nSize); + void Patch(const std::initializer_list bytes); + void Patch(const char* pBytes); + void NOP(const size_t nSize); + + bool IsMemoryReadable(const size_t nSize); +}; + +// based on https://github.com/Mauler125/r5sdk/blob/master/r5dev/public/include/module.h +class CModule : public MemoryAddress +{ + public: + struct ModuleSections_t + { + ModuleSections_t(void) = default; + ModuleSections_t(const std::string& svSectionName, uintptr_t pSectionBase, size_t nSectionSize) + : m_svSectionName(svSectionName), m_pSectionBase(pSectionBase), m_nSectionSize(nSectionSize) + { + } + + bool IsSectionValid(void) const + { + return m_nSectionSize != 0; + } + + std::string m_svSectionName; // Name of section. + uintptr_t m_pSectionBase {}; // Start address of section. + size_t m_nSectionSize {}; // Size of section. + }; + + ModuleSections_t m_ExecutableCode; + ModuleSections_t m_ExceptionTable; + ModuleSections_t m_RunTimeData; + ModuleSections_t m_ReadOnlyData; + + private: + std::string m_svModuleName; + uintptr_t m_pModuleBase {}; + DWORD m_nModuleSize {}; + IMAGE_NT_HEADERS64* m_pNTHeaders = nullptr; + IMAGE_DOS_HEADER* m_pDOSHeader = nullptr; + std::vector m_vModuleSections; + + public: + CModule() = delete; // no default, we need a module name + CModule(const HMODULE pModule); + CModule(const char* pModuleName); + + MemoryAddress GetExport(const char* pExportName); + MemoryAddress FindPattern(const uint8_t* pPattern, const char* pMask); + MemoryAddress FindPattern(const char* pPattern); +}; diff --git a/NorthstarDLL/miscclientfixes.cpp b/NorthstarDLL/miscclientfixes.cpp deleted file mode 100644 index fc57d534..00000000 --- a/NorthstarDLL/miscclientfixes.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "pch.h" -#include "miscclientfixes.h" -#include "hookutils.h" -#include "dedicated.h" - -typedef void* (*CrashingWeaponActivityFuncType)(void* a1); -CrashingWeaponActivityFuncType CrashingWeaponActivityFunc0; -CrashingWeaponActivityFuncType CrashingWeaponActivityFunc1; - -void* CrashingWeaponActivityFunc0Hook(void* a1) -{ - // this return is safe, other functions that use this value seemingly dont care about it being null - if (!a1) - return 0; - - return CrashingWeaponActivityFunc0(a1); -} - -void* CrashingWeaponActivityFunc1Hook(void* a1) -{ - // this return is safe, other functions that use this value seemingly dont care about it being null - if (!a1) - return 0; - - return CrashingWeaponActivityFunc1(a1); -} - -void InitialiseMiscClientFixes(HMODULE baseAddress) -{ - if (IsDedicatedServer()) - return; - - HookEnabler hook; - - // these functions will occasionally pass a null pointer on respawn, unsure what causes this but seems easiest just to return null if - // null, which seems to work fine fucking sucks this has to be fixed like this but unsure what exactly causes this serverside, breaks - // vanilla compatibility to a degree tho will say i have about 0 clue what exactly these functions do, testing this it doesn't even seem - // like they do much of anything i can see tbh - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x5A92D0, &CrashingWeaponActivityFunc0Hook, reinterpret_cast(&CrashingWeaponActivityFunc0)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x5A9310, &CrashingWeaponActivityFunc1Hook, reinterpret_cast(&CrashingWeaponActivityFunc1)); - - // experimental: allow cl_extrapolate to be enabled without cheats - { - void* ptr = (char*)baseAddress + 0x275F9D9; - *((char*)ptr) = (char)0; - } -} diff --git a/NorthstarDLL/miscclientfixes.h b/NorthstarDLL/miscclientfixes.h deleted file mode 100644 index d2aeea25..00000000 --- a/NorthstarDLL/miscclientfixes.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -void InitialiseMiscClientFixes(HMODULE baseAddress); diff --git a/NorthstarDLL/misccommands.cpp b/NorthstarDLL/misccommands.cpp index a05435b7..ea832de6 100644 --- a/NorthstarDLL/misccommands.cpp +++ b/NorthstarDLL/misccommands.cpp @@ -1,60 +1,162 @@ #include "pch.h" #include "misccommands.h" #include "concommand.h" -#include "gameutils.h" +#include "playlist.h" +#include "r2engine.h" +#include "r2client.h" +#include "tier0.h" +#include "hoststate.h" #include "masterserver.h" +#include "modmanager.h" #include "serverauthentication.h" #include "squirrel.h" +void ConCommand_force_newgame(const CCommand& arg) +{ + if (arg.ArgC() < 2) + return; + + R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME; + strncpy(R2::g_pHostState->m_levelName, arg.Arg(1), sizeof(R2::g_pHostState->m_levelName)); +} + +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_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) +{ + if (g_pServerAuthentication->m_RemoteAuthenticationData.size()) + R2::g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str()); + + // weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this + if (g_pSquirrel->m_pSQVM) + { + 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? + R2::SetCurrentPlaylist("tdm"); + strcpy(R2::g_pHostState->m_levelName, "mp_lobby"); + R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME; + } +} + void AddMiscConCommands() { - MAKE_CONCMD( + RegisterConCommand( "force_newgame", + ConCommand_force_newgame, "forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME", - FCVAR_NONE, - [](const CCommand& arg) - { - if (arg.ArgC() < 2) - return; + FCVAR_NONE); - g_pHostState->m_iNextState = HS_NEW_GAME; - strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName)); - }); - - MAKE_CONCMD( + RegisterConCommand( "ns_start_reauth_and_leave_to_lobby", + ConCommand_ns_start_reauth_and_leave_to_lobby, "called by the server, used to reauth and return the player to lobby when leaving a game", - FCVAR_SERVER_CAN_EXECUTE, - [](const CCommand& arg) - { - // hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect - g_MasterServerManager->m_bNewgameAfterSelfAuth = true; - g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, g_MasterServerManager->m_sOwnClientAuthToken); - }); + FCVAR_SERVER_CAN_EXECUTE); // this is a concommand because we make a deferred call to it from another thread - MAKE_CONCMD( - "ns_end_reauth_and_leave_to_lobby", - "", - FCVAR_NONE, - [](const CCommand& arg) + RegisterConCommand("ns_end_reauth_and_leave_to_lobby", ConCommand_ns_end_reauth_and_leave_to_lobby, "", FCVAR_NONE); +} + +// fixes up various cvar flags to have more sane values +void FixupCvarFlags() +{ + if (Tier0::CommandLine()->CheckParm("-allowdevcvars")) + { + // strip hidden and devonly cvar flags + int iNumCvarsAltered = 0; + for (auto& pair : R2::g_pCVar->DumpToMap()) { - Cbuf_AddText( - Cbuf_GetCurrentPlayer(), - fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(), - cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - - // weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this - if (g_ClientSquirrelManager->sqvm) + // strip flags + int flags = pair.second->GetFlags(); + if (flags & FCVAR_DEVELOPMENTONLY) { - g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true; + flags &= ~FCVAR_DEVELOPMENTONLY; + iNumCvarsAltered++; + } - // this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta - // fucks things should maybe set this in HostState_NewGame? - SetCurrentPlaylist("tdm"); - strcpy(g_pHostState->m_levelName, "mp_lobby"); - g_pHostState->m_iNextState = HS_NEW_GAME; + if (flags & FCVAR_HIDDEN) + { + flags &= ~FCVAR_HIDDEN; + iNumCvarsAltered++; } - }); + + pair.second->m_nFlags = flags; + } + + spdlog::info("Removed {} hidden/devonly cvar flags", iNumCvarsAltered); + } + + // make all engine client commands FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS + // these are usually checked through CGameClient::IsEngineClientCommand, but we get more control over this if we just do it through + // cvar flags + const char** ppEngineClientCommands = CModule("engine.dll").Offset(0x7C5EF0).As(); + + int i = 0; + do + { + ConCommandBase* pCommand = R2::g_pCVar->FindCommandBase(ppEngineClientCommands[i]); + if (pCommand) // not all the commands in this array actually exist in respawn source + pCommand->m_nFlags |= FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS; + } while (ppEngineClientCommands[++i]); + + // array of cvars and the flags we want to add to them + const std::vector> CVAR_FIXUP_ADD_FLAGS = { + // system commands (i.e. necessary for proper functionality) + // servers need to be able to disconnect + {"disconnect", FCVAR_SERVER_CAN_EXECUTE}, + + // cheat commands + {"give", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"give_server", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"givecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"takecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + + {"switchclass", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"set", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"_setClassVarServer", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + + {"ent_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"ent_throw", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"ent_setname", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"ent_teleport", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"ent_remove", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"ent_remove_all", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"ent_fire", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + + {"particle_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"particle_recreate", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"particle_kill", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + + {"test_setteam", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"melee_lunge_ent", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}}; + + // array of cvars and the flags we want to remove from them + const std::vector> CVAR_FIXUP_REMOVE_FLAGS = { + // unsure how this command works, not even sure it's used on retail servers, deffo shouldn't be used on northstar + {"migrateme", FCVAR_SERVER_CAN_EXECUTE | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"recheck", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, // we don't need this on northstar servers, it's for communities + + // unsure how these work exactly (rpt system likely somewhat stripped?), removing anyway since they won't be used + {"rpt_client_enable", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"rpt_password", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}}; + + for (auto& fixup : CVAR_FIXUP_ADD_FLAGS) + { + ConCommandBase* command = R2::g_pCVar->FindCommandBase(std::get<0>(fixup)); + if (command) + command->m_nFlags |= std::get<1>(fixup); + } + + for (auto& fixup : CVAR_FIXUP_REMOVE_FLAGS) + { + ConCommandBase* command = R2::g_pCVar->FindCommandBase(std::get<0>(fixup)); + if (command) + command->m_nFlags &= ~std::get<1>(fixup); + } } diff --git a/NorthstarDLL/misccommands.h b/NorthstarDLL/misccommands.h index fbd41b32..07a07fb3 100644 --- a/NorthstarDLL/misccommands.h +++ b/NorthstarDLL/misccommands.h @@ -1,2 +1,3 @@ #pragma once void AddMiscConCommands(); +void FixupCvarFlags(); diff --git a/NorthstarDLL/miscserverfixes.cpp b/NorthstarDLL/miscserverfixes.cpp index 728df737..4feca505 100644 --- a/NorthstarDLL/miscserverfixes.cpp +++ b/NorthstarDLL/miscserverfixes.cpp @@ -1,26 +1,7 @@ #include "pch.h" -#include "miscserverfixes.h" -#include "hookutils.h" -#include "nsmem.h" - -void InitialiseMiscServerFixes(HMODULE baseAddress) +ON_DLL_LOAD("server.dll", MiscServerFixes, (CModule module)) { - uintptr_t ba = (uintptr_t)baseAddress; - - // ret at the start of the concommand GenerateObjFile as it can crash servers - { - NSMem::BytePatch(ba + 0x38D920, "C3"); - } - // 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"); - } + module.Offset(0x154A96).NOP(5); } diff --git a/NorthstarDLL/miscserverfixes.h b/NorthstarDLL/miscserverfixes.h deleted file mode 100644 index ea31da6e..00000000 --- a/NorthstarDLL/miscserverfixes.h +++ /dev/null @@ -1 +0,0 @@ -void InitialiseMiscServerFixes(HMODULE baseAddress); diff --git a/NorthstarDLL/miscserverscript.cpp b/NorthstarDLL/miscserverscript.cpp index e6ddd58b..ca2a5b61 100644 --- a/NorthstarDLL/miscserverscript.cpp +++ b/NorthstarDLL/miscserverscript.cpp @@ -1,76 +1,73 @@ #include "pch.h" -#include "miscserverscript.h" #include "squirrel.h" #include "masterserver.h" #include "serverauthentication.h" -#include "gameutils.h" #include "dedicated.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); +#include - return player; -} - -// void function NSEarlyWritePlayerIndexPersistenceForLeave( int playerIndex ) -SQRESULT SQ_EarlyWritePlayerIndexPersistenceForLeave(void* sqvm) +// void function NSEarlyWritePlayerPersistenceForLeave( entity player ) +SQRESULT SQ_EarlyWritePlayerPersistenceForLeave(HSquirrelVM* sqvm) { - int playerIndex = ServerSq_getinteger(sqvm, 1); - void* player = GetPlayerByIndex(playerIndex); + const R2::CBasePlayer* pPlayer = g_pSquirrel->getentity(sqvm, 1); + if (!pPlayer) + { + spdlog::warn("NSEarlyWritePlayerPersistenceForLeave got null player"); - if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(player)) + g_pSquirrel->pushbool(sqvm, false); + return SQRESULT_NOTNULL; + } + + R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex]; + if (g_pServerAuthentication->m_PlayerAuthenticationData.find(pClient) == g_pServerAuthentication->m_PlayerAuthenticationData.end()) { - ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str()); - return SQRESULT_ERROR; + g_pSquirrel->pushbool(sqvm, false); + return SQRESULT_NOTNULL; } - g_ServerAuthenticationManager->m_additionalPlayerData[player].needPersistenceWriteOnLeave = false; - g_ServerAuthenticationManager->WritePersistentData(player); + g_pServerAuthentication->m_PlayerAuthenticationData[pClient].needPersistenceWriteOnLeave = false; + g_pServerAuthentication->WritePersistentData(pClient); return SQRESULT_NULL; } // bool function NSIsWritingPlayerPersistence() -SQRESULT SQ_IsWritingPlayerPersistence(void* sqvm) +SQRESULT SQ_IsWritingPlayerPersistence(HSquirrelVM* sqvm) { - ServerSq_pushbool(sqvm, g_MasterServerManager->m_bSavingPersistentData); + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bSavingPersistentData); return SQRESULT_NOTNULL; } -// bool function NSIsPlayerIndexLocalPlayer( int playerIndex ) -SQRESULT SQ_IsPlayerIndexLocalPlayer(void* sqvm) +// bool function NSIsPlayerLocalPlayer( entity player ) +SQRESULT SQ_IsPlayerLocalPlayer(HSquirrelVM* sqvm) { - int playerIndex = ServerSq_getinteger(sqvm, 1); - void* player = GetPlayerByIndex(playerIndex); - if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(player)) + const R2::CBasePlayer* pPlayer = g_pSquirrel->getentity(sqvm, 1); + if (!pPlayer) { - ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str()); - return SQRESULT_ERROR; + spdlog::warn("NSIsPlayerLocalPlayer got null player"); + + g_pSquirrel->pushbool(sqvm, false); + return SQRESULT_NOTNULL; } - ServerSq_pushbool(sqvm, !strcmp(g_LocalPlayerUserID, (char*)player + 0xF500)); + R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex]; + g_pSquirrel->pushbool(sqvm, !strcmp(R2::g_pLocalPlayerUserID, pClient->m_UID)); return SQRESULT_NOTNULL; } // bool function NSIsDedicated() - -SQRESULT SQ_IsDedicated(void* sqvm) +SQRESULT SQ_IsDedicated(HSquirrelVM* sqvm) { - ServerSq_pushbool(sqvm, IsDedicatedServer()); + g_pSquirrel->pushbool(sqvm, IsDedicatedServer()); return SQRESULT_NOTNULL; } -void InitialiseMiscServerScriptCommand(HMODULE baseAddress) +ON_DLL_LOAD_RELIESON("server.dll", MiscServerScriptCommands, ServerSquirrel, (CModule module)) { - g_ServerSquirrelManager->AddFuncRegistration( - "void", "NSEarlyWritePlayerIndexPersistenceForLeave", "int playerIndex", "", SQ_EarlyWritePlayerIndexPersistenceForLeave); - g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence); - g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsPlayerIndexLocalPlayer", "int playerIndex", "", SQ_IsPlayerIndexLocalPlayer); - g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated); + g_pSquirrel->AddFuncRegistration( + "void", "NSEarlyWritePlayerPersistenceForLeave", "entity player", "", SQ_EarlyWritePlayerPersistenceForLeave); + g_pSquirrel->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence); + g_pSquirrel->AddFuncRegistration("bool", "NSIsPlayerLocalPlayer", "entity player", "", SQ_IsPlayerLocalPlayer); + g_pSquirrel->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated); } diff --git a/NorthstarDLL/miscserverscript.h b/NorthstarDLL/miscserverscript.h deleted file mode 100644 index d11e7ac5..00000000 --- a/NorthstarDLL/miscserverscript.h +++ /dev/null @@ -1,2 +0,0 @@ -void InitialiseMiscServerScriptCommand(HMODULE baseAddress); -void* GetPlayerByIndex(int playerIndex); diff --git a/NorthstarDLL/modlocalisation.cpp b/NorthstarDLL/modlocalisation.cpp index c8e76d63..b12f0a40 100644 --- a/NorthstarDLL/modlocalisation.cpp +++ b/NorthstarDLL/modlocalisation.cpp @@ -1,37 +1,35 @@ #include "pch.h" -#include "modlocalisation.h" -#include "hookutils.h" #include "modmanager.h" -typedef bool (*AddLocalisationFileType)(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown); -AddLocalisationFileType AddLocalisationFile; +AUTOHOOK_INIT() -bool loadModLocalisationFiles = true; - -bool AddLocalisationFileHook(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown) +// clang-format off +AUTOHOOK(AddLocalisationFile, localize.dll + 0x6D80, +bool, __fastcall, (void* pVguiLocalize, const char* path, const char* pathId, char unknown)) +// clang-format on { - bool ret = AddLocalisationFile(g_pVguiLocalize, path, pathId, unknown); + static bool bLoadModLocalisationFiles = true; + bool ret = AddLocalisationFile(pVguiLocalize, path, pathId, unknown); if (ret) spdlog::info("Loaded localisation file {} successfully", path); - if (!loadModLocalisationFiles) + if (!bLoadModLocalisationFiles) return ret; - loadModLocalisationFiles = false; + bLoadModLocalisationFiles = false; - for (Mod mod : g_ModManager->m_loadedMods) - if (mod.Enabled) + for (Mod mod : g_pModManager->m_LoadedMods) + if (mod.m_bEnabled) for (std::string& localisationFile : mod.LocalisationFiles) - AddLocalisationFile(g_pVguiLocalize, localisationFile.c_str(), pathId, unknown); + AddLocalisationFile(pVguiLocalize, localisationFile.c_str(), pathId, unknown); - loadModLocalisationFiles = true; + bLoadModLocalisationFiles = true; return ret; } -void InitialiseModLocalisation(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT("localize.dll", Localize, (CModule module)) { - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x6D80, AddLocalisationFileHook, reinterpret_cast(&AddLocalisationFile)); + AUTOHOOK_DISPATCH() } diff --git a/NorthstarDLL/modlocalisation.h b/NorthstarDLL/modlocalisation.h deleted file mode 100644 index 260affad..00000000 --- a/NorthstarDLL/modlocalisation.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseModLocalisation(HMODULE baseAddress); diff --git a/NorthstarDLL/modmanager.cpp b/NorthstarDLL/modmanager.cpp index 2223109f..05060902 100644 --- a/NorthstarDLL/modmanager.cpp +++ b/NorthstarDLL/modmanager.cpp @@ -4,6 +4,10 @@ #include "concommand.h" #include "audio.h" #include "masterserver.h" +#include "filesystem.h" +#include "rpakfilesystem.h" +#include "nsprefix.h" + #include "rapidjson/error/en.h" #include "rapidjson/document.h" #include "rapidjson/ostreamwrapper.h" @@ -13,17 +17,14 @@ #include #include #include -#include "filesystem.h" -#include "rpakfilesystem.h" -#include "nsprefix.h" -ModManager* g_ModManager; +ModManager* g_pModManager; Mod::Mod(fs::path modDir, char* jsonBuf) { - wasReadSuccessfully = false; + m_bWasReadSuccessfully = false; - ModDirectory = modDir; + m_ModDirectory = modDir; rapidjson_document modJson; modJson.Parse(jsonBuf); @@ -106,11 +107,55 @@ Mod::Mod(fs::path modDir, char* jsonBuf) else convar->HelpString = ""; - // todo: could possibly parse FCVAR names here instead, would be easier + convar->Flags = FCVAR_NONE; + if (convarObj.HasMember("Flags")) - convar->Flags = convarObj["Flags"].GetInt(); - else - convar->Flags = FCVAR_NONE; + { + // read raw integer flags + if (convarObj["Flags"].IsInt()) + convar->Flags = convarObj["Flags"].GetInt(); + else if (convarObj["Flags"].IsString()) + { + // parse cvar flags from string + // example string: ARCHIVE_PLAYERPROFILE | GAMEDLL + + std::string sFlags = convarObj["Flags"].GetString(); + sFlags += '|'; // add additional | so we register the last flag + std::string sCurrentFlag; + + for (int i = 0; i < sFlags.length(); i++) + { + if (isspace(sFlags[i])) + continue; + + // if we encounter a |, add current string as a flag + if (sFlags[i] == '|') + { + bool bHasFlags = false; + int iCurrentFlags; + + for (auto& flagPair : g_PrintCommandFlags) + { + if (!sCurrentFlag.compare(flagPair.second)) + { + iCurrentFlags = flagPair.first; + bHasFlags = true; + break; + } + } + + if (bHasFlags) + convar->Flags |= iCurrentFlags; + else + spdlog::warn("Mod ConVar {} has unknown flag {}", convar->Name, sCurrentFlag); + + sCurrentFlag = ""; + } + else + sCurrentFlag += sFlags[i]; + } + } + } ConVars.push_back(convar); } @@ -127,7 +172,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf) ModScript script; script.Path = scriptObj["Path"].GetString(); - script.RsonRunOn = scriptObj["RunOn"].GetString(); + script.RunOn = scriptObj["RunOn"].GetString(); if (scriptObj.HasMember("ServerCallback") && scriptObj["ServerCallback"].IsObject()) { @@ -206,7 +251,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf) } } - wasReadSuccessfully = true; + m_bWasReadSuccessfully = true; } ModManager::ModManager() @@ -223,7 +268,7 @@ ModManager::ModManager() void ModManager::LoadMods() { - if (m_hasLoadedMods) + if (m_bHasLoadedMods) UnloadMods(); std::vector modDirs; @@ -232,7 +277,7 @@ void ModManager::LoadMods() fs::remove_all(GetCompiledAssetsPath()); fs::create_directories(GetModFolderPath()); - DependencyConstants.clear(); + m_DependencyConstants.clear(); // read enabled mods cfg std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json"); @@ -244,10 +289,10 @@ void ModManager::LoadMods() enabledModsStringStream << (char)enabledModsStream.get(); enabledModsStream.close(); - m_enabledModsCfg.Parse( + m_EnabledModsCfg.Parse( enabledModsStringStream.str().c_str()); - m_hasEnabledModsCfg = m_enabledModsCfg.IsObject(); + m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject(); } // get mod directories @@ -277,41 +322,41 @@ void ModManager::LoadMods() for (auto& pair : mod.DependencyConstants) { - if (DependencyConstants.find(pair.first) != DependencyConstants.end() && DependencyConstants[pair.first] != pair.second) + if (m_DependencyConstants.find(pair.first) != m_DependencyConstants.end() && m_DependencyConstants[pair.first] != pair.second) { spdlog::error("Constant {} in mod {} already exists in another mod.", pair.first, mod.Name); - mod.wasReadSuccessfully = false; + mod.m_bWasReadSuccessfully = false; break; } - if (DependencyConstants.find(pair.first) == DependencyConstants.end()) - DependencyConstants.emplace(pair); + if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end()) + m_DependencyConstants.emplace(pair); } - if (m_hasEnabledModsCfg && m_enabledModsCfg.HasMember(mod.Name.c_str())) - mod.Enabled = m_enabledModsCfg[mod.Name.c_str()].IsTrue(); + if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str())) + mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue(); else - mod.Enabled = true; + mod.m_bEnabled = true; - if (mod.wasReadSuccessfully) + if (mod.m_bWasReadSuccessfully) { spdlog::info("Loaded mod {} successfully", mod.Name); - if (mod.Enabled) + if (mod.m_bEnabled) spdlog::info("Mod {} is enabled", mod.Name); else spdlog::info("Mod {} is disabled", mod.Name); - m_loadedMods.push_back(mod); + m_LoadedMods.push_back(mod); } else spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string()); } // sort by load prio, lowest-highest - std::sort(m_loadedMods.begin(), m_loadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; }); + std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; }); - for (Mod& mod : m_loadedMods) + for (Mod& mod : m_LoadedMods) { - if (!mod.Enabled) + if (!mod.m_bEnabled) continue; // register convars @@ -319,15 +364,15 @@ void ModManager::LoadMods() // preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this // causes us to leak memory on reload, but not much, potentially find a way to not do this at some point for (ModConVar* convar : mod.ConVars) - if (!g_pCVar->FindVar(convar->Name.c_str())) // make sure convar isn't registered yet, unsure if necessary but idk what - // behaviour is for defining same convar multiple times + if (!R2::g_pCVar->FindVar(convar->Name.c_str())) // make sure convar isn't registered yet, unsure if necessary but idk what + // behaviour is for defining same convar multiple times new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str()); // read vpk paths - if (fs::exists(mod.ModDirectory / "vpk")) + if (fs::exists(mod.m_ModDirectory / "vpk")) { // read vpk cfg - std::ifstream vpkJsonStream(mod.ModDirectory / "vpk/vpk.json"); + std::ifstream vpkJsonStream(mod.m_ModDirectory / "vpk/vpk.json"); std::stringstream vpkJsonStringStream; bool bUseVPKJson = false; @@ -345,7 +390,7 @@ void ModManager::LoadMods() bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject(); } - for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "vpk")) + for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "vpk")) { // a bunch of checks to make sure we're only adding dir vpks and their paths are good // note: the game will literally only load vpks with the english prefix @@ -356,25 +401,24 @@ void ModManager::LoadMods() std::string formattedPath = file.path().filename().string(); // this really fucking sucks but it'll work - std::string vpkName = - (file.path().parent_path() / formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3)).string(); + std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3); ModVPKEntry& modVpk = mod.Vpks.emplace_back(); modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() && dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue()); - modVpk.m_sVpkPath = vpkName; + modVpk.m_sVpkPath = (file.path().parent_path() / vpkName).string(); - if (m_hasLoadedMods && modVpk.m_bAutoLoad) - (*g_Filesystem)->m_vtable->MountVPK(*g_Filesystem, vpkName.c_str()); + if (m_bHasLoadedMods && modVpk.m_bAutoLoad) + (*R2::g_pFilesystem)->m_vtable->MountVPK(*R2::g_pFilesystem, vpkName.c_str()); } } } // read rpak paths - if (fs::exists(mod.ModDirectory / "paks")) + if (fs::exists(mod.m_ModDirectory / "paks")) { // read rpak cfg - std::ifstream rpakJsonStream(mod.ModDirectory / "paks/rpak.json"); + std::ifstream rpakJsonStream(mod.m_ModDirectory / "paks/rpak.json"); std::stringstream rpakJsonStringStream; bool bUseRpakJson = false; @@ -406,7 +450,7 @@ void ModManager::LoadMods() } } - for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "paks")) + for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "paks")) { // ensure we're only loading rpaks if (fs::is_regular_file(file) && file.path().extension() == ".rpak") @@ -417,39 +461,39 @@ void ModManager::LoadMods() modPak.m_bAutoLoad = !bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue()); + // postload things if (!bUseRpakJson || (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName))) - { modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString(); - } modPak.m_sPakName = pakName; // not using atm because we need to resolve path to rpak // if (m_hasLoadedMods && modPak.m_bAutoLoad) - // g_PakLoadManager->LoadPakAsync(pakName.c_str()); + // g_pPakLoadManager->LoadPakAsync(pakName.c_str()); } } } // read keyvalues paths - if (fs::exists(mod.ModDirectory / "keyvalues")) + if (fs::exists(mod.m_ModDirectory / "keyvalues")) { - for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "keyvalues")) + for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "keyvalues")) { if (fs::is_regular_file(file)) { - std::string kvStr = file.path().lexically_relative(mod.ModDirectory / "keyvalues").lexically_normal().string(); + std::string kvStr = + g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / "keyvalues")); mod.KeyValues.emplace(STR_HASH(kvStr), kvStr); } } } // read pdiff - if (fs::exists(mod.ModDirectory / "mod.pdiff")) + if (fs::exists(mod.m_ModDirectory / "mod.pdiff")) { - std::ifstream pdiffStream(mod.ModDirectory / "mod.pdiff"); + std::ifstream pdiffStream(mod.m_ModDirectory / "mod.pdiff"); if (!pdiffStream.fail()) { @@ -464,17 +508,17 @@ void ModManager::LoadMods() } // read bink video paths - if (fs::exists(mod.ModDirectory / "media")) + if (fs::exists(mod.m_ModDirectory / "media")) { - for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "media")) + for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "media")) if (fs::is_regular_file(file) && file.path().extension() == ".bik") mod.BinkVideos.push_back(file.path().filename().string()); } // try to load audio - if (fs::exists(mod.ModDirectory / "audio")) + if (fs::exists(mod.m_ModDirectory / "audio")) { - for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "audio")) + for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "audio")) { if (fs::is_regular_file(file) && file.path().extension().string() == ".json") { @@ -489,23 +533,23 @@ void ModManager::LoadMods() } // in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised - for (int64_t i = m_loadedMods.size() - 1; i > -1; i--) + for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--) { - if (!m_loadedMods[i].Enabled) + if (!m_LoadedMods[i].m_bEnabled) continue; - if (fs::exists(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR)) + if (fs::exists(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)) { - for (fs::directory_entry file : fs::recursive_directory_iterator(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR)) + for (fs::directory_entry file : fs::recursive_directory_iterator(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)) { - fs::path path = file.path().lexically_relative(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR).lexically_normal(); - - if (file.is_regular_file() && m_modFiles.find(path.string()) == m_modFiles.end()) + std::string path = + g_pModManager->NormaliseModFilePath(file.path().lexically_relative(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)); + if (file.is_regular_file() && m_ModFiles.find(path) == m_ModFiles.end()) { ModOverrideFile modFile; - modFile.owningMod = &m_loadedMods[i]; - modFile.path = path; - m_modFiles.insert(std::make_pair(path.string(), modFile)); + modFile.m_pOwningMod = &m_LoadedMods[i]; + modFile.m_Path = path; + m_ModFiles.insert(std::make_pair(path, modFile)); } } } @@ -517,9 +561,9 @@ void ModManager::LoadMods() modinfoDoc.AddMember("Mods", rapidjson_document::GenericValue(rapidjson::kArrayType), modinfoDoc.GetAllocator()); int currentModIndex = 0; - for (Mod& mod : m_loadedMods) + for (Mod& mod : m_LoadedMods) { - if (!mod.Enabled || (!mod.RequiredOnClient && !mod.Pdiff.size())) + if (!mod.m_bEnabled || (!mod.RequiredOnClient && !mod.Pdiff.size())) continue; modinfoDoc["Mods"].PushBack(rapidjson_document::GenericValue(rapidjson::kObjectType), modinfoDoc.GetAllocator()); @@ -535,27 +579,27 @@ 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_hasLoadedMods = true; + m_bHasLoadedMods = true; } void ModManager::UnloadMods() { // clean up stuff from mods before we unload - m_modFiles.clear(); + m_ModFiles.clear(); fs::remove_all(GetCompiledAssetsPath()); g_CustomAudioManager.ClearAudioOverrides(); - if (!m_hasEnabledModsCfg) - m_enabledModsCfg.SetObject(); + if (!m_bHasEnabledModsCfg) + m_EnabledModsCfg.SetObject(); - for (Mod& mod : m_loadedMods) + for (Mod& mod : m_LoadedMods) { // remove all built kvs for (std::pair kvPaths : mod.KeyValues) - fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.ModDirectory)); + fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.m_ModDirectory)); mod.KeyValues.clear(); @@ -563,27 +607,39 @@ void ModManager::UnloadMods() // should we be doing this here or should scripts be doing this manually? // main issue with doing this here is when we reload mods for connecting to a server, we write enabled mods, which isn't necessarily // what we wanna do - if (!m_enabledModsCfg.HasMember(mod.Name.c_str())) - m_enabledModsCfg.AddMember( + if (!m_EnabledModsCfg.HasMember(mod.Name.c_str())) + m_EnabledModsCfg.AddMember( rapidjson_document::StringRefType(mod.Name.c_str()), rapidjson_document::GenericValue(false), - m_enabledModsCfg.GetAllocator()); + m_EnabledModsCfg.GetAllocator()); - m_enabledModsCfg[mod.Name.c_str()].SetBool(mod.Enabled); + m_EnabledModsCfg[mod.Name.c_str()].SetBool(mod.m_bEnabled); } std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json"); rapidjson::OStreamWrapper writeStreamWrapper(writeStream); rapidjson::Writer writer(writeStreamWrapper); - m_enabledModsCfg.Accept(writer); + m_EnabledModsCfg.Accept(writer); // do we need to dealloc individual entries in m_loadedMods? idk, rework - m_loadedMods.clear(); + m_LoadedMods.clear(); +} + +std::string ModManager::NormaliseModFilePath(const fs::path path) +{ + std::string str = path.lexically_normal().string(); + + // force to lowercase + for (char& c : str) + if (c <= 'Z' && c >= 'A') + c = c - ('Z' - 'z'); + + return str; } void ModManager::CompileAssetsForFile(const char* filename) { - size_t fileHash = STR_HASH(fs::path(filename).lexically_normal().string()); + size_t fileHash = STR_HASH(NormaliseModFilePath(fs::path(filename))); if (fileHash == m_hScriptsRsonHash) BuildScriptsRson(); @@ -592,9 +648,9 @@ void ModManager::CompileAssetsForFile(const char* filename) else { // check if we should build keyvalues, depending on whether any of our mods have patch kvs for this file - for (Mod& mod : m_loadedMods) + for (Mod& mod : m_LoadedMods) { - if (!mod.Enabled) + if (!mod.m_bEnabled) continue; if (mod.KeyValues.find(fileHash) != mod.KeyValues.end()) @@ -608,14 +664,7 @@ void ModManager::CompileAssetsForFile(const char* filename) void ConCommand_reload_mods(const CCommand& args) { - g_ModManager->LoadMods(); -} - -void InitialiseModManager(HMODULE baseAddress) -{ - g_ModManager = new ModManager; - - RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE); + g_pModManager->LoadMods(); } fs::path GetModFolderPath() @@ -626,3 +675,10 @@ fs::path GetCompiledAssetsPath() { return fs::path(GetNorthstarPrefix() + COMPILED_ASSETS_SUFFIX); } + +ON_DLL_LOAD_RELIESON("engine.dll", ModManager, (ConCommand, MasterServer), (CModule module)) +{ + g_pModManager = new ModManager; + + RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE); +} diff --git a/NorthstarDLL/modmanager.h b/NorthstarDLL/modmanager.h index 83cc0e40..f622c675 100644 --- a/NorthstarDLL/modmanager.h +++ b/NorthstarDLL/modmanager.h @@ -1,14 +1,15 @@ #pragma once #include "convar.h" +#include "memalloc.h" +#include "squirrel.h" + +#include "rapidjson/document.h" #include #include #include -#include "rapidjson/document.h" -#include "memalloc.h" - -namespace fs = std::filesystem; const std::string MOD_FOLDER_SUFFIX = "/mods"; +const std::string REMOTE_MOD_FOLDER_SUFFIX = "/runtime/remote/mods"; const fs::path MOD_OVERRIDE_DIR = "mod"; const std::string COMPILED_ASSETS_SUFFIX = "/runtime/compiled"; @@ -24,9 +25,6 @@ struct ModConVar struct ModScriptCallback { public: - // would've liked to make it possible to hook arbitrary codecallbacks, but couldn't find a function that calls some ui ones - // std::string HookedCodeCallback; - ScriptContext Context; // called before the codecallback is executed @@ -39,7 +37,7 @@ struct ModScript { public: std::string Path; - std::string RsonRunOn; + std::string RunOn; std::vector Callbacks; }; @@ -64,8 +62,10 @@ class Mod { public: // runtime stuff - fs::path ModDirectory; - bool Enabled = true; + bool m_bEnabled = true; + bool m_bWasReadSuccessfully = false; + fs::path m_ModDirectory; + // bool m_bIsRemote; // mod.json stuff: @@ -100,13 +100,8 @@ class Mod std::vector Rpaks; std::unordered_map RpakAliases; // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite - // iterated over to create squirrel VM constants depending if a mod exists or not. - // this only exists because we cannot access g_ModManager whilst mods are being loaded for the first time for some reason. - std::unordered_map DependencyConstants; - - // other stuff - bool wasReadSuccessfully = false; + std::unordered_map DependencyConstants; public: Mod(fs::path modPath, char* jsonBuf); @@ -115,42 +110,40 @@ class Mod struct ModOverrideFile { public: - Mod* owningMod; - fs::path path; + Mod* m_pOwningMod; + fs::path m_Path; }; class ModManager { private: - bool m_hasLoadedMods = false; - bool m_hasEnabledModsCfg; - rapidjson_document m_enabledModsCfg; + bool m_bHasLoadedMods = false; + bool m_bHasEnabledModsCfg; + rapidjson_document m_EnabledModsCfg; // precalculated hashes size_t m_hScriptsRsonHash; size_t m_hPdefHash; public: - std::vector m_loadedMods; - std::unordered_map m_modFiles; - // iterated over to create squirrel VM constants depending if a mod exists or not. - // here because constants are global anyways. - std::unordered_map DependencyConstants; + std::vector m_LoadedMods; + std::unordered_map m_ModFiles; + std::unordered_map m_DependencyConstants; public: ModManager(); void LoadMods(); void UnloadMods(); + std::string NormaliseModFilePath(const fs::path path); void CompileAssetsForFile(const char* filename); - // compile asset type stuff, these are done in files under Mods/Compiled/ + // compile asset type stuff, these are done in files under runtime/compiled/ void BuildScriptsRson(); void TryBuildKeyValues(const char* filename); void BuildPdef(); }; -void InitialiseModManager(HMODULE baseAddress); fs::path GetModFolderPath(); fs::path GetCompiledAssetsPath(); -extern ModManager* g_ModManager; +extern ModManager* g_pModManager; diff --git a/NorthstarDLL/nsmem.h b/NorthstarDLL/nsmem.h deleted file mode 100644 index 9b1f9103..00000000 --- a/NorthstarDLL/nsmem.h +++ /dev/null @@ -1,193 +0,0 @@ -#pragma once -#include "pch.h" - -// KittenPopo's memory stuff, made for northstar (because I really can't handle working with northstar's original memory stuff tbh) - -#pragma region Pattern Scanning -namespace NSMem -{ - inline std::vector HexBytesToString(const char* str) - { - std::vector patternNums; - int size = strlen(str); - for (int i = 0; i < size; i++) - { - char c = str[i]; - - // If this is a space character, ignore it - if (c == ' ' || c == '\t') - continue; - - if (c == '?') - { - // Add a wildcard (-1) - patternNums.push_back(-1); - } - else if (i < size - 1) - { - BYTE result = 0; - for (int j = 0; j < 2; j++) - { - int val = 0; - char c = *(str + i + j); - if (c >= 'a') - { - val = c - 'a' + 0xA; - } - else if (c >= 'A') - { - val = c - 'A' + 0xA; - } - else if (isdigit(c)) - { - val = c - '0'; - } - else - { - assert(false); - val = -1; - } - - result += (j == 0) ? val * 16 : val; - } - patternNums.push_back(result); - } - - i++; - } - - return patternNums; - } - - inline void* PatternScan(void* module, const int* pattern, int patternSize, int offset) - { - if (!module) - return NULL; - - auto dosHeader = (PIMAGE_DOS_HEADER)module; - auto ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)module + dosHeader->e_lfanew); - - auto sizeOfImage = ntHeaders->OptionalHeader.SizeOfImage; - - auto scanBytes = (BYTE*)module; - - for (auto i = 0; i < sizeOfImage - patternSize; ++i) - { - bool found = true; - for (auto j = 0; j < patternSize; ++j) - { - if (scanBytes[i + j] != pattern[j] && pattern[j] != -1) - { - found = false; - break; - } - } - - if (found) - { - uintptr_t addressInt = (uintptr_t)(&scanBytes[i]) + offset; - return (uint8_t*)addressInt; - } - } - - return nullptr; - } - - inline void* PatternScan(const char* moduleName, const char* pattern, int offset = 0) - { - std::vector patternNums = HexBytesToString(pattern); - - return PatternScan(GetModuleHandleA(moduleName), &patternNums[0], patternNums.size(), offset); - } - - inline void BytePatch(uintptr_t address, const BYTE* vals, int size) - { - WriteProcessMemory(GetCurrentProcess(), (LPVOID)address, vals, size, NULL); - } - - inline void BytePatch(uintptr_t address, std::initializer_list vals) - { - std::vector bytes = vals; - if (!bytes.empty()) - BytePatch(address, &bytes[0], bytes.size()); - } - - inline void BytePatch(uintptr_t address, const char* bytesStr) - { - std::vector byteInts = HexBytesToString(bytesStr); - std::vector bytes; - for (int v : byteInts) - bytes.push_back(v); - - if (!bytes.empty()) - BytePatch(address, &bytes[0], bytes.size()); - } - - inline void NOP(uintptr_t address, int size) - { - BYTE* buf = (BYTE*)malloc(size); - memset(buf, 0x90, size); - BytePatch(address, buf, size); - free(buf); - } - - inline bool IsMemoryReadable(void* ptr, size_t size) - { - static SYSTEM_INFO sysInfo; - if (!sysInfo.dwPageSize) - GetSystemInfo(&sysInfo); // This should always be 4096 unless ur playing on NES or some shit but whatever - - MEMORY_BASIC_INFORMATION memInfo; - - if (!VirtualQuery(ptr, &memInfo, sizeof(memInfo))) - return false; - - if (memInfo.RegionSize < size) - return false; - - return (memInfo.State & MEM_COMMIT) && !(memInfo.Protect & PAGE_NOACCESS); - } -} // namespace NSMem - -#pragma region KHOOK -struct KHookPatternInfo -{ - const char *moduleName, *pattern; - int offset = 0; - - KHookPatternInfo(const char* moduleName, const char* pattern, int offset = 0) : moduleName(moduleName), pattern(pattern), offset(offset) - { - } -}; - -struct KHook -{ - KHookPatternInfo targetFunc; - void* targetFuncAddr; - void* hookFunc; - void** original; - - static inline std::vector _allHooks; - - KHook(KHookPatternInfo targetFunc, void* hookFunc, void** original) : targetFunc(targetFunc) - { - this->hookFunc = hookFunc; - this->original = original; - _allHooks.push_back(this); - } - - bool Setup() - { - targetFuncAddr = NSMem::PatternScan(targetFunc.moduleName, targetFunc.pattern, targetFunc.offset); - if (!targetFuncAddr) - return false; - - return (MH_CreateHook(targetFuncAddr, hookFunc, original) == MH_OK) && (MH_EnableHook(targetFuncAddr) == MH_OK); - } -}; -#define KHOOK(name, funcPatternInfo, returnType, convention, args) \ - returnType convention hk##name args; \ - auto o##name = (returnType(convention*) args)0; \ - KHook k##name = KHook(KHookPatternInfo funcPatternInfo, reinterpret_cast(&hk##name), (void**)&o##name); \ - returnType convention hk##name args -#pragma endregion diff --git a/NorthstarDLL/nsprefix.cpp b/NorthstarDLL/nsprefix.cpp index 4c165e05..64a9d1f1 100644 --- a/NorthstarDLL/nsprefix.cpp +++ b/NorthstarDLL/nsprefix.cpp @@ -1,13 +1,13 @@ -#include #include "pch.h" #include "nsprefix.h" +#include std::string GetNorthstarPrefix() { return NORTHSTAR_FOLDER_PREFIX; } -void parseConfigurables() +void InitialiseNorthstarPrefix() { char* clachar = strstr(GetCommandLineA(), "-profile="); if (clachar) diff --git a/NorthstarDLL/nsprefix.h b/NorthstarDLL/nsprefix.h index cc98e15e..9f3087fb 100644 --- a/NorthstarDLL/nsprefix.h +++ b/NorthstarDLL/nsprefix.h @@ -3,5 +3,5 @@ static std::string NORTHSTAR_FOLDER_PREFIX; +void InitialiseNorthstarPrefix(); std::string GetNorthstarPrefix(); -void parseConfigurables(); diff --git a/NorthstarDLL/pch.h b/NorthstarDLL/pch.h index de22b40f..e23d6a9b 100644 --- a/NorthstarDLL/pch.h +++ b/NorthstarDLL/pch.h @@ -8,8 +8,6 @@ #define _WINSOCK_DEPRECATED_NO_WARNINGS // temp because i'm very lazy and want to use inet_addr, remove later #define RAPIDJSON_HAS_STDSTRING 1 -// httplib ssl - // add headers that you want to pre-compile here #include "memalloc.h" @@ -20,11 +18,14 @@ #include #include +namespace fs = std::filesystem; + #include "logging.h" -#include "include/MinHook.h" +#include "MinHook.h" #include "spdlog/spdlog.h" #include "libcurl/include/curl/curl.h" -#include "hookutils.h" +#include "hooks.h" +#include "memory.h" template ReturnType CallVFunc(int index, void* thisPtr, Args... args) { diff --git a/NorthstarDLL/pdef.cpp b/NorthstarDLL/pdef.cpp index bc6ec64d..05fba710 100644 --- a/NorthstarDLL/pdef.cpp +++ b/NorthstarDLL/pdef.cpp @@ -1,8 +1,8 @@ #include "pch.h" #include "modmanager.h" #include "filesystem.h" -#include "hookutils.h" #include "pdef.h" + #include #include #include @@ -14,11 +14,11 @@ void ModManager::BuildPdef() fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX); fs::remove(MOD_PDEF_PATH); - std::string pdef = ReadVPKOriginalFile(VPK_PDEF_PATH); + std::string pdef = R2::ReadVPKOriginalFile(VPK_PDEF_PATH); - for (Mod& mod : m_loadedMods) + for (Mod& mod : m_LoadedMods) { - if (!mod.Enabled || !mod.Pdiff.size()) + if (!mod.m_bEnabled || !mod.Pdiff.size()) continue; // this code probably isn't going to be pretty lol @@ -107,11 +107,11 @@ void ModManager::BuildPdef() writeStream.close(); ModOverrideFile overrideFile; - overrideFile.owningMod = nullptr; - overrideFile.path = VPK_PDEF_PATH; + overrideFile.m_pOwningMod = nullptr; + overrideFile.m_Path = VPK_PDEF_PATH; - if (m_modFiles.find(VPK_PDEF_PATH) == m_modFiles.end()) - m_modFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile)); + if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end()) + m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile)); else - m_modFiles[VPK_PDEF_PATH] = overrideFile; + m_ModFiles[VPK_PDEF_PATH] = overrideFile; } diff --git a/NorthstarDLL/playlist.cpp b/NorthstarDLL/playlist.cpp index f3e17a32..4f34dbb1 100644 --- a/NorthstarDLL/playlist.cpp +++ b/NorthstarDLL/playlist.cpp @@ -1,106 +1,130 @@ #include "pch.h" #include "playlist.h" -#include "nsmem.h" #include "concommand.h" #include "convar.h" -#include "gameutils.h" -#include "hookutils.h" #include "squirrel.h" +#include "hoststate.h" +#include "serverpresence.h" -typedef char (*Onclc_SetPlaylistVarOverrideType)(void* a1, void* a2); -Onclc_SetPlaylistVarOverrideType Onclc_SetPlaylistVarOverride; +AUTOHOOK_INIT() -typedef int (*GetCurrentGamemodeMaxPlayersType)(); -GetCurrentGamemodeMaxPlayersType GetCurrentGamemodeMaxPlayers; - -// function type defined in gameutils.h -SetPlaylistVarOverrideType SetPlaylistVarOverrideOriginal; -GetCurrentPlaylistVarType GetCurrentPlaylistVarOriginal; +// use the R2 namespace for game funcs +namespace R2 +{ + const char* (*GetCurrentPlaylistName)(); + void (*SetCurrentPlaylist)(const char* pPlaylistName); + void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue); + const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides); +} // namespace R2 ConVar* Cvar_ns_use_clc_SetPlaylistVarOverride; -void ConCommand_playlist(const CCommand& args) +// clang-format off +AUTOHOOK(clc_SetPlaylistVarOverride__Process, engine.dll + 0x222180, +char, __fastcall, (void* a1, void* a2)) +// clang-format on { - if (args.ArgC() < 2) - return; + // 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; - SetCurrentPlaylist(args.Arg(1)); + return clc_SetPlaylistVarOverride__Process(a1, a2); } -void ConCommand_setplaylistvaroverride(const CCommand& args) +// clang-format off +AUTOHOOK(SetCurrentPlaylist, engine.dll + 0x18EB20, +bool, __fastcall, (const char* pPlaylistName)) +// clang-format on { - if (args.ArgC() < 3) + bool bSuccess = SetCurrentPlaylist(pPlaylistName); + + if (bSuccess) + { + spdlog::info("Set playlist to {}", R2::GetCurrentPlaylistName()); + g_pServerPresence->SetPlaylist(R2::GetCurrentPlaylistName()); + } + + return bSuccess; +} + +// clang-format off +AUTOHOOK(SetPlaylistVarOverride, engine.dll + 0x18ED00, +void, __fastcall, (const char* pVarName, const char* pValue)) +// clang-format on +{ + if (strlen(pValue) >= 64) return; - for (int i = 1; i < args.ArgC(); i += 2) - SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1)); + SetPlaylistVarOverride(pVarName, pValue); } -char Onclc_SetPlaylistVarOverrideHook(void* a1, void* a2) +// clang-format off +AUTOHOOK(GetCurrentPlaylistVar, engine.dll + 0x18C680, +const char*, __fastcall, (const char* pVarName, bool bUseOverrides)) +// clang-format on { - // the private_match playlist is the only situation where there should be any legitimate sending of this netmessage - // todo: check mp_lobby here too - if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(GetCurrentPlaylistName(), "private_match")) - return 1; + if (!bUseOverrides && !strcmp(pVarName, "max_players")) + bUseOverrides = true; - return Onclc_SetPlaylistVarOverride(a1, a2); + return GetCurrentPlaylistVar(pVarName, bUseOverrides); } -void SetPlaylistVarOverrideHook(const char* varName, const char* value) +// clang-format off +AUTOHOOK(GetCurrentGamemodeMaxPlayers, engine.dll + 0x18C430, +int, __fastcall, ()) +// clang-format on { - if (strlen(value) >= 64) - return; + const char* pMaxPlayers = R2::GetCurrentPlaylistVar("max_players", 0); + if (!pMaxPlayers) + return GetCurrentGamemodeMaxPlayers(); - SetPlaylistVarOverrideOriginal(varName, value); + int iMaxPlayers = atoi(pMaxPlayers); + return iMaxPlayers; } -char* GetCurrentPlaylistVarHook(const char* varName, bool useOverrides) +void ConCommand_playlist(const CCommand& args) { - if (!useOverrides && !strcmp(varName, "max_players")) - useOverrides = true; + if (args.ArgC() < 2) + return; - return GetCurrentPlaylistVarOriginal(varName, useOverrides); + R2::SetCurrentPlaylist(args.Arg(1)); } -int GetCurrentGamemodeMaxPlayersHook() +void ConCommand_setplaylistvaroverride(const CCommand& args) { - char* maxPlayersStr = GetCurrentPlaylistVar("max_players", 0); - if (!maxPlayersStr) - return GetCurrentGamemodeMaxPlayers(); + if (args.ArgC() < 3) + return; - int maxPlayers = atoi(maxPlayersStr); - return maxPlayers; + for (int i = 1; i < args.ArgC(); i += 2) + R2::SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1)); } -void InitialisePlaylistHooks(HMODULE baseAddress) +ON_DLL_LOAD_RELIESON("engine.dll", PlaylistHooks, (ConCommand, ConVar), (CModule module)) { + AUTOHOOK_DISPATCH() + + R2::GetCurrentPlaylistName = module.Offset(0x18C640).As(); + R2::SetCurrentPlaylist = module.Offset(0x18EB20).As(); + R2::SetPlaylistVarOverride = module.Offset(0x18ED00).As(); + R2::GetCurrentPlaylistVar = module.Offset(0x18C680).As(); + + // playlist is the name of the command on respawn servers, but we already use setplaylist so can't get rid of it + RegisterConCommand("playlist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE); RegisterConCommand("setplaylist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE); RegisterConCommand("setplaylistvaroverrides", ConCommand_setplaylistvaroverride, "sets a playlist var override", FCVAR_NONE); + // note: clc_SetPlaylistVarOverride is pretty insecure, since it allows for entirely arbitrary playlist var overrides to be sent to the - // server this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be + // server, this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be // disabled altogether, since the custom menus won't use it anyway this should only really be accepted if you want vanilla client // compatibility Cvar_ns_use_clc_SetPlaylistVarOverride = new ConVar( "ns_use_clc_SetPlaylistVarOverride", "0", FCVAR_GAMEDLL, "Whether the server should accept clc_SetPlaylistVarOverride messages"); - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x222180, &Onclc_SetPlaylistVarOverrideHook, reinterpret_cast(&Onclc_SetPlaylistVarOverride)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x18ED00, &SetPlaylistVarOverrideHook, reinterpret_cast(&SetPlaylistVarOverrideOriginal)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x18C680, &GetCurrentPlaylistVarHook, reinterpret_cast(&GetCurrentPlaylistVarOriginal)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x18C430, &GetCurrentGamemodeMaxPlayersHook, reinterpret_cast(&GetCurrentGamemodeMaxPlayers)); - - uintptr_t ba = (uintptr_t)baseAddress; - // patch to prevent clc_SetPlaylistVarOverride from being able to crash servers if we reach max overrides due to a call to Error (why is // this possible respawn, wtf) todo: add a warning for this - { - NSMem::BytePatch(ba + 0x18ED8D, "C3"); - } + module.Offset(0x18ED8D).Patch("C3"); // patch to allow setplaylistvaroverride to be called before map init on dedicated and private match launched through the game - NSMem::NOP(ba + 0x18ED17, 6); + module.Offset(0x18ED17).NOP(6); } diff --git a/NorthstarDLL/playlist.h b/NorthstarDLL/playlist.h index da7de02d..c77b37d9 100644 --- a/NorthstarDLL/playlist.h +++ b/NorthstarDLL/playlist.h @@ -1,2 +1,10 @@ #pragma once -void InitialisePlaylistHooks(HMODULE baseAddress); + +// use the R2 namespace for game funcs +namespace R2 +{ + extern const char* (*GetCurrentPlaylistName)(); + extern void (*SetCurrentPlaylist)(const char* pPlaylistName); + extern void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue); + extern const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides); +} // namespace R2 diff --git a/NorthstarDLL/plugins.cpp b/NorthstarDLL/plugins.cpp index 57701fcd..790439d1 100644 --- a/NorthstarDLL/plugins.cpp +++ b/NorthstarDLL/plugins.cpp @@ -1,9 +1,11 @@ #include "pch.h" #include "squirrel.h" #include "plugins.h" -#include #include "masterserver.h" #include "convar.h" +#include "serverpresence.h" + +#include #include /// @@ -109,31 +111,31 @@ void initGameState() } // string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading -SQRESULT SQ_UpdateGameStateUI(void* sqvm) +SQRESULT SQ_UpdateGameStateUI(HSquirrelVM* sqvm) { AcquireSRWLockExclusive(&gameStateLock); - gameState.map = ClientSq_getstring(sqvm, 1); - gameState.mapDisplayName = ClientSq_getstring(sqvm, 2); - gameState.playlist = ClientSq_getstring(sqvm, 3); - gameState.playlistDisplayName = ClientSq_getstring(sqvm, 4); - gameState.connected = ClientSq_getbool(sqvm, 5); - gameState.loading = ClientSq_getbool(sqvm, 6); + gameState.map = g_pSquirrel->getstring(sqvm, 1); + gameState.mapDisplayName = g_pSquirrel->getstring(sqvm, 2); + gameState.playlist = g_pSquirrel->getstring(sqvm, 3); + gameState.playlistDisplayName = g_pSquirrel->getstring(sqvm, 4); + gameState.connected = g_pSquirrel->getbool(sqvm, 5); + gameState.loading = g_pSquirrel->getbool(sqvm, 6); ReleaseSRWLockExclusive(&gameStateLock); return SQRESULT_NOTNULL; } // int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit -SQRESULT SQ_UpdateGameStateClient(void* sqvm) +SQRESULT SQ_UpdateGameStateClient(HSquirrelVM* sqvm) { AcquireSRWLockExclusive(&gameStateLock); AcquireSRWLockExclusive(&serverInfoLock); - gameState.players = ClientSq_getinteger(sqvm, 1); - serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 2); - gameState.ourScore = ClientSq_getinteger(sqvm, 3); - gameState.secondHighestScore = ClientSq_getinteger(sqvm, 4); - gameState.highestScore = ClientSq_getinteger(sqvm, 5); - serverInfo.roundBased = ClientSq_getbool(sqvm, 6); - serverInfo.scoreLimit = ClientSq_getbool(sqvm, 7); + gameState.players = g_pSquirrel->getinteger(sqvm, 1); + serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 2); + gameState.ourScore = g_pSquirrel->getinteger(sqvm, 3); + gameState.secondHighestScore = g_pSquirrel->getinteger(sqvm, 4); + gameState.highestScore = g_pSquirrel->getinteger(sqvm, 5); + serverInfo.roundBased = g_pSquirrel->getbool(sqvm, 6); + serverInfo.scoreLimit = g_pSquirrel->getbool(sqvm, 7); ReleaseSRWLockExclusive(&gameStateLock); ReleaseSRWLockExclusive(&serverInfoLock); return SQRESULT_NOTNULL; @@ -141,59 +143,59 @@ SQRESULT SQ_UpdateGameStateClient(void* sqvm) // string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string // playlistDisplayName -SQRESULT SQ_UpdateServerInfo(void* sqvm) +SQRESULT SQ_UpdateServerInfo(HSquirrelVM* sqvm) { AcquireSRWLockExclusive(&gameStateLock); AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = ClientSq_getstring(sqvm, 1); - serverInfo.name = ClientSq_getstring(sqvm, 2); - serverInfo.password = ClientSq_getstring(sqvm, 3); - gameState.players = ClientSq_getinteger(sqvm, 4); - serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 5); - gameState.map = ClientSq_getstring(sqvm, 6); - gameState.mapDisplayName = ClientSq_getstring(sqvm, 7); - gameState.playlist = ClientSq_getstring(sqvm, 8); - gameState.playlistDisplayName = ClientSq_getstring(sqvm, 9); + serverInfo.id = g_pSquirrel->getstring(sqvm, 1); + serverInfo.name = g_pSquirrel->getstring(sqvm, 2); + serverInfo.password = g_pSquirrel->getstring(sqvm, 3); + gameState.players = g_pSquirrel->getinteger(sqvm, 4); + serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 5); + gameState.map = g_pSquirrel->getstring(sqvm, 6); + gameState.mapDisplayName = g_pSquirrel->getstring(sqvm, 7); + gameState.playlist = g_pSquirrel->getstring(sqvm, 8); + gameState.playlistDisplayName = g_pSquirrel->getstring(sqvm, 9); ReleaseSRWLockExclusive(&gameStateLock); ReleaseSRWLockExclusive(&serverInfoLock); return SQRESULT_NOTNULL; } // int maxPlayers -SQRESULT SQ_UpdateServerInfoBetweenRounds(void* sqvm) +SQRESULT SQ_UpdateServerInfoBetweenRounds(HSquirrelVM* sqvm) { AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = ClientSq_getstring(sqvm, 1); - serverInfo.name = ClientSq_getstring(sqvm, 2); - serverInfo.password = ClientSq_getstring(sqvm, 3); - serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 4); + serverInfo.id = g_pSquirrel->getstring(sqvm, 1); + serverInfo.name = g_pSquirrel->getstring(sqvm, 2); + serverInfo.password = g_pSquirrel->getstring(sqvm, 3); + serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 4); ReleaseSRWLockExclusive(&serverInfoLock); return SQRESULT_NOTNULL; } // float timeInFuture -SQRESULT SQ_UpdateTimeInfo(void* sqvm) +SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm) { AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.endTime = ceil(ClientSq_getfloat(sqvm, 1)); + serverInfo.endTime = ceil(g_pSquirrel->getfloat(sqvm, 1)); ReleaseSRWLockExclusive(&serverInfoLock); return SQRESULT_NOTNULL; } // bool loading -SQRESULT SQ_SetConnected(void* sqvm) +SQRESULT SQ_SetConnected(HSquirrelVM* sqvm) { AcquireSRWLockExclusive(&gameStateLock); - gameState.loading = ClientSq_getbool(sqvm, 1); + gameState.loading = g_pSquirrel->getbool(sqvm, 1); ReleaseSRWLockExclusive(&gameStateLock); return SQRESULT_NOTNULL; } -SQRESULT SQ_UpdateListenServer(void* sqvm) +SQRESULT SQ_UpdateListenServer(HSquirrelVM* sqvm) { AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = g_MasterServerManager->m_sOwnServerId; - serverInfo.password = Cvar_ns_server_password->GetString(); + serverInfo.id = g_pMasterServerManager->m_sOwnServerId; + serverInfo.password = ""; // g_pServerPresence->Cvar_ns_server_password->GetString(); todo this fr ReleaseSRWLockExclusive(&serverInfoLock); return SQRESULT_NOTNULL; } @@ -384,26 +386,26 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var) return n; } -void InitialisePluginCommands(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", PluginCommands, ClientSquirrel, (CModule module)) { // i swear there's a way to make this not have be run in 2 contexts but i can't figure it out // some funcs i need are just not available in UI or CLIENT - if (g_UISquirrelManager && g_ClientSquirrelManager) + if (g_pSquirrel && g_pSquirrel) { - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "void", "NSUpdateGameStateUI", "string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading", "", SQ_UpdateGameStateUI); - g_ClientSquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "void", "NSUpdateGameStateClient", "int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit", "", SQ_UpdateGameStateClient); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "void", "NSUpdateServerInfo", "string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, " @@ -411,10 +413,10 @@ void InitialisePluginCommands(HMODULE baseAddress) "playlistDisplayName", "", SQ_UpdateServerInfo); - g_ClientSquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds); - g_ClientSquirrelManager->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo); - g_UISquirrelManager->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected); - g_UISquirrelManager->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer); + g_pSquirrel->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo); + g_pSquirrel->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected); + g_pSquirrel->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer); } } diff --git a/NorthstarDLL/plugins.h b/NorthstarDLL/plugins.h index 84f3073f..953801a2 100644 --- a/NorthstarDLL/plugins.h +++ b/NorthstarDLL/plugins.h @@ -15,5 +15,3 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var); void initGameState(); void* getPluginObject(PluginObject var); - -void InitialisePluginCommands(HMODULE baseAddress); diff --git a/NorthstarDLL/printcommand.h b/NorthstarDLL/printcommand.h new file mode 100644 index 00000000..6c3ef850 --- /dev/null +++ b/NorthstarDLL/printcommand.h @@ -0,0 +1,6 @@ +#pragma once +#include "concommand.h" + +void PrintCommandHelpDialogue(const ConCommandBase* command, const char* name); +void TryPrintCvarHelpForCommand(const char* pCommand); +void InitialiseCommandPrint(); diff --git a/NorthstarDLL/printcommands.cpp b/NorthstarDLL/printcommands.cpp new file mode 100644 index 00000000..90af1575 --- /dev/null +++ b/NorthstarDLL/printcommands.cpp @@ -0,0 +1,174 @@ +#include "pch.h" +#include "printcommand.h" +#include "convar.h" +#include "concommand.h" + +void PrintCommandHelpDialogue(const ConCommandBase* command, const char* name) +{ + if (!command) + { + spdlog::info("unknown command {}", name); + return; + } + + // temp because command->IsCommand does not currently work + ConVar* cvar = R2::g_pCVar->FindVar(command->m_pszName); + + // build string for flags if not FCVAR_NONE + std::string flagString; + if (command->GetFlags() != FCVAR_NONE) + { + flagString = "( "; + + for (auto& flagPair : g_PrintCommandFlags) + { + if (command->GetFlags() & flagPair.first) + { + // special case, slightly hacky: PRINTABLEONLY is for commands, GAMEDLL_FOR_REMOTE_CLIENTS is for concommands, both have the + // same value + if (flagPair.first == FCVAR_PRINTABLEONLY) + { + if (cvar && !strcmp(flagPair.second, "GAMEDLL_FOR_REMOTE_CLIENTS")) + continue; + + if (!cvar && !strcmp(flagPair.second, "PRINTABLEONLY")) + continue; + } + + flagString += flagPair.second; + flagString += " "; + } + } + + flagString += ") "; + } + + if (cvar) + spdlog::info("\"{}\" = \"{}\" {}- {}", cvar->GetBaseName(), cvar->GetString(), flagString, cvar->GetHelpText()); + else + spdlog::info("\"{}\" {} - {}", command->m_pszName, flagString, command->GetHelpText()); +} + +void TryPrintCvarHelpForCommand(const char* pCommand) +{ + // try to display help text for an inputted command string from the console + int pCommandLen = strlen(pCommand); + char* pCvarStr = new char[pCommandLen]; + strcpy(pCvarStr, pCommand); + + // trim whitespace from right + for (int i = pCommandLen - 1; i; i--) + { + if (isspace(pCvarStr[i])) + pCvarStr[i] = '\0'; + else + break; + } + + // check if we're inputting a cvar, but not setting it at all + ConVar* cvar = R2::g_pCVar->FindVar(pCvarStr); + if (cvar) + PrintCommandHelpDialogue(&cvar->m_ConCommandBase, pCvarStr); + + delete[] pCvarStr; +} + +void ConCommand_help(const CCommand& arg) +{ + if (arg.ArgC() < 2) + { + spdlog::info("Usage: help "); + return; + } + + PrintCommandHelpDialogue(R2::g_pCVar->FindCommandBase(arg.Arg(1)), arg.Arg(1)); +} + +void ConCommand_find(const CCommand& arg) +{ + if (arg.ArgC() < 2) + { + spdlog::info("Usage: find [...]"); + return; + } + + char pTempName[256]; + char pTempSearchTerm[256]; + + for (auto& map : R2::g_pCVar->DumpToMap()) + { + bool bPrintCommand = true; + for (int i = 0; i < arg.ArgC() - 1; i++) + { + // make lowercase to avoid case sensitivity + strncpy_s(pTempName, sizeof(pTempName), map.second->m_pszName, sizeof(pTempName) - 1); + strncpy_s(pTempSearchTerm, sizeof(pTempSearchTerm), arg.Arg(i + 1), sizeof(pTempSearchTerm) - 1); + + for (int i = 0; pTempName[i]; i++) + pTempName[i] = tolower(pTempName[i]); + + for (int i = 0; pTempSearchTerm[i]; i++) + pTempSearchTerm[i] = tolower(pTempSearchTerm[i]); + + if (!strstr(pTempName, pTempSearchTerm)) + { + bPrintCommand = false; + break; + } + } + + if (bPrintCommand) + PrintCommandHelpDialogue(map.second, map.second->m_pszName); + } +} + +void ConCommand_findflags(const CCommand& arg) +{ + if (arg.ArgC() < 2) + { + spdlog::info("Usage: findflags "); + for (auto& flagPair : g_PrintCommandFlags) + spdlog::info(" - {}", flagPair.second); + + return; + } + + // convert input flag to uppercase + char* upperFlag = new char[strlen(arg.Arg(1))]; + strcpy(upperFlag, arg.Arg(1)); + + for (int i = 0; upperFlag[i]; i++) + upperFlag[i] = toupper(upperFlag[i]); + + // resolve flag name => int flags + int resolvedFlag = FCVAR_NONE; + for (auto& flagPair : g_PrintCommandFlags) + { + if (!strcmp(flagPair.second, upperFlag)) + { + resolvedFlag |= flagPair.first; + break; + } + } + + // print cvars + for (auto& map : R2::g_pCVar->DumpToMap()) + { + if (map.second->m_nFlags & resolvedFlag) + PrintCommandHelpDialogue(map.second, map.second->m_pszName); + } + + delete[] upperFlag; +} + +void InitialiseCommandPrint() +{ + RegisterConCommand("find", ConCommand_find, "Find concommands with the specified string in their name/help text.", FCVAR_NONE); + RegisterConCommand("findflags", ConCommand_findflags, "Find concommands by flags.", FCVAR_NONE); + + // help is already a command, so we need to modify the preexisting command to use our func instead + // and clear the flags also + ConCommand* helpCommand = R2::g_pCVar->FindCommand("help"); + helpCommand->m_nFlags = FCVAR_NONE; + helpCommand->m_pCommandCallback = ConCommand_help; +} diff --git a/NorthstarDLL/printmaps.cpp b/NorthstarDLL/printmaps.cpp new file mode 100644 index 00000000..c378daad --- /dev/null +++ b/NorthstarDLL/printmaps.cpp @@ -0,0 +1,168 @@ +#include "pch.h" +#include "printmaps.h" +#include "convar.h" +#include "concommand.h" +#include "modmanager.h" +#include "tier0.h" +#include "r2engine.h" + +#include +#include + +AUTOHOOK_INIT() + +enum class MapSource_t +{ + VPK, + GAMEDIR, + MOD +}; + +const std::unordered_map PrintMapSource = { + {MapSource_t::VPK, "VPK"}, {MapSource_t::MOD, "MOD"}, {MapSource_t::GAMEDIR, "R2"}}; + +struct MapVPKInfo +{ + std::string name; + std::string parent; + MapSource_t source; +}; + +// our current list of maps in the game +std::vector vMapList; + +void RefreshMapList() +{ + vMapList.clear(); + + // get modded maps + // TODO: could probably check mod vpks to get mapnames from there too? + for (auto& modFilePair : g_pModManager->m_ModFiles) + { + ModOverrideFile file = modFilePair.second; + if (file.m_Path.extension() == ".bsp" && file.m_Path.parent_path().string() == "maps") // only allow mod maps actually in /maps atm + { + MapVPKInfo& map = vMapList.emplace_back(); + map.name = file.m_Path.stem().string(); + map.parent = file.m_pOwningMod->Name; + map.source = MapSource_t::MOD; + } + } + + // get maps in vpk + { + const int iNumRetailNonMapVpks = 1; + static const char* const ppRetailNonMapVpks[] = { + "englishclient_frontend.bsp.pak000_dir.vpk"}; // don't include mp_common here as it contains mp_lobby + + // matches directory vpks, and captures their map name in the first group + static const std::regex rVpkMapRegex("englishclient_([a-zA-Z_]+)\\.bsp\\.pak000_dir\\.vpk", std::regex::icase); + + for (fs::directory_entry file : fs::directory_iterator("./vpk")) + { + std::string pathString = file.path().filename().string(); + + bool bIsValidMapVpk = true; + for (int i = 0; i < iNumRetailNonMapVpks; i++) + { + if (!pathString.compare(ppRetailNonMapVpks[i])) + { + bIsValidMapVpk = false; + break; + } + } + + if (!bIsValidMapVpk) + continue; + + // run our map vpk regex on the filename + std::smatch match; + std::regex_match(pathString, match, rVpkMapRegex); + + if (match.length() < 2) + continue; + + std::string mapName = match[1].str(); + // special case: englishclient_mp_common contains mp_lobby, so hardcode the name here + if (mapName == "mp_common") + mapName = "mp_lobby"; + + MapVPKInfo& map = vMapList.emplace_back(); + map.name = mapName; + map.parent = pathString; + map.source = MapSource_t::VPK; + } + } + + // get maps in game dir + for (fs::directory_entry file : fs::directory_iterator(fmt::format("{}/maps", R2::g_pModName))) + { + if (file.path().extension() == ".bsp") + { + MapVPKInfo& map = vMapList.emplace_back(); + map.name = file.path().stem().string(); + map.parent = "R2"; + map.source = MapSource_t::GAMEDIR; + } + } +} + +// clang-format off +AUTOHOOK(_Host_Map_f_CompletionFunc, engine.dll + 0x161AE0, +int, __fastcall, (const char const* cmdname, const char const* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH])) +// clang-format on +{ + // don't update our map list often from this func, only refresh every 10 seconds so we avoid constantly reading fs + static double flLastAutocompleteRefresh = -999; + + if (flLastAutocompleteRefresh + 10.0 < Tier0::Plat_FloatTime()) + { + RefreshMapList(); + flLastAutocompleteRefresh = Tier0::Plat_FloatTime(); + } + + // use a custom autocomplete func for all map loading commands + const int cmdLength = strlen(cmdname); + const char* query = partial + cmdLength; + const int queryLength = strlen(query); + + int numMaps = 0; + for (int i = 0; i < vMapList.size() && numMaps < COMMAND_COMPLETION_MAXITEMS; i++) + { + if (!strncmp(query, vMapList[i].name.c_str(), queryLength)) + { + strcpy(commands[numMaps], cmdname); + strncpy_s( + commands[numMaps++] + cmdLength, + COMMAND_COMPLETION_ITEM_LENGTH, + &vMapList[i].name[0], + COMMAND_COMPLETION_ITEM_LENGTH - cmdLength); + } + } + + return numMaps; +} + +void ConCommand_maps(const CCommand& args) +{ + if (args.ArgC() < 2) + { + spdlog::info("Usage: maps "); + spdlog::info("maps * for full listing"); + return; + } + + RefreshMapList(); + + for (MapVPKInfo& map : vMapList) // need to figure out a nice way to include parent path without making the formatting awful + if ((*args.Arg(1) == '*' && !args.Arg(1)[1]) || strstr(map.name.c_str(), args.Arg(1))) + spdlog::info("({}) {}", PrintMapSource.at(map.source), map.name); +} + +void InitialiseMapsPrint() +{ + AUTOHOOK_DISPATCH() + + ConCommand* mapsCommand = R2::g_pCVar->FindCommand("maps"); + mapsCommand->m_pCommandCallback = ConCommand_maps; +} diff --git a/NorthstarDLL/printmaps.h b/NorthstarDLL/printmaps.h new file mode 100644 index 00000000..b01761c0 --- /dev/null +++ b/NorthstarDLL/printmaps.h @@ -0,0 +1,2 @@ +#pragma once +void InitialiseMapsPrint(); diff --git a/NorthstarDLL/r2client.cpp b/NorthstarDLL/r2client.cpp new file mode 100644 index 00000000..2e7bd564 --- /dev/null +++ b/NorthstarDLL/r2client.cpp @@ -0,0 +1,20 @@ +#include "pch.h" +#include "r2client.h" + +using namespace R2; + +// use the R2 namespace for game funcs +namespace R2 +{ + char* g_pLocalPlayerUserID; + char* g_pLocalPlayerOriginToken; + GetBaseLocalClientType GetBaseLocalClient; +} // namespace R2 + +ON_DLL_LOAD("engine.dll", R2EngineClient, (CModule module)) +{ + g_pLocalPlayerUserID = module.Offset(0x13F8E688).As(); + g_pLocalPlayerOriginToken = module.Offset(0x13979C80).As(); + + GetBaseLocalClient = module.Offset(0x78200).As(); +} diff --git a/NorthstarDLL/r2client.h b/NorthstarDLL/r2client.h new file mode 100644 index 00000000..64ed6c61 --- /dev/null +++ b/NorthstarDLL/r2client.h @@ -0,0 +1,11 @@ +#pragma once + +// use the R2 namespace for game funcs +namespace R2 +{ + extern char* g_pLocalPlayerUserID; + extern char* g_pLocalPlayerOriginToken; + + typedef void* (*GetBaseLocalClientType)(); + extern GetBaseLocalClientType GetBaseLocalClient; +} // namespace R2 diff --git a/NorthstarDLL/r2engine.cpp b/NorthstarDLL/r2engine.cpp new file mode 100644 index 00000000..11233a2d --- /dev/null +++ b/NorthstarDLL/r2engine.cpp @@ -0,0 +1,36 @@ +#include "pch.h" +#include "r2engine.h" + +using namespace R2; + +// use the R2 namespace for game funcs +namespace R2 +{ + Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; + Cbuf_AddTextType Cbuf_AddText; + Cbuf_ExecuteType Cbuf_Execute; + + CEngine* g_pEngine; + + void (*CBaseClient__Disconnect)(void* self, uint32_t unknownButAlways1, const char* reason, ...); + CBaseClient* g_pClientArray; + + server_state_t* g_pServerState; + + char* g_pModName = + nullptr; // we cant set this up here atm since we dont have an offset to it in engine, instead we store it in IsRespawnMod +} // namespace R2 + +ON_DLL_LOAD("engine.dll", R2Engine, (CModule module)) +{ + Cbuf_GetCurrentPlayer = module.Offset(0x120630).As(); + Cbuf_AddText = module.Offset(0x1203B0).As(); + Cbuf_Execute = module.Offset(0x1204B0).As(); + + g_pEngine = module.Offset(0x7D70C8).Deref().As(); + + CBaseClient__Disconnect = module.Offset(0x1012C0).As(); + g_pClientArray = module.Offset(0x12A53F90).As(); + + g_pServerState = module.Offset(0x12A53D48).As(); +} diff --git a/NorthstarDLL/r2engine.h b/NorthstarDLL/r2engine.h new file mode 100644 index 00000000..1d67e8c1 --- /dev/null +++ b/NorthstarDLL/r2engine.h @@ -0,0 +1,206 @@ +#pragma once + +// use the R2 namespace for game funcs +namespace R2 +{ + // Cbuf + enum class ECommandTarget_t + { + CBUF_FIRST_PLAYER = 0, + CBUF_LAST_PLAYER = 1, // MAX_SPLITSCREEN_CLIENTS - 1, MAX_SPLITSCREEN_CLIENTS = 2 + CBUF_SERVER = CBUF_LAST_PLAYER + 1, + + CBUF_COUNT, + }; + + enum class cmd_source_t + { + // Added to the console buffer by gameplay code. Generally unrestricted. + kCommandSrcCode, + + // Sent from code via engine->ClientCmd, which is restricted to commands visible + // via FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS. + kCommandSrcClientCmd, + + // Typed in at the console or via a user key-bind. Generally unrestricted, although + // the client will throttle commands sent to the server this way to 16 per second. + kCommandSrcUserInput, + + // Came in over a net connection as a clc_stringcmd + // host_client will be valid during this state. + // + // Restricted to FCVAR_GAMEDLL commands (but not convars) and special non-ConCommand + // server commands hardcoded into gameplay code (e.g. "joingame") + kCommandSrcNetClient, + + // Received from the server as the client + // + // Restricted to commands with FCVAR_SERVER_CAN_EXECUTE + kCommandSrcNetServer, + + // Being played back from a demo file + // + // Not currently restricted by convar flag, but some commands manually ignore calls + // from this source. FIXME: Should be heavily restricted as demo commands can come + // from untrusted sources. + kCommandSrcDemoFile, + + // Invalid value used when cleared + kCommandSrcInvalid = -1 + }; + + typedef ECommandTarget_t (*Cbuf_GetCurrentPlayerType)(); + extern Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; + + typedef void (*Cbuf_AddTextType)(ECommandTarget_t eTarget, const char* text, cmd_source_t source); + extern Cbuf_AddTextType Cbuf_AddText; + + typedef void (*Cbuf_ExecuteType)(); + extern Cbuf_ExecuteType Cbuf_Execute; + + // CEngine + + enum EngineQuitState + { + QUIT_NOTQUITTING = 0, + QUIT_TODESKTOP, + QUIT_RESTART + }; + + enum class EngineState_t + { + DLL_INACTIVE = 0, // no dll + DLL_ACTIVE, // engine is focused + DLL_CLOSE, // closing down dll + DLL_RESTART, // engine is shutting down but will restart right away + DLL_PAUSED, // engine is paused, can become active from this state + }; + + class CEngine + { + public: + virtual void unknown() {} // unsure if this is where + virtual bool Load(bool dedicated, const char* baseDir) {} + virtual void Unload() {} + virtual void SetNextState(EngineState_t iNextState) {} + virtual EngineState_t GetState() {} + virtual void Frame() {} + virtual double GetFrameTime() {} + virtual float GetCurTime() {} + + EngineQuitState m_nQuitting; + EngineState_t m_nDllState; + EngineState_t m_nNextDllState; + double m_flCurrentTime; + float m_flFrameTime; + double m_flPreviousTime; + float m_flFilteredTime; + float m_flMinFrameTime; // Expected duration of a frame, or zero if it is unlimited. + }; + + 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) + + // #56169 $DB69 PData size + // #512 $200 Trailing data + // #100 $64 Safety buffer + const int PERSISTENCE_MAX_SIZE = 0xDDCD; + + // note: NOT_READY and READY are the only entries we have here that are defined by the vanilla game + // entries after this are custom and used to determine the source of persistence, e.g. whether it is local or remote + enum class ePersistenceReady : char + { + NOT_READY, + READY = 3, + READY_INSECURE = 3, + READY_REMOTE + }; + +#pragma pack(push, 1) + struct CBaseClient // 0x2D728 bytes + { + char pad0[0x16]; + + // +0x16 + char m_Name[64]; + + // +0x56 + char pad1[0x202]; + + void** m_ConVars; // this is a KeyValues* object but not got that struct mapped out atm + + char pad2[0x240]; + + // +0x4A0 + ePersistenceReady m_iPersistenceReady; + // +0x4A1 + + char pad3[0x59]; + + // +0x4FA + char m_PersistenceBuffer[PERSISTENCE_MAX_SIZE]; + + char pad4[0x2006]; + + // +0xF500 + char m_UID[32]; + // +0xF520 + + char pad5[0x1E208]; + }; +#pragma pack(pop) + + extern CBaseClient* g_pClientArray; + + enum server_state_t + { + ss_dead = 0, // Dead + ss_loading, // Spawning + ss_active, // Running + ss_paused, // Running, but paused + }; + + extern server_state_t* g_pServerState; + + extern char* g_pModName; +} // namespace R2 diff --git a/NorthstarDLL/r2server.cpp b/NorthstarDLL/r2server.cpp new file mode 100644 index 00000000..50cfa239 --- /dev/null +++ b/NorthstarDLL/r2server.cpp @@ -0,0 +1,17 @@ +#include "pch.h" +#include "r2server.h" + +using namespace R2; + +// use the R2 namespace for game funcs +namespace R2 +{ + CBaseEntity* (*Server_GetEntityByIndex)(int index); + CBasePlayer*(__fastcall* UTIL_PlayerByIndex)(int playerIndex); +} // namespace R2 + +ON_DLL_LOAD("server.dll", R2GameServer, (CModule module)) +{ + Server_GetEntityByIndex = module.Offset(0xFB820).As(); + UTIL_PlayerByIndex = module.Offset(0x26AA10).As(); +} diff --git a/NorthstarDLL/r2server.h b/NorthstarDLL/r2server.h new file mode 100644 index 00000000..1abe822d --- /dev/null +++ b/NorthstarDLL/r2server.h @@ -0,0 +1,23 @@ +#pragma once + +// use the R2 namespace for game funcs +namespace R2 +{ + // server entity stuff + class CBaseEntity; + extern CBaseEntity* (*Server_GetEntityByIndex)(int index); + +#pragma pack(push, 1) + struct CBasePlayer + { + char pad[0x58]; + uint32_t m_nPlayerIndex; + + // +0x5C + char pad1[0x1C75]; + char m_communityClanTag[16]; + }; +#pragma pack(pop) + + extern CBasePlayer*(__fastcall* UTIL_PlayerByIndex)(int playerIndex); +} // namespace R2 diff --git a/NorthstarDLL/rpakfilesystem.cpp b/NorthstarDLL/rpakfilesystem.cpp index 964042b5..b1363589 100644 --- a/NorthstarDLL/rpakfilesystem.cpp +++ b/NorthstarDLL/rpakfilesystem.cpp @@ -1,86 +1,99 @@ #include "pch.h" #include "rpakfilesystem.h" -#include "hookutils.h" #include "modmanager.h" #include "dedicated.h" +#include "tier0.h" -typedef void* (*LoadCommonPaksForMapType)(char* map); -LoadCommonPaksForMapType LoadCommonPaksForMap; - -typedef void* (*LoadPakSyncType)(const char* path, void* unknownSingleton, int flags); -typedef int (*LoadPakAsyncType)(const char* path, void* unknownSingleton, int flags, void* callback0, void* callback1); -typedef void* (*UnloadPakType)(int pakHandle, void* callback); -typedef void* (*ReadFullFileFromDiskType)(const char* requestedPath, void* a2); +AUTOHOOK_INIT() // there are more i'm just too lazy to add struct PakLoadFuncs { - void* unk0[2]; - LoadPakSyncType LoadPakSync; - LoadPakAsyncType LoadPakAsync; + void* unk0[3]; + int (*LoadPakAsync)(const char* pPath, void* unknownSingleton, int flags, void* callback0, void* callback1); void* unk1[2]; - UnloadPakType UnloadPak; - void* unk2[17]; - ReadFullFileFromDiskType ReadFullFileFromDisk; + void* (*UnloadPak)(int iPakHandle, void* callback); + void* unk2[6]; + void* (*LoadFile)(const char* path); // unsure + void* unk3[10]; + void* (*ReadFileAsync)(const char* pPath, void* a2); }; PakLoadFuncs* g_pakLoadApi; + +PakLoadManager* g_pPakLoadManager; void** pUnknownPakLoadSingleton; -PakLoadManager* g_PakLoadManager; -void PakLoadManager::LoadPakSync(const char* path) +int PakLoadManager::LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource) { - g_pakLoadApi->LoadPakSync(path, *pUnknownPakLoadSingleton, 0); + int nHandle = g_pakLoadApi->LoadPakAsync(pPath, *pUnknownPakLoadSingleton, 2, nullptr, nullptr); + + // set the load source of the pak we just loaded + if (nHandle != -1) + GetPakInfo(nHandle)->m_nLoadSource = nLoadSource; + + return nHandle; } -void PakLoadManager::LoadPakAsync(const char* path, bool bMarkForUnload) + +void PakLoadManager::UnloadPak(const int nPakHandle) { - int handle = g_pakLoadApi->LoadPakAsync(path, *pUnknownPakLoadSingleton, 2, nullptr, nullptr); + g_pakLoadApi->UnloadPak(nPakHandle, nullptr); +} - if (bMarkForUnload) - m_pakHandlesToUnload.push_back(handle); +void PakLoadManager::UnloadMapPaks() +{ + for (auto& pair : m_vLoadedPaks) + if (pair.second.m_nLoadSource == ePakLoadSource::MAP) + UnloadPak(pair.first); } -void PakLoadManager::UnloadPaks() +LoadedPak* PakLoadManager::TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash) { - for (int pakHandle : m_pakHandlesToUnload) - { - g_pakLoadApi->UnloadPak(pakHandle, nullptr); - // remove pak from loadedPaks and loadedPaksInv - RemoveLoadedPak(pakHandle); - } + LoadedPak pak; + pak.m_nLoadSource = nLoadSource; + pak.m_nPakHandle = nPakHandle; + pak.m_nPakNameHash = nPakNameHash; - m_pakHandlesToUnload.clear(); + m_vLoadedPaks.insert(std::make_pair(nPakHandle, pak)); + return &m_vLoadedPaks.at(nPakHandle); +} + +void PakLoadManager::RemoveLoadedPak(int nPakHandle) +{ + m_vLoadedPaks.erase(nPakHandle); } -bool PakLoadManager::IsPakLoaded(int32_t pakHandle) +LoadedPak* PakLoadManager::GetPakInfo(const int nPakHandle) { - return loadedPaks.find(pakHandle) != loadedPaks.end(); + return &m_vLoadedPaks.at(nPakHandle); } -bool PakLoadManager::IsPakLoaded(size_t hash) +int PakLoadManager::GetPakHandle(const size_t nPakNameHash) { - return loadedPaksInv.find(hash) != loadedPaksInv.end(); + for (auto& pair : m_vLoadedPaks) + if (pair.second.m_nPakNameHash == nPakNameHash) + return pair.first; + + return -1; } -void PakLoadManager::AddLoadedPak(int32_t pakHandle, size_t hash) +int PakLoadManager::GetPakHandle(const char* pPath) { - loadedPaks[pakHandle] = hash; - loadedPaksInv[hash] = pakHandle; + return GetPakHandle(STR_HASH(pPath)); } -void PakLoadManager::RemoveLoadedPak(int32_t pakHandle) +void* PakLoadManager::LoadFile(const char* path) { - loadedPaksInv.erase(loadedPaks[pakHandle]); - loadedPaks.erase(pakHandle); + return g_pakLoadApi->LoadFile(path); } void HandlePakAliases(char** map) { // convert the pak being loaded to it's aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift - for (int64_t i = g_ModManager->m_loadedMods.size() - 1; i > -1; i--) + for (int64_t i = g_pModManager->m_LoadedMods.size() - 1; i > -1; i--) { - Mod* mod = &g_ModManager->m_loadedMods[i]; - if (!mod->Enabled) + Mod* mod = &g_pModManager->m_LoadedMods[i]; + if (!mod->m_bEnabled) continue; if (mod->RpakAliases.find(*map) != mod->RpakAliases.end()) @@ -94,50 +107,50 @@ void HandlePakAliases(char** map) void LoadPreloadPaks() { // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { - if (!mod.Enabled) + if (!mod.m_bEnabled) continue; // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.ModDirectory / "paks"); + fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); for (ModRpakEntry& pak : mod.Rpaks) if (pak.m_bAutoLoad) - g_PakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), false); + g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT); } } -void LoadPostloadPaks(char** map) +void LoadPostloadPaks(const char* pPath) { // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { - if (!mod.Enabled) + if (!mod.m_bEnabled) continue; // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.ModDirectory / "paks"); + fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); for (ModRpakEntry& pak : mod.Rpaks) - if (pak.m_sLoadAfterPak == *map) - g_PakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), false); + if (pak.m_sLoadAfterPak == pPath) + g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT); } } void LoadCustomMapPaks(char** pakName, bool* bNeedToFreePakName) { // whether the vanilla game has this rpak - bool bHasOriginalPak = fs::exists(fs::path("./r2/paks/Win64/") / *pakName); + bool bHasOriginalPak = fs::exists(fs::path("r2/paks/Win64/") / *pakName); // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { - if (!mod.Enabled) + if (!mod.m_bEnabled) continue; // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.ModDirectory / "paks"); + fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); for (ModRpakEntry& pak : mod.Rpaks) { @@ -156,114 +169,95 @@ void LoadCustomMapPaks(char** pakName, bool* bNeedToFreePakName) true; // we can't free this memory until we're done with the pak, so let whatever's calling this deal with it } else - g_PakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), true); + g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::MAP); } } } } -LoadPakSyncType LoadPakSyncOriginal; -void* LoadPakSyncHook(char* path, void* unknownSingleton, int flags) +// clang-format off +HOOK(LoadPakAsyncHook, LoadPakAsync, +int, __fastcall, (char* pPath, void* unknownSingleton, int flags, void* pCallback0, void* pCallback1)) +// clang-format on { - HandlePakAliases(&path); - - bool bNeedToFreePakName = false; - - // note: we don't handle loading any preloaded custom paks synchronously since LoadPakSync is never actually called in retail, just load - // them async instead - static bool bShouldLoadPaks = true; - if (bShouldLoadPaks) - { - // disable preloading while we're doing this - bShouldLoadPaks = false; + HandlePakAliases(&pPath); - LoadPreloadPaks(); - LoadCustomMapPaks(&path, &bNeedToFreePakName); - - bShouldLoadPaks = true; - } - - spdlog::info("LoadPakSync {}", path); - void* ret = LoadPakSyncOriginal(path, unknownSingleton, flags); - - if (bNeedToFreePakName) - delete[] path; - - return ret; -} - -LoadPakAsyncType LoadPakAsyncOriginal; -int LoadPakAsyncHook(char* path, void* unknownSingleton, int flags, void* callback0, void* callback1) -{ - size_t hash = STR_HASH(path); - // if the hash is already in the map, dont load the pak, it has already been loaded - if (g_PakLoadManager->IsPakLoaded(hash)) - { + // dont load the pak if it's currently loaded already + size_t nPathHash = STR_HASH(pPath); + if (g_pPakLoadManager->GetPakHandle(nPathHash) != -1) return -1; - } - - HandlePakAliases(&path); bool bNeedToFreePakName = false; static bool bShouldLoadPaks = true; if (bShouldLoadPaks) { + // make a copy of the path for comparing to determine whether we should load this pak on dedi, before it could get overwritten by + // LoadCustomMapPaks + std::string originalPath(pPath); + // disable preloading while we're doing this bShouldLoadPaks = false; LoadPreloadPaks(); - LoadCustomMapPaks(&path, &bNeedToFreePakName); + LoadCustomMapPaks(&pPath, &bNeedToFreePakName); bShouldLoadPaks = true; // do this after custom paks load and in bShouldLoadPaks so we only ever call this on the root pakload call // todo: could probably add some way to flag custom paks to not be loaded on dedicated servers in rpak.json - if (IsDedicatedServer() && strncmp(path, "common", 6)) // dedicated only needs common and common_mp + if (IsDedicatedServer() && (Tier0::CommandLine()->CheckParm("-nopakdedi") || + strncmp(&originalPath[0], "common", 6))) // dedicated only needs common and common_mp + { + if (bNeedToFreePakName) + delete[] pPath; + + spdlog::info("Not loading pak {} for dedicated server", originalPath); return -1; + } } - int ret = LoadPakAsyncOriginal(path, unknownSingleton, flags, callback0, callback1); - spdlog::info("LoadPakAsync {} {}", path, ret); - - // add the hash to the map - g_PakLoadManager->AddLoadedPak(ret, hash); + int iPakHandle = LoadPakAsync(pPath, unknownSingleton, flags, pCallback0, pCallback1); + spdlog::info("LoadPakAsync {} {}", pPath, iPakHandle); - LoadPostloadPaks(&path); + // trak the pak + g_pPakLoadManager->TrackLoadedPak(ePakLoadSource::UNTRACKED, iPakHandle, nPathHash); + LoadPostloadPaks(pPath); if (bNeedToFreePakName) - delete[] path; + delete[] pPath; - return ret; + return iPakHandle; } -UnloadPakType UnloadPakOriginal; -void* UnloadPakHook(int pakHandle, void* callback) +// clang-format off +HOOK(UnloadPakHook, UnloadPak, +void*, __fastcall, (int nPakHandle, void* pCallback)) +// clang-format on { - if (g_PakLoadManager->IsPakLoaded(pakHandle)) - { - // remove the entry - g_PakLoadManager->RemoveLoadedPak(pakHandle); - } + // stop tracking the pak + g_pPakLoadManager->RemoveLoadedPak(nPakHandle); static bool bShouldUnloadPaks = true; if (bShouldUnloadPaks) { bShouldUnloadPaks = false; - g_PakLoadManager->UnloadPaks(); + g_pPakLoadManager->UnloadMapPaks(); bShouldUnloadPaks = true; } - spdlog::info("UnloadPak {}", pakHandle); - return UnloadPakOriginal(pakHandle, callback); + spdlog::info("UnloadPak {}", nPakHandle); + return UnloadPak(nPakHandle, pCallback); } -// we hook this exclusively for resolving stbsp paths, but seemingly it's also used for other stuff like vpk and rpak loads -// possibly just async loading all together? -ReadFullFileFromDiskType ReadFullFileFromDiskOriginal; -void* ReadFullFileFromDiskHook(const char* requestedPath, void* a2) +// we hook this exclusively for resolving stbsp paths, but seemingly it's also used for other stuff like vpk, rpak, mprj and starpak loads +// tbh this actually might be for memory mapped files or something, would make sense i think +// clang-format off +HOOK(ReadFileAsyncHook, ReadFileAsync, +void*, __fastcall, (const char* pPath, void* pCallback)) +// clang-format on { - fs::path path(requestedPath); + fs::path path(pPath); char* allocatedNewPath = nullptr; if (path.extension() == ".stbsp") @@ -272,42 +266,34 @@ void* ReadFullFileFromDiskHook(const char* requestedPath, void* a2) spdlog::info("LoadStreamBsp: {}", filename.string()); // resolve modded stbsp path so we can load mod stbsps - auto modFile = g_ModManager->m_modFiles.find(fs::path("maps" / filename).lexically_normal().string()); - if (modFile != g_ModManager->m_modFiles.end()) + auto modFile = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path("maps" / filename))); + if (modFile != g_pModManager->m_ModFiles.end()) { // need to allocate a new string for this - std::string newPath = (modFile->second.owningMod->ModDirectory / "mod" / modFile->second.path).string(); + std::string newPath = (modFile->second.m_pOwningMod->m_ModDirectory / "mod" / modFile->second.m_Path).string(); allocatedNewPath = new char[newPath.size() + 1]; - strncpy(allocatedNewPath, newPath.c_str(), newPath.size()); - allocatedNewPath[newPath.size()] = '\0'; - requestedPath = allocatedNewPath; + strncpy_s(allocatedNewPath, newPath.size() + 1, newPath.c_str(), newPath.size()); + pPath = allocatedNewPath; } } - void* ret = ReadFullFileFromDiskOriginal(requestedPath, a2); + void* ret = ReadFileAsync(pPath, pCallback); if (allocatedNewPath) delete[] allocatedNewPath; return ret; } -void InitialiseEngineRpakFilesystem(HMODULE baseAddress) +ON_DLL_LOAD("engine.dll", RpakFilesystem, (CModule module)) { - g_PakLoadManager = new PakLoadManager; - - g_pakLoadApi = *(PakLoadFuncs**)((char*)baseAddress + 0x5BED78); - pUnknownPakLoadSingleton = (void**)((char*)baseAddress + 0x7C5E20); - - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, reinterpret_cast(g_pakLoadApi->LoadPakSync), &LoadPakSyncHook, reinterpret_cast(&LoadPakSyncOriginal)); - ENABLER_CREATEHOOK( - hook, reinterpret_cast(g_pakLoadApi->LoadPakAsync), &LoadPakAsyncHook, reinterpret_cast(&LoadPakAsyncOriginal)); - ENABLER_CREATEHOOK( - hook, reinterpret_cast(g_pakLoadApi->UnloadPak), &UnloadPakHook, reinterpret_cast(&UnloadPakOriginal)); - ENABLER_CREATEHOOK( - hook, - reinterpret_cast(g_pakLoadApi->ReadFullFileFromDisk), - &ReadFullFileFromDiskHook, - reinterpret_cast(&ReadFullFileFromDiskOriginal)); + AUTOHOOK_DISPATCH(); + + g_pPakLoadManager = new PakLoadManager; + + g_pakLoadApi = module.Offset(0x5BED78).Deref().As(); + pUnknownPakLoadSingleton = module.Offset(0x7C5E20).As(); + + LoadPakAsyncHook.Dispatch(g_pakLoadApi->LoadPakAsync); + UnloadPakHook.Dispatch(g_pakLoadApi->UnloadPak); + ReadFileAsyncHook.Dispatch(g_pakLoadApi->ReadFileAsync); } diff --git a/NorthstarDLL/rpakfilesystem.h b/NorthstarDLL/rpakfilesystem.h index af51f6db..3f608dba 100644 --- a/NorthstarDLL/rpakfilesystem.h +++ b/NorthstarDLL/rpakfilesystem.h @@ -1,24 +1,39 @@ #pragma once -void InitialiseEngineRpakFilesystem(HMODULE baseAddress); +enum class ePakLoadSource +{ + UNTRACKED = -1, // not a pak we loaded, we shouldn't touch this one + + CONSTANT, // should be loaded at all times + MAP // loaded from a map, should be unloaded when the map is unloaded +}; + +struct LoadedPak +{ + ePakLoadSource m_nLoadSource; + int m_nPakHandle; + size_t m_nPakNameHash; +}; class PakLoadManager { + private: + std::map m_vLoadedPaks {}; + std::unordered_map m_HashToPakHandle {}; + public: - void LoadPakSync(const char* path); - void LoadPakAsync(const char* path, bool bMarkForUnload); - void UnloadPaks(); + int LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource); + void UnloadPak(const int nPakHandle); + void UnloadMapPaks(); + void* LoadFile(const char* path); // this is a guess - bool IsPakLoaded(int32_t pakHandle); - bool IsPakLoaded(size_t hash); - void AddLoadedPak(int32_t pakHandle, size_t hash); - void RemoveLoadedPak(int32_t pakHandle); + LoadedPak* TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash); + void RemoveLoadedPak(int nPakHandle); - private: - std::vector m_pakHandlesToUnload; - // these size_t s are the asset path hashed with STR_HASH - std::unordered_map loadedPaks {}; - std::unordered_map loadedPaksInv {}; + LoadedPak* GetPakInfo(const int nPakHandle); + + int GetPakHandle(const size_t nPakNameHash); + int GetPakHandle(const char* pPath); }; -extern PakLoadManager* g_PakLoadManager; +extern PakLoadManager* g_pPakLoadManager; diff --git a/NorthstarDLL/runframe.cpp b/NorthstarDLL/runframe.cpp new file mode 100644 index 00000000..eb401f51 --- /dev/null +++ b/NorthstarDLL/runframe.cpp @@ -0,0 +1,20 @@ +#include "pch.h" +#include "r2engine.h" +#include "r2server.h" +#include "hoststate.h" +#include "serverpresence.h" + +AUTOHOOK_INIT() + +// clang-format off +AUTOHOOK(CEngine__Frame, engine.dll + 0x1C8650, +void, __fastcall, (R2::CEngine* self)) +// clang-format on +{ + CEngine__Frame(self); +} + +ON_DLL_LOAD("engine.dll", RunFrame, (CModule module)) +{ + AUTOHOOK_DISPATCH() +} diff --git a/NorthstarDLL/scriptbrowserhooks.cpp b/NorthstarDLL/scriptbrowserhooks.cpp index 22f1101f..df4014de 100644 --- a/NorthstarDLL/scriptbrowserhooks.cpp +++ b/NorthstarDLL/scriptbrowserhooks.cpp @@ -1,27 +1,25 @@ #include "pch.h" -#include "scriptbrowserhooks.h" -#include "hookutils.h" -typedef void (*OpenExternalWebBrowserType)(char* url, char flags); -OpenExternalWebBrowserType OpenExternalWebBrowser; +AUTOHOOK_INIT() bool* bIsOriginOverlayEnabled; -void OpenExternalWebBrowserHook(char* url, char flags) +// clang-format off +AUTOHOOK(OpenExternalWebBrowser, engine.dll + 0x184E40, +void, __fastcall, (char* pUrl, char flags)) +// clang-format on { bool bIsOriginOverlayEnabledOriginal = *bIsOriginOverlayEnabled; - if (flags & 2 && !strncmp(url, "http", 4)) // custom force external browser flag + if (flags & 2 && !strncmp(pUrl, "http", 4)) // custom force external browser flag *bIsOriginOverlayEnabled = false; // if this bool is false, game will use an external browser rather than the origin overlay one - OpenExternalWebBrowser(url, flags); + OpenExternalWebBrowser(pUrl, flags); *bIsOriginOverlayEnabled = bIsOriginOverlayEnabledOriginal; } -void InitialiseScriptExternalBrowserHooks(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT("engine.dll", ScriptExternalBrowserHooks, (CModule module)) { - bIsOriginOverlayEnabled = (bool*)baseAddress + 0x13978255; + AUTOHOOK_DISPATCH() - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x184E40, &OpenExternalWebBrowserHook, reinterpret_cast(&OpenExternalWebBrowser)); + bIsOriginOverlayEnabled = module.Offset(0x13978255).As(); } diff --git a/NorthstarDLL/scriptbrowserhooks.h b/NorthstarDLL/scriptbrowserhooks.h deleted file mode 100644 index f303ea93..00000000 --- a/NorthstarDLL/scriptbrowserhooks.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseScriptExternalBrowserHooks(HMODULE baseAddress); diff --git a/NorthstarDLL/scriptdatatables.cpp b/NorthstarDLL/scriptdatatables.cpp new file mode 100644 index 00000000..fc2be943 --- /dev/null +++ b/NorthstarDLL/scriptdatatables.cpp @@ -0,0 +1,940 @@ +#include "pch.h" +#include "squirrel.h" +#include "rpakfilesystem.h" +#include "convar.h" +#include "dedicated.h" +#include "filesystem.h" +#include "vector.h" +#include "tier0.h" +#include "r2engine.h" +#include +#include +#include +#include +#include + +const uint64_t USERDATA_TYPE_DATATABLE = 0xFFF7FFF700000004; +const uint64_t USERDATA_TYPE_DATATABLE_CUSTOM = 0xFFFCFFFC12345678; + +enum class DatatableType : int +{ + BOOL = 0, + INT, + FLOAT, + VECTOR, + STRING, + ASSET, + UNK_STRING // unknown but deffo a string type +}; + +struct ColumnInfo +{ + char* name; + DatatableType type; + int offset; +}; + +struct Datatable +{ + int numColumns; + int numRows; + ColumnInfo* columnInfo; + char* data; // actually data pointer + int rowInfo; +}; + +ConVar* Cvar_ns_prefer_datatable_from_disk; + +template Datatable* (*SQ_GetDatatableInternal)(HSquirrelVM* sqvm); + +struct CSVData +{ + std::string m_sAssetName; + std::string m_sCSVString; + char* m_pDataBuf; + size_t m_nDataBufSize; + + std::vector columns; + std::vector> dataPointers; +}; + +std::unordered_map CSVCache; + +Vector3 StringToVector(char* pString) +{ + Vector3 vRet; + + int length = 0; + while (pString[length]) + { + if ((pString[length] == '<') || (pString[length] == '>')) + pString[length] = '\0'; + length++; + } + + int startOfFloat = 1; + int currentIndex = 1; + + while (pString[currentIndex] && (pString[currentIndex] != ',')) + currentIndex++; + pString[currentIndex] = '\0'; + vRet.x = std::stof(&pString[startOfFloat]); + startOfFloat = ++currentIndex; + + while (pString[currentIndex] && (pString[currentIndex] != ',')) + currentIndex++; + pString[currentIndex] = '\0'; + vRet.y = std::stof(&pString[startOfFloat]); + startOfFloat = ++currentIndex; + + while (pString[currentIndex] && (pString[currentIndex] != ',')) + currentIndex++; + pString[currentIndex] = '\0'; + vRet.z = std::stof(&pString[startOfFloat]); + startOfFloat = ++currentIndex; + + return vRet; +} + +// var function GetDataTable( asset path ) +template SQRESULT SQ_GetDatatable(HSquirrelVM* sqvm) +{ + const char* pAssetName; + g_pSquirrel->getasset(sqvm, 2, &pAssetName); + + if (strncmp(pAssetName, "datatable/", 10)) + { + g_pSquirrel->raiseerror(sqvm, fmt::format("Asset \"{}\" doesn't start with \"datatable/\"", pAssetName).c_str()); + return SQRESULT_ERROR; + } + else if (!Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->LoadFile(pAssetName)) + return g_pSquirrel->m_funcOriginals["GetDataTable"](sqvm); + // either we prefer disk datatables, or we're loading a datatable that wasn't found in rpak + else + { + std::string sAssetPath(fmt::format("scripts/{}", pAssetName)); + + // first, check the cache + if (CSVCache.find(pAssetName) != CSVCache.end()) + { + CSVData** pUserdata = g_pSquirrel->createuserdata(sqvm, sizeof(CSVData*)); + g_pSquirrel->setuserdatatypeid(sqvm, -1, USERDATA_TYPE_DATATABLE_CUSTOM); + *pUserdata = &CSVCache[pAssetName]; + + return SQRESULT_NOTNULL; + } + + // check files on disk + // we don't use .rpak as the extension for on-disk datatables, so we need to replace .rpak with .csv in the filename we're reading + fs::path diskAssetPath("scripts"); + if (fs::path(pAssetName).extension() == ".rpak") + diskAssetPath /= fs::path(pAssetName).remove_filename() / (fs::path(pAssetName).stem().string() + ".csv"); + else + diskAssetPath /= fs::path(pAssetName); + + std::string sDiskAssetPath(diskAssetPath.string()); + if ((*R2::g_pFilesystem)->m_vtable2->FileExists(&(*R2::g_pFilesystem)->m_vtable2, sDiskAssetPath.c_str(), "GAME")) + { + std::string sTableCSV = R2::ReadVPKFile(sDiskAssetPath.c_str()); + if (!sTableCSV.size()) + { + g_pSquirrel->raiseerror(sqvm, fmt::format("Datatable \"{}\" is empty", pAssetName).c_str()); + return SQRESULT_ERROR; + } + + // somewhat shit, but ensure we end with a newline to make parsing easier + if (sTableCSV[sTableCSV.length() - 1] != '\n') + sTableCSV += '\n'; + + CSVData csv; + csv.m_sAssetName = pAssetName; + csv.m_sCSVString = sTableCSV; + csv.m_nDataBufSize = sTableCSV.size(); + csv.m_pDataBuf = new char[csv.m_nDataBufSize]; + memcpy(csv.m_pDataBuf, &sTableCSV[0], csv.m_nDataBufSize); + + // parse the csv + // csvs are essentially comma and newline-deliniated sets of strings for parsing, only thing we need to worry about is quoted + // entries when we parse an element of the csv, rather than allocating an entry for it, we just convert that element to a + // null-terminated string i.e., store the ptr to the first char of it, then make the comma that delinates it a nullchar + + bool bHasColumns = false; + bool bInQuotes = false; + + std::vector vCurrentRow; + char* pElemStart = csv.m_pDataBuf; + char* pElemEnd = nullptr; + + for (int i = 0; i < csv.m_nDataBufSize; i++) + { + if (csv.m_pDataBuf[i] == '\r' && csv.m_pDataBuf[i + 1] == '\n') + { + if (!pElemEnd) + pElemEnd = csv.m_pDataBuf + i; + + continue; // next iteration can handle the \n + } + + // newline, end of a row + if (csv.m_pDataBuf[i] == '\n') + { + // shouldn't have newline in string + if (bInQuotes) + { + g_pSquirrel->raiseerror(sqvm, "Unexpected \\n in string"); + return SQRESULT_ERROR; + } + + // push last entry to current row + if (pElemEnd) + *pElemEnd = '\0'; + else + csv.m_pDataBuf[i] = '\0'; + + vCurrentRow.push_back(pElemStart); + + // newline, push last line to csv data and go from there + if (!bHasColumns) + { + bHasColumns = true; + csv.columns = vCurrentRow; + } + else + csv.dataPointers.push_back(vCurrentRow); + + vCurrentRow.clear(); + // put start of current element at char after newline + pElemStart = csv.m_pDataBuf + i + 1; + pElemEnd = nullptr; + } + // we're starting or ending a quoted string + else if (csv.m_pDataBuf[i] == '"') + { + // start quoted string + if (!bInQuotes) + { + // shouldn't have quoted strings in column names + if (!bHasColumns) + { + g_pSquirrel->raiseerror(sqvm, "Unexpected \" in column name"); + return SQRESULT_ERROR; + } + + bInQuotes = true; + // put start of current element at char after string begin + pElemStart = csv.m_pDataBuf + i + 1; + } + // end quoted string + else + { + pElemEnd = csv.m_pDataBuf + i; + bInQuotes = false; + } + } + // don't parse commas in quotes + else if (bInQuotes) + { + continue; + } + // comma, push new entry to current row + else if (csv.m_pDataBuf[i] == ',') + { + if (pElemEnd) + *pElemEnd = '\0'; + else + csv.m_pDataBuf[i] = '\0'; + + vCurrentRow.push_back(pElemStart); + // put start of next element at char after comma + pElemStart = csv.m_pDataBuf + i + 1; + pElemEnd = nullptr; + } + } + + // add to cache and return + CSVData** pUserdata = g_pSquirrel->createuserdata(sqvm, sizeof(CSVData*)); + g_pSquirrel->setuserdatatypeid(sqvm, -1, USERDATA_TYPE_DATATABLE_CUSTOM); + CSVCache[pAssetName] = csv; + *pUserdata = &CSVCache[pAssetName]; + + return SQRESULT_NOTNULL; + } + // the file doesn't exist on disk, check rpak if we haven't already + else if (Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->LoadFile(pAssetName)) + return g_pSquirrel->m_funcOriginals["GetDataTable"](sqvm); + // the file doesn't exist at all, error + else + { + g_pSquirrel->raiseerror(sqvm, fmt::format("Datatable {} not found", pAssetName).c_str()); + return SQRESULT_ERROR; + } + } +} + +// int function GetDataTableRowCount( var datatable, string columnName ) +template SQRESULT SQ_GetDataTableColumnByName(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableColumnByName"](sqvm); + + CSVData* csv = *pData; + const char* pColumnName = g_pSquirrel->getstring(sqvm, 2); + + for (int i = 0; i < csv->columns.size(); i++) + { + if (!strcmp(csv->columns[i], pColumnName)) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + // column not found + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowCount( var datatable ) +template SQRESULT SQ_GetDataTableRowCount(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDatatableRowCount"](sqvm); + + CSVData* csv = *pData; + g_pSquirrel->pushinteger(sqvm, csv->dataPointers.size()); + return SQRESULT_NOTNULL; +} + +// string function GetDataTableString( var datatable, int row, int col ) +template SQRESULT SQ_GetDataTableString(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableString"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel->getinteger(sqvm, 2); + const int nCol = g_pSquirrel->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel->pushstring(sqvm, csv->dataPointers[nRow][nCol], -1); + return SQRESULT_NOTNULL; +} + +// asset function GetDataTableAsset( var datatable, int row, int col ) +template SQRESULT SQ_GetDataTableAsset(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableAsset"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel->getinteger(sqvm, 2); + const int nCol = g_pSquirrel->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel->pushasset(sqvm, csv->dataPointers[nRow][nCol], -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableInt( var datatable, int row, int col ) +template SQRESULT SQ_GetDataTableInt(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableInt"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel->getinteger(sqvm, 2); + const int nCol = g_pSquirrel->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel->pushinteger(sqvm, std::stoi(csv->dataPointers[nRow][nCol])); + return SQRESULT_NOTNULL; +} + +// float function GetDataTableFloat( var datatable, int row, int col ) +template SQRESULT SQ_GetDataTableFloat(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableFloat"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel->getinteger(sqvm, 2); + const int nCol = g_pSquirrel->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel->pushfloat(sqvm, std::stof(csv->dataPointers[nRow][nCol])); + return SQRESULT_NOTNULL; +} + +// bool function GetDataTableBool( var datatable, int row, int col ) +template SQRESULT SQ_GetDataTableBool(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableBool"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel->getinteger(sqvm, 2); + const int nCol = g_pSquirrel->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel->pushbool(sqvm, std::stoi(csv->dataPointers[nRow][nCol])); + return SQRESULT_NOTNULL; +} + +// vector function GetDataTableVector( var datatable, int row, int col ) +template SQRESULT SQ_GetDataTableVector(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableVector"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel->getinteger(sqvm, 2); + const int nCol = g_pSquirrel->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel->pushvector(sqvm, StringToVector(csv->dataPointers[nRow][nCol])); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingStringValue( var datatable, int col, string value ) +template SQRESULT SQ_GetDataTableRowMatchingStringValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowMatchingStringValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const char* pStringVal = g_pSquirrel->getstring(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (!strcmp(csv->dataPointers[i][nCol], pStringVal)) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingAssetValue( var datatable, int col, asset value ) +template SQRESULT SQ_GetDataTableRowMatchingAssetValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowMatchingAssetValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const char* pStringVal; + g_pSquirrel->getasset(sqvm, 3, &pStringVal); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (!strcmp(csv->dataPointers[i][nCol], pStringVal)) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingFloatValue( var datatable, int col, float value ) +template SQRESULT SQ_GetDataTableRowMatchingFloatValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowMatchingFloatValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const float flFloatVal = g_pSquirrel->getfloat(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (flFloatVal == std::stof(csv->dataPointers[i][nCol])) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingIntValue( var datatable, int col, int value ) +template SQRESULT SQ_GetDataTableRowMatchingIntValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowMatchingIntValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const int nIntVal = g_pSquirrel->getinteger(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (nIntVal == std::stoi(csv->dataPointers[i][nCol])) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingVectorValue( var datatable, int col, vector value ) +template SQRESULT SQ_GetDataTableRowMatchingVectorValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowMatchingVectorValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const Vector3 vVectorVal = g_pSquirrel->getvector(sqvm, 3); + + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (vVectorVal == StringToVector(csv->dataPointers[i][nCol])) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowGreaterThanOrEqualToIntValue( var datatable, int col, int value ) +template SQRESULT SQ_GetDataTableRowGreaterThanOrEqualToIntValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowGreaterThanOrEqualToIntValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const int nIntVal = g_pSquirrel->getinteger(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (nIntVal >= std::stoi(csv->dataPointers[i][nCol])) + { + spdlog::info("datatable not loaded"); + g_pSquirrel->pushinteger(sqvm, 1); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowLessThanOrEqualToIntValue( var datatable, int col, int value ) +template SQRESULT SQ_GetDataTableRowLessThanOrEqualToIntValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowLessThanOrEqualToIntValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const int nIntVal = g_pSquirrel->getinteger(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (nIntVal <= std::stoi(csv->dataPointers[i][nCol])) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowGreaterThanOrEqualToFloatValue( var datatable, int col, float value ) +template SQRESULT SQ_GetDataTableRowGreaterThanOrEqualToFloatValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowGreaterThanOrEqualToFloatValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const float flFloatVal = g_pSquirrel->getfloat(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (flFloatVal >= std::stof(csv->dataPointers[i][nCol])) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowLessThanOrEqualToFloatValue( var datatable, int col, float value ) +template SQRESULT SQ_GetDataTableRowLessThanOrEqualToFloatValue(HSquirrelVM* sqvm) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel->m_funcOriginals["GetDataTableRowLessThanOrEqualToFloatValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel->getinteger(sqvm, 2); + const float flFloatVal = g_pSquirrel->getfloat(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (flFloatVal <= std::stof(csv->dataPointers[i][nCol])) + { + g_pSquirrel->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +std::string DataTableToString(Datatable* datatable) +{ + std::string sCSVString; + + // write columns + bool bShouldComma = false; + for (int i = 0; i < datatable->numColumns; i++) + { + if (bShouldComma) + sCSVString += ','; + else + bShouldComma = true; + + sCSVString += datatable->columnInfo[i].name; + } + + // write rows + for (int row = 0; row < datatable->numRows; row++) + { + sCSVString += '\n'; + + bool bShouldComma = false; + for (int col = 0; col < datatable->numColumns; col++) + { + if (bShouldComma) + sCSVString += ','; + else + bShouldComma = true; + + // output typed data + ColumnInfo column = datatable->columnInfo[col]; + const void* pUntypedVal = datatable->data + column.offset + row * datatable->rowInfo; + switch (column.type) + { + case DatatableType::BOOL: + { + sCSVString += *(bool*)pUntypedVal ? '1' : '0'; + break; + } + + case DatatableType::INT: + { + sCSVString += std::to_string(*(int*)pUntypedVal); + break; + } + + case DatatableType::FLOAT: + { + sCSVString += std::to_string(*(float*)pUntypedVal); + break; + } + + case DatatableType::VECTOR: + { + Vector3 pVector((float*)pUntypedVal); + sCSVString += fmt::format("<{},{},{}>", pVector.x, pVector.y, pVector.z); + break; + } + + case DatatableType::STRING: + case DatatableType::ASSET: + case DatatableType::UNK_STRING: + { + sCSVString += fmt::format("\"{}\"", *(char**)pUntypedVal); + break; + } + } + } + } + + return sCSVString; +} + +void DumpDatatable(const char* pDatatablePath) +{ + Datatable* pDatatable = (Datatable*)g_pPakLoadManager->LoadFile(pDatatablePath); + if (!pDatatable) + { + spdlog::error("couldn't load datatable {} (rpak containing it may not be loaded?)", pDatatablePath); + return; + } + + std::string sOutputPath(fmt::format("{}/scripts/datatable/{}.csv", R2::g_pModName, fs::path(pDatatablePath).stem().string())); + std::string sDatatableContents(DataTableToString(pDatatable)); + + fs::create_directories(fs::path(sOutputPath).remove_filename()); + std::ofstream outputStream(sOutputPath); + outputStream.write(sDatatableContents.c_str(), sDatatableContents.size()); + outputStream.close(); + + spdlog::info("dumped datatable {} {} to {}", pDatatablePath, (void*)pDatatable, sOutputPath); +} + +void ConCommand_dump_datatable(const CCommand& args) +{ + if (args.ArgC() < 2) + { + spdlog::info("usage: dump_datatable datatable/tablename.rpak"); + return; + } + + DumpDatatable(args.Arg(1)); +} + +void ConCommand_dump_datatables(const CCommand& args) +{ + // likely not a comprehensive list, might be missing a couple? + static const std::vector VANILLA_DATATABLE_PATHS = { + "datatable/burn_meter_rewards.rpak", + "datatable/burn_meter_store.rpak", + "datatable/calling_cards.rpak", + "datatable/callsign_icons.rpak", + "datatable/camo_skins.rpak", + "datatable/default_pilot_loadouts.rpak", + "datatable/default_titan_loadouts.rpak", + "datatable/faction_leaders.rpak", + "datatable/fd_awards.rpak", + "datatable/features_mp.rpak", + "datatable/non_loadout_weapons.rpak", + "datatable/pilot_abilities.rpak", + "datatable/pilot_executions.rpak", + "datatable/pilot_passives.rpak", + "datatable/pilot_properties.rpak", + "datatable/pilot_weapons.rpak", + "datatable/pilot_weapon_features.rpak", + "datatable/pilot_weapon_mods.rpak", + "datatable/pilot_weapon_mods_common.rpak", + "datatable/playlist_items.rpak", + "datatable/titans_mp.rpak", + "datatable/titan_abilities.rpak", + "datatable/titan_executions.rpak", + "datatable/titan_fd_upgrades.rpak", + "datatable/titan_nose_art.rpak", + "datatable/titan_passives.rpak", + "datatable/titan_primary_mods.rpak", + "datatable/titan_primary_mods_common.rpak", + "datatable/titan_primary_weapons.rpak", + "datatable/titan_properties.rpak", + "datatable/titan_skins.rpak", + "datatable/titan_voices.rpak", + "datatable/unlocks_faction_level.rpak", + "datatable/unlocks_fd_titan_level.rpak", + "datatable/unlocks_player_level.rpak", + "datatable/unlocks_random.rpak", + "datatable/unlocks_titan_level.rpak", + "datatable/unlocks_weapon_level_pilot.rpak", + "datatable/weapon_skins.rpak", + "datatable/xp_per_faction_level.rpak", + "datatable/xp_per_fd_titan_level.rpak", + "datatable/xp_per_player_level.rpak", + "datatable/xp_per_titan_level.rpak", + "datatable/xp_per_weapon_level.rpak", + "datatable/faction_leaders_dropship_anims.rpak", + "datatable/score_events.rpak", + "datatable/startpoints.rpak", + "datatable/sp_levels.rpak", + "datatable/community_entries.rpak", + "datatable/spotlight_images.rpak", + "datatable/death_hints_mp.rpak", + "datatable/flightpath_assets.rpak", + "datatable/earn_meter_mp.rpak", + "datatable/battle_chatter_voices.rpak", + "datatable/battle_chatter.rpak", + "datatable/titan_os_conversations.rpak", + "datatable/faction_dialogue.rpak", + "datatable/grunt_chatter_mp.rpak", + "datatable/spectre_chatter_mp.rpak", + "datatable/pain_death_sounds.rpak", + "datatable/caller_ids_mp.rpak"}; + + for (const char* datatable : VANILLA_DATATABLE_PATHS) + DumpDatatable(datatable); +} + +template void RegisterDataTableFunctions() +{ + g_pSquirrel->AddFuncOverride("GetDataTable", SQ_GetDatatable); + g_pSquirrel->AddFuncOverride("GetDataTableColumnByName", SQ_GetDataTableColumnByName); + g_pSquirrel->AddFuncOverride("GetDatatableRowCount", SQ_GetDataTableRowCount); + g_pSquirrel->AddFuncOverride("GetDataTableString", SQ_GetDataTableString); + g_pSquirrel->AddFuncOverride("GetDataTableInt", SQ_GetDataTableInt); + g_pSquirrel->AddFuncOverride("GetDataTableFloat", SQ_GetDataTableFloat); + g_pSquirrel->AddFuncOverride("GetDataTableBool", SQ_GetDataTableBool); + g_pSquirrel->AddFuncOverride("GetDataTableAsset", SQ_GetDataTableAsset); + g_pSquirrel->AddFuncOverride("GetDataTableVector", SQ_GetDataTableVector); + g_pSquirrel->AddFuncOverride("GetDataTableRowMatchingStringValue", SQ_GetDataTableRowMatchingStringValue); + g_pSquirrel->AddFuncOverride("GetDataTableRowMatchingAssetValue", SQ_GetDataTableRowMatchingAssetValue); + g_pSquirrel->AddFuncOverride("GetDataTableRowMatchingFloatValue", SQ_GetDataTableRowMatchingFloatValue); + g_pSquirrel->AddFuncOverride("GetDataTableRowMatchingIntValue", SQ_GetDataTableRowMatchingIntValue); + g_pSquirrel->AddFuncOverride("GetDataTableRowMatchingVectorValue", SQ_GetDataTableRowMatchingVectorValue); + g_pSquirrel->AddFuncOverride( + "GetDataTableRowLessThanOrEqualToFloatValue", SQ_GetDataTableRowLessThanOrEqualToFloatValue); + g_pSquirrel->AddFuncOverride( + "GetDataTableRowGreaterThanOrEqualToFloatValue", SQ_GetDataTableRowGreaterThanOrEqualToFloatValue); + g_pSquirrel->AddFuncOverride("GetDataTableRowLessThanOrEqualToIntValue", SQ_GetDataTableRowLessThanOrEqualToIntValue); + g_pSquirrel->AddFuncOverride( + "GetDataTableRowGreaterThanOrEqualToFloatValue", SQ_GetDataTableRowGreaterThanOrEqualToIntValue); +} + +ON_DLL_LOAD_RELIESON("server.dll", ServerScriptDatatables, ServerSquirrel, (CModule module)) +{ + RegisterDataTableFunctions(); + + SQ_GetDatatableInternal = module.Offset(0x1250f0).As(); +} + +ON_DLL_LOAD_RELIESON("client.dll", ClientScriptDatatables, ClientSquirrel, (CModule module)) +{ + RegisterDataTableFunctions(); + RegisterDataTableFunctions(); + + SQ_GetDatatableInternal = module.Offset(0x1C9070).As(); + SQ_GetDatatableInternal = SQ_GetDatatableInternal; +} + +ON_DLL_LOAD_RELIESON("engine.dll", SharedScriptDataTables, ConVar, (CModule module)) +{ + Cvar_ns_prefer_datatable_from_disk = new ConVar( + "ns_prefer_datatable_from_disk", + IsDedicatedServer() && Tier0::CommandLine()->CheckParm("-nopakdedi") ? "1" : "0", + FCVAR_NONE, + "whether to prefer loading datatables from disk, rather than rpak"); + + RegisterConCommand("dump_datatables", ConCommand_dump_datatables, "dumps all datatables from a hardcoded list", FCVAR_NONE); + RegisterConCommand("dump_datatable", ConCommand_dump_datatable, "dump a datatable", FCVAR_NONE); +} diff --git a/NorthstarDLL/scriptjson.cpp b/NorthstarDLL/scriptjson.cpp index bc65a5c1..a9767615 100644 --- a/NorthstarDLL/scriptjson.cpp +++ b/NorthstarDLL/scriptjson.cpp @@ -1,502 +1,133 @@ #include "pch.h" -#include "scriptjson.h" +#include "squirrel.h" + +#include "rapidjson/error/en.h" #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" -#include "squirrel.h" - #ifdef _MSC_VER #undef GetObject // fuck microsoft developers #endif -void SQ_EncodeJSON_Table( - rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj, - SQTable* sqTable, - rapidjson_document* allocatorDoc); -void SQ_EncodeJSON_Array( - rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj, - SQArray* sqArray, - rapidjson_document* allocatorDoc); -void ServerSq_DecodeJSON_Table( - void* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj); -void ServerSq_DecodeJSON_Array( - void* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* arr); -void ClientSq_DecodeJSON_Table( - void* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj); -void ClientSq_DecodeJSON_Array( - void* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* arr); - -SQRESULT ServerSq_DecodeJSON(void* sqvm) -{ - const char* json = ServerSq_getstring(sqvm, 1); - rapidjson_document doc; - doc.Parse(json); - if (doc.HasParseError()) - { - ServerSq_newTable(sqvm); - return SQRESULT_NOTNULL; - } - ServerSq_newTable(sqvm); - - for (int i = 0; i < doc.MemberCount(); i++) - { - - rapidjson::GenericMember, rapidjson::MemoryPoolAllocator>& itr = doc.MemberBegin()[i]; - - switch (itr.value.GetType()) - { - case rapidjson::kObjectType: - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_DecodeJSON_Table( - sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr.value); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kArrayType: - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_DecodeJSON_Array( - sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr.value); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kStringType: - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_pushstring(sqvm, itr.value.GetString(), -1); - - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kTrueType: - if ((long long)itr.name.GetString() == -1) - { - spdlog::info("Neagative String decoding True"); - continue; - } - - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_pushbool(sqvm, true); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kFalseType: - if ((long long)itr.name.GetString() == -1) - { - - continue; - } - - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_pushbool(sqvm, false); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kNumberType: - if (itr.value.IsDouble() || itr.value.IsFloat()) - { - - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_pushfloat(sqvm, itr.value.GetFloat()); - } - else - { - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_pushinteger(sqvm, itr.value.GetInt()); - } - ServerSq_newSlot(sqvm, -3, false); - break; - } - } - return SQRESULT_NOTNULL; -} - -void ServerSq_DecodeJSON_Table( - void* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj) +template void +DecodeJsonArray(HSquirrelVM* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* arr) { - ServerSq_newTable(sqvm); - - for (int i = 0; i < obj->MemberCount(); i++) - { - rapidjson::GenericMember, rapidjson::MemoryPoolAllocator>& itr = obj->MemberBegin()[i]; - if (!itr.name.IsString()) - { - spdlog::info("Decoding table with non-string key"); - continue; - } - const char* key = itr.name.GetString(); - switch (itr.value.GetType()) - { - case rapidjson::kObjectType: - ServerSq_pushstring(sqvm, key, -1); - ServerSq_DecodeJSON_Table( - sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr.value); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kArrayType: - ServerSq_pushstring(sqvm, key, -1); - ServerSq_DecodeJSON_Array( - sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr.value); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kStringType: - ServerSq_pushstring(sqvm, key, -1); - ServerSq_pushstring(sqvm, itr.value.GetString(), -1); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kTrueType: - - ServerSq_pushstring(sqvm, key, -1); - ServerSq_pushbool(sqvm, true); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kFalseType: - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_pushbool(sqvm, false); - ServerSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kNumberType: - if (itr.value.IsDouble() || itr.value.IsFloat()) - { - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_pushfloat(sqvm, itr.value.GetFloat()); - } - else - { - ServerSq_pushstring(sqvm, itr.name.GetString(), -1); - ServerSq_pushinteger(sqvm, itr.value.GetInt()); - } - ServerSq_newSlot(sqvm, -3, false); - break; - } - } -} + g_pSquirrel->newarray(sqvm, 0); -void ServerSq_DecodeJSON_Array( - void* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* arr) -{ - int usedType = arr->GetArray().Begin()->GetType(); - bool isFloat = arr->GetArray().Begin()->IsDouble() || arr->GetArray().Begin()->IsFloat(); - ServerSq_newarray(sqvm, 0); for (auto& itr : arr->GetArray()) { switch (itr.GetType()) { case rapidjson::kObjectType: - - if (usedType != itr.GetType()) - continue; - ServerSq_DecodeJSON_Table(sqvm, &itr); - ServerSq_arrayappend(sqvm, -2); + DecodeJsonTable(sqvm, &itr); + g_pSquirrel->arrayappend(sqvm, -2); break; case rapidjson::kArrayType: - - if (usedType != itr.GetType()) - continue; - ServerSq_DecodeJSON_Array(sqvm, &itr); - ServerSq_arrayappend(sqvm, -2); + DecodeJsonArray(sqvm, &itr); + g_pSquirrel->arrayappend(sqvm, -2); break; case rapidjson::kStringType: - if ((long long)itr.GetString() == -1) - { - - continue; - } - if (usedType != itr.GetType()) - continue; - ServerSq_pushstring(sqvm, itr.GetString(), -1); - ServerSq_arrayappend(sqvm, -2); + g_pSquirrel->pushstring(sqvm, itr.GetString(), -1); + g_pSquirrel->arrayappend(sqvm, -2); break; case rapidjson::kTrueType: - if (usedType != rapidjson::kTrueType && usedType != rapidjson::kFalseType) - continue; - ServerSq_pushbool(sqvm, true); - ServerSq_arrayappend(sqvm, -2); - break; case rapidjson::kFalseType: - if (usedType != rapidjson::kTrueType && usedType != rapidjson::kFalseType) - continue; - ServerSq_pushbool(sqvm, false); - ServerSq_arrayappend(sqvm, -2); + g_pSquirrel->pushbool(sqvm, itr.GetBool()); + g_pSquirrel->arrayappend(sqvm, -2); break; case rapidjson::kNumberType: - if (usedType != itr.GetType()) - continue; if (itr.IsDouble() || itr.IsFloat()) - { - - if (!isFloat) - continue; - ServerSq_pushfloat(sqvm, itr.GetFloat()); - } + g_pSquirrel->pushfloat(sqvm, itr.GetFloat()); else - { - if (isFloat) - continue; - ServerSq_pushinteger(sqvm, itr.GetInt()); - } - ServerSq_arrayappend(sqvm, -2); + g_pSquirrel->pushinteger(sqvm, itr.GetInt()); + g_pSquirrel->arrayappend(sqvm, -2); break; } } } -SQRESULT ClientSq_DecodeJSON(void* sqvm) +template void +DecodeJsonTable(HSquirrelVM* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj) { - const char* json = ClientSq_getstring(sqvm, 1); - rapidjson_document doc; - doc.Parse(json); - if (doc.HasParseError()) - { - ClientSq_newTable(sqvm); - return SQRESULT_NOTNULL; - } - ClientSq_newTable(sqvm); + g_pSquirrel->newtable(sqvm); - for (int i = 0; i < doc.MemberCount(); i++) + for (auto itr = obj->MemberBegin(); itr != obj->MemberEnd(); itr++) { - - rapidjson::GenericMember, rapidjson::MemoryPoolAllocator>& itr = doc.MemberBegin()[i]; - - switch (itr.value.GetType()) + switch (itr->value.GetType()) { case rapidjson::kObjectType: - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_DecodeJSON_Table( - sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr.value); - ClientSq_newSlot(sqvm, -3, false); + g_pSquirrel->pushstring(sqvm, itr->name.GetString(), -1); + DecodeJsonTable( + sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr->value); + g_pSquirrel->newslot(sqvm, -3, false); break; case rapidjson::kArrayType: - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_DecodeJSON_Array( - sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr.value); - ClientSq_newSlot(sqvm, -3, false); + g_pSquirrel->pushstring(sqvm, itr->name.GetString(), -1); + DecodeJsonArray( + sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr->value); + g_pSquirrel->newslot(sqvm, -3, false); break; case rapidjson::kStringType: - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_pushstring(sqvm, itr.value.GetString(), -1); + g_pSquirrel->pushstring(sqvm, itr->name.GetString(), -1); + g_pSquirrel->pushstring(sqvm, itr->value.GetString(), -1); - ClientSq_newSlot(sqvm, -3, false); + g_pSquirrel->newslot(sqvm, -3, false); break; case rapidjson::kTrueType: - if ((long long)itr.name.GetString() == -1) - { - spdlog::info("Neagative String decoding True"); - continue; - } - - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_pushbool(sqvm, true); - ClientSq_newSlot(sqvm, -3, false); - break; case rapidjson::kFalseType: - if ((long long)itr.name.GetString() == -1) - { - - continue; - } - - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_pushbool(sqvm, false); - ClientSq_newSlot(sqvm, -3, false); + g_pSquirrel->pushstring(sqvm, itr->name.GetString(), -1); + g_pSquirrel->pushbool(sqvm, itr->value.GetBool()); + g_pSquirrel->newslot(sqvm, -3, false); break; case rapidjson::kNumberType: - if (itr.value.IsDouble() || itr.value.IsFloat()) + if (itr->value.IsDouble() || itr->value.IsFloat()) { - - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_pushfloat(sqvm, itr.value.GetFloat()); + g_pSquirrel->pushstring(sqvm, itr->name.GetString(), -1); + g_pSquirrel->pushfloat(sqvm, itr->value.GetFloat()); } else { - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_pushinteger(sqvm, itr.value.GetInt()); + g_pSquirrel->pushstring(sqvm, itr->name.GetString(), -1); + g_pSquirrel->pushinteger(sqvm, itr->value.GetInt()); } - ClientSq_newSlot(sqvm, -3, false); + g_pSquirrel->newslot(sqvm, -3, false); break; } } - return SQRESULT_NOTNULL; } -void ClientSq_DecodeJSON_Table( - void* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj) -{ - ClientSq_newTable(sqvm); - - for (int i = 0; i < obj->MemberCount(); i++) - { - rapidjson::GenericMember, rapidjson::MemoryPoolAllocator>& itr = obj->MemberBegin()[i]; - if (!itr.name.IsString()) - { - spdlog::info("Decoding table with non-string key"); - continue; - } - const char* key = itr.name.GetString(); - - switch (itr.value.GetType()) - { - case rapidjson::kObjectType: - ClientSq_pushstring(sqvm, key, -1); - ClientSq_DecodeJSON_Table( - sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr.value); - ClientSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kArrayType: - ClientSq_pushstring(sqvm, key, -1); - ClientSq_DecodeJSON_Array( - sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&itr.value); - ClientSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kStringType: - ClientSq_pushstring(sqvm, key, -1); - ClientSq_pushstring(sqvm, itr.value.GetString(), -1); - ClientSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kTrueType: - - ClientSq_pushstring(sqvm, key, -1); - ClientSq_pushbool(sqvm, true); - ClientSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kFalseType: - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_pushbool(sqvm, false); - ClientSq_newSlot(sqvm, -3, false); - break; - case rapidjson::kNumberType: - if (itr.value.IsDouble() || itr.value.IsFloat()) - { - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_pushfloat(sqvm, itr.value.GetFloat()); - } - else - { - ClientSq_pushstring(sqvm, itr.name.GetString(), -1); - ClientSq_pushinteger(sqvm, itr.value.GetInt()); - } - ClientSq_newSlot(sqvm, -3, false); - break; - } - } -} - -void ClientSq_DecodeJSON_Array( - void* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* arr) -{ - int usedType = arr->GetArray().Begin()->GetType(); - bool isFloat = arr->GetArray().Begin()->IsDouble() || arr->GetArray().Begin()->IsFloat(); - ClientSq_newarray(sqvm, 0); - for (auto& itr : arr->GetArray()) - { - switch (itr.GetType()) - { - case rapidjson::kObjectType: - - if (usedType != itr.GetType()) - continue; - ClientSq_DecodeJSON_Table(sqvm, &itr); - ClientSq_arrayappend(sqvm, -2); - break; - case rapidjson::kArrayType: - - if (usedType != itr.GetType()) - continue; - ClientSq_DecodeJSON_Array(sqvm, &itr); - ClientSq_arrayappend(sqvm, -2); - break; - case rapidjson::kStringType: - if ((long long)itr.GetString() == -1) - { - - continue; - } - if (usedType != itr.GetType()) - continue; - ClientSq_pushstring(sqvm, itr.GetString(), -1); - ClientSq_arrayappend(sqvm, -2); - break; - case rapidjson::kTrueType: - if (usedType != rapidjson::kTrueType && usedType != rapidjson::kFalseType) - continue; - ClientSq_pushbool(sqvm, true); - ClientSq_arrayappend(sqvm, -2); - break; - case rapidjson::kFalseType: - if (usedType != rapidjson::kTrueType && usedType != rapidjson::kFalseType) - continue; - ClientSq_pushbool(sqvm, false); - ClientSq_arrayappend(sqvm, -2); - break; - case rapidjson::kNumberType: - if (usedType != itr.GetType()) - continue; - if (itr.IsDouble() || itr.IsFloat()) - { - - if (!isFloat) - continue; - ClientSq_pushfloat(sqvm, itr.GetFloat()); - } - else - { - if (isFloat) - continue; - ClientSq_pushinteger(sqvm, itr.GetInt()); - } - ClientSq_arrayappend(sqvm, -2); - break; - } - } -} - -SQRESULT SQ_EncodeJSON(void* sqvm) -{ - rapidjson_document doc; - doc.SetObject(); - - HSquirrelVM* vm = (HSquirrelVM*)sqvm; - SQTable* table = vm->_stackOfCurrentFunction[1]._VAL.asTable; - SQ_EncodeJSON_Table(&doc, table, &doc); - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - doc.Accept(writer); - const char* json = buffer.GetString(); - - ServerSq_pushstring(sqvm, json, -1); - return SQRESULT_NOTNULL; -} - -void SQ_EncodeJSON_Table( - rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj, +template void EncodeJSONTable( SQTable* table, - rapidjson_document* allocatorDoc) + rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj, + rapidjson::MemoryPoolAllocator& allocator) { for (int i = 0; i < table->_numOfNodes; i++) { tableNode* node = &table->_nodes[i]; if (node->key._Type == OT_STRING) { - rapidjson::GenericValue, rapidjson::MemoryPoolAllocator> newObj(rapidjson::kObjectType); rapidjson::GenericValue, rapidjson::MemoryPoolAllocator> newArray(rapidjson::kArrayType); switch (node->val._Type) { case OT_STRING: - obj->AddMember( - rapidjson::StringRef(node->key._VAL.asString->_val), - rapidjson::StringRef(node->val._VAL.asString->_val), - allocatorDoc->GetAllocator()); + rapidjson::StringRef(node->key._VAL.asString->_val), rapidjson::StringRef(node->val._VAL.asString->_val), allocator); break; case OT_INTEGER: obj->AddMember( rapidjson::StringRef(node->key._VAL.asString->_val), rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>( (int)node->val._VAL.asInteger), - allocatorDoc->GetAllocator()); + allocator); break; case OT_FLOAT: - obj->AddMember( rapidjson::StringRef(node->key._VAL.asString->_val), rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>(node->val._VAL.asFloat), - allocatorDoc->GetAllocator()); + allocator); break; case OT_BOOL: if (node->val._VAL.asInteger) @@ -504,106 +135,148 @@ void SQ_EncodeJSON_Table( obj->AddMember( rapidjson::StringRef(node->key._VAL.asString->_val), rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>(true), - allocatorDoc->GetAllocator()); + allocator); } else { obj->AddMember( rapidjson::StringRef(node->key._VAL.asString->_val), rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>(false), - allocatorDoc->GetAllocator()); + allocator); } break; case OT_TABLE: - - SQ_EncodeJSON_Table(&newObj, node->val._VAL.asTable, allocatorDoc); - obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), newObj, allocatorDoc->GetAllocator()); + EncodeJSONTable(node->val._VAL.asTable, &newObj, allocator); + obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), newObj, allocator); break; case OT_ARRAY: - - SQ_EncodeJSON_Array(&newArray, node->val._VAL.asArray, allocatorDoc); - obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), newArray, allocatorDoc->GetAllocator()); + EncodeJSONArray(node->val._VAL.asArray, &newArray, allocator); + obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), newArray, allocator); break; default: - spdlog::info("SQ encode Json type {} not supported", sq_getTypeName(node->val._Type)); + spdlog::warn("SQ_EncodeJSON: squirrel type {} not supported", SQTypeNameFromID(node->val._Type)); break; } } } } -void SQ_EncodeJSON_Array( +template void EncodeJSONArray( + SQArray* arr, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj, - SQArray* sqArray, - rapidjson_document* allocatorDoc) + rapidjson::MemoryPoolAllocator& allocator) { - int usedType = sqArray->_values->_Type; - for (int i = 0; i < sqArray->_usedSlots; i++) + for (int i = 0; i < arr->_usedSlots; i++) { - SQObject* node = &sqArray->_values[i]; - if (node->_Type != usedType) - { - const char* typeName = sq_getTypeName(node->_Type); - const char* usedTypeName = sq_getTypeName(usedType); - spdlog::info("SQ encode Json array not same type got %s expected %s", typeName, usedTypeName); - continue; - } + SQObject* node = &arr->_values[i]; + rapidjson::GenericValue, rapidjson::MemoryPoolAllocator> newObj(rapidjson::kObjectType); rapidjson::GenericValue, rapidjson::MemoryPoolAllocator> newArray(rapidjson::kArrayType); + switch (node->_Type) { case OT_STRING: - obj->PushBack(rapidjson::StringRef(node->_VAL.asString->_val), allocatorDoc->GetAllocator()); + obj->PushBack(rapidjson::StringRef(node->_VAL.asString->_val), allocator); break; case OT_INTEGER: obj->PushBack( rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>((int)node->_VAL.asInteger), - allocatorDoc->GetAllocator()); + allocator); break; case OT_FLOAT: obj->PushBack( rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>(node->_VAL.asFloat), - allocatorDoc->GetAllocator()); + allocator); break; case OT_BOOL: if (node->_VAL.asInteger) - { - obj->PushBack(rapidjson::StringRef("true"), allocatorDoc->GetAllocator()); - } + obj->PushBack(rapidjson::StringRef("true"), allocator); else - { - obj->PushBack(rapidjson::StringRef("false"), allocatorDoc->GetAllocator()); - } + obj->PushBack(rapidjson::StringRef("false"), allocator); break; case OT_TABLE: - - SQ_EncodeJSON_Table(&newObj, node->_VAL.asTable, allocatorDoc); - obj->PushBack(newObj, allocatorDoc->GetAllocator()); + EncodeJSONTable(node->_VAL.asTable, &newObj, allocator); + obj->PushBack(newObj, allocator); break; case OT_ARRAY: - - SQ_EncodeJSON_Array(&newArray, node->_VAL.asArray, allocatorDoc); - obj->PushBack(newArray, allocatorDoc->GetAllocator()); + EncodeJSONArray(node->_VAL.asArray, &newArray, allocator); + obj->PushBack(newArray, allocator); break; default: - - spdlog::info("SQ encode Json type {} not supported", sq_getTypeName(node->_Type)); + spdlog::info("SQ encode Json type {} not supported", SQTypeNameFromID(node->_Type)); } } } -void InitialiseServerSquirrelJson(HMODULE baseAddress) +// table function DecodeJSON( string json, bool fatalParseErrors = false ) +template SQRESULT SQ_DecodeJSON(HSquirrelVM* sqvm) { + const char* pJson = g_pSquirrel->getstring(sqvm, 1); + const bool bFatalParseErrors = g_pSquirrel->getbool(sqvm, 2); + + rapidjson_document doc; + doc.Parse(pJson); + if (doc.HasParseError()) + { + g_pSquirrel->newtable(sqvm); + + std::string sErrorString = fmt::format( + "Failed parsing json file: encountered parse error \"{}\" at offset {}", + GetParseError_En(doc.GetParseError()), + doc.GetErrorOffset()); - g_ServerSquirrelManager->AddFuncRegistration("table", "DecodeJSON", "string json", "", ServerSq_DecodeJSON); - g_ServerSquirrelManager->AddFuncRegistration("string", "EncodeJSON", "table data", "", SQ_EncodeJSON); + if (bFatalParseErrors) + g_pSquirrel->raiseerror(sqvm, sErrorString.c_str()); + else + spdlog::warn(sErrorString); + + return SQRESULT_NOTNULL; + } + + DecodeJsonTable(sqvm, (rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>*)&doc); } -void InitialiseClientSquirrelJson(HMODULE baseAddress) +// string function EncodeJSON( table data ) +template SQRESULT SQ_EncodeJSON(HSquirrelVM* sqvm) { - g_ClientSquirrelManager->AddFuncRegistration("table", "DecodeJSON", "string json", "", ClientSq_DecodeJSON); - g_ClientSquirrelManager->AddFuncRegistration("string", "EncodeJSON", "table data", "", SQ_EncodeJSON); + rapidjson_document doc; + doc.SetObject(); - g_UISquirrelManager->AddFuncRegistration("table", "DecodeJSON", "string json", "", ClientSq_DecodeJSON); - g_UISquirrelManager->AddFuncRegistration("string", "EncodeJSON", "table data", "", SQ_EncodeJSON); + // temp until this is just the func parameter type + HSquirrelVM* vm = (HSquirrelVM*)sqvm; + SQTable* table = vm->_stackOfCurrentFunction[1]._VAL.asTable; + EncodeJSONTable(table, &doc, doc.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + const char* pJsonString = buffer.GetString(); + + g_pSquirrel->pushstring(sqvm, pJsonString, -1); + return SQRESULT_NOTNULL; +} + +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientScriptJSON, ClientSquirrel, (CModule module)) +{ + g_pSquirrel->AddFuncRegistration( + "table", + "DecodeJSON", + "string json, bool fatalParseErrors = false", + "converts a json string to a squirrel table", + SQ_DecodeJSON); + g_pSquirrel->AddFuncRegistration( + "string", "EncodeJSON", "table data", "converts a squirrel table to a json string", SQ_EncodeJSON); + + g_pSquirrel->AddFuncRegistration( + "table", "DecodeJSON", "string json", "converts a json string to a squirrel table", SQ_DecodeJSON); + g_pSquirrel->AddFuncRegistration( + "string", "EncodeJSON", "table data", "converts a squirrel table to a json string", SQ_EncodeJSON); +} + +ON_DLL_LOAD_RELIESON("server.dll", ServerScriptJSON, ServerSquirrel, (CModule module)) +{ + g_pSquirrel->AddFuncRegistration( + "table", "DecodeJSON", "string json", "converts a json string to a squirrel table", SQ_DecodeJSON); + g_pSquirrel->AddFuncRegistration( + "string", "EncodeJSON", "table data", "converts a squirrel table to a json string", SQ_EncodeJSON); } diff --git a/NorthstarDLL/scriptjson.h b/NorthstarDLL/scriptjson.h deleted file mode 100644 index 5ee7400e..00000000 --- a/NorthstarDLL/scriptjson.h +++ /dev/null @@ -1,2 +0,0 @@ -void InitialiseServerSquirrelJson(HMODULE baseAddress); -void InitialiseClientSquirrelJson(HMODULE baseAddress); diff --git a/NorthstarDLL/scriptmainmenupromos.cpp b/NorthstarDLL/scriptmainmenupromos.cpp index e26d1df6..a1277235 100644 --- a/NorthstarDLL/scriptmainmenupromos.cpp +++ b/NorthstarDLL/scriptmainmenupromos.cpp @@ -1,5 +1,4 @@ #include "pch.h" -#include "scriptmainmenupromos.h" #include "squirrel.h" #include "masterserver.h" @@ -25,102 +24,102 @@ enum eMainMenuPromoDataProperty }; // void function NSRequestCustomMainMenuPromos() -SQRESULT SQ_RequestCustomMainMenuPromos(void* sqvm) +SQRESULT SQ_RequestCustomMainMenuPromos(HSquirrelVM* sqvm) { - g_MasterServerManager->RequestMainMenuPromos(); + g_pMasterServerManager->RequestMainMenuPromos(); return SQRESULT_NULL; } // bool function NSHasCustomMainMenuPromoData() -SQRESULT SQ_HasCustomMainMenuPromoData(void* sqvm) +SQRESULT SQ_HasCustomMainMenuPromoData(HSquirrelVM* sqvm) { - ClientSq_pushbool(sqvm, g_MasterServerManager->m_bHasMainMenuPromoData); + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bHasMainMenuPromoData); return SQRESULT_NOTNULL; } // var function NSGetCustomMainMenuPromoData( int promoDataKey ) -SQRESULT SQ_GetCustomMainMenuPromoData(void* sqvm) +SQRESULT SQ_GetCustomMainMenuPromoData(HSquirrelVM* sqvm) { - if (!g_MasterServerManager->m_bHasMainMenuPromoData) + if (!g_pMasterServerManager->m_bHasMainMenuPromoData) return SQRESULT_NULL; - switch (ClientSq_getinteger(sqvm, 1)) + switch (g_pSquirrel->getinteger(sqvm, 1)) { case eMainMenuPromoDataProperty::newInfoTitle1: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.newInfoTitle1.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle1.c_str()); break; } case eMainMenuPromoDataProperty::newInfoTitle2: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.newInfoTitle2.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle2.c_str()); break; } case eMainMenuPromoDataProperty::newInfoTitle3: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.newInfoTitle3.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle3.c_str()); break; } case eMainMenuPromoDataProperty::largeButtonTitle: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.largeButtonTitle.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonTitle.c_str()); break; } case eMainMenuPromoDataProperty::largeButtonText: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.largeButtonText.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonText.c_str()); break; } case eMainMenuPromoDataProperty::largeButtonUrl: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.largeButtonUrl.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonUrl.c_str()); break; } case eMainMenuPromoDataProperty::largeButtonImageIndex: { - ClientSq_pushinteger(sqvm, g_MasterServerManager->m_sMainMenuPromoData.largeButtonImageIndex); + g_pSquirrel->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonImageIndex); break; } case eMainMenuPromoDataProperty::smallButton1Title: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton1Title.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1Title.c_str()); break; } case eMainMenuPromoDataProperty::smallButton1Url: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton1Url.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1Url.c_str()); break; } case eMainMenuPromoDataProperty::smallButton1ImageIndex: { - ClientSq_pushinteger(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton1ImageIndex); + g_pSquirrel->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1ImageIndex); break; } case eMainMenuPromoDataProperty::smallButton2Title: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton2Title.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2Title.c_str()); break; } case eMainMenuPromoDataProperty::smallButton2Url: { - ClientSq_pushstring(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton2Url.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2Url.c_str()); break; } case eMainMenuPromoDataProperty::smallButton2ImageIndex: { - ClientSq_pushinteger(sqvm, g_MasterServerManager->m_sMainMenuPromoData.smallButton2ImageIndex); + g_pSquirrel->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2ImageIndex); break; } } @@ -128,9 +127,10 @@ SQRESULT SQ_GetCustomMainMenuPromoData(void* sqvm) return SQRESULT_NOTNULL; } -void InitialiseScriptMainMenuPromos(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ScriptMainMenuPromos, ClientSquirrel, (CModule module)) { - g_UISquirrelManager->AddFuncRegistration("void", "NSRequestCustomMainMenuPromos", "", "", SQ_RequestCustomMainMenuPromos); - g_UISquirrelManager->AddFuncRegistration("bool", "NSHasCustomMainMenuPromoData", "", "", SQ_HasCustomMainMenuPromoData); - g_UISquirrelManager->AddFuncRegistration("var", "NSGetCustomMainMenuPromoData", "int promoDataKey", "", SQ_GetCustomMainMenuPromoData); + g_pSquirrel->AddFuncRegistration("void", "NSRequestCustomMainMenuPromos", "", "", SQ_RequestCustomMainMenuPromos); + g_pSquirrel->AddFuncRegistration("bool", "NSHasCustomMainMenuPromoData", "", "", SQ_HasCustomMainMenuPromoData); + g_pSquirrel->AddFuncRegistration( + "var", "NSGetCustomMainMenuPromoData", "int promoDataKey", "", SQ_GetCustomMainMenuPromoData); } diff --git a/NorthstarDLL/scriptmainmenupromos.h b/NorthstarDLL/scriptmainmenupromos.h deleted file mode 100644 index f0aa6332..00000000 --- a/NorthstarDLL/scriptmainmenupromos.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseScriptMainMenuPromos(HMODULE baseAddress); diff --git a/NorthstarDLL/scriptmodmenu.cpp b/NorthstarDLL/scriptmodmenu.cpp index 767ede91..d2f35285 100644 --- a/NorthstarDLL/scriptmodmenu.cpp +++ b/NorthstarDLL/scriptmodmenu.cpp @@ -1,33 +1,32 @@ #include "pch.h" -#include "scriptmodmenu.h" #include "modmanager.h" #include "squirrel.h" // array function NSGetModNames() -SQRESULT SQ_GetModNames(void* sqvm) +SQRESULT SQ_GetModNames(HSquirrelVM* sqvm) { - ClientSq_newarray(sqvm, 0); + g_pSquirrel->newarray(sqvm, 0); - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { - ClientSq_pushstring(sqvm, mod.Name.c_str(), -1); - ClientSq_arrayappend(sqvm, -2); + g_pSquirrel->pushstring(sqvm, mod.Name.c_str()); + g_pSquirrel->arrayappend(sqvm, -2); } return SQRESULT_NOTNULL; } // bool function NSIsModEnabled(string modName) -SQRESULT SQ_IsModEnabled(void* sqvm) +SQRESULT SQ_IsModEnabled(HSquirrelVM* sqvm) { - const SQChar* modName = ClientSq_getstring(sqvm, 1); + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { - ClientSq_pushbool(sqvm, mod.Enabled); + g_pSquirrel->pushbool(sqvm, mod.m_bEnabled); return SQRESULT_NOTNULL; } } @@ -36,17 +35,17 @@ SQRESULT SQ_IsModEnabled(void* sqvm) } // void function NSSetModEnabled(string modName, bool enabled) -SQRESULT SQ_SetModEnabled(void* sqvm) +SQRESULT SQ_SetModEnabled(HSquirrelVM* sqvm) { - const SQChar* modName = ClientSq_getstring(sqvm, 1); - const SQBool enabled = ClientSq_getbool(sqvm, 2); + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + const SQBool enabled = g_pSquirrel->getbool(sqvm, 2); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { - mod.Enabled = enabled; + mod.m_bEnabled = enabled; return SQRESULT_NULL; } } @@ -55,16 +54,16 @@ SQRESULT SQ_SetModEnabled(void* sqvm) } // string function NSGetModDescriptionByModName(string modName) -SQRESULT SQ_GetModDescription(void* sqvm) +SQRESULT SQ_GetModDescription(HSquirrelVM* sqvm) { - const SQChar* modName = ClientSq_getstring(sqvm, 1); + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { - ClientSq_pushstring(sqvm, mod.Description.c_str(), -1); + g_pSquirrel->pushstring(sqvm, mod.Description.c_str()); return SQRESULT_NOTNULL; } } @@ -73,16 +72,16 @@ SQRESULT SQ_GetModDescription(void* sqvm) } // string function NSGetModVersionByModName(string modName) -SQRESULT SQ_GetModVersion(void* sqvm) +SQRESULT SQ_GetModVersion(HSquirrelVM* sqvm) { - const SQChar* modName = ClientSq_getstring(sqvm, 1); + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { - ClientSq_pushstring(sqvm, mod.Version.c_str(), -1); + g_pSquirrel->pushstring(sqvm, mod.Version.c_str()); return SQRESULT_NOTNULL; } } @@ -91,16 +90,16 @@ SQRESULT SQ_GetModVersion(void* sqvm) } // string function NSGetModDownloadLinkByModName(string modName) -SQRESULT SQ_GetModDownloadLink(void* sqvm) +SQRESULT SQ_GetModDownloadLink(HSquirrelVM* sqvm) { - const SQChar* modName = ClientSq_getstring(sqvm, 1); + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { - ClientSq_pushstring(sqvm, mod.DownloadLink.c_str(), -1); + g_pSquirrel->pushstring(sqvm, mod.DownloadLink.c_str()); return SQRESULT_NOTNULL; } } @@ -109,16 +108,16 @@ SQRESULT SQ_GetModDownloadLink(void* sqvm) } // int function NSGetModLoadPriority(string modName) -SQRESULT SQ_GetModLoadPriority(void* sqvm) +SQRESULT SQ_GetModLoadPriority(HSquirrelVM* sqvm) { - const SQChar* modName = ClientSq_getstring(sqvm, 1); + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { - ClientSq_pushinteger(sqvm, mod.LoadPriority); + g_pSquirrel->pushinteger(sqvm, mod.LoadPriority); return SQRESULT_NOTNULL; } } @@ -127,16 +126,16 @@ SQRESULT SQ_GetModLoadPriority(void* sqvm) } // bool function NSIsModRequiredOnClient(string modName) -SQRESULT SQ_IsModRequiredOnClient(void* sqvm) +SQRESULT SQ_IsModRequiredOnClient(HSquirrelVM* sqvm) { - const SQChar* modName = ClientSq_getstring(sqvm, 1); + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { - ClientSq_pushbool(sqvm, mod.RequiredOnClient); + g_pSquirrel->pushbool(sqvm, mod.RequiredOnClient); return SQRESULT_NOTNULL; } } @@ -145,20 +144,20 @@ SQRESULT SQ_IsModRequiredOnClient(void* sqvm) } // array function NSGetModConvarsByModName(string modName) -SQRESULT SQ_GetModConvars(void* sqvm) +SQRESULT SQ_GetModConvars(HSquirrelVM* sqvm) { - const SQChar* modName = ClientSq_getstring(sqvm, 1); - ClientSq_newarray(sqvm, 0); + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + g_pSquirrel->newarray(sqvm, 0); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_ModManager->m_loadedMods) + for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { for (ModConVar* cvar : mod.ConVars) { - ClientSq_pushstring(sqvm, cvar->Name.c_str(), -1); - ClientSq_arrayappend(sqvm, -2); + g_pSquirrel->pushstring(sqvm, cvar->Name.c_str()); + g_pSquirrel->arrayappend(sqvm, -2); } return SQRESULT_NOTNULL; @@ -169,35 +168,36 @@ SQRESULT SQ_GetModConvars(void* sqvm) } // void function NSReloadMods() -SQRESULT SQ_ReloadMods(void* sqvm) +SQRESULT SQ_ReloadMods(HSquirrelVM* sqvm) { - g_ModManager->LoadMods(); + g_pModManager->LoadMods(); return SQRESULT_NULL; } -void InitialiseScriptModMenu(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ScriptModMenu, ClientSquirrel, (CModule module)) { - g_UISquirrelManager->AddFuncRegistration("array", "NSGetModNames", "", "Returns the names of all loaded mods", SQ_GetModNames); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( + "array", "NSGetModNames", "", "Returns the names of all loaded mods", SQ_GetModNames); + g_pSquirrel->AddFuncRegistration( "bool", "NSIsModEnabled", "string modName", "Returns whether a given mod is enabled", SQ_IsModEnabled); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "void", "NSSetModEnabled", "string modName, bool enabled", "Sets whether a given mod is enabled", SQ_SetModEnabled); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "string", "NSGetModDescriptionByModName", "string modName", "Returns a given mod's description", SQ_GetModDescription); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "string", "NSGetModVersionByModName", "string modName", "Returns a given mod's version", SQ_GetModVersion); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "string", "NSGetModDownloadLinkByModName", "string modName", "Returns a given mod's download link", SQ_GetModDownloadLink); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "bool", "NSIsModRequiredOnClient", "string modName", "Returns whether a given mod is required on connecting clients", SQ_IsModRequiredOnClient); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "int", "NSGetModLoadPriority", "string modName", "Returns a given mod's load priority", SQ_GetModLoadPriority); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "array", "NSGetModConvarsByModName", "string modName", "Returns the names of all a given mod's cvars", SQ_GetModConvars); - g_UISquirrelManager->AddFuncRegistration("void", "NSReloadMods", "", "Reloads mods", SQ_ReloadMods); + g_pSquirrel->AddFuncRegistration("void", "NSReloadMods", "", "Reloads mods", SQ_ReloadMods); } diff --git a/NorthstarDLL/scriptmodmenu.h b/NorthstarDLL/scriptmodmenu.h deleted file mode 100644 index e7f71b11..00000000 --- a/NorthstarDLL/scriptmodmenu.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseScriptModMenu(HMODULE baseAddress); diff --git a/NorthstarDLL/scriptserverbrowser.cpp b/NorthstarDLL/scriptserverbrowser.cpp index 2a366b3f..5b2cf75f 100644 --- a/NorthstarDLL/scriptserverbrowser.cpp +++ b/NorthstarDLL/scriptserverbrowser.cpp @@ -1,383 +1,379 @@ #include "pch.h" -#include "scriptserverbrowser.h" #include "squirrel.h" #include "masterserver.h" -#include "gameutils.h" #include "serverauthentication.h" +#include "r2engine.h" +#include "r2client.h" // functions for viewing server browser // bool function NSIsMasterServerAuthenticated() -SQRESULT SQ_IsMasterServerAuthenticated(void* sqvm) +SQRESULT SQ_IsMasterServerAuthenticated(HSquirrelVM* sqvm) { - ClientSq_pushbool(sqvm, g_MasterServerManager->m_bOriginAuthWithMasterServerDone); + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bOriginAuthWithMasterServerDone); return SQRESULT_NOTNULL; } // void function NSRequestServerList() -SQRESULT SQ_RequestServerList(void* sqvm) +SQRESULT SQ_RequestServerList(HSquirrelVM* sqvm) { - g_MasterServerManager->RequestServerList(); + g_pMasterServerManager->RequestServerList(); return SQRESULT_NULL; } // bool function NSIsRequestingServerList() -SQRESULT SQ_IsRequestingServerList(void* sqvm) +SQRESULT SQ_IsRequestingServerList(HSquirrelVM* sqvm) { - ClientSq_pushbool(sqvm, g_MasterServerManager->m_bScriptRequestingServerList); + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bScriptRequestingServerList); return SQRESULT_NOTNULL; } // bool function NSMasterServerConnectionSuccessful() -SQRESULT SQ_MasterServerConnectionSuccessful(void* sqvm) +SQRESULT SQ_MasterServerConnectionSuccessful(HSquirrelVM* sqvm) { - ClientSq_pushbool(sqvm, g_MasterServerManager->m_bSuccessfullyConnected); + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bSuccessfullyConnected); return SQRESULT_NOTNULL; } // int function NSGetServerCount() -SQRESULT SQ_GetServerCount(void* sqvm) +SQRESULT SQ_GetServerCount(HSquirrelVM* sqvm) { - ClientSq_pushinteger(sqvm, g_MasterServerManager->m_vRemoteServers.size()); + g_pSquirrel->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers.size()); return SQRESULT_NOTNULL; } // string function NSGetServerName( int serverIndex ) -SQRESULT SQ_GetServerName(void* sqvm) +SQRESULT SQ_GetServerName(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].name, -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].name); return SQRESULT_NOTNULL; } // string function NSGetServerDescription( int serverIndex ) -SQRESULT SQ_GetServerDescription(void* sqvm) +SQRESULT SQ_GetServerDescription(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].description.c_str(), -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].description.c_str()); return SQRESULT_NOTNULL; } // string function NSGetServerMap( int serverIndex ) -SQInteger SQ_GetServerMap(void* sqvm) +SQRESULT SQ_GetServerMap(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].map, -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].map); return SQRESULT_NOTNULL; } // string function NSGetServerPlaylist( int serverIndex ) -SQRESULT SQ_GetServerPlaylist(void* sqvm) +SQRESULT SQ_GetServerPlaylist(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].playlist, -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].playlist); return SQRESULT_NOTNULL; } // int function NSGetServerPlayerCount( int serverIndex ) -SQRESULT SQ_GetServerPlayerCount(void* sqvm) +SQRESULT SQ_GetServerPlayerCount(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushinteger(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].playerCount); + g_pSquirrel->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].playerCount); return SQRESULT_NOTNULL; } // int function NSGetServerMaxPlayerCount( int serverIndex ) -SQRESULT SQ_GetServerMaxPlayerCount(void* sqvm) +SQRESULT SQ_GetServerMaxPlayerCount(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushinteger(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].maxPlayers); + g_pSquirrel->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].maxPlayers); return SQRESULT_NOTNULL; } // string function NSGetServerID( int serverIndex ) -SQRESULT SQ_GetServerID(void* sqvm) +SQRESULT SQ_GetServerID(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].id, -1); + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].id); return SQRESULT_NOTNULL; } // bool function NSServerRequiresPassword( int serverIndex ) -SQRESULT SQ_ServerRequiresPassword(void* sqvm) +SQRESULT SQ_ServerRequiresPassword(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushbool(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].requiresPassword); + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiresPassword); return SQRESULT_NOTNULL; } // int function NSGetServerRequiredModsCount( int serverIndex ) -SQRESULT SQ_GetServerRequiredModsCount(void* sqvm) +SQRESULT SQ_GetServerRequiredModsCount(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushinteger(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()); + g_pSquirrel->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()); return SQRESULT_NOTNULL; } // string function NSGetServerRequiredModName( int serverIndex, int modIndex ) -SQRESULT SQ_GetServerRequiredModName(void* sqvm) +SQRESULT SQ_GetServerRequiredModName(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); - SQInteger modIndex = ClientSq_getinteger(sqvm, 2); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); + SQInteger modIndex = g_pSquirrel->getinteger(sqvm, 2); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Name.c_str(), -1); + g_pSquirrel->pushstring( + sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Name.c_str()); return SQRESULT_NOTNULL; } // string function NSGetServerRequiredModVersion( int serverIndex, int modIndex ) -SQRESULT SQ_GetServerRequiredModVersion(void* sqvm) +SQRESULT SQ_GetServerRequiredModVersion(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); - SQInteger modIndex = ClientSq_getinteger(sqvm, 2); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); + SQInteger modIndex = g_pSquirrel->getinteger(sqvm, 2); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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()) { - ClientSq_pusherror( + g_pSquirrel->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; } - ClientSq_pushstring(sqvm, g_MasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Version.c_str(), -1); + g_pSquirrel->pushstring( + sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Version.c_str()); return SQRESULT_NOTNULL; } // void function NSClearRecievedServerList() -SQRESULT SQ_ClearRecievedServerList(void* sqvm) +SQRESULT SQ_ClearRecievedServerList(HSquirrelVM* sqvm) { - g_MasterServerManager->ClearServerList(); + g_pMasterServerManager->ClearServerList(); return SQRESULT_NULL; } // functions for authenticating with servers // void function NSTryAuthWithServer( int serverIndex, string password = "" ) -SQRESULT SQ_TryAuthWithServer(void* sqvm) +SQRESULT SQ_TryAuthWithServer(HSquirrelVM* sqvm) { - SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); - const SQChar* password = ClientSq_getstring(sqvm, 2); + SQInteger serverIndex = g_pSquirrel->getinteger(sqvm, 1); + const SQChar* password = g_pSquirrel->getstring(sqvm, 2); - if (serverIndex >= g_MasterServerManager->m_vRemoteServers.size()) + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) { - ClientSq_pusherror( + g_pSquirrel->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_ServerAuthenticationManager->m_additionalPlayerData) - g_ServerAuthenticationManager->WritePersistentData(pair.first); + for (auto& pair : g_pServerAuthentication->m_PlayerAuthenticationData) + g_pServerAuthentication->WritePersistentData(pair.first); // do auth - g_MasterServerManager->AuthenticateWithServer( - g_LocalPlayerUserID, - g_MasterServerManager->m_sOwnClientAuthToken, - g_MasterServerManager->m_vRemoteServers[serverIndex].id, + g_pMasterServerManager->AuthenticateWithServer( + R2::g_pLocalPlayerUserID, + g_pMasterServerManager->m_sOwnClientAuthToken, + g_pMasterServerManager->m_vRemoteServers[serverIndex].id, (char*)password); return SQRESULT_NULL; } // bool function NSIsAuthenticatingWithServer() -SQRESULT SQ_IsAuthComplete(void* sqvm) +SQRESULT SQ_IsAuthComplete(HSquirrelVM* sqvm) { - ClientSq_pushbool(sqvm, g_MasterServerManager->m_bScriptAuthenticatingWithGameServer); + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bScriptAuthenticatingWithGameServer); return SQRESULT_NOTNULL; } // bool function NSWasAuthSuccessful() -SQRESULT SQ_WasAuthSuccessful(void* sqvm) +SQRESULT SQ_WasAuthSuccessful(HSquirrelVM* sqvm) { - ClientSq_pushbool(sqvm, g_MasterServerManager->m_bSuccessfullyAuthenticatedWithGameServer); - return SQRESULT_NOTNULL; -} - -// bool function NSWasAuthSuccessful() -SQRESULT SQ_GetAuthFailReason(void* sqvm) -{ - ClientSq_pushstring(sqvm, g_MasterServerManager->s_authfail_reason.c_str(), -1); + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bSuccessfullyAuthenticatedWithGameServer); return SQRESULT_NOTNULL; } // void function NSConnectToAuthedServer() -SQRESULT SQ_ConnectToAuthedServer(void* sqvm) +SQRESULT SQ_ConnectToAuthedServer(HSquirrelVM* sqvm) { - if (!g_MasterServerManager->m_bHasPendingConnectionInfo) + if (!g_pMasterServerManager->m_bHasPendingConnectionInfo) { - ClientSq_pusherror(sqvm, fmt::format("Tried to connect to authed server before any pending connection info was available").c_str()); + g_pSquirrel->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 - Cbuf_AddText(Cbuf_GetCurrentPlayer(), fmt::format("serverfilter {}", info.authToken).c_str(), cmd_source_t::kCommandSrcCode); - Cbuf_AddText( - Cbuf_GetCurrentPlayer(), + R2::g_pCVar->FindVar("serverfilter")->SetValue(info.authToken); + R2::Cbuf_AddText( + R2::Cbuf_GetCurrentPlayer(), fmt::format( "connect {}.{}.{}.{}:{}", info.ip.S_un.S_un_b.s_b1, @@ -386,65 +382,74 @@ SQRESULT SQ_ConnectToAuthedServer(void* sqvm) info.ip.S_un.S_un_b.s_b4, info.port) .c_str(), - cmd_source_t::kCommandSrcCode); + R2::cmd_source_t::kCommandSrcCode); - g_MasterServerManager->m_bHasPendingConnectionInfo = false; + g_pMasterServerManager->m_bHasPendingConnectionInfo = false; return SQRESULT_NULL; } // void function NSTryAuthWithLocalServer() -SQRESULT SQ_TryAuthWithLocalServer(void* sqvm) +SQRESULT SQ_TryAuthWithLocalServer(HSquirrelVM* sqvm) { // do auth request - g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, g_MasterServerManager->m_sOwnClientAuthToken); + g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken); return SQRESULT_NULL; } // void function NSCompleteAuthWithLocalServer() -SQRESULT SQ_CompleteAuthWithLocalServer(void* sqvm) +SQRESULT SQ_CompleteAuthWithLocalServer(HSquirrelVM* sqvm) { // literally just set serverfilter // note: this assumes we have no authdata other than our own - Cbuf_AddText( - Cbuf_GetCurrentPlayer(), - fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(), - cmd_source_t::kCommandSrcCode); + if (g_pServerAuthentication->m_RemoteAuthenticationData.size()) + R2::g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str()); return SQRESULT_NULL; } -void InitialiseScriptServerBrowser(HMODULE baseAddress) +// string function NSGetAuthFailReason() +SQRESULT SQ_GetAuthFailReason(HSquirrelVM* sqvm) +{ + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sAuthFailureReason.c_str(), -1); + return SQRESULT_NOTNULL; +} + +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ScriptServerBrowser, ClientSquirrel, (CModule module)) { - g_UISquirrelManager->AddFuncRegistration("bool", "NSIsMasterServerAuthenticated", "", "", SQ_IsMasterServerAuthenticated); - g_UISquirrelManager->AddFuncRegistration("void", "NSRequestServerList", "", "", SQ_RequestServerList); - g_UISquirrelManager->AddFuncRegistration("bool", "NSIsRequestingServerList", "", "", SQ_IsRequestingServerList); - g_UISquirrelManager->AddFuncRegistration("bool", "NSMasterServerConnectionSuccessful", "", "", SQ_MasterServerConnectionSuccessful); - g_UISquirrelManager->AddFuncRegistration("int", "NSGetServerCount", "", "", SQ_GetServerCount); - g_UISquirrelManager->AddFuncRegistration("void", "NSClearRecievedServerList", "", "", SQ_ClearRecievedServerList); - - g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerName", "int serverIndex", "", SQ_GetServerName); - g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerDescription", "int serverIndex", "", SQ_GetServerDescription); - g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerMap", "int serverIndex", "", SQ_GetServerMap); - g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerPlaylist", "int serverIndex", "", SQ_GetServerPlaylist); - g_UISquirrelManager->AddFuncRegistration("int", "NSGetServerPlayerCount", "int serverIndex", "", SQ_GetServerPlayerCount); - g_UISquirrelManager->AddFuncRegistration("int", "NSGetServerMaxPlayerCount", "int serverIndex", "", SQ_GetServerMaxPlayerCount); - g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerID", "int serverIndex", "", SQ_GetServerID); - g_UISquirrelManager->AddFuncRegistration("bool", "NSServerRequiresPassword", "int serverIndex", "", SQ_ServerRequiresPassword); - g_UISquirrelManager->AddFuncRegistration("int", "NSGetServerRequiredModsCount", "int serverIndex", "", SQ_GetServerRequiredModsCount); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration("bool", "NSIsMasterServerAuthenticated", "", "", SQ_IsMasterServerAuthenticated); + g_pSquirrel->AddFuncRegistration("void", "NSRequestServerList", "", "", SQ_RequestServerList); + g_pSquirrel->AddFuncRegistration("bool", "NSIsRequestingServerList", "", "", SQ_IsRequestingServerList); + g_pSquirrel->AddFuncRegistration( + "bool", "NSMasterServerConnectionSuccessful", "", "", SQ_MasterServerConnectionSuccessful); + g_pSquirrel->AddFuncRegistration("int", "NSGetServerCount", "", "", SQ_GetServerCount); + g_pSquirrel->AddFuncRegistration("void", "NSClearRecievedServerList", "", "", SQ_ClearRecievedServerList); + + g_pSquirrel->AddFuncRegistration("string", "NSGetServerName", "int serverIndex", "", SQ_GetServerName); + g_pSquirrel->AddFuncRegistration("string", "NSGetServerDescription", "int serverIndex", "", SQ_GetServerDescription); + g_pSquirrel->AddFuncRegistration("string", "NSGetServerMap", "int serverIndex", "", SQ_GetServerMap); + g_pSquirrel->AddFuncRegistration("string", "NSGetServerPlaylist", "int serverIndex", "", SQ_GetServerPlaylist); + g_pSquirrel->AddFuncRegistration("int", "NSGetServerPlayerCount", "int serverIndex", "", SQ_GetServerPlayerCount); + g_pSquirrel->AddFuncRegistration( + "int", "NSGetServerMaxPlayerCount", "int serverIndex", "", SQ_GetServerMaxPlayerCount); + g_pSquirrel->AddFuncRegistration("string", "NSGetServerID", "int serverIndex", "", SQ_GetServerID); + g_pSquirrel->AddFuncRegistration( + "bool", "NSServerRequiresPassword", "int serverIndex", "", SQ_ServerRequiresPassword); + g_pSquirrel->AddFuncRegistration( + "int", "NSGetServerRequiredModsCount", "int serverIndex", "", SQ_GetServerRequiredModsCount); + g_pSquirrel->AddFuncRegistration( "string", "NSGetServerRequiredModName", "int serverIndex, int modIndex", "", SQ_GetServerRequiredModName); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "string", "NSGetServerRequiredModVersion", "int serverIndex, int modIndex", "", SQ_GetServerRequiredModVersion); - g_UISquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( "void", "NSTryAuthWithServer", "int serverIndex, string password = \"\"", "", SQ_TryAuthWithServer); - g_UISquirrelManager->AddFuncRegistration("bool", "NSIsAuthenticatingWithServer", "", "", SQ_IsAuthComplete); - g_UISquirrelManager->AddFuncRegistration("bool", "NSWasAuthSuccessful", "", "", SQ_WasAuthSuccessful); - g_UISquirrelManager->AddFuncRegistration("void", "NSConnectToAuthedServer", "", "", SQ_ConnectToAuthedServer); + g_pSquirrel->AddFuncRegistration("bool", "NSIsAuthenticatingWithServer", "", "", SQ_IsAuthComplete); + g_pSquirrel->AddFuncRegistration("bool", "NSWasAuthSuccessful", "", "", SQ_WasAuthSuccessful); + g_pSquirrel->AddFuncRegistration("void", "NSConnectToAuthedServer", "", "", SQ_ConnectToAuthedServer); - g_UISquirrelManager->AddFuncRegistration("void", "NSTryAuthWithLocalServer", "", "", SQ_TryAuthWithLocalServer); - g_UISquirrelManager->AddFuncRegistration("void", "NSCompleteAuthWithLocalServer", "", "", SQ_CompleteAuthWithLocalServer); + g_pSquirrel->AddFuncRegistration("void", "NSTryAuthWithLocalServer", "", "", SQ_TryAuthWithLocalServer); + g_pSquirrel->AddFuncRegistration("void", "NSCompleteAuthWithLocalServer", "", "", SQ_CompleteAuthWithLocalServer); - g_UISquirrelManager->AddFuncRegistration("string", "NSGetAuthFailReason", "", "", SQ_GetAuthFailReason); + g_pSquirrel->AddFuncRegistration("string", "NSGetAuthFailReason", "", "", SQ_GetAuthFailReason); } diff --git a/NorthstarDLL/scriptserverbrowser.h b/NorthstarDLL/scriptserverbrowser.h deleted file mode 100644 index 3a88a019..00000000 --- a/NorthstarDLL/scriptserverbrowser.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include - -void InitialiseScriptServerBrowser(HMODULE baseAddress); diff --git a/NorthstarDLL/scriptservertoclientstringcommand.cpp b/NorthstarDLL/scriptservertoclientstringcommand.cpp index 084db1e4..5c116973 100644 --- a/NorthstarDLL/scriptservertoclientstringcommand.cpp +++ b/NorthstarDLL/scriptservertoclientstringcommand.cpp @@ -1,20 +1,21 @@ #include "pch.h" -#include "scriptservertoclientstringcommand.h" #include "squirrel.h" #include "convar.h" #include "concommand.h" void ConCommand_ns_script_servertoclientstringcommand(const CCommand& arg) { - if (g_ClientSquirrelManager->sqvm && - g_ClientSquirrelManager->setupfunc("NSClientCodeCallback_RecievedServerToClientStringCommand") != SQRESULT_ERROR) + if (g_pSquirrel->m_pSQVM && + g_pSquirrel->setupfunc("NSClientCodeCallback_RecievedServerToClientStringCommand") != SQRESULT_ERROR) { - g_ClientSquirrelManager->pusharg(arg.ArgS()); - g_ClientSquirrelManager->call(1); // todo: doesn't throw or log errors from within this, probably not great behaviour + g_pSquirrel->pushstring(g_pSquirrel->m_pSQVM->sqvm, arg.ArgS()); + g_pSquirrel->call( + g_pSquirrel->m_pSQVM->sqvm, + 1); // todo: doesn't throw or log errors from within this, probably not great behaviour } } -void InitialiseScriptServerToClientStringCommands(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ScriptServerToClientStringCommand, ClientSquirrel, (CModule module)) { RegisterConCommand( "ns_script_servertoclientstringcommand", diff --git a/NorthstarDLL/scriptservertoclientstringcommand.h b/NorthstarDLL/scriptservertoclientstringcommand.h deleted file mode 100644 index 1970c1e6..00000000 --- a/NorthstarDLL/scriptservertoclientstringcommand.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void InitialiseScriptServerToClientStringCommands(HMODULE baseAddress); diff --git a/NorthstarDLL/scriptsrson.cpp b/NorthstarDLL/scriptsrson.cpp index 2346e7e1..1e0ded27 100644 --- a/NorthstarDLL/scriptsrson.cpp +++ b/NorthstarDLL/scriptsrson.cpp @@ -12,15 +12,12 @@ void ModManager::BuildScriptsRson() fs::path MOD_SCRIPTS_RSON_PATH = fs::path(GetCompiledAssetsPath() / MOD_SCRIPTS_RSON_SUFFIX); fs::remove(MOD_SCRIPTS_RSON_PATH); - // not really important since it doesn't affect actual functionality at all, but the rson we output is really weird - // has a shitload of newlines added, even in places where we don't modify it at all - - std::string scriptsRson = ReadVPKOriginalFile(VPK_SCRIPTS_RSON_PATH); + std::string scriptsRson = R2::ReadVPKOriginalFile(VPK_SCRIPTS_RSON_PATH); scriptsRson += "\n\n// START MODDED SCRIPT CONTENT\n\n"; // newline before we start custom stuff - for (Mod& mod : m_loadedMods) + for (Mod& mod : m_LoadedMods) { - if (!mod.Enabled) + if (!mod.m_bEnabled) continue; // this isn't needed at all, just nice to have imo @@ -38,7 +35,7 @@ void ModManager::BuildScriptsRson() ]*/ scriptsRson += "When: \""; - scriptsRson += script.RsonRunOn; + scriptsRson += script.RunOn; scriptsRson += "\"\n"; scriptsRson += "Scripts:\n[\n\t"; @@ -54,13 +51,13 @@ void ModManager::BuildScriptsRson() writeStream.close(); ModOverrideFile overrideFile; - overrideFile.owningMod = nullptr; - overrideFile.path = VPK_SCRIPTS_RSON_PATH; + overrideFile.m_pOwningMod = nullptr; + overrideFile.m_Path = VPK_SCRIPTS_RSON_PATH; - if (m_modFiles.find(VPK_SCRIPTS_RSON_PATH) == m_modFiles.end()) - m_modFiles.insert(std::make_pair(VPK_SCRIPTS_RSON_PATH, overrideFile)); + if (m_ModFiles.find(VPK_SCRIPTS_RSON_PATH) == m_ModFiles.end()) + m_ModFiles.insert(std::make_pair(VPK_SCRIPTS_RSON_PATH, overrideFile)); else - m_modFiles[VPK_SCRIPTS_RSON_PATH] = overrideFile; + m_ModFiles[VPK_SCRIPTS_RSON_PATH] = overrideFile; // todo: for preventing dupe scripts in scripts.rson, we could actually parse when conditions with the squirrel vm, just need a way to // get a result out of squirrelmanager.ExecuteCode this would probably be the best way to do this, imo diff --git a/NorthstarDLL/scriptutility.cpp b/NorthstarDLL/scriptutility.cpp index f781a51d..7a1936cf 100644 --- a/NorthstarDLL/scriptutility.cpp +++ b/NorthstarDLL/scriptutility.cpp @@ -1,29 +1,23 @@ #include "pch.h" -#include "scriptutility.h" #include "squirrel.h" -template SQRESULT SQ_StringToAsset(void* sqvm) +// asset function StringToAsset( string assetName ) +template SQRESULT SQ_StringToAsset(HSquirrelVM* sqvm) { - if (context == ScriptContext::SERVER) - { - const char* asset = ServerSq_getstring(sqvm, 1); - ServerSq_pushAsset(sqvm, asset, -1); - } - else - { - const char* asset = ClientSq_getstring(sqvm, 1); - ClientSq_pushAsset(sqvm, asset, -1); - } + g_pSquirrel->pushasset(sqvm, g_pSquirrel->getstring(sqvm, 1), -1); return SQRESULT_NOTNULL; } -void InitialiseClientSquirrelUtilityFunctions(HMODULE baseAddress) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientSharedScriptUtility, ClientSquirrel, (CModule module)) { - g_ClientSquirrelManager->AddFuncRegistration("asset", "StringToAsset", "string assetName", "", SQ_StringToAsset); - g_UISquirrelManager->AddFuncRegistration("asset", "StringToAsset", "string assetName", "", SQ_StringToAsset); + g_pSquirrel->AddFuncRegistration( + "asset", "StringToAsset", "string assetName", "converts a given string to an asset", SQ_StringToAsset); + g_pSquirrel->AddFuncRegistration( + "asset", "StringToAsset", "string assetName", "converts a given string to an asset", SQ_StringToAsset); } -void InitialiseServerSquirrelUtilityFunctions(HMODULE baseAddress) +ON_DLL_LOAD_RELIESON("server.dll", ServerSharedScriptUtility, ServerSquirrel, (CModule module)) { - g_ServerSquirrelManager->AddFuncRegistration("asset", "StringToAsset", "string assetName", "", SQ_StringToAsset); -} + g_pSquirrel->AddFuncRegistration( + "asset", "StringToAsset", "string assetName", "converts a given string to an asset", SQ_StringToAsset); +} \ No newline at end of file diff --git a/NorthstarDLL/scriptutility.h b/NorthstarDLL/scriptutility.h deleted file mode 100644 index 5fdd96c7..00000000 --- a/NorthstarDLL/scriptutility.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -void InitialiseClientSquirrelUtilityFunctions(HMODULE baseAddress); -void InitialiseServerSquirrelUtilityFunctions(HMODULE baseAddress); diff --git a/NorthstarDLL/serverauthentication.cpp b/NorthstarDLL/serverauthentication.cpp index af170766..98054dd7 100644 --- a/NorthstarDLL/serverauthentication.cpp +++ b/NorthstarDLL/serverauthentication.cpp @@ -1,99 +1,44 @@ #include "pch.h" #include "serverauthentication.h" +#include "limits.h" #include "cvar.h" #include "convar.h" -#include "hookutils.h" #include "masterserver.h" -#include "httplib.h" -#include "gameutils.h" +#include "serverpresence.h" +#include "hoststate.h" +#include "maxplayers.h" #include "bansystem.h" -#include "miscserverscript.h" #include "concommand.h" #include "dedicated.h" -#include -#include -#include #include "nsprefix.h" -#include "nsmem.h" - -const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; - -// This convar defines whether to log all client commands -ConVar* Cvar_ns_should_log_all_clientcommands; - -// hook types - -typedef void* (*CBaseServer__ConnectClientType)( - void* server, - void* a2, - void* a3, - uint32_t a4, - uint32_t a5, - int32_t a6, - void* a7, - void* a8, - char* serverFilter, - void* a10, - char a11, - void* a12, - char a13, - char a14, - int64_t uid, - uint32_t a16, - uint32_t a17); -CBaseServer__ConnectClientType CBaseServer__ConnectClient; - -typedef bool (*CBaseClient__ConnectType)( - void* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, void* a7); -CBaseClient__ConnectType CBaseClient__Connect; - -typedef void (*CBaseClient__ActivatePlayerType)(void* self); -CBaseClient__ActivatePlayerType CBaseClient__ActivatePlayer; - -CBaseClient__DisconnectType CBaseClient__Disconnect; - -typedef char (*CGameClient__ExecuteStringCommandType)(void* self, uint32_t unknown, const char* pCommandString); -CGameClient__ExecuteStringCommandType CGameClient__ExecuteStringCommand; +#include "tier0.h" +#include "r2engine.h" +#include "r2client.h" +#include "r2server.h" -typedef char (*__fastcall CNetChan___ProcessMessagesType)(void* self, void* buf); -CNetChan___ProcessMessagesType CNetChan___ProcessMessages; - -typedef char (*CBaseClient__SendServerInfoType)(void* self); -CBaseClient__SendServerInfoType CBaseClient__SendServerInfo; +#include "httplib.h" -typedef bool (*ProcessConnectionlessPacketType)(void* a1, netpacket_t* packet); -ProcessConnectionlessPacketType ProcessConnectionlessPacket; +#include +#include +#include -typedef void (*CServerGameDLL__OnReceivedSayTextMessageType)(void* self, unsigned int senderClientIndex, const char* message, char unknown); -CServerGameDLL__OnReceivedSayTextMessageType CServerGameDLL__OnReceivedSayTextMessage; +AUTOHOOK_INIT() -typedef void (*ConCommand__DispatchType)(ConCommand* command, const CCommand& args, void* a3); -ConCommand__DispatchType ConCommand__Dispatch; +const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; // global vars -ServerAuthenticationManager* g_ServerAuthenticationManager; - -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; +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; + g_pServerPresence->SetAuthPort(Cvar_ns_player_auth_port->GetInt()); // set auth port for presence + m_bRunningPlayerAuthThread = true; // listen is a blocking call so thread this std::thread serverThread( @@ -101,61 +46,42 @@ 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) { - // can't just do request.remote_addr == Cvar_ns_masterserver_hostname->GetString() because the cvar can be a url, gotta - // resolve an ip from it for comparisons - // unsigned long remoteAddr = inet_addr(request.remote_addr.c_str()); - // - // char* addrPtr = Cvar_ns_masterserver_hostname->GetString(); - // char* typeStart = strstr(addrPtr, "://"); - // if (typeStart) - // addrPtr = typeStart + 3; - // hostent* resolvedRemoteAddr = gethostbyname((const char*)addrPtr); - - 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, - request.get_param_value("serverAuthToken") - .c_str())) // || !resolvedRemoteAddr || ((in_addr**)resolvedRemoteAddr->h_addr_list)[0]->S_un.S_addr != - // remoteAddr) + strcmp(g_pMasterServerManager->m_sOwnServerAuthToken, request.get_param_value("serverAuthToken").c_str())) { response.set_content("{\"success\":false}", "application/json"); return; } - // Log playername and UID from request - spdlog::info( - "Player \"{}\" with UID \"{}\" requested to join", + RemoteAuthData newAuthData {}; + strncpy_s(newAuthData.uid, sizeof(newAuthData.uid), request.get_param_value("id").c_str(), sizeof(newAuthData.uid) - 1); + strncpy_s( + newAuthData.username, + sizeof(newAuthData.username), request.get_param_value("username").c_str(), - request.get_param_value("id").c_str()); - - AuthData newAuthData {}; - strncpy(newAuthData.uid, request.get_param_value("id").c_str(), sizeof(newAuthData.uid)); - newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; - - strncpy(newAuthData.username, request.get_param_value("username").c_str(), sizeof(newAuthData.username)); - newAuthData.username[sizeof(newAuthData.username) - 1] = 0; + sizeof(newAuthData.username) - 1); newAuthData.pdataSize = request.body.size(); 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(); @@ -163,144 +89,131 @@ 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::AddPlayer(R2::CBaseClient* player, const char* pToken) +{ + PlayerAuthenticationData additionalData; + additionalData.pdataSize = m_RemoteAuthenticationData[pToken].pdataSize; + additionalData.usingLocalPdata = player->m_iPersistenceReady == R2::ePersistenceReady::READY_INSECURE; + + m_PlayerAuthenticationData.insert(std::make_pair(player, additionalData)); +} + +void ServerAuthenticationManager::RemovePlayer(R2::CBaseClient* player) +{ + if (m_PlayerAuthenticationData.count(player)) + m_PlayerAuthenticationData.erase(player); } -char* ServerAuthenticationManager::VerifyPlayerName(void* player, char* authToken, char* name) +void ServerAuthenticationManager::VerifyPlayerName(R2::CBaseClient* 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 - strncpy(name, authData.username, 64); - name[63] = 0; + strncpy_s(name, 64, authData.username, 63); } } - return name; } -bool ServerAuthenticationManager::AuthenticatePlayer(void* player, int64_t uid, char* authToken) +bool ServerAuthenticationManager::CheckDuplicateAccounts(R2::CBaseClient* player) +{ + if (m_bAllowDuplicateAccounts) + return true; + + bool bHasUidPlayer = false; + for (int i = 0; i < R2::GetMaxPlayers(); i++) + if (&R2::g_pClientArray[i] != player && !strcmp(R2::g_pClientArray[i].m_UID, player->m_UID)) + return false; + + return true; +} + +bool ServerAuthenticationManager::AuthenticatePlayer(R2::CBaseClient* 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); + + // copy uuid + strcpy(player->m_UID, strUid.c_str()); 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]; + if (!CheckDuplicateAccounts(player)) + return false; - // Log playnername and UID from request - spdlog::info("Comparing connecting UID \"{}\" against stored UID from ms auth request \"{}\"", strUid.c_str(), authData.uid); + // use stored auth data + 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()); - - // reset from disk if we're doing that - if (m_bForceReadLocalPlayerPersistenceFromDisk && !strcmp(authData.uid, g_LocalPlayerUserID)) - { - std::fstream pdataStream(GetNorthstarPrefix() + "/placeholder_playerdata.pdata", std::ios_base::in); - - if (!pdataStream.fail()) - { - // get file length - pdataStream.seekg(0, pdataStream.end); - auto length = pdataStream.tellg(); - pdataStream.seekg(0, pdataStream.beg); - - // copy pdata into buffer - pdataStream.read((char*)player + 0x4FA, length); - } - else // fallback to remote pdata if no local default - memcpy((char*)player + 0x4FA, authData.pdata, authData.pdataSize); - } - else + // if we're resetting let script handle the reset + if (!m_bForceResetLocalPlayerPersistence || strcmp(authData.uid, R2::g_pLocalPlayerUserID)) { // 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; + authFail = false; } } if (authFail) { - // set persistent data as ready, we use 0x3 internally to mark the client as using local persistence - *((char*)player + 0x4a0) = (char)0x3; - - // no auth data and insecure connections aren't allowed, so dc the client - if (!CVar_ns_auth_allow_insecure->GetBool() && strncmp(GetCurrentPlaylistName(), "solo", 5) != 0) + if (CVar_ns_auth_allow_insecure->GetBool()) + { + // set persistent data as ready + // note: actual placeholder persistent data is populated in script with InitPersistentData() + player->m_iPersistenceReady = R2::ePersistenceReady::READY_INSECURE; + return true; + } + else return false; - - // insecure connections are allowed, try reading from disk - // uuid - strcpy((char*)player + 0xF500, strUid.c_str()); - - // try reading pdata file for player - std::string pdataPath = GetNorthstarPrefix() + "/playerdata_"; - pdataPath += strUid; - pdataPath += ".pdata"; - - std::fstream pdataStream(pdataPath, std::ios_base::in); - if (pdataStream.fail()) // file doesn't exist, use placeholder - pdataStream = std::fstream(GetNorthstarPrefix() + "/placeholder_playerdata.pdata", std::ios_base::in); - - // get file length - pdataStream.seekg(0, pdataStream.end); - auto length = pdataStream.tellg(); - pdataStream.seekg(0, pdataStream.beg); - - // copy pdata into buffer - pdataStream.read((char*)player + 0x4FA, length); - - pdataStream.close(); } return true; // auth successful, client stays on } -bool ServerAuthenticationManager::RemovePlayerAuthData(void* player) +bool ServerAuthenticationManager::RemovePlayerAuthData(R2::CBaseClient* 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, g_LocalPlayerUserID)) + 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)) { - // Log UID - spdlog::info("Erasing auth data from 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; } } @@ -308,13 +221,12 @@ bool ServerAuthenticationManager::RemovePlayerAuthData(void* player) return false; } -void ServerAuthenticationManager::WritePersistentData(void* player) +void ServerAuthenticationManager::WritePersistentData(R2::CBaseClient* 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()) { @@ -322,29 +234,16 @@ void ServerAuthenticationManager::WritePersistentData(void* player) } } -bool ServerAuthenticationManager::CheckPlayerChatRatelimit(void* player) -{ - if (Plat_FloatTime() - m_additionalPlayerData[player].lastSayTextLimitStart >= 1.0) - { - m_additionalPlayerData[player].lastSayTextLimitStart = 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 -char* nextPlayerToken; -uint64_t nextPlayerUid; +// 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; -void* CBaseServer__ConnectClientHook( +// clang-format off +AUTOHOOK(CBaseServer__ConnectClient, engine.dll + 0x114430, +void*,, ( void* server, void* a2, void* a3, @@ -361,396 +260,142 @@ void* CBaseServer__ConnectClientHook( char a14, int64_t uid, uint32_t a16, - uint32_t a17) + uint32_t a17)) +// clang-format on { // auth tokens are sent with serverfilter, can't be accessed from player struct to my knowledge, so have to do this here - nextPlayerToken = serverFilter; - nextPlayerUid = uid; - - // Random UID log - spdlog::info("CBaseServer__ConnectClientHook says UID \"{}\"", uid); + pNextPlayerToken = serverFilter; + iNextPlayerUid = uid; return CBaseServer__ConnectClient(server, a2, a3, a4, a5, a6, a7, a8, serverFilter, a10, a11, a12, a13, a14, uid, a16, a17); } -bool CBaseClient__ConnectHook(void* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, void* a7) +// clang-format off +AUTOHOOK(CBaseClient__Connect, engine.dll + 0x101740, +bool,, (R2::CBaseClient* self, char* name, void* netchan_ptr_arg, char b_fake_player_arg, void* a5, char* Buffer, void* a7)) +// clang-format on { // try changing name before all else - name = g_ServerAuthenticationManager->VerifyPlayerName(self, nextPlayerToken, 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); - - // Another UID log - spdlog::info("CBaseClient__ConnectHook says UID \"{}\"", nextPlayerUid); - if (!ret) return ret; - if (!g_ServerBanSystem->IsUIDAllowed(nextPlayerUid)) + if (!g_pBanSystem->IsUIDAllowed(iNextPlayerUid)) { - CBaseClient__Disconnect(self, 1, "Banned from server"); + R2::CBaseClient__Disconnect(self, 1, "Banned from server"); return ret; } if (strlen(name) >= 64) // fix for name overflow bug - CBaseClient__Disconnect(self, 1, "Invalid name"); + R2::CBaseClient__Disconnect(self, 1, "Invalid name"); else if ( - !g_ServerAuthenticationManager->AuthenticatePlayer(self, nextPlayerUid, nextPlayerToken) && - g_MasterServerManager->m_bRequireClientAuth) - CBaseClient__Disconnect(self, 1, "Authentication Failed"); + !g_pServerAuthentication->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken) && + g_pServerAuthentication->m_bRequireClientAuth) + R2::CBaseClient__Disconnect(self, 1, "Authentication Failed"); - if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(self)) - { - AdditionalPlayerData additionalData; - additionalData.pdataSize = g_ServerAuthenticationManager->m_authData[nextPlayerToken].pdataSize; - additionalData.usingLocalPdata = *((char*)self + 0x4a0) == (char)0x3; - - g_ServerAuthenticationManager->m_additionalPlayerData.insert(std::make_pair(self, additionalData)); - - g_ServerAuthenticationManager->m_additionalPlayerData[self].uid = nextPlayerUid; - } + g_pServerAuthentication->AddPlayer(self, pNextPlayerToken); + g_pServerLimits->AddPlayer(self); return ret; } -void CBaseClient__ActivatePlayerHook(void* self) +// clang-format off +AUTOHOOK(CBaseClient__ActivatePlayer, engine.dll + 0x100F80, +void,, (R2::CBaseClient* self)) +// clang-format on { - bool uidMatches = false; - if (g_ServerAuthenticationManager->m_additionalPlayerData.count(self)) - { - std::string strUid = std::to_string(g_ServerAuthenticationManager->m_additionalPlayerData[self].uid); - if (!strcmp(strUid.c_str(), (char*)self + 0xF500)) // connecting client's uid is the same as auth's uid - { - uidMatches = true; - } - } - if (!uidMatches) - { - CBaseClient__Disconnect(self, 1, "Authentication Failed"); - return; - } - // 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_ServerAuthenticationManager->RemovePlayerAuthData(self)) + if (self->m_iPersistenceReady >= R2::ePersistenceReady::READY && !g_pServerAuthentication->RemovePlayerAuthData(self)) { - g_ServerAuthenticationManager->m_bForceReadLocalPlayerPersistenceFromDisk = false; - g_ServerAuthenticationManager->WritePersistentData(self); - g_MasterServerManager->UpdateServerPlayerCount(g_ServerAuthenticationManager->m_additionalPlayerData.size()); + g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = false; + g_pServerAuthentication->WritePersistentData(self); + g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); } - // Log UID - spdlog::info("In CBaseClient__ActivatePlayerHook, activating UID \"{}\"", (char*)self + 0xF500); CBaseClient__ActivatePlayer(self); } -void CBaseClient__DisconnectHook(void* self, uint32_t unknownButAlways1, const char* reason, ...) +// clang-format off +AUTOHOOK(_CBaseClient__Disconnect, engine.dll + 0x1012C0, +void,, (R2::CBaseClient* self, uint32_t unknownButAlways1, const char* pReason, ...)) +// clang-format on { // have to manually format message because can't pass varargs to original func char buf[1024]; va_list va; - va_start(va, reason); - vsprintf(buf, reason, va); + va_start(va, pReason); + vsprintf(buf, pReason, va); va_end(va); // this reason is used while connecting to a local server, hacky, but just ignore it - if (strcmp(reason, "Connection closing")) + 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_ServerAuthenticationManager->m_additionalPlayerData[self].needPersistenceWriteOnLeave) - g_ServerAuthenticationManager->WritePersistentData(self); - g_ServerAuthenticationManager->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case - } - - if (g_ServerAuthenticationManager->m_additionalPlayerData.count(self)) - { - g_ServerAuthenticationManager->m_additionalPlayerData.erase(self); - g_MasterServerManager->UpdateServerPlayerCount(g_ServerAuthenticationManager->m_additionalPlayerData.size()); - } - - CBaseClient__Disconnect(self, unknownButAlways1, buf); -} - -// maybe this should be done outside of auth code, but effort to refactor rn and it sorta fits -typedef bool (*CCommand__TokenizeType)(CCommand& self, const char* pCommandString, cmd_source_t commandSource); -CCommand__TokenizeType CCommand__Tokenize; - -char CGameClient__ExecuteStringCommandHook(void* self, uint32_t unknown, const char* pCommandString) -{ - // Only log clientcommands if the convar `ns_should_log_all_clientcommands` equals 1 - if (Cvar_ns_should_log_all_clientcommands->GetBool()) - { - spdlog::info("{} (UID: {}) executed command: \"{}\"", (char*)self + 0x16, (char*)self + 0xF500, 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 (Plat_FloatTime() - g_ServerAuthenticationManager->m_additionalPlayerData[self].lastClientCommandQuotaStart >= 1.0) - { - // reset quota - g_ServerAuthenticationManager->m_additionalPlayerData[self].lastClientCommandQuotaStart = Plat_FloatTime(); - g_ServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota = 0; - } - - g_ServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota++; - if (g_ServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota > - CVar_sv_quota_stringcmdspersecond->GetInt()) - { - // too many stringcmds, dc player - CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands"); - return false; - } - } - - // verify the command we're trying to execute is FCVAR_CLIENTCMD_CAN_EXECUTE, 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, cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC()) - return false; - - ConCommand* command = 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_CLIENTCMD_CAN_EXECUTE)) - { - // ensure FCVAR_GAMEDLL concommands without FCVAR_CLIENTCMD_CAN_EXECUTE can't be executed by remote clients - if (IsDedicatedServer()) - return false; - - if (strcmp((char*)self + 0xF500, g_LocalPlayerUserID)) - return false; - } - - // todo later, basically just limit to CVar_sv_quota_stringcmdspersecond->GetInt() stringcmds per client per second - return CGameClient__ExecuteStringCommand(self, unknown, pCommandString); -} - -char __fastcall CNetChan___ProcessMessagesHook(void* self, void* buf) -{ - double startTime = Plat_FloatTime(); - char ret = CNetChan___ProcessMessages(self, buf); - - // check processing limits, unless we're in a level transition - if (g_pHostState->m_iCurrentState == HostState_t::HS_RUN && ThreadInServerFrameThread()) - { - // player that sent the message - void* sender = *(void**)((char*)self + 368); - - // if no sender, return - // relatively certain this is fine? - if (!sender || !g_ServerAuthenticationManager->m_additionalPlayerData.count(sender)) - return ret; - - // reset every second - if (startTime - g_ServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart >= 1.0 || - g_ServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart == -1.0) - { - g_ServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart = startTime; - g_ServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime = 0.0; - } - g_ServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime += - (Plat_FloatTime() * 1000) - (startTime * 1000); - - if (g_ServerAuthenticationManager->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_ServerAuthenticationManager->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(g_LocalPlayerUserID, (char*)sender + 0xF500)) - { - CBaseClient__Disconnect(sender, 1, "Exceeded net channel processing limit"); - return false; - } - } + 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 } - return ret; -} - -bool bWasWritingStringTableSuccessful; - -void CBaseClient__SendServerInfoHook(void* self) -{ - bWasWritingStringTableSuccessful = true; - CBaseClient__SendServerInfo(self); - if (!bWasWritingStringTableSuccessful) - CBaseClient__Disconnect( - self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting"); -} - -bool ProcessConnectionlessPacketHook(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_ServerAuthenticationManager->m_unconnectedPlayerSendData) - { - if (!memcmp(packet->adr.ip, foundSendData.ip, 16)) - { - sendData = &foundSendData; - break; - } - } - - if (!sendData) - { - sendData = &g_ServerAuthenticationManager->m_unconnectedPlayerSendData.emplace_back(); - memcpy(sendData->ip, packet->adr.ip, 16); - } - - if (Plat_FloatTime() < sendData->timeoutEnd) - return false; - - if (Plat_FloatTime() - sendData->lastQuotaStart >= 1.0) - { - sendData->lastQuotaStart = 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]); + g_pServerAuthentication->RemovePlayer(self); + g_pServerLimits->RemovePlayer(self); - // timeout for a minute - sendData->timeoutEnd = Plat_FloatTime() + 60.0; - return false; - } - } + g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); - return ProcessConnectionlessPacket(a1, packet); + _CBaseClient__Disconnect(self, unknownButAlways1, buf); } void ConCommand_ns_resetpersistence(const CCommand& args) { - if (*sv_m_State == server_state_t::ss_active) + if (*R2::g_pServerState == R2::server_state_t::ss_active) { spdlog::error("ns_resetpersistence must be entered from the main menu"); return; } spdlog::info("resetting persistence on next lobby load..."); - g_ServerAuthenticationManager->m_bForceReadLocalPlayerPersistenceFromDisk = true; + g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = true; } -void InitialiseServerAuthentication(HMODULE baseAddress) +ON_DLL_LOAD_RELIESON("engine.dll", ServerAuthentication, (ConCommand, ConVar), (CModule module)) { - g_ServerAuthenticationManager = new ServerAuthenticationManager; + AUTOHOOK_DISPATCH() - Cvar_ns_erase_auth_info = + g_pServerAuthentication = new ServerAuthenticationManager; + + 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", - "100", - FCVAR_GAMEDLL, - "Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget"); - Cvar_ns_should_log_all_clientcommands = - new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands"); - 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 = g_pCVar->FindVar("net_datablock_enabled"); RegisterConCommand( "ns_resetpersistence", ConCommand_ns_resetpersistence, "resets your pdata when you next enter the lobby", FCVAR_NONE); - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x114430, &CBaseServer__ConnectClientHook, reinterpret_cast(&CBaseServer__ConnectClient)); - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x101740, &CBaseClient__ConnectHook, reinterpret_cast(&CBaseClient__Connect)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x100F80, &CBaseClient__ActivatePlayerHook, reinterpret_cast(&CBaseClient__ActivatePlayer)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x1012C0, &CBaseClient__DisconnectHook, reinterpret_cast(&CBaseClient__Disconnect)); - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x1022E0, - &CGameClient__ExecuteStringCommandHook, - reinterpret_cast(&CGameClient__ExecuteStringCommand)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x2140A0, &CNetChan___ProcessMessagesHook, reinterpret_cast(&CNetChan___ProcessMessages)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x104FB0, &CBaseClient__SendServerInfoHook, reinterpret_cast(&CBaseClient__SendServerInfo)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x117800, &ProcessConnectionlessPacketHook, reinterpret_cast(&ProcessConnectionlessPacket)); - - CCommand__Tokenize = (CCommand__TokenizeType)((char*)baseAddress + 0x418380); - - uintptr_t ba = (uintptr_t)baseAddress; - // patch to disable kicking based on incorrect serverfilter in connectclient, since we repurpose it for use as an auth token - { - NSMem::BytePatch( - ba + 0x114655, - "EB" // jz => jmp - ); - } + module.Offset(0x114655).Patch("EB"); // patch to disable fairfight marking players as cheaters and kicking them - { - NSMem::BytePatch( - ba + 0x101012, - "E9 90 00" // jz => jmp - ); - } + module.Offset(0x101012).Patch("E9 90 00"); - // patch to allow same of multiple account - if (CommandLine()->CheckParm("-allowdupeaccounts")) + if (Tier0::CommandLine()->CheckParm("-allowdupeaccounts")) { - NSMem::BytePatch( - 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"); + // patch to allow same of multiple account + module.Offset(0x114510).Patch("EB"); - NSMem::NOP(addr + 10, 5); + g_pServerAuthentication->m_bAllowDuplicateAccounts = true; } } diff --git a/NorthstarDLL/serverauthentication.h b/NorthstarDLL/serverauthentication.h index e79577e6..08854ac0 100644 --- a/NorthstarDLL/serverauthentication.h +++ b/NorthstarDLL/serverauthentication.h @@ -1,10 +1,11 @@ #pragma once #include "convar.h" #include "httplib.h" +#include "r2engine.h" #include #include -struct AuthData +struct RemoteAuthData { char uid[33]; char username[64]; @@ -14,99 +15,43 @@ 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; - - uint64_t uid; -}; - -#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_bRequireClientAuth = true; + bool m_bAllowDuplicateAccounts = false; + bool m_bRunningPlayerAuthThread = false; bool m_bNeedLocalAuthForNewgame = false; - bool m_bForceReadLocalPlayerPersistenceFromDisk = false; + bool m_bForceResetLocalPlayerPersistence = 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 AddPlayer(R2::CBaseClient* player, const char* pToken); + void RemovePlayer(R2::CBaseClient* player); + bool CheckDuplicateAccounts(R2::CBaseClient* player); + bool AuthenticatePlayer(R2::CBaseClient* player, uint64_t uid, char* authToken); + void VerifyPlayerName(R2::CBaseClient* player, char* authToken, char* name); + bool RemovePlayerAuthData(R2::CBaseClient* player); + void WritePersistentData(R2::CBaseClient* player); }; -typedef void (*CBaseClient__DisconnectType)(void* self, uint32_t unknownButAlways1, const char* reason, ...); -extern CBaseClient__DisconnectType CBaseClient__Disconnect; - -void InitialiseServerAuthentication(HMODULE baseAddress); - -extern ServerAuthenticationManager* g_ServerAuthenticationManager; -extern ConVar* Cvar_ns_player_auth_port; +extern ServerAuthenticationManager* g_pServerAuthentication; diff --git a/NorthstarDLL/serverchathooks.cpp b/NorthstarDLL/serverchathooks.cpp index 2f5be5c1..1dd05830 100644 --- a/NorthstarDLL/serverchathooks.cpp +++ b/NorthstarDLL/serverchathooks.cpp @@ -1,14 +1,16 @@ #include "pch.h" #include "serverchathooks.h" +#include "limits.h" +#include "squirrel.h" +#include "r2server.h" + #include #include #include -#include "serverauthentication.h" -#include "squirrel.h" -#include "miscserverscript.h" + +AUTOHOOK_INIT() class CServerGameDLL; -class CBasePlayer; class CRecipientFilter { @@ -17,93 +19,68 @@ class CRecipientFilter CServerGameDLL* g_pServerGameDLL; -typedef void(__fastcall* CServerGameDLL__OnReceivedSayTextMessageType)( +void(__fastcall* CServerGameDLL__OnReceivedSayTextMessage)( CServerGameDLL* self, unsigned int senderPlayerId, const char* text, int channelId); -CServerGameDLL__OnReceivedSayTextMessageType CServerGameDLL__OnReceivedSayTextMessage; -CServerGameDLL__OnReceivedSayTextMessageType CServerGameDLL__OnReceivedSayTextMessageHookBase; - -typedef CBasePlayer*(__fastcall* UTIL_PlayerByIndexType)(int playerIndex); -UTIL_PlayerByIndexType UTIL_PlayerByIndex; - -typedef void(__fastcall* CRecipientFilter__ConstructType)(CRecipientFilter* self); -CRecipientFilter__ConstructType CRecipientFilter__Construct; - -typedef void(__fastcall* CRecipientFilter__DestructType)(CRecipientFilter* self); -CRecipientFilter__DestructType CRecipientFilter__Destruct; - -typedef void(__fastcall* CRecipientFilter__AddAllPlayersType)(CRecipientFilter* self); -CRecipientFilter__AddAllPlayersType CRecipientFilter__AddAllPlayers; - -typedef void(__fastcall* CRecipientFilter__AddRecipientType)(CRecipientFilter* self, const CBasePlayer* player); -CRecipientFilter__AddRecipientType CRecipientFilter__AddRecipient; - -typedef void(__fastcall* CRecipientFilter__MakeReliableType)(CRecipientFilter* self); -CRecipientFilter__MakeReliableType CRecipientFilter__MakeReliable; -typedef void(__fastcall* UserMessageBeginType)(CRecipientFilter* filter, const char* messagename); -UserMessageBeginType UserMessageBegin; - -typedef void(__fastcall* MessageEndType)(); -MessageEndType MessageEnd; - -typedef void(__fastcall* MessageWriteByteType)(int iValue); -MessageWriteByteType MessageWriteByte; - -typedef void(__fastcall* MessageWriteStringType)(const char* sz); -MessageWriteStringType MessageWriteString; - -typedef void(__fastcall* MessageWriteBoolType)(bool bValue); -MessageWriteBoolType MessageWriteBool; - -bool isSkippingHook = false; - -static void CServerGameDLL__OnReceivedSayTextMessageHook(CServerGameDLL* self, unsigned int senderPlayerId, const char* text, bool isTeam) +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 R2::CBasePlayer* player); +void(__fastcall* CRecipientFilter__MakeReliable)(CRecipientFilter* self); + +void(__fastcall* UserMessageBegin)(CRecipientFilter* filter, const char* messagename); +void(__fastcall* MessageEnd)(); +void(__fastcall* MessageWriteByte)(int iValue); +void(__fastcall* MessageWriteString)(const char* sz); +void(__fastcall* MessageWriteBool)(bool bValue); + +bool bShouldCallSayTextHook = false; +// clang-format off +AUTOHOOK(_CServerGameDLL__OnReceivedSayTextMessage, server.dll + 0x1595C0, +void, __fastcall, (CServerGameDLL* self, unsigned int senderPlayerId, const char* text, bool isTeam)) +// clang-format on { // MiniHook doesn't allow calling the base function outside of anywhere but the hook function. // To allow bypassing the hook, isSkippingHook can be set. - if (isSkippingHook) + if (bShouldCallSayTextHook) { - isSkippingHook = false; - CServerGameDLL__OnReceivedSayTextMessageHookBase(self, senderPlayerId, text, isTeam); + bShouldCallSayTextHook = false; + _CServerGameDLL__OnReceivedSayTextMessage(self, senderPlayerId, text, isTeam); return; } - void* sender = GetPlayerByIndex(senderPlayerId - 1); - // check chat ratelimits - if (!g_ServerAuthenticationManager->CheckPlayerChatRatelimit(sender)) - { + if (!g_pServerLimits->CheckChatLimits(&R2::g_pClientArray[senderPlayerId - 1])) return; - } - if (g_ServerSquirrelManager->setupfunc("CServerGameDLL_ProcessMessageStartThread") != SQRESULT_ERROR) + if (g_pSquirrel->setupfunc("CServerGameDLL_ProcessMessageStartThread") != SQRESULT_ERROR) { - g_ServerSquirrelManager->pusharg((int)senderPlayerId - 1); - g_ServerSquirrelManager->pusharg(text); - g_ServerSquirrelManager->pusharg(isTeam); - g_ServerSquirrelManager->call(3); + g_pSquirrel->pushinteger(g_pSquirrel->m_pSQVM->sqvm, (int)senderPlayerId - 1); + g_pSquirrel->pushstring(g_pSquirrel->m_pSQVM->sqvm, text); + g_pSquirrel->pushbool(g_pSquirrel->m_pSQVM->sqvm, isTeam); + g_pSquirrel->call(g_pSquirrel->m_pSQVM->sqvm, 3); } else - CServerGameDLL__OnReceivedSayTextMessageHookBase(self, senderPlayerId, text, isTeam); + _CServerGameDLL__OnReceivedSayTextMessage(self, senderPlayerId, text, isTeam); } -void ChatSendMessage(unsigned int playerIndex, const char* text, bool isteam) +void ChatSendMessage(unsigned int playerIndex, const char* text, bool isTeam) { - isSkippingHook = true; + bShouldCallSayTextHook = true; CServerGameDLL__OnReceivedSayTextMessage( g_pServerGameDLL, // Ensure the first bit isn't set, since this indicates a custom message (playerIndex + 1) & CUSTOM_MESSAGE_INDEX_MASK, text, - isteam); + 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; } @@ -111,8 +88,7 @@ void ChatBroadcastMessage(int fromPlayerIndex, int toPlayerIndex, const char* te // Build a new string where the first byte is the message type char sendText[256]; sendText[0] = (char)messageType; - strncpy(sendText + 1, text, 255); - sendText[255] = 0; + strncpy_s(sendText + 1, 255, text, 254); // Anonymous custom messages use playerId=0, non-anonymous ones use a player ID with the first bit set unsigned int fromPlayerId = fromPlayerIndex < 0 ? 0 : ((fromPlayerIndex + 1) | CUSTOM_MESSAGE_INDEX_BIT); @@ -139,29 +115,31 @@ void ChatBroadcastMessage(int fromPlayerIndex, int toPlayerIndex, const char* te CRecipientFilter__Destruct(&filter); } -SQRESULT SQ_SendMessage(void* sqvm) +// void function NSSendMessage( int playerIndex, string text, bool isTeam ) +SQRESULT SQ_SendMessage(HSquirrelVM* sqvm) { - int playerIndex = ServerSq_getinteger(sqvm, 1); - const char* text = ServerSq_getstring(sqvm, 2); - bool isTeam = ServerSq_getbool(sqvm, 3); + int playerIndex = g_pSquirrel->getinteger(sqvm, 1); + const char* text = g_pSquirrel->getstring(sqvm, 2); + bool isTeam = g_pSquirrel->getbool(sqvm, 3); ChatSendMessage(playerIndex, text, isTeam); return SQRESULT_NULL; } -SQRESULT SQ_BroadcastMessage(void* sqvm) +// void function NSBroadcastMessage( int fromPlayerIndex, int toPlayerIndex, string text, bool isTeam, bool isDead, int messageType ) +SQRESULT SQ_BroadcastMessage(HSquirrelVM* sqvm) { - int fromPlayerIndex = ServerSq_getinteger(sqvm, 1); - int toPlayerIndex = ServerSq_getinteger(sqvm, 2); - const char* text = ServerSq_getstring(sqvm, 3); - bool isTeam = ServerSq_getbool(sqvm, 4); - bool isDead = ServerSq_getbool(sqvm, 5); - int messageType = ServerSq_getinteger(sqvm, 6); + int fromPlayerIndex = g_pSquirrel->getinteger(sqvm, 1); + int toPlayerIndex = g_pSquirrel->getinteger(sqvm, 2); + const char* text = g_pSquirrel->getstring(sqvm, 3); + bool isTeam = g_pSquirrel->getbool(sqvm, 4); + bool isDead = g_pSquirrel->getbool(sqvm, 5); + int messageType = g_pSquirrel->getinteger(sqvm, 6); if (messageType < 1) { - ServerSq_pusherror(sqvm, fmt::format("Invalid message type {}", messageType).c_str()); + g_pSquirrel->raiseerror(sqvm, fmt::format("Invalid message type {}", messageType).c_str()); return SQRESULT_ERROR; } @@ -170,37 +148,33 @@ SQRESULT SQ_BroadcastMessage(void* sqvm) return SQRESULT_NULL; } -void InitialiseServerChatHooks_Engine(HMODULE baseAddress) +ON_DLL_LOAD("engine.dll", EngineServerChatHooks, (CModule module)) { - g_pServerGameDLL = (CServerGameDLL*)((char*)baseAddress + 0x13F0AA98); + g_pServerGameDLL = module.Offset(0x13F0AA98).As(); } -void InitialiseServerChatHooks_Server(HMODULE baseAddress) +ON_DLL_LOAD_RELIESON("server.dll", ServerChatHooks, ServerSquirrel, (CModule module)) { - CServerGameDLL__OnReceivedSayTextMessage = (CServerGameDLL__OnReceivedSayTextMessageType)((char*)baseAddress + 0x1595C0); - UTIL_PlayerByIndex = (UTIL_PlayerByIndexType)((char*)baseAddress + 0x26AA10); - CRecipientFilter__Construct = (CRecipientFilter__ConstructType)((char*)baseAddress + 0x1E9440); - CRecipientFilter__Destruct = (CRecipientFilter__DestructType)((char*)baseAddress + 0x1E9700); - CRecipientFilter__AddAllPlayers = (CRecipientFilter__AddAllPlayersType)((char*)baseAddress + 0x1E9940); - CRecipientFilter__AddRecipient = (CRecipientFilter__AddRecipientType)((char*)baseAddress + 0x1E9b30); - CRecipientFilter__MakeReliable = (CRecipientFilter__MakeReliableType)((char*)baseAddress + 0x1EA4E0); - - UserMessageBegin = (UserMessageBeginType)((char*)baseAddress + 0x15C520); - MessageEnd = (MessageEndType)((char*)baseAddress + 0x158880); - MessageWriteByte = (MessageWriteByteType)((char*)baseAddress + 0x158A90); - MessageWriteString = (MessageWriteStringType)((char*)baseAddress + 0x158D00); - MessageWriteBool = (MessageWriteBoolType)((char*)baseAddress + 0x158A00); - - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, - reinterpret_cast(CServerGameDLL__OnReceivedSayTextMessage), - &CServerGameDLL__OnReceivedSayTextMessageHook, - reinterpret_cast(&CServerGameDLL__OnReceivedSayTextMessageHookBase)); + AUTOHOOK_DISPATCH_MODULE(server.dll) + + CServerGameDLL__OnReceivedSayTextMessage = + module.Offset(0x1595C0).As(); + CRecipientFilter__Construct = module.Offset(0x1E9440).As(); + CRecipientFilter__Destruct = module.Offset(0x1E9700).As(); + CRecipientFilter__AddAllPlayers = module.Offset(0x1E9940).As(); + CRecipientFilter__AddRecipient = module.Offset(0x1E9B30).As(); + CRecipientFilter__MakeReliable = module.Offset(0x1EA4E0).As(); + + UserMessageBegin = module.Offset(0x15C520).As(); + MessageEnd = module.Offset(0x158880).As(); + MessageWriteByte = module.Offset(0x158A90).As(); + MessageWriteString = module.Offset(0x158D00).As(); + MessageWriteBool = module.Offset(0x158A00).As(); // Chat sending functions - g_ServerSquirrelManager->AddFuncRegistration("void", "NSSendMessage", "int playerIndex, string text, bool isTeam", "", SQ_SendMessage); - g_ServerSquirrelManager->AddFuncRegistration( + g_pSquirrel->AddFuncRegistration( + "void", "NSSendMessage", "int playerIndex, string text, bool isTeam", "", SQ_SendMessage); + g_pSquirrel->AddFuncRegistration( "void", "NSBroadcastMessage", "int fromPlayerIndex, int toPlayerIndex, string text, bool isTeam, bool isDead, int messageType", diff --git a/NorthstarDLL/serverchathooks.h b/NorthstarDLL/serverchathooks.h index f3425ae6..1d8a806a 100644 --- a/NorthstarDLL/serverchathooks.h +++ b/NorthstarDLL/serverchathooks.h @@ -23,7 +23,3 @@ void ChatSendMessage(unsigned int playerIndex, const char* text, bool isteam); // messageType: send a specific message type void ChatBroadcastMessage( int fromPlayerIndex, int toPlayerIndex, const char* text, bool isTeam, bool isDead, CustomMessageType messageType); - -void InitialiseServerChatHooks_Engine(HMODULE baseAddress); - -void InitialiseServerChatHooks_Server(HMODULE baseAddress); diff --git a/NorthstarDLL/serverpresence.cpp b/NorthstarDLL/serverpresence.cpp new file mode 100644 index 00000000..fb8cf624 --- /dev/null +++ b/NorthstarDLL/serverpresence.cpp @@ -0,0 +1,237 @@ +#include "pch.h" +#include "serverpresence.h" +#include "playlist.h" +#include "tier0.h" +#include "convar.h" + +#include + +ServerPresenceManager* g_pServerPresence; + +ConVar* Cvar_hostname; + +// Convert a hex digit char to integer. +inline int hctod(char c) +{ + if (c >= 'A' && c <= 'F') + { + return c - 'A' + 10; + } + else if (c >= 'a' && c <= 'f') + { + return c - 'a' + 10; + } + else + { + return c - '0'; + } +} + +// This function interprets all 4-hexadecimal-digit unicode codepoint characters like \u4E2D to UTF-8 encoding. +std::string UnescapeUnicode(const std::string& str) +{ + std::string result; + + std::regex r("\\\\u([a-f\\d]{4})", std::regex::icase); + auto matches_begin = std::sregex_iterator(str.begin(), str.end(), r); + auto matches_end = std::sregex_iterator(); + std::smatch last_match; + + for (std::sregex_iterator i = matches_begin; i != matches_end; ++i) + { + last_match = *i; + result.append(last_match.prefix()); + unsigned int cp = 0; + for (int i = 2; i <= 5; ++i) + { + cp *= 16; + cp += hctod(last_match.str()[i]); + } + if (cp <= 0x7F) + { + result.push_back(cp); + } + else if (cp <= 0x7FF) + { + result.push_back((cp >> 6) | 0b11000000 & (~(1 << 5))); + result.push_back(cp & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); + } + else if (cp <= 0xFFFF) + { + result.push_back((cp >> 12) | 0b11100000 & (~(1 << 4))); + result.push_back((cp >> 6) & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); + result.push_back(cp & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); + } + } + + if (!last_match.ready()) + return str; + else + result.append(last_match.suffix()); + + return result; +} + +ServerPresenceManager::ServerPresenceManager() +{ + // clang-format off + // register convars + Cvar_ns_server_presence_update_rate = new ConVar( + "ns_server_presence_update_rate", "5000", FCVAR_GAMEDLL, "How often we update our server's presence on server lists in ms"); + + 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_pServerPresence->SetName(UnescapeUnicode(g_pServerPresence->Cvar_ns_server_name->GetString())); + + // update engine hostname cvar + Cvar_hostname->SetValue(g_pServerPresence->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_pServerPresence->SetDescription(UnescapeUnicode(g_pServerPresence->Cvar_ns_server_desc->GetString())); + }); + + Cvar_ns_server_password = new ConVar("ns_server_password", "", FCVAR_GAMEDLL, "This server's password", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { + g_pServerPresence->SetPassword(g_pServerPresence->Cvar_ns_server_password->GetString()); + }); + + Cvar_ns_report_server_to_masterserver = new ConVar("ns_report_server_to_masterserver", "1", FCVAR_GAMEDLL, "Whether we should report this server to the masterserver"); + Cvar_ns_report_sp_server_to_masterserver = new ConVar("ns_report_sp_server_to_masterserver", "0", FCVAR_GAMEDLL, "Whether we should report this server to the masterserver, when started in singleplayer"); + // clang-format on +} + +void ServerPresenceManager::AddPresenceReporter(ServerPresenceReporter* reporter) +{ + m_vPresenceReporters.push_back(reporter); +} + +void ServerPresenceManager::CreatePresence() +{ + // reset presence fields that rely on runtime server state + // these being: port/auth port, map/playlist name, and playercount/maxplayers + m_ServerPresence.m_iPort = 0; + m_ServerPresence.m_iAuthPort = 0; + + m_ServerPresence.m_iPlayerCount = 0; // this should actually be 0 at this point, so shouldn't need updating later + m_ServerPresence.m_iMaxPlayers = 0; + + memset(m_ServerPresence.m_MapName, 0, sizeof(m_ServerPresence.m_MapName)); + memset(m_ServerPresence.m_PlaylistName, 0, sizeof(m_ServerPresence.m_PlaylistName)); + m_ServerPresence.m_bIsSingleplayerServer = false; + + m_bHasPresence = true; + m_bFirstPresenceUpdate = true; + + // code that's calling this should set up the reset fields at this point +} + +void ServerPresenceManager::DestroyPresence() +{ + m_bHasPresence = false; + + for (ServerPresenceReporter* reporter : m_vPresenceReporters) + reporter->DestroyPresence(&m_ServerPresence); +} + +void ServerPresenceManager::RunFrame(double flCurrentTime) +{ + if (!m_bHasPresence || !Cvar_ns_report_server_to_masterserver->GetBool()) // don't run until we actually have server presence + return; + + // don't run if we're sp and don't want to report sp + if (m_ServerPresence.m_bIsSingleplayerServer && !Cvar_ns_report_sp_server_to_masterserver->GetBool()) + return; + + // Call RunFrame() so that reporters can, for example, handle std::future results as soon as they arrive. + for (ServerPresenceReporter* reporter : m_vPresenceReporters) + reporter->RunFrame(flCurrentTime, &m_ServerPresence); + + // run on a specified delay + if ((flCurrentTime - m_flLastPresenceUpdate) * 1000 < Cvar_ns_server_presence_update_rate->GetFloat()) + return; + + // is this the first frame we're updating this presence? + if (m_bFirstPresenceUpdate) + { + // let reporters setup/clear any state + for (ServerPresenceReporter* reporter : m_vPresenceReporters) + reporter->CreatePresence(&m_ServerPresence); + + m_bFirstPresenceUpdate = false; + } + + m_flLastPresenceUpdate = flCurrentTime; + + for (ServerPresenceReporter* reporter : m_vPresenceReporters) + reporter->ReportPresence(&m_ServerPresence); +} + +void ServerPresenceManager::SetPort(const int iPort) +{ + // update port + m_ServerPresence.m_iPort = iPort; +} + +void ServerPresenceManager::SetAuthPort(const int iAuthPort) +{ + // update authport + m_ServerPresence.m_iAuthPort = iAuthPort; +} + +void ServerPresenceManager::SetName(const std::string sServerNameUnicode) +{ + // update name + m_ServerPresence.m_sServerName = sServerNameUnicode; +} + +void ServerPresenceManager::SetDescription(const std::string sServerDescUnicode) +{ + // update desc + m_ServerPresence.m_sServerDesc = sServerDescUnicode; +} + +void ServerPresenceManager::SetPassword(const char* pPassword) +{ + // update password + strncpy_s(m_ServerPresence.m_Password, sizeof(m_ServerPresence.m_Password), pPassword, sizeof(m_ServerPresence.m_Password) - 1); +} + +void ServerPresenceManager::SetMap(const char* pMapName, bool isInitialising) +{ + // if the server is initialising (i.e. this is first map) on sp, set the server to sp + if (isInitialising) + m_ServerPresence.m_bIsSingleplayerServer = !strncmp(pMapName, "sp_", 3); + + // update map + strncpy_s(m_ServerPresence.m_MapName, sizeof(m_ServerPresence.m_MapName), pMapName, sizeof(m_ServerPresence.m_MapName) - 1); +} + +void ServerPresenceManager::SetPlaylist(const char* pPlaylistName) +{ + // update playlist + strncpy_s( + m_ServerPresence.m_PlaylistName, + sizeof(m_ServerPresence.m_PlaylistName), + pPlaylistName, + sizeof(m_ServerPresence.m_PlaylistName) - 1); + + // update maxplayers + const char* pMaxPlayers = R2::GetCurrentPlaylistVar("max_players", true); + + // can be null in some situations, so default 6 + if (pMaxPlayers) + m_ServerPresence.m_iMaxPlayers = std::stoi(pMaxPlayers); + else + m_ServerPresence.m_iMaxPlayers = 6; +} + +void ServerPresenceManager::SetPlayerCount(const int iPlayerCount) +{ + m_ServerPresence.m_iPlayerCount = iPlayerCount; +} + +ON_DLL_LOAD_RELIESON("engine.dll", ServerPresence, ConVar, (CModule module)) +{ + g_pServerPresence = new ServerPresenceManager; + + Cvar_hostname = module.Offset(0x1315BAE8).Deref().As(); +} diff --git a/NorthstarDLL/serverpresence.h b/NorthstarDLL/serverpresence.h new file mode 100644 index 00000000..97b4654c --- /dev/null +++ b/NorthstarDLL/serverpresence.h @@ -0,0 +1,92 @@ +#pragma once +#include "convar.h" + +struct ServerPresence +{ + int m_iPort; + int m_iAuthPort; + + std::string m_sServerName; + std::string m_sServerDesc; + char m_Password[256]; // probably bigger than will ever be used in practice, lol + + char m_MapName[32]; + char m_PlaylistName[64]; + bool m_bIsSingleplayerServer; // whether the server started in sp + + int m_iPlayerCount; + int m_iMaxPlayers; + + ServerPresence() + { + memset(this, 0, sizeof(this)); + } + + ServerPresence(const ServerPresence* obj) + { + m_iPort = obj->m_iPort; + m_iAuthPort = obj->m_iAuthPort; + + m_sServerName = obj->m_sServerName; + m_sServerDesc = obj->m_sServerDesc; + memcpy(m_Password, obj->m_Password, sizeof(m_Password)); + + memcpy(m_MapName, obj->m_MapName, sizeof(m_MapName)); + memcpy(m_PlaylistName, obj->m_PlaylistName, sizeof(m_PlaylistName)); + + m_iPlayerCount = obj->m_iPlayerCount; + m_iMaxPlayers = obj->m_iMaxPlayers; + } +}; + +class ServerPresenceReporter +{ + public: + virtual void CreatePresence(const ServerPresence* pServerPresence) {} + virtual void ReportPresence(const ServerPresence* pServerPresence) {} + virtual void DestroyPresence(const ServerPresence* pServerPresence) {} + virtual void RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) {} +}; + +class ServerPresenceManager +{ + private: + ServerPresence m_ServerPresence; + + bool m_bHasPresence = false; + bool m_bFirstPresenceUpdate = false; + + std::vector m_vPresenceReporters; + + double m_flLastPresenceUpdate = 0; + ConVar* Cvar_ns_server_presence_update_rate; + + ConVar* Cvar_ns_server_name; + ConVar* Cvar_ns_server_desc; + ConVar* Cvar_ns_server_password; + + ConVar* Cvar_ns_report_server_to_masterserver; + ConVar* Cvar_ns_report_sp_server_to_masterserver; + + public: + ServerPresenceManager(); + + void AddPresenceReporter(ServerPresenceReporter* reporter); + + void CreatePresence(); + void DestroyPresence(); + void RunFrame(double flCurrentTime); + + void SetPort(const int iPort); + void SetAuthPort(const int iPort); + + void SetName(const std::string sServerNameUnicode); + void SetDescription(const std::string sServerDescUnicode); + void SetPassword(const char* pPassword); + + void SetMap(const char* pMapName, bool isInitialising = false); + void SetPlaylist(const char* pPlaylistName); + void SetPlayerCount(const int iPlayerCount); +}; + +extern ServerPresenceManager* g_pServerPresence; diff --git a/NorthstarDLL/sigscanning.cpp b/NorthstarDLL/sigscanning.cpp deleted file mode 100644 index 618645e0..00000000 --- a/NorthstarDLL/sigscanning.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "pch.h" -#include "sigscanning.h" -#include - -// note: sigscanning is only really intended to be used for resolving stuff like shared function definitions -// we mostly use raw function addresses for stuff - -size_t GetModuleLength(HMODULE moduleHandle) -{ - // based on sigscan code from ttf2sdk, which is in turn based on CSigScan from https://wiki.alliedmods.net/Signature_Scanning - MEMORY_BASIC_INFORMATION mem; - VirtualQuery(moduleHandle, &mem, sizeof(mem)); - - IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)mem.AllocationBase; - IMAGE_NT_HEADERS* pe = (IMAGE_NT_HEADERS*)((unsigned char*)dos + dos->e_lfanew); - - return pe->OptionalHeader.SizeOfImage; -} - -void* FindSignature(std::string dllName, const char* sig, const char* mask) -{ - HMODULE module = GetModuleHandleA(dllName.c_str()); - - unsigned char* dllAddress = (unsigned char*)module; - unsigned char* dllEnd = dllAddress + GetModuleLength(module); - - size_t sigLength = strlen(mask); - - for (auto i = dllAddress; i < dllEnd - sigLength + 1; i++) - { - int j = 0; - for (; j < sigLength; j++) - if (mask[j] != '?' && sig[j] != i[j]) - break; - - if (j == sigLength) // loop finished of its own accord - return i; - } - - return nullptr; -} diff --git a/NorthstarDLL/sigscanning.h b/NorthstarDLL/sigscanning.h deleted file mode 100644 index 5d255152..00000000 --- a/NorthstarDLL/sigscanning.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include - -// note: sigscanning is only really intended to be used for resolving stuff like shared function definitions -// we mostly use raw function addresses for stuff - -void* FindSignature(std::string dllName, const char* sig, const char* mask); diff --git a/NorthstarDLL/sourceconsole.cpp b/NorthstarDLL/sourceconsole.cpp index 2e816485..ad31e09d 100644 --- a/NorthstarDLL/sourceconsole.cpp +++ b/NorthstarDLL/sourceconsole.cpp @@ -3,78 +3,71 @@ #include "sourceconsole.h" #include "sourceinterface.h" #include "concommand.h" -#include "hookutils.h" +#include "printcommand.h" -SourceInterface* g_SourceGameConsole; +SourceInterface* g_pSourceGameConsole; void ConCommand_toggleconsole(const CCommand& arg) { - if ((*g_SourceGameConsole)->IsConsoleVisible()) - (*g_SourceGameConsole)->Hide(); + if ((*g_pSourceGameConsole)->IsConsoleVisible()) + (*g_pSourceGameConsole)->Hide(); else - (*g_SourceGameConsole)->Activate(); + (*g_pSourceGameConsole)->Activate(); } -typedef void (*OnCommandSubmittedType)(CConsoleDialog* consoleDialog, const char* pCommand); -OnCommandSubmittedType onCommandSubmittedOriginal; -void OnCommandSubmittedHook(CConsoleDialog* consoleDialog, const char* pCommand) +void ConCommand_showconsole(const CCommand& arg) +{ + (*g_pSourceGameConsole)->Activate(); +} + +void ConCommand_hideconsole(const CCommand& arg) +{ + (*g_pSourceGameConsole)->Hide(); +} + +void SourceConsoleSink::sink_it_(const spdlog::details::log_msg& msg) +{ + if (!(*g_pSourceGameConsole)->m_bInitialized) + return; + + spdlog::memory_buf_t formatted; + spdlog::sinks::base_sink::formatter_->format(msg, formatted); + (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->ColorPrint(m_LogColours[msg.level], fmt::to_string(formatted).c_str()); +} + +void SourceConsoleSink::flush_() {} + +// clang-format off +HOOK(OnCommandSubmittedHook, OnCommandSubmitted, +void, __fastcall, (CConsoleDialog* consoleDialog, const char* pCommand)) +// clang-format on { consoleDialog->m_pConsolePanel->Print("] "); consoleDialog->m_pConsolePanel->Print(pCommand); consoleDialog->m_pConsolePanel->Print("\n"); - // todo: call the help command in the future + TryPrintCvarHelpForCommand(pCommand); - onCommandSubmittedOriginal(consoleDialog, pCommand); + OnCommandSubmitted(consoleDialog, pCommand); } // called from sourceinterface.cpp in client createinterface hooks, on GameClientExports001 void InitialiseConsoleOnInterfaceCreation() { - (*g_SourceGameConsole)->Initialize(); + (*g_pSourceGameConsole)->Initialize(); + // hook OnCommandSubmitted so we print inputted commands + OnCommandSubmittedHook.Dispatch((*g_pSourceGameConsole)->m_pConsole->m_vtable->OnCommandSubmitted); auto consoleLogger = std::make_shared(); consoleLogger->set_pattern("[%l] %v"); - spdlog::default_logger()->sinks().push_back(consoleLogger); - - // hook OnCommandSubmitted so we print inputted commands - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, - (void*)((*g_SourceGameConsole)->m_pConsole->m_vtable->OnCommandSubmitted), - &OnCommandSubmittedHook, - reinterpret_cast(&onCommandSubmittedOriginal)); -} - -void InitialiseSourceConsole(HMODULE baseAddress) -{ - g_SourceGameConsole = new SourceInterface("client.dll", "GameConsole004"); - RegisterConCommand("toggleconsole", ConCommand_toggleconsole, "toggles the console", FCVAR_DONTRECORD); -} - -// logging stuff - -SourceConsoleSink::SourceConsoleSink() -{ - logColours.emplace(spdlog::level::trace, SourceColor(0, 255, 255, 255)); - logColours.emplace(spdlog::level::debug, SourceColor(0, 255, 255, 255)); - logColours.emplace(spdlog::level::info, SourceColor(255, 255, 255, 255)); - logColours.emplace(spdlog::level::warn, SourceColor(255, 255, 0, 255)); - logColours.emplace(spdlog::level::err, SourceColor(255, 0, 0, 255)); - logColours.emplace(spdlog::level::critical, SourceColor(255, 0, 0, 255)); - logColours.emplace(spdlog::level::off, SourceColor(0, 0, 0, 0)); } -void SourceConsoleSink::sink_it_(const spdlog::details::log_msg& msg) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", SourceConsole, ConCommand, (CModule module)) { - if (!(*g_SourceGameConsole)->m_bInitialized) - return; + g_pSourceGameConsole = new SourceInterface("client.dll", "GameConsole004"); - spdlog::memory_buf_t formatted; - spdlog::sinks::base_sink::formatter_->format(msg, formatted); - (*g_SourceGameConsole) - ->m_pConsole->m_pConsolePanel->ColorPrint(logColours[msg.level], fmt::to_string(formatted).c_str()); // todo needs colour support + RegisterConCommand("toggleconsole", ConCommand_toggleconsole, "Show/hide the console.", FCVAR_DONTRECORD); + RegisterConCommand("showconsole", ConCommand_showconsole, "Show the console.", FCVAR_DONTRECORD); + RegisterConCommand("hideconsole", ConCommand_hideconsole, "Hide the console.", FCVAR_DONTRECORD); } - -void SourceConsoleSink::flush_() {} diff --git a/NorthstarDLL/sourceconsole.h b/NorthstarDLL/sourceconsole.h index 1dee136a..e811f523 100644 --- a/NorthstarDLL/sourceconsole.h +++ b/NorthstarDLL/sourceconsole.h @@ -86,21 +86,24 @@ class CGameConsole CConsoleDialog* m_pConsole; }; -extern SourceInterface* g_SourceGameConsole; +extern SourceInterface* g_pSourceGameConsole; // spdlog logger class SourceConsoleSink : public spdlog::sinks::base_sink { private: - std::map logColours; - - public: - SourceConsoleSink(); + std::map m_LogColours = { + {spdlog::level::trace, SourceColor(0, 255, 255, 255)}, + {spdlog::level::debug, SourceColor(0, 255, 255, 255)}, + {spdlog::level::info, SourceColor(255, 255, 255, 255)}, + {spdlog::level::warn, SourceColor(255, 255, 0, 255)}, + {spdlog::level::err, SourceColor(255, 0, 0, 255)}, + {spdlog::level::critical, SourceColor(255, 0, 0, 255)}, + {spdlog::level::off, SourceColor(0, 0, 0, 0)}}; protected: void sink_it_(const spdlog::details::log_msg& msg) override; void flush_() override; }; -void InitialiseSourceConsole(HMODULE baseAddress); void InitialiseConsoleOnInterfaceCreation(); diff --git a/NorthstarDLL/sourceinterface.cpp b/NorthstarDLL/sourceinterface.cpp index 56020e5e..d5f7b7cd 100644 --- a/NorthstarDLL/sourceinterface.cpp +++ b/NorthstarDLL/sourceinterface.cpp @@ -1,82 +1,49 @@ #include "pch.h" #include "sourceinterface.h" -#include "hooks.h" -#include "hookutils.h" - #include "sourceconsole.h" -#include "context.h" -#include "convar.h" -#include + +AUTOHOOK_INIT() // really wanted to do a modular callback system here but honestly couldn't be bothered so hardcoding stuff for now: todo later -CreateInterfaceFn clientCreateInterfaceOriginal; -void* ClientCreateInterfaceHook(const char* pName, int* pReturnCode) +// clang-format off +AUTOHOOK_PROCADDRESS(ClientCreateInterface, client.dll, CreateInterface, +void*, __fastcall, (const char* pName, const int* pReturnCode)) +// clang-format on { - void* ret = clientCreateInterfaceOriginal(pName, pReturnCode); - + void* ret = ClientCreateInterface(pName, pReturnCode); spdlog::info("CreateInterface CLIENT {}", pName); + if (!strcmp(pName, "GameClientExports001")) InitialiseConsoleOnInterfaceCreation(); return ret; } -CreateInterfaceFn serverCreateInterfaceOriginal; -void* ServerCreateInterfaceHook(const char* pName, int* pReturnCode) +// clang-format off +AUTOHOOK_PROCADDRESS(ServerCreateInterface, server.dll, CreateInterface, +void*, __fastcall, (const char* pName, const int* pReturnCode)) +// clang-format on { - void* ret = serverCreateInterfaceOriginal(pName, pReturnCode); - - std::cout << "CreateInterface SERVER " << pName << std::endl; + void* ret = ServerCreateInterface(pName, pReturnCode); + spdlog::info("CreateInterface SERVER {}", pName); return ret; } -CreateInterfaceFn engineCreateInterfaceOriginal; -void* EngineCreateInterfaceHook(const char* pName, int* pReturnCode) +// clang-format off +AUTOHOOK_PROCADDRESS(EngineCreateInterface, engine.dll, CreateInterface, +void*, __fastcall, (const char* pName, const int* pReturnCode)) +// clang-format on { - void* ret = engineCreateInterfaceOriginal(pName, pReturnCode); - - std::cout << "CreateInterface ENGINE " << pName << std::endl; + void* ret = EngineCreateInterface(pName, pReturnCode); + spdlog::info("CreateInterface ENGINE {}", pName); return ret; } -void HookClientCreateInterface(HMODULE baseAddress) -{ - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, - reinterpret_cast(GetProcAddress(baseAddress, "CreateInterface")), - &ClientCreateInterfaceHook, - reinterpret_cast(&clientCreateInterfaceOriginal)); -} - -void HookServerCreateInterface(HMODULE baseAddress) -{ - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, - reinterpret_cast(GetProcAddress(baseAddress, "CreateInterface")), - &ServerCreateInterfaceHook, - reinterpret_cast(&serverCreateInterfaceOriginal)); -} - -void HookEngineCreateInterface(HMODULE baseAddress) -{ - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, - reinterpret_cast(GetProcAddress(baseAddress, "CreateInterface")), - &EngineCreateInterfaceHook, - reinterpret_cast(&engineCreateInterfaceOriginal)); -} - -void InitialiseInterfaceCreationHooks() -{ - AddDllLoadCallback("client.dll", HookClientCreateInterface); - - // not used atm - // AddDllLoadCallback("server.dll", HookServerCreateInterface); - // AddDllLoadCallback("engine.dll", HookEngineCreateInterface); -} +// clang-format off +ON_DLL_LOAD("client.dll", ClientInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(client.dll)} +ON_DLL_LOAD("server.dll", ServerInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(server.dll)} +ON_DLL_LOAD("engine.dll", EngineInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(engine.dll)} +// clang-format on diff --git a/NorthstarDLL/sourceinterface.h b/NorthstarDLL/sourceinterface.h index 238ebec8..474e961b 100644 --- a/NorthstarDLL/sourceinterface.h +++ b/NorthstarDLL/sourceinterface.h @@ -29,6 +29,3 @@ template class SourceInterface return m_interface; } }; - -// functions for interface creation callbacks -void InitialiseInterfaceCreationHooks(); diff --git a/NorthstarDLL/squirrel.cpp b/NorthstarDLL/squirrel.cpp index 2fa957fb..23935827 100644 --- a/NorthstarDLL/squirrel.cpp +++ b/NorthstarDLL/squirrel.cpp @@ -1,271 +1,224 @@ #include "pch.h" #include "squirrel.h" -#include "hooks.h" -#include "hookutils.h" -#include "sigscanning.h" #include "concommand.h" #include "modmanager.h" -#include -#include "gameutils.h" +#include "dedicated.h" +#include "r2engine.h" +#include "tier0.h" -// hook forward declarations -typedef SQInteger (*SQPrintType)(void* sqvm, char* fmt, ...); -SQPrintType ClientSQPrint; -SQPrintType UISQPrint; -SQPrintType ServerSQPrint; -template SQInteger SQPrintHook(void* sqvm, char* fmt, ...); +AUTOHOOK_INIT() -typedef void* (*CreateNewVMType)(void* a1, ScriptContext contextArg); -CreateNewVMType ClientCreateNewVM; // only need a client one since ui doesn't have its own func for this -CreateNewVMType ServerCreateNewVM; -template void* CreateNewVMHook(void* a1, ScriptContext contextArg); - -typedef void (*DestroyVMType)(void* a1, void* sqvm); -DestroyVMType ClientDestroyVM; // only need a client one since ui doesn't have its own func for this -DestroyVMType ServerDestroyVM; -template void DestroyVMHook(void* a1, void* sqvm); +const char* GetContextName(ScriptContext context) +{ + switch (context) + { + case ScriptContext::CLIENT: + return "CLIENT"; + case ScriptContext::SERVER: + return "SERVER"; + case ScriptContext::UI: + return "UI"; + default: + return "UNKNOWN"; + } +} -typedef void (*ScriptCompileError)(void* sqvm, const char* error, const char* file, int line, int column); -ScriptCompileError ClientSQCompileError; // only need a client one since ui doesn't have its own func for this -ScriptCompileError ServerSQCompileError; -template void ScriptCompileErrorHook(void* sqvm, const char* error, const char* file, int line, int column); +eSQReturnType SQReturnTypeFromString(const char* pReturnType) +{ + static const std::map sqReturnTypeNameToString = { + {"bool", eSQReturnType::Boolean}, + {"float", eSQReturnType::Float}, + {"vector", eSQReturnType::Vector}, + {"int", eSQReturnType::Integer}, + {"entity", eSQReturnType::Entity}, + {"string", eSQReturnType::String}, + {"array", eSQReturnType::Arrays}, + {"asset", eSQReturnType::Asset}, + {"table", eSQReturnType::Table}}; + + if (sqReturnTypeNameToString.find(pReturnType) != sqReturnTypeNameToString.end()) + return sqReturnTypeNameToString.at(pReturnType); + else + return eSQReturnType::Default; // previous default value +} -typedef char (*CallScriptInitCallbackType)(void* sqvm, const char* callback); -CallScriptInitCallbackType ClientCallScriptInitCallback; -CallScriptInitCallbackType ServerCallScriptInitCallback; -template char CallScriptInitCallbackHook(void* sqvm, const char* callback); +const char* SQTypeNameFromID(int type) +{ + switch (type) + { + case OT_ASSET: + return "asset"; + case OT_INTEGER: + return "int"; + case OT_BOOL: + return "bool"; + case SQOBJECT_NUMERIC: + return "float or int"; + case OT_NULL: + return "null"; + case OT_VECTOR: + return "vector"; + case 0: + return "var"; + case OT_USERDATA: + return "userdata"; + case OT_FLOAT: + return "float"; + case OT_STRING: + return "string"; + case OT_ARRAY: + return "array"; + case 0x8000200: + return "function"; + case 0x8100000: + return "structdef"; + case OT_THREAD: + return "thread"; + case OT_FUNCPROTO: + return "function"; + case OT_CLAAS: + return "class"; + case OT_WEAKREF: + return "weakref"; + case 0x8080000: + return "unimplemented function"; + case 0x8200000: + return "struct instance"; + case OT_TABLE: + return "table"; + case 0xA008000: + return "instance"; + case OT_ENTITY: + return "entity"; + } + return ""; +} -RegisterSquirrelFuncType ClientRegisterSquirrelFunc; -RegisterSquirrelFuncType ServerRegisterSquirrelFunc; -template int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown); +// needed to define implementations for squirrelmanager outside of squirrel.h without compiler errors +template class SquirrelManager; +template class SquirrelManager; +template class SquirrelManager; -// core sqvm funcs -sq_compilebufferType ClientSq_compilebuffer; -sq_compilebufferType ServerSq_compilebuffer; +template void SquirrelManager::VMCreated(CSquirrelVM* newSqvm) +{ + m_pSQVM = newSqvm; -sq_pushroottableType ClientSq_pushroottable; -sq_pushroottableType ServerSq_pushroottable; + for (SQFuncRegistration* funcReg : m_funcRegistrations) + { + spdlog::info("Registering {} function {}", GetContextName(context), funcReg->squirrelFuncName); + RegisterSquirrelFunc(m_pSQVM, funcReg, 1); + } -sq_callType ClientSq_call; -sq_callType ServerSq_call; + for (auto& pair : g_pModManager->m_DependencyConstants) + { + bool bWasFound = false; + for (Mod& dependency : g_pModManager->m_LoadedMods) + { + if (!dependency.m_bEnabled) + continue; -// sq stack array funcs -sq_newarrayType ClientSq_newarray; -sq_newarrayType ServerSq_newarray; + if (dependency.Name == pair.second) + { + bWasFound = true; + break; + } + } -sq_arrayappendType ClientSq_arrayappend; -sq_arrayappendType ServerSq_arrayappend; + defconst(m_pSQVM, pair.first.c_str(), bWasFound); + } +} -// sq stack push funcs -sq_pushstringType ClientSq_pushstring; -sq_pushstringType ServerSq_pushstring; +template void SquirrelManager::VMDestroyed() +{ + m_pSQVM = nullptr; +} -sq_pushintegerType ClientSq_pushinteger; -sq_pushintegerType ServerSq_pushinteger; +template void SquirrelManager::ExecuteCode(const char* pCode) +{ + if (!m_pSQVM || !m_pSQVM->sqvm) + { + spdlog::error("Cannot execute code, {} squirrel vm is not initialised", GetContextName(context)); + return; + } -sq_pushfloatType ClientSq_pushfloat; -sq_pushfloatType ServerSq_pushfloat; + spdlog::info("Executing {} script code {} ", GetContextName(context), pCode); -sq_pushboolType ClientSq_pushbool; -sq_pushboolType ServerSq_pushbool; + std::string strCode(pCode); + CompileBufferState bufferState = CompileBufferState(strCode); -sq_pusherrorType ClientSq_pusherror; -sq_pusherrorType ServerSq_pusherror; + SQRESULT compileResult = compilebuffer(&bufferState, "console"); + spdlog::info("sq_compilebuffer returned {}", PrintSQRESULT.at(compileResult)); -sq_defconst ClientSq_defconst; -sq_defconst ServerSq_defconst; + if (compileResult != SQRESULT_ERROR) + { + pushroottable(m_pSQVM->sqvm); + SQRESULT callResult = call(m_pSQVM->sqvm, 0); + spdlog::info("sq_call returned {}", PrintSQRESULT.at(callResult)); + } +} -sq_pushAssetType ClientSq_pushAsset; -sq_pushAssetType ServerSq_pushAsset; +template void SquirrelManager::AddFuncRegistration( + std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func) +{ + SQFuncRegistration* reg = new SQFuncRegistration; -// sq stack get funcs -sq_getstringType ClientSq_getstring; -sq_getstringType ServerSq_getstring; + reg->squirrelFuncName = new char[name.size() + 1]; + strcpy((char*)reg->squirrelFuncName, name.c_str()); + reg->cppFuncName = reg->squirrelFuncName; -sq_getintegerType ClientSq_getinteger; -sq_getintegerType ServerSq_getinteger; + reg->helpText = new char[helpText.size() + 1]; + strcpy((char*)reg->helpText, helpText.c_str()); -sq_getfloatType ClientSq_getfloat; -sq_getfloatType ServerSq_getfloat; + reg->returnTypeString = new char[returnType.size() + 1]; + strcpy((char*)reg->returnTypeString, returnType.c_str()); + reg->returnType = SQReturnTypeFromString(returnType.c_str()); -sq_getboolType ClientSq_getbool; -sq_getboolType ServerSq_getbool; + reg->argTypes = new char[argTypes.size() + 1]; + strcpy((char*)reg->argTypes, argTypes.c_str()); -sq_getType ClientSq_sq_get; -sq_getType ServerSq_sq_get; + reg->funcPtr = func; -sq_newSlotType ServerSq_newSlot; -sq_newSlotType ClientSq_newSlot; + m_funcRegistrations.push_back(reg); +} -sq_newTableType ServerSq_newTable; -sq_newTableType ClientSq_newTable; +template SQRESULT SquirrelManager::setupfunc(const SQChar* funcname) +{ + pushroottable(m_pSQVM->sqvm); + pushstring(m_pSQVM->sqvm, funcname, -1); -template void ExecuteCodeCommand(const CCommand& args); + SQRESULT result = get(m_pSQVM->sqvm, -2); + if (result != SQRESULT_ERROR) + pushroottable(m_pSQVM->sqvm); -// inits -SquirrelManager* g_ClientSquirrelManager; -SquirrelManager* g_ServerSquirrelManager; -SquirrelManager* g_UISquirrelManager; + return result; +} -SQInteger NSTestFunc(void* sqvm) +template void SquirrelManager::AddFuncOverride(std::string name, SQFunction func) { - return 1; + m_funcOverrides[name] = func; } -void InitialiseClientSquirrel(HMODULE baseAddress) +// hooks +bool IsUIVM(ScriptContext context, HSquirrelVM* pSqvm) { - HookEnabler hook; - - // client inits - g_ClientSquirrelManager = new SquirrelManager(); - - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x12B00, - &SQPrintHook, - reinterpret_cast(&ClientSQPrint)); // client print function - RegisterConCommand( - "script_client", ExecuteCodeCommand, "Executes script code on the client vm", FCVAR_CLIENTDLL); - - // ui inits - g_UISquirrelManager = new SquirrelManager(); - - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x12BA0, &SQPrintHook, reinterpret_cast(&UISQPrint)); // ui print function - RegisterConCommand("script_ui", ExecuteCodeCommand, "Executes script code on the ui vm", FCVAR_CLIENTDLL); - - // inits for both client and ui, since they share some functions - ClientSq_compilebuffer = (sq_compilebufferType)((char*)baseAddress + 0x3110); - ClientSq_pushroottable = (sq_pushroottableType)((char*)baseAddress + 0x5860); - ClientSq_call = (sq_callType)((char*)baseAddress + 0x8650); - ClientRegisterSquirrelFunc = (RegisterSquirrelFuncType)((char*)baseAddress + 0x108E0); - - ClientSq_newarray = (sq_newarrayType)((char*)baseAddress + 0x39F0); - ClientSq_arrayappend = (sq_arrayappendType)((char*)baseAddress + 0x3C70); - - ClientSq_pushstring = (sq_pushstringType)((char*)baseAddress + 0x3440); - ClientSq_pushinteger = (sq_pushintegerType)((char*)baseAddress + 0x36A0); - ClientSq_pushfloat = (sq_pushfloatType)((char*)baseAddress + 0x3800); - ClientSq_pushbool = (sq_pushboolType)((char*)baseAddress + 0x3710); - ClientSq_pusherror = (sq_pusherrorType)((char*)baseAddress + 0x8470); - ClientSq_pushAsset = (sq_pushAssetType)((char*)baseAddress + 0x3560); - - ClientSq_getstring = (sq_getstringType)((char*)baseAddress + 0x60C0); - ClientSq_getinteger = (sq_getintegerType)((char*)baseAddress + 0x60E0); - ClientSq_getfloat = (sq_getfloatType)((char*)baseAddress + 0x6100); - ClientSq_getbool = (sq_getboolType)((char*)baseAddress + 0x6130); - - ClientSq_sq_get = (sq_getType)((char*)baseAddress + 0x7C30); - - ClientSq_defconst = (sq_defconst)((char*)baseAddress + 0x12120); - - // Table functions - ClientSq_newTable = (sq_newTableType)((char*)baseAddress + 0x3960); - ClientSq_newSlot = (sq_newSlotType)((char*)baseAddress + 0x70B0); - - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x26130, - &CreateNewVMHook, - reinterpret_cast(&ClientCreateNewVM)); // client createnewvm function - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x26E70, - &DestroyVMHook, - reinterpret_cast(&ClientDestroyVM)); // client destroyvm function - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x79A50, - &ScriptCompileErrorHook, - reinterpret_cast(&ClientSQCompileError)); // client compileerror function - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x10190, - &CallScriptInitCallbackHook, - reinterpret_cast(&ClientCallScriptInitCallback)); // client callscriptinitcallback function - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x108E0, - &RegisterSquirrelFuncHook, - reinterpret_cast(&ClientRegisterSquirrelFunc)); // client registersquirrelfunc function + return context != ScriptContext::SERVER && g_pSquirrel->m_pSQVM && + g_pSquirrel->m_pSQVM->sqvm == pSqvm; } -void InitialiseServerSquirrel(HMODULE baseAddress) +template void* (*__fastcall sq_compiler_create)(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError); +template void* __fastcall sq_compiler_createHook(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError) { - g_ServerSquirrelManager = new SquirrelManager(); - - HookEnabler hook; - - ServerSq_compilebuffer = (sq_compilebufferType)((char*)baseAddress + 0x3110); - ServerSq_pushroottable = (sq_pushroottableType)((char*)baseAddress + 0x5840); - ServerSq_call = (sq_callType)((char*)baseAddress + 0x8620); - ServerRegisterSquirrelFunc = (RegisterSquirrelFuncType)((char*)baseAddress + 0x1DD10); - - ServerSq_newarray = (sq_newarrayType)((char*)baseAddress + 0x39F0); - ServerSq_arrayappend = (sq_arrayappendType)((char*)baseAddress + 0x3C70); - - ServerSq_pushstring = (sq_pushstringType)((char*)baseAddress + 0x3440); - ServerSq_pushinteger = (sq_pushintegerType)((char*)baseAddress + 0x36A0); - ServerSq_pushfloat = (sq_pushfloatType)((char*)baseAddress + 0x3800); - ServerSq_pushbool = (sq_pushboolType)((char*)baseAddress + 0x3710); - ServerSq_pusherror = (sq_pusherrorType)((char*)baseAddress + 0x8440); - ServerSq_pushAsset = (sq_pushAssetType)((char*)baseAddress + 0x3560); - - ServerSq_getstring = (sq_getstringType)((char*)baseAddress + 0x60A0); - ServerSq_getinteger = (sq_getintegerType)((char*)baseAddress + 0x60C0); - ServerSq_getfloat = (sq_getfloatType)((char*)baseAddress + 0x60E0); - ServerSq_getbool = (sq_getboolType)((char*)baseAddress + 0x6110); - - ServerSq_sq_get = (sq_getType)((char*)baseAddress + 0x7C00); - - ServerSq_defconst = (sq_defconst)((char*)baseAddress + 0x1F550); - - ServerSq_newSlot = (sq_newSlotType)((char*)baseAddress + 0x7080); - ServerSq_newTable = (sq_newTableType)((char*)baseAddress + 0x3960); - - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x1FE90, - &SQPrintHook, - reinterpret_cast(&ServerSQPrint)); // server print function - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x260E0, - &CreateNewVMHook, - reinterpret_cast(&ServerCreateNewVM)); // server createnewvm function - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x26E20, - &DestroyVMHook, - reinterpret_cast(&ServerDestroyVM)); // server destroyvm function - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x799E0, - &ScriptCompileErrorHook, - reinterpret_cast(&ServerSQCompileError)); // server compileerror function - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x1D5C0, - &CallScriptInitCallbackHook, - reinterpret_cast(&ServerCallScriptInitCallback)); // server callscriptinitcallback function - - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x1DD10, - &RegisterSquirrelFuncHook, - reinterpret_cast(&ServerRegisterSquirrelFunc)); // server registersquirrelfunc function - - // cheat and clientcmd_can_execute allows clients to execute this, but since it's unsafe we only allow it when cheats are enabled - // for script_client and script_ui, we don't use cheats, so clients can execute them on themselves all they want - RegisterConCommand( - "script", - ExecuteCodeCommand, - "Executes script code on the server vm", - FCVAR_GAMEDLL | FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_CHEAT); + // store whether errors generated from this compile should be fatal + if (IsUIVM(context, sqvm)) + g_pSquirrel->m_bFatalCompilationErrors = bShouldThrowError; + else + g_pSquirrel->m_bFatalCompilationErrors = bShouldThrowError; + + return sq_compiler_create(sqvm, a2, a3, bShouldThrowError); } -// hooks -template SQInteger SQPrintHook(void* sqvm, char* fmt, ...) +template SQInteger (*SQPrint)(HSquirrelVM* sqvm, const char* fmt); +template SQInteger SQPrintHook(HSquirrelVM* sqvm, const char* fmt, ...) { va_list va; va_start(va, fmt); @@ -285,189 +238,161 @@ template SQInteger SQPrintHook(void* sqvm, char* fmt, .. return 0; } -template void* CreateNewVMHook(void* a1, ScriptContext realContext) +template CSquirrelVM* (*__fastcall CreateNewVM)(void* a1, ScriptContext realContext); +template CSquirrelVM* __fastcall CreateNewVMHook(void* a1, ScriptContext realContext) { - void* sqvm; - - if (context == ScriptContext::CLIENT) - { - sqvm = ClientCreateNewVM(a1, realContext); - - if (realContext == ScriptContext::UI) - g_UISquirrelManager->VMCreated(sqvm); - else - g_ClientSquirrelManager->VMCreated(sqvm); - } - else if (context == ScriptContext::SERVER) - { - sqvm = ServerCreateNewVM(a1, context); - g_ServerSquirrelManager->VMCreated(sqvm); - } + CSquirrelVM* sqvm = CreateNewVM(a1, realContext); + if (realContext == ScriptContext::UI) + g_pSquirrel->VMCreated(sqvm); + else + g_pSquirrel->VMCreated(sqvm); - spdlog::info("CreateNewVM {} {}", GetContextName(realContext), sqvm); + spdlog::info("CreateNewVM {} {}", GetContextName(realContext), (void*)sqvm); return sqvm; } -template void DestroyVMHook(void* a1, void* sqvm) +template void (*__fastcall DestroyVM)(void* a1, HSquirrelVM* sqvm); +template void __fastcall DestroyVMHook(void* a1, HSquirrelVM* sqvm) { ScriptContext realContext = context; // ui and client use the same function so we use this for prints - - if (context == ScriptContext::CLIENT) + if (IsUIVM(context, sqvm)) { - if (g_ClientSquirrelManager->sqvm == sqvm) - g_ClientSquirrelManager->VMDestroyed(); - else if (g_UISquirrelManager->sqvm == sqvm) - { - g_UISquirrelManager->VMDestroyed(); - realContext = ScriptContext::UI; - } - - ClientDestroyVM(a1, sqvm); - } - else if (context == ScriptContext::SERVER) - { - g_ServerSquirrelManager->VMDestroyed(); - ServerDestroyVM(a1, sqvm); + realContext = ScriptContext::UI; + g_pSquirrel->VMDestroyed(); } + else + DestroyVM(a1, sqvm); - spdlog::info("DestroyVM {} {}", GetContextName(realContext), sqvm); + spdlog::info("DestroyVM {} {}", GetContextName(realContext), (void*)sqvm); } -template void ScriptCompileErrorHook(void* sqvm, const char* error, const char* file, int line, int column) +template +void (*__fastcall SQCompileError)(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column); +template +void __fastcall ScriptCompileErrorHook(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column) { + bool bIsFatalError = g_pSquirrel->m_bFatalCompilationErrors; ScriptContext realContext = context; // ui and client use the same function so we use this for prints - if (context == ScriptContext::CLIENT && sqvm == g_UISquirrelManager->sqvm) + if (IsUIVM(context, sqvm)) + { realContext = ScriptContext::UI; + bIsFatalError = g_pSquirrel->m_bFatalCompilationErrors; + } spdlog::error("{} SCRIPT COMPILE ERROR {}", GetContextName(realContext), error); spdlog::error("{} line [{}] column [{}]", file, line, column); - // dont call the original since it kills game - // in the future it'd be nice to do an actual error with UICodeCallback_ErrorDialog here, but only if we're compiling level scripts - // compilestring and stuff shouldn't tho - // though, that also has potential to be REALLY bad if we're compiling ui scripts lol + // use disconnect to display an error message for the compile error, but only if the compilation error was fatal + // todo, we could get this from sqvm itself probably, rather than hooking sq_compiler_create + if (bIsFatalError) + { + // kill dedicated server if we hit this + if (IsDedicatedServer()) + abort(); + else + { + R2::Cbuf_AddText( + R2::Cbuf_GetCurrentPlayer(), + fmt::format("disconnect \"Encountered {} script compilation error, see console for details.\"", GetContextName(realContext)) + .c_str(), + R2::cmd_source_t::kCommandSrcCode); + + // likely temp: show console so user can see any errors, as error message wont display if ui is dead + // maybe we could disable all mods other than the coremods and try a reload before doing this? + // could also maybe do some vgui bullshit to show something visually rather than console + if (realContext == ScriptContext::UI) + R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "showconsole", R2::cmd_source_t::kCommandSrcCode); + } + } + + // dont call the original function since it kills game lol } -template char CallScriptInitCallbackHook(void* sqvm, const char* callback) +template +int64_t (*__fastcall RegisterSquirrelFunction)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); +template +int64_t __fastcall RegisterSquirrelFunctionHook(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown) { - char ret; - - if (context == ScriptContext::CLIENT) + if (IsUIVM(context, sqvm->sqvm)) { - ScriptContext realContext = context; // ui and client use the same function so we use this for prints - bool shouldCallCustomCallbacks = false; - - // since we don't hook arbitrary callbacks yet, make sure we're only doing callbacks on inits - if (!strcmp(callback, "UICodeCallback_UIInit")) + if (g_pSquirrel->m_funcOverrides.count(funcReg->squirrelFuncName)) { - realContext = ScriptContext::UI; - shouldCallCustomCallbacks = true; + g_pSquirrel->m_funcOriginals[funcReg->squirrelFuncName] = funcReg->funcPtr; + funcReg->funcPtr = g_pSquirrel->m_funcOverrides[funcReg->squirrelFuncName]; + spdlog::info("Replacing {} in UI", std::string(funcReg->squirrelFuncName)); } - else if (!strcmp(callback, "ClientCodeCallback_MapSpawn")) - shouldCallCustomCallbacks = true; - // run before callbacks - // todo: we need to verify if RunOn is valid for current state before calling callbacks - if (shouldCallCustomCallbacks) - { - for (Mod mod : g_ModManager->m_loadedMods) - { - if (!mod.Enabled) - continue; + return g_pSquirrel->RegisterSquirrelFunc(sqvm, funcReg, unknown); + } - for (ModScript script : mod.Scripts) - { - for (ModScriptCallback modCallback : script.Callbacks) - { - if (modCallback.Context == realContext && modCallback.BeforeCallback.length()) - { - spdlog::info( - "Running custom {} script callback \"{}\"", GetContextName(realContext), modCallback.BeforeCallback); - ClientCallScriptInitCallback(sqvm, modCallback.BeforeCallback.c_str()); - } - } - } - } - } + if (g_pSquirrel->m_funcOverrides.find(funcReg->squirrelFuncName) != g_pSquirrel->m_funcOverrides.end()) + { + g_pSquirrel->m_funcOriginals[funcReg->squirrelFuncName] = funcReg->funcPtr; + funcReg->funcPtr = g_pSquirrel->m_funcOverrides[funcReg->squirrelFuncName]; + spdlog::info("Replacing {} in Client", std::string(funcReg->squirrelFuncName)); + } - spdlog::info("{} CodeCallback {} called", GetContextName(realContext), callback); - if (!shouldCallCustomCallbacks) - spdlog::info("Not executing custom callbacks for CodeCallback {}", callback); - ret = ClientCallScriptInitCallback(sqvm, callback); + return g_pSquirrel->RegisterSquirrelFunc(sqvm, funcReg, unknown); +} - // run after callbacks - if (shouldCallCustomCallbacks) - { - for (Mod mod : g_ModManager->m_loadedMods) - { - if (!mod.Enabled) - continue; +template bool (*__fastcall CallScriptInitCallback)(void* sqvm, const char* callback); +template bool __fastcall CallScriptInitCallbackHook(void* sqvm, const char* callback) +{ + ScriptContext realContext = context; + bool bShouldCallCustomCallbacks = true; - for (ModScript script : mod.Scripts) - { - for (ModScriptCallback modCallback : script.Callbacks) - { - if (modCallback.Context == realContext && modCallback.AfterCallback.length()) - { - spdlog::info( - "Running custom {} script callback \"{}\"", GetContextName(realContext), modCallback.AfterCallback); - ClientCallScriptInitCallback(sqvm, modCallback.AfterCallback.c_str()); - } - } - } - } - } + if (context == ScriptContext::CLIENT) + { + if (!strcmp(callback, "UICodeCallback_UIInit")) + realContext = ScriptContext::UI; + else if (strcmp(callback, "ClientCodeCallback_MapSpawn")) + bShouldCallCustomCallbacks = false; } else if (context == ScriptContext::SERVER) - { - // since we don't hook arbitrary callbacks yet, make sure we're only doing callbacks on inits - bool shouldCallCustomCallbacks = !strcmp(callback, "CodeCallback_MapSpawn"); + bShouldCallCustomCallbacks = !strcmp(callback, "CodeCallback_MapSpawn"); - // run before callbacks - // todo: we need to verify if RunOn is valid for current state before calling callbacks - if (shouldCallCustomCallbacks) + if (bShouldCallCustomCallbacks) + { + for (Mod mod : g_pModManager->m_LoadedMods) { - for (Mod mod : g_ModManager->m_loadedMods) - { - if (!mod.Enabled) - continue; + if (!mod.m_bEnabled) + continue; - for (ModScript script : mod.Scripts) + for (ModScript script : mod.Scripts) + { + for (ModScriptCallback modCallback : script.Callbacks) { - for (ModScriptCallback modCallback : script.Callbacks) + if (modCallback.Context == realContext && modCallback.BeforeCallback.length()) { - if (modCallback.Context == ScriptContext::SERVER && modCallback.BeforeCallback.length()) - { - spdlog::info("Running custom {} script callback \"{}\"", GetContextName(context), modCallback.BeforeCallback); - ServerCallScriptInitCallback(sqvm, modCallback.BeforeCallback.c_str()); - } + spdlog::info("Running custom {} script callback \"{}\"", GetContextName(realContext), modCallback.BeforeCallback); + CallScriptInitCallback(sqvm, modCallback.BeforeCallback.c_str()); } } } } + } - spdlog::info("{} CodeCallback {} called", GetContextName(context), callback); - if (!shouldCallCustomCallbacks) - spdlog::info("Not executing custom callbacks for CodeCallback {}", callback); - ret = ServerCallScriptInitCallback(sqvm, callback); + spdlog::info("{} CodeCallback {} called", GetContextName(realContext), callback); + if (!bShouldCallCustomCallbacks) + spdlog::info("Not executing custom callbacks for CodeCallback {}", callback); + bool ret = CallScriptInitCallback(sqvm, callback); - // run after callbacks - if (shouldCallCustomCallbacks) + // run after callbacks + if (bShouldCallCustomCallbacks) + { + for (Mod mod : g_pModManager->m_LoadedMods) { - for (Mod mod : g_ModManager->m_loadedMods) - { - if (!mod.Enabled) - continue; + if (!mod.m_bEnabled) + continue; - for (ModScript script : mod.Scripts) + for (ModScript script : mod.Scripts) + { + for (ModScriptCallback modCallback : script.Callbacks) { - for (ModScriptCallback modCallback : script.Callbacks) + if (modCallback.Context == realContext && modCallback.AfterCallback.length()) { - if (modCallback.Context == ScriptContext::SERVER && modCallback.AfterCallback.length()) - { - spdlog::info("Running custom {} script callback \"{}\"", GetContextName(context), modCallback.AfterCallback); - ServerCallScriptInitCallback(sqvm, modCallback.AfterCallback.c_str()); - } + spdlog::info("Running custom {} script callback \"{}\"", GetContextName(realContext), modCallback.AfterCallback); + CallScriptInitCallback(sqvm, modCallback.AfterCallback.c_str()); } } } @@ -477,113 +402,197 @@ template char CallScriptInitCallbackHook(void* sqvm, con return ret; } -template void ExecuteCodeCommand(const CCommand& args) +template void ConCommand_script(const CCommand& args) { - if (context == ScriptContext::CLIENT) - g_ClientSquirrelManager->ExecuteCode(args.ArgS()); - else if (context == ScriptContext::UI) - g_UISquirrelManager->ExecuteCode(args.ArgS()); - else if (context == ScriptContext::SERVER) - g_ServerSquirrelManager->ExecuteCode(args.ArgS()); + g_pSquirrel->ExecuteCode(args.ArgS()); } -SQRESULT SQ_DevFuncStub(void* sqvm) +// literal class type that wraps a constant expression string +template struct TemplateStringLiteral { - spdlog::warn("Blocked execution of squirrel developer function for security reasons. To re-enable them use start parameter " - "-allowSquirrelDevFunctions."); - return SQRESULT_NULL; -} + constexpr TemplateStringLiteral(const char (&str)[N]) + { + std::copy_n(str, N, value); + } -template int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown) + char value[N]; +}; + +template SQRESULT SQ_StubbedFunc(HSquirrelVM* sqvm) { - static std::set allowedDevFunctions = { - "Dev_CommandLineHasParm", - "Dev_CommandLineParmValue", - "Dev_CommandLineRemoveParm", - }; - - if ((funcReg->devLevel == 1) && (!CommandLine()->CheckParm("-allowSquirrelDevFunctions")) && - (!allowedDevFunctions.count(funcReg->squirrelFuncName))) - funcReg->funcPtr = reinterpret_cast(SQ_DevFuncStub); - - if (context == ScriptContext::SERVER) - return ServerRegisterSquirrelFunc(sqvm, funcReg, unknown); - else - return ClientRegisterSquirrelFunc(sqvm, funcReg, unknown); + spdlog::info("Blocking call to stubbed function {} in {}", funcName.value, GetContextName(context)); + return SQRESULT_NULL; } -const char* sq_getTypeName(int type) +template void StubUnsafeSQFuncs() { - switch (type) + if (!Tier0::CommandLine()->CheckParm("-allowunsafesqfuncs")) { - case OT_ASSET: - return "asset"; - case OT_INTEGER: - return "int"; - case OT_BOOL: - return "bool"; - case SQOBJECT_NUMERIC: - return "float or int"; - case OT_NULL: - return "null"; - case OT_VECTOR: - return "vector"; - case 0: - return "var"; - case OT_USERDATA: - return "userdata"; - case OT_FLOAT: - return "float"; - case OT_STRING: - return "string"; - case 0x8000040: - return "array"; - case 0x8000200: - return "function"; - case 0x8100000: - return "structdef"; - case OT_THREAD: - return "thread"; - case OT_FUNCPROTO: - return "function"; - case OT_CLAAS: - return "class"; - case OT_WEAKREF: - return "weakref"; - case 0x8080000: - return "unimplemented function"; - case 0x8200000: - return "struct instance"; - case 0xA000020: - return "table"; - case 0xA008000: - return "instance"; - case 0xA400000: - return "entity"; + g_pSquirrel->AddFuncOverride("DevTextBufferWrite", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("DevTextBufferClear", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("DevTextBufferDumpToFile", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("Dev_CommandLineAddParam", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("DevP4Checkout", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("DevP4Add", SQ_StubbedFunc); } - return ""; } -SQReturnTypeEnum GetReturnTypeEnumFromString(const char* returnTypeString) +ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) { + AUTOHOOK_DISPATCH_MODULE(client.dll) + + g_pSquirrel = new SquirrelManager; + g_pSquirrel = new SquirrelManager; + + g_pSquirrel->__sq_defconst = module.Offset(0x12120).As(); + g_pSquirrel->__sq_defconst = g_pSquirrel->__sq_defconst; + + g_pSquirrel->__sq_compilebuffer = module.Offset(0x3110).As(); + g_pSquirrel->__sq_pushroottable = module.Offset(0x5860).As(); + g_pSquirrel->__sq_compilebuffer = g_pSquirrel->__sq_compilebuffer; + g_pSquirrel->__sq_pushroottable = g_pSquirrel->__sq_pushroottable; + + g_pSquirrel->__sq_call = module.Offset(0x8650).As(); + g_pSquirrel->__sq_call = g_pSquirrel->__sq_call; + + g_pSquirrel->__sq_newarray = module.Offset(0x39F0).As(); + g_pSquirrel->__sq_arrayappend = module.Offset(0x3C70).As(); + g_pSquirrel->__sq_newarray = g_pSquirrel->__sq_newarray; + g_pSquirrel->__sq_arrayappend = g_pSquirrel->__sq_arrayappend; + + g_pSquirrel->__sq_newtable = module.Offset(0x3960).As(); + g_pSquirrel->__sq_newslot = module.Offset(0x70B0).As(); + g_pSquirrel->__sq_newtable = g_pSquirrel->__sq_newtable; + g_pSquirrel->__sq_newslot = g_pSquirrel->__sq_newslot; + + g_pSquirrel->__sq_pushstring = module.Offset(0x3440).As(); + g_pSquirrel->__sq_pushinteger = module.Offset(0x36A0).As(); + g_pSquirrel->__sq_pushfloat = module.Offset(0x3800).As(); + g_pSquirrel->__sq_pushbool = module.Offset(0x3710).As(); + g_pSquirrel->__sq_pushasset = module.Offset(0x3560).As(); + g_pSquirrel->__sq_pushvector = module.Offset(0x3780).As(); + g_pSquirrel->__sq_raiseerror = module.Offset(0x8470).As(); + g_pSquirrel->__sq_pushstring = g_pSquirrel->__sq_pushstring; + g_pSquirrel->__sq_pushinteger = g_pSquirrel->__sq_pushinteger; + g_pSquirrel->__sq_pushfloat = g_pSquirrel->__sq_pushfloat; + g_pSquirrel->__sq_pushbool = g_pSquirrel->__sq_pushbool; + g_pSquirrel->__sq_pushvector = g_pSquirrel->__sq_pushvector; + g_pSquirrel->__sq_pushasset = g_pSquirrel->__sq_pushasset; + g_pSquirrel->__sq_raiseerror = g_pSquirrel->__sq_raiseerror; + + g_pSquirrel->__sq_getstring = module.Offset(0x60C0).As(); + g_pSquirrel->__sq_getinteger = module.Offset(0x60E0).As(); + g_pSquirrel->__sq_getfloat = module.Offset(0x6100).As(); + g_pSquirrel->__sq_getbool = module.Offset(0x6130).As(); + g_pSquirrel->__sq_get = module.Offset(0x7C30).As(); + g_pSquirrel->__sq_getasset = module.Offset(0x6010).As(); + g_pSquirrel->__sq_getuserdata = module.Offset(0x63D0).As(); + g_pSquirrel->__sq_getvector = module.Offset(0x6140).As(); + g_pSquirrel->__sq_getthisentity = module.Offset(0x12F80).As(); + g_pSquirrel->__sq_getentity = module.Offset(0x6140).As(); + g_pSquirrel->__sq_getstring = g_pSquirrel->__sq_getstring; + g_pSquirrel->__sq_getinteger = g_pSquirrel->__sq_getinteger; + g_pSquirrel->__sq_getfloat = g_pSquirrel->__sq_getfloat; + g_pSquirrel->__sq_getbool = g_pSquirrel->__sq_getbool; + g_pSquirrel->__sq_get = g_pSquirrel->__sq_get; + g_pSquirrel->__sq_getasset = g_pSquirrel->__sq_getasset; + g_pSquirrel->__sq_getuserdata = g_pSquirrel->__sq_getuserdata; + g_pSquirrel->__sq_getvector = g_pSquirrel->__sq_getvector; + g_pSquirrel->__sq_getthisentity = g_pSquirrel->__sq_getthisentity; + g_pSquirrel->__sq_getentity = g_pSquirrel->__sq_getentity; + + g_pSquirrel->__sq_createuserdata = module.Offset(0x38D0).As(); + g_pSquirrel->__sq_setuserdatatypeid = module.Offset(0x6490).As(); + g_pSquirrel->__sq_createuserdata = g_pSquirrel->__sq_createuserdata; + g_pSquirrel->__sq_setuserdatatypeid = g_pSquirrel->__sq_setuserdatatypeid; + + MAKEHOOK( + module.Offset(0x108E0), + &RegisterSquirrelFunctionHook, + &g_pSquirrel->RegisterSquirrelFunc); + g_pSquirrel->RegisterSquirrelFunc = g_pSquirrel->RegisterSquirrelFunc; + + // uiscript_reset concommand: don't loop forever if compilation fails + module.Offset(0x3C6E4C).NOP(6); + + MAKEHOOK(module.Offset(0x8AD0), &sq_compiler_createHook, &sq_compiler_create); + + MAKEHOOK(module.Offset(0x12B00), &SQPrintHook, &SQPrint); + MAKEHOOK(module.Offset(0x12BA0), &SQPrintHook, &SQPrint); + + MAKEHOOK(module.Offset(0x26130), &CreateNewVMHook, &CreateNewVM); + MAKEHOOK(module.Offset(0x26E70), &DestroyVMHook, &DestroyVM); + MAKEHOOK(module.Offset(0x79A50), &ScriptCompileErrorHook, &SQCompileError); + + MAKEHOOK(module.Offset(0x10190), &CallScriptInitCallbackHook, &CallScriptInitCallback); + + RegisterConCommand("script_client", ConCommand_script, "Executes script code on the client vm", FCVAR_CLIENTDLL); + RegisterConCommand("script_ui", ConCommand_script, "Executes script code on the ui vm", FCVAR_CLIENTDLL); + + StubUnsafeSQFuncs(); + StubUnsafeSQFuncs(); +} - static std::map sqEnumStrMap = { - {"bool", SqReturnBoolean}, - {"float", SqReturnFloat}, - {"vector", SqReturnVector}, - {"int", SqReturnInteger}, - {"entity", SqReturnEntity}, - {"string", SqReturnString}, - {"array", SqReturnArrays}, - {"asset", SqReturnAsset}, - {"table", SqReturnTable}}; - - if (sqEnumStrMap.count(returnTypeString)) - { - return sqEnumStrMap[returnTypeString]; - } - else - { - return SqReturnDefault; // previous default value - } +ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(server.dll) + + g_pSquirrel = new SquirrelManager; + + g_pSquirrel->__sq_defconst = module.Offset(0x1F550).As(); + + g_pSquirrel->__sq_compilebuffer = module.Offset(0x3110).As(); + g_pSquirrel->__sq_pushroottable = module.Offset(0x5840).As(); + g_pSquirrel->__sq_call = module.Offset(0x8620).As(); + + g_pSquirrel->__sq_newarray = module.Offset(0x39F0).As(); + g_pSquirrel->__sq_arrayappend = module.Offset(0x3C70).As(); + + g_pSquirrel->__sq_newtable = module.Offset(0x3960).As(); + g_pSquirrel->__sq_newslot = module.Offset(0x7080).As(); + + g_pSquirrel->__sq_pushstring = module.Offset(0x3440).As(); + g_pSquirrel->__sq_pushinteger = module.Offset(0x36A0).As(); + g_pSquirrel->__sq_pushfloat = module.Offset(0x3800).As(); + g_pSquirrel->__sq_pushbool = module.Offset(0x3710).As(); + g_pSquirrel->__sq_pushasset = module.Offset(0x3560).As(); + g_pSquirrel->__sq_pushvector = module.Offset(0x3780).As(); + g_pSquirrel->__sq_raiseerror = module.Offset(0x8440).As(); + + g_pSquirrel->__sq_getstring = module.Offset(0x60A0).As(); + g_pSquirrel->__sq_getinteger = module.Offset(0x60C0).As(); + g_pSquirrel->__sq_getfloat = module.Offset(0x60E0).As(); + g_pSquirrel->__sq_getbool = module.Offset(0x6110).As(); + g_pSquirrel->__sq_getasset = module.Offset(0x5FF0).As(); + g_pSquirrel->__sq_getuserdata = module.Offset(0x63B0).As(); + g_pSquirrel->__sq_getvector = module.Offset(0x6120).As(); + g_pSquirrel->__sq_get = module.Offset(0x7C00).As(); + g_pSquirrel->__sq_getthisentity = module.Offset(0x203B0).As(); + g_pSquirrel->__sq_getentity = module.Offset(0x6120).As(); + + g_pSquirrel->__sq_createuserdata = module.Offset(0x38D0).As(); + g_pSquirrel->__sq_setuserdatatypeid = module.Offset(0x6470).As(); + + MAKEHOOK( + module.Offset(0x1DD10), + &RegisterSquirrelFunctionHook, + &g_pSquirrel->RegisterSquirrelFunc); + + MAKEHOOK(module.Offset(0x8AA0), &sq_compiler_createHook, &sq_compiler_create); + + MAKEHOOK(module.Offset(0x1FE90), &SQPrintHook, &SQPrint); + MAKEHOOK(module.Offset(0x260E0), &CreateNewVMHook, &CreateNewVM); + MAKEHOOK(module.Offset(0x26E20), &DestroyVMHook, &DestroyVM); + MAKEHOOK(module.Offset(0x799E0), &ScriptCompileErrorHook, &SQCompileError); + MAKEHOOK(module.Offset(0x1D5C0), &CallScriptInitCallbackHook, &CallScriptInitCallback); + + // FCVAR_CHEAT and FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS allows clients to execute this, but since it's unsafe we only allow it when cheats + // are enabled for script_client and script_ui, we don't use cheats, so clients can execute them on themselves all they want + RegisterConCommand( + "script", + ConCommand_script, + "Executes script code on the server vm", + FCVAR_GAMEDLL | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS | FCVAR_CHEAT); + + StubUnsafeSQFuncs(); } diff --git a/NorthstarDLL/squirrel.h b/NorthstarDLL/squirrel.h index e465ddc6..de99b9d0 100644 --- a/NorthstarDLL/squirrel.h +++ b/NorthstarDLL/squirrel.h @@ -1,39 +1,40 @@ -#include <../modmanager.h> #pragma once -void InitialiseClientSquirrel(HMODULE baseAddress); -void InitialiseServerSquirrel(HMODULE baseAddress); +#include "squirreldatatypes.h" +#include "vector.h" // stolen from ttf2sdk: sqvm types typedef float SQFloat; typedef long SQInteger; typedef unsigned long SQUnsignedInteger; typedef char SQChar; - typedef SQUnsignedInteger SQBool; -typedef SQInteger SQRESULT; -const SQRESULT SQRESULT_ERROR = -1; -const SQRESULT SQRESULT_NULL = 0; -const SQRESULT SQRESULT_NOTNULL = 1; +enum SQRESULT : SQInteger +{ + SQRESULT_ERROR = -1, + SQRESULT_NULL = 0, + SQRESULT_NOTNULL = 1, +}; -typedef SQInteger (*SQFunction)(void* sqvm); +typedef SQRESULT (*SQFunction)(HSquirrelVM* sqvm); -enum SQReturnTypeEnum +enum class eSQReturnType { - SqReturnFloat = 0x1, - SqReturnVector = 0x3, - SqReturnInteger = 0x5, - SqReturnBoolean = 0x6, - SqReturnEntity = 0xD, - SqReturnString = 0x21, - SqReturnDefault = 0x20, - SqReturnArrays = 0x25, - SqReturnAsset = 0x28, - SqReturnTable = 0x26, + Float = 0x1, + Vector = 0x3, + Integer = 0x5, + Boolean = 0x6, + Entity = 0xD, + String = 0x21, + Default = 0x20, + Arrays = 0x25, + Asset = 0x28, + Table = 0x26, }; -const char* sq_getTypeName(int type); +const std::map PrintSQRESULT = { + {SQRESULT_ERROR, "SQRESULT_ERROR"}, {SQRESULT_NULL, "SQRESULT_NULL"}, {SQRESULT_NOTNULL, "SQRESULT_NOTNULL"}}; struct CompileBufferState { @@ -56,758 +57,276 @@ struct SQFuncRegistration const char* helpText; const char* returnTypeString; const char* argTypes; - __int32 unknown1; - __int32 devLevel; + uint32_t unknown1; + uint32_t devLevel; const char* shortNameMaybe; - __int32 unknown2; - SQReturnTypeEnum returnTypeEnum; - __int32* externalBufferPointer; - __int64 externalBufferSize; - __int64 unknown3; - __int64 unknown4; - void* funcPtr; + uint32_t unknown2; + eSQReturnType returnType; + uint32_t* externalBufferPointer; + uint64_t externalBufferSize; + uint64_t unknown3; + uint64_t unknown4; + SQFunction funcPtr; SQFuncRegistration() { memset(this, 0, sizeof(SQFuncRegistration)); - this->returnTypeEnum = SqReturnDefault; + this->returnType = eSQReturnType::Default; } }; -SQReturnTypeEnum GetReturnTypeEnumFromString(const char* returnTypeString); - -struct CallInfo; -struct SQTable; -struct SQString; -struct SQFunctionProto; -struct SQClosure; -struct SQSharedState; -struct StringTable; -struct SQStructInstance; -struct SQStructDef; -struct SQNativeClosure; -struct SQArray; -struct SQInstruction; - -/* 127 */ -enum SQObjectType : __int32 -{ - _RT_NULL = 0x1, - _RT_INTEGER = 0x2, - _RT_FLOAT = 0x4, - _RT_BOOL = 0x8, - _RT_STRING = 0x10, - _RT_TABLE = 0x20, - _RT_ARRAY = 0x40, - _RT_USERDATA = 0x80, - _RT_CLOSURE = 0x100, - _RT_NATIVECLOSURE = 0x200, - _RT_GENERATOR = 0x400, - OT_USERPOINTER = 0x800, - _RT_USERPOINTER = 0x800, - _RT_THREAD = 0x1000, - _RT_FUNCPROTO = 0x2000, - _RT_CLASS = 0x4000, - _RT_INSTANCE = 0x8000, - _RT_WEAKREF = 0x10000, - OT_VECTOR = 0x40000, - SQOBJECT_CANBEFALSE = 0x1000000, - OT_NULL = 0x1000001, - OT_BOOL = 0x1000008, - SQOBJECT_DELEGABLE = 0x2000000, - SQOBJECT_NUMERIC = 0x4000000, - OT_INTEGER = 0x5000002, - OT_FLOAT = 0x5000004, - SQOBJECT_REF_COUNTED = 0x8000000, - OT_STRING = 0x8000010, - OT_ARRAY = 0x8000040, - OT_CLOSURE = 0x8000100, - OT_NATIVECLOSURE = 0x8000200, - OT_ASSET = 0x8000400, - OT_THREAD = 0x8001000, - OT_FUNCPROTO = 0x8002000, - OT_CLAAS = 0x8004000, - OT_STRUCT = 0x8200000, - OT_WEAKREF = 0x8010000, - OT_TABLE = 0xA000020, - OT_USERDATA = 0xA000080, - OT_INSTANCE = 0xA008000, - OT_ENTITY = 0xA400000, -}; - -/* 156 */ -union alignas(8) SQObjectValue -{ - SQString* asString; - SQTable* asTable; - SQClosure* asClosure; - SQFunctionProto* asFuncProto; - SQStructDef* asStructDef; - __int64 asInteger; - SQStructInstance* asStructInstance; - float asFloat; - SQNativeClosure* asNativeClosure; - SQArray* asArray; -}; - -/* 128 */ -struct alignas(8) SQObject -{ - SQObjectType _Type; - __int32 _structOffset; - SQObjectValue _VAL; -}; - -struct tableNode -{ - SQObject val; - SQObject key; - tableNode* next; -}; - -/* 138 */ -struct alignas(8) SQString -{ - __int64* vftable; - __int32 uiRef; - __int32 uiRef1; - SQString* _next_maybe; - SQSharedState* sharedState; - __int32 length; - uint8_t gap_24[4]; - char _hash[8]; - char _val[1]; -}; - -/* 137 */ -struct alignas(8) SQTable -{ - __int64* vftable; - uint8_t gap_08[4]; - __int32 uiRef; - uint8_t gap_10[8]; - void* pointer_18; - void* pointer_20; - void* _sharedState; - __int64 field_30; - tableNode* _nodes; - __int32 _numOfNodes; - __int32 size; - __int32 field_48; - __int32 _usedNodes; - uint8_t _gap_50[20]; - __int32 field_64; - uint8_t _gap_68[80]; -}; - -/* 140 */ -struct alignas(8) SQClosure -{ - void* vftable; - uint8_t gap_08[4]; - __int32 uiRef; - void* pointer_10; - void* pointer_18; - void* pointer_20; - void* sharedState; - SQObject obj_30; - SQObject _function; - SQObject* _outervalues; - uint8_t gap_58[8]; - uint8_t gap_60[96]; - SQObject* objectPointer_C0; -}; - -/* 139 */ -struct alignas(8) SQFunctionProto -{ - void* vftable; - uint8_t gap_08[4]; - __int32 uiRef; - uint8_t gap_10[8]; - void* pointer_18; - void* pointer_20; - void* sharedState; - void* pointer_30; - SQObject fileName; - SQObject funcName; - SQObject obj_58; - uint8_t gap_68[64]; - __int32 nParameters; - uint8_t gap_AC[60]; - __int32 nDefaultParams; - uint8_t gap_EC[200]; -}; - -/* 152 */ -struct SQStructDef -{ - uint8_t gap_0[56]; - SQString* name; - uint8_t gap_[300]; -}; - -/* 150 */ -struct SQStructInstance -{ - void* vftable; - uint8_t gap_8[16]; - void* pointer_18; - uint8_t gap_20[8]; - SQSharedState* _sharedState; - uint8_t gap_30[8]; - SQObject data[1]; -}; - -/* 157 */ -struct alignas(8) SQNativeClosure -{ - void* vftable; - uint8_t gap_08[4]; - __int32 uiRef; - uint8_t gap_10[88]; - SQString* _name; - uint8_t gap_0[300]; -}; - -/* 148 */ -struct SQSharedState -{ - uint8_t gap_0[72]; - StringTable* _stringtable; - uint8_t gap_50[30000]; -}; - -/* 149 */ -struct StringTable -{ - uint8_t gap_0[12]; - int _numofslots; - uint8_t gap_10[200]; -}; - -/* 129 */ -struct alignas(8) HSquirrelVM -{ - void* vftable; - __int32 uiRef; - uint8_t gap_8[12]; - void* _toString; - void* _roottable_pointer; - void* pointer_28; - CallInfo* ci; - CallInfo* _callsstack; - __int32 _callsstacksize; - __int32 _stackbase; - SQObject* _stackOfCurrentFunction; - SQSharedState* sharedState; - void* pointer_58; - void* pointer_60; - __int32 _top; - SQObject* _stack; - uint8_t gap_78[8]; - SQObject* _vargsstack; - uint8_t gap_88[8]; - SQObject temp_reg; - uint8_t gapA0[8]; - void* pointer_A8; - uint8_t gap_B0[8]; - SQObject _roottable_object; - SQObject _lasterror; - SQObject _errorHandler; - __int64 field_E8; - __int32 traps; - uint8_t gap_F4[12]; - __int32 _nnativecalls; - __int32 _suspended; - __int32 _suspended_root; - __int32 _callstacksize; - __int32 _suspended_target; - __int32 field_114; - __int32 _suspend_varargs; - SQObject* _object_pointer_120; -}; - -/* 136 */ -struct alignas(8) CallInfo -{ - SQInstruction* ip; - SQObject* _literals; - SQObject obj10; - SQObject closure; - __int32 _etraps[4]; - __int32 _root; - short _vargs_size; - short _vargs_base; -}; - -/* 135 */ -enum SQOpcode : int -{ - _OP_LOAD = 0x0, - _OP_LOADCOPY = 0x1, - _OP_LOADINT = 0x2, - _OP_LOADFLOAT = 0x3, - _OP_DLOAD = 0x4, - _OP_TAILCALL = 0x5, - _OP_CALL = 0x6, - _OP_PREPCALL = 0x7, - _OP_PREPCALLK = 0x8, - _OP_GETK = 0x9, - _OP_MOVE = 0xA, - _OP_NEWSLOT = 0xB, - _OP_DELETE = 0xC, - _OP_SET = 0xD, - _OP_GET = 0xE, - _OP_EQ = 0xF, - _OP_NE = 0x10, - _OP_ARITH = 0x11, - _OP_BITW = 0x12, - _OP_RETURN = 0x13, - _OP_LOADNULLS = 0x14, - _OP_LOADROOTTABLE = 0x15, - _OP_LOADBOOL = 0x16, - _OP_DMOVE = 0x17, - _OP_JMP = 0x18, - _OP_JNZ = 0x19, - _OP_JZ = 0x1A, - _OP_LOADFREEVAR = 0x1B, - _OP_VARGC = 0x1C, - _OP_GETVARGV = 0x1D, - _OP_NEWTABLE = 0x1E, - _OP_NEWARRAY = 0x1F, - _OP_APPENDARRAY = 0x20, - _OP_GETPARENT = 0x21, - _OP_COMPOUND_ARITH = 0x22, - _OP_COMPOUND_ARITH_LOCAL = 0x23, - _OP_INCREMENT_PREFIX = 0x24, - _OP_INCREMENT_PREFIX_LOCAL = 0x25, - _OP_INCREMENT_PREFIX_STRUCTFIELD = 0x26, - _OP_INCREMENT_POSTFIX = 0x27, - _OP_INCREMENT_POSTFIX_LOCAL = 0x28, - _OP_INCREMENT_POSTFIX_STRUCTFIELD = 0x29, - _OP_CMP = 0x2A, - _OP_EXISTS = 0x2B, - _OP_INSTANCEOF = 0x2C, - _OP_NEG = 0x2D, - _OP_NOT = 0x2E, - _OP_BWNOT = 0x2F, - _OP_CLOSURE = 0x30, - _OP_FOREACH = 0x31, - _OP_FOREACH_STATICARRAY_START = 0x32, - _OP_FOREACH_STATICARRAY_NEXT = 0x33, - _OP_FOREACH_STATICARRAY_NESTEDSTRUCT_START = 0x34, - _OP_FOREACH_STATICARRAY_NESTEDSTRUCT_NEXT = 0x35, - _OP_DELEGATE = 0x36, - _OP_CLONE = 0x37, - _OP_TYPEOF = 0x38, - _OP_PUSHTRAP = 0x39, - _OP_POPTRAP = 0x3A, - _OP_THROW = 0x3B, - _OP_CLASS = 0x3C, - _OP_NEWSLOTA = 0x3D, - _OP_EQ_LITERAL = 0x3E, - _OP_NE_LITERAL = 0x3F, - _OP_FOREACH_SETUP = 0x40, - _OP_ASSERT_FAILED = 0x41, - _OP_ADD = 0x42, - _OP_SUB = 0x43, - _OP_MUL = 0x44, - _OP_DIV = 0x45, - _OP_MOD = 0x46, - _OP_PREPCALLK_CALL = 0x47, - _OP_PREPCALLK_MOVE_CALL = 0x48, - _OP_PREPCALLK_LOADINT_CALL = 0x49, - _OP_CMP_JZ = 0x4A, - _OP_INCREMENT_LOCAL_DISCARD_JMP = 0x4B, - _OP_JZ_RETURN = 0x4C, - _OP_JZ_LOADBOOL_RETURN = 0x4D, - _OP_NEWVECTOR = 0x4E, - _OP_ZEROVECTOR = 0x4F, - _OP_GET_VECTOR_COMPONENT = 0x50, - _OP_SET_VECTOR_COMPONENT = 0x51, - _OP_VECTOR_COMPONENT_MINUSEQ = 0x52, - _OP_VECTOR_COMPONENT_PLUSEQ = 0x53, - _OP_VECTOR_COMPONENT_MULEQ = 0x54, - _OP_VECTOR_COMPONENT_DIVEQ = 0x55, - _OP_VECTOR_NORMALIZE = 0x56, - _OP_VECTOR_NORMALIZE_IN_PLACE = 0x57, - _OP_VECTOR_DOT_PRODUCT = 0x58, - _OP_VECTOR_DOT_PRODUCT2D = 0x59, - _OP_VECTOR_CROSS_PRODUCT = 0x5A, - _OP_VECTOR_CROSS_PRODUCT2D = 0x5B, - _OP_VECTOR_LENGTH = 0x5C, - _OP_VECTOR_LENGTHSQR = 0x5D, - _OP_VECTOR_LENGTH2D = 0x5E, - _OP_VECTOR_LENGTH2DSQR = 0x5F, - _OP_VECTOR_DISTANCE = 0x60, - _OP_VECTOR_DISTANCESQR = 0x61, - _OP_VECTOR_DISTANCE2D = 0x62, - _OP_VECTOR_DISTANCE2DSQR = 0x63, - _OP_INCREMENT_LOCAL_DISCARD = 0x64, - _OP_FASTCALL = 0x65, - _OP_FASTCALL_NATIVE = 0x66, - _OP_FASTCALL_NATIVE_ARGTYPECHECK = 0x67, - _OP_FASTCALL_ENV = 0x68, - _OP_FASTCALL_NATIVE_ENV = 0x69, - _OP_FASTCALL_NATIVE_ENV_ARGTYPECHECK = 0x6A, - _OP_LOADGLOBALARRAY = 0x6B, - _OP_GETGLOBAL = 0x6C, - _OP_SETGLOBAL = 0x6D, - _OP_COMPOUND_ARITH_GLOBAL = 0x6E, - _OP_GETSTRUCTFIELD = 0x6F, - _OP_SETSTRUCTFIELD = 0x70, - _OP_COMPOUND_ARITH_STRUCTFIELD = 0x71, - _OP_NEWSTRUCT = 0x72, - _OP_GETSUBSTRUCT = 0x73, - _OP_GETSUBSTRUCT_DYNAMIC = 0x74, - _OP_TYPECAST = 0x75, - _OP_TYPECHECK = 0x76, - _OP_TYPECHECK_ORNULL = 0x77, - _OP_TYPECHECK_NOTNULL = 0x78, - _OP_CHECK_ENTITY_CLASS = 0x79, - _OP_UNREACHABLE = 0x7A, - _OP_ARRAY_RESIZE = 0x7B, -}; - -/* 141 */ -struct alignas(8) SQStackInfos -{ - char* _name; - char* _sourceName; - __int32 _line; -}; - -/* 151 */ -struct alignas(4) SQInstruction -{ - int op; - int arg1; - int output; - __int16 arg2; - __int16 arg3; -}; - -/* 154 */ -struct SQLexer -{ - uint8_t gap_0[112]; -}; - -/* 153 */ -struct SQCompiler -{ - uint8_t gap_0[4]; - __int32 _token; - uint8_t gap_8[8]; - SQObject object_10; - SQLexer lexer; - uint8_t gap_1[768]; -}; - -/* 155 */ -struct CSquirrelVM -{ - uint8_t gap_0[8]; - HSquirrelVM* sqvm; -}; - -struct SQVector +enum class ScriptContext : int { - SQObjectType _Type; - float x; - float y; - float z; + SERVER, + CLIENT, + UI, }; -struct SQArray -{ - void* vftable; - __int32 uiRef; - uint8_t gap_24[36]; - SQObject* _values; - __int32 _usedSlots; - __int32 _allocated; -}; - -#define INCREMENT_REFERENCECOUNT(val) \ - if ((val->_Type & SQOBJECT_REF_COUNTED) != 0) \ - ++val->_VAL.asString->uiRef; - -#define DECREMENT_REFERENCECOUNT(val) \ - if ((val->_Type & SQOBJECT_REF_COUNTED) != 0) \ - { \ - if (val->_VAL.asString->uiRef-- == 1) \ - { \ - spdlog::info("Deleted SQObject of type {} with address {:X}", sq_getTypeName(val->_Type), val->_VAL.asInteger); \ - (*(void(__fastcall**)(SQString*))(&val->_VAL.asString->vftable[1]))(val->_VAL.asString); \ - } \ - } +const char* GetContextName(ScriptContext context); +eSQReturnType SQReturnTypeFromString(const char* pReturnType); +const char* SQTypeNameFromID(const int iTypeId); // core sqvm funcs -typedef SQRESULT (*sq_compilebufferType)(void* sqvm, CompileBufferState* compileBuffer, const char* file, int a1, ScriptContext a2); -extern sq_compilebufferType ClientSq_compilebuffer; -extern sq_compilebufferType ServerSq_compilebuffer; +typedef int64_t (*RegisterSquirrelFuncType)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); +typedef void (*sq_defconstType)(CSquirrelVM* sqvm, const SQChar* name, int value); -typedef void (*sq_pushroottableType)(void* sqvm); -extern sq_pushroottableType ClientSq_pushroottable; -extern sq_pushroottableType ServerSq_pushroottable; - -typedef SQRESULT (*sq_callType)(void* sqvm, SQInteger s1, SQBool a2, SQBool a3); -extern sq_callType ClientSq_call; -extern sq_callType ServerSq_call; - -typedef int64_t (*RegisterSquirrelFuncType)(void* sqvm, SQFuncRegistration* funcReg, char unknown); -extern RegisterSquirrelFuncType ClientRegisterSquirrelFunc; -extern RegisterSquirrelFuncType ServerRegisterSquirrelFunc; +typedef SQRESULT (*sq_compilebufferType)( + HSquirrelVM* sqvm, CompileBufferState* compileBuffer, const char* file, int a1, SQBool bShouldThrowError); +typedef SQRESULT (*sq_callType)(HSquirrelVM* sqvm, SQInteger iArgs, SQBool bShouldReturn, SQBool bThrowError); +typedef SQInteger (*sq_raiseerrorType)(HSquirrelVM* sqvm, const SQChar* pError); // sq stack array funcs -typedef void (*sq_newarrayType)(void* sqvm, SQInteger stackpos); -extern sq_newarrayType ClientSq_newarray; -extern sq_newarrayType ServerSq_newarray; +typedef void (*sq_newarrayType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQRESULT (*sq_arrayappendType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQRESULT (*sq_arrayappendType)(void* sqvm, SQInteger stackpos); -extern sq_arrayappendType ClientSq_arrayappend; -extern sq_arrayappendType ServerSq_arrayappend; +// sq table funcs +typedef SQRESULT (*sq_newtableType)(HSquirrelVM* sqvm); +typedef SQRESULT (*sq_newslotType)(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic); // sq stack push funcs -typedef void (*sq_pushstringType)(void* sqvm, const SQChar* str, SQInteger stackpos); -extern sq_pushstringType ClientSq_pushstring; -extern sq_pushstringType ServerSq_pushstring; +typedef void (*sq_pushroottableType)(HSquirrelVM* sqvm); +typedef void (*sq_pushstringType)(HSquirrelVM* sqvm, const SQChar* pStr, SQInteger iLength); +typedef void (*sq_pushintegerType)(HSquirrelVM* sqvm, SQInteger i); +typedef void (*sq_pushfloatType)(HSquirrelVM* sqvm, SQFloat f); +typedef void (*sq_pushboolType)(HSquirrelVM* sqvm, SQBool b); +typedef void (*sq_pushassetType)(HSquirrelVM* sqvm, const SQChar* str, SQInteger iLength); +typedef void (*sq_pushvectorType)(HSquirrelVM* sqvm, const SQFloat* pVec); -// weird how these don't take a stackpos arg? -typedef void (*sq_pushintegerType)(void* sqvm, SQInteger i); -extern sq_pushintegerType ClientSq_pushinteger; -extern sq_pushintegerType ServerSq_pushinteger; - -typedef void (*sq_pushfloatType)(void* sqvm, SQFloat f); -extern sq_pushfloatType ClientSq_pushfloat; -extern sq_pushfloatType ServerSq_pushfloat; +// sq stack get funcs +typedef const SQChar* (*sq_getstringType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQInteger (*sq_getintegerType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQFloat (*sq_getfloatType)(HSquirrelVM*, SQInteger iStackpos); +typedef SQBool (*sq_getboolType)(HSquirrelVM*, SQInteger iStackpos); +typedef SQRESULT (*sq_getType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQRESULT (*sq_getassetType)(HSquirrelVM* sqvm, SQInteger iStackpos, const char** pResult); +typedef SQRESULT (*sq_getuserdataType)(HSquirrelVM* sqvm, SQInteger iStackpos, void** pData, uint64_t* pTypeId); +typedef SQFloat* (*sq_getvectorType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQBool (*sq_getthisentityType)(HSquirrelVM*, void** ppEntity); +typedef void* (*sq_getentityType)(HSquirrelVM*, SQInteger iStackPos); + +// sq stack userpointer funcs +typedef void* (*sq_createuserdataType)(HSquirrelVM* sqvm, SQInteger iSize); +typedef SQRESULT (*sq_setuserdatatypeidType)(HSquirrelVM* sqvm, SQInteger iStackpos, uint64_t iTypeId); -typedef void (*sq_pushboolType)(void* sqvm, SQBool b); -extern sq_pushboolType ClientSq_pushbool; -extern sq_pushboolType ServerSq_pushbool; +template class SquirrelManager +{ + private: + std::vector m_funcRegistrations; -typedef SQInteger (*sq_pusherrorType)(void* sqvm, const SQChar* error); -extern sq_pusherrorType ClientSq_pusherror; -extern sq_pusherrorType ServerSq_pusherror; + public: + CSquirrelVM* m_pSQVM; + std::map m_funcOverrides = {}; + std::map m_funcOriginals = {}; + + bool m_bFatalCompilationErrors = false; + +#pragma region SQVM funcs + RegisterSquirrelFuncType RegisterSquirrelFunc; + sq_defconstType __sq_defconst; + + sq_compilebufferType __sq_compilebuffer; + sq_callType __sq_call; + sq_raiseerrorType __sq_raiseerror; + + sq_newarrayType __sq_newarray; + sq_arrayappendType __sq_arrayappend; + + sq_newtableType __sq_newtable; + sq_newslotType __sq_newslot; + + sq_pushroottableType __sq_pushroottable; + sq_pushstringType __sq_pushstring; + sq_pushintegerType __sq_pushinteger; + sq_pushfloatType __sq_pushfloat; + sq_pushboolType __sq_pushbool; + sq_pushassetType __sq_pushasset; + sq_pushvectorType __sq_pushvector; + + sq_getstringType __sq_getstring; + sq_getintegerType __sq_getinteger; + sq_getfloatType __sq_getfloat; + sq_getboolType __sq_getbool; + sq_getType __sq_get; + sq_getassetType __sq_getasset; + sq_getuserdataType __sq_getuserdata; + sq_getvectorType __sq_getvector; + sq_getthisentityType __sq_getthisentity; + sq_getentityType __sq_getentity; + + sq_createuserdataType __sq_createuserdata; + sq_setuserdatatypeidType __sq_setuserdatatypeid; +#pragma endregion -typedef void (*sq_defconst)(void* sqvm, const SQChar* name, int value); -extern sq_defconst ClientSq_defconst; -extern sq_defconst ServerSq_defconst; + public: + SquirrelManager() : m_pSQVM(nullptr) {} -typedef SQRESULT (*sq_pushAssetType)(void* sqvm, const SQChar* assetName, SQInteger nameLength); -extern sq_pushAssetType ServerSq_pushAsset; -extern sq_pushAssetType ClientSq_pushAsset; + void VMCreated(CSquirrelVM* newSqvm); + void VMDestroyed(); + void ExecuteCode(const char* code); + void AddFuncRegistration(std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func); + SQRESULT setupfunc(const SQChar* funcname); + void AddFuncOverride(std::string name, SQFunction func); -// sq stack get funcs -typedef const SQChar* (*sq_getstringType)(void* sqvm, SQInteger stackpos); -extern sq_getstringType ClientSq_getstring; -extern sq_getstringType ServerSq_getstring; +#pragma region SQVM func wrappers + inline void defconst(CSquirrelVM* sqvm, const SQChar* pName, int nValue) + { + __sq_defconst(sqvm, pName, nValue); + } -typedef SQInteger (*sq_getintegerType)(void* sqvm, SQInteger stackpos); -extern sq_getintegerType ClientSq_getinteger; -extern sq_getintegerType ServerSq_getinteger; + inline SQRESULT + compilebuffer(CompileBufferState* bufferState, const SQChar* bufferName = "unnamedbuffer", const SQBool bShouldThrowError = false) + { + return __sq_compilebuffer(m_pSQVM->sqvm, bufferState, bufferName, -1, bShouldThrowError); + } -typedef SQFloat (*sq_getfloatType)(void*, SQInteger stackpos); -extern sq_getfloatType ClientSq_getfloat; -extern sq_getfloatType ServerSq_getfloat; + inline SQRESULT call(HSquirrelVM* sqvm, const SQInteger args) + { + return __sq_call(sqvm, args + 1, false, false); + } -typedef SQBool (*sq_getboolType)(void*, SQInteger stackpos); -extern sq_getboolType ClientSq_getbool; -extern sq_getboolType ServerSq_getbool; + inline SQInteger raiseerror(HSquirrelVM* sqvm, const const SQChar* sError) + { + return __sq_raiseerror(sqvm, sError); + } -typedef SQRESULT (*sq_getType)(void* sqvm, SQInteger idx); -extern sq_getType ServerSq_sq_get; -extern sq_getType ClientSq_sq_get; + inline void newarray(HSquirrelVM* sqvm, const SQInteger stackpos = 0) + { + __sq_newarray(sqvm, stackpos); + } -// sq table functions -typedef SQRESULT (*sq_newTableType)(void* sqvm); -extern sq_newTableType ServerSq_newTable; -extern sq_newTableType ClientSq_newTable; + inline SQRESULT arrayappend(HSquirrelVM* sqvm, const SQInteger stackpos) + { + return __sq_arrayappend(sqvm, stackpos); + } -typedef SQRESULT (*sq_newSlotType)(void* sqvm, int idx, bool bStatic); -extern sq_newSlotType ServerSq_newSlot; -extern sq_newSlotType ClientSq_newSlot; + inline SQRESULT newtable(HSquirrelVM* sqvm) + { + return __sq_newtable(sqvm); + } -template class SquirrelManager -{ - private: - std::vector m_funcRegistrations; + inline SQRESULT newslot(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic) + { + return __sq_newslot(sqvm, idx, bStatic); + } - public: - void* sqvm; - void* sqvm2; + inline void pushroottable(HSquirrelVM* sqvm) + { + __sq_pushroottable(sqvm); + } - public: - SquirrelManager() : sqvm(nullptr) {} + inline void pushstring(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) + { + __sq_pushstring(sqvm, sVal, length); + } - void VMCreated(void* newSqvm) + inline void pushinteger(HSquirrelVM* sqvm, const SQInteger iVal) { - sqvm = newSqvm; - sqvm2 = *((void**)((char*)sqvm + 8)); // honestly not 100% sure on what this is, but alot of functions take it - - for (SQFuncRegistration* funcReg : m_funcRegistrations) - { - spdlog::info("Registering {} function {}", GetContextName(context), funcReg->squirrelFuncName); - - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - ClientRegisterSquirrelFunc(sqvm, funcReg, 1); - else - ServerRegisterSquirrelFunc(sqvm, funcReg, 1); - } - for (auto& pair : g_ModManager->DependencyConstants) - { - bool wasFound = false; - for (Mod& dependency : g_ModManager->m_loadedMods) - { - if (dependency.Name == pair.second) - { - wasFound = dependency.Enabled; - break; - } - } - if (context == ScriptContext::SERVER) - ServerSq_defconst(sqvm, pair.first.c_str(), wasFound); - else - ClientSq_defconst(sqvm, pair.first.c_str(), wasFound); - } + __sq_pushinteger(sqvm, iVal); } - void VMDestroyed() + inline void pushfloat(HSquirrelVM* sqvm, const SQFloat flVal) { - sqvm = nullptr; + __sq_pushfloat(sqvm, flVal); } - void ExecuteCode(const char* code) + inline void pushbool(HSquirrelVM* sqvm, const SQBool bVal) { - // ttf2sdk checks ThreadIsInMainThread here, might be good to do that? doesn't seem like an issue rn tho - - if (!sqvm) - { - spdlog::error("Cannot execute code, {} squirrel vm is not initialised", GetContextName(context)); - return; - } - - spdlog::info("Executing {} script code {} ", GetContextName(context), code); - - std::string strCode(code); - CompileBufferState bufferState = CompileBufferState(strCode); - - SQRESULT compileResult; - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - compileResult = ClientSq_compilebuffer(sqvm2, &bufferState, "console", -1, context); - else if (context == ScriptContext::SERVER) - compileResult = ServerSq_compilebuffer(sqvm2, &bufferState, "console", -1, context); - - spdlog::info("sq_compilebuffer returned {}", compileResult); - if (compileResult >= 0) - { - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - { - ClientSq_pushroottable(sqvm2); - SQRESULT callResult = ClientSq_call(sqvm2, 1, false, false); - spdlog::info("sq_call returned {}", callResult); - } - else if (context == ScriptContext::SERVER) - { - ServerSq_pushroottable(sqvm2); - SQRESULT callResult = ServerSq_call(sqvm2, 1, false, false); - spdlog::info("sq_call returned {}", callResult); - } - } + __sq_pushbool(sqvm, bVal); } - int setupfunc(const char* funcname) + inline void pushasset(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) { - int result = -2; - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - { - ClientSq_pushroottable(sqvm2); - ClientSq_pushstring(sqvm2, funcname, -1); - result = ClientSq_sq_get(sqvm2, -2); - if (result != SQRESULT_ERROR) - { - ClientSq_pushroottable(sqvm2); - } - } - else if (context == ScriptContext::SERVER) - { - ServerSq_pushroottable(sqvm2); - ServerSq_pushstring(sqvm2, funcname, -1); - result = ServerSq_sq_get(sqvm2, -2); - if (result != SQRESULT_ERROR) - { - ServerSq_pushroottable(sqvm2); - } - } - return result; + __sq_pushasset(sqvm, sVal, length); } - void pusharg(int arg) + inline void pushvector(HSquirrelVM* sqvm, const Vector3 pVal) { - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - ClientSq_pushinteger(sqvm2, arg); - else if (context == ScriptContext::SERVER) - ServerSq_pushinteger(sqvm2, arg); + __sq_pushvector(sqvm, *(float**)&pVal); } - void pusharg(const char* arg) + + inline const SQChar* getstring(HSquirrelVM* sqvm, const SQInteger stackpos) { - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - ClientSq_pushstring(sqvm2, arg, -1); - else if (context == ScriptContext::SERVER) - ServerSq_pushstring(sqvm2, arg, -1); + return __sq_getstring(sqvm, stackpos); } - void pusharg(float arg) + + inline SQInteger getinteger(HSquirrelVM* sqvm, const SQInteger stackpos) { - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - ClientSq_pushfloat(sqvm2, arg); - else if (context == ScriptContext::SERVER) - ServerSq_pushfloat(sqvm2, arg); + return __sq_getinteger(sqvm, stackpos); } - void pusharg(bool arg) + + inline SQFloat getfloat(HSquirrelVM* sqvm, const SQInteger stackpos) { - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - ClientSq_pushbool(sqvm2, arg); - else if (context == ScriptContext::SERVER) - ServerSq_pushbool(sqvm2, arg); + return __sq_getfloat(sqvm, stackpos); } - int call(int args) + inline SQBool getbool(HSquirrelVM* sqvm, const SQInteger stackpos) { - int result = -2; - if (context == ScriptContext::CLIENT || context == ScriptContext::UI) - result = ClientSq_call(sqvm2, args + 1, false, false); - else if (context == ScriptContext::SERVER) - result = ServerSq_call(sqvm2, args + 1, false, false); + return __sq_getbool(sqvm, stackpos); + } - return result; + inline SQRESULT get(HSquirrelVM* sqvm, const SQInteger stackpos) + { + return __sq_get(sqvm, stackpos); } - void AddFuncRegistration(std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func) + inline Vector3 getvector(HSquirrelVM* sqvm, const SQInteger stackpos) { - SQFuncRegistration* reg = new SQFuncRegistration; + float* pRet = __sq_getvector(sqvm, stackpos); + return *(Vector3*)&pRet; + } - reg->squirrelFuncName = new char[name.size() + 1]; - strcpy((char*)reg->squirrelFuncName, name.c_str()); - reg->cppFuncName = reg->squirrelFuncName; + inline SQRESULT getasset(HSquirrelVM* sqvm, const SQInteger stackpos, const char** result) + { + return __sq_getasset(sqvm, stackpos, result); + } - reg->helpText = new char[helpText.size() + 1]; - strcpy((char*)reg->helpText, helpText.c_str()); + template inline SQRESULT getuserdata(HSquirrelVM* sqvm, const SQInteger stackpos, T* data, uint64_t* typeId) + { + return __sq_getuserdata(sqvm, stackpos, (void**)data, typeId); // this sometimes crashes idk + } - reg->returnTypeString = new char[returnType.size() + 1]; - strcpy((char*)reg->returnTypeString, returnType.c_str()); - reg->returnTypeEnum = GetReturnTypeEnumFromString(returnType.c_str()); + template inline T* createuserdata(HSquirrelVM* sqvm, SQInteger size) + { + void* ret = __sq_createuserdata(sqvm, size); + memset(ret, 0, size); + return (T*)ret; + } - reg->argTypes = new char[argTypes.size() + 1]; - strcpy((char*)reg->argTypes, argTypes.c_str()); + inline SQRESULT setuserdatatypeid(HSquirrelVM* sqvm, const SQInteger stackpos, uint64_t typeId) + { + return __sq_setuserdatatypeid(sqvm, stackpos, typeId); + } - reg->funcPtr = reinterpret_cast(func); + template inline SQBool getthisentity(HSquirrelVM* sqvm, T* ppEntity) + { + return __sq_getentity(sqvm, (void**)ppEntity); + } - m_funcRegistrations.push_back(reg); + template inline T* getentity(HSquirrelVM* sqvm, SQInteger iStackPos) + { + return (T*)__sq_getentity(sqvm, iStackPos); } +#pragma endregion }; -extern SquirrelManager* g_ClientSquirrelManager; -extern SquirrelManager* g_ServerSquirrelManager; -extern SquirrelManager* g_UISquirrelManager; +template SquirrelManager* g_pSquirrel; diff --git a/NorthstarDLL/squirreldatatypes.h b/NorthstarDLL/squirreldatatypes.h new file mode 100644 index 00000000..818ce2a4 --- /dev/null +++ b/NorthstarDLL/squirreldatatypes.h @@ -0,0 +1,484 @@ +#pragma once +/* + This file has been generated by IDA. + It contains local type definitions from + the type library 'server.dll' +*/ + +struct HSquirrelVM; +struct CallInfo; +struct SQTable; +struct SQString; +struct SQFunctionProto; +struct SQClosure; +struct SQSharedState; +struct StringTable; +struct SQStructInstance; +struct SQStructDef; +struct SQNativeClosure; +struct SQArray; +struct tableNode; +struct SQUserData; + +typedef void (*releasehookType)(void* val, int size); + +/* 127 */ +enum SQObjectType : int +{ + _RT_NULL = 0x1, + _RT_INTEGER = 0x2, + _RT_FLOAT = 0x4, + _RT_BOOL = 0x8, + _RT_STRING = 0x10, + _RT_TABLE = 0x20, + _RT_ARRAY = 0x40, + _RT_USERDATA = 0x80, + _RT_CLOSURE = 0x100, + _RT_NATIVECLOSURE = 0x200, + _RT_GENERATOR = 0x400, + OT_USERPOINTER = 0x800, + _RT_USERPOINTER = 0x800, + _RT_THREAD = 0x1000, + _RT_FUNCPROTO = 0x2000, + _RT_CLASS = 0x4000, + _RT_INSTANCE = 0x8000, + _RT_WEAKREF = 0x10000, + OT_VECTOR = 0x40000, + SQOBJECT_CANBEFALSE = 0x1000000, + OT_NULL = 0x1000001, + OT_BOOL = 0x1000008, + SQOBJECT_DELEGABLE = 0x2000000, + SQOBJECT_NUMERIC = 0x4000000, + OT_INTEGER = 0x5000002, + OT_FLOAT = 0x5000004, + SQOBJECT_REF_COUNTED = 0x8000000, + OT_STRING = 0x8000010, + OT_ARRAY = 0x8000040, + OT_CLOSURE = 0x8000100, + OT_NATIVECLOSURE = 0x8000200, + OT_ASSET = 0x8000400, + OT_THREAD = 0x8001000, + OT_FUNCPROTO = 0x8002000, + OT_CLAAS = 0x8004000, + OT_STRUCT = 0x8200000, + OT_WEAKREF = 0x8010000, + OT_TABLE = 0xA000020, + OT_USERDATA = 0xA000080, + OT_INSTANCE = 0xA008000, + OT_ENTITY = 0xA400000, +}; + +/* 156 */ +union SQObjectValue +{ + SQString* asString; + SQTable* asTable; + SQClosure* asClosure; + SQFunctionProto* asFuncProto; + SQStructDef* asStructDef; + long long as64Integer; + SQNativeClosure* asNativeClosure; + SQArray* asArray; + HSquirrelVM* asThread; + float asFloat; + int asInteger; + SQUserData* asUserdata; +}; + +/* 160 */ +struct SQVector +{ + SQObjectType _Type; + float x; + float y; + float z; +}; + +/* 128 */ +struct SQObject +{ + SQObjectType _Type; + int structNumber; + SQObjectValue _VAL; +}; + +/* 138 */ +struct alignas(8) SQString +{ + void* vftable; + int uiRef; + int padding; + SQString* _next_maybe; + SQSharedState* sharedState; + int length; + unsigned char gap_24[4]; + char _hash[8]; + char _val[1]; +}; + +/* 137 */ +struct alignas(8) SQTable +{ + void* vftable; + unsigned char gap_08[4]; + int uiRef; + unsigned char gap_10[8]; + void* pointer_18; + void* pointer_20; + void* _sharedState; + long long field_30; + tableNode* _nodes; + int _numOfNodes; + int size; + int field_48; + int _usedNodes; + unsigned char _gap_50[20]; + int field_64; + unsigned char _gap_68[80]; +}; + +/* 140 */ +struct alignas(8) SQClosure +{ + void* vftable; + unsigned char gap_08[4]; + int uiRef; + void* pointer_10; + void* pointer_18; + void* pointer_20; + void* sharedState; + SQObject obj_30; + SQObject _function; + SQObject* _outervalues; + unsigned char gap_58[8]; + unsigned char gap_60[96]; + SQObject* objectPointer_C0; + unsigned char gap_C8[16]; +}; + +/* 139 */ +struct alignas(8) SQFunctionProto +{ + void* vftable; + unsigned char gap_08[4]; + int uiRef; + unsigned char gap_10[8]; + void* pointer_18; + void* pointer_20; + void* sharedState; + void* pointer_30; + SQObjectType _fileNameType; + SQString* _fileName; + SQObjectType _funcNameType; + SQString* _funcName; + SQObject obj_58; + unsigned char gap_68[12]; + int _stacksize; + unsigned char gap_78[48]; + int nParameters; + unsigned char gap_AC[60]; + int nDefaultParams; + unsigned char gap_EC[200]; +}; + +/* 152 */ +struct SQStructDef +{ + void* vtable; + int uiRef; + unsigned char padding_C[4]; + unsigned char unknown[24]; + SQSharedState* sharedState; + SQObjectType _nameType; + SQString* _name; + unsigned char gap_38[16]; + SQObjectType _variableNamesType; + SQTable* _variableNames; + unsigned char gap_[32]; +}; + +/* 157 */ +struct alignas(8) SQNativeClosure +{ + void* vftable; + int uiRef; + unsigned char gap_C[4]; + long long value_10; + long long value_18; + long long value_20; + SQSharedState* sharedState; + char unknown_30; + unsigned char padding_34[7]; + long long value_38; + long long value_40; + long long value_48; + long long value_50; + long long value_58; + SQObjectType _nameType; + SQString* _name; + long long value_70; + long long value_78; + unsigned char justInCaseGap_80[300]; +}; + +/* 162 */ +struct SQArray +{ + void* vftable; + int uiRef; + unsigned char gap_24[36]; + SQObject* _values; + int _usedSlots; + int _allocated; +}; + +/* 129 */ +struct alignas(8) HSquirrelVM +{ + void* vftable; + int uiRef; + unsigned char gap_8[12]; + void* _toString; + void* _roottable_pointer; + void* pointer_28; + CallInfo* ci; + CallInfo* _callstack; + int _callsstacksize; + int _stackbase; + SQObject* _stackOfCurrentFunction; + SQSharedState* sharedState; + void* pointer_58; + void* pointer_60; + int _top; + SQObject* _stack; + unsigned char gap_78[8]; + SQObject* _vargvstack; + unsigned char gap_88[8]; + SQObject temp_reg; + unsigned char gapA0[8]; + void* pointer_A8; + unsigned char gap_B0[8]; + SQObject _roottable_object; + SQObject _lasterror; + SQObject _errorHandler; + long long field_E8; + int traps; + unsigned char gap_F4[12]; + int _nnativecalls; + int _suspended; + int _suspended_root; + int _callstacksize; + int _suspended_target; + int trapAmount; + int _suspend_varargs; + int unknown_field_11C; + SQObject object_120; +}; + +/* 150 */ +struct SQStructInstance +{ + void* vftable; + unsigned char gap_8[16]; + void* pointer_18; + unsigned char gap_20[8]; + SQSharedState* _sharedState; + unsigned char gap[8]; + SQObject data[20]; +}; + +/* 148 */ +struct SQSharedState +{ + unsigned char gap_0[72]; + void* unknown; + unsigned char gap_50[16344]; + SQObjectType _unknownTableType00; + long long _unknownTableValue00; + unsigned char gap_4038[16]; + StringTable* _stringTable; + unsigned char gap_4050[32]; + SQObjectType _unknownTableType0; + long long _unknownTableValue0; + SQObjectType _unknownObjectType1; + long long _unknownObjectValue1; + unsigned char gap_4090[8]; + SQObjectType _unknownArrayType2; + long long _unknownArrayValue2; + SQObjectType _gobalsArrayType; + SQStructInstance* _globalsArray; + unsigned char gap_40B8[16]; + SQObjectType _nativeClosuresType; + SQTable* _nativeClosures; + SQObjectType _typedConstantsType; + SQTable* _typedConstants; + SQObjectType _untypedConstantsType; + SQTable* _untypedConstants; + SQObjectType _globalsMaybeType; + SQTable* _globals; + SQObjectType _functionsType; + SQTable* _functions; + SQObjectType _structsType; + SQTable* _structs; + SQObjectType _typeDefsType; + SQTable* _typeDefs; + SQObjectType unknownTableType; + SQTable* unknownTable; + SQObjectType _squirrelFilesType; + SQTable* _squirrelFiles; + unsigned char gap_4158[80]; + SQObjectType _nativeClosures2Type; + SQTable* _nativeClosures2; + SQObjectType _entityTypesMaybeType; + SQTable* _entityTypesMaybe; + SQObjectType unknownTable2Type; + SQTable* unknownTable2; + unsigned char gap_41D8[72]; + SQObjectType _compilerKeywordsType; + SQTable* _compilerKeywords; + HSquirrelVM* _currentThreadMaybe; + unsigned char gap_4238[8]; + SQObjectType unknownTable3Type; + SQTable* unknownTable3; + unsigned char gap_4250[16]; + SQObjectType unknownThreadType; + SQTable* unknownThread; + SQObjectType _tableNativeFunctionsType; + SQTable* _tableNativeFunctions; + SQObjectType _unknownTableType4; + long long _unknownObjectValue4; + SQObjectType _unknownObjectType5; + long long _unknownObjectValue5; + SQObjectType _unknownObjectType6; + long long _unknownObjectValue6; + SQObjectType _unknownObjectType7; + long long _unknownObjectValue7; + SQObjectType _unknownObjectType8; + long long _unknownObjectValue8; + SQObjectType _unknownObjectType9; + long long _unknownObjectValue9; + SQObjectType _unknownObjectType10; + long long _unknownObjectValue10; + SQObjectType _unknownObjectType11; + long long _unknownObjectValue11; + SQObjectType _unknownObjectType12; + long long _unknownObjectValue12; + SQObjectType _unknownObjectType13; + long long _unknownObjectValue13; + SQObjectType _unknownObjectType14; + long long _unknownObjectValue14; + SQObjectType _unknownObjectType15; + long long _unknownObjectValue15; + unsigned char gap_4340[16]; + void* printFunction; + unsigned char gap_4358[16]; + void* logEntityFunction; + unsigned char gap_4370[40]; + SQObjectType _waitStringType; + SQString* _waitStringValue; + SQObjectType _SpinOffAndWaitForStringType; + SQString* _SpinOffAndWaitForStringValue; + SQObjectType _SpinOffAndWaitForSoloStringType; + SQString* _SpinOffAndWaitForSoloStringValue; + SQObjectType _SpinOffStringType; + SQString* _SpinOffStringValue; + SQObjectType _SpinOffDelayedStringType; + SQString* _SpinOffDelayedStringValue; + unsigned char gap_43E8[8]; + bool enableDebugInfo; // functionality stripped + unsigned char gap_43F1[23]; +}; + +/* 165 */ +struct tableNode +{ + SQObject val; + SQObject key; + tableNode* next; +}; + +/* 136 */ +struct alignas(8) CallInfo +{ + long long ip; + SQObject* _literals; + SQObject obj10; + SQObject closure; + int _etraps[4]; + int _root; + short _vargs_size; + short _vargs_base; + unsigned char gap[16]; +}; + +/* 149 */ +struct StringTable +{ + unsigned char gap_0[12]; + int _numofslots; + unsigned char gap_10[200]; +}; + +/* 141 */ +struct alignas(8) SQStackInfos +{ + char* _name; + char* _sourceName; + int _line; +}; + +/* 151 */ +struct alignas(4) SQInstruction +{ + int op; + int arg1; + int output; + short arg2; + short arg3; +}; + +/* 154 */ +struct SQLexer +{ + unsigned char gap_0[112]; +}; + +/* 153 */ +struct SQCompiler +{ + unsigned char gap_0[4]; + int _token; + unsigned char gap_8[8]; + SQObject object_10; + SQLexer lexer; + unsigned char gap_90[752]; + HSquirrelVM* sqvm; + unsigned char gap_288[8]; +}; + +/* 155 */ +struct CSquirrelVM +{ + unsigned char gap_0[8]; + HSquirrelVM* sqvm; + unsigned char gap_10[44]; + int loadEnumFromFileMaybe; + unsigned char gap_40[200]; +}; + +struct SQUserData +{ + void* vftable; + int uiRef; + char gap_12[4]; + long long unknown_10; + long long unknown_18; + long long unknown_20; + long long sharedState; + long long unknown_30; + int size; + char padding1[4]; + releasehookType releaseHook; + long long typeId; + char data[1]; +}; diff --git a/NorthstarDLL/tier0.cpp b/NorthstarDLL/tier0.cpp new file mode 100644 index 00000000..61ad7783 --- /dev/null +++ b/NorthstarDLL/tier0.cpp @@ -0,0 +1,37 @@ +#include "pch.h" +#include "tier0.h" + +// use the Tier0 namespace for tier0 funcs +namespace Tier0 +{ + IMemAlloc* g_pMemAllocSingleton; + + ErrorType Error; + CommandLineType CommandLine; + Plat_FloatTimeType Plat_FloatTime; + ThreadInServerFrameThreadType ThreadInServerFrameThread; +} // namespace Tier0 + +typedef Tier0::IMemAlloc* (*CreateGlobalMemAllocType)(); +CreateGlobalMemAllocType CreateGlobalMemAlloc; + +// needs to be a seperate function, since memalloc.cpp calls it +void TryCreateGlobalMemAlloc() +{ + // init memalloc stuff + CreateGlobalMemAlloc = + reinterpret_cast(GetProcAddress(GetModuleHandleA("tier0.dll"), "CreateGlobalMemAlloc")); + Tier0::g_pMemAllocSingleton = CreateGlobalMemAlloc(); // if it already exists, this returns the preexisting IMemAlloc instance +} + +ON_DLL_LOAD("tier0.dll", Tier0GameFuncs, (CModule module)) +{ + // shouldn't be necessary, but do this just in case + TryCreateGlobalMemAlloc(); + + // setup tier0 funcs + Tier0::Error = module.GetExport("Error").As(); + Tier0::CommandLine = module.GetExport("CommandLine").As(); + Tier0::Plat_FloatTime = module.GetExport("Plat_FloatTime").As(); + Tier0::ThreadInServerFrameThread = module.GetExport("ThreadInServerFrameThread").As(); +} diff --git a/NorthstarDLL/tier0.h b/NorthstarDLL/tier0.h new file mode 100644 index 00000000..92a63027 --- /dev/null +++ b/NorthstarDLL/tier0.h @@ -0,0 +1,68 @@ +#pragma once +namespace Tier0 +{ + class IMemAlloc + { + public: + struct VTable + { + void* unknown[1]; // alloc debug + void* (*Alloc)(IMemAlloc* memAlloc, size_t nSize); + void* unknown2[1]; // realloc debug + void* (*Realloc)(IMemAlloc* memAlloc, void* pMem, size_t nSize); + void* unknown3[1]; // free #1 + void (*Free)(IMemAlloc* memAlloc, void* pMem); + void* unknown4[2]; // nullsubs, maybe CrtSetDbgFlag + size_t (*GetSize)(IMemAlloc* memAlloc, void* pMem); + void* unknown5[9]; // they all do literally nothing + void (*DumpStats)(IMemAlloc* memAlloc); + void (*DumpStatsFileBase)(IMemAlloc* memAlloc, const char* pchFileBase); + void* unknown6[4]; + int (*heapchk)(IMemAlloc* memAlloc); + }; + + VTable* m_vtable; + }; + + class CCommandLine + { + public: + // based on the defs in the 2013 source sdk, but for some reason has an extra function (may be another CreateCmdLine overload?) + // these seem to line up with what they should be though + virtual void CreateCmdLine(const char* commandline) {} + virtual void CreateCmdLine(int argc, char** argv) {} + virtual void unknown() {} + virtual const char* GetCmdLine(void) const {} + + virtual const char* CheckParm(const char* psz, const char** ppszValue = 0) const {} + virtual void RemoveParm() const {} + virtual void AppendParm(const char* pszParm, const char* pszValues) {} + + virtual const char* ParmValue(const char* psz, const char* pDefaultVal = 0) const {} + virtual int ParmValue(const char* psz, int nDefaultVal) const {} + virtual float ParmValue(const char* psz, float flDefaultVal) const {} + + virtual int ParmCount() const {} + virtual int FindParm(const char* psz) const {} + virtual const char* GetParm(int nIndex) const {} + virtual void SetParm(int nIndex, char const* pParm) {} + + // virtual const char** GetParms() const {} + }; + + extern IMemAlloc* g_pMemAllocSingleton; + + typedef void (*ErrorType)(const char* fmt, ...); + extern ErrorType Error; + + typedef CCommandLine* (*CommandLineType)(); + extern CommandLineType CommandLine; + + typedef double (*Plat_FloatTimeType)(); + extern Plat_FloatTimeType Plat_FloatTime; + + typedef bool (*ThreadInServerFrameThreadType)(); + extern ThreadInServerFrameThreadType ThreadInServerFrameThread; +} // namespace Tier0 + +void TryCreateGlobalMemAlloc(); diff --git a/NorthstarDLL/vector.h b/NorthstarDLL/vector.h new file mode 100644 index 00000000..c06e0bba --- /dev/null +++ b/NorthstarDLL/vector.h @@ -0,0 +1,52 @@ +#pragma once + +union Vector3 +{ + struct + { + float x; + float y; + float z; + }; + + float raw[3]; + + Vector3() : x(0), y(0), z(0) {} + Vector3(float x, float y, float z) : x(x), y(y), z(z) {} + Vector3(float* pRawFloats) // assumes float[3] => vector + { + memcpy(raw, pRawFloats, sizeof(this)); + } + + void MakeValid() + { + for (auto& fl : raw) + if (isnan(fl)) + fl = 0; + } + + // todo: more operators maybe + bool operator==(const Vector3& other) + { + return x == other.x && y == other.y && z == other.z; + } +}; + +union QAngle +{ + struct + { + float x; + float y; + float z; + float w; + }; + + float raw[4]; + + // todo: more operators maybe + bool operator==(const QAngle& other) + { + return x == other.x && y == other.y && z == other.z && w == other.w; + } +}; diff --git a/NorthstarLauncher/NorthstarLauncher.vcxproj b/NorthstarLauncher/NorthstarLauncher.vcxproj index 33930f14..b4c7845f 100644 --- a/NorthstarLauncher/NorthstarLauncher.vcxproj +++ b/NorthstarLauncher/NorthstarLauncher.vcxproj @@ -1,104 +1,110 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {0ea82cb0-53fe-4d4c-96df-47fa970513d0} - NorthstarLauncher - 10.0 - NorthstarLauncher - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - true - - - false - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - /F8000000 %(AdditionalOptions) - - - Console - true - shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - 8000000 - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - /F8000000 %(AdditionalOptions) - - - Console - true - true - true - shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - 8000000 - - - - - - - - - - - - - - - - - + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {0ea82cb0-53fe-4d4c-96df-47fa970513d0} + LauncherInjector + 10.0 + NorthstarLauncher + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + true + + + false + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + /F8000000 %(AdditionalOptions) + + + Console + true + shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + 8000000 + + + copy /Y "$(TargetPath)" "$(SolutionDir)..\..\" + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + /F8000000 %(AdditionalOptions) + + + Console + true + true + true + shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + 8000000 + + + IF EXIST "$(SolutionDir)..\..\Titanfall2.exe" del "$(SolutionDir)..\..\NorthstarLauncher.exe" && copy /Y "$(TargetPath)" "$(SolutionDir)..\..\ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NorthstarLauncher/NorthstarLauncher.vcxproj.filters b/NorthstarLauncher/NorthstarLauncher.vcxproj.filters index 9cdc5aab..76b907bf 100644 --- a/NorthstarLauncher/NorthstarLauncher.vcxproj.filters +++ b/NorthstarLauncher/NorthstarLauncher.vcxproj.filters @@ -1,4 +1,4 @@ - + diff --git a/R2Northstar.sln b/R2Northstar.sln index 341c66dd..b66b0dcd 100644 --- a/R2Northstar.sln +++ b/R2Northstar.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.32014.148 diff --git a/loader_launcher_proxy/Memory.cpp b/loader_launcher_proxy/Memory.cpp deleted file mode 100644 index 344fa834..00000000 --- a/loader_launcher_proxy/Memory.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "pch.h" -#include "Memory.h" - -extern HMODULE hTier0Module; -IMemAlloc** g_ppMemAllocSingleton; - -void LoadTier0Handle() -{ - if (!hTier0Module) - hTier0Module = GetModuleHandleA("tier0.dll"); - if (!hTier0Module) - return; - - g_ppMemAllocSingleton = (IMemAlloc**)GetProcAddress(hTier0Module, "g_pMemAllocSingleton"); -} - -const int STATIC_ALLOC_SIZE = 4096; - -size_t g_iStaticAllocated = 0; -void* g_pLastAllocated = nullptr; -char pStaticAllocBuf[STATIC_ALLOC_SIZE]; - -// they should never be used here, except in LibraryLoadError? - -void* malloc(size_t n) -{ - // allocate into static buffer - if (g_iStaticAllocated + n <= STATIC_ALLOC_SIZE) - { - void* ret = pStaticAllocBuf + g_iStaticAllocated; - g_iStaticAllocated += n; - return ret; - } - else - { - // try to fallback to g_pMemAllocSingleton - if (!hTier0Module || !g_ppMemAllocSingleton) - LoadTier0Handle(); - if (g_ppMemAllocSingleton && *g_ppMemAllocSingleton) - return (*g_ppMemAllocSingleton)->m_vtable->Alloc(*g_ppMemAllocSingleton, n); - else - throw "Cannot allocate"; - } -} - -void free(void* p) -{ - // if it was allocated into the static buffer, just do nothing, safest way to deal with it - if (p >= pStaticAllocBuf && p <= pStaticAllocBuf + STATIC_ALLOC_SIZE) - return; - - if (g_ppMemAllocSingleton && *g_ppMemAllocSingleton) - (*g_ppMemAllocSingleton)->m_vtable->Free(*g_ppMemAllocSingleton, p); -} - -void* realloc(void* old_ptr, size_t size) -{ - // it was allocated into the static buffer - if (old_ptr >= pStaticAllocBuf && old_ptr <= pStaticAllocBuf + STATIC_ALLOC_SIZE) - { - if (g_pLastAllocated == old_ptr) - { - // nothing was allocated after this - size_t old_size = g_iStaticAllocated - ((size_t)g_pLastAllocated - (size_t)pStaticAllocBuf); - size_t diff = size - old_size; - if (diff > 0) - g_iStaticAllocated += diff; - return old_ptr; - } - else - { - return malloc(size); - } - } - - if (g_ppMemAllocSingleton && *g_ppMemAllocSingleton) - return (*g_ppMemAllocSingleton)->m_vtable->Realloc(*g_ppMemAllocSingleton, old_ptr, size); - return nullptr; -} - -void* operator new(size_t n) -{ - return malloc(n); -} - -void operator delete(void* p) noexcept -{ - return free(p); -} diff --git a/loader_launcher_proxy/Memory.h b/loader_launcher_proxy/Memory.h deleted file mode 100644 index 824d9d0f..00000000 --- a/loader_launcher_proxy/Memory.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -class IMemAlloc -{ - public: - struct VTable - { - void* unknown[1]; // alloc debug - void* (*Alloc)(IMemAlloc* memAlloc, size_t nSize); - void* unknown2[1]; // realloc debug - void* (*Realloc)(IMemAlloc* memAlloc, void* pMem, size_t nSize); - void* unknown3[1]; // free #1 - void (*Free)(IMemAlloc* memAlloc, void* pMem); - void* unknown4[2]; // nullsubs, maybe CrtSetDbgFlag - size_t (*GetSize)(IMemAlloc* memAlloc, void* pMem); - void* unknown5[9]; // they all do literally nothing - void (*DumpStats)(IMemAlloc* memAlloc); - void (*DumpStatsFileBase)(IMemAlloc* memAlloc, const char* pchFileBase); - void* unknown6[4]; - int (*heapchk)(IMemAlloc* memAlloc); - }; - - VTable* m_vtable; -}; diff --git a/loader_launcher_proxy/loader_launcher_proxy.vcxproj.filters b/loader_launcher_proxy/loader_launcher_proxy.vcxproj.filters index f64932cd..4bf52578 100644 --- a/loader_launcher_proxy/loader_launcher_proxy.vcxproj.filters +++ b/loader_launcher_proxy/loader_launcher_proxy.vcxproj.filters @@ -1,4 +1,4 @@ - + diff --git a/loader_wsock32_proxy/hookutils.cpp b/loader_wsock32_proxy/hookutils.cpp deleted file mode 100644 index f359e6ec..00000000 --- a/loader_wsock32_proxy/hookutils.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include - -#include "pch.h" -#include "../NorthstarDLL/hookutils.h" - -#define HU_ERROR(...) \ - { \ - char err[2048]; \ - snprintf(err, sizeof(err), __VA_ARGS__); \ - MessageBoxA(GetForegroundWindow(), err, "Northstar Wsock32 Proxy Error", 0); \ - } - -void HookEnabler::CreateHook(LPVOID ppTarget, LPVOID ppDetour, LPVOID* ppOriginal, const char* targetName) -{ - // the macro for this uses ppTarget's name as targetName, and this typically starts with & - // targetname is used for debug stuff and debug output is nicer if we don't have this - if (*targetName == '&') - targetName++; - - if (MH_CreateHook(ppTarget, ppDetour, ppOriginal) == MH_OK) - { - HookTarget* target = new HookTarget; - target->targetAddress = ppTarget; - target->targetName = (char*)targetName; - - m_hookTargets.push_back(target); - } - else - { - if (targetName != nullptr) - { - HU_ERROR("MH_CreateHook failed for function %s", targetName); - } - else - { - HU_ERROR("MH_CreateHook failed for unknown function"); - } - } -} - -HookEnabler::~HookEnabler() -{ - for (auto& hook : m_hookTargets) - { - if (MH_EnableHook(hook->targetAddress) != MH_OK) - { - if (hook->targetName != nullptr) - { - HU_ERROR("MH_EnableHook failed for function %s", hook->targetName); - } - else - { - HU_ERROR("MH_EnableHook failed for unknown function"); - } - } - else - { - // HU_ERROR("Enabling hook %s", hook->targetName); - } - } -} diff --git a/loader_wsock32_proxy/include/MinHook.h b/loader_wsock32_proxy/include/MinHook.h new file mode 100644 index 00000000..15c0a875 --- /dev/null +++ b/loader_wsock32_proxy/include/MinHook.h @@ -0,0 +1,186 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__) + #error MinHook supports only x86 and x64 systems. +#endif + +#include + +// MinHook Error Codes. +typedef enum MH_STATUS +{ + // Unknown error. Should not be returned. + MH_UNKNOWN = -1, + + // Successful. + MH_OK = 0, + + // MinHook is already initialized. + MH_ERROR_ALREADY_INITIALIZED, + + // MinHook is not initialized yet, or already uninitialized. + MH_ERROR_NOT_INITIALIZED, + + // The hook for the specified target function is already created. + MH_ERROR_ALREADY_CREATED, + + // The hook for the specified target function is not created yet. + MH_ERROR_NOT_CREATED, + + // The hook for the specified target function is already enabled. + MH_ERROR_ENABLED, + + // The hook for the specified target function is not enabled yet, or already + // disabled. + MH_ERROR_DISABLED, + + // The specified pointer is invalid. It points the address of non-allocated + // and/or non-executable region. + MH_ERROR_NOT_EXECUTABLE, + + // The specified target function cannot be hooked. + MH_ERROR_UNSUPPORTED_FUNCTION, + + // Failed to allocate memory. + MH_ERROR_MEMORY_ALLOC, + + // Failed to change the memory protection. + MH_ERROR_MEMORY_PROTECT, + + // The specified module is not loaded. + MH_ERROR_MODULE_NOT_FOUND, + + // The specified function is not found. + MH_ERROR_FUNCTION_NOT_FOUND +} +MH_STATUS; + +// Can be passed as a parameter to MH_EnableHook, MH_DisableHook, +// MH_QueueEnableHook or MH_QueueDisableHook. +#define MH_ALL_HOOKS NULL + +#ifdef __cplusplus +extern "C" { +#endif + + // Initialize the MinHook library. You must call this function EXACTLY ONCE + // at the beginning of your program. + MH_STATUS WINAPI MH_Initialize(VOID); + + // Uninitialize the MinHook library. You must call this function EXACTLY + // ONCE at the end of your program. + MH_STATUS WINAPI MH_Uninitialize(VOID); + + // Creates a Hook for the specified target function, in disabled state. + // Parameters: + // pTarget [in] A pointer to the target function, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); + + // Creates a Hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszTarget [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApi( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal); + + // Creates a Hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszTarget [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + // ppTarget [out] A pointer to the target function, which will be used + // with other functions. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApiEx( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget); + + // Removes an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); + + // Enables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // enabled in one go. + MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); + + // Disables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // disabled in one go. + MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); + + // Queues to enable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be enabled. + MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); + + // Queues to disable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be disabled. + MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); + + // Applies all queued changes in one go. + MH_STATUS WINAPI MH_ApplyQueued(VOID); + + // Translates the MH_STATUS to its name as a string. + const char * WINAPI MH_StatusToString(MH_STATUS status); + +#ifdef __cplusplus +} +#endif + diff --git a/loader_wsock32_proxy/include/MinHook.x64.lib b/loader_wsock32_proxy/include/MinHook.x64.lib new file mode 100644 index 00000000..a346f386 Binary files /dev/null and b/loader_wsock32_proxy/include/MinHook.x64.lib differ diff --git a/loader_wsock32_proxy/loader.cpp b/loader_wsock32_proxy/loader.cpp index 9642df45..4af4daa1 100644 --- a/loader_wsock32_proxy/loader.cpp +++ b/loader_wsock32_proxy/loader.cpp @@ -1,6 +1,6 @@ #include "pch.h" #include "loader.h" -#include "../NorthstarDLL/hookutils.h" +#include "include/MinHook.h" #include #include #include @@ -36,7 +36,7 @@ bool ShouldLoadNorthstar() std::stringstream runNorthstarFileBuffer; runNorthstarFileBuffer << runNorthstarFile.rdbuf(); runNorthstarFile.close(); - if (!runNorthstarFileBuffer.str().starts_with("0")) + if (!runNorthstarFileBuffer.str()._Starts_with("0")) loadNorthstar = true; } return loadNorthstar; @@ -94,12 +94,10 @@ bool ProvisionNorthstar() return false; } - HookEnabler hook; - ENABLER_CREATEHOOK( - hook, - reinterpret_cast(GetProcAddress(launcherHandle, "LauncherMain")), - &LauncherMainHook, - reinterpret_cast(&LauncherMainOriginal)); + LPVOID pTarget = GetProcAddress(launcherHandle, "LauncherMain"); + if (MH_CreateHook(pTarget, &LauncherMainHook, reinterpret_cast(&LauncherMainOriginal)) != MH_OK || + MH_EnableHook(pTarget) != MH_OK) + MessageBoxA(GetForegroundWindow(), "Hook creation failed for function LauncherMain.", "Northstar Wsock32 Proxy Error", 0); return true; } diff --git a/loader_wsock32_proxy/loader_wsock32_proxy.vcxproj b/loader_wsock32_proxy/loader_wsock32_proxy.vcxproj index 3e72fb78..d65a8f12 100644 --- a/loader_wsock32_proxy/loader_wsock32_proxy.vcxproj +++ b/loader_wsock32_proxy/loader_wsock32_proxy.vcxproj @@ -61,14 +61,14 @@ Use pch.h stdcpp20 - ..\NorthstarDLL\ + ..\NorthstarDedicatedTest\ Windows true false wsock32.def - ..\NorthstarDLL\include\MinHook.x64.lib;mswsock.lib;ws2_32.lib;Shlwapi.lib;imagehlp.lib;dbghelp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\include\MinHook.x64.lib;mswsock.lib;ws2_32.lib;Shlwapi.lib;imagehlp.lib;dbghelp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -81,7 +81,7 @@ true Use pch.h - ..\NorthstarDLL\ + ..\NorthstarDedicatedTest\ stdcpp20 @@ -91,16 +91,16 @@ true false wsock32.def - ..\NorthstarDLL\include\MinHook.x64.lib;mswsock.lib;ws2_32.lib;Shlwapi.lib;imagehlp.lib;dbghelp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;wsock32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\include\MinHook.x64.lib;mswsock.lib;ws2_32.lib;Shlwapi.lib;imagehlp.lib;dbghelp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;wsock32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + - Create diff --git a/loader_wsock32_proxy/loader_wsock32_proxy.vcxproj.filters b/loader_wsock32_proxy/loader_wsock32_proxy.vcxproj.filters index 6d131e5b..d3c022de 100644 --- a/loader_wsock32_proxy/loader_wsock32_proxy.vcxproj.filters +++ b/loader_wsock32_proxy/loader_wsock32_proxy.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -13,6 +13,9 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + {1b1575f7-3332-478b-9e85-9c0978249a70} + @@ -21,6 +24,9 @@ Header Files + + Header Files\include + @@ -32,9 +38,6 @@ Source Files - - Source Files - @@ -46,4 +49,4 @@ Source Files - \ No newline at end of file + -- cgit v1.2.3