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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,15 @@
#include "pch.h" #include "pch.h"
#include "audio.h" #include "audio.h"
#include "dedicated.h" #include "dedicated.h"
#include "convar.h"
#include "rapidjson/error/en.h" #include "rapidjson/error/en.h"
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <random> #include <random>
#include "convar.h"
AUTOHOOK_INIT()
extern "C" 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 // 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.seekg(0, std::ios::beg);
wavStream.read(reinterpret_cast<char*>(data), sizeof(EMPTY_WAVE)); wavStream.read(reinterpret_cast<char*>(data), fileSize);
wavStream.close(); wavStream.close();
spdlog::info("Finished async read of audio sample {}", pathString); spdlog::info("Finished async read of audio sample {}", pathString);
@ -315,6 +315,7 @@ void CustomAudioManager::ClearAudioOverrides()
{ {
// stop all miles sounds beforehand // stop all miles sounds beforehand
// miles_stop_all // miles_stop_all
MilesStopAll(); MilesStopAll();
// this is cancer but it works // this is cancer but it works
@ -323,15 +324,12 @@ void CustomAudioManager::ClearAudioOverrides()
// slightly (very) bad // slightly (very) bad
// wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them // 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_loadedAudioOverrides.clear();
m_loadedAudioOverridesRegex.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) 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); 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 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 // DO NOT INLINE THIS FUNCTION
// See comment below. // See comment below.
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal( bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
@ -398,7 +416,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
if (!overrideData) if (!overrideData)
// not found either // not found either
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType); return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
else else
{ {
// cache found pattern to improve performance // cache found pattern to improve performance
@ -412,7 +430,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
overrideData = iter->second; overrideData = iter->second;
if (!ShouldPlayAudioEvent(eventName, overrideData)) if (!ShouldPlayAudioEvent(eventName, overrideData))
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType); return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
void* data = 0; void* data = 0;
unsigned int dataLength = 0; unsigned int dataLength = 0;
@ -454,7 +472,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
if (!data) if (!data)
{ {
spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName); 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; audioBuffer = data;
@ -465,51 +483,25 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
*(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength; *(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength;
// 64 - Auto-detect sample type // 64 - Auto-detect sample type
bool res = LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, 64); bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64);
if (!res) if (!res)
spdlog::error("LoadSampleMetadata failed! The game will crash :("); spdlog::error("LoadSampleMetadata failed! The game will crash :(");
return res; return res;
} }
// DO NOT TOUCH THIS FUNCTION // clang-format off
// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer. AUTOHOOK(MilesLog, client.dll + 0x57DAD0,
bool __fastcall LoadSampleMetadata_Hook(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType) void, __fastcall, (int level, const char* string))
{ // clang-format on
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)
{ {
spdlog::info("[MSS] {} - {}", level, string); 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, ""); Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, "");
MilesStopAll = module.Offset(0x580850).As<MilesStopAll_Type>();
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);
} }

View File

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

View File

@ -1,26 +1,28 @@
#pragma once
#include "pch.h" #include "pch.h"
#include "bansystem.h" #include "bansystem.h"
#include "serverauthentication.h" #include "serverauthentication.h"
#include "maxplayers.h"
#include "concommand.h" #include "concommand.h"
#include "miscserverscript.h" #include "r2server.h"
#include <filesystem> #include "r2engine.h"
#include "nsprefix.h" #include "nsprefix.h"
#include <ctime>
#include <filesystem>
const char* BANLIST_PATH_SUFFIX = "/banlist.txt"; const char* BANLIST_PATH_SUFFIX = "/banlist.txt";
const char BANLIST_COMMENT_CHAR = '#'; const char BANLIST_COMMENT_CHAR = '#';
ServerBanSystem* g_ServerBanSystem; ServerBanSystem* g_pBanSystem;
void ServerBanSystem::OpenBanlist() void ServerBanSystem::OpenBanlist()
{ {
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/banlist.txt"); std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt");
std::stringstream enabledModsStringStream;
if (!enabledModsStream.fail()) if (!banlistStream.fail())
{ {
std::string line; std::string line;
while (std::getline(enabledModsStream, line)) while (std::getline(banlistStream, line))
{ {
// ignore line if first char is # or line is empty // ignore line if first char is # or line is empty
if (line == "" || line.front() == BANLIST_COMMENT_CHAR) if (line == "" || line.front() == BANLIST_COMMENT_CHAR)
@ -41,7 +43,7 @@ void ServerBanSystem::OpenBanlist()
m_vBannedUids.push_back(strtoull(uid.c_str(), nullptr, 10)); 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 // 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) if (args.ArgC() < 2)
return; return;
// assuming maxplayers 32 for (int i = 0; i < R2::GetMaxPlayers(); i++)
for (int i = 0; i < 32; 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)); g_pBanSystem->BanUID(strtoull(player->m_UID, nullptr, 10));
CBaseClient__Disconnect(player, 1, "Banned from server"); R2::CBaseClient__Disconnect(player, 1, "Banned from server");
break; break;
} }
} }
@ -202,20 +203,20 @@ void ConCommand_unban(const CCommand& args)
return; return;
// assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything // 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) 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_pBanSystem = new ServerBanSystem;
g_ServerBanSystem->OpenBanlist(); g_pBanSystem->OpenBanlist();
RegisterConCommand("ban", ConCommand_ban, "bans a given player by uid or name", FCVAR_GAMEDLL); 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("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_GAMEDLL);
RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_NONE); 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); bool IsUIDAllowed(uint64_t uid);
}; };
extern ServerBanSystem* g_ServerBanSystem; extern ServerBanSystem* g_pBanSystem;
void InitialiseBanSystem(HMODULE baseAddress);

View File

@ -1,19 +1,19 @@
#include "pch.h" #include "pch.h"
#include "buildainfile.h"
#include "convar.h" #include "convar.h"
#include "hookutils.h" #include "hoststate.h"
#include "r2engine.h"
#include <fstream> #include <fstream>
#include <filesystem> #include <filesystem>
#include "nsmem.h"
namespace fs = std::filesystem; AUTOHOOK_INIT()
const int AINET_VERSION_NUMBER = 57; const int AINET_VERSION_NUMBER = 57;
const int AINET_SCRIPT_VERSION_NUMBER = 21; const int AINET_SCRIPT_VERSION_NUMBER = 21;
const int MAP_VERSION_TEMP = 30;
const int PLACEHOLDER_CRC = 0; const int PLACEHOLDER_CRC = 0;
const int MAX_HULLS = 5; const int MAX_HULLS = 5;
#pragma pack(push, 1)
struct CAI_NodeLink struct CAI_NodeLink
{ {
short srcId; short srcId;
@ -24,6 +24,7 @@ struct CAI_NodeLink
char unk2[5]; char unk2[5];
int64_t flags; int64_t flags;
}; };
#pragma pack(pop)
#pragma pack(push, 1) #pragma pack(push, 1)
struct CAI_NodeLinkDisk struct CAI_NodeLinkDisk
@ -33,7 +34,9 @@ struct CAI_NodeLinkDisk
char unk0; char unk0;
bool hulls[MAX_HULLS]; bool hulls[MAX_HULLS];
}; };
#pragma pack(pop)
#pragma pack(push, 1)
struct CAI_Node struct CAI_Node
{ {
int index; // not present on disk int index; // not present on disk
@ -62,6 +65,7 @@ struct CAI_Node
char unk9[8]; // padding until next bit char unk9[8]; // padding until next bit
char unk10[8]; // should match up to unk6 on disk char unk10[8]; // should match up to unk6 on disk
}; };
#pragma pack(pop)
// the way CAI_Nodes are represented in on-disk ain files // the way CAI_Nodes are represented in on-disk ain files
#pragma pack(push, 1) #pragma pack(push, 1)
@ -81,7 +85,9 @@ struct CAI_NodeDisk
short unk5; short unk5;
char unk6[8]; char unk6[8];
}; // total size of 68 bytes }; // total size of 68 bytes
#pragma pack(pop)
#pragma pack(push, 1)
struct UnkNodeStruct0 struct UnkNodeStruct0
{ {
int index; int index;
@ -106,10 +112,12 @@ struct UnkNodeStruct0
char pad4[132]; char pad4[132];
char unk5; char unk5;
}; };
#pragma pack(pop)
int* pUnkStruct0Count; int* pUnkStruct0Count;
UnkNodeStruct0*** pppUnkNodeStruct0s; UnkNodeStruct0*** pppUnkNodeStruct0s;
#pragma pack(push, 1)
struct UnkLinkStruct1 struct UnkLinkStruct1
{ {
short unk0; short unk0;
@ -119,10 +127,12 @@ struct UnkLinkStruct1
char unk4; char unk4;
char unk5; char unk5;
}; };
#pragma pack(pop)
int* pUnkLinkStruct1Count; int* pUnkLinkStruct1Count;
UnkLinkStruct1*** pppUnkStruct1s; UnkLinkStruct1*** pppUnkStruct1s;
#pragma pack(push, 1)
struct CAI_ScriptNode struct CAI_ScriptNode
{ {
float x; float x;
@ -130,7 +140,9 @@ struct CAI_ScriptNode
float z; float z;
uint64_t scriptdata; uint64_t scriptdata;
}; };
#pragma pack(pop)
#pragma pack(push, 1)
struct CAI_Network struct CAI_Network
{ {
// +0 // +0
@ -160,16 +172,16 @@ struct CAI_Network
// +84176 // +84176
CAI_Node** nodes; CAI_Node** nodes;
}; };
#pragma pack(pop)
char** pUnkServerMapversionGlobal; char** pUnkServerMapversionGlobal;
char* pMapName;
ConVar* Cvar_ns_ai_dumpAINfileFromLoad; ConVar* Cvar_ns_ai_dumpAINfileFromLoad;
void DumpAINInfo(CAI_Network* aiNetwork) void DumpAINInfo(CAI_Network* aiNetwork)
{ {
fs::path writePath("r2/maps/graphs"); fs::path writePath(fmt::format("{}/maps/graphs", R2::g_pModName));
writePath /= pMapName; writePath /= R2::g_pHostState->m_levelName;
writePath += ".ain"; writePath += ".ain";
// dump from memory // dump from memory
@ -349,20 +361,20 @@ void DumpAINInfo(CAI_Network* aiNetwork)
writeStream.close(); writeStream.close();
} }
typedef void (*CAI_NetworkBuilder__BuildType)(void* builder, CAI_Network* aiNetwork, void* unknown); // clang-format off
CAI_NetworkBuilder__BuildType CAI_NetworkBuilder__Build; AUTOHOOK(CAI_NetworkBuilder__Build, server.dll + 0x385E20,
void, __fastcall, (void* builder, CAI_Network* aiNetwork, void* unknown))
void CAI_NetworkBuilder__BuildHook(void* builder, CAI_Network* aiNetwork, void* unknown) // clang-format on
{ {
CAI_NetworkBuilder__Build(builder, aiNetwork, unknown); CAI_NetworkBuilder__Build(builder, aiNetwork, unknown);
DumpAINInfo(aiNetwork); DumpAINInfo(aiNetwork);
} }
typedef void (*LoadAINFileType)(void* aimanager, void* buf, const char* filename); // clang-format off
LoadAINFileType LoadAINFile; AUTOHOOK(LoadAINFile, server.dll + 0x3933A0,
void, __fastcall, (void* aimanager, void* buf, const char* filename))
void LoadAINFileHook(void* aimanager, void* buf, const char* filename) // clang-format on
{ {
LoadAINFile(aimanager, buf, filename); 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( 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"); "ns_ai_dumpAINfileFromLoad", "0", FCVAR_NONE, "For debugging: whether we should dump ain data for ains loaded from disk");
HookEnabler hook; pUnkStruct0Count = module.Offset(0x1063BF8).As<int*>();
ENABLER_CREATEHOOK( pppUnkNodeStruct0s = module.Offset(0x1063BE0).As<UnkNodeStruct0***>();
hook, (char*)baseAddress + 0x385E20, &CAI_NetworkBuilder__BuildHook, reinterpret_cast<LPVOID*>(&CAI_NetworkBuilder__Build)); pUnkLinkStruct1Count = module.Offset(0x1063AA8).As<int*>();
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x3933A0, &LoadAINFileHook, reinterpret_cast<LPVOID*>(&LoadAINFile)); pppUnkStruct1s = module.Offset(0x1063A90).As<UnkLinkStruct1***>();
pUnkServerMapversionGlobal = module.Offset(0xBFBE08).As<char**>();
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);
} }

View File

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

View File

@ -1,13 +1,11 @@
#include "pch.h" #include "pch.h"
#include "convar.h" #include "convar.h"
#include "concommand.h" #include "concommand.h"
#include "chatcommand.h"
#include "localchatwriter.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 // 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 // if isIngameChat is false, we use network chat instead
typedef void(__fastcall* ClientSayTextType)(void* a1, const char* message, __int64 isIngameChat, bool isTeamChat); void(__fastcall* ClientSayText)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat);
ClientSayTextType ClientSayText;
void ConCommand_say(const CCommand& args) 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", 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("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); 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 "pch.h"
#include "clientauthhooks.h"
#include "hookutils.h"
#include "gameutils.h"
#include "masterserver.h" #include "masterserver.h"
#include "convar.h" #include "convar.h"
#include "r2client.h"
typedef void (*AuthWithStryderType)(void* a1); AUTOHOOK_INIT()
AuthWithStryderType AuthWithStryder;
ConVar* Cvar_ns_has_agreed_to_send_token; 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 AGREED_TO_SEND_TOKEN = 1;
const int DISAGREED_TO_SEND_TOKEN = 2; const int DISAGREED_TO_SEND_TOKEN = 2;
typedef char* (*Auth3PTokenType)(); // clang-format off
Auth3PTokenType Auth3PToken; AUTOHOOK(AuthWithStryder, engine.dll + 0x1843A0,
void, __fastcall, (void* a1))
char* token_location = 0x0; // clang-format on
void AuthWithStryderHook(void* a1)
{ {
// game will call this forever, until it gets a valid auth key // 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 // 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 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 && if (Cvar_ns_has_agreed_to_send_token->GetInt() == AGREED_TO_SEND_TOKEN &&
!g_MasterServerManager->m_bOriginAuthWithMasterServerInProgress) !g_pMasterServerManager->m_bOriginAuthWithMasterServerInProgress)
g_MasterServerManager->AuthenticateOriginWithMasterServer(g_LocalPlayerUserID, g_LocalPlayerOriginToken); g_pMasterServerManager->AuthenticateOriginWithMasterServer(R2::g_pLocalPlayerUserID, R2::g_pLocalPlayerOriginToken);
// invalidate key so auth will fail // invalidate key so auth will fail
*g_LocalPlayerOriginToken = 0; *R2::g_pLocalPlayerOriginToken = 0;
} }
AuthWithStryder(a1); 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); memset(p3PToken, 0x0, 1024);
strcpy(token_location, "Protocol 3: Protect the Pilot"); strcpy(p3PToken, "Protocol 3: Protect the Pilot");
} }
return Auth3PToken(); 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 // this cvar will save to cfg once initially agreed with
Cvar_ns_has_agreed_to_send_token = new ConVar( Cvar_ns_has_agreed_to_send_token = new ConVar(
"ns_has_agreed_to_send_token", "ns_has_agreed_to_send_token",
"0", "0",
FCVAR_ARCHIVE_PLAYERPROFILE, FCVAR_ARCHIVE_PLAYERPROFILE,
"whether the user has agreed to send their origin token to the northstar masterserver"); "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 "pch.h"
#include "clientchathooks.h"
#include <rapidjson/document.h>
#include "squirrel.h" #include "squirrel.h"
#include "serverchathooks.h" #include "serverchathooks.h"
#include "localchatwriter.h" #include "localchatwriter.h"
typedef void(__fastcall* CHudChat__AddGameLineType)(void* self, const char* message, int fromPlayerId, bool isteam, bool isdead); #include <rapidjson/document.h>
CHudChat__AddGameLineType CHudChat__AddGameLine;
struct ChatTags AUTOHOOK_INIT()
{
bool whisper;
bool team;
bool dead;
};
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. // This hook is called for each HUD, but we only want our logic to run once.
if (self != *CHudChat::allHuds) if (self != *CHudChat::allHuds)
{
return; 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; int senderId = inboxId & CUSTOM_MESSAGE_INDEX_MASK;
bool isAnonymous = senderId == 0; bool isAnonymous = senderId == 0;
@ -38,58 +31,53 @@ static void CHudChat__AddGameLineHook(void* self, const char* message, int inbox
payload = message + 1; payload = message + 1;
} }
g_ClientSquirrelManager->pusharg((int)senderId - 1); g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, (int)senderId - 1);
g_ClientSquirrelManager->pusharg(payload); g_pSquirrel<ScriptContext::CLIENT>->pushstring(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, payload);
g_ClientSquirrelManager->pusharg(isTeam); g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isTeam);
g_ClientSquirrelManager->pusharg(isDead); g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isDead);
g_ClientSquirrelManager->pusharg(type); g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, type);
g_ClientSquirrelManager->call(5); g_pSquirrel<ScriptContext::CLIENT>->call(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, 5);
} }
else else
{
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next) for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
{
CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead); CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead);
}
}
} }
// void NSChatWrite( int context, string str ) // void NSChatWrite( int context, string str )
static SQRESULT SQ_ChatWrite(void* sqvm) SQRESULT SQ_ChatWrite(HSquirrelVM* sqvm)
{ {
int context = ClientSq_getinteger(sqvm, 1); int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = ClientSq_getstring(sqvm, 2); const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
LocalChatWriter((LocalChatWriter::Context)context).Write(str); LocalChatWriter((LocalChatWriter::Context)context).Write(str);
return SQRESULT_NOTNULL; return SQRESULT_NULL;
} }
// void NSChatWriteRaw( int context, string str ) // void NSChatWriteRaw( int context, string str )
static SQRESULT SQ_ChatWriteRaw(void* sqvm) SQRESULT SQ_ChatWriteRaw(HSquirrelVM* sqvm)
{ {
int context = ClientSq_getinteger(sqvm, 1); int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = ClientSq_getstring(sqvm, 2); const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
LocalChatWriter((LocalChatWriter::Context)context).InsertText(str); LocalChatWriter((LocalChatWriter::Context)context).InsertText(str);
return SQRESULT_NOTNULL; return SQRESULT_NULL;
} }
// void NSChatWriteLine( int context, string str ) // void NSChatWriteLine( int context, string str )
static SQRESULT SQ_ChatWriteLine(void* sqvm) SQRESULT SQ_ChatWriteLine(HSquirrelVM* sqvm)
{ {
int context = ClientSq_getinteger(sqvm, 1); int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = ClientSq_getstring(sqvm, 2); const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
LocalChatWriter((LocalChatWriter::Context)context).WriteLine(str); 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; AUTOHOOK_DISPATCH()
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x22E580, &CHudChat__AddGameLineHook, reinterpret_cast<LPVOID*>(&CHudChat__AddGameLine));
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite); g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw); g_pSquirrel<ScriptContext::CLIENT>->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", "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 "pch.h"
#include "clientruihooks.h"
#include "convar.h" #include "convar.h"
AUTOHOOK_INIT()
ConVar* Cvar_rui_drawEnable; ConVar* Cvar_rui_drawEnable;
typedef char (*DrawRUIFuncType)(void* a1, float* a2); // clang-format off
DrawRUIFuncType DrawRUIFunc; AUTOHOOK(DrawRUIFunc, engine.dll + 0xFC500,
bool, __fastcall, (void* a1, float* a2))
char DrawRUIFuncHook(void* a1, float* a2) // clang-format on
{ {
if (!Cvar_rui_drawEnable->GetBool()) if (!Cvar_rui_drawEnable->GetBool())
return 0; return 0;
@ -15,10 +16,9 @@ char DrawRUIFuncHook(void* a1, float* a2)
return DrawRUIFunc(a1, 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; Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn");
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xFC500, &DrawRUIFuncHook, reinterpret_cast<LPVOID*>(&DrawRUIFunc));
} }

View File

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

View File

@ -1,21 +1,21 @@
#include "pch.h" #include "pch.h"
#include "clientvideooverrides.h"
#include "modmanager.h" #include "modmanager.h"
#include "nsmem.h"
typedef void* (*BinkOpenType)(const char* path, uint32_t flags); AUTOHOOK_INIT()
BinkOpenType BinkOpen;
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()); std::string filename(fs::path(path).filename().string());
spdlog::info("BinkOpen {}", filename); spdlog::info("BinkOpen {}", filename);
// figure out which mod is handling the bink // figure out which mod is handling the bink
Mod* fileOwner = nullptr; 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; continue;
if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end()) 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) if (fileOwner)
{ {
// create new path // 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); return BinkOpen(binkPath.string().c_str(), flags);
} }
else else
return BinkOpen(path, flags); 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 // 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 // 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); module.Offset(0x459AD).NOP(6);
HookEnabler hook;
ENABLER_CREATEHOOK(
hook,
reinterpret_cast<void*>(GetProcAddress(GetModuleHandleA("bink2w64.dll"), "BinkOpen")),
&BinkOpenHook,
reinterpret_cast<LPVOID*>(&BinkOpen));
} }

View File

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

View File

@ -1,28 +1,9 @@
#include "pch.h" #include "pch.h"
#include "concommand.h" #include "concommand.h"
#include "gameutils.h"
#include "misccommands.h" #include "misccommands.h"
#include <iostream> #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 // Purpose: Returns true if this is a command
// Output : bool // Output : bool
@ -57,7 +38,7 @@ bool ConCommandBase::IsRegistered(void) const
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
bool ConCommandBase::IsFlagSet(int nFlags) 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; 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); 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 // From r5reloaded
class ConCommandBase class ConCommandBase
{ {
@ -113,8 +127,8 @@ class ConCommand : public ConCommandBase
void Init(void); void Init(void);
bool IsCommand(void) const; bool IsCommand(void) const;
void* m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase. FnCommandCallback_t m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase.
void* m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax'). FnCommandCompletionCallback m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax').
int m_nCallbackFlags {}; // 0x0050 int m_nCallbackFlags {}; // 0x0050
char pad_0054[4]; // 0x0054 char pad_0054[4]; // 0x0054
int unk0; // 0x0058 int unk0; // 0x0058
@ -122,6 +136,5 @@ class ConCommand : public ConCommandBase
}; // Size: 0x0060 }; // Size: 0x0060
void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags); void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags);
void InitialiseConCommands(HMODULE baseAddress); void RegisterConCommand(
const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, FnCommandCompletionCallback completionCallback);
#define MAKE_CONCMD(name, helpStr, flags, fn) RegisterConCommand(name, fn, helpStr, flags);

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 "pch.h"
#include "bits.h" #include "bits.h"
#include "cvar.h" #include "cvar.h"
#include "convar.h" #include "convar.h"
#include "hookutils.h"
#include "gameutils.h"
#include "sourceinterface.h" #include "sourceinterface.h"
#include <float.h>
typedef void (*ConVarRegisterType)( typedef void (*ConVarRegisterType)(
ConVar* pConVar, ConVar* pConVar,
const char* pszName, const char* pszName,
@ -27,25 +25,19 @@ ConVarMallocType conVarMalloc;
void* g_pConVar_Vtable = nullptr; void* g_pConVar_Vtable = nullptr;
void* g_pIConVar_Vtable = nullptr; void* g_pIConVar_Vtable = nullptr;
typedef bool (*CvarIsFlagSetType)(ConVar* self, int flags);
CvarIsFlagSetType CvarIsFlagSet;
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Purpose: ConVar interface initialization // Purpose: ConVar interface initialization
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void InitialiseConVars(HMODULE baseAddress) ON_DLL_LOAD("engine.dll", ConVar, (CModule module))
{ {
conVarMalloc = (ConVarMallocType)((char*)baseAddress + 0x415C20); conVarMalloc = module.Offset(0x415C20).As<ConVarMallocType>();
conVarRegister = (ConVarRegisterType)((char*)baseAddress + 0x417230); conVarRegister = module.Offset(0x417230).As<ConVarRegisterType>();
g_pConVar_Vtable = (char*)baseAddress + 0x67FD28; g_pConVar_Vtable = module.Offset(0x67FD28);
g_pIConVar_Vtable = (char*)baseAddress + 0x67FDC8; g_pIConVar_Vtable = module.Offset(0x67FDC8);
g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007"); R2::g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007");
g_pCVar = *g_pCVarInterface; R2::g_pCVar = *R2::g_pCVarInterface;
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x417FA0, &ConVar::IsFlagSet, reinterpret_cast<LPVOID*>(&CvarIsFlagSet));
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -74,7 +66,7 @@ ConVar::ConVar(
float fMin, float fMin,
bool bMax, bool bMax,
float fMax, float fMax,
void* pCallback) FnChangeCallback_t pCallback)
{ {
spdlog::info("Registering Convar {}", pszName); spdlog::info("Registering Convar {}", pszName);
@ -476,16 +468,12 @@ bool ConVar::IsCommand(void) const
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Purpose: Test each ConVar query before setting the value. // Purpose: Test each ConVar query before setting the value.
// Input : *pConVar - nFlags // Input : nFlags
// Output : False if change is permitted, true if not. // 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 return m_ConCommandBase.m_nFlags & nFlags;
if (pConVar && (nFlags == FCVAR_DEVELOPMENTONLY || nFlags == FCVAR_HIDDEN))
return false;
return CvarIsFlagSet(pConVar, nFlags);
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View File

@ -58,15 +58,58 @@
// ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd. // ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd.
#define FCVAR_SERVER_CANNOT_QUERY \ #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). (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 \ #define FCVAR_CLIENTCMD_CAN_EXECUTE \
(1 << 30) // IVEngineClient::ClientCmd is allowed to execute this command. (1 << 30) // IVEngineClient::ClientCmd is allowed to execute this command.
// Note: IVEngineClient::ClientCmd_Unrestricted can run any client 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 #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<<26)
// #define FCVAR_AVAILABLE (1<<27) // #define FCVAR_AVAILABLE (1<<27)
// #define FCVAR_AVAILABLE (1<<31) // #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 // Forward declarations
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -74,6 +117,8 @@ class ConCommandBase;
class ConCommand; class ConCommand;
class ConVar; class ConVar;
typedef void (*FnChangeCallback_t)(ConVar* var, const char* pOldValue, float flOldValue);
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Purpose: A console variable // Purpose: A console variable
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -91,7 +136,7 @@ class ConVar
float fMin, float fMin,
bool bMax, bool bMax,
float fMax, float fMax,
void* pCallback); FnChangeCallback_t pCallback);
~ConVar(void); ~ConVar(void);
const char* GetBaseName(void) const; const char* GetBaseName(void) const;
@ -125,7 +170,7 @@ class ConVar
bool IsRegistered(void) const; bool IsRegistered(void) const;
bool IsCommand(void) const; bool IsCommand(void) const;
static bool IsFlagSet(ConVar* pConVar, int nFlags); bool IsFlagSet(int nFlags) const;
struct CVValue_t struct CVValue_t
{ {
@ -145,5 +190,3 @@ class ConVar
void* m_pMalloc {}; // 0x0070 void* m_pMalloc {}; // 0x0070
char m_pPad80[10] {}; // 0x0080 char m_pPad80[10] {}; // 0x0080
}; // Size: 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; return allConVars;
} }
SourceInterface<CCvar>* g_pCVarInterface; // use the R2 namespace for game funcs
CCvar* g_pCVar; 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(); std::unordered_map<std::string, ConCommandBase*> DumpToMap();
}; };
extern SourceInterface<CCvar>* g_pCVarInterface; // use the R2 namespace for game funcs
extern CCvar* g_pCVar; namespace R2
{
extern SourceInterface<CCvar>* g_pCVarInterface;
extern CCvar* g_pCVar;
} // namespace R2

View File

@ -1,17 +1,9 @@
#include "pch.h" #include "pch.h"
#include "debugoverlay.h"
#include "dedicated.h" #include "dedicated.h"
#include "cvar.h" #include "cvar.h"
#include "vector.h"
struct Vector3 AUTOHOOK_INIT()
{
float x, y, z;
};
struct QAngle
{
float x, y, z, w;
};
enum OverlayType_t enum OverlayType_t
{ {
@ -40,7 +32,7 @@ struct OverlayBase_t
int m_nServerCount; // Latch server count, too int m_nServerCount; // Latch server count, too
float m_flEndTime; // When does this box go away float m_flEndTime; // When does this box go away
OverlayBase_t* m_pNextOverlay; OverlayBase_t* m_pNextOverlay;
__int64 m_pUnk; void* m_pUnk;
}; };
struct OverlayLine_t : public OverlayBase_t struct OverlayLine_t : public OverlayBase_t
@ -76,37 +68,18 @@ struct OverlayBox_t : public OverlayBase_t
int a; 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; static HMODULE sEngineModule;
typedef void (*DrawOverlayType)(OverlayBase_t* a1);
DrawOverlayType DrawOverlay;
typedef void (*RenderLineType)(Vector3 v1, Vector3 v2, Color c, bool bZBuffer); typedef void (*RenderLineType)(Vector3 v1, Vector3 v2, Color c, bool bZBuffer);
static RenderLineType RenderLine; static RenderLineType RenderLine;
typedef void (*RenderBoxType)(Vector3 vOrigin, QAngle angles, Vector3 vMins, Vector3 vMaxs, Color c, bool bZBuffer, bool bInsideOut); typedef void (*RenderBoxType)(Vector3 vOrigin, QAngle angles, Vector3 vMins, Vector3 vMaxs, Color c, bool bZBuffer, bool bInsideOut);
static RenderBoxType RenderBox; static RenderBoxType RenderBox;
static RenderBoxType RenderWireframeBox; static RenderBoxType RenderWireframeBox;
// engine.dll+0xABCB0 // clang-format off
void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay) AUTOHOOK(DrawOverlay, engine.dll + 0xABCB0,
void, __fastcall, (OverlayBase_t * pOverlay))
// clang-format on
{ {
EnterCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); // s_OverlayMutex EnterCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); // s_OverlayMutex
@ -151,24 +124,17 @@ void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay)
LeaveCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); LeaveCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38));
} }
void InitialiseDebugOverlay(HMODULE baseAddress) ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", DebugOverlay, ConVar, (CModule module))
{ {
if (IsDedicatedServer()) AUTOHOOK_DISPATCH()
return;
HookEnabler hook; RenderLine = module.Offset(0x192A70).As<RenderLineType>();
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xABCB0, &DrawOverlayHook, reinterpret_cast<LPVOID*>(&DrawOverlay)); RenderBox = module.Offset(0x192520).As<RenderBoxType>();
RenderWireframeBox = module.Offset(0x193DA0).As<RenderBoxType>();
RenderLine = reinterpret_cast<RenderLineType>((char*)baseAddress + 0x192A70); sEngineModule = reinterpret_cast<HMODULE>(module.m_nAddress);
RenderBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x192520);
RenderWireframeBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x193DA0);
sEngineModule = baseAddress;
// not in g_pCVar->FindVar by this point for whatever reason, so have to get from memory // 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->SetValue(false);
Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0"; Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0";
Cvar_enable_debug_overlays->AddFlags(FCVAR_CHEAT); 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 "pch.h"
#include "dedicated.h" #include "dedicated.h"
#include "hookutils.h" #include "tier0.h"
#include "gameutils.h" #include "playlist.h"
#include "r2engine.h"
#include "hoststate.h"
#include "serverauthentication.h" #include "serverauthentication.h"
#include "masterserver.h" #include "masterserver.h"
#include "printcommand.h"
AUTOHOOK_INIT()
using namespace R2;
bool IsDedicatedServer() bool IsDedicatedServer()
{ {
@ -33,32 +40,23 @@ void Sys_Printf(CDedicatedExports* dedicated, const char* msg)
spdlog::info("[DEDICATED SERVER] {}", msg); spdlog::info("[DEDICATED SERVER] {}", msg);
} }
typedef void (*CHostState__InitType)(CHostState* self);
void RunServer(CDedicatedExports* dedicated) void RunServer(CDedicatedExports* dedicated)
{ {
spdlog::info("CDedicatedExports::RunServer(): starting"); spdlog::info("CDedicatedExports::RunServer(): starting");
spdlog::info(CommandLine()->GetCmdLine()); spdlog::info(Tier0::CommandLine()->GetCmdLine());
// initialise engine // initialise engine
g_pEngine->Frame(); g_pEngine->Frame();
// add +map if not present // 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 // 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")) if (!Tier0::CommandLine()->CheckParm("+map"))
CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString()); Tier0::CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString());
// run server autoexec and re-run commandline // re-run commandline
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode);
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "stuffcmds", cmd_source_t::kCommandSrcCode); Cbuf_AddText(Cbuf_GetCurrentPlayer(), "stuffcmds", cmd_source_t::kCommandSrcCode);
Cbuf_Execute(); 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 // get tickinterval
ConVar* Cvar_base_tickinterval_mp = g_pCVar->FindVar("base_tickinterval_mp"); ConVar* Cvar_base_tickinterval_mp = g_pCVar->FindVar("base_tickinterval_mp");
@ -66,43 +64,31 @@ void RunServer(CDedicatedExports* dedicated)
double frameTitle = 0; double frameTitle = 0;
while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING) while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING)
{ {
double frameStart = Plat_FloatTime(); double frameStart = Tier0::Plat_FloatTime();
g_pEngine->Frame(); g_pEngine->Frame();
// only update the title after at least 500ms since the last update
if ((frameStart - frameTitle) > 0.5)
{
frameTitle = frameStart;
// this way of getting playercount/maxplayers honestly really sucks, but not got any other methods of doing it rn
const char* maxPlayers = GetCurrentPlaylistVar("max_players", false);
if (!maxPlayers)
maxPlayers = "6";
SetConsoleTitleA(fmt::format(
"{} - {} {}/{} players ({})",
g_MasterServerManager->m_sUnicodeServerName,
g_pHostState->m_levelName,
g_ServerAuthenticationManager->m_additionalPlayerData.size(),
maxPlayers,
GetCurrentPlaylistName())
.c_str());
}
std::this_thread::sleep_for(std::chrono::duration<double, std::ratio<1>>( std::this_thread::sleep_for(std::chrono::duration<double, std::ratio<1>>(
Cvar_base_tickinterval_mp->GetFloat() - fmin(Plat_FloatTime() - frameStart, 0.25))); Cvar_base_tickinterval_mp->GetFloat() - fmin(Tier0::Plat_FloatTime() - frameStart, 0.25)));
} }
} }
typedef bool (*IsGameActiveWindowType)(); // use server presence to update window title
IsGameActiveWindowType IsGameActiveWindow; class DedicatedConsoleServerPresence : public ServerPresenceReporter
bool IsGameActiveWindowHook()
{ {
return true; void ReportPresence(const ServerPresence* pServerPresence) override
} {
SetConsoleTitleA(fmt::format(
"{} - {} {}/{} players ({})",
pServerPresence->m_sServerName,
pServerPresence->m_MapName,
pServerPresence->m_iPlayerCount,
pServerPresence->m_iMaxPlayers,
pServerPresence->m_PlaylistName)
.c_str());
}
};
HANDLE consoleInputThreadHandle = NULL; HANDLE consoleInputThreadHandle = NULL;
DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter) DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
{ {
while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN) while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN)
@ -121,139 +107,106 @@ DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
{ {
input += "\n"; input += "\n";
Cbuf_AddText(Cbuf_GetCurrentPlayer(), input.c_str(), cmd_source_t::kCommandSrcCode); 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; return 0;
} }
#include "nsmem.h" // clang-format off
void InitialiseDedicated(HMODULE engineAddress) 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"); spdlog::info("InitialiseDedicated");
uintptr_t ea = (uintptr_t)engineAddress; AUTOHOOK_DISPATCH_MODULE("engine.dll")
{ // Host_Init
// Host_Init // prevent a particle init that relies on client dll
// prevent a particle init that relies on client dll module.Offset(0x156799).NOP(5);
NSMem::NOP(ea + 0x156799, 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 // CModAppSystemGroup::Create
// force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment // 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 // cmp => mov
NSMem::BytePatch(ptr + 1, "C6 87"); base.Offset(1).Patch("C6 87");
// 00 => 01 // 00 => 01
NSMem::BytePatch(ptr + 7, "01"); base.Offset(7).Patch("01");
} }
{ // Some init that i'm not sure of that crashes
// Some init that i'm not sure of that crashes // nop the call to it
// nop the call to it module.Offset(0x156A63).NOP(5);
NSMem::NOP(ea + 0x156A63, 5);
}
{ // runframeserver
// runframeserver // nop some access violations
// nop some access violations module.Offset(0x159819).NOP(17);
NSMem::NOP(ea + 0x159819, 17);
}
{ module.Offset(0x156B4C).NOP(7);
NSMem::NOP(ea + 0x156B4C, 7);
// previously patched these, took me a couple weeks to figure out they were the issue // 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 // 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 // so uhh, don't do that
// NSMem::NOP(ea + 0x156B4C + 7, 8); // 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
module.Offset(0xB934C).NOP(9);
{ // CEngineAPI::Connect
// HostState_State_NewGame // remove call to Shader_Connect
// nop an access violation module.Offset(0x1C4D7D).NOP(5);
NSMem::NOP(ea + 0xB934C, 9);
}
{ // Host_Init
// CEngineAPI::Connect // remove call to ui loading stuff
// remove call to Shader_Connect module.Offset(0x156595).NOP(5);
NSMem::NOP(ea + 0x1C4D7D, 5);
}
// currently does not work, crashes stuff, likely gotta keep this here // 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
// // CEngineAPI::Connect module.Offset(0x15A0BB).NOP(5);
// // remove calls to register ui rpak asset types
// NSMem::NOP(ea + 0x1C4E07, 5);
//}
{ // RunFrameServer
// Host_Init // nop a function that access violations
// remove call to ui loading stuff module.Offset(0x159BF3).NOP(5);
NSMem::NOP(ea + 0x156595, 5);
}
{ // func that checks if origin is inited
// some function that gets called from RunFrameServer // always return 1
// nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway module.Offset(0x183B70).Patch("B0 01 C3"); // mov al,01 ret
NSMem::NOP(ea + 0x15A0BB, 5);
}
{ // HostState_State_ChangeLevel
// RunFrameServer // nop clientinterface call
// nop a function that access violations module.Offset(0x1552ED).NOP(16);
NSMem::NOP(ea + 0x159BF3, 5);
}
{ // HostState_State_ChangeLevel
// func that checks if origin is inited // nop clientinterface call
// always return 1 module.Offset(0x155363).NOP(16);
NSMem::BytePatch(
ea + 0x183B70,
{
0xB0,
0x01, // mov al,01
0xC3 // ret
});
}
{ // IVideoMode::CreateGameWindow
// HostState_State_ChangeLevel // nop call to ShowWindow
// nop clientinterface call module.Offset(0x1CD146).NOP(5);
NSMem::NOP(ea + 0x1552ED, 16);
}
{
// HostState_State_ChangeLevel
// nop clientinterface call
NSMem::NOP(ea + 0x155363, 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);
}
CDedicatedExports* dedicatedExports = new CDedicatedExports; CDedicatedExports* dedicatedExports = new CDedicatedExports;
dedicatedExports->vtable = dedicatedExports; dedicatedExports->vtable = dedicatedExports;
dedicatedExports->Sys_Printf = Sys_Printf; dedicatedExports->Sys_Printf = Sys_Printf;
dedicatedExports->RunServer = RunServer; dedicatedExports->RunServer = RunServer;
CDedicatedExports** exports = (CDedicatedExports**)((char*)engineAddress + 0x13F0B668); *module.Offset(0x13F0B668).As<CDedicatedExports**>() = dedicatedExports;
*exports = dedicatedExports;
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)engineAddress + 0x1CDC80, &IsGameActiveWindowHook, reinterpret_cast<LPVOID*>(&IsGameActiveWindow));
// extra potential patches: // extra potential patches:
// nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow // nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow
@ -265,15 +218,19 @@ void InitialiseDedicated(HMODULE engineAddress)
// make sure it still gets registered // make sure it still gets registered
// add cmdline args that are good for dedi // add cmdline args that are good for dedi
CommandLine()->AppendParm("-nomenuvid", 0); Tier0::CommandLine()->AppendParm("-nomenuvid", 0);
CommandLine()->AppendParm("-nosound", 0); Tier0::CommandLine()->AppendParm("-nosound", 0);
CommandLine()->AppendParm("-windowed", 0); Tier0::CommandLine()->AppendParm("-windowed", 0);
CommandLine()->AppendParm("-nomessagebox", 0); Tier0::CommandLine()->AppendParm("-nomessagebox", 0);
CommandLine()->AppendParm("+host_preload_shaders", "0"); Tier0::CommandLine()->AppendParm("+host_preload_shaders", "0");
CommandLine()->AppendParm("+net_usesocketsforloopback", "1"); 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. // 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); HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode = 0; DWORD mode = 0;
@ -295,35 +252,42 @@ void InitialiseDedicated(HMODULE engineAddress)
spdlog::info("Quick Edit enabled by user request"); spdlog::info("Quick Edit enabled by user request");
// create console input thread // create console input thread
if (!CommandLine()->CheckParm("-noconsoleinput")) if (!Tier0::CommandLine()->CheckParm("-noconsoleinput"))
consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL); consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL);
else else
spdlog::info("Console input disabled by user request"); 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 // 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 // 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 // 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
module.GetExport("Tier0_InitOrigin").Patch("C3");
NSMem::BytePatch(
(uintptr_t)GetProcAddress(GetModuleHandleA("tier0.dll"), "Tier0_InitOrigin"),
{
0xC3 // ret
});
} }
typedef void (*PrintFatalSquirrelErrorType)(void* sqvm); // clang-format off
PrintFatalSquirrelErrorType PrintFatalSquirrelError; AUTOHOOK(PrintSquirrelError, server.dll + 0x794D0,
void PrintFatalSquirrelErrorHook(void* sqvm) void, __fastcall, (void* sqvm))
// clang-format on
{ {
PrintFatalSquirrelError(sqvm); PrintSquirrelError(sqvm);
g_pEngine->m_nQuitting = EngineQuitState::QUIT_TODESKTOP;
// 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; AUTOHOOK_DISPATCH_MODULE(server.dll)
ENABLER_CREATEHOOK(hook, baseAddress + 0x794D0, &PrintFatalSquirrelErrorHook, reinterpret_cast<LPVOID*>(&PrintFatalSquirrelError));
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 #pragma once
bool IsDedicatedServer(); bool IsDedicatedServer();
void InitialiseDedicated(HMODULE moduleAddress);
void InitialiseDedicatedOrigin(HMODULE baseAddress);
void InitialiseDedicatedServerGameDLL(HMODULE baseAddress);

View File

@ -1,11 +1,12 @@
#include "pch.h" #include "pch.h"
#include "dedicated.h" #include "dedicated.h"
#include "dedicatedmaterialsystem.h" #include "tier0.h"
#include "hookutils.h"
#include "gameutils.h"
#include "nsmem.h"
typedef HRESULT (*__stdcall D3D11CreateDeviceType)( AUTOHOOK_INIT()
// clang-format off
AUTOHOOK(D3D11CreateDevice, materialsystem_dx11.dll + 0xD9A0E,
HRESULT, __stdcall, (
void* pAdapter, void* pAdapter,
int DriverType, int DriverType,
HMODULE Software, HMODULE Software,
@ -15,108 +16,26 @@ typedef HRESULT (*__stdcall D3D11CreateDeviceType)(
UINT SDKVersion, UINT SDKVersion,
void** ppDevice, void** ppDevice,
int* pFeatureLevel, int* pFeatureLevel,
void** ppImmediateContext); void** ppImmediateContext))
D3D11CreateDeviceType D3D11CreateDevice; // clang-format on
HRESULT __stdcall D3D11CreateDeviceHook(
void* pAdapter,
int DriverType,
HMODULE Software,
UINT Flags,
int* pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
void** ppDevice,
int* pFeatureLevel,
void** ppImmediateContext)
{ {
// note: this is super duper temp pretty much just messing around with it // 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 // 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 // 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 // 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 DriverType = 5; // D3D_DRIVER_TYPE_WARP
return D3D11CreateDevice( return D3D11CreateDevice(
pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext); 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; AUTOHOOK_DISPATCH()
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xD9A0E, &D3D11CreateDeviceHook, reinterpret_cast<LPVOID*>(&D3D11CreateDevice));
// not using these for now since they're related to nopping renderthread/gamewindow i.e. very hard // CMaterialSystem::FindMaterial
//{ // make the game always use the error material
// // function that launches renderthread module.Offset(0x5F0F1).Patch("E9 34 03 00");
// 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));
} }

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 "pch.h"
#include "hooks.h"
#include "main.h" #include "main.h"
#include "squirrel.h"
#include "dedicated.h"
#include "dedicatedmaterialsystem.h"
#include "sourceconsole.h"
#include "logging.h" #include "logging.h"
#include "concommand.h" #include "crashhandler.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 "memalloc.h" #include "memalloc.h"
#include "maxplayers.h"
#include "languagehooks.h"
#include "audio.h"
#include "buildainfile.h"
#include "nsprefix.h" #include "nsprefix.h"
#include "serverchathooks.h"
#include "clientchathooks.h"
#include "localchatwriter.h"
#include "scriptservertoclientstringcommand.h"
#include "plugin_abi.h" #include "plugin_abi.h"
#include "plugins.h" #include "plugins.h"
#include "debugoverlay.h"
#include "clientvideooverrides.h"
#include "clientruihooks.h"
#include <string.h>
#include "version.h" #include "version.h"
#include "pch.h" #include "pch.h"
#include "scriptutility.h"
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h" #include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h" #include "rapidjson/writer.h"
#include "rapidjson/error/en.h" #include "rapidjson/error/en.h"
#include "exploitfixes.h"
#include "scriptjson.h" #include <string.h>
#include <filesystem>
typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject)); typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject));
bool initialised = false;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ {
switch (ul_reason_for_call) switch (ul_reason_for_call)
@ -72,18 +33,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
return TRUE; 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) void freeLibrary(HMODULE hLib)
{ {
if (!FreeLibrary(hLib)) if (!FreeLibrary(hLib))
@ -190,15 +139,13 @@ bool LoadPlugins()
bool InitialiseNorthstar() bool InitialiseNorthstar()
{ {
if (initialised) static bool bInitialised = false;
{ if (bInitialised)
// spdlog::warn("Called InitialiseNorthstar more than once!"); // it's actually 100% fine for that to happen
return false; return false;
}
initialised = true; bInitialised = true;
parseConfigurables(); InitialiseNorthstarPrefix();
InitialiseVersion(); InitialiseVersion();
// Fix some users' failure to connect to respawn datacenters // 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); curl_global_init_mem(CURL_GLOBAL_DEFAULT, _malloc_base, _free_base, _realloc_base, _strdup_base, _calloc_base);
InitialiseCrashHandler();
InitialiseLogging(); InitialiseLogging();
InstallInitialHooks(); InstallInitialHooks();
CreateLogFiles(); CreateLogFiles();
@ -213,94 +161,6 @@ bool InitialiseNorthstar()
// Write launcher version to log // Write launcher version to log
spdlog::info("NorthstarLauncher version: {}", version); 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 // run callbacks for any libraries that are already loaded by now
CallAllPendingDLLLoadCallbacks(); CallAllPendingDLLLoadCallbacks();

View File

@ -1,52 +1,63 @@
#include "pch.h" #include "pch.h"
#include "exploitfixes.h"
#include "exploitfixes_utf8parser.h"
#include "nsmem.h"
#include "cvar.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) \ #define BLOCKED_INFO(s) \
( \ ( \
[=]() -> bool \ [=]() -> bool \
{ \ { \
if (SHOULD_LOG) \ if (Cvar_ns_exploitfixes_log->GetBool()) \
{ \ { \
std::stringstream stream; \ std::stringstream stream; \
stream << "exploitfixes.cpp: " << BLOCK_PREFIX << s; \ stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \
spdlog::error(stream.str()); \ spdlog::error(stream.str()); \
} \ } \
return false; \ return false; \
}()) }())
struct Float3 // block bad netmessages
{
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; \
}
// Servers can literally request a screenshot from any client, yeah no // 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"); // clang-format off
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"); 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 // clang-format off
BLOCK_NETMSG_FUNC(Base_CmdKeyValues_ReadFromBuffer, "40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70"); 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; 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; 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: ";
{ if (bIsServerFrame)
// 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)
{ {
constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69; constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69;
if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT) 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; auto entry = msg->m_ConVars + i;
// Safety check for memory access // Safety check for memory access
if (NSMem::IsMemoryReadable(entry, sizeof(*entry))) if (MemoryAddress(entry).IsMemoryReadable(sizeof(*entry)))
{ {
// Find null terminators // Find null terminators
bool nameValid = false, valValid = false; bool nameValid = false, valValid = false;
for (int i = 0; i < ENTRY_STR_LEN; i++) 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) if (!nameValid || !valValid)
return BLOCKED_INFO("Missing null terminators"); 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( memcpy(
entry->name, entry->name,
realVar->m_ConCommandBase.m_pszName, pVar->m_ConCommandBase.m_pszName,
strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
bool isValidFlags = true; int iFlags = bIsServerFrame ? FCVAR_USERINFO : FCVAR_REPLICATED;
if (areWeServer) if (!pVar->IsFlagSet(iFlags))
{
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
{
return BLOCKED_INFO( 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 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 // prevent invalid user CMDs
KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg)) // 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 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 << ")"); 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) if (msg->m_nLength <= 0)
return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")"); 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 // 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) // Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing)
struct alignas(4) SV_CUserCmd 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 command_number;
DWORD tick_count; DWORD tick_count;
float command_time; float command_time;
Float3 worldViewAngles; Vector3 worldViewAngles;
BYTE gap18[4]; BYTE gap18[4];
Float3 localViewAngles; Vector3 localViewAngles;
Float3 attackangles; Vector3 attackangles;
Float3 move; Vector3 move;
DWORD buttons; DWORD buttons;
BYTE impulse; BYTE impulse;
short weaponselect; short weaponselect;
@ -221,8 +201,8 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
BYTE gap4C[24]; BYTE gap4C[24];
char headoffset; char headoffset;
BYTE gap65[11]; BYTE gap65[11];
Float3 cameraPos; Vector3 cameraPos;
Float3 cameraAngles; Vector3 cameraAngles;
BYTE gap88[4]; BYTE gap88[4];
int tickSomething; int tickSomething;
DWORD dword90; DWORD dword90;
@ -237,7 +217,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
std::string BLOCK_PREFIX = std::string BLOCK_PREFIX =
"ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): "; "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->worldViewAngles.MakeValid();
cmd->attackangles.MakeValid(); cmd->attackangles.MakeValid();
cmd->localViewAngles.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 // Fix invaid movement vector
cmd->move.MakeValid(); 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( BLOCKED_INFO(
"Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime "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; return;
INVALID_CMD: INVALID_CMD:
// Fix any gameplay-affecting cmd properties // Fix any gameplay-affecting cmd properties
// NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems // 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}; cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = {0, 0, 0};
@ -269,287 +250,209 @@ INVALID_CMD:
cmd->meleetarget = 0; cmd->meleetarget = 0;
} }
// basically: by default r2 isn't set as a valve mod, meaning that m_bRestrictServerCommands is false // ensure that GetLocalBaseClient().m_bRestrictServerCommands is set correctly, which the return value of this function controls
// this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients // this is IsValveMod in source, but we're making it IsRespawnMod now since valve didn't make this one
// especially since we have script commands this could theoretically be awful // clang-format off
KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ()) 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"); // somewhat temp, store the modname here, since we don't have a proper ptr in engine to it rn
spdlog::info("ExploitFixes: Overriding IsValveMod to {}...", result); int iSize = strlen(pModName);
return result; 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 -_- // ratelimit stringcmds, and prevent remote clients from calling commands that they shouldn't
// This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, R2::cmd_source_t commandSource);
KHOOK(
CrashFunc_ParseUTF8, // clang-format off
("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"), AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0,
bool, bool, __fastcall, (R2::CBaseClient* self, uint32_t unknown, const char* pCommandString))
__fastcall, // clang-format on
(INT64 * a1, DWORD* a2, char* strData))
{ {
if (Cvar_ns_should_log_all_clientcommands->GetBool())
spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString);
static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16"); if (!g_pServerLimits->CheckStringCommandLimits(self))
#ifdef _MSC_VER
if (_ReturnAddress() == targetRetAddr)
#else
if (__builtin_return_address(0) == targetRetAddr)
#endif
{ {
if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData)) R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands");
{ return false;
const char* BLOCK_PREFIX = "ParseUTF8 Hook: "; }
BLOCKED_INFO("Ignoring potentially-crashing utf8 string");
// verify the command we're trying to execute is FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, if it's a concommand
char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor
memset(commandBuf, 0, sizeof(commandBuf));
CCommand tempCommand = *(CCommand*)&commandBuf;
if (!CCommand__Tokenize(tempCommand, pCommandString, R2::cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC())
return false;
ConCommand* command = R2::g_pCVar->FindCommand(tempCommand.Arg(0));
// if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff
if (command && !command->IsFlagSet(FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS))
{
// ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients
if (IsDedicatedServer())
return false;
if (strcmp(self->m_UID, R2::g_pLocalPlayerUserID))
return false; return false;
}
} }
return oCrashFunc_ParseUTF8(a1, a2, strData); // 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())
// GetEntByIndex (called by ScriptGetEntByIndex) doesn't check for the index being out of bounds when it's
// above the max entity count. This allows it to be used to crash servers.
typedef void*(__fastcall* GetEntByIndexType)(int idx);
GetEntByIndexType GetEntByIndex;
static void* GetEntByIndexHook(int idx)
{
if (idx >= 0x4000)
{ {
spdlog::info("GetEntByIndex {} is out of bounds", idx); constexpr const char* blockedCommands[] = {
return nullptr; "emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
}
return GetEntByIndex(idx);
}
// RELOCATED FROM https://github.com/R2Northstar/NorthstarLauncher/commit/25dbf729cfc75107a0fcf0186924b58ecc05214b // These both execute a command for every single entity for some reason, nice one valve
// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read "pre_go_to_hub",
// out of the bounds of the output buffer. "pre_go_to_calibration",
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))
{
static constexpr int LZSS_LOOKSHIFT = 4;
uint32_t totalBytes = 0; "end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed
int getCmdByte = 0, cmdByte = 0; "load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
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 int iCmdLength = strlen(tempCommand.Arg(0));
for (auto exportName : ANTITAMPER_EXPORTS)
bool bIsBadCommand = false;
for (auto& blockedCommand : blockedCommands)
{ {
if (iCmdLength != strlen(blockedCommand))
continue;
auto address = (uintptr_t)GetProcAddress(GetModuleHandleA("server.dll"), exportName); for (int i = 0; tempCommand.Arg(0)[i]; i++)
if (!address) if (tolower(tempCommand.Arg(0)[i]) != blockedCommand[i])
{ goto NEXT_COMMAND; // break out of this loop, then go to next command
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); // this is a command we need to block
} return false;
NEXT_COMMAND:;
} }
} }
return CGameClient__ExecuteStringCommand(self, unknown, pCommandString);
} }
KHOOK( // prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines
SpecialClientCommand, bool bWasWritingStringTableSuccessful;
("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, // clang-format off
__fastcall, AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0,
(void* player, CCommand* command)) void, __fastcall, (void* self))
// clang-format on
{ {
bWasWritingStringTableSuccessful = true;
CBaseClient__SendServerInfo(self);
if (!bWasWritingStringTableSuccessful)
R2::CBaseClient__Disconnect(
self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting");
}
static ConVar* sv_cheats = g_pCVar->FindVar("sv_cheats"); // return null when GetEntByIndex is passed an index >= 0x4000
// this is called from exactly 1 script clientcommand that can be given an arbitrary index, and going above 0x4000 crashes
// clang-format off
AUTOHOOK(GetEntByIndex, server.dll + 0x2A8A50,
void*, __fastcall, (int i))
// clang-format on
{
const int MAX_ENT_IDX = 0x4000;
if (sv_cheats->GetBool()) if (i >= MAX_ENT_IDX)
return oSpecialClientCommand(player, command); // Don't block anything if sv_cheats is on {
spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX);
return nullptr;
}
// These are mostly from Portal 2 (sigh) return GetEntByIndex(i);
constexpr const char* blockedCommands[] = { }
"emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something) // clang-format off
AUTOHOOK(CL_CopyExistingEntity, engine.dll + 0x6F940,
// These both execute a command for every single entity for some reason, nice one valve bool, __fastcall, (void* a1))
"pre_go_to_hub", // clang-format on
"pre_go_to_calibration", {
struct CEntityReadInfo
"end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed {
"load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer() BYTE gap[40];
int nNewEntity;
}; };
if (command->ArgC() > 0) CEntityReadInfo* pReadInfo = (CEntityReadInfo*)a1;
if (pReadInfo->nNewEntity >= 0x1000 || pReadInfo->nNewEntity < 0)
{ {
std::string cmdStr = command->Arg(0); // Value isn't sanitized in release builds for
for (char& c : cmdStr) // every game powered by the Source Engine 1
c = tolower(c); // 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;
}
for (const char* blockedCommand : blockedCommands) return CL_CopyExistingEntity(a1);
}
ON_DLL_LOAD("engine.dll", EngineExploitFixes, (CModule module))
{
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
{
MemoryAddress writeAddress(&bWasWritingStringTableSuccessful - module.Offset(0x234EDC).m_nAddress);
MemoryAddress addr = module.Offset(0x234ED2);
addr.Patch("C7 05");
addr.Offset(2).Patch((BYTE*)&writeAddress, sizeof(writeAddress));
addr.Offset(6).Patch("00 00 00 00");
addr.Offset(10).NOP(5);
}
}
ON_DLL_LOAD_RELIESON("server.dll", ServerExploitFixes, ConVar, (CModule module))
{
AUTOHOOK_DISPATCH_MODULE(server.dll)
// ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue)
// this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches
module.Offset(0x153920).Patch("C3");
// Dumb ANTITAMPER patches (they negatively impact performance and security)
constexpr const char* ANTITAMPER_EXPORTS[] = {
"ANTITAMPER_SPOTCHECK_CODEMARKER",
"ANTITAMPER_TESTVALUE_CODEMARKER",
"ANTITAMPER_TRIGGER_CODEMARKER",
};
// Prevent these from actually doing anything
for (auto exportName : ANTITAMPER_EXPORTS)
{
MemoryAddress exportAddr = module.GetExport(exportName);
if (exportAddr)
{ {
if (cmdStr.find(blockedCommand) != std::string::npos) // Just return, none of them have any args or are userpurge
{ exportAddr.Patch("C3");
// Block this command spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr);
return true;
}
} }
} }
return oSpecialClientCommand(player, command); 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 =
void SetupKHook(KHook* hook) new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands");
{
if (hook->Setup()) Cvar_sv_cheats = R2::g_pCVar->FindVar("sv_cheats");
{
spdlog::debug("KHook::Setup(): Hooked at {}", hook->targetFuncAddr);
}
else
{
spdlog::critical("\tFAILED to initialize all exploit patches.");
// Force exit
MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR);
exit(0);
}
}
void ExploitFixes::LoadCallback_MultiModule(HMODULE baseAddress)
{
spdlog::info("ExploitFixes::LoadCallback_MultiModule({}) ...", (void*)baseAddress);
int hooksEnabled = 0;
for (auto itr = KHook::_allHooks.begin(); itr != KHook::_allHooks.end(); itr++)
{
auto curHook = *itr;
if (GetModuleHandleA(curHook->targetFunc.moduleName) == baseAddress)
{
SetupKHook(curHook);
itr = KHook::_allHooks.erase(itr); // Prevent repeated initialization
hooksEnabled++;
if (itr == KHook::_allHooks.end())
break;
}
}
spdlog::info("\tEnabled {} hooks.", hooksEnabled);
}
void ExploitFixes::LoadCallback_Full(HMODULE baseAddress)
{
spdlog::info("ExploitFixes::LoadCallback_Full ...");
spdlog::info("\tByte patching...");
DoBytePatches();
for (KHook* hook : KHook::_allHooks)
SetupKHook(hook);
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));
} }

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,87 +1,69 @@
#include "pch.h" #include "pch.h"
#include "filesystem.h" #include "filesystem.h"
#include "hooks.h"
#include "hookutils.h"
#include "sourceinterface.h" #include "sourceinterface.h"
#include "modmanager.h" #include "modmanager.h"
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
// hook forward declares AUTOHOOK_INIT()
typedef FileHandle_t (*ReadFileFromVPKType)(VPKData* vpkInfo, __int64* b, char* filename);
ReadFileFromVPKType readFileFromVPK;
FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename);
typedef bool (*ReadFromCacheType)(IFileSystem* filesystem, char* path, void* result); using namespace R2;
ReadFromCacheType readFromCache;
bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result);
typedef void (*AddSearchPathType)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType); bool bReadingOriginalFile = false;
AddSearchPathType addSearchPathOriginal; std::string sCurrentModPath;
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
typedef FileHandle_t (*ReadFileFromFilesystemType)( ConVar* Cvar_ns_fs_log_reads;
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);
typedef VPKData* (*MountVPKType)(IFileSystem* fileSystem, const char* vpkPath); // use the R2 namespace for game funcs
MountVPKType mountVPK; namespace R2
VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath);
bool readingOriginalFile;
std::string currentModPath;
SourceInterface<IFileSystem>* g_Filesystem;
void InitialiseFilesystem(HMODULE baseAddress)
{ {
g_Filesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017"); SourceInterface<IFileSystem>* g_pFilesystem;
// create hooks std::string ReadVPKFile(const char* path)
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));
}
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);
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); // read scripts.rson file, todo: check if this can be overwritten
fileStream.write(data, bytesRead); FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0);
} while (bytesRead == std::size(data));
(*g_Filesystem)->m_vtable2->Close(*g_Filesystem, fileHandle); std::stringstream fileStream;
int bytesRead = 0;
char data[4096];
do
{
bytesRead = (*g_pFilesystem)->m_vtable2->Read(&(*g_pFilesystem)->m_vtable2, data, (int)std::size(data), fileHandle);
fileStream.write(data, bytesRead);
} while (bytesRead == std::size(data));
return fileStream.str(); (*g_pFilesystem)->m_vtable2->Close(*g_pFilesystem, fileHandle);
}
std::string ReadVPKOriginalFile(const char* path) return fileStream.str();
}
std::string ReadVPKOriginalFile(const char* path)
{
// todo: should probably set search path to be g_pModName here also
bReadingOriginalFile = true;
std::string ret = ReadVPKFile(path);
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
{ {
readingOriginalFile = true; AddSearchPath(fileSystem, pPath, pathID, addType);
std::string ret = ReadVPKFile(path);
readingOriginalFile = false;
return ret; // make sure current mod paths are at head
if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
{
AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
} }
void SetNewModSearchPaths(Mod* mod) 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 // 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 (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( AddSearchPath(
&*(*g_Filesystem), (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD); &*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
currentModPath = (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string(); sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string();
} }
} }
else // push compiled to head 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; return false;
if (shouldCompile) if (shouldCompile)
(*g_ModManager).CompileAssetsForFile(path); g_pModManager->CompileAssetsForFile(pPath);
// idk how efficient the lexically normal check is // 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 // 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()); auto file = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path(pPath)));
if (file != g_ModManager->m_modFiles.end()) if (file != g_pModManager->m_ModFiles.end())
{ {
SetNewModSearchPaths(file->second.owningMod); SetNewModSearchPaths(file->second.m_pOwningMod);
return true; return true;
} }
return false; 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 if (TryReplaceFile(pPath, true))
// spdlog::info("ReadFileFromVPKHook {} {}", filename, vpkInfo->path); return false;
// there is literally never any reason to compile here, since we'll always compile in ReadFileFromFilesystemHook in the same codepath return ReadFromCache(filesystem, pPath, result);
// this is called }
// 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)) if (TryReplaceFile(filename, false))
{ {
*b = -1; *b = -1;
return b; 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 TryReplaceFile(pPath, true);
// spdlog::info("ReadFromCacheHook {}", path); return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename);
if (TryReplaceFile(path, true))
return false;
return readFromCache(filesystem, path, result);
} }
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 for (Mod mod : g_pModManager->m_LoadedMods)
if (!strcmp(pathID, "GAME") && currentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
{ {
addSearchPathOriginal(fileSystem, currentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD); if (!mod.m_bEnabled)
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)
continue; continue;
for (ModVPKEntry& vpkEntry : mod.Vpks) 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 // resolve vpk name and try to load one with the same name
// todo: we should be unloading these on map unload manually // 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()); std::string modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string());
if (mapName.compare(modMapName)) if (mapName.compare(modMapName))
continue; 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 if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here
ret = loaded; ret = loaded;
} }
@ -203,3 +173,14 @@ VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
return ret; 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)( FileHandle_t (*Open)(
IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pOptions, const char* pathID, int64_t unknown); IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pOptions, const char* pathID, int64_t unknown);
void (*Close)(IFileSystem* fileSystem, FileHandle_t file); 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); bool (*FileExists)(IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pPathID);
}; };
@ -67,8 +68,11 @@ class IFileSystem
VTable2* m_vtable2; VTable2* m_vtable2;
}; };
std::string ReadVPKFile(const char* path); // use the R2 namespace for game funcs
std::string ReadVPKOriginalFile(const char* path); namespace R2
{
extern SourceInterface<IFileSystem>* g_pFilesystem;
void InitialiseFilesystem(HMODULE baseAddress); std::string ReadVPKFile(const char* path);
extern SourceInterface<IFileSystem>* g_Filesystem; std::string ReadVPKOriginalFile(const char* path);
} // 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 "pch.h"
#include "hooks.h"
#include "hookutils.h"
#include "sigscanning.h"
#include "dedicated.h" #include "dedicated.h"
#include <iostream>
#include <wchar.h> #include <wchar.h>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include <Psapi.h>
typedef LPSTR (*GetCommandLineAType)(); AUTOHOOK_INIT()
LPSTR GetCommandLineAHook();
typedef HMODULE (*LoadLibraryExAType)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); // called from the ON_DLL_LOAD macros
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); __dllLoadCallback::__dllLoadCallback(
eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn)
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()
{ {
if (MH_Initialize() != MH_OK) // parse reliesOn array from string
spdlog::error("MH_Initialize (minhook initialization) failed"); std::vector<std::string> reliesOnArray;
HookEnabler hook; if (reliesOn.length() && reliesOn[0] != '(')
ENABLER_CREATEHOOK( {
hook, reinterpret_cast<void*>(&GetCommandLineA), &GetCommandLineAHook, reinterpret_cast<LPVOID*>(&GetCommandLineAOriginal)); reliesOnArray.push_back(reliesOn);
ENABLER_CREATEHOOK( }
hook, reinterpret_cast<void*>(&LoadLibraryExA), &LoadLibraryExAHook, reinterpret_cast<LPVOID*>(&LoadLibraryExAOriginal)); else
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryA), &LoadLibraryAHook, reinterpret_cast<LPVOID*>(&LoadLibraryAOriginal)); {
ENABLER_CREATEHOOK( // follows the format (tag, tag, tag)
hook, reinterpret_cast<void*>(&LoadLibraryExW), &LoadLibraryExWHook, reinterpret_cast<LPVOID*>(&LoadLibraryExWOriginal)); std::string sCurrentTag;
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryW), &LoadLibraryWHook, reinterpret_cast<LPVOID*>(&LoadLibraryWOriginal)); 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];
}
}
}
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;
}
}
} }
LPSTR GetCommandLineAHook() 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* cmdlineModified;
static char* cmdlineOrg; static char* cmdlineOrg;
if (cmdlineOrg == nullptr || cmdlineModified == nullptr) if (cmdlineOrg == nullptr || cmdlineModified == nullptr)
{ {
cmdlineOrg = GetCommandLineAOriginal(); cmdlineOrg = _GetCommandLineA();
bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument
bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs"); bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs");
@ -111,77 +243,86 @@ LPSTR GetCommandLineAHook()
return cmdlineModified; return cmdlineModified;
} }
// dll load callback stuff std::vector<std::string> calledTags;
// 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);
}
void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress) void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress)
{ {
for (auto& callbackStruct : dllLoadCallbacks) CModule cModule(moduleAddress);
while (true)
{ {
if (!callbackStruct->called && bool bDoneCalling = true;
strstr(lpLibFileName + (strlen(lpLibFileName) - callbackStruct->dll.length()), callbackStruct->dll.c_str()) != nullptr)
for (auto& callbackStruct : GetDllLoadCallbacks())
{ {
callbackStruct->callback(moduleAddress); if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
callbackStruct->called = true; {
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) 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()); bool bDoneCalling = true;
const wchar_t* callbackDll = wcharStrDll.c_str();
if (!callbackStruct->called && wcsstr(lpLibFileName + (wcslen(lpLibFileName) - wcharStrDll.length()), callbackDll) != nullptr) for (auto& callbackStruct : GetDllLoadCallbacks())
{ {
callbackStruct->callback(moduleAddress); if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
callbackStruct->called = true; {
bool bShouldContinue = false;
if (!callbackStruct.reliesOn.empty())
{
for (std::string tag : callbackStruct.reliesOn)
{
if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
{
bDoneCalling = false;
bShouldContinue = true;
break;
}
}
}
if (bShouldContinue)
continue;
callbackStruct.callback(moduleAddress);
calledTags.push_back(callbackStruct.tag);
callbackStruct.called = true;
}
} }
if (bDoneCalling)
break;
} }
} }
@ -208,65 +349,78 @@ void CallAllPendingDLLLoadCallbacks()
} }
} }
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) // clang-format off
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, LoadLibraryExA,
HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
// clang-format on
{ {
HMODULE moduleAddress;
// replace xinput dll with one that has ASLR
if (!strncmp(lpLibFileName, "XInput1_3.dll", 14)) if (!strncmp(lpLibFileName, "XInput1_3.dll", 14))
{ {
HMODULE moduleAddress = LoadLibraryExAOriginal("XInput9_1_0.dll", hFile, dwFlags); moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags);
if (moduleAddress)
{ if (!moduleAddress)
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
}
else
{ {
MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR); MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR);
exit(-1); exit(-1);
return nullptr;
} }
return moduleAddress;
} }
else else
{ moduleAddress = _LoadLibraryExA(lpLibFileName, hFile, dwFlags);
HMODULE moduleAddress = LoadLibraryExAOriginal(lpLibFileName, hFile, dwFlags);
if (moduleAddress)
{
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
}
return moduleAddress;
}
}
HMODULE LoadLibraryAHook(LPCSTR lpLibFileName)
{
HMODULE moduleAddress = LoadLibraryAOriginal(lpLibFileName);
if (moduleAddress) if (moduleAddress)
{
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
}
return moduleAddress; return moduleAddress;
} }
HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) // clang-format off
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryA, LoadLibraryA,
HMODULE, WINAPI, (LPCSTR lpLibFileName))
// clang-format on
{ {
HMODULE moduleAddress = LoadLibraryExWOriginal(lpLibFileName, hFile, dwFlags); HMODULE moduleAddress = _LoadLibraryA(lpLibFileName);
if (moduleAddress) if (moduleAddress)
{ CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
}
return moduleAddress; return moduleAddress;
} }
HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName) // clang-format off
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExW, LoadLibraryExW,
HMODULE, WINAPI, (LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
// clang-format on
{ {
HMODULE moduleAddress = LoadLibraryWOriginal(lpLibFileName); HMODULE moduleAddress = _LoadLibraryExW(lpLibFileName, hFile, dwFlags);
if (moduleAddress) if (moduleAddress)
{
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
}
return moduleAddress; return moduleAddress;
} }
// clang-format off
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryW, LoadLibraryW,
HMODULE, WINAPI, (LPCWSTR lpLibFileName))
// clang-format on
{
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 #pragma once
#include "memory.h"
#include <string> #include <string>
#include <iostream>
void InstallInitialHooks(); void InstallInitialHooks();
typedef void (*DllLoadCallbackFuncType)(HMODULE moduleAddress); typedef void (*DllLoadCallbackFuncType)(CModule moduleAddress);
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback); void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback); void AddDllLoadCallbackForDedicatedServer(
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback); 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(); 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 "pch.h"
#include "keyvalues.h"
#include "modmanager.h" #include "modmanager.h"
#include "filesystem.h" #include "filesystem.h"
#include "hookutils.h"
#include <fstream> #include <fstream>
// hook forward defs AUTOHOOK_INIT()
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);
}
void ModManager::TryBuildKeyValues(const char* filename) void ModManager::TryBuildKeyValues(const char* filename)
{ {
spdlog::info("Building KeyValues for file {}", 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 compiledPath = GetCompiledAssetsPath() / filename;
fs::path compiledDir = compiledPath.parent_path(); fs::path compiledDir = compiledPath.parent_path();
fs::create_directories(compiledDir); 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 // 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 // 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; continue;
size_t fileHash = STR_HASH(normalisedPath); size_t fileHash = STR_HASH(normalisedPath);
auto modKv = m_loadedMods[i].KeyValues.find(fileHash); auto modKv = m_LoadedMods[i].KeyValues.find(fileHash);
if (modKv != m_loadedMods[i].KeyValues.end()) if (modKv != m_LoadedMods[i].KeyValues.end())
{ {
// should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt" // 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::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"; newKvs += "\"\n";
// load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons) // 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()) if (!originalFile.length())
{ {
@ -96,7 +67,6 @@ void ModManager::TryBuildKeyValues(const char* filename)
char rootName[64]; char rootName[64];
memset(rootName, 0, sizeof(rootName)); 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 // iterate until we hit an ascii char that isn't in a # command or comment to get root obj name
int i = 0; int i = 0;
@ -127,11 +97,36 @@ void ModManager::TryBuildKeyValues(const char* filename)
writeStream.close(); writeStream.close();
ModOverrideFile overrideFile; ModOverrideFile overrideFile;
overrideFile.owningMod = nullptr; overrideFile.m_pOwningMod = nullptr;
overrideFile.path = normalisedPath; overrideFile.m_Path = normalisedPath;
if (m_modFiles.find(normalisedPath) == m_modFiles.end()) if (m_ModFiles.find(normalisedPath) == m_ModFiles.end())
m_modFiles.insert(std::make_pair(normalisedPath, overrideFile)); m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile));
else 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 "pch.h"
#include "languagehooks.h" #include "tier0.h"
#include "gameutils.h"
#include <filesystem> #include <filesystem>
#include <regex> #include <regex>
namespace fs = std::filesystem; AUTOHOOK_INIT()
typedef char* (*GetGameLanguageType)();
char* GetGameLanguage();
typedef LANGID (*Tier0_DetectDefaultLanguageType)(); typedef LANGID (*Tier0_DetectDefaultLanguageType)();
GetGameLanguageType GetGameLanguageOriginal;
bool CheckLangAudioExists(char* lang) bool CheckLangAudioExists(char* lang)
{ {
std::string path {"r2\\sound\\general_"}; std::string path {"r2\\sound\\general_"};
@ -52,7 +47,10 @@ std::string GetAnyInstalledAudioLanguage()
return "NO LANGUAGE DETECTED"; 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 tier0Handle = GetModuleHandleA("tier0.dll");
auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage"); auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage");
@ -60,7 +58,7 @@ char* GetGameLanguageHook()
bool& canOriginDictateLang = *(bool*)((char*)tier0Handle + 0xA9A90); bool& canOriginDictateLang = *(bool*)((char*)tier0Handle + 0xA9A90);
const char* forcedLanguage; const char* forcedLanguage;
if (CommandLine()->CheckParm("-language", &forcedLanguage)) if (Tier0::CommandLine()->CheckParm("-language", &forcedLanguage))
{ {
if (!CheckLangAudioExists((char*)forcedLanguage)) if (!CheckLangAudioExists((char*)forcedLanguage))
{ {
@ -79,7 +77,7 @@ char* GetGameLanguageHook()
canOriginDictateLang = true; // let it try canOriginDictateLang = true; // let it try
{ {
auto lang = GetGameLanguageOriginal(); auto lang = GetGameLanguage();
if (!CheckLangAudioExists(lang)) if (!CheckLangAudioExists(lang))
{ {
if (strcmp(lang, "russian") != 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 Tier0_DetectDefaultLanguageType(); // force the global in tier0 to be populated with language inferred from user's system rather than
// defaulting to Russian // defaulting to Russian
canOriginDictateLang = false; // Origin has no say anymore, we will fallback to user's system setup language 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); spdlog::info("Detected system language: {}", lang);
if (!CheckLangAudioExists(lang)) if (!CheckLangAudioExists(lang))
{ {
@ -112,8 +110,7 @@ char* GetGameLanguageHook()
return lang; return lang;
} }
void InitialiseTier0LanguageHooks(HMODULE baseAddress) ON_DLL_LOAD_CLIENT("tier0.dll", LanguageHooks, (CModule module))
{ {
HookEnabler hook; AUTOHOOK_DISPATCH()
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xF560, &GetGameLanguageHook, reinterpret_cast<LPVOID*>(&GetGameLanguageOriginal));
} }

View File

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

View File

@ -1,76 +1,44 @@
#include "pch.h" #include "pch.h"
#include "latencyflex.h"
#include "hookutils.h"
#include "convar.h" #include "convar.h"
typedef void (*OnRenderStartType)(); AUTOHOOK_INIT()
OnRenderStartType OnRenderStart;
ConVar* Cvar_r_latencyflex; ConVar* Cvar_r_latencyflex;
HMODULE m_lfxModule {}; void (*m_winelfx_WaitAndBeginFrame)();
typedef void (*PFN_lfx_WaitAndBeginFrame)();
PFN_lfx_WaitAndBeginFrame m_lfx_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->GetBool() && m_winelfx_WaitAndBeginFrame)
if (Cvar_r_latencyflex->GetInt()) m_winelfx_WaitAndBeginFrame();
{
if (m_lfx_WaitAndBeginFrame)
{
m_lfx_WaitAndBeginFrame();
}
}
OnRenderStart(); OnRenderStart();
} }
void InitialiseLatencyFleX(HMODULE baseAddress) ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module))
{ {
// Connect to the LatencyFleX service // Connect to the LatencyFleX service
// LatencyFleX is an open source vendor agnostic replacement for Nvidia Reflex input latency reduction technology. // LatencyFleX is an open source vendor agnostic replacement for Nvidia Reflex input latency reduction technology.
// https://ishitatsuyuki.github.io/post/latencyflex/ // https://ishitatsuyuki.github.io/post/latencyflex/
const auto lfxModuleName = "latencyflex_layer.dll"; HMODULE pLfxModule;
const auto lfxModuleNameFallback = "latencyflex_wine.dll";
auto useFallbackEntrypoints = false;
// Load LatencyFleX library. if (pLfxModule = LoadLibraryA("latencyflex_layer.dll"))
m_lfxModule = ::LoadLibraryA(lfxModuleName); m_winelfx_WaitAndBeginFrame =
if (m_lfxModule == nullptr && ::GetLastError() == ERROR_MOD_NOT_FOUND) 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: Primary LatencyFleX library not found, trying fallback."); spdlog::info("Unable to load LatencyFleX library, LatencyFleX disabled.");
m_lfxModule = ::LoadLibraryA(lfxModuleNameFallback);
if (m_lfxModule == nullptr)
{
if (::GetLastError() == ERROR_MOD_NOT_FOUND)
{
spdlog::info("LFX: Fallback LatencyFleX library not found.");
}
else
{
spdlog::info("LFX: Error loading fallback LatencyFleX library - Code: {}", ::GetLastError());
}
return;
}
useFallbackEntrypoints = true;
}
else if (m_lfxModule == nullptr)
{
spdlog::info("LFX: Error loading primary LatencyFleX library - Code: {}", ::GetLastError());
return; return;
} }
m_lfx_WaitAndBeginFrame = reinterpret_cast<PFN_lfx_WaitAndBeginFrame>(reinterpret_cast<void*>( AUTOHOOK_DISPATCH()
GetProcAddress(m_lfxModule, !useFallbackEntrypoints ? "lfx_WaitAndBeginFrame" : "winelfx_WaitAndBeginFrame")));
spdlog::info("LFX: Initialized.");
spdlog::info("LatencyFleX initialized.");
Cvar_r_latencyflex = new ConVar("r_latencyflex", "1", FCVAR_ARCHIVE, "Whether or not to use LatencyFleX input latency reduction."); 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* SetVerticalScrollbar)(vgui_BaseRichText* self, bool state);
void(__fastcall* SetMaximumCharCount)(vgui_BaseRichText* self, int maxChars); 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* InsertIndentChange)(vgui_BaseRichText* self, int pixelsIndent);
void(__fastcall* InsertClickableTextStart)(vgui_BaseRichText* self, const char* pchClickAction); void(__fastcall* InsertClickableTextStart)(vgui_BaseRichText* self, const char* pchClickAction);
void(__fastcall* InsertClickableTextEnd)(vgui_BaseRichText* self); void(__fastcall* InsertClickableTextEnd)(vgui_BaseRichText* self);
void(__fastcall* InsertPossibleURLString)( void(__fastcall* InsertPossibleURLString)(vgui_BaseRichText* self, const char* text, Color URLTextColor, Color normalTextColor);
vgui_BaseRichText* self, const char* text, vgui_Color URLTextColor, vgui_Color normalTextColor);
void(__fastcall* InsertFade)(vgui_BaseRichText* self, float flSustain, float flLength); void(__fastcall* InsertFade)(vgui_BaseRichText* self, float flSustain, float flLength);
void(__fastcall* ResetAllFades)(vgui_BaseRichText* self, bool bHold, bool bOnlyExpired, float flNewSustain); void(__fastcall* ResetAllFades)(vgui_BaseRichText* self, bool bHold, bool bOnlyExpired, float flNewSustain);
void(__fastcall* SetToFullHeight)(vgui_BaseRichText* self); void(__fastcall* SetToFullHeight)(vgui_BaseRichText* self);
@ -81,25 +80,25 @@ LocalChatWriter::SwatchColor swatchColors[4] = {
LocalChatWriter::NetworkNameColor, LocalChatWriter::NetworkNameColor,
}; };
vgui_Color darkColors[8] = { Color darkColors[8] = {
vgui_Color {0, 0, 0, 255}, Color {0, 0, 0, 255},
vgui_Color {205, 49, 49, 255}, Color {205, 49, 49, 255},
vgui_Color {13, 188, 121, 255}, Color {13, 188, 121, 255},
vgui_Color {229, 229, 16, 255}, Color {229, 229, 16, 255},
vgui_Color {36, 114, 200, 255}, Color {36, 114, 200, 255},
vgui_Color {188, 63, 188, 255}, Color {188, 63, 188, 255},
vgui_Color {17, 168, 205, 255}, Color {17, 168, 205, 255},
vgui_Color {229, 229, 229, 255}}; Color {229, 229, 229, 255}};
vgui_Color lightColors[8] = { Color lightColors[8] = {
vgui_Color {102, 102, 102, 255}, Color {102, 102, 102, 255},
vgui_Color {241, 76, 76, 255}, Color {241, 76, 76, 255},
vgui_Color {35, 209, 139, 255}, Color {35, 209, 139, 255},
vgui_Color {245, 245, 67, 255}, Color {245, 245, 67, 255},
vgui_Color {59, 142, 234, 255}, Color {59, 142, 234, 255},
vgui_Color {214, 112, 214, 255}, Color {214, 112, 214, 255},
vgui_Color {41, 184, 219, 255}, Color {41, 184, 219, 255},
vgui_Color {255, 255, 255, 255}}; Color {255, 255, 255, 255}};
class AnsiEscapeParser class AnsiEscapeParser
{ {
@ -144,7 +143,7 @@ class AnsiEscapeParser
LocalChatWriter* m_writer; LocalChatWriter* m_writer;
Next m_next = Next::ControlType; 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) Next HandleControlType(unsigned long val)
{ {
@ -190,7 +189,7 @@ class AnsiEscapeParser
// Next values are r,g,b // Next values are r,g,b
if (val == 2) if (val == 2)
{ {
m_expandedColor = {0, 0, 0, 255}; m_expandedColor.SetColor(0, 0, 0, 255);
return Next::ForegroundR; return Next::ForegroundR;
} }
// Next value is 8-bit swatch color // Next value is 8-bit swatch color
@ -219,13 +218,12 @@ class AnsiEscapeParser
unsigned char blue = code % 6; unsigned char blue = code % 6;
unsigned char green = ((code - blue) / 6) % 6; unsigned char green = ((code - blue) / 6) % 6;
unsigned char red = (code - blue - (green * 6)) / 36; unsigned char red = (code - blue - (green * 6)) / 36;
m_writer->InsertColorChange( m_writer->InsertColorChange(Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
vgui_Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
} }
else if (val < UCHAR_MAX) else if (val < UCHAR_MAX)
{ {
unsigned char brightness = (val - 232) * 10 + 8; 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; return Next::ControlType;
@ -236,7 +234,7 @@ class AnsiEscapeParser
if (val >= UCHAR_MAX) if (val >= UCHAR_MAX)
return Next::ControlType; return Next::ControlType;
m_expandedColor.r = (unsigned char)val; m_expandedColor[0] = (unsigned char)val;
return Next::ForegroundG; return Next::ForegroundG;
} }
@ -245,7 +243,7 @@ class AnsiEscapeParser
if (val >= UCHAR_MAX) if (val >= UCHAR_MAX)
return Next::ControlType; return Next::ControlType;
m_expandedColor.g = (unsigned char)val; m_expandedColor[1] = (unsigned char)val;
return Next::ForegroundB; return Next::ForegroundB;
} }
@ -254,7 +252,7 @@ class AnsiEscapeParser
if (val >= UCHAR_MAX) if (val >= UCHAR_MAX)
return Next::ControlType; return Next::ControlType;
m_expandedColor.b = (unsigned char)val; m_expandedColor[2] = (unsigned char)val;
m_writer->InsertColorChange(m_expandedColor); m_writer->InsertColorChange(m_expandedColor);
return Next::ControlType; return Next::ControlType;
} }
@ -280,12 +278,11 @@ void LocalChatWriter::Write(const char* str)
if (startOfEscape != str) if (startOfEscape != str)
{ {
// There is some text before the escape sequence, just print that // There is some text before the escape sequence, just print that
size_t copyChars = startOfEscape - str; size_t copyChars = startOfEscape - str;
if (copyChars > 255) if (copyChars > 255)
copyChars = 255; copyChars = 255;
strncpy(writeBuffer, str, copyChars);
writeBuffer[copyChars] = 0; strncpy_s(writeBuffer, copyChars + 1, str, copyChars);
InsertText(writeBuffer); InsertText(writeBuffer);
} }
@ -320,6 +317,8 @@ void LocalChatWriter::InsertChar(wchar_t ch)
void LocalChatWriter::InsertText(const char* str) void LocalChatWriter::InsertText(const char* str)
{ {
spdlog::info(str);
WCHAR messageUnicode[288]; WCHAR messageUnicode[288];
ConvertANSIToUnicode(str, -1, messageUnicode, 274); ConvertANSIToUnicode(str, -1, messageUnicode, 274);
@ -347,7 +346,7 @@ void LocalChatWriter::InsertText(const wchar_t* str)
InsertDefaultFade(); InsertDefaultFade();
} }
void LocalChatWriter::InsertColorChange(vgui_Color color) void LocalChatWriter::InsertColorChange(Color color)
{ {
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next) 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) switch (swatchColor)
{ {
case LocalChatWriter::MainTextColor: case LocalChatWriter::MainTextColor:
return hud->m_mainTextColor; return hud->m_mainTextColor;
case LocalChatWriter::SameTeamNameColor: case LocalChatWriter::SameTeamNameColor:
return hud->m_sameTeamColor; return hud->m_sameTeamColor;
case LocalChatWriter::EnemyTeamNameColor: case LocalChatWriter::EnemyTeamNameColor:
return hud->m_enemyTeamColor; return hud->m_enemyTeamColor;
case LocalChatWriter::NetworkNameColor: case LocalChatWriter::NetworkNameColor:
return hud->m_networkNameColor; return hud->m_networkNameColor;
} }
return vgui_Color {0, 0, 0, 0};
return Color(0, 0, 0, 0);
} }
void LocalChatWriter::InsertSwatchColorChange(SwatchColor swatchColor) 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); gGameSettings = module.Offset(0x11BAA48).As<CGameSettings**>();
gChatFadeLength = (CGameFloatVar**)((char*)baseAddress + 0x11BAB78); gChatFadeLength = module.Offset(0x11BAB78).As<CGameFloatVar**>();
gChatFadeSustain = (CGameFloatVar**)((char*)baseAddress + 0x11BAC08); gChatFadeSustain = module.Offset(0x11BAC08).As<CGameFloatVar**>();
CHudChat::allHuds = (CHudChat**)((char*)baseAddress + 0x11BA9E8); 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 #pragma once
#include "pch.h" #include "pch.h"
#include "color.h"
struct vgui_Color
{
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
};
class vgui_BaseRichText; class vgui_BaseRichText;
@ -18,10 +11,10 @@ class CHudChat
char unknown1[720]; char unknown1[720];
vgui_Color m_sameTeamColor; Color m_sameTeamColor;
vgui_Color m_enemyTeamColor; Color m_enemyTeamColor;
vgui_Color m_mainTextColor; Color m_mainTextColor;
vgui_Color m_networkNameColor; Color m_networkNameColor;
char unknown2[12]; char unknown2[12];
@ -61,7 +54,7 @@ class LocalChatWriter
void InsertChar(wchar_t ch); void InsertChar(wchar_t ch);
void InsertText(const char* str); void InsertText(const char* str);
void InsertText(const wchar_t* str); void InsertText(const wchar_t* str);
void InsertColorChange(vgui_Color color); void InsertColorChange(Color color);
void InsertSwatchColorChange(SwatchColor color); void InsertSwatchColorChange(SwatchColor color);
private: private:
@ -70,5 +63,3 @@ class LocalChatWriter
const char* ApplyAnsiEscape(const char* escape); const char* ApplyAnsiEscape(const char* escape);
void InsertDefaultFade(); void InsertDefaultFade();
}; };
void InitialiseLocalChatWriter(HMODULE baseAddress);

View File

@ -1,258 +1,20 @@
#include "pch.h" #include "pch.h"
#include "logging.h" #include "logging.h"
#include "sourceconsole.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "hookutils.h"
#include "dedicated.h"
#include "convar.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 <iomanip>
#include <sstream> #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 AUTOHOOK_INIT()
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);
}
ConVar* Cvar_spewlog_enable; ConVar* Cvar_spewlog_enable;
enum SpewType_t enum class SpewType_t
{ {
SPEW_MESSAGE = 0, SPEW_MESSAGE = 0,
@ -264,56 +26,24 @@ enum SpewType_t
SPEW_TYPE_COUNT SPEW_TYPE_COUNT
}; };
typedef void (*EngineSpewFuncType)(); const std::unordered_map<SpewType_t, const char*> PrintSpewTypes = {
EngineSpewFuncType EngineSpewFunc; {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()) if (!Cvar_spewlog_enable->GetBool())
return; return;
const char* typeStr; const char* typeStr = PrintSpewTypes.at(type);
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;
}
}
char formatted[2048] = {0}; 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 // 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 // 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: default:
{ {
shouldFormat = false; bShouldFormat = false;
break; break;
} }
} }
} }
} }
if (shouldFormat) if (bShouldFormat)
vsnprintf(formatted, sizeof(formatted), format, args); vsnprintf(formatted, sizeof(formatted), format, args);
else else
{
spdlog::warn("Failed to format {} \"{}\"", typeStr, format); spdlog::warn("Failed to format {} \"{}\"", typeStr, format);
}
auto endpos = strlen(formatted); auto endpos = strlen(formatted);
if (formatted[endpos - 1] == '\n') if (formatted[endpos - 1] == '\n')
@ -381,10 +109,11 @@ void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format,
spdlog::info("[SERVER {}] {}", typeStr, formatted); spdlog::info("[SERVER {}] {}", typeStr, formatted);
} }
typedef void (*Status_ConMsg_Type)(const char* text, ...); // used for printing the output of status
Status_ConMsg_Type Status_ConMsg_Original; // clang-format off
AUTOHOOK(Status_ConMsg, engine.dll + 0x15ABD0,
void Status_ConMsg_Hook(const char* text, ...) void,, (const char* text, ...))
// clang-format on
{ {
char formatted[2048]; char formatted[2048];
va_list list; va_list list;
@ -400,10 +129,10 @@ void Status_ConMsg_Hook(const char* text, ...)
spdlog::info(formatted); spdlog::info(formatted);
} }
typedef bool (*CClientState_ProcessPrint_Type)(__int64 thisptr, __int64 msg); // clang-format off
CClientState_ProcessPrint_Type CClientState_ProcessPrint_Original; AUTOHOOK(CClientState_ProcessPrint, engine.dll + 0x1A1530,
bool,, (void* thisptr, uintptr_t msg))
bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg) // clang-format on
{ {
char* text = *(char**)(msg + 0x20); char* text = *(char**)(msg + 0x20);
@ -415,32 +144,8 @@ bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg)
return true; 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; ConVar* Cvar_cl_showtextmsg;
typedef void (*TextMsg_Type)(__int64);
TextMsg_Type TextMsg_Original;
class ICenterPrint class ICenterPrint
{ {
public: public:
@ -453,11 +158,22 @@ class ICenterPrint
virtual void SetTextColor(int r, int g, int b, int a) = 0; 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]; char text[256];
msg->ReadString(text, sizeof(text)); msg->ReadString(text, sizeof(text));
@ -467,29 +183,86 @@ void TextMsgHook(BFRead* msg)
switch (msg_dest) switch (msg_dest)
{ {
case 4: // HUD_PRINTCENTER case TextMsgPrintType_t::HUD_PRINTCENTER:
internalCenterPrint->Print(text); pInternalCenterPrint->Print(text);
break; break;
default: default:
spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest); spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest);
[[fallthrough]]; [[fallthrough]];
case 2: // HUD_PRINTCONSOLE
case TextMsgPrintType_t::HUD_PRINTCONSOLE:
auto endpos = strlen(text); auto endpos = strlen(text);
if (text[endpos - 1] == '\n') if (text[endpos - 1] == '\n')
text[endpos - 1] = '\0'; // cut off repeated newline text[endpos - 1] = '\0'; // cut off repeated newline
spdlog::info(text); spdlog::info(text);
break; 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 stream << std::put_time(&currentTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str());
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x198710, TextMsgHook, reinterpret_cast<LPVOID*>(&TextMsg_Original)); 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."); 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 #pragma once
#include "context.h"
void CreateLogFiles(); void CreateLogFiles();
void InitialiseLogging(); 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 #pragma once
#include "convar.h" #include "convar.h"
#include "serverpresence.h"
#include <winsock2.h> #include <winsock2.h>
#include <string> #include <string>
#include <cstring> #include <cstring>
#include <future>
extern ConVar* Cvar_ns_masterserver_hostname;
extern ConVar* Cvar_ns_curl_log_enable;
struct RemoteModInfo struct RemoteModInfo
{ {
public: public:
@ -82,8 +89,6 @@ class MasterServerManager
char m_sOwnClientAuthToken[33]; char m_sOwnClientAuthToken[33];
std::string m_sOwnModInfoJson; 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_bOriginAuthWithMasterServerDone = false;
bool m_bOriginAuthWithMasterServerInProgress = false; bool m_bOriginAuthWithMasterServerInProgress = false;
@ -97,8 +102,7 @@ class MasterServerManager
bool m_bNewgameAfterSelfAuth = false; bool m_bNewgameAfterSelfAuth = false;
bool m_bScriptAuthenticatingWithGameServer = false; bool m_bScriptAuthenticatingWithGameServer = false;
bool m_bSuccessfullyAuthenticatedWithGameServer = false; bool m_bSuccessfullyAuthenticatedWithGameServer = false;
std::string m_sAuthFailureReason {};
std::string s_authfail_reason {};
bool m_bHasPendingConnectionInfo = false; bool m_bHasPendingConnectionInfo = false;
RemoteServerConnectionInfo m_pendingConnectionInfo; RemoteServerConnectionInfo m_pendingConnectionInfo;
@ -108,28 +112,76 @@ class MasterServerManager
bool m_bHasMainMenuPromoData = false; bool m_bHasMainMenuPromoData = false;
MainMenuPromoData m_sMainMenuPromoData; MainMenuPromoData m_sMainMenuPromoData;
private:
void SetCommonHttpClientOptions(CURL* curl);
public: public:
MasterServerManager(); MasterServerManager();
void ClearServerList(); void ClearServerList();
void RequestServerList(); void RequestServerList();
void RequestMainMenuPromos(); void RequestMainMenuPromos();
void AuthenticateOriginWithMasterServer(char* uid, char* originToken); void AuthenticateOriginWithMasterServer(const char* uid, const char* originToken);
void AuthenticateWithOwnServer(char* uid, char* playerToken); void AuthenticateWithOwnServer(const char* uid, const char* playerToken);
void AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password); void AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password);
void void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize);
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();
}; };
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_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 "pch.h"
#include "tier0.h"
#include "maxplayers.h" #include "maxplayers.h"
#include "gameutils.h"
AUTOHOOK_INIT()
// never set this to anything below 32 // never set this to anything below 32
#define NEW_MAX_PLAYERS 64 #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_PlayerArray_AddedSize = PAD_NUMBER(Team_PlayerArray_AddedLength * 8, 4);
constexpr int Team_AddedSize = Team_PlayerArray_AddedSize; constexpr int Team_AddedSize = Team_PlayerArray_AddedSize;
#include "nsmem.h" bool MaxPlayersIncreaseEnabled()
template <class T> void ChangeOffset(void* addr, unsigned int offset)
{ {
NSMem::BytePatch((uintptr_t)addr, (BYTE*)&offset, sizeof(T)); static bool bMaxPlayersIncreaseEnabled = Tier0::CommandLine()->CheckParm("-experimentalmaxplayersincrease");
return bMaxPlayersIncreaseEnabled;
} }
/* // should we use R2 for this? not sure
typedef bool(*MatchRecvPropsToSendProps_R_Type)(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable); namespace R2 // use R2 namespace for game funcs
MatchRecvPropsToSendProps_R_Type MatchRecvPropsToSendProps_R_Original;
bool MatchRecvPropsToSendProps_R_Hook(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable)
{ {
const char* tableName = *(const char**)(sendTable + 0x118); int GetMaxPlayers()
{
if (MaxPlayersIncreaseEnabled())
return NEW_MAX_PLAYERS;
spdlog::info("MatchRecvPropsToSendProps_R table name {}", tableName); return 32;
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)
{
const char* tableName = *(const char**)(sendTable + 0x118);
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);
} }
} // namespace R2
return DataTable_SetupReceiveTableFromSendTable_Original(sendTable, needsDecoder); template <class T> void ChangeOffset(MemoryAddress addr, unsigned int offset)
{
addr.Patch((BYTE*)&offset, sizeof(T));
} }
*/
typedef void* (*StringTables_CreateStringTable_Type)( // clang-format off
__int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags); AUTOHOOK(StringTables_CreateStringTable, engine.dll + 0x22E220,
StringTables_CreateStringTable_Type StringTables_CreateStringTable_Original; void*,, (void* thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags))
// clang-format on
void* StringTables_CreateStringTable_Hook(
__int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags)
{ {
// Change the amount of entries to account for a bigger player amount // Change the amount of entries to account for a bigger player amount
if (!strcmp(name, "userinfo")) if (!strcmp(name, "userinfo"))
@ -100,36 +85,33 @@ void* StringTables_CreateStringTable_Hook(
maxentries = maxPlayersPowerOf2; maxentries = maxPlayersPowerOf2;
} }
return StringTables_CreateStringTable_Original(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags); return StringTables_CreateStringTable(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags);
} }
bool MaxPlayersIncreaseEnabled() ON_DLL_LOAD("engine.dll", MaxPlayersOverride_Engine, (CModule module))
{
return CommandLine() && CommandLine()->CheckParm("-experimentalmaxplayersincrease");
}
void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress)
{ {
if (!MaxPlayersIncreaseEnabled()) if (!MaxPlayersIncreaseEnabled())
return; return;
AUTOHOOK_DISPATCH_MODULE(engine.dll)
// patch GetPlayerLimits to ignore the boundary limit // 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 // 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 // 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 // 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) // 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 // 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 // patch max players in userinfo stringtable creation
/*{ /*{
@ -142,22 +124,10 @@ void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress)
// proper fix below // proper fix below
// patch max players in userinfo stringtable creation loop // 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 // do not load prebaked SendTable message list
ChangeOffset<unsigned char>((char*)baseAddress + 0x75859, 0xEB); // jnz -> jmp module.Offset(0x75859).Patch("EB"); // 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));
} }
typedef void (*RunUserCmds_Type)(bool a1, float a2); typedef void (*RunUserCmds_Type)(bool a1, float a2);
@ -167,7 +137,10 @@ HMODULE serverBase = 0;
auto RandomIntZeroMax = (__int64(__fastcall*)())0; auto RandomIntZeroMax = (__int64(__fastcall*)())0;
// lazy rebuild // 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 unsigned char v3; // bl
int v5; // er14 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); // clang-format off
SendPropArray2_Type SendPropArray2_Original; AUTOHOOK(SendPropArray2, server.dll + 0x12B130,
__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1))
__int64 __fastcall SendPropArray2_Hook(__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 // Change the amount of elements to account for a bigger player amount
if (!strcmp(name, "\"player_array\"")) if (!strcmp(name, "\"player_array\""))
elements = NEW_MAX_PLAYERS; 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()) if (!MaxPlayersIncreaseEnabled())
return; return;
AUTOHOOK_DISPATCH_MODULE(server.dll)
// get required data // get required data
serverBase = GetModuleHandleA("server.dll"); serverBase = (HMODULE)module.m_nAddress;
RandomIntZeroMax = (decltype(RandomIntZeroMax))(GetProcAddress(GetModuleHandleA("vstdlib.dll"), "RandomIntZeroMax")); RandomIntZeroMax = (decltype(RandomIntZeroMax))(GetProcAddress(GetModuleHandleA("vstdlib.dll"), "RandomIntZeroMax"));
// patch max players amount // 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 // 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_OriginalSize = 4776;
constexpr int CPlayerResource_AddedSize = PlayerResource_TotalSize; constexpr int CPlayerResource_AddedSize = PlayerResource_TotalSize;
constexpr int CPlayerResource_ModifiedSize = CPlayerResource_OriginalSize + CPlayerResource_AddedSize; constexpr int CPlayerResource_ModifiedSize = CPlayerResource_OriginalSize + CPlayerResource_AddedSize;
// CPlayerResource class allocation function - allocate a bigger amount to fit all new max player data // 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 // DT_PlayerResource::m_iPing SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5059 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); ChangeOffset<unsigned int>(module.Offset(0x5C5059 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50A8 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); ChangeOffset<unsigned int>(module.Offset(0x5C50A8 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50E2 + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x5C50E2 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPing DataMap // DT_PlayerResource::m_iPing DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94598, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); ChangeOffset<unsigned int>(module.Offset(0xB94598), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9459C, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xB9459C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB945C0, PlayerResource_Ping_Size); ChangeOffset<unsigned short>(module.Offset(0xB945C0), PlayerResource_Ping_Size);
// DT_PlayerResource::m_iTeam SendProp // DT_PlayerResource::m_iTeam SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5110 + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start); ChangeOffset<unsigned int>(module.Offset(0x5C5110 + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C519C + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start); ChangeOffset<unsigned int>(module.Offset(0x5C519C + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C517E + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x5C517E + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iTeam DataMap // DT_PlayerResource::m_iTeam DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94600, CPlayerResource_OriginalSize + PlayerResource_Team_Start); ChangeOffset<unsigned int>(module.Offset(0xB94600), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94604, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xB94604), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94628, PlayerResource_Team_Size); ChangeOffset<unsigned short>(module.Offset(0xB94628), PlayerResource_Team_Size);
// DT_PlayerResource::m_iPRHealth SendProp // DT_PlayerResource::m_iPRHealth SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C51C0 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); ChangeOffset<unsigned int>(module.Offset(0x5C51C0 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5204 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); ChangeOffset<unsigned int>(module.Offset(0x5C5204 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C523E + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x5C523E + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPRHealth DataMap // DT_PlayerResource::m_iPRHealth DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94668, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); ChangeOffset<unsigned int>(module.Offset(0xB94668), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9466C, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xB9466C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94690, PlayerResource_PRHealth_Size); ChangeOffset<unsigned short>(module.Offset(0xB94690), PlayerResource_PRHealth_Size);
// DT_PlayerResource::m_bConnected SendProp // DT_PlayerResource::m_bConnected SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C526C + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0x5C526C + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52B4 + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0x5C52B4 + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52EE + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x5C52EE + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bConnected DataMap // DT_PlayerResource::m_bConnected DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB946D0, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0xB946D0), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946D4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xB946D4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946F8, PlayerResource_Connected_Size); ChangeOffset<unsigned short>(module.Offset(0xB946F8), PlayerResource_Connected_Size);
// DT_PlayerResource::m_bAlive SendProp // DT_PlayerResource::m_bAlive SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5321 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); ChangeOffset<unsigned int>(module.Offset(0x5C5321 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5364 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); ChangeOffset<unsigned int>(module.Offset(0x5C5364 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C539E + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x5C539E + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bAlive DataMap // DT_PlayerResource::m_bAlive DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94738, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); ChangeOffset<unsigned int>(module.Offset(0xB94738), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9473C, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xB9473C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94760, PlayerResource_Alive_Size); ChangeOffset<unsigned short>(module.Offset(0xB94760), PlayerResource_Alive_Size);
// DT_PlayerResource::m_boolStats SendProp // DT_PlayerResource::m_boolStats SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C53CC + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C53CC + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5414 + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C5414 + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C544E + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x5C544E + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_boolStats DataMap // DT_PlayerResource::m_boolStats DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB947A0, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); ChangeOffset<unsigned int>(module.Offset(0xB947A0), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947A4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xB947A4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947C8, PlayerResource_BoolStats_Size); ChangeOffset<unsigned short>(module.Offset(0xB947C8), PlayerResource_BoolStats_Size);
// DT_PlayerResource::m_killStats SendProp // DT_PlayerResource::m_killStats SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C547C + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C547C + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54E2 + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C54E2 + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54FE + 4, PlayerResource_KillStats_Length); ChangeOffset<unsigned int>(module.Offset(0x5C54FE + 4), PlayerResource_KillStats_Length);
// DT_PlayerResource::m_killStats DataMap // DT_PlayerResource::m_killStats DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94808, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); ChangeOffset<unsigned int>(module.Offset(0xB94808), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9480C, PlayerResource_KillStats_Length); ChangeOffset<unsigned short>(module.Offset(0xB9480C), PlayerResource_KillStats_Length);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94830, PlayerResource_KillStats_Size); ChangeOffset<unsigned short>(module.Offset(0xB94830), PlayerResource_KillStats_Size);
// DT_PlayerResource::m_scoreStats SendProp // DT_PlayerResource::m_scoreStats SendProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5528 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C5528 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5576 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C5576 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5584 + 4, PlayerResource_ScoreStats_Length); ChangeOffset<unsigned int>(module.Offset(0x5C5584 + 4), PlayerResource_ScoreStats_Length);
// DT_PlayerResource::m_scoreStats DataMap // DT_PlayerResource::m_scoreStats DataMap
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94870, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); ChangeOffset<unsigned int>(module.Offset(0xB94870), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94874, PlayerResource_ScoreStats_Length); ChangeOffset<unsigned short>(module.Offset(0xB94874), PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94898, PlayerResource_ScoreStats_Size); ChangeOffset<unsigned short>(module.Offset(0xB94898), PlayerResource_ScoreStats_Size);
// CPlayerResource::UpdatePlayerData - m_bConnected // CPlayerResource::UpdatePlayerData - m_bConnected
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C66EE + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0x5C66EE + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C672E + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0x5C672E + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
// CPlayerResource::UpdatePlayerData - m_iPing // CPlayerResource::UpdatePlayerData - m_iPing
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6394 + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); ChangeOffset<unsigned int>(module.Offset(0x5C6394 + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63DB + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start); ChangeOffset<unsigned int>(module.Offset(0x5C63DB + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
// CPlayerResource::UpdatePlayerData - m_iTeam // CPlayerResource::UpdatePlayerData - m_iTeam
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63FD + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start); ChangeOffset<unsigned int>(module.Offset(0x5C63FD + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6442 + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start); ChangeOffset<unsigned int>(module.Offset(0x5C6442 + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
// CPlayerResource::UpdatePlayerData - m_iPRHealth // CPlayerResource::UpdatePlayerData - m_iPRHealth
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C645B + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); ChangeOffset<unsigned int>(module.Offset(0x5C645B + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64A0 + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start); ChangeOffset<unsigned int>(module.Offset(0x5C64A0 + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
// CPlayerResource::UpdatePlayerData - m_bConnected // CPlayerResource::UpdatePlayerData - m_bConnected
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64AA + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0x5C64AA + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64F0 + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0x5C64F0 + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
// CPlayerResource::UpdatePlayerData - m_bAlive // CPlayerResource::UpdatePlayerData - m_bAlive
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C650A + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); ChangeOffset<unsigned int>(module.Offset(0x5C650A + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C654F + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start); ChangeOffset<unsigned int>(module.Offset(0x5C654F + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
// CPlayerResource::UpdatePlayerData - m_boolStats // CPlayerResource::UpdatePlayerData - m_boolStats
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6557 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C6557 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65A5 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C65A5 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
// CPlayerResource::UpdatePlayerData - m_scoreStats // CPlayerResource::UpdatePlayerData - m_scoreStats
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65C2 + 3, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C65C2 + 3), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65E3 + 4, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C65E3 + 4), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
// CPlayerResource::UpdatePlayerData - m_killStats // CPlayerResource::UpdatePlayerData - m_killStats
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6654 + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C6654 + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C665B + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start); ChangeOffset<unsigned int>(module.Offset(0x5C665B + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
// GameLoop::RunUserCmds - rebuild *module.Offset(0x14E7390).As<DWORD*>() = 0;
HookEnabler hook; auto DT_PlayerResource_Construct = module.Offset(0x5C4FE0).As<__int64(__fastcall*)()>();
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);
DT_PlayerResource_Construct(); DT_PlayerResource_Construct();
constexpr int CTeam_OriginalSize = 3336; constexpr int CTeam_OriginalSize = 3336;
@ -471,191 +442,188 @@ void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress)
constexpr int CTeam_ModifiedSize = CTeam_OriginalSize + CTeam_AddedSize; constexpr int CTeam_ModifiedSize = CTeam_OriginalSize + CTeam_AddedSize;
// CTeam class allocation function - allocate a bigger amount to fit all new team player data // 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 // 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" *module.Offset(0xC945A0).As<DWORD*>() = 0;
HookEnabler hook2; auto DT_Team_Construct = module.Offset(0x238F50).As<__int64(__fastcall*)()>();
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);
DT_Team_Construct(); DT_Team_Construct();
} }
typedef __int64 (*RecvPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn); // clang-format off
RecvPropArray2_Type RecvPropArray2_Original; AUTOHOOK(RecvPropArray2, client.dll + 0x1CEDA0,
__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn))
__int64 __fastcall RecvPropArray2_Hook(__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 // Change the amount of elements to account for a bigger player amount
if (!strcmp(name, "\"player_array\"")) if (!strcmp(name, "\"player_array\""))
elements = NEW_MAX_PLAYERS; 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()) if (!MaxPlayersIncreaseEnabled())
return; return;
AUTOHOOK_DISPATCH_MODULE(client.dll)
constexpr int C_PlayerResource_OriginalSize = 5768; constexpr int C_PlayerResource_OriginalSize = 5768;
constexpr int C_PlayerResource_AddedSize = PlayerResource_TotalSize; constexpr int C_PlayerResource_AddedSize = PlayerResource_TotalSize;
constexpr int C_PlayerResource_ModifiedSize = C_PlayerResource_OriginalSize + C_PlayerResource_AddedSize; 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 // 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 // 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 // C_PlayerResource::C_PlayerResource - change m_szName address
ChangeOffset<unsigned int>( 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 // C_PlayerResource::C_PlayerResource - change m_szName address
ChangeOffset<unsigned int>( 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // C_PlayerResource::m_szName
ChangeOffset<unsigned int>((char*)baseAddress + 0xc350f8, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); ChangeOffset<unsigned int>(module.Offset(0xc350f8), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc350f8 + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xc350f8 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource size // 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 // DT_PlayerResource::m_iPing RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163492 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); ChangeOffset<unsigned int>(module.Offset(0x163492 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1634D6 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); ChangeOffset<unsigned int>(module.Offset(0x1634D6 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163515 + 5, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x163515 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iPing // C_PlayerResource::m_iPing
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35170, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start); ChangeOffset<unsigned int>(module.Offset(0xc35170), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35170 + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xc35170 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iTeam RecvProp // DT_PlayerResource::m_iTeam RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163549 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start); ChangeOffset<unsigned int>(module.Offset(0x163549 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635C8 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start); ChangeOffset<unsigned int>(module.Offset(0x1635C8 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635AD + 5, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x1635AD + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iTeam // C_PlayerResource::m_iTeam
ChangeOffset<unsigned int>((char*)baseAddress + 0xc351e8, C_PlayerResource_OriginalSize + PlayerResource_Team_Start); ChangeOffset<unsigned int>(module.Offset(0xc351e8), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc351e8 + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xc351e8 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPRHealth RecvProp // DT_PlayerResource::m_iPRHealth RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635F9 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); ChangeOffset<unsigned int>(module.Offset(0x1635F9 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163625 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); ChangeOffset<unsigned int>(module.Offset(0x163625 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163675 + 5, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x163675 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iPRHealth // C_PlayerResource::m_iPRHealth
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35260, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start); ChangeOffset<unsigned int>(module.Offset(0xc35260), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35260 + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xc35260 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bConnected RecvProp // DT_PlayerResource::m_bConnected RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636A9 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0x1636A9 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636D5 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0x1636D5 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163725 + 5, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x163725 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_bConnected // C_PlayerResource::m_bConnected
ChangeOffset<unsigned int>((char*)baseAddress + 0xc352d8, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start); ChangeOffset<unsigned int>(module.Offset(0xc352d8), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc352d8 + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xc352d8 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bAlive RecvProp // DT_PlayerResource::m_bAlive RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163759 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); ChangeOffset<unsigned int>(module.Offset(0x163759 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163785 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); ChangeOffset<unsigned int>(module.Offset(0x163785 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1637D5 + 5, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x1637D5 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_bAlive // C_PlayerResource::m_bAlive
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35350, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start); ChangeOffset<unsigned int>(module.Offset(0xc35350), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35350 + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xc35350 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_boolStats RecvProp // DT_PlayerResource::m_boolStats RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163809 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); ChangeOffset<unsigned int>(module.Offset(0x163809 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163835 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); ChangeOffset<unsigned int>(module.Offset(0x163835 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163885 + 5, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned int>(module.Offset(0x163885 + 5), NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_boolStats // C_PlayerResource::m_boolStats
ChangeOffset<unsigned int>((char*)baseAddress + 0xc353c8, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start); ChangeOffset<unsigned int>(module.Offset(0xc353c8), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc353c8 + 4, NEW_MAX_PLAYERS + 1); ChangeOffset<unsigned short>(module.Offset(0xc353c8 + 4), NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_killStats RecvProp // DT_PlayerResource::m_killStats RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638B3 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); ChangeOffset<unsigned int>(module.Offset(0x1638B3 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638E5 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); ChangeOffset<unsigned int>(module.Offset(0x1638E5 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163935 + 5, PlayerResource_KillStats_Length); ChangeOffset<unsigned int>(module.Offset(0x163935 + 5), PlayerResource_KillStats_Length);
// C_PlayerResource::m_killStats // C_PlayerResource::m_killStats
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35440, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start); ChangeOffset<unsigned int>(module.Offset(0xc35440), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35440 + 4, PlayerResource_KillStats_Length); ChangeOffset<unsigned short>(module.Offset(0xc35440 + 4), PlayerResource_KillStats_Length);
// DT_PlayerResource::m_scoreStats RecvProp // DT_PlayerResource::m_scoreStats RecvProp
ChangeOffset<unsigned int>((char*)baseAddress + 0x163969 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); ChangeOffset<unsigned int>(module.Offset(0x163969 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163995 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); ChangeOffset<unsigned int>(module.Offset(0x163995 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1639E5 + 5, PlayerResource_ScoreStats_Length); ChangeOffset<unsigned int>(module.Offset(0x1639E5 + 5), PlayerResource_ScoreStats_Length);
// C_PlayerResource::m_scoreStats // C_PlayerResource::m_scoreStats
ChangeOffset<unsigned int>((char*)baseAddress + 0xc354b8, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start); ChangeOffset<unsigned int>(module.Offset(0xc354b8), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc354b8 + 4, PlayerResource_ScoreStats_Length); ChangeOffset<unsigned short>(module.Offset(0xc354b8 + 4), PlayerResource_ScoreStats_Length);
// C_PlayerResource::GetPlayerName - change m_bConnected address // 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 // 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 // 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 // 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 // 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; *module.Offset(0xC35068).As<DWORD*>() = 0;
auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x163400); auto DT_PlayerResource_Construct = module.Offset(0x163400).As<__int64(__fastcall*)()>();
DT_PlayerResource_Construct(); DT_PlayerResource_Construct();
constexpr int C_Team_OriginalSize = 3200; 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; 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 // 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 // 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 // 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" *module.Offset(0xC3AFF8).As<DWORD*>() = 0;
HookEnabler hook; auto DT_Team_Construct = module.Offset(0x17F950).As<__int64(__fastcall*)()>();
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);
DT_Team_Construct(); DT_Team_Construct();
} }

View File

@ -1,4 +1,7 @@
#pragma once #pragma once
void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress);
void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress); // should we use R2 for this? not sure
void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress); namespace R2 // use R2 namespace for game funcs
{
int GetMaxPlayers();
} // namespace R2

View File

@ -1,6 +1,8 @@
#include "pch.h" #include "pch.h"
#include "memalloc.h" #include "memalloc.h"
#include "gameutils.h" #include "tier0.h"
using namespace Tier0;
// TODO: rename to malloc and free after removing statically compiled .libs // 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 // allocate into static buffer if g_pMemAllocSingleton isn't initialised
if (!g_pMemAllocSingleton) if (!g_pMemAllocSingleton)
{ TryCreateGlobalMemAlloc();
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
}
return g_pMemAllocSingleton->m_vtable->Alloc(g_pMemAllocSingleton, n); 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) extern "C" void _free_base(void* p)
{ {
if (!g_pMemAllocSingleton) if (!g_pMemAllocSingleton)
{ TryCreateGlobalMemAlloc();
spdlog::warn("Trying to free something before g_pMemAllocSingleton was ready, this should never happen");
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
}
g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p); g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p);
} }
extern "C" void* _realloc_base(void* oldPtr, size_t size) extern "C" void* _realloc_base(void* oldPtr, size_t size)
{ {
if (!g_pMemAllocSingleton) if (!g_pMemAllocSingleton)
{ TryCreateGlobalMemAlloc();
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
}
return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size); 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 "pch.h"
#include "misccommands.h" #include "misccommands.h"
#include "concommand.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 "masterserver.h"
#include "modmanager.h"
#include "serverauthentication.h" #include "serverauthentication.h"
#include "squirrel.h" #include "squirrel.h"
void ConCommand_force_newgame(const CCommand& arg)
{
if (arg.ArgC() < 2)
return;
R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME;
strncpy(R2::g_pHostState->m_levelName, arg.Arg(1), sizeof(R2::g_pHostState->m_levelName));
}
void ConCommand_ns_start_reauth_and_leave_to_lobby(const CCommand& arg)
{
// hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect
g_pMasterServerManager->m_bNewgameAfterSelfAuth = true;
g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken);
}
void ConCommand_ns_end_reauth_and_leave_to_lobby(const CCommand& arg)
{
if (g_pServerAuthentication->m_RemoteAuthenticationData.size())
R2::g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str());
// weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this
if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM)
{
g_pServerAuthentication->m_bNeedLocalAuthForNewgame = true;
// this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta
// fucks things should maybe set this in HostState_NewGame?
R2::SetCurrentPlaylist("tdm");
strcpy(R2::g_pHostState->m_levelName, "mp_lobby");
R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME;
}
}
void AddMiscConCommands() void AddMiscConCommands()
{ {
MAKE_CONCMD( RegisterConCommand(
"force_newgame", "force_newgame",
ConCommand_force_newgame,
"forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME", "forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME",
FCVAR_NONE, FCVAR_NONE);
[](const CCommand& arg)
{
if (arg.ArgC() < 2)
return;
g_pHostState->m_iNextState = HS_NEW_GAME; RegisterConCommand(
strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName));
});
MAKE_CONCMD(
"ns_start_reauth_and_leave_to_lobby", "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", "called by the server, used to reauth and return the player to lobby when leaving a game",
FCVAR_SERVER_CAN_EXECUTE, FCVAR_SERVER_CAN_EXECUTE);
[](const CCommand& arg)
{
// hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect
g_MasterServerManager->m_bNewgameAfterSelfAuth = true;
g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, g_MasterServerManager->m_sOwnClientAuthToken);
});
// this is a concommand because we make a deferred call to it from another thread // this is a concommand because we make a deferred call to it from another thread
MAKE_CONCMD( RegisterConCommand("ns_end_reauth_and_leave_to_lobby", ConCommand_ns_end_reauth_and_leave_to_lobby, "", FCVAR_NONE);
"ns_end_reauth_and_leave_to_lobby", }
"",
FCVAR_NONE, // fixes up various cvar flags to have more sane values
[](const CCommand& arg) void FixupCvarFlags()
{ {
Cbuf_AddText( if (Tier0::CommandLine()->CheckParm("-allowdevcvars"))
Cbuf_GetCurrentPlayer(), {
fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(), // strip hidden and devonly cvar flags
cmd_source_t::kCommandSrcCode); int iNumCvarsAltered = 0;
Cbuf_Execute(); for (auto& pair : R2::g_pCVar->DumpToMap())
{
// weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this // strip flags
if (g_ClientSquirrelManager->sqvm) int flags = pair.second->GetFlags();
{ if (flags & FCVAR_DEVELOPMENTONLY)
g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true; {
flags &= ~FCVAR_DEVELOPMENTONLY;
// this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta iNumCvarsAltered++;
// fucks things should maybe set this in HostState_NewGame? }
SetCurrentPlaylist("tdm");
strcpy(g_pHostState->m_levelName, "mp_lobby"); if (flags & FCVAR_HIDDEN)
g_pHostState->m_iNextState = HS_NEW_GAME; {
} 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 #pragma once
void AddMiscConCommands(); void AddMiscConCommands();
void FixupCvarFlags();

View File

@ -1,26 +1,7 @@
#include "pch.h" #include "pch.h"
#include "miscserverfixes.h"
#include "hookutils.h"
#include "nsmem.h" ON_DLL_LOAD("server.dll", MiscServerFixes, (CModule module))
void InitialiseMiscServerFixes(HMODULE baseAddress)
{ {
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 // nop out call to VGUI shutdown since it crashes the game when quitting from the console
{ module.Offset(0x154A96).NOP(5);
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");
}
} }

View File

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

View File

@ -1,76 +1,73 @@
#include "pch.h" #include "pch.h"
#include "miscserverscript.h"
#include "squirrel.h" #include "squirrel.h"
#include "masterserver.h" #include "masterserver.h"
#include "serverauthentication.h" #include "serverauthentication.h"
#include "gameutils.h"
#include "dedicated.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 #include <filesystem>
// wish i didn't have to do it like this, but here we are
void* GetPlayerByIndex(int playerIndex) // void function NSEarlyWritePlayerPersistenceForLeave( entity player )
SQRESULT SQ_EarlyWritePlayerPersistenceForLeave(HSquirrelVM* sqvm)
{ {
const int PLAYER_ARRAY_OFFSET = 0x12A53F90; const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
const int PLAYER_SIZE = 0x2D728; if (!pPlayer)
void* playerArrayBase = (char*)GetModuleHandleA("engine.dll") + PLAYER_ARRAY_OFFSET;
void* player = (char*)playerArrayBase + (playerIndex * PLAYER_SIZE);
return player;
}
// void function NSEarlyWritePlayerIndexPersistenceForLeave( int playerIndex )
SQRESULT SQ_EarlyWritePlayerIndexPersistenceForLeave(void* sqvm)
{
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()); spdlog::warn("NSEarlyWritePlayerPersistenceForLeave got null player");
return SQRESULT_ERROR;
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
return SQRESULT_NOTNULL;
} }
g_ServerAuthenticationManager->m_additionalPlayerData[player].needPersistenceWriteOnLeave = false; R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex];
g_ServerAuthenticationManager->WritePersistentData(player); if (g_pServerAuthentication->m_PlayerAuthenticationData.find(pClient) == g_pServerAuthentication->m_PlayerAuthenticationData.end())
{
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
return SQRESULT_NOTNULL;
}
g_pServerAuthentication->m_PlayerAuthenticationData[pClient].needPersistenceWriteOnLeave = false;
g_pServerAuthentication->WritePersistentData(pClient);
return SQRESULT_NULL; return SQRESULT_NULL;
} }
// bool function NSIsWritingPlayerPersistence() // 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; return SQRESULT_NOTNULL;
} }
// bool function NSIsPlayerIndexLocalPlayer( int playerIndex ) // bool function NSIsPlayerLocalPlayer( entity player )
SQRESULT SQ_IsPlayerIndexLocalPlayer(void* sqvm) SQRESULT SQ_IsPlayerLocalPlayer(HSquirrelVM* sqvm)
{ {
int playerIndex = ServerSq_getinteger(sqvm, 1); const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
void* player = GetPlayerByIndex(playerIndex); if (!pPlayer)
if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(player))
{ {
ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str()); spdlog::warn("NSIsPlayerLocalPlayer got null player");
return SQRESULT_ERROR;
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; return SQRESULT_NOTNULL;
} }
// bool function NSIsDedicated() // bool function NSIsDedicated()
SQRESULT SQ_IsDedicated(HSquirrelVM* sqvm)
SQRESULT SQ_IsDedicated(void* sqvm)
{ {
ServerSq_pushbool(sqvm, IsDedicatedServer()); g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, IsDedicatedServer());
return SQRESULT_NOTNULL; return SQRESULT_NOTNULL;
} }
void InitialiseMiscServerScriptCommand(HMODULE baseAddress) ON_DLL_LOAD_RELIESON("server.dll", MiscServerScriptCommands, ServerSquirrel, (CModule module))
{ {
g_ServerSquirrelManager->AddFuncRegistration( g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration(
"void", "NSEarlyWritePlayerIndexPersistenceForLeave", "int playerIndex", "", SQ_EarlyWritePlayerIndexPersistenceForLeave); "void", "NSEarlyWritePlayerPersistenceForLeave", "entity player", "", SQ_EarlyWritePlayerPersistenceForLeave);
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence); g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence);
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsPlayerIndexLocalPlayer", "int playerIndex", "", SQ_IsPlayerIndexLocalPlayer); g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsPlayerLocalPlayer", "entity player", "", SQ_IsPlayerLocalPlayer);
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated); 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 "pch.h"
#include "modlocalisation.h"
#include "hookutils.h"
#include "modmanager.h" #include "modmanager.h"
typedef bool (*AddLocalisationFileType)(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown); AUTOHOOK_INIT()
AddLocalisationFileType AddLocalisationFile;
bool loadModLocalisationFiles = true; // clang-format off
AUTOHOOK(AddLocalisationFile, localize.dll + 0x6D80,
bool AddLocalisationFileHook(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown) 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) if (ret)
spdlog::info("Loaded localisation file {} successfully", path); spdlog::info("Loaded localisation file {} successfully", path);
if (!loadModLocalisationFiles) if (!bLoadModLocalisationFiles)
return ret; return ret;
loadModLocalisationFiles = false; bLoadModLocalisationFiles = false;
for (Mod mod : g_ModManager->m_loadedMods) for (Mod mod : g_pModManager->m_LoadedMods)
if (mod.Enabled) if (mod.m_bEnabled)
for (std::string& localisationFile : mod.LocalisationFiles) 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; return ret;
} }
void InitialiseModLocalisation(HMODULE baseAddress) ON_DLL_LOAD_CLIENT("localize.dll", Localize, (CModule module))
{ {
HookEnabler hook; AUTOHOOK_DISPATCH()
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x6D80, AddLocalisationFileHook, reinterpret_cast<LPVOID*>(&AddLocalisationFile));
} }

View File

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

View File

@ -4,6 +4,10 @@
#include "concommand.h" #include "concommand.h"
#include "audio.h" #include "audio.h"
#include "masterserver.h" #include "masterserver.h"
#include "filesystem.h"
#include "rpakfilesystem.h"
#include "nsprefix.h"
#include "rapidjson/error/en.h" #include "rapidjson/error/en.h"
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/ostreamwrapper.h" #include "rapidjson/ostreamwrapper.h"
@ -13,17 +17,14 @@
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
#include "filesystem.h"
#include "rpakfilesystem.h"
#include "nsprefix.h"
ModManager* g_ModManager; ModManager* g_pModManager;
Mod::Mod(fs::path modDir, char* jsonBuf) Mod::Mod(fs::path modDir, char* jsonBuf)
{ {
wasReadSuccessfully = false; m_bWasReadSuccessfully = false;
ModDirectory = modDir; m_ModDirectory = modDir;
rapidjson_document modJson; rapidjson_document modJson;
modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf); modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf);
@ -106,11 +107,55 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
else else
convar->HelpString = ""; convar->HelpString = "";
// todo: could possibly parse FCVAR names here instead, would be easier convar->Flags = FCVAR_NONE;
if (convarObj.HasMember("Flags")) if (convarObj.HasMember("Flags"))
convar->Flags = convarObj["Flags"].GetInt(); {
else // read raw integer flags
convar->Flags = FCVAR_NONE; 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); ConVars.push_back(convar);
} }
@ -127,7 +172,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
ModScript script; ModScript script;
script.Path = scriptObj["Path"].GetString(); script.Path = scriptObj["Path"].GetString();
script.RsonRunOn = scriptObj["RunOn"].GetString(); script.RunOn = scriptObj["RunOn"].GetString();
if (scriptObj.HasMember("ServerCallback") && scriptObj["ServerCallback"].IsObject()) 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() ModManager::ModManager()
@ -223,7 +268,7 @@ ModManager::ModManager()
void ModManager::LoadMods() void ModManager::LoadMods()
{ {
if (m_hasLoadedMods) if (m_bHasLoadedMods)
UnloadMods(); UnloadMods();
std::vector<fs::path> modDirs; std::vector<fs::path> modDirs;
@ -232,7 +277,7 @@ void ModManager::LoadMods()
fs::remove_all(GetCompiledAssetsPath()); fs::remove_all(GetCompiledAssetsPath());
fs::create_directories(GetModFolderPath()); fs::create_directories(GetModFolderPath());
DependencyConstants.clear(); m_DependencyConstants.clear();
// read enabled mods cfg // read enabled mods cfg
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json"); std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json");
@ -244,10 +289,10 @@ void ModManager::LoadMods()
enabledModsStringStream << (char)enabledModsStream.get(); enabledModsStringStream << (char)enabledModsStream.get();
enabledModsStream.close(); enabledModsStream.close();
m_enabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>( m_EnabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
enabledModsStringStream.str().c_str()); enabledModsStringStream.str().c_str());
m_hasEnabledModsCfg = m_enabledModsCfg.IsObject(); m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject();
} }
// get mod directories // get mod directories
@ -277,41 +322,41 @@ void ModManager::LoadMods()
for (auto& pair : mod.DependencyConstants) 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); spdlog::error("Constant {} in mod {} already exists in another mod.", pair.first, mod.Name);
mod.wasReadSuccessfully = false; mod.m_bWasReadSuccessfully = false;
break; break;
} }
if (DependencyConstants.find(pair.first) == DependencyConstants.end()) if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end())
DependencyConstants.emplace(pair); m_DependencyConstants.emplace(pair);
} }
if (m_hasEnabledModsCfg && m_enabledModsCfg.HasMember(mod.Name.c_str())) if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str()))
mod.Enabled = m_enabledModsCfg[mod.Name.c_str()].IsTrue(); mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue();
else else
mod.Enabled = true; mod.m_bEnabled = true;
if (mod.wasReadSuccessfully) if (mod.m_bWasReadSuccessfully)
{ {
spdlog::info("Loaded mod {} successfully", mod.Name); spdlog::info("Loaded mod {} successfully", mod.Name);
if (mod.Enabled) if (mod.m_bEnabled)
spdlog::info("Mod {} is enabled", mod.Name); spdlog::info("Mod {} is enabled", mod.Name);
else else
spdlog::info("Mod {} is disabled", mod.Name); spdlog::info("Mod {} is disabled", mod.Name);
m_loadedMods.push_back(mod); m_LoadedMods.push_back(mod);
} }
else else
spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string()); spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string());
} }
// sort by load prio, lowest-highest // 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; continue;
// register convars // 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 // 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 // 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) 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 // behaviour is for defining same convar multiple times
new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str()); new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str());
// read vpk paths // read vpk paths
if (fs::exists(mod.ModDirectory / "vpk")) if (fs::exists(mod.m_ModDirectory / "vpk"))
{ {
// read vpk cfg // read vpk cfg
std::ifstream vpkJsonStream(mod.ModDirectory / "vpk/vpk.json"); std::ifstream vpkJsonStream(mod.m_ModDirectory / "vpk/vpk.json");
std::stringstream vpkJsonStringStream; std::stringstream vpkJsonStringStream;
bool bUseVPKJson = false; bool bUseVPKJson = false;
@ -345,7 +390,7 @@ void ModManager::LoadMods()
bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject(); 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 // 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 // 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(); std::string formattedPath = file.path().filename().string();
// this really fucking sucks but it'll work // this really fucking sucks but it'll work
std::string vpkName = std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3);
(file.path().parent_path() / formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3)).string();
ModVPKEntry& modVpk = mod.Vpks.emplace_back(); ModVPKEntry& modVpk = mod.Vpks.emplace_back();
modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() && modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() &&
dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue()); 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) if (m_bHasLoadedMods && modVpk.m_bAutoLoad)
(*g_Filesystem)->m_vtable->MountVPK(*g_Filesystem, vpkName.c_str()); (*R2::g_pFilesystem)->m_vtable->MountVPK(*R2::g_pFilesystem, vpkName.c_str());
} }
} }
} }
// read rpak paths // read rpak paths
if (fs::exists(mod.ModDirectory / "paks")) if (fs::exists(mod.m_ModDirectory / "paks"))
{ {
// read rpak cfg // read rpak cfg
std::ifstream rpakJsonStream(mod.ModDirectory / "paks/rpak.json"); std::ifstream rpakJsonStream(mod.m_ModDirectory / "paks/rpak.json");
std::stringstream rpakJsonStringStream; std::stringstream rpakJsonStringStream;
bool bUseRpakJson = false; 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 // ensure we're only loading rpaks
if (fs::is_regular_file(file) && file.path().extension() == ".rpak") if (fs::is_regular_file(file) && file.path().extension() == ".rpak")
@ -417,39 +461,39 @@ void ModManager::LoadMods()
modPak.m_bAutoLoad = modPak.m_bAutoLoad =
!bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && !bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() &&
dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue()); dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue());
// postload things // postload things
if (!bUseRpakJson || if (!bUseRpakJson ||
(dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName))) (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName)))
{
modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString(); modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString();
}
modPak.m_sPakName = pakName; modPak.m_sPakName = pakName;
// not using atm because we need to resolve path to rpak // not using atm because we need to resolve path to rpak
// if (m_hasLoadedMods && modPak.m_bAutoLoad) // if (m_hasLoadedMods && modPak.m_bAutoLoad)
// g_PakLoadManager->LoadPakAsync(pakName.c_str()); // g_pPakLoadManager->LoadPakAsync(pakName.c_str());
} }
} }
} }
// read keyvalues paths // 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)) 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); mod.KeyValues.emplace(STR_HASH(kvStr), kvStr);
} }
} }
} }
// read pdiff // 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()) if (!pdiffStream.fail())
{ {
@ -464,17 +508,17 @@ void ModManager::LoadMods()
} }
// read bink video paths // 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") if (fs::is_regular_file(file) && file.path().extension() == ".bik")
mod.BinkVideos.push_back(file.path().filename().string()); mod.BinkVideos.push_back(file.path().filename().string());
} }
// try to load audio // 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") 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 // 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; 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(); 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.string()) == m_modFiles.end()) if (file.is_regular_file() && m_ModFiles.find(path) == m_ModFiles.end())
{ {
ModOverrideFile modFile; ModOverrideFile modFile;
modFile.owningMod = &m_loadedMods[i]; modFile.m_pOwningMod = &m_LoadedMods[i];
modFile.path = path; modFile.m_Path = path;
m_modFiles.insert(std::make_pair(path.string(), modFile)); 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()); modinfoDoc.AddMember("Mods", rapidjson_document::GenericValue(rapidjson::kArrayType), modinfoDoc.GetAllocator());
int currentModIndex = 0; 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; continue;
modinfoDoc["Mods"].PushBack(rapidjson_document::GenericValue(rapidjson::kObjectType), modinfoDoc.GetAllocator()); modinfoDoc["Mods"].PushBack(rapidjson_document::GenericValue(rapidjson::kObjectType), modinfoDoc.GetAllocator());
@ -535,27 +579,27 @@ void ModManager::LoadMods()
buffer.Clear(); buffer.Clear();
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
modinfoDoc.Accept(writer); 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() void ModManager::UnloadMods()
{ {
// clean up stuff from mods before we unload // clean up stuff from mods before we unload
m_modFiles.clear(); m_ModFiles.clear();
fs::remove_all(GetCompiledAssetsPath()); fs::remove_all(GetCompiledAssetsPath());
g_CustomAudioManager.ClearAudioOverrides(); g_CustomAudioManager.ClearAudioOverrides();
if (!m_hasEnabledModsCfg) if (!m_bHasEnabledModsCfg)
m_enabledModsCfg.SetObject(); m_EnabledModsCfg.SetObject();
for (Mod& mod : m_loadedMods) for (Mod& mod : m_LoadedMods)
{ {
// remove all built kvs // remove all built kvs
for (std::pair<size_t, std::string> kvPaths : mod.KeyValues) 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(); mod.KeyValues.clear();
@ -563,27 +607,39 @@ void ModManager::UnloadMods()
// should we be doing this here or should scripts be doing this manually? // 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 // 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 // what we wanna do
if (!m_enabledModsCfg.HasMember(mod.Name.c_str())) if (!m_EnabledModsCfg.HasMember(mod.Name.c_str()))
m_enabledModsCfg.AddMember( m_EnabledModsCfg.AddMember(
rapidjson_document::StringRefType(mod.Name.c_str()), rapidjson_document::StringRefType(mod.Name.c_str()),
rapidjson_document::GenericValue(false), 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"); std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json");
rapidjson::OStreamWrapper writeStreamWrapper(writeStream); rapidjson::OStreamWrapper writeStreamWrapper(writeStream);
rapidjson::Writer<rapidjson::OStreamWrapper> writer(writeStreamWrapper); 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 // 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) 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) if (fileHash == m_hScriptsRsonHash)
BuildScriptsRson(); BuildScriptsRson();
@ -592,9 +648,9 @@ void ModManager::CompileAssetsForFile(const char* filename)
else else
{ {
// check if we should build keyvalues, depending on whether any of our mods have patch kvs for this file // 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; continue;
if (mod.KeyValues.find(fileHash) != mod.KeyValues.end()) 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) void ConCommand_reload_mods(const CCommand& args)
{ {
g_ModManager->LoadMods(); g_pModManager->LoadMods();
}
void InitialiseModManager(HMODULE baseAddress)
{
g_ModManager = new ModManager;
RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE);
} }
fs::path GetModFolderPath() fs::path GetModFolderPath()
@ -626,3 +675,10 @@ fs::path GetCompiledAssetsPath()
{ {
return fs::path(GetNorthstarPrefix() + COMPILED_ASSETS_SUFFIX); 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 #pragma once
#include "convar.h" #include "convar.h"
#include "memalloc.h"
#include "squirrel.h"
#include "rapidjson/document.h"
#include <string> #include <string>
#include <vector> #include <vector>
#include <filesystem> #include <filesystem>
#include "rapidjson/document.h"
#include "memalloc.h"
namespace fs = std::filesystem;
const std::string MOD_FOLDER_SUFFIX = "/mods"; 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 fs::path MOD_OVERRIDE_DIR = "mod";
const std::string COMPILED_ASSETS_SUFFIX = "/runtime/compiled"; const std::string COMPILED_ASSETS_SUFFIX = "/runtime/compiled";
@ -24,9 +25,6 @@ struct ModConVar
struct ModScriptCallback struct ModScriptCallback
{ {
public: 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; ScriptContext Context;
// called before the codecallback is executed // called before the codecallback is executed
@ -39,7 +37,7 @@ struct ModScript
{ {
public: public:
std::string Path; std::string Path;
std::string RsonRunOn; std::string RunOn;
std::vector<ModScriptCallback> Callbacks; std::vector<ModScriptCallback> Callbacks;
}; };
@ -64,8 +62,10 @@ class Mod
{ {
public: public:
// runtime stuff // runtime stuff
fs::path ModDirectory; bool m_bEnabled = true;
bool Enabled = true; bool m_bWasReadSuccessfully = false;
fs::path m_ModDirectory;
// bool m_bIsRemote;
// mod.json stuff: // mod.json stuff:
@ -100,14 +100,9 @@ class Mod
std::vector<ModRpakEntry> Rpaks; std::vector<ModRpakEntry> Rpaks;
std::unordered_map<std::string, std::string> 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 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; std::unordered_map<std::string, std::string> DependencyConstants;
// other stuff
bool wasReadSuccessfully = false;
public: public:
Mod(fs::path modPath, char* jsonBuf); Mod(fs::path modPath, char* jsonBuf);
}; };
@ -115,42 +110,40 @@ class Mod
struct ModOverrideFile struct ModOverrideFile
{ {
public: public:
Mod* owningMod; Mod* m_pOwningMod;
fs::path path; fs::path m_Path;
}; };
class ModManager class ModManager
{ {
private: private:
bool m_hasLoadedMods = false; bool m_bHasLoadedMods = false;
bool m_hasEnabledModsCfg; bool m_bHasEnabledModsCfg;
rapidjson_document m_enabledModsCfg; rapidjson_document m_EnabledModsCfg;
// precalculated hashes // precalculated hashes
size_t m_hScriptsRsonHash; size_t m_hScriptsRsonHash;
size_t m_hPdefHash; size_t m_hPdefHash;
public: public:
std::vector<Mod> m_loadedMods; std::vector<Mod> m_LoadedMods;
std::unordered_map<std::string, ModOverrideFile> m_modFiles; std::unordered_map<std::string, ModOverrideFile> m_ModFiles;
// iterated over to create squirrel VM constants depending if a mod exists or not. std::unordered_map<std::string, std::string> m_DependencyConstants;
// here because constants are global anyways.
std::unordered_map<std::string, std::string> DependencyConstants;
public: public:
ModManager(); ModManager();
void LoadMods(); void LoadMods();
void UnloadMods(); void UnloadMods();
std::string NormaliseModFilePath(const fs::path path);
void CompileAssetsForFile(const char* filename); 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 BuildScriptsRson();
void TryBuildKeyValues(const char* filename); void TryBuildKeyValues(const char* filename);
void BuildPdef(); void BuildPdef();
}; };
void InitialiseModManager(HMODULE baseAddress);
fs::path GetModFolderPath(); fs::path GetModFolderPath();
fs::path GetCompiledAssetsPath(); 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 "pch.h"
#include "nsprefix.h" #include "nsprefix.h"
#include <string>
std::string GetNorthstarPrefix() std::string GetNorthstarPrefix()
{ {
return NORTHSTAR_FOLDER_PREFIX; return NORTHSTAR_FOLDER_PREFIX;
} }
void parseConfigurables() void InitialiseNorthstarPrefix()
{ {
char* clachar = strstr(GetCommandLineA(), "-profile="); char* clachar = strstr(GetCommandLineA(), "-profile=");
if (clachar) if (clachar)

View File

@ -3,5 +3,5 @@
static std::string NORTHSTAR_FOLDER_PREFIX; static std::string NORTHSTAR_FOLDER_PREFIX;
void InitialiseNorthstarPrefix();
std::string GetNorthstarPrefix(); 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 _WINSOCK_DEPRECATED_NO_WARNINGS // temp because i'm very lazy and want to use inet_addr, remove later
#define RAPIDJSON_HAS_STDSTRING 1 #define RAPIDJSON_HAS_STDSTRING 1
// httplib ssl
// add headers that you want to pre-compile here // add headers that you want to pre-compile here
#include "memalloc.h" #include "memalloc.h"
@ -20,11 +18,14 @@
#include <filesystem> #include <filesystem>
#include <sstream> #include <sstream>
namespace fs = std::filesystem;
#include "logging.h" #include "logging.h"
#include "include/MinHook.h" #include "MinHook.h"
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include "libcurl/include/curl/curl.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) template <typename ReturnType, typename... Args> ReturnType CallVFunc(int index, void* thisPtr, Args... args)
{ {

View File

@ -1,8 +1,8 @@
#include "pch.h" #include "pch.h"
#include "modmanager.h" #include "modmanager.h"
#include "filesystem.h" #include "filesystem.h"
#include "hookutils.h"
#include "pdef.h" #include "pdef.h"
#include <map> #include <map>
#include <sstream> #include <sstream>
#include <fstream> #include <fstream>
@ -14,11 +14,11 @@ void ModManager::BuildPdef()
fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX); fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX);
fs::remove(MOD_PDEF_PATH); 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; continue;
// this code probably isn't going to be pretty lol // this code probably isn't going to be pretty lol
@ -107,11 +107,11 @@ void ModManager::BuildPdef()
writeStream.close(); writeStream.close();
ModOverrideFile overrideFile; ModOverrideFile overrideFile;
overrideFile.owningMod = nullptr; overrideFile.m_pOwningMod = nullptr;
overrideFile.path = VPK_PDEF_PATH; overrideFile.m_Path = VPK_PDEF_PATH;
if (m_modFiles.find(VPK_PDEF_PATH) == m_modFiles.end()) if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end())
m_modFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile)); m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile));
else else
m_modFiles[VPK_PDEF_PATH] = overrideFile; m_ModFiles[VPK_PDEF_PATH] = overrideFile;
} }

View File

@ -1,30 +1,94 @@
#include "pch.h" #include "pch.h"
#include "playlist.h" #include "playlist.h"
#include "nsmem.h"
#include "concommand.h" #include "concommand.h"
#include "convar.h" #include "convar.h"
#include "gameutils.h"
#include "hookutils.h"
#include "squirrel.h" #include "squirrel.h"
#include "hoststate.h"
#include "serverpresence.h"
typedef char (*Onclc_SetPlaylistVarOverrideType)(void* a1, void* a2); AUTOHOOK_INIT()
Onclc_SetPlaylistVarOverrideType Onclc_SetPlaylistVarOverride;
typedef int (*GetCurrentGamemodeMaxPlayersType)(); // use the R2 namespace for game funcs
GetCurrentGamemodeMaxPlayersType GetCurrentGamemodeMaxPlayers; namespace R2
{
// function type defined in gameutils.h const char* (*GetCurrentPlaylistName)();
SetPlaylistVarOverrideType SetPlaylistVarOverrideOriginal; void (*SetCurrentPlaylist)(const char* pPlaylistName);
GetCurrentPlaylistVarType GetCurrentPlaylistVarOriginal; void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue);
const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides);
} // namespace R2
ConVar* Cvar_ns_use_clc_SetPlaylistVarOverride; 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) void ConCommand_playlist(const CCommand& args)
{ {
if (args.ArgC() < 2) if (args.ArgC() < 2)
return; return;
SetCurrentPlaylist(args.Arg(1)); R2::SetCurrentPlaylist(args.Arg(1));
} }
void ConCommand_setplaylistvaroverride(const CCommand& args) void ConCommand_setplaylistvaroverride(const CCommand& args)
@ -33,74 +97,34 @@ void ConCommand_setplaylistvaroverride(const CCommand& args)
return; return;
for (int i = 1; i < args.ArgC(); i += 2) 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 AUTOHOOK_DISPATCH()
// todo: check mp_lobby here too
if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(GetCurrentPlaylistName(), "private_match"))
return 1;
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) // 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);
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)
{
RegisterConCommand("setplaylist", 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); 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 // 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 // disabled altogether, since the custom menus won't use it anyway this should only really be accepted if you want vanilla client
// compatibility // compatibility
Cvar_ns_use_clc_SetPlaylistVarOverride = new ConVar( Cvar_ns_use_clc_SetPlaylistVarOverride = new ConVar(
"ns_use_clc_SetPlaylistVarOverride", "0", FCVAR_GAMEDLL, "Whether the server should accept clc_SetPlaylistVarOverride messages"); "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 // 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 // this possible respawn, wtf) todo: add a warning for this
{ module.Offset(0x18ED8D).Patch("C3");
NSMem::BytePatch(ba + 0x18ED8D, "C3");
}
// patch to allow setplaylistvaroverride to be called before map init on dedicated and private match launched through the game // 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 #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 "pch.h"
#include "squirrel.h" #include "squirrel.h"
#include "plugins.h" #include "plugins.h"
#include <chrono>
#include "masterserver.h" #include "masterserver.h"
#include "convar.h" #include "convar.h"
#include "serverpresence.h"
#include <chrono>
#include <windows.h> #include <windows.h>
/// <summary> /// <summary>
@ -109,31 +111,31 @@ void initGameState()
} }
// string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading // string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading
SQRESULT SQ_UpdateGameStateUI(void* sqvm) SQRESULT SQ_UpdateGameStateUI(HSquirrelVM* sqvm)
{ {
AcquireSRWLockExclusive(&gameStateLock); AcquireSRWLockExclusive(&gameStateLock);
gameState.map = ClientSq_getstring(sqvm, 1); gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
gameState.mapDisplayName = ClientSq_getstring(sqvm, 2); gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
gameState.playlist = ClientSq_getstring(sqvm, 3); gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
gameState.playlistDisplayName = ClientSq_getstring(sqvm, 4); gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 4);
gameState.connected = ClientSq_getbool(sqvm, 5); gameState.connected = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 5);
gameState.loading = ClientSq_getbool(sqvm, 6); gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 6);
ReleaseSRWLockExclusive(&gameStateLock); ReleaseSRWLockExclusive(&gameStateLock);
return SQRESULT_NOTNULL; return SQRESULT_NOTNULL;
} }
// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit // 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(&gameStateLock);
AcquireSRWLockExclusive(&serverInfoLock); AcquireSRWLockExclusive(&serverInfoLock);
gameState.players = ClientSq_getinteger(sqvm, 1); gameState.players = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 2); serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 2);
gameState.ourScore = ClientSq_getinteger(sqvm, 3); gameState.ourScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 3);
gameState.secondHighestScore = ClientSq_getinteger(sqvm, 4); gameState.secondHighestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
gameState.highestScore = ClientSq_getinteger(sqvm, 5); gameState.highestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 5);
serverInfo.roundBased = ClientSq_getbool(sqvm, 6); serverInfo.roundBased = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 6);
serverInfo.scoreLimit = ClientSq_getbool(sqvm, 7); serverInfo.scoreLimit = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 7);
ReleaseSRWLockExclusive(&gameStateLock); ReleaseSRWLockExclusive(&gameStateLock);
ReleaseSRWLockExclusive(&serverInfoLock); ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL; 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 // string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string
// playlistDisplayName // playlistDisplayName
SQRESULT SQ_UpdateServerInfo(void* sqvm) SQRESULT SQ_UpdateServerInfo(HSquirrelVM* sqvm)
{ {
AcquireSRWLockExclusive(&gameStateLock); AcquireSRWLockExclusive(&gameStateLock);
AcquireSRWLockExclusive(&serverInfoLock); AcquireSRWLockExclusive(&serverInfoLock);
serverInfo.id = ClientSq_getstring(sqvm, 1); serverInfo.id = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
serverInfo.name = ClientSq_getstring(sqvm, 2); serverInfo.name = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
serverInfo.password = ClientSq_getstring(sqvm, 3); serverInfo.password = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
gameState.players = ClientSq_getinteger(sqvm, 4); gameState.players = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 4);
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 5); serverInfo.maxPlayers = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 5);
gameState.map = ClientSq_getstring(sqvm, 6); gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 6);
gameState.mapDisplayName = ClientSq_getstring(sqvm, 7); gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 7);
gameState.playlist = ClientSq_getstring(sqvm, 8); gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 8);
gameState.playlistDisplayName = ClientSq_getstring(sqvm, 9); gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 9);
ReleaseSRWLockExclusive(&gameStateLock); ReleaseSRWLockExclusive(&gameStateLock);
ReleaseSRWLockExclusive(&serverInfoLock); ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL; return SQRESULT_NOTNULL;
} }
// int maxPlayers // int maxPlayers
SQRESULT SQ_UpdateServerInfoBetweenRounds(void* sqvm) SQRESULT SQ_UpdateServerInfoBetweenRounds(HSquirrelVM* sqvm)
{ {
AcquireSRWLockExclusive(&serverInfoLock); AcquireSRWLockExclusive(&serverInfoLock);
serverInfo.id = ClientSq_getstring(sqvm, 1); serverInfo.id = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 1);
serverInfo.name = ClientSq_getstring(sqvm, 2); serverInfo.name = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
serverInfo.password = ClientSq_getstring(sqvm, 3); serverInfo.password = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 3);
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 4); serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
ReleaseSRWLockExclusive(&serverInfoLock); ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL; return SQRESULT_NOTNULL;
} }
// float timeInFuture // float timeInFuture
SQRESULT SQ_UpdateTimeInfo(void* sqvm) SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm)
{ {
AcquireSRWLockExclusive(&serverInfoLock); AcquireSRWLockExclusive(&serverInfoLock);
serverInfo.endTime = ceil(ClientSq_getfloat(sqvm, 1)); serverInfo.endTime = ceil(g_pSquirrel<ScriptContext::CLIENT>->getfloat(sqvm, 1));
ReleaseSRWLockExclusive(&serverInfoLock); ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL; return SQRESULT_NOTNULL;
} }
// bool loading // bool loading
SQRESULT SQ_SetConnected(void* sqvm) SQRESULT SQ_SetConnected(HSquirrelVM* sqvm)
{ {
AcquireSRWLockExclusive(&gameStateLock); AcquireSRWLockExclusive(&gameStateLock);
gameState.loading = ClientSq_getbool(sqvm, 1); gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 1);
ReleaseSRWLockExclusive(&gameStateLock); ReleaseSRWLockExclusive(&gameStateLock);
return SQRESULT_NOTNULL; return SQRESULT_NOTNULL;
} }
SQRESULT SQ_UpdateListenServer(void* sqvm) SQRESULT SQ_UpdateListenServer(HSquirrelVM* sqvm)
{ {
AcquireSRWLockExclusive(&serverInfoLock); AcquireSRWLockExclusive(&serverInfoLock);
serverInfo.id = g_MasterServerManager->m_sOwnServerId; serverInfo.id = g_pMasterServerManager->m_sOwnServerId;
serverInfo.password = Cvar_ns_server_password->GetString(); serverInfo.password = ""; // g_pServerPresence->Cvar_ns_server_password->GetString(); todo this fr
ReleaseSRWLockExclusive(&serverInfoLock); ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL; return SQRESULT_NOTNULL;
} }
@ -384,26 +386,26 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var)
return n; 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 // 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 // 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", "void",
"NSUpdateGameStateUI", "NSUpdateGameStateUI",
"string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading", "string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading",
"", "",
SQ_UpdateGameStateUI); SQ_UpdateGameStateUI);
g_ClientSquirrelManager->AddFuncRegistration( g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
"void", "void",
"NSUpdateGameStateClient", "NSUpdateGameStateClient",
"int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit", "int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit",
"", "",
SQ_UpdateGameStateClient); SQ_UpdateGameStateClient);
g_UISquirrelManager->AddFuncRegistration( g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
"void", "void",
"NSUpdateServerInfo", "NSUpdateServerInfo",
"string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, " "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", "playlistDisplayName",
"", "",
SQ_UpdateServerInfo); SQ_UpdateServerInfo);
g_ClientSquirrelManager->AddFuncRegistration( g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
"void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds); "void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds);
g_ClientSquirrelManager->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo); g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
g_UISquirrelManager->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected); g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
g_UISquirrelManager->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer); 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 initGameState();
void* getPluginObject(PluginObject var); 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