Revert "Merge branch 'main' into log-colours"

This reverts commit d40b6496b7, reversing
changes made to a98bcc22e8.
This commit is contained in:
ASpoonPlaysGames 2022-10-18 00:24:40 +01:00
parent d40b6496b7
commit da792245b1
155 changed files with 9741 additions and 11614 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,13 @@
#include "pch.h"
#include "audio.h"
#include "dedicated.h"
#include "convar.h"
#include "rapidjson/error/en.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <random>
AUTOHOOK_INIT()
#include "convar.h"
extern "C"
{
@ -231,8 +229,10 @@ EventOverrideData::EventOverrideData(const std::string& data, const fs::path& pa
}
// read from after the header first to preserve the empty header, then read the header last
wavStream.seekg(sizeof(EMPTY_WAVE), std::ios::beg);
wavStream.read(reinterpret_cast<char*>(&data[sizeof(EMPTY_WAVE)]), fileSize - sizeof(EMPTY_WAVE));
wavStream.seekg(0, std::ios::beg);
wavStream.read(reinterpret_cast<char*>(data), fileSize);
wavStream.read(reinterpret_cast<char*>(data), sizeof(EMPTY_WAVE));
wavStream.close();
spdlog::info("Finished async read of audio sample {}", pathString);
@ -315,7 +315,6 @@ void CustomAudioManager::ClearAudioOverrides()
{
// stop all miles sounds beforehand
// miles_stop_all
MilesStopAll();
// this is cancer but it works
@ -324,12 +323,15 @@ void CustomAudioManager::ClearAudioOverrides()
// slightly (very) bad
// wait for all audio reads to complete so we don't kill preexisting audio buffers as we're writing to them
std::unique_lock lock(m_loadingMutex);
std::unique_lock lock(g_CustomAudioManager.m_loadingMutex);
m_loadedAudioOverrides.clear();
m_loadedAudioOverridesRegex.clear();
}
typedef bool (*LoadSampleMetadata_Type)(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType);
LoadSampleMetadata_Type LoadSampleMetadata_Original;
template <typename Iter, typename RandomGenerator> Iter select_randomly(Iter start, Iter end, RandomGenerator& g)
{
std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
@ -366,26 +368,6 @@ bool ShouldPlayAudioEvent(const char* eventName, const std::shared_ptr<EventOver
return true; // good to go
}
// forward declare
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
uintptr_t parentEvent, void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType);
// DO NOT TOUCH THIS FUNCTION
// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer.
// clang-format off
AUTOHOOK(LoadSampleMetadata, mileswin64.dll + 0xF110,
bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType))
// clang-format on
{
uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent();
// Raw source, used for voice data only
if (audioType == 0)
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType);
}
// DO NOT INLINE THIS FUNCTION
// See comment below.
bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
@ -416,7 +398,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
if (!overrideData)
// not found either
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
else
{
// cache found pattern to improve performance
@ -430,7 +412,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
overrideData = iter->second;
if (!ShouldPlayAudioEvent(eventName, overrideData))
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
void* data = 0;
unsigned int dataLength = 0;
@ -472,7 +454,7 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
if (!data)
{
spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName);
return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType);
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
}
audioBuffer = data;
@ -483,25 +465,51 @@ bool __declspec(noinline) __fastcall LoadSampleMetadata_Internal(
*(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength;
// 64 - Auto-detect sample type
bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64);
bool res = LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, 64);
if (!res)
spdlog::error("LoadSampleMetadata failed! The game will crash :(");
return res;
}
// clang-format off
AUTOHOOK(MilesLog, client.dll + 0x57DAD0,
void, __fastcall, (int level, const char* string))
// clang-format on
// DO NOT TOUCH THIS FUNCTION
// The actual logic of it in a separate function (forcefully not inlined) to preserve the r12 register, which holds the event pointer.
bool __fastcall LoadSampleMetadata_Hook(void* sample, void* audioBuffer, unsigned int audioBufferLength, int audioType)
{
uintptr_t parentEvent = (uintptr_t)Audio_GetParentEvent();
// Raw source, used for voice data only
if (audioType == 0)
return LoadSampleMetadata_Original(sample, audioBuffer, audioBufferLength, audioType);
return LoadSampleMetadata_Internal(parentEvent, sample, audioBuffer, audioBufferLength, audioType);
}
typedef bool (*MilesLog_Type)(int level, const char* string);
MilesLog_Type MilesLog_Original;
void __fastcall MilesLog_Hook(int level, const char* string)
{
spdlog::info("[MSS] {} - {}", level, string);
}
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module))
void InitialiseMilesAudioHooks(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, "");
MilesStopAll = module.Offset(0x580850).As<MilesStopAll_Type>();
if (IsDedicatedServer())
return;
uintptr_t milesAudioBase = (uintptr_t)GetModuleHandleA("mileswin64.dll");
if (!milesAudioBase)
return spdlog::error("miles audio not found :terror:");
HookEnabler hook;
ENABLER_CREATEHOOK(
hook, (char*)milesAudioBase + 0xF110, &LoadSampleMetadata_Hook, reinterpret_cast<LPVOID*>(&LoadSampleMetadata_Original));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x57DAD0, &MilesLog_Hook, reinterpret_cast<LPVOID*>(&MilesLog_Original));
MilesStopAll = (MilesStopAll_Type)((char*)baseAddress + 0x580850);
}

View File

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

View File

@ -1,28 +1,26 @@
#pragma once
#include "pch.h"
#include "bansystem.h"
#include "serverauthentication.h"
#include "maxplayers.h"
#include "concommand.h"
#include "r2server.h"
#include "r2engine.h"
#include "nsprefix.h"
#include "miscserverscript.h"
#include <filesystem>
#include "nsprefix.h"
#include <ctime>
const char* BANLIST_PATH_SUFFIX = "/banlist.txt";
const char BANLIST_COMMENT_CHAR = '#';
ServerBanSystem* g_pBanSystem;
ServerBanSystem* g_ServerBanSystem;
void ServerBanSystem::OpenBanlist()
{
std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt");
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/banlist.txt");
std::stringstream enabledModsStringStream;
if (!banlistStream.fail())
if (!enabledModsStream.fail())
{
std::string line;
while (std::getline(banlistStream, line))
while (std::getline(enabledModsStream, line))
{
// ignore line if first char is # or line is empty
if (line == "" || line.front() == BANLIST_COMMENT_CHAR)
@ -43,7 +41,7 @@ void ServerBanSystem::OpenBanlist()
m_vBannedUids.push_back(strtoull(uid.c_str(), nullptr, 10));
}
banlistStream.close();
enabledModsStream.close();
}
// open write stream for banlist // dont do this to allow for all time access
@ -184,14 +182,15 @@ void ConCommand_ban(const CCommand& args)
if (args.ArgC() < 2)
return;
for (int i = 0; i < R2::GetMaxPlayers(); i++)
// assuming maxplayers 32
for (int i = 0; i < 32; i++)
{
R2::CBaseClient* player = &R2::g_pClientArray[i];
void* player = GetPlayerByIndex(i);
if (!strcmp(player->m_Name, args.Arg(1)) || !strcmp(player->m_UID, args.Arg(1)))
if (!strcmp((char*)player + 0x16, args.Arg(1)) || !strcmp((char*)player + 0xF500, args.Arg(1)))
{
g_pBanSystem->BanUID(strtoull(player->m_UID, nullptr, 10));
R2::CBaseClient__Disconnect(player, 1, "Banned from server");
g_ServerBanSystem->BanUID(strtoull((char*)player + 0xF500, nullptr, 10));
CBaseClient__Disconnect(player, 1, "Banned from server");
break;
}
}
@ -203,20 +202,20 @@ void ConCommand_unban(const CCommand& args)
return;
// assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything
g_pBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10));
g_ServerBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10));
}
void ConCommand_clearbanlist(const CCommand& args)
{
g_pBanSystem->ClearBanlist();
g_ServerBanSystem->ClearBanlist();
}
ON_DLL_LOAD_RELIESON("engine.dll", BanSystem, ConCommand, (CModule module))
void InitialiseBanSystem(HMODULE baseAddress)
{
g_pBanSystem = new ServerBanSystem;
g_pBanSystem->OpenBanlist();
g_ServerBanSystem = new ServerBanSystem;
g_ServerBanSystem->OpenBanlist();
RegisterConCommand("ban", ConCommand_ban, "bans a given player by uid or name", FCVAR_GAMEDLL);
RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_GAMEDLL);
RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_GAMEDLL);
RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_NONE);
RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_NONE);
}

View File

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

View File

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

View File

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

View File

@ -1,11 +1,13 @@
#include "pch.h"
#include "convar.h"
#include "concommand.h"
#include "chatcommand.h"
#include "localchatwriter.h"
// note: isIngameChat is an int64 because the whole register the arg is stored in needs to be 0'd out to work
// if isIngameChat is false, we use network chat instead
void(__fastcall* ClientSayText)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat);
typedef void(__fastcall* ClientSayTextType)(void* a1, const char* message, __int64 isIngameChat, bool isTeamChat);
ClientSayTextType ClientSayText;
void ConCommand_say(const CCommand& args)
{
@ -27,9 +29,9 @@ void ConCommand_log(const CCommand& args)
}
}
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientChatCommand, ConCommand, (CModule module))
void InitialiseChatCommands(HMODULE baseAddress)
{
ClientSayText = module.Offset(0x54780).As<void(__fastcall*)(void* a1, const char* message, uint64_t isIngameChat, bool isTeamChat)>();
ClientSayText = (ClientSayTextType)((char*)baseAddress + 0x54780);
RegisterConCommand("say", ConCommand_say, "Enters a message in public chat", FCVAR_CLIENTDLL);
RegisterConCommand("say_team", ConCommand_say_team, "Enters a message in team chat", FCVAR_CLIENTDLL);
RegisterConCommand("log", ConCommand_log, "Log a message to the local chat window", FCVAR_CLIENTDLL);

View File

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

View File

@ -1,9 +1,12 @@
#include "pch.h"
#include "clientauthhooks.h"
#include "hookutils.h"
#include "gameutils.h"
#include "masterserver.h"
#include "convar.h"
#include "r2client.h"
AUTOHOOK_INIT()
typedef void (*AuthWithStryderType)(void* a1);
AuthWithStryderType AuthWithStryder;
ConVar* Cvar_ns_has_agreed_to_send_token;
@ -12,53 +15,51 @@ const int NOT_DECIDED_TO_SEND_TOKEN = 0;
const int AGREED_TO_SEND_TOKEN = 1;
const int DISAGREED_TO_SEND_TOKEN = 2;
// clang-format off
AUTOHOOK(AuthWithStryder, engine.dll + 0x1843A0,
void, __fastcall, (void* a1))
// clang-format on
typedef char* (*Auth3PTokenType)();
Auth3PTokenType Auth3PToken;
char* token_location = 0x0;
void AuthWithStryderHook(void* a1)
{
// game will call this forever, until it gets a valid auth key
// so, we need to manually invalidate our key until we're authed with northstar, then we'll allow game to auth with stryder
if (!g_pMasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN)
if (!g_MasterServerManager->m_bOriginAuthWithMasterServerDone && Cvar_ns_has_agreed_to_send_token->GetInt() != DISAGREED_TO_SEND_TOKEN)
{
// if player has agreed to send token and we aren't already authing, try to auth
if (Cvar_ns_has_agreed_to_send_token->GetInt() == AGREED_TO_SEND_TOKEN &&
!g_pMasterServerManager->m_bOriginAuthWithMasterServerInProgress)
g_pMasterServerManager->AuthenticateOriginWithMasterServer(R2::g_pLocalPlayerUserID, R2::g_pLocalPlayerOriginToken);
!g_MasterServerManager->m_bOriginAuthWithMasterServerInProgress)
g_MasterServerManager->AuthenticateOriginWithMasterServer(g_LocalPlayerUserID, g_LocalPlayerOriginToken);
// invalidate key so auth will fail
*R2::g_pLocalPlayerOriginToken = 0;
*g_LocalPlayerOriginToken = 0;
}
AuthWithStryder(a1);
}
char* p3PToken;
// clang-format off
AUTOHOOK(Auth3PToken, engine.dll + 0x183760,
char*, __fastcall, ())
// clang-format on
char* Auth3PTokenHook()
{
if (g_pMasterServerManager->m_sOwnClientAuthToken[0])
if (g_MasterServerManager->m_sOwnClientAuthToken[0] != 0)
{
memset(p3PToken, 0x0, 1024);
strcpy(p3PToken, "Protocol 3: Protect the Pilot");
memset(token_location, 0x0, 1024);
strcpy(token_location, "Protocol 3: Protect the Pilot");
}
return Auth3PToken();
}
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientAuthHooks, ConVar, (CModule module))
void InitialiseClientAuthHooks(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
p3PToken = module.Offset(0x13979D80).As<char*>();
// this cvar will save to cfg once initially agreed with
Cvar_ns_has_agreed_to_send_token = new ConVar(
"ns_has_agreed_to_send_token",
"0",
FCVAR_ARCHIVE_PLAYERPROFILE,
"whether the user has agreed to send their origin token to the northstar masterserver");
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1843A0, &AuthWithStryderHook, reinterpret_cast<LPVOID*>(&AuthWithStryder));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x183760, &Auth3PTokenHook, reinterpret_cast<LPVOID*>(&Auth3PToken));
token_location = (char*)baseAddress + 0x13979D80;
}

View File

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

View File

@ -1,22 +1,29 @@
#include "pch.h"
#include "clientchathooks.h"
#include <rapidjson/document.h>
#include "squirrel.h"
#include "serverchathooks.h"
#include "localchatwriter.h"
#include <rapidjson/document.h>
typedef void(__fastcall* CHudChat__AddGameLineType)(void* self, const char* message, int fromPlayerId, bool isteam, bool isdead);
CHudChat__AddGameLineType CHudChat__AddGameLine;
AUTOHOOK_INIT()
struct ChatTags
{
bool whisper;
bool team;
bool dead;
};
// 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
static void CHudChat__AddGameLineHook(void* self, const char* message, int inboxId, bool isTeam, bool isDead)
{
// This hook is called for each HUD, but we only want our logic to run once.
if (self != *CHudChat::allHuds)
{
return;
}
if (g_pSquirrel<ScriptContext::CLIENT>->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR)
if (g_ClientSquirrelManager->setupfunc("CHudChat_ProcessMessageStartThread") != SQRESULT_ERROR)
{
int senderId = inboxId & CUSTOM_MESSAGE_INDEX_MASK;
bool isAnonymous = senderId == 0;
@ -31,53 +38,58 @@ void, __fastcall, (void* self, const char* message, int inboxId, bool isTeam, bo
payload = message + 1;
}
g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, (int)senderId - 1);
g_pSquirrel<ScriptContext::CLIENT>->pushstring(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, payload);
g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isTeam);
g_pSquirrel<ScriptContext::CLIENT>->pushbool(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, isDead);
g_pSquirrel<ScriptContext::CLIENT>->pushinteger(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, type);
g_pSquirrel<ScriptContext::CLIENT>->call(g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm, 5);
g_ClientSquirrelManager->pusharg((int)senderId - 1);
g_ClientSquirrelManager->pusharg(payload);
g_ClientSquirrelManager->pusharg(isTeam);
g_ClientSquirrelManager->pusharg(isDead);
g_ClientSquirrelManager->pusharg(type);
g_ClientSquirrelManager->call(5);
}
else
{
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
{
CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead);
}
}
}
// void NSChatWrite( int context, string str )
SQRESULT SQ_ChatWrite(HSquirrelVM* sqvm)
static SQRESULT SQ_ChatWrite(void* sqvm)
{
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
int context = ClientSq_getinteger(sqvm, 1);
const char* str = ClientSq_getstring(sqvm, 2);
LocalChatWriter((LocalChatWriter::Context)context).Write(str);
return SQRESULT_NULL;
return SQRESULT_NOTNULL;
}
// void NSChatWriteRaw( int context, string str )
SQRESULT SQ_ChatWriteRaw(HSquirrelVM* sqvm)
static SQRESULT SQ_ChatWriteRaw(void* sqvm)
{
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
int context = ClientSq_getinteger(sqvm, 1);
const char* str = ClientSq_getstring(sqvm, 2);
LocalChatWriter((LocalChatWriter::Context)context).InsertText(str);
return SQRESULT_NULL;
return SQRESULT_NOTNULL;
}
// void NSChatWriteLine( int context, string str )
SQRESULT SQ_ChatWriteLine(HSquirrelVM* sqvm)
static SQRESULT SQ_ChatWriteLine(void* sqvm)
{
int context = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
int context = ClientSq_getinteger(sqvm, 1);
const char* str = ClientSq_getstring(sqvm, 2);
LocalChatWriter((LocalChatWriter::Context)context).WriteLine(str);
return SQRESULT_NULL;
return SQRESULT_NOTNULL;
}
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientChatHooks, ClientSquirrel, (CModule module))
void InitialiseClientChatHooks(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x22E580, &CHudChat__AddGameLineHook, reinterpret_cast<LPVOID*>(&CHudChat__AddGameLine));
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw);
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine);
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWrite", "int context, string text", "", SQ_ChatWrite);
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteRaw", "int context, string text", "", SQ_ChatWriteRaw);
g_ClientSquirrelManager->AddFuncRegistration("void", "NSChatWriteLine", "int context, string text", "", SQ_ChatWriteLine);
}

View File

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

View File

@ -1,14 +1,13 @@
#include "pch.h"
#include "clientruihooks.h"
#include "convar.h"
AUTOHOOK_INIT()
ConVar* Cvar_rui_drawEnable;
// clang-format off
AUTOHOOK(DrawRUIFunc, engine.dll + 0xFC500,
bool, __fastcall, (void* a1, float* a2))
// clang-format on
typedef char (*DrawRUIFuncType)(void* a1, float* a2);
DrawRUIFuncType DrawRUIFunc;
char DrawRUIFuncHook(void* a1, float* a2)
{
if (!Cvar_rui_drawEnable->GetBool())
return 0;
@ -16,9 +15,10 @@ bool, __fastcall, (void* a1, float* a2))
return DrawRUIFunc(a1, a2);
}
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", RUI, ConVar, (CModule module))
void InitialiseEngineClientRUIHooks(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn");
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xFC500, &DrawRUIFuncHook, reinterpret_cast<LPVOID*>(&DrawRUIFunc));
}

View File

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

View File

@ -1,21 +1,21 @@
#include "pch.h"
#include "clientvideooverrides.h"
#include "modmanager.h"
#include "nsmem.h"
AUTOHOOK_INIT()
typedef void* (*BinkOpenType)(const char* path, uint32_t flags);
BinkOpenType BinkOpen;
// clang-format off
AUTOHOOK_PROCADDRESS(BinkOpen, bink2w64.dll, BinkOpen,
void*, __fastcall, (const char* path, uint32_t flags))
// clang-format on
void* BinkOpenHook(const char* path, uint32_t flags)
{
std::string filename(fs::path(path).filename().string());
spdlog::info("BinkOpen {}", filename);
// figure out which mod is handling the bink
Mod* fileOwner = nullptr;
for (Mod& mod : g_pModManager->m_LoadedMods)
for (Mod& mod : g_ModManager->m_loadedMods)
{
if (!mod.m_bEnabled)
if (!mod.Enabled)
continue;
if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end())
@ -25,18 +25,23 @@ void*, __fastcall, (const char* path, uint32_t flags))
if (fileOwner)
{
// create new path
fs::path binkPath(fileOwner->m_ModDirectory / "media" / filename);
fs::path binkPath(fileOwner->ModDirectory / "media" / filename);
return BinkOpen(binkPath.string().c_str(), flags);
}
else
return BinkOpen(path, flags);
}
ON_DLL_LOAD_CLIENT("client.dll", BinkVideo, (CModule module))
void InitialiseEngineClientVideoOverrides(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
// remove engine check for whether the bik we're trying to load exists in r2/media, as this will fail for biks in mods
// note: the check in engine is actually unnecessary, so it's just useless in practice and we lose nothing by removing it
module.Offset(0x459AD).NOP(6);
NSMem::NOP((uintptr_t)baseAddress + 0x459AD, 6);
HookEnabler hook;
ENABLER_CREATEHOOK(
hook,
reinterpret_cast<void*>(GetProcAddress(GetModuleHandleA("bink2w64.dll"), "BinkOpen")),
&BinkOpenHook,
reinterpret_cast<LPVOID*>(&BinkOpen));
}

View File

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

View File

@ -1,9 +1,28 @@
#include "pch.h"
#include "concommand.h"
#include "gameutils.h"
#include "misccommands.h"
#include <iostream>
typedef void (*ConCommandConstructorType)(
ConCommand* newCommand, const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, void* parent);
ConCommandConstructorType conCommandConstructor;
void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags)
{
spdlog::info("Registering ConCommand {}", name);
// no need to free this ever really, it should exist as long as game does
ConCommand* newCommand = new ConCommand;
conCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
}
void InitialiseConCommands(HMODULE baseAddress)
{
conCommandConstructor = (ConCommandConstructorType)((char*)baseAddress + 0x415F60);
AddMiscConCommands();
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if this is a command
// Output : bool
@ -38,7 +57,7 @@ bool ConCommandBase::IsRegistered(void) const
//-----------------------------------------------------------------------------
bool ConCommandBase::IsFlagSet(int nFlags) const
{
return m_nFlags & nFlags;
return false; // !TODO: Returning false on every query? (original implementation in Northstar before ConCommandBase refactor)
}
//-----------------------------------------------------------------------------
@ -119,33 +138,3 @@ char* ConCommandBase::CopyString(const char* szFrom) const
}
return szTo;
}
typedef void (*ConCommandConstructorType)(
ConCommand* newCommand, const char* name, FnCommandCallback_t callback, const char* helpString, int flags, void* parent);
ConCommandConstructorType ConCommandConstructor;
void RegisterConCommand(const char* name, FnCommandCallback_t callback, const char* helpString, int flags)
{
spdlog::info("Registering ConCommand {}", name);
// no need to free this ever really, it should exist as long as game does
ConCommand* newCommand = new ConCommand;
ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
}
void RegisterConCommand(
const char* name, FnCommandCallback_t callback, const char* helpString, int flags, FnCommandCompletionCallback completionCallback)
{
spdlog::info("Registering ConCommand {}", name);
// no need to free this ever really, it should exist as long as game does
ConCommand* newCommand = new ConCommand;
ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr);
newCommand->m_pCompletionCallback = completionCallback;
}
ON_DLL_LOAD("engine.dll", ConCommand, (CModule module))
{
ConCommandConstructor = module.Offset(0x415F60).As<ConCommandConstructorType>();
AddMiscConCommands();
}

View File

@ -72,20 +72,6 @@ inline const char* CCommand::operator[](int nIndex) const
return Arg(nIndex);
}
//-----------------------------------------------------------------------------
// Called when a ConCommand needs to execute
//-----------------------------------------------------------------------------
typedef void (*FnCommandCallback_t)(const CCommand& command);
#define COMMAND_COMPLETION_MAXITEMS 64
#define COMMAND_COMPLETION_ITEM_LENGTH 128
//-----------------------------------------------------------------------------
// Returns 0 to COMMAND_COMPLETION_MAXITEMS worth of completion strings
//-----------------------------------------------------------------------------
typedef int (*__fastcall FnCommandCompletionCallback)(
const char* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]);
// From r5reloaded
class ConCommandBase
{
@ -127,8 +113,8 @@ class ConCommand : public ConCommandBase
void Init(void);
bool IsCommand(void) const;
FnCommandCallback_t m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase.
FnCommandCompletionCallback m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax').
void* m_pCommandCallback {}; // 0x0040 <- starts from 0x40 since we inherit ConCommandBase.
void* m_pCompletionCallback {}; // 0x0048 <- defaults to sub_180417410 ('xor eax, eax').
int m_nCallbackFlags {}; // 0x0050
char pad_0054[4]; // 0x0054
int unk0; // 0x0058
@ -136,5 +122,6 @@ class ConCommand : public ConCommandBase
}; // 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, FnCommandCompletionCallback completionCallback);
void InitialiseConCommands(HMODULE baseAddress);
#define MAKE_CONCMD(name, helpStr, flags, fn) RegisterConCommand(name, fn, helpStr, flags);

14
NorthstarDLL/context.cpp Normal file
View File

@ -0,0 +1,14 @@
#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 "";
}

11
NorthstarDLL/context.h Normal file
View File

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

View File

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

View File

@ -58,58 +58,15 @@
// ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd.
#define FCVAR_SERVER_CANNOT_QUERY \
(1 << 29) // If this is set, then the server is not allowed to query this cvar's value (via IServerPluginHelpers::StartQueryCvarValue).
// !!!NOTE!!! : this is likely incorrect, there are multiple concommands that the vanilla game registers with this flag that 100% should not
// be remotely executable i.e. multiple commands that only exist on client (screenshot, joystick_initialize) we now use
// FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS in all places this flag was previously used
#define FCVAR_CLIENTCMD_CAN_EXECUTE \
(1 << 30) // IVEngineClient::ClientCmd is allowed to execute this command.
// Note: IVEngineClient::ClientCmd_Unrestricted can run any client command.
#define FCVAR_ACCESSIBLE_FROM_THREADS (1 << 25) // used as a debugging tool necessary to check material system thread convars
// TODO: could be cool to repurpose these for northstar use somehow?
// #define FCVAR_AVAILABLE (1<<26)
// #define FCVAR_AVAILABLE (1<<27)
// #define FCVAR_AVAILABLE (1<<31)
// flag => string stuff
const std::multimap<int, const char*> g_PrintCommandFlags = {
{FCVAR_UNREGISTERED, "UNREGISTERED"},
{FCVAR_DEVELOPMENTONLY, "DEVELOPMENTONLY"},
{FCVAR_GAMEDLL, "GAMEDLL"},
{FCVAR_CLIENTDLL, "CLIENTDLL"},
{FCVAR_HIDDEN, "HIDDEN"},
{FCVAR_PROTECTED, "PROTECTED"},
{FCVAR_SPONLY, "SPONLY"},
{FCVAR_ARCHIVE, "ARCHIVE"},
{FCVAR_NOTIFY, "NOTIFY"},
{FCVAR_USERINFO, "USERINFO"},
// TODO: PRINTABLEONLY and GAMEDLL_FOR_REMOTE_CLIENTS are both 1<<10, one is for vars and one is for commands
// this fucking sucks i think
{FCVAR_PRINTABLEONLY, "PRINTABLEONLY"},
{FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, "GAMEDLL_FOR_REMOTE_CLIENTS"},
{FCVAR_UNLOGGED, "UNLOGGED"},
{FCVAR_NEVER_AS_STRING, "NEVER_AS_STRING"},
{FCVAR_REPLICATED, "REPLICATED"},
{FCVAR_CHEAT, "CHEAT"},
{FCVAR_SS, "SS"},
{FCVAR_DEMO, "DEMO"},
{FCVAR_DONTRECORD, "DONTRECORD"},
{FCVAR_SS_ADDED, "SS_ADDED"},
{FCVAR_RELEASE, "RELEASE"},
{FCVAR_RELOAD_MATERIALS, "RELOAD_MATERIALS"},
{FCVAR_RELOAD_TEXTURES, "RELOAD_TEXTURES"},
{FCVAR_NOT_CONNECTED, "NOT_CONNECTED"},
{FCVAR_MATERIAL_SYSTEM_THREAD, "MATERIAL_SYSTEM_THREAD"},
{FCVAR_ARCHIVE_PLAYERPROFILE, "ARCHIVE_PLAYERPROFILE"},
{FCVAR_SERVER_CAN_EXECUTE, "SERVER_CAN_EXECUTE"},
{FCVAR_SERVER_CANNOT_QUERY, "SERVER_CANNOT_QUERY"},
{FCVAR_CLIENTCMD_CAN_EXECUTE, "UNKNOWN"},
{FCVAR_ACCESSIBLE_FROM_THREADS, "ACCESSIBLE_FROM_THREADS"}};
//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
@ -117,8 +74,6 @@ class ConCommandBase;
class ConCommand;
class ConVar;
typedef void (*FnChangeCallback_t)(ConVar* var, const char* pOldValue, float flOldValue);
//-----------------------------------------------------------------------------
// Purpose: A console variable
//-----------------------------------------------------------------------------
@ -136,7 +91,7 @@ class ConVar
float fMin,
bool bMax,
float fMax,
FnChangeCallback_t pCallback);
void* pCallback);
~ConVar(void);
const char* GetBaseName(void) const;
@ -170,7 +125,7 @@ class ConVar
bool IsRegistered(void) const;
bool IsCommand(void) const;
bool IsFlagSet(int nFlags) const;
static bool IsFlagSet(ConVar* pConVar, int nFlags);
struct CVValue_t
{
@ -190,3 +145,5 @@ class ConVar
void* m_pMalloc {}; // 0x0070
char m_pPad80[10] {}; // 0x0080
}; // Size: 0x0080
void InitialiseConVars(HMODULE baseAddress);

View File

@ -1,216 +0,0 @@
#include "pch.h"
#include "crashhandler.h"
#include "dedicated.h"
#include "nsprefix.h"
#include <minidumpapiset.h>
HANDLE hExceptionFilter;
long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo)
{
static bool logged = false;
if (logged)
return EXCEPTION_CONTINUE_SEARCH;
if (!IsDebuggerPresent())
{
const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode;
if (exceptionCode != EXCEPTION_ACCESS_VIOLATION && exceptionCode != EXCEPTION_ARRAY_BOUNDS_EXCEEDED &&
exceptionCode != EXCEPTION_DATATYPE_MISALIGNMENT && exceptionCode != EXCEPTION_FLT_DENORMAL_OPERAND &&
exceptionCode != EXCEPTION_FLT_DIVIDE_BY_ZERO && exceptionCode != EXCEPTION_FLT_INEXACT_RESULT &&
exceptionCode != EXCEPTION_FLT_INVALID_OPERATION && exceptionCode != EXCEPTION_FLT_OVERFLOW &&
exceptionCode != EXCEPTION_FLT_STACK_CHECK && exceptionCode != EXCEPTION_FLT_UNDERFLOW &&
exceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION && exceptionCode != EXCEPTION_IN_PAGE_ERROR &&
exceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO && exceptionCode != EXCEPTION_INT_OVERFLOW &&
exceptionCode != EXCEPTION_INVALID_DISPOSITION && exceptionCode != EXCEPTION_NONCONTINUABLE_EXCEPTION &&
exceptionCode != EXCEPTION_PRIV_INSTRUCTION && exceptionCode != EXCEPTION_STACK_OVERFLOW)
return EXCEPTION_CONTINUE_SEARCH;
std::stringstream exceptionCause;
exceptionCause << "Cause: ";
switch (exceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_IN_PAGE_ERROR:
{
exceptionCause << "Access Violation" << std::endl;
auto exceptionInfo0 = exceptionInfo->ExceptionRecord->ExceptionInformation[0];
auto exceptionInfo1 = exceptionInfo->ExceptionRecord->ExceptionInformation[1];
if (!exceptionInfo0)
exceptionCause << "Attempted to read from: 0x" << (void*)exceptionInfo1;
else if (exceptionInfo0 == 1)
exceptionCause << "Attempted to write to: 0x" << (void*)exceptionInfo1;
else if (exceptionInfo0 == 8)
exceptionCause << "Data Execution Prevention (DEP) at: 0x" << (void*)std::hex << exceptionInfo1;
else
exceptionCause << "Unknown access violation at: 0x" << (void*)exceptionInfo1;
break;
}
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
exceptionCause << "Array bounds exceeded";
break;
case EXCEPTION_DATATYPE_MISALIGNMENT:
exceptionCause << "Datatype misalignment";
break;
case EXCEPTION_FLT_DENORMAL_OPERAND:
exceptionCause << "Denormal operand";
break;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
exceptionCause << "Divide by zero (float)";
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
exceptionCause << "Divide by zero (int)";
break;
case EXCEPTION_FLT_INEXACT_RESULT:
exceptionCause << "Inexact result";
break;
case EXCEPTION_FLT_INVALID_OPERATION:
exceptionCause << "Invalid operation";
break;
case EXCEPTION_FLT_OVERFLOW:
case EXCEPTION_INT_OVERFLOW:
exceptionCause << "Numeric overflow";
break;
case EXCEPTION_FLT_UNDERFLOW:
exceptionCause << "Numeric underflow";
break;
case EXCEPTION_FLT_STACK_CHECK:
exceptionCause << "Stack check";
break;
case EXCEPTION_ILLEGAL_INSTRUCTION:
exceptionCause << "Illegal instruction";
break;
case EXCEPTION_INVALID_DISPOSITION:
exceptionCause << "Invalid disposition";
break;
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
exceptionCause << "Noncontinuable exception";
break;
case EXCEPTION_PRIV_INSTRUCTION:
exceptionCause << "Priviledged instruction";
break;
case EXCEPTION_STACK_OVERFLOW:
exceptionCause << "Stack overflow";
break;
default:
exceptionCause << "Unknown";
break;
}
void* exceptionAddress = exceptionInfo->ExceptionRecord->ExceptionAddress;
HMODULE crashedModuleHandle;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<LPCSTR>(exceptionAddress), &crashedModuleHandle);
MODULEINFO crashedModuleInfo;
GetModuleInformation(GetCurrentProcess(), crashedModuleHandle, &crashedModuleInfo, sizeof(crashedModuleInfo));
char crashedModuleFullName[MAX_PATH];
GetModuleFileNameExA(GetCurrentProcess(), crashedModuleHandle, crashedModuleFullName, MAX_PATH);
char* crashedModuleName = strrchr(crashedModuleFullName, '\\') + 1;
DWORD64 crashedModuleOffset = ((DWORD64)exceptionAddress) - ((DWORD64)crashedModuleInfo.lpBaseOfDll);
CONTEXT* exceptionContext = exceptionInfo->ContextRecord;
spdlog::error("Northstar has crashed! a minidump has been written and exception info is available below:");
spdlog::error(exceptionCause.str());
spdlog::error("At: {} + {}", crashedModuleName, (void*)crashedModuleOffset);
PVOID framesToCapture[62];
int frames = RtlCaptureStackBackTrace(0, 62, framesToCapture, NULL);
for (int i = 0; i < frames; i++)
{
HMODULE backtraceModuleHandle;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<LPCSTR>(framesToCapture[i]), &backtraceModuleHandle);
char backtraceModuleFullName[MAX_PATH];
GetModuleFileNameExA(GetCurrentProcess(), backtraceModuleHandle, backtraceModuleFullName, MAX_PATH);
char* backtraceModuleName = strrchr(backtraceModuleFullName, '\\') + 1;
void* actualAddress = (void*)framesToCapture[i];
void* relativeAddress = (void*)(uintptr_t(actualAddress) - uintptr_t(backtraceModuleHandle));
spdlog::error(" {} + {} ({})", backtraceModuleName, relativeAddress, actualAddress);
}
spdlog::error("RAX: 0x{0:x}", exceptionContext->Rax);
spdlog::error("RBX: 0x{0:x}", exceptionContext->Rbx);
spdlog::error("RCX: 0x{0:x}", exceptionContext->Rcx);
spdlog::error("RDX: 0x{0:x}", exceptionContext->Rdx);
spdlog::error("RSI: 0x{0:x}", exceptionContext->Rsi);
spdlog::error("RDI: 0x{0:x}", exceptionContext->Rdi);
spdlog::error("RBP: 0x{0:x}", exceptionContext->Rbp);
spdlog::error("RSP: 0x{0:x}", exceptionContext->Rsp);
spdlog::error("R8: 0x{0:x}", exceptionContext->R8);
spdlog::error("R9: 0x{0:x}", exceptionContext->R9);
spdlog::error("R10: 0x{0:x}", exceptionContext->R10);
spdlog::error("R11: 0x{0:x}", exceptionContext->R11);
spdlog::error("R12: 0x{0:x}", exceptionContext->R12);
spdlog::error("R13: 0x{0:x}", exceptionContext->R13);
spdlog::error("R14: 0x{0:x}", exceptionContext->R14);
spdlog::error("R15: 0x{0:x}", exceptionContext->R15);
time_t time = std::time(nullptr);
tm currentTime = *std::localtime(&time);
std::stringstream stream;
stream << std::put_time(&currentTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str());
auto hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hMinidumpFile)
{
MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo;
dumpExceptionInfo.ThreadId = GetCurrentThreadId();
dumpExceptionInfo.ExceptionPointers = exceptionInfo;
dumpExceptionInfo.ClientPointers = false;
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hMinidumpFile,
MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory),
&dumpExceptionInfo,
nullptr,
nullptr);
CloseHandle(hMinidumpFile);
}
else
spdlog::error("Failed to write minidump file {}!", stream.str());
if (!IsDedicatedServer())
MessageBoxA(
0, "Northstar has crashed! Crash info can be found in R2Northstar/logs", "Northstar has crashed!", MB_ICONERROR | MB_OK);
}
logged = true;
return EXCEPTION_EXECUTE_HANDLER;
}
BOOL WINAPI ConsoleHandlerRoutine(DWORD eventCode)
{
switch (eventCode)
{
case CTRL_CLOSE_EVENT:
// User closed console, shut everything down
spdlog::info("Exiting due to console close...");
RemoveCrashHandler();
exit(EXIT_SUCCESS);
return FALSE;
}
return TRUE;
}
void InitialiseCrashHandler()
{
hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter);
SetConsoleCtrlHandler(ConsoleHandlerRoutine, true);
}
void RemoveCrashHandler()
{
RemoveVectoredExceptionHandler(hExceptionFilter);
}

View File

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

View File

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

View File

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

View File

@ -1,9 +1,17 @@
#include "pch.h"
#include "debugoverlay.h"
#include "dedicated.h"
#include "cvar.h"
#include "vector.h"
AUTOHOOK_INIT()
struct Vector3
{
float x, y, z;
};
struct QAngle
{
float x, y, z, w;
};
enum OverlayType_t
{
@ -32,7 +40,7 @@ struct OverlayBase_t
int m_nServerCount; // Latch server count, too
float m_flEndTime; // When does this box go away
OverlayBase_t* m_pNextOverlay;
void* m_pUnk;
__int64 m_pUnk;
};
struct OverlayLine_t : public OverlayBase_t
@ -68,18 +76,37 @@ struct OverlayBox_t : public OverlayBase_t
int a;
};
// this is in cvar.h, don't need it here
/*class Color
{
public:
Color(int r, int g, int b, int a)
{
_color[0] = (unsigned char)r;
_color[1] = (unsigned char)g;
_color[2] = (unsigned char)b;
_color[3] = (unsigned char)a;
}
private:
unsigned char _color[4];
};*/
static HMODULE sEngineModule;
typedef void (*DrawOverlayType)(OverlayBase_t* a1);
DrawOverlayType DrawOverlay;
typedef void (*RenderLineType)(Vector3 v1, Vector3 v2, Color c, bool bZBuffer);
static RenderLineType RenderLine;
typedef void (*RenderBoxType)(Vector3 vOrigin, QAngle angles, Vector3 vMins, Vector3 vMaxs, Color c, bool bZBuffer, bool bInsideOut);
static RenderBoxType RenderBox;
static RenderBoxType RenderWireframeBox;
// clang-format off
AUTOHOOK(DrawOverlay, engine.dll + 0xABCB0,
void, __fastcall, (OverlayBase_t * pOverlay))
// clang-format on
// engine.dll+0xABCB0
void __fastcall DrawOverlayHook(OverlayBase_t* pOverlay)
{
EnterCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38)); // s_OverlayMutex
@ -124,17 +151,24 @@ void, __fastcall, (OverlayBase_t * pOverlay))
LeaveCriticalSection((LPCRITICAL_SECTION)((char*)sEngineModule + 0x10DB0A38));
}
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", DebugOverlay, ConVar, (CModule module))
void InitialiseDebugOverlay(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
if (IsDedicatedServer())
return;
RenderLine = module.Offset(0x192A70).As<RenderLineType>();
RenderBox = module.Offset(0x192520).As<RenderBoxType>();
RenderWireframeBox = module.Offset(0x193DA0).As<RenderBoxType>();
sEngineModule = reinterpret_cast<HMODULE>(module.m_nAddress);
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xABCB0, &DrawOverlayHook, reinterpret_cast<LPVOID*>(&DrawOverlay));
RenderLine = reinterpret_cast<RenderLineType>((char*)baseAddress + 0x192A70);
RenderBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x192520);
RenderWireframeBox = reinterpret_cast<RenderBoxType>((char*)baseAddress + 0x193DA0);
sEngineModule = baseAddress;
// not in g_pCVar->FindVar by this point for whatever reason, so have to get from memory
ConVar* Cvar_enable_debug_overlays = module.Offset(0x10DB0990).As<ConVar*>();
ConVar* Cvar_enable_debug_overlays = (ConVar*)((char*)baseAddress + 0x10DB0990);
Cvar_enable_debug_overlays->SetValue(false);
Cvar_enable_debug_overlays->m_pszDefaultValue = (char*)"0";
Cvar_enable_debug_overlays->AddFlags(FCVAR_CHEAT);

View File

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

View File

@ -1,16 +1,9 @@
#include "pch.h"
#include "dedicated.h"
#include "tier0.h"
#include "playlist.h"
#include "r2engine.h"
#include "hoststate.h"
#include "hookutils.h"
#include "gameutils.h"
#include "serverauthentication.h"
#include "masterserver.h"
#include "printcommand.h"
AUTOHOOK_INIT()
using namespace R2;
bool IsDedicatedServer()
{
@ -40,23 +33,32 @@ void Sys_Printf(CDedicatedExports* dedicated, const char* msg)
spdlog::info("[DEDICATED SERVER] {}", msg);
}
typedef void (*CHostState__InitType)(CHostState* self);
void RunServer(CDedicatedExports* dedicated)
{
spdlog::info("CDedicatedExports::RunServer(): starting");
spdlog::info(Tier0::CommandLine()->GetCmdLine());
spdlog::info(CommandLine()->GetCmdLine());
// initialise engine
g_pEngine->Frame();
// add +map if not present
// don't manually execute this from cbuf as users may have it in their startup args anyway, easier just to run from stuffcmds if present
if (!Tier0::CommandLine()->CheckParm("+map"))
Tier0::CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString());
if (!CommandLine()->CheckParm("+map"))
CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString());
// re-run commandline
// run server autoexec and 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_Execute();
// ensure playlist initialises right, if we've not explicitly called setplaylist
SetCurrentPlaylist(GetCurrentPlaylistName());
// note: we no longer manually set map and hoststate to start server in g_pHostState, we just use +map which seems to initialise stuff
// better
// get tickinterval
ConVar* Cvar_base_tickinterval_mp = g_pCVar->FindVar("base_tickinterval_mp");
@ -64,31 +66,43 @@ void RunServer(CDedicatedExports* dedicated)
double frameTitle = 0;
while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING)
{
double frameStart = Tier0::Plat_FloatTime();
double frameStart = Plat_FloatTime();
g_pEngine->Frame();
// only update the title after at least 500ms since the last update
if ((frameStart - frameTitle) > 0.5)
{
frameTitle = frameStart;
// this way of getting playercount/maxplayers honestly really sucks, but not got any other methods of doing it rn
const char* maxPlayers = GetCurrentPlaylistVar("max_players", false);
if (!maxPlayers)
maxPlayers = "6";
SetConsoleTitleA(fmt::format(
"{} - {} {}/{} players ({})",
g_MasterServerManager->m_sUnicodeServerName,
g_pHostState->m_levelName,
g_ServerAuthenticationManager->m_additionalPlayerData.size(),
maxPlayers,
GetCurrentPlaylistName())
.c_str());
}
std::this_thread::sleep_for(std::chrono::duration<double, std::ratio<1>>(
Cvar_base_tickinterval_mp->GetFloat() - fmin(Tier0::Plat_FloatTime() - frameStart, 0.25)));
Cvar_base_tickinterval_mp->GetFloat() - fmin(Plat_FloatTime() - frameStart, 0.25)));
}
}
// use server presence to update window title
class DedicatedConsoleServerPresence : public ServerPresenceReporter
typedef bool (*IsGameActiveWindowType)();
IsGameActiveWindowType IsGameActiveWindow;
bool IsGameActiveWindowHook()
{
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());
}
};
return true;
}
HANDLE consoleInputThreadHandle = NULL;
DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
{
while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN)
@ -107,106 +121,139 @@ DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
{
input += "\n";
Cbuf_AddText(Cbuf_GetCurrentPlayer(), input.c_str(), cmd_source_t::kCommandSrcCode);
TryPrintCvarHelpForCommand(input.c_str()); // this needs to be done on main thread, unstable in this one
}
}
return 0;
}
// clang-format off
AUTOHOOK(IsGameActiveWindow, engine.dll + 0x1CDC80,
bool,, ())
// clang-format on
{
return true;
}
ON_DLL_LOAD_DEDI_RELIESON("engine.dll", DedicatedServer, ServerPresence, (CModule module))
#include "nsmem.h"
void InitialiseDedicated(HMODULE engineAddress)
{
spdlog::info("InitialiseDedicated");
AUTOHOOK_DISPATCH_MODULE("engine.dll")
uintptr_t ea = (uintptr_t)engineAddress;
// Host_Init
// prevent a particle init that relies on client dll
module.Offset(0x156799).NOP(5);
// Host_Init
// don't call Key_Init to avoid loading some extra rsons from rpak (will be necessary to boot if we ever wanna disable rpaks entirely on
// dedi)
module.Offset(0x1565B0).NOP(5);
{
// Host_Init
// prevent a particle init that relies on client dll
NSMem::NOP(ea + 0x156799, 5);
}
{
// CModAppSystemGroup::Create
// force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment
MemoryAddress base = module.Offset(0x1C4EBD);
auto ptr = ea + 0x1C4EBD;
// cmp => mov
base.Offset(1).Patch("C6 87");
NSMem::BytePatch(ptr + 1, "C6 87");
// 00 => 01
base.Offset(7).Patch("01");
NSMem::BytePatch(ptr + 7, "01");
}
// Some init that i'm not sure of that crashes
// nop the call to it
module.Offset(0x156A63).NOP(5);
{
// Some init that i'm not sure of that crashes
// nop the call to it
NSMem::NOP(ea + 0x156A63, 5);
}
// runframeserver
// nop some access violations
module.Offset(0x159819).NOP(17);
{
// runframeserver
// nop some access violations
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
// removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address
// so uhh, don't do that
// NSMem::NOP(ea + 0x156B4C + 7, 8);
module.Offset(0x156B4C).Offset(15).NOP(9);
// previously patched these, took me a couple weeks to figure out they were the issue
// removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address
// so uhh, don't do that
// NSMem::NOP(ea + 0x156B4C + 7, 8);
// HostState_State_NewGame
// nop an access violation
module.Offset(0xB934C).NOP(9);
NSMem::NOP(ea + 0x156B4C + 15, 9);
}
// CEngineAPI::Connect
// remove call to Shader_Connect
module.Offset(0x1C4D7D).NOP(5);
{
// HostState_State_NewGame
// nop an access violation
NSMem::NOP(ea + 0xB934C, 9);
}
// Host_Init
// remove call to ui loading stuff
module.Offset(0x156595).NOP(5);
{
// CEngineAPI::Connect
// remove call to Shader_Connect
NSMem::NOP(ea + 0x1C4D7D, 5);
}
// some function that gets called from RunFrameServer
// nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway
module.Offset(0x15A0BB).NOP(5);
// currently does not work, crashes stuff, likely gotta keep this here
//{
// // CEngineAPI::Connect
// // remove calls to register ui rpak asset types
// NSMem::NOP(ea + 0x1C4E07, 5);
//}
// RunFrameServer
// nop a function that access violations
module.Offset(0x159BF3).NOP(5);
{
// Host_Init
// remove call to ui loading stuff
NSMem::NOP(ea + 0x156595, 5);
}
// func that checks if origin is inited
// always return 1
module.Offset(0x183B70).Patch("B0 01 C3"); // mov al,01 ret
{
// some function that gets called from RunFrameServer
// nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway
NSMem::NOP(ea + 0x15A0BB, 5);
}
// HostState_State_ChangeLevel
// nop clientinterface call
module.Offset(0x1552ED).NOP(16);
{
// RunFrameServer
// nop a function that access violations
NSMem::NOP(ea + 0x159BF3, 5);
}
// HostState_State_ChangeLevel
// nop clientinterface call
module.Offset(0x155363).NOP(16);
{
// func that checks if origin is inited
// always return 1
NSMem::BytePatch(
ea + 0x183B70,
{
0xB0,
0x01, // mov al,01
0xC3 // ret
});
}
// IVideoMode::CreateGameWindow
// nop call to ShowWindow
module.Offset(0x1CD146).NOP(5);
{
// HostState_State_ChangeLevel
// nop clientinterface call
NSMem::NOP(ea + 0x1552ED, 16);
}
{
// HostState_State_ChangeLevel
// nop clientinterface call
NSMem::NOP(ea + 0x155363, 16);
}
// note: previously had DisableDedicatedWindowCreation patches here, but removing those rn since they're all shit and unstable and bad
// and such check commit history if any are needed for reimplementation
{
// IVideoMode::CreateGameWindow
// nop call to ShowWindow
NSMem::NOP(ea + 0x1CD146, 5);
}
CDedicatedExports* dedicatedExports = new CDedicatedExports;
dedicatedExports->vtable = dedicatedExports;
dedicatedExports->Sys_Printf = Sys_Printf;
dedicatedExports->RunServer = RunServer;
*module.Offset(0x13F0B668).As<CDedicatedExports**>() = dedicatedExports;
CDedicatedExports** exports = (CDedicatedExports**)((char*)engineAddress + 0x13F0B668);
*exports = dedicatedExports;
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)engineAddress + 0x1CDC80, &IsGameActiveWindowHook, reinterpret_cast<LPVOID*>(&IsGameActiveWindow));
// extra potential patches:
// nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow
@ -218,19 +265,15 @@ ON_DLL_LOAD_DEDI_RELIESON("engine.dll", DedicatedServer, ServerPresence, (CModul
// make sure it still gets registered
// add cmdline args that are good for dedi
Tier0::CommandLine()->AppendParm("-nomenuvid", 0);
Tier0::CommandLine()->AppendParm("-nosound", 0);
Tier0::CommandLine()->AppendParm("-windowed", 0);
Tier0::CommandLine()->AppendParm("-nomessagebox", 0);
Tier0::CommandLine()->AppendParm("+host_preload_shaders", "0");
Tier0::CommandLine()->AppendParm("+net_usesocketsforloopback", "1");
// use presence reporter for console title
DedicatedConsoleServerPresence* presenceReporter = new DedicatedConsoleServerPresence;
g_pServerPresence->AddPresenceReporter(presenceReporter);
CommandLine()->AppendParm("-nomenuvid", 0);
CommandLine()->AppendParm("-nosound", 0);
CommandLine()->AppendParm("-windowed", 0);
CommandLine()->AppendParm("-nomessagebox", 0);
CommandLine()->AppendParm("+host_preload_shaders", "0");
CommandLine()->AppendParm("+net_usesocketsforloopback", "1");
// Disable Quick Edit mode to reduce chance of user unintentionally hanging their server by selecting something.
if (!Tier0::CommandLine()->CheckParm("-bringbackquickedit"))
if (!CommandLine()->CheckParm("-bringbackquickedit"))
{
HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode = 0;
@ -252,42 +295,35 @@ ON_DLL_LOAD_DEDI_RELIESON("engine.dll", DedicatedServer, ServerPresence, (CModul
spdlog::info("Quick Edit enabled by user request");
// create console input thread
if (!Tier0::CommandLine()->CheckParm("-noconsoleinput"))
if (!CommandLine()->CheckParm("-noconsoleinput"))
consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL);
else
spdlog::info("Console input disabled by user request");
}
ON_DLL_LOAD_DEDI("tier0.dll", DedicatedServerOrigin, (CModule module))
void InitialiseDedicatedOrigin(HMODULE baseAddress)
{
// disable origin on dedicated
// for any big ea lawyers, this can't be used to play the game without origin, game will throw a fit if you try to do anything without
// an origin id as a client for dedi it's fine though, game doesn't care if origin is disabled as long as there's only a server
module.GetExport("Tier0_InitOrigin").Patch("C3");
NSMem::BytePatch(
(uintptr_t)GetProcAddress(GetModuleHandleA("tier0.dll"), "Tier0_InitOrigin"),
{
0xC3 // ret
});
}
// clang-format off
AUTOHOOK(PrintSquirrelError, server.dll + 0x794D0,
void, __fastcall, (void* sqvm))
// clang-format on
typedef void (*PrintFatalSquirrelErrorType)(void* sqvm);
PrintFatalSquirrelErrorType PrintFatalSquirrelError;
void PrintFatalSquirrelErrorHook(void* sqvm)
{
PrintSquirrelError(sqvm);
// close dedicated server if a fatal error is hit
static ConVar* Cvar_fatal_script_errors = g_pCVar->FindVar("fatal_script_errors");
if (Cvar_fatal_script_errors->GetBool())
abort();
PrintFatalSquirrelError(sqvm);
g_pEngine->m_nQuitting = EngineQuitState::QUIT_TODESKTOP;
}
ON_DLL_LOAD_DEDI("server.dll", DedicatedServerGameDLL, (CModule module))
void InitialiseDedicatedServerGameDLL(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH_MODULE(server.dll)
if (Tier0::CommandLine()->CheckParm("-nopakdedi"))
{
module.Offset(0x6BA350).Patch("C3"); // dont load skins.rson from rpak if we don't have rpaks, as loading it will cause a crash
module.Offset(0x6BA300).Patch(
"B8 C8 00 00 00 C3"); // return 200 as the number of skins from server.dll + 6BA300, this is the normal value read from
// skins.rson and should be updated when we need it more modular
}
HookEnabler hook;
ENABLER_CREATEHOOK(hook, baseAddress + 0x794D0, &PrintFatalSquirrelErrorHook, reinterpret_cast<LPVOID*>(&PrintFatalSquirrelError));
}

View File

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

View File

@ -1,12 +1,11 @@
#include "pch.h"
#include "dedicated.h"
#include "tier0.h"
#include "dedicatedmaterialsystem.h"
#include "hookutils.h"
#include "gameutils.h"
#include "nsmem.h"
AUTOHOOK_INIT()
// clang-format off
AUTOHOOK(D3D11CreateDevice, materialsystem_dx11.dll + 0xD9A0E,
HRESULT, __stdcall, (
typedef HRESULT (*__stdcall D3D11CreateDeviceType)(
void* pAdapter,
int DriverType,
HMODULE Software,
@ -16,26 +15,108 @@ HRESULT, __stdcall, (
UINT SDKVersion,
void** ppDevice,
int* pFeatureLevel,
void** ppImmediateContext))
// clang-format on
void** ppImmediateContext);
D3D11CreateDeviceType D3D11CreateDevice;
HRESULT __stdcall D3D11CreateDeviceHook(
void* pAdapter,
int DriverType,
HMODULE Software,
UINT Flags,
int* pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
void** ppDevice,
int* pFeatureLevel,
void** ppImmediateContext)
{
// note: this is super duper temp pretty much just messing around with it
// does run surprisingly well on dedi for a software driver tho if you ignore the +1gb ram usage at times, seems like dedi doesn't
// really call gpu much even with renderthread still being a thing will be using this hook for actual d3d stubbing and stuff later
// atm, i think the play might be to run d3d in software, and then just stub out any calls that allocate memory/use alot of resources
// (e.g. createtexture and that sorta thing)
// note: this has been succeeded by the d3d11 and gfsdk stubs, and is only being kept around for posterity and as a fallback option
if (Tier0::CommandLine()->CheckParm("-softwared3d11"))
if (CommandLine()->CheckParm("-softwared3d11"))
DriverType = 5; // D3D_DRIVER_TYPE_WARP
return D3D11CreateDevice(
pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext);
}
ON_DLL_LOAD_DEDI("materialsystem_dx11.dll", DedicatedServerMaterialSystem, (CModule module))
void InitialiseDedicatedMaterialSystem(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xD9A0E, &D3D11CreateDeviceHook, reinterpret_cast<LPVOID*>(&D3D11CreateDevice));
// CMaterialSystem::FindMaterial
// make the game always use the error material
module.Offset(0x5F0F1).Patch("E9 34 03 00");
// not using these for now since they're related to nopping renderthread/gamewindow i.e. very hard
//{
// // function that launches renderthread
// char* ptr = (char*)baseAddress + 0x87047;
// TempReadWrite rw(ptr);
//
// // make it not launch renderthread
// *ptr = (char)0x90;
// *(ptr + 1) = (char)0x90;
// *(ptr + 2) = (char)0x90;
// *(ptr + 3) = (char)0x90;
// *(ptr + 4) = (char)0x90;
// *(ptr + 5) = (char)0x90;
//}
//
//{
// // some function that waits on renderthread job
// char* ptr = (char*)baseAddress + 0x87d00;
// TempReadWrite rw(ptr);
//
// // return immediately
// *ptr = (char)0xC3;
//}
{
// CMaterialSystem::FindMaterial
// make the game always use the error material
NSMem::BytePatch((uintptr_t)baseAddress + 0x5F0F1, {0xE9, 0x34, 0x03, 0x00});
}
// previously had DisableDedicatedWindowCreation stuff here, removing for now since shit and unstable
// check commit history if needed
}
typedef void* (*PakLoadAPI__LoadRpakType)(char* filename, void* unknown, int flags);
PakLoadAPI__LoadRpakType PakLoadAPI__LoadRpak;
void* PakLoadAPI__LoadRpakHook(char* filename, void* unknown, int flags)
{
spdlog::info("PakLoadAPI__LoadRpakHook {}", filename);
// on dedi, don't load any paks that aren't required
if (strncmp(filename, "common", 6))
return 0;
return PakLoadAPI__LoadRpak(filename, unknown, flags);
}
typedef void* (*PakLoadAPI__LoadRpak2Type)(char* filename, void* unknown, int flags, void* callback, void* callback2);
PakLoadAPI__LoadRpak2Type PakLoadAPI__LoadRpak2;
void* PakLoadAPI__LoadRpak2Hook(char* filename, void* unknown, int flags, void* callback, void* callback2)
{
spdlog::info("PakLoadAPI__LoadRpak2Hook {}", filename);
// on dedi, don't load any paks that aren't required
if (strncmp(filename, "common", 6))
return 0;
return PakLoadAPI__LoadRpak2(filename, unknown, flags, callback, callback2);
}
void InitialiseDedicatedRtechGame(HMODULE baseAddress)
{
baseAddress = GetModuleHandleA("rtech_game.dll");
HookEnabler hook;
// unfortunately this is unstable, seems to freeze when changing maps
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB0F0, &PakLoadAPI__LoadRpakHook, reinterpret_cast<LPVOID*>(&PakLoadAPI__LoadRpak));
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xB170, &PakLoadAPI__LoadRpak2Hook, reinterpret_cast<LPVOID*>(&PakLoadAPI__LoadRpak2));
}

View File

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

View File

@ -1,26 +0,0 @@
#include "pch.h"
#include "convar.h"
ON_DLL_LOAD_CLIENT("engine.dll", EngineDemoFixes, (CModule module))
{
// allow demo recording on loopback
module.Offset(0x8E1B1).NOP(2);
module.Offset(0x56CC3).NOP(2);
}
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientDemoFixes, ConVar, (CModule module))
{
// change default values of demo cvars to enable them by default, but not autorecord
// this is before Host_Init, the setvalue calls here will get overwritten by custom cfgs/launch options
ConVar* Cvar_demo_enableDemos = R2::g_pCVar->FindVar("demo_enabledemos");
Cvar_demo_enableDemos->m_pszDefaultValue = "1";
Cvar_demo_enableDemos->SetValue(true);
ConVar* Cvar_demo_writeLocalFile = R2::g_pCVar->FindVar("demo_writeLocalFile");
Cvar_demo_writeLocalFile->m_pszDefaultValue = "1";
Cvar_demo_writeLocalFile->SetValue(true);
ConVar* Cvar_demo_autoRecord = R2::g_pCVar->FindVar("demo_autoRecord");
Cvar_demo_autoRecord->m_pszDefaultValue = "0";
Cvar_demo_autoRecord->SetValue(false);
}

View File

@ -1,24 +1,63 @@
#include "pch.h"
#include "hooks.h"
#include "main.h"
#include "squirrel.h"
#include "dedicated.h"
#include "dedicatedmaterialsystem.h"
#include "sourceconsole.h"
#include "logging.h"
#include "crashhandler.h"
#include "concommand.h"
#include "modmanager.h"
#include "filesystem.h"
#include "serverauthentication.h"
#include "scriptmodmenu.h"
#include "scriptserverbrowser.h"
#include "keyvalues.h"
#include "masterserver.h"
#include "gameutils.h"
#include "chatcommand.h"
#include "modlocalisation.h"
#include "playlist.h"
#include "miscserverscript.h"
#include "clientauthhooks.h"
#include "latencyflex.h"
#include "scriptbrowserhooks.h"
#include "scriptmainmenupromos.h"
#include "miscclientfixes.h"
#include "miscserverfixes.h"
#include "rpakfilesystem.h"
#include "bansystem.h"
#include "memalloc.h"
#include "maxplayers.h"
#include "languagehooks.h"
#include "audio.h"
#include "buildainfile.h"
#include "nsprefix.h"
#include "serverchathooks.h"
#include "clientchathooks.h"
#include "localchatwriter.h"
#include "scriptservertoclientstringcommand.h"
#include "plugin_abi.h"
#include "plugins.h"
#include "debugoverlay.h"
#include "clientvideooverrides.h"
#include "clientruihooks.h"
#include <string.h>
#include "version.h"
#include "pch.h"
#include "scriptutility.h"
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include "rapidjson/error/en.h"
#include <string.h>
#include <filesystem>
#include "exploitfixes.h"
#include "scriptjson.h"
typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject));
bool initialised = false;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
@ -33,6 +72,18 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
return TRUE;
}
void WaitForDebugger(HMODULE baseAddress)
{
// earlier waitfordebugger call than is in vanilla, just so we can debug stuff a little easier
if (CommandLine()->CheckParm("-waitfordebugger"))
{
spdlog::info("waiting for debugger...");
while (!IsDebuggerPresent())
Sleep(100);
}
}
void freeLibrary(HMODULE hLib)
{
if (!FreeLibrary(hLib))
@ -139,11 +190,13 @@ bool LoadPlugins()
bool InitialiseNorthstar()
{
static bool bInitialised = false;
if (bInitialised)
if (initialised)
{
// spdlog::warn("Called InitialiseNorthstar more than once!"); // it's actually 100% fine for that to happen
return false;
}
bInitialised = true;
initialised = true;
// initialise the console if needed (-northstar needs this)
InitialiseConsole();
@ -151,7 +204,6 @@ bool InitialiseNorthstar()
InitialiseLogging();
parseConfigurables();
InitialiseNorthstarPrefix();
InitialiseVersion();
// Fix some users' failure to connect to respawn datacenters
@ -165,6 +217,94 @@ bool InitialiseNorthstar()
// Write launcher version to log
spdlog::info("NorthstarLauncher version: {}", version);
InitialiseInterfaceCreationHooks();
AddDllLoadCallback("tier0.dll", InitialiseTier0GameUtilFunctions);
AddDllLoadCallback("engine.dll", WaitForDebugger);
AddDllLoadCallback("engine.dll", InitialiseEngineGameUtilFunctions);
AddDllLoadCallback("server.dll", InitialiseServerGameUtilFunctions);
// dedi patches
{
AddDllLoadCallbackForDedicatedServer("tier0.dll", InitialiseDedicatedOrigin);
AddDllLoadCallbackForDedicatedServer("engine.dll", InitialiseDedicated);
AddDllLoadCallbackForDedicatedServer("server.dll", InitialiseDedicatedServerGameDLL);
AddDllLoadCallbackForDedicatedServer("materialsystem_dx11.dll", InitialiseDedicatedMaterialSystem);
// this fucking sucks, but seemingly we somehow load after rtech_game???? unsure how, but because of this we have to apply patches
// here, not on rtech_game load
AddDllLoadCallbackForDedicatedServer("engine.dll", InitialiseDedicatedRtechGame);
}
AddDllLoadCallback("engine.dll", InitialiseConVars);
AddDllLoadCallback("engine.dll", InitialiseConCommands);
// client-exclusive patches
{
AddDllLoadCallbackForClient("tier0.dll", InitialiseTier0LanguageHooks);
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrel);
AddDllLoadCallbackForClient("client.dll", InitialiseSourceConsole);
AddDllLoadCallbackForClient("engine.dll", InitialiseChatCommands);
AddDllLoadCallbackForClient("client.dll", InitialiseScriptModMenu);
AddDllLoadCallbackForClient("client.dll", InitialiseScriptServerBrowser);
AddDllLoadCallbackForClient("localize.dll", InitialiseModLocalisation);
AddDllLoadCallbackForClient("engine.dll", InitialiseClientAuthHooks);
AddDllLoadCallbackForClient("client.dll", InitialiseLatencyFleX);
AddDllLoadCallbackForClient("engine.dll", InitialiseScriptExternalBrowserHooks);
AddDllLoadCallbackForClient("client.dll", InitialiseScriptMainMenuPromos);
AddDllLoadCallbackForClient("client.dll", InitialiseMiscClientFixes);
AddDllLoadCallbackForClient("client.dll", InitialiseClientPrintHooks);
AddDllLoadCallbackForClient("client.dll", InitialisePluginCommands);
AddDllLoadCallbackForClient("client.dll", InitialiseClientChatHooks);
AddDllLoadCallbackForClient("client.dll", InitialiseLocalChatWriter);
AddDllLoadCallbackForClient("client.dll", InitialiseScriptServerToClientStringCommands);
AddDllLoadCallbackForClient("engine.dll", InitialiseEngineClientVideoOverrides);
AddDllLoadCallbackForClient("engine.dll", InitialiseEngineClientRUIHooks);
AddDllLoadCallbackForClient("engine.dll", InitialiseDebugOverlay);
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrelJson);
AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrelUtilityFunctions);
// audio hooks
AddDllLoadCallbackForClient("client.dll", InitialiseMilesAudioHooks);
}
AddDllLoadCallback("engine.dll", InitialiseEngineSpewFuncHooks);
AddDllLoadCallback("server.dll", InitialiseServerSquirrel);
AddDllLoadCallback("engine.dll", InitialiseBanSystem);
AddDllLoadCallback("engine.dll", InitialiseServerAuthentication);
AddDllLoadCallback("engine.dll", InitialiseSharedMasterServer);
AddDllLoadCallback("server.dll", InitialiseMiscServerScriptCommand);
AddDllLoadCallback("server.dll", InitialiseMiscServerFixes);
AddDllLoadCallback("server.dll", InitialiseBuildAINFileHooks);
AddDllLoadCallback("server.dll", InitialiseServerSquirrelUtilityFunctions);
AddDllLoadCallback("server.dll", InitialiseServerSquirrelJson);
AddDllLoadCallback("engine.dll", InitialisePlaylistHooks);
AddDllLoadCallback("filesystem_stdio.dll", InitialiseFilesystem);
AddDllLoadCallback("engine.dll", InitialiseEngineRpakFilesystem);
AddDllLoadCallback("engine.dll", InitialiseKeyValues);
AddDllLoadCallback("engine.dll", InitialiseServerChatHooks_Engine);
AddDllLoadCallback("server.dll", InitialiseServerChatHooks_Server);
// maxplayers increase
AddDllLoadCallback("engine.dll", InitialiseMaxPlayersOverride_Engine);
AddDllLoadCallback("client.dll", InitialiseMaxPlayersOverride_Client);
AddDllLoadCallback("server.dll", InitialiseMaxPlayersOverride_Server);
// mod manager after everything else
AddDllLoadCallback("engine.dll", InitialiseModManager);
{
// activate multi-module exploitfixes callbacks
constexpr const char* EXPLOITFIXES_MULTICALLBACK_MODS[] = {"client.dll", "engine.dll", "server.dll"};
for (const char* mod : EXPLOITFIXES_MULTICALLBACK_MODS)
AddDllLoadCallback(mod, ExploitFixes::LoadCallback_MultiModule);
// activate exploit fixes later
AddDllLoadCallback("server.dll", ExploitFixes::LoadCallback_Full);
}
// run callbacks for any libraries that are already loaded by now
CallAllPendingDLLLoadCallbacks();

View File

@ -1,63 +1,52 @@
#include "pch.h"
#include "exploitfixes.h"
#include "exploitfixes_utf8parser.h"
#include "nsmem.h"
#include "cvar.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;
#include "gameutils.h"
ConVar* ns_exploitfixes_log;
#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0)
#define BLOCKED_INFO(s) \
( \
[=]() -> bool \
{ \
if (Cvar_ns_exploitfixes_log->GetBool()) \
if (SHOULD_LOG) \
{ \
std::stringstream stream; \
stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \
stream << "exploitfixes.cpp: " << BLOCK_PREFIX << s; \
spdlog::error(stream.str()); \
} \
return false; \
}())
// block bad netmessages
struct Float3
{
float vals[3];
void MakeValid()
{
for (auto& val : vals)
if (isnan(val))
val = 0;
}
};
#define BLOCK_NETMSG_FUNC(name, pattern) \
KHOOK(name, ("engine.dll", pattern), bool, __fastcall, (void* thisptr, void* buffer)) \
{ \
return false; \
}
// Servers can literally request a screenshot from any client, yeah no
// clang-format off
AUTOHOOK(CLC_Screenshot_WriteToBuffer, engine.dll + 0x22AF20,
bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 57 48 83 EC 20 8B 42 10
// clang-format on
{
return false;
}
BLOCK_NETMSG_FUNC(CLC_Screenshot_WriteToBuffer, "48 89 5C 24 ? 57 48 83 EC 20 8B 42 10");
BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38");
// clang-format off
AUTOHOOK(CLC_Screenshot_ReadFromBuffer, engine.dll + 0x221F00,
bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38
// clang-format on
{
return false;
}
// This is unused ingame and a big exploit vector
BLOCK_NETMSG_FUNC(Base_CmdKeyValues_ReadFromBuffer, "40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70");
// This is unused ingame and a big client=>server=>client exploit vector
// clang-format off
AUTOHOOK(Base_CmdKeyValues_ReadFromBuffer, engine.dll + 0x220040,
bool, __fastcall, (void* thisptr, void* buffer)) // 40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70
// clang-format on
{
return false;
}
// 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
KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10"), bool, __fastcall, (void* pMsg))
{
constexpr int ENTRY_STR_LEN = 260;
@ -80,12 +69,25 @@ bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10
};
auto msg = (NET_SetConVar*)pMsg;
bool bIsServerFrame = Tier0::ThreadInServerFrameThread();
std::string BLOCK_PREFIX =
std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
bool areWeServer;
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;
if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT)
@ -99,8 +101,9 @@ bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10
auto entry = msg->m_ConVars + i;
// Safety check for memory access
if (MemoryAddress(entry).IsMemoryReadable(sizeof(*entry)))
if (NSMem::IsMemoryReadable(entry, sizeof(*entry)))
{
// Find null terminators
bool nameValid = false, valValid = false;
for (int i = 0; i < ENTRY_STR_LEN; i++)
@ -114,19 +117,36 @@ bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10
if (!nameValid || !valValid)
return BLOCKED_INFO("Missing null terminators");
ConVar* pVar = R2::g_pCVar->FindVar(entry->name);
auto realVar = g_pCVar->FindVar(entry->name);
if (pVar)
{
if (realVar)
memcpy(
entry->name,
pVar->m_ConCommandBase.m_pszName,
strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
realVar->m_ConCommandBase.m_pszName,
strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
int iFlags = bIsServerFrame ? FCVAR_USERINFO : FCVAR_REPLICATED;
if (!pVar->IsFlagSet(iFlags))
bool isValidFlags = true;
if (areWeServer)
{
if (realVar)
isValidFlags = ConVar::IsFlagSet(realVar, FCVAR_USERINFO); // ConVar MUST be userinfo var
}
else
{
// TODO: Should probably have some sanity checks, but can't find any that are consistent
}
if (!isValidFlags)
{
if (!realVar)
{
return BLOCKED_INFO("Invalid flags on nonexistant cvar (how tho???)");
}
else
{
return BLOCKED_INFO(
"Invalid flags (" << std::hex << "0x" << pVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name);
"Invalid flags (" << std::hex << "0x" << realVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name);
}
}
}
else
@ -135,14 +155,11 @@ bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10
}
}
return CClient_ProcessSetConVar(msg);
return oCClient_ProcessSetConVar(msg);
}
// prevent invalid user CMDs
// clang-format off
AUTOHOOK(CClient_ProcessUsercmds, engine.dll + 0x1040F0,
bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58
// clang-format on
// Purpose: prevent invalid user CMDs
KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg))
{
struct CLC_Move
{
@ -169,19 +186,22 @@ bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58
return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")");
}
constexpr int NUMCMD_SANITY_LIMIT = 16;
if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT)
{
return BLOCKED_INFO("Command count is too high (new: " << msg->m_nNewCommands << ", backup: " << msg->m_nBackupCommands << ")");
}
if (msg->m_nLength <= 0)
return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")");
return CClient_ProcessUsercmds(thisptr, pMsg);
return oCClient_ProcessUsercmds(thisptr, pMsg);
}
// 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
KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from))
{
// Let normal usercmd read happen first, it's safe
ReadUsercmd(buf, pCmd_move, pCmd_from);
oReadUsercmd(buf, pCmd_move, pCmd_from);
// Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing)
struct alignas(4) SV_CUserCmd
@ -189,11 +209,11 @@ void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24
DWORD command_number;
DWORD tick_count;
float command_time;
Vector3 worldViewAngles;
Float3 worldViewAngles;
BYTE gap18[4];
Vector3 localViewAngles;
Vector3 attackangles;
Vector3 move;
Float3 localViewAngles;
Float3 attackangles;
Float3 move;
DWORD buttons;
BYTE impulse;
short weaponselect;
@ -201,8 +221,8 @@ void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24
BYTE gap4C[24];
char headoffset;
BYTE gap65[11];
Vector3 cameraPos;
Vector3 cameraAngles;
Float3 cameraPos;
Float3 cameraAngles;
BYTE gap88[4];
int tickSomething;
DWORD dword90;
@ -217,7 +237,7 @@ void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24
std::string BLOCK_PREFIX =
"ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): ";
// fix invalid player angles
// Fix invalid player angles
cmd->worldViewAngles.MakeValid();
cmd->attackangles.MakeValid();
cmd->localViewAngles.MakeValid();
@ -229,7 +249,7 @@ void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24
// Fix invaid movement vector
cmd->move.MakeValid();
if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0)
if (cmd->tick_count == 0 || cmd->command_time <= 0)
{
BLOCKED_INFO(
"Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime
@ -240,7 +260,6 @@ void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24
return;
INVALID_CMD:
// Fix any gameplay-affecting cmd properties
// NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems
cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = {0, 0, 0};
@ -250,209 +269,287 @@ INVALID_CMD:
cmd->meleetarget = 0;
}
// ensure that GetLocalBaseClient().m_bRestrictServerCommands is set correctly, which the return value of this function controls
// this is IsValveMod in source, but we're making it IsRespawnMod now since valve didn't make this one
// clang-format off
AUTOHOOK(IsRespawnMod, engine.dll + 0x1C6360,
bool, __fastcall, (const char* pModName)) // 48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63
// clang-format on
// basically: by default r2 isn't set as a valve mod, meaning that m_bRestrictServerCommands is false
// this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients
// especially since we have script commands this could theoretically be awful
KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ())
{
// somewhat temp, store the modname here, since we don't have a proper ptr in engine to it rn
int iSize = strlen(pModName);
R2::g_pModName = new char[iSize + 1];
strcpy(R2::g_pModName, pModName);
return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) && !Tier0::CommandLine()->CheckParm("-norestrictservercommands");
bool result = !CommandLine()->CheckParm("-norestrictservercommands");
spdlog::info("ExploitFixes: Overriding IsValveMod to {}...", result);
return result;
}
// ratelimit stringcmds, and prevent remote clients from calling commands that they shouldn't
bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, R2::cmd_source_t commandSource);
// clang-format off
AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0,
bool, __fastcall, (R2::CBaseClient* self, uint32_t unknown, const char* pCommandString))
// clang-format on
// Fix respawn's crappy UTF8 parser so it doesn't crash -_-
// This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome
KHOOK(
CrashFunc_ParseUTF8,
("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"),
bool,
__fastcall,
(INT64 * a1, DWORD* a2, char* strData))
{
if (Cvar_ns_should_log_all_clientcommands->GetBool())
spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString);
if (!g_pServerLimits->CheckStringCommandLimits(self))
static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16");
#ifdef _MSC_VER
if (_ReturnAddress() == targetRetAddr)
#else
if (__builtin_return_address(0) == targetRetAddr)
#endif
{
R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands");
return false;
}
// verify the command we're trying to execute is FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, if it's a concommand
char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor
memset(commandBuf, 0, sizeof(commandBuf));
CCommand tempCommand = *(CCommand*)&commandBuf;
if (!CCommand__Tokenize(tempCommand, pCommandString, R2::cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC())
return false;
ConCommand* command = R2::g_pCVar->FindCommand(tempCommand.Arg(0));
// if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff
if (command && !command->IsFlagSet(FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS))
{
// ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients
if (IsDedicatedServer())
return false;
if (strcmp(self->m_UID, R2::g_pLocalPlayerUserID))
return false;
}
// check for and block abusable legacy portal 2 commands
// these aren't actually concommands weirdly enough, they seem to just be hardcoded
if (!Cvar_sv_cheats->GetBool())
{
constexpr const char* blockedCommands[] = {
"emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
// These both execute a command for every single entity for some reason, nice one valve
"pre_go_to_hub",
"pre_go_to_calibration",
"end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed
"load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
};
int iCmdLength = strlen(tempCommand.Arg(0));
bool bIsBadCommand = false;
for (auto& blockedCommand : blockedCommands)
if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData))
{
if (iCmdLength != strlen(blockedCommand))
continue;
for (int i = 0; tempCommand.Arg(0)[i]; i++)
if (tolower(tempCommand.Arg(0)[i]) != blockedCommand[i])
goto NEXT_COMMAND; // break out of this loop, then go to next command
// this is a command we need to block
const char* BLOCK_PREFIX = "ParseUTF8 Hook: ";
BLOCKED_INFO("Ignoring potentially-crashing utf8 string");
return false;
NEXT_COMMAND:;
}
}
return CGameClient__ExecuteStringCommand(self, unknown, pCommandString);
return oCrashFunc_ParseUTF8(a1, a2, strData);
}
// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines
bool bWasWritingStringTableSuccessful;
// 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;
// clang-format off
AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0,
void, __fastcall, (void* self))
// clang-format on
static void* GetEntByIndexHook(int idx)
{
bWasWritingStringTableSuccessful = true;
CBaseClient__SendServerInfo(self);
if (!bWasWritingStringTableSuccessful)
R2::CBaseClient__Disconnect(
self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting");
}
// return null when GetEntByIndex is passed an index >= 0x4000
// this is called from exactly 1 script clientcommand that can be given an arbitrary index, and going above 0x4000 crashes
// clang-format off
AUTOHOOK(GetEntByIndex, server.dll + 0x2A8A50,
void*, __fastcall, (int i))
// clang-format on
{
const int MAX_ENT_IDX = 0x4000;
if (i >= MAX_ENT_IDX)
if (idx >= 0x4000)
{
spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX);
spdlog::info("GetEntByIndex {} is out of bounds", idx);
return nullptr;
}
return GetEntByIndex(i);
return GetEntByIndex(idx);
}
// clang-format off
AUTOHOOK(CL_CopyExistingEntity, engine.dll + 0x6F940,
bool, __fastcall, (void* a1))
// clang-format on
// RELOCATED FROM https://github.com/R2Northstar/NorthstarLauncher/commit/25dbf729cfc75107a0fcf0186924b58ecc05214b
// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read
// out of the bounds of the output buffer.
KHOOK(
LZSS_SafeUncompress,
("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 33 ED 41 8B F9"),
uint32_t,
__fastcall,
(void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize))
{
struct CEntityReadInfo
static constexpr int LZSS_LOOKSHIFT = 4;
uint32_t totalBytes = 0;
int getCmdByte = 0, cmdByte = 0;
struct lzss_header_t
{
BYTE gap[40];
int nNewEntity;
uint32_t id, actualSize;
};
CEntityReadInfo* pReadInfo = (CEntityReadInfo*)a1;
if (pReadInfo->nNewEntity >= 0x1000 || pReadInfo->nNewEntity < 0)
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 (;;)
{
// Value isn't sanitized in release builds for
// every game powered by the Source Engine 1
// causing read/write outside of array bounds.
// This defect has let to the achievement of a
// full-chain RCE exploit. We hook and perform
// sanity checks for the value of m_nNewEntity
// here to prevent this behavior from happening.
return false;
}
if (!getCmdByte)
cmdByte = *pInput++;
return CL_CopyExistingEntity(a1);
}
getCmdByte = (getCmdByte + 1) & 0x07;
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 (cmdByte & 0x01)
{
// Just return, none of them have any args or are userpurge
exportAddr.Patch("C3");
spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
int position = *pInput++ << LZSS_LOOKSHIFT;
position |= (*pInput >> LZSS_LOOKSHIFT);
position += 1;
int count = (*pInput++ & 0x0F) + 1;
if (count == 1)
break;
// Ensure reference chunk exists entirely within our buffer
if (position > totalBytes)
return 0;
totalBytes += count;
if (totalBytes > unBufSize)
return 0;
unsigned char* pSource = pOutput - position;
for (int i = 0; i < count; i++)
*pOutput++ = *pSource++;
}
else
{
totalBytes++;
if (totalBytes > unBufSize)
return 0;
*pOutput++ = *pInput++;
}
cmdByte = cmdByte >> 1;
}
if (totalBytes == header.actualSize)
{
return totalBytes;
}
else
{
return 0;
}
}
//////////////////////////////////////////////////
void DoBytePatches()
{
uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll");
uintptr_t serverBase = (uintptr_t)GetModuleHandleA("server.dll");
// patches to make commands run from client/ui script still work
// note: this is likely preventable in a nicer way? test prolly
NSMem::BytePatch(engineBase + 0x4FB65, "EB 11");
NSMem::BytePatch(engineBase + 0x4FBAC, "EB 16");
// disconnect concommand
{
uintptr_t addr = engineBase + 0x5ADA2D;
int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE;
NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int));
}
{ // Dumb ANTITAMPER patches (they negatively impact performance and security)
constexpr const char* ANTITAMPER_EXPORTS[] = {
"ANTITAMPER_SPOTCHECK_CODEMARKER",
"ANTITAMPER_TESTVALUE_CODEMARKER",
"ANTITAMPER_TRIGGER_CODEMARKER",
};
// Prevent thesefrom actually doing anything
for (auto exportName : ANTITAMPER_EXPORTS)
{
auto address = (uintptr_t)GetProcAddress(GetModuleHandleA("server.dll"), exportName);
if (!address)
{
spdlog::warn("Failed to find AntiTamper function export \"{}\"", exportName);
}
else
{
// Just return, none of them have any args or are userpurge
NSMem::BytePatch(address, "C3");
spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
}
}
}
}
KHOOK(
SpecialClientCommand,
("server.dll", "48 89 5C 24 ? 48 89 74 24 ? 55 57 41 56 48 8D 6C 24 ? 48 81 EC ? ? ? ? 83 3A 00"),
bool,
__fastcall,
(void* player, CCommand* command))
{
static ConVar* sv_cheats = g_pCVar->FindVar("sv_cheats");
if (sv_cheats->GetBool())
return oSpecialClientCommand(player, command); // Don't block anything if sv_cheats is on
// These are mostly from Portal 2 (sigh)
constexpr const char* blockedCommands[] = {
"emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
// These both execute a command for every single entity for some reason, nice one valve
"pre_go_to_hub",
"pre_go_to_calibration",
"end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed
"load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
};
if (command->ArgC() > 0)
{
std::string cmdStr = command->Arg(0);
for (char& c : cmdStr)
c = tolower(c);
for (const char* blockedCommand : blockedCommands)
{
if (cmdStr.find(blockedCommand) != std::string::npos)
{
// Block this command
spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr);
return true;
}
}
}
Cvar_ns_exploitfixes_log =
new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something");
Cvar_ns_should_log_all_clientcommands =
new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands");
Cvar_sv_cheats = R2::g_pCVar->FindVar("sv_cheats");
return oSpecialClientCommand(player, command);
}
void SetupKHook(KHook* hook)
{
if (hook->Setup())
{
spdlog::debug("KHook::Setup(): Hooked at {}", hook->targetFuncAddr);
}
else
{
spdlog::critical("\tFAILED to initialize all exploit patches.");
// Force exit
MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR);
exit(0);
}
}
void ExploitFixes::LoadCallback_MultiModule(HMODULE baseAddress)
{
spdlog::info("ExploitFixes::LoadCallback_MultiModule({}) ...", (void*)baseAddress);
int hooksEnabled = 0;
for (auto itr = KHook::_allHooks.begin(); itr != KHook::_allHooks.end(); itr++)
{
auto curHook = *itr;
if (GetModuleHandleA(curHook->targetFunc.moduleName) == baseAddress)
{
SetupKHook(curHook);
itr = KHook::_allHooks.erase(itr); // Prevent repeated initialization
hooksEnabled++;
if (itr == KHook::_allHooks.end())
break;
}
}
spdlog::info("\tEnabled {} hooks.", hooksEnabled);
}
void ExploitFixes::LoadCallback_Full(HMODULE baseAddress)
{
spdlog::info("ExploitFixes::LoadCallback_Full ...");
spdlog::info("\tByte patching...");
DoBytePatches();
for (KHook* hook : KHook::_allHooks)
SetupKHook(hook);
spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " late exploit-patch hooks.");
KHook::_allHooks.clear();
ns_exploitfixes_log =
new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever exploitfixes.cpp blocks/corrects something");
g_pCVar->FindCommand("migrateme")->m_nFlags &= ~FCVAR_SERVER_CAN_EXECUTE;
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x2a8a50, &GetEntByIndexHook, reinterpret_cast<LPVOID*>(&GetEntByIndex));
}

View File

@ -0,0 +1,12 @@
// KittenPopo's exploit fix hooks, feel free to add more here
#pragma once
#include "pch.h"
namespace ExploitFixes
{
// This callback will be ran muliple times on multiple module loads
void LoadCallback_MultiModule(HMODULE baseAddress);
void LoadCallback_Full(HMODULE unused);
} // namespace ExploitFixes

View File

@ -1,79 +0,0 @@
#include "pch.h"
AUTOHOOK_INIT()
static constexpr int LZSS_LOOKSHIFT = 4;
struct lzss_header_t
{
unsigned int id;
unsigned int actualSize;
};
// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read
// out of the bounds of the output buffer.
// clang-format off
AUTOHOOK(CLZSS__SafeDecompress, engine.dll + 0x432A10,
unsigned int, __fastcall, (void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize))
// clang-format on
{
unsigned int totalBytes = 0;
int getCmdByte = 0;
int cmdByte = 0;
lzss_header_t header = *(lzss_header_t*)pInput;
if (!pInput || !header.actualSize || header.id != 0x53535A4C || header.actualSize > unBufSize)
return 0;
pInput += sizeof(lzss_header_t);
for (;;)
{
if (!getCmdByte)
cmdByte = *pInput++;
getCmdByte = (getCmdByte + 1) & 0x07;
if (cmdByte & 0x01)
{
int position = *pInput++ << LZSS_LOOKSHIFT;
position |= (*pInput >> LZSS_LOOKSHIFT);
position += 1;
int count = (*pInput++ & 0x0F) + 1;
if (count == 1)
break;
// Ensure reference chunk exists entirely within our buffer
if (position > totalBytes)
return 0;
totalBytes += count;
if (totalBytes > unBufSize)
return 0;
unsigned char* pSource = pOutput - position;
for (int i = 0; i < count; i++)
*pOutput++ = *pSource++;
}
else
{
totalBytes++;
if (totalBytes > unBufSize)
return 0;
*pOutput++ = *pInput++;
}
cmdByte = cmdByte >> 1;
}
if (totalBytes != header.actualSize)
return 0;
return totalBytes;
}
ON_DLL_LOAD("engine.dll", ExploitFixes_LZSS, (CModule module))
{
AUTOHOOK_DISPATCH()
}

View File

@ -1,200 +0,0 @@
#include "pch.h"
AUTOHOOK_INIT()
INT64(__fastcall* sub_F1320)(DWORD a1, char* a2);
// Reimplementation of an exploitable UTF decoding function in titanfall
bool __fastcall CheckUTF8Valid(INT64* a1, DWORD* a2, char* strData)
{
DWORD v3; // eax
char* v4; // rbx
char v5; // si
char* _strData; // rdi
char* v7; // rbp
char v11; // al
DWORD v12; // er9
DWORD v13; // ecx
DWORD v14; // edx
DWORD v15; // er8
int v16; // eax
DWORD v17; // er9
int v18; // eax
DWORD v19; // er9
DWORD v20; // ecx
int v21; // eax
int v22; // er9
DWORD v23; // edx
int v24; // eax
int v25; // er9
DWORD v26; // er9
DWORD v27; // er10
DWORD v28; // ecx
DWORD v29; // edx
DWORD v30; // er8
int v31; // eax
DWORD v32; // er10
int v33; // eax
DWORD v34; // er10
DWORD v35; // ecx
int v36; // eax
int v37; // er10
DWORD v38; // edx
int v39; // eax
int v40; // er10
DWORD v41; // er10
INT64 v43; // r8
INT64 v44; // rdx
INT64 v45; // rcx
INT64 v46; // rax
INT64 v47; // rax
char v48; // al
INT64 v49; // r8
INT64 v50; // rdx
INT64 v51; // rcx
INT64 v52; // rax
INT64 v53; // rax
v3 = a2[2];
v4 = (char*)(a1[1] + *a2);
v5 = 0;
_strData = strData;
v7 = &v4[*((UINT16*)a2 + 2)];
if (v3 >= 2)
{
++v4;
--v7;
if (v3 != 2)
{
while (1)
{
if (!MemoryAddress(v4).IsMemoryReadable(1))
return false; // INVALID
v11 = *v4++; // crash potential
if (v11 != 92)
goto LABEL_6;
v11 = *v4++;
if (v11 == 110)
break;
switch (v11)
{
case 't':
v11 = 9;
goto LABEL_6;
case 'r':
v11 = 13;
goto LABEL_6;
case 'b':
v11 = 8;
goto LABEL_6;
case 'f':
v11 = 12;
goto LABEL_6;
}
if (v11 != 117)
goto LABEL_6;
v12 = *v4 | 0x20;
v13 = v4[1] | 0x20;
v14 = v4[2] | 0x20;
v15 = v4[3] | 0x20;
v16 = 87;
if (v12 <= 0x39)
v16 = 48;
v17 = v12 - v16;
v18 = 87;
v19 = v17 << 12;
if (v13 <= 0x39)
v18 = 48;
v20 = v13 - v18;
v21 = 87;
v22 = (v20 << 8) | v19;
if (v14 <= 0x39)
v21 = 48;
v23 = v14 - v21;
v24 = 87;
v25 = (16 * v23) | v22;
if (v15 <= 0x39)
v24 = 48;
v4 += 4;
v26 = (v15 - v24) | v25;
if (v26 - 55296 <= 0x7FF)
{
if (v26 >= 0xDC00)
return true;
if (*v4 != 92 || v4[1] != 117)
return true;
v27 = v4[2] | 0x20;
v28 = v4[3] | 0x20;
v29 = v4[4] | 0x20;
v30 = v4[5] | 0x20;
v31 = 87;
if (v27 <= 0x39)
v31 = 48;
v32 = v27 - v31;
v33 = 87;
v34 = v32 << 12;
if (v28 <= 0x39)
v33 = 48;
v35 = v28 - v33;
v36 = 87;
v37 = (v35 << 8) | v34;
if (v29 <= 0x39)
v36 = 48;
v38 = v29 - v36;
v39 = 87;
v40 = (16 * v38) | v37;
if (v30 <= 0x39)
v39 = 48;
v4 += 6;
v41 = ((v30 - v39) | v40) - 56320;
if (v41 > 0x3FF)
return true;
v26 = v41 | ((v26 - 55296) << 10);
}
_strData += (DWORD)sub_F1320(v26, _strData);
LABEL_7:
if (v4 == v7)
goto LABEL_48;
}
v11 = 10;
LABEL_6:
v5 |= v11;
*_strData++ = v11;
goto LABEL_7;
}
}
LABEL_48:
return true;
}
// prevent utf8 parser from crashing when provided bad data, which can be sent through user-controlled openinvites
// clang-format off
AUTOHOOK(Rson_ParseUTF8, engine.dll + 0xEF670,
bool, __fastcall, (INT64* a1, DWORD* a2, char* strData)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A
// clang-format on
{
static void* targetRetAddr = CModule("engine.dll").FindPattern("84 C0 75 2C 49 8B 16");
// only call if we're parsing utf8 data from the network (i.e. communities), otherwise we get perf issues
void* pReturnAddress =
#ifdef _MSC_VER
_ReturnAddress()
#else
__builtin_return_address(0)
#endif
;
if (pReturnAddress == targetRetAddr && !CheckUTF8Valid(a1, a2, strData))
return false;
return Rson_ParseUTF8(a1, a2, strData);
}
ON_DLL_LOAD("engine.dll", EngineExploitFixes_UTF8Parser, (CModule module))
{
AUTOHOOK_DISPATCH()
sub_F1320 = module.FindPattern("83 F9 7F 77 08 88 0A").As<INT64(__fastcall*)(DWORD, char*)>();
}

View File

@ -0,0 +1,175 @@
// Reimplementation of a exploitable UTF decoding function in titanfall
#include "NSMem.h"
#pragma once
namespace ExploitFixes_UTF8Parser
{
bool __fastcall CheckValid(INT64* a1, DWORD* a2, char* strData)
{
static auto sub_F1320 = (INT64(__fastcall*)(DWORD a1, char* a2))NSMem::PatternScan("engine.dll", "83 F9 7F 77 08 88 0A");
DWORD v3; // eax
char* v4; // rbx
char v5; // si
char* _strData; // rdi
char* v7; // rbp
char v11; // al
DWORD v12; // er9
DWORD v13; // ecx
DWORD v14; // edx
DWORD v15; // er8
int v16; // eax
DWORD v17; // er9
int v18; // eax
DWORD v19; // er9
DWORD v20; // ecx
int v21; // eax
int v22; // er9
DWORD v23; // edx
int v24; // eax
int v25; // er9
DWORD v26; // er9
DWORD v27; // er10
DWORD v28; // ecx
DWORD v29; // edx
DWORD v30; // er8
int v31; // eax
DWORD v32; // er10
int v33; // eax
DWORD v34; // er10
DWORD v35; // ecx
int v36; // eax
int v37; // er10
DWORD v38; // edx
int v39; // eax
int v40; // er10
DWORD v41; // er10
INT64 v43; // r8
INT64 v44; // rdx
INT64 v45; // rcx
INT64 v46; // rax
INT64 v47; // rax
char v48; // al
INT64 v49; // r8
INT64 v50; // rdx
INT64 v51; // rcx
INT64 v52; // rax
INT64 v53; // rax
v3 = a2[2];
v4 = (char*)(a1[1] + *a2);
v5 = 0;
_strData = strData;
v7 = &v4[*((UINT16*)a2 + 2)];
if (v3 >= 2)
{
++v4;
--v7;
if (v3 != 2)
{
while (1)
{
if (!NSMem::IsMemoryReadable(v4, 1))
return false; // INVALID
v11 = *v4++; // crash potential
if (v11 != 92)
goto LABEL_6;
v11 = *v4++;
if (v11 == 110)
break;
switch (v11)
{
case 't':
v11 = 9;
goto LABEL_6;
case 'r':
v11 = 13;
goto LABEL_6;
case 'b':
v11 = 8;
goto LABEL_6;
case 'f':
v11 = 12;
goto LABEL_6;
}
if (v11 != 117)
goto LABEL_6;
v12 = *v4 | 0x20;
v13 = v4[1] | 0x20;
v14 = v4[2] | 0x20;
v15 = v4[3] | 0x20;
v16 = 87;
if (v12 <= 0x39)
v16 = 48;
v17 = v12 - v16;
v18 = 87;
v19 = v17 << 12;
if (v13 <= 0x39)
v18 = 48;
v20 = v13 - v18;
v21 = 87;
v22 = (v20 << 8) | v19;
if (v14 <= 0x39)
v21 = 48;
v23 = v14 - v21;
v24 = 87;
v25 = (16 * v23) | v22;
if (v15 <= 0x39)
v24 = 48;
v4 += 4;
v26 = (v15 - v24) | v25;
if (v26 - 55296 <= 0x7FF)
{
if (v26 >= 0xDC00)
return true;
if (*v4 != 92 || v4[1] != 117)
return true;
v27 = v4[2] | 0x20;
v28 = v4[3] | 0x20;
v29 = v4[4] | 0x20;
v30 = v4[5] | 0x20;
v31 = 87;
if (v27 <= 0x39)
v31 = 48;
v32 = v27 - v31;
v33 = 87;
v34 = v32 << 12;
if (v28 <= 0x39)
v33 = 48;
v35 = v28 - v33;
v36 = 87;
v37 = (v35 << 8) | v34;
if (v29 <= 0x39)
v36 = 48;
v38 = v29 - v36;
v39 = 87;
v40 = (16 * v38) | v37;
if (v30 <= 0x39)
v39 = 48;
v4 += 6;
v41 = ((v30 - v39) | v40) - 56320;
if (v41 > 0x3FF)
return true;
v26 = v41 | ((v26 - 55296) << 10);
}
_strData += (DWORD)sub_F1320(v26, _strData);
LABEL_7:
if (v4 == v7)
goto LABEL_48;
}
v11 = 10;
LABEL_6:
v5 |= v11;
*_strData++ = v11;
goto LABEL_7;
}
}
LABEL_48:
return true;
}
} // namespace ExploitFixes_UTF8Parser

View File

@ -1,69 +1,87 @@
#include "pch.h"
#include "filesystem.h"
#include "hooks.h"
#include "hookutils.h"
#include "sourceinterface.h"
#include "modmanager.h"
#include <iostream>
#include <sstream>
AUTOHOOK_INIT()
// hook forward declares
typedef FileHandle_t (*ReadFileFromVPKType)(VPKData* vpkInfo, __int64* b, char* filename);
ReadFileFromVPKType readFileFromVPK;
FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename);
using namespace R2;
typedef bool (*ReadFromCacheType)(IFileSystem* filesystem, char* path, void* result);
ReadFromCacheType readFromCache;
bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result);
bool bReadingOriginalFile = false;
std::string sCurrentModPath;
typedef void (*AddSearchPathType)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
AddSearchPathType addSearchPathOriginal;
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
ConVar* Cvar_ns_fs_log_reads;
typedef FileHandle_t (*ReadFileFromFilesystemType)(
IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5);
ReadFileFromFilesystemType readFileFromFilesystem;
FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5);
// use the R2 namespace for game funcs
namespace R2
typedef VPKData* (*MountVPKType)(IFileSystem* fileSystem, const char* vpkPath);
MountVPKType mountVPK;
VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath);
bool readingOriginalFile;
std::string currentModPath;
SourceInterface<IFileSystem>* g_Filesystem;
void InitialiseFilesystem(HMODULE baseAddress)
{
SourceInterface<IFileSystem>* g_pFilesystem;
g_Filesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
std::string ReadVPKFile(const char* path)
{
// read scripts.rson file, todo: check if this can be overwritten
FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0);
// create hooks
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x5CBA0, &ReadFileFromVPKHook, reinterpret_cast<LPVOID*>(&readFileFromVPK));
ENABLER_CREATEHOOK(
hook,
reinterpret_cast<void*>((*g_Filesystem)->m_vtable->ReadFromCache),
&ReadFromCacheHook,
reinterpret_cast<LPVOID*>(&readFromCache));
ENABLER_CREATEHOOK(
hook,
reinterpret_cast<void*>((*g_Filesystem)->m_vtable->AddSearchPath),
&AddSearchPathHook,
reinterpret_cast<LPVOID*>(&addSearchPathOriginal));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15F20, &ReadFileFromFilesystemHook, reinterpret_cast<LPVOID*>(&readFileFromFilesystem));
ENABLER_CREATEHOOK(
hook, reinterpret_cast<void*>((*g_Filesystem)->m_vtable->MountVPK), &MountVPKHook, reinterpret_cast<LPVOID*>(&mountVPK));
}
std::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));
(*g_pFilesystem)->m_vtable2->Close(*g_pFilesystem, fileHandle);
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
std::string ReadVPKFile(const char* path)
{
AddSearchPath(fileSystem, pPath, pathID, addType);
// 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);
// make sure current mod paths are at head
if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
std::stringstream fileStream;
int bytesRead = 0;
char data[4096];
do
{
AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
bytesRead = (*g_Filesystem)->m_vtable2->Read(&(*g_Filesystem)->m_vtable2, data, (int)std::size(data), fileHandle);
fileStream.write(data, bytesRead);
} while (bytesRead == std::size(data));
(*g_Filesystem)->m_vtable2->Close(*g_Filesystem, fileHandle);
return fileStream.str();
}
std::string ReadVPKOriginalFile(const char* path)
{
readingOriginalFile = true;
std::string ret = ReadVPKFile(path);
readingOriginalFile = false;
return ret;
}
void SetNewModSearchPaths(Mod* mod)
@ -72,84 +90,96 @@ void SetNewModSearchPaths(Mod* mod)
// in the future we could also determine whether the file we're setting paths for needs a mod dir, or compiled assets
if (mod != nullptr)
{
if ((fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().compare(sCurrentModPath))
if ((fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().compare(currentModPath))
{
spdlog::info("changing mod search path from {} to {}", sCurrentModPath, mod->m_ModDirectory.string());
spdlog::info("changing mod search path from {} to {}", currentModPath, mod->ModDirectory.string());
AddSearchPath(
&*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string();
addSearchPathOriginal(
&*(*g_Filesystem), (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
currentModPath = (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string();
}
}
else // push compiled to head
AddSearchPath(&*(*g_pFilesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
addSearchPathOriginal(&*(*g_Filesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
bool TryReplaceFile(const char* pPath, bool shouldCompile)
bool TryReplaceFile(char* path, bool shouldCompile)
{
if (bReadingOriginalFile)
if (readingOriginalFile)
return false;
if (shouldCompile)
g_pModManager->CompileAssetsForFile(pPath);
(*g_ModManager).CompileAssetsForFile(path);
// idk how efficient the lexically normal check is
// can't just set all /s in path to \, since some paths aren't in writeable memory
auto file = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path(pPath)));
if (file != g_pModManager->m_ModFiles.end())
auto file = g_ModManager->m_modFiles.find(fs::path(path).lexically_normal().string());
if (file != g_ModManager->m_modFiles.end())
{
SetNewModSearchPaths(file->second.m_pOwningMod);
SetNewModSearchPaths(file->second.owningMod);
return true;
}
return false;
}
// 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
FileHandle_t ReadFileFromVPKHook(VPKData* vpkInfo, __int64* b, char* filename)
{
if (TryReplaceFile(pPath, true))
return false;
// move this to a convar at some point when we can read them in native
// spdlog::info("ReadFileFromVPKHook {} {}", filename, vpkInfo->path);
return ReadFromCache(filesystem, pPath, result);
}
// force modded files to be read from mods, not vpk
// clang-format off
AUTOHOOK(ReadFileFromVPK, filesystem_stdio.dll + 0x5CBA0,
FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename))
// clang-format on
{
// don't compile here because this is only ever called from OpenEx, which already compiles
// there is literally never any reason to compile here, since we'll always compile in ReadFileFromFilesystemHook in the same codepath
// this is called
if (TryReplaceFile(filename, false))
{
*b = -1;
return b;
}
return ReadFileFromVPK(vpkInfo, b, filename);
return readFileFromVPK(vpkInfo, b, filename);
}
// 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
bool ReadFromCacheHook(IFileSystem* filesystem, char* path, void* result)
{
TryReplaceFile(pPath, true);
return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename);
// move this to a convar at some point when we can read them in native
// spdlog::info("ReadFromCacheHook {}", path);
if (TryReplaceFile(path, true))
return false;
return readFromCache(filesystem, path, result);
}
HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath))
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)
{
spdlog::info("MountVPK {}", pVpkPath);
VPKData* ret = MountVPK(fileSystem, pVpkPath);
addSearchPathOriginal(fileSystem, pPath, pathID, addType);
for (Mod mod : g_pModManager->m_LoadedMods)
// make sure current mod paths are at head
if (!strcmp(pathID, "GAME") && currentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
{
if (!mod.m_bEnabled)
addSearchPathOriginal(fileSystem, currentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
addSearchPathOriginal(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
}
FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPath, const char* pOptions, int64_t a4, uint32_t a5)
{
// this isn't super efficient, but it's necessary, since calling addsearchpath in readfilefromvpk doesn't work, possibly refactor later
// it also might be possible to hook functions that are called later, idk look into callstack for ReadFileFromVPK
if (!readingOriginalFile)
TryReplaceFile((char*)pPath, true);
return readFileFromFilesystem(filesystem, pPath, pOptions, a4, a5);
}
VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
{
spdlog::info("MountVPK {}", vpkPath);
VPKData* ret = mountVPK(fileSystem, vpkPath);
for (Mod mod : g_ModManager->m_loadedMods)
{
if (!mod.Enabled)
continue;
for (ModVPKEntry& vpkEntry : mod.Vpks)
@ -159,13 +189,13 @@ HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char*
{
// resolve vpk name and try to load one with the same name
// todo: we should be unloading these on map unload manually
std::string mapName(fs::path(pVpkPath).filename().string());
std::string mapName(fs::path(vpkPath).filename().string());
std::string modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string());
if (mapName.compare(modMapName))
continue;
}
VPKData* loaded = MountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str());
VPKData* loaded = mountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str());
if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here
ret = loaded;
}
@ -173,14 +203,3 @@ HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char*
return ret;
}
ON_DLL_LOAD("filesystem_stdio.dll", Filesystem, (CModule module))
{
AUTOHOOK_DISPATCH()
R2::g_pFilesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
AddSearchPathHook.Dispatch((*g_pFilesystem)->m_vtable->AddSearchPath);
ReadFromCacheHook.Dispatch((*g_pFilesystem)->m_vtable->ReadFromCache);
MountVPKHook.Dispatch((*g_pFilesystem)->m_vtable->MountVPK);
}

View File

@ -59,8 +59,7 @@ class IFileSystem
FileHandle_t (*Open)(
IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pOptions, const char* pathID, int64_t unknown);
void (*Close)(IFileSystem* fileSystem, FileHandle_t file);
long long (*Seek)(IFileSystem::VTable2** fileSystem, FileHandle_t file, long long offset, long long whence);
void* unknown2[5];
void* unknown2[6];
bool (*FileExists)(IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pPathID);
};
@ -68,11 +67,8 @@ class IFileSystem
VTable2* m_vtable2;
};
// use the R2 namespace for game funcs
namespace R2
{
extern SourceInterface<IFileSystem>* g_pFilesystem;
std::string ReadVPKFile(const char* path);
std::string ReadVPKOriginalFile(const char* path);
std::string ReadVPKFile(const char* path);
std::string ReadVPKOriginalFile(const char* path);
} // namespace R2
void InitialiseFilesystem(HMODULE baseAddress);
extern SourceInterface<IFileSystem>* g_Filesystem;

119
NorthstarDLL/gameutils.cpp Normal file
View File

@ -0,0 +1,119 @@
#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"));
}

249
NorthstarDLL/gameutils.h Normal file
View File

@ -0,0 +1,249 @@
#pragma once
#include "convar.h"
// memory
class IMemAlloc
{
public:
struct VTable
{
void* unknown[1]; // alloc debug
void* (*Alloc)(IMemAlloc* memAlloc, size_t nSize);
void* unknown2[1]; // realloc debug
void* (*Realloc)(IMemAlloc* memAlloc, void* pMem, size_t nSize);
void* unknown3[1]; // free #1
void (*Free)(IMemAlloc* memAlloc, void* pMem);
void* unknown4[2]; // nullsubs, maybe CrtSetDbgFlag
size_t (*GetSize)(IMemAlloc* memAlloc, void* pMem);
void* unknown5[9]; // they all do literally nothing
void (*DumpStats)(IMemAlloc* memAlloc);
void (*DumpStatsFileBase)(IMemAlloc* memAlloc, const char* pchFileBase);
void* unknown6[4];
int (*heapchk)(IMemAlloc* memAlloc);
};
VTable* m_vtable;
};
extern IMemAlloc* g_pMemAllocSingleton;
typedef IMemAlloc* (*CreateGlobalMemAllocType)();
extern CreateGlobalMemAllocType CreateGlobalMemAlloc;
// cmd.h
enum class ECommandTarget_t
{
CBUF_FIRST_PLAYER = 0,
CBUF_LAST_PLAYER = 1, // MAX_SPLITSCREEN_CLIENTS - 1, MAX_SPLITSCREEN_CLIENTS = 2
CBUF_SERVER = CBUF_LAST_PLAYER + 1,
CBUF_COUNT,
};
enum class cmd_source_t
{
// Added to the console buffer by gameplay code. Generally unrestricted.
kCommandSrcCode,
// Sent from code via engine->ClientCmd, which is restricted to commands visible
// via FCVAR_CLIENTCMD_CAN_EXECUTE.
kCommandSrcClientCmd,
// Typed in at the console or via a user key-bind. Generally unrestricted, although
// the client will throttle commands sent to the server this way to 16 per second.
kCommandSrcUserInput,
// Came in over a net connection as a clc_stringcmd
// host_client will be valid during this state.
//
// Restricted to FCVAR_GAMEDLL commands (but not convars) and special non-ConCommand
// server commands hardcoded into gameplay code (e.g. "joingame")
kCommandSrcNetClient,
// Received from the server as the client
//
// Restricted to commands with FCVAR_SERVER_CAN_EXECUTE
kCommandSrcNetServer,
// Being played back from a demo file
//
// Not currently restricted by convar flag, but some commands manually ignore calls
// from this source. FIXME: Should be heavily restricted as demo commands can come
// from untrusted sources.
kCommandSrcDemoFile,
// Invalid value used when cleared
kCommandSrcInvalid = -1
};
typedef ECommandTarget_t (*Cbuf_GetCurrentPlayerType)();
extern Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer;
// compared to the defs i've seen, this is missing an arg, it could be nTickInterval or source, not sure, guessing it's source
typedef void (*Cbuf_AddTextType)(ECommandTarget_t eTarget, const char* text, cmd_source_t source);
extern Cbuf_AddTextType Cbuf_AddText;
typedef void (*Cbuf_ExecuteType)();
extern Cbuf_ExecuteType Cbuf_Execute;
// commandline stuff
class CCommandLine
{
public:
// based on the defs in the 2013 source sdk, but for some reason has an extra function (may be another CreateCmdLine overload?)
// these seem to line up with what they should be though
virtual void CreateCmdLine(const char* commandline) {}
virtual void CreateCmdLine(int argc, char** argv) {}
virtual void unknown() {}
virtual const char* GetCmdLine(void) const {}
virtual const char* CheckParm(const char* psz, const char** ppszValue = 0) const {}
virtual void RemoveParm() const {}
virtual void AppendParm(const char* pszParm, const char* pszValues) {}
virtual const char* ParmValue(const char* psz, const char* pDefaultVal = 0) const {}
virtual int ParmValue(const char* psz, int nDefaultVal) const {}
virtual float ParmValue(const char* psz, float flDefaultVal) const {}
virtual int ParmCount() const {}
virtual int FindParm(const char* psz) const {}
virtual const char* GetParm(int nIndex) const {}
virtual void SetParm(int nIndex, char const* pParm) {}
// virtual const char** GetParms() const {}
};
// hoststate stuff
enum HostState_t
{
HS_NEW_GAME = 0,
HS_LOAD_GAME,
HS_CHANGE_LEVEL_SP,
HS_CHANGE_LEVEL_MP,
HS_RUN,
HS_GAME_SHUTDOWN,
HS_SHUTDOWN,
HS_RESTART,
};
struct CHostState
{
public:
HostState_t m_iCurrentState;
HostState_t m_iNextState;
float m_vecLocation[3];
float m_angLocation[3];
char m_levelName[32];
char m_mapGroupName[32];
char m_landmarkName[32];
char m_saveName[32];
float m_flShortFrameTime; // run a few one-tick frames to avoid large timesteps while loading assets
bool m_activeGame;
bool m_bRememberLocation;
bool m_bBackgroundLevel;
bool m_bWaitingForConnection;
bool m_bLetToolsOverrideLoadGameEnts; // During a load game, this tells Foundry to override ents that are selected in Hammer.
bool m_bSplitScreenConnect;
bool m_bGameHasShutDownAndFlushedMemory; // This is false once we load a map into memory, and set to true once the map is unloaded and
// all memory flushed
bool m_bWorkshopMapDownloadPending;
};
extern CHostState* g_pHostState;
// cengine stuff
enum EngineQuitState
{
QUIT_NOTQUITTING = 0,
QUIT_TODESKTOP,
QUIT_RESTART
};
enum EngineState_t
{
DLL_INACTIVE = 0, // no dll
DLL_ACTIVE, // engine is focused
DLL_CLOSE, // closing down dll
DLL_RESTART, // engine is shutting down but will restart right away
DLL_PAUSED, // engine is paused, can become active from this state
};
class CEngine
{
public:
virtual void unknown() {} // unsure if this is where
virtual bool Load(bool dedicated, const char* baseDir) {}
virtual void Unload() {}
virtual void SetNextState(EngineState_t iNextState) {}
virtual EngineState_t GetState() {}
virtual void Frame() {}
virtual double GetFrameTime() {}
virtual float GetCurTime() {}
EngineQuitState m_nQuitting;
EngineState_t m_nDllState;
EngineState_t m_nNextDllState;
double m_flCurrentTime;
float m_flFrameTime;
double m_flPreviousTime;
float m_flFilteredTime;
float m_flMinFrameTime; // Expected duration of a frame, or zero if it is unlimited.
};
extern CEngine* g_pEngine;
enum server_state_t
{
ss_dead = 0, // Dead
ss_loading, // Spawning
ss_active, // Running
ss_paused, // Running, but paused
};
extern server_state_t* sv_m_State;
// network stuff
extern ConVar* Cvar_hostport;
// playlist stuff
typedef const char* (*GetCurrentPlaylistType)();
extern GetCurrentPlaylistType GetCurrentPlaylistName;
typedef void (*SetCurrentPlaylistType)(const char* playlistName);
extern SetCurrentPlaylistType SetCurrentPlaylist;
typedef void (*SetPlaylistVarOverrideType)(const char* varName, const char* value);
extern SetPlaylistVarOverrideType SetPlaylistVarOverride;
typedef char* (*GetCurrentPlaylistVarType)(const char* varName, bool useOverrides);
extern GetCurrentPlaylistVarType GetCurrentPlaylistVar;
// server entity stuff
typedef void* (*Server_GetEntityByIndexType)(int index);
extern Server_GetEntityByIndexType Server_GetEntityByIndex;
// auth
extern char* g_LocalPlayerUserID;
extern char* g_LocalPlayerOriginToken;
// misc stuff
typedef void (*ErrorType)(const char* fmt, ...);
extern ErrorType Error;
typedef CCommandLine* (*CommandLineType)();
extern CommandLineType CommandLine;
typedef double (*Plat_FloatTimeType)();
extern Plat_FloatTimeType Plat_FloatTime;
typedef bool (*ThreadInServerFrameThreadType)();
extern ThreadInServerFrameThreadType ThreadInServerFrameThread;
typedef void* (*GetBaseLocalClientType)();
extern GetBaseLocalClientType GetBaseLocalClient;
void InitialiseEngineGameUtilFunctions(HMODULE baseAddress);
void InitialiseServerGameUtilFunctions(HMODULE baseAddress);
void InitialiseTier0GameUtilFunctions(HMODULE baseAddress);

View File

@ -1,193 +1,61 @@
#include "pch.h"
#include "hooks.h"
#include "hookutils.h"
#include "sigscanning.h"
#include "dedicated.h"
#include <iostream>
#include <wchar.h>
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <filesystem>
#include <Psapi.h>
AUTOHOOK_INIT()
typedef LPSTR (*GetCommandLineAType)();
LPSTR GetCommandLineAHook();
// called from the ON_DLL_LOAD macros
__dllLoadCallback::__dllLoadCallback(
eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn)
typedef HMODULE (*LoadLibraryExAType)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
typedef HMODULE (*LoadLibraryAType)(LPCSTR lpLibFileName);
HMODULE LoadLibraryAHook(LPCSTR lpLibFileName);
typedef HMODULE (*LoadLibraryExWType)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
typedef HMODULE (*LoadLibraryWType)(LPCWSTR lpLibFileName);
HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName);
GetCommandLineAType GetCommandLineAOriginal;
LoadLibraryExAType LoadLibraryExAOriginal;
LoadLibraryAType LoadLibraryAOriginal;
LoadLibraryExWType LoadLibraryExWOriginal;
LoadLibraryWType LoadLibraryWOriginal;
void InstallInitialHooks()
{
// parse reliesOn array from string
std::vector<std::string> reliesOnArray;
if (MH_Initialize() != MH_OK)
spdlog::error("MH_Initialize (minhook initialization) failed");
if (reliesOn.length() && reliesOn[0] != '(')
{
reliesOnArray.push_back(reliesOn);
}
else
{
// follows the format (tag, tag, tag)
std::string sCurrentTag;
for (int i = 1; i < reliesOn.length(); i++)
{
if (!isspace(reliesOn[i]))
{
if (reliesOn[i] == ',' || reliesOn[i] == ')')
{
reliesOnArray.push_back(sCurrentTag);
sCurrentTag = "";
}
else
sCurrentTag += reliesOn[i];
}
}
}
switch (side)
{
case eDllLoadCallbackSide::UNSIDED:
{
AddDllLoadCallback(dllName, callback, uniqueStr, reliesOnArray);
break;
}
case eDllLoadCallbackSide::CLIENT:
{
AddDllLoadCallbackForClient(dllName, callback, uniqueStr, reliesOnArray);
break;
}
case eDllLoadCallbackSide::DEDICATED_SERVER:
{
AddDllLoadCallbackForDedicatedServer(dllName, callback, uniqueStr, reliesOnArray);
break;
}
}
HookEnabler hook;
ENABLER_CREATEHOOK(
hook, reinterpret_cast<void*>(&GetCommandLineA), &GetCommandLineAHook, reinterpret_cast<LPVOID*>(&GetCommandLineAOriginal));
ENABLER_CREATEHOOK(
hook, reinterpret_cast<void*>(&LoadLibraryExA), &LoadLibraryExAHook, reinterpret_cast<LPVOID*>(&LoadLibraryExAOriginal));
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryA), &LoadLibraryAHook, reinterpret_cast<LPVOID*>(&LoadLibraryAOriginal));
ENABLER_CREATEHOOK(
hook, reinterpret_cast<void*>(&LoadLibraryExW), &LoadLibraryExWHook, reinterpret_cast<LPVOID*>(&LoadLibraryExWOriginal));
ENABLER_CREATEHOOK(hook, reinterpret_cast<void*>(&LoadLibraryW), &LoadLibraryWHook, reinterpret_cast<LPVOID*>(&LoadLibraryWOriginal));
}
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, ())
LPSTR GetCommandLineAHook()
{
static char* cmdlineModified;
static char* cmdlineOrg;
if (cmdlineOrg == nullptr || cmdlineModified == nullptr)
{
cmdlineOrg = _GetCommandLineA();
cmdlineOrg = GetCommandLineAOriginal();
bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument
bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs");
@ -243,86 +111,77 @@ AUTOHOOK_ABSOLUTEADDR(_GetCommandLineA, GetCommandLineA, LPSTR, WINAPI, ())
return cmdlineModified;
}
std::vector<std::string> calledTags;
// dll load callback stuff
// this allows for code to register callbacks to be run as soon as a dll is loaded, mainly to allow for patches to be made on dll load
struct DllLoadCallback
{
std::string dll;
DllLoadCallbackFuncType callback;
bool called;
};
std::vector<DllLoadCallback*> dllLoadCallbacks;
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback)
{
DllLoadCallback* callbackStruct = new DllLoadCallback;
callbackStruct->dll = dll;
callbackStruct->callback = callback;
callbackStruct->called = false;
dllLoadCallbacks.push_back(callbackStruct);
}
void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback)
{
if (!IsDedicatedServer())
return;
DllLoadCallback* callbackStruct = new DllLoadCallback;
callbackStruct->dll = dll;
callbackStruct->callback = callback;
callbackStruct->called = false;
dllLoadCallbacks.push_back(callbackStruct);
}
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback)
{
if (IsDedicatedServer())
return;
DllLoadCallback* callbackStruct = new DllLoadCallback;
callbackStruct->dll = dll;
callbackStruct->callback = callback;
callbackStruct->called = false;
dllLoadCallbacks.push_back(callbackStruct);
}
void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress)
{
CModule cModule(moduleAddress);
while (true)
for (auto& callbackStruct : dllLoadCallbacks)
{
bool bDoneCalling = true;
for (auto& callbackStruct : GetDllLoadCallbacks())
if (!callbackStruct->called &&
strstr(lpLibFileName + (strlen(lpLibFileName) - callbackStruct->dll.length()), callbackStruct->dll.c_str()) != nullptr)
{
if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
{
bool bShouldContinue = false;
if (!callbackStruct.reliesOn.empty())
{
for (std::string tag : callbackStruct.reliesOn)
{
if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
{
bDoneCalling = false;
bShouldContinue = true;
break;
}
}
}
if (bShouldContinue)
continue;
callbackStruct.callback(moduleAddress);
calledTags.push_back(callbackStruct.tag);
callbackStruct.called = true;
}
callbackStruct->callback(moduleAddress);
callbackStruct->called = true;
}
if (bDoneCalling)
break;
}
}
void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress)
{
CModule cModule(moduleAddress);
while (true)
for (auto& callbackStruct : dllLoadCallbacks)
{
bool bDoneCalling = true;
for (auto& callbackStruct : GetDllLoadCallbacks())
std::wstring wcharStrDll = std::wstring(callbackStruct->dll.begin(), callbackStruct->dll.end());
const wchar_t* callbackDll = wcharStrDll.c_str();
if (!callbackStruct->called && wcsstr(lpLibFileName + (wcslen(lpLibFileName) - wcharStrDll.length()), callbackDll) != nullptr)
{
if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
{
bool bShouldContinue = false;
if (!callbackStruct.reliesOn.empty())
{
for (std::string tag : callbackStruct.reliesOn)
{
if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
{
bDoneCalling = false;
bShouldContinue = true;
break;
}
}
}
if (bShouldContinue)
continue;
callbackStruct.callback(moduleAddress);
calledTags.push_back(callbackStruct.tag);
callbackStruct.called = true;
}
callbackStruct->callback(moduleAddress);
callbackStruct->called = true;
}
if (bDoneCalling)
break;
}
}
@ -349,78 +208,65 @@ void CallAllPendingDLLLoadCallbacks()
}
}
// clang-format off
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, LoadLibraryExA,
HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
// clang-format on
HMODULE LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
{
HMODULE moduleAddress;
// replace xinput dll with one that has ASLR
if (!strncmp(lpLibFileName, "XInput1_3.dll", 14))
{
moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags);
if (!moduleAddress)
HMODULE moduleAddress = LoadLibraryExAOriginal("XInput9_1_0.dll", hFile, dwFlags);
if (moduleAddress)
{
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
}
else
{
MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR);
exit(-1);
return nullptr;
}
return moduleAddress;
}
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)
{
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
}
return moduleAddress;
}
// clang-format off
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryA, LoadLibraryA,
HMODULE, WINAPI, (LPCSTR lpLibFileName))
// clang-format on
HMODULE LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
{
HMODULE moduleAddress = _LoadLibraryA(lpLibFileName);
if (moduleAddress)
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
return moduleAddress;
}
// clang-format off
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExW, LoadLibraryExW,
HMODULE, WINAPI, (LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
// clang-format on
{
HMODULE moduleAddress = _LoadLibraryExW(lpLibFileName, hFile, dwFlags);
HMODULE moduleAddress = LoadLibraryExWOriginal(lpLibFileName, hFile, dwFlags);
if (moduleAddress)
{
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
}
return moduleAddress;
}
// clang-format off
AUTOHOOK_ABSOLUTEADDR(_LoadLibraryW, LoadLibraryW,
HMODULE, WINAPI, (LPCWSTR lpLibFileName))
// clang-format on
HMODULE LoadLibraryWHook(LPCWSTR lpLibFileName)
{
HMODULE moduleAddress = _LoadLibraryW(lpLibFileName);
HMODULE moduleAddress = LoadLibraryWOriginal(lpLibFileName);
if (moduleAddress)
{
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
}
return moduleAddress;
}
void InstallInitialHooks()
{
if (MH_Initialize() != MH_OK)
spdlog::error("MH_Initialize (minhook initialization) failed");
AUTOHOOK_DISPATCH()
}

View File

@ -1,311 +1,11 @@
#pragma once
#include "memory.h"
#include <string>
#include <iostream>
void InstallInitialHooks();
typedef void (*DllLoadCallbackFuncType)(CModule moduleAddress);
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
void AddDllLoadCallbackForDedicatedServer(
std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
void AddDllLoadCallbackForClient(
std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector<std::string> reliesOn = {});
typedef void (*DllLoadCallbackFuncType)(HMODULE moduleAddress);
void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback);
void AddDllLoadCallbackForDedicatedServer(std::string dll, DllLoadCallbackFuncType callback);
void AddDllLoadCallbackForClient(std::string dll, DllLoadCallbackFuncType callback);
void CallAllPendingDLLLoadCallbacks();
// new dll load callback stuff
enum class eDllLoadCallbackSide
{
UNSIDED,
CLIENT,
DEDICATED_SERVER
};
class __dllLoadCallback
{
public:
__dllLoadCallback() = delete;
__dllLoadCallback(
eDllLoadCallbackSide side,
const std::string dllName,
DllLoadCallbackFuncType callback,
std::string uniqueStr,
std::string reliesOn);
};
#define __CONCAT3(x, y, z) x##y##z
#define CONCAT3(x, y, z) __CONCAT3(x, y, z)
#define __CONCAT2(x, y) x##y
#define CONCAT2(x, y) __CONCAT2(x, y)
#define __STR(s) #s
// adds a callback to be called when a given dll is loaded, for creating hooks and such
#define __ON_DLL_LOAD(dllName, side, uniquestr, reliesOn, args) \
void CONCAT2(__dllLoadCallback, uniquestr) args; \
namespace \
{ \
__dllLoadCallback CONCAT2(__dllLoadCallbackInstance, __LINE__)( \
side, dllName, CONCAT2(__dllLoadCallback, uniquestr), __STR(uniquestr), reliesOn); \
} \
void CONCAT2(__dllLoadCallback, uniquestr) args
#define ON_DLL_LOAD(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, "", args)
#define ON_DLL_LOAD_RELIESON(dllName, uniquestr, reliesOn, args) \
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::UNSIDED, uniquestr, __STR(reliesOn), args)
#define ON_DLL_LOAD_CLIENT(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, "", args)
#define ON_DLL_LOAD_CLIENT_RELIESON(dllName, uniquestr, reliesOn, args) \
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::CLIENT, uniquestr, __STR(reliesOn), args)
#define ON_DLL_LOAD_DEDI(dllName, uniquestr, args) __ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, "", args)
#define ON_DLL_LOAD_DEDI_RELIESON(dllName, uniquestr, reliesOn, args) \
__ON_DLL_LOAD(dllName, eDllLoadCallbackSide::DEDICATED_SERVER, uniquestr, __STR(reliesOn), args)
// new macro hook stuff
class __autohook;
class __fileAutohook
{
public:
std::vector<__autohook*> hooks;
void Dispatch();
void DispatchForModule(const char* pModuleName);
};
// initialise autohooks for this file
#define AUTOHOOK_INIT() \
namespace \
{ \
__fileAutohook __FILEAUTOHOOK; \
}
// dispatch all autohooks in this file
#define AUTOHOOK_DISPATCH() __FILEAUTOHOOK.Dispatch();
#define AUTOHOOK_DISPATCH_MODULE(moduleName) __FILEAUTOHOOK.DispatchForModule(__STR(moduleName));
class __autohook
{
public:
enum AddressResolutionMode
{
OFFSET_STRING, // we're using a string that of the format dllname.dll + offset
ABSOLUTE_ADDR, // we're using an absolute address, we don't need to process it at all
PROCADDRESS // resolve using GetModuleHandle and GetProcAddress
};
char* pFuncName;
LPVOID pHookFunc;
LPVOID* ppOrigFunc;
// address resolution props
AddressResolutionMode iAddressResolutionMode;
char* pAddrString = nullptr; // for OFFSET_STRING
LPVOID iAbsoluteAddress = nullptr; // for ABSOLUTE_ADDR
char* pModuleName; // for PROCADDRESS
char* pProcName; // for PROCADDRESS
public:
__autohook() = delete;
__autohook(__fileAutohook* autohook, const char* funcName, LPVOID absoluteAddress, LPVOID* orig, LPVOID func)
: pHookFunc(func), ppOrigFunc(orig), iAbsoluteAddress(absoluteAddress)
{
iAddressResolutionMode = ABSOLUTE_ADDR;
const int iFuncNameStrlen = strlen(funcName) + 1;
pFuncName = new char[iFuncNameStrlen];
memcpy(pFuncName, funcName, iFuncNameStrlen);
autohook->hooks.push_back(this);
}
__autohook(__fileAutohook* autohook, const char* funcName, const char* addrString, LPVOID* orig, LPVOID func)
: pHookFunc(func), ppOrigFunc(orig)
{
iAddressResolutionMode = OFFSET_STRING;
const int iFuncNameStrlen = strlen(funcName) + 1;
pFuncName = new char[iFuncNameStrlen];
memcpy(pFuncName, funcName, iFuncNameStrlen);
const int iAddrStrlen = strlen(addrString) + 1;
pAddrString = new char[iAddrStrlen];
memcpy(pAddrString, addrString, iAddrStrlen);
autohook->hooks.push_back(this);
}
__autohook(__fileAutohook* autohook, const char* funcName, const char* moduleName, const char* procName, LPVOID* orig, LPVOID func)
: pHookFunc(func), ppOrigFunc(orig)
{
iAddressResolutionMode = PROCADDRESS;
const int iFuncNameStrlen = strlen(funcName) + 1;
pFuncName = new char[iFuncNameStrlen];
memcpy(pFuncName, funcName, iFuncNameStrlen);
const int iModuleNameStrlen = strlen(moduleName) + 1;
pModuleName = new char[iModuleNameStrlen];
memcpy(pModuleName, moduleName, iModuleNameStrlen);
const int iProcNameStrlen = strlen(procName) + 1;
pProcName = new char[iProcNameStrlen];
memcpy(pProcName, procName, iProcNameStrlen);
autohook->hooks.push_back(this);
}
~__autohook()
{
delete[] pFuncName;
if (pAddrString)
delete[] pAddrString;
if (pModuleName)
delete[] pModuleName;
if (pProcName)
delete[] pProcName;
}
void Dispatch()
{
LPVOID targetAddr = nullptr;
// determine the address of the function we're hooking
switch (iAddressResolutionMode)
{
case ABSOLUTE_ADDR:
{
targetAddr = iAbsoluteAddress;
break;
}
case OFFSET_STRING:
{
// in the format server.dll + 0xDEADBEEF
int iDllNameEnd = 0;
for (; !isspace(pAddrString[iDllNameEnd]) && pAddrString[iDllNameEnd] != '+'; iDllNameEnd++)
;
char* pModuleName = new char[iDllNameEnd + 1];
memcpy(pModuleName, pAddrString, iDllNameEnd);
pModuleName[iDllNameEnd] = '\0';
// get the module address
const HMODULE pModuleAddr = GetModuleHandleA(pModuleName);
if (!pModuleAddr)
break;
// get the offset string
uintptr_t iOffset = 0;
int iOffsetBegin = iDllNameEnd;
int iOffsetEnd = strlen(pAddrString);
// seek until we hit the start of the number offset
for (; !(pAddrString[iOffsetBegin] >= '0' && pAddrString[iOffsetBegin] <= '9') && pAddrString[iOffsetBegin]; iOffsetBegin++)
;
bool bIsHex =
pAddrString[iOffsetBegin] == '0' && (pAddrString[iOffsetBegin + 1] == 'X' || pAddrString[iOffsetBegin + 1] == 'x');
if (bIsHex)
iOffset = std::stoi(pAddrString + iOffsetBegin + 2, 0, 16);
else
iOffset = std::stoi(pAddrString + iOffsetBegin);
targetAddr = (LPVOID)((uintptr_t)pModuleAddr + iOffset);
break;
}
case PROCADDRESS:
{
targetAddr = GetProcAddress(GetModuleHandleA(pModuleName), pProcName);
break;
}
}
if (MH_CreateHook(targetAddr, pHookFunc, ppOrigFunc) == MH_OK)
{
if (MH_EnableHook(targetAddr) == MH_OK)
spdlog::info("Enabling hook {}", pFuncName);
else
spdlog::error("MH_EnableHook failed for function {}", pFuncName);
}
else
spdlog::error("MH_CreateHook failed for function {}", pFuncName);
}
};
// hook a function at a given offset from a dll to be dispatched with AUTOHOOK_DISPATCH()
#define AUTOHOOK(name, addrString, type, callingConvention, args) \
type callingConvention CONCAT2(__autohookfunc, name) args; \
namespace \
{ \
type(*callingConvention name) args; \
__autohook CONCAT2(__autohook, __LINE__)( \
&__FILEAUTOHOOK, __STR(name), __STR(addrString), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
} \
type callingConvention CONCAT2(__autohookfunc, name) args
// hook a function at a given absolute constant address to be dispatched with AUTOHOOK_DISPATCH()
#define AUTOHOOK_ABSOLUTEADDR(name, addr, type, callingConvention, args) \
type callingConvention CONCAT2(__autohookfunc, name) args; \
namespace \
{ \
type(*callingConvention name) args; \
__autohook \
CONCAT2(__autohook, __LINE__)(&__FILEAUTOHOOK, __STR(name), addr, (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
} \
type callingConvention CONCAT2(__autohookfunc, name) args
// hook a function at a given module and exported function to be dispatched with AUTOHOOK_DISPATCH()
#define AUTOHOOK_PROCADDRESS(name, moduleName, procName, type, callingConvention, args) \
type callingConvention CONCAT2(__autohookfunc, name) args; \
namespace \
{ \
type(*callingConvention name) args; \
__autohook CONCAT2(__autohook, __LINE__)( \
&__FILEAUTOHOOK, __STR(name), __STR(moduleName), __STR(procName), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \
} \
type callingConvention CONCAT2(__autohookfunc, name) \
args
class ManualHook
{
public:
char* pFuncName;
LPVOID pHookFunc;
LPVOID* ppOrigFunc;
public:
ManualHook() = delete;
ManualHook(const char* funcName, LPVOID func);
ManualHook(const char* funcName, LPVOID* orig, LPVOID func);
bool Dispatch(LPVOID addr, LPVOID* orig = nullptr);
};
// hook a function to be dispatched manually later
#define HOOK(varName, originalFunc, type, callingConvention, args) \
namespace \
{ \
type(*callingConvention originalFunc) args; \
} \
type callingConvention CONCAT2(__manualhookfunc, varName) args; \
ManualHook varName = ManualHook(__STR(varName), (LPVOID*)&originalFunc, (LPVOID)CONCAT2(__manualhookfunc, varName)); \
type callingConvention CONCAT2(__manualhookfunc, varName) args
#define HOOK_NOORIG(varName, type, callingConvention, args) \
type callingConvention CONCAT2(__manualhookfunc, varName) args; \
ManualHook varName = ManualHook(__STR(varName), (LPVOID)CONCAT2(__manualhookfunc, varName)); \
type callingConvention CONCAT2(__manualhookfunc, varName) \
args
void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFuncName = "");
#define MAKEHOOK(pTarget, pDetour, ppOriginal) MakeHook(pTarget, pDetour, ppOriginal, __STR(pDetour))

View File

@ -0,0 +1,44 @@
#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);
}
}

23
NorthstarDLL/hookutils.h Normal file
View File

@ -0,0 +1,23 @@
#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)

View File

@ -1,37 +0,0 @@
#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()
}

View File

@ -1,116 +0,0 @@
#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*>();
}

View File

@ -1,45 +0,0 @@
#pragma once
// use the R2 namespace for game funxcs
namespace R2
{
enum class HostState_t
{
HS_NEW_GAME = 0,
HS_LOAD_GAME,
HS_CHANGE_LEVEL_SP,
HS_CHANGE_LEVEL_MP,
HS_RUN,
HS_GAME_SHUTDOWN,
HS_SHUTDOWN,
HS_RESTART,
};
struct CHostState
{
public:
HostState_t m_iCurrentState;
HostState_t m_iNextState;
float m_vecLocation[3];
float m_angLocation[3];
char m_levelName[32];
char m_mapGroupName[32];
char m_landmarkName[32];
char m_saveName[32];
float m_flShortFrameTime; // run a few one-tick frames to avoid large timesteps while loading assets
bool m_activeGame;
bool m_bRememberLocation;
bool m_bBackgroundLevel;
bool m_bWaitingForConnection;
bool m_bLetToolsOverrideLoadGameEnts; // During a load game, this tells Foundry to override ents that are selected in Hammer.
bool m_bSplitScreenConnect;
bool m_bGameHasShutDownAndFlushedMemory; // This is false once we load a map into memory, and set to true once the map is unloaded
// and all memory flushed
bool m_bWorkshopMapDownloadPending;
};
extern CHostState* g_pHostState;
} // namespace R2

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,16 +1,45 @@
#include "pch.h"
#include "keyvalues.h"
#include "modmanager.h"
#include "filesystem.h"
#include "hookutils.h"
#include <fstream>
AUTOHOOK_INIT()
// hook forward defs
typedef char (*KeyValues__LoadFromBufferType)(
void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7);
KeyValues__LoadFromBufferType KeyValues__LoadFromBuffer;
char KeyValues__LoadFromBufferHook(
void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7);
void InitialiseKeyValues(HMODULE baseAddress)
{
HookEnabler hook;
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x426C30, &KeyValues__LoadFromBufferHook, reinterpret_cast<LPVOID*>(&KeyValues__LoadFromBuffer));
}
void* savedFilesystemPtr;
char KeyValues__LoadFromBufferHook(void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7)
{
// this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of
// LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it
// from a function call that always happens before playlists is loaded
if (pFileSystem != nullptr)
savedFilesystemPtr = pFileSystem;
if (!pFileSystem && !strcmp(resourceName, "playlists"))
pFileSystem = savedFilesystemPtr;
return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7);
}
void ModManager::TryBuildKeyValues(const char* filename)
{
spdlog::info("Building KeyValues for file {}", filename);
std::string normalisedPath = g_pModManager->NormaliseModFilePath(fs::path(filename));
std::string normalisedPath = fs::path(filename).lexically_normal().string();
fs::path compiledPath = GetCompiledAssetsPath() / filename;
fs::path compiledDir = compiledPath.parent_path();
fs::create_directories(compiledDir);
@ -25,14 +54,14 @@ void ModManager::TryBuildKeyValues(const char* filename)
// copy over patch kv files, and add #bases to new file, last mods' patches should be applied first
// note: #include should be identical but it's actually just broken, thanks respawn
for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
for (int64_t i = m_loadedMods.size() - 1; i > -1; i--)
{
if (!m_LoadedMods[i].m_bEnabled)
if (!m_loadedMods[i].Enabled)
continue;
size_t fileHash = STR_HASH(normalisedPath);
auto modKv = m_LoadedMods[i].KeyValues.find(fileHash);
if (modKv != m_LoadedMods[i].KeyValues.end())
auto modKv = m_loadedMods[i].KeyValues.find(fileHash);
if (modKv != m_loadedMods[i].KeyValues.end())
{
// should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt"
@ -47,7 +76,7 @@ void ModManager::TryBuildKeyValues(const char* filename)
fs::remove(compiledDir / patchFilePath);
fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
fs::copy_file(m_loadedMods[i].ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
}
}
@ -57,7 +86,7 @@ void ModManager::TryBuildKeyValues(const char* filename)
newKvs += "\"\n";
// load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons)
std::string originalFile = R2::ReadVPKOriginalFile(filename);
std::string originalFile = ReadVPKOriginalFile(filename);
if (!originalFile.length())
{
@ -67,6 +96,7 @@ void ModManager::TryBuildKeyValues(const char* filename)
char rootName[64];
memset(rootName, 0, sizeof(rootName));
rootName[63] = '\0';
// iterate until we hit an ascii char that isn't in a # command or comment to get root obj name
int i = 0;
@ -97,36 +127,11 @@ void ModManager::TryBuildKeyValues(const char* filename)
writeStream.close();
ModOverrideFile overrideFile;
overrideFile.m_pOwningMod = nullptr;
overrideFile.m_Path = normalisedPath;
overrideFile.owningMod = nullptr;
overrideFile.path = normalisedPath;
if (m_ModFiles.find(normalisedPath) == m_ModFiles.end())
m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile));
if (m_modFiles.find(normalisedPath) == m_modFiles.end())
m_modFiles.insert(std::make_pair(normalisedPath, overrideFile));
else
m_ModFiles[normalisedPath] = overrideFile;
}
// 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()
m_modFiles[normalisedPath] = overrideFile;
}

3
NorthstarDLL/keyvalues.h Normal file
View File

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

View File

@ -1,13 +1,18 @@
#include "pch.h"
#include "tier0.h"
#include "languagehooks.h"
#include "gameutils.h"
#include <filesystem>
#include <regex>
AUTOHOOK_INIT()
namespace fs = std::filesystem;
typedef char* (*GetGameLanguageType)();
char* GetGameLanguage();
typedef LANGID (*Tier0_DetectDefaultLanguageType)();
GetGameLanguageType GetGameLanguageOriginal;
bool CheckLangAudioExists(char* lang)
{
std::string path {"r2\\sound\\general_"};
@ -47,10 +52,7 @@ std::string GetAnyInstalledAudioLanguage()
return "NO LANGUAGE DETECTED";
}
// clang-format off
AUTOHOOK(GetGameLanguage, tier0.dll + 0xF560,
char*, __fastcall, ())
// clang-format on
char* GetGameLanguageHook()
{
auto tier0Handle = GetModuleHandleA("tier0.dll");
auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage");
@ -58,7 +60,7 @@ char*, __fastcall, ())
bool& canOriginDictateLang = *(bool*)((char*)tier0Handle + 0xA9A90);
const char* forcedLanguage;
if (Tier0::CommandLine()->CheckParm("-language", &forcedLanguage))
if (CommandLine()->CheckParm("-language", &forcedLanguage))
{
if (!CheckLangAudioExists((char*)forcedLanguage))
{
@ -77,7 +79,7 @@ char*, __fastcall, ())
canOriginDictateLang = true; // let it try
{
auto lang = GetGameLanguage();
auto lang = GetGameLanguageOriginal();
if (!CheckLangAudioExists(lang))
{
if (strcmp(lang, "russian") !=
@ -95,7 +97,7 @@ char*, __fastcall, ())
Tier0_DetectDefaultLanguageType(); // force the global in tier0 to be populated with language inferred from user's system rather than
// defaulting to Russian
canOriginDictateLang = false; // Origin has no say anymore, we will fallback to user's system setup language
auto lang = GetGameLanguage();
auto lang = GetGameLanguageOriginal();
spdlog::info("Detected system language: {}", lang);
if (!CheckLangAudioExists(lang))
{
@ -110,7 +112,8 @@ char*, __fastcall, ())
return lang;
}
ON_DLL_LOAD_CLIENT("tier0.dll", LanguageHooks, (CModule module))
void InitialiseTier0LanguageHooks(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0xF560, &GetGameLanguageHook, reinterpret_cast<LPVOID*>(&GetGameLanguageOriginal));
}

View File

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

View File

@ -1,44 +1,76 @@
#include "pch.h"
#include "latencyflex.h"
#include "hookutils.h"
#include "convar.h"
AUTOHOOK_INIT()
typedef void (*OnRenderStartType)();
OnRenderStartType OnRenderStart;
ConVar* Cvar_r_latencyflex;
void (*m_winelfx_WaitAndBeginFrame)();
HMODULE m_lfxModule {};
typedef void (*PFN_lfx_WaitAndBeginFrame)();
PFN_lfx_WaitAndBeginFrame m_lfx_WaitAndBeginFrame {};
// clang-format off
AUTOHOOK(OnRenderStart, client.dll + 0x1952C0,
void, __fastcall, ())
// clang-format on
void OnRenderStartHook()
{
if (Cvar_r_latencyflex->GetBool() && m_winelfx_WaitAndBeginFrame)
m_winelfx_WaitAndBeginFrame();
// Sleep before next frame as needed to reduce latency.
if (Cvar_r_latencyflex->GetInt())
{
if (m_lfx_WaitAndBeginFrame)
{
m_lfx_WaitAndBeginFrame();
}
}
OnRenderStart();
}
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module))
void InitialiseLatencyFleX(HMODULE baseAddress)
{
// Connect to the LatencyFleX service
// LatencyFleX is an open source vendor agnostic replacement for Nvidia Reflex input latency reduction technology.
// https://ishitatsuyuki.github.io/post/latencyflex/
HMODULE pLfxModule;
const auto lfxModuleName = "latencyflex_layer.dll";
const auto lfxModuleNameFallback = "latencyflex_wine.dll";
auto useFallbackEntrypoints = false;
if (pLfxModule = LoadLibraryA("latencyflex_layer.dll"))
m_winelfx_WaitAndBeginFrame =
reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "lfx_WaitAndBeginFrame")));
else if (pLfxModule = LoadLibraryA("latencyflex_wine.dll"))
m_winelfx_WaitAndBeginFrame =
reinterpret_cast<void (*)()>(reinterpret_cast<void*>(GetProcAddress(pLfxModule, "winelfx_WaitAndBeginFrame")));
else
// Load LatencyFleX library.
m_lfxModule = ::LoadLibraryA(lfxModuleName);
if (m_lfxModule == nullptr && ::GetLastError() == ERROR_MOD_NOT_FOUND)
{
spdlog::info("Unable to load LatencyFleX library, LatencyFleX disabled.");
spdlog::info("LFX: Primary LatencyFleX library not found, trying fallback.");
m_lfxModule = ::LoadLibraryA(lfxModuleNameFallback);
if (m_lfxModule == nullptr)
{
if (::GetLastError() == ERROR_MOD_NOT_FOUND)
{
spdlog::info("LFX: Fallback LatencyFleX library not found.");
}
else
{
spdlog::info("LFX: Error loading fallback LatencyFleX library - Code: {}", ::GetLastError());
}
return;
}
useFallbackEntrypoints = true;
}
else if (m_lfxModule == nullptr)
{
spdlog::info("LFX: Error loading primary LatencyFleX library - Code: {}", ::GetLastError());
return;
}
AUTOHOOK_DISPATCH()
m_lfx_WaitAndBeginFrame = reinterpret_cast<PFN_lfx_WaitAndBeginFrame>(reinterpret_cast<void*>(
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.");
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1952C0, &OnRenderStartHook, reinterpret_cast<LPVOID*>(&OnRenderStart));
}

View File

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

View File

@ -1,299 +0,0 @@
#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)
}

View File

@ -1,51 +0,0 @@
#pragma once
#include "r2engine.h"
#include "convar.h"
#include <unordered_map>
struct PlayerLimitData
{
double lastClientCommandQuotaStart = -1.0;
int numClientCommandsInQuota = 0;
double lastNetChanProcessingLimitStart = -1.0;
double netChanProcessingLimitTime = 0.0;
double lastSayTextLimitStart = -1.0;
int sayTextLimitCount = 0;
float flFrameUserCmdBudget = 0.0;
};
struct UnconnectedPlayerLimitData
{
char ip[16];
double lastQuotaStart = 0.0;
int packetCount = 0;
double timeoutEnd = -1.0;
};
class ServerLimitsManager
{
public:
ConVar* CVar_sv_quota_stringcmdspersecond;
ConVar* Cvar_net_chan_limit_mode;
ConVar* Cvar_net_chan_limit_msec_per_sec;
ConVar* Cvar_sv_querylimit_per_sec;
ConVar* Cvar_sv_max_chat_messages_per_sec;
ConVar* Cvar_sv_antispeedhack_enable;
ConVar* Cvar_sv_antispeedhack_maxtickbudget;
ConVar* Cvar_sv_antispeedhack_budgetincreasemultiplier;
std::unordered_map<R2::CBaseClient*, PlayerLimitData> m_PlayerLimitData;
std::vector<UnconnectedPlayerLimitData> m_UnconnectedPlayerLimitData;
public:
void RunFrame(double flCurrentTime, float flFrameTime);
void AddPlayer(R2::CBaseClient* player);
void RemovePlayer(R2::CBaseClient* player);
bool CheckStringCommandLimits(R2::CBaseClient* player);
bool CheckChatLimits(R2::CBaseClient* player);
};
extern ServerLimitsManager* g_pServerLimits;

View File

@ -37,11 +37,12 @@ class vgui_BaseRichText_vtable
void(__fastcall* SetVerticalScrollbar)(vgui_BaseRichText* self, bool state);
void(__fastcall* SetMaximumCharCount)(vgui_BaseRichText* self, int maxChars);
void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, Color col);
void(__fastcall* InsertColorChange)(vgui_BaseRichText* self, vgui_Color col);
void(__fastcall* InsertIndentChange)(vgui_BaseRichText* self, int pixelsIndent);
void(__fastcall* InsertClickableTextStart)(vgui_BaseRichText* self, const char* pchClickAction);
void(__fastcall* InsertClickableTextEnd)(vgui_BaseRichText* self);
void(__fastcall* InsertPossibleURLString)(vgui_BaseRichText* self, const char* text, Color URLTextColor, Color normalTextColor);
void(__fastcall* InsertPossibleURLString)(
vgui_BaseRichText* self, const char* text, vgui_Color URLTextColor, vgui_Color normalTextColor);
void(__fastcall* InsertFade)(vgui_BaseRichText* self, float flSustain, float flLength);
void(__fastcall* ResetAllFades)(vgui_BaseRichText* self, bool bHold, bool bOnlyExpired, float flNewSustain);
void(__fastcall* SetToFullHeight)(vgui_BaseRichText* self);
@ -80,25 +81,25 @@ LocalChatWriter::SwatchColor swatchColors[4] = {
LocalChatWriter::NetworkNameColor,
};
Color darkColors[8] = {
Color {0, 0, 0, 255},
Color {205, 49, 49, 255},
Color {13, 188, 121, 255},
Color {229, 229, 16, 255},
Color {36, 114, 200, 255},
Color {188, 63, 188, 255},
Color {17, 168, 205, 255},
Color {229, 229, 229, 255}};
vgui_Color darkColors[8] = {
vgui_Color {0, 0, 0, 255},
vgui_Color {205, 49, 49, 255},
vgui_Color {13, 188, 121, 255},
vgui_Color {229, 229, 16, 255},
vgui_Color {36, 114, 200, 255},
vgui_Color {188, 63, 188, 255},
vgui_Color {17, 168, 205, 255},
vgui_Color {229, 229, 229, 255}};
Color lightColors[8] = {
Color {102, 102, 102, 255},
Color {241, 76, 76, 255},
Color {35, 209, 139, 255},
Color {245, 245, 67, 255},
Color {59, 142, 234, 255},
Color {214, 112, 214, 255},
Color {41, 184, 219, 255},
Color {255, 255, 255, 255}};
vgui_Color lightColors[8] = {
vgui_Color {102, 102, 102, 255},
vgui_Color {241, 76, 76, 255},
vgui_Color {35, 209, 139, 255},
vgui_Color {245, 245, 67, 255},
vgui_Color {59, 142, 234, 255},
vgui_Color {214, 112, 214, 255},
vgui_Color {41, 184, 219, 255},
vgui_Color {255, 255, 255, 255}};
class AnsiEscapeParser
{
@ -143,7 +144,7 @@ class AnsiEscapeParser
LocalChatWriter* m_writer;
Next m_next = Next::ControlType;
Color m_expandedColor {0, 0, 0, 0};
vgui_Color m_expandedColor {0, 0, 0, 0};
Next HandleControlType(unsigned long val)
{
@ -189,7 +190,7 @@ class AnsiEscapeParser
// Next values are r,g,b
if (val == 2)
{
m_expandedColor.SetColor(0, 0, 0, 255);
m_expandedColor = {0, 0, 0, 255};
return Next::ForegroundR;
}
// Next value is 8-bit swatch color
@ -218,12 +219,13 @@ class AnsiEscapeParser
unsigned char blue = code % 6;
unsigned char green = ((code - blue) / 6) % 6;
unsigned char red = (code - blue - (green * 6)) / 36;
m_writer->InsertColorChange(Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
m_writer->InsertColorChange(
vgui_Color {(unsigned char)(red * 51), (unsigned char)(green * 51), (unsigned char)(blue * 51), 255});
}
else if (val < UCHAR_MAX)
{
unsigned char brightness = (val - 232) * 10 + 8;
m_writer->InsertColorChange(Color {brightness, brightness, brightness, 255});
m_writer->InsertColorChange(vgui_Color {brightness, brightness, brightness, 255});
}
return Next::ControlType;
@ -234,7 +236,7 @@ class AnsiEscapeParser
if (val >= UCHAR_MAX)
return Next::ControlType;
m_expandedColor[0] = (unsigned char)val;
m_expandedColor.r = (unsigned char)val;
return Next::ForegroundG;
}
@ -243,7 +245,7 @@ class AnsiEscapeParser
if (val >= UCHAR_MAX)
return Next::ControlType;
m_expandedColor[1] = (unsigned char)val;
m_expandedColor.g = (unsigned char)val;
return Next::ForegroundB;
}
@ -252,7 +254,7 @@ class AnsiEscapeParser
if (val >= UCHAR_MAX)
return Next::ControlType;
m_expandedColor[2] = (unsigned char)val;
m_expandedColor.b = (unsigned char)val;
m_writer->InsertColorChange(m_expandedColor);
return Next::ControlType;
}
@ -278,11 +280,12 @@ void LocalChatWriter::Write(const char* str)
if (startOfEscape != str)
{
// There is some text before the escape sequence, just print that
size_t copyChars = startOfEscape - str;
if (copyChars > 255)
copyChars = 255;
strncpy_s(writeBuffer, copyChars + 1, str, copyChars);
strncpy(writeBuffer, str, copyChars);
writeBuffer[copyChars] = 0;
InsertText(writeBuffer);
}
@ -317,8 +320,6 @@ void LocalChatWriter::InsertChar(wchar_t ch)
void LocalChatWriter::InsertText(const char* str)
{
spdlog::info(str);
WCHAR messageUnicode[288];
ConvertANSIToUnicode(str, -1, messageUnicode, 274);
@ -346,7 +347,7 @@ void LocalChatWriter::InsertText(const wchar_t* str)
InsertDefaultFade();
}
void LocalChatWriter::InsertColorChange(Color color)
void LocalChatWriter::InsertColorChange(vgui_Color color)
{
for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next)
{
@ -357,24 +358,20 @@ void LocalChatWriter::InsertColorChange(Color color)
}
}
static Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor)
static vgui_Color GetHudSwatchColor(CHudChat* hud, LocalChatWriter::SwatchColor swatchColor)
{
switch (swatchColor)
{
case LocalChatWriter::MainTextColor:
return hud->m_mainTextColor;
case LocalChatWriter::SameTeamNameColor:
return hud->m_sameTeamColor;
case LocalChatWriter::EnemyTeamNameColor:
return hud->m_enemyTeamColor;
case LocalChatWriter::NetworkNameColor:
return hud->m_networkNameColor;
}
return Color(0, 0, 0, 0);
return vgui_Color {0, 0, 0, 0};
}
void LocalChatWriter::InsertSwatchColorChange(SwatchColor swatchColor)
@ -439,12 +436,12 @@ void LocalChatWriter::InsertDefaultFade()
}
}
ON_DLL_LOAD_CLIENT("client.dll", LocalChatWriter, (CModule module))
void InitialiseLocalChatWriter(HMODULE baseAddress)
{
gGameSettings = module.Offset(0x11BAA48).As<CGameSettings**>();
gChatFadeLength = module.Offset(0x11BAB78).As<CGameFloatVar**>();
gChatFadeSustain = module.Offset(0x11BAC08).As<CGameFloatVar**>();
CHudChat::allHuds = module.Offset(0x11BA9E8).As<CHudChat**>();
gGameSettings = (CGameSettings**)((char*)baseAddress + 0x11BAA48);
gChatFadeLength = (CGameFloatVar**)((char*)baseAddress + 0x11BAB78);
gChatFadeSustain = (CGameFloatVar**)((char*)baseAddress + 0x11BAC08);
CHudChat::allHuds = (CHudChat**)((char*)baseAddress + 0x11BA9E8);
ConvertANSIToUnicode = module.Offset(0x7339A0).As<ConvertANSIToUnicodeType>();
ConvertANSIToUnicode = (ConvertANSIToUnicodeType)((char*)baseAddress + 0x7339A0);
}

View File

@ -1,6 +1,13 @@
#pragma once
#include "pch.h"
#include "color.h"
struct vgui_Color
{
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
};
class vgui_BaseRichText;
@ -11,10 +18,10 @@ class CHudChat
char unknown1[720];
Color m_sameTeamColor;
Color m_enemyTeamColor;
Color m_mainTextColor;
Color m_networkNameColor;
vgui_Color m_sameTeamColor;
vgui_Color m_enemyTeamColor;
vgui_Color m_mainTextColor;
vgui_Color m_networkNameColor;
char unknown2[12];
@ -54,7 +61,7 @@ class LocalChatWriter
void InsertChar(wchar_t ch);
void InsertText(const char* str);
void InsertText(const wchar_t* str);
void InsertColorChange(Color color);
void InsertColorChange(vgui_Color color);
void InsertSwatchColorChange(SwatchColor color);
private:
@ -63,3 +70,5 @@ class LocalChatWriter
const char* ApplyAnsiEscape(const char* escape);
void InsertDefaultFade();
};
void InitialiseLocalChatWriter(HMODULE baseAddress);

View File

@ -1,7 +1,12 @@
#include "pch.h"
#include "logging.h"
#include "sourceconsole.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "hookutils.h"
#include "dedicated.h"
#include "convar.h"
#include "concommand.h"
#include <iomanip>
#include <sstream>
#include "nsprefix.h"
#include <dbghelp.h>
#include <iostream>
@ -247,7 +252,8 @@ void InitialiseConsole()
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
AUTOHOOK_INIT()
SetConsoleCtrlHandler(ConsoleHandlerRoutine, true);
}
void InitialiseLogging()
{
@ -258,7 +264,7 @@ void InitialiseLogging()
ConVar* Cvar_spewlog_enable;
enum class SpewType_t
enum SpewType_t
{
SPEW_MESSAGE = 0,
@ -270,24 +276,56 @@ enum class SpewType_t
SPEW_TYPE_COUNT
};
const std::unordered_map<SpewType_t, const char*> PrintSpewTypes = {
{SpewType_t::SPEW_MESSAGE, "SPEW_MESSAGE"},
{SpewType_t::SPEW_WARNING, "SPEW_WARNING"},
{SpewType_t::SPEW_ASSERT, "SPEW_ASSERT"},
{SpewType_t::SPEW_ERROR, "SPEW_ERROR"},
{SpewType_t::SPEW_LOG, "SPEW_LOG"}};
typedef void (*EngineSpewFuncType)();
EngineSpewFuncType EngineSpewFunc;
// clang-format off
AUTOHOOK(EngineSpewFunc, engine.dll + 0x11CA80,
void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_list args))
// clang-format on
void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format, va_list args)
{
if (!Cvar_spewlog_enable->GetBool())
return;
const char* typeStr = PrintSpewTypes.at(type);
const char* typeStr;
switch (type)
{
case SPEW_MESSAGE:
{
typeStr = "SPEW_MESSAGE";
break;
}
case SPEW_WARNING:
{
typeStr = "SPEW_WARNING";
break;
}
case SPEW_ASSERT:
{
typeStr = "SPEW_ASSERT";
break;
}
case SPEW_ERROR:
{
typeStr = "SPEW_ERROR";
break;
}
case SPEW_LOG:
{
typeStr = "SPEW_LOG";
break;
}
default:
{
typeStr = "SPEW_UNKNOWN";
break;
}
}
char formatted[2048] = {0};
bool bShouldFormat = true;
bool shouldFormat = true;
// because titanfall 2 is quite possibly the worst thing to yet exist, it sometimes gives invalid specifiers which will crash
// ttf2sdk had a way to prevent them from crashing but it doesnt work in debug builds
@ -334,17 +372,19 @@ void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_
default:
{
bShouldFormat = false;
shouldFormat = false;
break;
}
}
}
}
if (bShouldFormat)
if (shouldFormat)
vsnprintf(formatted, sizeof(formatted), format, args);
else
{
spdlog::warn("Failed to format {} \"{}\"", typeStr, format);
}
auto endpos = strlen(formatted);
if (formatted[endpos - 1] == '\n')
@ -353,11 +393,10 @@ void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_
spdlog::info("[SERVER {}] {}", typeStr, formatted);
}
// used for printing the output of status
// clang-format off
AUTOHOOK(Status_ConMsg, engine.dll + 0x15ABD0,
void,, (const char* text, ...))
// clang-format on
typedef void (*Status_ConMsg_Type)(const char* text, ...);
Status_ConMsg_Type Status_ConMsg_Original;
void Status_ConMsg_Hook(const char* text, ...)
{
char formatted[2048];
va_list list;
@ -373,10 +412,10 @@ void,, (const char* text, ...))
spdlog::info(formatted);
}
// clang-format off
AUTOHOOK(CClientState_ProcessPrint, engine.dll + 0x1A1530,
bool,, (void* thisptr, uintptr_t msg))
// clang-format on
typedef bool (*CClientState_ProcessPrint_Type)(__int64 thisptr, __int64 msg);
CClientState_ProcessPrint_Type CClientState_ProcessPrint_Original;
bool CClientState_ProcessPrint_Hook(__int64 thisptr, __int64 msg)
{
char* text = *(char**)(msg + 0x20);
@ -388,8 +427,32 @@ bool,, (void* thisptr, uintptr_t msg))
return true;
}
void InitialiseEngineSpewFuncHooks(HMODULE baseAddress)
{
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x11CA80, EngineSpewFuncHook, reinterpret_cast<LPVOID*>(&EngineSpewFunc));
// Hook print function that status concmd uses to actually print data
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15ABD0, Status_ConMsg_Hook, reinterpret_cast<LPVOID*>(&Status_ConMsg_Original));
// Hook CClientState::ProcessPrint
ENABLER_CREATEHOOK(
hook,
(char*)baseAddress + 0x1A1530,
CClientState_ProcessPrint_Hook,
reinterpret_cast<LPVOID*>(&CClientState_ProcessPrint_Original));
Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged");
}
#include "bitbuf.h"
ConVar* Cvar_cl_showtextmsg;
typedef void (*TextMsg_Type)(__int64);
TextMsg_Type TextMsg_Original;
class ICenterPrint
{
public:
@ -402,22 +465,11 @@ class ICenterPrint
virtual void SetTextColor(int r, int g, int b, int a) = 0;
};
ICenterPrint* pInternalCenterPrint = NULL;
ICenterPrint* internalCenterPrint = NULL;
enum class TextMsgPrintType_t
void TextMsgHook(BFRead* msg)
{
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();
int msg_dest = msg->ReadByte();
char text[256];
msg->ReadString(text, sizeof(text));
@ -427,86 +479,29 @@ void,, (BFRead* msg))
switch (msg_dest)
{
case TextMsgPrintType_t::HUD_PRINTCENTER:
pInternalCenterPrint->Print(text);
case 4: // HUD_PRINTCENTER
internalCenterPrint->Print(text);
break;
default:
spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest);
[[fallthrough]];
case TextMsgPrintType_t::HUD_PRINTCONSOLE:
case 2: // HUD_PRINTCONSOLE
auto endpos = strlen(text);
if (text[endpos - 1] == '\n')
text[endpos - 1] = '\0'; // cut off repeated newline
spdlog::info(text);
break;
}
}
// clang-format off
AUTOHOOK(ConCommand_echo, engine.dll + 0x123680,
void,, (const CCommand& arg))
// clang-format on
void InitialiseClientPrintHooks(HMODULE baseAddress)
{
if (arg.ArgC() >= 2)
spdlog::info("[echo] {}", arg.ArgS());
}
HookEnabler hook;
// 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;
internalCenterPrint = (ICenterPrint*)((char*)baseAddress + 0x216E940);
stream << std::put_time(&currentTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str());
spdlog::default_logger()->sinks().push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(stream.str(), false));
spdlog::flush_on(spdlog::level::info);
}
catch (...)
{
spdlog::error("Failed creating log file!");
MessageBoxA(
0, "Failed creating log file! Make sure the profile directory is writable.", "Northstar Warning", MB_ICONWARNING | MB_OK);
}
}
}
void InitialiseLogging()
{
AllocConsole();
// Bind stdout to receive console output.
// these two lines are responsible for stuff to not show up in the console sometimes, from talking about it on discord
// apparently they were meant to make logging work when using -northstar, however from testing it seems that it doesnt
// work regardless of these two lines
// freopen("CONOUT$", "w", stdout);
// freopen("CONOUT$", "w", stderr);
spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v");
}
ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", EngineSpewFuncHooks, ConVar, (CModule module))
{
AUTOHOOK_DISPATCH_MODULE(engine.dll)
Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged");
}
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientPrintHooks, ConVar, (CModule module))
{
AUTOHOOK_DISPATCH_MODULE(client.dll)
// "TextMsg" usermessage
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x198710, TextMsgHook, reinterpret_cast<LPVOID*>(&TextMsg_Original));
Cvar_cl_showtextmsg = new ConVar("cl_showtextmsg", "1", FCVAR_NONE, "Enable/disable text messages printing on the screen.");
pInternalCenterPrint = module.Offset(0x216E940).As<ICenterPrint*>();
}

View File

@ -1,4 +1,5 @@
#pragma once
#include "context.h"
void CreateLogFiles();
void InitialiseLogging();

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,8 @@
#pragma once
#include "convar.h"
#include "serverpresence.h"
#include <winsock2.h>
#include <string>
#include <cstring>
#include <future>
extern ConVar* Cvar_ns_masterserver_hostname;
extern ConVar* Cvar_ns_curl_log_enable;
struct RemoteModInfo
{
public:
@ -89,6 +82,8 @@ class MasterServerManager
char m_sOwnClientAuthToken[33];
std::string m_sOwnModInfoJson;
std::string m_sUnicodeServerName; // Unicode unescaped version of Cvar_ns_auth_servername for support in cjk characters
std::string m_sUnicodeServerDesc; // Unicode unescaped version of Cvar_ns_auth_serverdesc for support in cjk characters
bool m_bOriginAuthWithMasterServerDone = false;
bool m_bOriginAuthWithMasterServerInProgress = false;
@ -102,7 +97,8 @@ class MasterServerManager
bool m_bNewgameAfterSelfAuth = false;
bool m_bScriptAuthenticatingWithGameServer = false;
bool m_bSuccessfullyAuthenticatedWithGameServer = false;
std::string m_sAuthFailureReason {};
std::string s_authfail_reason {};
bool m_bHasPendingConnectionInfo = false;
RemoteServerConnectionInfo m_pendingConnectionInfo;
@ -112,76 +108,28 @@ class MasterServerManager
bool m_bHasMainMenuPromoData = false;
MainMenuPromoData m_sMainMenuPromoData;
private:
void SetCommonHttpClientOptions(CURL* curl);
public:
MasterServerManager();
void ClearServerList();
void RequestServerList();
void RequestMainMenuPromos();
void AuthenticateOriginWithMasterServer(const char* uid, const char* originToken);
void AuthenticateWithOwnServer(const char* uid, const char* playerToken);
void AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password);
void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize);
void AuthenticateOriginWithMasterServer(char* uid, char* originToken);
void AuthenticateWithOwnServer(char* uid, char* playerToken);
void AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password);
void
AddSelfToServerList(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password);
void UpdateServerMapAndPlaylist(char* map, char* playlist, int playerCount);
void UpdateServerPlayerCount(int playerCount);
void WritePlayerPersistentData(char* playerId, char* pdata, size_t pdataSize);
void RemoveSelfFromServerList();
};
std::string unescape_unicode(const std::string& str);
void UpdateServerInfoFromUnicodeToUTF8();
void InitialiseSharedMasterServer(HMODULE baseAddress);
extern MasterServerManager* g_pMasterServerManager;
extern MasterServerManager* g_MasterServerManager;
extern ConVar* Cvar_ns_masterserver_hostname;
/** 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;
};
extern ConVar* Cvar_ns_server_password;

View File

@ -1,8 +1,6 @@
#include "pch.h"
#include "tier0.h"
#include "maxplayers.h"
AUTOHOOK_INIT()
#include "gameutils.h"
// never set this to anything below 32
#define NEW_MAX_PLAYERS 64
@ -47,33 +45,50 @@ constexpr int Team_PlayerArray_AddedLength = NEW_MAX_PLAYERS - 32;
constexpr int Team_PlayerArray_AddedSize = PAD_NUMBER(Team_PlayerArray_AddedLength * 8, 4);
constexpr int Team_AddedSize = Team_PlayerArray_AddedSize;
bool MaxPlayersIncreaseEnabled()
#include "nsmem.h"
template <class T> void ChangeOffset(void* addr, unsigned int offset)
{
static bool bMaxPlayersIncreaseEnabled = Tier0::CommandLine()->CheckParm("-experimentalmaxplayersincrease");
return bMaxPlayersIncreaseEnabled;
NSMem::BytePatch((uintptr_t)addr, (BYTE*)&offset, sizeof(T));
}
// should we use R2 for this? not sure
namespace R2 // use R2 namespace for game funcs
{
int GetMaxPlayers()
{
if (MaxPlayersIncreaseEnabled())
return NEW_MAX_PLAYERS;
/*
typedef bool(*MatchRecvPropsToSendProps_R_Type)(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable);
MatchRecvPropsToSendProps_R_Type MatchRecvPropsToSendProps_R_Original;
return 32;
bool MatchRecvPropsToSendProps_R_Hook(__int64 lookup, __int64 tableNameBroken, __int64 sendTable, __int64 recvTable)
{
const char* tableName = *(const char**)(sendTable + 0x118);
spdlog::info("MatchRecvPropsToSendProps_R table name {}", tableName);
bool orig = MatchRecvPropsToSendProps_R_Original(lookup, tableNameBroken, sendTable, recvTable);
return orig;
}
typedef bool(*DataTable_SetupReceiveTableFromSendTable_Type)(__int64 sendTable, bool needsDecoder);
DataTable_SetupReceiveTableFromSendTable_Type DataTable_SetupReceiveTableFromSendTable_Original;
bool DataTable_SetupReceiveTableFromSendTable_Hook(__int64 sendTable, bool needsDecoder)
{
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
template <class T> void ChangeOffset(MemoryAddress addr, unsigned int offset)
{
addr.Patch((BYTE*)&offset, sizeof(T));
return DataTable_SetupReceiveTableFromSendTable_Original(sendTable, needsDecoder);
}
*/
// clang-format off
AUTOHOOK(StringTables_CreateStringTable, engine.dll + 0x22E220,
void*,, (void* thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags))
// clang-format on
typedef void* (*StringTables_CreateStringTable_Type)(
__int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags);
StringTables_CreateStringTable_Type StringTables_CreateStringTable_Original;
void* StringTables_CreateStringTable_Hook(
__int64 thisptr, const char* name, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags)
{
// Change the amount of entries to account for a bigger player amount
if (!strcmp(name, "userinfo"))
@ -85,33 +100,36 @@ void*,, (void* thisptr, const char* name, int maxentries, int userdatafixedsize,
maxentries = maxPlayersPowerOf2;
}
return StringTables_CreateStringTable(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags);
return StringTables_CreateStringTable_Original(thisptr, name, maxentries, userdatafixedsize, userdatanetworkbits, flags);
}
ON_DLL_LOAD("engine.dll", MaxPlayersOverride_Engine, (CModule module))
bool MaxPlayersIncreaseEnabled()
{
return CommandLine() && CommandLine()->CheckParm("-experimentalmaxplayersincrease");
}
void InitialiseMaxPlayersOverride_Engine(HMODULE baseAddress)
{
if (!MaxPlayersIncreaseEnabled())
return;
AUTOHOOK_DISPATCH_MODULE(engine.dll)
// patch GetPlayerLimits to ignore the boundary limit
module.Offset(0x116458).Patch("0xEB"); // jle => jmp
ChangeOffset<unsigned char>((char*)baseAddress + 0x116458, 0xEB); // jle => jmp
// patch ED_Alloc to change nFirstIndex
ChangeOffset<int>(module.Offset(0x18F46C + 1), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
ChangeOffset<int>((char*)baseAddress + 0x18F46C + 1, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
// patch CGameServer::SpawnServer to change GetMaxClients inline
ChangeOffset<int>(module.Offset(0x119543 + 2), NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
ChangeOffset<int>((char*)baseAddress + 0x119543 + 2, NEW_MAX_PLAYERS + 8 + 1); // original: 41 (sv.GetMaxClients() + 1)
// patch CGameServer::SpawnServer to change for loop
ChangeOffset<unsigned char>(module.Offset(0x11957F + 2), NEW_MAX_PLAYERS); // original: 32
ChangeOffset<unsigned char>((char*)baseAddress + 0x11957F + 2, NEW_MAX_PLAYERS); // original: 32
// patch CGameServer::SpawnServer to change for loop (there are two)
ChangeOffset<unsigned char>(module.Offset(0x119586 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
ChangeOffset<unsigned char>((char*)baseAddress + 0x119586 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
// patch max players somewhere in CClientState
ChangeOffset<unsigned char>(module.Offset(0x1A162C + 2), NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1)
ChangeOffset<unsigned char>((char*)baseAddress + 0x1A162C + 2, NEW_MAX_PLAYERS - 1); // original: 31 (32 - 1)
// patch max players in userinfo stringtable creation
/*{
@ -124,10 +142,22 @@ ON_DLL_LOAD("engine.dll", MaxPlayersOverride_Engine, (CModule module))
// proper fix below
// patch max players in userinfo stringtable creation loop
ChangeOffset<unsigned char>(module.Offset(0x114C48 + 2), NEW_MAX_PLAYERS); // original: 32
ChangeOffset<unsigned char>((char*)baseAddress + 0x114C48 + 2, NEW_MAX_PLAYERS); // original: 32
// do not load prebaked SendTable message list
module.Offset(0x75859).Patch("EB"); // jnz -> jmp
ChangeOffset<unsigned char>((char*)baseAddress + 0x75859, 0xEB); // jnz -> jmp
HookEnabler hook;
// ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x209000, &MatchRecvPropsToSendProps_R_Hook,
// reinterpret_cast<LPVOID*>(&MatchRecvPropsToSendProps_R_Original)); ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1FACD0,
// &DataTable_SetupReceiveTableFromSendTable_Hook, reinterpret_cast<LPVOID*>(&DataTable_SetupReceiveTableFromSendTable_Original));
ENABLER_CREATEHOOK(
hook,
(char*)baseAddress + 0x22E220,
&StringTables_CreateStringTable_Hook,
reinterpret_cast<LPVOID*>(&StringTables_CreateStringTable_Original));
}
typedef void (*RunUserCmds_Type)(bool a1, float a2);
@ -137,10 +167,7 @@ HMODULE serverBase = 0;
auto RandomIntZeroMax = (__int64(__fastcall*)())0;
// lazy rebuild
// clang-format off
AUTOHOOK(RunUserCmds, server.dll + 0x483D10,
void,, (bool a1, float a2))
// clang-format on
void RunUserCmds_Hook(bool a1, float a2)
{
unsigned char v3; // bl
int v5; // er14
@ -281,160 +308,162 @@ void,, (bool a1, float a2))
}
}
// clang-format off
AUTOHOOK(SendPropArray2, server.dll + 0x12B130,
__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1))
// clang-format on
typedef __int64 (*SendPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1);
SendPropArray2_Type SendPropArray2_Original;
__int64 __fastcall SendPropArray2_Hook(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1)
{
// Change the amount of elements to account for a bigger player amount
if (!strcmp(name, "\"player_array\""))
elements = NEW_MAX_PLAYERS;
return SendPropArray2(recvProp, elements, flags, name, proxyFn, unk1);
return SendPropArray2_Original(recvProp, elements, flags, name, proxyFn, unk1);
}
ON_DLL_LOAD("server.dll", MaxPlayersOverride_Server, (CModule module))
void InitialiseMaxPlayersOverride_Server(HMODULE baseAddress)
{
if (!MaxPlayersIncreaseEnabled())
return;
AUTOHOOK_DISPATCH_MODULE(server.dll)
// get required data
serverBase = (HMODULE)module.m_nAddress;
serverBase = GetModuleHandleA("server.dll");
RandomIntZeroMax = (decltype(RandomIntZeroMax))(GetProcAddress(GetModuleHandleA("vstdlib.dll"), "RandomIntZeroMax"));
// patch max players amount
ChangeOffset<unsigned char>(module.Offset(0x9A44D + 3), NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128)
ChangeOffset<unsigned char>((char*)baseAddress + 0x9A44D + 3, NEW_MAX_PLAYERS); // 0x20 (32) => 0x80 (128)
// patch SpawnGlobalNonRewinding to change forced edict index
ChangeOffset<unsigned char>(module.Offset(0x2BC403 + 2), NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
ChangeOffset<unsigned char>((char*)baseAddress + 0x2BC403 + 2, NEW_MAX_PLAYERS + 1); // original: 33 (32 + 1)
constexpr int CPlayerResource_OriginalSize = 4776;
constexpr int CPlayerResource_AddedSize = PlayerResource_TotalSize;
constexpr int CPlayerResource_ModifiedSize = CPlayerResource_OriginalSize + CPlayerResource_AddedSize;
// CPlayerResource class allocation function - allocate a bigger amount to fit all new max player data
ChangeOffset<unsigned int>(module.Offset(0x5C560A + 1), CPlayerResource_ModifiedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C560A + 1, CPlayerResource_ModifiedSize);
// DT_PlayerResource::m_iPing SendProp
ChangeOffset<unsigned int>(module.Offset(0x5C5059 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C50A8 + 2), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C50E2 + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5059 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50A8 + 2, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C50E2 + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPing DataMap
ChangeOffset<unsigned int>(module.Offset(0xB94598), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>(module.Offset(0xB9459C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB945C0), PlayerResource_Ping_Size);
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94598, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9459C, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB945C0, PlayerResource_Ping_Size);
// DT_PlayerResource::m_iTeam SendProp
ChangeOffset<unsigned int>(module.Offset(0x5C5110 + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C519C + 2), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C517E + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5110 + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C519C + 2, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C517E + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iTeam DataMap
ChangeOffset<unsigned int>(module.Offset(0xB94600), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>(module.Offset(0xB94604), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB94628), PlayerResource_Team_Size);
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94600, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94604, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94628, PlayerResource_Team_Size);
// DT_PlayerResource::m_iPRHealth SendProp
ChangeOffset<unsigned int>(module.Offset(0x5C51C0 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5204 + 2), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C523E + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C51C0 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5204 + 2, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C523E + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPRHealth DataMap
ChangeOffset<unsigned int>(module.Offset(0xB94668), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>(module.Offset(0xB9466C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB94690), PlayerResource_PRHealth_Size);
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94668, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9466C, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94690, PlayerResource_PRHealth_Size);
// DT_PlayerResource::m_bConnected SendProp
ChangeOffset<unsigned int>(module.Offset(0x5C526C + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C52B4 + 2), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C52EE + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C526C + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52B4 + 2, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C52EE + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bConnected DataMap
ChangeOffset<unsigned int>(module.Offset(0xB946D0), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>(module.Offset(0xB946D4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB946F8), PlayerResource_Connected_Size);
ChangeOffset<unsigned int>((char*)baseAddress + 0xB946D0, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946D4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB946F8, PlayerResource_Connected_Size);
// DT_PlayerResource::m_bAlive SendProp
ChangeOffset<unsigned int>(module.Offset(0x5C5321 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5364 + 2), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C539E + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5321 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5364 + 2, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C539E + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bAlive DataMap
ChangeOffset<unsigned int>(module.Offset(0xB94738), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>(module.Offset(0xB9473C), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB94760), PlayerResource_Alive_Size);
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94738, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9473C, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94760, PlayerResource_Alive_Size);
// DT_PlayerResource::m_boolStats SendProp
ChangeOffset<unsigned int>(module.Offset(0x5C53CC + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5414 + 2), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C544E + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C53CC + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5414 + 2, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C544E + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_boolStats DataMap
ChangeOffset<unsigned int>(module.Offset(0xB947A0), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xB947A4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>(module.Offset(0xB947C8), PlayerResource_BoolStats_Size);
ChangeOffset<unsigned int>((char*)baseAddress + 0xB947A0, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947A4, NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB947C8, PlayerResource_BoolStats_Size);
// DT_PlayerResource::m_killStats SendProp
ChangeOffset<unsigned int>(module.Offset(0x5C547C + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C54E2 + 2), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C54FE + 4), PlayerResource_KillStats_Length);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C547C + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54E2 + 2, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C54FE + 4, PlayerResource_KillStats_Length);
// DT_PlayerResource::m_killStats DataMap
ChangeOffset<unsigned int>(module.Offset(0xB94808), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xB9480C), PlayerResource_KillStats_Length);
ChangeOffset<unsigned short>(module.Offset(0xB94830), PlayerResource_KillStats_Size);
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94808, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB9480C, PlayerResource_KillStats_Length);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94830, PlayerResource_KillStats_Size);
// DT_PlayerResource::m_scoreStats SendProp
ChangeOffset<unsigned int>(module.Offset(0x5C5528 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5576 + 2), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C5584 + 4), PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5528 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5576 + 2, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C5584 + 4, PlayerResource_ScoreStats_Length);
// DT_PlayerResource::m_scoreStats DataMap
ChangeOffset<unsigned int>(module.Offset(0xB94870), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xB94874), PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned short>(module.Offset(0xB94898), PlayerResource_ScoreStats_Size);
ChangeOffset<unsigned int>((char*)baseAddress + 0xB94870, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94874, PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned short>((char*)baseAddress + 0xB94898, PlayerResource_ScoreStats_Size);
// CPlayerResource::UpdatePlayerData - m_bConnected
ChangeOffset<unsigned int>(module.Offset(0x5C66EE + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C672E + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C66EE + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C672E + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
// CPlayerResource::UpdatePlayerData - m_iPing
ChangeOffset<unsigned int>(module.Offset(0x5C6394 + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C63DB + 4), CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6394 + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63DB + 4, CPlayerResource_OriginalSize + PlayerResource_Ping_Start);
// CPlayerResource::UpdatePlayerData - m_iTeam
ChangeOffset<unsigned int>(module.Offset(0x5C63FD + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C6442 + 4), CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C63FD + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6442 + 4, CPlayerResource_OriginalSize + PlayerResource_Team_Start);
// CPlayerResource::UpdatePlayerData - m_iPRHealth
ChangeOffset<unsigned int>(module.Offset(0x5C645B + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C64A0 + 4), CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C645B + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64A0 + 4, CPlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
// CPlayerResource::UpdatePlayerData - m_bConnected
ChangeOffset<unsigned int>(module.Offset(0x5C64AA + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C64F0 + 4), CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64AA + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C64F0 + 4, CPlayerResource_OriginalSize + PlayerResource_Connected_Start);
// CPlayerResource::UpdatePlayerData - m_bAlive
ChangeOffset<unsigned int>(module.Offset(0x5C650A + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C654F + 4), CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C650A + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C654F + 4, CPlayerResource_OriginalSize + PlayerResource_Alive_Start);
// CPlayerResource::UpdatePlayerData - m_boolStats
ChangeOffset<unsigned int>(module.Offset(0x5C6557 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C65A5 + 4), CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6557 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65A5 + 4, CPlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
// CPlayerResource::UpdatePlayerData - m_scoreStats
ChangeOffset<unsigned int>(module.Offset(0x5C65C2 + 3), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C65E3 + 4), CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65C2 + 3, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C65E3 + 4, CPlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
// CPlayerResource::UpdatePlayerData - m_killStats
ChangeOffset<unsigned int>(module.Offset(0x5C6654 + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x5C665B + 3), CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C6654 + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x5C665B + 3, CPlayerResource_OriginalSize + PlayerResource_KillStats_Start);
*module.Offset(0x14E7390).As<DWORD*>() = 0;
auto DT_PlayerResource_Construct = module.Offset(0x5C4FE0).As<__int64(__fastcall*)()>();
// GameLoop::RunUserCmds - rebuild
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x483D10, &RunUserCmds_Hook, reinterpret_cast<LPVOID*>(&RunUserCmds_Original));
*(DWORD*)((char*)baseAddress + 0x14E7390) = 0;
auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x5C4FE0);
DT_PlayerResource_Construct();
constexpr int CTeam_OriginalSize = 3336;
@ -442,188 +471,191 @@ ON_DLL_LOAD("server.dll", MaxPlayersOverride_Server, (CModule module))
constexpr int CTeam_ModifiedSize = CTeam_OriginalSize + CTeam_AddedSize;
// CTeam class allocation function - allocate a bigger amount to fit all new team player data
ChangeOffset<unsigned int>(module.Offset(0x23924A + 1), CTeam_ModifiedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0x23924A + 1, CTeam_ModifiedSize);
// CTeam::CTeam - increase memset length to clean newly allocated data
ChangeOffset<unsigned int>(module.Offset(0x2395AE + 2), 256 + CTeam_AddedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0x2395AE + 2, 256 + CTeam_AddedSize);
*module.Offset(0xC945A0).As<DWORD*>() = 0;
auto DT_Team_Construct = module.Offset(0x238F50).As<__int64(__fastcall*)()>();
// hook required to change the size of DT_Team::"player_array"
HookEnabler hook2;
ENABLER_CREATEHOOK(hook2, (char*)baseAddress + 0x12B130, &SendPropArray2_Hook, reinterpret_cast<LPVOID*>(&SendPropArray2_Original));
hook2.~HookEnabler(); // force hook before calling construct function
*(DWORD*)((char*)baseAddress + 0xC945A0) = 0;
auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x238F50);
DT_Team_Construct();
}
// clang-format off
AUTOHOOK(RecvPropArray2, client.dll + 0x1CEDA0,
__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn))
// clang-format on
typedef __int64 (*RecvPropArray2_Type)(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn);
RecvPropArray2_Type RecvPropArray2_Original;
__int64 __fastcall RecvPropArray2_Hook(__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn)
{
// Change the amount of elements to account for a bigger player amount
if (!strcmp(name, "\"player_array\""))
elements = NEW_MAX_PLAYERS;
return RecvPropArray2(recvProp, elements, flags, name, proxyFn);
return RecvPropArray2_Original(recvProp, elements, flags, name, proxyFn);
}
ON_DLL_LOAD("client.dll", MaxPlayersOverride_Client, (CModule module))
void InitialiseMaxPlayersOverride_Client(HMODULE baseAddress)
{
if (!MaxPlayersIncreaseEnabled())
return;
AUTOHOOK_DISPATCH_MODULE(client.dll)
constexpr int C_PlayerResource_OriginalSize = 5768;
constexpr int C_PlayerResource_AddedSize = PlayerResource_TotalSize;
constexpr int C_PlayerResource_ModifiedSize = C_PlayerResource_OriginalSize + C_PlayerResource_AddedSize;
// C_PlayerResource class allocation function - allocate a bigger amount to fit all new max player data
ChangeOffset<unsigned int>(module.Offset(0x164C41 + 1), C_PlayerResource_ModifiedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164C41 + 1, C_PlayerResource_ModifiedSize);
// C_PlayerResource::C_PlayerResource - change loop end value
ChangeOffset<unsigned char>(module.Offset(0x1640C4 + 2), NEW_MAX_PLAYERS - 32);
ChangeOffset<unsigned char>((char*)baseAddress + 0x1640C4 + 2, NEW_MAX_PLAYERS - 32);
// C_PlayerResource::C_PlayerResource - change m_szName address
ChangeOffset<unsigned int>(
module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
(char*)baseAddress + 0x1640D0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
// C_PlayerResource::C_PlayerResource - change m_szName address
ChangeOffset<unsigned int>(
module.Offset(0x1640D0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
(char*)baseAddress + 0x1640D0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start); // appended to the end of the class
// C_PlayerResource::C_PlayerResource - increase memset length to clean newly allocated data
ChangeOffset<unsigned int>(module.Offset(0x1640D0 + 3), 2244 + C_PlayerResource_AddedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1640D0 + 3, 2244 + C_PlayerResource_AddedSize);
// C_PlayerResource::UpdatePlayerName - change m_szName address
ChangeOffset<unsigned int>(module.Offset(0x16431F + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x16431F + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName - change m_szName address 1
ChangeOffset<unsigned int>(module.Offset(0x1645B1 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645B1 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName - change m_szName address 2
ChangeOffset<unsigned int>(module.Offset(0x1645C0 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645C0 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName - change m_szName address 3
ChangeOffset<unsigned int>(module.Offset(0x1645DD + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1645DD + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName address 1
ChangeOffset<unsigned int>(module.Offset(0x164B71 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B71 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName address 2
ChangeOffset<unsigned int>(module.Offset(0x164B9B + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B9B + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 1
ChangeOffset<unsigned int>(module.Offset(0x164641 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164641 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 2
ChangeOffset<unsigned int>(module.Offset(0x164650 + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164650 + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName2 (?) - change m_szName address 3
ChangeOffset<unsigned int>(module.Offset(0x16466D + 3), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x16466D + 3, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 1
ChangeOffset<unsigned int>(module.Offset(0x164BA3 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BA3 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 2
ChangeOffset<unsigned int>(module.Offset(0x164BCE + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BCE + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::GetPlayerName internal func - change m_szName2 (?) address 3
ChangeOffset<unsigned int>(module.Offset(0x164BE7 + 4), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164BE7 + 4, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
// C_PlayerResource::m_szName
ChangeOffset<unsigned int>(module.Offset(0xc350f8), C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned short>(module.Offset(0xc350f8 + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc350f8, C_PlayerResource_OriginalSize + PlayerResource_Name_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc350f8 + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource size
ChangeOffset<unsigned int>(module.Offset(0x163415 + 6), C_PlayerResource_ModifiedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163415 + 6, C_PlayerResource_ModifiedSize);
// DT_PlayerResource::m_iPing RecvProp
ChangeOffset<unsigned int>(module.Offset(0x163492 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x1634D6 + 2), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>(module.Offset(0x163515 + 5), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163492 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1634D6 + 2, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163515 + 5, NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iPing
ChangeOffset<unsigned int>(module.Offset(0xc35170), C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>(module.Offset(0xc35170 + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35170, C_PlayerResource_OriginalSize + PlayerResource_Ping_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35170 + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iTeam RecvProp
ChangeOffset<unsigned int>(module.Offset(0x163549 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x1635C8 + 2), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>(module.Offset(0x1635AD + 5), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163549 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635C8 + 2, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635AD + 5, NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iTeam
ChangeOffset<unsigned int>(module.Offset(0xc351e8), C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>(module.Offset(0xc351e8 + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc351e8, C_PlayerResource_OriginalSize + PlayerResource_Team_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc351e8 + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_iPRHealth RecvProp
ChangeOffset<unsigned int>(module.Offset(0x1635F9 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x163625 + 2), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>(module.Offset(0x163675 + 5), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1635F9 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163625 + 2, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163675 + 5, NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_iPRHealth
ChangeOffset<unsigned int>(module.Offset(0xc35260), C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>(module.Offset(0xc35260 + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35260, C_PlayerResource_OriginalSize + PlayerResource_PRHealth_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35260 + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bConnected RecvProp
ChangeOffset<unsigned int>(module.Offset(0x1636A9 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x1636D5 + 2), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>(module.Offset(0x163725 + 5), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636A9 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1636D5 + 2, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163725 + 5, NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_bConnected
ChangeOffset<unsigned int>(module.Offset(0xc352d8), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>(module.Offset(0xc352d8 + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc352d8, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc352d8 + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_bAlive RecvProp
ChangeOffset<unsigned int>(module.Offset(0x163759 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x163785 + 2), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>(module.Offset(0x1637D5 + 5), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163759 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163785 + 2, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1637D5 + 5, NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_bAlive
ChangeOffset<unsigned int>(module.Offset(0xc35350), C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>(module.Offset(0xc35350 + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35350, C_PlayerResource_OriginalSize + PlayerResource_Alive_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35350 + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_boolStats RecvProp
ChangeOffset<unsigned int>(module.Offset(0x163809 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x163835 + 2), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x163885 + 5), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163809 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163835 + 2, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163885 + 5, NEW_MAX_PLAYERS + 1);
// C_PlayerResource::m_boolStats
ChangeOffset<unsigned int>(module.Offset(0xc353c8), C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xc353c8 + 4), NEW_MAX_PLAYERS + 1);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc353c8, C_PlayerResource_OriginalSize + PlayerResource_BoolStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc353c8 + 4, NEW_MAX_PLAYERS + 1);
// DT_PlayerResource::m_killStats RecvProp
ChangeOffset<unsigned int>(module.Offset(0x1638B3 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x1638E5 + 2), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x163935 + 5), PlayerResource_KillStats_Length);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638B3 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1638E5 + 2, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163935 + 5, PlayerResource_KillStats_Length);
// C_PlayerResource::m_killStats
ChangeOffset<unsigned int>(module.Offset(0xc35440), C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xc35440 + 4), PlayerResource_KillStats_Length);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc35440, C_PlayerResource_OriginalSize + PlayerResource_KillStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc35440 + 4, PlayerResource_KillStats_Length);
// DT_PlayerResource::m_scoreStats RecvProp
ChangeOffset<unsigned int>(module.Offset(0x163969 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x163995 + 2), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>(module.Offset(0x1639E5 + 5), PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163969 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x163995 + 2, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1639E5 + 5, PlayerResource_ScoreStats_Length);
// C_PlayerResource::m_scoreStats
ChangeOffset<unsigned int>(module.Offset(0xc354b8), C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>(module.Offset(0xc354b8 + 4), PlayerResource_ScoreStats_Length);
ChangeOffset<unsigned int>((char*)baseAddress + 0xc354b8, C_PlayerResource_OriginalSize + PlayerResource_ScoreStats_Start);
ChangeOffset<unsigned short>((char*)baseAddress + 0xc354b8 + 4, PlayerResource_ScoreStats_Length);
// C_PlayerResource::GetPlayerName - change m_bConnected address
ChangeOffset<unsigned int>(module.Offset(0x164599 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164599 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
// C_PlayerResource::GetPlayerName2 (?) - change m_bConnected address
ChangeOffset<unsigned int>(module.Offset(0x164629 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164629 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
// C_PlayerResource::GetPlayerName internal func - change m_bConnected address
ChangeOffset<unsigned int>(module.Offset(0x164B13 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164B13 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
// Some other get name func (that seems to be unused) - change m_bConnected address
ChangeOffset<unsigned int>(module.Offset(0x164860 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164860 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
// Some other get name func 2 (that seems to be unused too) - change m_bConnected address
ChangeOffset<unsigned int>(module.Offset(0x164834 + 3), C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
ChangeOffset<unsigned int>((char*)baseAddress + 0x164834 + 3, C_PlayerResource_OriginalSize + PlayerResource_Connected_Start);
*module.Offset(0xC35068).As<DWORD*>() = 0;
auto DT_PlayerResource_Construct = module.Offset(0x163400).As<__int64(__fastcall*)()>();
*(DWORD*)((char*)baseAddress + 0xC35068) = 0;
auto DT_PlayerResource_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x163400);
DT_PlayerResource_Construct();
constexpr int C_Team_OriginalSize = 3200;
@ -631,15 +663,20 @@ ON_DLL_LOAD("client.dll", MaxPlayersOverride_Client, (CModule module))
constexpr int C_Team_ModifiedSize = C_Team_OriginalSize + C_Team_AddedSize;
// C_Team class allocation function - allocate a bigger amount to fit all new team player data
ChangeOffset<unsigned int>(module.Offset(0x182321 + 1), C_Team_ModifiedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0x182321 + 1, C_Team_ModifiedSize);
// C_Team::C_Team - increase memset length to clean newly allocated data
ChangeOffset<unsigned int>(module.Offset(0x1804A2 + 2), 256 + C_Team_AddedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0x1804A2 + 2, 256 + C_Team_AddedSize);
// DT_Team size
ChangeOffset<unsigned int>(module.Offset(0xC3AA0C), C_Team_ModifiedSize);
ChangeOffset<unsigned int>((char*)baseAddress + 0xC3AA0C, C_Team_ModifiedSize);
*module.Offset(0xC3AFF8).As<DWORD*>() = 0;
auto DT_Team_Construct = module.Offset(0x17F950).As<__int64(__fastcall*)()>();
// hook required to change the size of DT_Team::"player_array"
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1CEDA0, &RecvPropArray2_Hook, reinterpret_cast<LPVOID*>(&RecvPropArray2_Original));
hook.~HookEnabler(); // force hook before calling construct function
*(DWORD*)((char*)baseAddress + 0xC3AFF8) = 0;
auto DT_Team_Construct = (__int64(__fastcall*)())((char*)baseAddress + 0x17F950);
DT_Team_Construct();
}

View File

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

View File

@ -1,8 +1,6 @@
#include "pch.h"
#include "memalloc.h"
#include "tier0.h"
using namespace Tier0;
#include "gameutils.h"
// TODO: rename to malloc and free after removing statically compiled .libs
@ -10,8 +8,9 @@ extern "C" void* _malloc_base(size_t n)
{
// allocate into static buffer if g_pMemAllocSingleton isn't initialised
if (!g_pMemAllocSingleton)
TryCreateGlobalMemAlloc();
{
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
}
return g_pMemAllocSingleton->m_vtable->Alloc(g_pMemAllocSingleton, n);
}
@ -23,16 +22,19 @@ extern "C" void* _malloc_base(size_t n)
extern "C" void _free_base(void* p)
{
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);
}
extern "C" void* _realloc_base(void* oldPtr, size_t size)
{
if (!g_pMemAllocSingleton)
TryCreateGlobalMemAlloc();
{
InitialiseTier0GameUtilFunctions(GetModuleHandleA("tier0.dll"));
}
return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size);
}

View File

@ -1,348 +0,0 @@
#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());
}

View File

@ -1,90 +0,0 @@
#pragma once
class MemoryAddress
{
public:
uintptr_t m_nAddress;
public:
MemoryAddress();
MemoryAddress(const uintptr_t nAddress);
MemoryAddress(const void* pAddress);
// operators
operator uintptr_t() const;
operator void*() const;
operator bool() const;
bool operator==(const MemoryAddress& other) const;
bool operator!=(const MemoryAddress& other) const;
bool operator==(const uintptr_t& addr) const;
bool operator!=(const uintptr_t& addr) const;
MemoryAddress operator+(const MemoryAddress& other) const;
MemoryAddress operator-(const MemoryAddress& other) const;
MemoryAddress operator+(const uintptr_t& other) const;
MemoryAddress operator-(const uintptr_t& other) const;
MemoryAddress operator*() const;
template <typename T> T As()
{
return reinterpret_cast<T>(m_nAddress);
}
// traversal
MemoryAddress Offset(const uintptr_t nOffset) const;
MemoryAddress Deref(const int nNumDerefs = 1) const;
// patching
void Patch(const uint8_t* pBytes, const size_t nSize);
void Patch(const std::initializer_list<uint8_t> bytes);
void Patch(const char* pBytes);
void NOP(const size_t nSize);
bool IsMemoryReadable(const size_t nSize);
};
// based on https://github.com/Mauler125/r5sdk/blob/master/r5dev/public/include/module.h
class CModule : public MemoryAddress
{
public:
struct ModuleSections_t
{
ModuleSections_t(void) = default;
ModuleSections_t(const std::string& svSectionName, uintptr_t pSectionBase, size_t nSectionSize)
: m_svSectionName(svSectionName), m_pSectionBase(pSectionBase), m_nSectionSize(nSectionSize)
{
}
bool IsSectionValid(void) const
{
return m_nSectionSize != 0;
}
std::string m_svSectionName; // Name of section.
uintptr_t m_pSectionBase {}; // Start address of section.
size_t m_nSectionSize {}; // Size of section.
};
ModuleSections_t m_ExecutableCode;
ModuleSections_t m_ExceptionTable;
ModuleSections_t m_RunTimeData;
ModuleSections_t m_ReadOnlyData;
private:
std::string m_svModuleName;
uintptr_t m_pModuleBase {};
DWORD m_nModuleSize {};
IMAGE_NT_HEADERS64* m_pNTHeaders = nullptr;
IMAGE_DOS_HEADER* m_pDOSHeader = nullptr;
std::vector<ModuleSections_t> m_vModuleSections;
public:
CModule() = delete; // no default, we need a module name
CModule(const HMODULE pModule);
CModule(const char* pModuleName);
MemoryAddress GetExport(const char* pExportName);
MemoryAddress FindPattern(const uint8_t* pPattern, const char* pMask);
MemoryAddress FindPattern(const char* pPattern);
};

View File

@ -0,0 +1,49 @@
#include "pch.h"
#include "miscclientfixes.h"
#include "hookutils.h"
#include "dedicated.h"
typedef void* (*CrashingWeaponActivityFuncType)(void* a1);
CrashingWeaponActivityFuncType CrashingWeaponActivityFunc0;
CrashingWeaponActivityFuncType CrashingWeaponActivityFunc1;
void* CrashingWeaponActivityFunc0Hook(void* a1)
{
// this return is safe, other functions that use this value seemingly dont care about it being null
if (!a1)
return 0;
return CrashingWeaponActivityFunc0(a1);
}
void* CrashingWeaponActivityFunc1Hook(void* a1)
{
// this return is safe, other functions that use this value seemingly dont care about it being null
if (!a1)
return 0;
return CrashingWeaponActivityFunc1(a1);
}
void InitialiseMiscClientFixes(HMODULE baseAddress)
{
if (IsDedicatedServer())
return;
HookEnabler hook;
// these functions will occasionally pass a null pointer on respawn, unsure what causes this but seems easiest just to return null if
// null, which seems to work fine fucking sucks this has to be fixed like this but unsure what exactly causes this serverside, breaks
// vanilla compatibility to a degree tho will say i have about 0 clue what exactly these functions do, testing this it doesn't even seem
// like they do much of anything i can see tbh
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x5A92D0, &CrashingWeaponActivityFunc0Hook, reinterpret_cast<LPVOID*>(&CrashingWeaponActivityFunc0));
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x5A9310, &CrashingWeaponActivityFunc1Hook, reinterpret_cast<LPVOID*>(&CrashingWeaponActivityFunc1));
// experimental: allow cl_extrapolate to be enabled without cheats
{
void* ptr = (char*)baseAddress + 0x275F9D9;
*((char*)ptr) = (char)0;
}
}

View File

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

View File

@ -1,162 +1,60 @@
#include "pch.h"
#include "misccommands.h"
#include "concommand.h"
#include "playlist.h"
#include "r2engine.h"
#include "r2client.h"
#include "tier0.h"
#include "hoststate.h"
#include "gameutils.h"
#include "masterserver.h"
#include "modmanager.h"
#include "serverauthentication.h"
#include "squirrel.h"
void ConCommand_force_newgame(const CCommand& arg)
{
if (arg.ArgC() < 2)
return;
R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME;
strncpy(R2::g_pHostState->m_levelName, arg.Arg(1), sizeof(R2::g_pHostState->m_levelName));
}
void ConCommand_ns_start_reauth_and_leave_to_lobby(const CCommand& arg)
{
// hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect
g_pMasterServerManager->m_bNewgameAfterSelfAuth = true;
g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken);
}
void ConCommand_ns_end_reauth_and_leave_to_lobby(const CCommand& arg)
{
if (g_pServerAuthentication->m_RemoteAuthenticationData.size())
R2::g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str());
// weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this
if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM)
{
g_pServerAuthentication->m_bNeedLocalAuthForNewgame = true;
// this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta
// fucks things should maybe set this in HostState_NewGame?
R2::SetCurrentPlaylist("tdm");
strcpy(R2::g_pHostState->m_levelName, "mp_lobby");
R2::g_pHostState->m_iNextState = R2::HostState_t::HS_NEW_GAME;
}
}
void AddMiscConCommands()
{
RegisterConCommand(
MAKE_CONCMD(
"force_newgame",
ConCommand_force_newgame,
"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;
RegisterConCommand(
g_pHostState->m_iNextState = HS_NEW_GAME;
strncpy(g_pHostState->m_levelName, arg.Arg(1), sizeof(g_pHostState->m_levelName));
});
MAKE_CONCMD(
"ns_start_reauth_and_leave_to_lobby",
ConCommand_ns_start_reauth_and_leave_to_lobby,
"called by the server, used to reauth and return the player to lobby when leaving a game",
FCVAR_SERVER_CAN_EXECUTE);
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
RegisterConCommand("ns_end_reauth_and_leave_to_lobby", ConCommand_ns_end_reauth_and_leave_to_lobby, "", FCVAR_NONE);
}
// fixes up various cvar flags to have more sane values
void FixupCvarFlags()
{
if (Tier0::CommandLine()->CheckParm("-allowdevcvars"))
{
// strip hidden and devonly cvar flags
int iNumCvarsAltered = 0;
for (auto& pair : R2::g_pCVar->DumpToMap())
MAKE_CONCMD(
"ns_end_reauth_and_leave_to_lobby",
"",
FCVAR_NONE,
[](const CCommand& arg)
{
// strip flags
int flags = pair.second->GetFlags();
if (flags & FCVAR_DEVELOPMENTONLY)
Cbuf_AddText(
Cbuf_GetCurrentPlayer(),
fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(),
cmd_source_t::kCommandSrcCode);
Cbuf_Execute();
// weird way of checking, but check if client script vm is initialised, mainly just to allow players to cancel this
if (g_ClientSquirrelManager->sqvm)
{
flags &= ~FCVAR_DEVELOPMENTONLY;
iNumCvarsAltered++;
g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = true;
// this won't set playlist correctly on remote clients, don't think they can set playlist until they've left which sorta
// fucks things should maybe set this in HostState_NewGame?
SetCurrentPlaylist("tdm");
strcpy(g_pHostState->m_levelName, "mp_lobby");
g_pHostState->m_iNextState = HS_NEW_GAME;
}
if (flags & FCVAR_HIDDEN)
{
flags &= ~FCVAR_HIDDEN;
iNumCvarsAltered++;
}
pair.second->m_nFlags = flags;
}
spdlog::info("Removed {} hidden/devonly cvar flags", iNumCvarsAltered);
}
// make all engine client commands FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS
// these are usually checked through CGameClient::IsEngineClientCommand, but we get more control over this if we just do it through
// cvar flags
const char** ppEngineClientCommands = CModule("engine.dll").Offset(0x7C5EF0).As<const char**>();
int i = 0;
do
{
ConCommandBase* pCommand = R2::g_pCVar->FindCommandBase(ppEngineClientCommands[i]);
if (pCommand) // not all the commands in this array actually exist in respawn source
pCommand->m_nFlags |= FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS;
} while (ppEngineClientCommands[++i]);
// array of cvars and the flags we want to add to them
const std::vector<std::tuple<const char*, uint32_t>> CVAR_FIXUP_ADD_FLAGS = {
// system commands (i.e. necessary for proper functionality)
// servers need to be able to disconnect
{"disconnect", FCVAR_SERVER_CAN_EXECUTE},
// cheat commands
{"give", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"give_server", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"givecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"takecurrentammo", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"switchclass", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"set", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"_setClassVarServer", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"ent_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"ent_throw", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"ent_setname", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"ent_teleport", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"ent_remove", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"ent_remove_all", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"ent_fire", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"particle_create", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"particle_recreate", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"particle_kill", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"test_setteam", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"melee_lunge_ent", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}};
// array of cvars and the flags we want to remove from them
const std::vector<std::tuple<const char*, uint32_t>> CVAR_FIXUP_REMOVE_FLAGS = {
// unsure how this command works, not even sure it's used on retail servers, deffo shouldn't be used on northstar
{"migrateme", FCVAR_SERVER_CAN_EXECUTE | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"recheck", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, // we don't need this on northstar servers, it's for communities
// unsure how these work exactly (rpt system likely somewhat stripped?), removing anyway since they won't be used
{"rpt_client_enable", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS},
{"rpt_password", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}};
for (auto& fixup : CVAR_FIXUP_ADD_FLAGS)
{
ConCommandBase* command = R2::g_pCVar->FindCommandBase(std::get<0>(fixup));
if (command)
command->m_nFlags |= std::get<1>(fixup);
}
for (auto& fixup : CVAR_FIXUP_REMOVE_FLAGS)
{
ConCommandBase* command = R2::g_pCVar->FindCommandBase(std::get<0>(fixup));
if (command)
command->m_nFlags &= ~std::get<1>(fixup);
}
});
}

View File

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

View File

@ -1,7 +1,26 @@
#include "pch.h"
#include "miscserverfixes.h"
#include "hookutils.h"
ON_DLL_LOAD("server.dll", MiscServerFixes, (CModule module))
#include "nsmem.h"
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
module.Offset(0x154A96).NOP(5);
{
NSMem::NOP(ba + 0x154A96, 5);
}
// ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue)
// this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches
{
NSMem::BytePatch(ba + 0x153920, "C3");
}
}

View File

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

View File

@ -1,73 +1,76 @@
#include "pch.h"
#include "miscserverscript.h"
#include "squirrel.h"
#include "masterserver.h"
#include "serverauthentication.h"
#include "gameutils.h"
#include "dedicated.h"
#include "r2client.h"
#include "r2server.h"
#include <filesystem>
// void function NSEarlyWritePlayerPersistenceForLeave( entity player )
SQRESULT SQ_EarlyWritePlayerPersistenceForLeave(HSquirrelVM* sqvm)
// annoying helper function because i can't figure out getting players or entities from sqvm rn
// wish i didn't have to do it like this, but here we are
void* GetPlayerByIndex(int playerIndex)
{
const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
if (!pPlayer)
{
spdlog::warn("NSEarlyWritePlayerPersistenceForLeave got null player");
const int PLAYER_ARRAY_OFFSET = 0x12A53F90;
const int PLAYER_SIZE = 0x2D728;
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
return SQRESULT_NOTNULL;
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());
return SQRESULT_ERROR;
}
R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex];
if (g_pServerAuthentication->m_PlayerAuthenticationData.find(pClient) == g_pServerAuthentication->m_PlayerAuthenticationData.end())
{
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
return SQRESULT_NOTNULL;
}
g_pServerAuthentication->m_PlayerAuthenticationData[pClient].needPersistenceWriteOnLeave = false;
g_pServerAuthentication->WritePersistentData(pClient);
g_ServerAuthenticationManager->m_additionalPlayerData[player].needPersistenceWriteOnLeave = false;
g_ServerAuthenticationManager->WritePersistentData(player);
return SQRESULT_NULL;
}
// bool function NSIsWritingPlayerPersistence()
SQRESULT SQ_IsWritingPlayerPersistence(HSquirrelVM* sqvm)
SQRESULT SQ_IsWritingPlayerPersistence(void* sqvm)
{
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, g_pMasterServerManager->m_bSavingPersistentData);
ServerSq_pushbool(sqvm, g_MasterServerManager->m_bSavingPersistentData);
return SQRESULT_NOTNULL;
}
// bool function NSIsPlayerLocalPlayer( entity player )
SQRESULT SQ_IsPlayerLocalPlayer(HSquirrelVM* sqvm)
// bool function NSIsPlayerIndexLocalPlayer( int playerIndex )
SQRESULT SQ_IsPlayerIndexLocalPlayer(void* sqvm)
{
const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1);
if (!pPlayer)
int playerIndex = ServerSq_getinteger(sqvm, 1);
void* player = GetPlayerByIndex(playerIndex);
if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(player))
{
spdlog::warn("NSIsPlayerLocalPlayer got null player");
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, false);
return SQRESULT_NOTNULL;
ServerSq_pusherror(sqvm, fmt::format("Invalid playerindex {}", playerIndex).c_str());
return SQRESULT_ERROR;
}
R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex];
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, !strcmp(R2::g_pLocalPlayerUserID, pClient->m_UID));
ServerSq_pushbool(sqvm, !strcmp(g_LocalPlayerUserID, (char*)player + 0xF500));
return SQRESULT_NOTNULL;
}
// bool function NSIsDedicated()
SQRESULT SQ_IsDedicated(HSquirrelVM* sqvm)
SQRESULT SQ_IsDedicated(void* sqvm)
{
g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, IsDedicatedServer());
ServerSq_pushbool(sqvm, IsDedicatedServer());
return SQRESULT_NOTNULL;
}
ON_DLL_LOAD_RELIESON("server.dll", MiscServerScriptCommands, ServerSquirrel, (CModule module))
void InitialiseMiscServerScriptCommand(HMODULE baseAddress)
{
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration(
"void", "NSEarlyWritePlayerPersistenceForLeave", "entity player", "", SQ_EarlyWritePlayerPersistenceForLeave);
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence);
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsPlayerLocalPlayer", "entity player", "", SQ_IsPlayerLocalPlayer);
g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated);
g_ServerSquirrelManager->AddFuncRegistration(
"void", "NSEarlyWritePlayerIndexPersistenceForLeave", "int playerIndex", "", SQ_EarlyWritePlayerIndexPersistenceForLeave);
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsWritingPlayerPersistence", "", "", SQ_IsWritingPlayerPersistence);
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsPlayerIndexLocalPlayer", "int playerIndex", "", SQ_IsPlayerIndexLocalPlayer);
g_ServerSquirrelManager->AddFuncRegistration("bool", "NSIsDedicated", "", "", SQ_IsDedicated);
}

View File

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

View File

@ -1,35 +1,37 @@
#include "pch.h"
#include "modlocalisation.h"
#include "hookutils.h"
#include "modmanager.h"
AUTOHOOK_INIT()
typedef bool (*AddLocalisationFileType)(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown);
AddLocalisationFileType AddLocalisationFile;
// clang-format off
AUTOHOOK(AddLocalisationFile, localize.dll + 0x6D80,
bool, __fastcall, (void* pVguiLocalize, const char* path, const char* pathId, char unknown))
// clang-format on
bool loadModLocalisationFiles = true;
bool AddLocalisationFileHook(void* g_pVguiLocalize, const char* path, const char* pathId, char unknown)
{
static bool bLoadModLocalisationFiles = true;
bool ret = AddLocalisationFile(pVguiLocalize, path, pathId, unknown);
bool ret = AddLocalisationFile(g_pVguiLocalize, path, pathId, unknown);
if (ret)
spdlog::info("Loaded localisation file {} successfully", path);
if (!bLoadModLocalisationFiles)
if (!loadModLocalisationFiles)
return ret;
bLoadModLocalisationFiles = false;
loadModLocalisationFiles = false;
for (Mod mod : g_pModManager->m_LoadedMods)
if (mod.m_bEnabled)
for (Mod mod : g_ModManager->m_loadedMods)
if (mod.Enabled)
for (std::string& localisationFile : mod.LocalisationFiles)
AddLocalisationFile(pVguiLocalize, localisationFile.c_str(), pathId, unknown);
AddLocalisationFile(g_pVguiLocalize, localisationFile.c_str(), pathId, unknown);
bLoadModLocalisationFiles = true;
loadModLocalisationFiles = true;
return ret;
}
ON_DLL_LOAD_CLIENT("localize.dll", Localize, (CModule module))
void InitialiseModLocalisation(HMODULE baseAddress)
{
AUTOHOOK_DISPATCH()
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x6D80, AddLocalisationFileHook, reinterpret_cast<LPVOID*>(&AddLocalisationFile));
}

View File

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

View File

@ -4,10 +4,6 @@
#include "concommand.h"
#include "audio.h"
#include "masterserver.h"
#include "filesystem.h"
#include "rpakfilesystem.h"
#include "nsprefix.h"
#include "rapidjson/error/en.h"
#include "rapidjson/document.h"
#include "rapidjson/ostreamwrapper.h"
@ -17,14 +13,17 @@
#include <string>
#include <sstream>
#include <vector>
#include "filesystem.h"
#include "rpakfilesystem.h"
#include "nsprefix.h"
ModManager* g_pModManager;
ModManager* g_ModManager;
Mod::Mod(fs::path modDir, char* jsonBuf)
{
m_bWasReadSuccessfully = false;
wasReadSuccessfully = false;
m_ModDirectory = modDir;
ModDirectory = modDir;
rapidjson_document modJson;
modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf);
@ -107,55 +106,11 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
else
convar->HelpString = "";
convar->Flags = FCVAR_NONE;
// todo: could possibly parse FCVAR names here instead, would be easier
if (convarObj.HasMember("Flags"))
{
// read raw integer flags
if (convarObj["Flags"].IsInt())
convar->Flags = convarObj["Flags"].GetInt();
else if (convarObj["Flags"].IsString())
{
// parse cvar flags from string
// example string: ARCHIVE_PLAYERPROFILE | GAMEDLL
std::string sFlags = convarObj["Flags"].GetString();
sFlags += '|'; // add additional | so we register the last flag
std::string sCurrentFlag;
for (int i = 0; i < sFlags.length(); i++)
{
if (isspace(sFlags[i]))
continue;
// if we encounter a |, add current string as a flag
if (sFlags[i] == '|')
{
bool bHasFlags = false;
int iCurrentFlags;
for (auto& flagPair : g_PrintCommandFlags)
{
if (!sCurrentFlag.compare(flagPair.second))
{
iCurrentFlags = flagPair.first;
bHasFlags = true;
break;
}
}
if (bHasFlags)
convar->Flags |= iCurrentFlags;
else
spdlog::warn("Mod ConVar {} has unknown flag {}", convar->Name, sCurrentFlag);
sCurrentFlag = "";
}
else
sCurrentFlag += sFlags[i];
}
}
}
convar->Flags = convarObj["Flags"].GetInt();
else
convar->Flags = FCVAR_NONE;
ConVars.push_back(convar);
}
@ -172,7 +127,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
ModScript script;
script.Path = scriptObj["Path"].GetString();
script.RunOn = scriptObj["RunOn"].GetString();
script.RsonRunOn = scriptObj["RunOn"].GetString();
if (scriptObj.HasMember("ServerCallback") && scriptObj["ServerCallback"].IsObject())
{
@ -251,7 +206,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
}
}
m_bWasReadSuccessfully = true;
wasReadSuccessfully = true;
}
ModManager::ModManager()
@ -268,7 +223,7 @@ ModManager::ModManager()
void ModManager::LoadMods()
{
if (m_bHasLoadedMods)
if (m_hasLoadedMods)
UnloadMods();
std::vector<fs::path> modDirs;
@ -277,7 +232,7 @@ void ModManager::LoadMods()
fs::remove_all(GetCompiledAssetsPath());
fs::create_directories(GetModFolderPath());
m_DependencyConstants.clear();
DependencyConstants.clear();
// read enabled mods cfg
std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json");
@ -289,10 +244,10 @@ void ModManager::LoadMods()
enabledModsStringStream << (char)enabledModsStream.get();
enabledModsStream.close();
m_EnabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
m_enabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
enabledModsStringStream.str().c_str());
m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject();
m_hasEnabledModsCfg = m_enabledModsCfg.IsObject();
}
// get mod directories
@ -322,41 +277,41 @@ void ModManager::LoadMods()
for (auto& pair : mod.DependencyConstants)
{
if (m_DependencyConstants.find(pair.first) != m_DependencyConstants.end() && m_DependencyConstants[pair.first] != pair.second)
if (DependencyConstants.find(pair.first) != DependencyConstants.end() && DependencyConstants[pair.first] != pair.second)
{
spdlog::error("Constant {} in mod {} already exists in another mod.", pair.first, mod.Name);
mod.m_bWasReadSuccessfully = false;
mod.wasReadSuccessfully = false;
break;
}
if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end())
m_DependencyConstants.emplace(pair);
if (DependencyConstants.find(pair.first) == DependencyConstants.end())
DependencyConstants.emplace(pair);
}
if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str()))
mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue();
if (m_hasEnabledModsCfg && m_enabledModsCfg.HasMember(mod.Name.c_str()))
mod.Enabled = m_enabledModsCfg[mod.Name.c_str()].IsTrue();
else
mod.m_bEnabled = true;
mod.Enabled = true;
if (mod.m_bWasReadSuccessfully)
if (mod.wasReadSuccessfully)
{
spdlog::info("Loaded mod {} successfully", mod.Name);
if (mod.m_bEnabled)
if (mod.Enabled)
spdlog::info("Mod {} is enabled", mod.Name);
else
spdlog::info("Mod {} is disabled", mod.Name);
m_LoadedMods.push_back(mod);
m_loadedMods.push_back(mod);
}
else
spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string());
}
// sort by load prio, lowest-highest
std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; });
std::sort(m_loadedMods.begin(), m_loadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; });
for (Mod& mod : m_LoadedMods)
for (Mod& mod : m_loadedMods)
{
if (!mod.m_bEnabled)
if (!mod.Enabled)
continue;
// register convars
@ -364,15 +319,15 @@ void ModManager::LoadMods()
// preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this
// causes us to leak memory on reload, but not much, potentially find a way to not do this at some point
for (ModConVar* convar : mod.ConVars)
if (!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
if (!g_pCVar->FindVar(convar->Name.c_str())) // make sure convar isn't registered yet, unsure if necessary but idk what
// behaviour is for defining same convar multiple times
new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str());
// read vpk paths
if (fs::exists(mod.m_ModDirectory / "vpk"))
if (fs::exists(mod.ModDirectory / "vpk"))
{
// read vpk cfg
std::ifstream vpkJsonStream(mod.m_ModDirectory / "vpk/vpk.json");
std::ifstream vpkJsonStream(mod.ModDirectory / "vpk/vpk.json");
std::stringstream vpkJsonStringStream;
bool bUseVPKJson = false;
@ -390,7 +345,7 @@ void ModManager::LoadMods()
bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject();
}
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "vpk"))
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "vpk"))
{
// a bunch of checks to make sure we're only adding dir vpks and their paths are good
// note: the game will literally only load vpks with the english prefix
@ -401,24 +356,25 @@ void ModManager::LoadMods()
std::string formattedPath = file.path().filename().string();
// this really fucking sucks but it'll work
std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3);
std::string vpkName =
(file.path().parent_path() / formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3)).string();
ModVPKEntry& modVpk = mod.Vpks.emplace_back();
modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() &&
dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue());
modVpk.m_sVpkPath = (file.path().parent_path() / vpkName).string();
modVpk.m_sVpkPath = vpkName;
if (m_bHasLoadedMods && modVpk.m_bAutoLoad)
(*R2::g_pFilesystem)->m_vtable->MountVPK(*R2::g_pFilesystem, vpkName.c_str());
if (m_hasLoadedMods && modVpk.m_bAutoLoad)
(*g_Filesystem)->m_vtable->MountVPK(*g_Filesystem, vpkName.c_str());
}
}
}
// read rpak paths
if (fs::exists(mod.m_ModDirectory / "paks"))
if (fs::exists(mod.ModDirectory / "paks"))
{
// read rpak cfg
std::ifstream rpakJsonStream(mod.m_ModDirectory / "paks/rpak.json");
std::ifstream rpakJsonStream(mod.ModDirectory / "paks/rpak.json");
std::stringstream rpakJsonStringStream;
bool bUseRpakJson = false;
@ -450,7 +406,7 @@ void ModManager::LoadMods()
}
}
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "paks"))
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "paks"))
{
// ensure we're only loading rpaks
if (fs::is_regular_file(file) && file.path().extension() == ".rpak")
@ -461,39 +417,39 @@ void ModManager::LoadMods()
modPak.m_bAutoLoad =
!bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() &&
dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue());
// postload things
if (!bUseRpakJson ||
(dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName)))
{
modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString();
}
modPak.m_sPakName = pakName;
// not using atm because we need to resolve path to rpak
// if (m_hasLoadedMods && modPak.m_bAutoLoad)
// g_pPakLoadManager->LoadPakAsync(pakName.c_str());
// g_PakLoadManager->LoadPakAsync(pakName.c_str());
}
}
}
// read keyvalues paths
if (fs::exists(mod.m_ModDirectory / "keyvalues"))
if (fs::exists(mod.ModDirectory / "keyvalues"))
{
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "keyvalues"))
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "keyvalues"))
{
if (fs::is_regular_file(file))
{
std::string kvStr =
g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / "keyvalues"));
std::string kvStr = file.path().lexically_relative(mod.ModDirectory / "keyvalues").lexically_normal().string();
mod.KeyValues.emplace(STR_HASH(kvStr), kvStr);
}
}
}
// read pdiff
if (fs::exists(mod.m_ModDirectory / "mod.pdiff"))
if (fs::exists(mod.ModDirectory / "mod.pdiff"))
{
std::ifstream pdiffStream(mod.m_ModDirectory / "mod.pdiff");
std::ifstream pdiffStream(mod.ModDirectory / "mod.pdiff");
if (!pdiffStream.fail())
{
@ -508,17 +464,17 @@ void ModManager::LoadMods()
}
// read bink video paths
if (fs::exists(mod.m_ModDirectory / "media"))
if (fs::exists(mod.ModDirectory / "media"))
{
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "media"))
for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "media"))
if (fs::is_regular_file(file) && file.path().extension() == ".bik")
mod.BinkVideos.push_back(file.path().filename().string());
}
// try to load audio
if (fs::exists(mod.m_ModDirectory / "audio"))
if (fs::exists(mod.ModDirectory / "audio"))
{
for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "audio"))
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "audio"))
{
if (fs::is_regular_file(file) && file.path().extension().string() == ".json")
{
@ -533,23 +489,23 @@ void ModManager::LoadMods()
}
// in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised
for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
for (int64_t i = m_loadedMods.size() - 1; i > -1; i--)
{
if (!m_LoadedMods[i].m_bEnabled)
if (!m_loadedMods[i].Enabled)
continue;
if (fs::exists(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR))
if (fs::exists(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR))
{
for (fs::directory_entry file : fs::recursive_directory_iterator(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR))
for (fs::directory_entry file : fs::recursive_directory_iterator(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR))
{
std::string path =
g_pModManager->NormaliseModFilePath(file.path().lexically_relative(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR));
if (file.is_regular_file() && m_ModFiles.find(path) == m_ModFiles.end())
fs::path path = file.path().lexically_relative(m_loadedMods[i].ModDirectory / MOD_OVERRIDE_DIR).lexically_normal();
if (file.is_regular_file() && m_modFiles.find(path.string()) == m_modFiles.end())
{
ModOverrideFile modFile;
modFile.m_pOwningMod = &m_LoadedMods[i];
modFile.m_Path = path;
m_ModFiles.insert(std::make_pair(path, modFile));
modFile.owningMod = &m_loadedMods[i];
modFile.path = path;
m_modFiles.insert(std::make_pair(path.string(), modFile));
}
}
}
@ -561,9 +517,9 @@ void ModManager::LoadMods()
modinfoDoc.AddMember("Mods", rapidjson_document::GenericValue(rapidjson::kArrayType), modinfoDoc.GetAllocator());
int currentModIndex = 0;
for (Mod& mod : m_LoadedMods)
for (Mod& mod : m_loadedMods)
{
if (!mod.m_bEnabled || (!mod.RequiredOnClient && !mod.Pdiff.size()))
if (!mod.Enabled || (!mod.RequiredOnClient && !mod.Pdiff.size()))
continue;
modinfoDoc["Mods"].PushBack(rapidjson_document::GenericValue(rapidjson::kObjectType), modinfoDoc.GetAllocator());
@ -579,27 +535,27 @@ void ModManager::LoadMods()
buffer.Clear();
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
modinfoDoc.Accept(writer);
g_pMasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString());
g_MasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString());
m_bHasLoadedMods = true;
m_hasLoadedMods = true;
}
void ModManager::UnloadMods()
{
// clean up stuff from mods before we unload
m_ModFiles.clear();
m_modFiles.clear();
fs::remove_all(GetCompiledAssetsPath());
g_CustomAudioManager.ClearAudioOverrides();
if (!m_bHasEnabledModsCfg)
m_EnabledModsCfg.SetObject();
if (!m_hasEnabledModsCfg)
m_enabledModsCfg.SetObject();
for (Mod& mod : m_LoadedMods)
for (Mod& mod : m_loadedMods)
{
// remove all built kvs
for (std::pair<size_t, std::string> kvPaths : mod.KeyValues)
fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.m_ModDirectory));
fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.ModDirectory));
mod.KeyValues.clear();
@ -607,39 +563,27 @@ void ModManager::UnloadMods()
// should we be doing this here or should scripts be doing this manually?
// main issue with doing this here is when we reload mods for connecting to a server, we write enabled mods, which isn't necessarily
// what we wanna do
if (!m_EnabledModsCfg.HasMember(mod.Name.c_str()))
m_EnabledModsCfg.AddMember(
if (!m_enabledModsCfg.HasMember(mod.Name.c_str()))
m_enabledModsCfg.AddMember(
rapidjson_document::StringRefType(mod.Name.c_str()),
rapidjson_document::GenericValue(false),
m_EnabledModsCfg.GetAllocator());
m_enabledModsCfg.GetAllocator());
m_EnabledModsCfg[mod.Name.c_str()].SetBool(mod.m_bEnabled);
m_enabledModsCfg[mod.Name.c_str()].SetBool(mod.Enabled);
}
std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json");
rapidjson::OStreamWrapper writeStreamWrapper(writeStream);
rapidjson::Writer<rapidjson::OStreamWrapper> writer(writeStreamWrapper);
m_EnabledModsCfg.Accept(writer);
m_enabledModsCfg.Accept(writer);
// do we need to dealloc individual entries in m_loadedMods? idk, rework
m_LoadedMods.clear();
}
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;
m_loadedMods.clear();
}
void ModManager::CompileAssetsForFile(const char* filename)
{
size_t fileHash = STR_HASH(NormaliseModFilePath(fs::path(filename)));
size_t fileHash = STR_HASH(fs::path(filename).lexically_normal().string());
if (fileHash == m_hScriptsRsonHash)
BuildScriptsRson();
@ -648,9 +592,9 @@ void ModManager::CompileAssetsForFile(const char* filename)
else
{
// check if we should build keyvalues, depending on whether any of our mods have patch kvs for this file
for (Mod& mod : m_LoadedMods)
for (Mod& mod : m_loadedMods)
{
if (!mod.m_bEnabled)
if (!mod.Enabled)
continue;
if (mod.KeyValues.find(fileHash) != mod.KeyValues.end())
@ -664,7 +608,14 @@ void ModManager::CompileAssetsForFile(const char* filename)
void ConCommand_reload_mods(const CCommand& args)
{
g_pModManager->LoadMods();
g_ModManager->LoadMods();
}
void InitialiseModManager(HMODULE baseAddress)
{
g_ModManager = new ModManager;
RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE);
}
fs::path GetModFolderPath()
@ -675,10 +626,3 @@ fs::path GetCompiledAssetsPath()
{
return fs::path(GetNorthstarPrefix() + COMPILED_ASSETS_SUFFIX);
}
ON_DLL_LOAD_RELIESON("engine.dll", ModManager, (ConCommand, MasterServer), (CModule module))
{
g_pModManager = new ModManager;
RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE);
}

View File

@ -1,15 +1,14 @@
#pragma once
#include "convar.h"
#include "memalloc.h"
#include "squirrel.h"
#include "rapidjson/document.h"
#include <string>
#include <vector>
#include <filesystem>
#include "rapidjson/document.h"
#include "memalloc.h"
namespace fs = std::filesystem;
const std::string MOD_FOLDER_SUFFIX = "/mods";
const std::string REMOTE_MOD_FOLDER_SUFFIX = "/runtime/remote/mods";
const fs::path MOD_OVERRIDE_DIR = "mod";
const std::string COMPILED_ASSETS_SUFFIX = "/runtime/compiled";
@ -25,6 +24,9 @@ struct ModConVar
struct ModScriptCallback
{
public:
// would've liked to make it possible to hook arbitrary codecallbacks, but couldn't find a function that calls some ui ones
// std::string HookedCodeCallback;
ScriptContext Context;
// called before the codecallback is executed
@ -37,7 +39,7 @@ struct ModScript
{
public:
std::string Path;
std::string RunOn;
std::string RsonRunOn;
std::vector<ModScriptCallback> Callbacks;
};
@ -62,10 +64,8 @@ class Mod
{
public:
// runtime stuff
bool m_bEnabled = true;
bool m_bWasReadSuccessfully = false;
fs::path m_ModDirectory;
// bool m_bIsRemote;
fs::path ModDirectory;
bool Enabled = true;
// mod.json stuff:
@ -100,9 +100,14 @@ class Mod
std::vector<ModRpakEntry> Rpaks;
std::unordered_map<std::string, std::string>
RpakAliases; // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite
// iterated over to create squirrel VM constants depending if a mod exists or not.
// this only exists because we cannot access g_ModManager whilst mods are being loaded for the first time for some reason.
std::unordered_map<std::string, std::string> DependencyConstants;
// other stuff
bool wasReadSuccessfully = false;
public:
Mod(fs::path modPath, char* jsonBuf);
};
@ -110,40 +115,42 @@ class Mod
struct ModOverrideFile
{
public:
Mod* m_pOwningMod;
fs::path m_Path;
Mod* owningMod;
fs::path path;
};
class ModManager
{
private:
bool m_bHasLoadedMods = false;
bool m_bHasEnabledModsCfg;
rapidjson_document m_EnabledModsCfg;
bool m_hasLoadedMods = false;
bool m_hasEnabledModsCfg;
rapidjson_document m_enabledModsCfg;
// precalculated hashes
size_t m_hScriptsRsonHash;
size_t m_hPdefHash;
public:
std::vector<Mod> m_LoadedMods;
std::unordered_map<std::string, ModOverrideFile> m_ModFiles;
std::unordered_map<std::string, std::string> m_DependencyConstants;
std::vector<Mod> m_loadedMods;
std::unordered_map<std::string, ModOverrideFile> m_modFiles;
// iterated over to create squirrel VM constants depending if a mod exists or not.
// here because constants are global anyways.
std::unordered_map<std::string, std::string> DependencyConstants;
public:
ModManager();
void LoadMods();
void UnloadMods();
std::string NormaliseModFilePath(const fs::path path);
void CompileAssetsForFile(const char* filename);
// compile asset type stuff, these are done in files under runtime/compiled/
// compile asset type stuff, these are done in files under Mods/Compiled/
void BuildScriptsRson();
void TryBuildKeyValues(const char* filename);
void BuildPdef();
};
void InitialiseModManager(HMODULE baseAddress);
fs::path GetModFolderPath();
fs::path GetCompiledAssetsPath();
extern ModManager* g_pModManager;
extern ModManager* g_ModManager;

193
NorthstarDLL/nsmem.h Normal file
View File

@ -0,0 +1,193 @@
#pragma once
#include "pch.h"
// KittenPopo's memory stuff, made for northstar (because I really can't handle working with northstar's original memory stuff tbh)
#pragma region Pattern Scanning
namespace NSMem
{
inline std::vector<int> HexBytesToString(const char* str)
{
std::vector<int> patternNums;
int size = strlen(str);
for (int i = 0; i < size; i++)
{
char c = str[i];
// If this is a space character, ignore it
if (c == ' ' || c == '\t')
continue;
if (c == '?')
{
// Add a wildcard (-1)
patternNums.push_back(-1);
}
else if (i < size - 1)
{
BYTE result = 0;
for (int j = 0; j < 2; j++)
{
int val = 0;
char c = *(str + i + j);
if (c >= 'a')
{
val = c - 'a' + 0xA;
}
else if (c >= 'A')
{
val = c - 'A' + 0xA;
}
else if (isdigit(c))
{
val = c - '0';
}
else
{
assert(false);
val = -1;
}
result += (j == 0) ? val * 16 : val;
}
patternNums.push_back(result);
}
i++;
}
return patternNums;
}
inline void* PatternScan(void* module, const int* pattern, int patternSize, int offset)
{
if (!module)
return NULL;
auto dosHeader = (PIMAGE_DOS_HEADER)module;
auto ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)module + dosHeader->e_lfanew);
auto sizeOfImage = ntHeaders->OptionalHeader.SizeOfImage;
auto scanBytes = (BYTE*)module;
for (auto i = 0; i < sizeOfImage - patternSize; ++i)
{
bool found = true;
for (auto j = 0; j < patternSize; ++j)
{
if (scanBytes[i + j] != pattern[j] && pattern[j] != -1)
{
found = false;
break;
}
}
if (found)
{
uintptr_t addressInt = (uintptr_t)(&scanBytes[i]) + offset;
return (uint8_t*)addressInt;
}
}
return nullptr;
}
inline void* PatternScan(const char* moduleName, const char* pattern, int offset = 0)
{
std::vector<int> patternNums = HexBytesToString(pattern);
return PatternScan(GetModuleHandleA(moduleName), &patternNums[0], patternNums.size(), offset);
}
inline void BytePatch(uintptr_t address, const BYTE* vals, int size)
{
WriteProcessMemory(GetCurrentProcess(), (LPVOID)address, vals, size, NULL);
}
inline void BytePatch(uintptr_t address, std::initializer_list<BYTE> vals)
{
std::vector<BYTE> bytes = vals;
if (!bytes.empty())
BytePatch(address, &bytes[0], bytes.size());
}
inline void BytePatch(uintptr_t address, const char* bytesStr)
{
std::vector<int> byteInts = HexBytesToString(bytesStr);
std::vector<BYTE> bytes;
for (int v : byteInts)
bytes.push_back(v);
if (!bytes.empty())
BytePatch(address, &bytes[0], bytes.size());
}
inline void NOP(uintptr_t address, int size)
{
BYTE* buf = (BYTE*)malloc(size);
memset(buf, 0x90, size);
BytePatch(address, buf, size);
free(buf);
}
inline bool IsMemoryReadable(void* ptr, size_t size)
{
static SYSTEM_INFO sysInfo;
if (!sysInfo.dwPageSize)
GetSystemInfo(&sysInfo); // This should always be 4096 unless ur playing on NES or some shit but whatever
MEMORY_BASIC_INFORMATION memInfo;
if (!VirtualQuery(ptr, &memInfo, sizeof(memInfo)))
return false;
if (memInfo.RegionSize < size)
return false;
return (memInfo.State & MEM_COMMIT) && !(memInfo.Protect & PAGE_NOACCESS);
}
} // namespace NSMem
#pragma region KHOOK
struct KHookPatternInfo
{
const char *moduleName, *pattern;
int offset = 0;
KHookPatternInfo(const char* moduleName, const char* pattern, int offset = 0) : moduleName(moduleName), pattern(pattern), offset(offset)
{
}
};
struct KHook
{
KHookPatternInfo targetFunc;
void* targetFuncAddr;
void* hookFunc;
void** original;
static inline std::vector<KHook*> _allHooks;
KHook(KHookPatternInfo targetFunc, void* hookFunc, void** original) : targetFunc(targetFunc)
{
this->hookFunc = hookFunc;
this->original = original;
_allHooks.push_back(this);
}
bool Setup()
{
targetFuncAddr = NSMem::PatternScan(targetFunc.moduleName, targetFunc.pattern, targetFunc.offset);
if (!targetFuncAddr)
return false;
return (MH_CreateHook(targetFuncAddr, hookFunc, original) == MH_OK) && (MH_EnableHook(targetFuncAddr) == MH_OK);
}
};
#define KHOOK(name, funcPatternInfo, returnType, convention, args) \
returnType convention hk##name args; \
auto o##name = (returnType(convention*) args)0; \
KHook k##name = KHook(KHookPatternInfo funcPatternInfo, reinterpret_cast<void*>(&hk##name), (void**)&o##name); \
returnType convention hk##name args
#pragma endregion

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
#include "pch.h"
#include "modmanager.h"
#include "filesystem.h"
#include "hookutils.h"
#include "pdef.h"
#include <map>
#include <sstream>
#include <fstream>
@ -14,11 +14,11 @@ void ModManager::BuildPdef()
fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX);
fs::remove(MOD_PDEF_PATH);
std::string pdef = R2::ReadVPKOriginalFile(VPK_PDEF_PATH);
std::string pdef = ReadVPKOriginalFile(VPK_PDEF_PATH);
for (Mod& mod : m_LoadedMods)
for (Mod& mod : m_loadedMods)
{
if (!mod.m_bEnabled || !mod.Pdiff.size())
if (!mod.Enabled || !mod.Pdiff.size())
continue;
// this code probably isn't going to be pretty lol
@ -107,11 +107,11 @@ void ModManager::BuildPdef()
writeStream.close();
ModOverrideFile overrideFile;
overrideFile.m_pOwningMod = nullptr;
overrideFile.m_Path = VPK_PDEF_PATH;
overrideFile.owningMod = nullptr;
overrideFile.path = VPK_PDEF_PATH;
if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end())
m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile));
if (m_modFiles.find(VPK_PDEF_PATH) == m_modFiles.end())
m_modFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile));
else
m_ModFiles[VPK_PDEF_PATH] = overrideFile;
m_modFiles[VPK_PDEF_PATH] = overrideFile;
}

View File

@ -1,94 +1,30 @@
#include "pch.h"
#include "playlist.h"
#include "nsmem.h"
#include "concommand.h"
#include "convar.h"
#include "gameutils.h"
#include "hookutils.h"
#include "squirrel.h"
#include "hoststate.h"
#include "serverpresence.h"
AUTOHOOK_INIT()
typedef char (*Onclc_SetPlaylistVarOverrideType)(void* a1, void* a2);
Onclc_SetPlaylistVarOverrideType Onclc_SetPlaylistVarOverride;
// use the R2 namespace for game funcs
namespace R2
{
const char* (*GetCurrentPlaylistName)();
void (*SetCurrentPlaylist)(const char* pPlaylistName);
void (*SetPlaylistVarOverride)(const char* pVarName, const char* pValue);
const char* (*GetCurrentPlaylistVar)(const char* pVarName, bool bUseOverrides);
} // namespace R2
typedef int (*GetCurrentGamemodeMaxPlayersType)();
GetCurrentGamemodeMaxPlayersType GetCurrentGamemodeMaxPlayers;
// function type defined in gameutils.h
SetPlaylistVarOverrideType SetPlaylistVarOverrideOriginal;
GetCurrentPlaylistVarType GetCurrentPlaylistVarOriginal;
ConVar* Cvar_ns_use_clc_SetPlaylistVarOverride;
// clang-format off
AUTOHOOK(clc_SetPlaylistVarOverride__Process, engine.dll + 0x222180,
char, __fastcall, (void* a1, void* a2))
// clang-format on
{
// the private_match playlist on mp_lobby is the only situation where there should be any legitimate sending of this netmessage
if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(R2::GetCurrentPlaylistName(), "private_match") ||
strcmp(R2::g_pHostState->m_levelName, "mp_lobby"))
return 1;
return clc_SetPlaylistVarOverride__Process(a1, a2);
}
// clang-format off
AUTOHOOK(SetCurrentPlaylist, engine.dll + 0x18EB20,
bool, __fastcall, (const char* pPlaylistName))
// clang-format on
{
bool bSuccess = SetCurrentPlaylist(pPlaylistName);
if (bSuccess)
{
spdlog::info("Set playlist to {}", R2::GetCurrentPlaylistName());
g_pServerPresence->SetPlaylist(R2::GetCurrentPlaylistName());
}
return bSuccess;
}
// clang-format off
AUTOHOOK(SetPlaylistVarOverride, engine.dll + 0x18ED00,
void, __fastcall, (const char* pVarName, const char* pValue))
// clang-format on
{
if (strlen(pValue) >= 64)
return;
SetPlaylistVarOverride(pVarName, pValue);
}
// clang-format off
AUTOHOOK(GetCurrentPlaylistVar, engine.dll + 0x18C680,
const char*, __fastcall, (const char* pVarName, bool bUseOverrides))
// clang-format on
{
if (!bUseOverrides && !strcmp(pVarName, "max_players"))
bUseOverrides = true;
return GetCurrentPlaylistVar(pVarName, bUseOverrides);
}
// clang-format off
AUTOHOOK(GetCurrentGamemodeMaxPlayers, engine.dll + 0x18C430,
int, __fastcall, ())
// clang-format on
{
const char* pMaxPlayers = R2::GetCurrentPlaylistVar("max_players", 0);
if (!pMaxPlayers)
return GetCurrentGamemodeMaxPlayers();
int iMaxPlayers = atoi(pMaxPlayers);
return iMaxPlayers;
}
void ConCommand_playlist(const CCommand& args)
{
if (args.ArgC() < 2)
return;
R2::SetCurrentPlaylist(args.Arg(1));
SetCurrentPlaylist(args.Arg(1));
}
void ConCommand_setplaylistvaroverride(const CCommand& args)
@ -97,34 +33,74 @@ void ConCommand_setplaylistvaroverride(const CCommand& args)
return;
for (int i = 1; i < args.ArgC(); i += 2)
R2::SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1));
SetPlaylistVarOverride(args.Arg(i), args.Arg(i + 1));
}
ON_DLL_LOAD_RELIESON("engine.dll", PlaylistHooks, (ConCommand, ConVar), (CModule module))
char Onclc_SetPlaylistVarOverrideHook(void* a1, void* a2)
{
AUTOHOOK_DISPATCH()
// the private_match playlist is the only situation where there should be any legitimate sending of this netmessage
// todo: check mp_lobby here too
if (!Cvar_ns_use_clc_SetPlaylistVarOverride->GetBool() || strcmp(GetCurrentPlaylistName(), "private_match"))
return 1;
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)>();
return Onclc_SetPlaylistVarOverride(a1, a2);
}
// 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);
void SetPlaylistVarOverrideHook(const char* varName, const char* value)
{
if (strlen(value) >= 64)
return;
SetPlaylistVarOverrideOriginal(varName, value);
}
char* GetCurrentPlaylistVarHook(const char* varName, bool useOverrides)
{
if (!useOverrides && !strcmp(varName, "max_players"))
useOverrides = true;
return GetCurrentPlaylistVarOriginal(varName, useOverrides);
}
int GetCurrentGamemodeMaxPlayersHook()
{
char* maxPlayersStr = GetCurrentPlaylistVar("max_players", 0);
if (!maxPlayersStr)
return GetCurrentGamemodeMaxPlayers();
int maxPlayers = atoi(maxPlayersStr);
return maxPlayers;
}
void InitialisePlaylistHooks(HMODULE baseAddress)
{
RegisterConCommand("setplaylist", ConCommand_playlist, "Sets the current playlist", FCVAR_NONE);
RegisterConCommand("setplaylistvaroverrides", ConCommand_setplaylistvaroverride, "sets a playlist var override", FCVAR_NONE);
// note: clc_SetPlaylistVarOverride is pretty insecure, since it allows for entirely arbitrary playlist var overrides to be sent to the
// server, this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be
// server this is somewhat restricted on custom servers to prevent it being done outside of private matches, but ideally it should be
// disabled altogether, since the custom menus won't use it anyway this should only really be accepted if you want vanilla client
// compatibility
Cvar_ns_use_clc_SetPlaylistVarOverride = new ConVar(
"ns_use_clc_SetPlaylistVarOverride", "0", FCVAR_GAMEDLL, "Whether the server should accept clc_SetPlaylistVarOverride messages");
HookEnabler hook;
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x222180, &Onclc_SetPlaylistVarOverrideHook, reinterpret_cast<LPVOID*>(&Onclc_SetPlaylistVarOverride));
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x18ED00, &SetPlaylistVarOverrideHook, reinterpret_cast<LPVOID*>(&SetPlaylistVarOverrideOriginal));
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x18C680, &GetCurrentPlaylistVarHook, reinterpret_cast<LPVOID*>(&GetCurrentPlaylistVarOriginal));
ENABLER_CREATEHOOK(
hook, (char*)baseAddress + 0x18C430, &GetCurrentGamemodeMaxPlayersHook, reinterpret_cast<LPVOID*>(&GetCurrentGamemodeMaxPlayers));
uintptr_t ba = (uintptr_t)baseAddress;
// patch to prevent clc_SetPlaylistVarOverride from being able to crash servers if we reach max overrides due to a call to Error (why is
// this possible respawn, wtf) todo: add a warning for this
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
module.Offset(0x18ED17).NOP(6);
NSMem::NOP(ba + 0x18ED17, 6);
}

View File

@ -1,10 +1,2 @@
#pragma once
// 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
void InitialisePlaylistHooks(HMODULE baseAddress);

View File

@ -1,11 +1,9 @@
#include "pch.h"
#include "squirrel.h"
#include "plugins.h"
#include <chrono>
#include "masterserver.h"
#include "convar.h"
#include "serverpresence.h"
#include <chrono>
#include <windows.h>
/// <summary>
@ -111,31 +109,31 @@ void initGameState()
}
// string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading
SQRESULT SQ_UpdateGameStateUI(HSquirrelVM* sqvm)
SQRESULT SQ_UpdateGameStateUI(void* sqvm)
{
AcquireSRWLockExclusive(&gameStateLock);
gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 4);
gameState.connected = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 5);
gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 6);
gameState.map = ClientSq_getstring(sqvm, 1);
gameState.mapDisplayName = ClientSq_getstring(sqvm, 2);
gameState.playlist = ClientSq_getstring(sqvm, 3);
gameState.playlistDisplayName = ClientSq_getstring(sqvm, 4);
gameState.connected = ClientSq_getbool(sqvm, 5);
gameState.loading = ClientSq_getbool(sqvm, 6);
ReleaseSRWLockExclusive(&gameStateLock);
return SQRESULT_NOTNULL;
}
// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit
SQRESULT SQ_UpdateGameStateClient(HSquirrelVM* sqvm)
SQRESULT SQ_UpdateGameStateClient(void* sqvm)
{
AcquireSRWLockExclusive(&gameStateLock);
AcquireSRWLockExclusive(&serverInfoLock);
gameState.players = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 2);
gameState.ourScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 3);
gameState.secondHighestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
gameState.highestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 5);
serverInfo.roundBased = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 6);
serverInfo.scoreLimit = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 7);
gameState.players = ClientSq_getinteger(sqvm, 1);
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 2);
gameState.ourScore = ClientSq_getinteger(sqvm, 3);
gameState.secondHighestScore = ClientSq_getinteger(sqvm, 4);
gameState.highestScore = ClientSq_getinteger(sqvm, 5);
serverInfo.roundBased = ClientSq_getbool(sqvm, 6);
serverInfo.scoreLimit = ClientSq_getbool(sqvm, 7);
ReleaseSRWLockExclusive(&gameStateLock);
ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL;
@ -143,59 +141,59 @@ SQRESULT SQ_UpdateGameStateClient(HSquirrelVM* sqvm)
// string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string
// playlistDisplayName
SQRESULT SQ_UpdateServerInfo(HSquirrelVM* sqvm)
SQRESULT SQ_UpdateServerInfo(void* sqvm)
{
AcquireSRWLockExclusive(&gameStateLock);
AcquireSRWLockExclusive(&serverInfoLock);
serverInfo.id = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
serverInfo.name = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
serverInfo.password = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
gameState.players = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 4);
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 5);
gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 6);
gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 7);
gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 8);
gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 9);
serverInfo.id = ClientSq_getstring(sqvm, 1);
serverInfo.name = ClientSq_getstring(sqvm, 2);
serverInfo.password = ClientSq_getstring(sqvm, 3);
gameState.players = ClientSq_getinteger(sqvm, 4);
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 5);
gameState.map = ClientSq_getstring(sqvm, 6);
gameState.mapDisplayName = ClientSq_getstring(sqvm, 7);
gameState.playlist = ClientSq_getstring(sqvm, 8);
gameState.playlistDisplayName = ClientSq_getstring(sqvm, 9);
ReleaseSRWLockExclusive(&gameStateLock);
ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL;
}
// int maxPlayers
SQRESULT SQ_UpdateServerInfoBetweenRounds(HSquirrelVM* sqvm)
SQRESULT SQ_UpdateServerInfoBetweenRounds(void* sqvm)
{
AcquireSRWLockExclusive(&serverInfoLock);
serverInfo.id = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 1);
serverInfo.name = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
serverInfo.password = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 3);
serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
serverInfo.id = ClientSq_getstring(sqvm, 1);
serverInfo.name = ClientSq_getstring(sqvm, 2);
serverInfo.password = ClientSq_getstring(sqvm, 3);
serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 4);
ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL;
}
// float timeInFuture
SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm)
SQRESULT SQ_UpdateTimeInfo(void* sqvm)
{
AcquireSRWLockExclusive(&serverInfoLock);
serverInfo.endTime = ceil(g_pSquirrel<ScriptContext::CLIENT>->getfloat(sqvm, 1));
serverInfo.endTime = ceil(ClientSq_getfloat(sqvm, 1));
ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL;
}
// bool loading
SQRESULT SQ_SetConnected(HSquirrelVM* sqvm)
SQRESULT SQ_SetConnected(void* sqvm)
{
AcquireSRWLockExclusive(&gameStateLock);
gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 1);
gameState.loading = ClientSq_getbool(sqvm, 1);
ReleaseSRWLockExclusive(&gameStateLock);
return SQRESULT_NOTNULL;
}
SQRESULT SQ_UpdateListenServer(HSquirrelVM* sqvm)
SQRESULT SQ_UpdateListenServer(void* sqvm)
{
AcquireSRWLockExclusive(&serverInfoLock);
serverInfo.id = g_pMasterServerManager->m_sOwnServerId;
serverInfo.password = ""; // g_pServerPresence->Cvar_ns_server_password->GetString(); todo this fr
serverInfo.id = g_MasterServerManager->m_sOwnServerId;
serverInfo.password = Cvar_ns_server_password->GetString();
ReleaseSRWLockExclusive(&serverInfoLock);
return SQRESULT_NOTNULL;
}
@ -386,26 +384,26 @@ int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var)
return n;
}
ON_DLL_LOAD_CLIENT_RELIESON("client.dll", PluginCommands, ClientSquirrel, (CModule module))
void InitialisePluginCommands(HMODULE baseAddress)
{
// i swear there's a way to make this not have be run in 2 contexts but i can't figure it out
// some funcs i need are just not available in UI or CLIENT
if (g_pSquirrel<ScriptContext::UI> && g_pSquirrel<ScriptContext::CLIENT>)
if (g_UISquirrelManager && g_ClientSquirrelManager)
{
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
g_UISquirrelManager->AddFuncRegistration(
"void",
"NSUpdateGameStateUI",
"string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading",
"",
SQ_UpdateGameStateUI);
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
g_ClientSquirrelManager->AddFuncRegistration(
"void",
"NSUpdateGameStateClient",
"int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit",
"",
SQ_UpdateGameStateClient);
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
g_UISquirrelManager->AddFuncRegistration(
"void",
"NSUpdateServerInfo",
"string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, "
@ -413,10 +411,10 @@ ON_DLL_LOAD_CLIENT_RELIESON("client.dll", PluginCommands, ClientSquirrel, (CModu
"playlistDisplayName",
"",
SQ_UpdateServerInfo);
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
g_ClientSquirrelManager->AddFuncRegistration(
"void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds);
g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
g_ClientSquirrelManager->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
g_UISquirrelManager->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
g_UISquirrelManager->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
}
}

View File

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

View File

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

View File

@ -1,174 +0,0 @@
#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;
}

View File

@ -1,168 +0,0 @@
#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;
}

View File

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

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