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:
parent
dc0934d29c
commit
841881af9e
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +1,15 @@
|
|||
#include "pch.h"
|
||||
#include "audio.h"
|
||||
#include "dedicated.h"
|
||||
#include "convar.h"
|
||||
|
||||
#include "rapidjson/error/en.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <random>
|
||||
#include "convar.h"
|
||||
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
@ -229,10 +231,8 @@ EventOverrideData::EventOverrideData(const std::string& data, const fs::path& pa
|
|||
}
|
||||
|
||||
// read from after the header first to preserve the empty header, then read the header last
|
||||
wavStream.seekg(sizeof(EMPTY_WAVE), std::ios::beg);
|
||||
wavStream.read(reinterpret_cast<char*>(&data[sizeof(EMPTY_WAVE)]), fileSize - sizeof(EMPTY_WAVE));
|
||||
wavStream.seekg(0, std::ios::beg);
|
||||
wavStream.read(reinterpret_cast<char*>(data), sizeof(EMPTY_WAVE));
|
||||
wavStream.read(reinterpret_cast<char*>(data), fileSize);
|
||||
wavStream.close();
|
||||
|
||||
spdlog::info("Finished async read of audio sample {}", pathString);
|
||||
|
@ -315,6 +315,7 @@ void CustomAudioManager::ClearAudioOverrides()
|
|||
{
|
||||
// stop all miles sounds beforehand
|
||||
// miles_stop_all
|
||||
|
||||
MilesStopAll();
|
||||
|
||||
// this is cancer but it works
|
||||
|
@ -323,15 +324,12 @@ void CustomAudioManager::ClearAudioOverrides()
|
|||
|
||||
// slightly (very) bad
|
||||
// wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them
|
||||
std::unique_lock lock(g_CustomAudioManager.m_loadingMutex);
|
||||
std::unique_lock lock(m_loadingMutex);
|
||||
|
||||
m_loadedAudioOverrides.clear();
|
||||
m_loadedAudioOverridesRegex.clear();
|
||||
}
|
||||
|
||||
typedef bool (*LoadSampleMetadata_Type)(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType);
|
||||
LoadSampleMetadata_Type LoadSampleMetadata_Original;
|
||||
|
||||
template <typename Iter, typename RandomGenerator> Iter select_randomly(Iter start, Iter end, RandomGenerator& g)
|
||||
{
|
||||
std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
|
||||
|
@ -368,6 +366,26 @@ bool ShouldPlayAudioEvent(const char* eventName, const std::shared_ptr<EventOver
|
|||
return true; // good to go
|
||||
}
|
||||
|
||||
// forward declare
|
||||
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
||||
uintptr_t parentEvent, void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType);
|
||||
|
||||
// DO NOT TOUCH THIS FUNCTION
|
||||
// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer.
|
||||
// clang-format off
|
||||
AUTOHOOK(LoadSampleMetadata, mileswin64.dll + 0xF110,
|
||||
bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType))
|
||||
// clang-format on
|
||||
{
|
||||
uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent();
|
||||
|
||||
// Raw source, used for voice data only
|
||||
if (audioType == 0)
|
||||
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
|
||||
|
||||
return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType);
|
||||
}
|
||||
|
||||
// DO NOT INLINE THIS FUNCTION
|
||||
// See comment below.
|
||||
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
||||
|
@ -398,7 +416,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
|||
|
||||
if (!overrideData)
|
||||
// not found either
|
||||
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
|
||||
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
|
||||
else
|
||||
{
|
||||
// cache found pattern to improve performance
|
||||
|
@ -412,7 +430,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
|||
overrideData = iter->second;
|
||||
|
||||
if (!ShouldPlayAudioEvent(eventName, overrideData))
|
||||
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
|
||||
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
|
||||
|
||||
void* data = 0;
|
||||
unsigned int dataLength = 0;
|
||||
|
@ -454,7 +472,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
|||
if (!data)
|
||||
{
|
||||
spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName);
|
||||
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
|
||||
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
|
||||
}
|
||||
|
||||
audioBuffer = data;
|
||||
|
@ -465,51 +483,25 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
|||
*(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength;
|
||||
|
||||
// 64 - Auto-detect sample type
|
||||
bool res = LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, 64);
|
||||
bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64);
|
||||
if (!res)
|
||||
spdlog::error("LoadSampleMetadata failed! The game will crash :(");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// DO NOT TOUCH THIS FUNCTION
|
||||
// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer.
|
||||
bool __fastcall LoadSampleMetadata_Hook(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType)
|
||||
{
|
||||
uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent();
|
||||
|
||||
// Raw source, used for voice data only
|
||||
if (audioType == 0)
|
||||
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
|
||||
|
||||
return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType);
|
||||
}
|
||||
|
||||
typedef bool (*MilesLog_Type)(int level, const char* string);
|
||||
MilesLog_Type MilesLog_Original;
|
||||
|
||||
void __fastcall MilesLog_Hook(int level, const char* string)
|
||||
// clang-format off
|
||||
AUTOHOOK(MilesLog, client.dll + 0x57DAD0,
|
||||
void, __fastcall, (int level, const char* string))
|
||||
// clang-format on
|
||||
{
|
||||
spdlog::info("[MSS] {} - {}", level, string);
|
||||
}
|
||||
|
||||
void InitialiseMilesAudioHooks(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module))
|
||||
{
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, "");
|
||||
|
||||
if (IsDedicatedServer())
|
||||
return;
|
||||
|
||||
uintptr_t milesAudioBase = (uintptr_t)GetModuleHandleA("mileswin64.dll");
|
||||
|
||||
if (!milesAudioBase)
|
||||
return spdlog::error("miles audio not found :terror:");
|
||||
|
||||
HookEnabler hook;
|
||||
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, (char*)milesAudioBase + 0xF110, &LoadSampleMetadata_Hook, reinterpret_cast<LPVOID*>(&LoadSampleMetadata_Original));
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x57DAD0, &MilesLog_Hook, reinterpret_cast<LPVOID*>(&MilesLog_Original));
|
||||
|
||||
MilesStopAll = (MilesStopAll_Type)((char*)baseAddress + 0x580850);
|
||||
MilesStopAll = module.Offset(0x580850).As<MilesStopAll_Type>();
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
#include <regex>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
enum class AudioSelectionStrategy
|
||||
{
|
||||
INVALID = -1,
|
||||
|
@ -46,5 +44,3 @@ class CustomAudioManager
|
|||
};
|
||||
|
||||
extern CustomAudioManager g_CustomAudioManager;
|
||||
|
||||
void InitialiseMilesAudioHooks(HMODULE baseAddress);
|
||||
|
|
|
@ -1,26 +1,28 @@
|
|||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "bansystem.h"
|
||||
#include "serverauthentication.h"
|
||||
#include "maxplayers.h"
|
||||
#include "concommand.h"
|
||||
#include "miscserverscript.h"
|
||||
#include <filesystem>
|
||||
#include "r2server.h"
|
||||
#include "r2engine.h"
|
||||
#include "nsprefix.h"
|
||||
#include <ctime>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
const char* BANLIST_PATH_SUFFIX = "/banlist.txt";
|
||||
const char BANLIST_COMMENT_CHAR = '#';
|
||||
|
||||
ServerBanSystem* g_ServerBanSystem;
|
||||
ServerBanSystem* g_pBanSystem;
|
||||
|
||||
void ServerBanSystem::OpenBanlist()
|
||||
{
|
||||
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/banlist.txt");
|
||||
std::stringstream enabledModsStringStream;
|
||||
std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt");
|
||||
|
||||
if (!enabledModsStream.fail())
|
||||
if (!banlistStream.fail())
|
||||
{
|
||||
std::string line;
|
||||
while (std::getline(enabledModsStream, line))
|
||||
while (std::getline(banlistStream, line))
|
||||
{
|
||||
// ignore line if first char is # or line is empty
|
||||
if (line == "" || line.front() == BANLIST_COMMENT_CHAR)
|
||||
|
@ -41,7 +43,7 @@ void ServerBanSystem::OpenBanlist()
|
|||
m_vBannedUids.push_back(strtoull(uid.c_str(), nullptr, 10));
|
||||
}
|
||||
|
||||
enabledModsStream.close();
|
||||
banlistStream.close();
|
||||
}
|
||||
|
||||
// open write stream for banlist // dont do this to allow for all time access
|
||||
|
@ -182,15 +184,14 @@ void ConCommand_ban(const CCommand& args)
|
|||
if (args.ArgC() < 2)
|
||||
return;
|
||||
|
||||
// assuming maxplayers 32
|
||||
for (int i = 0; i < 32; i++)
|
||||
for (int i = 0; i < R2::GetMaxPlayers(); i++)
|
||||
{
|
||||
void* player = GetPlayerByIndex(i);
|
||||
R2::CBaseClient* player = &R2::g_pClientArray[i];
|
||||
|
||||
if (!strcmp((char*)player + 0x16, args.Arg(1)) || !strcmp((char*)player + 0xF500, args.Arg(1)))
|
||||
if (!strcmp(player->m_Name, args.Arg(1)) || !strcmp(player->m_UID, args.Arg(1)))
|
||||
{
|
||||
g_ServerBanSystem->BanUID(strtoull((char*)player + 0xF500, nullptr, 10));
|
||||
CBaseClient__Disconnect(player, 1, "Banned from server");
|
||||
g_pBanSystem->BanUID(strtoull(player->m_UID, nullptr, 10));
|
||||
R2::CBaseClient__Disconnect(player, 1, "Banned from server");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -202,20 +203,20 @@ void ConCommand_unban(const CCommand& args)
|
|||
return;
|
||||
|
||||
// assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything
|
||||
g_ServerBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10));
|
||||
g_pBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10));
|
||||
}
|
||||
|
||||
void ConCommand_clearbanlist(const CCommand& args)
|
||||
{
|
||||
g_ServerBanSystem->ClearBanlist();
|
||||
g_pBanSystem->ClearBanlist();
|
||||
}
|
||||
|
||||
void InitialiseBanSystem(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_RELIESON("engine.dll", BanSystem, ConCommand, (CModule module))
|
||||
{
|
||||
g_ServerBanSystem = new ServerBanSystem;
|
||||
g_ServerBanSystem->OpenBanlist();
|
||||
g_pBanSystem = new ServerBanSystem;
|
||||
g_pBanSystem->OpenBanlist();
|
||||
|
||||
RegisterConCommand("ban", ConCommand_ban, "bans a given player by uid or name", FCVAR_GAMEDLL);
|
||||
RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_NONE);
|
||||
RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_NONE);
|
||||
RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_GAMEDLL);
|
||||
RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_GAMEDLL);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,4 @@ class ServerBanSystem
|
|||
bool IsUIDAllowed(uint64_t uid);
|
||||
};
|
||||
|
||||
extern ServerBanSystem* g_ServerBanSystem;
|
||||
|
||||
void InitialiseBanSystem(HMODULE baseAddress);
|
||||
extern ServerBanSystem* g_pBanSystem;
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
#include "pch.h"
|
||||
#include "buildainfile.h"
|
||||
#include "convar.h"
|
||||
#include "hookutils.h"
|
||||
#include "hoststate.h"
|
||||
#include "r2engine.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include "nsmem.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
const int AINET_VERSION_NUMBER = 57;
|
||||
const int AINET_SCRIPT_VERSION_NUMBER = 21;
|
||||
const int MAP_VERSION_TEMP = 30;
|
||||
const int PLACEHOLDER_CRC = 0;
|
||||
const int MAX_HULLS = 5;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CAI_NodeLink
|
||||
{
|
||||
short srcId;
|
||||
|
@ -24,6 +24,7 @@ struct CAI_NodeLink
|
|||
char unk2[5];
|
||||
int64_t flags;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CAI_NodeLinkDisk
|
||||
|
@ -33,7 +34,9 @@ struct CAI_NodeLinkDisk
|
|||
char unk0;
|
||||
bool hulls[MAX_HULLS];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CAI_Node
|
||||
{
|
||||
int index; // not present on disk
|
||||
|
@ -62,6 +65,7 @@ struct CAI_Node
|
|||
char unk9[8]; // padding until next bit
|
||||
char unk10[8]; // should match up to unk6 on disk
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// the way CAI_Nodes are represented in on-disk ain files
|
||||
#pragma pack(push, 1)
|
||||
|
@ -81,7 +85,9 @@ struct CAI_NodeDisk
|
|||
short unk5;
|
||||
char unk6[8];
|
||||
}; // total size of 68 bytes
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct UnkNodeStruct0
|
||||
{
|
||||
int index;
|
||||
|
@ -106,10 +112,12 @@ struct UnkNodeStruct0
|
|||
char pad4[132];
|
||||
char unk5;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
int* pUnkStruct0Count;
|
||||
UnkNodeStruct0*** pppUnkNodeStruct0s;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct UnkLinkStruct1
|
||||
{
|
||||
short unk0;
|
||||
|
@ -119,10 +127,12 @@ struct UnkLinkStruct1
|
|||
char unk4;
|
||||
char unk5;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
int* pUnkLinkStruct1Count;
|
||||
UnkLinkStruct1*** pppUnkStruct1s;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CAI_ScriptNode
|
||||
{
|
||||
float x;
|
||||
|
@ -130,7 +140,9 @@ struct CAI_ScriptNode
|
|||
float z;
|
||||
uint64_t scriptdata;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CAI_Network
|
||||
{
|
||||
// +0
|
||||
|
@ -160,16 +172,16 @@ struct CAI_Network
|
|||
// +84176
|
||||
CAI_Node** nodes;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
char** pUnkServerMapversionGlobal;
|
||||
char* pMapName;
|
||||
|
||||
ConVar* Cvar_ns_ai_dumpAINfileFromLoad;
|
||||
|
||||
void DumpAINInfo(CAI_Network* aiNetwork)
|
||||
{
|
||||
fs::path writePath("r2/maps/graphs");
|
||||
writePath /= pMapName;
|
||||
fs::path writePath(fmt::format("{}/maps/graphs", R2::g_pModName));
|
||||
writePath /= R2::g_pHostState->m_levelName;
|
||||
writePath += ".ain";
|
||||
|
||||
// dump from memory
|
||||
|
@ -349,20 +361,20 @@ void DumpAINInfo(CAI_Network* aiNetwork)
|
|||
writeStream.close();
|
||||
}
|
||||
|
||||
typedef void (*CAI_NetworkBuilder__BuildType)(void* builder, CAI_Network* aiNetwork, void* unknown);
|
||||
CAI_NetworkBuilder__BuildType CAI_NetworkBuilder__Build;
|
||||
|
||||
void CAI_NetworkBuilder__BuildHook(void* builder, CAI_Network* aiNetwork, void* unknown)
|
||||
// clang-format off
|
||||
AUTOHOOK(CAI_NetworkBuilder__Build, server.dll + 0x385E20,
|
||||
void, __fastcall, (void* builder, CAI_Network* aiNetwork, void* unknown))
|
||||
// clang-format on
|
||||
{
|
||||
CAI_NetworkBuilder__Build(builder, aiNetwork, unknown);
|
||||
|
||||
DumpAINInfo(aiNetwork);
|
||||
}
|
||||
|
||||
typedef void (*LoadAINFileType)(void* aimanager, void* buf, const char* filename);
|
||||
LoadAINFileType LoadAINFile;
|
||||
|
||||
void LoadAINFileHook(void* aimanager, void* buf, const char* filename)
|
||||
// clang-format off
|
||||
AUTOHOOK(LoadAINFile, server.dll + 0x3933A0,
|
||||
void, __fastcall, (void* aimanager, void* buf, const char* filename))
|
||||
// clang-format on
|
||||
{
|
||||
LoadAINFile(aimanager, buf, filename);
|
||||
|
||||
|
@ -373,28 +385,16 @@ void LoadAINFileHook(void* aimanager, void* buf, const char* filename)
|
|||
}
|
||||
}
|
||||
|
||||
void InitialiseBuildAINFileHooks(HMODULE baseAddress)
|
||||
ON_DLL_LOAD("server.dll", BuildAINFile, (CModule module))
|
||||
{
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
Cvar_ns_ai_dumpAINfileFromLoad = new ConVar(
|
||||
"ns_ai_dumpAINfileFromLoad", "0", FCVAR_NONE, "For debugging: whether we should dump ain data for ains loaded from disk");
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, (char*)baseAddress + 0x385E20, &CAI_NetworkBuilder__BuildHook, reinterpret_cast<LPVOID*>(&CAI_NetworkBuilder__Build));
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x3933A0, &LoadAINFileHook, reinterpret_cast<LPVOID*>(&LoadAINFile));
|
||||
|
||||
pUnkStruct0Count = (int*)((char*)baseAddress + 0x1063BF8);
|
||||
pppUnkNodeStruct0s = (UnkNodeStruct0***)((char*)baseAddress + 0x1063BE0);
|
||||
|
||||
pUnkLinkStruct1Count = (int*)((char*)baseAddress + 0x1063AA8);
|
||||
pppUnkStruct1s = (UnkLinkStruct1***)((char*)baseAddress + 0x1063A90);
|
||||
pUnkServerMapversionGlobal = (char**)((char*)baseAddress + 0xBFBE08);
|
||||
pMapName = (char*)baseAddress + 0x1053370;
|
||||
|
||||
uintptr_t base = (uintptr_t)baseAddress;
|
||||
|
||||
// remove a check that prevents a logging function in link generation from working
|
||||
// due to the sheer amount of logging this is a massive perf hit to generation, but spewlog_enable 0 exists so whatever
|
||||
NSMem::NOP(base + 0x3889B6, 6);
|
||||
NSMem::NOP(base + 0x3889BF, 6);
|
||||
pUnkStruct0Count = module.Offset(0x1063BF8).As<int*>();
|
||||
pppUnkNodeStruct0s = module.Offset(0x1063BE0).As<UnkNodeStruct0***>();
|
||||
pUnkLinkStruct1Count = module.Offset(0x1063AA8).As<int*>();
|
||||
pppUnkStruct1s = module.Offset(0x1063A90).As<UnkLinkStruct1***>();
|
||||
pUnkServerMapversionGlobal = module.Offset(0xBFBE08).As<char**>();
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void InitialiseBuildAINFileHooks(HMODULE baseAddress);
|
|
@ -1,13 +1,11 @@
|
|||
#include "pch.h"
|
||||
#include "convar.h"
|
||||
#include "concommand.h"
|
||||
#include "chatcommand.h"
|
||||
#include "localchatwriter.h"
|
||||
|
||||
// note: isIngameChat is an int64 because the whole register the arg is stored in needs to be 0'd out to work
|
||||
// if isIngameChat is false, we use network chat instead
|
||||
typedef void(__fastcall* ClientSayTextType)(void* a1, const char* message, __int64 isIngameChat, bool isTeamChat);
|
||||
ClientSayTextType ClientSayText;
|
||||
void(__fastcall* ClientSayText)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat);
|
||||
|
||||
void ConCommand_say(const CCommand& args)
|
||||
{
|
||||
|
@ -29,9 +27,9 @@ void ConCommand_log(const CCommand& args)
|
|||
}
|
||||
}
|
||||
|
||||
void InitialiseChatCommands(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientChatCommand, ConCommand, (CModule module))
|
||||
{
|
||||
ClientSayText = (ClientSayTextType)((char*)baseAddress + 0x54780);
|
||||
ClientSayText = module.Offset(0x54780).As<void(__fastcall*)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat)>();
|
||||
RegisterConCommand("say", ConCommand_say, "Enters a message in public chat", FCVAR_CLIENTDLL);
|
||||
RegisterConCommand("say_team", ConCommand_say_team, "Enters a message in team chat", FCVAR_CLIENTDLL);
|
||||
RegisterConCommand("log", ConCommand_log, "Log a message to the local chat window", FCVAR_CLIENTDLL);
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void InitialiseChatCommands(HMODULE baseAddress);
|
|
@ -1,12 +1,9 @@
|
|||
#include "pch.h"
|
||||
#include "clientauthhooks.h"
|
||||
#include "hookutils.h"
|
||||
#include "gameutils.h"
|
||||
#include "masterserver.h"
|
||||
#include "convar.h"
|
||||
#include "r2client.h"
|
||||
|
||||
typedef void (*AuthWithStryderType)(void* a1);
|
||||
AuthWithStryderType AuthWithStryder;
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
ConVar* Cvar_ns_has_agreed_to_send_token;
|
||||
|
||||
|
@ -15,51 +12,53 @@ const int NOT_DECIDED_TO_SEND_TOKEN = 0;
|
|||
const int AGREED_TO_SEND_TOKEN = 1;
|
||||
const int DISAGREED_TO_SEND_TOKEN = 2;
|
||||
|
||||
typedef char* (*Auth3PTokenType)();
|
||||
Auth3PTokenType Auth3PToken;
|
||||
|
||||
char* token_location = 0x0;
|
||||
|
||||
void AuthWithStryderHook(void* a1)
|
||||
// clang-format off
|
||||
AUTOHOOK(AuthWithStryder, engine.dll + 0x1843A0,
|
||||
void, __fastcall, (void* a1))
|
||||
// clang-format on
|
||||
{
|
||||
// game will call this forever, until it gets a valid auth key
|
||||
// so, we need to manually invalidate our key until we're authed with northstar, then we'll allow game to auth with stryder
|
||||
if (!g_MasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN)
|
||||
if (!g_pMasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN)
|
||||
{
|
||||
// if player has agreed to send token and we aren't already authing, try to auth
|
||||
if (Cvar_ns_has_agreed_to_send_token->GetInt() == AGREED_TO_SEND_TOKEN &&
|
||||
!g_MasterServerManager->m_bOriginAuthWithMasterServerInProgress)
|
||||
g_MasterServerManager->AuthenticateOriginWithMasterServer(g_LocalPlayerUserID, g_LocalPlayerOriginToken);
|
||||
!g_pMasterServerManager->m_bOriginAuthWithMasterServerInProgress)
|
||||
g_pMasterServerManager->AuthenticateOriginWithMasterServer(R2::g_pLocalPlayerUserID, R2::g_pLocalPlayerOriginToken);
|
||||
|
||||
// invalidate key so auth will fail
|
||||
*g_LocalPlayerOriginToken = 0;
|
||||
*R2::g_pLocalPlayerOriginToken = 0;
|
||||
}
|
||||
|
||||
AuthWithStryder(a1);
|
||||
}
|
||||
|
||||
char* Auth3PTokenHook()
|
||||
char* p3PToken;
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(Auth3PToken, engine.dll + 0x183760,
|
||||
char*, __fastcall, ())
|
||||
// clang-format on
|
||||
{
|
||||
if (g_MasterServerManager->m_sOwnClientAuthToken[0] != 0)
|
||||
if (g_pMasterServerManager->m_sOwnClientAuthToken[0])
|
||||
{
|
||||
memset(token_location, 0x0, 1024);
|
||||
strcpy(token_location, "Protocol 3: Protect the Pilot");
|
||||
memset(p3PToken, 0x0, 1024);
|
||||
strcpy(p3PToken, "Protocol 3: Protect the Pilot");
|
||||
}
|
||||
|
||||
return Auth3PToken();
|
||||
}
|
||||
|
||||
void InitialiseClientAuthHooks(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientAuthHooks, ConVar, (CModule module))
|
||||
{
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
p3PToken = module.Offset(0x13979D80).As<char*>();
|
||||
|
||||
// this cvar will save to cfg once initially agreed with
|
||||
Cvar_ns_has_agreed_to_send_token = new ConVar(
|
||||
"ns_has_agreed_to_send_token",
|
||||
"0",
|
||||
FCVAR_ARCHIVE_PLAYERPROFILE,
|
||||
"whether the user has agreed to send their origin token to the northstar masterserver");
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1843A0, &AuthWithStryderHook, reinterpret_cast<LPVOID*>(&AuthWithStryder));
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x183760, &Auth3PTokenHook, reinterpret_cast<LPVOID*>(&Auth3PToken));
|
||||
token_location = (char*)baseAddress + 0x13979D80;
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#pragma once
|
||||
void InitialiseClientAuthHooks(HMODULE baseAddress);
|
|
@ -1,29 +1,22 @@
|
|||
#include "pch.h"
|
||||
#include "clientchathooks.h"
|
||||
#include <rapidjson/document.h>
|
||||
#include "squirrel.h"
|
||||
#include "serverchathooks.h"
|
||||
#include "localchatwriter.h"
|
||||
|
||||
typedef void(__fastcall* CHudChat__AddGameLineType)(void* self, const char* message, int fromPlayerId, bool isteam, bool isdead);
|
||||
CHudChat__AddGameLineType CHudChat__AddGameLine;
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
struct ChatTags
|
||||
{
|
||||
bool whisper;
|
||||
bool team;
|
||||
bool dead;
|
||||
};
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
static void CHudChat__AddGameLineHook(void* self, const char* message, int inboxId, bool isTeam, bool isDead)
|
||||
// clang-format off
|
||||
AUTOHOOK(CHudChat__AddGameLine, client.dll + 0x22E580,
|
||||
void, __fastcall, (void* self, const char* message, int inboxId, bool isTeam, bool isDead))
|
||||
// clang-format on
|
||||
{
|
||||
// This hook is called for each HUD, but we only want our logic to run once.
|
||||
if (self != *CHudChat::allHuds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_ClientSquirrelManager->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR)
|
||||
if (g_pSquirrel<ScriptContext::CLIENT>->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR)
|
||||
{
|
||||
int senderId = inboxId & CUSTOM_MESSAGE_INDEX_MASK;
|
||||
bool isAnonymous = senderId == 0;
|
||||
|
@ -38,58 +31,53 @@ static void CHudChat__AddGameLineHook(void* self, const char* message, int inbox
|
|||
payload = message + 1;
|
||||
}
|
||||
|
||||
g_ClientSquirrelManager->pusharg((int)senderId - 1);
|
||||
g_ClientSquirrelManager->pusharg(payload);
|
||||
g_ClientSquirrelManager->pusharg(isTeam);
|
||||
g_ClientSquirrelManager->pusharg(isDead);
|
||||
g_ClientSquirrelManager->pusharg(type);
|
||||
g_ClientSquirrelManager->call(5);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, (int)senderId - 1);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->pushstring(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, payload);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isTeam);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isDead);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, type);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->call(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
|
||||
{
|
||||
CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// void NSChatWrite( int context, string str )
|
||||
static SQRESULT SQ_ChatWrite(void* sqvm)
|
||||
SQRESULT SQ_ChatWrite(HSquirrelVM* sqvm)
|
||||
{
|
||||
int context = ClientSq_getinteger(sqvm, 1);
|
||||
const char* str = ClientSq_getstring(sqvm, 2);
|
||||
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
|
||||
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
|
||||
|
||||
LocalChatWriter((LocalChatWriter::Context)context).Write(str);
|
||||
return SQRESULT_NOTNULL;
|
||||
return SQRESULT_NULL;
|
||||
}
|
||||
|
||||
// void NSChatWriteRaw( int context, string str )
|
||||
static SQRESULT SQ_ChatWriteRaw(void* sqvm)
|
||||
SQRESULT SQ_ChatWriteRaw(HSquirrelVM* sqvm)
|
||||
{
|
||||
int context = ClientSq_getinteger(sqvm, 1);
|
||||
const char* str = ClientSq_getstring(sqvm, 2);
|
||||
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
|
||||
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
|
||||
|
||||
LocalChatWriter((LocalChatWriter::Context)context).InsertText(str);
|
||||
return SQRESULT_NOTNULL;
|
||||
return SQRESULT_NULL;
|
||||
}
|
||||
|
||||
// void NSChatWriteLine( int context, string str )
|
||||
static SQRESULT SQ_ChatWriteLine(void* sqvm)
|
||||
SQRESULT SQ_ChatWriteLine(HSquirrelVM* sqvm)
|
||||
{
|
||||
int context = ClientSq_getinteger(sqvm, 1);
|
||||
const char* str = ClientSq_getstring(sqvm, 2);
|
||||
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
|
||||
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
|
||||
|
||||
LocalChatWriter((LocalChatWriter::Context)context).WriteLine(str);
|
||||
return SQRESULT_NOTNULL;
|
||||
return SQRESULT_NULL;
|
||||
}
|
||||
|
||||
void InitialiseClientChatHooks(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientChatHooks, ClientSquirrel, (CModule module))
|
||||
{
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x22E580, &CHudChat__AddGameLineHook, reinterpret_cast<LPVOID*>(&CHudChat__AddGameLine));
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
|
||||
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw);
|
||||
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine);
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "serverchathooks.h"
|
||||
|
||||
void InitialiseClientChatHooks(HMODULE baseAddress);
|
|
@ -1,13 +1,14 @@
|
|||
#include "pch.h"
|
||||
#include "clientruihooks.h"
|
||||
#include "convar.h"
|
||||
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
ConVar* Cvar_rui_drawEnable;
|
||||
|
||||
typedef char (*DrawRUIFuncType)(void* a1, float* a2);
|
||||
DrawRUIFuncType DrawRUIFunc;
|
||||
|
||||
char DrawRUIFuncHook(void* a1, float* a2)
|
||||
// clang-format off
|
||||
AUTOHOOK(DrawRUIFunc, engine.dll + 0xFC500,
|
||||
bool, __fastcall, (void* a1, float* a2))
|
||||
// clang-format on
|
||||
{
|
||||
if (!Cvar_rui_drawEnable->GetBool())
|
||||
return 0;
|
||||
|
@ -15,10 +16,9 @@ char DrawRUIFuncHook(void* a1, float* a2)
|
|||
return DrawRUIFunc(a1, a2);
|
||||
}
|
||||
|
||||
void InitialiseEngineClientRUIHooks(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", RUI, ConVar, (CModule module))
|
||||
{
|
||||
Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn");
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xFC500, &DrawRUIFuncHook, reinterpret_cast<LPVOID*>(&DrawRUIFunc));
|
||||
Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn");
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#pragma once
|
||||
void InitialiseEngineClientRUIHooks(HMODULE baseAddress);
|
|
@ -1,21 +1,21 @@
|
|||
#include "pch.h"
|
||||
#include "clientvideooverrides.h"
|
||||
#include "modmanager.h"
|
||||
#include "nsmem.h"
|
||||
|
||||
typedef void* (*BinkOpenType)(const char* path, uint32_t flags);
|
||||
BinkOpenType BinkOpen;
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
void* BinkOpenHook(const char* path, uint32_t flags)
|
||||
// clang-format off
|
||||
AUTOHOOK_PROCADDRESS(BinkOpen, bink2w64.dll, BinkOpen,
|
||||
void*, __fastcall, (const char* path, uint32_t flags))
|
||||
// clang-format on
|
||||
{
|
||||
std::string filename(fs::path(path).filename().string());
|
||||
spdlog::info("BinkOpen {}", filename);
|
||||
|
||||
// figure out which mod is handling the bink
|
||||
Mod* fileOwner = nullptr;
|
||||
for (Mod& mod : g_ModManager->m_loadedMods)
|
||||
for (Mod& mod : g_pModManager->m_LoadedMods)
|
||||
{
|
||||
if (!mod.Enabled)
|
||||
if (!mod.m_bEnabled)
|
||||
continue;
|
||||
|
||||
if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end())
|
||||
|
@ -25,23 +25,18 @@ void* BinkOpenHook(const char* path, uint32_t flags)
|
|||
if (fileOwner)
|
||||
{
|
||||
// create new path
|
||||
fs::path binkPath(fileOwner->ModDirectory / "media" / filename);
|
||||
fs::path binkPath(fileOwner->m_ModDirectory / "media" / filename);
|
||||
return BinkOpen(binkPath.string().c_str(), flags);
|
||||
}
|
||||
else
|
||||
return BinkOpen(path, flags);
|
||||
}
|
||||
|
||||
void InitialiseEngineClientVideoOverrides(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT("client.dll", BinkVideo, (CModule module))
|
||||
{
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
// remove engine check for whether the bik we're trying to load exists in r2/media, as this will fail for biks in mods
|
||||
// note: the check in engine is actually unnecessary, so it's just useless in practice and we lose nothing by removing it
|
||||
NSMem::NOP((uintptr_t)baseAddress + 0x459AD, 6);
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(
|
||||
hook,
|
||||
reinterpret_cast<void*>(GetProcAddress(GetModuleHandleA("bink2w64.dll"), "BinkOpen")),
|
||||
&BinkOpenHook,
|
||||
reinterpret_cast<LPVOID*>(&BinkOpen));
|
||||
module.Offset(0x459AD).NOP(6);
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#pragma once
|
||||
void InitialiseEngineClientVideoOverrides(HMODULE baseAddress);
|
|
@ -1,28 +1,9 @@
|
|||
#include "pch.h"
|
||||
#include "concommand.h"
|
||||
#include "gameutils.h"
|
||||
#include "misccommands.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
typedef void (*ConCommandConstructorType)(
|
||||
ConCommand* newCommand, const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, void* parent);
|
||||
ConCommandConstructorType conCommandConstructor;
|
||||
|
||||
void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags)
|
||||
{
|
||||
spdlog::info("Registering ConCommand {}", name);
|
||||
|
||||
// no need to free this ever really, it should exist as long as game does
|
||||
ConCommand* newCommand = new ConCommand;
|
||||
conCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
|
||||
}
|
||||
|
||||
void InitialiseConCommands(HMODULE baseAddress)
|
||||
{
|
||||
conCommandConstructor = (ConCommandConstructorType)((char*)baseAddress + 0x415F60);
|
||||
AddMiscConCommands();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns true if this is a command
|
||||
// Output : bool
|
||||
|
@ -57,7 +38,7 @@ bool ConCommandBase::IsRegistered(void) const
|
|||
//-----------------------------------------------------------------------------
|
||||
bool ConCommandBase::IsFlagSet(int nFlags) const
|
||||
{
|
||||
return false; // !TODO: Returning false on every query? (original implementation in Northstar before ConCommandBase refactor)
|
||||
return m_nFlags & nFlags;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -138,3 +119,33 @@ char* ConCommandBase::CopyString(const char* szFrom) const
|
|||
}
|
||||
return szTo;
|
||||
}
|
||||
|
||||
typedef void (*ConCommandConstructorType)(
|
||||
ConCommand* newCommand, const char* name, FnCommandCallback_t callback, const char* helpString, int flags, void* parent);
|
||||
ConCommandConstructorType ConCommandConstructor;
|
||||
|
||||
void RegisterConCommand(const char* name, FnCommandCallback_t callback, const char* helpString, int flags)
|
||||
{
|
||||
spdlog::info("Registering ConCommand {}", name);
|
||||
|
||||
// no need to free this ever really, it should exist as long as game does
|
||||
ConCommand* newCommand = new ConCommand;
|
||||
ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
|
||||
}
|
||||
|
||||
void RegisterConCommand(
|
||||
const char* name, FnCommandCallback_t callback, const char* helpString, int flags, FnCommandCompletionCallback completionCallback)
|
||||
{
|
||||
spdlog::info("Registering ConCommand {}", name);
|
||||
|
||||
// no need to free this ever really, it should exist as long as game does
|
||||
ConCommand* newCommand = new ConCommand;
|
||||
ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
|
||||
newCommand->m_pCompletionCallback = completionCallback;
|
||||
}
|
||||
|
||||
ON_DLL_LOAD("engine.dll", ConCommand, (CModule module))
|
||||
{
|
||||
ConCommandConstructor = module.Offset(0x415F60).As<ConCommandConstructorType>();
|
||||
AddMiscConCommands();
|
||||
}
|
||||
|
|
|
@ -72,6 +72,20 @@ inline const char* CCommand::operator[](int nIndex) const
|
|||
return Arg(nIndex);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Called when a ConCommand needs to execute
|
||||
//-----------------------------------------------------------------------------
|
||||
typedef void (*FnCommandCallback_t)(const CCommand& command);
|
||||
|
||||
#define COMMAND_COMPLETION_MAXITEMS 64
|
||||
#define COMMAND_COMPLETION_ITEM_LENGTH 128
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Returns 0 to COMMAND_COMPLETION_MAXITEMS worth of completion strings
|
||||
//-----------------------------------------------------------------------------
|
||||
typedef int (*__fastcall FnCommandCompletionCallback)(
|
||||
const char* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]);
|
||||
|
||||
// From r5reloaded
|
||||
class ConCommandBase
|
||||
{
|
||||
|
@ -113,8 +127,8 @@ class ConCommand : public ConCommandBase
|
|||
void Init(void);
|
||||
bool IsCommand(void) const;
|
||||
|
||||
void* m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase.
|
||||
void* m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax').
|
||||
FnCommandCallback_t m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase.
|
||||
FnCommandCompletionCallback m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax').
|
||||
int m_nCallbackFlags {}; // 0x0050
|
||||
char pad_0054[4]; // 0x0054
|
||||
int unk0; // 0x0058
|
||||
|
@ -122,6 +136,5 @@ class ConCommand : public ConCommandBase
|
|||
}; // Size: 0x0060
|
||||
|
||||
void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags);
|
||||
void InitialiseConCommands(HMODULE baseAddress);
|
||||
|
||||
#define MAKE_CONCMD(name, helpStr, flags, fn) RegisterConCommand(name, fn, helpStr, flags);
|
||||
void RegisterConCommand(
|
||||
const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, FnCommandCompletionCallback completionCallback);
|
||||
|
|
|
@ -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 "";
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
enum class ScriptContext : int
|
||||
{
|
||||
SERVER,
|
||||
CLIENT,
|
||||
UI,
|
||||
NONE
|
||||
};
|
||||
|
||||
const char* GetContextName(ScriptContext context);
|
|
@ -1,13 +1,11 @@
|
|||
#include <float.h>
|
||||
|
||||
#include "pch.h"
|
||||
#include "bits.h"
|
||||
#include "cvar.h"
|
||||
#include "convar.h"
|
||||
#include "hookutils.h"
|
||||
#include "gameutils.h"
|
||||
#include "sourceinterface.h"
|
||||
|
||||
#include <float.h>
|
||||
|
||||
typedef void (*ConVarRegisterType)(
|
||||
ConVar* pConVar,
|
||||
const char* pszName,
|
||||
|
@ -27,25 +25,19 @@ ConVarMallocType conVarMalloc;
|
|||
void* g_pConVar_Vtable = nullptr;
|
||||
void* g_pIConVar_Vtable = nullptr;
|
||||
|
||||
typedef bool (*CvarIsFlagSetType)(ConVar* self, int flags);
|
||||
CvarIsFlagSetType CvarIsFlagSet;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: ConVar interface initialization
|
||||
//-----------------------------------------------------------------------------
|
||||
void InitialiseConVars(HMODULE baseAddress)
|
||||
ON_DLL_LOAD("engine.dll", ConVar, (CModule module))
|
||||
{
|
||||
conVarMalloc = (ConVarMallocType)((char*)baseAddress + 0x415C20);
|
||||
conVarRegister = (ConVarRegisterType)((char*)baseAddress + 0x417230);
|
||||
conVarMalloc = module.Offset(0x415C20).As<ConVarMallocType>();
|
||||
conVarRegister = module.Offset(0x417230).As<ConVarRegisterType>();
|
||||
|
||||
g_pConVar_Vtable = (char*)baseAddress + 0x67FD28;
|
||||
g_pIConVar_Vtable = (char*)baseAddress + 0x67FDC8;
|
||||
g_pConVar_Vtable = module.Offset(0x67FD28);
|
||||
g_pIConVar_Vtable = module.Offset(0x67FDC8);
|
||||
|
||||
g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007");
|
||||
g_pCVar = *g_pCVarInterface;
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x417FA0, &ConVar::IsFlagSet, reinterpret_cast<LPVOID*>(&CvarIsFlagSet));
|
||||
R2::g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007");
|
||||
R2::g_pCVar = *R2::g_pCVarInterface;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -74,7 +66,7 @@ ConVar::ConVar(
|
|||
float fMin,
|
||||
bool bMax,
|
||||
float fMax,
|
||||
void* pCallback)
|
||||
FnChangeCallback_t pCallback)
|
||||
{
|
||||
spdlog::info("Registering Convar {}", pszName);
|
||||
|
||||
|
@ -476,16 +468,12 @@ bool ConVar::IsCommand(void) const
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Test each ConVar query before setting the value.
|
||||
// Input : *pConVar - nFlags
|
||||
// Input : nFlags
|
||||
// Output : False if change is permitted, true if not.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool ConVar::IsFlagSet(ConVar* pConVar, int nFlags)
|
||||
bool ConVar::IsFlagSet(int nFlags) const
|
||||
{
|
||||
// unrestrict FCVAR_DEVELOPMENTONLY and FCVAR_HIDDEN
|
||||
if (pConVar && (nFlags == FCVAR_DEVELOPMENTONLY || nFlags == FCVAR_HIDDEN))
|
||||
return false;
|
||||
|
||||
return CvarIsFlagSet(pConVar, nFlags);
|
||||
return m_ConCommandBase.m_nFlags & nFlags;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -58,15 +58,58 @@
|
|||
// ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd.
|
||||
#define FCVAR_SERVER_CANNOT_QUERY \
|
||||
(1 << 29) // If this is set, then the server is not allowed to query this cvar's value (via IServerPluginHelpers::StartQueryCvarValue).
|
||||
|
||||
// !!!NOTE!!! : this is likely incorrect, there are multiple concommands that the vanilla game registers with this flag that 100% should not
|
||||
// be remotely executable i.e. multiple commands that only exist on client (screenshot, joystick_initialize) we now use
|
||||
// FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS in all places this flag was previously used
|
||||
#define FCVAR_CLIENTCMD_CAN_EXECUTE \
|
||||
(1 << 30) // IVEngineClient::ClientCmd is allowed to execute this command.
|
||||
// Note: IVEngineClient::ClientCmd_Unrestricted can run any client command.
|
||||
|
||||
#define FCVAR_ACCESSIBLE_FROM_THREADS (1 << 25) // used as a debugging tool necessary to check material system thread convars
|
||||
|
||||
// TODO: could be cool to repurpose these for northstar use somehow?
|
||||
// #define FCVAR_AVAILABLE (1<<26)
|
||||
// #define FCVAR_AVAILABLE (1<<27)
|
||||
// #define FCVAR_AVAILABLE (1<<31)
|
||||
|
||||
// flag => string stuff
|
||||
const std::multimap<int, const char*> g_PrintCommandFlags = {
|
||||
{FCVAR_UNREGISTERED, "UNREGISTERED"},
|
||||
{FCVAR_DEVELOPMENTONLY, "DEVELOPMENTONLY"},
|
||||
{FCVAR_GAMEDLL, "GAMEDLL"},
|
||||
{FCVAR_CLIENTDLL, "CLIENTDLL"},
|
||||
{FCVAR_HIDDEN, "HIDDEN"},
|
||||
{FCVAR_PROTECTED, "PROTECTED"},
|
||||
{FCVAR_SPONLY, "SPONLY"},
|
||||
{FCVAR_ARCHIVE, "ARCHIVE"},
|
||||
{FCVAR_NOTIFY, "NOTIFY"},
|
||||
{FCVAR_USERINFO, "USERINFO"},
|
||||
|
||||
// TODO: PRINTABLEONLY and GAMEDLL_FOR_REMOTE_CLIENTS are both 1<<10, one is for vars and one is for commands
|
||||
// this fucking sucks i think
|
||||
{FCVAR_PRINTABLEONLY, "PRINTABLEONLY"},
|
||||
{FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, "GAMEDLL_FOR_REMOTE_CLIENTS"},
|
||||
|
||||
{FCVAR_UNLOGGED, "UNLOGGED"},
|
||||
{FCVAR_NEVER_AS_STRING, "NEVER_AS_STRING"},
|
||||
{FCVAR_REPLICATED, "REPLICATED"},
|
||||
{FCVAR_CHEAT, "CHEAT"},
|
||||
{FCVAR_SS, "SS"},
|
||||
{FCVAR_DEMO, "DEMO"},
|
||||
{FCVAR_DONTRECORD, "DONTRECORD"},
|
||||
{FCVAR_SS_ADDED, "SS_ADDED"},
|
||||
{FCVAR_RELEASE, "RELEASE"},
|
||||
{FCVAR_RELOAD_MATERIALS, "RELOAD_MATERIALS"},
|
||||
{FCVAR_RELOAD_TEXTURES, "RELOAD_TEXTURES"},
|
||||
{FCVAR_NOT_CONNECTED, "NOT_CONNECTED"},
|
||||
{FCVAR_MATERIAL_SYSTEM_THREAD, "MATERIAL_SYSTEM_THREAD"},
|
||||
{FCVAR_ARCHIVE_PLAYERPROFILE, "ARCHIVE_PLAYERPROFILE"},
|
||||
{FCVAR_SERVER_CAN_EXECUTE, "SERVER_CAN_EXECUTE"},
|
||||
{FCVAR_SERVER_CANNOT_QUERY, "SERVER_CANNOT_QUERY"},
|
||||
{FCVAR_CLIENTCMD_CAN_EXECUTE, "UNKNOWN"},
|
||||
{FCVAR_ACCESSIBLE_FROM_THREADS, "ACCESSIBLE_FROM_THREADS"}};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Forward declarations
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -74,6 +117,8 @@ class ConCommandBase;
|
|||
class ConCommand;
|
||||
class ConVar;
|
||||
|
||||
typedef void (*FnChangeCallback_t)(ConVar* var, const char* pOldValue, float flOldValue);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: A console variable
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -91,7 +136,7 @@ class ConVar
|
|||
float fMin,
|
||||
bool bMax,
|
||||
float fMax,
|
||||
void* pCallback);
|
||||
FnChangeCallback_t pCallback);
|
||||
~ConVar(void);
|
||||
|
||||
const char* GetBaseName(void) const;
|
||||
|
@ -125,7 +170,7 @@ class ConVar
|
|||
|
||||
bool IsRegistered(void) const;
|
||||
bool IsCommand(void) const;
|
||||
static bool IsFlagSet(ConVar* pConVar, int nFlags);
|
||||
bool IsFlagSet(int nFlags) const;
|
||||
|
||||
struct CVValue_t
|
||||
{
|
||||
|
@ -145,5 +190,3 @@ class ConVar
|
|||
void* m_pMalloc {}; // 0x0070
|
||||
char m_pPad80[10] {}; // 0x0080
|
||||
}; // Size: 0x0080
|
||||
|
||||
void InitialiseConVars(HMODULE baseAddress);
|
||||
|
|
|
@ -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(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str());
|
||||
|
||||
auto hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (hMinidumpFile)
|
||||
{
|
||||
MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo;
|
||||
dumpExceptionInfo.ThreadId = GetCurrentThreadId();
|
||||
dumpExceptionInfo.ExceptionPointers = exceptionInfo;
|
||||
dumpExceptionInfo.ClientPointers = false;
|
||||
|
||||
MiniDumpWriteDump(
|
||||
GetCurrentProcess(),
|
||||
GetCurrentProcessId(),
|
||||
hMinidumpFile,
|
||||
MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory),
|
||||
&dumpExceptionInfo,
|
||||
nullptr,
|
||||
nullptr);
|
||||
CloseHandle(hMinidumpFile);
|
||||
}
|
||||
else
|
||||
spdlog::error("Failed to write minidump file {}!", stream.str());
|
||||
|
||||
if (!IsDedicatedServer())
|
||||
MessageBoxA(
|
||||
0, "Northstar has crashed! Crash info can be found in R2Northstar/logs", "Northstar has crashed!", MB_ICONERROR | MB_OK);
|
||||
}
|
||||
|
||||
logged = true;
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
BOOL WINAPI ConsoleHandlerRoutine(DWORD eventCode)
|
||||
{
|
||||
switch (eventCode)
|
||||
{
|
||||
case CTRL_CLOSE_EVENT:
|
||||
// User closed console, shut everything down
|
||||
spdlog::info("Exiting due to console close...");
|
||||
RemoveCrashHandler();
|
||||
exit(EXIT_SUCCESS);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void InitialiseCrashHandler()
|
||||
{
|
||||
hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter);
|
||||
SetConsoleCtrlHandler(ConsoleHandlerRoutine, true);
|
||||
}
|
||||
|
||||
void RemoveCrashHandler()
|
||||
{
|
||||
RemoveVectoredExceptionHandler(hExceptionFilter);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
void InitialiseCrashHandler();
|
||||
void RemoveCrashHandler();
|
|
@ -23,5 +23,9 @@ std::unordered_map<std::string, ConCommandBase*> CCvar::DumpToMap()
|
|||
return allConVars;
|
||||
}
|
||||
|
||||
SourceInterface<CCvar>* g_pCVarInterface;
|
||||
CCvar* g_pCVar;
|
||||
// use the R2 namespace for game funcs
|
||||
namespace R2
|
||||
{
|
||||
SourceInterface<CCvar>* g_pCVarInterface;
|
||||
CCvar* g_pCVar;
|
||||
} // namespace R2
|
||||
|
|
|
@ -35,5 +35,9 @@ class CCvar
|
|||
std::unordered_map<std::string, ConCommandBase*> DumpToMap();
|
||||
};
|
||||
|
||||
extern SourceInterface<CCvar>* g_pCVarInterface;
|
||||
extern CCvar* g_pCVar;
|
||||
// use the R2 namespace for game funcs
|
||||
namespace R2
|
||||
{
|
||||
extern SourceInterface<CCvar>* g_pCVarInterface;
|
||||
extern CCvar* g_pCVar;
|
||||
} // namespace R2
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
#include "pch.h"
|
||||
#include "debugoverlay.h"
|
||||
#include "dedicated.h"
|
||||
#include "cvar.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct Vector3
|
||||
{
|
||||
float x, y, z;
|
||||
};
|
||||
|
||||
struct QAngle
|
||||
{
|
||||
float x, y, z, w;
|
||||
};
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
enum OverlayType_t
|
||||
{
|
||||
|
@ -40,7 +32,7 @@ struct OverlayBase_t
|
|||
int m_nServerCount; // Latch server count, too
|
||||
float m_flEndTime; // When does this box go away
|
||||
OverlayBase_t* m_pNextOverlay;
|
||||
__int64 m_pUnk;
|
||||
void* m_pUnk;
|
||||
};
|
||||
|
||||
struct OverlayLine_t : public OverlayBase_t
|
||||
|
@ -76,37 +68,18 @@ struct OverlayBox_t : public OverlayBase_t
|
|||
int a;
|
||||
};
|
||||
|
||||
// this is in cvar.h, don't need it here
|
||||
/*class Color
|
||||
{
|
||||
public:
|
||||
Color(int r, int g, int b, int a)
|
||||
{
|
||||
_color[0] = (unsigned char)r;
|
||||
_color[1] = (unsigned char)g;
|
||||
_color[2] = (unsigned char)b;
|
||||
_color[3] = (unsigned char)a;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned char _color[4];
|
||||
};*/
|
||||
|
||||
static HMODULE sEngineModule;
|
||||
|
||||
typedef void (*DrawOverlayType)(OverlayBase_t* a1);
|
||||
DrawOverlayType DrawOverlay;
|
||||
|
||||
typedef void (*RenderLineType)(Vector3 v1, Vector3 v2, Color c, bool bZBuffer);
|
||||
static RenderLineType RenderLine;
|
||||
|
||||
typedef void (*RenderBoxType)(Vector3 vOrigin, QAngle angles, Vector3 vMins, Vector3 vMaxs, Color c, bool bZBuffer, bool bInsideOut);
|
||||
static RenderBoxType RenderBox;
|
||||
|
||||
static RenderBoxType RenderWireframeBox;
|
||||
|
||||
// engine.dll+0xABCB0
|
||||
void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay)
|
||||
// clang-format off
|
||||
AUTOHOOK(DrawOverlay, engine.dll + 0xABCB0,
|
||||
void, __fastcall, (OverlayBase_t * pOverlay))
|
||||
// clang-format on
|
||||
{
|
||||
EnterCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); // s_OverlayMutex
|
||||
|
||||
|
@ -151,24 +124,17 @@ void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay)
|
|||
LeaveCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38));
|
||||
}
|
||||
|
||||
void InitialiseDebugOverlay(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", DebugOverlay, ConVar, (CModule module))
|
||||
{
|
||||
if (IsDedicatedServer())
|
||||
return;
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xABCB0, &DrawOverlayHook, reinterpret_cast<LPVOID*>(&DrawOverlay));
|
||||
|
||||
RenderLine = reinterpret_cast<RenderLineType>((char*)baseAddress + 0x192A70);
|
||||
|
||||
RenderBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x192520);
|
||||
|
||||
RenderWireframeBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x193DA0);
|
||||
|
||||
sEngineModule = baseAddress;
|
||||
RenderLine = module.Offset(0x192A70).As<RenderLineType>();
|
||||
RenderBox = module.Offset(0x192520).As<RenderBoxType>();
|
||||
RenderWireframeBox = module.Offset(0x193DA0).As<RenderBoxType>();
|
||||
sEngineModule = reinterpret_cast<HMODULE>(module.m_nAddress);
|
||||
|
||||
// not in g_pCVar->FindVar by this point for whatever reason, so have to get from memory
|
||||
ConVar* Cvar_enable_debug_overlays = (ConVar*)((char*)baseAddress + 0x10DB0990);
|
||||
ConVar* Cvar_enable_debug_overlays = module.Offset(0x10DB0990).As<ConVar*>();
|
||||
Cvar_enable_debug_overlays->SetValue(false);
|
||||
Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0";
|
||||
Cvar_enable_debug_overlays->AddFlags(FCVAR_CHEAT);
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void InitialiseDebugOverlay(HMODULE baseAddress);
|
|
@ -1,9 +1,16 @@
|
|||
#include "pch.h"
|
||||
#include "dedicated.h"
|
||||
#include "hookutils.h"
|
||||
#include "gameutils.h"
|
||||
#include "tier0.h"
|
||||
#include "playlist.h"
|
||||
#include "r2engine.h"
|
||||
#include "hoststate.h"
|
||||
#include "serverauthentication.h"
|
||||
#include "masterserver.h"
|
||||
#include "printcommand.h"
|
||||
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
using namespace R2;
|
||||
|
||||
bool IsDedicatedServer()
|
||||
{
|
||||
|
@ -33,32 +40,23 @@ void Sys_Printf(CDedicatedExports* dedicated, const char* msg)
|
|||
spdlog::info("[DEDICATED SERVER] {}", msg);
|
||||
}
|
||||
|
||||
typedef void (*CHostState__InitType)(CHostState* self);
|
||||
|
||||
void RunServer(CDedicatedExports* dedicated)
|
||||
{
|
||||
spdlog::info("CDedicatedExports::RunServer(): starting");
|
||||
spdlog::info(CommandLine()->GetCmdLine());
|
||||
spdlog::info(Tier0::CommandLine()->GetCmdLine());
|
||||
|
||||
// initialise engine
|
||||
g_pEngine->Frame();
|
||||
|
||||
// add +map if not present
|
||||
// don't manually execute this from cbuf as users may have it in their startup args anyway, easier just to run from stuffcmds if present
|
||||
if (!CommandLine()->CheckParm("+map"))
|
||||
CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString());
|
||||
if (!Tier0::CommandLine()->CheckParm("+map"))
|
||||
Tier0::CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString());
|
||||
|
||||
// run server autoexec and re-run commandline
|
||||
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode);
|
||||
// re-run commandline
|
||||
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "stuffcmds", cmd_source_t::kCommandSrcCode);
|
||||
Cbuf_Execute();
|
||||
|
||||
// ensure playlist initialises right, if we've not explicitly called setplaylist
|
||||
SetCurrentPlaylist(GetCurrentPlaylistName());
|
||||
|
||||
// note: we no longer manually set map and hoststate to start server in g_pHostState, we just use +map which seems to initialise stuff
|
||||
// better
|
||||
|
||||
// get tickinterval
|
||||
ConVar* Cvar_base_tickinterval_mp = g_pCVar->FindVar("base_tickinterval_mp");
|
||||
|
||||
|
@ -66,43 +64,31 @@ void RunServer(CDedicatedExports* dedicated)
|
|||
double frameTitle = 0;
|
||||
while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING)
|
||||
{
|
||||
double frameStart = Plat_FloatTime();
|
||||
double frameStart = Tier0::Plat_FloatTime();
|
||||
g_pEngine->Frame();
|
||||
|
||||
// only update the title after at least 500ms since the last update
|
||||
if ((frameStart - frameTitle) > 0.5)
|
||||
{
|
||||
frameTitle = frameStart;
|
||||
|
||||
// this way of getting playercount/maxplayers honestly really sucks, but not got any other methods of doing it rn
|
||||
const char* maxPlayers = GetCurrentPlaylistVar("max_players", false);
|
||||
if (!maxPlayers)
|
||||
maxPlayers = "6";
|
||||
|
||||
SetConsoleTitleA(fmt::format(
|
||||
"{} - {} {}/{} players ({})",
|
||||
g_MasterServerManager->m_sUnicodeServerName,
|
||||
g_pHostState->m_levelName,
|
||||
g_ServerAuthenticationManager->m_additionalPlayerData.size(),
|
||||
maxPlayers,
|
||||
GetCurrentPlaylistName())
|
||||
.c_str());
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::duration<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)();
|
||||
IsGameActiveWindowType IsGameActiveWindow;
|
||||
bool IsGameActiveWindowHook()
|
||||
// use server presence to update window title
|
||||
class DedicatedConsoleServerPresence : public ServerPresenceReporter
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void ReportPresence(const ServerPresence* pServerPresence) override
|
||||
{
|
||||
SetConsoleTitleA(fmt::format(
|
||||
"{} - {} {}/{} players ({})",
|
||||
pServerPresence->m_sServerName,
|
||||
pServerPresence->m_MapName,
|
||||
pServerPresence->m_iPlayerCount,
|
||||
pServerPresence->m_iMaxPlayers,
|
||||
pServerPresence->m_PlaylistName)
|
||||
.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
HANDLE consoleInputThreadHandle = NULL;
|
||||
|
||||
DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
|
||||
{
|
||||
while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN)
|
||||
|
@ -121,139 +107,106 @@ DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
|
|||
{
|
||||
input += "\n";
|
||||
Cbuf_AddText(Cbuf_GetCurrentPlayer(), input.c_str(), cmd_source_t::kCommandSrcCode);
|
||||
TryPrintCvarHelpForCommand(input.c_str()); // this needs to be done on main thread, unstable in this one
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "nsmem.h"
|
||||
void InitialiseDedicated(HMODULE engineAddress)
|
||||
// clang-format off
|
||||
AUTOHOOK(IsGameActiveWindow, engine.dll + 0x1CDC80,
|
||||
bool,, ())
|
||||
// clang-format on
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ON_DLL_LOAD_DEDI_RELIESON("engine.dll", DedicatedServer, ServerPresence, (CModule module))
|
||||
{
|
||||
spdlog::info("InitialiseDedicated");
|
||||
|
||||
uintptr_t ea = (uintptr_t)engineAddress;
|
||||
AUTOHOOK_DISPATCH_MODULE("engine.dll")
|
||||
|
||||
{
|
||||
// Host_Init
|
||||
// prevent a particle init that relies on client dll
|
||||
NSMem::NOP(ea + 0x156799, 5);
|
||||
}
|
||||
// Host_Init
|
||||
// prevent a particle init that relies on client dll
|
||||
module.Offset(0x156799).NOP(5);
|
||||
|
||||
// Host_Init
|
||||
// don't call Key_Init to avoid loading some extra rsons from rpak (will be necessary to boot if we ever wanna disable rpaks entirely on
|
||||
// dedi)
|
||||
module.Offset(0x1565B0).NOP(5);
|
||||
|
||||
{
|
||||
// CModAppSystemGroup::Create
|
||||
// force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment
|
||||
auto ptr = ea + 0x1C4EBD;
|
||||
MemoryAddress base = module.Offset(0x1C4EBD);
|
||||
|
||||
// cmp => mov
|
||||
NSMem::BytePatch(ptr + 1, "C6 87");
|
||||
base.Offset(1).Patch("C6 87");
|
||||
|
||||
// 00 => 01
|
||||
NSMem::BytePatch(ptr + 7, "01");
|
||||
base.Offset(7).Patch("01");
|
||||
}
|
||||
|
||||
{
|
||||
// Some init that i'm not sure of that crashes
|
||||
// nop the call to it
|
||||
NSMem::NOP(ea + 0x156A63, 5);
|
||||
}
|
||||
// Some init that i'm not sure of that crashes
|
||||
// nop the call to it
|
||||
module.Offset(0x156A63).NOP(5);
|
||||
|
||||
{
|
||||
// runframeserver
|
||||
// nop some access violations
|
||||
NSMem::NOP(ea + 0x159819, 17);
|
||||
}
|
||||
// runframeserver
|
||||
// nop some access violations
|
||||
module.Offset(0x159819).NOP(17);
|
||||
|
||||
{
|
||||
NSMem::NOP(ea + 0x156B4C, 7);
|
||||
module.Offset(0x156B4C).NOP(7);
|
||||
|
||||
// previously patched these, took me a couple weeks to figure out they were the issue
|
||||
// removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address
|
||||
// so uhh, don't do that
|
||||
// NSMem::NOP(ea + 0x156B4C + 7, 8);
|
||||
// previously patched these, took me a couple weeks to figure out they were the issue
|
||||
// removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address
|
||||
// so uhh, don't do that
|
||||
// NSMem::NOP(ea + 0x156B4C + 7, 8);
|
||||
module.Offset(0x156B4C).Offset(15).NOP(9);
|
||||
|
||||
NSMem::NOP(ea + 0x156B4C + 15, 9);
|
||||
}
|
||||
// HostState_State_NewGame
|
||||
// nop an access violation
|
||||
module.Offset(0xB934C).NOP(9);
|
||||
|
||||
{
|
||||
// HostState_State_NewGame
|
||||
// nop an access violation
|
||||
NSMem::NOP(ea + 0xB934C, 9);
|
||||
}
|
||||
// CEngineAPI::Connect
|
||||
// remove call to Shader_Connect
|
||||
module.Offset(0x1C4D7D).NOP(5);
|
||||
|
||||
{
|
||||
// CEngineAPI::Connect
|
||||
// remove call to Shader_Connect
|
||||
NSMem::NOP(ea + 0x1C4D7D, 5);
|
||||
}
|
||||
// Host_Init
|
||||
// remove call to ui loading stuff
|
||||
module.Offset(0x156595).NOP(5);
|
||||
|
||||
// currently does not work, crashes stuff, likely gotta keep this here
|
||||
//{
|
||||
// // CEngineAPI::Connect
|
||||
// // remove calls to register ui rpak asset types
|
||||
// NSMem::NOP(ea + 0x1C4E07, 5);
|
||||
//}
|
||||
// some function that gets called from RunFrameServer
|
||||
// nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway
|
||||
module.Offset(0x15A0BB).NOP(5);
|
||||
|
||||
{
|
||||
// Host_Init
|
||||
// remove call to ui loading stuff
|
||||
NSMem::NOP(ea + 0x156595, 5);
|
||||
}
|
||||
// RunFrameServer
|
||||
// nop a function that access violations
|
||||
module.Offset(0x159BF3).NOP(5);
|
||||
|
||||
{
|
||||
// some function that gets called from RunFrameServer
|
||||
// nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway
|
||||
NSMem::NOP(ea + 0x15A0BB, 5);
|
||||
}
|
||||
// func that checks if origin is inited
|
||||
// always return 1
|
||||
module.Offset(0x183B70).Patch("B0 01 C3"); // mov al,01 ret
|
||||
|
||||
{
|
||||
// RunFrameServer
|
||||
// nop a function that access violations
|
||||
NSMem::NOP(ea + 0x159BF3, 5);
|
||||
}
|
||||
// HostState_State_ChangeLevel
|
||||
// nop clientinterface call
|
||||
module.Offset(0x1552ED).NOP(16);
|
||||
|
||||
{
|
||||
// func that checks if origin is inited
|
||||
// always return 1
|
||||
NSMem::BytePatch(
|
||||
ea + 0x183B70,
|
||||
{
|
||||
0xB0,
|
||||
0x01, // mov al,01
|
||||
0xC3 // ret
|
||||
});
|
||||
}
|
||||
// HostState_State_ChangeLevel
|
||||
// nop clientinterface call
|
||||
module.Offset(0x155363).NOP(16);
|
||||
|
||||
{
|
||||
// HostState_State_ChangeLevel
|
||||
// nop clientinterface call
|
||||
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);
|
||||
}
|
||||
// IVideoMode::CreateGameWindow
|
||||
// nop call to ShowWindow
|
||||
module.Offset(0x1CD146).NOP(5);
|
||||
|
||||
CDedicatedExports* dedicatedExports = new CDedicatedExports;
|
||||
dedicatedExports->vtable = dedicatedExports;
|
||||
dedicatedExports->Sys_Printf = Sys_Printf;
|
||||
dedicatedExports->RunServer = RunServer;
|
||||
|
||||
CDedicatedExports** exports = (CDedicatedExports**)((char*)engineAddress + 0x13F0B668);
|
||||
*exports = dedicatedExports;
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)engineAddress + 0x1CDC80, &IsGameActiveWindowHook, reinterpret_cast<LPVOID*>(&IsGameActiveWindow));
|
||||
*module.Offset(0x13F0B668).As<CDedicatedExports**>() = dedicatedExports;
|
||||
|
||||
// extra potential patches:
|
||||
// nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow
|
||||
|
@ -265,15 +218,19 @@ void InitialiseDedicated(HMODULE engineAddress)
|
|||
// make sure it still gets registered
|
||||
|
||||
// add cmdline args that are good for dedi
|
||||
CommandLine()->AppendParm("-nomenuvid", 0);
|
||||
CommandLine()->AppendParm("-nosound", 0);
|
||||
CommandLine()->AppendParm("-windowed", 0);
|
||||
CommandLine()->AppendParm("-nomessagebox", 0);
|
||||
CommandLine()->AppendParm("+host_preload_shaders", "0");
|
||||
CommandLine()->AppendParm("+net_usesocketsforloopback", "1");
|
||||
Tier0::CommandLine()->AppendParm("-nomenuvid", 0);
|
||||
Tier0::CommandLine()->AppendParm("-nosound", 0);
|
||||
Tier0::CommandLine()->AppendParm("-windowed", 0);
|
||||
Tier0::CommandLine()->AppendParm("-nomessagebox", 0);
|
||||
Tier0::CommandLine()->AppendParm("+host_preload_shaders", "0");
|
||||
Tier0::CommandLine()->AppendParm("+net_usesocketsforloopback", "1");
|
||||
|
||||
// use presence reporter for console title
|
||||
DedicatedConsoleServerPresence* presenceReporter = new DedicatedConsoleServerPresence;
|
||||
g_pServerPresence->AddPresenceReporter(presenceReporter);
|
||||
|
||||
// Disable Quick Edit mode to reduce chance of user unintentionally hanging their server by selecting something.
|
||||
if (!CommandLine()->CheckParm("-bringbackquickedit"))
|
||||
if (!Tier0::CommandLine()->CheckParm("-bringbackquickedit"))
|
||||
{
|
||||
HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD mode = 0;
|
||||
|
@ -295,35 +252,42 @@ void InitialiseDedicated(HMODULE engineAddress)
|
|||
spdlog::info("Quick Edit enabled by user request");
|
||||
|
||||
// create console input thread
|
||||
if (!CommandLine()->CheckParm("-noconsoleinput"))
|
||||
if (!Tier0::CommandLine()->CheckParm("-noconsoleinput"))
|
||||
consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL);
|
||||
else
|
||||
spdlog::info("Console input disabled by user request");
|
||||
}
|
||||
|
||||
void InitialiseDedicatedOrigin(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_DEDI("tier0.dll", DedicatedServerOrigin, (CModule module))
|
||||
{
|
||||
// disable origin on dedicated
|
||||
// for any big ea lawyers, this can't be used to play the game without origin, game will throw a fit if you try to do anything without
|
||||
// an origin id as a client for dedi it's fine though, game doesn't care if origin is disabled as long as there's only a server
|
||||
|
||||
NSMem::BytePatch(
|
||||
(uintptr_t)GetProcAddress(GetModuleHandleA("tier0.dll"), "Tier0_InitOrigin"),
|
||||
{
|
||||
0xC3 // ret
|
||||
});
|
||||
module.GetExport("Tier0_InitOrigin").Patch("C3");
|
||||
}
|
||||
|
||||
typedef void (*PrintFatalSquirrelErrorType)(void* sqvm);
|
||||
PrintFatalSquirrelErrorType PrintFatalSquirrelError;
|
||||
void PrintFatalSquirrelErrorHook(void* sqvm)
|
||||
// clang-format off
|
||||
AUTOHOOK(PrintSquirrelError, server.dll + 0x794D0,
|
||||
void, __fastcall, (void* sqvm))
|
||||
// clang-format on
|
||||
{
|
||||
PrintFatalSquirrelError(sqvm);
|
||||
g_pEngine->m_nQuitting = EngineQuitState::QUIT_TODESKTOP;
|
||||
PrintSquirrelError(sqvm);
|
||||
|
||||
// close dedicated server if a fatal error is hit
|
||||
static ConVar* Cvar_fatal_script_errors = g_pCVar->FindVar("fatal_script_errors");
|
||||
if (Cvar_fatal_script_errors->GetBool())
|
||||
abort();
|
||||
}
|
||||
|
||||
void InitialiseDedicatedServerGameDLL(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_DEDI("server.dll", DedicatedServerGameDLL, (CModule module))
|
||||
{
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, baseAddress + 0x794D0, &PrintFatalSquirrelErrorHook, reinterpret_cast<LPVOID*>(&PrintFatalSquirrelError));
|
||||
AUTOHOOK_DISPATCH_MODULE(server.dll)
|
||||
|
||||
if (Tier0::CommandLine()->CheckParm("-nopakdedi"))
|
||||
{
|
||||
module.Offset(0x6BA350).Patch("C3"); // dont load skins.rson from rpak if we don't have rpaks, as loading it will cause a crash
|
||||
module.Offset(0x6BA300).Patch(
|
||||
"B8 C8 00 00 00 C3"); // return 200 as the number of skins from server.dll + 6BA300, this is the normal value read from
|
||||
// skins.rson and should be updated when we need it more modular
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
bool IsDedicatedServer();
|
||||
|
||||
void InitialiseDedicated(HMODULE moduleAddress);
|
||||
void InitialiseDedicatedOrigin(HMODULE baseAddress);
|
||||
void InitialiseDedicatedServerGameDLL(HMODULE baseAddress);
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#include "pch.h"
|
||||
#include "dedicated.h"
|
||||
#include "dedicatedmaterialsystem.h"
|
||||
#include "hookutils.h"
|
||||
#include "gameutils.h"
|
||||
#include "nsmem.h"
|
||||
#include "tier0.h"
|
||||
|
||||
typedef HRESULT (*__stdcall D3D11CreateDeviceType)(
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(D3D11CreateDevice, materialsystem_dx11.dll + 0xD9A0E,
|
||||
HRESULT, __stdcall, (
|
||||
void* pAdapter,
|
||||
int DriverType,
|
||||
HMODULE Software,
|
||||
|
@ -15,108 +16,26 @@ typedef HRESULT (*__stdcall D3D11CreateDeviceType)(
|
|||
UINT SDKVersion,
|
||||
void** ppDevice,
|
||||
int* pFeatureLevel,
|
||||
void** ppImmediateContext);
|
||||
D3D11CreateDeviceType D3D11CreateDevice;
|
||||
|
||||
HRESULT __stdcall D3D11CreateDeviceHook(
|
||||
void* pAdapter,
|
||||
int DriverType,
|
||||
HMODULE Software,
|
||||
UINT Flags,
|
||||
int* pFeatureLevels,
|
||||
UINT FeatureLevels,
|
||||
UINT SDKVersion,
|
||||
void** ppDevice,
|
||||
int* pFeatureLevel,
|
||||
void** ppImmediateContext)
|
||||
void** ppImmediateContext))
|
||||
// clang-format on
|
||||
{
|
||||
// note: this is super duper temp pretty much just messing around with it
|
||||
// does run surprisingly well on dedi for a software driver tho if you ignore the +1gb ram usage at times, seems like dedi doesn't
|
||||
// really call gpu much even with renderthread still being a thing will be using this hook for actual d3d stubbing and stuff later
|
||||
|
||||
// atm, i think the play might be to run d3d in software, and then just stub out any calls that allocate memory/use alot of resources
|
||||
// (e.g. createtexture and that sorta thing)
|
||||
// note: this has been succeeded by the d3d11 and gfsdk stubs, and is only being kept around for posterity and as a fallback option
|
||||
if (CommandLine()->CheckParm("-softwared3d11"))
|
||||
if (Tier0::CommandLine()->CheckParm("-softwared3d11"))
|
||||
DriverType = 5; // D3D_DRIVER_TYPE_WARP
|
||||
|
||||
return D3D11CreateDevice(
|
||||
pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext);
|
||||
}
|
||||
|
||||
void InitialiseDedicatedMaterialSystem(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_DEDI("materialsystem_dx11.dll", DedicatedServerMaterialSystem, (CModule module))
|
||||
{
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xD9A0E, &D3D11CreateDeviceHook, reinterpret_cast<LPVOID*>(&D3D11CreateDevice));
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
// not using these for now since they're related to nopping renderthread/gamewindow i.e. very hard
|
||||
//{
|
||||
// // function that launches renderthread
|
||||
// char* ptr = (char*)baseAddress + 0x87047;
|
||||
// TempReadWrite rw(ptr);
|
||||
//
|
||||
// // make it not launch renderthread
|
||||
// *ptr = (char)0x90;
|
||||
// *(ptr + 1) = (char)0x90;
|
||||
// *(ptr + 2) = (char)0x90;
|
||||
// *(ptr + 3) = (char)0x90;
|
||||
// *(ptr + 4) = (char)0x90;
|
||||
// *(ptr + 5) = (char)0x90;
|
||||
//}
|
||||
//
|
||||
//{
|
||||
// // some function that waits on renderthread job
|
||||
// char* ptr = (char*)baseAddress + 0x87d00;
|
||||
// TempReadWrite rw(ptr);
|
||||
//
|
||||
// // return immediately
|
||||
// *ptr = (char)0xC3;
|
||||
//}
|
||||
|
||||
{
|
||||
// CMaterialSystem::FindMaterial
|
||||
// make the game always use the error material
|
||||
NSMem::BytePatch((uintptr_t)baseAddress + 0x5F0F1, {0xE9, 0x34, 0x03, 0x00});
|
||||
}
|
||||
|
||||
// previously had DisableDedicatedWindowCreation stuff here, removing for now since shit and unstable
|
||||
// check commit history if needed
|
||||
}
|
||||
|
||||
typedef void* (*PakLoadAPI__LoadRpakType)(char* filename, void* unknown, int flags);
|
||||
PakLoadAPI__LoadRpakType PakLoadAPI__LoadRpak;
|
||||
|
||||
void* PakLoadAPI__LoadRpakHook(char* filename, void* unknown, int flags)
|
||||
{
|
||||
spdlog::info("PakLoadAPI__LoadRpakHook {}", filename);
|
||||
|
||||
// on dedi, don't load any paks that aren't required
|
||||
if (strncmp(filename, "common", 6))
|
||||
return 0;
|
||||
|
||||
return PakLoadAPI__LoadRpak(filename, unknown, flags);
|
||||
}
|
||||
|
||||
typedef void* (*PakLoadAPI__LoadRpak2Type)(char* filename, void* unknown, int flags, void* callback, void* callback2);
|
||||
PakLoadAPI__LoadRpak2Type PakLoadAPI__LoadRpak2;
|
||||
|
||||
void* PakLoadAPI__LoadRpak2Hook(char* filename, void* unknown, int flags, void* callback, void* callback2)
|
||||
{
|
||||
spdlog::info("PakLoadAPI__LoadRpak2Hook {}", filename);
|
||||
|
||||
// on dedi, don't load any paks that aren't required
|
||||
if (strncmp(filename, "common", 6))
|
||||
return 0;
|
||||
|
||||
return PakLoadAPI__LoadRpak2(filename, unknown, flags, callback, callback2);
|
||||
}
|
||||
|
||||
void InitialiseDedicatedRtechGame(HMODULE baseAddress)
|
||||
{
|
||||
baseAddress = GetModuleHandleA("rtech_game.dll");
|
||||
|
||||
HookEnabler hook;
|
||||
// unfortunately this is unstable, seems to freeze when changing maps
|
||||
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB0F0, &PakLoadAPI__LoadRpakHook, reinterpret_cast<LPVOID*>(&PakLoadAPI__LoadRpak));
|
||||
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB170, &PakLoadAPI__LoadRpak2Hook, reinterpret_cast<LPVOID*>(&PakLoadAPI__LoadRpak2));
|
||||
// CMaterialSystem::FindMaterial
|
||||
// make the game always use the error material
|
||||
module.Offset(0x5F0F1).Patch("E9 34 03 00");
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
void InitialiseDedicatedMaterialSystem(HMODULE baseAddress);
|
||||
void InitialiseDedicatedRtechGame(HMODULE baseAddress);
|
|
@ -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);
|
||||
}
|
|
@ -1,63 +1,24 @@
|
|||
#include "pch.h"
|
||||
#include "hooks.h"
|
||||
#include "main.h"
|
||||
#include "squirrel.h"
|
||||
#include "dedicated.h"
|
||||
#include "dedicatedmaterialsystem.h"
|
||||
#include "sourceconsole.h"
|
||||
#include "logging.h"
|
||||
#include "concommand.h"
|
||||
#include "modmanager.h"
|
||||
#include "filesystem.h"
|
||||
#include "serverauthentication.h"
|
||||
#include "scriptmodmenu.h"
|
||||
#include "scriptserverbrowser.h"
|
||||
#include "keyvalues.h"
|
||||
#include "masterserver.h"
|
||||
#include "gameutils.h"
|
||||
#include "chatcommand.h"
|
||||
#include "modlocalisation.h"
|
||||
#include "playlist.h"
|
||||
#include "miscserverscript.h"
|
||||
#include "clientauthhooks.h"
|
||||
#include "latencyflex.h"
|
||||
#include "scriptbrowserhooks.h"
|
||||
#include "scriptmainmenupromos.h"
|
||||
#include "miscclientfixes.h"
|
||||
#include "miscserverfixes.h"
|
||||
#include "rpakfilesystem.h"
|
||||
#include "bansystem.h"
|
||||
#include "crashhandler.h"
|
||||
#include "memalloc.h"
|
||||
#include "maxplayers.h"
|
||||
#include "languagehooks.h"
|
||||
#include "audio.h"
|
||||
#include "buildainfile.h"
|
||||
#include "nsprefix.h"
|
||||
#include "serverchathooks.h"
|
||||
#include "clientchathooks.h"
|
||||
#include "localchatwriter.h"
|
||||
#include "scriptservertoclientstringcommand.h"
|
||||
#include "plugin_abi.h"
|
||||
#include "plugins.h"
|
||||
#include "debugoverlay.h"
|
||||
#include "clientvideooverrides.h"
|
||||
#include "clientruihooks.h"
|
||||
#include <string.h>
|
||||
#include "version.h"
|
||||
#include "pch.h"
|
||||
#include "scriptutility.h"
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/writer.h"
|
||||
#include "rapidjson/error/en.h"
|
||||
#include "exploitfixes.h"
|
||||
#include "scriptjson.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <filesystem>
|
||||
|
||||
typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject));
|
||||
|
||||
bool initialised = false;
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
|
@ -72,18 +33,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
void WaitForDebugger(HMODULE baseAddress)
|
||||
{
|
||||
// earlier waitfordebugger call than is in vanilla, just so we can debug stuff a little easier
|
||||
if (CommandLine()->CheckParm("-waitfordebugger"))
|
||||
{
|
||||
spdlog::info("waiting for debugger...");
|
||||
|
||||
while (!IsDebuggerPresent())
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
void freeLibrary(HMODULE hLib)
|
||||
{
|
||||
if (!FreeLibrary(hLib))
|
||||
|
@ -190,15 +139,13 @@ bool LoadPlugins()
|
|||
|
||||
bool InitialiseNorthstar()
|
||||
{
|
||||
if (initialised)
|
||||
{
|
||||
// spdlog::warn("Called InitialiseNorthstar more than once!"); // it's actually 100% fine for that to happen
|
||||
static bool bInitialised = false;
|
||||
if (bInitialised)
|
||||
return false;
|
||||
}
|
||||
|
||||
initialised = true;
|
||||
bInitialised = true;
|
||||
|
||||
parseConfigurables();
|
||||
InitialiseNorthstarPrefix();
|
||||
InitialiseVersion();
|
||||
|
||||
// Fix some users' failure to connect to respawn datacenters
|
||||
|
@ -206,6 +153,7 @@ bool InitialiseNorthstar()
|
|||
|
||||
curl_global_init_mem(CURL_GLOBAL_DEFAULT, _malloc_base, _free_base, _realloc_base, _strdup_base, _calloc_base);
|
||||
|
||||
InitialiseCrashHandler();
|
||||
InitialiseLogging();
|
||||
InstallInitialHooks();
|
||||
CreateLogFiles();
|
||||
|
@ -213,94 +161,6 @@ bool InitialiseNorthstar()
|
|||
// Write launcher version to log
|
||||
spdlog::info("NorthstarLauncher version: {}", version);
|
||||
|
||||
InitialiseInterfaceCreationHooks();
|
||||
|
||||
AddDllLoadCallback("tier0.dll", InitialiseTier0GameUtilFunctions);
|
||||
AddDllLoadCallback("engine.dll", WaitForDebugger);
|
||||
AddDllLoadCallback("engine.dll", InitialiseEngineGameUtilFunctions);
|
||||
AddDllLoadCallback("server.dll", InitialiseServerGameUtilFunctions);
|
||||
|
||||
// dedi patches
|
||||
{
|
||||
AddDllLoadCallbackForDedicatedServer("tier0.dll", InitialiseDedicatedOrigin);
|
||||
AddDllLoadCallbackForDedicatedServer("engine.dll", InitialiseDedicated);
|
||||
AddDllLoadCallbackForDedicatedServer("server.dll", InitialiseDedicatedServerGameDLL);
|
||||
AddDllLoadCallbackForDedicatedServer("materialsystem_dx11.dll", InitialiseDedicatedMaterialSystem);
|
||||
// this fucking sucks, but seemingly we somehow load after rtech_game???? unsure how, but because of this we have to apply patches
|
||||
// here, not on rtech_game load
|
||||
AddDllLoadCallbackForDedicatedServer("engine.dll", InitialiseDedicatedRtechGame);
|
||||
}
|
||||
|
||||
AddDllLoadCallback("engine.dll", InitialiseConVars);
|
||||
AddDllLoadCallback("engine.dll", InitialiseConCommands);
|
||||
|
||||
// client-exclusive patches
|
||||
{
|
||||
|
||||
AddDllLoadCallbackForClient("tier0.dll", InitialiseTier0LanguageHooks);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrel);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseSourceConsole);
|
||||
AddDllLoadCallbackForClient("engine.dll", InitialiseChatCommands);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseScriptModMenu);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseScriptServerBrowser);
|
||||
AddDllLoadCallbackForClient("localize.dll", InitialiseModLocalisation);
|
||||
AddDllLoadCallbackForClient("engine.dll", InitialiseClientAuthHooks);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseLatencyFleX);
|
||||
AddDllLoadCallbackForClient("engine.dll", InitialiseScriptExternalBrowserHooks);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseScriptMainMenuPromos);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseMiscClientFixes);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientPrintHooks);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialisePluginCommands);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientChatHooks);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseLocalChatWriter);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseScriptServerToClientStringCommands);
|
||||
AddDllLoadCallbackForClient("engine.dll", InitialiseEngineClientVideoOverrides);
|
||||
AddDllLoadCallbackForClient("engine.dll", InitialiseEngineClientRUIHooks);
|
||||
AddDllLoadCallbackForClient("engine.dll", InitialiseDebugOverlay);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrelJson);
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrelUtilityFunctions);
|
||||
// audio hooks
|
||||
AddDllLoadCallbackForClient("client.dll", InitialiseMilesAudioHooks);
|
||||
}
|
||||
|
||||
AddDllLoadCallback("engine.dll", InitialiseEngineSpewFuncHooks);
|
||||
AddDllLoadCallback("server.dll", InitialiseServerSquirrel);
|
||||
AddDllLoadCallback("engine.dll", InitialiseBanSystem);
|
||||
AddDllLoadCallback("engine.dll", InitialiseServerAuthentication);
|
||||
AddDllLoadCallback("engine.dll", InitialiseSharedMasterServer);
|
||||
AddDllLoadCallback("server.dll", InitialiseMiscServerScriptCommand);
|
||||
AddDllLoadCallback("server.dll", InitialiseMiscServerFixes);
|
||||
AddDllLoadCallback("server.dll", InitialiseBuildAINFileHooks);
|
||||
AddDllLoadCallback("server.dll", InitialiseServerSquirrelUtilityFunctions);
|
||||
AddDllLoadCallback("server.dll", InitialiseServerSquirrelJson);
|
||||
|
||||
AddDllLoadCallback("engine.dll", InitialisePlaylistHooks);
|
||||
|
||||
AddDllLoadCallback("filesystem_stdio.dll", InitialiseFilesystem);
|
||||
AddDllLoadCallback("engine.dll", InitialiseEngineRpakFilesystem);
|
||||
AddDllLoadCallback("engine.dll", InitialiseKeyValues);
|
||||
|
||||
AddDllLoadCallback("engine.dll", InitialiseServerChatHooks_Engine);
|
||||
AddDllLoadCallback("server.dll", InitialiseServerChatHooks_Server);
|
||||
|
||||
// maxplayers increase
|
||||
AddDllLoadCallback("engine.dll", InitialiseMaxPlayersOverride_Engine);
|
||||
AddDllLoadCallback("client.dll", InitialiseMaxPlayersOverride_Client);
|
||||
AddDllLoadCallback("server.dll", InitialiseMaxPlayersOverride_Server);
|
||||
|
||||
// mod manager after everything else
|
||||
AddDllLoadCallback("engine.dll", InitialiseModManager);
|
||||
|
||||
{
|
||||
// activate multi-module exploitfixes callbacks
|
||||
constexpr const char* EXPLOITFIXES_MULTICALLBACK_MODS[] = {"client.dll", "engine.dll", "server.dll"};
|
||||
for (const char* mod : EXPLOITFIXES_MULTICALLBACK_MODS)
|
||||
AddDllLoadCallback(mod, ExploitFixes::LoadCallback_MultiModule);
|
||||
|
||||
// activate exploit fixes later
|
||||
AddDllLoadCallback("server.dll", ExploitFixes::LoadCallback_Full);
|
||||
}
|
||||
|
||||
// run callbacks for any libraries that are already loaded by now
|
||||
CallAllPendingDLLLoadCallbacks();
|
||||
|
||||
|
|
|
@ -1,52 +1,63 @@
|
|||
#include "pch.h"
|
||||
|
||||
#include "exploitfixes.h"
|
||||
#include "exploitfixes_utf8parser.h"
|
||||
#include "nsmem.h"
|
||||
#include "cvar.h"
|
||||
#include "gameutils.h"
|
||||
#include "limits.h"
|
||||
#include "dedicated.h"
|
||||
#include "tier0.h"
|
||||
#include "r2engine.h"
|
||||
#include "r2client.h"
|
||||
#include "vector.h"
|
||||
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
ConVar* Cvar_ns_exploitfixes_log;
|
||||
ConVar* Cvar_ns_should_log_all_clientcommands;
|
||||
|
||||
ConVar* Cvar_sv_cheats;
|
||||
|
||||
ConVar* ns_exploitfixes_log;
|
||||
#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0)
|
||||
#define BLOCKED_INFO(s) \
|
||||
( \
|
||||
[=]() -> bool \
|
||||
{ \
|
||||
if (SHOULD_LOG) \
|
||||
if (Cvar_ns_exploitfixes_log->GetBool()) \
|
||||
{ \
|
||||
std::stringstream stream; \
|
||||
stream << "exploitfixes.cpp: " << BLOCK_PREFIX << s; \
|
||||
stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \
|
||||
spdlog::error(stream.str()); \
|
||||
} \
|
||||
return false; \
|
||||
}())
|
||||
|
||||
struct Float3
|
||||
{
|
||||
float vals[3];
|
||||
|
||||
void MakeValid()
|
||||
{
|
||||
for (auto& val : vals)
|
||||
if (isnan(val))
|
||||
val = 0;
|
||||
}
|
||||
};
|
||||
|
||||
#define BLOCK_NETMSG_FUNC(name, pattern) \
|
||||
KHOOK(name, ("engine.dll", pattern), bool, __fastcall, (void* thisptr, void* buffer)) \
|
||||
{ \
|
||||
return false; \
|
||||
}
|
||||
|
||||
// block bad netmessages
|
||||
// Servers can literally request a screenshot from any client, yeah no
|
||||
BLOCK_NETMSG_FUNC(CLC_Screenshot_WriteToBuffer, "48 89 5C 24 ? 57 48 83 EC 20 8B 42 10");
|
||||
BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38");
|
||||
// clang-format off
|
||||
AUTOHOOK(CLC_Screenshot_WriteToBuffer, engine.dll + 0x22AF20,
|
||||
bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 57 48 83 EC 20 8B 42 10
|
||||
// clang-format on
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is unused ingame and a big exploit vector
|
||||
BLOCK_NETMSG_FUNC(Base_CmdKeyValues_ReadFromBuffer, "40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70");
|
||||
// clang-format off
|
||||
AUTOHOOK(CLC_Screenshot_ReadFromBuffer, engine.dll + 0x221F00,
|
||||
bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38
|
||||
// clang-format on
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10"), bool, __fastcall, (void* pMsg))
|
||||
// This is unused ingame and a big client=>server=>client exploit vector
|
||||
// clang-format off
|
||||
AUTOHOOK(Base_CmdKeyValues_ReadFromBuffer, engine.dll + 0x220040,
|
||||
bool, __fastcall, (void* thisptr, void* buffer)) // 40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70
|
||||
// clang-format on
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(CClient_ProcessSetConVar, engine.dll + 0x75CF0,
|
||||
bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10
|
||||
// clang-format on
|
||||
{
|
||||
|
||||
constexpr int ENTRY_STR_LEN = 260;
|
||||
|
@ -69,25 +80,12 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
|
|||
};
|
||||
|
||||
auto msg = (NET_SetConVar*)pMsg;
|
||||
bool bIsServerFrame = Tier0::ThreadInServerFrameThread();
|
||||
|
||||
bool areWeServer;
|
||||
std::string BLOCK_PREFIX =
|
||||
std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
|
||||
|
||||
{
|
||||
// Figure out of we are the client or the server
|
||||
// To do this, we utilize the msg's m_pMessageHandler pointer
|
||||
// m_pMessageHandler points to a virtual class that handles all net messages
|
||||
// The first virtual table function of our m_pMessageHandler will differ if it is IServerMessageHandler or IClientMessageHandler
|
||||
void* msgHandlerVTableFirstFunc = **(void****)(msg->m_pMessageHandler);
|
||||
static auto engineBaseAddress = (uintptr_t)GetModuleHandleA("engine.dll");
|
||||
auto offset = uintptr_t(msgHandlerVTableFirstFunc) - engineBaseAddress;
|
||||
|
||||
constexpr uintptr_t CLIENTSTATE_FIRST_VFUNC_OFFSET = 0x8A15C;
|
||||
areWeServer = offset != CLIENTSTATE_FIRST_VFUNC_OFFSET;
|
||||
}
|
||||
|
||||
std::string BLOCK_PREFIX = std::string {"NET_SetConVar ("} + (areWeServer ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
|
||||
|
||||
if (areWeServer)
|
||||
if (bIsServerFrame)
|
||||
{
|
||||
constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69;
|
||||
if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT)
|
||||
|
@ -101,9 +99,8 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
|
|||
auto entry = msg->m_ConVars + i;
|
||||
|
||||
// Safety check for memory access
|
||||
if (NSMem::IsMemoryReadable(entry, sizeof(*entry)))
|
||||
if (MemoryAddress(entry).IsMemoryReadable(sizeof(*entry)))
|
||||
{
|
||||
|
||||
// Find null terminators
|
||||
bool nameValid = false, valValid = false;
|
||||
for (int i = 0; i < ENTRY_STR_LEN; i++)
|
||||
|
@ -117,36 +114,19 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
|
|||
if (!nameValid || !valValid)
|
||||
return BLOCKED_INFO("Missing null terminators");
|
||||
|
||||
auto realVar = g_pCVar->FindVar(entry->name);
|
||||
ConVar* pVar = R2::g_pCVar->FindVar(entry->name);
|
||||
|
||||
if (realVar)
|
||||
if (pVar)
|
||||
{
|
||||
memcpy(
|
||||
entry->name,
|
||||
realVar->m_ConCommandBase.m_pszName,
|
||||
strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
|
||||
pVar->m_ConCommandBase.m_pszName,
|
||||
strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
|
||||
|
||||
bool isValidFlags = true;
|
||||
if (areWeServer)
|
||||
{
|
||||
if (realVar)
|
||||
isValidFlags = ConVar::IsFlagSet(realVar, FCVAR_USERINFO); // ConVar MUST be userinfo var
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Should probably have some sanity checks, but can't find any that are consistent
|
||||
}
|
||||
|
||||
if (!isValidFlags)
|
||||
{
|
||||
if (!realVar)
|
||||
{
|
||||
return BLOCKED_INFO("Invalid flags on nonexistant cvar (how tho???)");
|
||||
}
|
||||
else
|
||||
{
|
||||
int iFlags = bIsServerFrame ? FCVAR_USERINFO : FCVAR_REPLICATED;
|
||||
if (!pVar->IsFlagSet(iFlags))
|
||||
return BLOCKED_INFO(
|
||||
"Invalid flags (" << std::hex << "0x" << realVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name);
|
||||
}
|
||||
"Invalid flags (" << std::hex << "0x" << pVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -155,11 +135,14 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
|
|||
}
|
||||
}
|
||||
|
||||
return oCClient_ProcessSetConVar(msg);
|
||||
return CClient_ProcessSetConVar(msg);
|
||||
}
|
||||
|
||||
// Purpose: prevent invalid user CMDs
|
||||
KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg))
|
||||
// prevent invalid user CMDs
|
||||
// clang-format off
|
||||
AUTOHOOK(CClient_ProcessUsercmds, engine.dll + 0x1040F0,
|
||||
bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58
|
||||
// clang-format on
|
||||
{
|
||||
struct CLC_Move
|
||||
{
|
||||
|
@ -186,22 +169,19 @@ KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __f
|
|||
return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")");
|
||||
}
|
||||
|
||||
constexpr int NUMCMD_SANITY_LIMIT = 16;
|
||||
if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT)
|
||||
{
|
||||
return BLOCKED_INFO("Command count is too high (new: " << msg->m_nNewCommands << ", backup: " << msg->m_nBackupCommands << ")");
|
||||
}
|
||||
|
||||
if (msg->m_nLength <= 0)
|
||||
return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")");
|
||||
|
||||
return oCClient_ProcessUsercmds(thisptr, pMsg);
|
||||
return CClient_ProcessUsercmds(thisptr, pMsg);
|
||||
}
|
||||
|
||||
KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from))
|
||||
// clang-format off
|
||||
AUTOHOOK(ReadUsercmd, server.dll + 0x2603F0,
|
||||
void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24 ? 53 55 56 57
|
||||
// clang-format on
|
||||
{
|
||||
// Let normal usercmd read happen first, it's safe
|
||||
oReadUsercmd(buf, pCmd_move, pCmd_from);
|
||||
ReadUsercmd(buf, pCmd_move, pCmd_from);
|
||||
|
||||
// Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing)
|
||||
struct alignas(4) SV_CUserCmd
|
||||
|
@ -209,11 +189,11 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
|||
DWORD command_number;
|
||||
DWORD tick_count;
|
||||
float command_time;
|
||||
Float3 worldViewAngles;
|
||||
Vector3 worldViewAngles;
|
||||
BYTE gap18[4];
|
||||
Float3 localViewAngles;
|
||||
Float3 attackangles;
|
||||
Float3 move;
|
||||
Vector3 localViewAngles;
|
||||
Vector3 attackangles;
|
||||
Vector3 move;
|
||||
DWORD buttons;
|
||||
BYTE impulse;
|
||||
short weaponselect;
|
||||
|
@ -221,8 +201,8 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
|||
BYTE gap4C[24];
|
||||
char headoffset;
|
||||
BYTE gap65[11];
|
||||
Float3 cameraPos;
|
||||
Float3 cameraAngles;
|
||||
Vector3 cameraPos;
|
||||
Vector3 cameraAngles;
|
||||
BYTE gap88[4];
|
||||
int tickSomething;
|
||||
DWORD dword90;
|
||||
|
@ -237,7 +217,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
|||
std::string BLOCK_PREFIX =
|
||||
"ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): ";
|
||||
|
||||
// Fix invalid player angles
|
||||
// fix invalid player angles
|
||||
cmd->worldViewAngles.MakeValid();
|
||||
cmd->attackangles.MakeValid();
|
||||
cmd->localViewAngles.MakeValid();
|
||||
|
@ -249,7 +229,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
|||
// Fix invaid movement vector
|
||||
cmd->move.MakeValid();
|
||||
|
||||
if (cmd->tick_count == 0 || cmd->command_time <= 0)
|
||||
if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0)
|
||||
{
|
||||
BLOCKED_INFO(
|
||||
"Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime
|
||||
|
@ -260,6 +240,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
|||
return;
|
||||
|
||||
INVALID_CMD:
|
||||
|
||||
// Fix any gameplay-affecting cmd properties
|
||||
// NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems
|
||||
cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = {0, 0, 0};
|
||||
|
@ -269,287 +250,209 @@ INVALID_CMD:
|
|||
cmd->meleetarget = 0;
|
||||
}
|
||||
|
||||
// basically: by default r2 isn't set as a valve mod, meaning that m_bRestrictServerCommands is false
|
||||
// this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients
|
||||
// especially since we have script commands this could theoretically be awful
|
||||
KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ())
|
||||
// ensure that GetLocalBaseClient().m_bRestrictServerCommands is set correctly, which the return value of this function controls
|
||||
// this is IsValveMod in source, but we're making it IsRespawnMod now since valve didn't make this one
|
||||
// clang-format off
|
||||
AUTOHOOK(IsRespawnMod, engine.dll + 0x1C6360,
|
||||
bool, __fastcall, (const char* pModName)) // 48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63
|
||||
// clang-format on
|
||||
{
|
||||
bool result = !CommandLine()->CheckParm("-norestrictservercommands");
|
||||
spdlog::info("ExploitFixes: Overriding IsValveMod to {}...", result);
|
||||
return result;
|
||||
// somewhat temp, store the modname here, since we don't have a proper ptr in engine to it rn
|
||||
int iSize = strlen(pModName);
|
||||
R2::g_pModName = new char[iSize + 1];
|
||||
strcpy(R2::g_pModName, pModName);
|
||||
|
||||
return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) && !Tier0::CommandLine()->CheckParm("-norestrictservercommands");
|
||||
}
|
||||
|
||||
// Fix respawn's crappy UTF8 parser so it doesn't crash -_-
|
||||
// This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome
|
||||
KHOOK(
|
||||
CrashFunc_ParseUTF8,
|
||||
("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"),
|
||||
bool,
|
||||
__fastcall,
|
||||
(INT64 * a1, DWORD* a2, char* strData))
|
||||
// ratelimit stringcmds, and prevent remote clients from calling commands that they shouldn't
|
||||
bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, R2::cmd_source_t commandSource);
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0,
|
||||
bool, __fastcall, (R2::CBaseClient* self, uint32_t unknown, const char* pCommandString))
|
||||
// clang-format on
|
||||
{
|
||||
if (Cvar_ns_should_log_all_clientcommands->GetBool())
|
||||
spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString);
|
||||
|
||||
static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16");
|
||||
|
||||
#ifdef _MSC_VER
|
||||
if (_ReturnAddress() == targetRetAddr)
|
||||
#else
|
||||
if (__builtin_return_address(0) == targetRetAddr)
|
||||
#endif
|
||||
if (!g_pServerLimits->CheckStringCommandLimits(self))
|
||||
{
|
||||
if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData))
|
||||
{
|
||||
const char* BLOCK_PREFIX = "ParseUTF8 Hook: ";
|
||||
BLOCKED_INFO("Ignoring potentially-crashing utf8 string");
|
||||
R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 oCrashFunc_ParseUTF8(a1, a2, strData);
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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())
|
||||
{
|
||||
spdlog::info("GetEntByIndex {} is out of bounds", idx);
|
||||
return nullptr;
|
||||
}
|
||||
return GetEntByIndex(idx);
|
||||
}
|
||||
constexpr const char* blockedCommands[] = {
|
||||
"emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
|
||||
|
||||
// RELOCATED FROM https://github.com/R2Northstar/NorthstarLauncher/commit/25dbf729cfc75107a0fcf0186924b58ecc05214b
|
||||
// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read
|
||||
// out of the bounds of the output buffer.
|
||||
KHOOK(
|
||||
LZSS_SafeUncompress,
|
||||
("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 33 ED 41 8B F9"),
|
||||
uint32_t,
|
||||
__fastcall,
|
||||
(void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize))
|
||||
{
|
||||
static constexpr int LZSS_LOOKSHIFT = 4;
|
||||
// These both execute a command for every single entity for some reason, nice one valve
|
||||
"pre_go_to_hub",
|
||||
"pre_go_to_calibration",
|
||||
|
||||
uint32_t totalBytes = 0;
|
||||
int getCmdByte = 0, cmdByte = 0;
|
||||
|
||||
struct lzss_header_t
|
||||
{
|
||||
uint32_t id, actualSize;
|
||||
};
|
||||
|
||||
lzss_header_t header = *(lzss_header_t*)pInput;
|
||||
|
||||
if (pInput == NULL || header.id != 'SSZL' || header.actualSize == 0 || header.actualSize > unBufSize)
|
||||
return 0;
|
||||
|
||||
pInput += sizeof(lzss_header_t);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (!getCmdByte)
|
||||
cmdByte = *pInput++;
|
||||
|
||||
getCmdByte = (getCmdByte + 1) & 0x07;
|
||||
|
||||
if (cmdByte & 0x01)
|
||||
{
|
||||
int position = *pInput++ << LZSS_LOOKSHIFT;
|
||||
position |= (*pInput >> LZSS_LOOKSHIFT);
|
||||
position += 1;
|
||||
|
||||
int count = (*pInput++ & 0x0F) + 1;
|
||||
if (count == 1)
|
||||
break;
|
||||
|
||||
// Ensure reference chunk exists entirely within our buffer
|
||||
if (position > totalBytes)
|
||||
return 0;
|
||||
|
||||
totalBytes += count;
|
||||
if (totalBytes > unBufSize)
|
||||
return 0;
|
||||
|
||||
unsigned char* pSource = pOutput - position;
|
||||
for (int i = 0; i < count; i++)
|
||||
*pOutput++ = *pSource++;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalBytes++;
|
||||
if (totalBytes > unBufSize)
|
||||
return 0;
|
||||
|
||||
*pOutput++ = *pInput++;
|
||||
}
|
||||
cmdByte = cmdByte >> 1;
|
||||
}
|
||||
|
||||
if (totalBytes == header.actualSize)
|
||||
{
|
||||
return totalBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
void DoBytePatches()
|
||||
{
|
||||
uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll");
|
||||
uintptr_t serverBase = (uintptr_t)GetModuleHandleA("server.dll");
|
||||
|
||||
// patches to make commands run from client/ui script still work
|
||||
// note: this is likely preventable in a nicer way? test prolly
|
||||
NSMem::BytePatch(engineBase + 0x4FB65, "EB 11");
|
||||
NSMem::BytePatch(engineBase + 0x4FBAC, "EB 16");
|
||||
|
||||
// disconnect concommand
|
||||
{
|
||||
uintptr_t addr = engineBase + 0x5ADA2D;
|
||||
int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE;
|
||||
NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int));
|
||||
}
|
||||
|
||||
{ // Dumb ANTITAMPER patches (they negatively impact performance and security)
|
||||
|
||||
constexpr const char* ANTITAMPER_EXPORTS[] = {
|
||||
"ANTITAMPER_SPOTCHECK_CODEMARKER",
|
||||
"ANTITAMPER_TESTVALUE_CODEMARKER",
|
||||
"ANTITAMPER_TRIGGER_CODEMARKER",
|
||||
"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()
|
||||
};
|
||||
|
||||
// Prevent thesefrom actually doing anything
|
||||
for (auto exportName : ANTITAMPER_EXPORTS)
|
||||
int iCmdLength = strlen(tempCommand.Arg(0));
|
||||
|
||||
bool bIsBadCommand = false;
|
||||
for (auto& blockedCommand : blockedCommands)
|
||||
{
|
||||
if (iCmdLength != strlen(blockedCommand))
|
||||
continue;
|
||||
|
||||
auto address = (uintptr_t)GetProcAddress(GetModuleHandleA("server.dll"), exportName);
|
||||
if (!address)
|
||||
{
|
||||
spdlog::warn("Failed to find AntiTamper function export \"{}\"", exportName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just return, none of them have any args or are userpurge
|
||||
NSMem::BytePatch(address, "C3");
|
||||
for (int i = 0; tempCommand.Arg(0)[i]; i++)
|
||||
if (tolower(tempCommand.Arg(0)[i]) != blockedCommand[i])
|
||||
goto NEXT_COMMAND; // break out of this loop, then go to next command
|
||||
|
||||
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(
|
||||
SpecialClientCommand,
|
||||
("server.dll", "48 89 5C 24 ? 48 89 74 24 ? 55 57 41 56 48 8D 6C 24 ? 48 81 EC ? ? ? ? 83 3A 00"),
|
||||
bool,
|
||||
__fastcall,
|
||||
(void* player, CCommand* command))
|
||||
// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines
|
||||
bool bWasWritingStringTableSuccessful;
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0,
|
||||
void, __fastcall, (void* self))
|
||||
// clang-format on
|
||||
{
|
||||
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())
|
||||
return oSpecialClientCommand(player, command); // Don't block anything if sv_cheats is on
|
||||
if (i >= MAX_ENT_IDX)
|
||||
{
|
||||
spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// These are mostly from Portal 2 (sigh)
|
||||
constexpr const char* blockedCommands[] = {
|
||||
"emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
|
||||
|
||||
// These both execute a command for every single entity for some reason, nice one valve
|
||||
"pre_go_to_hub",
|
||||
"pre_go_to_calibration",
|
||||
|
||||
"end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed
|
||||
"load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
|
||||
return GetEntByIndex(i);
|
||||
}
|
||||
// clang-format off
|
||||
AUTOHOOK(CL_CopyExistingEntity, engine.dll + 0x6F940,
|
||||
bool, __fastcall, (void* a1))
|
||||
// clang-format on
|
||||
{
|
||||
struct CEntityReadInfo
|
||||
{
|
||||
BYTE gap[40];
|
||||
int nNewEntity;
|
||||
};
|
||||
|
||||
if (command->ArgC() > 0)
|
||||
CEntityReadInfo* pReadInfo = (CEntityReadInfo*)a1;
|
||||
if (pReadInfo->nNewEntity >= 0x1000 || pReadInfo->nNewEntity < 0)
|
||||
{
|
||||
std::string cmdStr = command->Arg(0);
|
||||
for (char& c : cmdStr)
|
||||
c = tolower(c);
|
||||
// Value isn't sanitized in release builds for
|
||||
// every game powered by the Source Engine 1
|
||||
// causing read/write outside of array bounds.
|
||||
// This defect has let to the achievement of a
|
||||
// full-chain RCE exploit. We hook and perform
|
||||
// sanity checks for the value of m_nNewEntity
|
||||
// here to prevent this behavior from happening.
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Block this command
|
||||
spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr);
|
||||
return true;
|
||||
}
|
||||
// Just return, none of them have any args or are userpurge
|
||||
exportAddr.Patch("C3");
|
||||
spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
|
||||
}
|
||||
}
|
||||
|
||||
return oSpecialClientCommand(player, command);
|
||||
}
|
||||
|
||||
void SetupKHook(KHook* hook)
|
||||
{
|
||||
if (hook->Setup())
|
||||
{
|
||||
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));
|
||||
Cvar_ns_exploitfixes_log =
|
||||
new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something");
|
||||
Cvar_ns_should_log_all_clientcommands =
|
||||
new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands");
|
||||
|
||||
Cvar_sv_cheats = R2::g_pCVar->FindVar("sv_cheats");
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
}
|
|
@ -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*)>();
|
||||
}
|
|
@ -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
|
|
@ -1,87 +1,69 @@
|
|||
#include "pch.h"
|
||||
#include "filesystem.h"
|
||||
#include "hooks.h"
|
||||
#include "hookutils.h"
|
||||
#include "sourceinterface.h"
|
||||
#include "modmanager.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
// hook forward declares
|
||||
typedef FileHandle_t (*ReadFileFromVPKType)(VPKData* vpkInfo, __int64* b, char* filename);
|
||||
ReadFileFromVPKType readFileFromVPK;
|
||||
FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename);
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
typedef bool (*ReadFromCacheType)(IFileSystem* filesystem, char* path, void* result);
|
||||
ReadFromCacheType readFromCache;
|
||||
bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result);
|
||||
using namespace R2;
|
||||
|
||||
typedef void (*AddSearchPathType)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
|
||||
AddSearchPathType addSearchPathOriginal;
|
||||
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
|
||||
bool bReadingOriginalFile = false;
|
||||
std::string sCurrentModPath;
|
||||
|
||||
typedef FileHandle_t (*ReadFileFromFilesystemType)(
|
||||
IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5);
|
||||
ReadFileFromFilesystemType readFileFromFilesystem;
|
||||
FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5);
|
||||
ConVar* Cvar_ns_fs_log_reads;
|
||||
|
||||
typedef VPKData* (*MountVPKType)(IFileSystem* fileSystem, const char* vpkPath);
|
||||
MountVPKType mountVPK;
|
||||
VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath);
|
||||
|
||||
bool readingOriginalFile;
|
||||
std::string currentModPath;
|
||||
SourceInterface<IFileSystem>* g_Filesystem;
|
||||
|
||||
void InitialiseFilesystem(HMODULE baseAddress)
|
||||
// use the R2 namespace for game funcs
|
||||
namespace R2
|
||||
{
|
||||
g_Filesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
|
||||
SourceInterface<IFileSystem>* g_pFilesystem;
|
||||
|
||||
// create hooks
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x5CBA0, &ReadFileFromVPKHook, reinterpret_cast<LPVOID*>(&readFileFromVPK));
|
||||
ENABLER_CREATEHOOK(
|
||||
hook,
|
||||
reinterpret_cast<void*>((*g_Filesystem)->m_vtable->ReadFromCache),
|
||||
&ReadFromCacheHook,
|
||||
reinterpret_cast<LPVOID*>(&readFromCache));
|
||||
ENABLER_CREATEHOOK(
|
||||
hook,
|
||||
reinterpret_cast<void*>((*g_Filesystem)->m_vtable->AddSearchPath),
|
||||
&AddSearchPathHook,
|
||||
reinterpret_cast<LPVOID*>(&addSearchPathOriginal));
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15F20, &ReadFileFromFilesystemHook, reinterpret_cast<LPVOID*>(&readFileFromFilesystem));
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, reinterpret_cast<void*>((*g_Filesystem)->m_vtable->MountVPK), &MountVPKHook, reinterpret_cast<LPVOID*>(&mountVPK));
|
||||
}
|
||||
|
||||
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
|
||||
std::string ReadVPKFile(const char* path)
|
||||
{
|
||||
bytesRead = (*g_Filesystem)->m_vtable2->Read(&(*g_Filesystem)->m_vtable2, data, (int)std::size(data), fileHandle);
|
||||
fileStream.write(data, bytesRead);
|
||||
} while (bytesRead == std::size(data));
|
||||
// read scripts.rson file, todo: check if this can be overwritten
|
||||
FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0);
|
||||
|
||||
(*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;
|
||||
std::string ret = ReadVPKFile(path);
|
||||
readingOriginalFile = false;
|
||||
AddSearchPath(fileSystem, pPath, pathID, addType);
|
||||
|
||||
return ret;
|
||||
// make sure current mod paths are at head
|
||||
if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
|
||||
{
|
||||
AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||
AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||
}
|
||||
}
|
||||
|
||||
void SetNewModSearchPaths(Mod* mod)
|
||||
|
@ -90,96 +72,84 @@ void SetNewModSearchPaths(Mod* mod)
|
|||
// in the future we could also determine whether the file we're setting paths for needs a mod dir, or compiled assets
|
||||
if (mod != nullptr)
|
||||
{
|
||||
if ((fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().compare(currentModPath))
|
||||
if ((fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().compare(sCurrentModPath))
|
||||
{
|
||||
spdlog::info("changing mod search path from {} to {}", currentModPath, mod->ModDirectory.string());
|
||||
spdlog::info("changing mod search path from {} to {}", sCurrentModPath, mod->m_ModDirectory.string());
|
||||
|
||||
addSearchPathOriginal(
|
||||
&*(*g_Filesystem), (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||
currentModPath = (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string();
|
||||
AddSearchPath(
|
||||
&*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||
sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string();
|
||||
}
|
||||
}
|
||||
else // push compiled to head
|
||||
addSearchPathOriginal(&*(*g_Filesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||
AddSearchPath(&*(*g_pFilesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||
}
|
||||
|
||||
bool TryReplaceFile(char* path, bool shouldCompile)
|
||||
bool TryReplaceFile(const char* pPath, bool shouldCompile)
|
||||
{
|
||||
if (readingOriginalFile)
|
||||
if (bReadingOriginalFile)
|
||||
return false;
|
||||
|
||||
if (shouldCompile)
|
||||
(*g_ModManager).CompileAssetsForFile(path);
|
||||
g_pModManager->CompileAssetsForFile(pPath);
|
||||
|
||||
// idk how efficient the lexically normal check is
|
||||
// can't just set all /s in path to \, since some paths aren't in writeable memory
|
||||
auto file = g_ModManager->m_modFiles.find(fs::path(path).lexically_normal().string());
|
||||
if (file != g_ModManager->m_modFiles.end())
|
||||
auto file = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path(pPath)));
|
||||
if (file != g_pModManager->m_ModFiles.end())
|
||||
{
|
||||
SetNewModSearchPaths(file->second.owningMod);
|
||||
SetNewModSearchPaths(file->second.m_pOwningMod);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename)
|
||||
// force modded files to be read from mods, not cache
|
||||
// clang-format off
|
||||
HOOK(ReadFromCacheHook, ReadFromCache,
|
||||
bool, __fastcall, (IFileSystem * filesystem, char* pPath, void* result))
|
||||
// clang-format off
|
||||
{
|
||||
// move this to a convar at some point when we can read them in native
|
||||
// spdlog::info("ReadFileFromVPKHook {} {}", filename, vpkInfo->path);
|
||||
if (TryReplaceFile(pPath, true))
|
||||
return false;
|
||||
|
||||
// there is literally never any reason to compile here, since we'll always compile in ReadFileFromFilesystemHook in the same codepath
|
||||
// this is called
|
||||
return ReadFromCache(filesystem, pPath, result);
|
||||
}
|
||||
|
||||
// force modded files to be read from mods, not vpk
|
||||
// clang-format off
|
||||
AUTOHOOK(ReadFileFromVPK, filesystem_stdio.dll + 0x5CBA0,
|
||||
FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename))
|
||||
// clang-format on
|
||||
{
|
||||
// don't compile here because this is only ever called from OpenEx, which already compiles
|
||||
if (TryReplaceFile(filename, false))
|
||||
{
|
||||
*b = -1;
|
||||
return b;
|
||||
}
|
||||
|
||||
return readFileFromVPK(vpkInfo, b, filename);
|
||||
return ReadFileFromVPK(vpkInfo, b, filename);
|
||||
}
|
||||
|
||||
bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result)
|
||||
// clang-format off
|
||||
AUTOHOOK(CBaseFileSystem__OpenEx, filesystem_stdio.dll + 0x15F50,
|
||||
FileHandle_t, __fastcall, (IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char **ppszResolvedFilename))
|
||||
// clang-format on
|
||||
{
|
||||
// move this to a convar at some point when we can read them in native
|
||||
// spdlog::info("ReadFromCacheHook {}", path);
|
||||
|
||||
if (TryReplaceFile(path, true))
|
||||
return false;
|
||||
|
||||
return readFromCache(filesystem, path, result);
|
||||
TryReplaceFile(pPath, true);
|
||||
return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename);
|
||||
}
|
||||
|
||||
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)
|
||||
HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath))
|
||||
{
|
||||
addSearchPathOriginal(fileSystem, pPath, pathID, addType);
|
||||
spdlog::info("MountVPK {}", pVpkPath);
|
||||
VPKData* ret = MountVPK(fileSystem, pVpkPath);
|
||||
|
||||
// make sure current mod paths are at head
|
||||
if (!strcmp(pathID, "GAME") && currentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
|
||||
for (Mod mod : g_pModManager->m_LoadedMods)
|
||||
{
|
||||
addSearchPathOriginal(fileSystem, currentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||
addSearchPathOriginal(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||
}
|
||||
}
|
||||
|
||||
FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5)
|
||||
{
|
||||
// this isn't super efficient, but it's necessary, since calling addsearchpath in readfilefromvpk doesn't work, possibly refactor later
|
||||
// it also might be possible to hook functions that are called later, idk look into callstack for ReadFileFromVPK
|
||||
if (!readingOriginalFile)
|
||||
TryReplaceFile((char*)pPath, true);
|
||||
|
||||
return readFileFromFilesystem(filesystem, pPath, pOptions, a4, a5);
|
||||
}
|
||||
|
||||
VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
|
||||
{
|
||||
spdlog::info("MountVPK {}", vpkPath);
|
||||
VPKData* ret = mountVPK(fileSystem, vpkPath);
|
||||
|
||||
for (Mod mod : g_ModManager->m_loadedMods)
|
||||
{
|
||||
if (!mod.Enabled)
|
||||
if (!mod.m_bEnabled)
|
||||
continue;
|
||||
|
||||
for (ModVPKEntry& vpkEntry : mod.Vpks)
|
||||
|
@ -189,13 +159,13 @@ VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
|
|||
{
|
||||
// resolve vpk name and try to load one with the same name
|
||||
// todo: we should be unloading these on map unload manually
|
||||
std::string mapName(fs::path(vpkPath).filename().string());
|
||||
std::string mapName(fs::path(pVpkPath).filename().string());
|
||||
std::string modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string());
|
||||
if (mapName.compare(modMapName))
|
||||
continue;
|
||||
}
|
||||
|
||||
VPKData* loaded = mountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str());
|
||||
VPKData* loaded = MountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str());
|
||||
if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here
|
||||
ret = loaded;
|
||||
}
|
||||
|
@ -203,3 +173,14 @@ VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ON_DLL_LOAD("filesystem_stdio.dll", Filesystem, (CModule module))
|
||||
{
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
R2::g_pFilesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
|
||||
|
||||
AddSearchPathHook.Dispatch((*g_pFilesystem)->m_vtable->AddSearchPath);
|
||||
ReadFromCacheHook.Dispatch((*g_pFilesystem)->m_vtable->ReadFromCache);
|
||||
MountVPKHook.Dispatch((*g_pFilesystem)->m_vtable->MountVPK);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ class IFileSystem
|
|||
FileHandle_t (*Open)(
|
||||
IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pOptions, const char* pathID, int64_t unknown);
|
||||
void (*Close)(IFileSystem* fileSystem, FileHandle_t file);
|
||||
void* unknown2[6];
|
||||
long long (*Seek)(IFileSystem::VTable2** fileSystem, FileHandle_t file, long long offset, long long whence);
|
||||
void* unknown2[5];
|
||||
bool (*FileExists)(IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pPathID);
|
||||
};
|
||||
|
||||
|
@ -67,8 +68,11 @@ class IFileSystem
|
|||
VTable2* m_vtable2;
|
||||
};
|
||||
|
||||
std::string ReadVPKFile(const char* path);
|
||||
std::string ReadVPKOriginalFile(const char* path);
|
||||
// use the R2 namespace for game funcs
|
||||
namespace R2
|
||||
{
|
||||
extern SourceInterface<IFileSystem>* g_pFilesystem;
|
||||
|
||||
void InitialiseFilesystem(HMODULE baseAddress);
|
||||
extern SourceInterface<IFileSystem>* g_Filesystem;
|
||||
std::string ReadVPKFile(const char* path);
|
||||
std::string ReadVPKOriginalFile(const char* path);
|
||||
} // namespace R2
|
||||
|
|
|
@ -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"));
|
||||
}
|
|
@ -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);
|
|
@ -1,61 +1,193 @@
|
|||
#include "pch.h"
|
||||
#include "hooks.h"
|
||||
#include "hookutils.h"
|
||||
#include "sigscanning.h"
|
||||
#include "dedicated.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <wchar.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <Psapi.h>
|
||||
|
||||
typedef LPSTR (*GetCommandLineAType)();
|
||||
LPSTR GetCommandLineAHook();
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
typedef HMODULE (*LoadLibraryExAType)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
|
||||
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
|
||||
|
||||
typedef HMODULE (*LoadLibraryAType)(LPCSTR lpLibFileName);
|
||||
HMODULE LoadLibraryAHook(LPCSTR lpLibFileName);
|
||||
|
||||
typedef HMODULE (*LoadLibraryExWType)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
|
||||
HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
|
||||
|
||||
typedef HMODULE (*LoadLibraryWType)(LPCWSTR lpLibFileName);
|
||||
HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName);
|
||||
|
||||
GetCommandLineAType GetCommandLineAOriginal;
|
||||
LoadLibraryExAType LoadLibraryExAOriginal;
|
||||
LoadLibraryAType LoadLibraryAOriginal;
|
||||
LoadLibraryExWType LoadLibraryExWOriginal;
|
||||
LoadLibraryWType LoadLibraryWOriginal;
|
||||
|
||||
void InstallInitialHooks()
|
||||
// called from the ON_DLL_LOAD macros
|
||||
__dllLoadCallback::__dllLoadCallback(
|
||||
eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn)
|
||||
{
|
||||
if (MH_Initialize() != MH_OK)
|
||||
spdlog::error("MH_Initialize (minhook initialization) failed");
|
||||
// parse reliesOn array from string
|
||||
std::vector<std::string> reliesOnArray;
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, reinterpret_cast<void*>(&GetCommandLineA), &GetCommandLineAHook, reinterpret_cast<LPVOID*>(&GetCommandLineAOriginal));
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, reinterpret_cast<void*>(&LoadLibraryExA), &LoadLibraryExAHook, reinterpret_cast<LPVOID*>(&LoadLibraryExAOriginal));
|
||||
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryA), &LoadLibraryAHook, reinterpret_cast<LPVOID*>(&LoadLibraryAOriginal));
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, reinterpret_cast<void*>(&LoadLibraryExW), &LoadLibraryExWHook, reinterpret_cast<LPVOID*>(&LoadLibraryExWOriginal));
|
||||
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryW), &LoadLibraryWHook, reinterpret_cast<LPVOID*>(&LoadLibraryWOriginal));
|
||||
if (reliesOn.length() && reliesOn[0] != '(')
|
||||
{
|
||||
reliesOnArray.push_back(reliesOn);
|
||||
}
|
||||
else
|
||||
{
|
||||
// follows the format (tag, tag, tag)
|
||||
std::string sCurrentTag;
|
||||
for (int i = 1; i < reliesOn.length(); i++)
|
||||
{
|
||||
if (!isspace(reliesOn[i]))
|
||||
{
|
||||
if (reliesOn[i] == ',' || reliesOn[i] == ')')
|
||||
{
|
||||
reliesOnArray.push_back(sCurrentTag);
|
||||
sCurrentTag = "";
|
||||
}
|
||||
else
|
||||
sCurrentTag += reliesOn[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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* cmdlineOrg;
|
||||
|
||||
if (cmdlineOrg == nullptr || cmdlineModified == nullptr)
|
||||
{
|
||||
cmdlineOrg = GetCommandLineAOriginal();
|
||||
cmdlineOrg = _GetCommandLineA();
|
||||
bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument
|
||||
bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs");
|
||||
|
||||
|
@ -111,77 +243,86 @@ LPSTR GetCommandLineAHook()
|
|||
return cmdlineModified;
|
||||
}
|
||||
|
||||
// dll load callback stuff
|
||||
// this allows for code to register callbacks to be run as soon as a dll is loaded, mainly to allow for patches to be made on dll load
|
||||
struct DllLoadCallback
|
||||
{
|
||||
std::string dll;
|
||||
DllLoadCallbackFuncType callback;
|
||||
bool called;
|
||||
};
|
||||
|
||||
std::vector<DllLoadCallback*> dllLoadCallbacks;
|
||||
|
||||
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback)
|
||||
{
|
||||
DllLoadCallback* callbackStruct = new DllLoadCallback;
|
||||
callbackStruct->dll = dll;
|
||||
callbackStruct->callback = callback;
|
||||
callbackStruct->called = false;
|
||||
|
||||
dllLoadCallbacks.push_back(callbackStruct);
|
||||
}
|
||||
|
||||
void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback)
|
||||
{
|
||||
if (!IsDedicatedServer())
|
||||
return;
|
||||
|
||||
DllLoadCallback* callbackStruct = new DllLoadCallback;
|
||||
callbackStruct->dll = dll;
|
||||
callbackStruct->callback = callback;
|
||||
callbackStruct->called = false;
|
||||
|
||||
dllLoadCallbacks.push_back(callbackStruct);
|
||||
}
|
||||
|
||||
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback)
|
||||
{
|
||||
if (IsDedicatedServer())
|
||||
return;
|
||||
|
||||
DllLoadCallback* callbackStruct = new DllLoadCallback;
|
||||
callbackStruct->dll = dll;
|
||||
callbackStruct->callback = callback;
|
||||
callbackStruct->called = false;
|
||||
|
||||
dllLoadCallbacks.push_back(callbackStruct);
|
||||
}
|
||||
|
||||
std::vector<std::string> calledTags;
|
||||
void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress)
|
||||
{
|
||||
for (auto& callbackStruct : dllLoadCallbacks)
|
||||
CModule cModule(moduleAddress);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!callbackStruct->called &&
|
||||
strstr(lpLibFileName + (strlen(lpLibFileName) - callbackStruct->dll.length()), callbackStruct->dll.c_str()) != nullptr)
|
||||
bool bDoneCalling = true;
|
||||
|
||||
for (auto& callbackStruct : GetDllLoadCallbacks())
|
||||
{
|
||||
callbackStruct->callback(moduleAddress);
|
||||
callbackStruct->called = true;
|
||||
if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
|
||||
{
|
||||
bool bShouldContinue = false;
|
||||
|
||||
if (!callbackStruct.reliesOn.empty())
|
||||
{
|
||||
for (std::string tag : callbackStruct.reliesOn)
|
||||
{
|
||||
if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
|
||||
{
|
||||
bDoneCalling = false;
|
||||
bShouldContinue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bShouldContinue)
|
||||
continue;
|
||||
|
||||
callbackStruct.callback(moduleAddress);
|
||||
calledTags.push_back(callbackStruct.tag);
|
||||
callbackStruct.called = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bDoneCalling)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress)
|
||||
{
|
||||
for (auto& callbackStruct : dllLoadCallbacks)
|
||||
CModule cModule(moduleAddress);
|
||||
|
||||
while (true)
|
||||
{
|
||||
std::wstring wcharStrDll = std::wstring(callbackStruct->dll.begin(), callbackStruct->dll.end());
|
||||
const wchar_t* callbackDll = wcharStrDll.c_str();
|
||||
if (!callbackStruct->called && wcsstr(lpLibFileName + (wcslen(lpLibFileName) - wcharStrDll.length()), callbackDll) != nullptr)
|
||||
bool bDoneCalling = true;
|
||||
|
||||
for (auto& callbackStruct : GetDllLoadCallbacks())
|
||||
{
|
||||
callbackStruct->callback(moduleAddress);
|
||||
callbackStruct->called = true;
|
||||
if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
|
||||
{
|
||||
bool bShouldContinue = false;
|
||||
|
||||
if (!callbackStruct.reliesOn.empty())
|
||||
{
|
||||
for (std::string tag : callbackStruct.reliesOn)
|
||||
{
|
||||
if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
|
||||
{
|
||||
bDoneCalling = false;
|
||||
bShouldContinue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bShouldContinue)
|
||||
continue;
|
||||
|
||||
callbackStruct.callback(moduleAddress);
|
||||
calledTags.push_back(callbackStruct.tag);
|
||||
callbackStruct.called = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bDoneCalling)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,65 +349,78 @@ void CallAllPendingDLLLoadCallbacks()
|
|||
}
|
||||
}
|
||||
|
||||
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
|
||||
// clang-format off
|
||||
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, LoadLibraryExA,
|
||||
HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
|
||||
// clang-format on
|
||||
{
|
||||
HMODULE moduleAddress;
|
||||
|
||||
// replace xinput dll with one that has ASLR
|
||||
if (!strncmp(lpLibFileName, "XInput1_3.dll", 14))
|
||||
{
|
||||
HMODULE moduleAddress = LoadLibraryExAOriginal("XInput9_1_0.dll", hFile, dwFlags);
|
||||
if (moduleAddress)
|
||||
{
|
||||
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
|
||||
}
|
||||
else
|
||||
moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags);
|
||||
|
||||
if (!moduleAddress)
|
||||
{
|
||||
MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR);
|
||||
exit(-1);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
return moduleAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
HMODULE moduleAddress = LoadLibraryExAOriginal(lpLibFileName, hFile, dwFlags);
|
||||
if (moduleAddress)
|
||||
{
|
||||
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
|
||||
}
|
||||
return moduleAddress;
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE LoadLibraryAHook(LPCSTR lpLibFileName)
|
||||
{
|
||||
HMODULE moduleAddress = LoadLibraryAOriginal(lpLibFileName);
|
||||
moduleAddress = _LoadLibraryExA(lpLibFileName, hFile, dwFlags);
|
||||
|
||||
if (moduleAddress)
|
||||
{
|
||||
CallLoadLibraryACallbacks(lpLibFileName, 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)
|
||||
{
|
||||
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
|
||||
}
|
||||
CallLoadLibraryACallbacks(lpLibFileName, 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)
|
||||
{
|
||||
CallLoadLibraryWCallbacks(lpLibFileName, 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()
|
||||
}
|
||||
|
|
|
@ -1,11 +1,311 @@
|
|||
#pragma once
|
||||
#include "memory.h"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
void InstallInitialHooks();
|
||||
|
||||
typedef void (*DllLoadCallbackFuncType)(HMODULE moduleAddress);
|
||||
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback);
|
||||
void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback);
|
||||
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback);
|
||||
typedef void (*DllLoadCallbackFuncType)(CModule moduleAddress);
|
||||
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
|
||||
void AddDllLoadCallbackForDedicatedServer(
|
||||
std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
|
||||
void AddDllLoadCallbackForClient(
|
||||
std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
|
||||
|
||||
void CallAllPendingDLLLoadCallbacks();
|
||||
|
||||
// new dll load callback stuff
|
||||
enum class eDllLoadCallbackSide
|
||||
{
|
||||
UNSIDED,
|
||||
CLIENT,
|
||||
DEDICATED_SERVER
|
||||
};
|
||||
|
||||
class __dllLoadCallback
|
||||
{
|
||||
public:
|
||||
__dllLoadCallback() = delete;
|
||||
__dllLoadCallback(
|
||||
eDllLoadCallbackSide side,
|
||||
const std::string dllName,
|
||||
DllLoadCallbackFuncType callback,
|
||||
std::string uniqueStr,
|
||||
std::string reliesOn);
|
||||
};
|
||||
|
||||
#define __CONCAT3(x, y, z) x##y##z
|
||||
#define CONCAT3(x, y, z) __CONCAT3(x, y, z)
|
||||
#define __CONCAT2(x, y) x##y
|
||||
#define CONCAT2(x, y) __CONCAT2(x, y)
|
||||
#define __STR(s) #s
|
||||
|
||||
// adds a callback to be called when a given dll is loaded, for creating hooks and such
|
||||
#define __ON_DLL_LOAD(dllName, side, uniquestr, reliesOn, args) \
|
||||
void CONCAT2(__dllLoadCallback, uniquestr) args; \
|
||||
namespace \
|
||||
{ \
|
||||
__dllLoadCallback CONCAT2(__dllLoadCallbackInstance, __LINE__)( \
|
||||
side, dllName, CONCAT2(__dllLoadCallback, uniquestr), __STR(uniquestr), reliesOn); \
|
||||
} \
|
||||
void CONCAT2(__dllLoadCallback, uniquestr) args
|
||||
|
||||
#define ON_DLL_LOAD(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, "", args)
|
||||
#define ON_DLL_LOAD_RELIESON(dllName, uniquestr, reliesOn, args) \
|
||||
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, __STR(reliesOn), args)
|
||||
#define ON_DLL_LOAD_CLIENT(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, "", args)
|
||||
#define ON_DLL_LOAD_CLIENT_RELIESON(dllName, uniquestr, reliesOn, args) \
|
||||
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, __STR(reliesOn), args)
|
||||
#define ON_DLL_LOAD_DEDI(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, "", args)
|
||||
#define ON_DLL_LOAD_DEDI_RELIESON(dllName, uniquestr, reliesOn, args) \
|
||||
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, __STR(reliesOn), args)
|
||||
|
||||
// new macro hook stuff
|
||||
class __autohook;
|
||||
|
||||
class __fileAutohook
|
||||
{
|
||||
public:
|
||||
std::vector<__autohook*> hooks;
|
||||
|
||||
void Dispatch();
|
||||
void DispatchForModule(const char* pModuleName);
|
||||
};
|
||||
|
||||
// initialise autohooks for this file
|
||||
#define AUTOHOOK_INIT() \
|
||||
namespace \
|
||||
{ \
|
||||
__fileAutohook __FILEAUTOHOOK; \
|
||||
}
|
||||
|
||||
// dispatch all autohooks in this file
|
||||
#define AUTOHOOK_DISPATCH() __FILEAUTOHOOK.Dispatch();
|
||||
|
||||
#define AUTOHOOK_DISPATCH_MODULE(moduleName) __FILEAUTOHOOK.DispatchForModule(__STR(moduleName));
|
||||
|
||||
class __autohook
|
||||
{
|
||||
public:
|
||||
enum AddressResolutionMode
|
||||
{
|
||||
OFFSET_STRING, // we're using a string that of the format dllname.dll + offset
|
||||
ABSOLUTE_ADDR, // we're using an absolute address, we don't need to process it at all
|
||||
PROCADDRESS // resolve using GetModuleHandle and GetProcAddress
|
||||
};
|
||||
|
||||
char* pFuncName;
|
||||
|
||||
LPVOID pHookFunc;
|
||||
LPVOID* ppOrigFunc;
|
||||
|
||||
// address resolution props
|
||||
AddressResolutionMode iAddressResolutionMode;
|
||||
char* pAddrString = nullptr; // for OFFSET_STRING
|
||||
LPVOID iAbsoluteAddress = nullptr; // for ABSOLUTE_ADDR
|
||||
char* pModuleName; // for PROCADDRESS
|
||||
char* pProcName; // for PROCADDRESS
|
||||
|
||||
public:
|
||||
__autohook() = delete;
|
||||
|
||||
__autohook(__fileAutohook* autohook, const char* funcName, LPVOID absoluteAddress, LPVOID* orig, LPVOID func)
|
||||
: pHookFunc(func), ppOrigFunc(orig), iAbsoluteAddress(absoluteAddress)
|
||||
{
|
||||
iAddressResolutionMode = ABSOLUTE_ADDR;
|
||||
|
||||
const int iFuncNameStrlen = strlen(funcName) + 1;
|
||||
pFuncName = new char[iFuncNameStrlen];
|
||||
memcpy(pFuncName, funcName, iFuncNameStrlen);
|
||||
|
||||
autohook->hooks.push_back(this);
|
||||
}
|
||||
|
||||
__autohook(__fileAutohook* autohook, const char* funcName, const char* addrString, LPVOID* orig, LPVOID func)
|
||||
: pHookFunc(func), ppOrigFunc(orig)
|
||||
{
|
||||
iAddressResolutionMode = OFFSET_STRING;
|
||||
|
||||
const int iFuncNameStrlen = strlen(funcName) + 1;
|
||||
pFuncName = new char[iFuncNameStrlen];
|
||||
memcpy(pFuncName, funcName, iFuncNameStrlen);
|
||||
|
||||
const int iAddrStrlen = strlen(addrString) + 1;
|
||||
pAddrString = new char[iAddrStrlen];
|
||||
memcpy(pAddrString, addrString, iAddrStrlen);
|
||||
|
||||
autohook->hooks.push_back(this);
|
||||
}
|
||||
|
||||
__autohook(__fileAutohook* autohook, const char* funcName, const char* moduleName, const char* procName, LPVOID* orig, LPVOID func)
|
||||
: pHookFunc(func), ppOrigFunc(orig)
|
||||
{
|
||||
iAddressResolutionMode = PROCADDRESS;
|
||||
|
||||
const int iFuncNameStrlen = strlen(funcName) + 1;
|
||||
pFuncName = new char[iFuncNameStrlen];
|
||||
memcpy(pFuncName, funcName, iFuncNameStrlen);
|
||||
|
||||
const int iModuleNameStrlen = strlen(moduleName) + 1;
|
||||
pModuleName = new char[iModuleNameStrlen];
|
||||
memcpy(pModuleName, moduleName, iModuleNameStrlen);
|
||||
|
||||
const int iProcNameStrlen = strlen(procName) + 1;
|
||||
pProcName = new char[iProcNameStrlen];
|
||||
memcpy(pProcName, procName, iProcNameStrlen);
|
||||
|
||||
autohook->hooks.push_back(this);
|
||||
}
|
||||
|
||||
~__autohook()
|
||||
{
|
||||
delete[] pFuncName;
|
||||
|
||||
if (pAddrString)
|
||||
delete[] pAddrString;
|
||||
|
||||
if (pModuleName)
|
||||
delete[] pModuleName;
|
||||
|
||||
if (pProcName)
|
||||
delete[] pProcName;
|
||||
}
|
||||
|
||||
void Dispatch()
|
||||
{
|
||||
LPVOID targetAddr = nullptr;
|
||||
|
||||
// determine the address of the function we're hooking
|
||||
switch (iAddressResolutionMode)
|
||||
{
|
||||
case ABSOLUTE_ADDR:
|
||||
{
|
||||
targetAddr = iAbsoluteAddress;
|
||||
break;
|
||||
}
|
||||
|
||||
case OFFSET_STRING:
|
||||
{
|
||||
// in the format server.dll + 0xDEADBEEF
|
||||
int iDllNameEnd = 0;
|
||||
for (; !isspace(pAddrString[iDllNameEnd]) && pAddrString[iDllNameEnd] != '+'; iDllNameEnd++)
|
||||
;
|
||||
|
||||
char* pModuleName = new char[iDllNameEnd + 1];
|
||||
memcpy(pModuleName, pAddrString, iDllNameEnd);
|
||||
pModuleName[iDllNameEnd] = '\0';
|
||||
|
||||
// get the module address
|
||||
const HMODULE pModuleAddr = GetModuleHandleA(pModuleName);
|
||||
|
||||
if (!pModuleAddr)
|
||||
break;
|
||||
|
||||
// get the offset string
|
||||
uintptr_t iOffset = 0;
|
||||
|
||||
int iOffsetBegin = iDllNameEnd;
|
||||
int iOffsetEnd = strlen(pAddrString);
|
||||
|
||||
// seek until we hit the start of the number offset
|
||||
for (; !(pAddrString[iOffsetBegin] >= '0' && pAddrString[iOffsetBegin] <= '9') && pAddrString[iOffsetBegin]; iOffsetBegin++)
|
||||
;
|
||||
|
||||
bool bIsHex =
|
||||
pAddrString[iOffsetBegin] == '0' && (pAddrString[iOffsetBegin + 1] == 'X' || pAddrString[iOffsetBegin + 1] == 'x');
|
||||
if (bIsHex)
|
||||
iOffset = std::stoi(pAddrString + iOffsetBegin + 2, 0, 16);
|
||||
else
|
||||
iOffset = std::stoi(pAddrString + iOffsetBegin);
|
||||
|
||||
targetAddr = (LPVOID)((uintptr_t)pModuleAddr + iOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
case PROCADDRESS:
|
||||
{
|
||||
targetAddr = GetProcAddress(GetModuleHandleA(pModuleName), pProcName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (MH_CreateHook(targetAddr, pHookFunc, ppOrigFunc) == MH_OK)
|
||||
{
|
||||
if (MH_EnableHook(targetAddr) == MH_OK)
|
||||
spdlog::info("Enabling hook {}", pFuncName);
|
||||
else
|
||||
spdlog::error("MH_EnableHook failed for function {}", pFuncName);
|
||||
}
|
||||
else
|
||||
spdlog::error("MH_CreateHook failed for function {}", pFuncName);
|
||||
}
|
||||
};
|
||||
|
||||
// hook a function at a given offset from a dll to be dispatched with AUTOHOOK_DISPATCH()
|
||||
#define AUTOHOOK(name, addrString, type, callingConvention, args) \
|
||||
type callingConvention CONCAT2(__autohookfunc, name) args; \
|
||||
namespace \
|
||||
{ \
|
||||
type(*callingConvention name) args; \
|
||||
__autohook CONCAT2(__autohook, __LINE__)( \
|
||||
&__FILEAUTOHOOK, __STR(name), __STR(addrString), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
|
||||
} \
|
||||
type callingConvention CONCAT2(__autohookfunc, name) args
|
||||
|
||||
// hook a function at a given absolute constant address to be dispatched with AUTOHOOK_DISPATCH()
|
||||
#define AUTOHOOK_ABSOLUTEADDR(name, addr, type, callingConvention, args) \
|
||||
type callingConvention CONCAT2(__autohookfunc, name) args; \
|
||||
namespace \
|
||||
{ \
|
||||
type(*callingConvention name) args; \
|
||||
__autohook \
|
||||
CONCAT2(__autohook, __LINE__)(&__FILEAUTOHOOK, __STR(name), addr, (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
|
||||
} \
|
||||
type callingConvention CONCAT2(__autohookfunc, name) args
|
||||
|
||||
// hook a function at a given module and exported function to be dispatched with AUTOHOOK_DISPATCH()
|
||||
#define AUTOHOOK_PROCADDRESS(name, moduleName, procName, type, callingConvention, args) \
|
||||
type callingConvention CONCAT2(__autohookfunc, name) args; \
|
||||
namespace \
|
||||
{ \
|
||||
type(*callingConvention name) args; \
|
||||
__autohook CONCAT2(__autohook, __LINE__)( \
|
||||
&__FILEAUTOHOOK, __STR(name), __STR(moduleName), __STR(procName), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
|
||||
} \
|
||||
type callingConvention CONCAT2(__autohookfunc, name) \
|
||||
args
|
||||
|
||||
class ManualHook
|
||||
{
|
||||
public:
|
||||
char* pFuncName;
|
||||
|
||||
LPVOID pHookFunc;
|
||||
LPVOID* ppOrigFunc;
|
||||
|
||||
public:
|
||||
ManualHook() = delete;
|
||||
ManualHook(const char* funcName, LPVOID func);
|
||||
ManualHook(const char* funcName, LPVOID* orig, LPVOID func);
|
||||
bool Dispatch(LPVOID addr, LPVOID* orig = nullptr);
|
||||
};
|
||||
|
||||
// hook a function to be dispatched manually later
|
||||
#define HOOK(varName, originalFunc, type, callingConvention, args) \
|
||||
namespace \
|
||||
{ \
|
||||
type(*callingConvention originalFunc) args; \
|
||||
} \
|
||||
type callingConvention CONCAT2(__manualhookfunc, varName) args; \
|
||||
ManualHook varName = ManualHook(__STR(varName), (LPVOID*)&originalFunc, (LPVOID)CONCAT2(__manualhookfunc, varName)); \
|
||||
type callingConvention CONCAT2(__manualhookfunc, varName) args
|
||||
|
||||
#define HOOK_NOORIG(varName, type, callingConvention, args) \
|
||||
type callingConvention CONCAT2(__manualhookfunc, varName) args; \
|
||||
ManualHook varName = ManualHook(__STR(varName), (LPVOID)CONCAT2(__manualhookfunc, varName)); \
|
||||
type callingConvention CONCAT2(__manualhookfunc, varName) \
|
||||
args
|
||||
|
||||
void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFuncName = "");
|
||||
#define MAKEHOOK(pTarget, pDetour, ppOriginal) MakeHook(pTarget, pDetour, ppOriginal, __STR(pDetour))
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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()
|
||||
}
|
|
@ -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*>();
|
||||
}
|
|
@ -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.
|
@ -1,45 +1,16 @@
|
|||
#include "pch.h"
|
||||
#include "keyvalues.h"
|
||||
#include "modmanager.h"
|
||||
#include "filesystem.h"
|
||||
#include "hookutils.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
// hook forward defs
|
||||
typedef char (*KeyValues__LoadFromBufferType)(
|
||||
void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7);
|
||||
KeyValues__LoadFromBufferType KeyValues__LoadFromBuffer;
|
||||
char KeyValues__LoadFromBufferHook(
|
||||
void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7);
|
||||
|
||||
void InitialiseKeyValues(HMODULE baseAddress)
|
||||
{
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, (char*)baseAddress + 0x426C30, &KeyValues__LoadFromBufferHook, reinterpret_cast<LPVOID*>(&KeyValues__LoadFromBuffer));
|
||||
}
|
||||
|
||||
void* savedFilesystemPtr;
|
||||
|
||||
char KeyValues__LoadFromBufferHook(void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7)
|
||||
{
|
||||
// this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of
|
||||
// LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it
|
||||
// from a function call that always happens before playlists is loaded
|
||||
if (pFileSystem != nullptr)
|
||||
savedFilesystemPtr = pFileSystem;
|
||||
if (!pFileSystem && !strcmp(resourceName, "playlists"))
|
||||
pFileSystem = savedFilesystemPtr;
|
||||
|
||||
return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7);
|
||||
}
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
void ModManager::TryBuildKeyValues(const char* filename)
|
||||
{
|
||||
spdlog::info("Building KeyValues for file {}", filename);
|
||||
|
||||
std::string normalisedPath = fs::path(filename).lexically_normal().string();
|
||||
std::string normalisedPath = g_pModManager->NormaliseModFilePath(fs::path(filename));
|
||||
fs::path compiledPath = GetCompiledAssetsPath() / filename;
|
||||
fs::path compiledDir = compiledPath.parent_path();
|
||||
fs::create_directories(compiledDir);
|
||||
|
@ -54,14 +25,14 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
|||
|
||||
// copy over patch kv files, and add #bases to new file, last mods' patches should be applied first
|
||||
// note: #include should be identical but it's actually just broken, thanks respawn
|
||||
for (int64_t i = m_loadedMods.size() - 1; i > -1; i--)
|
||||
for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
|
||||
{
|
||||
if (!m_loadedMods[i].Enabled)
|
||||
if (!m_LoadedMods[i].m_bEnabled)
|
||||
continue;
|
||||
|
||||
size_t fileHash = STR_HASH(normalisedPath);
|
||||
auto modKv = m_loadedMods[i].KeyValues.find(fileHash);
|
||||
if (modKv != m_loadedMods[i].KeyValues.end())
|
||||
auto modKv = m_LoadedMods[i].KeyValues.find(fileHash);
|
||||
if (modKv != m_LoadedMods[i].KeyValues.end())
|
||||
{
|
||||
// should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt"
|
||||
|
||||
|
@ -76,7 +47,7 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
|||
|
||||
fs::remove(compiledDir / patchFilePath);
|
||||
|
||||
fs::copy_file(m_loadedMods[i].ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
|
||||
fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +57,7 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
|||
newKvs += "\"\n";
|
||||
|
||||
// load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons)
|
||||
std::string originalFile = ReadVPKOriginalFile(filename);
|
||||
std::string originalFile = R2::ReadVPKOriginalFile(filename);
|
||||
|
||||
if (!originalFile.length())
|
||||
{
|
||||
|
@ -96,7 +67,6 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
|||
|
||||
char rootName[64];
|
||||
memset(rootName, 0, sizeof(rootName));
|
||||
rootName[63] = '\0';
|
||||
|
||||
// iterate until we hit an ascii char that isn't in a # command or comment to get root obj name
|
||||
int i = 0;
|
||||
|
@ -127,11 +97,36 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
|||
writeStream.close();
|
||||
|
||||
ModOverrideFile overrideFile;
|
||||
overrideFile.owningMod = nullptr;
|
||||
overrideFile.path = normalisedPath;
|
||||
overrideFile.m_pOwningMod = nullptr;
|
||||
overrideFile.m_Path = normalisedPath;
|
||||
|
||||
if (m_modFiles.find(normalisedPath) == m_modFiles.end())
|
||||
m_modFiles.insert(std::make_pair(normalisedPath, overrideFile));
|
||||
if (m_ModFiles.find(normalisedPath) == m_ModFiles.end())
|
||||
m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile));
|
||||
else
|
||||
m_modFiles[normalisedPath] = overrideFile;
|
||||
m_ModFiles[normalisedPath] = overrideFile;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(KeyValues__LoadFromBuffer, engine.dll + 0x426C30,
|
||||
char, __fastcall, (void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7))
|
||||
// clang-format on
|
||||
{
|
||||
static void* pSavedFilesystemPtr = nullptr;
|
||||
|
||||
// this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of
|
||||
// LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it
|
||||
// from a function call that always happens before playlists is loaded
|
||||
|
||||
// note: would be better if we could serialize this to disk for playlists, as this method breaks saving playlists in demos
|
||||
if (pFileSystem != nullptr)
|
||||
pSavedFilesystemPtr = pFileSystem;
|
||||
if (!pFileSystem && !strcmp(resourceName, "playlists"))
|
||||
pFileSystem = pSavedFilesystemPtr;
|
||||
|
||||
return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7);
|
||||
}
|
||||
|
||||
ON_DLL_LOAD("engine.dll", KeyValues, (CModule module))
|
||||
{
|
||||
AUTOHOOK_DISPATCH()
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void InitialiseKeyValues(HMODULE baseAddress);
|
|
@ -1,18 +1,13 @@
|
|||
#include "pch.h"
|
||||
#include "languagehooks.h"
|
||||
#include "gameutils.h"
|
||||
#include "tier0.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <regex>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
typedef char* (*GetGameLanguageType)();
|
||||
char* GetGameLanguage();
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
typedef LANGID (*Tier0_DetectDefaultLanguageType)();
|
||||
|
||||
GetGameLanguageType GetGameLanguageOriginal;
|
||||
|
||||
bool CheckLangAudioExists(char* lang)
|
||||
{
|
||||
std::string path {"r2\\sound\\general_"};
|
||||
|
@ -52,7 +47,10 @@ std::string GetAnyInstalledAudioLanguage()
|
|||
return "NO LANGUAGE DETECTED";
|
||||
}
|
||||
|
||||
char* GetGameLanguageHook()
|
||||
// clang-format off
|
||||
AUTOHOOK(GetGameLanguage, tier0.dll + 0xF560,
|
||||
char*, __fastcall, ())
|
||||
// clang-format on
|
||||
{
|
||||
auto tier0Handle = GetModuleHandleA("tier0.dll");
|
||||
auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage");
|
||||
|
@ -60,7 +58,7 @@ char* GetGameLanguageHook()
|
|||
bool& canOriginDictateLang = *(bool*)((char*)tier0Handle + 0xA9A90);
|
||||
|
||||
const char* forcedLanguage;
|
||||
if (CommandLine()->CheckParm("-language", &forcedLanguage))
|
||||
if (Tier0::CommandLine()->CheckParm("-language", &forcedLanguage))
|
||||
{
|
||||
if (!CheckLangAudioExists((char*)forcedLanguage))
|
||||
{
|
||||
|
@ -79,7 +77,7 @@ char* GetGameLanguageHook()
|
|||
|
||||
canOriginDictateLang = true; // let it try
|
||||
{
|
||||
auto lang = GetGameLanguageOriginal();
|
||||
auto lang = GetGameLanguage();
|
||||
if (!CheckLangAudioExists(lang))
|
||||
{
|
||||
if (strcmp(lang, "russian") !=
|
||||
|
@ -97,7 +95,7 @@ char* GetGameLanguageHook()
|
|||
Tier0_DetectDefaultLanguageType(); // force the global in tier0 to be populated with language inferred from user's system rather than
|
||||
// defaulting to Russian
|
||||
canOriginDictateLang = false; // Origin has no say anymore, we will fallback to user's system setup language
|
||||
auto lang = GetGameLanguageOriginal();
|
||||
auto lang = GetGameLanguage();
|
||||
spdlog::info("Detected system language: {}", lang);
|
||||
if (!CheckLangAudioExists(lang))
|
||||
{
|
||||
|
@ -112,8 +110,7 @@ char* GetGameLanguageHook()
|
|||
return lang;
|
||||
}
|
||||
|
||||
void InitialiseTier0LanguageHooks(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT("tier0.dll", LanguageHooks, (CModule module))
|
||||
{
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xF560, &GetGameLanguageHook, reinterpret_cast<LPVOID*>(&GetGameLanguageOriginal));
|
||||
AUTOHOOK_DISPATCH()
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void InitialiseTier0LanguageHooks(HMODULE baseAddress);
|
|
@ -1,76 +1,44 @@
|
|||
#include "pch.h"
|
||||
#include "latencyflex.h"
|
||||
#include "hookutils.h"
|
||||
#include "convar.h"
|
||||
|
||||
typedef void (*OnRenderStartType)();
|
||||
OnRenderStartType OnRenderStart;
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
ConVar* Cvar_r_latencyflex;
|
||||
|
||||
HMODULE m_lfxModule {};
|
||||
typedef void (*PFN_lfx_WaitAndBeginFrame)();
|
||||
PFN_lfx_WaitAndBeginFrame m_lfx_WaitAndBeginFrame {};
|
||||
void (*m_winelfx_WaitAndBeginFrame)();
|
||||
|
||||
void OnRenderStartHook()
|
||||
// clang-format off
|
||||
AUTOHOOK(OnRenderStart, client.dll + 0x1952C0,
|
||||
void, __fastcall, ())
|
||||
// clang-format on
|
||||
{
|
||||
// Sleep before next frame as needed to reduce latency.
|
||||
if (Cvar_r_latencyflex->GetInt())
|
||||
{
|
||||
if (m_lfx_WaitAndBeginFrame)
|
||||
{
|
||||
m_lfx_WaitAndBeginFrame();
|
||||
}
|
||||
}
|
||||
if (Cvar_r_latencyflex->GetBool() && m_winelfx_WaitAndBeginFrame)
|
||||
m_winelfx_WaitAndBeginFrame();
|
||||
|
||||
OnRenderStart();
|
||||
}
|
||||
|
||||
void InitialiseLatencyFleX(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module))
|
||||
{
|
||||
// Connect to the LatencyFleX service
|
||||
// LatencyFleX is an open source vendor agnostic replacement for Nvidia Reflex input latency reduction technology.
|
||||
// https://ishitatsuyuki.github.io/post/latencyflex/
|
||||
const auto lfxModuleName = "latencyflex_layer.dll";
|
||||
const auto lfxModuleNameFallback = "latencyflex_wine.dll";
|
||||
auto useFallbackEntrypoints = false;
|
||||
HMODULE pLfxModule;
|
||||
|
||||
// Load LatencyFleX library.
|
||||
m_lfxModule = ::LoadLibraryA(lfxModuleName);
|
||||
if (m_lfxModule == nullptr && ::GetLastError() == ERROR_MOD_NOT_FOUND)
|
||||
if (pLfxModule = LoadLibraryA("latencyflex_layer.dll"))
|
||||
m_winelfx_WaitAndBeginFrame =
|
||||
reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "lfx_WaitAndBeginFrame")));
|
||||
else if (pLfxModule = LoadLibraryA("latencyflex_wine.dll"))
|
||||
m_winelfx_WaitAndBeginFrame =
|
||||
reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "winelfx_WaitAndBeginFrame")));
|
||||
else
|
||||
{
|
||||
spdlog::info("LFX: Primary LatencyFleX library not found, trying fallback.");
|
||||
|
||||
m_lfxModule = ::LoadLibraryA(lfxModuleNameFallback);
|
||||
if (m_lfxModule == nullptr)
|
||||
{
|
||||
if (::GetLastError() == ERROR_MOD_NOT_FOUND)
|
||||
{
|
||||
spdlog::info("LFX: Fallback LatencyFleX library not found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::info("LFX: Error loading fallback LatencyFleX library - Code: {}", ::GetLastError());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
useFallbackEntrypoints = true;
|
||||
}
|
||||
else if (m_lfxModule == nullptr)
|
||||
{
|
||||
spdlog::info("LFX: Error loading primary LatencyFleX library - Code: {}", ::GetLastError());
|
||||
spdlog::info("Unable to load LatencyFleX library, LatencyFleX disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_lfx_WaitAndBeginFrame = reinterpret_cast<PFN_lfx_WaitAndBeginFrame>(reinterpret_cast<void*>(
|
||||
GetProcAddress(m_lfxModule, !useFallbackEntrypoints ? "lfx_WaitAndBeginFrame" : "winelfx_WaitAndBeginFrame")));
|
||||
|
||||
spdlog::info("LFX: Initialized.");
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
spdlog::info("LatencyFleX initialized.");
|
||||
Cvar_r_latencyflex = new ConVar("r_latencyflex", "1", FCVAR_ARCHIVE, "Whether or not to use LatencyFleX input latency reduction.");
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1952C0, &OnRenderStartHook, reinterpret_cast<LPVOID*>(&OnRenderStart));
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#pragma once
|
||||
void InitialiseLatencyFleX(HMODULE baseAddress);
|
|
@ -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)
|
||||
}
|
|
@ -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;
|
|
@ -37,12 +37,11 @@ class vgui_BaseRichText_vtable
|
|||
|
||||
void(__fastcall* SetVerticalScrollbar)(vgui_BaseRichText* self, bool state);
|
||||
void(__fastcall* SetMaximumCharCount)(vgui_BaseRichText* self, int maxChars);
|
||||
void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, vgui_Color col);
|
||||
void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, Color col);
|
||||
void(__fastcall* InsertIndentChange)(vgui_BaseRichText* self, int pixelsIndent);
|
||||
void(__fastcall* InsertClickableTextStart)(vgui_BaseRichText* self, const char* pchClickAction);
|
||||
void(__fastcall* InsertClickableTextEnd)(vgui_BaseRichText* self);
|
||||
void(__fastcall* InsertPossibleURLString)(
|
||||
vgui_BaseRichText* self, const char* text, vgui_Color URLTextColor, vgui_Color normalTextColor);
|
||||
void(__fastcall* InsertPossibleURLString)(vgui_BaseRichText* self, const char* text, Color URLTextColor, Color normalTextColor);
|
||||
void(__fastcall* InsertFade)(vgui_BaseRichText* self, float flSustain, float flLength);
|
||||
void(__fastcall* ResetAllFades)(vgui_BaseRichText* self, bool bHold, bool bOnlyExpired, float flNewSustain);
|
||||
void(__fastcall* SetToFullHeight)(vgui_BaseRichText* self);
|
||||
|
@ -81,25 +80,25 @@ LocalChatWriter::SwatchColor swatchColors[4] = {
|
|||
LocalChatWriter::NetworkNameColor,
|
||||
};
|
||||
|
||||
vgui_Color darkColors[8] = {
|
||||
vgui_Color {0, 0, 0, 255},
|
||||
vgui_Color {205, 49, 49, 255},
|
||||
vgui_Color {13, 188, 121, 255},
|
||||
vgui_Color {229, 229, 16, 255},
|
||||
vgui_Color {36, 114, 200, 255},
|
||||
vgui_Color {188, 63, 188, 255},
|
||||
vgui_Color {17, 168, 205, 255},
|
||||
vgui_Color {229, 229, 229, 255}};
|
||||
Color darkColors[8] = {
|
||||
Color {0, 0, 0, 255},
|
||||
Color {205, 49, 49, 255},
|
||||
Color {13, 188, 121, 255},
|
||||
Color {229, 229, 16, 255},
|
||||
Color {36, 114, 200, 255},
|
||||
Color {188, 63, 188, 255},
|
||||
Color {17, 168, 205, 255},
|
||||
Color {229, 229, 229, 255}};
|
||||
|
||||
vgui_Color lightColors[8] = {
|
||||
vgui_Color {102, 102, 102, 255},
|
||||
vgui_Color {241, 76, 76, 255},
|
||||
vgui_Color {35, 209, 139, 255},
|
||||
vgui_Color {245, 245, 67, 255},
|
||||
vgui_Color {59, 142, 234, 255},
|
||||
vgui_Color {214, 112, 214, 255},
|
||||
vgui_Color {41, 184, 219, 255},
|
||||
vgui_Color {255, 255, 255, 255}};
|
||||
Color lightColors[8] = {
|
||||
Color {102, 102, 102, 255},
|
||||
Color {241, 76, 76, 255},
|
||||
Color {35, 209, 139, 255},
|
||||
Color {245, 245, 67, 255},
|
||||
Color {59, 142, 234, 255},
|
||||
Color {214, 112, 214, 255},
|
||||
Color {41, 184, 219, 255},
|
||||
Color {255, 255, 255, 255}};
|
||||
|
||||
class AnsiEscapeParser
|
||||
{
|
||||
|
@ -144,7 +143,7 @@ class AnsiEscapeParser
|
|||
|
||||
LocalChatWriter* m_writer;
|
||||
Next m_next = Next::ControlType;
|
||||
vgui_Color m_expandedColor {0, 0, 0, 0};
|
||||
Color m_expandedColor {0, 0, 0, 0};
|
||||
|
||||
Next HandleControlType(unsigned long val)
|
||||
{
|
||||
|
@ -190,7 +189,7 @@ class AnsiEscapeParser
|
|||
// Next values are r,g,b
|
||||
if (val == 2)
|
||||
{
|
||||
m_expandedColor = {0, 0, 0, 255};
|
||||
m_expandedColor.SetColor(0, 0, 0, 255);
|
||||
return Next::ForegroundR;
|
||||
}
|
||||
// Next value is 8-bit swatch color
|
||||
|
@ -219,13 +218,12 @@ class AnsiEscapeParser
|
|||
unsigned char blue = code % 6;
|
||||
unsigned char green = ((code - blue) / 6) % 6;
|
||||
unsigned char red = (code - blue - (green * 6)) / 36;
|
||||
m_writer->InsertColorChange(
|
||||
vgui_Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
|
||||
m_writer->InsertColorChange(Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
|
||||
}
|
||||
else if (val < UCHAR_MAX)
|
||||
{
|
||||
unsigned char brightness = (val - 232) * 10 + 8;
|
||||
m_writer->InsertColorChange(vgui_Color {brightness, brightness, brightness, 255});
|
||||
m_writer->InsertColorChange(Color {brightness, brightness, brightness, 255});
|
||||
}
|
||||
|
||||
return Next::ControlType;
|
||||
|
@ -236,7 +234,7 @@ class AnsiEscapeParser
|
|||
if (val >= UCHAR_MAX)
|
||||
return Next::ControlType;
|
||||
|
||||
m_expandedColor.r = (unsigned char)val;
|
||||
m_expandedColor[0] = (unsigned char)val;
|
||||
return Next::ForegroundG;
|
||||
}
|
||||
|
||||
|
@ -245,7 +243,7 @@ class AnsiEscapeParser
|
|||
if (val >= UCHAR_MAX)
|
||||
return Next::ControlType;
|
||||
|
||||
m_expandedColor.g = (unsigned char)val;
|
||||
m_expandedColor[1] = (unsigned char)val;
|
||||
return Next::ForegroundB;
|
||||
}
|
||||
|
||||
|
@ -254,7 +252,7 @@ class AnsiEscapeParser
|
|||
if (val >= UCHAR_MAX)
|
||||
return Next::ControlType;
|
||||
|
||||
m_expandedColor.b = (unsigned char)val;
|
||||
m_expandedColor[2] = (unsigned char)val;
|
||||
m_writer->InsertColorChange(m_expandedColor);
|
||||
return Next::ControlType;
|
||||
}
|
||||
|
@ -280,12 +278,11 @@ void LocalChatWriter::Write(const char* str)
|
|||
if (startOfEscape != str)
|
||||
{
|
||||
// There is some text before the escape sequence, just print that
|
||||
|
||||
size_t copyChars = startOfEscape - str;
|
||||
if (copyChars > 255)
|
||||
copyChars = 255;
|
||||
strncpy(writeBuffer, str, copyChars);
|
||||
writeBuffer[copyChars] = 0;
|
||||
|
||||
strncpy_s(writeBuffer, copyChars + 1, str, copyChars);
|
||||
|
||||
InsertText(writeBuffer);
|
||||
}
|
||||
|
@ -320,6 +317,8 @@ void LocalChatWriter::InsertChar(wchar_t ch)
|
|||
|
||||
void LocalChatWriter::InsertText(const char* str)
|
||||
{
|
||||
spdlog::info(str);
|
||||
|
||||
WCHAR messageUnicode[288];
|
||||
ConvertANSIToUnicode(str, -1, messageUnicode, 274);
|
||||
|
||||
|
@ -347,7 +346,7 @@ void LocalChatWriter::InsertText(const wchar_t* str)
|
|||
InsertDefaultFade();
|
||||
}
|
||||
|
||||
void LocalChatWriter::InsertColorChange(vgui_Color color)
|
||||
void LocalChatWriter::InsertColorChange(Color color)
|
||||
{
|
||||
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
|
||||
{
|
||||
|
@ -358,20 +357,24 @@ void LocalChatWriter::InsertColorChange(vgui_Color color)
|
|||
}
|
||||
}
|
||||
|
||||
static vgui_Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor)
|
||||
static Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor)
|
||||
{
|
||||
switch (swatchColor)
|
||||
{
|
||||
case LocalChatWriter::MainTextColor:
|
||||
return hud->m_mainTextColor;
|
||||
|
||||
case LocalChatWriter::SameTeamNameColor:
|
||||
return hud->m_sameTeamColor;
|
||||
|
||||
case LocalChatWriter::EnemyTeamNameColor:
|
||||
return hud->m_enemyTeamColor;
|
||||
|
||||
case LocalChatWriter::NetworkNameColor:
|
||||
return hud->m_networkNameColor;
|
||||
}
|
||||
return vgui_Color {0, 0, 0, 0};
|
||||
|
||||
return Color(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void LocalChatWriter::InsertSwatchColorChange(SwatchColor swatchColor)
|
||||
|
@ -436,12 +439,12 @@ void LocalChatWriter::InsertDefaultFade()
|
|||
}
|
||||
}
|
||||
|
||||
void InitialiseLocalChatWriter(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT("client.dll", LocalChatWriter, (CModule module))
|
||||
{
|
||||
gGameSettings = (CGameSettings**)((char*)baseAddress + 0x11BAA48);
|
||||
gChatFadeLength = (CGameFloatVar**)((char*)baseAddress + 0x11BAB78);
|
||||
gChatFadeSustain = (CGameFloatVar**)((char*)baseAddress + 0x11BAC08);
|
||||
CHudChat::allHuds = (CHudChat**)((char*)baseAddress + 0x11BA9E8);
|
||||
gGameSettings = module.Offset(0x11BAA48).As<CGameSettings**>();
|
||||
gChatFadeLength = module.Offset(0x11BAB78).As<CGameFloatVar**>();
|
||||
gChatFadeSustain = module.Offset(0x11BAC08).As<CGameFloatVar**>();
|
||||
CHudChat::allHuds = module.Offset(0x11BA9E8).As<CHudChat**>();
|
||||
|
||||
ConvertANSIToUnicode = (ConvertANSIToUnicodeType)((char*)baseAddress + 0x7339A0);
|
||||
ConvertANSIToUnicode = module.Offset(0x7339A0).As<ConvertANSIToUnicodeType>();
|
||||
}
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
#pragma once
|
||||
#include "pch.h"
|
||||
|
||||
struct vgui_Color
|
||||
{
|
||||
unsigned char r;
|
||||
unsigned char g;
|
||||
unsigned char b;
|
||||
unsigned char a;
|
||||
};
|
||||
#include "color.h"
|
||||
|
||||
class vgui_BaseRichText;
|
||||
|
||||
|
@ -18,10 +11,10 @@ class CHudChat
|
|||
|
||||
char unknown1[720];
|
||||
|
||||
vgui_Color m_sameTeamColor;
|
||||
vgui_Color m_enemyTeamColor;
|
||||
vgui_Color m_mainTextColor;
|
||||
vgui_Color m_networkNameColor;
|
||||
Color m_sameTeamColor;
|
||||
Color m_enemyTeamColor;
|
||||
Color m_mainTextColor;
|
||||
Color m_networkNameColor;
|
||||
|
||||
char unknown2[12];
|
||||
|
||||
|
@ -61,7 +54,7 @@ class LocalChatWriter
|
|||
void InsertChar(wchar_t ch);
|
||||
void InsertText(const char* str);
|
||||
void InsertText(const wchar_t* str);
|
||||
void InsertColorChange(vgui_Color color);
|
||||
void InsertColorChange(Color color);
|
||||
void InsertSwatchColorChange(SwatchColor color);
|
||||
|
||||
private:
|
||||
|
@ -70,5 +63,3 @@ class LocalChatWriter
|
|||
const char* ApplyAnsiEscape(const char* escape);
|
||||
void InsertDefaultFade();
|
||||
};
|
||||
|
||||
void InitialiseLocalChatWriter(HMODULE baseAddress);
|
||||
|
|
|
@ -1,258 +1,20 @@
|
|||
#include "pch.h"
|
||||
#include "logging.h"
|
||||
#include "sourceconsole.h"
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
#include "hookutils.h"
|
||||
#include "dedicated.h"
|
||||
#include "convar.h"
|
||||
#include "concommand.h"
|
||||
#include "nsprefix.h"
|
||||
#include "bitbuf.h"
|
||||
#include "tier0.h"
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include "nsprefix.h"
|
||||
#include <dbghelp.h>
|
||||
|
||||
// This needs to be called after hooks are loaded so we can access the command line args
|
||||
void CreateLogFiles()
|
||||
{
|
||||
if (strstr(GetCommandLineA(), "-disablelogs"))
|
||||
{
|
||||
spdlog::default_logger()->set_level(spdlog::level::off);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// todo: might be good to delete logs that are too old
|
||||
time_t time = std::time(nullptr);
|
||||
tm currentTime = *std::localtime(&time);
|
||||
std::stringstream stream;
|
||||
|
||||
stream << std::put_time(¤tTime, (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(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str());
|
||||
|
||||
auto hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (hMinidumpFile)
|
||||
{
|
||||
MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo;
|
||||
dumpExceptionInfo.ThreadId = GetCurrentThreadId();
|
||||
dumpExceptionInfo.ExceptionPointers = exceptionInfo;
|
||||
dumpExceptionInfo.ClientPointers = false;
|
||||
|
||||
MiniDumpWriteDump(
|
||||
GetCurrentProcess(),
|
||||
GetCurrentProcessId(),
|
||||
hMinidumpFile,
|
||||
MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory),
|
||||
&dumpExceptionInfo,
|
||||
nullptr,
|
||||
nullptr);
|
||||
CloseHandle(hMinidumpFile);
|
||||
}
|
||||
else
|
||||
spdlog::error("Failed to write minidump file {}!", stream.str());
|
||||
|
||||
if (!IsDedicatedServer())
|
||||
MessageBoxA(
|
||||
0, "Northstar has crashed! Crash info can be found in R2Northstar/logs", "Northstar has crashed!", MB_ICONERROR | MB_OK);
|
||||
}
|
||||
|
||||
logged = true;
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
HANDLE hExceptionFilter;
|
||||
|
||||
BOOL WINAPI ConsoleHandlerRoutine(DWORD eventCode)
|
||||
{
|
||||
switch (eventCode)
|
||||
{
|
||||
case CTRL_CLOSE_EVENT:
|
||||
// User closed console, shut everything down
|
||||
spdlog::info("Exiting due to console close...");
|
||||
RemoveVectoredExceptionHandler(hExceptionFilter);
|
||||
exit(EXIT_SUCCESS);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void InitialiseLogging()
|
||||
{
|
||||
hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter);
|
||||
|
||||
AllocConsole();
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v");
|
||||
|
||||
SetConsoleCtrlHandler(ConsoleHandlerRoutine, true);
|
||||
}
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
ConVar* Cvar_spewlog_enable;
|
||||
|
||||
enum SpewType_t
|
||||
enum class SpewType_t
|
||||
{
|
||||
SPEW_MESSAGE = 0,
|
||||
|
||||
|
@ -264,56 +26,24 @@ enum SpewType_t
|
|||
SPEW_TYPE_COUNT
|
||||
};
|
||||
|
||||
typedef void (*EngineSpewFuncType)();
|
||||
EngineSpewFuncType EngineSpewFunc;
|
||||
const std::unordered_map<SpewType_t, const char*> PrintSpewTypes = {
|
||||
{SpewType_t::SPEW_MESSAGE, "SPEW_MESSAGE"},
|
||||
{SpewType_t::SPEW_WARNING, "SPEW_WARNING"},
|
||||
{SpewType_t::SPEW_ASSERT, "SPEW_ASSERT"},
|
||||
{SpewType_t::SPEW_ERROR, "SPEW_ERROR"},
|
||||
{SpewType_t::SPEW_LOG, "SPEW_LOG"}};
|
||||
|
||||
void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format, va_list args)
|
||||
// clang-format off
|
||||
AUTOHOOK(EngineSpewFunc, engine.dll + 0x11CA80,
|
||||
void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_list args))
|
||||
// clang-format on
|
||||
{
|
||||
if (!Cvar_spewlog_enable->GetBool())
|
||||
return;
|
||||
|
||||
const char* typeStr;
|
||||
switch (type)
|
||||
{
|
||||
case SPEW_MESSAGE:
|
||||
{
|
||||
typeStr = "SPEW_MESSAGE";
|
||||
break;
|
||||
}
|
||||
|
||||
case SPEW_WARNING:
|
||||
{
|
||||
typeStr = "SPEW_WARNING";
|
||||
break;
|
||||
}
|
||||
|
||||
case SPEW_ASSERT:
|
||||
{
|
||||
typeStr = "SPEW_ASSERT";
|
||||
break;
|
||||
}
|
||||
|
||||
case SPEW_ERROR:
|
||||
{
|
||||
typeStr = "SPEW_ERROR";
|
||||
break;
|
||||
}
|
||||
|
||||
case SPEW_LOG:
|
||||
{
|
||||
typeStr = "SPEW_LOG";
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
typeStr = "SPEW_UNKNOWN";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const char* typeStr = PrintSpewTypes.at(type);
|
||||
char formatted[2048] = {0};
|
||||
bool shouldFormat = true;
|
||||
bool bShouldFormat = true;
|
||||
|
||||
// because titanfall 2 is quite possibly the worst thing to yet exist, it sometimes gives invalid specifiers which will crash
|
||||
// ttf2sdk had a way to prevent them from crashing but it doesnt work in debug builds
|
||||
|
@ -360,19 +90,17 @@ void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format,
|
|||
|
||||
default:
|
||||
{
|
||||
shouldFormat = false;
|
||||
bShouldFormat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldFormat)
|
||||
if (bShouldFormat)
|
||||
vsnprintf(formatted, sizeof(formatted), format, args);
|
||||
else
|
||||
{
|
||||
spdlog::warn("Failed to format {} \"{}\"", typeStr, format);
|
||||
}
|
||||
|
||||
auto endpos = strlen(formatted);
|
||||
if (formatted[endpos - 1] == '\n')
|
||||
|
@ -381,10 +109,11 @@ void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format,
|
|||
spdlog::info("[SERVER {}] {}", typeStr, formatted);
|
||||
}
|
||||
|
||||
typedef void (*Status_ConMsg_Type)(const char* text, ...);
|
||||
Status_ConMsg_Type Status_ConMsg_Original;
|
||||
|
||||
void Status_ConMsg_Hook(const char* text, ...)
|
||||
// used for printing the output of status
|
||||
// clang-format off
|
||||
AUTOHOOK(Status_ConMsg, engine.dll + 0x15ABD0,
|
||||
void,, (const char* text, ...))
|
||||
// clang-format on
|
||||
{
|
||||
char formatted[2048];
|
||||
va_list list;
|
||||
|
@ -400,10 +129,10 @@ void Status_ConMsg_Hook(const char* text, ...)
|
|||
spdlog::info(formatted);
|
||||
}
|
||||
|
||||
typedef bool (*CClientState_ProcessPrint_Type)(__int64 thisptr, __int64 msg);
|
||||
CClientState_ProcessPrint_Type CClientState_ProcessPrint_Original;
|
||||
|
||||
bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg)
|
||||
// clang-format off
|
||||
AUTOHOOK(CClientState_ProcessPrint, engine.dll + 0x1A1530,
|
||||
bool,, (void* thisptr, uintptr_t msg))
|
||||
// clang-format on
|
||||
{
|
||||
char* text = *(char**)(msg + 0x20);
|
||||
|
||||
|
@ -415,32 +144,8 @@ bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg)
|
|||
return true;
|
||||
}
|
||||
|
||||
void InitialiseEngineSpewFuncHooks(HMODULE baseAddress)
|
||||
{
|
||||
HookEnabler hook;
|
||||
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x11CA80, EngineSpewFuncHook, reinterpret_cast<LPVOID*>(&EngineSpewFunc));
|
||||
|
||||
// Hook print function that status concmd uses to actually print data
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15ABD0, Status_ConMsg_Hook, reinterpret_cast<LPVOID*>(&Status_ConMsg_Original));
|
||||
|
||||
// Hook CClientState::ProcessPrint
|
||||
ENABLER_CREATEHOOK(
|
||||
hook,
|
||||
(char*)baseAddress + 0x1A1530,
|
||||
CClientState_ProcessPrint_Hook,
|
||||
reinterpret_cast<LPVOID*>(&CClientState_ProcessPrint_Original));
|
||||
|
||||
Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged");
|
||||
}
|
||||
|
||||
#include "bitbuf.h"
|
||||
|
||||
ConVar* Cvar_cl_showtextmsg;
|
||||
|
||||
typedef void (*TextMsg_Type)(__int64);
|
||||
TextMsg_Type TextMsg_Original;
|
||||
|
||||
class ICenterPrint
|
||||
{
|
||||
public:
|
||||
|
@ -453,11 +158,22 @@ class ICenterPrint
|
|||
virtual void SetTextColor(int r, int g, int b, int a) = 0;
|
||||
};
|
||||
|
||||
ICenterPrint* internalCenterPrint = NULL;
|
||||
ICenterPrint* pInternalCenterPrint = NULL;
|
||||
|
||||
void TextMsgHook(BFRead* msg)
|
||||
enum class TextMsgPrintType_t
|
||||
{
|
||||
int msg_dest = msg->ReadByte();
|
||||
HUD_PRINTNOTIFY = 1,
|
||||
HUD_PRINTCONSOLE,
|
||||
HUD_PRINTTALK,
|
||||
HUD_PRINTCENTER
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(TextMsg, client.dll + 0x198710,
|
||||
void,, (BFRead* msg))
|
||||
// clang-format on
|
||||
{
|
||||
TextMsgPrintType_t msg_dest = (TextMsgPrintType_t)msg->ReadByte();
|
||||
|
||||
char text[256];
|
||||
msg->ReadString(text, sizeof(text));
|
||||
|
@ -467,29 +183,86 @@ void TextMsgHook(BFRead* msg)
|
|||
|
||||
switch (msg_dest)
|
||||
{
|
||||
case 4: // HUD_PRINTCENTER
|
||||
internalCenterPrint->Print(text);
|
||||
case TextMsgPrintType_t::HUD_PRINTCENTER:
|
||||
pInternalCenterPrint->Print(text);
|
||||
break;
|
||||
|
||||
default:
|
||||
spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest);
|
||||
[[fallthrough]];
|
||||
case 2: // HUD_PRINTCONSOLE
|
||||
|
||||
case TextMsgPrintType_t::HUD_PRINTCONSOLE:
|
||||
auto endpos = strlen(text);
|
||||
if (text[endpos - 1] == '\n')
|
||||
text[endpos - 1] = '\0'; // cut off repeated newline
|
||||
|
||||
spdlog::info(text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void InitialiseClientPrintHooks(HMODULE baseAddress)
|
||||
// clang-format off
|
||||
AUTOHOOK(ConCommand_echo, engine.dll + 0x123680,
|
||||
void,, (const CCommand& arg))
|
||||
// clang-format on
|
||||
{
|
||||
HookEnabler hook;
|
||||
if (arg.ArgC() >= 2)
|
||||
spdlog::info("[echo] {}", arg.ArgS());
|
||||
}
|
||||
|
||||
internalCenterPrint = (ICenterPrint*)((char*)baseAddress + 0x216E940);
|
||||
// This needs to be called after hooks are loaded so we can access the command line args
|
||||
void CreateLogFiles()
|
||||
{
|
||||
if (strstr(GetCommandLineA(), "-disablelogs"))
|
||||
{
|
||||
spdlog::default_logger()->set_level(spdlog::level::off);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// todo: might be good to delete logs that are too old
|
||||
time_t time = std::time(nullptr);
|
||||
tm currentTime = *std::localtime(&time);
|
||||
std::stringstream stream;
|
||||
|
||||
// "TextMsg" usermessage
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x198710, TextMsgHook, reinterpret_cast<LPVOID*>(&TextMsg_Original));
|
||||
stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str());
|
||||
spdlog::default_logger()->sinks().push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(stream.str(), false));
|
||||
spdlog::flush_on(spdlog::level::info);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
spdlog::error("Failed creating log file!");
|
||||
MessageBoxA(
|
||||
0, "Failed creating log file! Make sure the profile directory is writable.", "Northstar Warning", MB_ICONWARNING | MB_OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitialiseLogging()
|
||||
{
|
||||
AllocConsole();
|
||||
|
||||
// Bind stdout to receive console output.
|
||||
// these two lines are responsible for stuff to not show up in the console sometimes, from talking about it on discord
|
||||
// apparently they were meant to make logging work when using -northstar, however from testing it seems that it doesnt
|
||||
// work regardless of these two lines
|
||||
// freopen("CONOUT$", "w", stdout);
|
||||
// freopen("CONOUT$", "w", stderr);
|
||||
spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v");
|
||||
}
|
||||
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", EngineSpewFuncHooks, ConVar, (CModule module))
|
||||
{
|
||||
AUTOHOOK_DISPATCH_MODULE(engine.dll)
|
||||
|
||||
Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged");
|
||||
}
|
||||
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientPrintHooks, ConVar, (CModule module))
|
||||
{
|
||||
AUTOHOOK_DISPATCH_MODULE(client.dll)
|
||||
|
||||
Cvar_cl_showtextmsg = new ConVar("cl_showtextmsg", "1", FCVAR_NONE, "Enable/disable text messages printing on the screen.");
|
||||
pInternalCenterPrint = module.Offset(0x216E940).As<ICenterPrint*>();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
#pragma once
|
||||
#include "context.h"
|
||||
|
||||
void CreateLogFiles();
|
||||
void InitialiseLogging();
|
||||
void InitialiseEngineSpewFuncHooks(HMODULE baseAddress);
|
||||
void InitialiseClientPrintHooks(HMODULE baseAddress);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "convar.h"
|
||||
#include "serverpresence.h"
|
||||
#include <winsock2.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <future>
|
||||
|
||||
extern ConVar* Cvar_ns_masterserver_hostname;
|
||||
extern ConVar* Cvar_ns_curl_log_enable;
|
||||
|
||||
struct RemoteModInfo
|
||||
{
|
||||
public:
|
||||
|
@ -82,8 +89,6 @@ class MasterServerManager
|
|||
char m_sOwnClientAuthToken[33];
|
||||
|
||||
std::string m_sOwnModInfoJson;
|
||||
std::string m_sUnicodeServerName; // Unicode unescaped version of Cvar_ns_auth_servername for support in cjk characters
|
||||
std::string m_sUnicodeServerDesc; // Unicode unescaped version of Cvar_ns_auth_serverdesc for support in cjk characters
|
||||
|
||||
bool m_bOriginAuthWithMasterServerDone = false;
|
||||
bool m_bOriginAuthWithMasterServerInProgress = false;
|
||||
|
@ -97,8 +102,7 @@ class MasterServerManager
|
|||
bool m_bNewgameAfterSelfAuth = false;
|
||||
bool m_bScriptAuthenticatingWithGameServer = false;
|
||||
bool m_bSuccessfullyAuthenticatedWithGameServer = false;
|
||||
|
||||
std::string s_authfail_reason {};
|
||||
std::string m_sAuthFailureReason {};
|
||||
|
||||
bool m_bHasPendingConnectionInfo = false;
|
||||
RemoteServerConnectionInfo m_pendingConnectionInfo;
|
||||
|
@ -108,28 +112,76 @@ class MasterServerManager
|
|||
bool m_bHasMainMenuPromoData = false;
|
||||
MainMenuPromoData m_sMainMenuPromoData;
|
||||
|
||||
private:
|
||||
void SetCommonHttpClientOptions(CURL* curl);
|
||||
|
||||
public:
|
||||
MasterServerManager();
|
||||
|
||||
void ClearServerList();
|
||||
void RequestServerList();
|
||||
void RequestMainMenuPromos();
|
||||
void AuthenticateOriginWithMasterServer(char* uid, char* originToken);
|
||||
void AuthenticateWithOwnServer(char* uid, char* playerToken);
|
||||
void AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password);
|
||||
void
|
||||
AddSelfToServerList(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password);
|
||||
void UpdateServerMapAndPlaylist(char* map, char* playlist, int playerCount);
|
||||
void UpdateServerPlayerCount(int playerCount);
|
||||
void WritePlayerPersistentData(char* playerId, char* pdata, size_t pdataSize);
|
||||
void RemoveSelfFromServerList();
|
||||
void AuthenticateOriginWithMasterServer(const char* uid, const char* originToken);
|
||||
void AuthenticateWithOwnServer(const char* uid, const char* playerToken);
|
||||
void AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password);
|
||||
void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize);
|
||||
};
|
||||
std::string unescape_unicode(const std::string& str);
|
||||
void UpdateServerInfoFromUnicodeToUTF8();
|
||||
void InitialiseSharedMasterServer(HMODULE baseAddress);
|
||||
|
||||
extern MasterServerManager* g_MasterServerManager;
|
||||
extern MasterServerManager* g_pMasterServerManager;
|
||||
extern ConVar* Cvar_ns_masterserver_hostname;
|
||||
extern ConVar* Cvar_ns_server_password;
|
||||
|
||||
/** Result returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */
|
||||
enum class MasterServerReportPresenceResult
|
||||
{
|
||||
// Adding this server to the MS was successful.
|
||||
Success,
|
||||
// We failed to add this server to the MS and should retry.
|
||||
Failed,
|
||||
// We failed to add this server to the MS and shouldn't retry.
|
||||
FailedNoRetry,
|
||||
// We failed to even reach the MS.
|
||||
FailedNoConnect,
|
||||
// We failed to add the server because an existing server with the same ip:port exists.
|
||||
FailedDuplicateServer,
|
||||
};
|
||||
|
||||
class MasterServerPresenceReporter : public ServerPresenceReporter
|
||||
{
|
||||
public:
|
||||
/** Full data returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */
|
||||
struct ReportPresenceResultData
|
||||
{
|
||||
MasterServerReportPresenceResult result;
|
||||
|
||||
std::optional<std::string> id;
|
||||
std::optional<std::string> serverAuthToken;
|
||||
};
|
||||
|
||||
const int MAX_REGISTRATION_ATTEMPTS = 5;
|
||||
|
||||
// Called to initialise the master server presence reporter's state.
|
||||
void CreatePresence(const ServerPresence* pServerPresence) override;
|
||||
|
||||
// Run on an internal to either add the server to the MS or update it.
|
||||
void ReportPresence(const ServerPresence* pServerPresence) override;
|
||||
|
||||
// Called when we need to remove the server from the master server.
|
||||
void DestroyPresence(const ServerPresence* pServerPresence) override;
|
||||
|
||||
// Called every frame.
|
||||
void RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) override;
|
||||
|
||||
protected:
|
||||
// Contains the async logic to add the server to the MS.
|
||||
void InternalAddServer(const ServerPresence* pServerPresence);
|
||||
|
||||
// Contains the async logic to update the server on the MS.
|
||||
void InternalUpdateServer(const ServerPresence* pServerPresence);
|
||||
|
||||
// The future used for InternalAddServer() calls.
|
||||
std::future<ReportPresenceResultData> addServerFuture;
|
||||
|
||||
// The future used for InternalAddServer() calls.
|
||||
std::future<ReportPresenceResultData> updateServerFuture;
|
||||
|
||||
int m_nNumRegistrationAttempts;
|
||||
|
||||
double m_fNextAddServerAttemptTime;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "pch.h"
|
||||
#include "tier0.h"
|
||||
#include "maxplayers.h"
|
||||
#include "gameutils.h"
|
||||
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
// never set this to anything below 32
|
||||
#define NEW_MAX_PLAYERS 64
|
||||
|
@ -45,50 +47,33 @@ constexpr int Team_PlayerArray_AddedLength = NEW_MAX_PLAYERS - 32;
|
|||
constexpr int Team_PlayerArray_AddedSize = PAD_NUMBER(Team_PlayerArray_AddedLength * 8, 4);
|
||||
constexpr int Team_AddedSize = Team_PlayerArray_AddedSize;
|
||||
|
||||
#include "nsmem.h"
|
||||
template <class T> void ChangeOffset(void* addr, unsigned int offset)
|
||||
bool MaxPlayersIncreaseEnabled()
|
||||
{
|
||||
NSMem::BytePatch((uintptr_t)addr, (BYTE*)&offset, sizeof(T));
|
||||
static bool bMaxPlayersIncreaseEnabled = Tier0::CommandLine()->CheckParm("-experimentalmaxplayersincrease");
|
||||
return bMaxPlayersIncreaseEnabled;
|
||||
}
|
||||
|
||||
/*
|
||||
typedef bool(*MatchRecvPropsToSendProps_R_Type)(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable);
|
||||
MatchRecvPropsToSendProps_R_Type MatchRecvPropsToSendProps_R_Original;
|
||||
|
||||
bool MatchRecvPropsToSendProps_R_Hook(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable)
|
||||
// should we use R2 for this? not sure
|
||||
namespace R2 // use R2 namespace for game funcs
|
||||
{
|
||||
const char* tableName = *(const char**)(sendTable + 0x118);
|
||||
int GetMaxPlayers()
|
||||
{
|
||||
if (MaxPlayersIncreaseEnabled())
|
||||
return NEW_MAX_PLAYERS;
|
||||
|
||||
spdlog::info("MatchRecvPropsToSendProps_R table name {}", tableName);
|
||||
|
||||
bool orig = MatchRecvPropsToSendProps_R_Original(lookup, tableNameBroken, sendTable, recvTable);
|
||||
return orig;
|
||||
}
|
||||
|
||||
typedef bool(*DataTable_SetupReceiveTableFromSendTable_Type)(__int64 sendTable, bool needsDecoder);
|
||||
DataTable_SetupReceiveTableFromSendTable_Type DataTable_SetupReceiveTableFromSendTable_Original;
|
||||
|
||||
bool DataTable_SetupReceiveTableFromSendTable_Hook(__int64 sendTable, bool needsDecoder)
|
||||
{
|
||||
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);
|
||||
return 32;
|
||||
}
|
||||
} // 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)(
|
||||
__int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags);
|
||||
StringTables_CreateStringTable_Type StringTables_CreateStringTable_Original;
|
||||
|
||||
void* StringTables_CreateStringTable_Hook(
|
||||
__int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags)
|
||||
// clang-format off
|
||||
AUTOHOOK(StringTables_CreateStringTable, engine.dll + 0x22E220,
|
||||
void*,, (void* thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags))
|
||||
// clang-format on
|
||||
{
|
||||
// Change the amount of entries to account for a bigger player amount
|
||||
if (!strcmp(name, "userinfo"))
|
||||
|
@ -100,36 +85,33 @@ void* StringTables_CreateStringTable_Hook(
|
|||
maxentries = maxPlayersPowerOf2;
|
||||
}
|
||||
|
||||
return StringTables_CreateStringTable_Original(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags);
|
||||
return StringTables_CreateStringTable(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags);
|
||||
}
|
||||
|
||||
bool MaxPlayersIncreaseEnabled()
|
||||
{
|
||||
return CommandLine() && CommandLine()->CheckParm("-experimentalmaxplayersincrease");
|
||||
}
|
||||
|
||||
void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress)
|
||||
ON_DLL_LOAD("engine.dll", MaxPlayersOverride_Engine, (CModule module))
|
||||
{
|
||||
if (!MaxPlayersIncreaseEnabled())
|
||||
return;
|
||||
|
||||
AUTOHOOK_DISPATCH_MODULE(engine.dll)
|
||||
|
||||
// patch GetPlayerLimits to ignore the boundary limit
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x116458, 0xEB); // jle => jmp
|
||||
module.Offset(0x116458).Patch("0xEB"); // jle => jmp
|
||||
|
||||
// patch ED_Alloc to change nFirstIndex
|
||||
ChangeOffset<int>((char*)baseAddress + 0x18F46C + 1, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
|
||||
ChangeOffset<int>(module.Offset(0x18F46C + 1), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
|
||||
|
||||
// patch CGameServer::SpawnServer to change GetMaxClients inline
|
||||
ChangeOffset<int>((char*)baseAddress + 0x119543 + 2, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
|
||||
ChangeOffset<int>(module.Offset(0x119543 + 2), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
|
||||
|
||||
// patch CGameServer::SpawnServer to change for loop
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x11957F + 2, NEW_MAX_PLAYERS); // original: 32
|
||||
ChangeOffset<unsigned char>(module.Offset(0x11957F + 2), NEW_MAX_PLAYERS); // original: 32
|
||||
|
||||
// patch CGameServer::SpawnServer to change for loop (there are two)
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x119586 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
|
||||
ChangeOffset<unsigned char>(module.Offset(0x119586 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
|
||||
|
||||
// patch max players somewhere in CClientState
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x1A162C + 2, NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1)
|
||||
ChangeOffset<unsigned char>(module.Offset(0x1A162C + 2), NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1)
|
||||
|
||||
// patch max players in userinfo stringtable creation
|
||||
/*{
|
||||
|
@ -142,22 +124,10 @@ void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress)
|
|||
// proper fix below
|
||||
|
||||
// patch max players in userinfo stringtable creation loop
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x114C48 + 2, NEW_MAX_PLAYERS); // original: 32
|
||||
ChangeOffset<unsigned char>(module.Offset(0x114C48 + 2), NEW_MAX_PLAYERS); // original: 32
|
||||
|
||||
// do not load prebaked SendTable message list
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x75859, 0xEB); // jnz -> jmp
|
||||
|
||||
HookEnabler hook;
|
||||
|
||||
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x209000, &MatchRecvPropsToSendProps_R_Hook,
|
||||
// reinterpret_cast<LPVOID*>(&MatchRecvPropsToSendProps_R_Original)); ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1FACD0,
|
||||
// &DataTable_SetupReceiveTableFromSendTable_Hook, reinterpret_cast<LPVOID*>(&DataTable_SetupReceiveTableFromSendTable_Original));
|
||||
|
||||
ENABLER_CREATEHOOK(
|
||||
hook,
|
||||
(char*)baseAddress + 0x22E220,
|
||||
&StringTables_CreateStringTable_Hook,
|
||||
reinterpret_cast<LPVOID*>(&StringTables_CreateStringTable_Original));
|
||||
module.Offset(0x75859).Patch("EB"); // jnz -> jmp
|
||||
}
|
||||
|
||||
typedef void (*RunUserCmds_Type)(bool a1, float a2);
|
||||
|
@ -167,7 +137,10 @@ HMODULE serverBase = 0;
|
|||
auto RandomIntZeroMax = (__int64(__fastcall*)())0;
|
||||
|
||||
// lazy rebuild
|
||||
void RunUserCmds_Hook(bool a1, float a2)
|
||||
// clang-format off
|
||||
AUTOHOOK(RunUserCmds, server.dll + 0x483D10,
|
||||
void,, (bool a1, float a2))
|
||||
// clang-format on
|
||||
{
|
||||
unsigned char v3; // bl
|
||||
int v5; // er14
|
||||
|
@ -308,162 +281,160 @@ void RunUserCmds_Hook(bool a1, float a2)
|
|||
}
|
||||
}
|
||||
|
||||
typedef __int64 (*SendPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1);
|
||||
SendPropArray2_Type SendPropArray2_Original;
|
||||
|
||||
__int64 __fastcall SendPropArray2_Hook(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1)
|
||||
// clang-format off
|
||||
AUTOHOOK(SendPropArray2, server.dll + 0x12B130,
|
||||
__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1))
|
||||
// clang-format on
|
||||
{
|
||||
// Change the amount of elements to account for a bigger player amount
|
||||
if (!strcmp(name, "\"player_array\""))
|
||||
elements = NEW_MAX_PLAYERS;
|
||||
|
||||
return SendPropArray2_Original(recvProp, elements, flags, name, proxyFn, unk1);
|
||||
return SendPropArray2(recvProp, elements, flags, name, proxyFn, unk1);
|
||||
}
|
||||
|
||||
void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress)
|
||||
ON_DLL_LOAD("server.dll", MaxPlayersOverride_Server, (CModule module))
|
||||
{
|
||||
if (!MaxPlayersIncreaseEnabled())
|
||||
return;
|
||||
|
||||
AUTOHOOK_DISPATCH_MODULE(server.dll)
|
||||
|
||||
// get required data
|
||||
serverBase = GetModuleHandleA("server.dll");
|
||||
serverBase = (HMODULE)module.m_nAddress;
|
||||
RandomIntZeroMax = (decltype(RandomIntZeroMax))(GetProcAddress(GetModuleHandleA("vstdlib.dll"), "RandomIntZeroMax"));
|
||||
|
||||
// patch max players amount
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x9A44D + 3, NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128)
|
||||
ChangeOffset<unsigned char>(module.Offset(0x9A44D + 3), NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128)
|
||||
|
||||
// patch SpawnGlobalNonRewinding to change forced edict index
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x2BC403 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
|
||||
ChangeOffset<unsigned char>(module.Offset(0x2BC403 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
|
||||
|
||||
constexpr int CPlayerResource_OriginalSize = 4776;
|
||||
constexpr int CPlayerResource_AddedSize = PlayerResource_TotalSize;
|
||||
constexpr int CPlayerResource_ModifiedSize = CPlayerResource_OriginalSize + CPlayerResource_AddedSize;
|
||||
|
||||
// CPlayerResource class allocation function - allocate a bigger amount to fit all new max player data
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C560A + 1, CPlayerResource_ModifiedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C560A + 1), CPlayerResource_ModifiedSize);
|
||||
|
||||
// DT_PlayerResource::m_iPing SendProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5059 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50A8 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50E2 + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5059 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C50A8 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C50E2 + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_iPing DataMap
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94598, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9459C, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB945C0, PlayerResource_Ping_Size);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xB94598), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB9459C), NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB945C0), PlayerResource_Ping_Size);
|
||||
|
||||
// DT_PlayerResource::m_iTeam SendProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5110 + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C519C + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C517E + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5110 + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C519C + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C517E + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_iTeam DataMap
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94600, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94604, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94628, PlayerResource_Team_Size);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xB94600), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB94604), NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB94628), PlayerResource_Team_Size);
|
||||
|
||||
// DT_PlayerResource::m_iPRHealth SendProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C51C0 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5204 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C523E + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C51C0 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5204 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C523E + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_iPRHealth DataMap
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94668, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9466C, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94690, PlayerResource_PRHealth_Size);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xB94668), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB9466C), NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB94690), PlayerResource_PRHealth_Size);
|
||||
|
||||
// DT_PlayerResource::m_bConnected SendProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C526C + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52B4 + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52EE + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C526C + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C52B4 + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C52EE + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_bConnected DataMap
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB946D0, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946D4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946F8, PlayerResource_Connected_Size);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xB946D0), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB946D4), NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB946F8), PlayerResource_Connected_Size);
|
||||
|
||||
// DT_PlayerResource::m_bAlive SendProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5321 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5364 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C539E + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5321 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5364 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C539E + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_bAlive DataMap
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94738, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9473C, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94760, PlayerResource_Alive_Size);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xB94738), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB9473C), NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB94760), PlayerResource_Alive_Size);
|
||||
|
||||
// DT_PlayerResource::m_boolStats SendProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C53CC + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5414 + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C544E + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C53CC + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5414 + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C544E + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_boolStats DataMap
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB947A0, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947A4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947C8, PlayerResource_BoolStats_Size);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xB947A0), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB947A4), NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB947C8), PlayerResource_BoolStats_Size);
|
||||
|
||||
// DT_PlayerResource::m_killStats SendProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C547C + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54E2 + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54FE + 4, PlayerResource_KillStats_Length);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C547C + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C54E2 + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C54FE + 4), PlayerResource_KillStats_Length);
|
||||
|
||||
// DT_PlayerResource::m_killStats DataMap
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94808, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9480C, PlayerResource_KillStats_Length);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94830, PlayerResource_KillStats_Size);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xB94808), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB9480C), PlayerResource_KillStats_Length);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB94830), PlayerResource_KillStats_Size);
|
||||
|
||||
// DT_PlayerResource::m_scoreStats SendProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5528 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5576 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5584 + 4, PlayerResource_ScoreStats_Length);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5528 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5576 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C5584 + 4), PlayerResource_ScoreStats_Length);
|
||||
|
||||
// DT_PlayerResource::m_scoreStats DataMap
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94870, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94874, PlayerResource_ScoreStats_Length);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94898, PlayerResource_ScoreStats_Size);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xB94870), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB94874), PlayerResource_ScoreStats_Length);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xB94898), PlayerResource_ScoreStats_Size);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_bConnected
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C66EE + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C672E + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C66EE + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C672E + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_iPing
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6394 + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63DB + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C6394 + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C63DB + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_iTeam
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63FD + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6442 + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C63FD + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C6442 + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_iPRHealth
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C645B + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64A0 + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C645B + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C64A0 + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_bConnected
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64AA + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64F0 + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C64AA + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C64F0 + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_bAlive
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C650A + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C654F + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C650A + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C654F + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_boolStats
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6557 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65A5 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C6557 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C65A5 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_scoreStats
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65C2 + 3, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65E3 + 4, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C65C2 + 3), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C65E3 + 4), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
|
||||
// CPlayerResource::UpdatePlayerData - m_killStats
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6654 + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C665B + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C6654 + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x5C665B + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
|
||||
// GameLoop::RunUserCmds - rebuild
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x483D10, &RunUserCmds_Hook, reinterpret_cast<LPVOID*>(&RunUserCmds_Original));
|
||||
|
||||
*(DWORD*)((char*)baseAddress + 0x14E7390) = 0;
|
||||
auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x5C4FE0);
|
||||
*module.Offset(0x14E7390).As<DWORD*>() = 0;
|
||||
auto DT_PlayerResource_Construct = module.Offset(0x5C4FE0).As<__int64(__fastcall*)()>();
|
||||
DT_PlayerResource_Construct();
|
||||
|
||||
constexpr int CTeam_OriginalSize = 3336;
|
||||
|
@ -471,191 +442,188 @@ void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress)
|
|||
constexpr int CTeam_ModifiedSize = CTeam_OriginalSize + CTeam_AddedSize;
|
||||
|
||||
// CTeam class allocation function - allocate a bigger amount to fit all new team player data
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x23924A + 1, CTeam_ModifiedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x23924A + 1), CTeam_ModifiedSize);
|
||||
|
||||
// CTeam::CTeam - increase memset length to clean newly allocated data
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x2395AE + 2, 256 + CTeam_AddedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x2395AE + 2), 256 + CTeam_AddedSize);
|
||||
|
||||
// hook required to change the size of DT_Team::"player_array"
|
||||
HookEnabler hook2;
|
||||
ENABLER_CREATEHOOK(hook2, (char*)baseAddress + 0x12B130, &SendPropArray2_Hook, reinterpret_cast<LPVOID*>(&SendPropArray2_Original));
|
||||
hook2.~HookEnabler(); // force hook before calling construct function
|
||||
|
||||
*(DWORD*)((char*)baseAddress + 0xC945A0) = 0;
|
||||
auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x238F50);
|
||||
*module.Offset(0xC945A0).As<DWORD*>() = 0;
|
||||
auto DT_Team_Construct = module.Offset(0x238F50).As<__int64(__fastcall*)()>();
|
||||
DT_Team_Construct();
|
||||
}
|
||||
|
||||
typedef __int64 (*RecvPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn);
|
||||
RecvPropArray2_Type RecvPropArray2_Original;
|
||||
|
||||
__int64 __fastcall RecvPropArray2_Hook(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn)
|
||||
// clang-format off
|
||||
AUTOHOOK(RecvPropArray2, client.dll + 0x1CEDA0,
|
||||
__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn))
|
||||
// clang-format on
|
||||
{
|
||||
// Change the amount of elements to account for a bigger player amount
|
||||
if (!strcmp(name, "\"player_array\""))
|
||||
elements = NEW_MAX_PLAYERS;
|
||||
|
||||
return RecvPropArray2_Original(recvProp, elements, flags, name, proxyFn);
|
||||
return RecvPropArray2(recvProp, elements, flags, name, proxyFn);
|
||||
}
|
||||
|
||||
void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress)
|
||||
ON_DLL_LOAD("client.dll", MaxPlayersOverride_Client, (CModule module))
|
||||
{
|
||||
if (!MaxPlayersIncreaseEnabled())
|
||||
return;
|
||||
|
||||
AUTOHOOK_DISPATCH_MODULE(client.dll)
|
||||
|
||||
constexpr int C_PlayerResource_OriginalSize = 5768;
|
||||
constexpr int C_PlayerResource_AddedSize = PlayerResource_TotalSize;
|
||||
constexpr int C_PlayerResource_ModifiedSize = C_PlayerResource_OriginalSize + C_PlayerResource_AddedSize;
|
||||
|
||||
// C_PlayerResource class allocation function - allocate a bigger amount to fit all new max player data
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164C41 + 1, C_PlayerResource_ModifiedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164C41 + 1), C_PlayerResource_ModifiedSize);
|
||||
|
||||
// C_PlayerResource::C_PlayerResource - change loop end value
|
||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x1640C4 + 2, NEW_MAX_PLAYERS - 32);
|
||||
ChangeOffset<unsigned char>(module.Offset(0x1640C4 + 2), NEW_MAX_PLAYERS - 32);
|
||||
|
||||
// C_PlayerResource::C_PlayerResource - change m_szName address
|
||||
ChangeOffset<unsigned int>(
|
||||
(char*)baseAddress + 0x1640D0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
|
||||
module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
|
||||
|
||||
// C_PlayerResource::C_PlayerResource - change m_szName address
|
||||
ChangeOffset<unsigned int>(
|
||||
(char*)baseAddress + 0x1640D0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
|
||||
module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
|
||||
|
||||
// C_PlayerResource::C_PlayerResource - increase memset length to clean newly allocated data
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1640D0 + 3, 2244 + C_PlayerResource_AddedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1640D0 + 3), 2244 + C_PlayerResource_AddedSize);
|
||||
|
||||
// C_PlayerResource::UpdatePlayerName - change m_szName address
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x16431F + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x16431F + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName - change m_szName address 1
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645B1 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1645B1 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName - change m_szName address 2
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645C0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1645C0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName - change m_szName address 3
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645DD + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1645DD + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName internal func - change m_szName address 1
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B71 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164B71 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName internal func - change m_szName address 2
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B9B + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164B9B + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 1
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164641 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164641 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 2
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164650 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164650 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 3
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x16466D + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x16466D + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 1
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BA3 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164BA3 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 2
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BCE + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164BCE + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 3
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BE7 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164BE7 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
|
||||
// C_PlayerResource::m_szName
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc350f8, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc350f8 + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc350f8), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc350f8 + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource size
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163415 + 6, C_PlayerResource_ModifiedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163415 + 6), C_PlayerResource_ModifiedSize);
|
||||
|
||||
// DT_PlayerResource::m_iPing RecvProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163492 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1634D6 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163515 + 5, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163492 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1634D6 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163515 + 5), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// C_PlayerResource::m_iPing
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35170, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35170 + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc35170), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc35170 + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_iTeam RecvProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163549 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635C8 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635AD + 5, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163549 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1635C8 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1635AD + 5), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// C_PlayerResource::m_iTeam
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc351e8, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc351e8 + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc351e8), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc351e8 + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_iPRHealth RecvProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635F9 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163625 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163675 + 5, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1635F9 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163625 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163675 + 5), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// C_PlayerResource::m_iPRHealth
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35260, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35260 + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc35260), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc35260 + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_bConnected RecvProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636A9 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636D5 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163725 + 5, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1636A9 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1636D5 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163725 + 5), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// C_PlayerResource::m_bConnected
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc352d8, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc352d8 + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc352d8), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc352d8 + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_bAlive RecvProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163759 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163785 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1637D5 + 5, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163759 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163785 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1637D5 + 5), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// C_PlayerResource::m_bAlive
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35350, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35350 + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc35350), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc35350 + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_boolStats RecvProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163809 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163835 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163885 + 5, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163809 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163835 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163885 + 5), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// C_PlayerResource::m_boolStats
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc353c8, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc353c8 + 4, NEW_MAX_PLAYERS + 1);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc353c8), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc353c8 + 4), NEW_MAX_PLAYERS + 1);
|
||||
|
||||
// DT_PlayerResource::m_killStats RecvProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638B3 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638E5 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163935 + 5, PlayerResource_KillStats_Length);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1638B3 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1638E5 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163935 + 5), PlayerResource_KillStats_Length);
|
||||
|
||||
// C_PlayerResource::m_killStats
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35440, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35440 + 4, PlayerResource_KillStats_Length);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc35440), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc35440 + 4), PlayerResource_KillStats_Length);
|
||||
|
||||
// DT_PlayerResource::m_scoreStats RecvProp
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163969 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163995 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1639E5 + 5, PlayerResource_ScoreStats_Length);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163969 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x163995 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1639E5 + 5), PlayerResource_ScoreStats_Length);
|
||||
|
||||
// C_PlayerResource::m_scoreStats
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc354b8, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc354b8 + 4, PlayerResource_ScoreStats_Length);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xc354b8), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||
ChangeOffset<unsigned short>(module.Offset(0xc354b8 + 4), PlayerResource_ScoreStats_Length);
|
||||
|
||||
// C_PlayerResource::GetPlayerName - change m_bConnected address
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164599 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164599 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName2 (?) - change m_bConnected address
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164629 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164629 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
|
||||
// C_PlayerResource::GetPlayerName internal func - change m_bConnected address
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B13 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164B13 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
|
||||
// Some other get name func (that seems to be unused) - change m_bConnected address
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164860 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164860 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
|
||||
// Some other get name func 2 (that seems to be unused too) - change m_bConnected address
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164834 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x164834 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||
|
||||
*(DWORD*)((char*)baseAddress + 0xC35068) = 0;
|
||||
auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x163400);
|
||||
*module.Offset(0xC35068).As<DWORD*>() = 0;
|
||||
auto DT_PlayerResource_Construct = module.Offset(0x163400).As<__int64(__fastcall*)()>();
|
||||
DT_PlayerResource_Construct();
|
||||
|
||||
constexpr int C_Team_OriginalSize = 3200;
|
||||
|
@ -663,20 +631,15 @@ void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress)
|
|||
constexpr int C_Team_ModifiedSize = C_Team_OriginalSize + C_Team_AddedSize;
|
||||
|
||||
// C_Team class allocation function - allocate a bigger amount to fit all new team player data
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x182321 + 1, C_Team_ModifiedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x182321 + 1), C_Team_ModifiedSize);
|
||||
|
||||
// C_Team::C_Team - increase memset length to clean newly allocated data
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1804A2 + 2, 256 + C_Team_AddedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0x1804A2 + 2), 256 + C_Team_AddedSize);
|
||||
|
||||
// DT_Team size
|
||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xC3AA0C, C_Team_ModifiedSize);
|
||||
ChangeOffset<unsigned int>(module.Offset(0xC3AA0C), C_Team_ModifiedSize);
|
||||
|
||||
// hook required to change the size of DT_Team::"player_array"
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1CEDA0, &RecvPropArray2_Hook, reinterpret_cast<LPVOID*>(&RecvPropArray2_Original));
|
||||
hook.~HookEnabler(); // force hook before calling construct function
|
||||
|
||||
*(DWORD*)((char*)baseAddress + 0xC3AFF8) = 0;
|
||||
auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x17F950);
|
||||
*module.Offset(0xC3AFF8).As<DWORD*>() = 0;
|
||||
auto DT_Team_Construct = module.Offset(0x17F950).As<__int64(__fastcall*)()>();
|
||||
DT_Team_Construct();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#pragma once
|
||||
void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress);
|
||||
void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress);
|
||||
void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress);
|
||||
|
||||
// should we use R2 for this? not sure
|
||||
namespace R2 // use R2 namespace for game funcs
|
||||
{
|
||||
int GetMaxPlayers();
|
||||
} // namespace R2
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "pch.h"
|
||||
#include "memalloc.h"
|
||||
#include "gameutils.h"
|
||||
#include "tier0.h"
|
||||
|
||||
using namespace Tier0;
|
||||
|
||||
// TODO: rename to malloc and free after removing statically compiled .libs
|
||||
|
||||
|
@ -8,9 +10,8 @@ extern "C" void* _malloc_base(size_t n)
|
|||
{
|
||||
// allocate into static buffer if g_pMemAllocSingleton isn't initialised
|
||||
if (!g_pMemAllocSingleton)
|
||||
{
|
||||
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
|
||||
}
|
||||
TryCreateGlobalMemAlloc();
|
||||
|
||||
return g_pMemAllocSingleton->m_vtable->Alloc(g_pMemAllocSingleton, n);
|
||||
}
|
||||
|
||||
|
@ -22,19 +23,16 @@ extern "C" void* _malloc_base(size_t n)
|
|||
extern "C" void _free_base(void* p)
|
||||
{
|
||||
if (!g_pMemAllocSingleton)
|
||||
{
|
||||
spdlog::warn("Trying to free something before g_pMemAllocSingleton was ready, this should never happen");
|
||||
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
|
||||
}
|
||||
TryCreateGlobalMemAlloc();
|
||||
|
||||
g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p);
|
||||
}
|
||||
|
||||
extern "C" void* _realloc_base(void* oldPtr, size_t size)
|
||||
{
|
||||
if (!g_pMemAllocSingleton)
|
||||
{
|
||||
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
|
||||
}
|
||||
TryCreateGlobalMemAlloc();
|
||||
|
||||
return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
#pragma once
|
||||
void InitialiseMiscClientFixes(HMODULE baseAddress);
|
|
@ -1,60 +1,162 @@
|
|||
#include "pch.h"
|
||||
#include "misccommands.h"
|
||||
#include "concommand.h"
|
||||
#include "gameutils.h"
|
||||
#include "playlist.h"
|
||||
#include "r2engine.h"
|
||||
#include "r2client.h"
|
||||
#include "tier0.h"
|
||||
#include "hoststate.h"
|
||||
#include "masterserver.h"
|
||||
#include "modmanager.h"
|
||||
#include "serverauthentication.h"
|
||||
#include "squirrel.h"
|
||||
|
||||
void ConCommand_force_newgame(const CCommand& arg)
|
||||
{
|
||||
if (arg.ArgC() < 2)
|
||||
return;
|
||||
|
||||
R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME;
|
||||
strncpy(R2::g_pHostState->m_levelName, arg.Arg(1), sizeof(R2::g_pHostState->m_levelName));
|
||||
}
|
||||
|
||||
void ConCommand_ns_start_reauth_and_leave_to_lobby(const CCommand& arg)
|
||||
{
|
||||
// hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect
|
||||
g_pMasterServerManager->m_bNewgameAfterSelfAuth = true;
|
||||
g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken);
|
||||
}
|
||||
|
||||
void ConCommand_ns_end_reauth_and_leave_to_lobby(const CCommand& arg)
|
||||
{
|
||||
if (g_pServerAuthentication->m_RemoteAuthenticationData.size())
|
||||
R2::g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str());
|
||||
|
||||
// weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this
|
||||
if (g_pSquirrel<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()
|
||||
{
|
||||
MAKE_CONCMD(
|
||||
RegisterConCommand(
|
||||
"force_newgame",
|
||||
ConCommand_force_newgame,
|
||||
"forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME",
|
||||
FCVAR_NONE,
|
||||
[](const CCommand& arg)
|
||||
{
|
||||
if (arg.ArgC() < 2)
|
||||
return;
|
||||
FCVAR_NONE);
|
||||
|
||||
g_pHostState->m_iNextState = HS_NEW_GAME;
|
||||
strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName));
|
||||
});
|
||||
|
||||
MAKE_CONCMD(
|
||||
RegisterConCommand(
|
||||
"ns_start_reauth_and_leave_to_lobby",
|
||||
ConCommand_ns_start_reauth_and_leave_to_lobby,
|
||||
"called by the server, used to reauth and return the player to lobby when leaving a game",
|
||||
FCVAR_SERVER_CAN_EXECUTE,
|
||||
[](const CCommand& arg)
|
||||
{
|
||||
// hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect
|
||||
g_MasterServerManager->m_bNewgameAfterSelfAuth = true;
|
||||
g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, g_MasterServerManager->m_sOwnClientAuthToken);
|
||||
});
|
||||
FCVAR_SERVER_CAN_EXECUTE);
|
||||
|
||||
// this is a concommand because we make a deferred call to it from another thread
|
||||
MAKE_CONCMD(
|
||||
"ns_end_reauth_and_leave_to_lobby",
|
||||
"",
|
||||
FCVAR_NONE,
|
||||
[](const CCommand& arg)
|
||||
{
|
||||
Cbuf_AddText(
|
||||
Cbuf_GetCurrentPlayer(),
|
||||
fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(),
|
||||
cmd_source_t::kCommandSrcCode);
|
||||
Cbuf_Execute();
|
||||
|
||||
// weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this
|
||||
if (g_ClientSquirrelManager->sqvm)
|
||||
{
|
||||
g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true;
|
||||
|
||||
// this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta
|
||||
// fucks things should maybe set this in HostState_NewGame?
|
||||
SetCurrentPlaylist("tdm");
|
||||
strcpy(g_pHostState->m_levelName, "mp_lobby");
|
||||
g_pHostState->m_iNextState = HS_NEW_GAME;
|
||||
}
|
||||
});
|
||||
RegisterConCommand("ns_end_reauth_and_leave_to_lobby", ConCommand_ns_end_reauth_and_leave_to_lobby, "", FCVAR_NONE);
|
||||
}
|
||||
|
||||
// fixes up various cvar flags to have more sane values
|
||||
void FixupCvarFlags()
|
||||
{
|
||||
if (Tier0::CommandLine()->CheckParm("-allowdevcvars"))
|
||||
{
|
||||
// strip hidden and devonly cvar flags
|
||||
int iNumCvarsAltered = 0;
|
||||
for (auto& pair : R2::g_pCVar->DumpToMap())
|
||||
{
|
||||
// strip flags
|
||||
int flags = pair.second->GetFlags();
|
||||
if (flags & FCVAR_DEVELOPMENTONLY)
|
||||
{
|
||||
flags &= ~FCVAR_DEVELOPMENTONLY;
|
||||
iNumCvarsAltered++;
|
||||
}
|
||||
|
||||
if (flags & FCVAR_HIDDEN)
|
||||
{
|
||||
flags &= ~FCVAR_HIDDEN;
|
||||
iNumCvarsAltered++;
|
||||
}
|
||||
|
||||
pair.second->m_nFlags = flags;
|
||||
}
|
||||
|
||||
spdlog::info("Removed {} hidden/devonly cvar flags", iNumCvarsAltered);
|
||||
}
|
||||
|
||||
// make all engine client commands FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS
|
||||
// these are usually checked through CGameClient::IsEngineClientCommand, but we get more control over this if we just do it through
|
||||
// cvar flags
|
||||
const char** ppEngineClientCommands = CModule("engine.dll").Offset(0x7C5EF0).As<const char**>();
|
||||
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
ConCommandBase* pCommand = R2::g_pCVar->FindCommandBase(ppEngineClientCommands[i]);
|
||||
if (pCommand) // not all the commands in this array actually exist in respawn source
|
||||
pCommand->m_nFlags |= FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS;
|
||||
} while (ppEngineClientCommands[++i]);
|
||||
|
||||
// array of cvars and the flags we want to add to them
|
||||
const std::vector<std::tuple<const char*, uint32_t>> CVAR_FIXUP_ADD_FLAGS = {
|
||||
// system commands (i.e. necessary for proper functionality)
|
||||
// servers need to be able to disconnect
|
||||
{"disconnect", FCVAR_SERVER_CAN_EXECUTE},
|
||||
|
||||
// cheat commands
|
||||
{"give", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"give_server", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"givecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"takecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
|
||||
{"switchclass", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"set", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"_setClassVarServer", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
|
||||
{"ent_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"ent_throw", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"ent_setname", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"ent_teleport", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"ent_remove", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"ent_remove_all", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"ent_fire", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
|
||||
{"particle_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"particle_recreate", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"particle_kill", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
|
||||
{"test_setteam", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"melee_lunge_ent", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}};
|
||||
|
||||
// array of cvars and the flags we want to remove from them
|
||||
const std::vector<std::tuple<const char*, uint32_t>> CVAR_FIXUP_REMOVE_FLAGS = {
|
||||
// unsure how this command works, not even sure it's used on retail servers, deffo shouldn't be used on northstar
|
||||
{"migrateme", FCVAR_SERVER_CAN_EXECUTE | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"recheck", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, // we don't need this on northstar servers, it's for communities
|
||||
|
||||
// unsure how these work exactly (rpt system likely somewhat stripped?), removing anyway since they won't be used
|
||||
{"rpt_client_enable", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||
{"rpt_password", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}};
|
||||
|
||||
for (auto& fixup : CVAR_FIXUP_ADD_FLAGS)
|
||||
{
|
||||
ConCommandBase* command = R2::g_pCVar->FindCommandBase(std::get<0>(fixup));
|
||||
if (command)
|
||||
command->m_nFlags |= std::get<1>(fixup);
|
||||
}
|
||||
|
||||
for (auto& fixup : CVAR_FIXUP_REMOVE_FLAGS)
|
||||
{
|
||||
ConCommandBase* command = R2::g_pCVar->FindCommandBase(std::get<0>(fixup));
|
||||
if (command)
|
||||
command->m_nFlags &= ~std::get<1>(fixup);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
#pragma once
|
||||
void AddMiscConCommands();
|
||||
void FixupCvarFlags();
|
||||
|
|
|
@ -1,26 +1,7 @@
|
|||
#include "pch.h"
|
||||
#include "miscserverfixes.h"
|
||||
#include "hookutils.h"
|
||||
|
||||
#include "nsmem.h"
|
||||
|
||||
void InitialiseMiscServerFixes(HMODULE baseAddress)
|
||||
ON_DLL_LOAD("server.dll", MiscServerFixes, (CModule module))
|
||||
{
|
||||
uintptr_t ba = (uintptr_t)baseAddress;
|
||||
|
||||
// ret at the start of the concommand GenerateObjFile as it can crash servers
|
||||
{
|
||||
NSMem::BytePatch(ba + 0x38D920, "C3");
|
||||
}
|
||||
|
||||
// nop out call to VGUI shutdown since it crashes the game when quitting from the console
|
||||
{
|
||||
NSMem::NOP(ba + 0x154A96, 5);
|
||||
}
|
||||
|
||||
// ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue)
|
||||
// this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches
|
||||
{
|
||||
NSMem::BytePatch(ba + 0x153920, "C3");
|
||||
}
|
||||
module.Offset(0x154A96).NOP(5);
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
void InitialiseMiscServerFixes(HMODULE baseAddress);
|
|
@ -1,76 +1,73 @@
|
|||
#include "pch.h"
|
||||
#include "miscserverscript.h"
|
||||
#include "squirrel.h"
|
||||
#include "masterserver.h"
|
||||
#include "serverauthentication.h"
|
||||
#include "gameutils.h"
|
||||
#include "dedicated.h"
|
||||
#include "r2client.h"
|
||||
#include "r2server.h"
|
||||
|
||||
// annoying helper function because i can't figure out getting players or entities from sqvm rn
|
||||
// wish i didn't have to do it like this, but here we are
|
||||
void* GetPlayerByIndex(int playerIndex)
|
||||
#include <filesystem>
|
||||
|
||||
// void function NSEarlyWritePlayerPersistenceForLeave( entity player )
|
||||
SQRESULT SQ_EarlyWritePlayerPersistenceForLeave(HSquirrelVM* sqvm)
|
||||
{
|
||||
const int PLAYER_ARRAY_OFFSET = 0x12A53F90;
|
||||
const int PLAYER_SIZE = 0x2D728;
|
||||
|
||||
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))
|
||||
const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
|
||||
if (!pPlayer)
|
||||
{
|
||||
ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str());
|
||||
return SQRESULT_ERROR;
|
||||
spdlog::warn("NSEarlyWritePlayerPersistenceForLeave got null player");
|
||||
|
||||
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
g_ServerAuthenticationManager->m_additionalPlayerData[player].needPersistenceWriteOnLeave = false;
|
||||
g_ServerAuthenticationManager->WritePersistentData(player);
|
||||
R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex];
|
||||
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;
|
||||
}
|
||||
|
||||
// bool function NSIsWritingPlayerPersistence()
|
||||
SQRESULT SQ_IsWritingPlayerPersistence(void* sqvm)
|
||||
SQRESULT SQ_IsWritingPlayerPersistence(HSquirrelVM* sqvm)
|
||||
{
|
||||
ServerSq_pushbool(sqvm, g_MasterServerManager->m_bSavingPersistentData);
|
||||
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, g_pMasterServerManager->m_bSavingPersistentData);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
// bool function NSIsPlayerIndexLocalPlayer( int playerIndex )
|
||||
SQRESULT SQ_IsPlayerIndexLocalPlayer(void* sqvm)
|
||||
// bool function NSIsPlayerLocalPlayer( entity player )
|
||||
SQRESULT SQ_IsPlayerLocalPlayer(HSquirrelVM* sqvm)
|
||||
{
|
||||
int playerIndex = ServerSq_getinteger(sqvm, 1);
|
||||
void* player = GetPlayerByIndex(playerIndex);
|
||||
if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(player))
|
||||
const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
|
||||
if (!pPlayer)
|
||||
{
|
||||
ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str());
|
||||
return SQRESULT_ERROR;
|
||||
spdlog::warn("NSIsPlayerLocalPlayer got null player");
|
||||
|
||||
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
ServerSq_pushbool(sqvm, !strcmp(g_LocalPlayerUserID, (char*)player + 0xF500));
|
||||
R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex];
|
||||
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, !strcmp(R2::g_pLocalPlayerUserID, pClient->m_UID));
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
// bool function NSIsDedicated()
|
||||
|
||||
SQRESULT SQ_IsDedicated(void* sqvm)
|
||||
SQRESULT SQ_IsDedicated(HSquirrelVM* sqvm)
|
||||
{
|
||||
ServerSq_pushbool(sqvm, IsDedicatedServer());
|
||||
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, IsDedicatedServer());
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
void InitialiseMiscServerScriptCommand(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_RELIESON("server.dll", MiscServerScriptCommands, ServerSquirrel, (CModule module))
|
||||
{
|
||||
g_ServerSquirrelManager->AddFuncRegistration(
|
||||
"void", "NSEarlyWritePlayerIndexPersistenceForLeave", "int playerIndex", "", SQ_EarlyWritePlayerIndexPersistenceForLeave);
|
||||
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence);
|
||||
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsPlayerIndexLocalPlayer", "int playerIndex", "", SQ_IsPlayerIndexLocalPlayer);
|
||||
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated);
|
||||
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration(
|
||||
"void", "NSEarlyWritePlayerPersistenceForLeave", "entity player", "", SQ_EarlyWritePlayerPersistenceForLeave);
|
||||
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence);
|
||||
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsPlayerLocalPlayer", "entity player", "", SQ_IsPlayerLocalPlayer);
|
||||
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated);
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
void InitialiseMiscServerScriptCommand(HMODULE baseAddress);
|
||||
void* GetPlayerByIndex(int playerIndex);
|
|
@ -1,37 +1,35 @@
|
|||
#include "pch.h"
|
||||
#include "modlocalisation.h"
|
||||
#include "hookutils.h"
|
||||
#include "modmanager.h"
|
||||
|
||||
typedef bool (*AddLocalisationFileType)(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown);
|
||||
AddLocalisationFileType AddLocalisationFile;
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
bool loadModLocalisationFiles = true;
|
||||
|
||||
bool AddLocalisationFileHook(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown)
|
||||
// clang-format off
|
||||
AUTOHOOK(AddLocalisationFile, localize.dll + 0x6D80,
|
||||
bool, __fastcall, (void* pVguiLocalize, const char* path, const char* pathId, char unknown))
|
||||
// clang-format on
|
||||
{
|
||||
bool ret = AddLocalisationFile(g_pVguiLocalize, path, pathId, unknown);
|
||||
static bool bLoadModLocalisationFiles = true;
|
||||
bool ret = AddLocalisationFile(pVguiLocalize, path, pathId, unknown);
|
||||
|
||||
if (ret)
|
||||
spdlog::info("Loaded localisation file {} successfully", path);
|
||||
|
||||
if (!loadModLocalisationFiles)
|
||||
if (!bLoadModLocalisationFiles)
|
||||
return ret;
|
||||
|
||||
loadModLocalisationFiles = false;
|
||||
bLoadModLocalisationFiles = false;
|
||||
|
||||
for (Mod mod : g_ModManager->m_loadedMods)
|
||||
if (mod.Enabled)
|
||||
for (Mod mod : g_pModManager->m_LoadedMods)
|
||||
if (mod.m_bEnabled)
|
||||
for (std::string& localisationFile : mod.LocalisationFiles)
|
||||
AddLocalisationFile(g_pVguiLocalize, localisationFile.c_str(), pathId, unknown);
|
||||
AddLocalisationFile(pVguiLocalize, localisationFile.c_str(), pathId, unknown);
|
||||
|
||||
loadModLocalisationFiles = true;
|
||||
bLoadModLocalisationFiles = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void InitialiseModLocalisation(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT("localize.dll", Localize, (CModule module))
|
||||
{
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x6D80, AddLocalisationFileHook, reinterpret_cast<LPVOID*>(&AddLocalisationFile));
|
||||
AUTOHOOK_DISPATCH()
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void InitialiseModLocalisation(HMODULE baseAddress);
|
|
@ -4,6 +4,10 @@
|
|||
#include "concommand.h"
|
||||
#include "audio.h"
|
||||
#include "masterserver.h"
|
||||
#include "filesystem.h"
|
||||
#include "rpakfilesystem.h"
|
||||
#include "nsprefix.h"
|
||||
|
||||
#include "rapidjson/error/en.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/ostreamwrapper.h"
|
||||
|
@ -13,17 +17,14 @@
|
|||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "filesystem.h"
|
||||
#include "rpakfilesystem.h"
|
||||
#include "nsprefix.h"
|
||||
|
||||
ModManager* g_ModManager;
|
||||
ModManager* g_pModManager;
|
||||
|
||||
Mod::Mod(fs::path modDir, char* jsonBuf)
|
||||
{
|
||||
wasReadSuccessfully = false;
|
||||
m_bWasReadSuccessfully = false;
|
||||
|
||||
ModDirectory = modDir;
|
||||
m_ModDirectory = modDir;
|
||||
|
||||
rapidjson_document modJson;
|
||||
modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf);
|
||||
|
@ -106,11 +107,55 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
|
|||
else
|
||||
convar->HelpString = "";
|
||||
|
||||
// todo: could possibly parse FCVAR names here instead, would be easier
|
||||
convar->Flags = FCVAR_NONE;
|
||||
|
||||
if (convarObj.HasMember("Flags"))
|
||||
convar->Flags = convarObj["Flags"].GetInt();
|
||||
else
|
||||
convar->Flags = FCVAR_NONE;
|
||||
{
|
||||
// read raw integer flags
|
||||
if (convarObj["Flags"].IsInt())
|
||||
convar->Flags = convarObj["Flags"].GetInt();
|
||||
else if (convarObj["Flags"].IsString())
|
||||
{
|
||||
// parse cvar flags from string
|
||||
// example string: ARCHIVE_PLAYERPROFILE | GAMEDLL
|
||||
|
||||
std::string sFlags = convarObj["Flags"].GetString();
|
||||
sFlags += '|'; // add additional | so we register the last flag
|
||||
std::string sCurrentFlag;
|
||||
|
||||
for (int i = 0; i < sFlags.length(); i++)
|
||||
{
|
||||
if (isspace(sFlags[i]))
|
||||
continue;
|
||||
|
||||
// if we encounter a |, add current string as a flag
|
||||
if (sFlags[i] == '|')
|
||||
{
|
||||
bool bHasFlags = false;
|
||||
int iCurrentFlags;
|
||||
|
||||
for (auto& flagPair : g_PrintCommandFlags)
|
||||
{
|
||||
if (!sCurrentFlag.compare(flagPair.second))
|
||||
{
|
||||
iCurrentFlags = flagPair.first;
|
||||
bHasFlags = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bHasFlags)
|
||||
convar->Flags |= iCurrentFlags;
|
||||
else
|
||||
spdlog::warn("Mod ConVar {} has unknown flag {}", convar->Name, sCurrentFlag);
|
||||
|
||||
sCurrentFlag = "";
|
||||
}
|
||||
else
|
||||
sCurrentFlag += sFlags[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConVars.push_back(convar);
|
||||
}
|
||||
|
@ -127,7 +172,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
|
|||
ModScript script;
|
||||
|
||||
script.Path = scriptObj["Path"].GetString();
|
||||
script.RsonRunOn = scriptObj["RunOn"].GetString();
|
||||
script.RunOn = scriptObj["RunOn"].GetString();
|
||||
|
||||
if (scriptObj.HasMember("ServerCallback") && scriptObj["ServerCallback"].IsObject())
|
||||
{
|
||||
|
@ -206,7 +251,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
|
|||
}
|
||||
}
|
||||
|
||||
wasReadSuccessfully = true;
|
||||
m_bWasReadSuccessfully = true;
|
||||
}
|
||||
|
||||
ModManager::ModManager()
|
||||
|
@ -223,7 +268,7 @@ ModManager::ModManager()
|
|||
|
||||
void ModManager::LoadMods()
|
||||
{
|
||||
if (m_hasLoadedMods)
|
||||
if (m_bHasLoadedMods)
|
||||
UnloadMods();
|
||||
|
||||
std::vector<fs::path> modDirs;
|
||||
|
@ -232,7 +277,7 @@ void ModManager::LoadMods()
|
|||
fs::remove_all(GetCompiledAssetsPath());
|
||||
fs::create_directories(GetModFolderPath());
|
||||
|
||||
DependencyConstants.clear();
|
||||
m_DependencyConstants.clear();
|
||||
|
||||
// read enabled mods cfg
|
||||
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json");
|
||||
|
@ -244,10 +289,10 @@ void ModManager::LoadMods()
|
|||
enabledModsStringStream << (char)enabledModsStream.get();
|
||||
|
||||
enabledModsStream.close();
|
||||
m_enabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
|
||||
m_EnabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
|
||||
enabledModsStringStream.str().c_str());
|
||||
|
||||
m_hasEnabledModsCfg = m_enabledModsCfg.IsObject();
|
||||
m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject();
|
||||
}
|
||||
|
||||
// get mod directories
|
||||
|
@ -277,41 +322,41 @@ void ModManager::LoadMods()
|
|||
|
||||
for (auto& pair : mod.DependencyConstants)
|
||||
{
|
||||
if (DependencyConstants.find(pair.first) != DependencyConstants.end() && DependencyConstants[pair.first] != pair.second)
|
||||
if (m_DependencyConstants.find(pair.first) != m_DependencyConstants.end() && m_DependencyConstants[pair.first] != pair.second)
|
||||
{
|
||||
spdlog::error("Constant {} in mod {} already exists in another mod.", pair.first, mod.Name);
|
||||
mod.wasReadSuccessfully = false;
|
||||
mod.m_bWasReadSuccessfully = false;
|
||||
break;
|
||||
}
|
||||
if (DependencyConstants.find(pair.first) == DependencyConstants.end())
|
||||
DependencyConstants.emplace(pair);
|
||||
if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end())
|
||||
m_DependencyConstants.emplace(pair);
|
||||
}
|
||||
|
||||
if (m_hasEnabledModsCfg && m_enabledModsCfg.HasMember(mod.Name.c_str()))
|
||||
mod.Enabled = m_enabledModsCfg[mod.Name.c_str()].IsTrue();
|
||||
if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str()))
|
||||
mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue();
|
||||
else
|
||||
mod.Enabled = true;
|
||||
mod.m_bEnabled = true;
|
||||
|
||||
if (mod.wasReadSuccessfully)
|
||||
if (mod.m_bWasReadSuccessfully)
|
||||
{
|
||||
spdlog::info("Loaded mod {} successfully", mod.Name);
|
||||
if (mod.Enabled)
|
||||
if (mod.m_bEnabled)
|
||||
spdlog::info("Mod {} is enabled", mod.Name);
|
||||
else
|
||||
spdlog::info("Mod {} is disabled", mod.Name);
|
||||
|
||||
m_loadedMods.push_back(mod);
|
||||
m_LoadedMods.push_back(mod);
|
||||
}
|
||||
else
|
||||
spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string());
|
||||
}
|
||||
|
||||
// sort by load prio, lowest-highest
|
||||
std::sort(m_loadedMods.begin(), m_loadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; });
|
||||
std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; });
|
||||
|
||||
for (Mod& mod : m_loadedMods)
|
||||
for (Mod& mod : m_LoadedMods)
|
||||
{
|
||||
if (!mod.Enabled)
|
||||
if (!mod.m_bEnabled)
|
||||
continue;
|
||||
|
||||
// register convars
|
||||
|
@ -319,15 +364,15 @@ void ModManager::LoadMods()
|
|||
// preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this
|
||||
// causes us to leak memory on reload, but not much, potentially find a way to not do this at some point
|
||||
for (ModConVar* convar : mod.ConVars)
|
||||
if (!g_pCVar->FindVar(convar->Name.c_str())) // make sure convar isn't registered yet, unsure if necessary but idk what
|
||||
// behaviour is for defining same convar multiple times
|
||||
if (!R2::g_pCVar->FindVar(convar->Name.c_str())) // make sure convar isn't registered yet, unsure if necessary but idk what
|
||||
// behaviour is for defining same convar multiple times
|
||||
new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str());
|
||||
|
||||
// read vpk paths
|
||||
if (fs::exists(mod.ModDirectory / "vpk"))
|
||||
if (fs::exists(mod.m_ModDirectory / "vpk"))
|
||||
{
|
||||
// read vpk cfg
|
||||
std::ifstream vpkJsonStream(mod.ModDirectory / "vpk/vpk.json");
|
||||
std::ifstream vpkJsonStream(mod.m_ModDirectory / "vpk/vpk.json");
|
||||
std::stringstream vpkJsonStringStream;
|
||||
|
||||
bool bUseVPKJson = false;
|
||||
|
@ -345,7 +390,7 @@ void ModManager::LoadMods()
|
|||
bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject();
|
||||
}
|
||||
|
||||
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "vpk"))
|
||||
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "vpk"))
|
||||
{
|
||||
// a bunch of checks to make sure we're only adding dir vpks and their paths are good
|
||||
// note: the game will literally only load vpks with the english prefix
|
||||
|
@ -356,25 +401,24 @@ void ModManager::LoadMods()
|
|||
std::string formattedPath = file.path().filename().string();
|
||||
|
||||
// this really fucking sucks but it'll work
|
||||
std::string vpkName =
|
||||
(file.path().parent_path() / formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3)).string();
|
||||
std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3);
|
||||
|
||||
ModVPKEntry& modVpk = mod.Vpks.emplace_back();
|
||||
modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() &&
|
||||
dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue());
|
||||
modVpk.m_sVpkPath = vpkName;
|
||||
modVpk.m_sVpkPath = (file.path().parent_path() / vpkName).string();
|
||||
|
||||
if (m_hasLoadedMods && modVpk.m_bAutoLoad)
|
||||
(*g_Filesystem)->m_vtable->MountVPK(*g_Filesystem, vpkName.c_str());
|
||||
if (m_bHasLoadedMods && modVpk.m_bAutoLoad)
|
||||
(*R2::g_pFilesystem)->m_vtable->MountVPK(*R2::g_pFilesystem, vpkName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read rpak paths
|
||||
if (fs::exists(mod.ModDirectory / "paks"))
|
||||
if (fs::exists(mod.m_ModDirectory / "paks"))
|
||||
{
|
||||
// read rpak cfg
|
||||
std::ifstream rpakJsonStream(mod.ModDirectory / "paks/rpak.json");
|
||||
std::ifstream rpakJsonStream(mod.m_ModDirectory / "paks/rpak.json");
|
||||
std::stringstream rpakJsonStringStream;
|
||||
|
||||
bool bUseRpakJson = false;
|
||||
|
@ -406,7 +450,7 @@ void ModManager::LoadMods()
|
|||
}
|
||||
}
|
||||
|
||||
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "paks"))
|
||||
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "paks"))
|
||||
{
|
||||
// ensure we're only loading rpaks
|
||||
if (fs::is_regular_file(file) && file.path().extension() == ".rpak")
|
||||
|
@ -417,39 +461,39 @@ void ModManager::LoadMods()
|
|||
modPak.m_bAutoLoad =
|
||||
!bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() &&
|
||||
dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue());
|
||||
|
||||
// postload things
|
||||
if (!bUseRpakJson ||
|
||||
(dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName)))
|
||||
{
|
||||
modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString();
|
||||
}
|
||||
|
||||
modPak.m_sPakName = pakName;
|
||||
|
||||
// not using atm because we need to resolve path to rpak
|
||||
// if (m_hasLoadedMods && modPak.m_bAutoLoad)
|
||||
// g_PakLoadManager->LoadPakAsync(pakName.c_str());
|
||||
// g_pPakLoadManager->LoadPakAsync(pakName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read keyvalues paths
|
||||
if (fs::exists(mod.ModDirectory / "keyvalues"))
|
||||
if (fs::exists(mod.m_ModDirectory / "keyvalues"))
|
||||
{
|
||||
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "keyvalues"))
|
||||
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "keyvalues"))
|
||||
{
|
||||
if (fs::is_regular_file(file))
|
||||
{
|
||||
std::string kvStr = file.path().lexically_relative(mod.ModDirectory / "keyvalues").lexically_normal().string();
|
||||
std::string kvStr =
|
||||
g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / "keyvalues"));
|
||||
mod.KeyValues.emplace(STR_HASH(kvStr), kvStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read pdiff
|
||||
if (fs::exists(mod.ModDirectory / "mod.pdiff"))
|
||||
if (fs::exists(mod.m_ModDirectory / "mod.pdiff"))
|
||||
{
|
||||
std::ifstream pdiffStream(mod.ModDirectory / "mod.pdiff");
|
||||
std::ifstream pdiffStream(mod.m_ModDirectory / "mod.pdiff");
|
||||
|
||||
if (!pdiffStream.fail())
|
||||
{
|
||||
|
@ -464,17 +508,17 @@ void ModManager::LoadMods()
|
|||
}
|
||||
|
||||
// read bink video paths
|
||||
if (fs::exists(mod.ModDirectory / "media"))
|
||||
if (fs::exists(mod.m_ModDirectory / "media"))
|
||||
{
|
||||
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "media"))
|
||||
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "media"))
|
||||
if (fs::is_regular_file(file) && file.path().extension() == ".bik")
|
||||
mod.BinkVideos.push_back(file.path().filename().string());
|
||||
}
|
||||
|
||||
// try to load audio
|
||||
if (fs::exists(mod.ModDirectory / "audio"))
|
||||
if (fs::exists(mod.m_ModDirectory / "audio"))
|
||||
{
|
||||
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "audio"))
|
||||
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "audio"))
|
||||
{
|
||||
if (fs::is_regular_file(file) && file.path().extension().string() == ".json")
|
||||
{
|
||||
|
@ -489,23 +533,23 @@ void ModManager::LoadMods()
|
|||
}
|
||||
|
||||
// in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised
|
||||
for (int64_t i = m_loadedMods.size() - 1; i > -1; i--)
|
||||
for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
|
||||
{
|
||||
if (!m_loadedMods[i].Enabled)
|
||||
if (!m_LoadedMods[i].m_bEnabled)
|
||||
continue;
|
||||
|
||||
if (fs::exists(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR))
|
||||
if (fs::exists(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR))
|
||||
{
|
||||
for (fs::directory_entry file : fs::recursive_directory_iterator(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR))
|
||||
for (fs::directory_entry file : fs::recursive_directory_iterator(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR))
|
||||
{
|
||||
fs::path path = file.path().lexically_relative(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR).lexically_normal();
|
||||
|
||||
if (file.is_regular_file() && m_modFiles.find(path.string()) == m_modFiles.end())
|
||||
std::string path =
|
||||
g_pModManager->NormaliseModFilePath(file.path().lexically_relative(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR));
|
||||
if (file.is_regular_file() && m_ModFiles.find(path) == m_ModFiles.end())
|
||||
{
|
||||
ModOverrideFile modFile;
|
||||
modFile.owningMod = &m_loadedMods[i];
|
||||
modFile.path = path;
|
||||
m_modFiles.insert(std::make_pair(path.string(), modFile));
|
||||
modFile.m_pOwningMod = &m_LoadedMods[i];
|
||||
modFile.m_Path = path;
|
||||
m_ModFiles.insert(std::make_pair(path, modFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -517,9 +561,9 @@ void ModManager::LoadMods()
|
|||
modinfoDoc.AddMember("Mods", rapidjson_document::GenericValue(rapidjson::kArrayType), modinfoDoc.GetAllocator());
|
||||
|
||||
int currentModIndex = 0;
|
||||
for (Mod& mod : m_loadedMods)
|
||||
for (Mod& mod : m_LoadedMods)
|
||||
{
|
||||
if (!mod.Enabled || (!mod.RequiredOnClient && !mod.Pdiff.size()))
|
||||
if (!mod.m_bEnabled || (!mod.RequiredOnClient && !mod.Pdiff.size()))
|
||||
continue;
|
||||
|
||||
modinfoDoc["Mods"].PushBack(rapidjson_document::GenericValue(rapidjson::kObjectType), modinfoDoc.GetAllocator());
|
||||
|
@ -535,27 +579,27 @@ void ModManager::LoadMods()
|
|||
buffer.Clear();
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
modinfoDoc.Accept(writer);
|
||||
g_MasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString());
|
||||
g_pMasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString());
|
||||
|
||||
m_hasLoadedMods = true;
|
||||
m_bHasLoadedMods = true;
|
||||
}
|
||||
|
||||
void ModManager::UnloadMods()
|
||||
{
|
||||
// clean up stuff from mods before we unload
|
||||
m_modFiles.clear();
|
||||
m_ModFiles.clear();
|
||||
fs::remove_all(GetCompiledAssetsPath());
|
||||
|
||||
g_CustomAudioManager.ClearAudioOverrides();
|
||||
|
||||
if (!m_hasEnabledModsCfg)
|
||||
m_enabledModsCfg.SetObject();
|
||||
if (!m_bHasEnabledModsCfg)
|
||||
m_EnabledModsCfg.SetObject();
|
||||
|
||||
for (Mod& mod : m_loadedMods)
|
||||
for (Mod& mod : m_LoadedMods)
|
||||
{
|
||||
// remove all built kvs
|
||||
for (std::pair<size_t, std::string> kvPaths : mod.KeyValues)
|
||||
fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.ModDirectory));
|
||||
fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.m_ModDirectory));
|
||||
|
||||
mod.KeyValues.clear();
|
||||
|
||||
|
@ -563,27 +607,39 @@ void ModManager::UnloadMods()
|
|||
// should we be doing this here or should scripts be doing this manually?
|
||||
// main issue with doing this here is when we reload mods for connecting to a server, we write enabled mods, which isn't necessarily
|
||||
// what we wanna do
|
||||
if (!m_enabledModsCfg.HasMember(mod.Name.c_str()))
|
||||
m_enabledModsCfg.AddMember(
|
||||
if (!m_EnabledModsCfg.HasMember(mod.Name.c_str()))
|
||||
m_EnabledModsCfg.AddMember(
|
||||
rapidjson_document::StringRefType(mod.Name.c_str()),
|
||||
rapidjson_document::GenericValue(false),
|
||||
m_enabledModsCfg.GetAllocator());
|
||||
m_EnabledModsCfg.GetAllocator());
|
||||
|
||||
m_enabledModsCfg[mod.Name.c_str()].SetBool(mod.Enabled);
|
||||
m_EnabledModsCfg[mod.Name.c_str()].SetBool(mod.m_bEnabled);
|
||||
}
|
||||
|
||||
std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json");
|
||||
rapidjson::OStreamWrapper writeStreamWrapper(writeStream);
|
||||
rapidjson::Writer<rapidjson::OStreamWrapper> writer(writeStreamWrapper);
|
||||
m_enabledModsCfg.Accept(writer);
|
||||
m_EnabledModsCfg.Accept(writer);
|
||||
|
||||
// do we need to dealloc individual entries in m_loadedMods? idk, rework
|
||||
m_loadedMods.clear();
|
||||
m_LoadedMods.clear();
|
||||
}
|
||||
|
||||
std::string ModManager::NormaliseModFilePath(const fs::path path)
|
||||
{
|
||||
std::string str = path.lexically_normal().string();
|
||||
|
||||
// force to lowercase
|
||||
for (char& c : str)
|
||||
if (c <= 'Z' && c >= 'A')
|
||||
c = c - ('Z' - 'z');
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void ModManager::CompileAssetsForFile(const char* filename)
|
||||
{
|
||||
size_t fileHash = STR_HASH(fs::path(filename).lexically_normal().string());
|
||||
size_t fileHash = STR_HASH(NormaliseModFilePath(fs::path(filename)));
|
||||
|
||||
if (fileHash == m_hScriptsRsonHash)
|
||||
BuildScriptsRson();
|
||||
|
@ -592,9 +648,9 @@ void ModManager::CompileAssetsForFile(const char* filename)
|
|||
else
|
||||
{
|
||||
// check if we should build keyvalues, depending on whether any of our mods have patch kvs for this file
|
||||
for (Mod& mod : m_loadedMods)
|
||||
for (Mod& mod : m_LoadedMods)
|
||||
{
|
||||
if (!mod.Enabled)
|
||||
if (!mod.m_bEnabled)
|
||||
continue;
|
||||
|
||||
if (mod.KeyValues.find(fileHash) != mod.KeyValues.end())
|
||||
|
@ -608,14 +664,7 @@ void ModManager::CompileAssetsForFile(const char* filename)
|
|||
|
||||
void ConCommand_reload_mods(const CCommand& args)
|
||||
{
|
||||
g_ModManager->LoadMods();
|
||||
}
|
||||
|
||||
void InitialiseModManager(HMODULE baseAddress)
|
||||
{
|
||||
g_ModManager = new ModManager;
|
||||
|
||||
RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE);
|
||||
g_pModManager->LoadMods();
|
||||
}
|
||||
|
||||
fs::path GetModFolderPath()
|
||||
|
@ -626,3 +675,10 @@ fs::path GetCompiledAssetsPath()
|
|||
{
|
||||
return fs::path(GetNorthstarPrefix() + COMPILED_ASSETS_SUFFIX);
|
||||
}
|
||||
|
||||
ON_DLL_LOAD_RELIESON("engine.dll", ModManager, (ConCommand, MasterServer), (CModule module))
|
||||
{
|
||||
g_pModManager = new ModManager;
|
||||
|
||||
RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
#pragma once
|
||||
#include "convar.h"
|
||||
#include "memalloc.h"
|
||||
#include "squirrel.h"
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include "rapidjson/document.h"
|
||||
#include "memalloc.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const std::string MOD_FOLDER_SUFFIX = "/mods";
|
||||
const std::string REMOTE_MOD_FOLDER_SUFFIX = "/runtime/remote/mods";
|
||||
const fs::path MOD_OVERRIDE_DIR = "mod";
|
||||
const std::string COMPILED_ASSETS_SUFFIX = "/runtime/compiled";
|
||||
|
||||
|
@ -24,9 +25,6 @@ struct ModConVar
|
|||
struct ModScriptCallback
|
||||
{
|
||||
public:
|
||||
// would've liked to make it possible to hook arbitrary codecallbacks, but couldn't find a function that calls some ui ones
|
||||
// std::string HookedCodeCallback;
|
||||
|
||||
ScriptContext Context;
|
||||
|
||||
// called before the codecallback is executed
|
||||
|
@ -39,7 +37,7 @@ struct ModScript
|
|||
{
|
||||
public:
|
||||
std::string Path;
|
||||
std::string RsonRunOn;
|
||||
std::string RunOn;
|
||||
|
||||
std::vector<ModScriptCallback> Callbacks;
|
||||
};
|
||||
|
@ -64,8 +62,10 @@ class Mod
|
|||
{
|
||||
public:
|
||||
// runtime stuff
|
||||
fs::path ModDirectory;
|
||||
bool Enabled = true;
|
||||
bool m_bEnabled = true;
|
||||
bool m_bWasReadSuccessfully = false;
|
||||
fs::path m_ModDirectory;
|
||||
// bool m_bIsRemote;
|
||||
|
||||
// mod.json stuff:
|
||||
|
||||
|
@ -100,14 +100,9 @@ class Mod
|
|||
std::vector<ModRpakEntry> Rpaks;
|
||||
std::unordered_map<std::string, std::string>
|
||||
RpakAliases; // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite
|
||||
// iterated over to create squirrel VM constants depending if a mod exists or not.
|
||||
// this only exists because we cannot access g_ModManager whilst mods are being loaded for the first time for some reason.
|
||||
|
||||
std::unordered_map<std::string, std::string> DependencyConstants;
|
||||
|
||||
// other stuff
|
||||
|
||||
bool wasReadSuccessfully = false;
|
||||
|
||||
public:
|
||||
Mod(fs::path modPath, char* jsonBuf);
|
||||
};
|
||||
|
@ -115,42 +110,40 @@ class Mod
|
|||
struct ModOverrideFile
|
||||
{
|
||||
public:
|
||||
Mod* owningMod;
|
||||
fs::path path;
|
||||
Mod* m_pOwningMod;
|
||||
fs::path m_Path;
|
||||
};
|
||||
|
||||
class ModManager
|
||||
{
|
||||
private:
|
||||
bool m_hasLoadedMods = false;
|
||||
bool m_hasEnabledModsCfg;
|
||||
rapidjson_document m_enabledModsCfg;
|
||||
bool m_bHasLoadedMods = false;
|
||||
bool m_bHasEnabledModsCfg;
|
||||
rapidjson_document m_EnabledModsCfg;
|
||||
|
||||
// precalculated hashes
|
||||
size_t m_hScriptsRsonHash;
|
||||
size_t m_hPdefHash;
|
||||
|
||||
public:
|
||||
std::vector<Mod> m_loadedMods;
|
||||
std::unordered_map<std::string, ModOverrideFile> m_modFiles;
|
||||
// iterated over to create squirrel VM constants depending if a mod exists or not.
|
||||
// here because constants are global anyways.
|
||||
std::unordered_map<std::string, std::string> DependencyConstants;
|
||||
std::vector<Mod> m_LoadedMods;
|
||||
std::unordered_map<std::string, ModOverrideFile> m_ModFiles;
|
||||
std::unordered_map<std::string, std::string> m_DependencyConstants;
|
||||
|
||||
public:
|
||||
ModManager();
|
||||
void LoadMods();
|
||||
void UnloadMods();
|
||||
std::string NormaliseModFilePath(const fs::path path);
|
||||
void CompileAssetsForFile(const char* filename);
|
||||
|
||||
// compile asset type stuff, these are done in files under Mods/Compiled/
|
||||
// compile asset type stuff, these are done in files under runtime/compiled/
|
||||
void BuildScriptsRson();
|
||||
void TryBuildKeyValues(const char* filename);
|
||||
void BuildPdef();
|
||||
};
|
||||
|
||||
void InitialiseModManager(HMODULE baseAddress);
|
||||
fs::path GetModFolderPath();
|
||||
fs::path GetCompiledAssetsPath();
|
||||
|
||||
extern ModManager* g_ModManager;
|
||||
extern ModManager* g_pModManager;
|
||||
|
|
|
@ -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
|
|
@ -1,13 +1,13 @@
|
|||
#include <string>
|
||||
#include "pch.h"
|
||||
#include "nsprefix.h"
|
||||
#include <string>
|
||||
|
||||
std::string GetNorthstarPrefix()
|
||||
{
|
||||
return NORTHSTAR_FOLDER_PREFIX;
|
||||
}
|
||||
|
||||
void parseConfigurables()
|
||||
void InitialiseNorthstarPrefix()
|
||||
{
|
||||
char* clachar = strstr(GetCommandLineA(), "-profile=");
|
||||
if (clachar)
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
static std::string NORTHSTAR_FOLDER_PREFIX;
|
||||
|
||||
void InitialiseNorthstarPrefix();
|
||||
std::string GetNorthstarPrefix();
|
||||
void parseConfigurables();
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
#define _WINSOCK_DEPRECATED_NO_WARNINGS // temp because i'm very lazy and want to use inet_addr, remove later
|
||||
#define RAPIDJSON_HAS_STDSTRING 1
|
||||
|
||||
// httplib ssl
|
||||
|
||||
// add headers that you want to pre-compile here
|
||||
#include "memalloc.h"
|
||||
|
||||
|
@ -20,11 +18,14 @@
|
|||
#include <filesystem>
|
||||
#include <sstream>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#include "logging.h"
|
||||
#include "include/MinHook.h"
|
||||
#include "MinHook.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "libcurl/include/curl/curl.h"
|
||||
#include "hookutils.h"
|
||||
#include "hooks.h"
|
||||
#include "memory.h"
|
||||
|
||||
template <typename ReturnType, typename... Args> ReturnType CallVFunc(int index, void* thisPtr, Args... args)
|
||||
{
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "pch.h"
|
||||
#include "modmanager.h"
|
||||
#include "filesystem.h"
|
||||
#include "hookutils.h"
|
||||
#include "pdef.h"
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
@ -14,11 +14,11 @@ void ModManager::BuildPdef()
|
|||
fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX);
|
||||
|
||||
fs::remove(MOD_PDEF_PATH);
|
||||
std::string pdef = ReadVPKOriginalFile(VPK_PDEF_PATH);
|
||||
std::string pdef = R2::ReadVPKOriginalFile(VPK_PDEF_PATH);
|
||||
|
||||
for (Mod& mod : m_loadedMods)
|
||||
for (Mod& mod : m_LoadedMods)
|
||||
{
|
||||
if (!mod.Enabled || !mod.Pdiff.size())
|
||||
if (!mod.m_bEnabled || !mod.Pdiff.size())
|
||||
continue;
|
||||
|
||||
// this code probably isn't going to be pretty lol
|
||||
|
@ -107,11 +107,11 @@ void ModManager::BuildPdef()
|
|||
writeStream.close();
|
||||
|
||||
ModOverrideFile overrideFile;
|
||||
overrideFile.owningMod = nullptr;
|
||||
overrideFile.path = VPK_PDEF_PATH;
|
||||
overrideFile.m_pOwningMod = nullptr;
|
||||
overrideFile.m_Path = VPK_PDEF_PATH;
|
||||
|
||||
if (m_modFiles.find(VPK_PDEF_PATH) == m_modFiles.end())
|
||||
m_modFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile));
|
||||
if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end())
|
||||
m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile));
|
||||
else
|
||||
m_modFiles[VPK_PDEF_PATH] = overrideFile;
|
||||
m_ModFiles[VPK_PDEF_PATH] = overrideFile;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,94 @@
|
|||
#include "pch.h"
|
||||
#include "playlist.h"
|
||||
#include "nsmem.h"
|
||||
#include "concommand.h"
|
||||
#include "convar.h"
|
||||
#include "gameutils.h"
|
||||
#include "hookutils.h"
|
||||
#include "squirrel.h"
|
||||
#include "hoststate.h"
|
||||
#include "serverpresence.h"
|
||||
|
||||
typedef char (*Onclc_SetPlaylistVarOverrideType)(void* a1, void* a2);
|
||||
Onclc_SetPlaylistVarOverrideType Onclc_SetPlaylistVarOverride;
|
||||
AUTOHOOK_INIT()
|
||||
|
||||
typedef int (*GetCurrentGamemodeMaxPlayersType)();
|
||||
GetCurrentGamemodeMaxPlayersType GetCurrentGamemodeMaxPlayers;
|
||||
|
||||
// function type defined in gameutils.h
|
||||
SetPlaylistVarOverrideType SetPlaylistVarOverrideOriginal;
|
||||
GetCurrentPlaylistVarType GetCurrentPlaylistVarOriginal;
|
||||
// use the R2 namespace for game funcs
|
||||
namespace R2
|
||||
{
|
||||
const char* (*GetCurrentPlaylistName)();
|
||||
void (*SetCurrentPlaylist)(const char* pPlaylistName);
|
||||
void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue);
|
||||
const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides);
|
||||
} // namespace R2
|
||||
|
||||
ConVar* Cvar_ns_use_clc_SetPlaylistVarOverride;
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(clc_SetPlaylistVarOverride__Process, engine.dll + 0x222180,
|
||||
char, __fastcall, (void* a1, void* a2))
|
||||
// clang-format on
|
||||
{
|
||||
// the private_match playlist on mp_lobby is the only situation where there should be any legitimate sending of this netmessage
|
||||
if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(R2::GetCurrentPlaylistName(), "private_match") ||
|
||||
strcmp(R2::g_pHostState->m_levelName, "mp_lobby"))
|
||||
return 1;
|
||||
|
||||
return clc_SetPlaylistVarOverride__Process(a1, a2);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(SetCurrentPlaylist, engine.dll + 0x18EB20,
|
||||
bool, __fastcall, (const char* pPlaylistName))
|
||||
// clang-format on
|
||||
{
|
||||
bool bSuccess = SetCurrentPlaylist(pPlaylistName);
|
||||
|
||||
if (bSuccess)
|
||||
{
|
||||
spdlog::info("Set playlist to {}", R2::GetCurrentPlaylistName());
|
||||
g_pServerPresence->SetPlaylist(R2::GetCurrentPlaylistName());
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(SetPlaylistVarOverride, engine.dll + 0x18ED00,
|
||||
void, __fastcall, (const char* pVarName, const char* pValue))
|
||||
// clang-format on
|
||||
{
|
||||
if (strlen(pValue) >= 64)
|
||||
return;
|
||||
|
||||
SetPlaylistVarOverride(pVarName, pValue);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(GetCurrentPlaylistVar, engine.dll + 0x18C680,
|
||||
const char*, __fastcall, (const char* pVarName, bool bUseOverrides))
|
||||
// clang-format on
|
||||
{
|
||||
if (!bUseOverrides && !strcmp(pVarName, "max_players"))
|
||||
bUseOverrides = true;
|
||||
|
||||
return GetCurrentPlaylistVar(pVarName, bUseOverrides);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
AUTOHOOK(GetCurrentGamemodeMaxPlayers, engine.dll + 0x18C430,
|
||||
int, __fastcall, ())
|
||||
// clang-format on
|
||||
{
|
||||
const char* pMaxPlayers = R2::GetCurrentPlaylistVar("max_players", 0);
|
||||
if (!pMaxPlayers)
|
||||
return GetCurrentGamemodeMaxPlayers();
|
||||
|
||||
int iMaxPlayers = atoi(pMaxPlayers);
|
||||
return iMaxPlayers;
|
||||
}
|
||||
|
||||
void ConCommand_playlist(const CCommand& args)
|
||||
{
|
||||
if (args.ArgC() < 2)
|
||||
return;
|
||||
|
||||
SetCurrentPlaylist(args.Arg(1));
|
||||
R2::SetCurrentPlaylist(args.Arg(1));
|
||||
}
|
||||
|
||||
void ConCommand_setplaylistvaroverride(const CCommand& args)
|
||||
|
@ -33,74 +97,34 @@ void ConCommand_setplaylistvaroverride(const CCommand& args)
|
|||
return;
|
||||
|
||||
for (int i = 1; i < args.ArgC(); i += 2)
|
||||
SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1));
|
||||
R2::SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1));
|
||||
}
|
||||
|
||||
char Onclc_SetPlaylistVarOverrideHook(void* a1, void* a2)
|
||||
ON_DLL_LOAD_RELIESON("engine.dll", PlaylistHooks, (ConCommand, ConVar), (CModule module))
|
||||
{
|
||||
// the private_match playlist is the only situation where there should be any legitimate sending of this netmessage
|
||||
// todo: check mp_lobby here too
|
||||
if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(GetCurrentPlaylistName(), "private_match"))
|
||||
return 1;
|
||||
AUTOHOOK_DISPATCH()
|
||||
|
||||
return Onclc_SetPlaylistVarOverride(a1, a2);
|
||||
}
|
||||
R2::GetCurrentPlaylistName = module.Offset(0x18C640).As<const char* (*)()>();
|
||||
R2::SetCurrentPlaylist = module.Offset(0x18EB20).As<void (*)(const char*)>();
|
||||
R2::SetPlaylistVarOverride = module.Offset(0x18ED00).As<void (*)(const char*, const char*)>();
|
||||
R2::GetCurrentPlaylistVar = module.Offset(0x18C680).As<const char* (*)(const char*, bool)>();
|
||||
|
||||
void SetPlaylistVarOverrideHook(const char* varName, const char* value)
|
||||
{
|
||||
if (strlen(value) >= 64)
|
||||
return;
|
||||
|
||||
SetPlaylistVarOverrideOriginal(varName, value);
|
||||
}
|
||||
|
||||
char* GetCurrentPlaylistVarHook(const char* varName, bool useOverrides)
|
||||
{
|
||||
if (!useOverrides && !strcmp(varName, "max_players"))
|
||||
useOverrides = true;
|
||||
|
||||
return GetCurrentPlaylistVarOriginal(varName, useOverrides);
|
||||
}
|
||||
|
||||
int GetCurrentGamemodeMaxPlayersHook()
|
||||
{
|
||||
char* maxPlayersStr = GetCurrentPlaylistVar("max_players", 0);
|
||||
if (!maxPlayersStr)
|
||||
return GetCurrentGamemodeMaxPlayers();
|
||||
|
||||
int maxPlayers = atoi(maxPlayersStr);
|
||||
return maxPlayers;
|
||||
}
|
||||
|
||||
void InitialisePlaylistHooks(HMODULE baseAddress)
|
||||
{
|
||||
// playlist is the name of the command on respawn servers, but we already use setplaylist so can't get rid of it
|
||||
RegisterConCommand("playlist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE);
|
||||
RegisterConCommand("setplaylist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE);
|
||||
RegisterConCommand("setplaylistvaroverrides", ConCommand_setplaylistvaroverride, "sets a playlist var override", FCVAR_NONE);
|
||||
|
||||
// note: clc_SetPlaylistVarOverride is pretty insecure, since it allows for entirely arbitrary playlist var overrides to be sent to the
|
||||
// server this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be
|
||||
// server, this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be
|
||||
// disabled altogether, since the custom menus won't use it anyway this should only really be accepted if you want vanilla client
|
||||
// compatibility
|
||||
Cvar_ns_use_clc_SetPlaylistVarOverride = new ConVar(
|
||||
"ns_use_clc_SetPlaylistVarOverride", "0", FCVAR_GAMEDLL, "Whether the server should accept clc_SetPlaylistVarOverride messages");
|
||||
|
||||
HookEnabler hook;
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, (char*)baseAddress + 0x222180, &Onclc_SetPlaylistVarOverrideHook, reinterpret_cast<LPVOID*>(&Onclc_SetPlaylistVarOverride));
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, (char*)baseAddress + 0x18ED00, &SetPlaylistVarOverrideHook, reinterpret_cast<LPVOID*>(&SetPlaylistVarOverrideOriginal));
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, (char*)baseAddress + 0x18C680, &GetCurrentPlaylistVarHook, reinterpret_cast<LPVOID*>(&GetCurrentPlaylistVarOriginal));
|
||||
ENABLER_CREATEHOOK(
|
||||
hook, (char*)baseAddress + 0x18C430, &GetCurrentGamemodeMaxPlayersHook, reinterpret_cast<LPVOID*>(&GetCurrentGamemodeMaxPlayers));
|
||||
|
||||
uintptr_t ba = (uintptr_t)baseAddress;
|
||||
|
||||
// patch to prevent clc_SetPlaylistVarOverride from being able to crash servers if we reach max overrides due to a call to Error (why is
|
||||
// this possible respawn, wtf) todo: add a warning for this
|
||||
{
|
||||
NSMem::BytePatch(ba + 0x18ED8D, "C3");
|
||||
}
|
||||
module.Offset(0x18ED8D).Patch("C3");
|
||||
|
||||
// patch to allow setplaylistvaroverride to be called before map init on dedicated and private match launched through the game
|
||||
NSMem::NOP(ba + 0x18ED17, 6);
|
||||
module.Offset(0x18ED17).NOP(6);
|
||||
}
|
||||
|
|
|
@ -1,2 +1,10 @@
|
|||
#pragma once
|
||||
void InitialisePlaylistHooks(HMODULE baseAddress);
|
||||
|
||||
// use the R2 namespace for game funcs
|
||||
namespace R2
|
||||
{
|
||||
extern const char* (*GetCurrentPlaylistName)();
|
||||
extern void (*SetCurrentPlaylist)(const char* pPlaylistName);
|
||||
extern void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue);
|
||||
extern const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides);
|
||||
} // namespace R2
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#include "pch.h"
|
||||
#include "squirrel.h"
|
||||
#include "plugins.h"
|
||||
#include <chrono>
|
||||
#include "masterserver.h"
|
||||
#include "convar.h"
|
||||
#include "serverpresence.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <windows.h>
|
||||
|
||||
/// <summary>
|
||||
|
@ -109,31 +111,31 @@ void initGameState()
|
|||
}
|
||||
|
||||
// string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading
|
||||
SQRESULT SQ_UpdateGameStateUI(void* sqvm)
|
||||
SQRESULT SQ_UpdateGameStateUI(HSquirrelVM* sqvm)
|
||||
{
|
||||
AcquireSRWLockExclusive(&gameStateLock);
|
||||
gameState.map = ClientSq_getstring(sqvm, 1);
|
||||
gameState.mapDisplayName = ClientSq_getstring(sqvm, 2);
|
||||
gameState.playlist = ClientSq_getstring(sqvm, 3);
|
||||
gameState.playlistDisplayName = ClientSq_getstring(sqvm, 4);
|
||||
gameState.connected = ClientSq_getbool(sqvm, 5);
|
||||
gameState.loading = ClientSq_getbool(sqvm, 6);
|
||||
gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
|
||||
gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
|
||||
gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
|
||||
gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 4);
|
||||
gameState.connected = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 5);
|
||||
gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 6);
|
||||
ReleaseSRWLockExclusive(&gameStateLock);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit
|
||||
SQRESULT SQ_UpdateGameStateClient(void* sqvm)
|
||||
SQRESULT SQ_UpdateGameStateClient(HSquirrelVM* sqvm)
|
||||
{
|
||||
AcquireSRWLockExclusive(&gameStateLock);
|
||||
AcquireSRWLockExclusive(&serverInfoLock);
|
||||
gameState.players = ClientSq_getinteger(sqvm, 1);
|
||||
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 2);
|
||||
gameState.ourScore = ClientSq_getinteger(sqvm, 3);
|
||||
gameState.secondHighestScore = ClientSq_getinteger(sqvm, 4);
|
||||
gameState.highestScore = ClientSq_getinteger(sqvm, 5);
|
||||
serverInfo.roundBased = ClientSq_getbool(sqvm, 6);
|
||||
serverInfo.scoreLimit = ClientSq_getbool(sqvm, 7);
|
||||
gameState.players = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
|
||||
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 2);
|
||||
gameState.ourScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 3);
|
||||
gameState.secondHighestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
|
||||
gameState.highestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 5);
|
||||
serverInfo.roundBased = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 6);
|
||||
serverInfo.scoreLimit = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 7);
|
||||
ReleaseSRWLockExclusive(&gameStateLock);
|
||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||
return SQRESULT_NOTNULL;
|
||||
|
@ -141,59 +143,59 @@ SQRESULT SQ_UpdateGameStateClient(void* sqvm)
|
|||
|
||||
// string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string
|
||||
// playlistDisplayName
|
||||
SQRESULT SQ_UpdateServerInfo(void* sqvm)
|
||||
SQRESULT SQ_UpdateServerInfo(HSquirrelVM* sqvm)
|
||||
{
|
||||
AcquireSRWLockExclusive(&gameStateLock);
|
||||
AcquireSRWLockExclusive(&serverInfoLock);
|
||||
serverInfo.id = ClientSq_getstring(sqvm, 1);
|
||||
serverInfo.name = ClientSq_getstring(sqvm, 2);
|
||||
serverInfo.password = ClientSq_getstring(sqvm, 3);
|
||||
gameState.players = ClientSq_getinteger(sqvm, 4);
|
||||
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 5);
|
||||
gameState.map = ClientSq_getstring(sqvm, 6);
|
||||
gameState.mapDisplayName = ClientSq_getstring(sqvm, 7);
|
||||
gameState.playlist = ClientSq_getstring(sqvm, 8);
|
||||
gameState.playlistDisplayName = ClientSq_getstring(sqvm, 9);
|
||||
serverInfo.id = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
|
||||
serverInfo.name = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
|
||||
serverInfo.password = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
|
||||
gameState.players = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 4);
|
||||
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 5);
|
||||
gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 6);
|
||||
gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 7);
|
||||
gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 8);
|
||||
gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 9);
|
||||
ReleaseSRWLockExclusive(&gameStateLock);
|
||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
// int maxPlayers
|
||||
SQRESULT SQ_UpdateServerInfoBetweenRounds(void* sqvm)
|
||||
SQRESULT SQ_UpdateServerInfoBetweenRounds(HSquirrelVM* sqvm)
|
||||
{
|
||||
AcquireSRWLockExclusive(&serverInfoLock);
|
||||
serverInfo.id = ClientSq_getstring(sqvm, 1);
|
||||
serverInfo.name = ClientSq_getstring(sqvm, 2);
|
||||
serverInfo.password = ClientSq_getstring(sqvm, 3);
|
||||
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 4);
|
||||
serverInfo.id = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 1);
|
||||
serverInfo.name = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
|
||||
serverInfo.password = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 3);
|
||||
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
|
||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
// float timeInFuture
|
||||
SQRESULT SQ_UpdateTimeInfo(void* sqvm)
|
||||
SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm)
|
||||
{
|
||||
AcquireSRWLockExclusive(&serverInfoLock);
|
||||
serverInfo.endTime = ceil(ClientSq_getfloat(sqvm, 1));
|
||||
serverInfo.endTime = ceil(g_pSquirrel<ScriptContext::CLIENT>->getfloat(sqvm, 1));
|
||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
// bool loading
|
||||
SQRESULT SQ_SetConnected(void* sqvm)
|
||||
SQRESULT SQ_SetConnected(HSquirrelVM* sqvm)
|
||||
{
|
||||
AcquireSRWLockExclusive(&gameStateLock);
|
||||
gameState.loading = ClientSq_getbool(sqvm, 1);
|
||||
gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 1);
|
||||
ReleaseSRWLockExclusive(&gameStateLock);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
||||
SQRESULT SQ_UpdateListenServer(void* sqvm)
|
||||
SQRESULT SQ_UpdateListenServer(HSquirrelVM* sqvm)
|
||||
{
|
||||
AcquireSRWLockExclusive(&serverInfoLock);
|
||||
serverInfo.id = g_MasterServerManager->m_sOwnServerId;
|
||||
serverInfo.password = Cvar_ns_server_password->GetString();
|
||||
serverInfo.id = g_pMasterServerManager->m_sOwnServerId;
|
||||
serverInfo.password = ""; // g_pServerPresence->Cvar_ns_server_password->GetString(); todo this fr
|
||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||
return SQRESULT_NOTNULL;
|
||||
}
|
||||
|
@ -384,26 +386,26 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var)
|
|||
return n;
|
||||
}
|
||||
|
||||
void InitialisePluginCommands(HMODULE baseAddress)
|
||||
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", PluginCommands, ClientSquirrel, (CModule module))
|
||||
{
|
||||
// i swear there's a way to make this not have be run in 2 contexts but i can't figure it out
|
||||
// some funcs i need are just not available in UI or CLIENT
|
||||
|
||||
if (g_UISquirrelManager && g_ClientSquirrelManager)
|
||||
if (g_pSquirrel<ScriptContext::UI> && g_pSquirrel<ScriptContext::CLIENT>)
|
||||
{
|
||||
g_UISquirrelManager->AddFuncRegistration(
|
||||
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
|
||||
"void",
|
||||
"NSUpdateGameStateUI",
|
||||
"string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading",
|
||||
"",
|
||||
SQ_UpdateGameStateUI);
|
||||
g_ClientSquirrelManager->AddFuncRegistration(
|
||||
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
|
||||
"void",
|
||||
"NSUpdateGameStateClient",
|
||||
"int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit",
|
||||
"",
|
||||
SQ_UpdateGameStateClient);
|
||||
g_UISquirrelManager->AddFuncRegistration(
|
||||
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
|
||||
"void",
|
||||
"NSUpdateServerInfo",
|
||||
"string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, "
|
||||
|
@ -411,10 +413,10 @@ void InitialisePluginCommands(HMODULE baseAddress)
|
|||
"playlistDisplayName",
|
||||
"",
|
||||
SQ_UpdateServerInfo);
|
||||
g_ClientSquirrelManager->AddFuncRegistration(
|
||||
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
|
||||
"void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds);
|
||||
g_ClientSquirrelManager->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
|
||||
g_UISquirrelManager->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
|
||||
g_UISquirrelManager->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
|
||||
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
|
||||
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
|
||||
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,5 +15,3 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var);
|
|||
|
||||
void initGameState();
|
||||
void* getPluginObject(PluginObject var);
|
||||
|
||||
void InitialisePluginCommands(HMODULE baseAddress);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
#include "concommand.h"
|
||||
|
||||
void PrintCommandHelpDialogue(const ConCommandBase* command, const char* name);
|
||||
void TryPrintCvarHelpForCommand(const char* pCommand);
|
||||
void InitialiseCommandPrint();
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
#pragma once
|
||||
void InitialiseMapsPrint();
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue