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 <realEmail@veryRealURL.com>

* 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 <malte.hoermeyer@web.de>
Co-authored-by: RoyalBlue1 <realEmail@veryRealURL.com>
Co-authored-by: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com>
Co-authored-by: ScureX <47725553+ScureX@users.noreply.github.com>
Co-authored-by: Erlite <ys.aameziane@gmail.com>
Co-authored-by: Emma Miler <emma.pi@protonmail.com>
Co-authored-by: pg9182 <96569817+pg9182@users.noreply.github.com>
This commit is contained in:
BobTheBob 2022-10-17 23:26:07 +01:00 committed by GitHub
parent dc0934d29c
commit 841881af9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
155 changed files with 11504 additions and 9865 deletions

View File

@ -14,9 +14,9 @@
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{cfad2623-064f-453c-8196-79ee10292e32}</ProjectGuid>
<RootNamespace>NorthstarDLL</RootNamespace>
<RootNamespace>NorthstarDedicatedTest</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>Northstar</ProjectName>
<ProjectName>NorthstarDLL</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
@ -47,9 +47,11 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<TargetName>Northstar</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<TargetName>Northstar</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
@ -75,6 +77,9 @@
<Command>
</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>copy /Y "$(TargetPath)" "$(SolutionDir)..\..\"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
@ -105,38 +110,39 @@
<Command>
</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>IF EXIST "$(SolutionDir)..\..\Titanfall2.exe" del "$(SolutionDir)..\..\Northstar.dll" &amp;&amp; copy /Y "$(TargetPath)" "$(SolutionDir)..\..\</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="audio.h" />
<ClInclude Include="bansystem.h" />
<ClInclude Include="bitbuf.h" />
<ClInclude Include="bits.h" />
<ClInclude Include="buildainfile.h" />
<ClInclude Include="chatcommand.h" />
<ClInclude Include="clientchathooks.h" />
<ClInclude Include="debugoverlay.h" />
<ClInclude Include="clientruihooks.h" />
<ClInclude Include="clientvideooverrides.h" />
<ClInclude Include="crashhandler.h" />
<ClInclude Include="squirreldatatypes.h" />
<ClInclude Include="limits.h" />
<ClInclude Include="maxplayers.h" />
<ClInclude Include="memory.h" />
<ClInclude Include="printcommand.h" />
<ClInclude Include="hoststate.h" />
<ClInclude Include="localchatwriter.h" />
<ClInclude Include="printmaps.h" />
<ClInclude Include="ns_version.h" />
<ClInclude Include="plugins.h" />
<ClInclude Include="plugin_abi.h" />
<ClInclude Include="scriptutility.h" />
<ClInclude Include="scriptjson.h" />
<ClInclude Include="r2client.h" />
<ClInclude Include="r2engine.h" />
<ClInclude Include="r2server.h" />
<ClInclude Include="serverchathooks.h" />
<ClInclude Include="clientauthhooks.h" />
<ClInclude Include="color.h" />
<ClInclude Include="concommand.h" />
<ClInclude Include="nsprefix.h" />
<ClInclude Include="context.h" />
<ClInclude Include="convar.h" />
<ClInclude Include="cvar.h" />
<ClInclude Include="dedicated.h" />
<ClInclude Include="dedicatedmaterialsystem.h" />
<ClInclude Include="filesystem.h" />
<ClInclude Include="gameutils.h" />
<ClInclude Include="hooks.h" />
<ClInclude Include="hookutils.h" />
<ClInclude Include="include\crypto\aes_platform.h" />
<ClInclude Include="include\crypto\aria.h" />
<ClInclude Include="include\crypto\asn1.h" />
@ -534,39 +540,25 @@
<ClInclude Include="include\spdlog\stopwatch.h" />
<ClInclude Include="include\spdlog\tweakme.h" />
<ClInclude Include="include\spdlog\version.h" />
<ClInclude Include="keyvalues.h" />
<ClInclude Include="languagehooks.h" />
<ClInclude Include="latencyflex.h" />
<ClInclude Include="logging.h" />
<ClInclude Include="main.h" />
<ClInclude Include="masterserver.h" />
<ClInclude Include="maxplayers.h" />
<ClInclude Include="memalloc.h" />
<ClInclude Include="miscclientfixes.h" />
<ClInclude Include="misccommands.h" />
<ClInclude Include="miscserverfixes.h" />
<ClInclude Include="modlocalisation.h" />
<ClInclude Include="modmanager.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="pdef.h" />
<ClInclude Include="playlist.h" />
<ClInclude Include="miscserverscript.h" />
<ClInclude Include="rpakfilesystem.h" />
<ClInclude Include="scriptbrowserhooks.h" />
<ClInclude Include="scriptmainmenupromos.h" />
<ClInclude Include="scriptmodmenu.h" />
<ClInclude Include="scriptserverbrowser.h" />
<ClInclude Include="scriptsrson.h" />
<ClInclude Include="serverauthentication.h" />
<ClInclude Include="scriptservertoclientstringcommand.h" />
<ClInclude Include="sigscanning.h" />
<ClInclude Include="serverpresence.h" />
<ClInclude Include="sourceconsole.h" />
<ClInclude Include="sourceinterface.h" />
<ClInclude Include="squirrel.h" />
<ClInclude Include="state.h" />
<ClInclude Include="exploitfixes.h" />
<ClInclude Include="exploitfixes_utf8parser.h" />
<ClInclude Include="nsmem.h" />
<ClInclude Include="exploitfixes_utf8parser.cpp" />
<ClInclude Include="tier0.h" />
<ClInclude Include="vector.h" />
<ClInclude Include="version.h" />
</ItemGroup>
<ItemGroup>
@ -580,25 +572,29 @@
<ClCompile Include="clientruihooks.cpp" />
<ClCompile Include="clientvideooverrides.cpp" />
<ClCompile Include="concommand.cpp" />
<ClCompile Include="exploitfixes_lzss.cpp" />
<ClCompile Include="limits.cpp" />
<ClCompile Include="memory.cpp" />
<ClCompile Include="nsprefix.cpp" />
<ClCompile Include="context.cpp" />
<ClCompile Include="convar.cpp" />
<ClCompile Include="crashhandler.cpp" />
<ClCompile Include="cvar.cpp" />
<ClCompile Include="debugoverlay.cpp" />
<ClCompile Include="dedicated.cpp" />
<ClCompile Include="dedicatedmaterialsystem.cpp" />
<ClCompile Include="demofixes.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="filesystem.cpp" />
<ClCompile Include="gameutils.cpp" />
<ClCompile Include="hooks.cpp" />
<ClCompile Include="hookutils.cpp" />
<ClCompile Include="host.cpp" />
<ClCompile Include="hoststate.cpp" />
<ClCompile Include="keyvalues.cpp" />
<ClCompile Include="latencyflex.cpp" />
<ClCompile Include="localchatwriter.cpp" />
<ClCompile Include="printmaps.cpp" />
<ClCompile Include="maxplayers.cpp" />
<ClCompile Include="languagehooks.cpp" />
<ClCompile Include="memalloc.cpp" />
<ClCompile Include="miscclientfixes.cpp" />
<ClCompile Include="misccommands.cpp" />
<ClCompile Include="miscserverfixes.cpp" />
<ClCompile Include="modlocalisation.cpp" />
@ -612,9 +608,15 @@
<ClCompile Include="pdef.cpp" />
<ClCompile Include="playlist.cpp" />
<ClCompile Include="plugins.cpp" />
<ClCompile Include="printcommands.cpp" />
<ClCompile Include="r2client.cpp" />
<ClCompile Include="r2engine.cpp" />
<ClCompile Include="r2server.cpp" />
<ClCompile Include="rpakfilesystem.cpp" />
<ClCompile Include="runframe.cpp" />
<ClCompile Include="scriptbrowserhooks.cpp" />
<ClCompile Include="scriptjson.cpp" />
<ClCompile Include="scriptdatatables.cpp" />
<ClCompile Include="scriptmainmenupromos.cpp" />
<ClCompile Include="scriptmodmenu.cpp" />
<ClCompile Include="scriptserverbrowser.cpp" />
@ -624,11 +626,12 @@
<ClCompile Include="miscserverscript.cpp" />
<ClCompile Include="serverchathooks.cpp" />
<ClCompile Include="scriptservertoclientstringcommand.cpp" />
<ClCompile Include="sigscanning.cpp" />
<ClCompile Include="serverpresence.cpp" />
<ClCompile Include="sourceconsole.cpp" />
<ClCompile Include="sourceinterface.cpp" />
<ClCompile Include="squirrel.cpp" />
<ClCompile Include="exploitfixes.cpp" />
<ClCompile Include="tier0.cpp" />
<ClCompile Include="version.cpp" />
</ItemGroup>
<ItemGroup>

View File

@ -16,30 +16,12 @@
<Filter Include="Header Files\include">
<UniqueIdentifier>{d4199e4b-10d2-43ce-af9c-e1fa79e1e64e}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Shared">
<UniqueIdentifier>{4d322431-dcaa-4f75-aee0-3b6371cf52a6}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Shared\Hooks">
<UniqueIdentifier>{94259c8c-5411-48bf-af4f-46ca32b7d0bb}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared">
<UniqueIdentifier>{4f525372-34a8-40b3-8a95-81d77cdfcf7f}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Dedicated">
<UniqueIdentifier>{8b8ed12a-9269-4dc3-b932-0daefdf6a388}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Client">
<UniqueIdentifier>{b6f79919-9735-476d-8798-067a75cbeca0}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Client">
<UniqueIdentifier>{ca657be5-c2d8-4322-a689-1154aaafe57b}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared\Convar">
<UniqueIdentifier>{a18afb37-5fdd-4340-a6b4-a6541593e398}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Shared\Convar">
<UniqueIdentifier>{9751b551-5886-45d4-a039-cbd10445263d}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\include\spdlog">
<UniqueIdentifier>{8596cc1c-0492-4467-91e3-1f03b7e19f77}</UniqueIdentifier>
</Filter>
@ -58,12 +40,6 @@
<Filter Include="Header Files\include\spdlog\details">
<UniqueIdentifier>{74567974-c66b-45ef-ab28-97b7154ca224}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared\Mods">
<UniqueIdentifier>{3e892d07-2239-44da-9cf3-c288a34cf9a2}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Shared\Mods">
<UniqueIdentifier>{6bbce8a5-38b4-4763-a7cb-4e98012ec245}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\include\rapidjson">
<UniqueIdentifier>{4ca5392e-7d3d-4066-833f-f534cd5787c3}</UniqueIdentifier>
</Filter>
@ -76,15 +52,6 @@
<Filter Include="Header Files\include\rapidjson\msinttypes">
<UniqueIdentifier>{85aacdee-0f92-4ec4-b20c-0739c1175055}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared\Hooks">
<UniqueIdentifier>{4db0d1e9-9035-457f-87f1-5dc3f13b6b9e}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Shared\Mods\Compiled">
<UniqueIdentifier>{d1f93d1e-0ecb-44fe-a277-d3e75aec2570}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared\Mods\Compiled">
<UniqueIdentifier>{14fc0931-acad-46ec-a55e-94f4469d4235}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Server">
<UniqueIdentifier>{3d41d3fc-8a3b-4358-b3e8-4f06dc96abfe}</UniqueIdentifier>
</Filter>
@ -97,12 +64,6 @@
<Filter Include="Header Files\Server\Authentication">
<UniqueIdentifier>{24fd0855-9288-4129-93ba-c6cafdc98d1b}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared\Game Functions">
<UniqueIdentifier>{2cbddb28-0b17-4881-847d-8773da52b268}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Shared\Game Functions">
<UniqueIdentifier>{0c93d909-e0d6-4c35-a8a4-a13f681a1012}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\include\openssl">
<UniqueIdentifier>{4cb0dd89-5f16-4549-a864-34ca3075352a}</UniqueIdentifier>
</Filter>
@ -118,23 +79,83 @@
<Filter Include="Header Files\include\libcurl">
<UniqueIdentifier>{ea1e17a6-40b7-4e1b-8edb-e9ae704ce604}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared\Math">
<UniqueIdentifier>{59b0f68f-daa7-4641-b6fa-8464b56da2bb}</UniqueIdentifier>
<Filter Include="Source Files\Client\Scripted">
<UniqueIdentifier>{51910ba0-2ff8-461d-9f67-8d7907b57d22}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Shared\Math">
<UniqueIdentifier>{44a83740-9d70-480d-9a7a-43b81f8eab9e}</UniqueIdentifier>
<Filter Include="Source Files\Server\Scripted">
<UniqueIdentifier>{325e0d7d-6832-496d-8d8e-968fdfa5dd40}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared\Exploit Fixes">
<Filter Include="Header Files\Server\Scripted">
<UniqueIdentifier>{802d0771-62f1-4733-89f9-57a4d8864b8d}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Console">
<UniqueIdentifier>{04fd662a-6e70-494c-b720-c694a5cc2fb1}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Convar">
<UniqueIdentifier>{a18afb37-5fdd-4340-a6b4-a6541593e398}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Exploit Fixes">
<UniqueIdentifier>{4a8a695a-a103-4b1f-b314-0ec19a253119}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Shared\Exploit Fixes\UTF8Parser">
<UniqueIdentifier>{b30e08b1-b962-4264-8cbb-a0a31924b93e}</UniqueIdentifier>
<Filter Include="Source Files\Filesystem">
<UniqueIdentifier>{d8a83b5e-9a23-4124-824f-eab37880cb08}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Dedicated">
<Filter Include="Source Files\Game Functions">
<UniqueIdentifier>{2cbddb28-0b17-4881-847d-8773da52b268}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Hooks">
<UniqueIdentifier>{4db0d1e9-9035-457f-87f1-5dc3f13b6b9e}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Math">
<UniqueIdentifier>{59b0f68f-daa7-4641-b6fa-8464b56da2bb}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Mods">
<UniqueIdentifier>{3e892d07-2239-44da-9cf3-c288a34cf9a2}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Mods\Compiled Assets">
<UniqueIdentifier>{14fc0931-acad-46ec-a55e-94f4469d4235}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Dedicated Server">
<UniqueIdentifier>{947835db-67d6-42c0-870d-62743f85231f}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Shared\ExploitFixes">
<UniqueIdentifier>{7f609cee-d2c0-46a2-b06e-83b9f0511915}</UniqueIdentifier>
<Filter Include="Header Files\Console">
<UniqueIdentifier>{bf0769d8-40fd-4701-85e9-7ed94aab2283}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Convar">
<UniqueIdentifier>{9751b551-5886-45d4-a039-cbd10445263d}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Exploit Fixes">
<UniqueIdentifier>{96101d42-72af-4fd1-8559-8d1d1ff66240}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Filesystem">
<UniqueIdentifier>{ee3ba13a-3061-41d7-981d-328ac2596fd2}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Game Functions">
<UniqueIdentifier>{0c93d909-e0d6-4c35-a8a4-a13f681a1012}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Hooks">
<UniqueIdentifier>{94259c8c-5411-48bf-af4f-46ca32b7d0bb}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Math">
<UniqueIdentifier>{44a83740-9d70-480d-9a7a-43b81f8eab9e}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Mods">
<UniqueIdentifier>{6bbce8a5-38b4-4763-a7cb-4e98012ec245}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Mods\Compiled Assets">
<UniqueIdentifier>{826d5193-3ad0-434b-ba7c-dd24ed4bbd0c}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Dedicated Server">
<UniqueIdentifier>{0f1ba4c4-78ee-4b05-afa5-6f598063f5c1}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Squirrel">
<UniqueIdentifier>{ca669b16-b8bb-4654-993f-fffa44c914f1}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Squirrel">
<UniqueIdentifier>{26365f16-ff52-4e80-a01b-2ca020376c93}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Scripted">
<UniqueIdentifier>{7263403a-7550-4aa2-a724-f622ab200eed}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
@ -144,39 +165,12 @@
<ClInclude Include="include\MinHook.h">
<Filter>Header Files\include</Filter>
</ClInclude>
<ClInclude Include="hooks.h">
<Filter>Header Files\Shared\Hooks</Filter>
</ClInclude>
<ClInclude Include="hookutils.h">
<Filter>Header Files\Shared\Hooks</Filter>
</ClInclude>
<ClInclude Include="main.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dedicated.h">
<Filter>Header Files\Dedicated</Filter>
</ClInclude>
<ClInclude Include="sourceconsole.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="squirrel.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="sigscanning.h">
<Filter>Header Files\Shared\Hooks</Filter>
</ClInclude>
<ClInclude Include="logging.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="context.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="sourceinterface.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="concommand.h">
<Filter>Header Files\Shared\Convar</Filter>
</ClInclude>
<ClInclude Include="include\spdlog\async.h">
<Filter>Header Files\include\spdlog</Filter>
</ClInclude>
@ -447,12 +441,6 @@
<ClInclude Include="include\spdlog\details\windows_include.h">
<Filter>Header Files\include\spdlog\details</Filter>
</ClInclude>
<ClInclude Include="convar.h">
<Filter>Header Files\Shared\Convar</Filter>
</ClInclude>
<ClInclude Include="modmanager.h">
<Filter>Header Files\Shared\Mods</Filter>
</ClInclude>
<ClInclude Include="include\rapidjson\allocators.h">
<Filter>Header Files\include\rapidjson</Filter>
</ClInclude>
@ -558,69 +546,12 @@
<ClInclude Include="include\rapidjson\msinttypes\stdint.h">
<Filter>Header Files\include\rapidjson\msinttypes</Filter>
</ClInclude>
<ClInclude Include="filesystem.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="scriptsrson.h">
<Filter>Header Files\Shared\Mods\Compiled</Filter>
</ClInclude>
<ClInclude Include="serverauthentication.h">
<Filter>Header Files\Server\Authentication</Filter>
</ClInclude>
<ClInclude Include="scriptmodmenu.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="scriptserverbrowser.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="keyvalues.h">
<Filter>Header Files\Shared\Mods\Compiled</Filter>
</ClInclude>
<ClInclude Include="include\httplib.h">
<Filter>Header Files\include</Filter>
</ClInclude>
<ClInclude Include="masterserver.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="chatcommand.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="modlocalisation.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="playlist.h">
<Filter>Header Files\Server</Filter>
</ClInclude>
<ClInclude Include="dedicatedmaterialsystem.h">
<Filter>Header Files\Dedicated</Filter>
</ClInclude>
<ClInclude Include="misccommands.h">
<Filter>Header Files\Shared\Convar</Filter>
</ClInclude>
<ClInclude Include="miscserverscript.h">
<Filter>Header Files\Server</Filter>
</ClInclude>
<ClInclude Include="pdef.h">
<Filter>Header Files\Shared\Mods\Compiled</Filter>
</ClInclude>
<ClInclude Include="clientauthhooks.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="scriptbrowserhooks.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="gameutils.h">
<Filter>Header Files\Shared\Game Functions</Filter>
</ClInclude>
<ClInclude Include="memalloc.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="scriptmainmenupromos.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="miscclientfixes.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="include\openssl\__DECC_INCLUDE_EPILOGUE.H">
<Filter>Header Files\include\openssl\openssl</Filter>
</ClInclude>
@ -1401,12 +1332,6 @@
<ClInclude Include="include\internal\unicode.h">
<Filter>Header Files\include\openssl\internal</Filter>
</ClInclude>
<ClInclude Include="miscserverfixes.h">
<Filter>Header Files\Server</Filter>
</ClInclude>
<ClInclude Include="maxplayers.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="include\libcurl\include\curl\curl.h">
<Filter>Header Files\include\libcurl</Filter>
</ClInclude>
@ -1437,157 +1362,160 @@
<ClInclude Include="include\libcurl\include\curl\urlapi.h">
<Filter>Header Files\include\libcurl</Filter>
</ClInclude>
<ClInclude Include="rpakfilesystem.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="bansystem.h">
<Filter>Header Files\Server\Authentication</Filter>
</ClInclude>
<ClInclude Include="languagehooks.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="latencyflex.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="audio.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="buildainfile.h">
<Filter>Header Files\Server</Filter>
</ClInclude>
<ClInclude Include="bitbuf.h">
<Filter>Header Files\Shared</Filter>
</ClInclude>
<ClInclude Include="nsprefix.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="cvar.h">
<Filter>Header Files\Shared\Convar</Filter>
</ClInclude>
<ClInclude Include="color.h">
<Filter>Header Files\Shared\Math</Filter>
</ClInclude>
<ClInclude Include="bits.h">
<Filter>Header Files\Shared\Math</Filter>
</ClInclude>
<ClInclude Include="serverchathooks.h">
<Filter>Header Files\Server</Filter>
</ClInclude>
<ClInclude Include="clientchathooks.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="localchatwriter.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="scriptservertoclientstringcommand.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="plugins.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="state.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="plugin_abi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="debugoverlay.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="clientvideooverrides.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="exploitfixes_utf8parser.h">
<Filter>Source Files\Shared\Exploit Fixes\UTF8Parser</Filter>
</ClInclude>
<ClInclude Include="version.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="clientruihooks.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
<ClInclude Include="ns_version.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="exploitfixes.h">
<Filter>Header Files\Shared\ExploitFixes</Filter>
<ClInclude Include="serverchathooks.h">
<Filter>Header Files\Server\Scripted</Filter>
</ClInclude>
<ClInclude Include="nsmem.h">
<Filter>Header Files\Shared\ExploitFixes</Filter>
<ClInclude Include="dedicated.h">
<Filter>Header Files\Dedicated Server</Filter>
</ClInclude>
<ClInclude Include="scriptutility.h">
<Filter>Header Files\Shared</Filter>
<ClInclude Include="nsprefix.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="scriptjson.h">
<Filter>Header Files\Shared</Filter>
<ClInclude Include="exploitfixes_utf8parser.cpp">
<Filter>Source Files\Exploit Fixes</Filter>
</ClInclude>
<ClInclude Include="crashhandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="hoststate.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="masterserver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="memalloc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="playlist.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="sourceinterface.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bits.h">
<Filter>Header Files\Math</Filter>
</ClInclude>
<ClInclude Include="bitbuf.h">
<Filter>Header Files\Math</Filter>
</ClInclude>
<ClInclude Include="convar.h">
<Filter>Header Files\Convar</Filter>
</ClInclude>
<ClInclude Include="concommand.h">
<Filter>Header Files\Convar</Filter>
</ClInclude>
<ClInclude Include="cvar.h">
<Filter>Header Files\Convar</Filter>
</ClInclude>
<ClInclude Include="filesystem.h">
<Filter>Header Files\Filesystem</Filter>
</ClInclude>
<ClInclude Include="hooks.h">
<Filter>Header Files\Hooks</Filter>
</ClInclude>
<ClInclude Include="limits.h">
<Filter>Header Files\Exploit Fixes</Filter>
</ClInclude>
<ClInclude Include="logging.h">
<Filter>Header Files\Console</Filter>
</ClInclude>
<ClInclude Include="misccommands.h">
<Filter>Header Files\Convar</Filter>
</ClInclude>
<ClInclude Include="modmanager.h">
<Filter>Header Files\Mods</Filter>
</ClInclude>
<ClInclude Include="pdef.h">
<Filter>Header Files\Mods\Compiled Assets</Filter>
</ClInclude>
<ClInclude Include="printcommand.h">
<Filter>Header Files\Console</Filter>
</ClInclude>
<ClInclude Include="printmaps.h">
<Filter>Header Files\Console</Filter>
</ClInclude>
<ClInclude Include="r2client.h">
<Filter>Header Files\Game Functions</Filter>
</ClInclude>
<ClInclude Include="r2engine.h">
<Filter>Header Files\Game Functions</Filter>
</ClInclude>
<ClInclude Include="r2server.h">
<Filter>Header Files\Game Functions</Filter>
</ClInclude>
<ClInclude Include="scriptsrson.h">
<Filter>Header Files\Mods\Compiled Assets</Filter>
</ClInclude>
<ClInclude Include="tier0.h">
<Filter>Header Files\Game Functions</Filter>
</ClInclude>
<ClInclude Include="rpakfilesystem.h">
<Filter>Header Files\Filesystem</Filter>
</ClInclude>
<ClInclude Include="color.h">
<Filter>Header Files\Math</Filter>
</ClInclude>
<ClInclude Include="serverpresence.h">
<Filter>Header Files\Server</Filter>
</ClInclude>
<ClInclude Include="memory.h">
<Filter>Header Files\Hooks</Filter>
</ClInclude>
<ClInclude Include="maxplayers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="squirrel.h">
<Filter>Header Files\Squirrel</Filter>
</ClInclude>
<ClInclude Include="squirreldatatypes.h">
<Filter>Header Files\Squirrel</Filter>
</ClInclude>
<ClInclude Include="vector.h">
<Filter>Header Files\Math</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="hooks.cpp">
<Filter>Source Files\Shared\Hooks</Filter>
</ClCompile>
<ClCompile Include="hookutils.cpp">
<Filter>Source Files\Shared\Hooks</Filter>
</ClCompile>
<ClCompile Include="dedicated.cpp">
<Filter>Source Files\Server\Dedicated</Filter>
<Filter>Source Files\Dedicated Server</Filter>
</ClCompile>
<ClCompile Include="sourceconsole.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="squirrel.cpp">
<Filter>Source Files\Shared</Filter>
</ClCompile>
<ClCompile Include="sigscanning.cpp">
<Filter>Source Files\Shared\Hooks</Filter>
</ClCompile>
<ClCompile Include="logging.cpp">
<Filter>Source Files\Shared</Filter>
</ClCompile>
<ClCompile Include="context.cpp">
<Filter>Source Files\Shared</Filter>
</ClCompile>
<ClCompile Include="sourceinterface.cpp">
<Filter>Source Files\Shared</Filter>
</ClCompile>
<ClCompile Include="convar.cpp">
<Filter>Source Files\Shared\Convar</Filter>
</ClCompile>
<ClCompile Include="concommand.cpp">
<Filter>Source Files\Shared\Convar</Filter>
</ClCompile>
<ClCompile Include="modmanager.cpp">
<Filter>Source Files\Shared\Mods</Filter>
</ClCompile>
<ClCompile Include="filesystem.cpp">
<Filter>Source Files\Shared</Filter>
<Filter>Source Files\Mods</Filter>
</ClCompile>
<ClCompile Include="scriptsrson.cpp">
<Filter>Source Files\Shared\Mods\Compiled</Filter>
<Filter>Source Files\Mods\Compiled Assets</Filter>
</ClCompile>
<ClCompile Include="serverauthentication.cpp">
<Filter>Source Files\Server\Authentication</Filter>
</ClCompile>
<ClCompile Include="scriptmodmenu.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="scriptserverbrowser.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="keyvalues.cpp">
<Filter>Source Files\Shared\Mods\Compiled</Filter>
</ClCompile>
<ClCompile Include="masterserver.cpp">
<Filter>Source Files\Shared</Filter>
</ClCompile>
<ClCompile Include="gameutils.cpp">
<Filter>Source Files\Shared\Game Functions</Filter>
<Filter>Source Files\Mods\Compiled Assets</Filter>
</ClCompile>
<ClCompile Include="chatcommand.cpp">
<Filter>Source Files\Client</Filter>
@ -1595,45 +1523,18 @@
<ClCompile Include="modlocalisation.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="playlist.cpp">
<Filter>Source Files\Server</Filter>
</ClCompile>
<ClCompile Include="dedicatedmaterialsystem.cpp">
<Filter>Source Files\Server\Dedicated</Filter>
</ClCompile>
<ClCompile Include="misccommands.cpp">
<Filter>Source Files\Shared\Convar</Filter>
</ClCompile>
<ClCompile Include="miscserverscript.cpp">
<Filter>Source Files\Server</Filter>
<Filter>Source Files\Dedicated Server</Filter>
</ClCompile>
<ClCompile Include="pdef.cpp">
<Filter>Source Files\Shared\Mods\Compiled</Filter>
<Filter>Source Files\Mods\Compiled Assets</Filter>
</ClCompile>
<ClCompile Include="clientauthhooks.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="scriptbrowserhooks.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="memalloc.cpp">
<Filter>Source Files\Shared</Filter>
</ClCompile>
<ClCompile Include="scriptmainmenupromos.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="miscclientfixes.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="maxplayers.cpp">
<Filter>Source Files\Shared</Filter>
</ClCompile>
<ClCompile Include="miscserverfixes.cpp">
<Filter>Source Files\Server</Filter>
</ClCompile>
<ClCompile Include="rpakfilesystem.cpp">
<Filter>Source Files\Shared</Filter>
</ClCompile>
<ClCompile Include="bansystem.cpp">
<Filter>Source Files\Server\Authentication</Filter>
</ClCompile>
@ -1649,27 +1550,9 @@
<ClCompile Include="buildainfile.cpp">
<Filter>Source Files\Server</Filter>
</ClCompile>
<ClCompile Include="nsprefix.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="cvar.cpp">
<Filter>Source Files\Shared\Convar</Filter>
</ClCompile>
<ClCompile Include="bits.cpp">
<Filter>Source Files\Shared\Math</Filter>
</ClCompile>
<ClCompile Include="serverchathooks.cpp">
<Filter>Source Files\Server</Filter>
</ClCompile>
<ClCompile Include="clientchathooks.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="localchatwriter.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="scriptservertoclientstringcommand.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="plugins.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -1679,20 +1562,143 @@
<ClCompile Include="clientvideooverrides.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="exploitfixes.cpp">
<Filter>Source Files\Shared\Exploit Fixes</Filter>
</ClCompile>
<ClCompile Include="version.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="clientruihooks.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="scriptjson.cpp">
<Filter>Source Files\Shared</Filter>
<ClCompile Include="scriptmainmenupromos.cpp">
<Filter>Source Files\Client\Scripted</Filter>
</ClCompile>
<ClCompile Include="clientchathooks.cpp">
<Filter>Source Files\Client\Scripted</Filter>
</ClCompile>
<ClCompile Include="scriptmodmenu.cpp">
<Filter>Source Files\Client\Scripted</Filter>
</ClCompile>
<ClCompile Include="scriptservertoclientstringcommand.cpp">
<Filter>Source Files\Client\Scripted</Filter>
</ClCompile>
<ClCompile Include="scriptserverbrowser.cpp">
<Filter>Source Files\Client\Scripted</Filter>
</ClCompile>
<ClCompile Include="scriptbrowserhooks.cpp">
<Filter>Source Files\Client\Scripted</Filter>
</ClCompile>
<ClCompile Include="serverchathooks.cpp">
<Filter>Source Files\Server\Scripted</Filter>
</ClCompile>
<ClCompile Include="miscserverscript.cpp">
<Filter>Source Files\Server\Scripted</Filter>
</ClCompile>
<ClCompile Include="demofixes.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
<ClCompile Include="nsprefix.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="crashhandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="host.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="hoststate.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="masterserver.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="maxplayers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="memalloc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="playlist.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="sourceinterface.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="r2server.cpp">
<Filter>Source Files\Game Functions</Filter>
</ClCompile>
<ClCompile Include="r2client.cpp">
<Filter>Source Files\Game Functions</Filter>
</ClCompile>
<ClCompile Include="r2engine.cpp">
<Filter>Source Files\Game Functions</Filter>
</ClCompile>
<ClCompile Include="rpakfilesystem.cpp">
<Filter>Source Files\Filesystem</Filter>
</ClCompile>
<ClCompile Include="filesystem.cpp">
<Filter>Source Files\Filesystem</Filter>
</ClCompile>
<ClCompile Include="exploitfixes.cpp">
<Filter>Source Files\Exploit Fixes</Filter>
</ClCompile>
<ClCompile Include="limits.cpp">
<Filter>Source Files\Exploit Fixes</Filter>
</ClCompile>
<ClCompile Include="hooks.cpp">
<Filter>Source Files\Hooks</Filter>
</ClCompile>
<ClCompile Include="bits.cpp">
<Filter>Source Files\Math</Filter>
</ClCompile>
<ClCompile Include="convar.cpp">
<Filter>Source Files\Convar</Filter>
</ClCompile>
<ClCompile Include="concommand.cpp">
<Filter>Source Files\Convar</Filter>
</ClCompile>
<ClCompile Include="printcommands.cpp">
<Filter>Source Files\Console</Filter>
</ClCompile>
<ClCompile Include="printmaps.cpp">
<Filter>Source Files\Console</Filter>
</ClCompile>
<ClCompile Include="cvar.cpp">
<Filter>Source Files\Convar</Filter>
</ClCompile>
<ClCompile Include="misccommands.cpp">
<Filter>Source Files\Convar</Filter>
</ClCompile>
<ClCompile Include="tier0.cpp">
<Filter>Source Files\Game Functions</Filter>
</ClCompile>
<ClCompile Include="logging.cpp">
<Filter>Source Files\Console</Filter>
</ClCompile>
<ClCompile Include="serverpresence.cpp">
<Filter>Source Files\Server</Filter>
</ClCompile>
<ClCompile Include="runframe.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="memory.cpp">
<Filter>Source Files\Hooks</Filter>
</ClCompile>
<ClCompile Include="exploitfixes_lzss.cpp">
<Filter>Source Files\Exploit Fixes</Filter>
</ClCompile>
<ClCompile Include="scriptutility.cpp">
<Filter>Source Files\Shared</Filter>
<Filter>Source Files\Scripted</Filter>
</ClCompile>
<ClCompile Include="scriptjson.cpp">
<Filter>Source Files\Scripted</Filter>
</ClCompile>
<ClCompile Include="squirrel.cpp">
<Filter>Source Files\Squirrel</Filter>
</ClCompile>
<ClCompile Include="scriptdatatables.cpp">
<Filter>Source Files\Scripted</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>

View File

@ -1,13 +1,15 @@
#include "pch.h"
#include "audio.h"
#include "dedicated.h"
#include "convar.h"
#include "rapidjson/error/en.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <random>
#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<char*>(&data[sizeof(EMPTY_WAVE)]), fileSize - sizeof(EMPTY_WAVE));
wavStream.seekg(0, std::ios::beg);
wavStream.read(reinterpret_cast<char*>(data), sizeof(EMPTY_WAVE));
wavStream.read(reinterpret_cast<char*>(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 <typename Iter, typename RandomGenerator> 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_ptr<EventOver
return true; // good to go
}
// forward declare
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
uintptr_t parentEvent, void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType);
// 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.
// clang-format off
AUTOHOOK(LoadSampleMetadata, mileswin64.dll + 0xF110,
bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType))
// clang-format on
{
uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent();
// Raw source, used for voice data only
if (audioType == 0)
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType);
}
// DO NOT INLINE THIS FUNCTION
// See comment below.
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
@ -398,7 +416,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
if (!overrideData)
// not found either
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
else
{
// cache found pattern to improve performance
@ -412,7 +430,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
overrideData = iter->second;
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))
{
AUTOHOOK_DISPATCH()
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");
if (!milesAudioBase)
return spdlog::error("miles audio not found :terror:");
HookEnabler hook;
ENABLER_CREATEHOOK(
hook, (char*)milesAudioBase + 0xF110, &LoadSampleMetadata_Hook, reinterpret_cast<LPVOID*>(&LoadSampleMetadata_Original));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x57DAD0, &MilesLog_Hook, reinterpret_cast<LPVOID*>(&MilesLog_Original));
MilesStopAll = (MilesStopAll_Type)((char*)baseAddress + 0x580850);
MilesStopAll = module.Offset(0x580850).As<MilesStopAll_Type>();
}

View File

@ -5,8 +5,6 @@
#include <regex>
#include <shared_mutex>
namespace fs = std::filesystem;
enum class AudioSelectionStrategy
{
INVALID = -1,
@ -46,5 +44,3 @@ class CustomAudioManager
};
extern CustomAudioManager g_CustomAudioManager;
void InitialiseMilesAudioHooks(HMODULE baseAddress);

View File

@ -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 <filesystem>
#include "r2server.h"
#include "r2engine.h"
#include "nsprefix.h"
#include <ctime>
#include <filesystem>
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);
}

View File

@ -16,6 +16,4 @@ class ServerBanSystem
bool IsUIDAllowed(uint64_t uid);
};
extern ServerBanSystem* g_ServerBanSystem;
void InitialiseBanSystem(HMODULE baseAddress);
extern ServerBanSystem* g_pBanSystem;

View File

@ -1,19 +1,19 @@
#include "pch.h"
#include "buildainfile.h"
#include "convar.h"
#include "hookutils.h"
#include "hoststate.h"
#include "r2engine.h"
#include <fstream>
#include <filesystem>
#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<LPVOID*>(&CAI_NetworkBuilder__Build));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x3933A0, &LoadAINFileHook, reinterpret_cast<LPVOID*>(&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<int*>();
pppUnkNodeStruct0s = module.Offset(0x1063BE0).As<UnkNodeStruct0***>();
pUnkLinkStruct1Count = module.Offset(0x1063AA8).As<int*>();
pppUnkStruct1s = module.Offset(0x1063A90).As<UnkLinkStruct1***>();
pUnkServerMapversionGlobal = module.Offset(0xBFBE08).As<char**>();
}

View File

@ -1,3 +0,0 @@
#pragma once
void InitialiseBuildAINFileHooks(HMODULE baseAddress);

View File

@ -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<void(__fastcall*)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat)>();
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);

View File

@ -1,3 +0,0 @@
#pragma once
void InitialiseChatCommands(HMODULE baseAddress);

View File

@ -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<char*>();
// 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<LPVOID*>(&AuthWithStryder));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x183760, &Auth3PTokenHook, reinterpret_cast<LPVOID*>(&Auth3PToken));
token_location = (char*)baseAddress + 0x13979D80;
}

View File

@ -1,2 +0,0 @@
#pragma once
void InitialiseClientAuthHooks(HMODULE baseAddress);

View File

@ -1,29 +1,22 @@
#include "pch.h"
#include "clientchathooks.h"
#include <rapidjson/document.h>
#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 <rapidjson/document.h>
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<ScriptContext::CLIENT>->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<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, (int)senderId - 1);
g_pSquirrel<ScriptContext::CLIENT>->pushstring(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, payload);
g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isTeam);
g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isDead);
g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, type);
g_pSquirrel<ScriptContext::CLIENT>->call(g_pSquirrel<ScriptContext::CLIENT>->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<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = g_pSquirrel<ScriptContext::CLIENT>->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<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = g_pSquirrel<ScriptContext::CLIENT>->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<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = g_pSquirrel<ScriptContext::CLIENT>->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<LPVOID*>(&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<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw);
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine);
}

View File

@ -1,5 +0,0 @@
#pragma once
#include "pch.h"
#include "serverchathooks.h"
void InitialiseClientChatHooks(HMODULE baseAddress);

View File

@ -1,13 +1,14 @@
#include "pch.h"
#include "clientruihooks.h"
#include "convar.h"
AUTOHOOK_INIT()
ConVar* Cvar_rui_drawEnable;
typedef char (*DrawRUIFuncType)(void* a1, float* a2);
DrawRUIFuncType DrawRUIFunc;
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<LPVOID*>(&DrawRUIFunc));
Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn");
}

View File

@ -1,2 +0,0 @@
#pragma once
void InitialiseEngineClientRUIHooks(HMODULE baseAddress);

View File

@ -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<void*>(GetProcAddress(GetModuleHandleA("bink2w64.dll"), "BinkOpen")),
&BinkOpenHook,
reinterpret_cast<LPVOID*>(&BinkOpen));
module.Offset(0x459AD).NOP(6);
}

View File

@ -1,2 +0,0 @@
#pragma once
void InitialiseEngineClientVideoOverrides(HMODULE baseAddress);

View File

@ -1,28 +1,9 @@
#include "pch.h"
#include "concommand.h"
#include "gameutils.h"
#include "misccommands.h"
#include <iostream>
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();
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if this is a command
// Output : bool
@ -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<ConCommandConstructorType>();
AddMiscConCommands();
}

View File

@ -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);

View File

@ -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 "";
}

View File

@ -1,11 +0,0 @@
#pragma once
enum class ScriptContext : int
{
SERVER,
CLIENT,
UI,
NONE
};
const char* GetContextName(ScriptContext context);

View File

@ -1,13 +1,11 @@
#include <float.h>
#include "pch.h"
#include "bits.h"
#include "cvar.h"
#include "convar.h"
#include "hookutils.h"
#include "gameutils.h"
#include "sourceinterface.h"
#include <float.h>
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);
conVarMalloc = module.Offset(0x415C20).As<ConVarMallocType>();
conVarRegister = module.Offset(0x417230).As<ConVarRegisterType>();
g_pConVar_Vtable = (char*)baseAddress + 0x67FD28;
g_pIConVar_Vtable = (char*)baseAddress + 0x67FDC8;
g_pConVar_Vtable = module.Offset(0x67FD28);
g_pIConVar_Vtable = module.Offset(0x67FDC8);
g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007");
g_pCVar = *g_pCVarInterface;
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x417FA0, &ConVar::IsFlagSet, reinterpret_cast<LPVOID*>(&CvarIsFlagSet));
R2::g_pCVarInterface = new SourceInterface<CCvar>("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;
}
//-----------------------------------------------------------------------------

View File

@ -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<int, const char*> 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);

View File

@ -0,0 +1,216 @@
#include "pch.h"
#include "crashhandler.h"
#include "dedicated.h"
#include "nsprefix.h"
#include <minidumpapiset.h>
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<LPCSTR>(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<LPCSTR>(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(&currentTime, (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);
}

View File

@ -0,0 +1,4 @@
#pragma once
void InitialiseCrashHandler();
void RemoveCrashHandler();

View File

@ -23,5 +23,9 @@ std::unordered_map<std::string, ConCommandBase*> CCvar::DumpToMap()
return allConVars;
}
// use the R2 namespace for game funcs
namespace R2
{
SourceInterface<CCvar>* g_pCVarInterface;
CCvar* g_pCVar;
} // namespace R2

View File

@ -35,5 +35,9 @@ class CCvar
std::unordered_map<std::string, ConCommandBase*> DumpToMap();
};
// use the R2 namespace for game funcs
namespace R2
{
extern SourceInterface<CCvar>* g_pCVarInterface;
extern CCvar* g_pCVar;
} // namespace R2

View File

@ -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;
AUTOHOOK_DISPATCH()
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xABCB0, &DrawOverlayHook, reinterpret_cast<LPVOID*>(&DrawOverlay));
RenderLine = reinterpret_cast<RenderLineType>((char*)baseAddress + 0x192A70);
RenderBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x192520);
RenderWireframeBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x193DA0);
sEngineModule = baseAddress;
RenderLine = module.Offset(0x192A70).As<RenderLineType>();
RenderBox = module.Offset(0x192520).As<RenderBoxType>();
RenderWireframeBox = module.Offset(0x193DA0).As<RenderBoxType>();
sEngineModule = reinterpret_cast<HMODULE>(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<ConVar*>();
Cvar_enable_debug_overlays->SetValue(false);
Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0";
Cvar_enable_debug_overlays->AddFlags(FCVAR_CHEAT);

View File

@ -1,3 +0,0 @@
#pragma once
void InitialiseDebugOverlay(HMODULE baseAddress);

View File

@ -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)
std::this_thread::sleep_for(std::chrono::duration<double, std::ratio<1>>(
Cvar_base_tickinterval_mp->GetFloat() - fmin(Tier0::Plat_FloatTime() - frameStart, 0.25)));
}
}
// use server presence to update window title
class DedicatedConsoleServerPresence : public ServerPresenceReporter
{
void ReportPresence(const ServerPresence* pServerPresence) override
{
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())
pServerPresence->m_sServerName,
pServerPresence->m_MapName,
pServerPresence->m_iPlayerCount,
pServerPresence->m_iMaxPlayers,
pServerPresence->m_PlaylistName)
.c_str());
}
std::this_thread::sleep_for(std::chrono::duration<double, std::ratio<1>>(
Cvar_base_tickinterval_mp->GetFloat() - fmin(Plat_FloatTime() - frameStart, 0.25)));
}
}
typedef bool (*IsGameActiveWindowType)();
IsGameActiveWindowType IsGameActiveWindow;
bool IsGameActiveWindowHook()
{
return true;
}
};
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);
}
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);
}
module.Offset(0x156A63).NOP(5);
{
// runframeserver
// nop some access violations
NSMem::NOP(ea + 0x159819, 17);
}
module.Offset(0x159819).NOP(17);
{
NSMem::NOP(ea + 0x156B4C, 7);
module.Offset(0x156B4C).NOP(7);
// 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);
NSMem::NOP(ea + 0x156B4C + 15, 9);
}
{
// HostState_State_NewGame
// nop an access violation
NSMem::NOP(ea + 0xB934C, 9);
}
module.Offset(0xB934C).NOP(9);
{
// CEngineAPI::Connect
// remove call to Shader_Connect
NSMem::NOP(ea + 0x1C4D7D, 5);
}
module.Offset(0x1C4D7D).NOP(5);
// 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);
//}
{
// Host_Init
// remove call to ui loading stuff
NSMem::NOP(ea + 0x156595, 5);
}
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);
}
module.Offset(0x15A0BB).NOP(5);
{
// RunFrameServer
// nop a function that access violations
NSMem::NOP(ea + 0x159BF3, 5);
}
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
});
}
module.Offset(0x183B70).Patch("B0 01 C3"); // mov al,01 ret
{
// HostState_State_ChangeLevel
// nop clientinterface call
NSMem::NOP(ea + 0x1552ED, 16);
}
module.Offset(0x1552ED).NOP(16);
{
// HostState_State_ChangeLevel
// nop clientinterface call
NSMem::NOP(ea + 0x155363, 16);
}
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);
}
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<LPVOID*>(&IsGameActiveWindow));
*module.Offset(0x13F0B668).As<CDedicatedExports**>() = 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<LPVOID*>(&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
}
}

View File

@ -1,7 +1,3 @@
#pragma once
bool IsDedicatedServer();
void InitialiseDedicated(HMODULE moduleAddress);
void InitialiseDedicatedOrigin(HMODULE baseAddress);
void InitialiseDedicatedServerGameDLL(HMODULE baseAddress);

View File

@ -1,11 +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)(
AUTOHOOK_INIT()
// clang-format off
AUTOHOOK(D3D11CreateDevice, materialsystem_dx11.dll + 0xD9A0E,
HRESULT, __stdcall, (
void* pAdapter,
int DriverType,
HMODULE Software,
@ -15,108 +16,26 @@ typedef HRESULT (*__stdcall D3D11CreateDeviceType)(
UINT SDKVersion,
void** ppDevice,
int* pFeatureLevel,
void** ppImmediateContext);
D3D11CreateDeviceType D3D11CreateDevice;
HRESULT __stdcall D3D11CreateDeviceHook(
void* pAdapter,
int DriverType,
HMODULE Software,
UINT Flags,
int* pFeatureLevels,
UINT FeatureLevels,
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)
ON_DLL_LOAD_DEDI("materialsystem_dx11.dll", DedicatedServerMaterialSystem, (CModule module))
{
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xD9A0E, &D3D11CreateDeviceHook, reinterpret_cast<LPVOID*>(&D3D11CreateDevice));
AUTOHOOK_DISPATCH()
// 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)
{
baseAddress = GetModuleHandleA("rtech_game.dll");
HookEnabler hook;
// unfortunately this is unstable, seems to freeze when changing maps
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB0F0, &PakLoadAPI__LoadRpakHook, reinterpret_cast<LPVOID*>(&PakLoadAPI__LoadRpak));
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB170, &PakLoadAPI__LoadRpak2Hook, reinterpret_cast<LPVOID*>(&PakLoadAPI__LoadRpak2));
module.Offset(0x5F0F1).Patch("E9 34 03 00");
}

View File

@ -1,3 +0,0 @@
#pragma once
void InitialiseDedicatedMaterialSystem(HMODULE baseAddress);
void InitialiseDedicatedRtechGame(HMODULE baseAddress);

View File

@ -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);
}

View File

@ -1,63 +1,24 @@
#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 <string.h>
#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"
#include <string.h>
#include <filesystem>
typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject));
bool initialised = false;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
@ -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();

View File

@ -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
{
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; \
}
// block bad netmessages
// 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_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
{
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");
// 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;
}
KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10"), bool, __fastcall, (void* pMsg))
// 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;
}
// 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;
std::string BLOCK_PREFIX =
std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
{
// 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: ";
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
pVar->m_ConCommandBase.m_pszName,
strlen(pVar->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
}
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,198 +250,63 @@ 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, ())
// 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
{
bool result = !CommandLine()->CheckParm("-norestrictservercommands");
spdlog::info("ExploitFixes: Overriding IsValveMod to {}...", result);
return result;
// 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);
return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) && !Tier0::CommandLine()->CheckParm("-norestrictservercommands");
}
// Fix respawn's crappy UTF8 parser so it doesn't crash -_-
// This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome
KHOOK(
CrashFunc_ParseUTF8,
("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"),
bool,
__fastcall,
(INT64 * a1, DWORD* a2, char* strData))
{
// 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);
static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16");
// clang-format off
AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0,
bool, __fastcall, (R2::CBaseClient* self, uint32_t unknown, const char* pCommandString))
// clang-format on
{
if (Cvar_ns_should_log_all_clientcommands->GetBool())
spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString);
#ifdef _MSC_VER
if (_ReturnAddress() == targetRetAddr)
#else
if (__builtin_return_address(0) == targetRetAddr)
#endif
if (!g_pServerLimits->CheckStringCommandLimits(self))
{
if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData))
{
const char* BLOCK_PREFIX = "ParseUTF8 Hook: ";
BLOCKED_INFO("Ignoring potentially-crashing utf8 string");
R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands");
return false;
}
}
return oCrashFunc_ParseUTF8(a1, a2, strData);
}
// 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;
// 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;
if (!CCommand__Tokenize(tempCommand, pCommandString, R2::cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC())
return false;
static void* GetEntByIndexHook(int idx)
ConCommand* command = R2::g_pCVar->FindCommand(tempCommand.Arg(0));
// if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff
if (command && !command->IsFlagSet(FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS))
{
if (idx >= 0x4000)
{
spdlog::info("GetEntByIndex {} is out of bounds", idx);
return nullptr;
}
return GetEntByIndex(idx);
// ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients
if (IsDedicatedServer())
return false;
if (strcmp(self->m_UID, R2::g_pLocalPlayerUserID))
return false;
}
// 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))
// 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())
{
static constexpr int LZSS_LOOKSHIFT = 4;
uint32_t totalBytes = 0;
int getCmdByte = 0, cmdByte = 0;
struct lzss_header_t
{
uint32_t id, actualSize;
};
lzss_header_t header = *(lzss_header_t*)pInput;
if (pInput == NULL || header.id != 'SSZL' || header.actualSize == 0 || 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 totalBytes;
}
else
{
return 0;
}
}
//////////////////////////////////////////////////
void DoBytePatches()
{
uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll");
uintptr_t serverBase = (uintptr_t)GetModuleHandleA("server.dll");
// patches to make commands run from client/ui script still work
// note: this is likely preventable in a nicer way? test prolly
NSMem::BytePatch(engineBase + 0x4FB65, "EB 11");
NSMem::BytePatch(engineBase + 0x4FBAC, "EB 16");
// disconnect concommand
{
uintptr_t addr = engineBase + 0x5ADA2D;
int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE;
NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int));
}
{ // Dumb ANTITAMPER patches (they negatively impact performance and security)
constexpr const char* ANTITAMPER_EXPORTS[] = {
"ANTITAMPER_SPOTCHECK_CODEMARKER",
"ANTITAMPER_TESTVALUE_CODEMARKER",
"ANTITAMPER_TRIGGER_CODEMARKER",
};
// Prevent thesefrom actually doing anything
for (auto exportName : ANTITAMPER_EXPORTS)
{
auto address = (uintptr_t)GetProcAddress(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);
}
}
}
}
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))
{
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)
@ -472,84 +318,141 @@ KHOOK(
"load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
};
if (command->ArgC() > 0)
int iCmdLength = strlen(tempCommand.Arg(0));
bool bIsBadCommand = false;
for (auto& blockedCommand : blockedCommands)
{
std::string cmdStr = command->Arg(0);
for (char& c : cmdStr)
c = tolower(c);
if (iCmdLength != strlen(blockedCommand))
continue;
for (const char* blockedCommand : blockedCommands)
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
// this is a command we need to block
return false;
NEXT_COMMAND:;
}
}
return CGameClient__ExecuteStringCommand(self, unknown, pCommandString);
}
// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines
bool bWasWritingStringTableSuccessful;
// clang-format off
AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0,
void, __fastcall, (void* self))
// clang-format on
{
if (cmdStr.find(blockedCommand) != std::string::npos)
bWasWritingStringTableSuccessful = true;
CBaseClient__SendServerInfo(self);
if (!bWasWritingStringTableSuccessful)
R2::CBaseClient__Disconnect(
self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting");
}
// 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
{
// Block this command
spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr);
return true;
}
}
}
const int MAX_ENT_IDX = 0x4000;
return oSpecialClientCommand(player, command);
}
void SetupKHook(KHook* hook)
if (i >= MAX_ENT_IDX)
{
if (hook->Setup())
spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX);
return nullptr;
}
return GetEntByIndex(i);
}
// clang-format off
AUTOHOOK(CL_CopyExistingEntity, engine.dll + 0x6F940,
bool, __fastcall, (void* a1))
// clang-format on
{
spdlog::debug("KHook::Setup(): Hooked at {}", hook->targetFuncAddr);
}
else
struct CEntityReadInfo
{
spdlog::critical("\tFAILED to initialize all exploit patches.");
BYTE gap[40];
int nNewEntity;
};
// Force exit
MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR);
exit(0);
}
}
void ExploitFixes::LoadCallback_MultiModule(HMODULE baseAddress)
CEntityReadInfo* pReadInfo = (CEntityReadInfo*)a1;
if (pReadInfo->nNewEntity >= 0x1000 || pReadInfo->nNewEntity < 0)
{
// 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;
}
spdlog::info("ExploitFixes::LoadCallback_MultiModule({}) ...", (void*)baseAddress);
return CL_CopyExistingEntity(a1);
}
int hooksEnabled = 0;
for (auto itr = KHook::_allHooks.begin(); itr != KHook::_allHooks.end(); itr++)
ON_DLL_LOAD("engine.dll", EngineExploitFixes, (CModule module))
{
auto curHook = *itr;
if (GetModuleHandleA(curHook->targetFunc.moduleName) == baseAddress)
AUTOHOOK_DISPATCH_MODULE(engine.dll)
CCommand__Tokenize = module.Offset(0x418380).As<bool (*)(CCommand&, const char*, R2::cmd_source_t)>();
// allow client/ui to run clientcommands despite restricting servercommands
module.Offset(0x4FB65).Patch("EB 11");
module.Offset(0x4FBAC).Patch("EB 16");
// patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails
{
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");
addr.Offset(10).NOP(5);
}
}
spdlog::info("\tEnabled {} hooks.", hooksEnabled);
}
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);
// Dumb ANTITAMPER patches (they negatively impact performance and security)
constexpr const char* ANTITAMPER_EXPORTS[] = {
"ANTITAMPER_SPOTCHECK_CODEMARKER",
"ANTITAMPER_TESTVALUE_CODEMARKER",
"ANTITAMPER_TRIGGER_CODEMARKER",
};
spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " late exploit-patch hooks.");
KHook::_allHooks.clear();
ns_exploitfixes_log =
new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever exploitfixes.cpp blocks/corrects something");
g_pCVar->FindCommand("migrateme")->m_nFlags &= ~FCVAR_SERVER_CAN_EXECUTE;
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x2a8a50, &GetEntByIndexHook, reinterpret_cast<LPVOID*>(&GetEntByIndex));
// 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);
}
}
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");
Cvar_sv_cheats = R2::g_pCVar->FindVar("sv_cheats");
}

View File

@ -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

View File

@ -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()
}

View File

@ -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<INT64(__fastcall*)(DWORD, char*)>();
}

View File

@ -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

View File

@ -1,88 +1,70 @@
#include "pch.h"
#include "filesystem.h"
#include "hooks.h"
#include "hookutils.h"
#include "sourceinterface.h"
#include "modmanager.h"
#include <iostream>
#include <sstream>
// 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);
bool readingOriginalFile;
std::string currentModPath;
SourceInterface<IFileSystem>* g_Filesystem;
void InitialiseFilesystem(HMODULE baseAddress)
// use the R2 namespace for game funcs
namespace R2
{
g_Filesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
// create hooks
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x5CBA0, &ReadFileFromVPKHook, reinterpret_cast<LPVOID*>(&readFileFromVPK));
ENABLER_CREATEHOOK(
hook,
reinterpret_cast<void*>((*g_Filesystem)->m_vtable->ReadFromCache),
&ReadFromCacheHook,
reinterpret_cast<LPVOID*>(&readFromCache));
ENABLER_CREATEHOOK(
hook,
reinterpret_cast<void*>((*g_Filesystem)->m_vtable->AddSearchPath),
&AddSearchPathHook,
reinterpret_cast<LPVOID*>(&addSearchPathOriginal));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15F20, &ReadFileFromFilesystemHook, reinterpret_cast<LPVOID*>(&readFileFromFilesystem));
ENABLER_CREATEHOOK(
hook, reinterpret_cast<void*>((*g_Filesystem)->m_vtable->MountVPK), &MountVPKHook, reinterpret_cast<LPVOID*>(&mountVPK));
}
SourceInterface<IFileSystem>* g_pFilesystem;
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);
FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0);
std::stringstream fileStream;
int bytesRead = 0;
char data[4096];
do
{
bytesRead = (*g_Filesystem)->m_vtable2->Read(&(*g_Filesystem)->m_vtable2, data, (int)std::size(data), fileHandle);
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));
(*g_Filesystem)->m_vtable2->Close(*g_Filesystem, fileHandle);
(*g_pFilesystem)->m_vtable2->Close(*g_pFilesystem, fileHandle);
return fileStream.str();
}
std::string ReadVPKOriginalFile(const char* path)
{
readingOriginalFile = true;
// todo: should probably set search path to be g_pModName here also
bReadingOriginalFile = true;
std::string ret = ReadVPKFile(path);
readingOriginalFile = false;
bReadingOriginalFile = false;
return ret;
}
} // namespace R2
// clang-format off
HOOK(AddSearchPathHook, AddSearchPath,
void, __fastcall, (IFileSystem * fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType))
// clang-format on
{
AddSearchPath(fileSystem, pPath, pathID, addType);
// 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);
if (TryReplaceFile(pPath, true))
return false;
// there is literally never any reason to compile here, since we'll always compile in ReadFileFromFilesystemHook in the same codepath
// this is called
return ReadFromCache(filesystem, pPath, result);
}
// 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
{
// don't compile here because this is only ever called from OpenEx, which already compiles
if (TryReplaceFile(filename, false))
{
*b = -1;
return b;
}
return readFileFromVPK(vpkInfo, b, filename);
return ReadFileFromVPK(vpkInfo, b, filename);
}
bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result)
// 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
{
// move this to a convar at some point when we can read them in native
// spdlog::info("ReadFromCacheHook {}", path);
if (TryReplaceFile(path, true))
return false;
return readFromCache(filesystem, path, result);
TryReplaceFile(pPath, true);
return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename);
}
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)
HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath))
{
addSearchPathOriginal(fileSystem, pPath, pathID, addType);
spdlog::info("MountVPK {}", pVpkPath);
VPKData* ret = MountVPK(fileSystem, pVpkPath);
// make sure current mod paths are at head
if (!strcmp(pathID, "GAME") && currentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
for (Mod mod : g_pModManager->m_LoadedMods)
{
addSearchPathOriginal(fileSystem, currentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
addSearchPathOriginal(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
}
FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5)
{
// 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);
}
VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
{
spdlog::info("MountVPK {}", vpkPath);
VPKData* ret = mountVPK(fileSystem, vpkPath);
for (Mod mod : g_ModManager->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<IFileSystem>("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);
}

View File

@ -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;
};
// use the R2 namespace for game funcs
namespace R2
{
extern SourceInterface<IFileSystem>* g_pFilesystem;
std::string ReadVPKFile(const char* path);
std::string ReadVPKOriginalFile(const char* path);
void InitialiseFilesystem(HMODULE baseAddress);
extern SourceInterface<IFileSystem>* g_Filesystem;
} // namespace R2

View File

@ -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<ConVar*> 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<CreateGlobalMemAllocType>(GetProcAddress(baseAddress, "CreateGlobalMemAlloc"));
IMemAlloc** ppMemAllocSingleton = reinterpret_cast<IMemAlloc**>(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<ErrorType>(GetProcAddress(baseAddress, "Error"));
CommandLine = reinterpret_cast<CommandLineType>(GetProcAddress(baseAddress, "CommandLine"));
Plat_FloatTime = reinterpret_cast<Plat_FloatTimeType>(GetProcAddress(baseAddress, "Plat_FloatTime"));
ThreadInServerFrameThread = reinterpret_cast<ThreadInServerFrameThreadType>(GetProcAddress(baseAddress, "ThreadInServerFrameThread"));
}

View File

@ -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);

View File

@ -1,61 +1,193 @@
#include "pch.h"
#include "hooks.h"
#include "hookutils.h"
#include "sigscanning.h"
#include "dedicated.h"
#include <iostream>
#include <wchar.h>
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <filesystem>
#include <Psapi.h>
typedef LPSTR (*GetCommandLineAType)();
LPSTR GetCommandLineAHook();
AUTOHOOK_INIT()
typedef HMODULE (*LoadLibraryExAType)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
typedef HMODULE (*LoadLibraryAType)(LPCSTR lpLibFileName);
HMODULE LoadLibraryAHook(LPCSTR lpLibFileName);
typedef HMODULE (*LoadLibraryExWType)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
typedef HMODULE (*LoadLibraryWType)(LPCWSTR lpLibFileName);
HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName);
GetCommandLineAType GetCommandLineAOriginal;
LoadLibraryExAType LoadLibraryExAOriginal;
LoadLibraryAType LoadLibraryAOriginal;
LoadLibraryExWType LoadLibraryExWOriginal;
LoadLibraryWType LoadLibraryWOriginal;
void InstallInitialHooks()
// called from the ON_DLL_LOAD macros
__dllLoadCallback::__dllLoadCallback(
eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn)
{
if (MH_Initialize() != MH_OK)
spdlog::error("MH_Initialize (minhook initialization) failed");
// parse reliesOn array from string
std::vector<std::string> reliesOnArray;
HookEnabler hook;
ENABLER_CREATEHOOK(
hook, reinterpret_cast<void*>(&GetCommandLineA), &GetCommandLineAHook, reinterpret_cast<LPVOID*>(&GetCommandLineAOriginal));
ENABLER_CREATEHOOK(
hook, reinterpret_cast<void*>(&LoadLibraryExA), &LoadLibraryExAHook, reinterpret_cast<LPVOID*>(&LoadLibraryExAOriginal));
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryA), &LoadLibraryAHook, reinterpret_cast<LPVOID*>(&LoadLibraryAOriginal));
ENABLER_CREATEHOOK(
hook, reinterpret_cast<void*>(&LoadLibraryExW), &LoadLibraryExWHook, reinterpret_cast<LPVOID*>(&LoadLibraryExWOriginal));
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryW), &LoadLibraryWHook, reinterpret_cast<LPVOID*>(&LoadLibraryWOriginal));
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];
}
}
}
LPSTR GetCommandLineAHook()
switch (side)
{
case eDllLoadCallbackSide::UNSIDED:
{
AddDllLoadCallback(dllName, callback, uniqueStr, reliesOnArray);
break;
}
case eDllLoadCallbackSide::CLIENT:
{
AddDllLoadCallbackForClient(dllName, callback, uniqueStr, reliesOnArray);
break;
}
case eDllLoadCallbackSide::DEDICATED_SERVER:
{
AddDllLoadCallbackForDedicatedServer(dllName, callback, uniqueStr, reliesOnArray);
break;
}
}
}
void __fileAutohook::Dispatch()
{
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();
}
ManualHook::ManualHook(const char* funcName, LPVOID func) : pHookFunc(func), ppOrigFunc(nullptr)
{
const int iFuncNameStrlen = strlen(funcName);
pFuncName = new char[iFuncNameStrlen];
memcpy(pFuncName, funcName, iFuncNameStrlen);
}
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<std::string> 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<DllLoadCallback>& GetDllLoadCallbacks()
{
static std::vector<DllLoadCallback> vec = std::vector<DllLoadCallback>();
return vec;
}
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector<std::string> 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<std::string> reliesOn)
{
if (!IsDedicatedServer())
return;
AddDllLoadCallback(dll, callback, tag, reliesOn);
}
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector<std::string> 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,80 +243,89 @@ 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<DllLoadCallback*> 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<std::string> 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;
}
}
void CallAllPendingDLLLoadCallbacks()
{
HMODULE hMods[1024];
@ -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);
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()
}

View File

@ -1,11 +1,311 @@
#pragma once
#include "memory.h"
#include <string>
#include <iostream>
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<std::string> reliesOn = {});
void AddDllLoadCallbackForDedicatedServer(
std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
void AddDllLoadCallbackForClient(
std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> 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))

View File

@ -1,44 +0,0 @@
#include "pch.h"
#include "hookutils.h"
#include <iostream>
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);
}
}

View File

@ -1,23 +0,0 @@
#pragma once
#include <vector>
// 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<HookTarget*> 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<void*>(ppDetour), ppOriginal, #ppDetour)

37
NorthstarDLL/host.cpp Normal file
View File

@ -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()
}

116
NorthstarDLL/hoststate.cpp Normal file
View File

@ -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<CHostState*>();
Cvar_hostport = module.Offset(0x13FA6070).As<ConVar*>();
}

45
NorthstarDLL/hoststate.h Normal file
View File

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,45 +1,16 @@
#include "pch.h"
#include "keyvalues.h"
#include "modmanager.h"
#include "filesystem.h"
#include "hookutils.h"
#include <fstream>
// 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<LPVOID*>(&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()
}

View File

@ -1,3 +0,0 @@
#pragma once
void InitialiseKeyValues(HMODULE baseAddress);

View File

@ -1,18 +1,13 @@
#include "pch.h"
#include "languagehooks.h"
#include "gameutils.h"
#include "tier0.h"
#include <filesystem>
#include <regex>
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<LPVOID*>(&GetGameLanguageOriginal));
AUTOHOOK_DISPATCH()
}

View File

@ -1,3 +0,0 @@
#pragma once
void InitialiseTier0LanguageHooks(HMODULE baseAddress);

View File

@ -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;
HMODULE pLfxModule;
// 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.");
}
if (pLfxModule = LoadLibraryA("latencyflex_layer.dll"))
m_winelfx_WaitAndBeginFrame =
reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "lfx_WaitAndBeginFrame")));
else if (pLfxModule = LoadLibraryA("latencyflex_wine.dll"))
m_winelfx_WaitAndBeginFrame =
reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "winelfx_WaitAndBeginFrame")));
else
{
spdlog::info("LFX: Error loading fallback LatencyFleX library - Code: {}", ::GetLastError());
}
spdlog::info("Unable to load LatencyFleX library, LatencyFleX disabled.");
return;
}
useFallbackEntrypoints = true;
}
else if (m_lfxModule == nullptr)
{
spdlog::info("LFX: Error loading primary LatencyFleX library - Code: {}", ::GetLastError());
return;
}
m_lfx_WaitAndBeginFrame = reinterpret_cast<PFN_lfx_WaitAndBeginFrame>(reinterpret_cast<void*>(
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<LPVOID*>(&OnRenderStart));
}

View File

@ -1,2 +0,0 @@
#pragma once
void InitialiseLatencyFleX(HMODULE baseAddress);

299
NorthstarDLL/limits.cpp Normal file
View File

@ -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)
}

51
NorthstarDLL/limits.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include "r2engine.h"
#include "convar.h"
#include <unordered_map>
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<R2::CBaseClient*, PlayerLimitData> m_PlayerLimitData;
std::vector<UnconnectedPlayerLimitData> 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;

View File

@ -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}};
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}};
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 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<CGameSettings**>();
gChatFadeLength = module.Offset(0x11BAB78).As<CGameFloatVar**>();
gChatFadeSustain = module.Offset(0x11BAC08).As<CGameFloatVar**>();
CHudChat::allHuds = module.Offset(0x11BA9E8).As<CHudChat**>();
ConvertANSIToUnicode = (ConvertANSIToUnicodeType)((char*)baseAddress + 0x7339A0);
ConvertANSIToUnicode = module.Offset(0x7339A0).As<ConvertANSIToUnicodeType>();
}

View File

@ -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);

View File

@ -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 "concommand.h"
#include "nsprefix.h"
#include "bitbuf.h"
#include "tier0.h"
#include "spdlog/sinks/basic_file_sink.h"
#include <iomanip>
#include <sstream>
#include "nsprefix.h"
#include <dbghelp.h>
// 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(&currentTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str());
spdlog::default_logger()->sinks().push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(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<LPCSTR>(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<LPCSTR>(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(&currentTime, (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);
AllocConsole();
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v");
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;
const std::unordered_map<SpewType_t, const char*> 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"}};
void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format, va_list args)
// 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<LPVOID*>(&EngineSpewFunc));
// Hook print function that status concmd uses to actually print data
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15ABD0, Status_ConMsg_Hook, reinterpret_cast<LPVOID*>(&Status_ConMsg_Original));
// Hook CClientState::ProcessPrint
ENABLER_CREATEHOOK(
hook,
(char*)baseAddress + 0x1A1530,
CClientState_ProcessPrint_Hook,
reinterpret_cast<LPVOID*>(&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
{
HookEnabler hook;
if (arg.ArgC() >= 2)
spdlog::info("[echo] {}", arg.ArgS());
}
internalCenterPrint = (ICenterPrint*)((char*)baseAddress + 0x216E940);
// 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;
// "TextMsg" usermessage
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x198710, TextMsgHook, reinterpret_cast<LPVOID*>(&TextMsg_Original));
stream << std::put_time(&currentTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str());
spdlog::default_logger()->sinks().push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(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))
{
AUTOHOOK_DISPATCH_MODULE(engine.dll)
Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged");
}
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<ICenterPrint*>();
}

View File

@ -1,7 +1,4 @@
#pragma once
#include "context.h"
void CreateLogFiles();
void InitialiseLogging();
void InitialiseEngineSpewFuncHooks(HMODULE baseAddress);
void InitialiseClientPrintHooks(HMODULE baseAddress);

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,15 @@
#pragma once
#include "convar.h"
#include "serverpresence.h"
#include <winsock2.h>
#include <string>
#include <cstring>
#include <future>
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<std::string> id;
std::optional<std::string> 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<ReportPresenceResultData> addServerFuture;
// The future used for InternalAddServer() calls.
std::future<ReportPresenceResultData> updateServerFuture;
int m_nNumRegistrationAttempts;
double m_fNextAddServerAttemptTime;
};

View File

@ -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 <class T> void ChangeOffset(void* addr, unsigned int offset)
bool MaxPlayersIncreaseEnabled()
{
NSMem::BytePatch((uintptr_t)addr, (BYTE*)&offset, sizeof(T));
static bool bMaxPlayersIncreaseEnabled = Tier0::CommandLine()->CheckParm("-experimentalmaxplayersincrease");
return bMaxPlayersIncreaseEnabled;
}
/*
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)
// should we use R2 for this? not sure
namespace R2 // use R2 namespace for game funcs
{
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;
}
typedef bool(*DataTable_SetupReceiveTableFromSendTable_Type)(__int64 sendTable, bool needsDecoder);
DataTable_SetupReceiveTableFromSendTable_Type DataTable_SetupReceiveTableFromSendTable_Original;
bool DataTable_SetupReceiveTableFromSendTable_Hook(__int64 sendTable, bool needsDecoder)
int GetMaxPlayers()
{
const char* tableName = *(const char**)(sendTable + 0x118);
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
template <class T> void ChangeOffset(MemoryAddress addr, unsigned int offset)
{
addr.Patch((BYTE*)&offset, sizeof(T));
}
return DataTable_SetupReceiveTableFromSendTable_Original(sendTable, needsDecoder);
}
*/
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<unsigned char>((char*)baseAddress + 0x116458, 0xEB); // jle => jmp
module.Offset(0x116458).Patch("0xEB"); // jle => jmp
// patch ED_Alloc to change nFirstIndex
ChangeOffset<int>((char*)baseAddress + 0x18F46C + 1, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
ChangeOffset<int>(module.Offset(0x18F46C + 1), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
// patch CGameServer::SpawnServer to change GetMaxClients inline
ChangeOffset<int>((char*)baseAddress + 0x119543 + 2, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
ChangeOffset<int>(module.Offset(0x119543 + 2), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
// patch CGameServer::SpawnServer to change for loop
ChangeOffset<unsigned char>((char*)baseAddress + 0x11957F + 2, NEW_MAX_PLAYERS); // original: 32
ChangeOffset<unsigned char>(module.Offset(0x11957F + 2), NEW_MAX_PLAYERS); // original: 32
// patch CGameServer::SpawnServer to change for loop (there are two)
ChangeOffset<unsigned char>((char*)baseAddress + 0x119586 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
ChangeOffset<unsigned char>(module.Offset(0x119586 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
// patch max players somewhere in CClientState
ChangeOffset<unsigned char>((char*)baseAddress + 0x1A162C + 2, NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1)
ChangeOffset<unsigned char>(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<unsigned char>((char*)baseAddress + 0x114C48 + 2, NEW_MAX_PLAYERS); // original: 32
ChangeOffset<unsigned char>(module.Offset(0x114C48 + 2), NEW_MAX_PLAYERS); // original: 32
// do not load prebaked SendTable message list
ChangeOffset<unsigned char>((char*)baseAddress + 0x75859, 0xEB); // jnz -> jmp
HookEnabler hook;
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x209000, &MatchRecvPropsToSendProps_R_Hook,
// reinterpret_cast<LPVOID*>(&MatchRecvPropsToSendProps_R_Original)); ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1FACD0,
// &DataTable_SetupReceiveTableFromSendTable_Hook, reinterpret_cast<LPVOID*>(&DataTable_SetupReceiveTableFromSendTable_Original));
ENABLER_CREATEHOOK(
hook,
(char*)baseAddress + 0x22E220,
&StringTables_CreateStringTable_Hook,
reinterpret_cast<LPVOID*>(&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<unsigned char>((char*)baseAddress + 0x9A44D + 3, NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128)
ChangeOffset<unsigned char>(module.Offset(0x9A44D + 3), NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128)
// patch SpawnGlobalNonRewinding to change forced edict index
ChangeOffset<unsigned char>((char*)baseAddress + 0x2BC403 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
ChangeOffset<unsigned char>(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<unsigned int>((char*)baseAddress + 0x5C560A + 1, CPlayerResource_ModifiedSize);
ChangeOffset<unsigned int>(module.Offset(0x5C560A + 1), CPlayerResource_ModifiedSize);
// DT_PlayerResource::m_iPing SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5059 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50A8 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50E2 + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x5C5059 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C50A8 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C50E2 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPing DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94598, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9459C, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB945C0, PlayerResource_Ping_Size);
ChangeOffset<unsigned int>(module.Offset(0xB94598), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>(module.Offset(0xB9459C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB945C0), PlayerResource_Ping_Size);
// DT_PlayerResource::m_iTeam SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5110 + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C519C + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C517E + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x5C5110 + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C519C + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C517E + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iTeam DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94600, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94604, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94628, PlayerResource_Team_Size);
ChangeOffset<unsigned int>(module.Offset(0xB94600), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>(module.Offset(0xB94604), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB94628), PlayerResource_Team_Size);
// DT_PlayerResource::m_iPRHealth SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C51C0 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5204 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C523E + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x5C51C0 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5204 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C523E + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPRHealth DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94668, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9466C, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94690, PlayerResource_PRHealth_Size);
ChangeOffset<unsigned int>(module.Offset(0xB94668), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>(module.Offset(0xB9466C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB94690), PlayerResource_PRHealth_Size);
// DT_PlayerResource::m_bConnected SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C526C + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52B4 + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52EE + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x5C526C + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C52B4 + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C52EE + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bConnected DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB946D0, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946D4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946F8, PlayerResource_Connected_Size);
ChangeOffset<unsigned int>(module.Offset(0xB946D0), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>(module.Offset(0xB946D4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB946F8), PlayerResource_Connected_Size);
// DT_PlayerResource::m_bAlive SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5321 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5364 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C539E + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x5C5321 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5364 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C539E + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bAlive DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94738, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9473C, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94760, PlayerResource_Alive_Size);
ChangeOffset<unsigned int>(module.Offset(0xB94738), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>(module.Offset(0xB9473C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB94760), PlayerResource_Alive_Size);
// DT_PlayerResource::m_boolStats SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C53CC + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5414 + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C544E + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x5C53CC + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5414 + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C544E + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_boolStats DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB947A0, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947A4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947C8, PlayerResource_BoolStats_Size);
ChangeOffset<unsigned int>(module.Offset(0xB947A0), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xB947A4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB947C8), PlayerResource_BoolStats_Size);
// DT_PlayerResource::m_killStats SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C547C + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54E2 + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54FE + 4, PlayerResource_KillStats_Length);
ChangeOffset<unsigned int>(module.Offset(0x5C547C + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C54E2 + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C54FE + 4), PlayerResource_KillStats_Length);
// DT_PlayerResource::m_killStats DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94808, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9480C, PlayerResource_KillStats_Length);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94830, PlayerResource_KillStats_Size);
ChangeOffset<unsigned int>(module.Offset(0xB94808), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xB9480C), PlayerResource_KillStats_Length);
ChangeOffset<unsigned short>(module.Offset(0xB94830), PlayerResource_KillStats_Size);
// DT_PlayerResource::m_scoreStats SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5528 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5576 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5584 + 4, PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned int>(module.Offset(0x5C5528 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5576 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5584 + 4), PlayerResource_ScoreStats_Length);
// DT_PlayerResource::m_scoreStats DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94870, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94874, PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94898, PlayerResource_ScoreStats_Size);
ChangeOffset<unsigned int>(module.Offset(0xB94870), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xB94874), PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned short>(module.Offset(0xB94898), PlayerResource_ScoreStats_Size);
// CPlayerResource::UpdatePlayerData - m_bConnected
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C66EE + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C672E + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C66EE + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C672E + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
// CPlayerResource::UpdatePlayerData - m_iPing
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6394 + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63DB + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C6394 + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C63DB + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
// CPlayerResource::UpdatePlayerData - m_iTeam
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63FD + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6442 + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C63FD + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C6442 + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
// CPlayerResource::UpdatePlayerData - m_iPRHealth
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C645B + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64A0 + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C645B + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C64A0 + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
// CPlayerResource::UpdatePlayerData - m_bConnected
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64AA + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64F0 + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C64AA + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C64F0 + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
// CPlayerResource::UpdatePlayerData - m_bAlive
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C650A + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C654F + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C650A + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C654F + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
// CPlayerResource::UpdatePlayerData - m_boolStats
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6557 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65A5 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C6557 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C65A5 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
// CPlayerResource::UpdatePlayerData - m_scoreStats
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65C2 + 3, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65E3 + 4, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C65C2 + 3), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C65E3 + 4), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
// CPlayerResource::UpdatePlayerData - m_killStats
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6654 + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C665B + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C6654 + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C665B + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
// GameLoop::RunUserCmds - rebuild
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x483D10, &RunUserCmds_Hook, reinterpret_cast<LPVOID*>(&RunUserCmds_Original));
*(DWORD*)((char*)baseAddress + 0x14E7390) = 0;
auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x5C4FE0);
*module.Offset(0x14E7390).As<DWORD*>() = 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<unsigned int>((char*)baseAddress + 0x23924A + 1, CTeam_ModifiedSize);
ChangeOffset<unsigned int>(module.Offset(0x23924A + 1), CTeam_ModifiedSize);
// CTeam::CTeam - increase memset length to clean newly allocated data
ChangeOffset<unsigned int>((char*)baseAddress + 0x2395AE + 2, 256 + CTeam_AddedSize);
ChangeOffset<unsigned int>(module.Offset(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<LPVOID*>(&SendPropArray2_Original));
hook2.~HookEnabler(); // force hook before calling construct function
*(DWORD*)((char*)baseAddress + 0xC945A0) = 0;
auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x238F50);
*module.Offset(0xC945A0).As<DWORD*>() = 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<unsigned int>((char*)baseAddress + 0x164C41 + 1, C_PlayerResource_ModifiedSize);
ChangeOffset<unsigned int>(module.Offset(0x164C41 + 1), C_PlayerResource_ModifiedSize);
// C_PlayerResource::C_PlayerResource - change loop end value
ChangeOffset<unsigned char>((char*)baseAddress + 0x1640C4 + 2, NEW_MAX_PLAYERS - 32);
ChangeOffset<unsigned char>(module.Offset(0x1640C4 + 2), NEW_MAX_PLAYERS - 32);
// C_PlayerResource::C_PlayerResource - change m_szName address
ChangeOffset<unsigned int>(
(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<unsigned int>(
(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<unsigned int>((char*)baseAddress + 0x1640D0 + 3, 2244 + C_PlayerResource_AddedSize);
ChangeOffset<unsigned int>(module.Offset(0x1640D0 + 3), 2244 + C_PlayerResource_AddedSize);
// C_PlayerResource::UpdatePlayerName - change m_szName address
ChangeOffset<unsigned int>((char*)baseAddress + 0x16431F + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x16431F + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName - change m_szName address 1
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645B1 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x1645B1 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName - change m_szName address 2
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645C0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x1645C0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName - change m_szName address 3
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645DD + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x1645DD + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName address 1
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B71 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x164B71 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName address 2
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B9B + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x164B9B + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 1
ChangeOffset<unsigned int>((char*)baseAddress + 0x164641 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x164641 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 2
ChangeOffset<unsigned int>((char*)baseAddress + 0x164650 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x164650 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 3
ChangeOffset<unsigned int>((char*)baseAddress + 0x16466D + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x16466D + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 1
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BA3 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x164BA3 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 2
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BCE + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x164BCE + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 3
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BE7 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>(module.Offset(0x164BE7 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::m_szName
ChangeOffset<unsigned int>((char*)baseAddress + 0xc350f8, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc350f8 + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0xc350f8), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned short>(module.Offset(0xc350f8 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource size
ChangeOffset<unsigned int>((char*)baseAddress + 0x163415 + 6, C_PlayerResource_ModifiedSize);
ChangeOffset<unsigned int>(module.Offset(0x163415 + 6), C_PlayerResource_ModifiedSize);
// DT_PlayerResource::m_iPing RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163492 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1634D6 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163515 + 5, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x163492 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x1634D6 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x163515 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iPing
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35170, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35170 + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0xc35170), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>(module.Offset(0xc35170 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iTeam RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163549 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635C8 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635AD + 5, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x163549 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x1635C8 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x1635AD + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iTeam
ChangeOffset<unsigned int>((char*)baseAddress + 0xc351e8, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc351e8 + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0xc351e8), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>(module.Offset(0xc351e8 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPRHealth RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635F9 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163625 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163675 + 5, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x1635F9 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x163625 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x163675 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iPRHealth
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35260, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35260 + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0xc35260), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>(module.Offset(0xc35260 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bConnected RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636A9 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636D5 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163725 + 5, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x1636A9 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x1636D5 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x163725 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_bConnected
ChangeOffset<unsigned int>((char*)baseAddress + 0xc352d8, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc352d8 + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0xc352d8), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>(module.Offset(0xc352d8 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bAlive RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163759 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163785 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1637D5 + 5, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x163759 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x163785 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x1637D5 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_bAlive
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35350, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35350 + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0xc35350), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>(module.Offset(0xc35350 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_boolStats RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163809 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163835 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163885 + 5, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0x163809 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x163835 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x163885 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_boolStats
ChangeOffset<unsigned int>((char*)baseAddress + 0xc353c8, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc353c8 + 4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>(module.Offset(0xc353c8), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xc353c8 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_killStats RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638B3 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638E5 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163935 + 5, PlayerResource_KillStats_Length);
ChangeOffset<unsigned int>(module.Offset(0x1638B3 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x1638E5 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x163935 + 5), PlayerResource_KillStats_Length);
// C_PlayerResource::m_killStats
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35440, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35440 + 4, PlayerResource_KillStats_Length);
ChangeOffset<unsigned int>(module.Offset(0xc35440), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xc35440 + 4), PlayerResource_KillStats_Length);
// DT_PlayerResource::m_scoreStats RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163969 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163995 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1639E5 + 5, PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned int>(module.Offset(0x163969 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x163995 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x1639E5 + 5), PlayerResource_ScoreStats_Length);
// C_PlayerResource::m_scoreStats
ChangeOffset<unsigned int>((char*)baseAddress + 0xc354b8, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc354b8 + 4, PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned int>(module.Offset(0xc354b8), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xc354b8 + 4), PlayerResource_ScoreStats_Length);
// C_PlayerResource::GetPlayerName - change m_bConnected address
ChangeOffset<unsigned int>((char*)baseAddress + 0x164599 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x164599 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
// C_PlayerResource::GetPlayerName2 (?) - change m_bConnected address
ChangeOffset<unsigned int>((char*)baseAddress + 0x164629 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x164629 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
// C_PlayerResource::GetPlayerName internal func - change m_bConnected address
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B13 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(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<unsigned int>((char*)baseAddress + 0x164860 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(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<unsigned int>((char*)baseAddress + 0x164834 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(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<DWORD*>() = 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<unsigned int>((char*)baseAddress + 0x182321 + 1, C_Team_ModifiedSize);
ChangeOffset<unsigned int>(module.Offset(0x182321 + 1), C_Team_ModifiedSize);
// C_Team::C_Team - increase memset length to clean newly allocated data
ChangeOffset<unsigned int>((char*)baseAddress + 0x1804A2 + 2, 256 + C_Team_AddedSize);
ChangeOffset<unsigned int>(module.Offset(0x1804A2 + 2), 256 + C_Team_AddedSize);
// DT_Team size
ChangeOffset<unsigned int>((char*)baseAddress + 0xC3AA0C, C_Team_ModifiedSize);
ChangeOffset<unsigned int>(module.Offset(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<LPVOID*>(&RecvPropArray2_Original));
hook.~HookEnabler(); // force hook before calling construct function
*(DWORD*)((char*)baseAddress + 0xC3AFF8) = 0;
auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x17F950);
*module.Offset(0xC3AFF8).As<DWORD*>() = 0;
auto DT_Team_Construct = module.Offset(0x17F950).As<__int64(__fastcall*)()>();
DT_Team_Construct();
}

View File

@ -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

View File

@ -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);
}

348
NorthstarDLL/memory.cpp Normal file
View File

@ -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<uintptr_t>(pAddress)) {}
// operators
MemoryAddress::operator uintptr_t() const
{
return m_nAddress;
}
MemoryAddress::operator void*() const
{
return reinterpret_cast<void*>(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<uintptr_t*>(ret);
return MemoryAddress(ret);
}
// patching
void MemoryAddress::Patch(const uint8_t* pBytes, const size_t nSize)
{
if (nSize)
WriteProcessMemory(GetCurrentProcess(), reinterpret_cast<LPVOID>(m_nAddress), pBytes, nSize, NULL);
}
void MemoryAddress::Patch(const std::initializer_list<uint8_t> 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<uint8_t> HexBytesToString(const char* pHexString)
{
std::vector<uint8_t> 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<uint8_t> 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<LPCVOID>(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<size_t>(mInfo.SizeOfImage);
m_pModuleBase = reinterpret_cast<uintptr_t>(mInfo.lpBaseOfDll);
m_nAddress = m_pModuleBase;
if (!m_nModuleSize || !m_pModuleBase)
return;
m_pDOSHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(m_pModuleBase);
m_pNTHeaders = reinterpret_cast<IMAGE_NT_HEADERS64*>(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<const char*>(hCurrentSection.Name)),
static_cast<uintptr_t>(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<uintptr_t>(GetProcAddress(reinterpret_cast<HMODULE>(m_nAddress), pExportName)));
}
MemoryAddress CModule::FindPattern(const uint8_t* pPattern, const char* pMask)
{
if (!m_ExecutableCode.IsSectionValid())
return MemoryAddress();
uint64_t nBase = static_cast<uint64_t>(m_ExecutableCode.m_pSectionBase);
uint64_t nSize = static_cast<uint64_t>(m_ExecutableCode.m_nSectionSize);
const uint8_t* pData = reinterpret_cast<uint8_t*>(nBase);
const uint8_t* pEnd = pData + static_cast<uint32_t>(nSize) - strlen(pMask);
int nMasks[64]; // 64*16 = enough masks for 1024 bytes.
int iNumMasks = static_cast<int>(ceil(static_cast<float>(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<LONG*>(&nMasks[i]), j);
}
}
}
__m128i xmm1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pPattern));
__m128i xmm2, xmm3, msks;
for (; pData != pEnd; _mm_prefetch(reinterpret_cast<const char*>(++pData + 64), _MM_HINT_NTA))
{
if (pPattern[0] == pData[0])
{
xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pData));
msks = _mm_cmpeq_epi8(xmm1, xmm2);
if ((_mm_movemask_epi8(msks) & nMasks[0]) == nMasks[0])
{
for (uintptr_t i = 1; i < static_cast<uintptr_t>(iNumMasks); ++i)
{
xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>((pData + i * 16)));
xmm3 = _mm_loadu_si128(reinterpret_cast<const __m128i*>((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<uint8_t*>(pData));
}
}
else
goto CONTINUE;
}
return MemoryAddress((&*(const_cast<uint8_t*>(pData))));
}
}
CONTINUE:;
}
return MemoryAddress();
}
inline std::pair<std::vector<uint8_t>, std::string> MaskedBytesFromPattern(const char* pPatternString)
{
std::vector<uint8_t> 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());
}

90
NorthstarDLL/memory.h Normal file
View File

@ -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 <typename T> T As()
{
return reinterpret_cast<T>(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<uint8_t> 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<ModuleSections_t> 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);
};

View File

@ -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<LPVOID*>(&CrashingWeaponActivityFunc0));
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x5A9310, &CrashingWeaponActivityFunc1Hook, reinterpret_cast<LPVOID*>(&CrashingWeaponActivityFunc1));
// experimental: allow cl_extrapolate to be enabled without cheats
{
void* ptr = (char*)baseAddress + 0x275F9D9;
*((char*)ptr) = (char)0;
}
}

View File

@ -1,2 +0,0 @@
#pragma once
void InitialiseMiscClientFixes(HMODULE baseAddress);

View File

@ -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 AddMiscConCommands()
{
MAKE_CONCMD(
"force_newgame",
"forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME",
FCVAR_NONE,
[](const CCommand& arg)
void ConCommand_force_newgame(const CCommand& arg)
{
if (arg.ArgC() < 2)
return;
g_pHostState->m_iNextState = HS_NEW_GAME;
strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName));
});
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));
}
MAKE_CONCMD(
"ns_start_reauth_and_leave_to_lobby",
"called by the server, used to reauth and return the player to lobby when leaving a game",
FCVAR_SERVER_CAN_EXECUTE,
[](const CCommand& arg)
void ConCommand_ns_start_reauth_and_leave_to_lobby(const CCommand& arg)
{
// hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect
g_MasterServerManager->m_bNewgameAfterSelfAuth = true;
g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, g_MasterServerManager->m_sOwnClientAuthToken);
});
g_pMasterServerManager->m_bNewgameAfterSelfAuth = true;
g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken);
}
// 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)
void ConCommand_ns_end_reauth_and_leave_to_lobby(const CCommand& arg)
{
Cbuf_AddText(
Cbuf_GetCurrentPlayer(),
fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(),
cmd_source_t::kCommandSrcCode);
Cbuf_Execute();
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_ClientSquirrelManager->sqvm)
if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM)
{
g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true;
g_pServerAuthentication->m_bNeedLocalAuthForNewgame = true;
// this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta
// fucks things should maybe set this in HostState_NewGame?
SetCurrentPlaylist("tdm");
strcpy(g_pHostState->m_levelName, "mp_lobby");
g_pHostState->m_iNextState = HS_NEW_GAME;
R2::SetCurrentPlaylist("tdm");
strcpy(R2::g_pHostState->m_levelName, "mp_lobby");
R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME;
}
}
void AddMiscConCommands()
{
RegisterConCommand(
"force_newgame",
ConCommand_force_newgame,
"forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME",
FCVAR_NONE);
RegisterConCommand(
"ns_start_reauth_and_leave_to_lobby",
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);
// this is a concommand because we make a deferred call to it from another thread
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())
{
// strip flags
int flags = pair.second->GetFlags();
if (flags & FCVAR_DEVELOPMENTONLY)
{
flags &= ~FCVAR_DEVELOPMENTONLY;
iNumCvarsAltered++;
}
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<const char**>();
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<std::tuple<const char*, uint32_t>> 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<std::tuple<const char*, uint32_t>> 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);
}
});
}

View File

@ -1,2 +1,3 @@
#pragma once
void AddMiscConCommands();
void FixupCvarFlags();

View File

@ -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);
}

View File

@ -1 +0,0 @@
void InitialiseMiscServerFixes(HMODULE baseAddress);

View File

@ -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)
#include <filesystem>
// void function NSEarlyWritePlayerPersistenceForLeave( entity player )
SQRESULT SQ_EarlyWritePlayerPersistenceForLeave(HSquirrelVM* sqvm)
{
const int PLAYER_ARRAY_OFFSET = 0x12A53F90;
const int PLAYER_SIZE = 0x2D728;
const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
if (!pPlayer)
{
spdlog::warn("NSEarlyWritePlayerPersistenceForLeave got null player");
void* playerArrayBase = (char*)GetModuleHandleA("engine.dll") + PLAYER_ARRAY_OFFSET;
void* player = (char*)playerArrayBase + (playerIndex * PLAYER_SIZE);
return player;
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
return SQRESULT_NOTNULL;
}
// void function NSEarlyWritePlayerIndexPersistenceForLeave( int playerIndex )
SQRESULT SQ_EarlyWritePlayerIndexPersistenceForLeave(void* sqvm)
R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex];
if (g_pServerAuthentication->m_PlayerAuthenticationData.find(pClient) == g_pServerAuthentication->m_PlayerAuthenticationData.end())
{
int playerIndex = ServerSq_getinteger(sqvm, 1);
void* player = GetPlayerByIndex(playerIndex);
if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(player))
{
ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str());
return SQRESULT_ERROR;
g_pSquirrel<ScriptContext::SERVER>->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<ScriptContext::SERVER>->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<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(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<ScriptContext::SERVER>->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<ScriptContext::SERVER>->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<ScriptContext::SERVER>->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<ScriptContext::SERVER>->AddFuncRegistration(
"void", "NSEarlyWritePlayerPersistenceForLeave", "entity player", "", SQ_EarlyWritePlayerPersistenceForLeave);
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence);
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsPlayerLocalPlayer", "entity player", "", SQ_IsPlayerLocalPlayer);
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated);
}

View File

@ -1,2 +0,0 @@
void InitialiseMiscServerScriptCommand(HMODULE baseAddress);
void* GetPlayerByIndex(int playerIndex);

View File

@ -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<LPVOID*>(&AddLocalisationFile));
AUTOHOOK_DISPATCH()
}

View File

@ -1,3 +0,0 @@
#pragma once
void InitialiseModLocalisation(HMODULE baseAddress);

View File

@ -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 <string>
#include <sstream>
#include <vector>
#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<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf);
@ -106,12 +107,56 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
else
convar->HelpString = "";
// todo: could possibly parse FCVAR names here instead, would be easier
if (convarObj.HasMember("Flags"))
convar->Flags = convarObj["Flags"].GetInt();
else
convar->Flags = FCVAR_NONE;
if (convarObj.HasMember("Flags"))
{
// 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<fs::path> 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<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
m_EnabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
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
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<rapidjson::StringBuffer> 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<size_t, std::string> 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<rapidjson::OStreamWrapper> 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);
}

View File

@ -1,14 +1,15 @@
#pragma once
#include "convar.h"
#include "memalloc.h"
#include "squirrel.h"
#include "rapidjson/document.h"
#include <string>
#include <vector>
#include <filesystem>
#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<ModScriptCallback> 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,14 +100,9 @@ class Mod
std::vector<ModRpakEntry> Rpaks;
std::unordered_map<std::string, std::string>
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<std::string, std::string> DependencyConstants;
// other stuff
bool wasReadSuccessfully = false;
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<Mod> m_loadedMods;
std::unordered_map<std::string, ModOverrideFile> 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<std::string, std::string> DependencyConstants;
std::vector<Mod> m_LoadedMods;
std::unordered_map<std::string, ModOverrideFile> m_ModFiles;
std::unordered_map<std::string, std::string> 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;

View File

@ -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<int> HexBytesToString(const char* str)
{
std::vector<int> 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<int> 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<BYTE> vals)
{
std::vector<BYTE> bytes = vals;
if (!bytes.empty())
BytePatch(address, &bytes[0], bytes.size());
}
inline void BytePatch(uintptr_t address, const char* bytesStr)
{
std::vector<int> byteInts = HexBytesToString(bytesStr);
std::vector<BYTE> 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<KHook*> _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<void*>(&hk##name), (void**)&o##name); \
returnType convention hk##name args
#pragma endregion

View File

@ -1,13 +1,13 @@
#include <string>
#include "pch.h"
#include "nsprefix.h"
#include <string>
std::string GetNorthstarPrefix()
{
return NORTHSTAR_FOLDER_PREFIX;
}
void parseConfigurables()
void InitialiseNorthstarPrefix()
{
char* clachar = strstr(GetCommandLineA(), "-profile=");
if (clachar)

View File

@ -3,5 +3,5 @@
static std::string NORTHSTAR_FOLDER_PREFIX;
void InitialiseNorthstarPrefix();
std::string GetNorthstarPrefix();
void parseConfigurables();

View File

@ -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 <filesystem>
#include <sstream>
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 <typename ReturnType, typename... Args> ReturnType CallVFunc(int index, void* thisPtr, Args... args)
{

View File

@ -1,8 +1,8 @@
#include "pch.h"
#include "modmanager.h"
#include "filesystem.h"
#include "hookutils.h"
#include "pdef.h"
#include <map>
#include <sstream>
#include <fstream>
@ -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;
}

View File

@ -1,30 +1,94 @@
#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;
// clang-format off
AUTOHOOK(clc_SetPlaylistVarOverride__Process, engine.dll + 0x222180,
char, __fastcall, (void* a1, void* a2))
// clang-format on
{
// the private_match playlist on mp_lobby is the only situation where there should be any legitimate sending of this netmessage
if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(R2::GetCurrentPlaylistName(), "private_match") ||
strcmp(R2::g_pHostState->m_levelName, "mp_lobby"))
return 1;
return clc_SetPlaylistVarOverride__Process(a1, a2);
}
// clang-format off
AUTOHOOK(SetCurrentPlaylist, engine.dll + 0x18EB20,
bool, __fastcall, (const char* pPlaylistName))
// clang-format on
{
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;
SetPlaylistVarOverride(pVarName, pValue);
}
// clang-format off
AUTOHOOK(GetCurrentPlaylistVar, engine.dll + 0x18C680,
const char*, __fastcall, (const char* pVarName, bool bUseOverrides))
// clang-format on
{
if (!bUseOverrides && !strcmp(pVarName, "max_players"))
bUseOverrides = true;
return GetCurrentPlaylistVar(pVarName, bUseOverrides);
}
// clang-format off
AUTOHOOK(GetCurrentGamemodeMaxPlayers, engine.dll + 0x18C430,
int, __fastcall, ())
// clang-format on
{
const char* pMaxPlayers = R2::GetCurrentPlaylistVar("max_players", 0);
if (!pMaxPlayers)
return GetCurrentGamemodeMaxPlayers();
int iMaxPlayers = atoi(pMaxPlayers);
return iMaxPlayers;
}
void ConCommand_playlist(const CCommand& args)
{
if (args.ArgC() < 2)
return;
SetCurrentPlaylist(args.Arg(1));
R2::SetCurrentPlaylist(args.Arg(1));
}
void ConCommand_setplaylistvaroverride(const CCommand& args)
@ -33,74 +97,34 @@ void ConCommand_setplaylistvaroverride(const CCommand& args)
return;
for (int i = 1; i < args.ArgC(); i += 2)
SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1));
R2::SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1));
}
char Onclc_SetPlaylistVarOverrideHook(void* a1, void* a2)
ON_DLL_LOAD_RELIESON("engine.dll", PlaylistHooks, (ConCommand, ConVar), (CModule module))
{
// 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;
AUTOHOOK_DISPATCH()
return Onclc_SetPlaylistVarOverride(a1, a2);
}
R2::GetCurrentPlaylistName = module.Offset(0x18C640).As<const char* (*)()>();
R2::SetCurrentPlaylist = module.Offset(0x18EB20).As<void (*)(const char*)>();
R2::SetPlaylistVarOverride = module.Offset(0x18ED00).As<void (*)(const char*, const char*)>();
R2::GetCurrentPlaylistVar = module.Offset(0x18C680).As<const char* (*)(const char*, bool)>();
void SetPlaylistVarOverrideHook(const char* varName, const char* value)
{
if (strlen(value) >= 64)
return;
SetPlaylistVarOverrideOriginal(varName, value);
}
char* GetCurrentPlaylistVarHook(const char* varName, bool useOverrides)
{
if (!useOverrides && !strcmp(varName, "max_players"))
useOverrides = true;
return GetCurrentPlaylistVarOriginal(varName, useOverrides);
}
int GetCurrentGamemodeMaxPlayersHook()
{
char* maxPlayersStr = GetCurrentPlaylistVar("max_players", 0);
if (!maxPlayersStr)
return GetCurrentGamemodeMaxPlayers();
int maxPlayers = atoi(maxPlayersStr);
return maxPlayers;
}
void InitialisePlaylistHooks(HMODULE baseAddress)
{
// 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<LPVOID*>(&Onclc_SetPlaylistVarOverride));
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x18ED00, &SetPlaylistVarOverrideHook, reinterpret_cast<LPVOID*>(&SetPlaylistVarOverrideOriginal));
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x18C680, &GetCurrentPlaylistVarHook, reinterpret_cast<LPVOID*>(&GetCurrentPlaylistVarOriginal));
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x18C430, &GetCurrentGamemodeMaxPlayersHook, reinterpret_cast<LPVOID*>(&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);
}

View File

@ -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

View File

@ -1,9 +1,11 @@
#include "pch.h"
#include "squirrel.h"
#include "plugins.h"
#include <chrono>
#include "masterserver.h"
#include "convar.h"
#include "serverpresence.h"
#include <chrono>
#include <windows.h>
/// <summary>
@ -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<ScriptContext::UI>->getstring(sqvm, 1);
gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 4);
gameState.connected = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 5);
gameState.loading = g_pSquirrel<ScriptContext::UI>->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<ScriptContext::CLIENT>->getinteger(sqvm, 1);
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 2);
gameState.ourScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 3);
gameState.secondHighestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
gameState.highestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 5);
serverInfo.roundBased = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 6);
serverInfo.scoreLimit = g_pSquirrel<ScriptContext::CLIENT>->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<ScriptContext::UI>->getstring(sqvm, 1);
serverInfo.name = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
serverInfo.password = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
gameState.players = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 4);
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 5);
gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 6);
gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 7);
gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 8);
gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->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<ScriptContext::CLIENT>->getstring(sqvm, 1);
serverInfo.name = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
serverInfo.password = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 3);
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->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<ScriptContext::CLIENT>->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<ScriptContext::UI>->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<ScriptContext::UI> && g_pSquirrel<ScriptContext::CLIENT>)
{
g_UISquirrelManager->AddFuncRegistration(
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
"void",
"NSUpdateGameStateUI",
"string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading",
"",
SQ_UpdateGameStateUI);
g_ClientSquirrelManager->AddFuncRegistration(
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
"void",
"NSUpdateGameStateClient",
"int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit",
"",
SQ_UpdateGameStateClient);
g_UISquirrelManager->AddFuncRegistration(
g_pSquirrel<ScriptContext::UI>->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<ScriptContext::CLIENT>->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<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
}
}

View File

@ -15,5 +15,3 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var);
void initGameState();
void* getPluginObject(PluginObject var);
void InitialisePluginCommands(HMODULE baseAddress);

View File

@ -0,0 +1,6 @@
#pragma once
#include "concommand.h"
void PrintCommandHelpDialogue(const ConCommandBase* command, const char* name);
void TryPrintCvarHelpForCommand(const char* pCommand);
void InitialiseCommandPrint();

View File

@ -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 <cvarname>");
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 <string> [<string>...]");
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 <string>");
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;
}

168
NorthstarDLL/printmaps.cpp Normal file
View File

@ -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 <filesystem>
#include <regex>
AUTOHOOK_INIT()
enum class MapSource_t
{
VPK,
GAMEDIR,
MOD
};
const std::unordered_map<MapSource_t, const char*> 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<MapVPKInfo> 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 <substring>");
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;
}

2
NorthstarDLL/printmaps.h Normal file
View File

@ -0,0 +1,2 @@
#pragma once
void InitialiseMapsPrint();

Some files were not shown because too many files have changed in this diff Show More