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 "pch.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "dedicated.h"
|
#include "dedicated.h"
|
||||||
|
#include "convar.h"
|
||||||
|
|
||||||
#include "rapidjson/error/en.h"
|
#include "rapidjson/error/en.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include "convar.h"
|
|
||||||
|
AUTOHOOK_INIT()
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
|
@ -229,10 +231,8 @@ EventOverrideData::EventOverrideData(const std::string& data, const fs::path& pa
|
||||||
}
|
}
|
||||||
|
|
||||||
// read from after the header first to preserve the empty header, then read the header last
|
// read from after the header first to preserve the empty header, then read the header last
|
||||||
wavStream.seekg(sizeof(EMPTY_WAVE), std::ios::beg);
|
|
||||||
wavStream.read(reinterpret_cast<char*>(&data[sizeof(EMPTY_WAVE)]), fileSize - sizeof(EMPTY_WAVE));
|
|
||||||
wavStream.seekg(0, std::ios::beg);
|
wavStream.seekg(0, std::ios::beg);
|
||||||
wavStream.read(reinterpret_cast<char*>(data), sizeof(EMPTY_WAVE));
|
wavStream.read(reinterpret_cast<char*>(data), fileSize);
|
||||||
wavStream.close();
|
wavStream.close();
|
||||||
|
|
||||||
spdlog::info("Finished async read of audio sample {}", pathString);
|
spdlog::info("Finished async read of audio sample {}", pathString);
|
||||||
|
@ -315,6 +315,7 @@ void CustomAudioManager::ClearAudioOverrides()
|
||||||
{
|
{
|
||||||
// stop all miles sounds beforehand
|
// stop all miles sounds beforehand
|
||||||
// miles_stop_all
|
// miles_stop_all
|
||||||
|
|
||||||
MilesStopAll();
|
MilesStopAll();
|
||||||
|
|
||||||
// this is cancer but it works
|
// this is cancer but it works
|
||||||
|
@ -323,15 +324,12 @@ void CustomAudioManager::ClearAudioOverrides()
|
||||||
|
|
||||||
// slightly (very) bad
|
// slightly (very) bad
|
||||||
// wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them
|
// wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them
|
||||||
std::unique_lock lock(g_CustomAudioManager.m_loadingMutex);
|
std::unique_lock lock(m_loadingMutex);
|
||||||
|
|
||||||
m_loadedAudioOverrides.clear();
|
m_loadedAudioOverrides.clear();
|
||||||
m_loadedAudioOverridesRegex.clear();
|
m_loadedAudioOverridesRegex.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef bool (*LoadSampleMetadata_Type)(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType);
|
|
||||||
LoadSampleMetadata_Type LoadSampleMetadata_Original;
|
|
||||||
|
|
||||||
template <typename Iter, typename RandomGenerator> Iter select_randomly(Iter start, Iter end, RandomGenerator& g)
|
template <typename Iter, typename RandomGenerator> Iter select_randomly(Iter start, Iter end, RandomGenerator& g)
|
||||||
{
|
{
|
||||||
std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
|
std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
|
||||||
|
@ -368,6 +366,26 @@ bool ShouldPlayAudioEvent(const char* eventName, const std::shared_ptr<EventOver
|
||||||
return true; // good to go
|
return true; // good to go
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forward declare
|
||||||
|
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
||||||
|
uintptr_t parentEvent, void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType);
|
||||||
|
|
||||||
|
// DO NOT TOUCH THIS FUNCTION
|
||||||
|
// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer.
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(LoadSampleMetadata, mileswin64.dll + 0xF110,
|
||||||
|
bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent();
|
||||||
|
|
||||||
|
// Raw source, used for voice data only
|
||||||
|
if (audioType == 0)
|
||||||
|
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
|
||||||
|
|
||||||
|
return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType);
|
||||||
|
}
|
||||||
|
|
||||||
// DO NOT INLINE THIS FUNCTION
|
// DO NOT INLINE THIS FUNCTION
|
||||||
// See comment below.
|
// See comment below.
|
||||||
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
||||||
|
@ -398,7 +416,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
||||||
|
|
||||||
if (!overrideData)
|
if (!overrideData)
|
||||||
// not found either
|
// not found either
|
||||||
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
|
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// cache found pattern to improve performance
|
// cache found pattern to improve performance
|
||||||
|
@ -412,7 +430,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
||||||
overrideData = iter->second;
|
overrideData = iter->second;
|
||||||
|
|
||||||
if (!ShouldPlayAudioEvent(eventName, overrideData))
|
if (!ShouldPlayAudioEvent(eventName, overrideData))
|
||||||
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
|
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
|
||||||
|
|
||||||
void* data = 0;
|
void* data = 0;
|
||||||
unsigned int dataLength = 0;
|
unsigned int dataLength = 0;
|
||||||
|
@ -454,7 +472,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
||||||
if (!data)
|
if (!data)
|
||||||
{
|
{
|
||||||
spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName);
|
spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName);
|
||||||
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
|
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
|
||||||
}
|
}
|
||||||
|
|
||||||
audioBuffer = data;
|
audioBuffer = data;
|
||||||
|
@ -465,51 +483,25 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
|
||||||
*(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength;
|
*(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength;
|
||||||
|
|
||||||
// 64 - Auto-detect sample type
|
// 64 - Auto-detect sample type
|
||||||
bool res = LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, 64);
|
bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64);
|
||||||
if (!res)
|
if (!res)
|
||||||
spdlog::error("LoadSampleMetadata failed! The game will crash :(");
|
spdlog::error("LoadSampleMetadata failed! The game will crash :(");
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT TOUCH THIS FUNCTION
|
// clang-format off
|
||||||
// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer.
|
AUTOHOOK(MilesLog, client.dll + 0x57DAD0,
|
||||||
bool __fastcall LoadSampleMetadata_Hook(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType)
|
void, __fastcall, (int level, const char* string))
|
||||||
{
|
// clang-format on
|
||||||
uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent();
|
|
||||||
|
|
||||||
// Raw source, used for voice data only
|
|
||||||
if (audioType == 0)
|
|
||||||
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
|
|
||||||
|
|
||||||
return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef bool (*MilesLog_Type)(int level, const char* string);
|
|
||||||
MilesLog_Type MilesLog_Original;
|
|
||||||
|
|
||||||
void __fastcall MilesLog_Hook(int level, const char* string)
|
|
||||||
{
|
{
|
||||||
spdlog::info("[MSS] {} - {}", level, string);
|
spdlog::info("[MSS] {} - {}", level, string);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseMilesAudioHooks(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module))
|
||||||
{
|
{
|
||||||
|
AUTOHOOK_DISPATCH()
|
||||||
|
|
||||||
Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, "");
|
Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, "");
|
||||||
|
MilesStopAll = module.Offset(0x580850).As<MilesStopAll_Type>();
|
||||||
if (IsDedicatedServer())
|
|
||||||
return;
|
|
||||||
|
|
||||||
uintptr_t milesAudioBase = (uintptr_t)GetModuleHandleA("mileswin64.dll");
|
|
||||||
|
|
||||||
if (!milesAudioBase)
|
|
||||||
return spdlog::error("miles audio not found :terror:");
|
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook, (char*)milesAudioBase + 0xF110, &LoadSampleMetadata_Hook, reinterpret_cast<LPVOID*>(&LoadSampleMetadata_Original));
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x57DAD0, &MilesLog_Hook, reinterpret_cast<LPVOID*>(&MilesLog_Original));
|
|
||||||
|
|
||||||
MilesStopAll = (MilesStopAll_Type)((char*)baseAddress + 0x580850);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
enum class AudioSelectionStrategy
|
enum class AudioSelectionStrategy
|
||||||
{
|
{
|
||||||
INVALID = -1,
|
INVALID = -1,
|
||||||
|
@ -46,5 +44,3 @@ class CustomAudioManager
|
||||||
};
|
};
|
||||||
|
|
||||||
extern CustomAudioManager g_CustomAudioManager;
|
extern CustomAudioManager g_CustomAudioManager;
|
||||||
|
|
||||||
void InitialiseMilesAudioHooks(HMODULE baseAddress);
|
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
|
#pragma once
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "bansystem.h"
|
#include "bansystem.h"
|
||||||
#include "serverauthentication.h"
|
#include "serverauthentication.h"
|
||||||
|
#include "maxplayers.h"
|
||||||
#include "concommand.h"
|
#include "concommand.h"
|
||||||
#include "miscserverscript.h"
|
#include "r2server.h"
|
||||||
#include <filesystem>
|
#include "r2engine.h"
|
||||||
#include "nsprefix.h"
|
#include "nsprefix.h"
|
||||||
#include <ctime>
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
const char* BANLIST_PATH_SUFFIX = "/banlist.txt";
|
const char* BANLIST_PATH_SUFFIX = "/banlist.txt";
|
||||||
const char BANLIST_COMMENT_CHAR = '#';
|
const char BANLIST_COMMENT_CHAR = '#';
|
||||||
|
|
||||||
ServerBanSystem* g_ServerBanSystem;
|
ServerBanSystem* g_pBanSystem;
|
||||||
|
|
||||||
void ServerBanSystem::OpenBanlist()
|
void ServerBanSystem::OpenBanlist()
|
||||||
{
|
{
|
||||||
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/banlist.txt");
|
std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt");
|
||||||
std::stringstream enabledModsStringStream;
|
|
||||||
|
|
||||||
if (!enabledModsStream.fail())
|
if (!banlistStream.fail())
|
||||||
{
|
{
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(enabledModsStream, line))
|
while (std::getline(banlistStream, line))
|
||||||
{
|
{
|
||||||
// ignore line if first char is # or line is empty
|
// ignore line if first char is # or line is empty
|
||||||
if (line == "" || line.front() == BANLIST_COMMENT_CHAR)
|
if (line == "" || line.front() == BANLIST_COMMENT_CHAR)
|
||||||
|
@ -41,7 +43,7 @@ void ServerBanSystem::OpenBanlist()
|
||||||
m_vBannedUids.push_back(strtoull(uid.c_str(), nullptr, 10));
|
m_vBannedUids.push_back(strtoull(uid.c_str(), nullptr, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledModsStream.close();
|
banlistStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// open write stream for banlist // dont do this to allow for all time access
|
// open write stream for banlist // dont do this to allow for all time access
|
||||||
|
@ -182,15 +184,14 @@ void ConCommand_ban(const CCommand& args)
|
||||||
if (args.ArgC() < 2)
|
if (args.ArgC() < 2)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// assuming maxplayers 32
|
for (int i = 0; i < R2::GetMaxPlayers(); i++)
|
||||||
for (int i = 0; i < 32; i++)
|
|
||||||
{
|
{
|
||||||
void* player = GetPlayerByIndex(i);
|
R2::CBaseClient* player = &R2::g_pClientArray[i];
|
||||||
|
|
||||||
if (!strcmp((char*)player + 0x16, args.Arg(1)) || !strcmp((char*)player + 0xF500, args.Arg(1)))
|
if (!strcmp(player->m_Name, args.Arg(1)) || !strcmp(player->m_UID, args.Arg(1)))
|
||||||
{
|
{
|
||||||
g_ServerBanSystem->BanUID(strtoull((char*)player + 0xF500, nullptr, 10));
|
g_pBanSystem->BanUID(strtoull(player->m_UID, nullptr, 10));
|
||||||
CBaseClient__Disconnect(player, 1, "Banned from server");
|
R2::CBaseClient__Disconnect(player, 1, "Banned from server");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,20 +203,20 @@ void ConCommand_unban(const CCommand& args)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything
|
// assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything
|
||||||
g_ServerBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10));
|
g_pBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConCommand_clearbanlist(const CCommand& args)
|
void ConCommand_clearbanlist(const CCommand& args)
|
||||||
{
|
{
|
||||||
g_ServerBanSystem->ClearBanlist();
|
g_pBanSystem->ClearBanlist();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseBanSystem(HMODULE baseAddress)
|
ON_DLL_LOAD_RELIESON("engine.dll", BanSystem, ConCommand, (CModule module))
|
||||||
{
|
{
|
||||||
g_ServerBanSystem = new ServerBanSystem;
|
g_pBanSystem = new ServerBanSystem;
|
||||||
g_ServerBanSystem->OpenBanlist();
|
g_pBanSystem->OpenBanlist();
|
||||||
|
|
||||||
RegisterConCommand("ban", ConCommand_ban, "bans a given player by uid or name", FCVAR_GAMEDLL);
|
RegisterConCommand("ban", ConCommand_ban, "bans a given player by uid or name", FCVAR_GAMEDLL);
|
||||||
RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_NONE);
|
RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_GAMEDLL);
|
||||||
RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_NONE);
|
RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_GAMEDLL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,4 @@ class ServerBanSystem
|
||||||
bool IsUIDAllowed(uint64_t uid);
|
bool IsUIDAllowed(uint64_t uid);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ServerBanSystem* g_ServerBanSystem;
|
extern ServerBanSystem* g_pBanSystem;
|
||||||
|
|
||||||
void InitialiseBanSystem(HMODULE baseAddress);
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "buildainfile.h"
|
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
#include "hookutils.h"
|
#include "hoststate.h"
|
||||||
|
#include "r2engine.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "nsmem.h"
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
AUTOHOOK_INIT()
|
||||||
|
|
||||||
const int AINET_VERSION_NUMBER = 57;
|
const int AINET_VERSION_NUMBER = 57;
|
||||||
const int AINET_SCRIPT_VERSION_NUMBER = 21;
|
const int AINET_SCRIPT_VERSION_NUMBER = 21;
|
||||||
const int MAP_VERSION_TEMP = 30;
|
|
||||||
const int PLACEHOLDER_CRC = 0;
|
const int PLACEHOLDER_CRC = 0;
|
||||||
const int MAX_HULLS = 5;
|
const int MAX_HULLS = 5;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
struct CAI_NodeLink
|
struct CAI_NodeLink
|
||||||
{
|
{
|
||||||
short srcId;
|
short srcId;
|
||||||
|
@ -24,6 +24,7 @@ struct CAI_NodeLink
|
||||||
char unk2[5];
|
char unk2[5];
|
||||||
int64_t flags;
|
int64_t flags;
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct CAI_NodeLinkDisk
|
struct CAI_NodeLinkDisk
|
||||||
|
@ -33,7 +34,9 @@ struct CAI_NodeLinkDisk
|
||||||
char unk0;
|
char unk0;
|
||||||
bool hulls[MAX_HULLS];
|
bool hulls[MAX_HULLS];
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
struct CAI_Node
|
struct CAI_Node
|
||||||
{
|
{
|
||||||
int index; // not present on disk
|
int index; // not present on disk
|
||||||
|
@ -62,6 +65,7 @@ struct CAI_Node
|
||||||
char unk9[8]; // padding until next bit
|
char unk9[8]; // padding until next bit
|
||||||
char unk10[8]; // should match up to unk6 on disk
|
char unk10[8]; // should match up to unk6 on disk
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
// the way CAI_Nodes are represented in on-disk ain files
|
// the way CAI_Nodes are represented in on-disk ain files
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
|
@ -81,7 +85,9 @@ struct CAI_NodeDisk
|
||||||
short unk5;
|
short unk5;
|
||||||
char unk6[8];
|
char unk6[8];
|
||||||
}; // total size of 68 bytes
|
}; // total size of 68 bytes
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
struct UnkNodeStruct0
|
struct UnkNodeStruct0
|
||||||
{
|
{
|
||||||
int index;
|
int index;
|
||||||
|
@ -106,10 +112,12 @@ struct UnkNodeStruct0
|
||||||
char pad4[132];
|
char pad4[132];
|
||||||
char unk5;
|
char unk5;
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
int* pUnkStruct0Count;
|
int* pUnkStruct0Count;
|
||||||
UnkNodeStruct0*** pppUnkNodeStruct0s;
|
UnkNodeStruct0*** pppUnkNodeStruct0s;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
struct UnkLinkStruct1
|
struct UnkLinkStruct1
|
||||||
{
|
{
|
||||||
short unk0;
|
short unk0;
|
||||||
|
@ -119,10 +127,12 @@ struct UnkLinkStruct1
|
||||||
char unk4;
|
char unk4;
|
||||||
char unk5;
|
char unk5;
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
int* pUnkLinkStruct1Count;
|
int* pUnkLinkStruct1Count;
|
||||||
UnkLinkStruct1*** pppUnkStruct1s;
|
UnkLinkStruct1*** pppUnkStruct1s;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
struct CAI_ScriptNode
|
struct CAI_ScriptNode
|
||||||
{
|
{
|
||||||
float x;
|
float x;
|
||||||
|
@ -130,7 +140,9 @@ struct CAI_ScriptNode
|
||||||
float z;
|
float z;
|
||||||
uint64_t scriptdata;
|
uint64_t scriptdata;
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
struct CAI_Network
|
struct CAI_Network
|
||||||
{
|
{
|
||||||
// +0
|
// +0
|
||||||
|
@ -160,16 +172,16 @@ struct CAI_Network
|
||||||
// +84176
|
// +84176
|
||||||
CAI_Node** nodes;
|
CAI_Node** nodes;
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
char** pUnkServerMapversionGlobal;
|
char** pUnkServerMapversionGlobal;
|
||||||
char* pMapName;
|
|
||||||
|
|
||||||
ConVar* Cvar_ns_ai_dumpAINfileFromLoad;
|
ConVar* Cvar_ns_ai_dumpAINfileFromLoad;
|
||||||
|
|
||||||
void DumpAINInfo(CAI_Network* aiNetwork)
|
void DumpAINInfo(CAI_Network* aiNetwork)
|
||||||
{
|
{
|
||||||
fs::path writePath("r2/maps/graphs");
|
fs::path writePath(fmt::format("{}/maps/graphs", R2::g_pModName));
|
||||||
writePath /= pMapName;
|
writePath /= R2::g_pHostState->m_levelName;
|
||||||
writePath += ".ain";
|
writePath += ".ain";
|
||||||
|
|
||||||
// dump from memory
|
// dump from memory
|
||||||
|
@ -349,20 +361,20 @@ void DumpAINInfo(CAI_Network* aiNetwork)
|
||||||
writeStream.close();
|
writeStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (*CAI_NetworkBuilder__BuildType)(void* builder, CAI_Network* aiNetwork, void* unknown);
|
// clang-format off
|
||||||
CAI_NetworkBuilder__BuildType CAI_NetworkBuilder__Build;
|
AUTOHOOK(CAI_NetworkBuilder__Build, server.dll + 0x385E20,
|
||||||
|
void, __fastcall, (void* builder, CAI_Network* aiNetwork, void* unknown))
|
||||||
void CAI_NetworkBuilder__BuildHook(void* builder, CAI_Network* aiNetwork, void* unknown)
|
// clang-format on
|
||||||
{
|
{
|
||||||
CAI_NetworkBuilder__Build(builder, aiNetwork, unknown);
|
CAI_NetworkBuilder__Build(builder, aiNetwork, unknown);
|
||||||
|
|
||||||
DumpAINInfo(aiNetwork);
|
DumpAINInfo(aiNetwork);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (*LoadAINFileType)(void* aimanager, void* buf, const char* filename);
|
// clang-format off
|
||||||
LoadAINFileType LoadAINFile;
|
AUTOHOOK(LoadAINFile, server.dll + 0x3933A0,
|
||||||
|
void, __fastcall, (void* aimanager, void* buf, const char* filename))
|
||||||
void LoadAINFileHook(void* aimanager, void* buf, const char* filename)
|
// clang-format on
|
||||||
{
|
{
|
||||||
LoadAINFile(aimanager, buf, filename);
|
LoadAINFile(aimanager, buf, filename);
|
||||||
|
|
||||||
|
@ -373,28 +385,16 @@ void LoadAINFileHook(void* aimanager, void* buf, const char* filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseBuildAINFileHooks(HMODULE baseAddress)
|
ON_DLL_LOAD("server.dll", BuildAINFile, (CModule module))
|
||||||
{
|
{
|
||||||
|
AUTOHOOK_DISPATCH()
|
||||||
|
|
||||||
Cvar_ns_ai_dumpAINfileFromLoad = new ConVar(
|
Cvar_ns_ai_dumpAINfileFromLoad = new ConVar(
|
||||||
"ns_ai_dumpAINfileFromLoad", "0", FCVAR_NONE, "For debugging: whether we should dump ain data for ains loaded from disk");
|
"ns_ai_dumpAINfileFromLoad", "0", FCVAR_NONE, "For debugging: whether we should dump ain data for ains loaded from disk");
|
||||||
|
|
||||||
HookEnabler hook;
|
pUnkStruct0Count = module.Offset(0x1063BF8).As<int*>();
|
||||||
ENABLER_CREATEHOOK(
|
pppUnkNodeStruct0s = module.Offset(0x1063BE0).As<UnkNodeStruct0***>();
|
||||||
hook, (char*)baseAddress + 0x385E20, &CAI_NetworkBuilder__BuildHook, reinterpret_cast<LPVOID*>(&CAI_NetworkBuilder__Build));
|
pUnkLinkStruct1Count = module.Offset(0x1063AA8).As<int*>();
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x3933A0, &LoadAINFileHook, reinterpret_cast<LPVOID*>(&LoadAINFile));
|
pppUnkStruct1s = module.Offset(0x1063A90).As<UnkLinkStruct1***>();
|
||||||
|
pUnkServerMapversionGlobal = module.Offset(0xBFBE08).As<char**>();
|
||||||
pUnkStruct0Count = (int*)((char*)baseAddress + 0x1063BF8);
|
|
||||||
pppUnkNodeStruct0s = (UnkNodeStruct0***)((char*)baseAddress + 0x1063BE0);
|
|
||||||
|
|
||||||
pUnkLinkStruct1Count = (int*)((char*)baseAddress + 0x1063AA8);
|
|
||||||
pppUnkStruct1s = (UnkLinkStruct1***)((char*)baseAddress + 0x1063A90);
|
|
||||||
pUnkServerMapversionGlobal = (char**)((char*)baseAddress + 0xBFBE08);
|
|
||||||
pMapName = (char*)baseAddress + 0x1053370;
|
|
||||||
|
|
||||||
uintptr_t base = (uintptr_t)baseAddress;
|
|
||||||
|
|
||||||
// remove a check that prevents a logging function in link generation from working
|
|
||||||
// due to the sheer amount of logging this is a massive perf hit to generation, but spewlog_enable 0 exists so whatever
|
|
||||||
NSMem::NOP(base + 0x3889B6, 6);
|
|
||||||
NSMem::NOP(base + 0x3889BF, 6);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
void InitialiseBuildAINFileHooks(HMODULE baseAddress);
|
|
|
@ -1,13 +1,11 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
#include "concommand.h"
|
#include "concommand.h"
|
||||||
#include "chatcommand.h"
|
|
||||||
#include "localchatwriter.h"
|
#include "localchatwriter.h"
|
||||||
|
|
||||||
// note: isIngameChat is an int64 because the whole register the arg is stored in needs to be 0'd out to work
|
// note: isIngameChat is an int64 because the whole register the arg is stored in needs to be 0'd out to work
|
||||||
// if isIngameChat is false, we use network chat instead
|
// if isIngameChat is false, we use network chat instead
|
||||||
typedef void(__fastcall* ClientSayTextType)(void* a1, const char* message, __int64 isIngameChat, bool isTeamChat);
|
void(__fastcall* ClientSayText)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat);
|
||||||
ClientSayTextType ClientSayText;
|
|
||||||
|
|
||||||
void ConCommand_say(const CCommand& args)
|
void ConCommand_say(const CCommand& args)
|
||||||
{
|
{
|
||||||
|
@ -29,9 +27,9 @@ void ConCommand_log(const CCommand& args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseChatCommands(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientChatCommand, ConCommand, (CModule module))
|
||||||
{
|
{
|
||||||
ClientSayText = (ClientSayTextType)((char*)baseAddress + 0x54780);
|
ClientSayText = module.Offset(0x54780).As<void(__fastcall*)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat)>();
|
||||||
RegisterConCommand("say", ConCommand_say, "Enters a message in public chat", FCVAR_CLIENTDLL);
|
RegisterConCommand("say", ConCommand_say, "Enters a message in public chat", FCVAR_CLIENTDLL);
|
||||||
RegisterConCommand("say_team", ConCommand_say_team, "Enters a message in team chat", FCVAR_CLIENTDLL);
|
RegisterConCommand("say_team", ConCommand_say_team, "Enters a message in team chat", FCVAR_CLIENTDLL);
|
||||||
RegisterConCommand("log", ConCommand_log, "Log a message to the local chat window", FCVAR_CLIENTDLL);
|
RegisterConCommand("log", ConCommand_log, "Log a message to the local chat window", FCVAR_CLIENTDLL);
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
void InitialiseChatCommands(HMODULE baseAddress);
|
|
|
@ -1,12 +1,9 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "clientauthhooks.h"
|
|
||||||
#include "hookutils.h"
|
|
||||||
#include "gameutils.h"
|
|
||||||
#include "masterserver.h"
|
#include "masterserver.h"
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
|
#include "r2client.h"
|
||||||
|
|
||||||
typedef void (*AuthWithStryderType)(void* a1);
|
AUTOHOOK_INIT()
|
||||||
AuthWithStryderType AuthWithStryder;
|
|
||||||
|
|
||||||
ConVar* Cvar_ns_has_agreed_to_send_token;
|
ConVar* Cvar_ns_has_agreed_to_send_token;
|
||||||
|
|
||||||
|
@ -15,51 +12,53 @@ const int NOT_DECIDED_TO_SEND_TOKEN = 0;
|
||||||
const int AGREED_TO_SEND_TOKEN = 1;
|
const int AGREED_TO_SEND_TOKEN = 1;
|
||||||
const int DISAGREED_TO_SEND_TOKEN = 2;
|
const int DISAGREED_TO_SEND_TOKEN = 2;
|
||||||
|
|
||||||
typedef char* (*Auth3PTokenType)();
|
// clang-format off
|
||||||
Auth3PTokenType Auth3PToken;
|
AUTOHOOK(AuthWithStryder, engine.dll + 0x1843A0,
|
||||||
|
void, __fastcall, (void* a1))
|
||||||
char* token_location = 0x0;
|
// clang-format on
|
||||||
|
|
||||||
void AuthWithStryderHook(void* a1)
|
|
||||||
{
|
{
|
||||||
// game will call this forever, until it gets a valid auth key
|
// game will call this forever, until it gets a valid auth key
|
||||||
// so, we need to manually invalidate our key until we're authed with northstar, then we'll allow game to auth with stryder
|
// so, we need to manually invalidate our key until we're authed with northstar, then we'll allow game to auth with stryder
|
||||||
if (!g_MasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN)
|
if (!g_pMasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN)
|
||||||
{
|
{
|
||||||
// if player has agreed to send token and we aren't already authing, try to auth
|
// if player has agreed to send token and we aren't already authing, try to auth
|
||||||
if (Cvar_ns_has_agreed_to_send_token->GetInt() == AGREED_TO_SEND_TOKEN &&
|
if (Cvar_ns_has_agreed_to_send_token->GetInt() == AGREED_TO_SEND_TOKEN &&
|
||||||
!g_MasterServerManager->m_bOriginAuthWithMasterServerInProgress)
|
!g_pMasterServerManager->m_bOriginAuthWithMasterServerInProgress)
|
||||||
g_MasterServerManager->AuthenticateOriginWithMasterServer(g_LocalPlayerUserID, g_LocalPlayerOriginToken);
|
g_pMasterServerManager->AuthenticateOriginWithMasterServer(R2::g_pLocalPlayerUserID, R2::g_pLocalPlayerOriginToken);
|
||||||
|
|
||||||
// invalidate key so auth will fail
|
// invalidate key so auth will fail
|
||||||
*g_LocalPlayerOriginToken = 0;
|
*R2::g_pLocalPlayerOriginToken = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthWithStryder(a1);
|
AuthWithStryder(a1);
|
||||||
}
|
}
|
||||||
|
|
||||||
char* Auth3PTokenHook()
|
char* p3PToken;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(Auth3PToken, engine.dll + 0x183760,
|
||||||
|
char*, __fastcall, ())
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
if (g_MasterServerManager->m_sOwnClientAuthToken[0] != 0)
|
if (g_pMasterServerManager->m_sOwnClientAuthToken[0])
|
||||||
{
|
{
|
||||||
memset(token_location, 0x0, 1024);
|
memset(p3PToken, 0x0, 1024);
|
||||||
strcpy(token_location, "Protocol 3: Protect the Pilot");
|
strcpy(p3PToken, "Protocol 3: Protect the Pilot");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Auth3PToken();
|
return Auth3PToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseClientAuthHooks(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientAuthHooks, ConVar, (CModule module))
|
||||||
{
|
{
|
||||||
|
AUTOHOOK_DISPATCH()
|
||||||
|
|
||||||
|
p3PToken = module.Offset(0x13979D80).As<char*>();
|
||||||
|
|
||||||
// this cvar will save to cfg once initially agreed with
|
// this cvar will save to cfg once initially agreed with
|
||||||
Cvar_ns_has_agreed_to_send_token = new ConVar(
|
Cvar_ns_has_agreed_to_send_token = new ConVar(
|
||||||
"ns_has_agreed_to_send_token",
|
"ns_has_agreed_to_send_token",
|
||||||
"0",
|
"0",
|
||||||
FCVAR_ARCHIVE_PLAYERPROFILE,
|
FCVAR_ARCHIVE_PLAYERPROFILE,
|
||||||
"whether the user has agreed to send their origin token to the northstar masterserver");
|
"whether the user has agreed to send their origin token to the northstar masterserver");
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1843A0, &AuthWithStryderHook, reinterpret_cast<LPVOID*>(&AuthWithStryder));
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x183760, &Auth3PTokenHook, reinterpret_cast<LPVOID*>(&Auth3PToken));
|
|
||||||
token_location = (char*)baseAddress + 0x13979D80;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#pragma once
|
|
||||||
void InitialiseClientAuthHooks(HMODULE baseAddress);
|
|
|
@ -1,29 +1,22 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "clientchathooks.h"
|
|
||||||
#include <rapidjson/document.h>
|
|
||||||
#include "squirrel.h"
|
#include "squirrel.h"
|
||||||
#include "serverchathooks.h"
|
#include "serverchathooks.h"
|
||||||
#include "localchatwriter.h"
|
#include "localchatwriter.h"
|
||||||
|
|
||||||
typedef void(__fastcall* CHudChat__AddGameLineType)(void* self, const char* message, int fromPlayerId, bool isteam, bool isdead);
|
#include <rapidjson/document.h>
|
||||||
CHudChat__AddGameLineType CHudChat__AddGameLine;
|
|
||||||
|
|
||||||
struct ChatTags
|
AUTOHOOK_INIT()
|
||||||
{
|
|
||||||
bool whisper;
|
|
||||||
bool team;
|
|
||||||
bool dead;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void CHudChat__AddGameLineHook(void* self, const char* message, int inboxId, bool isTeam, bool isDead)
|
// clang-format off
|
||||||
|
AUTOHOOK(CHudChat__AddGameLine, client.dll + 0x22E580,
|
||||||
|
void, __fastcall, (void* self, const char* message, int inboxId, bool isTeam, bool isDead))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
// This hook is called for each HUD, but we only want our logic to run once.
|
// This hook is called for each HUD, but we only want our logic to run once.
|
||||||
if (self != *CHudChat::allHuds)
|
if (self != *CHudChat::allHuds)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (g_ClientSquirrelManager->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR)
|
if (g_pSquirrel<ScriptContext::CLIENT>->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR)
|
||||||
{
|
{
|
||||||
int senderId = inboxId & CUSTOM_MESSAGE_INDEX_MASK;
|
int senderId = inboxId & CUSTOM_MESSAGE_INDEX_MASK;
|
||||||
bool isAnonymous = senderId == 0;
|
bool isAnonymous = senderId == 0;
|
||||||
|
@ -38,58 +31,53 @@ static void CHudChat__AddGameLineHook(void* self, const char* message, int inbox
|
||||||
payload = message + 1;
|
payload = message + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_ClientSquirrelManager->pusharg((int)senderId - 1);
|
g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, (int)senderId - 1);
|
||||||
g_ClientSquirrelManager->pusharg(payload);
|
g_pSquirrel<ScriptContext::CLIENT>->pushstring(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, payload);
|
||||||
g_ClientSquirrelManager->pusharg(isTeam);
|
g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isTeam);
|
||||||
g_ClientSquirrelManager->pusharg(isDead);
|
g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isDead);
|
||||||
g_ClientSquirrelManager->pusharg(type);
|
g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, type);
|
||||||
g_ClientSquirrelManager->call(5);
|
g_pSquirrel<ScriptContext::CLIENT>->call(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, 5);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
|
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
|
||||||
{
|
|
||||||
CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead);
|
CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// void NSChatWrite( int context, string str )
|
// void NSChatWrite( int context, string str )
|
||||||
static SQRESULT SQ_ChatWrite(void* sqvm)
|
SQRESULT SQ_ChatWrite(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
int context = ClientSq_getinteger(sqvm, 1);
|
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
|
||||||
const char* str = ClientSq_getstring(sqvm, 2);
|
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
|
||||||
|
|
||||||
LocalChatWriter((LocalChatWriter::Context)context).Write(str);
|
LocalChatWriter((LocalChatWriter::Context)context).Write(str);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// void NSChatWriteRaw( int context, string str )
|
// void NSChatWriteRaw( int context, string str )
|
||||||
static SQRESULT SQ_ChatWriteRaw(void* sqvm)
|
SQRESULT SQ_ChatWriteRaw(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
int context = ClientSq_getinteger(sqvm, 1);
|
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
|
||||||
const char* str = ClientSq_getstring(sqvm, 2);
|
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
|
||||||
|
|
||||||
LocalChatWriter((LocalChatWriter::Context)context).InsertText(str);
|
LocalChatWriter((LocalChatWriter::Context)context).InsertText(str);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// void NSChatWriteLine( int context, string str )
|
// void NSChatWriteLine( int context, string str )
|
||||||
static SQRESULT SQ_ChatWriteLine(void* sqvm)
|
SQRESULT SQ_ChatWriteLine(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
int context = ClientSq_getinteger(sqvm, 1);
|
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
|
||||||
const char* str = ClientSq_getstring(sqvm, 2);
|
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
|
||||||
|
|
||||||
LocalChatWriter((LocalChatWriter::Context)context).WriteLine(str);
|
LocalChatWriter((LocalChatWriter::Context)context).WriteLine(str);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseClientChatHooks(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientChatHooks, ClientSquirrel, (CModule module))
|
||||||
{
|
{
|
||||||
HookEnabler hook;
|
AUTOHOOK_DISPATCH()
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x22E580, &CHudChat__AddGameLineHook, reinterpret_cast<LPVOID*>(&CHudChat__AddGameLine));
|
|
||||||
|
|
||||||
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
|
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
|
||||||
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw);
|
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw);
|
||||||
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine);
|
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "pch.h"
|
|
||||||
#include "serverchathooks.h"
|
|
||||||
|
|
||||||
void InitialiseClientChatHooks(HMODULE baseAddress);
|
|
|
@ -1,13 +1,14 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "clientruihooks.h"
|
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
|
|
||||||
|
AUTOHOOK_INIT()
|
||||||
|
|
||||||
ConVar* Cvar_rui_drawEnable;
|
ConVar* Cvar_rui_drawEnable;
|
||||||
|
|
||||||
typedef char (*DrawRUIFuncType)(void* a1, float* a2);
|
// clang-format off
|
||||||
DrawRUIFuncType DrawRUIFunc;
|
AUTOHOOK(DrawRUIFunc, engine.dll + 0xFC500,
|
||||||
|
bool, __fastcall, (void* a1, float* a2))
|
||||||
char DrawRUIFuncHook(void* a1, float* a2)
|
// clang-format on
|
||||||
{
|
{
|
||||||
if (!Cvar_rui_drawEnable->GetBool())
|
if (!Cvar_rui_drawEnable->GetBool())
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -15,10 +16,9 @@ char DrawRUIFuncHook(void* a1, float* a2)
|
||||||
return DrawRUIFunc(a1, a2);
|
return DrawRUIFunc(a1, a2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseEngineClientRUIHooks(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", RUI, ConVar, (CModule module))
|
||||||
{
|
{
|
||||||
Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn");
|
AUTOHOOK_DISPATCH()
|
||||||
|
|
||||||
HookEnabler hook;
|
Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn");
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xFC500, &DrawRUIFuncHook, reinterpret_cast<LPVOID*>(&DrawRUIFunc));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#pragma once
|
|
||||||
void InitialiseEngineClientRUIHooks(HMODULE baseAddress);
|
|
|
@ -1,21 +1,21 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "clientvideooverrides.h"
|
|
||||||
#include "modmanager.h"
|
#include "modmanager.h"
|
||||||
#include "nsmem.h"
|
|
||||||
|
|
||||||
typedef void* (*BinkOpenType)(const char* path, uint32_t flags);
|
AUTOHOOK_INIT()
|
||||||
BinkOpenType BinkOpen;
|
|
||||||
|
|
||||||
void* BinkOpenHook(const char* path, uint32_t flags)
|
// clang-format off
|
||||||
|
AUTOHOOK_PROCADDRESS(BinkOpen, bink2w64.dll, BinkOpen,
|
||||||
|
void*, __fastcall, (const char* path, uint32_t flags))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
std::string filename(fs::path(path).filename().string());
|
std::string filename(fs::path(path).filename().string());
|
||||||
spdlog::info("BinkOpen {}", filename);
|
spdlog::info("BinkOpen {}", filename);
|
||||||
|
|
||||||
// figure out which mod is handling the bink
|
// figure out which mod is handling the bink
|
||||||
Mod* fileOwner = nullptr;
|
Mod* fileOwner = nullptr;
|
||||||
for (Mod& mod : g_ModManager->m_loadedMods)
|
for (Mod& mod : g_pModManager->m_LoadedMods)
|
||||||
{
|
{
|
||||||
if (!mod.Enabled)
|
if (!mod.m_bEnabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end())
|
if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end())
|
||||||
|
@ -25,23 +25,18 @@ void* BinkOpenHook(const char* path, uint32_t flags)
|
||||||
if (fileOwner)
|
if (fileOwner)
|
||||||
{
|
{
|
||||||
// create new path
|
// create new path
|
||||||
fs::path binkPath(fileOwner->ModDirectory / "media" / filename);
|
fs::path binkPath(fileOwner->m_ModDirectory / "media" / filename);
|
||||||
return BinkOpen(binkPath.string().c_str(), flags);
|
return BinkOpen(binkPath.string().c_str(), flags);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return BinkOpen(path, flags);
|
return BinkOpen(path, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseEngineClientVideoOverrides(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT("client.dll", BinkVideo, (CModule module))
|
||||||
{
|
{
|
||||||
|
AUTOHOOK_DISPATCH()
|
||||||
|
|
||||||
// remove engine check for whether the bik we're trying to load exists in r2/media, as this will fail for biks in mods
|
// remove engine check for whether the bik we're trying to load exists in r2/media, as this will fail for biks in mods
|
||||||
// note: the check in engine is actually unnecessary, so it's just useless in practice and we lose nothing by removing it
|
// note: the check in engine is actually unnecessary, so it's just useless in practice and we lose nothing by removing it
|
||||||
NSMem::NOP((uintptr_t)baseAddress + 0x459AD, 6);
|
module.Offset(0x459AD).NOP(6);
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook,
|
|
||||||
reinterpret_cast<void*>(GetProcAddress(GetModuleHandleA("bink2w64.dll"), "BinkOpen")),
|
|
||||||
&BinkOpenHook,
|
|
||||||
reinterpret_cast<LPVOID*>(&BinkOpen));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#pragma once
|
|
||||||
void InitialiseEngineClientVideoOverrides(HMODULE baseAddress);
|
|
|
@ -1,28 +1,9 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "concommand.h"
|
#include "concommand.h"
|
||||||
#include "gameutils.h"
|
|
||||||
#include "misccommands.h"
|
#include "misccommands.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
typedef void (*ConCommandConstructorType)(
|
|
||||||
ConCommand* newCommand, const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, void* parent);
|
|
||||||
ConCommandConstructorType conCommandConstructor;
|
|
||||||
|
|
||||||
void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags)
|
|
||||||
{
|
|
||||||
spdlog::info("Registering ConCommand {}", name);
|
|
||||||
|
|
||||||
// no need to free this ever really, it should exist as long as game does
|
|
||||||
ConCommand* newCommand = new ConCommand;
|
|
||||||
conCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitialiseConCommands(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
conCommandConstructor = (ConCommandConstructorType)((char*)baseAddress + 0x415F60);
|
|
||||||
AddMiscConCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Purpose: Returns true if this is a command
|
// Purpose: Returns true if this is a command
|
||||||
// Output : bool
|
// Output : bool
|
||||||
|
@ -57,7 +38,7 @@ bool ConCommandBase::IsRegistered(void) const
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
bool ConCommandBase::IsFlagSet(int nFlags) const
|
bool ConCommandBase::IsFlagSet(int nFlags) const
|
||||||
{
|
{
|
||||||
return false; // !TODO: Returning false on every query? (original implementation in Northstar before ConCommandBase refactor)
|
return m_nFlags & nFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -138,3 +119,33 @@ char* ConCommandBase::CopyString(const char* szFrom) const
|
||||||
}
|
}
|
||||||
return szTo;
|
return szTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef void (*ConCommandConstructorType)(
|
||||||
|
ConCommand* newCommand, const char* name, FnCommandCallback_t callback, const char* helpString, int flags, void* parent);
|
||||||
|
ConCommandConstructorType ConCommandConstructor;
|
||||||
|
|
||||||
|
void RegisterConCommand(const char* name, FnCommandCallback_t callback, const char* helpString, int flags)
|
||||||
|
{
|
||||||
|
spdlog::info("Registering ConCommand {}", name);
|
||||||
|
|
||||||
|
// no need to free this ever really, it should exist as long as game does
|
||||||
|
ConCommand* newCommand = new ConCommand;
|
||||||
|
ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterConCommand(
|
||||||
|
const char* name, FnCommandCallback_t callback, const char* helpString, int flags, FnCommandCompletionCallback completionCallback)
|
||||||
|
{
|
||||||
|
spdlog::info("Registering ConCommand {}", name);
|
||||||
|
|
||||||
|
// no need to free this ever really, it should exist as long as game does
|
||||||
|
ConCommand* newCommand = new ConCommand;
|
||||||
|
ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
|
||||||
|
newCommand->m_pCompletionCallback = completionCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD("engine.dll", ConCommand, (CModule module))
|
||||||
|
{
|
||||||
|
ConCommandConstructor = module.Offset(0x415F60).As<ConCommandConstructorType>();
|
||||||
|
AddMiscConCommands();
|
||||||
|
}
|
||||||
|
|
|
@ -72,6 +72,20 @@ inline const char* CCommand::operator[](int nIndex) const
|
||||||
return Arg(nIndex);
|
return Arg(nIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Called when a ConCommand needs to execute
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
typedef void (*FnCommandCallback_t)(const CCommand& command);
|
||||||
|
|
||||||
|
#define COMMAND_COMPLETION_MAXITEMS 64
|
||||||
|
#define COMMAND_COMPLETION_ITEM_LENGTH 128
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Returns 0 to COMMAND_COMPLETION_MAXITEMS worth of completion strings
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
typedef int (*__fastcall FnCommandCompletionCallback)(
|
||||||
|
const char* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]);
|
||||||
|
|
||||||
// From r5reloaded
|
// From r5reloaded
|
||||||
class ConCommandBase
|
class ConCommandBase
|
||||||
{
|
{
|
||||||
|
@ -113,8 +127,8 @@ class ConCommand : public ConCommandBase
|
||||||
void Init(void);
|
void Init(void);
|
||||||
bool IsCommand(void) const;
|
bool IsCommand(void) const;
|
||||||
|
|
||||||
void* m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase.
|
FnCommandCallback_t m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase.
|
||||||
void* m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax').
|
FnCommandCompletionCallback m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax').
|
||||||
int m_nCallbackFlags {}; // 0x0050
|
int m_nCallbackFlags {}; // 0x0050
|
||||||
char pad_0054[4]; // 0x0054
|
char pad_0054[4]; // 0x0054
|
||||||
int unk0; // 0x0058
|
int unk0; // 0x0058
|
||||||
|
@ -122,6 +136,5 @@ class ConCommand : public ConCommandBase
|
||||||
}; // Size: 0x0060
|
}; // Size: 0x0060
|
||||||
|
|
||||||
void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags);
|
void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags);
|
||||||
void InitialiseConCommands(HMODULE baseAddress);
|
void RegisterConCommand(
|
||||||
|
const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, FnCommandCompletionCallback completionCallback);
|
||||||
#define MAKE_CONCMD(name, helpStr, flags, fn) RegisterConCommand(name, fn, helpStr, flags);
|
|
||||||
|
|
|
@ -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 "pch.h"
|
||||||
#include "bits.h"
|
#include "bits.h"
|
||||||
#include "cvar.h"
|
#include "cvar.h"
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
#include "hookutils.h"
|
|
||||||
#include "gameutils.h"
|
|
||||||
#include "sourceinterface.h"
|
#include "sourceinterface.h"
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
typedef void (*ConVarRegisterType)(
|
typedef void (*ConVarRegisterType)(
|
||||||
ConVar* pConVar,
|
ConVar* pConVar,
|
||||||
const char* pszName,
|
const char* pszName,
|
||||||
|
@ -27,25 +25,19 @@ ConVarMallocType conVarMalloc;
|
||||||
void* g_pConVar_Vtable = nullptr;
|
void* g_pConVar_Vtable = nullptr;
|
||||||
void* g_pIConVar_Vtable = nullptr;
|
void* g_pIConVar_Vtable = nullptr;
|
||||||
|
|
||||||
typedef bool (*CvarIsFlagSetType)(ConVar* self, int flags);
|
|
||||||
CvarIsFlagSetType CvarIsFlagSet;
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Purpose: ConVar interface initialization
|
// Purpose: ConVar interface initialization
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void InitialiseConVars(HMODULE baseAddress)
|
ON_DLL_LOAD("engine.dll", ConVar, (CModule module))
|
||||||
{
|
{
|
||||||
conVarMalloc = (ConVarMallocType)((char*)baseAddress + 0x415C20);
|
conVarMalloc = module.Offset(0x415C20).As<ConVarMallocType>();
|
||||||
conVarRegister = (ConVarRegisterType)((char*)baseAddress + 0x417230);
|
conVarRegister = module.Offset(0x417230).As<ConVarRegisterType>();
|
||||||
|
|
||||||
g_pConVar_Vtable = (char*)baseAddress + 0x67FD28;
|
g_pConVar_Vtable = module.Offset(0x67FD28);
|
||||||
g_pIConVar_Vtable = (char*)baseAddress + 0x67FDC8;
|
g_pIConVar_Vtable = module.Offset(0x67FDC8);
|
||||||
|
|
||||||
g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007");
|
R2::g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007");
|
||||||
g_pCVar = *g_pCVarInterface;
|
R2::g_pCVar = *R2::g_pCVarInterface;
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x417FA0, &ConVar::IsFlagSet, reinterpret_cast<LPVOID*>(&CvarIsFlagSet));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -74,7 +66,7 @@ ConVar::ConVar(
|
||||||
float fMin,
|
float fMin,
|
||||||
bool bMax,
|
bool bMax,
|
||||||
float fMax,
|
float fMax,
|
||||||
void* pCallback)
|
FnChangeCallback_t pCallback)
|
||||||
{
|
{
|
||||||
spdlog::info("Registering Convar {}", pszName);
|
spdlog::info("Registering Convar {}", pszName);
|
||||||
|
|
||||||
|
@ -476,16 +468,12 @@ bool ConVar::IsCommand(void) const
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Purpose: Test each ConVar query before setting the value.
|
// Purpose: Test each ConVar query before setting the value.
|
||||||
// Input : *pConVar - nFlags
|
// Input : nFlags
|
||||||
// Output : False if change is permitted, true if not.
|
// Output : False if change is permitted, true if not.
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
bool ConVar::IsFlagSet(ConVar* pConVar, int nFlags)
|
bool ConVar::IsFlagSet(int nFlags) const
|
||||||
{
|
{
|
||||||
// unrestrict FCVAR_DEVELOPMENTONLY and FCVAR_HIDDEN
|
return m_ConCommandBase.m_nFlags & nFlags;
|
||||||
if (pConVar && (nFlags == FCVAR_DEVELOPMENTONLY || nFlags == FCVAR_HIDDEN))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return CvarIsFlagSet(pConVar, nFlags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
|
@ -58,15 +58,58 @@
|
||||||
// ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd.
|
// ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd.
|
||||||
#define FCVAR_SERVER_CANNOT_QUERY \
|
#define FCVAR_SERVER_CANNOT_QUERY \
|
||||||
(1 << 29) // If this is set, then the server is not allowed to query this cvar's value (via IServerPluginHelpers::StartQueryCvarValue).
|
(1 << 29) // If this is set, then the server is not allowed to query this cvar's value (via IServerPluginHelpers::StartQueryCvarValue).
|
||||||
|
|
||||||
|
// !!!NOTE!!! : this is likely incorrect, there are multiple concommands that the vanilla game registers with this flag that 100% should not
|
||||||
|
// be remotely executable i.e. multiple commands that only exist on client (screenshot, joystick_initialize) we now use
|
||||||
|
// FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS in all places this flag was previously used
|
||||||
#define FCVAR_CLIENTCMD_CAN_EXECUTE \
|
#define FCVAR_CLIENTCMD_CAN_EXECUTE \
|
||||||
(1 << 30) // IVEngineClient::ClientCmd is allowed to execute this command.
|
(1 << 30) // IVEngineClient::ClientCmd is allowed to execute this command.
|
||||||
// Note: IVEngineClient::ClientCmd_Unrestricted can run any client command.
|
// Note: IVEngineClient::ClientCmd_Unrestricted can run any client command.
|
||||||
|
|
||||||
#define FCVAR_ACCESSIBLE_FROM_THREADS (1 << 25) // used as a debugging tool necessary to check material system thread convars
|
#define FCVAR_ACCESSIBLE_FROM_THREADS (1 << 25) // used as a debugging tool necessary to check material system thread convars
|
||||||
|
|
||||||
|
// TODO: could be cool to repurpose these for northstar use somehow?
|
||||||
// #define FCVAR_AVAILABLE (1<<26)
|
// #define FCVAR_AVAILABLE (1<<26)
|
||||||
// #define FCVAR_AVAILABLE (1<<27)
|
// #define FCVAR_AVAILABLE (1<<27)
|
||||||
// #define FCVAR_AVAILABLE (1<<31)
|
// #define FCVAR_AVAILABLE (1<<31)
|
||||||
|
|
||||||
|
// flag => string stuff
|
||||||
|
const std::multimap<int, const char*> g_PrintCommandFlags = {
|
||||||
|
{FCVAR_UNREGISTERED, "UNREGISTERED"},
|
||||||
|
{FCVAR_DEVELOPMENTONLY, "DEVELOPMENTONLY"},
|
||||||
|
{FCVAR_GAMEDLL, "GAMEDLL"},
|
||||||
|
{FCVAR_CLIENTDLL, "CLIENTDLL"},
|
||||||
|
{FCVAR_HIDDEN, "HIDDEN"},
|
||||||
|
{FCVAR_PROTECTED, "PROTECTED"},
|
||||||
|
{FCVAR_SPONLY, "SPONLY"},
|
||||||
|
{FCVAR_ARCHIVE, "ARCHIVE"},
|
||||||
|
{FCVAR_NOTIFY, "NOTIFY"},
|
||||||
|
{FCVAR_USERINFO, "USERINFO"},
|
||||||
|
|
||||||
|
// TODO: PRINTABLEONLY and GAMEDLL_FOR_REMOTE_CLIENTS are both 1<<10, one is for vars and one is for commands
|
||||||
|
// this fucking sucks i think
|
||||||
|
{FCVAR_PRINTABLEONLY, "PRINTABLEONLY"},
|
||||||
|
{FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, "GAMEDLL_FOR_REMOTE_CLIENTS"},
|
||||||
|
|
||||||
|
{FCVAR_UNLOGGED, "UNLOGGED"},
|
||||||
|
{FCVAR_NEVER_AS_STRING, "NEVER_AS_STRING"},
|
||||||
|
{FCVAR_REPLICATED, "REPLICATED"},
|
||||||
|
{FCVAR_CHEAT, "CHEAT"},
|
||||||
|
{FCVAR_SS, "SS"},
|
||||||
|
{FCVAR_DEMO, "DEMO"},
|
||||||
|
{FCVAR_DONTRECORD, "DONTRECORD"},
|
||||||
|
{FCVAR_SS_ADDED, "SS_ADDED"},
|
||||||
|
{FCVAR_RELEASE, "RELEASE"},
|
||||||
|
{FCVAR_RELOAD_MATERIALS, "RELOAD_MATERIALS"},
|
||||||
|
{FCVAR_RELOAD_TEXTURES, "RELOAD_TEXTURES"},
|
||||||
|
{FCVAR_NOT_CONNECTED, "NOT_CONNECTED"},
|
||||||
|
{FCVAR_MATERIAL_SYSTEM_THREAD, "MATERIAL_SYSTEM_THREAD"},
|
||||||
|
{FCVAR_ARCHIVE_PLAYERPROFILE, "ARCHIVE_PLAYERPROFILE"},
|
||||||
|
{FCVAR_SERVER_CAN_EXECUTE, "SERVER_CAN_EXECUTE"},
|
||||||
|
{FCVAR_SERVER_CANNOT_QUERY, "SERVER_CANNOT_QUERY"},
|
||||||
|
{FCVAR_CLIENTCMD_CAN_EXECUTE, "UNKNOWN"},
|
||||||
|
{FCVAR_ACCESSIBLE_FROM_THREADS, "ACCESSIBLE_FROM_THREADS"}};
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -74,6 +117,8 @@ class ConCommandBase;
|
||||||
class ConCommand;
|
class ConCommand;
|
||||||
class ConVar;
|
class ConVar;
|
||||||
|
|
||||||
|
typedef void (*FnChangeCallback_t)(ConVar* var, const char* pOldValue, float flOldValue);
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Purpose: A console variable
|
// Purpose: A console variable
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -91,7 +136,7 @@ class ConVar
|
||||||
float fMin,
|
float fMin,
|
||||||
bool bMax,
|
bool bMax,
|
||||||
float fMax,
|
float fMax,
|
||||||
void* pCallback);
|
FnChangeCallback_t pCallback);
|
||||||
~ConVar(void);
|
~ConVar(void);
|
||||||
|
|
||||||
const char* GetBaseName(void) const;
|
const char* GetBaseName(void) const;
|
||||||
|
@ -125,7 +170,7 @@ class ConVar
|
||||||
|
|
||||||
bool IsRegistered(void) const;
|
bool IsRegistered(void) const;
|
||||||
bool IsCommand(void) const;
|
bool IsCommand(void) const;
|
||||||
static bool IsFlagSet(ConVar* pConVar, int nFlags);
|
bool IsFlagSet(int nFlags) const;
|
||||||
|
|
||||||
struct CVValue_t
|
struct CVValue_t
|
||||||
{
|
{
|
||||||
|
@ -145,5 +190,3 @@ class ConVar
|
||||||
void* m_pMalloc {}; // 0x0070
|
void* m_pMalloc {}; // 0x0070
|
||||||
char m_pPad80[10] {}; // 0x0080
|
char m_pPad80[10] {}; // 0x0080
|
||||||
}; // Size: 0x0080
|
}; // Size: 0x0080
|
||||||
|
|
||||||
void InitialiseConVars(HMODULE baseAddress);
|
|
||||||
|
|
|
@ -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;
|
return allConVars;
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceInterface<CCvar>* g_pCVarInterface;
|
// use the R2 namespace for game funcs
|
||||||
CCvar* g_pCVar;
|
namespace R2
|
||||||
|
{
|
||||||
|
SourceInterface<CCvar>* g_pCVarInterface;
|
||||||
|
CCvar* g_pCVar;
|
||||||
|
} // namespace R2
|
||||||
|
|
|
@ -35,5 +35,9 @@ class CCvar
|
||||||
std::unordered_map<std::string, ConCommandBase*> DumpToMap();
|
std::unordered_map<std::string, ConCommandBase*> DumpToMap();
|
||||||
};
|
};
|
||||||
|
|
||||||
extern SourceInterface<CCvar>* g_pCVarInterface;
|
// use the R2 namespace for game funcs
|
||||||
extern CCvar* g_pCVar;
|
namespace R2
|
||||||
|
{
|
||||||
|
extern SourceInterface<CCvar>* g_pCVarInterface;
|
||||||
|
extern CCvar* g_pCVar;
|
||||||
|
} // namespace R2
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "debugoverlay.h"
|
|
||||||
#include "dedicated.h"
|
#include "dedicated.h"
|
||||||
#include "cvar.h"
|
#include "cvar.h"
|
||||||
|
#include "vector.h"
|
||||||
|
|
||||||
struct Vector3
|
AUTOHOOK_INIT()
|
||||||
{
|
|
||||||
float x, y, z;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct QAngle
|
|
||||||
{
|
|
||||||
float x, y, z, w;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OverlayType_t
|
enum OverlayType_t
|
||||||
{
|
{
|
||||||
|
@ -40,7 +32,7 @@ struct OverlayBase_t
|
||||||
int m_nServerCount; // Latch server count, too
|
int m_nServerCount; // Latch server count, too
|
||||||
float m_flEndTime; // When does this box go away
|
float m_flEndTime; // When does this box go away
|
||||||
OverlayBase_t* m_pNextOverlay;
|
OverlayBase_t* m_pNextOverlay;
|
||||||
__int64 m_pUnk;
|
void* m_pUnk;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OverlayLine_t : public OverlayBase_t
|
struct OverlayLine_t : public OverlayBase_t
|
||||||
|
@ -76,37 +68,18 @@ struct OverlayBox_t : public OverlayBase_t
|
||||||
int a;
|
int a;
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is in cvar.h, don't need it here
|
|
||||||
/*class Color
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Color(int r, int g, int b, int a)
|
|
||||||
{
|
|
||||||
_color[0] = (unsigned char)r;
|
|
||||||
_color[1] = (unsigned char)g;
|
|
||||||
_color[2] = (unsigned char)b;
|
|
||||||
_color[3] = (unsigned char)a;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned char _color[4];
|
|
||||||
};*/
|
|
||||||
|
|
||||||
static HMODULE sEngineModule;
|
static HMODULE sEngineModule;
|
||||||
|
|
||||||
typedef void (*DrawOverlayType)(OverlayBase_t* a1);
|
|
||||||
DrawOverlayType DrawOverlay;
|
|
||||||
|
|
||||||
typedef void (*RenderLineType)(Vector3 v1, Vector3 v2, Color c, bool bZBuffer);
|
typedef void (*RenderLineType)(Vector3 v1, Vector3 v2, Color c, bool bZBuffer);
|
||||||
static RenderLineType RenderLine;
|
static RenderLineType RenderLine;
|
||||||
|
|
||||||
typedef void (*RenderBoxType)(Vector3 vOrigin, QAngle angles, Vector3 vMins, Vector3 vMaxs, Color c, bool bZBuffer, bool bInsideOut);
|
typedef void (*RenderBoxType)(Vector3 vOrigin, QAngle angles, Vector3 vMins, Vector3 vMaxs, Color c, bool bZBuffer, bool bInsideOut);
|
||||||
static RenderBoxType RenderBox;
|
static RenderBoxType RenderBox;
|
||||||
|
|
||||||
static RenderBoxType RenderWireframeBox;
|
static RenderBoxType RenderWireframeBox;
|
||||||
|
|
||||||
// engine.dll+0xABCB0
|
// clang-format off
|
||||||
void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay)
|
AUTOHOOK(DrawOverlay, engine.dll + 0xABCB0,
|
||||||
|
void, __fastcall, (OverlayBase_t * pOverlay))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
EnterCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); // s_OverlayMutex
|
EnterCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); // s_OverlayMutex
|
||||||
|
|
||||||
|
@ -151,24 +124,17 @@ void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay)
|
||||||
LeaveCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38));
|
LeaveCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseDebugOverlay(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", DebugOverlay, ConVar, (CModule module))
|
||||||
{
|
{
|
||||||
if (IsDedicatedServer())
|
AUTOHOOK_DISPATCH()
|
||||||
return;
|
|
||||||
|
|
||||||
HookEnabler hook;
|
RenderLine = module.Offset(0x192A70).As<RenderLineType>();
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xABCB0, &DrawOverlayHook, reinterpret_cast<LPVOID*>(&DrawOverlay));
|
RenderBox = module.Offset(0x192520).As<RenderBoxType>();
|
||||||
|
RenderWireframeBox = module.Offset(0x193DA0).As<RenderBoxType>();
|
||||||
RenderLine = reinterpret_cast<RenderLineType>((char*)baseAddress + 0x192A70);
|
sEngineModule = reinterpret_cast<HMODULE>(module.m_nAddress);
|
||||||
|
|
||||||
RenderBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x192520);
|
|
||||||
|
|
||||||
RenderWireframeBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x193DA0);
|
|
||||||
|
|
||||||
sEngineModule = baseAddress;
|
|
||||||
|
|
||||||
// not in g_pCVar->FindVar by this point for whatever reason, so have to get from memory
|
// not in g_pCVar->FindVar by this point for whatever reason, so have to get from memory
|
||||||
ConVar* Cvar_enable_debug_overlays = (ConVar*)((char*)baseAddress + 0x10DB0990);
|
ConVar* Cvar_enable_debug_overlays = module.Offset(0x10DB0990).As<ConVar*>();
|
||||||
Cvar_enable_debug_overlays->SetValue(false);
|
Cvar_enable_debug_overlays->SetValue(false);
|
||||||
Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0";
|
Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0";
|
||||||
Cvar_enable_debug_overlays->AddFlags(FCVAR_CHEAT);
|
Cvar_enable_debug_overlays->AddFlags(FCVAR_CHEAT);
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
void InitialiseDebugOverlay(HMODULE baseAddress);
|
|
|
@ -1,9 +1,16 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "dedicated.h"
|
#include "dedicated.h"
|
||||||
#include "hookutils.h"
|
#include "tier0.h"
|
||||||
#include "gameutils.h"
|
#include "playlist.h"
|
||||||
|
#include "r2engine.h"
|
||||||
|
#include "hoststate.h"
|
||||||
#include "serverauthentication.h"
|
#include "serverauthentication.h"
|
||||||
#include "masterserver.h"
|
#include "masterserver.h"
|
||||||
|
#include "printcommand.h"
|
||||||
|
|
||||||
|
AUTOHOOK_INIT()
|
||||||
|
|
||||||
|
using namespace R2;
|
||||||
|
|
||||||
bool IsDedicatedServer()
|
bool IsDedicatedServer()
|
||||||
{
|
{
|
||||||
|
@ -33,32 +40,23 @@ void Sys_Printf(CDedicatedExports* dedicated, const char* msg)
|
||||||
spdlog::info("[DEDICATED SERVER] {}", msg);
|
spdlog::info("[DEDICATED SERVER] {}", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (*CHostState__InitType)(CHostState* self);
|
|
||||||
|
|
||||||
void RunServer(CDedicatedExports* dedicated)
|
void RunServer(CDedicatedExports* dedicated)
|
||||||
{
|
{
|
||||||
spdlog::info("CDedicatedExports::RunServer(): starting");
|
spdlog::info("CDedicatedExports::RunServer(): starting");
|
||||||
spdlog::info(CommandLine()->GetCmdLine());
|
spdlog::info(Tier0::CommandLine()->GetCmdLine());
|
||||||
|
|
||||||
// initialise engine
|
// initialise engine
|
||||||
g_pEngine->Frame();
|
g_pEngine->Frame();
|
||||||
|
|
||||||
// add +map if not present
|
// add +map if not present
|
||||||
// don't manually execute this from cbuf as users may have it in their startup args anyway, easier just to run from stuffcmds if present
|
// don't manually execute this from cbuf as users may have it in their startup args anyway, easier just to run from stuffcmds if present
|
||||||
if (!CommandLine()->CheckParm("+map"))
|
if (!Tier0::CommandLine()->CheckParm("+map"))
|
||||||
CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString());
|
Tier0::CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString());
|
||||||
|
|
||||||
// run server autoexec and re-run commandline
|
// re-run commandline
|
||||||
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode);
|
|
||||||
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "stuffcmds", cmd_source_t::kCommandSrcCode);
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "stuffcmds", cmd_source_t::kCommandSrcCode);
|
||||||
Cbuf_Execute();
|
Cbuf_Execute();
|
||||||
|
|
||||||
// ensure playlist initialises right, if we've not explicitly called setplaylist
|
|
||||||
SetCurrentPlaylist(GetCurrentPlaylistName());
|
|
||||||
|
|
||||||
// note: we no longer manually set map and hoststate to start server in g_pHostState, we just use +map which seems to initialise stuff
|
|
||||||
// better
|
|
||||||
|
|
||||||
// get tickinterval
|
// get tickinterval
|
||||||
ConVar* Cvar_base_tickinterval_mp = g_pCVar->FindVar("base_tickinterval_mp");
|
ConVar* Cvar_base_tickinterval_mp = g_pCVar->FindVar("base_tickinterval_mp");
|
||||||
|
|
||||||
|
@ -66,43 +64,31 @@ void RunServer(CDedicatedExports* dedicated)
|
||||||
double frameTitle = 0;
|
double frameTitle = 0;
|
||||||
while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING)
|
while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING)
|
||||||
{
|
{
|
||||||
double frameStart = Plat_FloatTime();
|
double frameStart = Tier0::Plat_FloatTime();
|
||||||
g_pEngine->Frame();
|
g_pEngine->Frame();
|
||||||
|
|
||||||
// only update the title after at least 500ms since the last update
|
|
||||||
if ((frameStart - frameTitle) > 0.5)
|
|
||||||
{
|
|
||||||
frameTitle = frameStart;
|
|
||||||
|
|
||||||
// this way of getting playercount/maxplayers honestly really sucks, but not got any other methods of doing it rn
|
|
||||||
const char* maxPlayers = GetCurrentPlaylistVar("max_players", false);
|
|
||||||
if (!maxPlayers)
|
|
||||||
maxPlayers = "6";
|
|
||||||
|
|
||||||
SetConsoleTitleA(fmt::format(
|
|
||||||
"{} - {} {}/{} players ({})",
|
|
||||||
g_MasterServerManager->m_sUnicodeServerName,
|
|
||||||
g_pHostState->m_levelName,
|
|
||||||
g_ServerAuthenticationManager->m_additionalPlayerData.size(),
|
|
||||||
maxPlayers,
|
|
||||||
GetCurrentPlaylistName())
|
|
||||||
.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::duration<double, std::ratio<1>>(
|
std::this_thread::sleep_for(std::chrono::duration<double, std::ratio<1>>(
|
||||||
Cvar_base_tickinterval_mp->GetFloat() - fmin(Plat_FloatTime() - frameStart, 0.25)));
|
Cvar_base_tickinterval_mp->GetFloat() - fmin(Tier0::Plat_FloatTime() - frameStart, 0.25)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef bool (*IsGameActiveWindowType)();
|
// use server presence to update window title
|
||||||
IsGameActiveWindowType IsGameActiveWindow;
|
class DedicatedConsoleServerPresence : public ServerPresenceReporter
|
||||||
bool IsGameActiveWindowHook()
|
|
||||||
{
|
{
|
||||||
return true;
|
void ReportPresence(const ServerPresence* pServerPresence) override
|
||||||
}
|
{
|
||||||
|
SetConsoleTitleA(fmt::format(
|
||||||
|
"{} - {} {}/{} players ({})",
|
||||||
|
pServerPresence->m_sServerName,
|
||||||
|
pServerPresence->m_MapName,
|
||||||
|
pServerPresence->m_iPlayerCount,
|
||||||
|
pServerPresence->m_iMaxPlayers,
|
||||||
|
pServerPresence->m_PlaylistName)
|
||||||
|
.c_str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
HANDLE consoleInputThreadHandle = NULL;
|
HANDLE consoleInputThreadHandle = NULL;
|
||||||
|
|
||||||
DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
|
DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
|
||||||
{
|
{
|
||||||
while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN)
|
while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN)
|
||||||
|
@ -121,139 +107,106 @@ DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
|
||||||
{
|
{
|
||||||
input += "\n";
|
input += "\n";
|
||||||
Cbuf_AddText(Cbuf_GetCurrentPlayer(), input.c_str(), cmd_source_t::kCommandSrcCode);
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), input.c_str(), cmd_source_t::kCommandSrcCode);
|
||||||
|
TryPrintCvarHelpForCommand(input.c_str()); // this needs to be done on main thread, unstable in this one
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "nsmem.h"
|
// clang-format off
|
||||||
void InitialiseDedicated(HMODULE engineAddress)
|
AUTOHOOK(IsGameActiveWindow, engine.dll + 0x1CDC80,
|
||||||
|
bool,, ())
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD_DEDI_RELIESON("engine.dll", DedicatedServer, ServerPresence, (CModule module))
|
||||||
{
|
{
|
||||||
spdlog::info("InitialiseDedicated");
|
spdlog::info("InitialiseDedicated");
|
||||||
|
|
||||||
uintptr_t ea = (uintptr_t)engineAddress;
|
AUTOHOOK_DISPATCH_MODULE("engine.dll")
|
||||||
|
|
||||||
{
|
// Host_Init
|
||||||
// Host_Init
|
// prevent a particle init that relies on client dll
|
||||||
// prevent a particle init that relies on client dll
|
module.Offset(0x156799).NOP(5);
|
||||||
NSMem::NOP(ea + 0x156799, 5);
|
|
||||||
}
|
// Host_Init
|
||||||
|
// don't call Key_Init to avoid loading some extra rsons from rpak (will be necessary to boot if we ever wanna disable rpaks entirely on
|
||||||
|
// dedi)
|
||||||
|
module.Offset(0x1565B0).NOP(5);
|
||||||
|
|
||||||
{
|
{
|
||||||
// CModAppSystemGroup::Create
|
// CModAppSystemGroup::Create
|
||||||
// force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment
|
// force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment
|
||||||
auto ptr = ea + 0x1C4EBD;
|
MemoryAddress base = module.Offset(0x1C4EBD);
|
||||||
|
|
||||||
// cmp => mov
|
// cmp => mov
|
||||||
NSMem::BytePatch(ptr + 1, "C6 87");
|
base.Offset(1).Patch("C6 87");
|
||||||
|
|
||||||
// 00 => 01
|
// 00 => 01
|
||||||
NSMem::BytePatch(ptr + 7, "01");
|
base.Offset(7).Patch("01");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// Some init that i'm not sure of that crashes
|
||||||
// Some init that i'm not sure of that crashes
|
// nop the call to it
|
||||||
// nop the call to it
|
module.Offset(0x156A63).NOP(5);
|
||||||
NSMem::NOP(ea + 0x156A63, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
// runframeserver
|
||||||
// runframeserver
|
// nop some access violations
|
||||||
// nop some access violations
|
module.Offset(0x159819).NOP(17);
|
||||||
NSMem::NOP(ea + 0x159819, 17);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
module.Offset(0x156B4C).NOP(7);
|
||||||
NSMem::NOP(ea + 0x156B4C, 7);
|
|
||||||
|
|
||||||
// previously patched these, took me a couple weeks to figure out they were the issue
|
// previously patched these, took me a couple weeks to figure out they were the issue
|
||||||
// removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address
|
// removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address
|
||||||
// so uhh, don't do that
|
// so uhh, don't do that
|
||||||
// NSMem::NOP(ea + 0x156B4C + 7, 8);
|
// NSMem::NOP(ea + 0x156B4C + 7, 8);
|
||||||
|
module.Offset(0x156B4C).Offset(15).NOP(9);
|
||||||
|
|
||||||
NSMem::NOP(ea + 0x156B4C + 15, 9);
|
// HostState_State_NewGame
|
||||||
}
|
// nop an access violation
|
||||||
|
module.Offset(0xB934C).NOP(9);
|
||||||
|
|
||||||
{
|
// CEngineAPI::Connect
|
||||||
// HostState_State_NewGame
|
// remove call to Shader_Connect
|
||||||
// nop an access violation
|
module.Offset(0x1C4D7D).NOP(5);
|
||||||
NSMem::NOP(ea + 0xB934C, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
// Host_Init
|
||||||
// CEngineAPI::Connect
|
// remove call to ui loading stuff
|
||||||
// remove call to Shader_Connect
|
module.Offset(0x156595).NOP(5);
|
||||||
NSMem::NOP(ea + 0x1C4D7D, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// currently does not work, crashes stuff, likely gotta keep this here
|
// some function that gets called from RunFrameServer
|
||||||
//{
|
// nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway
|
||||||
// // CEngineAPI::Connect
|
module.Offset(0x15A0BB).NOP(5);
|
||||||
// // remove calls to register ui rpak asset types
|
|
||||||
// NSMem::NOP(ea + 0x1C4E07, 5);
|
|
||||||
//}
|
|
||||||
|
|
||||||
{
|
// RunFrameServer
|
||||||
// Host_Init
|
// nop a function that access violations
|
||||||
// remove call to ui loading stuff
|
module.Offset(0x159BF3).NOP(5);
|
||||||
NSMem::NOP(ea + 0x156595, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
// func that checks if origin is inited
|
||||||
// some function that gets called from RunFrameServer
|
// always return 1
|
||||||
// nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway
|
module.Offset(0x183B70).Patch("B0 01 C3"); // mov al,01 ret
|
||||||
NSMem::NOP(ea + 0x15A0BB, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
// HostState_State_ChangeLevel
|
||||||
// RunFrameServer
|
// nop clientinterface call
|
||||||
// nop a function that access violations
|
module.Offset(0x1552ED).NOP(16);
|
||||||
NSMem::NOP(ea + 0x159BF3, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
// HostState_State_ChangeLevel
|
||||||
// func that checks if origin is inited
|
// nop clientinterface call
|
||||||
// always return 1
|
module.Offset(0x155363).NOP(16);
|
||||||
NSMem::BytePatch(
|
|
||||||
ea + 0x183B70,
|
|
||||||
{
|
|
||||||
0xB0,
|
|
||||||
0x01, // mov al,01
|
|
||||||
0xC3 // ret
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
// IVideoMode::CreateGameWindow
|
||||||
// HostState_State_ChangeLevel
|
// nop call to ShowWindow
|
||||||
// nop clientinterface call
|
module.Offset(0x1CD146).NOP(5);
|
||||||
NSMem::NOP(ea + 0x1552ED, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// HostState_State_ChangeLevel
|
|
||||||
// nop clientinterface call
|
|
||||||
NSMem::NOP(ea + 0x155363, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
// note: previously had DisableDedicatedWindowCreation patches here, but removing those rn since they're all shit and unstable and bad
|
|
||||||
// and such check commit history if any are needed for reimplementation
|
|
||||||
{
|
|
||||||
// IVideoMode::CreateGameWindow
|
|
||||||
// nop call to ShowWindow
|
|
||||||
NSMem::NOP(ea + 0x1CD146, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
CDedicatedExports* dedicatedExports = new CDedicatedExports;
|
CDedicatedExports* dedicatedExports = new CDedicatedExports;
|
||||||
dedicatedExports->vtable = dedicatedExports;
|
dedicatedExports->vtable = dedicatedExports;
|
||||||
dedicatedExports->Sys_Printf = Sys_Printf;
|
dedicatedExports->Sys_Printf = Sys_Printf;
|
||||||
dedicatedExports->RunServer = RunServer;
|
dedicatedExports->RunServer = RunServer;
|
||||||
|
|
||||||
CDedicatedExports** exports = (CDedicatedExports**)((char*)engineAddress + 0x13F0B668);
|
*module.Offset(0x13F0B668).As<CDedicatedExports**>() = dedicatedExports;
|
||||||
*exports = dedicatedExports;
|
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)engineAddress + 0x1CDC80, &IsGameActiveWindowHook, reinterpret_cast<LPVOID*>(&IsGameActiveWindow));
|
|
||||||
|
|
||||||
// extra potential patches:
|
// extra potential patches:
|
||||||
// nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow
|
// nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow
|
||||||
|
@ -265,15 +218,19 @@ void InitialiseDedicated(HMODULE engineAddress)
|
||||||
// make sure it still gets registered
|
// make sure it still gets registered
|
||||||
|
|
||||||
// add cmdline args that are good for dedi
|
// add cmdline args that are good for dedi
|
||||||
CommandLine()->AppendParm("-nomenuvid", 0);
|
Tier0::CommandLine()->AppendParm("-nomenuvid", 0);
|
||||||
CommandLine()->AppendParm("-nosound", 0);
|
Tier0::CommandLine()->AppendParm("-nosound", 0);
|
||||||
CommandLine()->AppendParm("-windowed", 0);
|
Tier0::CommandLine()->AppendParm("-windowed", 0);
|
||||||
CommandLine()->AppendParm("-nomessagebox", 0);
|
Tier0::CommandLine()->AppendParm("-nomessagebox", 0);
|
||||||
CommandLine()->AppendParm("+host_preload_shaders", "0");
|
Tier0::CommandLine()->AppendParm("+host_preload_shaders", "0");
|
||||||
CommandLine()->AppendParm("+net_usesocketsforloopback", "1");
|
Tier0::CommandLine()->AppendParm("+net_usesocketsforloopback", "1");
|
||||||
|
|
||||||
|
// use presence reporter for console title
|
||||||
|
DedicatedConsoleServerPresence* presenceReporter = new DedicatedConsoleServerPresence;
|
||||||
|
g_pServerPresence->AddPresenceReporter(presenceReporter);
|
||||||
|
|
||||||
// Disable Quick Edit mode to reduce chance of user unintentionally hanging their server by selecting something.
|
// Disable Quick Edit mode to reduce chance of user unintentionally hanging their server by selecting something.
|
||||||
if (!CommandLine()->CheckParm("-bringbackquickedit"))
|
if (!Tier0::CommandLine()->CheckParm("-bringbackquickedit"))
|
||||||
{
|
{
|
||||||
HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
|
HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
DWORD mode = 0;
|
DWORD mode = 0;
|
||||||
|
@ -295,35 +252,42 @@ void InitialiseDedicated(HMODULE engineAddress)
|
||||||
spdlog::info("Quick Edit enabled by user request");
|
spdlog::info("Quick Edit enabled by user request");
|
||||||
|
|
||||||
// create console input thread
|
// create console input thread
|
||||||
if (!CommandLine()->CheckParm("-noconsoleinput"))
|
if (!Tier0::CommandLine()->CheckParm("-noconsoleinput"))
|
||||||
consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL);
|
consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL);
|
||||||
else
|
else
|
||||||
spdlog::info("Console input disabled by user request");
|
spdlog::info("Console input disabled by user request");
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseDedicatedOrigin(HMODULE baseAddress)
|
ON_DLL_LOAD_DEDI("tier0.dll", DedicatedServerOrigin, (CModule module))
|
||||||
{
|
{
|
||||||
// disable origin on dedicated
|
// disable origin on dedicated
|
||||||
// for any big ea lawyers, this can't be used to play the game without origin, game will throw a fit if you try to do anything without
|
// for any big ea lawyers, this can't be used to play the game without origin, game will throw a fit if you try to do anything without
|
||||||
// an origin id as a client for dedi it's fine though, game doesn't care if origin is disabled as long as there's only a server
|
// an origin id as a client for dedi it's fine though, game doesn't care if origin is disabled as long as there's only a server
|
||||||
|
module.GetExport("Tier0_InitOrigin").Patch("C3");
|
||||||
NSMem::BytePatch(
|
|
||||||
(uintptr_t)GetProcAddress(GetModuleHandleA("tier0.dll"), "Tier0_InitOrigin"),
|
|
||||||
{
|
|
||||||
0xC3 // ret
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (*PrintFatalSquirrelErrorType)(void* sqvm);
|
// clang-format off
|
||||||
PrintFatalSquirrelErrorType PrintFatalSquirrelError;
|
AUTOHOOK(PrintSquirrelError, server.dll + 0x794D0,
|
||||||
void PrintFatalSquirrelErrorHook(void* sqvm)
|
void, __fastcall, (void* sqvm))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
PrintFatalSquirrelError(sqvm);
|
PrintSquirrelError(sqvm);
|
||||||
g_pEngine->m_nQuitting = EngineQuitState::QUIT_TODESKTOP;
|
|
||||||
|
// close dedicated server if a fatal error is hit
|
||||||
|
static ConVar* Cvar_fatal_script_errors = g_pCVar->FindVar("fatal_script_errors");
|
||||||
|
if (Cvar_fatal_script_errors->GetBool())
|
||||||
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseDedicatedServerGameDLL(HMODULE baseAddress)
|
ON_DLL_LOAD_DEDI("server.dll", DedicatedServerGameDLL, (CModule module))
|
||||||
{
|
{
|
||||||
HookEnabler hook;
|
AUTOHOOK_DISPATCH_MODULE(server.dll)
|
||||||
ENABLER_CREATEHOOK(hook, baseAddress + 0x794D0, &PrintFatalSquirrelErrorHook, reinterpret_cast<LPVOID*>(&PrintFatalSquirrelError));
|
|
||||||
|
if (Tier0::CommandLine()->CheckParm("-nopakdedi"))
|
||||||
|
{
|
||||||
|
module.Offset(0x6BA350).Patch("C3"); // dont load skins.rson from rpak if we don't have rpaks, as loading it will cause a crash
|
||||||
|
module.Offset(0x6BA300).Patch(
|
||||||
|
"B8 C8 00 00 00 C3"); // return 200 as the number of skins from server.dll + 6BA300, this is the normal value read from
|
||||||
|
// skins.rson and should be updated when we need it more modular
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
bool IsDedicatedServer();
|
bool IsDedicatedServer();
|
||||||
|
|
||||||
void InitialiseDedicated(HMODULE moduleAddress);
|
|
||||||
void InitialiseDedicatedOrigin(HMODULE baseAddress);
|
|
||||||
void InitialiseDedicatedServerGameDLL(HMODULE baseAddress);
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "dedicated.h"
|
#include "dedicated.h"
|
||||||
#include "dedicatedmaterialsystem.h"
|
#include "tier0.h"
|
||||||
#include "hookutils.h"
|
|
||||||
#include "gameutils.h"
|
|
||||||
#include "nsmem.h"
|
|
||||||
|
|
||||||
typedef HRESULT (*__stdcall D3D11CreateDeviceType)(
|
AUTOHOOK_INIT()
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(D3D11CreateDevice, materialsystem_dx11.dll + 0xD9A0E,
|
||||||
|
HRESULT, __stdcall, (
|
||||||
void* pAdapter,
|
void* pAdapter,
|
||||||
int DriverType,
|
int DriverType,
|
||||||
HMODULE Software,
|
HMODULE Software,
|
||||||
|
@ -15,108 +16,26 @@ typedef HRESULT (*__stdcall D3D11CreateDeviceType)(
|
||||||
UINT SDKVersion,
|
UINT SDKVersion,
|
||||||
void** ppDevice,
|
void** ppDevice,
|
||||||
int* pFeatureLevel,
|
int* pFeatureLevel,
|
||||||
void** ppImmediateContext);
|
void** ppImmediateContext))
|
||||||
D3D11CreateDeviceType D3D11CreateDevice;
|
// clang-format on
|
||||||
|
|
||||||
HRESULT __stdcall D3D11CreateDeviceHook(
|
|
||||||
void* pAdapter,
|
|
||||||
int DriverType,
|
|
||||||
HMODULE Software,
|
|
||||||
UINT Flags,
|
|
||||||
int* pFeatureLevels,
|
|
||||||
UINT FeatureLevels,
|
|
||||||
UINT SDKVersion,
|
|
||||||
void** ppDevice,
|
|
||||||
int* pFeatureLevel,
|
|
||||||
void** ppImmediateContext)
|
|
||||||
{
|
{
|
||||||
// note: this is super duper temp pretty much just messing around with it
|
// note: this is super duper temp pretty much just messing around with it
|
||||||
// does run surprisingly well on dedi for a software driver tho if you ignore the +1gb ram usage at times, seems like dedi doesn't
|
// does run surprisingly well on dedi for a software driver tho if you ignore the +1gb ram usage at times, seems like dedi doesn't
|
||||||
// really call gpu much even with renderthread still being a thing will be using this hook for actual d3d stubbing and stuff later
|
// really call gpu much even with renderthread still being a thing will be using this hook for actual d3d stubbing and stuff later
|
||||||
|
|
||||||
// atm, i think the play might be to run d3d in software, and then just stub out any calls that allocate memory/use alot of resources
|
|
||||||
// (e.g. createtexture and that sorta thing)
|
|
||||||
// note: this has been succeeded by the d3d11 and gfsdk stubs, and is only being kept around for posterity and as a fallback option
|
// note: this has been succeeded by the d3d11 and gfsdk stubs, and is only being kept around for posterity and as a fallback option
|
||||||
if (CommandLine()->CheckParm("-softwared3d11"))
|
if (Tier0::CommandLine()->CheckParm("-softwared3d11"))
|
||||||
DriverType = 5; // D3D_DRIVER_TYPE_WARP
|
DriverType = 5; // D3D_DRIVER_TYPE_WARP
|
||||||
|
|
||||||
return D3D11CreateDevice(
|
return D3D11CreateDevice(
|
||||||
pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext);
|
pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseDedicatedMaterialSystem(HMODULE baseAddress)
|
ON_DLL_LOAD_DEDI("materialsystem_dx11.dll", DedicatedServerMaterialSystem, (CModule module))
|
||||||
{
|
{
|
||||||
HookEnabler hook;
|
AUTOHOOK_DISPATCH()
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xD9A0E, &D3D11CreateDeviceHook, reinterpret_cast<LPVOID*>(&D3D11CreateDevice));
|
|
||||||
|
|
||||||
// not using these for now since they're related to nopping renderthread/gamewindow i.e. very hard
|
// CMaterialSystem::FindMaterial
|
||||||
//{
|
// make the game always use the error material
|
||||||
// // function that launches renderthread
|
module.Offset(0x5F0F1).Patch("E9 34 03 00");
|
||||||
// char* ptr = (char*)baseAddress + 0x87047;
|
|
||||||
// TempReadWrite rw(ptr);
|
|
||||||
//
|
|
||||||
// // make it not launch renderthread
|
|
||||||
// *ptr = (char)0x90;
|
|
||||||
// *(ptr + 1) = (char)0x90;
|
|
||||||
// *(ptr + 2) = (char)0x90;
|
|
||||||
// *(ptr + 3) = (char)0x90;
|
|
||||||
// *(ptr + 4) = (char)0x90;
|
|
||||||
// *(ptr + 5) = (char)0x90;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//{
|
|
||||||
// // some function that waits on renderthread job
|
|
||||||
// char* ptr = (char*)baseAddress + 0x87d00;
|
|
||||||
// TempReadWrite rw(ptr);
|
|
||||||
//
|
|
||||||
// // return immediately
|
|
||||||
// *ptr = (char)0xC3;
|
|
||||||
//}
|
|
||||||
|
|
||||||
{
|
|
||||||
// CMaterialSystem::FindMaterial
|
|
||||||
// make the game always use the error material
|
|
||||||
NSMem::BytePatch((uintptr_t)baseAddress + 0x5F0F1, {0xE9, 0x34, 0x03, 0x00});
|
|
||||||
}
|
|
||||||
|
|
||||||
// previously had DisableDedicatedWindowCreation stuff here, removing for now since shit and unstable
|
|
||||||
// check commit history if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void* (*PakLoadAPI__LoadRpakType)(char* filename, void* unknown, int flags);
|
|
||||||
PakLoadAPI__LoadRpakType PakLoadAPI__LoadRpak;
|
|
||||||
|
|
||||||
void* PakLoadAPI__LoadRpakHook(char* filename, void* unknown, int flags)
|
|
||||||
{
|
|
||||||
spdlog::info("PakLoadAPI__LoadRpakHook {}", filename);
|
|
||||||
|
|
||||||
// on dedi, don't load any paks that aren't required
|
|
||||||
if (strncmp(filename, "common", 6))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return PakLoadAPI__LoadRpak(filename, unknown, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void* (*PakLoadAPI__LoadRpak2Type)(char* filename, void* unknown, int flags, void* callback, void* callback2);
|
|
||||||
PakLoadAPI__LoadRpak2Type PakLoadAPI__LoadRpak2;
|
|
||||||
|
|
||||||
void* PakLoadAPI__LoadRpak2Hook(char* filename, void* unknown, int flags, void* callback, void* callback2)
|
|
||||||
{
|
|
||||||
spdlog::info("PakLoadAPI__LoadRpak2Hook {}", filename);
|
|
||||||
|
|
||||||
// on dedi, don't load any paks that aren't required
|
|
||||||
if (strncmp(filename, "common", 6))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return PakLoadAPI__LoadRpak2(filename, unknown, flags, callback, callback2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitialiseDedicatedRtechGame(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
baseAddress = GetModuleHandleA("rtech_game.dll");
|
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
// unfortunately this is unstable, seems to freeze when changing maps
|
|
||||||
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB0F0, &PakLoadAPI__LoadRpakHook, reinterpret_cast<LPVOID*>(&PakLoadAPI__LoadRpak));
|
|
||||||
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB170, &PakLoadAPI__LoadRpak2Hook, reinterpret_cast<LPVOID*>(&PakLoadAPI__LoadRpak2));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 "pch.h"
|
||||||
#include "hooks.h"
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "squirrel.h"
|
|
||||||
#include "dedicated.h"
|
|
||||||
#include "dedicatedmaterialsystem.h"
|
|
||||||
#include "sourceconsole.h"
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include "concommand.h"
|
#include "crashhandler.h"
|
||||||
#include "modmanager.h"
|
|
||||||
#include "filesystem.h"
|
|
||||||
#include "serverauthentication.h"
|
|
||||||
#include "scriptmodmenu.h"
|
|
||||||
#include "scriptserverbrowser.h"
|
|
||||||
#include "keyvalues.h"
|
|
||||||
#include "masterserver.h"
|
|
||||||
#include "gameutils.h"
|
|
||||||
#include "chatcommand.h"
|
|
||||||
#include "modlocalisation.h"
|
|
||||||
#include "playlist.h"
|
|
||||||
#include "miscserverscript.h"
|
|
||||||
#include "clientauthhooks.h"
|
|
||||||
#include "latencyflex.h"
|
|
||||||
#include "scriptbrowserhooks.h"
|
|
||||||
#include "scriptmainmenupromos.h"
|
|
||||||
#include "miscclientfixes.h"
|
|
||||||
#include "miscserverfixes.h"
|
|
||||||
#include "rpakfilesystem.h"
|
|
||||||
#include "bansystem.h"
|
|
||||||
#include "memalloc.h"
|
#include "memalloc.h"
|
||||||
#include "maxplayers.h"
|
|
||||||
#include "languagehooks.h"
|
|
||||||
#include "audio.h"
|
|
||||||
#include "buildainfile.h"
|
|
||||||
#include "nsprefix.h"
|
#include "nsprefix.h"
|
||||||
#include "serverchathooks.h"
|
|
||||||
#include "clientchathooks.h"
|
|
||||||
#include "localchatwriter.h"
|
|
||||||
#include "scriptservertoclientstringcommand.h"
|
|
||||||
#include "plugin_abi.h"
|
#include "plugin_abi.h"
|
||||||
#include "plugins.h"
|
#include "plugins.h"
|
||||||
#include "debugoverlay.h"
|
|
||||||
#include "clientvideooverrides.h"
|
|
||||||
#include "clientruihooks.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "scriptutility.h"
|
|
||||||
|
|
||||||
#include "rapidjson/document.h"
|
#include "rapidjson/document.h"
|
||||||
#include "rapidjson/stringbuffer.h"
|
#include "rapidjson/stringbuffer.h"
|
||||||
#include "rapidjson/writer.h"
|
#include "rapidjson/writer.h"
|
||||||
#include "rapidjson/error/en.h"
|
#include "rapidjson/error/en.h"
|
||||||
#include "exploitfixes.h"
|
|
||||||
#include "scriptjson.h"
|
#include <string.h>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject));
|
typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject));
|
||||||
|
|
||||||
bool initialised = false;
|
|
||||||
|
|
||||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||||
{
|
{
|
||||||
switch (ul_reason_for_call)
|
switch (ul_reason_for_call)
|
||||||
|
@ -72,18 +33,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaitForDebugger(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
// earlier waitfordebugger call than is in vanilla, just so we can debug stuff a little easier
|
|
||||||
if (CommandLine()->CheckParm("-waitfordebugger"))
|
|
||||||
{
|
|
||||||
spdlog::info("waiting for debugger...");
|
|
||||||
|
|
||||||
while (!IsDebuggerPresent())
|
|
||||||
Sleep(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void freeLibrary(HMODULE hLib)
|
void freeLibrary(HMODULE hLib)
|
||||||
{
|
{
|
||||||
if (!FreeLibrary(hLib))
|
if (!FreeLibrary(hLib))
|
||||||
|
@ -190,15 +139,13 @@ bool LoadPlugins()
|
||||||
|
|
||||||
bool InitialiseNorthstar()
|
bool InitialiseNorthstar()
|
||||||
{
|
{
|
||||||
if (initialised)
|
static bool bInitialised = false;
|
||||||
{
|
if (bInitialised)
|
||||||
// spdlog::warn("Called InitialiseNorthstar more than once!"); // it's actually 100% fine for that to happen
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
initialised = true;
|
bInitialised = true;
|
||||||
|
|
||||||
parseConfigurables();
|
InitialiseNorthstarPrefix();
|
||||||
InitialiseVersion();
|
InitialiseVersion();
|
||||||
|
|
||||||
// Fix some users' failure to connect to respawn datacenters
|
// Fix some users' failure to connect to respawn datacenters
|
||||||
|
@ -206,6 +153,7 @@ bool InitialiseNorthstar()
|
||||||
|
|
||||||
curl_global_init_mem(CURL_GLOBAL_DEFAULT, _malloc_base, _free_base, _realloc_base, _strdup_base, _calloc_base);
|
curl_global_init_mem(CURL_GLOBAL_DEFAULT, _malloc_base, _free_base, _realloc_base, _strdup_base, _calloc_base);
|
||||||
|
|
||||||
|
InitialiseCrashHandler();
|
||||||
InitialiseLogging();
|
InitialiseLogging();
|
||||||
InstallInitialHooks();
|
InstallInitialHooks();
|
||||||
CreateLogFiles();
|
CreateLogFiles();
|
||||||
|
@ -213,94 +161,6 @@ bool InitialiseNorthstar()
|
||||||
// Write launcher version to log
|
// Write launcher version to log
|
||||||
spdlog::info("NorthstarLauncher version: {}", version);
|
spdlog::info("NorthstarLauncher version: {}", version);
|
||||||
|
|
||||||
InitialiseInterfaceCreationHooks();
|
|
||||||
|
|
||||||
AddDllLoadCallback("tier0.dll", InitialiseTier0GameUtilFunctions);
|
|
||||||
AddDllLoadCallback("engine.dll", WaitForDebugger);
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseEngineGameUtilFunctions);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseServerGameUtilFunctions);
|
|
||||||
|
|
||||||
// dedi patches
|
|
||||||
{
|
|
||||||
AddDllLoadCallbackForDedicatedServer("tier0.dll", InitialiseDedicatedOrigin);
|
|
||||||
AddDllLoadCallbackForDedicatedServer("engine.dll", InitialiseDedicated);
|
|
||||||
AddDllLoadCallbackForDedicatedServer("server.dll", InitialiseDedicatedServerGameDLL);
|
|
||||||
AddDllLoadCallbackForDedicatedServer("materialsystem_dx11.dll", InitialiseDedicatedMaterialSystem);
|
|
||||||
// this fucking sucks, but seemingly we somehow load after rtech_game???? unsure how, but because of this we have to apply patches
|
|
||||||
// here, not on rtech_game load
|
|
||||||
AddDllLoadCallbackForDedicatedServer("engine.dll", InitialiseDedicatedRtechGame);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseConVars);
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseConCommands);
|
|
||||||
|
|
||||||
// client-exclusive patches
|
|
||||||
{
|
|
||||||
|
|
||||||
AddDllLoadCallbackForClient("tier0.dll", InitialiseTier0LanguageHooks);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrel);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseSourceConsole);
|
|
||||||
AddDllLoadCallbackForClient("engine.dll", InitialiseChatCommands);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseScriptModMenu);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseScriptServerBrowser);
|
|
||||||
AddDllLoadCallbackForClient("localize.dll", InitialiseModLocalisation);
|
|
||||||
AddDllLoadCallbackForClient("engine.dll", InitialiseClientAuthHooks);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseLatencyFleX);
|
|
||||||
AddDllLoadCallbackForClient("engine.dll", InitialiseScriptExternalBrowserHooks);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseScriptMainMenuPromos);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseMiscClientFixes);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientPrintHooks);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialisePluginCommands);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientChatHooks);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseLocalChatWriter);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseScriptServerToClientStringCommands);
|
|
||||||
AddDllLoadCallbackForClient("engine.dll", InitialiseEngineClientVideoOverrides);
|
|
||||||
AddDllLoadCallbackForClient("engine.dll", InitialiseEngineClientRUIHooks);
|
|
||||||
AddDllLoadCallbackForClient("engine.dll", InitialiseDebugOverlay);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrelJson);
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrelUtilityFunctions);
|
|
||||||
// audio hooks
|
|
||||||
AddDllLoadCallbackForClient("client.dll", InitialiseMilesAudioHooks);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseEngineSpewFuncHooks);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseServerSquirrel);
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseBanSystem);
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseServerAuthentication);
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseSharedMasterServer);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseMiscServerScriptCommand);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseMiscServerFixes);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseBuildAINFileHooks);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseServerSquirrelUtilityFunctions);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseServerSquirrelJson);
|
|
||||||
|
|
||||||
AddDllLoadCallback("engine.dll", InitialisePlaylistHooks);
|
|
||||||
|
|
||||||
AddDllLoadCallback("filesystem_stdio.dll", InitialiseFilesystem);
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseEngineRpakFilesystem);
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseKeyValues);
|
|
||||||
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseServerChatHooks_Engine);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseServerChatHooks_Server);
|
|
||||||
|
|
||||||
// maxplayers increase
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseMaxPlayersOverride_Engine);
|
|
||||||
AddDllLoadCallback("client.dll", InitialiseMaxPlayersOverride_Client);
|
|
||||||
AddDllLoadCallback("server.dll", InitialiseMaxPlayersOverride_Server);
|
|
||||||
|
|
||||||
// mod manager after everything else
|
|
||||||
AddDllLoadCallback("engine.dll", InitialiseModManager);
|
|
||||||
|
|
||||||
{
|
|
||||||
// activate multi-module exploitfixes callbacks
|
|
||||||
constexpr const char* EXPLOITFIXES_MULTICALLBACK_MODS[] = {"client.dll", "engine.dll", "server.dll"};
|
|
||||||
for (const char* mod : EXPLOITFIXES_MULTICALLBACK_MODS)
|
|
||||||
AddDllLoadCallback(mod, ExploitFixes::LoadCallback_MultiModule);
|
|
||||||
|
|
||||||
// activate exploit fixes later
|
|
||||||
AddDllLoadCallback("server.dll", ExploitFixes::LoadCallback_Full);
|
|
||||||
}
|
|
||||||
|
|
||||||
// run callbacks for any libraries that are already loaded by now
|
// run callbacks for any libraries that are already loaded by now
|
||||||
CallAllPendingDLLLoadCallbacks();
|
CallAllPendingDLLLoadCallbacks();
|
||||||
|
|
||||||
|
|
|
@ -1,52 +1,63 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
#include "exploitfixes.h"
|
|
||||||
#include "exploitfixes_utf8parser.h"
|
|
||||||
#include "nsmem.h"
|
|
||||||
#include "cvar.h"
|
#include "cvar.h"
|
||||||
#include "gameutils.h"
|
#include "limits.h"
|
||||||
|
#include "dedicated.h"
|
||||||
|
#include "tier0.h"
|
||||||
|
#include "r2engine.h"
|
||||||
|
#include "r2client.h"
|
||||||
|
#include "vector.h"
|
||||||
|
|
||||||
|
AUTOHOOK_INIT()
|
||||||
|
|
||||||
|
ConVar* Cvar_ns_exploitfixes_log;
|
||||||
|
ConVar* Cvar_ns_should_log_all_clientcommands;
|
||||||
|
|
||||||
|
ConVar* Cvar_sv_cheats;
|
||||||
|
|
||||||
ConVar* ns_exploitfixes_log;
|
|
||||||
#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0)
|
|
||||||
#define BLOCKED_INFO(s) \
|
#define BLOCKED_INFO(s) \
|
||||||
( \
|
( \
|
||||||
[=]() -> bool \
|
[=]() -> bool \
|
||||||
{ \
|
{ \
|
||||||
if (SHOULD_LOG) \
|
if (Cvar_ns_exploitfixes_log->GetBool()) \
|
||||||
{ \
|
{ \
|
||||||
std::stringstream stream; \
|
std::stringstream stream; \
|
||||||
stream << "exploitfixes.cpp: " << BLOCK_PREFIX << s; \
|
stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \
|
||||||
spdlog::error(stream.str()); \
|
spdlog::error(stream.str()); \
|
||||||
} \
|
} \
|
||||||
return false; \
|
return false; \
|
||||||
}())
|
}())
|
||||||
|
|
||||||
struct Float3
|
// block bad netmessages
|
||||||
{
|
|
||||||
float vals[3];
|
|
||||||
|
|
||||||
void MakeValid()
|
|
||||||
{
|
|
||||||
for (auto& val : vals)
|
|
||||||
if (isnan(val))
|
|
||||||
val = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#define BLOCK_NETMSG_FUNC(name, pattern) \
|
|
||||||
KHOOK(name, ("engine.dll", pattern), bool, __fastcall, (void* thisptr, void* buffer)) \
|
|
||||||
{ \
|
|
||||||
return false; \
|
|
||||||
}
|
|
||||||
|
|
||||||
// Servers can literally request a screenshot from any client, yeah no
|
// Servers can literally request a screenshot from any client, yeah no
|
||||||
BLOCK_NETMSG_FUNC(CLC_Screenshot_WriteToBuffer, "48 89 5C 24 ? 57 48 83 EC 20 8B 42 10");
|
// clang-format off
|
||||||
BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38");
|
AUTOHOOK(CLC_Screenshot_WriteToBuffer, engine.dll + 0x22AF20,
|
||||||
|
bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 57 48 83 EC 20 8B 42 10
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// This is unused ingame and a big exploit vector
|
// clang-format off
|
||||||
BLOCK_NETMSG_FUNC(Base_CmdKeyValues_ReadFromBuffer, "40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70");
|
AUTOHOOK(CLC_Screenshot_ReadFromBuffer, engine.dll + 0x221F00,
|
||||||
|
bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10"), bool, __fastcall, (void* pMsg))
|
// This is unused ingame and a big client=>server=>client exploit vector
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(Base_CmdKeyValues_ReadFromBuffer, engine.dll + 0x220040,
|
||||||
|
bool, __fastcall, (void* thisptr, void* buffer)) // 40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(CClient_ProcessSetConVar, engine.dll + 0x75CF0,
|
||||||
|
bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
|
|
||||||
constexpr int ENTRY_STR_LEN = 260;
|
constexpr int ENTRY_STR_LEN = 260;
|
||||||
|
@ -69,25 +80,12 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
|
||||||
};
|
};
|
||||||
|
|
||||||
auto msg = (NET_SetConVar*)pMsg;
|
auto msg = (NET_SetConVar*)pMsg;
|
||||||
|
bool bIsServerFrame = Tier0::ThreadInServerFrameThread();
|
||||||
|
|
||||||
bool areWeServer;
|
std::string BLOCK_PREFIX =
|
||||||
|
std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
|
||||||
|
|
||||||
{
|
if (bIsServerFrame)
|
||||||
// Figure out of we are the client or the server
|
|
||||||
// To do this, we utilize the msg's m_pMessageHandler pointer
|
|
||||||
// m_pMessageHandler points to a virtual class that handles all net messages
|
|
||||||
// The first virtual table function of our m_pMessageHandler will differ if it is IServerMessageHandler or IClientMessageHandler
|
|
||||||
void* msgHandlerVTableFirstFunc = **(void****)(msg->m_pMessageHandler);
|
|
||||||
static auto engineBaseAddress = (uintptr_t)GetModuleHandleA("engine.dll");
|
|
||||||
auto offset = uintptr_t(msgHandlerVTableFirstFunc) - engineBaseAddress;
|
|
||||||
|
|
||||||
constexpr uintptr_t CLIENTSTATE_FIRST_VFUNC_OFFSET = 0x8A15C;
|
|
||||||
areWeServer = offset != CLIENTSTATE_FIRST_VFUNC_OFFSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string BLOCK_PREFIX = std::string {"NET_SetConVar ("} + (areWeServer ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
|
|
||||||
|
|
||||||
if (areWeServer)
|
|
||||||
{
|
{
|
||||||
constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69;
|
constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69;
|
||||||
if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT)
|
if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT)
|
||||||
|
@ -101,9 +99,8 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
|
||||||
auto entry = msg->m_ConVars + i;
|
auto entry = msg->m_ConVars + i;
|
||||||
|
|
||||||
// Safety check for memory access
|
// Safety check for memory access
|
||||||
if (NSMem::IsMemoryReadable(entry, sizeof(*entry)))
|
if (MemoryAddress(entry).IsMemoryReadable(sizeof(*entry)))
|
||||||
{
|
{
|
||||||
|
|
||||||
// Find null terminators
|
// Find null terminators
|
||||||
bool nameValid = false, valValid = false;
|
bool nameValid = false, valValid = false;
|
||||||
for (int i = 0; i < ENTRY_STR_LEN; i++)
|
for (int i = 0; i < ENTRY_STR_LEN; i++)
|
||||||
|
@ -117,36 +114,19 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
|
||||||
if (!nameValid || !valValid)
|
if (!nameValid || !valValid)
|
||||||
return BLOCKED_INFO("Missing null terminators");
|
return BLOCKED_INFO("Missing null terminators");
|
||||||
|
|
||||||
auto realVar = g_pCVar->FindVar(entry->name);
|
ConVar* pVar = R2::g_pCVar->FindVar(entry->name);
|
||||||
|
|
||||||
if (realVar)
|
if (pVar)
|
||||||
|
{
|
||||||
memcpy(
|
memcpy(
|
||||||
entry->name,
|
entry->name,
|
||||||
realVar->m_ConCommandBase.m_pszName,
|
pVar->m_ConCommandBase.m_pszName,
|
||||||
strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
|
strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
|
||||||
|
|
||||||
bool isValidFlags = true;
|
int iFlags = bIsServerFrame ? FCVAR_USERINFO : FCVAR_REPLICATED;
|
||||||
if (areWeServer)
|
if (!pVar->IsFlagSet(iFlags))
|
||||||
{
|
|
||||||
if (realVar)
|
|
||||||
isValidFlags = ConVar::IsFlagSet(realVar, FCVAR_USERINFO); // ConVar MUST be userinfo var
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: Should probably have some sanity checks, but can't find any that are consistent
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidFlags)
|
|
||||||
{
|
|
||||||
if (!realVar)
|
|
||||||
{
|
|
||||||
return BLOCKED_INFO("Invalid flags on nonexistant cvar (how tho???)");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return BLOCKED_INFO(
|
return BLOCKED_INFO(
|
||||||
"Invalid flags (" << std::hex << "0x" << realVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name);
|
"Invalid flags (" << std::hex << "0x" << pVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -155,11 +135,14 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oCClient_ProcessSetConVar(msg);
|
return CClient_ProcessSetConVar(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purpose: prevent invalid user CMDs
|
// prevent invalid user CMDs
|
||||||
KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg))
|
// clang-format off
|
||||||
|
AUTOHOOK(CClient_ProcessUsercmds, engine.dll + 0x1040F0,
|
||||||
|
bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
struct CLC_Move
|
struct CLC_Move
|
||||||
{
|
{
|
||||||
|
@ -186,22 +169,19 @@ KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __f
|
||||||
return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")");
|
return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int NUMCMD_SANITY_LIMIT = 16;
|
|
||||||
if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT)
|
|
||||||
{
|
|
||||||
return BLOCKED_INFO("Command count is too high (new: " << msg->m_nNewCommands << ", backup: " << msg->m_nBackupCommands << ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg->m_nLength <= 0)
|
if (msg->m_nLength <= 0)
|
||||||
return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")");
|
return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")");
|
||||||
|
|
||||||
return oCClient_ProcessUsercmds(thisptr, pMsg);
|
return CClient_ProcessUsercmds(thisptr, pMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from))
|
// clang-format off
|
||||||
|
AUTOHOOK(ReadUsercmd, server.dll + 0x2603F0,
|
||||||
|
void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24 ? 53 55 56 57
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
// Let normal usercmd read happen first, it's safe
|
// Let normal usercmd read happen first, it's safe
|
||||||
oReadUsercmd(buf, pCmd_move, pCmd_from);
|
ReadUsercmd(buf, pCmd_move, pCmd_from);
|
||||||
|
|
||||||
// Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing)
|
// Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing)
|
||||||
struct alignas(4) SV_CUserCmd
|
struct alignas(4) SV_CUserCmd
|
||||||
|
@ -209,11 +189,11 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
||||||
DWORD command_number;
|
DWORD command_number;
|
||||||
DWORD tick_count;
|
DWORD tick_count;
|
||||||
float command_time;
|
float command_time;
|
||||||
Float3 worldViewAngles;
|
Vector3 worldViewAngles;
|
||||||
BYTE gap18[4];
|
BYTE gap18[4];
|
||||||
Float3 localViewAngles;
|
Vector3 localViewAngles;
|
||||||
Float3 attackangles;
|
Vector3 attackangles;
|
||||||
Float3 move;
|
Vector3 move;
|
||||||
DWORD buttons;
|
DWORD buttons;
|
||||||
BYTE impulse;
|
BYTE impulse;
|
||||||
short weaponselect;
|
short weaponselect;
|
||||||
|
@ -221,8 +201,8 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
||||||
BYTE gap4C[24];
|
BYTE gap4C[24];
|
||||||
char headoffset;
|
char headoffset;
|
||||||
BYTE gap65[11];
|
BYTE gap65[11];
|
||||||
Float3 cameraPos;
|
Vector3 cameraPos;
|
||||||
Float3 cameraAngles;
|
Vector3 cameraAngles;
|
||||||
BYTE gap88[4];
|
BYTE gap88[4];
|
||||||
int tickSomething;
|
int tickSomething;
|
||||||
DWORD dword90;
|
DWORD dword90;
|
||||||
|
@ -237,7 +217,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
||||||
std::string BLOCK_PREFIX =
|
std::string BLOCK_PREFIX =
|
||||||
"ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): ";
|
"ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): ";
|
||||||
|
|
||||||
// Fix invalid player angles
|
// fix invalid player angles
|
||||||
cmd->worldViewAngles.MakeValid();
|
cmd->worldViewAngles.MakeValid();
|
||||||
cmd->attackangles.MakeValid();
|
cmd->attackangles.MakeValid();
|
||||||
cmd->localViewAngles.MakeValid();
|
cmd->localViewAngles.MakeValid();
|
||||||
|
@ -249,7 +229,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
||||||
// Fix invaid movement vector
|
// Fix invaid movement vector
|
||||||
cmd->move.MakeValid();
|
cmd->move.MakeValid();
|
||||||
|
|
||||||
if (cmd->tick_count == 0 || cmd->command_time <= 0)
|
if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0)
|
||||||
{
|
{
|
||||||
BLOCKED_INFO(
|
BLOCKED_INFO(
|
||||||
"Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime
|
"Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime
|
||||||
|
@ -260,6 +240,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
|
||||||
return;
|
return;
|
||||||
|
|
||||||
INVALID_CMD:
|
INVALID_CMD:
|
||||||
|
|
||||||
// Fix any gameplay-affecting cmd properties
|
// Fix any gameplay-affecting cmd properties
|
||||||
// NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems
|
// NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems
|
||||||
cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = {0, 0, 0};
|
cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = {0, 0, 0};
|
||||||
|
@ -269,287 +250,209 @@ INVALID_CMD:
|
||||||
cmd->meleetarget = 0;
|
cmd->meleetarget = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// basically: by default r2 isn't set as a valve mod, meaning that m_bRestrictServerCommands is false
|
// ensure that GetLocalBaseClient().m_bRestrictServerCommands is set correctly, which the return value of this function controls
|
||||||
// this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients
|
// this is IsValveMod in source, but we're making it IsRespawnMod now since valve didn't make this one
|
||||||
// especially since we have script commands this could theoretically be awful
|
// clang-format off
|
||||||
KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ())
|
AUTOHOOK(IsRespawnMod, engine.dll + 0x1C6360,
|
||||||
|
bool, __fastcall, (const char* pModName)) // 48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
bool result = !CommandLine()->CheckParm("-norestrictservercommands");
|
// somewhat temp, store the modname here, since we don't have a proper ptr in engine to it rn
|
||||||
spdlog::info("ExploitFixes: Overriding IsValveMod to {}...", result);
|
int iSize = strlen(pModName);
|
||||||
return result;
|
R2::g_pModName = new char[iSize + 1];
|
||||||
|
strcpy(R2::g_pModName, pModName);
|
||||||
|
|
||||||
|
return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) && !Tier0::CommandLine()->CheckParm("-norestrictservercommands");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix respawn's crappy UTF8 parser so it doesn't crash -_-
|
// ratelimit stringcmds, and prevent remote clients from calling commands that they shouldn't
|
||||||
// This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome
|
bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, R2::cmd_source_t commandSource);
|
||||||
KHOOK(
|
|
||||||
CrashFunc_ParseUTF8,
|
// clang-format off
|
||||||
("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"),
|
AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0,
|
||||||
bool,
|
bool, __fastcall, (R2::CBaseClient* self, uint32_t unknown, const char* pCommandString))
|
||||||
__fastcall,
|
// clang-format on
|
||||||
(INT64 * a1, DWORD* a2, char* strData))
|
|
||||||
{
|
{
|
||||||
|
if (Cvar_ns_should_log_all_clientcommands->GetBool())
|
||||||
|
spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString);
|
||||||
|
|
||||||
static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16");
|
if (!g_pServerLimits->CheckStringCommandLimits(self))
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
if (_ReturnAddress() == targetRetAddr)
|
|
||||||
#else
|
|
||||||
if (__builtin_return_address(0) == targetRetAddr)
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData))
|
R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands");
|
||||||
{
|
return false;
|
||||||
const char* BLOCK_PREFIX = "ParseUTF8 Hook: ";
|
}
|
||||||
BLOCKED_INFO("Ignoring potentially-crashing utf8 string");
|
|
||||||
|
// verify the command we're trying to execute is FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, if it's a concommand
|
||||||
|
char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor
|
||||||
|
memset(commandBuf, 0, sizeof(commandBuf));
|
||||||
|
CCommand tempCommand = *(CCommand*)&commandBuf;
|
||||||
|
|
||||||
|
if (!CCommand__Tokenize(tempCommand, pCommandString, R2::cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ConCommand* command = R2::g_pCVar->FindCommand(tempCommand.Arg(0));
|
||||||
|
|
||||||
|
// if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff
|
||||||
|
if (command && !command->IsFlagSet(FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS))
|
||||||
|
{
|
||||||
|
// ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients
|
||||||
|
if (IsDedicatedServer())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (strcmp(self->m_UID, R2::g_pLocalPlayerUserID))
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return oCrashFunc_ParseUTF8(a1, a2, strData);
|
// check for and block abusable legacy portal 2 commands
|
||||||
}
|
// these aren't actually concommands weirdly enough, they seem to just be hardcoded
|
||||||
|
if (!Cvar_sv_cheats->GetBool())
|
||||||
// GetEntByIndex (called by ScriptGetEntByIndex) doesn't check for the index being out of bounds when it's
|
|
||||||
// above the max entity count. This allows it to be used to crash servers.
|
|
||||||
typedef void*(__fastcall* GetEntByIndexType)(int idx);
|
|
||||||
GetEntByIndexType GetEntByIndex;
|
|
||||||
|
|
||||||
static void* GetEntByIndexHook(int idx)
|
|
||||||
{
|
|
||||||
if (idx >= 0x4000)
|
|
||||||
{
|
{
|
||||||
spdlog::info("GetEntByIndex {} is out of bounds", idx);
|
constexpr const char* blockedCommands[] = {
|
||||||
return nullptr;
|
"emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
|
||||||
}
|
|
||||||
return GetEntByIndex(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// RELOCATED FROM https://github.com/R2Northstar/NorthstarLauncher/commit/25dbf729cfc75107a0fcf0186924b58ecc05214b
|
// These both execute a command for every single entity for some reason, nice one valve
|
||||||
// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read
|
"pre_go_to_hub",
|
||||||
// out of the bounds of the output buffer.
|
"pre_go_to_calibration",
|
||||||
KHOOK(
|
|
||||||
LZSS_SafeUncompress,
|
|
||||||
("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 33 ED 41 8B F9"),
|
|
||||||
uint32_t,
|
|
||||||
__fastcall,
|
|
||||||
(void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize))
|
|
||||||
{
|
|
||||||
static constexpr int LZSS_LOOKSHIFT = 4;
|
|
||||||
|
|
||||||
uint32_t totalBytes = 0;
|
"end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed
|
||||||
int getCmdByte = 0, cmdByte = 0;
|
"load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
|
||||||
|
|
||||||
struct lzss_header_t
|
|
||||||
{
|
|
||||||
uint32_t id, actualSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
lzss_header_t header = *(lzss_header_t*)pInput;
|
|
||||||
|
|
||||||
if (pInput == NULL || header.id != 'SSZL' || header.actualSize == 0 || header.actualSize > unBufSize)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
pInput += sizeof(lzss_header_t);
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (!getCmdByte)
|
|
||||||
cmdByte = *pInput++;
|
|
||||||
|
|
||||||
getCmdByte = (getCmdByte + 1) & 0x07;
|
|
||||||
|
|
||||||
if (cmdByte & 0x01)
|
|
||||||
{
|
|
||||||
int position = *pInput++ << LZSS_LOOKSHIFT;
|
|
||||||
position |= (*pInput >> LZSS_LOOKSHIFT);
|
|
||||||
position += 1;
|
|
||||||
|
|
||||||
int count = (*pInput++ & 0x0F) + 1;
|
|
||||||
if (count == 1)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Ensure reference chunk exists entirely within our buffer
|
|
||||||
if (position > totalBytes)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
totalBytes += count;
|
|
||||||
if (totalBytes > unBufSize)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
unsigned char* pSource = pOutput - position;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
*pOutput++ = *pSource++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
totalBytes++;
|
|
||||||
if (totalBytes > unBufSize)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
*pOutput++ = *pInput++;
|
|
||||||
}
|
|
||||||
cmdByte = cmdByte >> 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalBytes == header.actualSize)
|
|
||||||
{
|
|
||||||
return totalBytes;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
|
||||||
|
|
||||||
void DoBytePatches()
|
|
||||||
{
|
|
||||||
uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll");
|
|
||||||
uintptr_t serverBase = (uintptr_t)GetModuleHandleA("server.dll");
|
|
||||||
|
|
||||||
// patches to make commands run from client/ui script still work
|
|
||||||
// note: this is likely preventable in a nicer way? test prolly
|
|
||||||
NSMem::BytePatch(engineBase + 0x4FB65, "EB 11");
|
|
||||||
NSMem::BytePatch(engineBase + 0x4FBAC, "EB 16");
|
|
||||||
|
|
||||||
// disconnect concommand
|
|
||||||
{
|
|
||||||
uintptr_t addr = engineBase + 0x5ADA2D;
|
|
||||||
int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE;
|
|
||||||
NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int));
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Dumb ANTITAMPER patches (they negatively impact performance and security)
|
|
||||||
|
|
||||||
constexpr const char* ANTITAMPER_EXPORTS[] = {
|
|
||||||
"ANTITAMPER_SPOTCHECK_CODEMARKER",
|
|
||||||
"ANTITAMPER_TESTVALUE_CODEMARKER",
|
|
||||||
"ANTITAMPER_TRIGGER_CODEMARKER",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prevent thesefrom actually doing anything
|
int iCmdLength = strlen(tempCommand.Arg(0));
|
||||||
for (auto exportName : ANTITAMPER_EXPORTS)
|
|
||||||
|
bool bIsBadCommand = false;
|
||||||
|
for (auto& blockedCommand : blockedCommands)
|
||||||
{
|
{
|
||||||
|
if (iCmdLength != strlen(blockedCommand))
|
||||||
|
continue;
|
||||||
|
|
||||||
auto address = (uintptr_t)GetProcAddress(GetModuleHandleA("server.dll"), exportName);
|
for (int i = 0; tempCommand.Arg(0)[i]; i++)
|
||||||
if (!address)
|
if (tolower(tempCommand.Arg(0)[i]) != blockedCommand[i])
|
||||||
{
|
goto NEXT_COMMAND; // break out of this loop, then go to next command
|
||||||
spdlog::warn("Failed to find AntiTamper function export \"{}\"", exportName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Just return, none of them have any args or are userpurge
|
|
||||||
NSMem::BytePatch(address, "C3");
|
|
||||||
|
|
||||||
spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
|
// this is a command we need to block
|
||||||
}
|
return false;
|
||||||
|
NEXT_COMMAND:;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return CGameClient__ExecuteStringCommand(self, unknown, pCommandString);
|
||||||
}
|
}
|
||||||
|
|
||||||
KHOOK(
|
// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines
|
||||||
SpecialClientCommand,
|
bool bWasWritingStringTableSuccessful;
|
||||||
("server.dll", "48 89 5C 24 ? 48 89 74 24 ? 55 57 41 56 48 8D 6C 24 ? 48 81 EC ? ? ? ? 83 3A 00"),
|
|
||||||
bool,
|
// clang-format off
|
||||||
__fastcall,
|
AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0,
|
||||||
(void* player, CCommand* command))
|
void, __fastcall, (void* self))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
|
bWasWritingStringTableSuccessful = true;
|
||||||
|
CBaseClient__SendServerInfo(self);
|
||||||
|
if (!bWasWritingStringTableSuccessful)
|
||||||
|
R2::CBaseClient__Disconnect(
|
||||||
|
self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting");
|
||||||
|
}
|
||||||
|
|
||||||
static ConVar* sv_cheats = g_pCVar->FindVar("sv_cheats");
|
// return null when GetEntByIndex is passed an index >= 0x4000
|
||||||
|
// this is called from exactly 1 script clientcommand that can be given an arbitrary index, and going above 0x4000 crashes
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(GetEntByIndex, server.dll + 0x2A8A50,
|
||||||
|
void*, __fastcall, (int i))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
const int MAX_ENT_IDX = 0x4000;
|
||||||
|
|
||||||
if (sv_cheats->GetBool())
|
if (i >= MAX_ENT_IDX)
|
||||||
return oSpecialClientCommand(player, command); // Don't block anything if sv_cheats is on
|
{
|
||||||
|
spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// These are mostly from Portal 2 (sigh)
|
return GetEntByIndex(i);
|
||||||
constexpr const char* blockedCommands[] = {
|
}
|
||||||
"emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
|
// clang-format off
|
||||||
|
AUTOHOOK(CL_CopyExistingEntity, engine.dll + 0x6F940,
|
||||||
// These both execute a command for every single entity for some reason, nice one valve
|
bool, __fastcall, (void* a1))
|
||||||
"pre_go_to_hub",
|
// clang-format on
|
||||||
"pre_go_to_calibration",
|
{
|
||||||
|
struct CEntityReadInfo
|
||||||
"end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed
|
{
|
||||||
"load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
|
BYTE gap[40];
|
||||||
|
int nNewEntity;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (command->ArgC() > 0)
|
CEntityReadInfo* pReadInfo = (CEntityReadInfo*)a1;
|
||||||
|
if (pReadInfo->nNewEntity >= 0x1000 || pReadInfo->nNewEntity < 0)
|
||||||
{
|
{
|
||||||
std::string cmdStr = command->Arg(0);
|
// Value isn't sanitized in release builds for
|
||||||
for (char& c : cmdStr)
|
// every game powered by the Source Engine 1
|
||||||
c = tolower(c);
|
// causing read/write outside of array bounds.
|
||||||
|
// This defect has let to the achievement of a
|
||||||
|
// full-chain RCE exploit. We hook and perform
|
||||||
|
// sanity checks for the value of m_nNewEntity
|
||||||
|
// here to prevent this behavior from happening.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (const char* blockedCommand : blockedCommands)
|
return CL_CopyExistingEntity(a1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD("engine.dll", EngineExploitFixes, (CModule module))
|
||||||
|
{
|
||||||
|
AUTOHOOK_DISPATCH_MODULE(engine.dll)
|
||||||
|
|
||||||
|
CCommand__Tokenize = module.Offset(0x418380).As<bool (*)(CCommand&, const char*, R2::cmd_source_t)>();
|
||||||
|
|
||||||
|
// allow client/ui to run clientcommands despite restricting servercommands
|
||||||
|
module.Offset(0x4FB65).Patch("EB 11");
|
||||||
|
module.Offset(0x4FBAC).Patch("EB 16");
|
||||||
|
|
||||||
|
// patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails
|
||||||
|
{
|
||||||
|
MemoryAddress writeAddress(&bWasWritingStringTableSuccessful - module.Offset(0x234EDC).m_nAddress);
|
||||||
|
|
||||||
|
MemoryAddress addr = module.Offset(0x234ED2);
|
||||||
|
addr.Patch("C7 05");
|
||||||
|
addr.Offset(2).Patch((BYTE*)&writeAddress, sizeof(writeAddress));
|
||||||
|
|
||||||
|
addr.Offset(6).Patch("00 00 00 00");
|
||||||
|
|
||||||
|
addr.Offset(10).NOP(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD_RELIESON("server.dll", ServerExploitFixes, ConVar, (CModule module))
|
||||||
|
{
|
||||||
|
AUTOHOOK_DISPATCH_MODULE(server.dll)
|
||||||
|
|
||||||
|
// ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue)
|
||||||
|
// this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches
|
||||||
|
module.Offset(0x153920).Patch("C3");
|
||||||
|
|
||||||
|
// Dumb ANTITAMPER patches (they negatively impact performance and security)
|
||||||
|
constexpr const char* ANTITAMPER_EXPORTS[] = {
|
||||||
|
"ANTITAMPER_SPOTCHECK_CODEMARKER",
|
||||||
|
"ANTITAMPER_TESTVALUE_CODEMARKER",
|
||||||
|
"ANTITAMPER_TRIGGER_CODEMARKER",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prevent these from actually doing anything
|
||||||
|
for (auto exportName : ANTITAMPER_EXPORTS)
|
||||||
|
{
|
||||||
|
MemoryAddress exportAddr = module.GetExport(exportName);
|
||||||
|
if (exportAddr)
|
||||||
{
|
{
|
||||||
if (cmdStr.find(blockedCommand) != std::string::npos)
|
// Just return, none of them have any args or are userpurge
|
||||||
{
|
exportAddr.Patch("C3");
|
||||||
// Block this command
|
spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
|
||||||
spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oSpecialClientCommand(player, command);
|
Cvar_ns_exploitfixes_log =
|
||||||
}
|
new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something");
|
||||||
|
Cvar_ns_should_log_all_clientcommands =
|
||||||
void SetupKHook(KHook* hook)
|
new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands");
|
||||||
{
|
|
||||||
if (hook->Setup())
|
Cvar_sv_cheats = R2::g_pCVar->FindVar("sv_cheats");
|
||||||
{
|
|
||||||
spdlog::debug("KHook::Setup(): Hooked at {}", hook->targetFuncAddr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
spdlog::critical("\tFAILED to initialize all exploit patches.");
|
|
||||||
|
|
||||||
// Force exit
|
|
||||||
MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExploitFixes::LoadCallback_MultiModule(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
|
|
||||||
spdlog::info("ExploitFixes::LoadCallback_MultiModule({}) ...", (void*)baseAddress);
|
|
||||||
|
|
||||||
int hooksEnabled = 0;
|
|
||||||
for (auto itr = KHook::_allHooks.begin(); itr != KHook::_allHooks.end(); itr++)
|
|
||||||
{
|
|
||||||
auto curHook = *itr;
|
|
||||||
if (GetModuleHandleA(curHook->targetFunc.moduleName) == baseAddress)
|
|
||||||
{
|
|
||||||
SetupKHook(curHook);
|
|
||||||
itr = KHook::_allHooks.erase(itr); // Prevent repeated initialization
|
|
||||||
|
|
||||||
hooksEnabled++;
|
|
||||||
|
|
||||||
if (itr == KHook::_allHooks.end())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::info("\tEnabled {} hooks.", hooksEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExploitFixes::LoadCallback_Full(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
spdlog::info("ExploitFixes::LoadCallback_Full ...");
|
|
||||||
|
|
||||||
spdlog::info("\tByte patching...");
|
|
||||||
DoBytePatches();
|
|
||||||
|
|
||||||
for (KHook* hook : KHook::_allHooks)
|
|
||||||
SetupKHook(hook);
|
|
||||||
|
|
||||||
spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " late exploit-patch hooks.");
|
|
||||||
KHook::_allHooks.clear();
|
|
||||||
|
|
||||||
ns_exploitfixes_log =
|
|
||||||
new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever exploitfixes.cpp blocks/corrects something");
|
|
||||||
|
|
||||||
g_pCVar->FindCommand("migrateme")->m_nFlags &= ~FCVAR_SERVER_CAN_EXECUTE;
|
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x2a8a50, &GetEntByIndexHook, reinterpret_cast<LPVOID*>(&GetEntByIndex));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 "pch.h"
|
||||||
#include "filesystem.h"
|
#include "filesystem.h"
|
||||||
#include "hooks.h"
|
|
||||||
#include "hookutils.h"
|
|
||||||
#include "sourceinterface.h"
|
#include "sourceinterface.h"
|
||||||
#include "modmanager.h"
|
#include "modmanager.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
// hook forward declares
|
AUTOHOOK_INIT()
|
||||||
typedef FileHandle_t (*ReadFileFromVPKType)(VPKData* vpkInfo, __int64* b, char* filename);
|
|
||||||
ReadFileFromVPKType readFileFromVPK;
|
|
||||||
FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename);
|
|
||||||
|
|
||||||
typedef bool (*ReadFromCacheType)(IFileSystem* filesystem, char* path, void* result);
|
using namespace R2;
|
||||||
ReadFromCacheType readFromCache;
|
|
||||||
bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result);
|
|
||||||
|
|
||||||
typedef void (*AddSearchPathType)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
|
bool bReadingOriginalFile = false;
|
||||||
AddSearchPathType addSearchPathOriginal;
|
std::string sCurrentModPath;
|
||||||
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
|
|
||||||
|
|
||||||
typedef FileHandle_t (*ReadFileFromFilesystemType)(
|
ConVar* Cvar_ns_fs_log_reads;
|
||||||
IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5);
|
|
||||||
ReadFileFromFilesystemType readFileFromFilesystem;
|
|
||||||
FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5);
|
|
||||||
|
|
||||||
typedef VPKData* (*MountVPKType)(IFileSystem* fileSystem, const char* vpkPath);
|
// use the R2 namespace for game funcs
|
||||||
MountVPKType mountVPK;
|
namespace R2
|
||||||
VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath);
|
|
||||||
|
|
||||||
bool readingOriginalFile;
|
|
||||||
std::string currentModPath;
|
|
||||||
SourceInterface<IFileSystem>* g_Filesystem;
|
|
||||||
|
|
||||||
void InitialiseFilesystem(HMODULE baseAddress)
|
|
||||||
{
|
{
|
||||||
g_Filesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
|
SourceInterface<IFileSystem>* g_pFilesystem;
|
||||||
|
|
||||||
// create hooks
|
std::string ReadVPKFile(const char* path)
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x5CBA0, &ReadFileFromVPKHook, reinterpret_cast<LPVOID*>(&readFileFromVPK));
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook,
|
|
||||||
reinterpret_cast<void*>((*g_Filesystem)->m_vtable->ReadFromCache),
|
|
||||||
&ReadFromCacheHook,
|
|
||||||
reinterpret_cast<LPVOID*>(&readFromCache));
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook,
|
|
||||||
reinterpret_cast<void*>((*g_Filesystem)->m_vtable->AddSearchPath),
|
|
||||||
&AddSearchPathHook,
|
|
||||||
reinterpret_cast<LPVOID*>(&addSearchPathOriginal));
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15F20, &ReadFileFromFilesystemHook, reinterpret_cast<LPVOID*>(&readFileFromFilesystem));
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook, reinterpret_cast<void*>((*g_Filesystem)->m_vtable->MountVPK), &MountVPKHook, reinterpret_cast<LPVOID*>(&mountVPK));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ReadVPKFile(const char* path)
|
|
||||||
{
|
|
||||||
// read scripts.rson file, todo: check if this can be overwritten
|
|
||||||
FileHandle_t fileHandle = (*g_Filesystem)->m_vtable2->Open(&(*g_Filesystem)->m_vtable2, path, "rb", "GAME", 0);
|
|
||||||
|
|
||||||
std::stringstream fileStream;
|
|
||||||
int bytesRead = 0;
|
|
||||||
char data[4096];
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
bytesRead = (*g_Filesystem)->m_vtable2->Read(&(*g_Filesystem)->m_vtable2, data, (int)std::size(data), fileHandle);
|
// read scripts.rson file, todo: check if this can be overwritten
|
||||||
fileStream.write(data, bytesRead);
|
FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0);
|
||||||
} while (bytesRead == std::size(data));
|
|
||||||
|
|
||||||
(*g_Filesystem)->m_vtable2->Close(*g_Filesystem, fileHandle);
|
std::stringstream fileStream;
|
||||||
|
int bytesRead = 0;
|
||||||
|
char data[4096];
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bytesRead = (*g_pFilesystem)->m_vtable2->Read(&(*g_pFilesystem)->m_vtable2, data, (int)std::size(data), fileHandle);
|
||||||
|
fileStream.write(data, bytesRead);
|
||||||
|
} while (bytesRead == std::size(data));
|
||||||
|
|
||||||
return fileStream.str();
|
(*g_pFilesystem)->m_vtable2->Close(*g_pFilesystem, fileHandle);
|
||||||
}
|
|
||||||
|
|
||||||
std::string ReadVPKOriginalFile(const char* path)
|
return fileStream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ReadVPKOriginalFile(const char* path)
|
||||||
|
{
|
||||||
|
// todo: should probably set search path to be g_pModName here also
|
||||||
|
|
||||||
|
bReadingOriginalFile = true;
|
||||||
|
std::string ret = ReadVPKFile(path);
|
||||||
|
bReadingOriginalFile = false;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} // namespace R2
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
HOOK(AddSearchPathHook, AddSearchPath,
|
||||||
|
void, __fastcall, (IFileSystem * fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
readingOriginalFile = true;
|
AddSearchPath(fileSystem, pPath, pathID, addType);
|
||||||
std::string ret = ReadVPKFile(path);
|
|
||||||
readingOriginalFile = false;
|
|
||||||
|
|
||||||
return ret;
|
// make sure current mod paths are at head
|
||||||
|
if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
|
||||||
|
{
|
||||||
|
AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||||
|
AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetNewModSearchPaths(Mod* mod)
|
void SetNewModSearchPaths(Mod* mod)
|
||||||
|
@ -90,96 +72,84 @@ void SetNewModSearchPaths(Mod* mod)
|
||||||
// in the future we could also determine whether the file we're setting paths for needs a mod dir, or compiled assets
|
// in the future we could also determine whether the file we're setting paths for needs a mod dir, or compiled assets
|
||||||
if (mod != nullptr)
|
if (mod != nullptr)
|
||||||
{
|
{
|
||||||
if ((fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().compare(currentModPath))
|
if ((fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().compare(sCurrentModPath))
|
||||||
{
|
{
|
||||||
spdlog::info("changing mod search path from {} to {}", currentModPath, mod->ModDirectory.string());
|
spdlog::info("changing mod search path from {} to {}", sCurrentModPath, mod->m_ModDirectory.string());
|
||||||
|
|
||||||
addSearchPathOriginal(
|
AddSearchPath(
|
||||||
&*(*g_Filesystem), (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
&*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||||
currentModPath = (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string();
|
sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // push compiled to head
|
else // push compiled to head
|
||||||
addSearchPathOriginal(&*(*g_Filesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
AddSearchPath(&*(*g_pFilesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TryReplaceFile(char* path, bool shouldCompile)
|
bool TryReplaceFile(const char* pPath, bool shouldCompile)
|
||||||
{
|
{
|
||||||
if (readingOriginalFile)
|
if (bReadingOriginalFile)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (shouldCompile)
|
if (shouldCompile)
|
||||||
(*g_ModManager).CompileAssetsForFile(path);
|
g_pModManager->CompileAssetsForFile(pPath);
|
||||||
|
|
||||||
// idk how efficient the lexically normal check is
|
// idk how efficient the lexically normal check is
|
||||||
// can't just set all /s in path to \, since some paths aren't in writeable memory
|
// can't just set all /s in path to \, since some paths aren't in writeable memory
|
||||||
auto file = g_ModManager->m_modFiles.find(fs::path(path).lexically_normal().string());
|
auto file = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path(pPath)));
|
||||||
if (file != g_ModManager->m_modFiles.end())
|
if (file != g_pModManager->m_ModFiles.end())
|
||||||
{
|
{
|
||||||
SetNewModSearchPaths(file->second.owningMod);
|
SetNewModSearchPaths(file->second.m_pOwningMod);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename)
|
// force modded files to be read from mods, not cache
|
||||||
|
// clang-format off
|
||||||
|
HOOK(ReadFromCacheHook, ReadFromCache,
|
||||||
|
bool, __fastcall, (IFileSystem * filesystem, char* pPath, void* result))
|
||||||
|
// clang-format off
|
||||||
{
|
{
|
||||||
// move this to a convar at some point when we can read them in native
|
if (TryReplaceFile(pPath, true))
|
||||||
// spdlog::info("ReadFileFromVPKHook {} {}", filename, vpkInfo->path);
|
return false;
|
||||||
|
|
||||||
// there is literally never any reason to compile here, since we'll always compile in ReadFileFromFilesystemHook in the same codepath
|
return ReadFromCache(filesystem, pPath, result);
|
||||||
// this is called
|
}
|
||||||
|
|
||||||
|
// force modded files to be read from mods, not vpk
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(ReadFileFromVPK, filesystem_stdio.dll + 0x5CBA0,
|
||||||
|
FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
// don't compile here because this is only ever called from OpenEx, which already compiles
|
||||||
if (TryReplaceFile(filename, false))
|
if (TryReplaceFile(filename, false))
|
||||||
{
|
{
|
||||||
*b = -1;
|
*b = -1;
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
return readFileFromVPK(vpkInfo, b, filename);
|
return ReadFileFromVPK(vpkInfo, b, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result)
|
// clang-format off
|
||||||
|
AUTOHOOK(CBaseFileSystem__OpenEx, filesystem_stdio.dll + 0x15F50,
|
||||||
|
FileHandle_t, __fastcall, (IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char **ppszResolvedFilename))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
// move this to a convar at some point when we can read them in native
|
TryReplaceFile(pPath, true);
|
||||||
// spdlog::info("ReadFromCacheHook {}", path);
|
return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename);
|
||||||
|
|
||||||
if (TryReplaceFile(path, true))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return readFromCache(filesystem, path, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)
|
HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath))
|
||||||
{
|
{
|
||||||
addSearchPathOriginal(fileSystem, pPath, pathID, addType);
|
spdlog::info("MountVPK {}", pVpkPath);
|
||||||
|
VPKData* ret = MountVPK(fileSystem, pVpkPath);
|
||||||
|
|
||||||
// make sure current mod paths are at head
|
for (Mod mod : g_pModManager->m_LoadedMods)
|
||||||
if (!strcmp(pathID, "GAME") && currentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
|
|
||||||
{
|
{
|
||||||
addSearchPathOriginal(fileSystem, currentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
|
if (!mod.m_bEnabled)
|
||||||
addSearchPathOriginal(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5)
|
|
||||||
{
|
|
||||||
// this isn't super efficient, but it's necessary, since calling addsearchpath in readfilefromvpk doesn't work, possibly refactor later
|
|
||||||
// it also might be possible to hook functions that are called later, idk look into callstack for ReadFileFromVPK
|
|
||||||
if (!readingOriginalFile)
|
|
||||||
TryReplaceFile((char*)pPath, true);
|
|
||||||
|
|
||||||
return readFileFromFilesystem(filesystem, pPath, pOptions, a4, a5);
|
|
||||||
}
|
|
||||||
|
|
||||||
VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
|
|
||||||
{
|
|
||||||
spdlog::info("MountVPK {}", vpkPath);
|
|
||||||
VPKData* ret = mountVPK(fileSystem, vpkPath);
|
|
||||||
|
|
||||||
for (Mod mod : g_ModManager->m_loadedMods)
|
|
||||||
{
|
|
||||||
if (!mod.Enabled)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (ModVPKEntry& vpkEntry : mod.Vpks)
|
for (ModVPKEntry& vpkEntry : mod.Vpks)
|
||||||
|
@ -189,13 +159,13 @@ VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
|
||||||
{
|
{
|
||||||
// resolve vpk name and try to load one with the same name
|
// resolve vpk name and try to load one with the same name
|
||||||
// todo: we should be unloading these on map unload manually
|
// todo: we should be unloading these on map unload manually
|
||||||
std::string mapName(fs::path(vpkPath).filename().string());
|
std::string mapName(fs::path(pVpkPath).filename().string());
|
||||||
std::string modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string());
|
std::string modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string());
|
||||||
if (mapName.compare(modMapName))
|
if (mapName.compare(modMapName))
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
VPKData* loaded = mountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str());
|
VPKData* loaded = MountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str());
|
||||||
if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here
|
if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here
|
||||||
ret = loaded;
|
ret = loaded;
|
||||||
}
|
}
|
||||||
|
@ -203,3 +173,14 @@ VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD("filesystem_stdio.dll", Filesystem, (CModule module))
|
||||||
|
{
|
||||||
|
AUTOHOOK_DISPATCH()
|
||||||
|
|
||||||
|
R2::g_pFilesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
|
||||||
|
|
||||||
|
AddSearchPathHook.Dispatch((*g_pFilesystem)->m_vtable->AddSearchPath);
|
||||||
|
ReadFromCacheHook.Dispatch((*g_pFilesystem)->m_vtable->ReadFromCache);
|
||||||
|
MountVPKHook.Dispatch((*g_pFilesystem)->m_vtable->MountVPK);
|
||||||
|
}
|
||||||
|
|
|
@ -59,7 +59,8 @@ class IFileSystem
|
||||||
FileHandle_t (*Open)(
|
FileHandle_t (*Open)(
|
||||||
IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pOptions, const char* pathID, int64_t unknown);
|
IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pOptions, const char* pathID, int64_t unknown);
|
||||||
void (*Close)(IFileSystem* fileSystem, FileHandle_t file);
|
void (*Close)(IFileSystem* fileSystem, FileHandle_t file);
|
||||||
void* unknown2[6];
|
long long (*Seek)(IFileSystem::VTable2** fileSystem, FileHandle_t file, long long offset, long long whence);
|
||||||
|
void* unknown2[5];
|
||||||
bool (*FileExists)(IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pPathID);
|
bool (*FileExists)(IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pPathID);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,8 +68,11 @@ class IFileSystem
|
||||||
VTable2* m_vtable2;
|
VTable2* m_vtable2;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string ReadVPKFile(const char* path);
|
// use the R2 namespace for game funcs
|
||||||
std::string ReadVPKOriginalFile(const char* path);
|
namespace R2
|
||||||
|
{
|
||||||
|
extern SourceInterface<IFileSystem>* g_pFilesystem;
|
||||||
|
|
||||||
void InitialiseFilesystem(HMODULE baseAddress);
|
std::string ReadVPKFile(const char* path);
|
||||||
extern SourceInterface<IFileSystem>* g_Filesystem;
|
std::string ReadVPKOriginalFile(const char* path);
|
||||||
|
} // namespace R2
|
||||||
|
|
|
@ -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 "pch.h"
|
||||||
#include "hooks.h"
|
|
||||||
#include "hookutils.h"
|
|
||||||
#include "sigscanning.h"
|
|
||||||
#include "dedicated.h"
|
#include "dedicated.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <Psapi.h>
|
||||||
|
|
||||||
typedef LPSTR (*GetCommandLineAType)();
|
AUTOHOOK_INIT()
|
||||||
LPSTR GetCommandLineAHook();
|
|
||||||
|
|
||||||
typedef HMODULE (*LoadLibraryExAType)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
|
// called from the ON_DLL_LOAD macros
|
||||||
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
|
__dllLoadCallback::__dllLoadCallback(
|
||||||
|
eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn)
|
||||||
typedef HMODULE (*LoadLibraryAType)(LPCSTR lpLibFileName);
|
|
||||||
HMODULE LoadLibraryAHook(LPCSTR lpLibFileName);
|
|
||||||
|
|
||||||
typedef HMODULE (*LoadLibraryExWType)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
|
|
||||||
HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
|
|
||||||
|
|
||||||
typedef HMODULE (*LoadLibraryWType)(LPCWSTR lpLibFileName);
|
|
||||||
HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName);
|
|
||||||
|
|
||||||
GetCommandLineAType GetCommandLineAOriginal;
|
|
||||||
LoadLibraryExAType LoadLibraryExAOriginal;
|
|
||||||
LoadLibraryAType LoadLibraryAOriginal;
|
|
||||||
LoadLibraryExWType LoadLibraryExWOriginal;
|
|
||||||
LoadLibraryWType LoadLibraryWOriginal;
|
|
||||||
|
|
||||||
void InstallInitialHooks()
|
|
||||||
{
|
{
|
||||||
if (MH_Initialize() != MH_OK)
|
// parse reliesOn array from string
|
||||||
spdlog::error("MH_Initialize (minhook initialization) failed");
|
std::vector<std::string> reliesOnArray;
|
||||||
|
|
||||||
HookEnabler hook;
|
if (reliesOn.length() && reliesOn[0] != '(')
|
||||||
ENABLER_CREATEHOOK(
|
{
|
||||||
hook, reinterpret_cast<void*>(&GetCommandLineA), &GetCommandLineAHook, reinterpret_cast<LPVOID*>(&GetCommandLineAOriginal));
|
reliesOnArray.push_back(reliesOn);
|
||||||
ENABLER_CREATEHOOK(
|
}
|
||||||
hook, reinterpret_cast<void*>(&LoadLibraryExA), &LoadLibraryExAHook, reinterpret_cast<LPVOID*>(&LoadLibraryExAOriginal));
|
else
|
||||||
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryA), &LoadLibraryAHook, reinterpret_cast<LPVOID*>(&LoadLibraryAOriginal));
|
{
|
||||||
ENABLER_CREATEHOOK(
|
// follows the format (tag, tag, tag)
|
||||||
hook, reinterpret_cast<void*>(&LoadLibraryExW), &LoadLibraryExWHook, reinterpret_cast<LPVOID*>(&LoadLibraryExWOriginal));
|
std::string sCurrentTag;
|
||||||
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryW), &LoadLibraryWHook, reinterpret_cast<LPVOID*>(&LoadLibraryWOriginal));
|
for (int i = 1; i < reliesOn.length(); i++)
|
||||||
|
{
|
||||||
|
if (!isspace(reliesOn[i]))
|
||||||
|
{
|
||||||
|
if (reliesOn[i] == ',' || reliesOn[i] == ')')
|
||||||
|
{
|
||||||
|
reliesOnArray.push_back(sCurrentTag);
|
||||||
|
sCurrentTag = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sCurrentTag += reliesOn[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (side)
|
||||||
|
{
|
||||||
|
case eDllLoadCallbackSide::UNSIDED:
|
||||||
|
{
|
||||||
|
AddDllLoadCallback(dllName, callback, uniqueStr, reliesOnArray);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case eDllLoadCallbackSide::CLIENT:
|
||||||
|
{
|
||||||
|
AddDllLoadCallbackForClient(dllName, callback, uniqueStr, reliesOnArray);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case eDllLoadCallbackSide::DEDICATED_SERVER:
|
||||||
|
{
|
||||||
|
AddDllLoadCallbackForDedicatedServer(dllName, callback, uniqueStr, reliesOnArray);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LPSTR GetCommandLineAHook()
|
void __fileAutohook::Dispatch()
|
||||||
|
{
|
||||||
|
for (__autohook* hook : hooks)
|
||||||
|
hook->Dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void __fileAutohook::DispatchForModule(const char* pModuleName)
|
||||||
|
{
|
||||||
|
const int iModuleNameLen = strlen(pModuleName);
|
||||||
|
|
||||||
|
for (__autohook* hook : hooks)
|
||||||
|
if ((hook->iAddressResolutionMode == __autohook::OFFSET_STRING && !strncmp(pModuleName, hook->pAddrString, iModuleNameLen)) ||
|
||||||
|
(hook->iAddressResolutionMode == __autohook::PROCADDRESS && !strcmp(pModuleName, hook->pModuleName)))
|
||||||
|
hook->Dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
ManualHook::ManualHook(const char* funcName, LPVOID func) : pHookFunc(func), ppOrigFunc(nullptr)
|
||||||
|
{
|
||||||
|
const int iFuncNameStrlen = strlen(funcName);
|
||||||
|
pFuncName = new char[iFuncNameStrlen];
|
||||||
|
memcpy(pFuncName, funcName, iFuncNameStrlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
ManualHook::ManualHook(const char* funcName, LPVOID* orig, LPVOID func) : pHookFunc(func), ppOrigFunc(orig)
|
||||||
|
{
|
||||||
|
const int iFuncNameStrlen = strlen(funcName);
|
||||||
|
pFuncName = new char[iFuncNameStrlen];
|
||||||
|
memcpy(pFuncName, funcName, iFuncNameStrlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManualHook::Dispatch(LPVOID addr, LPVOID* orig)
|
||||||
|
{
|
||||||
|
if (orig)
|
||||||
|
ppOrigFunc = orig;
|
||||||
|
|
||||||
|
if (MH_CreateHook(addr, pHookFunc, ppOrigFunc) == MH_OK)
|
||||||
|
{
|
||||||
|
if (MH_EnableHook(addr) == MH_OK)
|
||||||
|
{
|
||||||
|
spdlog::info("Enabling hook {}", pFuncName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
spdlog::error("MH_EnableHook failed for function {}", pFuncName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
spdlog::error("MH_CreateHook failed for function {}", pFuncName);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dll load callback stuff
|
||||||
|
// this allows for code to register callbacks to be run as soon as a dll is loaded, mainly to allow for patches to be made on dll load
|
||||||
|
struct DllLoadCallback
|
||||||
|
{
|
||||||
|
std::string dll;
|
||||||
|
DllLoadCallbackFuncType callback;
|
||||||
|
std::string tag;
|
||||||
|
std::vector<std::string> reliesOn;
|
||||||
|
bool called;
|
||||||
|
};
|
||||||
|
|
||||||
|
// HACK: declaring and initialising this vector at file scope crashes on debug builds due to static initialisation order
|
||||||
|
// using a static var like this ensures that the vector is initialised lazily when it's used
|
||||||
|
std::vector<DllLoadCallback>& GetDllLoadCallbacks()
|
||||||
|
{
|
||||||
|
static std::vector<DllLoadCallback> vec = std::vector<DllLoadCallback>();
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector<std::string> reliesOn)
|
||||||
|
{
|
||||||
|
DllLoadCallback& callbackStruct = GetDllLoadCallbacks().emplace_back();
|
||||||
|
|
||||||
|
callbackStruct.dll = dll;
|
||||||
|
callbackStruct.callback = callback;
|
||||||
|
callbackStruct.tag = tag;
|
||||||
|
callbackStruct.reliesOn = reliesOn;
|
||||||
|
callbackStruct.called = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddDllLoadCallbackForDedicatedServer(
|
||||||
|
std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector<std::string> reliesOn)
|
||||||
|
{
|
||||||
|
if (!IsDedicatedServer())
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddDllLoadCallback(dll, callback, tag, reliesOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback, std::string tag, std::vector<std::string> reliesOn)
|
||||||
|
{
|
||||||
|
if (IsDedicatedServer())
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddDllLoadCallback(dll, callback, tag, reliesOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFuncName)
|
||||||
|
{
|
||||||
|
char* pStrippedFuncName = (char*)pFuncName;
|
||||||
|
// strip & char from funcname
|
||||||
|
if (*pStrippedFuncName == '&')
|
||||||
|
pStrippedFuncName++;
|
||||||
|
|
||||||
|
if (MH_CreateHook(pTarget, pDetour, (LPVOID*)ppOriginal) == MH_OK)
|
||||||
|
{
|
||||||
|
if (MH_EnableHook(pTarget) == MH_OK)
|
||||||
|
spdlog::info("Enabling hook {}", pStrippedFuncName);
|
||||||
|
else
|
||||||
|
spdlog::error("MH_EnableHook failed for function {}", pStrippedFuncName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
spdlog::error("MH_CreateHook failed for function {}", pStrippedFuncName);
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTOHOOK_ABSOLUTEADDR(_GetCommandLineA, GetCommandLineA, LPSTR, WINAPI, ())
|
||||||
{
|
{
|
||||||
static char* cmdlineModified;
|
static char* cmdlineModified;
|
||||||
static char* cmdlineOrg;
|
static char* cmdlineOrg;
|
||||||
|
|
||||||
if (cmdlineOrg == nullptr || cmdlineModified == nullptr)
|
if (cmdlineOrg == nullptr || cmdlineModified == nullptr)
|
||||||
{
|
{
|
||||||
cmdlineOrg = GetCommandLineAOriginal();
|
cmdlineOrg = _GetCommandLineA();
|
||||||
bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument
|
bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument
|
||||||
bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs");
|
bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs");
|
||||||
|
|
||||||
|
@ -111,77 +243,86 @@ LPSTR GetCommandLineAHook()
|
||||||
return cmdlineModified;
|
return cmdlineModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dll load callback stuff
|
std::vector<std::string> calledTags;
|
||||||
// this allows for code to register callbacks to be run as soon as a dll is loaded, mainly to allow for patches to be made on dll load
|
|
||||||
struct DllLoadCallback
|
|
||||||
{
|
|
||||||
std::string dll;
|
|
||||||
DllLoadCallbackFuncType callback;
|
|
||||||
bool called;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<DllLoadCallback*> dllLoadCallbacks;
|
|
||||||
|
|
||||||
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback)
|
|
||||||
{
|
|
||||||
DllLoadCallback* callbackStruct = new DllLoadCallback;
|
|
||||||
callbackStruct->dll = dll;
|
|
||||||
callbackStruct->callback = callback;
|
|
||||||
callbackStruct->called = false;
|
|
||||||
|
|
||||||
dllLoadCallbacks.push_back(callbackStruct);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback)
|
|
||||||
{
|
|
||||||
if (!IsDedicatedServer())
|
|
||||||
return;
|
|
||||||
|
|
||||||
DllLoadCallback* callbackStruct = new DllLoadCallback;
|
|
||||||
callbackStruct->dll = dll;
|
|
||||||
callbackStruct->callback = callback;
|
|
||||||
callbackStruct->called = false;
|
|
||||||
|
|
||||||
dllLoadCallbacks.push_back(callbackStruct);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback)
|
|
||||||
{
|
|
||||||
if (IsDedicatedServer())
|
|
||||||
return;
|
|
||||||
|
|
||||||
DllLoadCallback* callbackStruct = new DllLoadCallback;
|
|
||||||
callbackStruct->dll = dll;
|
|
||||||
callbackStruct->callback = callback;
|
|
||||||
callbackStruct->called = false;
|
|
||||||
|
|
||||||
dllLoadCallbacks.push_back(callbackStruct);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress)
|
void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress)
|
||||||
{
|
{
|
||||||
for (auto& callbackStruct : dllLoadCallbacks)
|
CModule cModule(moduleAddress);
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
if (!callbackStruct->called &&
|
bool bDoneCalling = true;
|
||||||
strstr(lpLibFileName + (strlen(lpLibFileName) - callbackStruct->dll.length()), callbackStruct->dll.c_str()) != nullptr)
|
|
||||||
|
for (auto& callbackStruct : GetDllLoadCallbacks())
|
||||||
{
|
{
|
||||||
callbackStruct->callback(moduleAddress);
|
if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
|
||||||
callbackStruct->called = true;
|
{
|
||||||
|
bool bShouldContinue = false;
|
||||||
|
|
||||||
|
if (!callbackStruct.reliesOn.empty())
|
||||||
|
{
|
||||||
|
for (std::string tag : callbackStruct.reliesOn)
|
||||||
|
{
|
||||||
|
if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
|
||||||
|
{
|
||||||
|
bDoneCalling = false;
|
||||||
|
bShouldContinue = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bShouldContinue)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
callbackStruct.callback(moduleAddress);
|
||||||
|
calledTags.push_back(callbackStruct.tag);
|
||||||
|
callbackStruct.called = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bDoneCalling)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress)
|
void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress)
|
||||||
{
|
{
|
||||||
for (auto& callbackStruct : dllLoadCallbacks)
|
CModule cModule(moduleAddress);
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
std::wstring wcharStrDll = std::wstring(callbackStruct->dll.begin(), callbackStruct->dll.end());
|
bool bDoneCalling = true;
|
||||||
const wchar_t* callbackDll = wcharStrDll.c_str();
|
|
||||||
if (!callbackStruct->called && wcsstr(lpLibFileName + (wcslen(lpLibFileName) - wcharStrDll.length()), callbackDll) != nullptr)
|
for (auto& callbackStruct : GetDllLoadCallbacks())
|
||||||
{
|
{
|
||||||
callbackStruct->callback(moduleAddress);
|
if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
|
||||||
callbackStruct->called = true;
|
{
|
||||||
|
bool bShouldContinue = false;
|
||||||
|
|
||||||
|
if (!callbackStruct.reliesOn.empty())
|
||||||
|
{
|
||||||
|
for (std::string tag : callbackStruct.reliesOn)
|
||||||
|
{
|
||||||
|
if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
|
||||||
|
{
|
||||||
|
bDoneCalling = false;
|
||||||
|
bShouldContinue = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bShouldContinue)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
callbackStruct.callback(moduleAddress);
|
||||||
|
calledTags.push_back(callbackStruct.tag);
|
||||||
|
callbackStruct.called = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bDoneCalling)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,65 +349,78 @@ void CallAllPendingDLLLoadCallbacks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
|
// clang-format off
|
||||||
|
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, LoadLibraryExA,
|
||||||
|
HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
|
HMODULE moduleAddress;
|
||||||
|
|
||||||
|
// replace xinput dll with one that has ASLR
|
||||||
if (!strncmp(lpLibFileName, "XInput1_3.dll", 14))
|
if (!strncmp(lpLibFileName, "XInput1_3.dll", 14))
|
||||||
{
|
{
|
||||||
HMODULE moduleAddress = LoadLibraryExAOriginal("XInput9_1_0.dll", hFile, dwFlags);
|
moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags);
|
||||||
if (moduleAddress)
|
|
||||||
{
|
if (!moduleAddress)
|
||||||
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR);
|
MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR);
|
||||||
exit(-1);
|
exit(-1);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
return moduleAddress;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
moduleAddress = _LoadLibraryExA(lpLibFileName, hFile, dwFlags);
|
||||||
HMODULE moduleAddress = LoadLibraryExAOriginal(lpLibFileName, hFile, dwFlags);
|
|
||||||
if (moduleAddress)
|
|
||||||
{
|
|
||||||
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
|
|
||||||
}
|
|
||||||
return moduleAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HMODULE LoadLibraryAHook(LPCSTR lpLibFileName)
|
|
||||||
{
|
|
||||||
HMODULE moduleAddress = LoadLibraryAOriginal(lpLibFileName);
|
|
||||||
|
|
||||||
if (moduleAddress)
|
if (moduleAddress)
|
||||||
{
|
|
||||||
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
|
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
|
||||||
}
|
|
||||||
|
|
||||||
return moduleAddress;
|
return moduleAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
|
// clang-format off
|
||||||
|
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryA, LoadLibraryA,
|
||||||
|
HMODULE, WINAPI, (LPCSTR lpLibFileName))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
HMODULE moduleAddress = LoadLibraryExWOriginal(lpLibFileName, hFile, dwFlags);
|
HMODULE moduleAddress = _LoadLibraryA(lpLibFileName);
|
||||||
|
|
||||||
if (moduleAddress)
|
if (moduleAddress)
|
||||||
{
|
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
|
||||||
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
return moduleAddress;
|
return moduleAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName)
|
// clang-format off
|
||||||
|
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExW, LoadLibraryExW,
|
||||||
|
HMODULE, WINAPI, (LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
HMODULE moduleAddress = LoadLibraryWOriginal(lpLibFileName);
|
HMODULE moduleAddress = _LoadLibraryExW(lpLibFileName, hFile, dwFlags);
|
||||||
|
|
||||||
if (moduleAddress)
|
if (moduleAddress)
|
||||||
{
|
|
||||||
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
|
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
|
||||||
}
|
|
||||||
|
|
||||||
return moduleAddress;
|
return moduleAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryW, LoadLibraryW,
|
||||||
|
HMODULE, WINAPI, (LPCWSTR lpLibFileName))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
HMODULE moduleAddress = _LoadLibraryW(lpLibFileName);
|
||||||
|
|
||||||
|
if (moduleAddress)
|
||||||
|
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
|
||||||
|
|
||||||
|
return moduleAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstallInitialHooks()
|
||||||
|
{
|
||||||
|
if (MH_Initialize() != MH_OK)
|
||||||
|
spdlog::error("MH_Initialize (minhook initialization) failed");
|
||||||
|
|
||||||
|
AUTOHOOK_DISPATCH()
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,311 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "memory.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
void InstallInitialHooks();
|
void InstallInitialHooks();
|
||||||
|
|
||||||
typedef void (*DllLoadCallbackFuncType)(HMODULE moduleAddress);
|
typedef void (*DllLoadCallbackFuncType)(CModule moduleAddress);
|
||||||
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback);
|
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
|
||||||
void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback);
|
void AddDllLoadCallbackForDedicatedServer(
|
||||||
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback);
|
std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
|
||||||
|
void AddDllLoadCallbackForClient(
|
||||||
|
std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
|
||||||
|
|
||||||
void CallAllPendingDLLLoadCallbacks();
|
void CallAllPendingDLLLoadCallbacks();
|
||||||
|
|
||||||
|
// new dll load callback stuff
|
||||||
|
enum class eDllLoadCallbackSide
|
||||||
|
{
|
||||||
|
UNSIDED,
|
||||||
|
CLIENT,
|
||||||
|
DEDICATED_SERVER
|
||||||
|
};
|
||||||
|
|
||||||
|
class __dllLoadCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
__dllLoadCallback() = delete;
|
||||||
|
__dllLoadCallback(
|
||||||
|
eDllLoadCallbackSide side,
|
||||||
|
const std::string dllName,
|
||||||
|
DllLoadCallbackFuncType callback,
|
||||||
|
std::string uniqueStr,
|
||||||
|
std::string reliesOn);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define __CONCAT3(x, y, z) x##y##z
|
||||||
|
#define CONCAT3(x, y, z) __CONCAT3(x, y, z)
|
||||||
|
#define __CONCAT2(x, y) x##y
|
||||||
|
#define CONCAT2(x, y) __CONCAT2(x, y)
|
||||||
|
#define __STR(s) #s
|
||||||
|
|
||||||
|
// adds a callback to be called when a given dll is loaded, for creating hooks and such
|
||||||
|
#define __ON_DLL_LOAD(dllName, side, uniquestr, reliesOn, args) \
|
||||||
|
void CONCAT2(__dllLoadCallback, uniquestr) args; \
|
||||||
|
namespace \
|
||||||
|
{ \
|
||||||
|
__dllLoadCallback CONCAT2(__dllLoadCallbackInstance, __LINE__)( \
|
||||||
|
side, dllName, CONCAT2(__dllLoadCallback, uniquestr), __STR(uniquestr), reliesOn); \
|
||||||
|
} \
|
||||||
|
void CONCAT2(__dllLoadCallback, uniquestr) args
|
||||||
|
|
||||||
|
#define ON_DLL_LOAD(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, "", args)
|
||||||
|
#define ON_DLL_LOAD_RELIESON(dllName, uniquestr, reliesOn, args) \
|
||||||
|
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, __STR(reliesOn), args)
|
||||||
|
#define ON_DLL_LOAD_CLIENT(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, "", args)
|
||||||
|
#define ON_DLL_LOAD_CLIENT_RELIESON(dllName, uniquestr, reliesOn, args) \
|
||||||
|
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, __STR(reliesOn), args)
|
||||||
|
#define ON_DLL_LOAD_DEDI(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, "", args)
|
||||||
|
#define ON_DLL_LOAD_DEDI_RELIESON(dllName, uniquestr, reliesOn, args) \
|
||||||
|
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, __STR(reliesOn), args)
|
||||||
|
|
||||||
|
// new macro hook stuff
|
||||||
|
class __autohook;
|
||||||
|
|
||||||
|
class __fileAutohook
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<__autohook*> hooks;
|
||||||
|
|
||||||
|
void Dispatch();
|
||||||
|
void DispatchForModule(const char* pModuleName);
|
||||||
|
};
|
||||||
|
|
||||||
|
// initialise autohooks for this file
|
||||||
|
#define AUTOHOOK_INIT() \
|
||||||
|
namespace \
|
||||||
|
{ \
|
||||||
|
__fileAutohook __FILEAUTOHOOK; \
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch all autohooks in this file
|
||||||
|
#define AUTOHOOK_DISPATCH() __FILEAUTOHOOK.Dispatch();
|
||||||
|
|
||||||
|
#define AUTOHOOK_DISPATCH_MODULE(moduleName) __FILEAUTOHOOK.DispatchForModule(__STR(moduleName));
|
||||||
|
|
||||||
|
class __autohook
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum AddressResolutionMode
|
||||||
|
{
|
||||||
|
OFFSET_STRING, // we're using a string that of the format dllname.dll + offset
|
||||||
|
ABSOLUTE_ADDR, // we're using an absolute address, we don't need to process it at all
|
||||||
|
PROCADDRESS // resolve using GetModuleHandle and GetProcAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
char* pFuncName;
|
||||||
|
|
||||||
|
LPVOID pHookFunc;
|
||||||
|
LPVOID* ppOrigFunc;
|
||||||
|
|
||||||
|
// address resolution props
|
||||||
|
AddressResolutionMode iAddressResolutionMode;
|
||||||
|
char* pAddrString = nullptr; // for OFFSET_STRING
|
||||||
|
LPVOID iAbsoluteAddress = nullptr; // for ABSOLUTE_ADDR
|
||||||
|
char* pModuleName; // for PROCADDRESS
|
||||||
|
char* pProcName; // for PROCADDRESS
|
||||||
|
|
||||||
|
public:
|
||||||
|
__autohook() = delete;
|
||||||
|
|
||||||
|
__autohook(__fileAutohook* autohook, const char* funcName, LPVOID absoluteAddress, LPVOID* orig, LPVOID func)
|
||||||
|
: pHookFunc(func), ppOrigFunc(orig), iAbsoluteAddress(absoluteAddress)
|
||||||
|
{
|
||||||
|
iAddressResolutionMode = ABSOLUTE_ADDR;
|
||||||
|
|
||||||
|
const int iFuncNameStrlen = strlen(funcName) + 1;
|
||||||
|
pFuncName = new char[iFuncNameStrlen];
|
||||||
|
memcpy(pFuncName, funcName, iFuncNameStrlen);
|
||||||
|
|
||||||
|
autohook->hooks.push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
__autohook(__fileAutohook* autohook, const char* funcName, const char* addrString, LPVOID* orig, LPVOID func)
|
||||||
|
: pHookFunc(func), ppOrigFunc(orig)
|
||||||
|
{
|
||||||
|
iAddressResolutionMode = OFFSET_STRING;
|
||||||
|
|
||||||
|
const int iFuncNameStrlen = strlen(funcName) + 1;
|
||||||
|
pFuncName = new char[iFuncNameStrlen];
|
||||||
|
memcpy(pFuncName, funcName, iFuncNameStrlen);
|
||||||
|
|
||||||
|
const int iAddrStrlen = strlen(addrString) + 1;
|
||||||
|
pAddrString = new char[iAddrStrlen];
|
||||||
|
memcpy(pAddrString, addrString, iAddrStrlen);
|
||||||
|
|
||||||
|
autohook->hooks.push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
__autohook(__fileAutohook* autohook, const char* funcName, const char* moduleName, const char* procName, LPVOID* orig, LPVOID func)
|
||||||
|
: pHookFunc(func), ppOrigFunc(orig)
|
||||||
|
{
|
||||||
|
iAddressResolutionMode = PROCADDRESS;
|
||||||
|
|
||||||
|
const int iFuncNameStrlen = strlen(funcName) + 1;
|
||||||
|
pFuncName = new char[iFuncNameStrlen];
|
||||||
|
memcpy(pFuncName, funcName, iFuncNameStrlen);
|
||||||
|
|
||||||
|
const int iModuleNameStrlen = strlen(moduleName) + 1;
|
||||||
|
pModuleName = new char[iModuleNameStrlen];
|
||||||
|
memcpy(pModuleName, moduleName, iModuleNameStrlen);
|
||||||
|
|
||||||
|
const int iProcNameStrlen = strlen(procName) + 1;
|
||||||
|
pProcName = new char[iProcNameStrlen];
|
||||||
|
memcpy(pProcName, procName, iProcNameStrlen);
|
||||||
|
|
||||||
|
autohook->hooks.push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~__autohook()
|
||||||
|
{
|
||||||
|
delete[] pFuncName;
|
||||||
|
|
||||||
|
if (pAddrString)
|
||||||
|
delete[] pAddrString;
|
||||||
|
|
||||||
|
if (pModuleName)
|
||||||
|
delete[] pModuleName;
|
||||||
|
|
||||||
|
if (pProcName)
|
||||||
|
delete[] pProcName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatch()
|
||||||
|
{
|
||||||
|
LPVOID targetAddr = nullptr;
|
||||||
|
|
||||||
|
// determine the address of the function we're hooking
|
||||||
|
switch (iAddressResolutionMode)
|
||||||
|
{
|
||||||
|
case ABSOLUTE_ADDR:
|
||||||
|
{
|
||||||
|
targetAddr = iAbsoluteAddress;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OFFSET_STRING:
|
||||||
|
{
|
||||||
|
// in the format server.dll + 0xDEADBEEF
|
||||||
|
int iDllNameEnd = 0;
|
||||||
|
for (; !isspace(pAddrString[iDllNameEnd]) && pAddrString[iDllNameEnd] != '+'; iDllNameEnd++)
|
||||||
|
;
|
||||||
|
|
||||||
|
char* pModuleName = new char[iDllNameEnd + 1];
|
||||||
|
memcpy(pModuleName, pAddrString, iDllNameEnd);
|
||||||
|
pModuleName[iDllNameEnd] = '\0';
|
||||||
|
|
||||||
|
// get the module address
|
||||||
|
const HMODULE pModuleAddr = GetModuleHandleA(pModuleName);
|
||||||
|
|
||||||
|
if (!pModuleAddr)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// get the offset string
|
||||||
|
uintptr_t iOffset = 0;
|
||||||
|
|
||||||
|
int iOffsetBegin = iDllNameEnd;
|
||||||
|
int iOffsetEnd = strlen(pAddrString);
|
||||||
|
|
||||||
|
// seek until we hit the start of the number offset
|
||||||
|
for (; !(pAddrString[iOffsetBegin] >= '0' && pAddrString[iOffsetBegin] <= '9') && pAddrString[iOffsetBegin]; iOffsetBegin++)
|
||||||
|
;
|
||||||
|
|
||||||
|
bool bIsHex =
|
||||||
|
pAddrString[iOffsetBegin] == '0' && (pAddrString[iOffsetBegin + 1] == 'X' || pAddrString[iOffsetBegin + 1] == 'x');
|
||||||
|
if (bIsHex)
|
||||||
|
iOffset = std::stoi(pAddrString + iOffsetBegin + 2, 0, 16);
|
||||||
|
else
|
||||||
|
iOffset = std::stoi(pAddrString + iOffsetBegin);
|
||||||
|
|
||||||
|
targetAddr = (LPVOID)((uintptr_t)pModuleAddr + iOffset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PROCADDRESS:
|
||||||
|
{
|
||||||
|
targetAddr = GetProcAddress(GetModuleHandleA(pModuleName), pProcName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MH_CreateHook(targetAddr, pHookFunc, ppOrigFunc) == MH_OK)
|
||||||
|
{
|
||||||
|
if (MH_EnableHook(targetAddr) == MH_OK)
|
||||||
|
spdlog::info("Enabling hook {}", pFuncName);
|
||||||
|
else
|
||||||
|
spdlog::error("MH_EnableHook failed for function {}", pFuncName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
spdlog::error("MH_CreateHook failed for function {}", pFuncName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// hook a function at a given offset from a dll to be dispatched with AUTOHOOK_DISPATCH()
|
||||||
|
#define AUTOHOOK(name, addrString, type, callingConvention, args) \
|
||||||
|
type callingConvention CONCAT2(__autohookfunc, name) args; \
|
||||||
|
namespace \
|
||||||
|
{ \
|
||||||
|
type(*callingConvention name) args; \
|
||||||
|
__autohook CONCAT2(__autohook, __LINE__)( \
|
||||||
|
&__FILEAUTOHOOK, __STR(name), __STR(addrString), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
|
||||||
|
} \
|
||||||
|
type callingConvention CONCAT2(__autohookfunc, name) args
|
||||||
|
|
||||||
|
// hook a function at a given absolute constant address to be dispatched with AUTOHOOK_DISPATCH()
|
||||||
|
#define AUTOHOOK_ABSOLUTEADDR(name, addr, type, callingConvention, args) \
|
||||||
|
type callingConvention CONCAT2(__autohookfunc, name) args; \
|
||||||
|
namespace \
|
||||||
|
{ \
|
||||||
|
type(*callingConvention name) args; \
|
||||||
|
__autohook \
|
||||||
|
CONCAT2(__autohook, __LINE__)(&__FILEAUTOHOOK, __STR(name), addr, (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
|
||||||
|
} \
|
||||||
|
type callingConvention CONCAT2(__autohookfunc, name) args
|
||||||
|
|
||||||
|
// hook a function at a given module and exported function to be dispatched with AUTOHOOK_DISPATCH()
|
||||||
|
#define AUTOHOOK_PROCADDRESS(name, moduleName, procName, type, callingConvention, args) \
|
||||||
|
type callingConvention CONCAT2(__autohookfunc, name) args; \
|
||||||
|
namespace \
|
||||||
|
{ \
|
||||||
|
type(*callingConvention name) args; \
|
||||||
|
__autohook CONCAT2(__autohook, __LINE__)( \
|
||||||
|
&__FILEAUTOHOOK, __STR(name), __STR(moduleName), __STR(procName), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
|
||||||
|
} \
|
||||||
|
type callingConvention CONCAT2(__autohookfunc, name) \
|
||||||
|
args
|
||||||
|
|
||||||
|
class ManualHook
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
char* pFuncName;
|
||||||
|
|
||||||
|
LPVOID pHookFunc;
|
||||||
|
LPVOID* ppOrigFunc;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ManualHook() = delete;
|
||||||
|
ManualHook(const char* funcName, LPVOID func);
|
||||||
|
ManualHook(const char* funcName, LPVOID* orig, LPVOID func);
|
||||||
|
bool Dispatch(LPVOID addr, LPVOID* orig = nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
// hook a function to be dispatched manually later
|
||||||
|
#define HOOK(varName, originalFunc, type, callingConvention, args) \
|
||||||
|
namespace \
|
||||||
|
{ \
|
||||||
|
type(*callingConvention originalFunc) args; \
|
||||||
|
} \
|
||||||
|
type callingConvention CONCAT2(__manualhookfunc, varName) args; \
|
||||||
|
ManualHook varName = ManualHook(__STR(varName), (LPVOID*)&originalFunc, (LPVOID)CONCAT2(__manualhookfunc, varName)); \
|
||||||
|
type callingConvention CONCAT2(__manualhookfunc, varName) args
|
||||||
|
|
||||||
|
#define HOOK_NOORIG(varName, type, callingConvention, args) \
|
||||||
|
type callingConvention CONCAT2(__manualhookfunc, varName) args; \
|
||||||
|
ManualHook varName = ManualHook(__STR(varName), (LPVOID)CONCAT2(__manualhookfunc, varName)); \
|
||||||
|
type callingConvention CONCAT2(__manualhookfunc, varName) \
|
||||||
|
args
|
||||||
|
|
||||||
|
void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFuncName = "");
|
||||||
|
#define MAKEHOOK(pTarget, pDetour, ppOriginal) MakeHook(pTarget, pDetour, ppOriginal, __STR(pDetour))
|
||||||
|
|
|
@ -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 "pch.h"
|
||||||
#include "keyvalues.h"
|
|
||||||
#include "modmanager.h"
|
#include "modmanager.h"
|
||||||
#include "filesystem.h"
|
#include "filesystem.h"
|
||||||
#include "hookutils.h"
|
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
// hook forward defs
|
AUTOHOOK_INIT()
|
||||||
typedef char (*KeyValues__LoadFromBufferType)(
|
|
||||||
void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7);
|
|
||||||
KeyValues__LoadFromBufferType KeyValues__LoadFromBuffer;
|
|
||||||
char KeyValues__LoadFromBufferHook(
|
|
||||||
void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7);
|
|
||||||
|
|
||||||
void InitialiseKeyValues(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook, (char*)baseAddress + 0x426C30, &KeyValues__LoadFromBufferHook, reinterpret_cast<LPVOID*>(&KeyValues__LoadFromBuffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
void* savedFilesystemPtr;
|
|
||||||
|
|
||||||
char KeyValues__LoadFromBufferHook(void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7)
|
|
||||||
{
|
|
||||||
// this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of
|
|
||||||
// LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it
|
|
||||||
// from a function call that always happens before playlists is loaded
|
|
||||||
if (pFileSystem != nullptr)
|
|
||||||
savedFilesystemPtr = pFileSystem;
|
|
||||||
if (!pFileSystem && !strcmp(resourceName, "playlists"))
|
|
||||||
pFileSystem = savedFilesystemPtr;
|
|
||||||
|
|
||||||
return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModManager::TryBuildKeyValues(const char* filename)
|
void ModManager::TryBuildKeyValues(const char* filename)
|
||||||
{
|
{
|
||||||
spdlog::info("Building KeyValues for file {}", filename);
|
spdlog::info("Building KeyValues for file {}", filename);
|
||||||
|
|
||||||
std::string normalisedPath = fs::path(filename).lexically_normal().string();
|
std::string normalisedPath = g_pModManager->NormaliseModFilePath(fs::path(filename));
|
||||||
fs::path compiledPath = GetCompiledAssetsPath() / filename;
|
fs::path compiledPath = GetCompiledAssetsPath() / filename;
|
||||||
fs::path compiledDir = compiledPath.parent_path();
|
fs::path compiledDir = compiledPath.parent_path();
|
||||||
fs::create_directories(compiledDir);
|
fs::create_directories(compiledDir);
|
||||||
|
@ -54,14 +25,14 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
||||||
|
|
||||||
// copy over patch kv files, and add #bases to new file, last mods' patches should be applied first
|
// copy over patch kv files, and add #bases to new file, last mods' patches should be applied first
|
||||||
// note: #include should be identical but it's actually just broken, thanks respawn
|
// note: #include should be identical but it's actually just broken, thanks respawn
|
||||||
for (int64_t i = m_loadedMods.size() - 1; i > -1; i--)
|
for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
|
||||||
{
|
{
|
||||||
if (!m_loadedMods[i].Enabled)
|
if (!m_LoadedMods[i].m_bEnabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
size_t fileHash = STR_HASH(normalisedPath);
|
size_t fileHash = STR_HASH(normalisedPath);
|
||||||
auto modKv = m_loadedMods[i].KeyValues.find(fileHash);
|
auto modKv = m_LoadedMods[i].KeyValues.find(fileHash);
|
||||||
if (modKv != m_loadedMods[i].KeyValues.end())
|
if (modKv != m_LoadedMods[i].KeyValues.end())
|
||||||
{
|
{
|
||||||
// should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt"
|
// should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt"
|
||||||
|
|
||||||
|
@ -76,7 +47,7 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
||||||
|
|
||||||
fs::remove(compiledDir / patchFilePath);
|
fs::remove(compiledDir / patchFilePath);
|
||||||
|
|
||||||
fs::copy_file(m_loadedMods[i].ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
|
fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +57,7 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
||||||
newKvs += "\"\n";
|
newKvs += "\"\n";
|
||||||
|
|
||||||
// load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons)
|
// load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons)
|
||||||
std::string originalFile = ReadVPKOriginalFile(filename);
|
std::string originalFile = R2::ReadVPKOriginalFile(filename);
|
||||||
|
|
||||||
if (!originalFile.length())
|
if (!originalFile.length())
|
||||||
{
|
{
|
||||||
|
@ -96,7 +67,6 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
||||||
|
|
||||||
char rootName[64];
|
char rootName[64];
|
||||||
memset(rootName, 0, sizeof(rootName));
|
memset(rootName, 0, sizeof(rootName));
|
||||||
rootName[63] = '\0';
|
|
||||||
|
|
||||||
// iterate until we hit an ascii char that isn't in a # command or comment to get root obj name
|
// iterate until we hit an ascii char that isn't in a # command or comment to get root obj name
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
@ -127,11 +97,36 @@ void ModManager::TryBuildKeyValues(const char* filename)
|
||||||
writeStream.close();
|
writeStream.close();
|
||||||
|
|
||||||
ModOverrideFile overrideFile;
|
ModOverrideFile overrideFile;
|
||||||
overrideFile.owningMod = nullptr;
|
overrideFile.m_pOwningMod = nullptr;
|
||||||
overrideFile.path = normalisedPath;
|
overrideFile.m_Path = normalisedPath;
|
||||||
|
|
||||||
if (m_modFiles.find(normalisedPath) == m_modFiles.end())
|
if (m_ModFiles.find(normalisedPath) == m_ModFiles.end())
|
||||||
m_modFiles.insert(std::make_pair(normalisedPath, overrideFile));
|
m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile));
|
||||||
else
|
else
|
||||||
m_modFiles[normalisedPath] = overrideFile;
|
m_ModFiles[normalisedPath] = overrideFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(KeyValues__LoadFromBuffer, engine.dll + 0x426C30,
|
||||||
|
char, __fastcall, (void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
static void* pSavedFilesystemPtr = nullptr;
|
||||||
|
|
||||||
|
// this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of
|
||||||
|
// LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it
|
||||||
|
// from a function call that always happens before playlists is loaded
|
||||||
|
|
||||||
|
// note: would be better if we could serialize this to disk for playlists, as this method breaks saving playlists in demos
|
||||||
|
if (pFileSystem != nullptr)
|
||||||
|
pSavedFilesystemPtr = pFileSystem;
|
||||||
|
if (!pFileSystem && !strcmp(resourceName, "playlists"))
|
||||||
|
pFileSystem = pSavedFilesystemPtr;
|
||||||
|
|
||||||
|
return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7);
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD("engine.dll", KeyValues, (CModule module))
|
||||||
|
{
|
||||||
|
AUTOHOOK_DISPATCH()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
void InitialiseKeyValues(HMODULE baseAddress);
|
|
|
@ -1,18 +1,13 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "languagehooks.h"
|
#include "tier0.h"
|
||||||
#include "gameutils.h"
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
AUTOHOOK_INIT()
|
||||||
|
|
||||||
typedef char* (*GetGameLanguageType)();
|
|
||||||
char* GetGameLanguage();
|
|
||||||
|
|
||||||
typedef LANGID (*Tier0_DetectDefaultLanguageType)();
|
typedef LANGID (*Tier0_DetectDefaultLanguageType)();
|
||||||
|
|
||||||
GetGameLanguageType GetGameLanguageOriginal;
|
|
||||||
|
|
||||||
bool CheckLangAudioExists(char* lang)
|
bool CheckLangAudioExists(char* lang)
|
||||||
{
|
{
|
||||||
std::string path {"r2\\sound\\general_"};
|
std::string path {"r2\\sound\\general_"};
|
||||||
|
@ -52,7 +47,10 @@ std::string GetAnyInstalledAudioLanguage()
|
||||||
return "NO LANGUAGE DETECTED";
|
return "NO LANGUAGE DETECTED";
|
||||||
}
|
}
|
||||||
|
|
||||||
char* GetGameLanguageHook()
|
// clang-format off
|
||||||
|
AUTOHOOK(GetGameLanguage, tier0.dll + 0xF560,
|
||||||
|
char*, __fastcall, ())
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
auto tier0Handle = GetModuleHandleA("tier0.dll");
|
auto tier0Handle = GetModuleHandleA("tier0.dll");
|
||||||
auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage");
|
auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage");
|
||||||
|
@ -60,7 +58,7 @@ char* GetGameLanguageHook()
|
||||||
bool& canOriginDictateLang = *(bool*)((char*)tier0Handle + 0xA9A90);
|
bool& canOriginDictateLang = *(bool*)((char*)tier0Handle + 0xA9A90);
|
||||||
|
|
||||||
const char* forcedLanguage;
|
const char* forcedLanguage;
|
||||||
if (CommandLine()->CheckParm("-language", &forcedLanguage))
|
if (Tier0::CommandLine()->CheckParm("-language", &forcedLanguage))
|
||||||
{
|
{
|
||||||
if (!CheckLangAudioExists((char*)forcedLanguage))
|
if (!CheckLangAudioExists((char*)forcedLanguage))
|
||||||
{
|
{
|
||||||
|
@ -79,7 +77,7 @@ char* GetGameLanguageHook()
|
||||||
|
|
||||||
canOriginDictateLang = true; // let it try
|
canOriginDictateLang = true; // let it try
|
||||||
{
|
{
|
||||||
auto lang = GetGameLanguageOriginal();
|
auto lang = GetGameLanguage();
|
||||||
if (!CheckLangAudioExists(lang))
|
if (!CheckLangAudioExists(lang))
|
||||||
{
|
{
|
||||||
if (strcmp(lang, "russian") !=
|
if (strcmp(lang, "russian") !=
|
||||||
|
@ -97,7 +95,7 @@ char* GetGameLanguageHook()
|
||||||
Tier0_DetectDefaultLanguageType(); // force the global in tier0 to be populated with language inferred from user's system rather than
|
Tier0_DetectDefaultLanguageType(); // force the global in tier0 to be populated with language inferred from user's system rather than
|
||||||
// defaulting to Russian
|
// defaulting to Russian
|
||||||
canOriginDictateLang = false; // Origin has no say anymore, we will fallback to user's system setup language
|
canOriginDictateLang = false; // Origin has no say anymore, we will fallback to user's system setup language
|
||||||
auto lang = GetGameLanguageOriginal();
|
auto lang = GetGameLanguage();
|
||||||
spdlog::info("Detected system language: {}", lang);
|
spdlog::info("Detected system language: {}", lang);
|
||||||
if (!CheckLangAudioExists(lang))
|
if (!CheckLangAudioExists(lang))
|
||||||
{
|
{
|
||||||
|
@ -112,8 +110,7 @@ char* GetGameLanguageHook()
|
||||||
return lang;
|
return lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseTier0LanguageHooks(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT("tier0.dll", LanguageHooks, (CModule module))
|
||||||
{
|
{
|
||||||
HookEnabler hook;
|
AUTOHOOK_DISPATCH()
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xF560, &GetGameLanguageHook, reinterpret_cast<LPVOID*>(&GetGameLanguageOriginal));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
void InitialiseTier0LanguageHooks(HMODULE baseAddress);
|
|
|
@ -1,76 +1,44 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "latencyflex.h"
|
|
||||||
#include "hookutils.h"
|
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
|
|
||||||
typedef void (*OnRenderStartType)();
|
AUTOHOOK_INIT()
|
||||||
OnRenderStartType OnRenderStart;
|
|
||||||
|
|
||||||
ConVar* Cvar_r_latencyflex;
|
ConVar* Cvar_r_latencyflex;
|
||||||
|
|
||||||
HMODULE m_lfxModule {};
|
void (*m_winelfx_WaitAndBeginFrame)();
|
||||||
typedef void (*PFN_lfx_WaitAndBeginFrame)();
|
|
||||||
PFN_lfx_WaitAndBeginFrame m_lfx_WaitAndBeginFrame {};
|
|
||||||
|
|
||||||
void OnRenderStartHook()
|
// clang-format off
|
||||||
|
AUTOHOOK(OnRenderStart, client.dll + 0x1952C0,
|
||||||
|
void, __fastcall, ())
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
// Sleep before next frame as needed to reduce latency.
|
if (Cvar_r_latencyflex->GetBool() && m_winelfx_WaitAndBeginFrame)
|
||||||
if (Cvar_r_latencyflex->GetInt())
|
m_winelfx_WaitAndBeginFrame();
|
||||||
{
|
|
||||||
if (m_lfx_WaitAndBeginFrame)
|
|
||||||
{
|
|
||||||
m_lfx_WaitAndBeginFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnRenderStart();
|
OnRenderStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseLatencyFleX(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module))
|
||||||
{
|
{
|
||||||
// Connect to the LatencyFleX service
|
// Connect to the LatencyFleX service
|
||||||
// LatencyFleX is an open source vendor agnostic replacement for Nvidia Reflex input latency reduction technology.
|
// LatencyFleX is an open source vendor agnostic replacement for Nvidia Reflex input latency reduction technology.
|
||||||
// https://ishitatsuyuki.github.io/post/latencyflex/
|
// https://ishitatsuyuki.github.io/post/latencyflex/
|
||||||
const auto lfxModuleName = "latencyflex_layer.dll";
|
HMODULE pLfxModule;
|
||||||
const auto lfxModuleNameFallback = "latencyflex_wine.dll";
|
|
||||||
auto useFallbackEntrypoints = false;
|
|
||||||
|
|
||||||
// Load LatencyFleX library.
|
if (pLfxModule = LoadLibraryA("latencyflex_layer.dll"))
|
||||||
m_lfxModule = ::LoadLibraryA(lfxModuleName);
|
m_winelfx_WaitAndBeginFrame =
|
||||||
if (m_lfxModule == nullptr && ::GetLastError() == ERROR_MOD_NOT_FOUND)
|
reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "lfx_WaitAndBeginFrame")));
|
||||||
|
else if (pLfxModule = LoadLibraryA("latencyflex_wine.dll"))
|
||||||
|
m_winelfx_WaitAndBeginFrame =
|
||||||
|
reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "winelfx_WaitAndBeginFrame")));
|
||||||
|
else
|
||||||
{
|
{
|
||||||
spdlog::info("LFX: Primary LatencyFleX library not found, trying fallback.");
|
spdlog::info("Unable to load LatencyFleX library, LatencyFleX disabled.");
|
||||||
|
|
||||||
m_lfxModule = ::LoadLibraryA(lfxModuleNameFallback);
|
|
||||||
if (m_lfxModule == nullptr)
|
|
||||||
{
|
|
||||||
if (::GetLastError() == ERROR_MOD_NOT_FOUND)
|
|
||||||
{
|
|
||||||
spdlog::info("LFX: Fallback LatencyFleX library not found.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
spdlog::info("LFX: Error loading fallback LatencyFleX library - Code: {}", ::GetLastError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
useFallbackEntrypoints = true;
|
|
||||||
}
|
|
||||||
else if (m_lfxModule == nullptr)
|
|
||||||
{
|
|
||||||
spdlog::info("LFX: Error loading primary LatencyFleX library - Code: {}", ::GetLastError());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_lfx_WaitAndBeginFrame = reinterpret_cast<PFN_lfx_WaitAndBeginFrame>(reinterpret_cast<void*>(
|
AUTOHOOK_DISPATCH()
|
||||||
GetProcAddress(m_lfxModule, !useFallbackEntrypoints ? "lfx_WaitAndBeginFrame" : "winelfx_WaitAndBeginFrame")));
|
|
||||||
|
|
||||||
spdlog::info("LFX: Initialized.");
|
|
||||||
|
|
||||||
|
spdlog::info("LatencyFleX initialized.");
|
||||||
Cvar_r_latencyflex = new ConVar("r_latencyflex", "1", FCVAR_ARCHIVE, "Whether or not to use LatencyFleX input latency reduction.");
|
Cvar_r_latencyflex = new ConVar("r_latencyflex", "1", FCVAR_ARCHIVE, "Whether or not to use LatencyFleX input latency reduction.");
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1952C0, &OnRenderStartHook, reinterpret_cast<LPVOID*>(&OnRenderStart));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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* SetVerticalScrollbar)(vgui_BaseRichText* self, bool state);
|
||||||
void(__fastcall* SetMaximumCharCount)(vgui_BaseRichText* self, int maxChars);
|
void(__fastcall* SetMaximumCharCount)(vgui_BaseRichText* self, int maxChars);
|
||||||
void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, vgui_Color col);
|
void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, Color col);
|
||||||
void(__fastcall* InsertIndentChange)(vgui_BaseRichText* self, int pixelsIndent);
|
void(__fastcall* InsertIndentChange)(vgui_BaseRichText* self, int pixelsIndent);
|
||||||
void(__fastcall* InsertClickableTextStart)(vgui_BaseRichText* self, const char* pchClickAction);
|
void(__fastcall* InsertClickableTextStart)(vgui_BaseRichText* self, const char* pchClickAction);
|
||||||
void(__fastcall* InsertClickableTextEnd)(vgui_BaseRichText* self);
|
void(__fastcall* InsertClickableTextEnd)(vgui_BaseRichText* self);
|
||||||
void(__fastcall* InsertPossibleURLString)(
|
void(__fastcall* InsertPossibleURLString)(vgui_BaseRichText* self, const char* text, Color URLTextColor, Color normalTextColor);
|
||||||
vgui_BaseRichText* self, const char* text, vgui_Color URLTextColor, vgui_Color normalTextColor);
|
|
||||||
void(__fastcall* InsertFade)(vgui_BaseRichText* self, float flSustain, float flLength);
|
void(__fastcall* InsertFade)(vgui_BaseRichText* self, float flSustain, float flLength);
|
||||||
void(__fastcall* ResetAllFades)(vgui_BaseRichText* self, bool bHold, bool bOnlyExpired, float flNewSustain);
|
void(__fastcall* ResetAllFades)(vgui_BaseRichText* self, bool bHold, bool bOnlyExpired, float flNewSustain);
|
||||||
void(__fastcall* SetToFullHeight)(vgui_BaseRichText* self);
|
void(__fastcall* SetToFullHeight)(vgui_BaseRichText* self);
|
||||||
|
@ -81,25 +80,25 @@ LocalChatWriter::SwatchColor swatchColors[4] = {
|
||||||
LocalChatWriter::NetworkNameColor,
|
LocalChatWriter::NetworkNameColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
vgui_Color darkColors[8] = {
|
Color darkColors[8] = {
|
||||||
vgui_Color {0, 0, 0, 255},
|
Color {0, 0, 0, 255},
|
||||||
vgui_Color {205, 49, 49, 255},
|
Color {205, 49, 49, 255},
|
||||||
vgui_Color {13, 188, 121, 255},
|
Color {13, 188, 121, 255},
|
||||||
vgui_Color {229, 229, 16, 255},
|
Color {229, 229, 16, 255},
|
||||||
vgui_Color {36, 114, 200, 255},
|
Color {36, 114, 200, 255},
|
||||||
vgui_Color {188, 63, 188, 255},
|
Color {188, 63, 188, 255},
|
||||||
vgui_Color {17, 168, 205, 255},
|
Color {17, 168, 205, 255},
|
||||||
vgui_Color {229, 229, 229, 255}};
|
Color {229, 229, 229, 255}};
|
||||||
|
|
||||||
vgui_Color lightColors[8] = {
|
Color lightColors[8] = {
|
||||||
vgui_Color {102, 102, 102, 255},
|
Color {102, 102, 102, 255},
|
||||||
vgui_Color {241, 76, 76, 255},
|
Color {241, 76, 76, 255},
|
||||||
vgui_Color {35, 209, 139, 255},
|
Color {35, 209, 139, 255},
|
||||||
vgui_Color {245, 245, 67, 255},
|
Color {245, 245, 67, 255},
|
||||||
vgui_Color {59, 142, 234, 255},
|
Color {59, 142, 234, 255},
|
||||||
vgui_Color {214, 112, 214, 255},
|
Color {214, 112, 214, 255},
|
||||||
vgui_Color {41, 184, 219, 255},
|
Color {41, 184, 219, 255},
|
||||||
vgui_Color {255, 255, 255, 255}};
|
Color {255, 255, 255, 255}};
|
||||||
|
|
||||||
class AnsiEscapeParser
|
class AnsiEscapeParser
|
||||||
{
|
{
|
||||||
|
@ -144,7 +143,7 @@ class AnsiEscapeParser
|
||||||
|
|
||||||
LocalChatWriter* m_writer;
|
LocalChatWriter* m_writer;
|
||||||
Next m_next = Next::ControlType;
|
Next m_next = Next::ControlType;
|
||||||
vgui_Color m_expandedColor {0, 0, 0, 0};
|
Color m_expandedColor {0, 0, 0, 0};
|
||||||
|
|
||||||
Next HandleControlType(unsigned long val)
|
Next HandleControlType(unsigned long val)
|
||||||
{
|
{
|
||||||
|
@ -190,7 +189,7 @@ class AnsiEscapeParser
|
||||||
// Next values are r,g,b
|
// Next values are r,g,b
|
||||||
if (val == 2)
|
if (val == 2)
|
||||||
{
|
{
|
||||||
m_expandedColor = {0, 0, 0, 255};
|
m_expandedColor.SetColor(0, 0, 0, 255);
|
||||||
return Next::ForegroundR;
|
return Next::ForegroundR;
|
||||||
}
|
}
|
||||||
// Next value is 8-bit swatch color
|
// Next value is 8-bit swatch color
|
||||||
|
@ -219,13 +218,12 @@ class AnsiEscapeParser
|
||||||
unsigned char blue = code % 6;
|
unsigned char blue = code % 6;
|
||||||
unsigned char green = ((code - blue) / 6) % 6;
|
unsigned char green = ((code - blue) / 6) % 6;
|
||||||
unsigned char red = (code - blue - (green * 6)) / 36;
|
unsigned char red = (code - blue - (green * 6)) / 36;
|
||||||
m_writer->InsertColorChange(
|
m_writer->InsertColorChange(Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
|
||||||
vgui_Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
|
|
||||||
}
|
}
|
||||||
else if (val < UCHAR_MAX)
|
else if (val < UCHAR_MAX)
|
||||||
{
|
{
|
||||||
unsigned char brightness = (val - 232) * 10 + 8;
|
unsigned char brightness = (val - 232) * 10 + 8;
|
||||||
m_writer->InsertColorChange(vgui_Color {brightness, brightness, brightness, 255});
|
m_writer->InsertColorChange(Color {brightness, brightness, brightness, 255});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Next::ControlType;
|
return Next::ControlType;
|
||||||
|
@ -236,7 +234,7 @@ class AnsiEscapeParser
|
||||||
if (val >= UCHAR_MAX)
|
if (val >= UCHAR_MAX)
|
||||||
return Next::ControlType;
|
return Next::ControlType;
|
||||||
|
|
||||||
m_expandedColor.r = (unsigned char)val;
|
m_expandedColor[0] = (unsigned char)val;
|
||||||
return Next::ForegroundG;
|
return Next::ForegroundG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +243,7 @@ class AnsiEscapeParser
|
||||||
if (val >= UCHAR_MAX)
|
if (val >= UCHAR_MAX)
|
||||||
return Next::ControlType;
|
return Next::ControlType;
|
||||||
|
|
||||||
m_expandedColor.g = (unsigned char)val;
|
m_expandedColor[1] = (unsigned char)val;
|
||||||
return Next::ForegroundB;
|
return Next::ForegroundB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +252,7 @@ class AnsiEscapeParser
|
||||||
if (val >= UCHAR_MAX)
|
if (val >= UCHAR_MAX)
|
||||||
return Next::ControlType;
|
return Next::ControlType;
|
||||||
|
|
||||||
m_expandedColor.b = (unsigned char)val;
|
m_expandedColor[2] = (unsigned char)val;
|
||||||
m_writer->InsertColorChange(m_expandedColor);
|
m_writer->InsertColorChange(m_expandedColor);
|
||||||
return Next::ControlType;
|
return Next::ControlType;
|
||||||
}
|
}
|
||||||
|
@ -280,12 +278,11 @@ void LocalChatWriter::Write(const char* str)
|
||||||
if (startOfEscape != str)
|
if (startOfEscape != str)
|
||||||
{
|
{
|
||||||
// There is some text before the escape sequence, just print that
|
// There is some text before the escape sequence, just print that
|
||||||
|
|
||||||
size_t copyChars = startOfEscape - str;
|
size_t copyChars = startOfEscape - str;
|
||||||
if (copyChars > 255)
|
if (copyChars > 255)
|
||||||
copyChars = 255;
|
copyChars = 255;
|
||||||
strncpy(writeBuffer, str, copyChars);
|
|
||||||
writeBuffer[copyChars] = 0;
|
strncpy_s(writeBuffer, copyChars + 1, str, copyChars);
|
||||||
|
|
||||||
InsertText(writeBuffer);
|
InsertText(writeBuffer);
|
||||||
}
|
}
|
||||||
|
@ -320,6 +317,8 @@ void LocalChatWriter::InsertChar(wchar_t ch)
|
||||||
|
|
||||||
void LocalChatWriter::InsertText(const char* str)
|
void LocalChatWriter::InsertText(const char* str)
|
||||||
{
|
{
|
||||||
|
spdlog::info(str);
|
||||||
|
|
||||||
WCHAR messageUnicode[288];
|
WCHAR messageUnicode[288];
|
||||||
ConvertANSIToUnicode(str, -1, messageUnicode, 274);
|
ConvertANSIToUnicode(str, -1, messageUnicode, 274);
|
||||||
|
|
||||||
|
@ -347,7 +346,7 @@ void LocalChatWriter::InsertText(const wchar_t* str)
|
||||||
InsertDefaultFade();
|
InsertDefaultFade();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalChatWriter::InsertColorChange(vgui_Color color)
|
void LocalChatWriter::InsertColorChange(Color color)
|
||||||
{
|
{
|
||||||
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
|
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
|
||||||
{
|
{
|
||||||
|
@ -358,20 +357,24 @@ void LocalChatWriter::InsertColorChange(vgui_Color color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static vgui_Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor)
|
static Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor)
|
||||||
{
|
{
|
||||||
switch (swatchColor)
|
switch (swatchColor)
|
||||||
{
|
{
|
||||||
case LocalChatWriter::MainTextColor:
|
case LocalChatWriter::MainTextColor:
|
||||||
return hud->m_mainTextColor;
|
return hud->m_mainTextColor;
|
||||||
|
|
||||||
case LocalChatWriter::SameTeamNameColor:
|
case LocalChatWriter::SameTeamNameColor:
|
||||||
return hud->m_sameTeamColor;
|
return hud->m_sameTeamColor;
|
||||||
|
|
||||||
case LocalChatWriter::EnemyTeamNameColor:
|
case LocalChatWriter::EnemyTeamNameColor:
|
||||||
return hud->m_enemyTeamColor;
|
return hud->m_enemyTeamColor;
|
||||||
|
|
||||||
case LocalChatWriter::NetworkNameColor:
|
case LocalChatWriter::NetworkNameColor:
|
||||||
return hud->m_networkNameColor;
|
return hud->m_networkNameColor;
|
||||||
}
|
}
|
||||||
return vgui_Color {0, 0, 0, 0};
|
|
||||||
|
return Color(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalChatWriter::InsertSwatchColorChange(SwatchColor swatchColor)
|
void LocalChatWriter::InsertSwatchColorChange(SwatchColor swatchColor)
|
||||||
|
@ -436,12 +439,12 @@ void LocalChatWriter::InsertDefaultFade()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseLocalChatWriter(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT("client.dll", LocalChatWriter, (CModule module))
|
||||||
{
|
{
|
||||||
gGameSettings = (CGameSettings**)((char*)baseAddress + 0x11BAA48);
|
gGameSettings = module.Offset(0x11BAA48).As<CGameSettings**>();
|
||||||
gChatFadeLength = (CGameFloatVar**)((char*)baseAddress + 0x11BAB78);
|
gChatFadeLength = module.Offset(0x11BAB78).As<CGameFloatVar**>();
|
||||||
gChatFadeSustain = (CGameFloatVar**)((char*)baseAddress + 0x11BAC08);
|
gChatFadeSustain = module.Offset(0x11BAC08).As<CGameFloatVar**>();
|
||||||
CHudChat::allHuds = (CHudChat**)((char*)baseAddress + 0x11BA9E8);
|
CHudChat::allHuds = module.Offset(0x11BA9E8).As<CHudChat**>();
|
||||||
|
|
||||||
ConvertANSIToUnicode = (ConvertANSIToUnicodeType)((char*)baseAddress + 0x7339A0);
|
ConvertANSIToUnicode = module.Offset(0x7339A0).As<ConvertANSIToUnicodeType>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
#include "color.h"
|
||||||
struct vgui_Color
|
|
||||||
{
|
|
||||||
unsigned char r;
|
|
||||||
unsigned char g;
|
|
||||||
unsigned char b;
|
|
||||||
unsigned char a;
|
|
||||||
};
|
|
||||||
|
|
||||||
class vgui_BaseRichText;
|
class vgui_BaseRichText;
|
||||||
|
|
||||||
|
@ -18,10 +11,10 @@ class CHudChat
|
||||||
|
|
||||||
char unknown1[720];
|
char unknown1[720];
|
||||||
|
|
||||||
vgui_Color m_sameTeamColor;
|
Color m_sameTeamColor;
|
||||||
vgui_Color m_enemyTeamColor;
|
Color m_enemyTeamColor;
|
||||||
vgui_Color m_mainTextColor;
|
Color m_mainTextColor;
|
||||||
vgui_Color m_networkNameColor;
|
Color m_networkNameColor;
|
||||||
|
|
||||||
char unknown2[12];
|
char unknown2[12];
|
||||||
|
|
||||||
|
@ -61,7 +54,7 @@ class LocalChatWriter
|
||||||
void InsertChar(wchar_t ch);
|
void InsertChar(wchar_t ch);
|
||||||
void InsertText(const char* str);
|
void InsertText(const char* str);
|
||||||
void InsertText(const wchar_t* str);
|
void InsertText(const wchar_t* str);
|
||||||
void InsertColorChange(vgui_Color color);
|
void InsertColorChange(Color color);
|
||||||
void InsertSwatchColorChange(SwatchColor color);
|
void InsertSwatchColorChange(SwatchColor color);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -70,5 +63,3 @@ class LocalChatWriter
|
||||||
const char* ApplyAnsiEscape(const char* escape);
|
const char* ApplyAnsiEscape(const char* escape);
|
||||||
void InsertDefaultFade();
|
void InsertDefaultFade();
|
||||||
};
|
};
|
||||||
|
|
||||||
void InitialiseLocalChatWriter(HMODULE baseAddress);
|
|
||||||
|
|
|
@ -1,258 +1,20 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include "sourceconsole.h"
|
|
||||||
#include "spdlog/sinks/basic_file_sink.h"
|
|
||||||
#include "hookutils.h"
|
|
||||||
#include "dedicated.h"
|
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
|
#include "concommand.h"
|
||||||
|
#include "nsprefix.h"
|
||||||
|
#include "bitbuf.h"
|
||||||
|
#include "tier0.h"
|
||||||
|
#include "spdlog/sinks/basic_file_sink.h"
|
||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include "nsprefix.h"
|
|
||||||
#include <dbghelp.h>
|
|
||||||
|
|
||||||
// This needs to be called after hooks are loaded so we can access the command line args
|
AUTOHOOK_INIT()
|
||||||
void CreateLogFiles()
|
|
||||||
{
|
|
||||||
if (strstr(GetCommandLineA(), "-disablelogs"))
|
|
||||||
{
|
|
||||||
spdlog::default_logger()->set_level(spdlog::level::off);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// todo: might be good to delete logs that are too old
|
|
||||||
time_t time = std::time(nullptr);
|
|
||||||
tm currentTime = *std::localtime(&time);
|
|
||||||
std::stringstream stream;
|
|
||||||
|
|
||||||
stream << std::put_time(¤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);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConVar* Cvar_spewlog_enable;
|
ConVar* Cvar_spewlog_enable;
|
||||||
|
|
||||||
enum SpewType_t
|
enum class SpewType_t
|
||||||
{
|
{
|
||||||
SPEW_MESSAGE = 0,
|
SPEW_MESSAGE = 0,
|
||||||
|
|
||||||
|
@ -264,56 +26,24 @@ enum SpewType_t
|
||||||
SPEW_TYPE_COUNT
|
SPEW_TYPE_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (*EngineSpewFuncType)();
|
const std::unordered_map<SpewType_t, const char*> PrintSpewTypes = {
|
||||||
EngineSpewFuncType EngineSpewFunc;
|
{SpewType_t::SPEW_MESSAGE, "SPEW_MESSAGE"},
|
||||||
|
{SpewType_t::SPEW_WARNING, "SPEW_WARNING"},
|
||||||
|
{SpewType_t::SPEW_ASSERT, "SPEW_ASSERT"},
|
||||||
|
{SpewType_t::SPEW_ERROR, "SPEW_ERROR"},
|
||||||
|
{SpewType_t::SPEW_LOG, "SPEW_LOG"}};
|
||||||
|
|
||||||
void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format, va_list args)
|
// clang-format off
|
||||||
|
AUTOHOOK(EngineSpewFunc, engine.dll + 0x11CA80,
|
||||||
|
void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_list args))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
if (!Cvar_spewlog_enable->GetBool())
|
if (!Cvar_spewlog_enable->GetBool())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const char* typeStr;
|
const char* typeStr = PrintSpewTypes.at(type);
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case SPEW_MESSAGE:
|
|
||||||
{
|
|
||||||
typeStr = "SPEW_MESSAGE";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SPEW_WARNING:
|
|
||||||
{
|
|
||||||
typeStr = "SPEW_WARNING";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SPEW_ASSERT:
|
|
||||||
{
|
|
||||||
typeStr = "SPEW_ASSERT";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SPEW_ERROR:
|
|
||||||
{
|
|
||||||
typeStr = "SPEW_ERROR";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SPEW_LOG:
|
|
||||||
{
|
|
||||||
typeStr = "SPEW_LOG";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
typeStr = "SPEW_UNKNOWN";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char formatted[2048] = {0};
|
char formatted[2048] = {0};
|
||||||
bool shouldFormat = true;
|
bool bShouldFormat = true;
|
||||||
|
|
||||||
// because titanfall 2 is quite possibly the worst thing to yet exist, it sometimes gives invalid specifiers which will crash
|
// because titanfall 2 is quite possibly the worst thing to yet exist, it sometimes gives invalid specifiers which will crash
|
||||||
// ttf2sdk had a way to prevent them from crashing but it doesnt work in debug builds
|
// ttf2sdk had a way to prevent them from crashing but it doesnt work in debug builds
|
||||||
|
@ -360,19 +90,17 @@ void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format,
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
shouldFormat = false;
|
bShouldFormat = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldFormat)
|
if (bShouldFormat)
|
||||||
vsnprintf(formatted, sizeof(formatted), format, args);
|
vsnprintf(formatted, sizeof(formatted), format, args);
|
||||||
else
|
else
|
||||||
{
|
|
||||||
spdlog::warn("Failed to format {} \"{}\"", typeStr, format);
|
spdlog::warn("Failed to format {} \"{}\"", typeStr, format);
|
||||||
}
|
|
||||||
|
|
||||||
auto endpos = strlen(formatted);
|
auto endpos = strlen(formatted);
|
||||||
if (formatted[endpos - 1] == '\n')
|
if (formatted[endpos - 1] == '\n')
|
||||||
|
@ -381,10 +109,11 @@ void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format,
|
||||||
spdlog::info("[SERVER {}] {}", typeStr, formatted);
|
spdlog::info("[SERVER {}] {}", typeStr, formatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (*Status_ConMsg_Type)(const char* text, ...);
|
// used for printing the output of status
|
||||||
Status_ConMsg_Type Status_ConMsg_Original;
|
// clang-format off
|
||||||
|
AUTOHOOK(Status_ConMsg, engine.dll + 0x15ABD0,
|
||||||
void Status_ConMsg_Hook(const char* text, ...)
|
void,, (const char* text, ...))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
char formatted[2048];
|
char formatted[2048];
|
||||||
va_list list;
|
va_list list;
|
||||||
|
@ -400,10 +129,10 @@ void Status_ConMsg_Hook(const char* text, ...)
|
||||||
spdlog::info(formatted);
|
spdlog::info(formatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef bool (*CClientState_ProcessPrint_Type)(__int64 thisptr, __int64 msg);
|
// clang-format off
|
||||||
CClientState_ProcessPrint_Type CClientState_ProcessPrint_Original;
|
AUTOHOOK(CClientState_ProcessPrint, engine.dll + 0x1A1530,
|
||||||
|
bool,, (void* thisptr, uintptr_t msg))
|
||||||
bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg)
|
// clang-format on
|
||||||
{
|
{
|
||||||
char* text = *(char**)(msg + 0x20);
|
char* text = *(char**)(msg + 0x20);
|
||||||
|
|
||||||
|
@ -415,32 +144,8 @@ bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseEngineSpewFuncHooks(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
HookEnabler hook;
|
|
||||||
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x11CA80, EngineSpewFuncHook, reinterpret_cast<LPVOID*>(&EngineSpewFunc));
|
|
||||||
|
|
||||||
// Hook print function that status concmd uses to actually print data
|
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15ABD0, Status_ConMsg_Hook, reinterpret_cast<LPVOID*>(&Status_ConMsg_Original));
|
|
||||||
|
|
||||||
// Hook CClientState::ProcessPrint
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook,
|
|
||||||
(char*)baseAddress + 0x1A1530,
|
|
||||||
CClientState_ProcessPrint_Hook,
|
|
||||||
reinterpret_cast<LPVOID*>(&CClientState_ProcessPrint_Original));
|
|
||||||
|
|
||||||
Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged");
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "bitbuf.h"
|
|
||||||
|
|
||||||
ConVar* Cvar_cl_showtextmsg;
|
ConVar* Cvar_cl_showtextmsg;
|
||||||
|
|
||||||
typedef void (*TextMsg_Type)(__int64);
|
|
||||||
TextMsg_Type TextMsg_Original;
|
|
||||||
|
|
||||||
class ICenterPrint
|
class ICenterPrint
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -453,11 +158,22 @@ class ICenterPrint
|
||||||
virtual void SetTextColor(int r, int g, int b, int a) = 0;
|
virtual void SetTextColor(int r, int g, int b, int a) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
ICenterPrint* internalCenterPrint = NULL;
|
ICenterPrint* pInternalCenterPrint = NULL;
|
||||||
|
|
||||||
void TextMsgHook(BFRead* msg)
|
enum class TextMsgPrintType_t
|
||||||
{
|
{
|
||||||
int msg_dest = msg->ReadByte();
|
HUD_PRINTNOTIFY = 1,
|
||||||
|
HUD_PRINTCONSOLE,
|
||||||
|
HUD_PRINTTALK,
|
||||||
|
HUD_PRINTCENTER
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(TextMsg, client.dll + 0x198710,
|
||||||
|
void,, (BFRead* msg))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
TextMsgPrintType_t msg_dest = (TextMsgPrintType_t)msg->ReadByte();
|
||||||
|
|
||||||
char text[256];
|
char text[256];
|
||||||
msg->ReadString(text, sizeof(text));
|
msg->ReadString(text, sizeof(text));
|
||||||
|
@ -467,29 +183,86 @@ void TextMsgHook(BFRead* msg)
|
||||||
|
|
||||||
switch (msg_dest)
|
switch (msg_dest)
|
||||||
{
|
{
|
||||||
case 4: // HUD_PRINTCENTER
|
case TextMsgPrintType_t::HUD_PRINTCENTER:
|
||||||
internalCenterPrint->Print(text);
|
pInternalCenterPrint->Print(text);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest);
|
spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case 2: // HUD_PRINTCONSOLE
|
|
||||||
|
case TextMsgPrintType_t::HUD_PRINTCONSOLE:
|
||||||
auto endpos = strlen(text);
|
auto endpos = strlen(text);
|
||||||
if (text[endpos - 1] == '\n')
|
if (text[endpos - 1] == '\n')
|
||||||
text[endpos - 1] = '\0'; // cut off repeated newline
|
text[endpos - 1] = '\0'; // cut off repeated newline
|
||||||
|
|
||||||
spdlog::info(text);
|
spdlog::info(text);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseClientPrintHooks(HMODULE baseAddress)
|
// clang-format off
|
||||||
|
AUTOHOOK(ConCommand_echo, engine.dll + 0x123680,
|
||||||
|
void,, (const CCommand& arg))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
HookEnabler hook;
|
if (arg.ArgC() >= 2)
|
||||||
|
spdlog::info("[echo] {}", arg.ArgS());
|
||||||
|
}
|
||||||
|
|
||||||
internalCenterPrint = (ICenterPrint*)((char*)baseAddress + 0x216E940);
|
// This needs to be called after hooks are loaded so we can access the command line args
|
||||||
|
void CreateLogFiles()
|
||||||
|
{
|
||||||
|
if (strstr(GetCommandLineA(), "-disablelogs"))
|
||||||
|
{
|
||||||
|
spdlog::default_logger()->set_level(spdlog::level::off);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// todo: might be good to delete logs that are too old
|
||||||
|
time_t time = std::time(nullptr);
|
||||||
|
tm currentTime = *std::localtime(&time);
|
||||||
|
std::stringstream stream;
|
||||||
|
|
||||||
// "TextMsg" usermessage
|
stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str());
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x198710, TextMsgHook, reinterpret_cast<LPVOID*>(&TextMsg_Original));
|
spdlog::default_logger()->sinks().push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(stream.str(), false));
|
||||||
|
spdlog::flush_on(spdlog::level::info);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
spdlog::error("Failed creating log file!");
|
||||||
|
MessageBoxA(
|
||||||
|
0, "Failed creating log file! Make sure the profile directory is writable.", "Northstar Warning", MB_ICONWARNING | MB_OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitialiseLogging()
|
||||||
|
{
|
||||||
|
AllocConsole();
|
||||||
|
|
||||||
|
// Bind stdout to receive console output.
|
||||||
|
// these two lines are responsible for stuff to not show up in the console sometimes, from talking about it on discord
|
||||||
|
// apparently they were meant to make logging work when using -northstar, however from testing it seems that it doesnt
|
||||||
|
// work regardless of these two lines
|
||||||
|
// freopen("CONOUT$", "w", stdout);
|
||||||
|
// freopen("CONOUT$", "w", stderr);
|
||||||
|
spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v");
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", EngineSpewFuncHooks, ConVar, (CModule module))
|
||||||
|
{
|
||||||
|
AUTOHOOK_DISPATCH_MODULE(engine.dll)
|
||||||
|
|
||||||
|
Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged");
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientPrintHooks, ConVar, (CModule module))
|
||||||
|
{
|
||||||
|
AUTOHOOK_DISPATCH_MODULE(client.dll)
|
||||||
|
|
||||||
Cvar_cl_showtextmsg = new ConVar("cl_showtextmsg", "1", FCVAR_NONE, "Enable/disable text messages printing on the screen.");
|
Cvar_cl_showtextmsg = new ConVar("cl_showtextmsg", "1", FCVAR_NONE, "Enable/disable text messages printing on the screen.");
|
||||||
|
pInternalCenterPrint = module.Offset(0x216E940).As<ICenterPrint*>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "context.h"
|
|
||||||
|
|
||||||
void CreateLogFiles();
|
void CreateLogFiles();
|
||||||
void InitialiseLogging();
|
void InitialiseLogging();
|
||||||
void InitialiseEngineSpewFuncHooks(HMODULE baseAddress);
|
|
||||||
void InitialiseClientPrintHooks(HMODULE baseAddress);
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
|
#include "serverpresence.h"
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
extern ConVar* Cvar_ns_masterserver_hostname;
|
||||||
|
extern ConVar* Cvar_ns_curl_log_enable;
|
||||||
|
|
||||||
struct RemoteModInfo
|
struct RemoteModInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -82,8 +89,6 @@ class MasterServerManager
|
||||||
char m_sOwnClientAuthToken[33];
|
char m_sOwnClientAuthToken[33];
|
||||||
|
|
||||||
std::string m_sOwnModInfoJson;
|
std::string m_sOwnModInfoJson;
|
||||||
std::string m_sUnicodeServerName; // Unicode unescaped version of Cvar_ns_auth_servername for support in cjk characters
|
|
||||||
std::string m_sUnicodeServerDesc; // Unicode unescaped version of Cvar_ns_auth_serverdesc for support in cjk characters
|
|
||||||
|
|
||||||
bool m_bOriginAuthWithMasterServerDone = false;
|
bool m_bOriginAuthWithMasterServerDone = false;
|
||||||
bool m_bOriginAuthWithMasterServerInProgress = false;
|
bool m_bOriginAuthWithMasterServerInProgress = false;
|
||||||
|
@ -97,8 +102,7 @@ class MasterServerManager
|
||||||
bool m_bNewgameAfterSelfAuth = false;
|
bool m_bNewgameAfterSelfAuth = false;
|
||||||
bool m_bScriptAuthenticatingWithGameServer = false;
|
bool m_bScriptAuthenticatingWithGameServer = false;
|
||||||
bool m_bSuccessfullyAuthenticatedWithGameServer = false;
|
bool m_bSuccessfullyAuthenticatedWithGameServer = false;
|
||||||
|
std::string m_sAuthFailureReason {};
|
||||||
std::string s_authfail_reason {};
|
|
||||||
|
|
||||||
bool m_bHasPendingConnectionInfo = false;
|
bool m_bHasPendingConnectionInfo = false;
|
||||||
RemoteServerConnectionInfo m_pendingConnectionInfo;
|
RemoteServerConnectionInfo m_pendingConnectionInfo;
|
||||||
|
@ -108,28 +112,76 @@ class MasterServerManager
|
||||||
bool m_bHasMainMenuPromoData = false;
|
bool m_bHasMainMenuPromoData = false;
|
||||||
MainMenuPromoData m_sMainMenuPromoData;
|
MainMenuPromoData m_sMainMenuPromoData;
|
||||||
|
|
||||||
private:
|
|
||||||
void SetCommonHttpClientOptions(CURL* curl);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MasterServerManager();
|
MasterServerManager();
|
||||||
|
|
||||||
void ClearServerList();
|
void ClearServerList();
|
||||||
void RequestServerList();
|
void RequestServerList();
|
||||||
void RequestMainMenuPromos();
|
void RequestMainMenuPromos();
|
||||||
void AuthenticateOriginWithMasterServer(char* uid, char* originToken);
|
void AuthenticateOriginWithMasterServer(const char* uid, const char* originToken);
|
||||||
void AuthenticateWithOwnServer(char* uid, char* playerToken);
|
void AuthenticateWithOwnServer(const char* uid, const char* playerToken);
|
||||||
void AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password);
|
void AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password);
|
||||||
void
|
void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize);
|
||||||
AddSelfToServerList(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password);
|
|
||||||
void UpdateServerMapAndPlaylist(char* map, char* playlist, int playerCount);
|
|
||||||
void UpdateServerPlayerCount(int playerCount);
|
|
||||||
void WritePlayerPersistentData(char* playerId, char* pdata, size_t pdataSize);
|
|
||||||
void RemoveSelfFromServerList();
|
|
||||||
};
|
};
|
||||||
std::string unescape_unicode(const std::string& str);
|
|
||||||
void UpdateServerInfoFromUnicodeToUTF8();
|
|
||||||
void InitialiseSharedMasterServer(HMODULE baseAddress);
|
|
||||||
|
|
||||||
extern MasterServerManager* g_MasterServerManager;
|
extern MasterServerManager* g_pMasterServerManager;
|
||||||
extern ConVar* Cvar_ns_masterserver_hostname;
|
extern ConVar* Cvar_ns_masterserver_hostname;
|
||||||
extern ConVar* Cvar_ns_server_password;
|
|
||||||
|
/** Result returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */
|
||||||
|
enum class MasterServerReportPresenceResult
|
||||||
|
{
|
||||||
|
// Adding this server to the MS was successful.
|
||||||
|
Success,
|
||||||
|
// We failed to add this server to the MS and should retry.
|
||||||
|
Failed,
|
||||||
|
// We failed to add this server to the MS and shouldn't retry.
|
||||||
|
FailedNoRetry,
|
||||||
|
// We failed to even reach the MS.
|
||||||
|
FailedNoConnect,
|
||||||
|
// We failed to add the server because an existing server with the same ip:port exists.
|
||||||
|
FailedDuplicateServer,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MasterServerPresenceReporter : public ServerPresenceReporter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Full data returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */
|
||||||
|
struct ReportPresenceResultData
|
||||||
|
{
|
||||||
|
MasterServerReportPresenceResult result;
|
||||||
|
|
||||||
|
std::optional<std::string> id;
|
||||||
|
std::optional<std::string> serverAuthToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
const int MAX_REGISTRATION_ATTEMPTS = 5;
|
||||||
|
|
||||||
|
// Called to initialise the master server presence reporter's state.
|
||||||
|
void CreatePresence(const ServerPresence* pServerPresence) override;
|
||||||
|
|
||||||
|
// Run on an internal to either add the server to the MS or update it.
|
||||||
|
void ReportPresence(const ServerPresence* pServerPresence) override;
|
||||||
|
|
||||||
|
// Called when we need to remove the server from the master server.
|
||||||
|
void DestroyPresence(const ServerPresence* pServerPresence) override;
|
||||||
|
|
||||||
|
// Called every frame.
|
||||||
|
void RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Contains the async logic to add the server to the MS.
|
||||||
|
void InternalAddServer(const ServerPresence* pServerPresence);
|
||||||
|
|
||||||
|
// Contains the async logic to update the server on the MS.
|
||||||
|
void InternalUpdateServer(const ServerPresence* pServerPresence);
|
||||||
|
|
||||||
|
// The future used for InternalAddServer() calls.
|
||||||
|
std::future<ReportPresenceResultData> addServerFuture;
|
||||||
|
|
||||||
|
// The future used for InternalAddServer() calls.
|
||||||
|
std::future<ReportPresenceResultData> updateServerFuture;
|
||||||
|
|
||||||
|
int m_nNumRegistrationAttempts;
|
||||||
|
|
||||||
|
double m_fNextAddServerAttemptTime;
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
#include "tier0.h"
|
||||||
#include "maxplayers.h"
|
#include "maxplayers.h"
|
||||||
#include "gameutils.h"
|
|
||||||
|
AUTOHOOK_INIT()
|
||||||
|
|
||||||
// never set this to anything below 32
|
// never set this to anything below 32
|
||||||
#define NEW_MAX_PLAYERS 64
|
#define NEW_MAX_PLAYERS 64
|
||||||
|
@ -45,50 +47,33 @@ constexpr int Team_PlayerArray_AddedLength = NEW_MAX_PLAYERS - 32;
|
||||||
constexpr int Team_PlayerArray_AddedSize = PAD_NUMBER(Team_PlayerArray_AddedLength * 8, 4);
|
constexpr int Team_PlayerArray_AddedSize = PAD_NUMBER(Team_PlayerArray_AddedLength * 8, 4);
|
||||||
constexpr int Team_AddedSize = Team_PlayerArray_AddedSize;
|
constexpr int Team_AddedSize = Team_PlayerArray_AddedSize;
|
||||||
|
|
||||||
#include "nsmem.h"
|
bool MaxPlayersIncreaseEnabled()
|
||||||
template <class T> void ChangeOffset(void* addr, unsigned int offset)
|
|
||||||
{
|
{
|
||||||
NSMem::BytePatch((uintptr_t)addr, (BYTE*)&offset, sizeof(T));
|
static bool bMaxPlayersIncreaseEnabled = Tier0::CommandLine()->CheckParm("-experimentalmaxplayersincrease");
|
||||||
|
return bMaxPlayersIncreaseEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// should we use R2 for this? not sure
|
||||||
typedef bool(*MatchRecvPropsToSendProps_R_Type)(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable);
|
namespace R2 // use R2 namespace for game funcs
|
||||||
MatchRecvPropsToSendProps_R_Type MatchRecvPropsToSendProps_R_Original;
|
|
||||||
|
|
||||||
bool MatchRecvPropsToSendProps_R_Hook(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable)
|
|
||||||
{
|
{
|
||||||
const char* tableName = *(const char**)(sendTable + 0x118);
|
int GetMaxPlayers()
|
||||||
|
{
|
||||||
|
if (MaxPlayersIncreaseEnabled())
|
||||||
|
return NEW_MAX_PLAYERS;
|
||||||
|
|
||||||
spdlog::info("MatchRecvPropsToSendProps_R table name {}", tableName);
|
return 32;
|
||||||
|
|
||||||
bool orig = MatchRecvPropsToSendProps_R_Original(lookup, tableNameBroken, sendTable, recvTable);
|
|
||||||
return orig;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef bool(*DataTable_SetupReceiveTableFromSendTable_Type)(__int64 sendTable, bool needsDecoder);
|
|
||||||
DataTable_SetupReceiveTableFromSendTable_Type DataTable_SetupReceiveTableFromSendTable_Original;
|
|
||||||
|
|
||||||
bool DataTable_SetupReceiveTableFromSendTable_Hook(__int64 sendTable, bool needsDecoder)
|
|
||||||
{
|
|
||||||
const char* tableName = *(const char**)(sendTable + 0x118);
|
|
||||||
|
|
||||||
spdlog::info("DataTable_SetupReceiveTableFromSendTable table name {}", tableName);
|
|
||||||
if (!strcmp(tableName, "m_bConnected")) {
|
|
||||||
char f[64];
|
|
||||||
sprintf_s(f, "%p", sendTable);
|
|
||||||
MessageBoxA(0, f, "DataTable_SetupReceiveTableFromSendTable", 0);
|
|
||||||
}
|
}
|
||||||
|
} // namespace R2
|
||||||
|
|
||||||
return DataTable_SetupReceiveTableFromSendTable_Original(sendTable, needsDecoder);
|
template <class T> void ChangeOffset(MemoryAddress addr, unsigned int offset)
|
||||||
|
{
|
||||||
|
addr.Patch((BYTE*)&offset, sizeof(T));
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
typedef void* (*StringTables_CreateStringTable_Type)(
|
// clang-format off
|
||||||
__int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags);
|
AUTOHOOK(StringTables_CreateStringTable, engine.dll + 0x22E220,
|
||||||
StringTables_CreateStringTable_Type StringTables_CreateStringTable_Original;
|
void*,, (void* thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags))
|
||||||
|
// clang-format on
|
||||||
void* StringTables_CreateStringTable_Hook(
|
|
||||||
__int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags)
|
|
||||||
{
|
{
|
||||||
// Change the amount of entries to account for a bigger player amount
|
// Change the amount of entries to account for a bigger player amount
|
||||||
if (!strcmp(name, "userinfo"))
|
if (!strcmp(name, "userinfo"))
|
||||||
|
@ -100,36 +85,33 @@ void* StringTables_CreateStringTable_Hook(
|
||||||
maxentries = maxPlayersPowerOf2;
|
maxentries = maxPlayersPowerOf2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return StringTables_CreateStringTable_Original(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags);
|
return StringTables_CreateStringTable(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MaxPlayersIncreaseEnabled()
|
ON_DLL_LOAD("engine.dll", MaxPlayersOverride_Engine, (CModule module))
|
||||||
{
|
|
||||||
return CommandLine() && CommandLine()->CheckParm("-experimentalmaxplayersincrease");
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress)
|
|
||||||
{
|
{
|
||||||
if (!MaxPlayersIncreaseEnabled())
|
if (!MaxPlayersIncreaseEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
AUTOHOOK_DISPATCH_MODULE(engine.dll)
|
||||||
|
|
||||||
// patch GetPlayerLimits to ignore the boundary limit
|
// patch GetPlayerLimits to ignore the boundary limit
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x116458, 0xEB); // jle => jmp
|
module.Offset(0x116458).Patch("0xEB"); // jle => jmp
|
||||||
|
|
||||||
// patch ED_Alloc to change nFirstIndex
|
// patch ED_Alloc to change nFirstIndex
|
||||||
ChangeOffset<int>((char*)baseAddress + 0x18F46C + 1, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
|
ChangeOffset<int>(module.Offset(0x18F46C + 1), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
|
||||||
|
|
||||||
// patch CGameServer::SpawnServer to change GetMaxClients inline
|
// patch CGameServer::SpawnServer to change GetMaxClients inline
|
||||||
ChangeOffset<int>((char*)baseAddress + 0x119543 + 2, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
|
ChangeOffset<int>(module.Offset(0x119543 + 2), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
|
||||||
|
|
||||||
// patch CGameServer::SpawnServer to change for loop
|
// patch CGameServer::SpawnServer to change for loop
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x11957F + 2, NEW_MAX_PLAYERS); // original: 32
|
ChangeOffset<unsigned char>(module.Offset(0x11957F + 2), NEW_MAX_PLAYERS); // original: 32
|
||||||
|
|
||||||
// patch CGameServer::SpawnServer to change for loop (there are two)
|
// patch CGameServer::SpawnServer to change for loop (there are two)
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x119586 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
|
ChangeOffset<unsigned char>(module.Offset(0x119586 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
|
||||||
|
|
||||||
// patch max players somewhere in CClientState
|
// patch max players somewhere in CClientState
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x1A162C + 2, NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1)
|
ChangeOffset<unsigned char>(module.Offset(0x1A162C + 2), NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1)
|
||||||
|
|
||||||
// patch max players in userinfo stringtable creation
|
// patch max players in userinfo stringtable creation
|
||||||
/*{
|
/*{
|
||||||
|
@ -142,22 +124,10 @@ void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress)
|
||||||
// proper fix below
|
// proper fix below
|
||||||
|
|
||||||
// patch max players in userinfo stringtable creation loop
|
// patch max players in userinfo stringtable creation loop
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x114C48 + 2, NEW_MAX_PLAYERS); // original: 32
|
ChangeOffset<unsigned char>(module.Offset(0x114C48 + 2), NEW_MAX_PLAYERS); // original: 32
|
||||||
|
|
||||||
// do not load prebaked SendTable message list
|
// do not load prebaked SendTable message list
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x75859, 0xEB); // jnz -> jmp
|
module.Offset(0x75859).Patch("EB"); // jnz -> jmp
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
|
|
||||||
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x209000, &MatchRecvPropsToSendProps_R_Hook,
|
|
||||||
// reinterpret_cast<LPVOID*>(&MatchRecvPropsToSendProps_R_Original)); ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1FACD0,
|
|
||||||
// &DataTable_SetupReceiveTableFromSendTable_Hook, reinterpret_cast<LPVOID*>(&DataTable_SetupReceiveTableFromSendTable_Original));
|
|
||||||
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook,
|
|
||||||
(char*)baseAddress + 0x22E220,
|
|
||||||
&StringTables_CreateStringTable_Hook,
|
|
||||||
reinterpret_cast<LPVOID*>(&StringTables_CreateStringTable_Original));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (*RunUserCmds_Type)(bool a1, float a2);
|
typedef void (*RunUserCmds_Type)(bool a1, float a2);
|
||||||
|
@ -167,7 +137,10 @@ HMODULE serverBase = 0;
|
||||||
auto RandomIntZeroMax = (__int64(__fastcall*)())0;
|
auto RandomIntZeroMax = (__int64(__fastcall*)())0;
|
||||||
|
|
||||||
// lazy rebuild
|
// lazy rebuild
|
||||||
void RunUserCmds_Hook(bool a1, float a2)
|
// clang-format off
|
||||||
|
AUTOHOOK(RunUserCmds, server.dll + 0x483D10,
|
||||||
|
void,, (bool a1, float a2))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
unsigned char v3; // bl
|
unsigned char v3; // bl
|
||||||
int v5; // er14
|
int v5; // er14
|
||||||
|
@ -308,162 +281,160 @@ void RunUserCmds_Hook(bool a1, float a2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef __int64 (*SendPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1);
|
// clang-format off
|
||||||
SendPropArray2_Type SendPropArray2_Original;
|
AUTOHOOK(SendPropArray2, server.dll + 0x12B130,
|
||||||
|
__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1))
|
||||||
__int64 __fastcall SendPropArray2_Hook(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1)
|
// clang-format on
|
||||||
{
|
{
|
||||||
// Change the amount of elements to account for a bigger player amount
|
// Change the amount of elements to account for a bigger player amount
|
||||||
if (!strcmp(name, "\"player_array\""))
|
if (!strcmp(name, "\"player_array\""))
|
||||||
elements = NEW_MAX_PLAYERS;
|
elements = NEW_MAX_PLAYERS;
|
||||||
|
|
||||||
return SendPropArray2_Original(recvProp, elements, flags, name, proxyFn, unk1);
|
return SendPropArray2(recvProp, elements, flags, name, proxyFn, unk1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress)
|
ON_DLL_LOAD("server.dll", MaxPlayersOverride_Server, (CModule module))
|
||||||
{
|
{
|
||||||
if (!MaxPlayersIncreaseEnabled())
|
if (!MaxPlayersIncreaseEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
AUTOHOOK_DISPATCH_MODULE(server.dll)
|
||||||
|
|
||||||
// get required data
|
// get required data
|
||||||
serverBase = GetModuleHandleA("server.dll");
|
serverBase = (HMODULE)module.m_nAddress;
|
||||||
RandomIntZeroMax = (decltype(RandomIntZeroMax))(GetProcAddress(GetModuleHandleA("vstdlib.dll"), "RandomIntZeroMax"));
|
RandomIntZeroMax = (decltype(RandomIntZeroMax))(GetProcAddress(GetModuleHandleA("vstdlib.dll"), "RandomIntZeroMax"));
|
||||||
|
|
||||||
// patch max players amount
|
// patch max players amount
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x9A44D + 3, NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128)
|
ChangeOffset<unsigned char>(module.Offset(0x9A44D + 3), NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128)
|
||||||
|
|
||||||
// patch SpawnGlobalNonRewinding to change forced edict index
|
// patch SpawnGlobalNonRewinding to change forced edict index
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x2BC403 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
|
ChangeOffset<unsigned char>(module.Offset(0x2BC403 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
|
||||||
|
|
||||||
constexpr int CPlayerResource_OriginalSize = 4776;
|
constexpr int CPlayerResource_OriginalSize = 4776;
|
||||||
constexpr int CPlayerResource_AddedSize = PlayerResource_TotalSize;
|
constexpr int CPlayerResource_AddedSize = PlayerResource_TotalSize;
|
||||||
constexpr int CPlayerResource_ModifiedSize = CPlayerResource_OriginalSize + CPlayerResource_AddedSize;
|
constexpr int CPlayerResource_ModifiedSize = CPlayerResource_OriginalSize + CPlayerResource_AddedSize;
|
||||||
|
|
||||||
// CPlayerResource class allocation function - allocate a bigger amount to fit all new max player data
|
// CPlayerResource class allocation function - allocate a bigger amount to fit all new max player data
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C560A + 1, CPlayerResource_ModifiedSize);
|
ChangeOffset<unsigned int>(module.Offset(0x5C560A + 1), CPlayerResource_ModifiedSize);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iPing SendProp
|
// DT_PlayerResource::m_iPing SendProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5059 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5059 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50A8 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C50A8 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50E2 + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x5C50E2 + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iPing DataMap
|
// DT_PlayerResource::m_iPing DataMap
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94598, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xB94598), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9459C, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xB9459C), NEW_MAX_PLAYERS + 1);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB945C0, PlayerResource_Ping_Size);
|
ChangeOffset<unsigned short>(module.Offset(0xB945C0), PlayerResource_Ping_Size);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iTeam SendProp
|
// DT_PlayerResource::m_iTeam SendProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5110 + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5110 + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C519C + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C519C + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C517E + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x5C517E + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iTeam DataMap
|
// DT_PlayerResource::m_iTeam DataMap
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94600, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xB94600), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94604, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xB94604), NEW_MAX_PLAYERS + 1);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94628, PlayerResource_Team_Size);
|
ChangeOffset<unsigned short>(module.Offset(0xB94628), PlayerResource_Team_Size);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iPRHealth SendProp
|
// DT_PlayerResource::m_iPRHealth SendProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C51C0 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C51C0 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5204 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5204 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C523E + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x5C523E + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iPRHealth DataMap
|
// DT_PlayerResource::m_iPRHealth DataMap
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94668, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xB94668), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9466C, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xB9466C), NEW_MAX_PLAYERS + 1);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94690, PlayerResource_PRHealth_Size);
|
ChangeOffset<unsigned short>(module.Offset(0xB94690), PlayerResource_PRHealth_Size);
|
||||||
|
|
||||||
// DT_PlayerResource::m_bConnected SendProp
|
// DT_PlayerResource::m_bConnected SendProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C526C + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C526C + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52B4 + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C52B4 + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52EE + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x5C52EE + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_bConnected DataMap
|
// DT_PlayerResource::m_bConnected DataMap
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB946D0, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xB946D0), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946D4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xB946D4), NEW_MAX_PLAYERS + 1);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946F8, PlayerResource_Connected_Size);
|
ChangeOffset<unsigned short>(module.Offset(0xB946F8), PlayerResource_Connected_Size);
|
||||||
|
|
||||||
// DT_PlayerResource::m_bAlive SendProp
|
// DT_PlayerResource::m_bAlive SendProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5321 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5321 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5364 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5364 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C539E + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x5C539E + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_bAlive DataMap
|
// DT_PlayerResource::m_bAlive DataMap
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94738, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xB94738), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9473C, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xB9473C), NEW_MAX_PLAYERS + 1);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94760, PlayerResource_Alive_Size);
|
ChangeOffset<unsigned short>(module.Offset(0xB94760), PlayerResource_Alive_Size);
|
||||||
|
|
||||||
// DT_PlayerResource::m_boolStats SendProp
|
// DT_PlayerResource::m_boolStats SendProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C53CC + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C53CC + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5414 + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5414 + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C544E + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x5C544E + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_boolStats DataMap
|
// DT_PlayerResource::m_boolStats DataMap
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB947A0, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xB947A0), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947A4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xB947A4), NEW_MAX_PLAYERS + 1);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947C8, PlayerResource_BoolStats_Size);
|
ChangeOffset<unsigned short>(module.Offset(0xB947C8), PlayerResource_BoolStats_Size);
|
||||||
|
|
||||||
// DT_PlayerResource::m_killStats SendProp
|
// DT_PlayerResource::m_killStats SendProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C547C + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C547C + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54E2 + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C54E2 + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54FE + 4, PlayerResource_KillStats_Length);
|
ChangeOffset<unsigned int>(module.Offset(0x5C54FE + 4), PlayerResource_KillStats_Length);
|
||||||
|
|
||||||
// DT_PlayerResource::m_killStats DataMap
|
// DT_PlayerResource::m_killStats DataMap
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94808, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xB94808), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9480C, PlayerResource_KillStats_Length);
|
ChangeOffset<unsigned short>(module.Offset(0xB9480C), PlayerResource_KillStats_Length);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94830, PlayerResource_KillStats_Size);
|
ChangeOffset<unsigned short>(module.Offset(0xB94830), PlayerResource_KillStats_Size);
|
||||||
|
|
||||||
// DT_PlayerResource::m_scoreStats SendProp
|
// DT_PlayerResource::m_scoreStats SendProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5528 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5528 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5576 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5576 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5584 + 4, PlayerResource_ScoreStats_Length);
|
ChangeOffset<unsigned int>(module.Offset(0x5C5584 + 4), PlayerResource_ScoreStats_Length);
|
||||||
|
|
||||||
// DT_PlayerResource::m_scoreStats DataMap
|
// DT_PlayerResource::m_scoreStats DataMap
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94870, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xB94870), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94874, PlayerResource_ScoreStats_Length);
|
ChangeOffset<unsigned short>(module.Offset(0xB94874), PlayerResource_ScoreStats_Length);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94898, PlayerResource_ScoreStats_Size);
|
ChangeOffset<unsigned short>(module.Offset(0xB94898), PlayerResource_ScoreStats_Size);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_bConnected
|
// CPlayerResource::UpdatePlayerData - m_bConnected
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C66EE + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C66EE + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C672E + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C672E + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_iPing
|
// CPlayerResource::UpdatePlayerData - m_iPing
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6394 + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C6394 + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63DB + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C63DB + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_iTeam
|
// CPlayerResource::UpdatePlayerData - m_iTeam
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63FD + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C63FD + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6442 + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C6442 + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_iPRHealth
|
// CPlayerResource::UpdatePlayerData - m_iPRHealth
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C645B + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C645B + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64A0 + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C64A0 + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_bConnected
|
// CPlayerResource::UpdatePlayerData - m_bConnected
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64AA + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C64AA + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64F0 + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C64F0 + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_bAlive
|
// CPlayerResource::UpdatePlayerData - m_bAlive
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C650A + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C650A + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C654F + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C654F + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_boolStats
|
// CPlayerResource::UpdatePlayerData - m_boolStats
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6557 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C6557 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65A5 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C65A5 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_scoreStats
|
// CPlayerResource::UpdatePlayerData - m_scoreStats
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65C2 + 3, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C65C2 + 3), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65E3 + 4, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C65E3 + 4), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||||
|
|
||||||
// CPlayerResource::UpdatePlayerData - m_killStats
|
// CPlayerResource::UpdatePlayerData - m_killStats
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6654 + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C6654 + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C665B + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x5C665B + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||||
|
|
||||||
// GameLoop::RunUserCmds - rebuild
|
*module.Offset(0x14E7390).As<DWORD*>() = 0;
|
||||||
HookEnabler hook;
|
auto DT_PlayerResource_Construct = module.Offset(0x5C4FE0).As<__int64(__fastcall*)()>();
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x483D10, &RunUserCmds_Hook, reinterpret_cast<LPVOID*>(&RunUserCmds_Original));
|
|
||||||
|
|
||||||
*(DWORD*)((char*)baseAddress + 0x14E7390) = 0;
|
|
||||||
auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x5C4FE0);
|
|
||||||
DT_PlayerResource_Construct();
|
DT_PlayerResource_Construct();
|
||||||
|
|
||||||
constexpr int CTeam_OriginalSize = 3336;
|
constexpr int CTeam_OriginalSize = 3336;
|
||||||
|
@ -471,191 +442,188 @@ void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress)
|
||||||
constexpr int CTeam_ModifiedSize = CTeam_OriginalSize + CTeam_AddedSize;
|
constexpr int CTeam_ModifiedSize = CTeam_OriginalSize + CTeam_AddedSize;
|
||||||
|
|
||||||
// CTeam class allocation function - allocate a bigger amount to fit all new team player data
|
// CTeam class allocation function - allocate a bigger amount to fit all new team player data
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x23924A + 1, CTeam_ModifiedSize);
|
ChangeOffset<unsigned int>(module.Offset(0x23924A + 1), CTeam_ModifiedSize);
|
||||||
|
|
||||||
// CTeam::CTeam - increase memset length to clean newly allocated data
|
// CTeam::CTeam - increase memset length to clean newly allocated data
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x2395AE + 2, 256 + CTeam_AddedSize);
|
ChangeOffset<unsigned int>(module.Offset(0x2395AE + 2), 256 + CTeam_AddedSize);
|
||||||
|
|
||||||
// hook required to change the size of DT_Team::"player_array"
|
*module.Offset(0xC945A0).As<DWORD*>() = 0;
|
||||||
HookEnabler hook2;
|
auto DT_Team_Construct = module.Offset(0x238F50).As<__int64(__fastcall*)()>();
|
||||||
ENABLER_CREATEHOOK(hook2, (char*)baseAddress + 0x12B130, &SendPropArray2_Hook, reinterpret_cast<LPVOID*>(&SendPropArray2_Original));
|
|
||||||
hook2.~HookEnabler(); // force hook before calling construct function
|
|
||||||
|
|
||||||
*(DWORD*)((char*)baseAddress + 0xC945A0) = 0;
|
|
||||||
auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x238F50);
|
|
||||||
DT_Team_Construct();
|
DT_Team_Construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef __int64 (*RecvPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn);
|
// clang-format off
|
||||||
RecvPropArray2_Type RecvPropArray2_Original;
|
AUTOHOOK(RecvPropArray2, client.dll + 0x1CEDA0,
|
||||||
|
__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn))
|
||||||
__int64 __fastcall RecvPropArray2_Hook(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn)
|
// clang-format on
|
||||||
{
|
{
|
||||||
// Change the amount of elements to account for a bigger player amount
|
// Change the amount of elements to account for a bigger player amount
|
||||||
if (!strcmp(name, "\"player_array\""))
|
if (!strcmp(name, "\"player_array\""))
|
||||||
elements = NEW_MAX_PLAYERS;
|
elements = NEW_MAX_PLAYERS;
|
||||||
|
|
||||||
return RecvPropArray2_Original(recvProp, elements, flags, name, proxyFn);
|
return RecvPropArray2(recvProp, elements, flags, name, proxyFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress)
|
ON_DLL_LOAD("client.dll", MaxPlayersOverride_Client, (CModule module))
|
||||||
{
|
{
|
||||||
if (!MaxPlayersIncreaseEnabled())
|
if (!MaxPlayersIncreaseEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
AUTOHOOK_DISPATCH_MODULE(client.dll)
|
||||||
|
|
||||||
constexpr int C_PlayerResource_OriginalSize = 5768;
|
constexpr int C_PlayerResource_OriginalSize = 5768;
|
||||||
constexpr int C_PlayerResource_AddedSize = PlayerResource_TotalSize;
|
constexpr int C_PlayerResource_AddedSize = PlayerResource_TotalSize;
|
||||||
constexpr int C_PlayerResource_ModifiedSize = C_PlayerResource_OriginalSize + C_PlayerResource_AddedSize;
|
constexpr int C_PlayerResource_ModifiedSize = C_PlayerResource_OriginalSize + C_PlayerResource_AddedSize;
|
||||||
|
|
||||||
// C_PlayerResource class allocation function - allocate a bigger amount to fit all new max player data
|
// C_PlayerResource class allocation function - allocate a bigger amount to fit all new max player data
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164C41 + 1, C_PlayerResource_ModifiedSize);
|
ChangeOffset<unsigned int>(module.Offset(0x164C41 + 1), C_PlayerResource_ModifiedSize);
|
||||||
|
|
||||||
// C_PlayerResource::C_PlayerResource - change loop end value
|
// C_PlayerResource::C_PlayerResource - change loop end value
|
||||||
ChangeOffset<unsigned char>((char*)baseAddress + 0x1640C4 + 2, NEW_MAX_PLAYERS - 32);
|
ChangeOffset<unsigned char>(module.Offset(0x1640C4 + 2), NEW_MAX_PLAYERS - 32);
|
||||||
|
|
||||||
// C_PlayerResource::C_PlayerResource - change m_szName address
|
// C_PlayerResource::C_PlayerResource - change m_szName address
|
||||||
ChangeOffset<unsigned int>(
|
ChangeOffset<unsigned int>(
|
||||||
(char*)baseAddress + 0x1640D0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
|
module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
|
||||||
|
|
||||||
// C_PlayerResource::C_PlayerResource - change m_szName address
|
// C_PlayerResource::C_PlayerResource - change m_szName address
|
||||||
ChangeOffset<unsigned int>(
|
ChangeOffset<unsigned int>(
|
||||||
(char*)baseAddress + 0x1640D0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
|
module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
|
||||||
|
|
||||||
// C_PlayerResource::C_PlayerResource - increase memset length to clean newly allocated data
|
// C_PlayerResource::C_PlayerResource - increase memset length to clean newly allocated data
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1640D0 + 3, 2244 + C_PlayerResource_AddedSize);
|
ChangeOffset<unsigned int>(module.Offset(0x1640D0 + 3), 2244 + C_PlayerResource_AddedSize);
|
||||||
|
|
||||||
// C_PlayerResource::UpdatePlayerName - change m_szName address
|
// C_PlayerResource::UpdatePlayerName - change m_szName address
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x16431F + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x16431F + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName - change m_szName address 1
|
// C_PlayerResource::GetPlayerName - change m_szName address 1
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645B1 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1645B1 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName - change m_szName address 2
|
// C_PlayerResource::GetPlayerName - change m_szName address 2
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645C0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1645C0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName - change m_szName address 3
|
// C_PlayerResource::GetPlayerName - change m_szName address 3
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645DD + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1645DD + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName internal func - change m_szName address 1
|
// C_PlayerResource::GetPlayerName internal func - change m_szName address 1
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B71 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164B71 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName internal func - change m_szName address 2
|
// C_PlayerResource::GetPlayerName internal func - change m_szName address 2
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B9B + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164B9B + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 1
|
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 1
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164641 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164641 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 2
|
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 2
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164650 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164650 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 3
|
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 3
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x16466D + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x16466D + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 1
|
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 1
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BA3 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164BA3 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 2
|
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 2
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BCE + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164BCE + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 3
|
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 3
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BE7 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164BE7 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
|
|
||||||
// C_PlayerResource::m_szName
|
// C_PlayerResource::m_szName
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc350f8, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc350f8), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc350f8 + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xc350f8 + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource size
|
// DT_PlayerResource size
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163415 + 6, C_PlayerResource_ModifiedSize);
|
ChangeOffset<unsigned int>(module.Offset(0x163415 + 6), C_PlayerResource_ModifiedSize);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iPing RecvProp
|
// DT_PlayerResource::m_iPing RecvProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163492 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163492 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1634D6 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1634D6 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163515 + 5, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x163515 + 5), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// C_PlayerResource::m_iPing
|
// C_PlayerResource::m_iPing
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35170, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc35170), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35170 + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xc35170 + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iTeam RecvProp
|
// DT_PlayerResource::m_iTeam RecvProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163549 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163549 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635C8 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1635C8 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635AD + 5, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x1635AD + 5), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// C_PlayerResource::m_iTeam
|
// C_PlayerResource::m_iTeam
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc351e8, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc351e8), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc351e8 + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xc351e8 + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_iPRHealth RecvProp
|
// DT_PlayerResource::m_iPRHealth RecvProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635F9 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1635F9 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163625 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163625 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163675 + 5, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x163675 + 5), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// C_PlayerResource::m_iPRHealth
|
// C_PlayerResource::m_iPRHealth
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35260, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc35260), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35260 + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xc35260 + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_bConnected RecvProp
|
// DT_PlayerResource::m_bConnected RecvProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636A9 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1636A9 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636D5 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1636D5 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163725 + 5, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x163725 + 5), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// C_PlayerResource::m_bConnected
|
// C_PlayerResource::m_bConnected
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc352d8, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc352d8), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc352d8 + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xc352d8 + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_bAlive RecvProp
|
// DT_PlayerResource::m_bAlive RecvProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163759 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163759 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163785 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163785 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1637D5 + 5, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x1637D5 + 5), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// C_PlayerResource::m_bAlive
|
// C_PlayerResource::m_bAlive
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35350, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc35350), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35350 + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xc35350 + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_boolStats RecvProp
|
// DT_PlayerResource::m_boolStats RecvProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163809 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163809 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163835 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163835 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163885 + 5, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned int>(module.Offset(0x163885 + 5), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// C_PlayerResource::m_boolStats
|
// C_PlayerResource::m_boolStats
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc353c8, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc353c8), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc353c8 + 4, NEW_MAX_PLAYERS + 1);
|
ChangeOffset<unsigned short>(module.Offset(0xc353c8 + 4), NEW_MAX_PLAYERS + 1);
|
||||||
|
|
||||||
// DT_PlayerResource::m_killStats RecvProp
|
// DT_PlayerResource::m_killStats RecvProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638B3 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1638B3 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638E5 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x1638E5 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163935 + 5, PlayerResource_KillStats_Length);
|
ChangeOffset<unsigned int>(module.Offset(0x163935 + 5), PlayerResource_KillStats_Length);
|
||||||
|
|
||||||
// C_PlayerResource::m_killStats
|
// C_PlayerResource::m_killStats
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35440, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc35440), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35440 + 4, PlayerResource_KillStats_Length);
|
ChangeOffset<unsigned short>(module.Offset(0xc35440 + 4), PlayerResource_KillStats_Length);
|
||||||
|
|
||||||
// DT_PlayerResource::m_scoreStats RecvProp
|
// DT_PlayerResource::m_scoreStats RecvProp
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163969 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163969 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x163995 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x163995 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1639E5 + 5, PlayerResource_ScoreStats_Length);
|
ChangeOffset<unsigned int>(module.Offset(0x1639E5 + 5), PlayerResource_ScoreStats_Length);
|
||||||
|
|
||||||
// C_PlayerResource::m_scoreStats
|
// C_PlayerResource::m_scoreStats
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xc354b8, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
ChangeOffset<unsigned int>(module.Offset(0xc354b8), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
|
||||||
ChangeOffset<unsigned short>((char*)baseAddress + 0xc354b8 + 4, PlayerResource_ScoreStats_Length);
|
ChangeOffset<unsigned short>(module.Offset(0xc354b8 + 4), PlayerResource_ScoreStats_Length);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName - change m_bConnected address
|
// C_PlayerResource::GetPlayerName - change m_bConnected address
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164599 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164599 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName2 (?) - change m_bConnected address
|
// C_PlayerResource::GetPlayerName2 (?) - change m_bConnected address
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164629 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164629 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
|
|
||||||
// C_PlayerResource::GetPlayerName internal func - change m_bConnected address
|
// C_PlayerResource::GetPlayerName internal func - change m_bConnected address
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B13 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164B13 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
|
|
||||||
// Some other get name func (that seems to be unused) - change m_bConnected address
|
// Some other get name func (that seems to be unused) - change m_bConnected address
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164860 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164860 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
|
|
||||||
// Some other get name func 2 (that seems to be unused too) - change m_bConnected address
|
// Some other get name func 2 (that seems to be unused too) - change m_bConnected address
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x164834 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
ChangeOffset<unsigned int>(module.Offset(0x164834 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
|
||||||
|
|
||||||
*(DWORD*)((char*)baseAddress + 0xC35068) = 0;
|
*module.Offset(0xC35068).As<DWORD*>() = 0;
|
||||||
auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x163400);
|
auto DT_PlayerResource_Construct = module.Offset(0x163400).As<__int64(__fastcall*)()>();
|
||||||
DT_PlayerResource_Construct();
|
DT_PlayerResource_Construct();
|
||||||
|
|
||||||
constexpr int C_Team_OriginalSize = 3200;
|
constexpr int C_Team_OriginalSize = 3200;
|
||||||
|
@ -663,20 +631,15 @@ void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress)
|
||||||
constexpr int C_Team_ModifiedSize = C_Team_OriginalSize + C_Team_AddedSize;
|
constexpr int C_Team_ModifiedSize = C_Team_OriginalSize + C_Team_AddedSize;
|
||||||
|
|
||||||
// C_Team class allocation function - allocate a bigger amount to fit all new team player data
|
// C_Team class allocation function - allocate a bigger amount to fit all new team player data
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x182321 + 1, C_Team_ModifiedSize);
|
ChangeOffset<unsigned int>(module.Offset(0x182321 + 1), C_Team_ModifiedSize);
|
||||||
|
|
||||||
// C_Team::C_Team - increase memset length to clean newly allocated data
|
// C_Team::C_Team - increase memset length to clean newly allocated data
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0x1804A2 + 2, 256 + C_Team_AddedSize);
|
ChangeOffset<unsigned int>(module.Offset(0x1804A2 + 2), 256 + C_Team_AddedSize);
|
||||||
|
|
||||||
// DT_Team size
|
// DT_Team size
|
||||||
ChangeOffset<unsigned int>((char*)baseAddress + 0xC3AA0C, C_Team_ModifiedSize);
|
ChangeOffset<unsigned int>(module.Offset(0xC3AA0C), C_Team_ModifiedSize);
|
||||||
|
|
||||||
// hook required to change the size of DT_Team::"player_array"
|
*module.Offset(0xC3AFF8).As<DWORD*>() = 0;
|
||||||
HookEnabler hook;
|
auto DT_Team_Construct = module.Offset(0x17F950).As<__int64(__fastcall*)()>();
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1CEDA0, &RecvPropArray2_Hook, reinterpret_cast<LPVOID*>(&RecvPropArray2_Original));
|
|
||||||
hook.~HookEnabler(); // force hook before calling construct function
|
|
||||||
|
|
||||||
*(DWORD*)((char*)baseAddress + 0xC3AFF8) = 0;
|
|
||||||
auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x17F950);
|
|
||||||
DT_Team_Construct();
|
DT_Team_Construct();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress);
|
|
||||||
void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress);
|
// should we use R2 for this? not sure
|
||||||
void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress);
|
namespace R2 // use R2 namespace for game funcs
|
||||||
|
{
|
||||||
|
int GetMaxPlayers();
|
||||||
|
} // namespace R2
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "memalloc.h"
|
#include "memalloc.h"
|
||||||
#include "gameutils.h"
|
#include "tier0.h"
|
||||||
|
|
||||||
|
using namespace Tier0;
|
||||||
|
|
||||||
// TODO: rename to malloc and free after removing statically compiled .libs
|
// TODO: rename to malloc and free after removing statically compiled .libs
|
||||||
|
|
||||||
|
@ -8,9 +10,8 @@ extern "C" void* _malloc_base(size_t n)
|
||||||
{
|
{
|
||||||
// allocate into static buffer if g_pMemAllocSingleton isn't initialised
|
// allocate into static buffer if g_pMemAllocSingleton isn't initialised
|
||||||
if (!g_pMemAllocSingleton)
|
if (!g_pMemAllocSingleton)
|
||||||
{
|
TryCreateGlobalMemAlloc();
|
||||||
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
|
|
||||||
}
|
|
||||||
return g_pMemAllocSingleton->m_vtable->Alloc(g_pMemAllocSingleton, n);
|
return g_pMemAllocSingleton->m_vtable->Alloc(g_pMemAllocSingleton, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,19 +23,16 @@ extern "C" void* _malloc_base(size_t n)
|
||||||
extern "C" void _free_base(void* p)
|
extern "C" void _free_base(void* p)
|
||||||
{
|
{
|
||||||
if (!g_pMemAllocSingleton)
|
if (!g_pMemAllocSingleton)
|
||||||
{
|
TryCreateGlobalMemAlloc();
|
||||||
spdlog::warn("Trying to free something before g_pMemAllocSingleton was ready, this should never happen");
|
|
||||||
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
|
|
||||||
}
|
|
||||||
g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p);
|
g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void* _realloc_base(void* oldPtr, size_t size)
|
extern "C" void* _realloc_base(void* oldPtr, size_t size)
|
||||||
{
|
{
|
||||||
if (!g_pMemAllocSingleton)
|
if (!g_pMemAllocSingleton)
|
||||||
{
|
TryCreateGlobalMemAlloc();
|
||||||
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
|
|
||||||
}
|
|
||||||
return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size);
|
return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 "pch.h"
|
||||||
#include "misccommands.h"
|
#include "misccommands.h"
|
||||||
#include "concommand.h"
|
#include "concommand.h"
|
||||||
#include "gameutils.h"
|
#include "playlist.h"
|
||||||
|
#include "r2engine.h"
|
||||||
|
#include "r2client.h"
|
||||||
|
#include "tier0.h"
|
||||||
|
#include "hoststate.h"
|
||||||
#include "masterserver.h"
|
#include "masterserver.h"
|
||||||
|
#include "modmanager.h"
|
||||||
#include "serverauthentication.h"
|
#include "serverauthentication.h"
|
||||||
#include "squirrel.h"
|
#include "squirrel.h"
|
||||||
|
|
||||||
|
void ConCommand_force_newgame(const CCommand& arg)
|
||||||
|
{
|
||||||
|
if (arg.ArgC() < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME;
|
||||||
|
strncpy(R2::g_pHostState->m_levelName, arg.Arg(1), sizeof(R2::g_pHostState->m_levelName));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConCommand_ns_start_reauth_and_leave_to_lobby(const CCommand& arg)
|
||||||
|
{
|
||||||
|
// hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect
|
||||||
|
g_pMasterServerManager->m_bNewgameAfterSelfAuth = true;
|
||||||
|
g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConCommand_ns_end_reauth_and_leave_to_lobby(const CCommand& arg)
|
||||||
|
{
|
||||||
|
if (g_pServerAuthentication->m_RemoteAuthenticationData.size())
|
||||||
|
R2::g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str());
|
||||||
|
|
||||||
|
// weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this
|
||||||
|
if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM)
|
||||||
|
{
|
||||||
|
g_pServerAuthentication->m_bNeedLocalAuthForNewgame = true;
|
||||||
|
|
||||||
|
// this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta
|
||||||
|
// fucks things should maybe set this in HostState_NewGame?
|
||||||
|
R2::SetCurrentPlaylist("tdm");
|
||||||
|
strcpy(R2::g_pHostState->m_levelName, "mp_lobby");
|
||||||
|
R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AddMiscConCommands()
|
void AddMiscConCommands()
|
||||||
{
|
{
|
||||||
MAKE_CONCMD(
|
RegisterConCommand(
|
||||||
"force_newgame",
|
"force_newgame",
|
||||||
|
ConCommand_force_newgame,
|
||||||
"forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME",
|
"forces a map load through directly setting g_pHostState->m_iNextState to HS_NEW_GAME",
|
||||||
FCVAR_NONE,
|
FCVAR_NONE);
|
||||||
[](const CCommand& arg)
|
|
||||||
{
|
|
||||||
if (arg.ArgC() < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
g_pHostState->m_iNextState = HS_NEW_GAME;
|
RegisterConCommand(
|
||||||
strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName));
|
|
||||||
});
|
|
||||||
|
|
||||||
MAKE_CONCMD(
|
|
||||||
"ns_start_reauth_and_leave_to_lobby",
|
"ns_start_reauth_and_leave_to_lobby",
|
||||||
|
ConCommand_ns_start_reauth_and_leave_to_lobby,
|
||||||
"called by the server, used to reauth and return the player to lobby when leaving a game",
|
"called by the server, used to reauth and return the player to lobby when leaving a game",
|
||||||
FCVAR_SERVER_CAN_EXECUTE,
|
FCVAR_SERVER_CAN_EXECUTE);
|
||||||
[](const CCommand& arg)
|
|
||||||
{
|
|
||||||
// hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect
|
|
||||||
g_MasterServerManager->m_bNewgameAfterSelfAuth = true;
|
|
||||||
g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, g_MasterServerManager->m_sOwnClientAuthToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
// this is a concommand because we make a deferred call to it from another thread
|
// this is a concommand because we make a deferred call to it from another thread
|
||||||
MAKE_CONCMD(
|
RegisterConCommand("ns_end_reauth_and_leave_to_lobby", ConCommand_ns_end_reauth_and_leave_to_lobby, "", FCVAR_NONE);
|
||||||
"ns_end_reauth_and_leave_to_lobby",
|
}
|
||||||
"",
|
|
||||||
FCVAR_NONE,
|
// fixes up various cvar flags to have more sane values
|
||||||
[](const CCommand& arg)
|
void FixupCvarFlags()
|
||||||
{
|
{
|
||||||
Cbuf_AddText(
|
if (Tier0::CommandLine()->CheckParm("-allowdevcvars"))
|
||||||
Cbuf_GetCurrentPlayer(),
|
{
|
||||||
fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(),
|
// strip hidden and devonly cvar flags
|
||||||
cmd_source_t::kCommandSrcCode);
|
int iNumCvarsAltered = 0;
|
||||||
Cbuf_Execute();
|
for (auto& pair : R2::g_pCVar->DumpToMap())
|
||||||
|
{
|
||||||
// weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this
|
// strip flags
|
||||||
if (g_ClientSquirrelManager->sqvm)
|
int flags = pair.second->GetFlags();
|
||||||
{
|
if (flags & FCVAR_DEVELOPMENTONLY)
|
||||||
g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true;
|
{
|
||||||
|
flags &= ~FCVAR_DEVELOPMENTONLY;
|
||||||
// this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta
|
iNumCvarsAltered++;
|
||||||
// fucks things should maybe set this in HostState_NewGame?
|
}
|
||||||
SetCurrentPlaylist("tdm");
|
|
||||||
strcpy(g_pHostState->m_levelName, "mp_lobby");
|
if (flags & FCVAR_HIDDEN)
|
||||||
g_pHostState->m_iNextState = HS_NEW_GAME;
|
{
|
||||||
}
|
flags &= ~FCVAR_HIDDEN;
|
||||||
});
|
iNumCvarsAltered++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pair.second->m_nFlags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::info("Removed {} hidden/devonly cvar flags", iNumCvarsAltered);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make all engine client commands FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS
|
||||||
|
// these are usually checked through CGameClient::IsEngineClientCommand, but we get more control over this if we just do it through
|
||||||
|
// cvar flags
|
||||||
|
const char** ppEngineClientCommands = CModule("engine.dll").Offset(0x7C5EF0).As<const char**>();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ConCommandBase* pCommand = R2::g_pCVar->FindCommandBase(ppEngineClientCommands[i]);
|
||||||
|
if (pCommand) // not all the commands in this array actually exist in respawn source
|
||||||
|
pCommand->m_nFlags |= FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS;
|
||||||
|
} while (ppEngineClientCommands[++i]);
|
||||||
|
|
||||||
|
// array of cvars and the flags we want to add to them
|
||||||
|
const std::vector<std::tuple<const char*, uint32_t>> CVAR_FIXUP_ADD_FLAGS = {
|
||||||
|
// system commands (i.e. necessary for proper functionality)
|
||||||
|
// servers need to be able to disconnect
|
||||||
|
{"disconnect", FCVAR_SERVER_CAN_EXECUTE},
|
||||||
|
|
||||||
|
// cheat commands
|
||||||
|
{"give", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"give_server", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"givecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"takecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
|
||||||
|
{"switchclass", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"set", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"_setClassVarServer", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
|
||||||
|
{"ent_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"ent_throw", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"ent_setname", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"ent_teleport", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"ent_remove", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"ent_remove_all", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"ent_fire", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
|
||||||
|
{"particle_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"particle_recreate", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"particle_kill", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
|
||||||
|
{"test_setteam", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"melee_lunge_ent", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}};
|
||||||
|
|
||||||
|
// array of cvars and the flags we want to remove from them
|
||||||
|
const std::vector<std::tuple<const char*, uint32_t>> CVAR_FIXUP_REMOVE_FLAGS = {
|
||||||
|
// unsure how this command works, not even sure it's used on retail servers, deffo shouldn't be used on northstar
|
||||||
|
{"migrateme", FCVAR_SERVER_CAN_EXECUTE | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"recheck", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, // we don't need this on northstar servers, it's for communities
|
||||||
|
|
||||||
|
// unsure how these work exactly (rpt system likely somewhat stripped?), removing anyway since they won't be used
|
||||||
|
{"rpt_client_enable", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
|
||||||
|
{"rpt_password", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}};
|
||||||
|
|
||||||
|
for (auto& fixup : CVAR_FIXUP_ADD_FLAGS)
|
||||||
|
{
|
||||||
|
ConCommandBase* command = R2::g_pCVar->FindCommandBase(std::get<0>(fixup));
|
||||||
|
if (command)
|
||||||
|
command->m_nFlags |= std::get<1>(fixup);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& fixup : CVAR_FIXUP_REMOVE_FLAGS)
|
||||||
|
{
|
||||||
|
ConCommandBase* command = R2::g_pCVar->FindCommandBase(std::get<0>(fixup));
|
||||||
|
if (command)
|
||||||
|
command->m_nFlags &= ~std::get<1>(fixup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
void AddMiscConCommands();
|
void AddMiscConCommands();
|
||||||
|
void FixupCvarFlags();
|
||||||
|
|
|
@ -1,26 +1,7 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "miscserverfixes.h"
|
|
||||||
#include "hookutils.h"
|
|
||||||
|
|
||||||
#include "nsmem.h"
|
ON_DLL_LOAD("server.dll", MiscServerFixes, (CModule module))
|
||||||
|
|
||||||
void InitialiseMiscServerFixes(HMODULE baseAddress)
|
|
||||||
{
|
{
|
||||||
uintptr_t ba = (uintptr_t)baseAddress;
|
|
||||||
|
|
||||||
// ret at the start of the concommand GenerateObjFile as it can crash servers
|
|
||||||
{
|
|
||||||
NSMem::BytePatch(ba + 0x38D920, "C3");
|
|
||||||
}
|
|
||||||
|
|
||||||
// nop out call to VGUI shutdown since it crashes the game when quitting from the console
|
// nop out call to VGUI shutdown since it crashes the game when quitting from the console
|
||||||
{
|
module.Offset(0x154A96).NOP(5);
|
||||||
NSMem::NOP(ba + 0x154A96, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue)
|
|
||||||
// this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches
|
|
||||||
{
|
|
||||||
NSMem::BytePatch(ba + 0x153920, "C3");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
void InitialiseMiscServerFixes(HMODULE baseAddress);
|
|
|
@ -1,76 +1,73 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "miscserverscript.h"
|
|
||||||
#include "squirrel.h"
|
#include "squirrel.h"
|
||||||
#include "masterserver.h"
|
#include "masterserver.h"
|
||||||
#include "serverauthentication.h"
|
#include "serverauthentication.h"
|
||||||
#include "gameutils.h"
|
|
||||||
#include "dedicated.h"
|
#include "dedicated.h"
|
||||||
|
#include "r2client.h"
|
||||||
|
#include "r2server.h"
|
||||||
|
|
||||||
// annoying helper function because i can't figure out getting players or entities from sqvm rn
|
#include <filesystem>
|
||||||
// wish i didn't have to do it like this, but here we are
|
|
||||||
void* GetPlayerByIndex(int playerIndex)
|
// void function NSEarlyWritePlayerPersistenceForLeave( entity player )
|
||||||
|
SQRESULT SQ_EarlyWritePlayerPersistenceForLeave(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
const int PLAYER_ARRAY_OFFSET = 0x12A53F90;
|
const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
|
||||||
const int PLAYER_SIZE = 0x2D728;
|
if (!pPlayer)
|
||||||
|
|
||||||
void* playerArrayBase = (char*)GetModuleHandleA("engine.dll") + PLAYER_ARRAY_OFFSET;
|
|
||||||
void* player = (char*)playerArrayBase + (playerIndex * PLAYER_SIZE);
|
|
||||||
|
|
||||||
return player;
|
|
||||||
}
|
|
||||||
|
|
||||||
// void function NSEarlyWritePlayerIndexPersistenceForLeave( int playerIndex )
|
|
||||||
SQRESULT SQ_EarlyWritePlayerIndexPersistenceForLeave(void* sqvm)
|
|
||||||
{
|
|
||||||
int playerIndex = ServerSq_getinteger(sqvm, 1);
|
|
||||||
void* player = GetPlayerByIndex(playerIndex);
|
|
||||||
|
|
||||||
if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(player))
|
|
||||||
{
|
{
|
||||||
ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str());
|
spdlog::warn("NSEarlyWritePlayerPersistenceForLeave got null player");
|
||||||
return SQRESULT_ERROR;
|
|
||||||
|
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
|
||||||
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_ServerAuthenticationManager->m_additionalPlayerData[player].needPersistenceWriteOnLeave = false;
|
R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex];
|
||||||
g_ServerAuthenticationManager->WritePersistentData(player);
|
if (g_pServerAuthentication->m_PlayerAuthenticationData.find(pClient) == g_pServerAuthentication->m_PlayerAuthenticationData.end())
|
||||||
|
{
|
||||||
|
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
|
||||||
|
return SQRESULT_NOTNULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_pServerAuthentication->m_PlayerAuthenticationData[pClient].needPersistenceWriteOnLeave = false;
|
||||||
|
g_pServerAuthentication->WritePersistentData(pClient);
|
||||||
return SQRESULT_NULL;
|
return SQRESULT_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// bool function NSIsWritingPlayerPersistence()
|
// bool function NSIsWritingPlayerPersistence()
|
||||||
SQRESULT SQ_IsWritingPlayerPersistence(void* sqvm)
|
SQRESULT SQ_IsWritingPlayerPersistence(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
ServerSq_pushbool(sqvm, g_MasterServerManager->m_bSavingPersistentData);
|
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, g_pMasterServerManager->m_bSavingPersistentData);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// bool function NSIsPlayerIndexLocalPlayer( int playerIndex )
|
// bool function NSIsPlayerLocalPlayer( entity player )
|
||||||
SQRESULT SQ_IsPlayerIndexLocalPlayer(void* sqvm)
|
SQRESULT SQ_IsPlayerLocalPlayer(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
int playerIndex = ServerSq_getinteger(sqvm, 1);
|
const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
|
||||||
void* player = GetPlayerByIndex(playerIndex);
|
if (!pPlayer)
|
||||||
if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(player))
|
|
||||||
{
|
{
|
||||||
ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str());
|
spdlog::warn("NSIsPlayerLocalPlayer got null player");
|
||||||
return SQRESULT_ERROR;
|
|
||||||
|
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
|
||||||
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerSq_pushbool(sqvm, !strcmp(g_LocalPlayerUserID, (char*)player + 0xF500));
|
R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex];
|
||||||
|
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, !strcmp(R2::g_pLocalPlayerUserID, pClient->m_UID));
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// bool function NSIsDedicated()
|
// bool function NSIsDedicated()
|
||||||
|
SQRESULT SQ_IsDedicated(HSquirrelVM* sqvm)
|
||||||
SQRESULT SQ_IsDedicated(void* sqvm)
|
|
||||||
{
|
{
|
||||||
ServerSq_pushbool(sqvm, IsDedicatedServer());
|
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, IsDedicatedServer());
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseMiscServerScriptCommand(HMODULE baseAddress)
|
ON_DLL_LOAD_RELIESON("server.dll", MiscServerScriptCommands, ServerSquirrel, (CModule module))
|
||||||
{
|
{
|
||||||
g_ServerSquirrelManager->AddFuncRegistration(
|
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration(
|
||||||
"void", "NSEarlyWritePlayerIndexPersistenceForLeave", "int playerIndex", "", SQ_EarlyWritePlayerIndexPersistenceForLeave);
|
"void", "NSEarlyWritePlayerPersistenceForLeave", "entity player", "", SQ_EarlyWritePlayerPersistenceForLeave);
|
||||||
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence);
|
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence);
|
||||||
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsPlayerIndexLocalPlayer", "int playerIndex", "", SQ_IsPlayerIndexLocalPlayer);
|
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsPlayerLocalPlayer", "entity player", "", SQ_IsPlayerLocalPlayer);
|
||||||
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated);
|
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
void InitialiseMiscServerScriptCommand(HMODULE baseAddress);
|
|
||||||
void* GetPlayerByIndex(int playerIndex);
|
|
|
@ -1,37 +1,35 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "modlocalisation.h"
|
|
||||||
#include "hookutils.h"
|
|
||||||
#include "modmanager.h"
|
#include "modmanager.h"
|
||||||
|
|
||||||
typedef bool (*AddLocalisationFileType)(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown);
|
AUTOHOOK_INIT()
|
||||||
AddLocalisationFileType AddLocalisationFile;
|
|
||||||
|
|
||||||
bool loadModLocalisationFiles = true;
|
// clang-format off
|
||||||
|
AUTOHOOK(AddLocalisationFile, localize.dll + 0x6D80,
|
||||||
bool AddLocalisationFileHook(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown)
|
bool, __fastcall, (void* pVguiLocalize, const char* path, const char* pathId, char unknown))
|
||||||
|
// clang-format on
|
||||||
{
|
{
|
||||||
bool ret = AddLocalisationFile(g_pVguiLocalize, path, pathId, unknown);
|
static bool bLoadModLocalisationFiles = true;
|
||||||
|
bool ret = AddLocalisationFile(pVguiLocalize, path, pathId, unknown);
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
spdlog::info("Loaded localisation file {} successfully", path);
|
spdlog::info("Loaded localisation file {} successfully", path);
|
||||||
|
|
||||||
if (!loadModLocalisationFiles)
|
if (!bLoadModLocalisationFiles)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
loadModLocalisationFiles = false;
|
bLoadModLocalisationFiles = false;
|
||||||
|
|
||||||
for (Mod mod : g_ModManager->m_loadedMods)
|
for (Mod mod : g_pModManager->m_LoadedMods)
|
||||||
if (mod.Enabled)
|
if (mod.m_bEnabled)
|
||||||
for (std::string& localisationFile : mod.LocalisationFiles)
|
for (std::string& localisationFile : mod.LocalisationFiles)
|
||||||
AddLocalisationFile(g_pVguiLocalize, localisationFile.c_str(), pathId, unknown);
|
AddLocalisationFile(pVguiLocalize, localisationFile.c_str(), pathId, unknown);
|
||||||
|
|
||||||
loadModLocalisationFiles = true;
|
bLoadModLocalisationFiles = true;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseModLocalisation(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT("localize.dll", Localize, (CModule module))
|
||||||
{
|
{
|
||||||
HookEnabler hook;
|
AUTOHOOK_DISPATCH()
|
||||||
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x6D80, AddLocalisationFileHook, reinterpret_cast<LPVOID*>(&AddLocalisationFile));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
void InitialiseModLocalisation(HMODULE baseAddress);
|
|
|
@ -4,6 +4,10 @@
|
||||||
#include "concommand.h"
|
#include "concommand.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "masterserver.h"
|
#include "masterserver.h"
|
||||||
|
#include "filesystem.h"
|
||||||
|
#include "rpakfilesystem.h"
|
||||||
|
#include "nsprefix.h"
|
||||||
|
|
||||||
#include "rapidjson/error/en.h"
|
#include "rapidjson/error/en.h"
|
||||||
#include "rapidjson/document.h"
|
#include "rapidjson/document.h"
|
||||||
#include "rapidjson/ostreamwrapper.h"
|
#include "rapidjson/ostreamwrapper.h"
|
||||||
|
@ -13,17 +17,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "filesystem.h"
|
|
||||||
#include "rpakfilesystem.h"
|
|
||||||
#include "nsprefix.h"
|
|
||||||
|
|
||||||
ModManager* g_ModManager;
|
ModManager* g_pModManager;
|
||||||
|
|
||||||
Mod::Mod(fs::path modDir, char* jsonBuf)
|
Mod::Mod(fs::path modDir, char* jsonBuf)
|
||||||
{
|
{
|
||||||
wasReadSuccessfully = false;
|
m_bWasReadSuccessfully = false;
|
||||||
|
|
||||||
ModDirectory = modDir;
|
m_ModDirectory = modDir;
|
||||||
|
|
||||||
rapidjson_document modJson;
|
rapidjson_document modJson;
|
||||||
modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf);
|
modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf);
|
||||||
|
@ -106,11 +107,55 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
|
||||||
else
|
else
|
||||||
convar->HelpString = "";
|
convar->HelpString = "";
|
||||||
|
|
||||||
// todo: could possibly parse FCVAR names here instead, would be easier
|
convar->Flags = FCVAR_NONE;
|
||||||
|
|
||||||
if (convarObj.HasMember("Flags"))
|
if (convarObj.HasMember("Flags"))
|
||||||
convar->Flags = convarObj["Flags"].GetInt();
|
{
|
||||||
else
|
// read raw integer flags
|
||||||
convar->Flags = FCVAR_NONE;
|
if (convarObj["Flags"].IsInt())
|
||||||
|
convar->Flags = convarObj["Flags"].GetInt();
|
||||||
|
else if (convarObj["Flags"].IsString())
|
||||||
|
{
|
||||||
|
// parse cvar flags from string
|
||||||
|
// example string: ARCHIVE_PLAYERPROFILE | GAMEDLL
|
||||||
|
|
||||||
|
std::string sFlags = convarObj["Flags"].GetString();
|
||||||
|
sFlags += '|'; // add additional | so we register the last flag
|
||||||
|
std::string sCurrentFlag;
|
||||||
|
|
||||||
|
for (int i = 0; i < sFlags.length(); i++)
|
||||||
|
{
|
||||||
|
if (isspace(sFlags[i]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// if we encounter a |, add current string as a flag
|
||||||
|
if (sFlags[i] == '|')
|
||||||
|
{
|
||||||
|
bool bHasFlags = false;
|
||||||
|
int iCurrentFlags;
|
||||||
|
|
||||||
|
for (auto& flagPair : g_PrintCommandFlags)
|
||||||
|
{
|
||||||
|
if (!sCurrentFlag.compare(flagPair.second))
|
||||||
|
{
|
||||||
|
iCurrentFlags = flagPair.first;
|
||||||
|
bHasFlags = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bHasFlags)
|
||||||
|
convar->Flags |= iCurrentFlags;
|
||||||
|
else
|
||||||
|
spdlog::warn("Mod ConVar {} has unknown flag {}", convar->Name, sCurrentFlag);
|
||||||
|
|
||||||
|
sCurrentFlag = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sCurrentFlag += sFlags[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConVars.push_back(convar);
|
ConVars.push_back(convar);
|
||||||
}
|
}
|
||||||
|
@ -127,7 +172,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
|
||||||
ModScript script;
|
ModScript script;
|
||||||
|
|
||||||
script.Path = scriptObj["Path"].GetString();
|
script.Path = scriptObj["Path"].GetString();
|
||||||
script.RsonRunOn = scriptObj["RunOn"].GetString();
|
script.RunOn = scriptObj["RunOn"].GetString();
|
||||||
|
|
||||||
if (scriptObj.HasMember("ServerCallback") && scriptObj["ServerCallback"].IsObject())
|
if (scriptObj.HasMember("ServerCallback") && scriptObj["ServerCallback"].IsObject())
|
||||||
{
|
{
|
||||||
|
@ -206,7 +251,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wasReadSuccessfully = true;
|
m_bWasReadSuccessfully = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModManager::ModManager()
|
ModManager::ModManager()
|
||||||
|
@ -223,7 +268,7 @@ ModManager::ModManager()
|
||||||
|
|
||||||
void ModManager::LoadMods()
|
void ModManager::LoadMods()
|
||||||
{
|
{
|
||||||
if (m_hasLoadedMods)
|
if (m_bHasLoadedMods)
|
||||||
UnloadMods();
|
UnloadMods();
|
||||||
|
|
||||||
std::vector<fs::path> modDirs;
|
std::vector<fs::path> modDirs;
|
||||||
|
@ -232,7 +277,7 @@ void ModManager::LoadMods()
|
||||||
fs::remove_all(GetCompiledAssetsPath());
|
fs::remove_all(GetCompiledAssetsPath());
|
||||||
fs::create_directories(GetModFolderPath());
|
fs::create_directories(GetModFolderPath());
|
||||||
|
|
||||||
DependencyConstants.clear();
|
m_DependencyConstants.clear();
|
||||||
|
|
||||||
// read enabled mods cfg
|
// read enabled mods cfg
|
||||||
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json");
|
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json");
|
||||||
|
@ -244,10 +289,10 @@ void ModManager::LoadMods()
|
||||||
enabledModsStringStream << (char)enabledModsStream.get();
|
enabledModsStringStream << (char)enabledModsStream.get();
|
||||||
|
|
||||||
enabledModsStream.close();
|
enabledModsStream.close();
|
||||||
m_enabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
|
m_EnabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
|
||||||
enabledModsStringStream.str().c_str());
|
enabledModsStringStream.str().c_str());
|
||||||
|
|
||||||
m_hasEnabledModsCfg = m_enabledModsCfg.IsObject();
|
m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get mod directories
|
// get mod directories
|
||||||
|
@ -277,41 +322,41 @@ void ModManager::LoadMods()
|
||||||
|
|
||||||
for (auto& pair : mod.DependencyConstants)
|
for (auto& pair : mod.DependencyConstants)
|
||||||
{
|
{
|
||||||
if (DependencyConstants.find(pair.first) != DependencyConstants.end() && DependencyConstants[pair.first] != pair.second)
|
if (m_DependencyConstants.find(pair.first) != m_DependencyConstants.end() && m_DependencyConstants[pair.first] != pair.second)
|
||||||
{
|
{
|
||||||
spdlog::error("Constant {} in mod {} already exists in another mod.", pair.first, mod.Name);
|
spdlog::error("Constant {} in mod {} already exists in another mod.", pair.first, mod.Name);
|
||||||
mod.wasReadSuccessfully = false;
|
mod.m_bWasReadSuccessfully = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (DependencyConstants.find(pair.first) == DependencyConstants.end())
|
if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end())
|
||||||
DependencyConstants.emplace(pair);
|
m_DependencyConstants.emplace(pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_hasEnabledModsCfg && m_enabledModsCfg.HasMember(mod.Name.c_str()))
|
if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str()))
|
||||||
mod.Enabled = m_enabledModsCfg[mod.Name.c_str()].IsTrue();
|
mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue();
|
||||||
else
|
else
|
||||||
mod.Enabled = true;
|
mod.m_bEnabled = true;
|
||||||
|
|
||||||
if (mod.wasReadSuccessfully)
|
if (mod.m_bWasReadSuccessfully)
|
||||||
{
|
{
|
||||||
spdlog::info("Loaded mod {} successfully", mod.Name);
|
spdlog::info("Loaded mod {} successfully", mod.Name);
|
||||||
if (mod.Enabled)
|
if (mod.m_bEnabled)
|
||||||
spdlog::info("Mod {} is enabled", mod.Name);
|
spdlog::info("Mod {} is enabled", mod.Name);
|
||||||
else
|
else
|
||||||
spdlog::info("Mod {} is disabled", mod.Name);
|
spdlog::info("Mod {} is disabled", mod.Name);
|
||||||
|
|
||||||
m_loadedMods.push_back(mod);
|
m_LoadedMods.push_back(mod);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string());
|
spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by load prio, lowest-highest
|
// sort by load prio, lowest-highest
|
||||||
std::sort(m_loadedMods.begin(), m_loadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; });
|
std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; });
|
||||||
|
|
||||||
for (Mod& mod : m_loadedMods)
|
for (Mod& mod : m_LoadedMods)
|
||||||
{
|
{
|
||||||
if (!mod.Enabled)
|
if (!mod.m_bEnabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// register convars
|
// register convars
|
||||||
|
@ -319,15 +364,15 @@ void ModManager::LoadMods()
|
||||||
// preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this
|
// preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this
|
||||||
// causes us to leak memory on reload, but not much, potentially find a way to not do this at some point
|
// causes us to leak memory on reload, but not much, potentially find a way to not do this at some point
|
||||||
for (ModConVar* convar : mod.ConVars)
|
for (ModConVar* convar : mod.ConVars)
|
||||||
if (!g_pCVar->FindVar(convar->Name.c_str())) // make sure convar isn't registered yet, unsure if necessary but idk what
|
if (!R2::g_pCVar->FindVar(convar->Name.c_str())) // make sure convar isn't registered yet, unsure if necessary but idk what
|
||||||
// behaviour is for defining same convar multiple times
|
// behaviour is for defining same convar multiple times
|
||||||
new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str());
|
new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str());
|
||||||
|
|
||||||
// read vpk paths
|
// read vpk paths
|
||||||
if (fs::exists(mod.ModDirectory / "vpk"))
|
if (fs::exists(mod.m_ModDirectory / "vpk"))
|
||||||
{
|
{
|
||||||
// read vpk cfg
|
// read vpk cfg
|
||||||
std::ifstream vpkJsonStream(mod.ModDirectory / "vpk/vpk.json");
|
std::ifstream vpkJsonStream(mod.m_ModDirectory / "vpk/vpk.json");
|
||||||
std::stringstream vpkJsonStringStream;
|
std::stringstream vpkJsonStringStream;
|
||||||
|
|
||||||
bool bUseVPKJson = false;
|
bool bUseVPKJson = false;
|
||||||
|
@ -345,7 +390,7 @@ void ModManager::LoadMods()
|
||||||
bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject();
|
bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "vpk"))
|
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "vpk"))
|
||||||
{
|
{
|
||||||
// a bunch of checks to make sure we're only adding dir vpks and their paths are good
|
// a bunch of checks to make sure we're only adding dir vpks and their paths are good
|
||||||
// note: the game will literally only load vpks with the english prefix
|
// note: the game will literally only load vpks with the english prefix
|
||||||
|
@ -356,25 +401,24 @@ void ModManager::LoadMods()
|
||||||
std::string formattedPath = file.path().filename().string();
|
std::string formattedPath = file.path().filename().string();
|
||||||
|
|
||||||
// this really fucking sucks but it'll work
|
// this really fucking sucks but it'll work
|
||||||
std::string vpkName =
|
std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3);
|
||||||
(file.path().parent_path() / formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3)).string();
|
|
||||||
|
|
||||||
ModVPKEntry& modVpk = mod.Vpks.emplace_back();
|
ModVPKEntry& modVpk = mod.Vpks.emplace_back();
|
||||||
modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() &&
|
modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() &&
|
||||||
dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue());
|
dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue());
|
||||||
modVpk.m_sVpkPath = vpkName;
|
modVpk.m_sVpkPath = (file.path().parent_path() / vpkName).string();
|
||||||
|
|
||||||
if (m_hasLoadedMods && modVpk.m_bAutoLoad)
|
if (m_bHasLoadedMods && modVpk.m_bAutoLoad)
|
||||||
(*g_Filesystem)->m_vtable->MountVPK(*g_Filesystem, vpkName.c_str());
|
(*R2::g_pFilesystem)->m_vtable->MountVPK(*R2::g_pFilesystem, vpkName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read rpak paths
|
// read rpak paths
|
||||||
if (fs::exists(mod.ModDirectory / "paks"))
|
if (fs::exists(mod.m_ModDirectory / "paks"))
|
||||||
{
|
{
|
||||||
// read rpak cfg
|
// read rpak cfg
|
||||||
std::ifstream rpakJsonStream(mod.ModDirectory / "paks/rpak.json");
|
std::ifstream rpakJsonStream(mod.m_ModDirectory / "paks/rpak.json");
|
||||||
std::stringstream rpakJsonStringStream;
|
std::stringstream rpakJsonStringStream;
|
||||||
|
|
||||||
bool bUseRpakJson = false;
|
bool bUseRpakJson = false;
|
||||||
|
@ -406,7 +450,7 @@ void ModManager::LoadMods()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "paks"))
|
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "paks"))
|
||||||
{
|
{
|
||||||
// ensure we're only loading rpaks
|
// ensure we're only loading rpaks
|
||||||
if (fs::is_regular_file(file) && file.path().extension() == ".rpak")
|
if (fs::is_regular_file(file) && file.path().extension() == ".rpak")
|
||||||
|
@ -417,39 +461,39 @@ void ModManager::LoadMods()
|
||||||
modPak.m_bAutoLoad =
|
modPak.m_bAutoLoad =
|
||||||
!bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() &&
|
!bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() &&
|
||||||
dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue());
|
dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue());
|
||||||
|
|
||||||
// postload things
|
// postload things
|
||||||
if (!bUseRpakJson ||
|
if (!bUseRpakJson ||
|
||||||
(dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName)))
|
(dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName)))
|
||||||
{
|
|
||||||
modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString();
|
modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString();
|
||||||
}
|
|
||||||
|
|
||||||
modPak.m_sPakName = pakName;
|
modPak.m_sPakName = pakName;
|
||||||
|
|
||||||
// not using atm because we need to resolve path to rpak
|
// not using atm because we need to resolve path to rpak
|
||||||
// if (m_hasLoadedMods && modPak.m_bAutoLoad)
|
// if (m_hasLoadedMods && modPak.m_bAutoLoad)
|
||||||
// g_PakLoadManager->LoadPakAsync(pakName.c_str());
|
// g_pPakLoadManager->LoadPakAsync(pakName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read keyvalues paths
|
// read keyvalues paths
|
||||||
if (fs::exists(mod.ModDirectory / "keyvalues"))
|
if (fs::exists(mod.m_ModDirectory / "keyvalues"))
|
||||||
{
|
{
|
||||||
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "keyvalues"))
|
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "keyvalues"))
|
||||||
{
|
{
|
||||||
if (fs::is_regular_file(file))
|
if (fs::is_regular_file(file))
|
||||||
{
|
{
|
||||||
std::string kvStr = file.path().lexically_relative(mod.ModDirectory / "keyvalues").lexically_normal().string();
|
std::string kvStr =
|
||||||
|
g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / "keyvalues"));
|
||||||
mod.KeyValues.emplace(STR_HASH(kvStr), kvStr);
|
mod.KeyValues.emplace(STR_HASH(kvStr), kvStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read pdiff
|
// read pdiff
|
||||||
if (fs::exists(mod.ModDirectory / "mod.pdiff"))
|
if (fs::exists(mod.m_ModDirectory / "mod.pdiff"))
|
||||||
{
|
{
|
||||||
std::ifstream pdiffStream(mod.ModDirectory / "mod.pdiff");
|
std::ifstream pdiffStream(mod.m_ModDirectory / "mod.pdiff");
|
||||||
|
|
||||||
if (!pdiffStream.fail())
|
if (!pdiffStream.fail())
|
||||||
{
|
{
|
||||||
|
@ -464,17 +508,17 @@ void ModManager::LoadMods()
|
||||||
}
|
}
|
||||||
|
|
||||||
// read bink video paths
|
// read bink video paths
|
||||||
if (fs::exists(mod.ModDirectory / "media"))
|
if (fs::exists(mod.m_ModDirectory / "media"))
|
||||||
{
|
{
|
||||||
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "media"))
|
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "media"))
|
||||||
if (fs::is_regular_file(file) && file.path().extension() == ".bik")
|
if (fs::is_regular_file(file) && file.path().extension() == ".bik")
|
||||||
mod.BinkVideos.push_back(file.path().filename().string());
|
mod.BinkVideos.push_back(file.path().filename().string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to load audio
|
// try to load audio
|
||||||
if (fs::exists(mod.ModDirectory / "audio"))
|
if (fs::exists(mod.m_ModDirectory / "audio"))
|
||||||
{
|
{
|
||||||
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "audio"))
|
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "audio"))
|
||||||
{
|
{
|
||||||
if (fs::is_regular_file(file) && file.path().extension().string() == ".json")
|
if (fs::is_regular_file(file) && file.path().extension().string() == ".json")
|
||||||
{
|
{
|
||||||
|
@ -489,23 +533,23 @@ void ModManager::LoadMods()
|
||||||
}
|
}
|
||||||
|
|
||||||
// in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised
|
// in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised
|
||||||
for (int64_t i = m_loadedMods.size() - 1; i > -1; i--)
|
for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
|
||||||
{
|
{
|
||||||
if (!m_loadedMods[i].Enabled)
|
if (!m_LoadedMods[i].m_bEnabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (fs::exists(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR))
|
if (fs::exists(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR))
|
||||||
{
|
{
|
||||||
for (fs::directory_entry file : fs::recursive_directory_iterator(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR))
|
for (fs::directory_entry file : fs::recursive_directory_iterator(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR))
|
||||||
{
|
{
|
||||||
fs::path path = file.path().lexically_relative(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR).lexically_normal();
|
std::string path =
|
||||||
|
g_pModManager->NormaliseModFilePath(file.path().lexically_relative(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR));
|
||||||
if (file.is_regular_file() && m_modFiles.find(path.string()) == m_modFiles.end())
|
if (file.is_regular_file() && m_ModFiles.find(path) == m_ModFiles.end())
|
||||||
{
|
{
|
||||||
ModOverrideFile modFile;
|
ModOverrideFile modFile;
|
||||||
modFile.owningMod = &m_loadedMods[i];
|
modFile.m_pOwningMod = &m_LoadedMods[i];
|
||||||
modFile.path = path;
|
modFile.m_Path = path;
|
||||||
m_modFiles.insert(std::make_pair(path.string(), modFile));
|
m_ModFiles.insert(std::make_pair(path, modFile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -517,9 +561,9 @@ void ModManager::LoadMods()
|
||||||
modinfoDoc.AddMember("Mods", rapidjson_document::GenericValue(rapidjson::kArrayType), modinfoDoc.GetAllocator());
|
modinfoDoc.AddMember("Mods", rapidjson_document::GenericValue(rapidjson::kArrayType), modinfoDoc.GetAllocator());
|
||||||
|
|
||||||
int currentModIndex = 0;
|
int currentModIndex = 0;
|
||||||
for (Mod& mod : m_loadedMods)
|
for (Mod& mod : m_LoadedMods)
|
||||||
{
|
{
|
||||||
if (!mod.Enabled || (!mod.RequiredOnClient && !mod.Pdiff.size()))
|
if (!mod.m_bEnabled || (!mod.RequiredOnClient && !mod.Pdiff.size()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
modinfoDoc["Mods"].PushBack(rapidjson_document::GenericValue(rapidjson::kObjectType), modinfoDoc.GetAllocator());
|
modinfoDoc["Mods"].PushBack(rapidjson_document::GenericValue(rapidjson::kObjectType), modinfoDoc.GetAllocator());
|
||||||
|
@ -535,27 +579,27 @@ void ModManager::LoadMods()
|
||||||
buffer.Clear();
|
buffer.Clear();
|
||||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||||
modinfoDoc.Accept(writer);
|
modinfoDoc.Accept(writer);
|
||||||
g_MasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString());
|
g_pMasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString());
|
||||||
|
|
||||||
m_hasLoadedMods = true;
|
m_bHasLoadedMods = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModManager::UnloadMods()
|
void ModManager::UnloadMods()
|
||||||
{
|
{
|
||||||
// clean up stuff from mods before we unload
|
// clean up stuff from mods before we unload
|
||||||
m_modFiles.clear();
|
m_ModFiles.clear();
|
||||||
fs::remove_all(GetCompiledAssetsPath());
|
fs::remove_all(GetCompiledAssetsPath());
|
||||||
|
|
||||||
g_CustomAudioManager.ClearAudioOverrides();
|
g_CustomAudioManager.ClearAudioOverrides();
|
||||||
|
|
||||||
if (!m_hasEnabledModsCfg)
|
if (!m_bHasEnabledModsCfg)
|
||||||
m_enabledModsCfg.SetObject();
|
m_EnabledModsCfg.SetObject();
|
||||||
|
|
||||||
for (Mod& mod : m_loadedMods)
|
for (Mod& mod : m_LoadedMods)
|
||||||
{
|
{
|
||||||
// remove all built kvs
|
// remove all built kvs
|
||||||
for (std::pair<size_t, std::string> kvPaths : mod.KeyValues)
|
for (std::pair<size_t, std::string> kvPaths : mod.KeyValues)
|
||||||
fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.ModDirectory));
|
fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.m_ModDirectory));
|
||||||
|
|
||||||
mod.KeyValues.clear();
|
mod.KeyValues.clear();
|
||||||
|
|
||||||
|
@ -563,27 +607,39 @@ void ModManager::UnloadMods()
|
||||||
// should we be doing this here or should scripts be doing this manually?
|
// should we be doing this here or should scripts be doing this manually?
|
||||||
// main issue with doing this here is when we reload mods for connecting to a server, we write enabled mods, which isn't necessarily
|
// main issue with doing this here is when we reload mods for connecting to a server, we write enabled mods, which isn't necessarily
|
||||||
// what we wanna do
|
// what we wanna do
|
||||||
if (!m_enabledModsCfg.HasMember(mod.Name.c_str()))
|
if (!m_EnabledModsCfg.HasMember(mod.Name.c_str()))
|
||||||
m_enabledModsCfg.AddMember(
|
m_EnabledModsCfg.AddMember(
|
||||||
rapidjson_document::StringRefType(mod.Name.c_str()),
|
rapidjson_document::StringRefType(mod.Name.c_str()),
|
||||||
rapidjson_document::GenericValue(false),
|
rapidjson_document::GenericValue(false),
|
||||||
m_enabledModsCfg.GetAllocator());
|
m_EnabledModsCfg.GetAllocator());
|
||||||
|
|
||||||
m_enabledModsCfg[mod.Name.c_str()].SetBool(mod.Enabled);
|
m_EnabledModsCfg[mod.Name.c_str()].SetBool(mod.m_bEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json");
|
std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json");
|
||||||
rapidjson::OStreamWrapper writeStreamWrapper(writeStream);
|
rapidjson::OStreamWrapper writeStreamWrapper(writeStream);
|
||||||
rapidjson::Writer<rapidjson::OStreamWrapper> writer(writeStreamWrapper);
|
rapidjson::Writer<rapidjson::OStreamWrapper> writer(writeStreamWrapper);
|
||||||
m_enabledModsCfg.Accept(writer);
|
m_EnabledModsCfg.Accept(writer);
|
||||||
|
|
||||||
// do we need to dealloc individual entries in m_loadedMods? idk, rework
|
// do we need to dealloc individual entries in m_loadedMods? idk, rework
|
||||||
m_loadedMods.clear();
|
m_LoadedMods.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ModManager::NormaliseModFilePath(const fs::path path)
|
||||||
|
{
|
||||||
|
std::string str = path.lexically_normal().string();
|
||||||
|
|
||||||
|
// force to lowercase
|
||||||
|
for (char& c : str)
|
||||||
|
if (c <= 'Z' && c >= 'A')
|
||||||
|
c = c - ('Z' - 'z');
|
||||||
|
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModManager::CompileAssetsForFile(const char* filename)
|
void ModManager::CompileAssetsForFile(const char* filename)
|
||||||
{
|
{
|
||||||
size_t fileHash = STR_HASH(fs::path(filename).lexically_normal().string());
|
size_t fileHash = STR_HASH(NormaliseModFilePath(fs::path(filename)));
|
||||||
|
|
||||||
if (fileHash == m_hScriptsRsonHash)
|
if (fileHash == m_hScriptsRsonHash)
|
||||||
BuildScriptsRson();
|
BuildScriptsRson();
|
||||||
|
@ -592,9 +648,9 @@ void ModManager::CompileAssetsForFile(const char* filename)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// check if we should build keyvalues, depending on whether any of our mods have patch kvs for this file
|
// check if we should build keyvalues, depending on whether any of our mods have patch kvs for this file
|
||||||
for (Mod& mod : m_loadedMods)
|
for (Mod& mod : m_LoadedMods)
|
||||||
{
|
{
|
||||||
if (!mod.Enabled)
|
if (!mod.m_bEnabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (mod.KeyValues.find(fileHash) != mod.KeyValues.end())
|
if (mod.KeyValues.find(fileHash) != mod.KeyValues.end())
|
||||||
|
@ -608,14 +664,7 @@ void ModManager::CompileAssetsForFile(const char* filename)
|
||||||
|
|
||||||
void ConCommand_reload_mods(const CCommand& args)
|
void ConCommand_reload_mods(const CCommand& args)
|
||||||
{
|
{
|
||||||
g_ModManager->LoadMods();
|
g_pModManager->LoadMods();
|
||||||
}
|
|
||||||
|
|
||||||
void InitialiseModManager(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
g_ModManager = new ModManager;
|
|
||||||
|
|
||||||
RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::path GetModFolderPath()
|
fs::path GetModFolderPath()
|
||||||
|
@ -626,3 +675,10 @@ fs::path GetCompiledAssetsPath()
|
||||||
{
|
{
|
||||||
return fs::path(GetNorthstarPrefix() + COMPILED_ASSETS_SUFFIX);
|
return fs::path(GetNorthstarPrefix() + COMPILED_ASSETS_SUFFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ON_DLL_LOAD_RELIESON("engine.dll", ModManager, (ConCommand, MasterServer), (CModule module))
|
||||||
|
{
|
||||||
|
g_pModManager = new ModManager;
|
||||||
|
|
||||||
|
RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE);
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
|
#include "memalloc.h"
|
||||||
|
#include "squirrel.h"
|
||||||
|
|
||||||
|
#include "rapidjson/document.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "rapidjson/document.h"
|
|
||||||
#include "memalloc.h"
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
const std::string MOD_FOLDER_SUFFIX = "/mods";
|
const std::string MOD_FOLDER_SUFFIX = "/mods";
|
||||||
|
const std::string REMOTE_MOD_FOLDER_SUFFIX = "/runtime/remote/mods";
|
||||||
const fs::path MOD_OVERRIDE_DIR = "mod";
|
const fs::path MOD_OVERRIDE_DIR = "mod";
|
||||||
const std::string COMPILED_ASSETS_SUFFIX = "/runtime/compiled";
|
const std::string COMPILED_ASSETS_SUFFIX = "/runtime/compiled";
|
||||||
|
|
||||||
|
@ -24,9 +25,6 @@ struct ModConVar
|
||||||
struct ModScriptCallback
|
struct ModScriptCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// would've liked to make it possible to hook arbitrary codecallbacks, but couldn't find a function that calls some ui ones
|
|
||||||
// std::string HookedCodeCallback;
|
|
||||||
|
|
||||||
ScriptContext Context;
|
ScriptContext Context;
|
||||||
|
|
||||||
// called before the codecallback is executed
|
// called before the codecallback is executed
|
||||||
|
@ -39,7 +37,7 @@ struct ModScript
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::string Path;
|
std::string Path;
|
||||||
std::string RsonRunOn;
|
std::string RunOn;
|
||||||
|
|
||||||
std::vector<ModScriptCallback> Callbacks;
|
std::vector<ModScriptCallback> Callbacks;
|
||||||
};
|
};
|
||||||
|
@ -64,8 +62,10 @@ class Mod
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// runtime stuff
|
// runtime stuff
|
||||||
fs::path ModDirectory;
|
bool m_bEnabled = true;
|
||||||
bool Enabled = true;
|
bool m_bWasReadSuccessfully = false;
|
||||||
|
fs::path m_ModDirectory;
|
||||||
|
// bool m_bIsRemote;
|
||||||
|
|
||||||
// mod.json stuff:
|
// mod.json stuff:
|
||||||
|
|
||||||
|
@ -100,14 +100,9 @@ class Mod
|
||||||
std::vector<ModRpakEntry> Rpaks;
|
std::vector<ModRpakEntry> Rpaks;
|
||||||
std::unordered_map<std::string, std::string>
|
std::unordered_map<std::string, std::string>
|
||||||
RpakAliases; // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite
|
RpakAliases; // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite
|
||||||
// iterated over to create squirrel VM constants depending if a mod exists or not.
|
|
||||||
// this only exists because we cannot access g_ModManager whilst mods are being loaded for the first time for some reason.
|
|
||||||
std::unordered_map<std::string, std::string> DependencyConstants;
|
std::unordered_map<std::string, std::string> DependencyConstants;
|
||||||
|
|
||||||
// other stuff
|
|
||||||
|
|
||||||
bool wasReadSuccessfully = false;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Mod(fs::path modPath, char* jsonBuf);
|
Mod(fs::path modPath, char* jsonBuf);
|
||||||
};
|
};
|
||||||
|
@ -115,42 +110,40 @@ class Mod
|
||||||
struct ModOverrideFile
|
struct ModOverrideFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Mod* owningMod;
|
Mod* m_pOwningMod;
|
||||||
fs::path path;
|
fs::path m_Path;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModManager
|
class ModManager
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
bool m_hasLoadedMods = false;
|
bool m_bHasLoadedMods = false;
|
||||||
bool m_hasEnabledModsCfg;
|
bool m_bHasEnabledModsCfg;
|
||||||
rapidjson_document m_enabledModsCfg;
|
rapidjson_document m_EnabledModsCfg;
|
||||||
|
|
||||||
// precalculated hashes
|
// precalculated hashes
|
||||||
size_t m_hScriptsRsonHash;
|
size_t m_hScriptsRsonHash;
|
||||||
size_t m_hPdefHash;
|
size_t m_hPdefHash;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::vector<Mod> m_loadedMods;
|
std::vector<Mod> m_LoadedMods;
|
||||||
std::unordered_map<std::string, ModOverrideFile> m_modFiles;
|
std::unordered_map<std::string, ModOverrideFile> m_ModFiles;
|
||||||
// iterated over to create squirrel VM constants depending if a mod exists or not.
|
std::unordered_map<std::string, std::string> m_DependencyConstants;
|
||||||
// here because constants are global anyways.
|
|
||||||
std::unordered_map<std::string, std::string> DependencyConstants;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ModManager();
|
ModManager();
|
||||||
void LoadMods();
|
void LoadMods();
|
||||||
void UnloadMods();
|
void UnloadMods();
|
||||||
|
std::string NormaliseModFilePath(const fs::path path);
|
||||||
void CompileAssetsForFile(const char* filename);
|
void CompileAssetsForFile(const char* filename);
|
||||||
|
|
||||||
// compile asset type stuff, these are done in files under Mods/Compiled/
|
// compile asset type stuff, these are done in files under runtime/compiled/
|
||||||
void BuildScriptsRson();
|
void BuildScriptsRson();
|
||||||
void TryBuildKeyValues(const char* filename);
|
void TryBuildKeyValues(const char* filename);
|
||||||
void BuildPdef();
|
void BuildPdef();
|
||||||
};
|
};
|
||||||
|
|
||||||
void InitialiseModManager(HMODULE baseAddress);
|
|
||||||
fs::path GetModFolderPath();
|
fs::path GetModFolderPath();
|
||||||
fs::path GetCompiledAssetsPath();
|
fs::path GetCompiledAssetsPath();
|
||||||
|
|
||||||
extern ModManager* g_ModManager;
|
extern ModManager* g_pModManager;
|
||||||
|
|
|
@ -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 "pch.h"
|
||||||
#include "nsprefix.h"
|
#include "nsprefix.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
std::string GetNorthstarPrefix()
|
std::string GetNorthstarPrefix()
|
||||||
{
|
{
|
||||||
return NORTHSTAR_FOLDER_PREFIX;
|
return NORTHSTAR_FOLDER_PREFIX;
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseConfigurables()
|
void InitialiseNorthstarPrefix()
|
||||||
{
|
{
|
||||||
char* clachar = strstr(GetCommandLineA(), "-profile=");
|
char* clachar = strstr(GetCommandLineA(), "-profile=");
|
||||||
if (clachar)
|
if (clachar)
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
|
|
||||||
static std::string NORTHSTAR_FOLDER_PREFIX;
|
static std::string NORTHSTAR_FOLDER_PREFIX;
|
||||||
|
|
||||||
|
void InitialiseNorthstarPrefix();
|
||||||
std::string GetNorthstarPrefix();
|
std::string GetNorthstarPrefix();
|
||||||
void parseConfigurables();
|
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS // temp because i'm very lazy and want to use inet_addr, remove later
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS // temp because i'm very lazy and want to use inet_addr, remove later
|
||||||
#define RAPIDJSON_HAS_STDSTRING 1
|
#define RAPIDJSON_HAS_STDSTRING 1
|
||||||
|
|
||||||
// httplib ssl
|
|
||||||
|
|
||||||
// add headers that you want to pre-compile here
|
// add headers that you want to pre-compile here
|
||||||
#include "memalloc.h"
|
#include "memalloc.h"
|
||||||
|
|
||||||
|
@ -20,11 +18,14 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include "include/MinHook.h"
|
#include "MinHook.h"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include "libcurl/include/curl/curl.h"
|
#include "libcurl/include/curl/curl.h"
|
||||||
#include "hookutils.h"
|
#include "hooks.h"
|
||||||
|
#include "memory.h"
|
||||||
|
|
||||||
template <typename ReturnType, typename... Args> ReturnType CallVFunc(int index, void* thisPtr, Args... args)
|
template <typename ReturnType, typename... Args> ReturnType CallVFunc(int index, void* thisPtr, Args... args)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "modmanager.h"
|
#include "modmanager.h"
|
||||||
#include "filesystem.h"
|
#include "filesystem.h"
|
||||||
#include "hookutils.h"
|
|
||||||
#include "pdef.h"
|
#include "pdef.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -14,11 +14,11 @@ void ModManager::BuildPdef()
|
||||||
fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX);
|
fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX);
|
||||||
|
|
||||||
fs::remove(MOD_PDEF_PATH);
|
fs::remove(MOD_PDEF_PATH);
|
||||||
std::string pdef = ReadVPKOriginalFile(VPK_PDEF_PATH);
|
std::string pdef = R2::ReadVPKOriginalFile(VPK_PDEF_PATH);
|
||||||
|
|
||||||
for (Mod& mod : m_loadedMods)
|
for (Mod& mod : m_LoadedMods)
|
||||||
{
|
{
|
||||||
if (!mod.Enabled || !mod.Pdiff.size())
|
if (!mod.m_bEnabled || !mod.Pdiff.size())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// this code probably isn't going to be pretty lol
|
// this code probably isn't going to be pretty lol
|
||||||
|
@ -107,11 +107,11 @@ void ModManager::BuildPdef()
|
||||||
writeStream.close();
|
writeStream.close();
|
||||||
|
|
||||||
ModOverrideFile overrideFile;
|
ModOverrideFile overrideFile;
|
||||||
overrideFile.owningMod = nullptr;
|
overrideFile.m_pOwningMod = nullptr;
|
||||||
overrideFile.path = VPK_PDEF_PATH;
|
overrideFile.m_Path = VPK_PDEF_PATH;
|
||||||
|
|
||||||
if (m_modFiles.find(VPK_PDEF_PATH) == m_modFiles.end())
|
if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end())
|
||||||
m_modFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile));
|
m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile));
|
||||||
else
|
else
|
||||||
m_modFiles[VPK_PDEF_PATH] = overrideFile;
|
m_ModFiles[VPK_PDEF_PATH] = overrideFile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,94 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
#include "nsmem.h"
|
|
||||||
#include "concommand.h"
|
#include "concommand.h"
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
#include "gameutils.h"
|
|
||||||
#include "hookutils.h"
|
|
||||||
#include "squirrel.h"
|
#include "squirrel.h"
|
||||||
|
#include "hoststate.h"
|
||||||
|
#include "serverpresence.h"
|
||||||
|
|
||||||
typedef char (*Onclc_SetPlaylistVarOverrideType)(void* a1, void* a2);
|
AUTOHOOK_INIT()
|
||||||
Onclc_SetPlaylistVarOverrideType Onclc_SetPlaylistVarOverride;
|
|
||||||
|
|
||||||
typedef int (*GetCurrentGamemodeMaxPlayersType)();
|
// use the R2 namespace for game funcs
|
||||||
GetCurrentGamemodeMaxPlayersType GetCurrentGamemodeMaxPlayers;
|
namespace R2
|
||||||
|
{
|
||||||
// function type defined in gameutils.h
|
const char* (*GetCurrentPlaylistName)();
|
||||||
SetPlaylistVarOverrideType SetPlaylistVarOverrideOriginal;
|
void (*SetCurrentPlaylist)(const char* pPlaylistName);
|
||||||
GetCurrentPlaylistVarType GetCurrentPlaylistVarOriginal;
|
void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue);
|
||||||
|
const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides);
|
||||||
|
} // namespace R2
|
||||||
|
|
||||||
ConVar* Cvar_ns_use_clc_SetPlaylistVarOverride;
|
ConVar* Cvar_ns_use_clc_SetPlaylistVarOverride;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(clc_SetPlaylistVarOverride__Process, engine.dll + 0x222180,
|
||||||
|
char, __fastcall, (void* a1, void* a2))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
// the private_match playlist on mp_lobby is the only situation where there should be any legitimate sending of this netmessage
|
||||||
|
if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(R2::GetCurrentPlaylistName(), "private_match") ||
|
||||||
|
strcmp(R2::g_pHostState->m_levelName, "mp_lobby"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return clc_SetPlaylistVarOverride__Process(a1, a2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(SetCurrentPlaylist, engine.dll + 0x18EB20,
|
||||||
|
bool, __fastcall, (const char* pPlaylistName))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
bool bSuccess = SetCurrentPlaylist(pPlaylistName);
|
||||||
|
|
||||||
|
if (bSuccess)
|
||||||
|
{
|
||||||
|
spdlog::info("Set playlist to {}", R2::GetCurrentPlaylistName());
|
||||||
|
g_pServerPresence->SetPlaylist(R2::GetCurrentPlaylistName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return bSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(SetPlaylistVarOverride, engine.dll + 0x18ED00,
|
||||||
|
void, __fastcall, (const char* pVarName, const char* pValue))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
if (strlen(pValue) >= 64)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetPlaylistVarOverride(pVarName, pValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(GetCurrentPlaylistVar, engine.dll + 0x18C680,
|
||||||
|
const char*, __fastcall, (const char* pVarName, bool bUseOverrides))
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
if (!bUseOverrides && !strcmp(pVarName, "max_players"))
|
||||||
|
bUseOverrides = true;
|
||||||
|
|
||||||
|
return GetCurrentPlaylistVar(pVarName, bUseOverrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
AUTOHOOK(GetCurrentGamemodeMaxPlayers, engine.dll + 0x18C430,
|
||||||
|
int, __fastcall, ())
|
||||||
|
// clang-format on
|
||||||
|
{
|
||||||
|
const char* pMaxPlayers = R2::GetCurrentPlaylistVar("max_players", 0);
|
||||||
|
if (!pMaxPlayers)
|
||||||
|
return GetCurrentGamemodeMaxPlayers();
|
||||||
|
|
||||||
|
int iMaxPlayers = atoi(pMaxPlayers);
|
||||||
|
return iMaxPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
void ConCommand_playlist(const CCommand& args)
|
void ConCommand_playlist(const CCommand& args)
|
||||||
{
|
{
|
||||||
if (args.ArgC() < 2)
|
if (args.ArgC() < 2)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SetCurrentPlaylist(args.Arg(1));
|
R2::SetCurrentPlaylist(args.Arg(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConCommand_setplaylistvaroverride(const CCommand& args)
|
void ConCommand_setplaylistvaroverride(const CCommand& args)
|
||||||
|
@ -33,74 +97,34 @@ void ConCommand_setplaylistvaroverride(const CCommand& args)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 1; i < args.ArgC(); i += 2)
|
for (int i = 1; i < args.ArgC(); i += 2)
|
||||||
SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1));
|
R2::SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
char Onclc_SetPlaylistVarOverrideHook(void* a1, void* a2)
|
ON_DLL_LOAD_RELIESON("engine.dll", PlaylistHooks, (ConCommand, ConVar), (CModule module))
|
||||||
{
|
{
|
||||||
// the private_match playlist is the only situation where there should be any legitimate sending of this netmessage
|
AUTOHOOK_DISPATCH()
|
||||||
// todo: check mp_lobby here too
|
|
||||||
if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(GetCurrentPlaylistName(), "private_match"))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return Onclc_SetPlaylistVarOverride(a1, a2);
|
R2::GetCurrentPlaylistName = module.Offset(0x18C640).As<const char* (*)()>();
|
||||||
}
|
R2::SetCurrentPlaylist = module.Offset(0x18EB20).As<void (*)(const char*)>();
|
||||||
|
R2::SetPlaylistVarOverride = module.Offset(0x18ED00).As<void (*)(const char*, const char*)>();
|
||||||
|
R2::GetCurrentPlaylistVar = module.Offset(0x18C680).As<const char* (*)(const char*, bool)>();
|
||||||
|
|
||||||
void SetPlaylistVarOverrideHook(const char* varName, const char* value)
|
// playlist is the name of the command on respawn servers, but we already use setplaylist so can't get rid of it
|
||||||
{
|
RegisterConCommand("playlist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE);
|
||||||
if (strlen(value) >= 64)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SetPlaylistVarOverrideOriginal(varName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
char* GetCurrentPlaylistVarHook(const char* varName, bool useOverrides)
|
|
||||||
{
|
|
||||||
if (!useOverrides && !strcmp(varName, "max_players"))
|
|
||||||
useOverrides = true;
|
|
||||||
|
|
||||||
return GetCurrentPlaylistVarOriginal(varName, useOverrides);
|
|
||||||
}
|
|
||||||
|
|
||||||
int GetCurrentGamemodeMaxPlayersHook()
|
|
||||||
{
|
|
||||||
char* maxPlayersStr = GetCurrentPlaylistVar("max_players", 0);
|
|
||||||
if (!maxPlayersStr)
|
|
||||||
return GetCurrentGamemodeMaxPlayers();
|
|
||||||
|
|
||||||
int maxPlayers = atoi(maxPlayersStr);
|
|
||||||
return maxPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitialisePlaylistHooks(HMODULE baseAddress)
|
|
||||||
{
|
|
||||||
RegisterConCommand("setplaylist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE);
|
RegisterConCommand("setplaylist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE);
|
||||||
RegisterConCommand("setplaylistvaroverrides", ConCommand_setplaylistvaroverride, "sets a playlist var override", FCVAR_NONE);
|
RegisterConCommand("setplaylistvaroverrides", ConCommand_setplaylistvaroverride, "sets a playlist var override", FCVAR_NONE);
|
||||||
|
|
||||||
// note: clc_SetPlaylistVarOverride is pretty insecure, since it allows for entirely arbitrary playlist var overrides to be sent to the
|
// note: clc_SetPlaylistVarOverride is pretty insecure, since it allows for entirely arbitrary playlist var overrides to be sent to the
|
||||||
// server this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be
|
// server, this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be
|
||||||
// disabled altogether, since the custom menus won't use it anyway this should only really be accepted if you want vanilla client
|
// disabled altogether, since the custom menus won't use it anyway this should only really be accepted if you want vanilla client
|
||||||
// compatibility
|
// compatibility
|
||||||
Cvar_ns_use_clc_SetPlaylistVarOverride = new ConVar(
|
Cvar_ns_use_clc_SetPlaylistVarOverride = new ConVar(
|
||||||
"ns_use_clc_SetPlaylistVarOverride", "0", FCVAR_GAMEDLL, "Whether the server should accept clc_SetPlaylistVarOverride messages");
|
"ns_use_clc_SetPlaylistVarOverride", "0", FCVAR_GAMEDLL, "Whether the server should accept clc_SetPlaylistVarOverride messages");
|
||||||
|
|
||||||
HookEnabler hook;
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook, (char*)baseAddress + 0x222180, &Onclc_SetPlaylistVarOverrideHook, reinterpret_cast<LPVOID*>(&Onclc_SetPlaylistVarOverride));
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook, (char*)baseAddress + 0x18ED00, &SetPlaylistVarOverrideHook, reinterpret_cast<LPVOID*>(&SetPlaylistVarOverrideOriginal));
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook, (char*)baseAddress + 0x18C680, &GetCurrentPlaylistVarHook, reinterpret_cast<LPVOID*>(&GetCurrentPlaylistVarOriginal));
|
|
||||||
ENABLER_CREATEHOOK(
|
|
||||||
hook, (char*)baseAddress + 0x18C430, &GetCurrentGamemodeMaxPlayersHook, reinterpret_cast<LPVOID*>(&GetCurrentGamemodeMaxPlayers));
|
|
||||||
|
|
||||||
uintptr_t ba = (uintptr_t)baseAddress;
|
|
||||||
|
|
||||||
// patch to prevent clc_SetPlaylistVarOverride from being able to crash servers if we reach max overrides due to a call to Error (why is
|
// patch to prevent clc_SetPlaylistVarOverride from being able to crash servers if we reach max overrides due to a call to Error (why is
|
||||||
// this possible respawn, wtf) todo: add a warning for this
|
// this possible respawn, wtf) todo: add a warning for this
|
||||||
{
|
module.Offset(0x18ED8D).Patch("C3");
|
||||||
NSMem::BytePatch(ba + 0x18ED8D, "C3");
|
|
||||||
}
|
|
||||||
|
|
||||||
// patch to allow setplaylistvaroverride to be called before map init on dedicated and private match launched through the game
|
// patch to allow setplaylistvaroverride to be called before map init on dedicated and private match launched through the game
|
||||||
NSMem::NOP(ba + 0x18ED17, 6);
|
module.Offset(0x18ED17).NOP(6);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
void InitialisePlaylistHooks(HMODULE baseAddress);
|
|
||||||
|
// use the R2 namespace for game funcs
|
||||||
|
namespace R2
|
||||||
|
{
|
||||||
|
extern const char* (*GetCurrentPlaylistName)();
|
||||||
|
extern void (*SetCurrentPlaylist)(const char* pPlaylistName);
|
||||||
|
extern void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue);
|
||||||
|
extern const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides);
|
||||||
|
} // namespace R2
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "squirrel.h"
|
#include "squirrel.h"
|
||||||
#include "plugins.h"
|
#include "plugins.h"
|
||||||
#include <chrono>
|
|
||||||
#include "masterserver.h"
|
#include "masterserver.h"
|
||||||
#include "convar.h"
|
#include "convar.h"
|
||||||
|
#include "serverpresence.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -109,31 +111,31 @@ void initGameState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading
|
// string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading
|
||||||
SQRESULT SQ_UpdateGameStateUI(void* sqvm)
|
SQRESULT SQ_UpdateGameStateUI(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
AcquireSRWLockExclusive(&gameStateLock);
|
AcquireSRWLockExclusive(&gameStateLock);
|
||||||
gameState.map = ClientSq_getstring(sqvm, 1);
|
gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
|
||||||
gameState.mapDisplayName = ClientSq_getstring(sqvm, 2);
|
gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
|
||||||
gameState.playlist = ClientSq_getstring(sqvm, 3);
|
gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
|
||||||
gameState.playlistDisplayName = ClientSq_getstring(sqvm, 4);
|
gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 4);
|
||||||
gameState.connected = ClientSq_getbool(sqvm, 5);
|
gameState.connected = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 5);
|
||||||
gameState.loading = ClientSq_getbool(sqvm, 6);
|
gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 6);
|
||||||
ReleaseSRWLockExclusive(&gameStateLock);
|
ReleaseSRWLockExclusive(&gameStateLock);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit
|
// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit
|
||||||
SQRESULT SQ_UpdateGameStateClient(void* sqvm)
|
SQRESULT SQ_UpdateGameStateClient(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
AcquireSRWLockExclusive(&gameStateLock);
|
AcquireSRWLockExclusive(&gameStateLock);
|
||||||
AcquireSRWLockExclusive(&serverInfoLock);
|
AcquireSRWLockExclusive(&serverInfoLock);
|
||||||
gameState.players = ClientSq_getinteger(sqvm, 1);
|
gameState.players = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
|
||||||
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 2);
|
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 2);
|
||||||
gameState.ourScore = ClientSq_getinteger(sqvm, 3);
|
gameState.ourScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 3);
|
||||||
gameState.secondHighestScore = ClientSq_getinteger(sqvm, 4);
|
gameState.secondHighestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
|
||||||
gameState.highestScore = ClientSq_getinteger(sqvm, 5);
|
gameState.highestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 5);
|
||||||
serverInfo.roundBased = ClientSq_getbool(sqvm, 6);
|
serverInfo.roundBased = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 6);
|
||||||
serverInfo.scoreLimit = ClientSq_getbool(sqvm, 7);
|
serverInfo.scoreLimit = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 7);
|
||||||
ReleaseSRWLockExclusive(&gameStateLock);
|
ReleaseSRWLockExclusive(&gameStateLock);
|
||||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
|
@ -141,59 +143,59 @@ SQRESULT SQ_UpdateGameStateClient(void* sqvm)
|
||||||
|
|
||||||
// string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string
|
// string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string
|
||||||
// playlistDisplayName
|
// playlistDisplayName
|
||||||
SQRESULT SQ_UpdateServerInfo(void* sqvm)
|
SQRESULT SQ_UpdateServerInfo(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
AcquireSRWLockExclusive(&gameStateLock);
|
AcquireSRWLockExclusive(&gameStateLock);
|
||||||
AcquireSRWLockExclusive(&serverInfoLock);
|
AcquireSRWLockExclusive(&serverInfoLock);
|
||||||
serverInfo.id = ClientSq_getstring(sqvm, 1);
|
serverInfo.id = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
|
||||||
serverInfo.name = ClientSq_getstring(sqvm, 2);
|
serverInfo.name = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
|
||||||
serverInfo.password = ClientSq_getstring(sqvm, 3);
|
serverInfo.password = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
|
||||||
gameState.players = ClientSq_getinteger(sqvm, 4);
|
gameState.players = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 4);
|
||||||
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 5);
|
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 5);
|
||||||
gameState.map = ClientSq_getstring(sqvm, 6);
|
gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 6);
|
||||||
gameState.mapDisplayName = ClientSq_getstring(sqvm, 7);
|
gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 7);
|
||||||
gameState.playlist = ClientSq_getstring(sqvm, 8);
|
gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 8);
|
||||||
gameState.playlistDisplayName = ClientSq_getstring(sqvm, 9);
|
gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 9);
|
||||||
ReleaseSRWLockExclusive(&gameStateLock);
|
ReleaseSRWLockExclusive(&gameStateLock);
|
||||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// int maxPlayers
|
// int maxPlayers
|
||||||
SQRESULT SQ_UpdateServerInfoBetweenRounds(void* sqvm)
|
SQRESULT SQ_UpdateServerInfoBetweenRounds(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
AcquireSRWLockExclusive(&serverInfoLock);
|
AcquireSRWLockExclusive(&serverInfoLock);
|
||||||
serverInfo.id = ClientSq_getstring(sqvm, 1);
|
serverInfo.id = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 1);
|
||||||
serverInfo.name = ClientSq_getstring(sqvm, 2);
|
serverInfo.name = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
|
||||||
serverInfo.password = ClientSq_getstring(sqvm, 3);
|
serverInfo.password = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 3);
|
||||||
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 4);
|
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
|
||||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// float timeInFuture
|
// float timeInFuture
|
||||||
SQRESULT SQ_UpdateTimeInfo(void* sqvm)
|
SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
AcquireSRWLockExclusive(&serverInfoLock);
|
AcquireSRWLockExclusive(&serverInfoLock);
|
||||||
serverInfo.endTime = ceil(ClientSq_getfloat(sqvm, 1));
|
serverInfo.endTime = ceil(g_pSquirrel<ScriptContext::CLIENT>->getfloat(sqvm, 1));
|
||||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// bool loading
|
// bool loading
|
||||||
SQRESULT SQ_SetConnected(void* sqvm)
|
SQRESULT SQ_SetConnected(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
AcquireSRWLockExclusive(&gameStateLock);
|
AcquireSRWLockExclusive(&gameStateLock);
|
||||||
gameState.loading = ClientSq_getbool(sqvm, 1);
|
gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 1);
|
||||||
ReleaseSRWLockExclusive(&gameStateLock);
|
ReleaseSRWLockExclusive(&gameStateLock);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
SQRESULT SQ_UpdateListenServer(void* sqvm)
|
SQRESULT SQ_UpdateListenServer(HSquirrelVM* sqvm)
|
||||||
{
|
{
|
||||||
AcquireSRWLockExclusive(&serverInfoLock);
|
AcquireSRWLockExclusive(&serverInfoLock);
|
||||||
serverInfo.id = g_MasterServerManager->m_sOwnServerId;
|
serverInfo.id = g_pMasterServerManager->m_sOwnServerId;
|
||||||
serverInfo.password = Cvar_ns_server_password->GetString();
|
serverInfo.password = ""; // g_pServerPresence->Cvar_ns_server_password->GetString(); todo this fr
|
||||||
ReleaseSRWLockExclusive(&serverInfoLock);
|
ReleaseSRWLockExclusive(&serverInfoLock);
|
||||||
return SQRESULT_NOTNULL;
|
return SQRESULT_NOTNULL;
|
||||||
}
|
}
|
||||||
|
@ -384,26 +386,26 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var)
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialisePluginCommands(HMODULE baseAddress)
|
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", PluginCommands, ClientSquirrel, (CModule module))
|
||||||
{
|
{
|
||||||
// i swear there's a way to make this not have be run in 2 contexts but i can't figure it out
|
// i swear there's a way to make this not have be run in 2 contexts but i can't figure it out
|
||||||
// some funcs i need are just not available in UI or CLIENT
|
// some funcs i need are just not available in UI or CLIENT
|
||||||
|
|
||||||
if (g_UISquirrelManager && g_ClientSquirrelManager)
|
if (g_pSquirrel<ScriptContext::UI> && g_pSquirrel<ScriptContext::CLIENT>)
|
||||||
{
|
{
|
||||||
g_UISquirrelManager->AddFuncRegistration(
|
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
|
||||||
"void",
|
"void",
|
||||||
"NSUpdateGameStateUI",
|
"NSUpdateGameStateUI",
|
||||||
"string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading",
|
"string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading",
|
||||||
"",
|
"",
|
||||||
SQ_UpdateGameStateUI);
|
SQ_UpdateGameStateUI);
|
||||||
g_ClientSquirrelManager->AddFuncRegistration(
|
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
|
||||||
"void",
|
"void",
|
||||||
"NSUpdateGameStateClient",
|
"NSUpdateGameStateClient",
|
||||||
"int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit",
|
"int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit",
|
||||||
"",
|
"",
|
||||||
SQ_UpdateGameStateClient);
|
SQ_UpdateGameStateClient);
|
||||||
g_UISquirrelManager->AddFuncRegistration(
|
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
|
||||||
"void",
|
"void",
|
||||||
"NSUpdateServerInfo",
|
"NSUpdateServerInfo",
|
||||||
"string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, "
|
"string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, "
|
||||||
|
@ -411,10 +413,10 @@ void InitialisePluginCommands(HMODULE baseAddress)
|
||||||
"playlistDisplayName",
|
"playlistDisplayName",
|
||||||
"",
|
"",
|
||||||
SQ_UpdateServerInfo);
|
SQ_UpdateServerInfo);
|
||||||
g_ClientSquirrelManager->AddFuncRegistration(
|
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
|
||||||
"void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds);
|
"void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds);
|
||||||
g_ClientSquirrelManager->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
|
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
|
||||||
g_UISquirrelManager->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
|
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
|
||||||
g_UISquirrelManager->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
|
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,5 +15,3 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var);
|
||||||
|
|
||||||
void initGameState();
|
void initGameState();
|
||||||
void* getPluginObject(PluginObject var);
|
void* getPluginObject(PluginObject var);
|
||||||
|
|
||||||
void InitialisePluginCommands(HMODULE baseAddress);
|
|
||||||
|
|
|
@ -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