aboutsummaryrefslogtreecommitdiff
path: root/primedev/core
diff options
context:
space:
mode:
authorJack <66967891+ASpoonPlaysGames@users.noreply.github.com>2023-12-27 00:32:01 +0000
committerGitHub <noreply@github.com>2023-12-27 01:32:01 +0100
commitf5ab6fb5e8be7b73e6003d4145081d5e0c0ce287 (patch)
tree90f2c6a4885dbd181799e2325cf33588697674e1 /primedev/core
parentbb8ed59f6891b1196c5f5bbe7346cd171c8215fa (diff)
downloadNorthstarLauncher-f5ab6fb5e8be7b73e6003d4145081d5e0c0ce287.tar.gz
NorthstarLauncher-f5ab6fb5e8be7b73e6003d4145081d5e0c0ce287.zip
Folder restructuring from primedev (#624)v1.21.2-rc3v1.21.2
Copies of over the primedev folder structure for easier cherry-picking of further changes Co-authored-by: F1F7Y <filip.bartos07@proton.me>
Diffstat (limited to 'primedev/core')
-rw-r--r--primedev/core/convar/concommand.cpp157
-rw-r--r--primedev/core/convar/concommand.h139
-rw-r--r--primedev/core/convar/convar.cpp534
-rw-r--r--primedev/core/convar/convar.h194
-rw-r--r--primedev/core/convar/cvar.cpp26
-rw-r--r--primedev/core/convar/cvar.h38
-rw-r--r--primedev/core/filesystem/filesystem.cpp177
-rw-r--r--primedev/core/filesystem/filesystem.h69
-rw-r--r--primedev/core/filesystem/rpakfilesystem.cpp347
-rw-r--r--primedev/core/filesystem/rpakfilesystem.h39
-rw-r--r--primedev/core/hooks.cpp474
-rw-r--r--primedev/core/hooks.h331
-rw-r--r--primedev/core/macros.h19
-rw-r--r--primedev/core/math/bitbuf.h1148
-rw-r--r--primedev/core/math/bits.cpp45
-rw-r--r--primedev/core/math/bits.h10
-rw-r--r--primedev/core/math/color.cpp27
-rw-r--r--primedev/core/math/color.h199
-rw-r--r--primedev/core/math/vector.h47
-rw-r--r--primedev/core/memalloc.cpp71
-rw-r--r--primedev/core/memalloc.h49
-rw-r--r--primedev/core/memory.cpp347
-rw-r--r--primedev/core/memory.h90
-rw-r--r--primedev/core/sourceinterface.cpp48
-rw-r--r--primedev/core/sourceinterface.h31
-rw-r--r--primedev/core/structs.h77
-rw-r--r--primedev/core/tier0.cpp30
-rw-r--r--primedev/core/tier0.h63
-rw-r--r--primedev/core/vanilla.h29
29 files changed, 4855 insertions, 0 deletions
diff --git a/primedev/core/convar/concommand.cpp b/primedev/core/convar/concommand.cpp
new file mode 100644
index 00000000..41f54c76
--- /dev/null
+++ b/primedev/core/convar/concommand.cpp
@@ -0,0 +1,157 @@
+#include "concommand.h"
+#include "shared/misccommands.h"
+#include "engine/r2engine.h"
+
+#include "plugins/pluginbackend.h"
+#include "plugins/plugin_abi.h"
+
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this is a command
+// Output : bool
+//-----------------------------------------------------------------------------
+bool ConCommand::IsCommand(void) const
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this is a command
+// Output : bool
+//-----------------------------------------------------------------------------
+bool ConCommandBase::IsCommand(void) const
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Has this cvar been registered
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool ConCommandBase::IsRegistered(void) const
+{
+ return m_bRegistered;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Test each ConCommand query before execution.
+// Input : *pCommandBase - nFlags
+// Output : False if execution is permitted, true if not.
+//-----------------------------------------------------------------------------
+bool ConCommandBase::IsFlagSet(int nFlags) const
+{
+ return m_nFlags & nFlags;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks if ConCommand has requested flags.
+// Input : nFlags -
+// Output : True if ConCommand has nFlags.
+//-----------------------------------------------------------------------------
+bool ConCommandBase::HasFlags(int nFlags)
+{
+ return m_nFlags & nFlags;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add's flags to ConCommand.
+// Input : nFlags -
+//-----------------------------------------------------------------------------
+void ConCommandBase::AddFlags(int nFlags)
+{
+ m_nFlags |= nFlags;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes flags from ConCommand.
+// Input : nFlags -
+//-----------------------------------------------------------------------------
+void ConCommandBase::RemoveFlags(int nFlags)
+{
+ m_nFlags &= ~nFlags;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns current flags.
+// Output : int
+//-----------------------------------------------------------------------------
+int ConCommandBase::GetFlags(void) const
+{
+ return m_nFlags;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const ConCommandBase
+//-----------------------------------------------------------------------------
+ConCommandBase* ConCommandBase::GetNext(void) const
+{
+ return m_pNext;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the ConCommandBase help text.
+// Output : const char*
+//-----------------------------------------------------------------------------
+const char* ConCommandBase::GetHelpText(void) const
+{
+ return m_pszHelpString;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Copies string using local new/delete operators
+// Input : *szFrom -
+// Output : char
+//-----------------------------------------------------------------------------
+char* ConCommandBase::CopyString(const char* szFrom) const
+{
+ size_t nLen;
+ char* szTo;
+
+ nLen = strlen(szFrom);
+ if (nLen <= 0)
+ {
+ szTo = new char[1];
+ szTo[0] = 0;
+ }
+ else
+ {
+ szTo = new char[nLen + 1];
+ memmove(szTo, szFrom, nLen + 1);
+ }
+ 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).RCast<ConCommandConstructorType>();
+ AddMiscConCommands();
+
+ g_pPluginCommunicationhandler->m_sEngineData.ConCommandConstructor =
+ reinterpret_cast<PluginConCommandConstructorType>(ConCommandConstructor);
+}
diff --git a/primedev/core/convar/concommand.h b/primedev/core/convar/concommand.h
new file mode 100644
index 00000000..71a82fec
--- /dev/null
+++ b/primedev/core/convar/concommand.h
@@ -0,0 +1,139 @@
+#pragma once
+
+// From Source SDK
+class ConCommandBase;
+class IConCommandBaseAccessor
+{
+public:
+ // Flags is a combination of FCVAR flags in cvar.h.
+ // hOut is filled in with a handle to the variable.
+ virtual bool RegisterConCommandBase(ConCommandBase* pVar) = 0;
+};
+
+class CCommand
+{
+public:
+ CCommand() = delete;
+
+ int64_t ArgC() const;
+ const char** ArgV() const;
+ const char* ArgS() const; // All args that occur after the 0th arg, in string form
+ const char* GetCommandString() const; // The entire command in string form, including the 0th arg
+ const char* operator[](int nIndex) const; // Gets at arguments
+ const char* Arg(int nIndex) const; // Gets at arguments
+
+ static int MaxCommandLength();
+
+private:
+ enum
+ {
+ COMMAND_MAX_ARGC = 64,
+ COMMAND_MAX_LENGTH = 512,
+ };
+
+ int64_t m_nArgc;
+ int64_t m_nArgv0Size;
+ char m_pArgSBuffer[COMMAND_MAX_LENGTH];
+ char m_pArgvBuffer[COMMAND_MAX_LENGTH];
+ const char* m_ppArgv[COMMAND_MAX_ARGC];
+};
+
+inline int CCommand::MaxCommandLength()
+{
+ return COMMAND_MAX_LENGTH - 1;
+}
+inline int64_t CCommand::ArgC() const
+{
+ return m_nArgc;
+}
+inline const char** CCommand::ArgV() const
+{
+ return m_nArgc ? (const char**)m_ppArgv : NULL;
+}
+inline const char* CCommand::ArgS() const
+{
+ return m_nArgv0Size ? &m_pArgSBuffer[m_nArgv0Size] : "";
+}
+inline const char* CCommand::GetCommandString() const
+{
+ return m_nArgc ? m_pArgSBuffer : "";
+}
+inline const char* CCommand::Arg(int nIndex) const
+{
+ // FIXME: Many command handlers appear to not be particularly careful
+ // about checking for valid argc range. For now, we're going to
+ // do the extra check and return an empty string if it's out of range
+ if (nIndex < 0 || nIndex >= m_nArgc)
+ return "";
+ return m_ppArgv[nIndex];
+}
+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 (*FnCommandCompletionCallback)(const char* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]);
+
+// From r5reloaded
+class ConCommandBase
+{
+public:
+ bool HasFlags(int nFlags);
+ void AddFlags(int nFlags);
+ void RemoveFlags(int nFlags);
+
+ bool IsCommand(void) const;
+ bool IsRegistered(void) const;
+ bool IsFlagSet(int nFlags) const;
+ static bool IsFlagSet(ConCommandBase* pCommandBase, int nFlags); // For hooking to engine's implementation.
+
+ int GetFlags(void) const;
+ ConCommandBase* GetNext(void) const;
+ const char* GetHelpText(void) const;
+
+ char* CopyString(const char* szFrom) const;
+
+ void* m_pConCommandBaseVTable; // 0x0000
+ ConCommandBase* m_pNext; // 0x0008
+ bool m_bRegistered; // 0x0010
+ char pad_0011[7]; // 0x0011 <- 3 bytes padding + unk int32.
+ const char* m_pszName; // 0x0018
+ const char* m_pszHelpString; // 0x0020
+ int m_nFlags; // 0x0028
+ ConCommandBase* s_pConCommandBases; // 0x002C
+ IConCommandBaseAccessor* s_pAccessor; // 0x0034
+}; // Size: 0x0040
+
+// taken from ttf2sdk
+class ConCommand : public ConCommandBase
+{
+ friend class CCVar;
+
+public:
+ ConCommand(void) {}; // !TODO: Rebuild engine constructor in SDK instead.
+ ConCommand(const char* szName, const char* szHelpString, int nFlags, void* pCallback, void* pCommandCompletionCallback);
+ 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').
+ int m_nCallbackFlags {}; // 0x0050
+ char pad_0054[4]; // 0x0054
+ int unk0; // 0x0058
+ int unk1; // 0x005C
+}; // 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);
diff --git a/primedev/core/convar/convar.cpp b/primedev/core/convar/convar.cpp
new file mode 100644
index 00000000..e77ae1fd
--- /dev/null
+++ b/primedev/core/convar/convar.cpp
@@ -0,0 +1,534 @@
+#include "bits.h"
+#include "cvar.h"
+#include "convar.h"
+#include "core/sourceinterface.h"
+
+#include "plugins/pluginbackend.h"
+#include "plugins/plugin_abi.h"
+
+#include <float.h>
+
+typedef void (*ConVarRegisterType)(
+ ConVar* pConVar,
+ const char* pszName,
+ const char* pszDefaultValue,
+ int nFlags,
+ const char* pszHelpString,
+ bool bMin,
+ float fMin,
+ bool bMax,
+ float fMax,
+ void* pCallback);
+ConVarRegisterType conVarRegister;
+
+typedef void (*ConVarMallocType)(void* pConVarMaloc, int a2, int a3);
+ConVarMallocType conVarMalloc;
+
+void* g_pConVar_Vtable = nullptr;
+void* g_pIConVar_Vtable = nullptr;
+
+//-----------------------------------------------------------------------------
+// Purpose: ConVar interface initialization
+//-----------------------------------------------------------------------------
+ON_DLL_LOAD("engine.dll", ConVar, (CModule module))
+{
+ conVarMalloc = module.Offset(0x415C20).RCast<ConVarMallocType>();
+ conVarRegister = module.Offset(0x417230).RCast<ConVarRegisterType>();
+
+ g_pConVar_Vtable = module.Offset(0x67FD28);
+ g_pIConVar_Vtable = module.Offset(0x67FDC8);
+
+ g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007");
+ g_pCVar = *g_pCVarInterface;
+
+ g_pPluginCommunicationhandler->m_sEngineData.conVarMalloc = reinterpret_cast<PluginConVarMallocType>(conVarMalloc);
+ g_pPluginCommunicationhandler->m_sEngineData.conVarRegister = reinterpret_cast<PluginConVarRegisterType>(conVarRegister);
+ g_pPluginCommunicationhandler->m_sEngineData.ConVar_Vtable = reinterpret_cast<void*>(g_pConVar_Vtable);
+ g_pPluginCommunicationhandler->m_sEngineData.IConVar_Vtable = reinterpret_cast<void*>(g_pIConVar_Vtable);
+ g_pPluginCommunicationhandler->m_sEngineData.g_pCVar = reinterpret_cast<void*>(g_pCVar);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: constructor
+//-----------------------------------------------------------------------------
+ConVar::ConVar(const char* pszName, const char* pszDefaultValue, int nFlags, const char* pszHelpString)
+{
+ spdlog::info("Registering Convar {}", pszName);
+
+ this->m_ConCommandBase.m_pConCommandBaseVTable = g_pConVar_Vtable;
+ this->m_ConCommandBase.s_pConCommandBases = (ConCommandBase*)g_pIConVar_Vtable;
+
+ conVarMalloc(&this->m_pMalloc, 0, 0); // Allocate new memory for ConVar.
+ conVarRegister(this, pszName, pszDefaultValue, nFlags, pszHelpString, 0, 0, 0, 0, 0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: constructor
+//-----------------------------------------------------------------------------
+ConVar::ConVar(
+ const char* pszName,
+ const char* pszDefaultValue,
+ int nFlags,
+ const char* pszHelpString,
+ bool bMin,
+ float fMin,
+ bool bMax,
+ float fMax,
+ FnChangeCallback_t pCallback)
+{
+ spdlog::info("Registering Convar {}", pszName);
+
+ this->m_ConCommandBase.m_pConCommandBaseVTable = g_pConVar_Vtable;
+ this->m_ConCommandBase.s_pConCommandBases = (ConCommandBase*)g_pIConVar_Vtable;
+
+ conVarMalloc(&this->m_pMalloc, 0, 0); // Allocate new memory for ConVar.
+ conVarRegister(this, pszName, pszDefaultValue, nFlags, pszHelpString, bMin, fMin, bMax, fMax, (void*)pCallback);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: destructor
+//-----------------------------------------------------------------------------
+ConVar::~ConVar(void)
+{
+ if (m_Value.m_pszString)
+ delete[] m_Value.m_pszString;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the base ConVar name.
+// Output : const char*
+//-----------------------------------------------------------------------------
+const char* ConVar::GetBaseName(void) const
+{
+ return m_ConCommandBase.m_pszName;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the ConVar help text.
+// Output : const char*
+//-----------------------------------------------------------------------------
+const char* ConVar::GetHelpText(void) const
+{
+ return m_ConCommandBase.m_pszHelpString;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add's flags to ConVar.
+// Input : nFlags -
+//-----------------------------------------------------------------------------
+void ConVar::AddFlags(int nFlags)
+{
+ m_ConCommandBase.m_nFlags |= nFlags;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes flags from ConVar.
+// Input : nFlags -
+//-----------------------------------------------------------------------------
+void ConVar::RemoveFlags(int nFlags)
+{
+ m_ConCommandBase.m_nFlags &= ~nFlags;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return ConVar value as a boolean.
+// Output : bool
+//-----------------------------------------------------------------------------
+bool ConVar::GetBool(void) const
+{
+ return !!GetInt();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return ConVar value as a float.
+// Output : float
+//-----------------------------------------------------------------------------
+float ConVar::GetFloat(void) const
+{
+ return m_Value.m_fValue;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return ConVar value as an integer.
+// Output : int
+//-----------------------------------------------------------------------------
+int ConVar::GetInt(void) const
+{
+ return m_Value.m_nValue;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return ConVar value as a color.
+// Output : Color
+//-----------------------------------------------------------------------------
+Color ConVar::GetColor(void) const
+{
+ unsigned char* pColorElement = ((unsigned char*)&m_Value.m_nValue);
+ return Color(pColorElement[0], pColorElement[1], pColorElement[2], pColorElement[3]);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return ConVar value as a string.
+// Output : const char *
+//-----------------------------------------------------------------------------
+const char* ConVar::GetString(void) const
+{
+ if (m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING)
+ {
+ return "FCVAR_NEVER_AS_STRING";
+ }
+
+ char const* str = m_Value.m_pszString;
+ return str ? str : "";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flMinVal -
+// Output : true if there is a min set.
+//-----------------------------------------------------------------------------
+bool ConVar::GetMin(float& flMinVal) const
+{
+ flMinVal = m_fMinVal;
+ return m_bHasMin;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flMaxVal -
+// Output : true if there is a max set.
+//-----------------------------------------------------------------------------
+bool ConVar::GetMax(float& flMaxVal) const
+{
+ flMaxVal = m_fMaxVal;
+ return m_bHasMax;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the min value.
+// Output : float
+//-----------------------------------------------------------------------------
+float ConVar::GetMinValue(void) const
+{
+ return m_fMinVal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the max value.
+// Output : float
+//-----------------------------------------------------------------------------
+float ConVar::GetMaxValue(void) const
+{
+ return m_fMaxVal;
+ ;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: checks if ConVar has min value.
+// Output : bool
+//-----------------------------------------------------------------------------
+bool ConVar::HasMin(void) const
+{
+ return m_bHasMin;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: checks if ConVar has max value.
+// Output : bool
+//-----------------------------------------------------------------------------
+bool ConVar::HasMax(void) const
+{
+ return m_bHasMax;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets the ConVar int value.
+// Input : nValue -
+//-----------------------------------------------------------------------------
+void ConVar::SetValue(int nValue)
+{
+ if (nValue == m_Value.m_nValue)
+ {
+ return;
+ }
+
+ float flValue = (float)nValue;
+
+ // Check bounds.
+ if (ClampValue(flValue))
+ {
+ nValue = (int)(flValue);
+ }
+
+ // Redetermine value.
+ float flOldValue = m_Value.m_fValue;
+ m_Value.m_fValue = flValue;
+ m_Value.m_nValue = nValue;
+
+ if (!(m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING))
+ {
+ char szTempValue[32];
+ snprintf(szTempValue, sizeof(szTempValue), "%d", m_Value.m_nValue);
+ ChangeStringValue(szTempValue, flOldValue);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets the ConVar float value.
+// Input : flValue -
+//-----------------------------------------------------------------------------
+void ConVar::SetValue(float flValue)
+{
+ if (flValue == m_Value.m_fValue)
+ {
+ return;
+ }
+
+ // Check bounds.
+ ClampValue(flValue);
+
+ // Redetermine value.
+ float flOldValue = m_Value.m_fValue;
+ m_Value.m_fValue = flValue;
+ m_Value.m_nValue = (int)m_Value.m_fValue;
+
+ if (!(m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING))
+ {
+ char szTempValue[32];
+ snprintf(szTempValue, sizeof(szTempValue), "%f", m_Value.m_fValue);
+ ChangeStringValue(szTempValue, flOldValue);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets the ConVar string value.
+// Input : *szValue -
+//-----------------------------------------------------------------------------
+void ConVar::SetValue(const char* pszValue)
+{
+ if (strcmp(this->m_Value.m_pszString, pszValue) == 0)
+ return;
+
+ char szTempValue[32] {};
+ const char* pszNewValue {};
+
+ float flOldValue = m_Value.m_fValue;
+ pszNewValue = (char*)pszValue;
+ if (!pszNewValue)
+ {
+ pszNewValue = "";
+ }
+
+ if (!SetColorFromString(pszValue))
+ {
+ // Not a color, do the standard thing
+ float flNewValue = (float)atof(pszValue);
+ if (!std::isfinite(flNewValue))
+ {
+ spdlog::warn("Warning: ConVar '{}' = '{}' is infinite, clamping value.\n", GetBaseName(), pszValue);
+ flNewValue = FLT_MAX;
+ }
+
+ if (ClampValue(flNewValue))
+ {
+ snprintf(szTempValue, sizeof(szTempValue), "%f", flNewValue);
+ pszNewValue = szTempValue;
+ }
+
+ // Redetermine value
+ m_Value.m_fValue = flNewValue;
+ m_Value.m_nValue = (int)(m_Value.m_fValue);
+ }
+
+ if (!(m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING))
+ {
+ ChangeStringValue(pszNewValue, flOldValue);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets the ConVar color value.
+// Input : clValue -
+//-----------------------------------------------------------------------------
+void ConVar::SetValue(Color clValue)
+{
+ std::string svResult = "";
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (!(clValue.GetValue(i) == 0 && svResult.size() == 0))
+ {
+ svResult += std::to_string(clValue.GetValue(i));
+ svResult.append(" ");
+ }
+ }
+
+ this->m_Value.m_pszString = svResult.c_str();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: changes the ConVar string value.
+// Input : *pszTempVal - flOldValue
+//-----------------------------------------------------------------------------
+void ConVar::ChangeStringValue(const char* pszTempVal, float flOldValue)
+{
+ assert(!(m_ConCommandBase.m_nFlags & FCVAR_NEVER_AS_STRING));
+
+ char* pszOldValue = (char*)_malloca(m_Value.m_iStringLength);
+ if (pszOldValue != NULL)
+ {
+ memcpy(pszOldValue, m_Value.m_pszString, m_Value.m_iStringLength);
+ }
+
+ if (pszTempVal)
+ {
+ int len = strlen(pszTempVal) + 1;
+
+ if (len > m_Value.m_iStringLength)
+ {
+ if (m_Value.m_pszString)
+ delete[] m_Value.m_pszString;
+
+ m_Value.m_pszString = new char[len];
+ m_Value.m_iStringLength = len;
+ }
+
+ memcpy((char*)m_Value.m_pszString, pszTempVal, len);
+ }
+ else
+ {
+ m_Value.m_pszString = NULL;
+ }
+
+ pszOldValue = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets the ConVar color value from string.
+// Input : *pszValue -
+//-----------------------------------------------------------------------------
+bool ConVar::SetColorFromString(const char* pszValue)
+{
+ bool bColor = false;
+
+ // Try pulling RGBA color values out of the string.
+ int nRGBA[4] {};
+ int nParamsRead = sscanf_s(pszValue, "%i %i %i %i", &(nRGBA[0]), &(nRGBA[1]), &(nRGBA[2]), &(nRGBA[3]));
+
+ if (nParamsRead >= 3)
+ {
+ // This is probably a color!
+ if (nParamsRead == 3)
+ {
+ // Assume they wanted full alpha.
+ nRGBA[3] = 255;
+ }
+
+ if (nRGBA[0] >= 0 && nRGBA[0] <= 255 && nRGBA[1] >= 0 && nRGBA[1] <= 255 && nRGBA[2] >= 0 && nRGBA[2] <= 255 && nRGBA[3] >= 0 &&
+ nRGBA[3] <= 255)
+ {
+ // printf("*** WOW! Found a color!! ***\n");
+
+ // This is definitely a color!
+ bColor = true;
+
+ // Stuff all the values into each byte of our int.
+ unsigned char* pColorElement = ((unsigned char*)&m_Value.m_nValue);
+ pColorElement[0] = nRGBA[0];
+ pColorElement[1] = nRGBA[1];
+ pColorElement[2] = nRGBA[2];
+ pColorElement[3] = nRGBA[3];
+
+ // Copy that value into our float.
+ m_Value.m_fValue = (float)(m_Value.m_nValue);
+ }
+ }
+
+ return bColor;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks if ConVar is registered.
+// Output : bool
+//-----------------------------------------------------------------------------
+bool ConVar::IsRegistered(void) const
+{
+ return m_ConCommandBase.m_bRegistered;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this is a command
+// Output : bool
+//-----------------------------------------------------------------------------
+bool ConVar::IsCommand(void) const
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Test each ConVar query before setting the value.
+// Input : nFlags
+// Output : False if change is permitted, true if not.
+//-----------------------------------------------------------------------------
+bool ConVar::IsFlagSet(int nFlags) const
+{
+ return m_ConCommandBase.m_nFlags & nFlags;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check whether to clamp and then perform clamp.
+// Input : flValue -
+// Output : Returns true if value changed.
+//-----------------------------------------------------------------------------
+bool ConVar::ClampValue(float& flValue)
+{
+ if (m_bHasMin && (flValue < m_fMinVal))
+ {
+ flValue = m_fMinVal;
+ return true;
+ }
+
+ if (m_bHasMax && (flValue > m_fMaxVal))
+ {
+ flValue = m_fMaxVal;
+ return true;
+ }
+
+ return false;
+}
+
+int ParseConVarFlagsString(std::string modName, std::string sFlags)
+{
+ int iFlags = 0;
+ std::stringstream stFlags(sFlags);
+ std::string sFlag;
+
+ while (std::getline(stFlags, sFlag, '|'))
+ {
+ // trim the flag
+ sFlag.erase(sFlag.find_last_not_of(" \t\n\f\v\r") + 1);
+ sFlag.erase(0, sFlag.find_first_not_of(" \t\n\f\v\r"));
+
+ // skip if empty
+ if (sFlag.empty())
+ continue;
+
+ // find the matching flag value
+ bool ok = false;
+ for (auto const& flagPair : g_PrintCommandFlags)
+ {
+ if (sFlag == flagPair.second)
+ {
+ iFlags |= flagPair.first;
+ ok = true;
+ break;
+ }
+ }
+ if (!ok)
+ {
+ spdlog::warn("Mod ConCommand {} has unknown flag {}", modName, sFlag);
+ }
+ }
+
+ return iFlags;
+}
diff --git a/primedev/core/convar/convar.h b/primedev/core/convar/convar.h
new file mode 100644
index 00000000..f0366b46
--- /dev/null
+++ b/primedev/core/convar/convar.h
@@ -0,0 +1,194 @@
+#pragma once
+#include "core/sourceinterface.h"
+#include "core/math/color.h"
+#include "cvar.h"
+#include "concommand.h"
+
+// taken directly from iconvar.h
+
+// The default, no flags at all
+#define FCVAR_NONE 0
+
+// Command to ConVars and ConCommands
+// ConVar Systems
+#define FCVAR_UNREGISTERED (1 << 0) // If this is set, don't add to linked list, etc.
+#define FCVAR_DEVELOPMENTONLY (1 << 1) // Hidden in released products. Flag is removed automatically if ALLOW_DEVELOPMENT_CVARS is defined.
+#define FCVAR_GAMEDLL (1 << 2) // defined by the game DLL
+#define FCVAR_CLIENTDLL (1 << 3) // defined by the client DLL
+#define FCVAR_HIDDEN (1 << 4) // Hidden. Doesn't appear in find or auto complete. Like DEVELOPMENTONLY, but can't be compiled out.
+
+// ConVar only
+#define FCVAR_PROTECTED \
+ (1 << 5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as
+ // value.
+#define FCVAR_SPONLY (1 << 6) // This cvar cannot be changed by clients connected to a multiplayer server.
+#define FCVAR_ARCHIVE (1 << 7) // set to cause it to be saved to vars.rc
+#define FCVAR_NOTIFY (1 << 8) // notifies players when changed
+#define FCVAR_USERINFO (1 << 9) // changes the client's info string
+
+#define FCVAR_PRINTABLEONLY (1 << 10) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ).
+#define FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS \
+ (1 << 10) // When on concommands this allows remote clients to execute this cmd on the server.
+ // We are changing the default behavior of concommands to disallow execution by remote clients without
+ // this flag due to the number existing concommands that can lag or crash the server when clients abuse them.
+
+#define FCVAR_UNLOGGED (1 << 11) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log
+#define FCVAR_NEVER_AS_STRING (1 << 12) // never try to print that cvar
+
+// It's a ConVar that's shared between the client and the server.
+// At signon, the values of all such ConVars are sent from the server to the client (skipped for local client, of course )
+// If a change is requested it must come from the console (i.e., no remote client changes)
+// If a value is changed while a server is active, it's replicated to all connected clients
+#define FCVAR_REPLICATED (1 << 13) // server setting enforced on clients, TODO rename to FCAR_SERVER at some time
+#define FCVAR_CHEAT (1 << 14) // Only useable in singleplayer / debug / multiplayer & sv_cheats
+#define FCVAR_SS (1 << 15) // causes varnameN where N == 2 through max splitscreen slots for mod to be autogenerated
+#define FCVAR_DEMO (1 << 16) // record this cvar when starting a demo file
+#define FCVAR_DONTRECORD (1 << 17) // don't record these command in demofiles
+#define FCVAR_SS_ADDED (1 << 18) // This is one of the "added" FCVAR_SS variables for the splitscreen players
+#define FCVAR_RELEASE (1 << 19) // Cvars tagged with this are the only cvars avaliable to customers
+#define FCVAR_RELOAD_MATERIALS (1 << 20) // If this cvar changes, it forces a material reload
+#define FCVAR_RELOAD_TEXTURES (1 << 21) // If this cvar changes, if forces a texture reload
+
+#define FCVAR_NOT_CONNECTED (1 << 22) // cvar cannot be changed by a client that is connected to a server
+#define FCVAR_MATERIAL_SYSTEM_THREAD (1 << 23) // Indicates this cvar is read from the material system thread
+#define FCVAR_ARCHIVE_PLAYERPROFILE (1 << 24) // respawn-defined flag, same as FCVAR_ARCHIVE but writes to profile.cfg
+
+#define FCVAR_SERVER_CAN_EXECUTE \
+ (1 << 28) // the server is allowed to execute this command on clients via
+ // 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
+//-----------------------------------------------------------------------------
+class ConCommandBase;
+class ConCommand;
+class ConVar;
+
+typedef void (*FnChangeCallback_t)(ConVar* var, const char* pOldValue, float flOldValue);
+
+//-----------------------------------------------------------------------------
+// Purpose: A console variable
+//-----------------------------------------------------------------------------
+class ConVar
+{
+public:
+ ConVar(void) {};
+ ConVar(const char* pszName, const char* pszDefaultValue, int nFlags, const char* pszHelpString);
+ ConVar(
+ const char* pszName,
+ const char* pszDefaultValue,
+ int nFlags,
+ const char* pszHelpString,
+ bool bMin,
+ float fMin,
+ bool bMax,
+ float fMax,
+ FnChangeCallback_t pCallback);
+ ~ConVar(void);
+
+ const char* GetBaseName(void) const;
+ const char* GetHelpText(void) const;
+
+ void AddFlags(int nFlags);
+ void RemoveFlags(int nFlags);
+
+ bool GetBool(void) const;
+ float GetFloat(void) const;
+ int GetInt(void) const;
+ Color GetColor(void) const;
+ const char* GetString(void) const;
+
+ bool GetMin(float& flMinValue) const;
+ bool GetMax(float& flMaxValue) const;
+ float GetMinValue(void) const;
+ float GetMaxValue(void) const;
+
+ bool HasMin(void) const;
+ bool HasMax(void) const;
+
+ void SetValue(int nValue);
+ void SetValue(float flValue);
+ void SetValue(const char* pszValue);
+ void SetValue(Color clValue);
+
+ void ChangeStringValue(const char* pszTempValue, float flOldValue);
+ bool SetColorFromString(const char* pszValue);
+ bool ClampValue(float& value);
+
+ bool IsRegistered(void) const;
+ bool IsCommand(void) const;
+ bool IsFlagSet(int nFlags) const;
+
+ struct CVValue_t
+ {
+ const char* m_pszString;
+ int64_t m_iStringLength;
+ float m_fValue;
+ int m_nValue;
+ };
+
+ ConCommandBase m_ConCommandBase {}; // 0x0000
+ const char* m_pszDefaultValue {}; // 0x0040
+ CVValue_t m_Value {}; // 0x0048
+ bool m_bHasMin {}; // 0x005C
+ float m_fMinVal {}; // 0x0060
+ bool m_bHasMax {}; // 0x0064
+ float m_fMaxVal {}; // 0x0068
+ void* m_pMalloc {}; // 0x0070
+ char m_pPad80[10] {}; // 0x0080
+}; // Size: 0x0080
+
+int ParseConVarFlagsString(std::string modName, std::string flags);
diff --git a/primedev/core/convar/cvar.cpp b/primedev/core/convar/cvar.cpp
new file mode 100644
index 00000000..aa5f0365
--- /dev/null
+++ b/primedev/core/convar/cvar.cpp
@@ -0,0 +1,26 @@
+#include "cvar.h"
+#include "convar.h"
+#include "concommand.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: returns all ConVars
+//-----------------------------------------------------------------------------
+std::unordered_map<std::string, ConCommandBase*> CCvar::DumpToMap()
+{
+ std::stringstream ss;
+ CCVarIteratorInternal* itint = FactoryInternalIterator(); // Allocate new InternalIterator.
+
+ std::unordered_map<std::string, ConCommandBase*> allConVars;
+
+ for (itint->SetFirst(); itint->IsValid(); itint->Next()) // Loop through all instances.
+ {
+ ConCommandBase* pCommand = itint->Get();
+ const char* pszCommandName = pCommand->m_pszName;
+ allConVars[pszCommandName] = pCommand;
+ }
+
+ return allConVars;
+}
+
+SourceInterface<CCvar>* g_pCVarInterface;
+CCvar* g_pCVar;
diff --git a/primedev/core/convar/cvar.h b/primedev/core/convar/cvar.h
new file mode 100644
index 00000000..beaa84f4
--- /dev/null
+++ b/primedev/core/convar/cvar.h
@@ -0,0 +1,38 @@
+#pragma once
+#include "convar.h"
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class ConCommandBase;
+class ConCommand;
+class ConVar;
+
+//-----------------------------------------------------------------------------
+// Internals for ICVarIterator
+//-----------------------------------------------------------------------------
+class CCVarIteratorInternal // Fully reversed table, just look at the virtual function table and rename the function.
+{
+public:
+ virtual void SetFirst(void) = 0; // 0
+ virtual void Next(void) = 0; // 1
+ virtual bool IsValid(void) = 0; // 2
+ virtual ConCommandBase* Get(void) = 0; // 3
+};
+
+//-----------------------------------------------------------------------------
+// Default implementation
+//-----------------------------------------------------------------------------
+class CCvar
+{
+public:
+ M_VMETHOD(ConCommandBase*, FindCommandBase, 14, (const char* pszCommandName), (this, pszCommandName));
+ M_VMETHOD(ConVar*, FindVar, 16, (const char* pszVarName), (this, pszVarName));
+ M_VMETHOD(ConCommand*, FindCommand, 18, (const char* pszCommandName), (this, pszCommandName));
+ M_VMETHOD(CCVarIteratorInternal*, FactoryInternalIterator, 41, (), (this));
+
+ std::unordered_map<std::string, ConCommandBase*> DumpToMap();
+};
+
+extern SourceInterface<CCvar>* g_pCVarInterface;
+extern CCvar* g_pCVar;
diff --git a/primedev/core/filesystem/filesystem.cpp b/primedev/core/filesystem/filesystem.cpp
new file mode 100644
index 00000000..b39939e4
--- /dev/null
+++ b/primedev/core/filesystem/filesystem.cpp
@@ -0,0 +1,177 @@
+#include "filesystem.h"
+#include "core/sourceinterface.h"
+#include "mods/modmanager.h"
+
+#include <iostream>
+#include <sstream>
+
+AUTOHOOK_INIT()
+
+bool bReadingOriginalFile = false;
+std::string sCurrentModPath;
+
+ConVar* Cvar_ns_fs_log_reads;
+
+SourceInterface<IFileSystem>* g_pFilesystem;
+
+std::string ReadVPKFile(const char* path)
+{
+ // read scripts.rson file, todo: check if this can be overwritten
+ FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0);
+
+ 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;
+}
+
+// clang-format off
+HOOK(AddSearchPathHook, AddSearchPath,
+void, __fastcall, (IFileSystem * fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType))
+// clang-format on
+{
+ AddSearchPath(fileSystem, pPath, pathID, addType);
+
+ // make sure current mod paths are at head
+ if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
+ {
+ AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
+ AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
+ }
+}
+
+void SetNewModSearchPaths(Mod* mod)
+{
+ // put our new path to the head if we need to read from a different mod path
+ // 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))
+ {
+ AddSearchPath(
+ &*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
+ sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string();
+ }
+ }
+ else // push compiled to head
+ AddSearchPath(&*(*g_pFilesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
+}
+
+bool TryReplaceFile(const char* pPath, bool shouldCompile)
+{
+ if (bReadingOriginalFile)
+ return false;
+
+ if (shouldCompile)
+ g_pModManager->CompileAssetsForFile(pPath);
+
+ // idk how efficient the lexically normal check is
+ // can't just set all /s in path to \, since some paths aren't in writeable memory
+ auto file = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path(pPath)));
+ if (file != g_pModManager->m_ModFiles.end())
+ {
+ SetNewModSearchPaths(file->second.m_pOwningMod);
+ 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
+{
+ if (TryReplaceFile(pPath, true))
+ return false;
+
+ return ReadFromCache(filesystem, pPath, result);
+}
+
+// force modded files to be read from mods, not vpk
+// clang-format off
+AUTOHOOK(ReadFileFromVPK, filesystem_stdio.dll + 0x5CBA0,
+FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename))
+// clang-format on
+{
+ // don't compile here because this is only ever called from OpenEx, which already compiles
+ if (TryReplaceFile(filename, false))
+ {
+ *b = -1;
+ return b;
+ }
+
+ return ReadFileFromVPK(vpkInfo, b, filename);
+}
+
+// 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
+{
+ TryReplaceFile(pPath, true);
+ return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename);
+}
+
+HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath))
+{
+ NS::log::fs->info("MountVPK {}", pVpkPath);
+ VPKData* ret = MountVPK(fileSystem, pVpkPath);
+
+ for (Mod mod : g_pModManager->m_LoadedMods)
+ {
+ if (!mod.m_bEnabled)
+ continue;
+
+ for (ModVPKEntry& vpkEntry : mod.Vpks)
+ {
+ // if we're autoloading, just load no matter what
+ if (!vpkEntry.m_bAutoLoad)
+ {
+ // 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 modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string());
+ if (mapName.compare(modMapName))
+ continue;
+ }
+
+ 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;
+ }
+ }
+
+ return ret;
+}
+
+ON_DLL_LOAD("filesystem_stdio.dll", Filesystem, (CModule module))
+{
+ AUTOHOOK_DISPATCH()
+
+ g_pFilesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
+
+ AddSearchPathHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->AddSearchPath);
+ ReadFromCacheHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->ReadFromCache);
+ MountVPKHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->MountVPK);
+}
diff --git a/primedev/core/filesystem/filesystem.h b/primedev/core/filesystem/filesystem.h
new file mode 100644
index 00000000..fcd1bb2f
--- /dev/null
+++ b/primedev/core/filesystem/filesystem.h
@@ -0,0 +1,69 @@
+#pragma once
+#include "core/sourceinterface.h"
+
+// taken from ttf2sdk
+typedef void* FileHandle_t;
+
+#pragma pack(push, 1)
+
+// clang-format off
+OFFSET_STRUCT(VPKFileEntry)
+{
+ STRUCT_SIZE(0x44);
+ FIELDS(0x0,
+ char* directory;
+ char* filename;
+ char* extension;
+ )
+};
+// clang-format on
+#pragma pack(pop)
+
+struct VPKData;
+
+enum SearchPathAdd_t
+{
+ PATH_ADD_TO_HEAD, // First path searched
+ PATH_ADD_TO_TAIL, // Last path searched
+};
+
+class CSearchPath
+{
+public:
+ unsigned char unknown[0x18];
+ const char* debugPath;
+};
+
+class IFileSystem
+{
+public:
+ struct VTable
+ {
+ void* unknown[10];
+ void (*AddSearchPath)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
+ void* unknown2[84];
+ bool (*ReadFromCache)(IFileSystem* fileSystem, const char* path, void* result);
+ void* unknown3[15];
+ VPKData* (*MountVPK)(IFileSystem* fileSystem, const char* vpkPath);
+ };
+
+ struct VTable2
+ {
+ int (*Read)(IFileSystem::VTable2** fileSystem, void* pOutput, int size, FileHandle_t file);
+ void* unknown[1];
+ 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];
+ bool (*FileExists)(IFileSystem::VTable2** fileSystem, const char* pFileName, const char* pPathID);
+ };
+
+ VTable* m_vtable;
+ VTable2* m_vtable2;
+};
+
+extern SourceInterface<IFileSystem>* g_pFilesystem;
+
+std::string ReadVPKFile(const char* path);
+std::string ReadVPKOriginalFile(const char* path);
diff --git a/primedev/core/filesystem/rpakfilesystem.cpp b/primedev/core/filesystem/rpakfilesystem.cpp
new file mode 100644
index 00000000..da72646b
--- /dev/null
+++ b/primedev/core/filesystem/rpakfilesystem.cpp
@@ -0,0 +1,347 @@
+#include "rpakfilesystem.h"
+#include "mods/modmanager.h"
+#include "dedicated/dedicated.h"
+#include "core/tier0.h"
+
+AUTOHOOK_INIT()
+
+// there are more i'm just too lazy to add
+struct PakLoadFuncs
+{
+ void* unk0[3];
+ int (*LoadPakAsync)(const char* pPath, void* unknownSingleton, int flags, void* callback0, void* callback1);
+ void* unk1[2];
+ void* (*UnloadPak)(int iPakHandle, void* callback);
+ void* unk2[6];
+ void* (*LoadFile)(const char* path); // unsure
+ void* unk3[10];
+ void* (*ReadFileAsync)(const char* pPath, void* a2);
+};
+
+PakLoadFuncs* g_pakLoadApi;
+
+PakLoadManager* g_pPakLoadManager;
+void** pUnknownPakLoadSingleton;
+
+int PakLoadManager::LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource)
+{
+ int nHandle = g_pakLoadApi->LoadPakAsync(pPath, *pUnknownPakLoadSingleton, 2, nullptr, nullptr);
+
+ // set the load source of the pak we just loaded
+ if (nHandle != -1)
+ GetPakInfo(nHandle)->m_nLoadSource = nLoadSource;
+
+ return nHandle;
+}
+
+void PakLoadManager::UnloadPak(const int nPakHandle)
+{
+ g_pakLoadApi->UnloadPak(nPakHandle, nullptr);
+}
+
+void PakLoadManager::UnloadMapPaks()
+{
+ for (auto& pair : m_vLoadedPaks)
+ if (pair.second.m_nLoadSource == ePakLoadSource::MAP)
+ UnloadPak(pair.first);
+}
+
+LoadedPak* PakLoadManager::TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash)
+{
+ LoadedPak pak;
+ pak.m_nLoadSource = nLoadSource;
+ pak.m_nPakHandle = nPakHandle;
+ pak.m_nPakNameHash = nPakNameHash;
+
+ m_vLoadedPaks.insert(std::make_pair(nPakHandle, pak));
+ return &m_vLoadedPaks.at(nPakHandle);
+}
+
+void PakLoadManager::RemoveLoadedPak(int nPakHandle)
+{
+ m_vLoadedPaks.erase(nPakHandle);
+}
+
+LoadedPak* PakLoadManager::GetPakInfo(const int nPakHandle)
+{
+ return &m_vLoadedPaks.at(nPakHandle);
+}
+
+int PakLoadManager::GetPakHandle(const size_t nPakNameHash)
+{
+ for (auto& pair : m_vLoadedPaks)
+ if (pair.second.m_nPakNameHash == nPakNameHash)
+ return pair.first;
+
+ return -1;
+}
+
+int PakLoadManager::GetPakHandle(const char* pPath)
+{
+ return GetPakHandle(STR_HASH(pPath));
+}
+
+void* PakLoadManager::LoadFile(const char* path)
+{
+ return g_pakLoadApi->LoadFile(path);
+}
+
+void HandlePakAliases(char** map)
+{
+ // convert the pak being loaded to it's aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift
+ for (int64_t i = g_pModManager->m_LoadedMods.size() - 1; i > -1; i--)
+ {
+ Mod* mod = &g_pModManager->m_LoadedMods[i];
+ if (!mod->m_bEnabled)
+ continue;
+
+ if (mod->RpakAliases.find(*map) != mod->RpakAliases.end())
+ {
+ *map = &mod->RpakAliases[*map][0];
+ return;
+ }
+ }
+}
+
+void LoadPreloadPaks()
+{
+ // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks
+ for (Mod& mod : g_pModManager->m_LoadedMods)
+ {
+ if (!mod.m_bEnabled)
+ continue;
+
+ // need to get a relative path of mod to mod folder
+ fs::path modPakPath("./" / mod.m_ModDirectory / "paks");
+
+ for (ModRpakEntry& pak : mod.Rpaks)
+ if (pak.m_bAutoLoad)
+ g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT);
+ }
+}
+
+void LoadPostloadPaks(const char* pPath)
+{
+ // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks
+ for (Mod& mod : g_pModManager->m_LoadedMods)
+ {
+ if (!mod.m_bEnabled)
+ continue;
+
+ // need to get a relative path of mod to mod folder
+ fs::path modPakPath("./" / mod.m_ModDirectory / "paks");
+
+ for (ModRpakEntry& pak : mod.Rpaks)
+ if (pak.m_sLoadAfterPak == pPath)
+ g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT);
+ }
+}
+
+void LoadCustomMapPaks(char** pakName, bool* bNeedToFreePakName)
+{
+ // whether the vanilla game has this rpak
+ bool bHasOriginalPak = fs::exists(fs::path("r2/paks/Win64/") / *pakName);
+
+ // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks
+ for (Mod& mod : g_pModManager->m_LoadedMods)
+ {
+ if (!mod.m_bEnabled)
+ continue;
+
+ // need to get a relative path of mod to mod folder
+ fs::path modPakPath("./" / mod.m_ModDirectory / "paks");
+
+ for (ModRpakEntry& pak : mod.Rpaks)
+ {
+ if (!pak.m_bAutoLoad && !pak.m_sPakName.compare(*pakName))
+ {
+ // if the game doesn't have the original pak, let it handle loading this one as if it was the one it was loading originally
+ if (!bHasOriginalPak)
+ {
+ std::string path = (modPakPath / pak.m_sPakName).string();
+ *pakName = new char[path.size() + 1];
+ strcpy(*pakName, &path[0]);
+ (*pakName)[path.size()] = '\0';
+
+ bHasOriginalPak = true;
+ *bNeedToFreePakName =
+ true; // we can't free this memory until we're done with the pak, so let whatever's calling this deal with it
+ }
+ else
+ g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::MAP);
+ }
+ }
+ }
+}
+
+// clang-format off
+HOOK(LoadPakAsyncHook, LoadPakAsync,
+int, __fastcall, (char* pPath, void* unknownSingleton, int flags, void* pCallback0, void* pCallback1))
+// clang-format on
+{
+ HandlePakAliases(&pPath);
+
+ // dont load the pak if it's currently loaded already
+ size_t nPathHash = STR_HASH(pPath);
+ if (g_pPakLoadManager->GetPakHandle(nPathHash) != -1)
+ return -1;
+
+ bool bNeedToFreePakName = false;
+
+ static bool bShouldLoadPaks = true;
+ if (bShouldLoadPaks)
+ {
+ // make a copy of the path for comparing to determine whether we should load this pak on dedi, before it could get overwritten by
+ // LoadCustomMapPaks
+ std::string originalPath(pPath);
+
+ // disable preloading while we're doing this
+ bShouldLoadPaks = false;
+
+ LoadPreloadPaks();
+ LoadCustomMapPaks(&pPath, &bNeedToFreePakName);
+
+ bShouldLoadPaks = true;
+
+ // do this after custom paks load and in bShouldLoadPaks so we only ever call this on the root pakload call
+ // todo: could probably add some way to flag custom paks to not be loaded on dedicated servers in rpak.json
+
+ // dedicated only needs common, common_mp, common_sp, and sp_<map> rpaks
+ // sp_<map> rpaks contain tutorial ghost data
+ // sucks to have to load the entire rpak for that but sp was never meant to be done on dedi
+ if (IsDedicatedServer() &&
+ (CommandLine()->CheckParm("-nopakdedi") || strncmp(&originalPath[0], "common", 6) && strncmp(&originalPath[0], "sp_", 3)))
+ {
+ if (bNeedToFreePakName)
+ delete[] pPath;
+
+ NS::log::rpak->info("Not loading pak {} for dedicated server", originalPath);
+ return -1;
+ }
+ }
+
+ int iPakHandle = LoadPakAsync(pPath, unknownSingleton, flags, pCallback0, pCallback1);
+ NS::log::rpak->info("LoadPakAsync {} {}", pPath, iPakHandle);
+
+ // trak the pak
+ g_pPakLoadManager->TrackLoadedPak(ePakLoadSource::UNTRACKED, iPakHandle, nPathHash);
+ LoadPostloadPaks(pPath);
+
+ if (bNeedToFreePakName)
+ delete[] pPath;
+
+ return iPakHandle;
+}
+
+// clang-format off
+HOOK(UnloadPakHook, UnloadPak,
+void*, __fastcall, (int nPakHandle, void* pCallback))
+// clang-format on
+{
+ // stop tracking the pak
+ g_pPakLoadManager->RemoveLoadedPak(nPakHandle);
+
+ static bool bShouldUnloadPaks = true;
+ if (bShouldUnloadPaks)
+ {
+ bShouldUnloadPaks = false;
+ g_pPakLoadManager->UnloadMapPaks();
+ bShouldUnloadPaks = true;
+ }
+
+ NS::log::rpak->info("UnloadPak {}", nPakHandle);
+ return UnloadPak(nPakHandle, pCallback);
+}
+
+// we hook this exclusively for resolving stbsp paths, but seemingly it's also used for other stuff like vpk, rpak, mprj and starpak loads
+// tbh this actually might be for memory mapped files or something, would make sense i think
+// clang-format off
+HOOK(ReadFileAsyncHook, ReadFileAsync,
+void*, __fastcall, (const char* pPath, void* pCallback))
+// clang-format on
+{
+ fs::path path(pPath);
+ std::string newPath = "";
+ fs::path filename = path.filename();
+
+ if (path.extension() == ".stbsp")
+ {
+ if (IsDedicatedServer())
+ return nullptr;
+
+ NS::log::rpak->info("LoadStreamBsp: {}", filename.string());
+
+ // resolve modded stbsp path so we can load mod stbsps
+ auto modFile = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path("maps" / filename)));
+ if (modFile != g_pModManager->m_ModFiles.end())
+ {
+ newPath = (modFile->second.m_pOwningMod->m_ModDirectory / "mod" / modFile->second.m_Path).string();
+ pPath = newPath.c_str();
+ }
+ }
+ else if (path.extension() == ".starpak")
+ {
+ if (IsDedicatedServer())
+ return nullptr;
+
+ // code for this is mostly stolen from above
+
+ // unfortunately I can't find a way to get the rpak that is causing this function call, so I have to
+ // store them on mod init and then compare the current path with the stored paths
+
+ // game adds r2\ to every path, so assume that a starpak path that begins with r2\paks\ is a vanilla one
+ // modded starpaks will be in the mod's paks folder (but can be in folders within the paks folder)
+
+ // this might look a bit confusing, but its just an iterator over the various directories in a path.
+ // path.begin() being the first directory, r2 in this case, which is guaranteed anyway,
+ // so increment the iterator with ++ to get the first actual directory, * just gets the actual value
+ // then we compare to "paks" to determine if it's a vanilla rpak or not
+ if (*++path.begin() != "paks")
+ {
+ // remove the r2\ from the start used for path lookups
+ std::string starpakPath = path.string().substr(3);
+ // hash the starpakPath to compare with stored entries
+ size_t hashed = STR_HASH(starpakPath);
+
+ // loop through all loaded mods
+ for (Mod& mod : g_pModManager->m_LoadedMods)
+ {
+ // ignore non-loaded mods
+ if (!mod.m_bEnabled)
+ continue;
+
+ // loop through the stored starpak paths
+ for (size_t hash : mod.StarpakPaths)
+ {
+ if (hash == hashed)
+ {
+ // construct new path
+ newPath = (mod.m_ModDirectory / "paks" / starpakPath).string();
+ // set path to the new path
+ pPath = newPath.c_str();
+ goto LOG_STARPAK;
+ }
+ }
+ }
+ }
+
+ LOG_STARPAK:
+ NS::log::rpak->info("LoadStreamPak: {}", filename.string());
+ }
+
+ return ReadFileAsync(pPath, pCallback);
+}
+
+ON_DLL_LOAD("engine.dll", RpakFilesystem, (CModule module))
+{
+ AUTOHOOK_DISPATCH();
+
+ g_pPakLoadManager = new PakLoadManager;
+
+ g_pakLoadApi = module.Offset(0x5BED78).Deref().RCast<PakLoadFuncs*>();
+ pUnknownPakLoadSingleton = module.Offset(0x7C5E20).RCast<void**>();
+
+ LoadPakAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->LoadPakAsync);
+ UnloadPakHook.Dispatch((LPVOID*)g_pakLoadApi->UnloadPak);
+ ReadFileAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->ReadFileAsync);
+}
diff --git a/primedev/core/filesystem/rpakfilesystem.h b/primedev/core/filesystem/rpakfilesystem.h
new file mode 100644
index 00000000..bcd57a73
--- /dev/null
+++ b/primedev/core/filesystem/rpakfilesystem.h
@@ -0,0 +1,39 @@
+#pragma once
+
+enum class ePakLoadSource
+{
+ UNTRACKED = -1, // not a pak we loaded, we shouldn't touch this one
+
+ CONSTANT, // should be loaded at all times
+ MAP // loaded from a map, should be unloaded when the map is unloaded
+};
+
+struct LoadedPak
+{
+ ePakLoadSource m_nLoadSource;
+ int m_nPakHandle;
+ size_t m_nPakNameHash;
+};
+
+class PakLoadManager
+{
+private:
+ std::map<int, LoadedPak> m_vLoadedPaks {};
+ std::unordered_map<size_t, int> m_HashToPakHandle {};
+
+public:
+ int LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource);
+ void UnloadPak(const int nPakHandle);
+ void UnloadMapPaks();
+ void* LoadFile(const char* path); // this is a guess
+
+ LoadedPak* TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash);
+ void RemoveLoadedPak(int nPakHandle);
+
+ LoadedPak* GetPakInfo(const int nPakHandle);
+
+ int GetPakHandle(const size_t nPakNameHash);
+ int GetPakHandle(const char* pPath);
+};
+
+extern PakLoadManager* g_pPakLoadManager;
diff --git a/primedev/core/hooks.cpp b/primedev/core/hooks.cpp
new file mode 100644
index 00000000..26b3fe57
--- /dev/null
+++ b/primedev/core/hooks.cpp
@@ -0,0 +1,474 @@
+#include "dedicated/dedicated.h"
+#include "plugins/pluginbackend.h"
+
+#include <iostream>
+#include <wchar.h>
+#include <iostream>
+#include <vector>
+#include <fstream>
+#include <sstream>
+#include <filesystem>
+#include <Psapi.h>
+
+#define XINPUT1_3_DLL "XInput1_3.dll"
+
+AUTOHOOK_INIT()
+
+// called from the ON_DLL_LOAD macros
+__dllLoadCallback::__dllLoadCallback(
+ eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn)
+{
+ // parse reliesOn array from string
+ std::vector<std::string> reliesOnArray;
+
+ 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;
+ }
+ }
+}
+
+void __fileAutohook::Dispatch()
+{
+ for (__autovar* var : vars)
+ var->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;
+}
+
+uintptr_t ParseDLLOffsetString(const char* pAddrString)
+{
+ // 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)
+ return 0;
+
+ // 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);
+
+ return ((uintptr_t)pModuleAddr + iOffset);
+}
+
+// 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, (LPVOID)GetCommandLineA, LPSTR, WINAPI, ())
+{
+ static char* cmdlineModified;
+ static char* cmdlineOrg;
+
+ if (cmdlineOrg == nullptr || cmdlineModified == nullptr)
+ {
+ cmdlineOrg = _GetCommandLineA();
+ bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument
+ bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs");
+
+ std::string args;
+ std::ifstream cmdlineArgFile;
+
+ // it looks like CommandLine() prioritizes parameters apprearing first, so we want the real commandline to take priority
+ // not to mention that cmdlineOrg starts with the EXE path
+ args.append(cmdlineOrg);
+ args.append(" ");
+
+ // append those from the file
+
+ if (!ignoreStartupArgs)
+ {
+
+ cmdlineArgFile = std::ifstream(!isDedi ? "ns_startup_args.txt" : "ns_startup_args_dedi.txt");
+
+ if (cmdlineArgFile)
+ {
+ std::stringstream argBuffer;
+ argBuffer << cmdlineArgFile.rdbuf();
+ cmdlineArgFile.close();
+
+ // if some other command line option includes "-northstar" in the future then you have to refactor this check to check with
+ // both either space after or ending with
+ if (!isDedi && argBuffer.str().find("-northstar") != std::string::npos)
+ MessageBoxA(
+ NULL,
+ "The \"-northstar\" command line option is NOT supposed to go into ns_startup_args.txt file!\n\nThis option is "
+ "supposed to go into Origin/Steam game launch options, and then you are supposed to launch the original "
+ "Titanfall2.exe "
+ "rather than NorthstarLauncher.exe to make use of it.",
+ "Northstar Warning",
+ MB_ICONWARNING);
+
+ args.append(argBuffer.str());
+ }
+ }
+
+ auto len = args.length();
+ cmdlineModified = new char[len + 1];
+ if (!cmdlineModified)
+ {
+ spdlog::error("malloc failed for command line");
+ return cmdlineOrg;
+ }
+ memcpy(cmdlineModified, args.c_str(), len + 1);
+ }
+
+ return cmdlineModified;
+}
+
+std::vector<std::string> calledTags;
+void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress)
+{
+ CModule cModule(moduleAddress);
+
+ while (true)
+ {
+ bool bDoneCalling = true;
+
+ for (auto& callbackStruct : GetDllLoadCallbacks())
+ {
+ if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
+ {
+ bool bShouldContinue = false;
+
+ if (!callbackStruct.reliesOn.empty())
+ {
+ for (std::string tag : callbackStruct.reliesOn)
+ {
+ if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
+ {
+ bDoneCalling = false;
+ bShouldContinue = true;
+ break;
+ }
+ }
+ }
+
+ if (bShouldContinue)
+ continue;
+
+ callbackStruct.callback(moduleAddress);
+ calledTags.push_back(callbackStruct.tag);
+ callbackStruct.called = true;
+ }
+ }
+
+ if (bDoneCalling)
+ break;
+ }
+}
+
+void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress)
+{
+ CModule cModule(moduleAddress);
+
+ while (true)
+ {
+ bool bDoneCalling = true;
+
+ for (auto& callbackStruct : GetDllLoadCallbacks())
+ {
+ if (!callbackStruct.called && fs::path(lpLibFileName).filename() == fs::path(callbackStruct.dll).filename())
+ {
+ bool bShouldContinue = false;
+
+ if (!callbackStruct.reliesOn.empty())
+ {
+ for (std::string tag : callbackStruct.reliesOn)
+ {
+ if (std::find(calledTags.begin(), calledTags.end(), tag) == calledTags.end())
+ {
+ bDoneCalling = false;
+ bShouldContinue = true;
+ break;
+ }
+ }
+ }
+
+ if (bShouldContinue)
+ continue;
+
+ callbackStruct.callback(moduleAddress);
+ calledTags.push_back(callbackStruct.tag);
+ callbackStruct.called = true;
+ }
+ }
+
+ if (bDoneCalling)
+ break;
+ }
+}
+
+void CallAllPendingDLLLoadCallbacks()
+{
+ HMODULE hMods[1024];
+ HANDLE hProcess = GetCurrentProcess();
+ DWORD cbNeeded;
+ unsigned int i;
+
+ // Get a list of all the modules in this process.
+ if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
+ {
+ for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
+ {
+ wchar_t szModName[MAX_PATH];
+
+ // Get the full path to the module's file.
+ if (GetModuleFileNameExW(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR)))
+ {
+ CallLoadLibraryWCallbacks(szModName, hMods[i]);
+ }
+ }
+ }
+}
+
+// clang-format off
+AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, (LPVOID)LoadLibraryExA,
+HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
+// clang-format on
+{
+ HMODULE moduleAddress;
+
+ LPCSTR lpLibFileNameEnd = lpLibFileName + strlen(lpLibFileName);
+ LPCSTR lpLibName = lpLibFileNameEnd - strlen(XINPUT1_3_DLL);
+
+ // replace xinput dll with one that has ASLR
+ if (lpLibFileName <= lpLibName && !strncmp(lpLibName, XINPUT1_3_DLL, strlen(XINPUT1_3_DLL) + 1))
+ {
+ moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags);
+
+ if (!moduleAddress)
+ {
+ MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR);
+ exit(EXIT_FAILURE);
+
+ return nullptr;
+ }
+ }
+ else
+ moduleAddress = _LoadLibraryExA(lpLibFileName, hFile, dwFlags);
+
+ if (moduleAddress)
+ {
+ CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
+ InformPluginsDLLLoad(fs::path(lpLibFileName), moduleAddress);
+ }
+
+ return moduleAddress;
+}
+
+// clang-format off
+AUTOHOOK_ABSOLUTEADDR(_LoadLibraryA, (LPVOID)LoadLibraryA,
+HMODULE, WINAPI, (LPCSTR lpLibFileName))
+// clang-format on
+{
+ HMODULE moduleAddress = _LoadLibraryA(lpLibFileName);
+
+ if (moduleAddress)
+ CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
+
+ return moduleAddress;
+}
+
+// clang-format off
+AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExW, (LPVOID)LoadLibraryExW,
+HMODULE, WINAPI, (LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
+// clang-format on
+{
+ HMODULE moduleAddress = _LoadLibraryExW(lpLibFileName, hFile, dwFlags);
+
+ if (moduleAddress)
+ CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
+
+ return moduleAddress;
+}
+
+// clang-format off
+AUTOHOOK_ABSOLUTEADDR(_LoadLibraryW, (LPVOID)LoadLibraryW,
+HMODULE, WINAPI, (LPCWSTR lpLibFileName))
+// clang-format on
+{
+ HMODULE moduleAddress = _LoadLibraryW(lpLibFileName);
+
+ if (moduleAddress)
+ {
+ CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
+ InformPluginsDLLLoad(fs::path(lpLibFileName), moduleAddress);
+ }
+
+ return moduleAddress;
+}
+
+void InstallInitialHooks()
+{
+ if (MH_Initialize() != MH_OK)
+ spdlog::error("MH_Initialize (minhook initialization) failed");
+
+ AUTOHOOK_DISPATCH()
+}
diff --git a/primedev/core/hooks.h b/primedev/core/hooks.h
new file mode 100644
index 00000000..15edbf0b
--- /dev/null
+++ b/primedev/core/hooks.h
@@ -0,0 +1,331 @@
+#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 = {});
+
+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 __autovar;
+
+class __fileAutohook
+{
+public:
+ std::vector<__autohook*> hooks;
+ std::vector<__autovar*> vars;
+
+ void Dispatch();
+ void DispatchForModule(const char* pModuleName);
+};
+
+uintptr_t ParseDLLOffsetString(const char* pAddrString);
+
+// 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:
+ {
+ targetAddr = (LPVOID)ParseDLLOffsetString(pAddrString);
+ break;
+ }
+
+ case PROCADDRESS:
+ {
+ targetAddr = (LPVOID)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(*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(*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(*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(*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((LPVOID)pTarget, (LPVOID)pDetour, (void*)ppOriginal, __STR(pDetour))
+
+class __autovar
+{
+public:
+ char* m_pAddrString;
+ void** m_pTarget;
+
+public:
+ __autovar(__fileAutohook* pAutohook, const char* pAddrString, void** pTarget)
+ {
+ m_pTarget = pTarget;
+
+ const int iAddrStrlen = strlen(pAddrString) + 1;
+ m_pAddrString = new char[iAddrStrlen];
+ memcpy(m_pAddrString, pAddrString, iAddrStrlen);
+
+ pAutohook->vars.push_back(this);
+ }
+
+ void Dispatch()
+ {
+ *m_pTarget = (void*)ParseDLLOffsetString(m_pAddrString);
+ }
+};
+
+// VAR_AT(engine.dll+0x404, ConVar*, Cvar_host_timescale)
+#define VAR_AT(addrString, type, name) \
+ type name; \
+ namespace \
+ { \
+ __autovar CONCAT2(__autovar, __LINE__)(&__FILEAUTOHOOK, __STR(addrString), (void**)&name); \
+ }
+
+// FUNCTION_AT(engine.dll + 0xDEADBEEF, void, __fastcall, SomeFunc, (void* a1))
+#define FUNCTION_AT(addrString, type, callingConvention, name, args) \
+ type(*name) args; \
+ namespace \
+ { \
+ __autovar CONCAT2(__autovar, __LINE__)(&__FILEAUTOHOOK, __STR(addrString), (void**)&name); \
+ }
+
+// int* g_pSomeInt;
+// DEFINED_VAR_AT(engine.dll + 0x5005, g_pSomeInt)
+#define DEFINED_VAR_AT(addrString, name) \
+ namespace \
+ { \
+ __autovar CONCAT2(__autovar, __LINE__)(&__FILEAUTOHOOK, __STR(addrString), (void**)&name); \
+ }
diff --git a/primedev/core/macros.h b/primedev/core/macros.h
new file mode 100644
index 00000000..ae944cca
--- /dev/null
+++ b/primedev/core/macros.h
@@ -0,0 +1,19 @@
+#pragma once
+template <typename ReturnType, typename... Args> ReturnType CallVFunc(int index, void* thisPtr, Args... args)
+{
+ return (*reinterpret_cast<ReturnType(__fastcall***)(void*, Args...)>(thisPtr))[index](thisPtr, args...);
+}
+
+template <typename T, size_t index, typename... Args> constexpr T CallVFunc_Alt(void* classBase, Args... args) noexcept
+{
+ return ((*(T(__thiscall***)(void*, Args...))(classBase))[index])(classBase, args...);
+}
+
+#define STR_HASH(s) (std::hash<std::string>()(s))
+
+// Example usage: M_VMETHOD(int, GetEntityIndex, 8, (CBaseEntity* ent), (this, ent))
+#define M_VMETHOD(returnType, name, index, args, argsRaw) \
+ FORCEINLINE returnType name args noexcept \
+ { \
+ return CallVFunc_Alt<returnType, index> argsRaw; \
+ }
diff --git a/primedev/core/math/bitbuf.h b/primedev/core/math/bitbuf.h
new file mode 100644
index 00000000..5ca75455
--- /dev/null
+++ b/primedev/core/math/bitbuf.h
@@ -0,0 +1,1148 @@
+#pragma once
+
+#define INLINE inline
+
+#define BITS_PER_INT 32
+
+INLINE int GetBitForBitnum(int bitNum)
+{
+ static int bitsForBitnum[] = {
+ (1 << 0), (1 << 1), (1 << 2), (1 << 3), (1 << 4), (1 << 5), (1 << 6), (1 << 7), (1 << 8), (1 << 9), (1 << 10),
+ (1 << 11), (1 << 12), (1 << 13), (1 << 14), (1 << 15), (1 << 16), (1 << 17), (1 << 18), (1 << 19), (1 << 20), (1 << 21),
+ (1 << 22), (1 << 23), (1 << 24), (1 << 25), (1 << 26), (1 << 27), (1 << 28), (1 << 29), (1 << 30), (1 << 31),
+ };
+
+ return bitsForBitnum[(bitNum) & (BITS_PER_INT - 1)];
+}
+
+#undef BITS_PER_INT
+
+using u8 = uint8_t;
+using u16 = uint16_t;
+using u32 = uint32_t;
+using u64 = uint64_t;
+using uptr = uintptr_t;
+
+using i8 = int8_t;
+using i16 = int16_t;
+using i32 = int32_t;
+using i64 = int64_t;
+using iptr = intptr_t;
+
+// Endianess, don't use on PPC64 nor ARM64BE
+#define LittleDWord(val) (val)
+
+static INLINE void StoreLittleDWord(u32* base, size_t dwordIndex, u32 dword)
+{
+ base[dwordIndex] = LittleDWord(dword);
+}
+
+static INLINE u32 LoadLittleDWord(u32* base, size_t dwordIndex)
+{
+ return LittleDWord(base[dwordIndex]);
+}
+
+#include <algorithm>
+
+static inline const u32 s_nMaskTable[33] = {
+ 0,
+ (1 << 1) - 1,
+ (1 << 2) - 1,
+ (1 << 3) - 1,
+ (1 << 4) - 1,
+ (1 << 5) - 1,
+ (1 << 6) - 1,
+ (1 << 7) - 1,
+ (1 << 8) - 1,
+ (1 << 9) - 1,
+ (1 << 10) - 1,
+ (1 << 11) - 1,
+ (1 << 12) - 1,
+ (1 << 13) - 1,
+ (1 << 14) - 1,
+ (1 << 15) - 1,
+ (1 << 16) - 1,
+ (1 << 17) - 1,
+ (1 << 18) - 1,
+ (1 << 19) - 1,
+ (1 << 20) - 1,
+ (1 << 21) - 1,
+ (1 << 22) - 1,
+ (1 << 23) - 1,
+ (1 << 24) - 1,
+ (1 << 25) - 1,
+ (1 << 26) - 1,
+ (1 << 27) - 1,
+ (1 << 28) - 1,
+ (1 << 29) - 1,
+ (1 << 30) - 1,
+ 0x7fffffff,
+ 0xffffffff,
+};
+
+enum EBitCoordType
+{
+ kCW_None,
+ kCW_LowPrecision,
+ kCW_Integral
+};
+
+class BitBufferBase
+{
+protected:
+ INLINE void SetName(const char* name)
+ {
+ m_BufferName = name;
+ }
+
+public:
+ INLINE bool IsOverflowed()
+ {
+ return m_Overflow;
+ }
+ INLINE void SetOverflowed()
+ {
+ m_Overflow = true;
+ }
+
+ INLINE const char* GetName()
+ {
+ return m_BufferName;
+ }
+
+private:
+ const char* m_BufferName = "";
+
+protected:
+ u8 m_Overflow = false;
+};
+
+class BFRead : public BitBufferBase
+{
+public:
+ BFRead() = default;
+
+ INLINE BFRead(uptr data, size_t byteLength, size_t startPos = 0, const char* bufferName = 0)
+ {
+ StartReading(data, byteLength, startPos);
+
+ if (bufferName)
+ SetName(bufferName);
+ }
+
+public:
+ INLINE void StartReading(uptr data, size_t byteLength, size_t startPos = 0)
+ {
+ m_Data = reinterpret_cast<u32 const*>(data);
+ m_DataIn = m_Data;
+
+ m_DataBytes = byteLength;
+ m_DataBits = byteLength << 3;
+
+ m_DataEnd = reinterpret_cast<u32 const*>(reinterpret_cast<u8 const*>(m_Data) + m_DataBytes);
+
+ Seek(startPos);
+ }
+
+ INLINE void GrabNextDWord(bool overflow = false)
+ {
+ if (m_Data == m_DataEnd)
+ {
+ m_CachedBitsLeft = 1;
+ m_CachedBufWord = 0;
+
+ m_DataIn++;
+
+ if (overflow)
+ SetOverflowed();
+ }
+ else
+ {
+ if (m_DataIn > m_DataEnd)
+ {
+ SetOverflowed();
+ m_CachedBufWord = 0;
+ }
+ else
+ {
+ m_CachedBufWord = LittleDWord(*(m_DataIn++));
+ }
+ }
+ }
+
+ INLINE void FetchNext()
+ {
+ m_CachedBitsLeft = 32;
+ GrabNextDWord(false);
+ }
+
+ INLINE i32 ReadOneBit()
+ {
+ i32 ret = m_CachedBufWord & 1;
+
+ if (--m_CachedBitsLeft == 0)
+ FetchNext();
+ else
+ m_CachedBufWord >>= 1;
+
+ return ret;
+ }
+
+ INLINE u32 ReadUBitLong(i32 numBits)
+ {
+ if (m_CachedBitsLeft >= numBits)
+ {
+ u32 ret = m_CachedBufWord & s_nMaskTable[numBits];
+
+ m_CachedBitsLeft -= numBits;
+
+ if (m_CachedBitsLeft)
+ m_CachedBufWord >>= numBits;
+ else
+ FetchNext();
+
+ return ret;
+ }
+ else
+ {
+ // need to merge words
+ u32 ret = m_CachedBufWord;
+ numBits -= m_CachedBitsLeft;
+
+ GrabNextDWord(true);
+
+ if (IsOverflowed())
+ return 0;
+
+ ret |= ((m_CachedBufWord & s_nMaskTable[numBits]) << m_CachedBitsLeft);
+
+ m_CachedBitsLeft = 32 - numBits;
+ m_CachedBufWord >>= numBits;
+
+ return ret;
+ }
+ }
+
+ INLINE i32 ReadSBitLong(int numBits)
+ {
+ i32 ret = ReadUBitLong(numBits);
+ return (ret << (32 - numBits)) >> (32 - numBits);
+ }
+
+ INLINE u32 ReadUBitVar()
+ {
+ u32 ret = ReadUBitLong(6);
+
+ switch (ret & (16 | 32))
+ {
+ case 16:
+ ret = (ret & 15) | (ReadUBitLong(4) << 4);
+ // Assert(ret >= 16);
+ break;
+ case 32:
+ ret = (ret & 15) | (ReadUBitLong(8) << 4);
+ // Assert(ret >= 256);
+ break;
+ case 48:
+ ret = (ret & 15) | (ReadUBitLong(32 - 4) << 4);
+ // Assert(ret >= 4096);
+ break;
+ }
+
+ return ret;
+ }
+
+ INLINE u32 PeekUBitLong(i32 numBits)
+ {
+ i32 nSaveBA = m_CachedBitsLeft;
+ i32 nSaveW = m_CachedBufWord;
+ u32 const* pSaveP = m_DataIn;
+ u32 nRet = ReadUBitLong(numBits);
+
+ m_CachedBitsLeft = nSaveBA;
+ m_CachedBufWord = nSaveW;
+ m_DataIn = pSaveP;
+
+ return nRet;
+ }
+
+ INLINE float ReadBitFloat()
+ {
+ u32 value = ReadUBitLong(32);
+ return *reinterpret_cast<float*>(&value);
+ }
+
+ /*INLINE float ReadBitCoord() {
+ i32 intval = 0, fractval = 0, signbit = 0;
+ float value = 0.0;
+
+ // Read the required integer and fraction flags
+ intval = ReadOneBit();
+ fractval = ReadOneBit();
+
+ // If we got either parse them, otherwise it's a zero.
+ if (intval || fractval) {
+ // Read the sign bit
+ signbit = ReadOneBit();
+
+ // If there's an integer, read it in
+ if (intval) {
+ // Adjust the integers from [0..MAX_COORD_VALUE-1] to [1..MAX_COORD_VALUE]
+ intval = ReadUBitLong(COORD_INTEGER_BITS) + 1;
+ }
+
+ // If there's a fraction, read it in
+ if (fractval) {
+ fractval = ReadUBitLong(COORD_FRACTIONAL_BITS);
+ }
+
+ // Calculate the correct floating point value
+ value = intval + ((float)fractval * COORD_RESOLUTION);
+
+ // Fixup the sign if negative.
+ if (signbit)
+ value = -value;
+ }
+
+ return value;
+ }
+
+ INLINE float ReadBitCoordMP() {
+ i32 intval = 0, fractval = 0, signbit = 0;
+ float value = 0.0;
+
+ bool inBounds = ReadOneBit() ? true : false;
+
+ // Read the required integer and fraction flags
+ intval = ReadOneBit();
+
+ // If we got either parse them, otherwise it's a zero.
+ if (intval) {
+ // Read the sign bit
+ signbit = ReadOneBit();
+
+ // If there's an integer, read it in
+ // Adjust the integers from [0..MAX_COORD_VALUE-1] to [1..MAX_COORD_VALUE]
+ if (inBounds)
+ value = ReadUBitLong(COORD_INTEGER_BITS_MP) + 1;
+ else
+ value = ReadUBitLong(COORD_INTEGER_BITS) + 1;
+ }
+
+ // Fixup the sign if negative.
+ if (signbit)
+ value = -value;
+
+ return value;
+ }
+
+ INLINE float ReadBitCellCoord(int bits, EBitCoordType coordType) {
+ bool bIntegral = (coordType == kCW_Integral);
+ bool bLowPrecision = (coordType == kCW_LowPrecision);
+
+ int intval = 0, fractval = 0;
+ float value = 0.0;
+
+ if (bIntegral)
+ value = ReadUBitLong(bits);
+ else {
+ intval = ReadUBitLong(bits);
+
+ // If there's a fraction, read it in
+ fractval = ReadUBitLong(bLowPrecision ? COORD_FRACTIONAL_BITS_MP_LOWPRECISION : COORD_FRACTIONAL_BITS);
+
+ // Calculate the correct floating point value
+ value = intval + ((float)fractval * (bLowPrecision ? COORD_RESOLUTION_LOWPRECISION : COORD_RESOLUTION));
+ }
+
+ return value;
+ }
+
+ INLINE float ReadBitNormal() {
+ // Read the sign bit
+ i32 signbit = ReadOneBit();
+
+ // Read the fractional part
+ u32 fractval = ReadUBitLong(NORMAL_FRACTIONAL_BITS);
+
+ // Calculate the correct floating point value
+ float value = (float)fractval * NORMAL_RESOLUTION;
+
+ // Fixup the sign if negative.
+ if (signbit)
+ value = -value;
+
+ return value;
+ }
+
+ INLINE void ReadBitVec3Coord(Vector& fa) {
+ i32 xflag, yflag, zflag;
+
+ // This vector must be initialized! Otherwise, If any of the flags aren't set,
+ // the corresponding component will not be read and will be stack garbage.
+ fa.Init(0, 0, 0);
+
+ xflag = ReadOneBit();
+ yflag = ReadOneBit();
+ zflag = ReadOneBit();
+
+ if (xflag)
+ fa[0] = ReadBitCoord();
+ if (yflag)
+ fa[1] = ReadBitCoord();
+ if (zflag)
+ fa[2] = ReadBitCoord();
+ }
+
+ INLINE void ReadBitVec3Normal(Vector& fa) {
+ i32 xflag = ReadOneBit();
+ i32 yflag = ReadOneBit();
+
+ if (xflag)
+ fa[0] = ReadBitNormal();
+ else
+ fa[0] = 0.0f;
+
+ if (yflag)
+ fa[1] = ReadBitNormal();
+ else
+ fa[1] = 0.0f;
+
+ // The first two imply the third (but not its sign)
+ i32 znegative = ReadOneBit();
+
+ float fafafbfb = fa[0] * fa[0] + fa[1] * fa[1];
+ if (fafafbfb < 1.0f)
+ fa[2] = sqrt(1.0f - fafafbfb);
+ else
+ fa[2] = 0.0f;
+
+ if (znegative)
+ fa[2] = -fa[2];
+ }
+
+ INLINE void ReadBitAngles(QAngle& fa) {
+ Vector tmp;
+ ReadBitVec3Coord(tmp);
+ fa.Init(tmp.x, tmp.y, tmp.z);
+ }*/
+
+ INLINE float ReadBitAngle(int numBits)
+ {
+ float shift = (float)(GetBitForBitnum(numBits));
+
+ i32 i = ReadUBitLong(numBits);
+ float fReturn = (float)i * (360.0 / shift);
+
+ return fReturn;
+ }
+
+ INLINE i32 ReadChar()
+ {
+ return ReadSBitLong(sizeof(char) << 3);
+ }
+ INLINE u32 ReadByte()
+ {
+ return ReadUBitLong(sizeof(unsigned char) << 3);
+ }
+
+ INLINE i32 ReadShort()
+ {
+ return ReadSBitLong(sizeof(short) << 3);
+ }
+ INLINE u32 ReadWord()
+ {
+ return ReadUBitLong(sizeof(unsigned short) << 3);
+ }
+
+ INLINE i32 ReadLong()
+ {
+ return (i32)(ReadUBitLong(sizeof(i32) << 3));
+ }
+ INLINE float ReadFloat()
+ {
+ u32 temp = ReadUBitLong(sizeof(float) << 3);
+ return *reinterpret_cast<float*>(&temp);
+ }
+
+ INLINE u32 ReadVarInt32()
+ {
+ constexpr int kMaxVarint32Bytes = 5;
+
+ u32 result = 0;
+ int count = 0;
+ u32 b;
+
+ do
+ {
+ if (count == kMaxVarint32Bytes)
+ return result;
+
+ b = ReadUBitLong(8);
+ result |= (b & 0x7F) << (7 * count);
+ ++count;
+ } while (b & 0x80);
+
+ return result;
+ }
+
+ INLINE u64 ReadVarInt64()
+ {
+ constexpr int kMaxVarintBytes = 10;
+
+ u64 result = 0;
+ int count = 0;
+ u64 b;
+
+ do
+ {
+ if (count == kMaxVarintBytes)
+ return result;
+
+ b = ReadUBitLong(8);
+ result |= static_cast<u64>(b & 0x7F) << (7 * count);
+ ++count;
+ } while (b & 0x80);
+
+ return result;
+ }
+
+ INLINE void ReadBits(uptr outData, u32 bitLength)
+ {
+ u8* out = reinterpret_cast<u8*>(outData);
+ int bitsLeft = bitLength;
+
+ // align output to dword boundary
+ while (((uptr)out & 3) != 0 && bitsLeft >= 8)
+ {
+ *out = (unsigned char)ReadUBitLong(8);
+ ++out;
+ bitsLeft -= 8;
+ }
+
+ // read dwords
+ while (bitsLeft >= 32)
+ {
+ *((u32*)out) = ReadUBitLong(32);
+ out += sizeof(u32);
+ bitsLeft -= 32;
+ }
+
+ // read remaining bytes
+ while (bitsLeft >= 8)
+ {
+ *out = ReadUBitLong(8);
+ ++out;
+ bitsLeft -= 8;
+ }
+
+ // read remaining bits
+ if (bitsLeft)
+ *out = ReadUBitLong(bitsLeft);
+ }
+
+ INLINE bool ReadBytes(uptr outData, u32 byteLength)
+ {
+ ReadBits(outData, byteLength << 3);
+ return !IsOverflowed();
+ }
+
+ INLINE bool ReadString(char* str, i32 maxLength, bool stopAtLineTermination = false, i32* outNumChars = 0)
+ {
+ bool tooSmall = false;
+ int iChar = 0;
+
+ while (1)
+ {
+ char val = ReadChar();
+
+ if (val == 0)
+ break;
+ else if (stopAtLineTermination && val == '\n')
+ break;
+
+ if (iChar < (maxLength - 1))
+ {
+ str[iChar] = val;
+ ++iChar;
+ }
+ else
+ {
+ tooSmall = true;
+ }
+ }
+
+ // Make sure it's null-terminated.
+ // Assert(iChar < maxLength);
+ str[iChar] = 0;
+
+ if (outNumChars)
+ *outNumChars = iChar;
+
+ return !IsOverflowed() && !tooSmall;
+ }
+
+ INLINE char* ReadAndAllocateString(bool* hasOverflowed = 0)
+ {
+ char str[2048];
+
+ int chars = 0;
+ bool overflowed = !ReadString(str, sizeof(str), false, &chars);
+
+ if (hasOverflowed)
+ *hasOverflowed = overflowed;
+
+ // Now copy into the output and return it;
+ char* ret = new char[chars + 1];
+ for (u32 i = 0; i <= chars; i++)
+ ret[i] = str[i];
+
+ return ret;
+ }
+
+ INLINE i64 ReadLongLong()
+ {
+ i64 retval;
+ u32* longs = (u32*)&retval;
+
+ // Read the two DWORDs according to network endian
+ const short endianIndex = 0x0100;
+ u8* idx = (u8*)&endianIndex;
+
+ longs[*idx++] = ReadUBitLong(sizeof(i32) << 3);
+ longs[*idx] = ReadUBitLong(sizeof(i32) << 3);
+
+ return retval;
+ }
+
+ INLINE bool Seek(size_t startPos)
+ {
+ bool bSucc = true;
+
+ if (startPos < 0 || startPos > m_DataBits)
+ {
+ SetOverflowed();
+ bSucc = false;
+ startPos = m_DataBits;
+ }
+
+ // non-multiple-of-4 bytes at head of buffer. We put the "round off"
+ // at the head to make reading and detecting the end efficient.
+ int nHead = m_DataBytes & 3;
+
+ int posBytes = startPos / 8;
+ if ((m_DataBytes < 4) || (nHead && (posBytes < nHead)))
+ {
+ // partial first dword
+ u8 const* partial = (u8 const*)m_Data;
+
+ if (m_Data)
+ {
+ m_CachedBufWord = *(partial++);
+ if (nHead > 1)
+ m_CachedBufWord |= (*partial++) << 8;
+ if (nHead > 2)
+ m_CachedBufWord |= (*partial++) << 16;
+ }
+
+ m_DataIn = (u32 const*)partial;
+
+ m_CachedBufWord >>= (startPos & 31);
+ m_CachedBitsLeft = (nHead << 3) - (startPos & 31);
+ }
+ else
+ {
+ int adjustedPos = startPos - (nHead << 3);
+
+ m_DataIn = reinterpret_cast<u32 const*>(reinterpret_cast<u8 const*>(m_Data) + ((adjustedPos / 32) << 2) + nHead);
+
+ if (m_Data)
+ {
+ m_CachedBitsLeft = 32;
+ GrabNextDWord();
+ }
+ else
+ {
+ m_CachedBufWord = 0;
+ m_CachedBitsLeft = 1;
+ }
+
+ m_CachedBufWord >>= (adjustedPos & 31);
+ m_CachedBitsLeft = std::min(m_CachedBitsLeft, u32(32 - (adjustedPos & 31))); // in case grabnextdword overflowed
+ }
+
+ return bSucc;
+ }
+
+ INLINE size_t GetNumBitsRead()
+ {
+ if (!m_Data)
+ return 0;
+
+ size_t nCurOfs = size_t(((iptr(m_DataIn) - iptr(m_Data)) / 4) - 1);
+ nCurOfs *= 32;
+ nCurOfs += (32 - m_CachedBitsLeft);
+
+ size_t nAdjust = 8 * (m_DataBytes & 3);
+ return std::min(nCurOfs + nAdjust, m_DataBits);
+ }
+
+ INLINE bool SeekRelative(size_t offset)
+ {
+ return Seek(GetNumBitsRead() + offset);
+ }
+
+ INLINE size_t TotalBytesAvailable()
+ {
+ return m_DataBytes;
+ }
+
+ INLINE size_t GetNumBitsLeft()
+ {
+ return m_DataBits - GetNumBitsRead();
+ }
+ INLINE size_t GetNumBytesLeft()
+ {
+ return GetNumBitsLeft() >> 3;
+ }
+
+private:
+ size_t m_DataBits; // 0x0010
+ size_t m_DataBytes; // 0x0018
+
+ u32 m_CachedBufWord; // 0x0020
+ u32 m_CachedBitsLeft; // 0x0024
+
+ const u32* m_DataIn; // 0x0028
+ const u32* m_DataEnd; // 0x0030
+ const u32* m_Data; // 0x0038
+};
+
+class BFWrite : public BitBufferBase
+{
+public:
+ BFWrite() = default;
+
+ INLINE BFWrite(uptr data, size_t byteLength, const char* bufferName = 0)
+ {
+ StartWriting(data, byteLength);
+
+ if (bufferName)
+ SetName(bufferName);
+ }
+
+public:
+ INLINE void StartWriting(uptr data, size_t byteLength)
+ {
+ m_Data = reinterpret_cast<u32*>(data);
+ m_DataOut = m_Data;
+
+ m_DataBytes = byteLength;
+ m_DataBits = byteLength << 3;
+
+ m_DataEnd = reinterpret_cast<u32*>(reinterpret_cast<u8*>(m_Data) + m_DataBytes);
+ }
+
+ INLINE int GetNumBitsLeft()
+ {
+ return m_OutBitsLeft + (32 * (m_DataEnd - m_DataOut - 1));
+ }
+
+ INLINE void Reset()
+ {
+ m_Overflow = false;
+ m_OutBufWord = 0;
+ m_OutBitsLeft = 32;
+ m_DataOut = m_Data;
+ }
+
+ INLINE void TempFlush()
+ {
+ if (m_OutBitsLeft != 32)
+ {
+ if (m_DataOut == m_DataEnd)
+ SetOverflowed();
+ else
+ StoreLittleDWord(m_DataOut, 0, LoadLittleDWord(m_DataOut, 0) & ~s_nMaskTable[32 - m_OutBitsLeft] | m_OutBufWord);
+ }
+
+ m_Flushed = true;
+ }
+
+ INLINE u8* GetBasePointer()
+ {
+ TempFlush();
+ return reinterpret_cast<u8*>(m_Data);
+ }
+
+ INLINE u8* GetData()
+ {
+ return GetBasePointer();
+ }
+
+ INLINE void Finish()
+ {
+ if (m_OutBitsLeft != 32)
+ {
+ if (m_DataOut == m_DataEnd)
+ SetOverflowed();
+
+ StoreLittleDWord(m_DataOut, 0, m_OutBufWord);
+ }
+ }
+
+ INLINE void FlushNoCheck()
+ {
+ StoreLittleDWord(m_DataOut++, 0, m_OutBufWord);
+
+ m_OutBitsLeft = 32;
+ m_OutBufWord = 0;
+ }
+
+ INLINE void Flush()
+ {
+ if (m_DataOut == m_DataEnd)
+ SetOverflowed();
+ else
+ StoreLittleDWord(m_DataOut++, 0, m_OutBufWord);
+
+ m_OutBitsLeft = 32;
+ m_OutBufWord = 0;
+ }
+
+ INLINE void WriteOneBitNoCheck(i32 value)
+ {
+ m_OutBufWord |= (value & 1) << (32 - m_OutBitsLeft);
+
+ if (--m_OutBitsLeft == 0)
+ FlushNoCheck();
+ }
+
+ INLINE void WriteOneBit(i32 value)
+ {
+ m_OutBufWord |= (value & 1) << (32 - m_OutBitsLeft);
+
+ if (--m_OutBitsLeft == 0)
+ Flush();
+ }
+
+ INLINE void WriteUBitLong(u32 data, i32 numBits, bool checkRange = true)
+ {
+ if (numBits <= m_OutBitsLeft)
+ {
+ if (checkRange)
+ m_OutBufWord |= (data) << (32 - m_OutBitsLeft);
+ else
+ m_OutBufWord |= (data & s_nMaskTable[numBits]) << (32 - m_OutBitsLeft);
+
+ m_OutBitsLeft -= numBits;
+
+ if (m_OutBitsLeft == 0)
+ Flush();
+ }
+ else
+ {
+ // split dwords case
+ i32 overflowBits = (numBits - m_OutBitsLeft);
+ m_OutBufWord |= (data & s_nMaskTable[m_OutBitsLeft]) << (32 - m_OutBitsLeft);
+ Flush();
+ m_OutBufWord = (data >> (numBits - overflowBits));
+ m_OutBitsLeft = 32 - overflowBits;
+ }
+ }
+
+ INLINE void WriteSBitLong(i32 data, i32 numBits)
+ {
+ WriteUBitLong((u32)data, numBits, false);
+ }
+
+ INLINE void WriteUBitVar(u32 n)
+ {
+ if (n < 16)
+ WriteUBitLong(n, 6);
+ else if (n < 256)
+ WriteUBitLong((n & 15) | 16 | ((n & (128 | 64 | 32 | 16)) << 2), 10);
+ else if (n < 4096)
+ WriteUBitLong((n & 15) | 32 | ((n & (2048 | 1024 | 512 | 256 | 128 | 64 | 32 | 16)) << 2), 14);
+ else
+ {
+ WriteUBitLong((n & 15) | 48, 6);
+ WriteUBitLong((n >> 4), 32 - 4);
+ }
+ }
+
+ INLINE void WriteBitFloat(float value)
+ {
+ auto temp = &value;
+ WriteUBitLong(*reinterpret_cast<u32*>(temp), 32);
+ }
+
+ INLINE void WriteFloat(float value)
+ {
+ auto temp = &value;
+ WriteUBitLong(*reinterpret_cast<u32*>(temp), 32);
+ }
+
+ INLINE bool WriteBits(const uptr data, i32 numBits)
+ {
+ u8* out = (u8*)data;
+ i32 numBitsLeft = numBits;
+
+ // Bounds checking..
+ if ((GetNumBitsWritten() + numBits) > m_DataBits)
+ {
+ SetOverflowed();
+ return false;
+ }
+
+ // !! speed!! need fast paths
+ // write remaining bytes
+ while (numBitsLeft >= 8)
+ {
+ WriteUBitLong(*out, 8, false);
+ ++out;
+ numBitsLeft -= 8;
+ }
+
+ // write remaining bits
+ if (numBitsLeft)
+ WriteUBitLong(*out, numBitsLeft, false);
+
+ return !IsOverflowed();
+ }
+
+ INLINE bool WriteBytes(const uptr data, i32 numBytes)
+ {
+ return WriteBits(data, numBytes << 3);
+ }
+
+ INLINE i32 GetNumBitsWritten()
+ {
+ return (32 - m_OutBitsLeft) + (32 * (m_DataOut - m_Data));
+ }
+
+ INLINE i32 GetNumBytesWritten()
+ {
+ return (GetNumBitsWritten() + 7) >> 3;
+ }
+
+ INLINE void WriteChar(i32 val)
+ {
+ WriteSBitLong(val, sizeof(char) << 3);
+ }
+
+ INLINE void WriteByte(i32 val)
+ {
+ WriteUBitLong(val, sizeof(unsigned char) << 3, false);
+ }
+
+ INLINE void WriteShort(i32 val)
+ {
+ WriteSBitLong(val, sizeof(short) << 3);
+ }
+
+ INLINE void WriteWord(i32 val)
+ {
+ WriteUBitLong(val, sizeof(unsigned short) << 3);
+ }
+
+ INLINE bool WriteString(const char* str)
+ {
+ if (str)
+ while (*str)
+ WriteChar(*(str++));
+
+ WriteChar(0);
+
+ return !IsOverflowed();
+ }
+
+ INLINE void WriteLongLong(i64 val)
+ {
+ u32* pLongs = (u32*)&val;
+
+ // Insert the two DWORDS according to network endian
+ const short endianIndex = 0x0100;
+ u8* idx = (u8*)&endianIndex;
+
+ WriteUBitLong(pLongs[*idx++], sizeof(i32) << 3);
+ WriteUBitLong(pLongs[*idx], sizeof(i32) << 3);
+ }
+
+ /*INLINE void WriteBitCoord(const float f) {
+ i32 signbit = (f <= -COORD_RESOLUTION);
+ i32 intval = (i32)abs(f);
+ i32 fractval = abs((i32)(f * COORD_DENOMINATOR)) & (COORD_DENOMINATOR - 1);
+
+ // Send the bit flags that indicate whether we have an integer part and/or a fraction part.
+ WriteOneBit(intval);
+ WriteOneBit(fractval);
+
+ if (intval || fractval) {
+ // Send the sign bit
+ WriteOneBit(signbit);
+
+ // Send the integer if we have one.
+ if (intval) {
+ // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1]
+ intval--;
+ WriteUBitLong((u32)intval, COORD_INTEGER_BITS);
+ }
+
+ // Send the fraction if we have one
+ if (fractval) {
+ WriteUBitLong((u32)fractval, COORD_FRACTIONAL_BITS);
+ }
+ }
+ }
+
+ INLINE void WriteBitCoordMP(const float f) {
+ i32 signbit = (f <= -COORD_RESOLUTION);
+ i32 intval = (i32)abs(f);
+ i32 fractval = (abs((i32)(f * COORD_DENOMINATOR)) & (COORD_DENOMINATOR - 1));
+
+ bool bInBounds = intval < (1 << COORD_INTEGER_BITS_MP);
+
+ WriteOneBit(bInBounds);
+
+ // Send the sign bit
+ WriteOneBit(intval);
+
+ if (intval) {
+ WriteOneBit(signbit);
+
+ // Send the integer if we have one.
+ // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1]
+ intval--;
+
+ if (bInBounds)
+ WriteUBitLong((u32)intval, COORD_INTEGER_BITS_MP);
+ else
+ WriteUBitLong((u32)intval, COORD_INTEGER_BITS);
+ }
+ }
+
+ INLINE void WriteBitCellCoord(const float f, int bits, EBitCoordType coordType) {
+ bool bIntegral = (coordType == kCW_Integral);
+ bool bLowPrecision = (coordType == kCW_LowPrecision);
+
+ i32 intval = (i32)abs(f);
+ i32 fractval = bLowPrecision ? (abs((i32)(f * COORD_DENOMINATOR_LOWPRECISION)) & (COORD_DENOMINATOR_LOWPRECISION - 1)) :
+ (abs((i32)(f * COORD_DENOMINATOR)) & (COORD_DENOMINATOR - 1));
+
+ if (bIntegral)
+ WriteUBitLong((u32)intval, bits);
+ else {
+ WriteUBitLong((u32)intval, bits);
+ WriteUBitLong((u32)fractval, bLowPrecision ? COORD_FRACTIONAL_BITS_MP_LOWPRECISION : COORD_FRACTIONAL_BITS);
+ }
+ }*/
+
+ INLINE void SeekToBit(int bit)
+ {
+ TempFlush();
+
+ m_DataOut = m_Data + (bit / 32);
+ m_OutBufWord = LoadLittleDWord(m_DataOut, 0);
+ m_OutBitsLeft = 32 - (bit & 31);
+ }
+
+ /*INLINE void WriteBitVec3Coord(const Vector& fa) {
+ i32 xflag, yflag, zflag;
+
+ xflag = (fa[0] >= COORD_RESOLUTION) || (fa[0] <= -COORD_RESOLUTION);
+ yflag = (fa[1] >= COORD_RESOLUTION) || (fa[1] <= -COORD_RESOLUTION);
+ zflag = (fa[2] >= COORD_RESOLUTION) || (fa[2] <= -COORD_RESOLUTION);
+
+ WriteOneBit(xflag);
+ WriteOneBit(yflag);
+ WriteOneBit(zflag);
+
+ if (xflag)
+ WriteBitCoord(fa[0]);
+ if (yflag)
+ WriteBitCoord(fa[1]);
+ if (zflag)
+ WriteBitCoord(fa[2]);
+ }
+
+ INLINE void WriteBitNormal(float f) {
+ i32 signbit = (f <= -NORMAL_RESOLUTION);
+
+ // NOTE: Since +/-1 are valid values for a normal, I'm going to encode that as all ones
+ u32 fractval = abs((i32)(f * NORMAL_DENOMINATOR));
+
+ // clamp..
+ if (fractval > NORMAL_DENOMINATOR)
+ fractval = NORMAL_DENOMINATOR;
+
+ // Send the sign bit
+ WriteOneBit(signbit);
+
+ // Send the fractional component
+ WriteUBitLong(fractval, NORMAL_FRACTIONAL_BITS);
+ }
+
+ INLINE void WriteBitVec3Normal(const Vector& fa) {
+ i32 xflag, yflag;
+
+ xflag = (fa[0] >= NORMAL_RESOLUTION) || (fa[0] <= -NORMAL_RESOLUTION);
+ yflag = (fa[1] >= NORMAL_RESOLUTION) || (fa[1] <= -NORMAL_RESOLUTION);
+
+ WriteOneBit(xflag);
+ WriteOneBit(yflag);
+
+ if (xflag)
+ WriteBitNormal(fa[0]);
+ if (yflag)
+ WriteBitNormal(fa[1]);
+
+ // Write z sign bit
+ i32 signbit = (fa[2] <= -NORMAL_RESOLUTION);
+ WriteOneBit(signbit);
+ }*/
+
+ INLINE void WriteBitAngle(float angle, int numBits)
+ {
+ u32 shift = GetBitForBitnum(numBits);
+ u32 mask = shift - 1;
+
+ i32 d = (i32)((angle / 360.0) * shift);
+ d &= mask;
+
+ WriteUBitLong((u32)d, numBits);
+ }
+
+ INLINE bool WriteBitsFromBuffer(BFRead* in, int numBits)
+ {
+ while (numBits > 32)
+ {
+ WriteUBitLong(in->ReadUBitLong(32), 32);
+ numBits -= 32;
+ }
+
+ WriteUBitLong(in->ReadUBitLong(numBits), numBits);
+ return !IsOverflowed() && !in->IsOverflowed();
+ }
+
+ /*INLINE void WriteBitAngles(const QAngle& fa) {
+ // FIXME:
+ Vector tmp(fa.x, fa.y, fa.z);
+ WriteBitVec3Coord(tmp);
+ }*/
+
+private:
+ size_t m_DataBits = 0;
+ size_t m_DataBytes = 0;
+
+ u32 m_OutBufWord = 0;
+ u32 m_OutBitsLeft = 32;
+
+ u32* m_DataOut = nullptr;
+ u32* m_DataEnd = nullptr;
+ u32* m_Data = nullptr;
+
+ bool m_Flushed = false; // :flushed:
+};
+
+#undef INLINE
diff --git a/primedev/core/math/bits.cpp b/primedev/core/math/bits.cpp
new file mode 100644
index 00000000..c879a45c
--- /dev/null
+++ b/primedev/core/math/bits.cpp
@@ -0,0 +1,45 @@
+//=============================================================================//
+//
+// Purpose: look for NANs, infinities, and underflows.
+//
+//=============================================================================//
+
+#include "bits.h"
+
+//-----------------------------------------------------------------------------
+// This follows the ANSI/IEEE 754-1985 standard
+//-----------------------------------------------------------------------------
+unsigned long& FloatBits(float& f)
+{
+ return *reinterpret_cast<unsigned long*>(&f);
+}
+
+unsigned long const& FloatBits(float const& f)
+{
+ return *reinterpret_cast<unsigned long const*>(&f);
+}
+
+float BitsToFloat(unsigned long i)
+{
+ return *reinterpret_cast<float*>(&i);
+}
+
+bool IsFinite(float f)
+{
+ return ((FloatBits(f) & 0x7F800000) != 0x7F800000);
+}
+
+unsigned long FloatAbsBits(float f)
+{
+ return FloatBits(f) & 0x7FFFFFFF;
+}
+
+float FloatMakePositive(float f)
+{
+ return fabsf(f);
+}
+
+float FloatNegate(float f)
+{
+ return -f; // BitsToFloat( FloatBits(f) ^ 0x80000000 );
+}
diff --git a/primedev/core/math/bits.h b/primedev/core/math/bits.h
new file mode 100644
index 00000000..0532a9bd
--- /dev/null
+++ b/primedev/core/math/bits.h
@@ -0,0 +1,10 @@
+#pragma once
+
+unsigned long& FloatBits(float& f);
+unsigned long const& FloatBits(float const& f);
+float BitsToFloat(unsigned long i);
+bool IsFinite(float f);
+unsigned long FloatAbsBits(float f);
+
+#define FLOAT32_NAN_BITS (std::uint32_t)0x7FC00000 // NaN!
+#define FLOAT32_NAN BitsToFloat(FLOAT32_NAN_BITS)
diff --git a/primedev/core/math/color.cpp b/primedev/core/math/color.cpp
new file mode 100644
index 00000000..7b98043a
--- /dev/null
+++ b/primedev/core/math/color.cpp
@@ -0,0 +1,27 @@
+
+// clang-format off
+namespace NS::Colors
+{
+ Color SCRIPT_UI (100, 255, 255);
+ Color SCRIPT_CL (100, 255, 100);
+ Color SCRIPT_SV (255, 100, 255);
+ Color NATIVE_UI (50 , 150, 150);
+ Color NATIVE_CL (50 , 150, 50 );
+ Color NATIVE_SV (150, 50 , 150);
+ Color NATIVE_ENGINE (252, 133, 153);
+ Color FILESYSTEM (0 , 150, 150);
+ Color RPAK (255, 190, 0 );
+ Color NORTHSTAR (66 , 72 , 128);
+ Color ECHO (150, 150, 159);
+ Color PLUGINSYS (244, 60 , 14);
+ Color PLUGIN (244, 106, 14);
+
+ Color TRACE (0 , 255, 255);
+ Color DEBUG (0 , 255, 255);
+ Color INFO (16 , 160, 16 );
+ Color WARN (255, 255, 0 );
+ Color ERR (255, 50 , 50 );
+ Color CRIT (255, 0 , 0 );
+ Color OFF (0 , 0 , 0 );
+};
+// clang-format on
diff --git a/primedev/core/math/color.h b/primedev/core/math/color.h
new file mode 100644
index 00000000..013c4e4c
--- /dev/null
+++ b/primedev/core/math/color.h
@@ -0,0 +1,199 @@
+#pragma once
+
+struct color24
+{
+ uint8_t r, g, b;
+};
+
+typedef struct color32_s
+{
+ bool operator!=(const struct color32_s& other) const
+ {
+ return r != other.r || g != other.g || b != other.b || a != other.a;
+ }
+ inline unsigned* asInt(void)
+ {
+ return reinterpret_cast<unsigned*>(this);
+ }
+ inline const unsigned* asInt(void) const
+ {
+ return reinterpret_cast<const unsigned*>(this);
+ }
+ inline void Copy(const color32_s& rhs)
+ {
+ *asInt() = *rhs.asInt();
+ }
+
+ uint8_t r, g, b, a;
+} color32;
+
+struct SourceColor
+{
+ unsigned char R;
+ unsigned char G;
+ unsigned char B;
+ unsigned char A;
+
+ SourceColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+ {
+ R = r;
+ G = g;
+ B = b;
+ A = a;
+ }
+
+ SourceColor()
+ {
+ R = 0;
+ G = 0;
+ B = 0;
+ A = 0;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Basic handler for an rgb set of colors
+// This class is fully inline
+//-----------------------------------------------------------------------------
+class Color
+{
+public:
+ Color(int r, int g, int b, int a = 255)
+ {
+ _color[0] = (unsigned char)r;
+ _color[1] = (unsigned char)g;
+ _color[2] = (unsigned char)b;
+ _color[3] = (unsigned char)a;
+ }
+ void SetColor(int _r, int _g, int _b, int _a = 0)
+ {
+ _color[0] = (unsigned char)_r;
+ _color[1] = (unsigned char)_g;
+ _color[2] = (unsigned char)_b;
+ _color[3] = (unsigned char)_a;
+ }
+ void GetColor(int& _r, int& _g, int& _b, int& _a) const
+ {
+ _r = _color[0];
+ _g = _color[1];
+ _b = _color[2];
+ _a = _color[3];
+ }
+ int GetValue(int index) const
+ {
+ return _color[index];
+ }
+ void SetRawColor(int color32)
+ {
+ *((int*)this) = color32;
+ }
+ int GetRawColor(void) const
+ {
+ return *((int*)this);
+ }
+
+ inline int r() const
+ {
+ return _color[0];
+ }
+ inline int g() const
+ {
+ return _color[1];
+ }
+ inline int b() const
+ {
+ return _color[2];
+ }
+ inline int a() const
+ {
+ return _color[3];
+ }
+
+ unsigned char& operator[](int index)
+ {
+ return _color[index];
+ }
+
+ const unsigned char& operator[](int index) const
+ {
+ return _color[index];
+ }
+
+ bool operator==(const Color& rhs) const
+ {
+ return (*((int*)this) == *((int*)&rhs));
+ }
+
+ bool operator!=(const Color& rhs) const
+ {
+ return !(operator==(rhs));
+ }
+
+ Color& operator=(const Color& rhs)
+ {
+ SetRawColor(rhs.GetRawColor());
+ return *this;
+ }
+
+ Color& operator=(const color32& rhs)
+ {
+ _color[0] = rhs.r;
+ _color[1] = rhs.g;
+ _color[2] = rhs.b;
+ _color[3] = rhs.a;
+ return *this;
+ }
+
+ color32 ToColor32(void) const
+ {
+ color32 newColor {};
+ newColor.r = _color[0];
+ newColor.g = _color[1];
+ newColor.b = _color[2];
+ newColor.a = _color[3];
+ return newColor;
+ }
+
+ std::string ToANSIColor()
+ {
+ std::string out = "\033[38;2;";
+ out += std::to_string(_color[0]) + ";";
+ out += std::to_string(_color[1]) + ";";
+ out += std::to_string(_color[2]) + ";";
+ out += "49m";
+ return out;
+ }
+
+ SourceColor ToSourceColor()
+ {
+ return SourceColor(_color[0], _color[1], _color[2], _color[3]);
+ }
+
+private:
+ unsigned char _color[4];
+};
+
+namespace NS::Colors
+{
+ extern Color SCRIPT_UI;
+ extern Color SCRIPT_CL;
+ extern Color SCRIPT_SV;
+ extern Color NATIVE_UI;
+ extern Color NATIVE_CL;
+ extern Color NATIVE_SV;
+ extern Color NATIVE_ENGINE;
+ extern Color FILESYSTEM;
+ extern Color RPAK;
+ extern Color NORTHSTAR;
+ extern Color ECHO;
+ extern Color PLUGINSYS;
+ extern Color PLUGIN;
+
+ extern Color TRACE;
+ extern Color DEBUG;
+ extern Color INFO;
+ extern Color WARN;
+ extern Color ERR;
+ extern Color CRIT;
+ extern Color OFF;
+}; // namespace NS::Colors
diff --git a/primedev/core/math/vector.h b/primedev/core/math/vector.h
new file mode 100644
index 00000000..8684908f
--- /dev/null
+++ b/primedev/core/math/vector.h
@@ -0,0 +1,47 @@
+#include <cmath>
+
+#pragma once
+
+union Vector3
+{
+ struct
+ {
+ float x;
+ float y;
+ float z;
+ };
+
+ float raw[3];
+
+ void MakeValid()
+ {
+ for (auto& fl : raw)
+ if (std::isnan(fl))
+ fl = 0;
+ }
+
+ // todo: more operators maybe
+ bool operator==(const Vector3& other)
+ {
+ return x == other.x && y == other.y && z == other.z;
+ }
+};
+
+union QAngle
+{
+ struct
+ {
+ float x;
+ float y;
+ float z;
+ float w;
+ };
+
+ float raw[4];
+
+ // todo: more operators maybe
+ bool operator==(const QAngle& other)
+ {
+ return x == other.x && y == other.y && z == other.z && w == other.w;
+ }
+};
diff --git a/primedev/core/memalloc.cpp b/primedev/core/memalloc.cpp
new file mode 100644
index 00000000..0a75bc2b
--- /dev/null
+++ b/primedev/core/memalloc.cpp
@@ -0,0 +1,71 @@
+#include "core/memalloc.h"
+#include "core/tier0.h"
+
+// TODO: rename to malloc and free after removing statically compiled .libs
+
+extern "C" void* _malloc_base(size_t n)
+{
+ // allocate into static buffer if g_pMemAllocSingleton isn't initialised
+ if (!g_pMemAllocSingleton)
+ TryCreateGlobalMemAlloc();
+
+ return g_pMemAllocSingleton->m_vtable->Alloc(g_pMemAllocSingleton, n);
+}
+
+/*extern "C" void* malloc(size_t n)
+{
+ return _malloc_base(n);
+}*/
+
+extern "C" void _free_base(void* p)
+{
+ if (!g_pMemAllocSingleton)
+ TryCreateGlobalMemAlloc();
+
+ g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p);
+}
+
+extern "C" void* _realloc_base(void* oldPtr, size_t size)
+{
+ if (!g_pMemAllocSingleton)
+ TryCreateGlobalMemAlloc();
+
+ return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size);
+}
+
+extern "C" void* _calloc_base(size_t n, size_t size)
+{
+ size_t bytes = n * size;
+ void* memory = _malloc_base(bytes);
+ if (memory)
+ {
+ memset(memory, 0, bytes);
+ }
+ return memory;
+}
+
+extern "C" char* _strdup_base(const char* src)
+{
+ char* str;
+ char* p;
+ int len = 0;
+
+ while (src[len])
+ len++;
+ str = reinterpret_cast<char*>(_malloc_base(len + 1));
+ p = str;
+ while (*src)
+ *p++ = *src++;
+ *p = '\0';
+ return str;
+}
+
+void* operator new(size_t n)
+{
+ return _malloc_base(n);
+}
+
+void operator delete(void* p) noexcept
+{
+ _free_base(p);
+} // /FORCE:MULTIPLE
diff --git a/primedev/core/memalloc.h b/primedev/core/memalloc.h
new file mode 100644
index 00000000..2f383335
--- /dev/null
+++ b/primedev/core/memalloc.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include "rapidjson/document.h"
+// #include "include/rapidjson/allocators.h"
+
+extern "C" void* _malloc_base(size_t size);
+extern "C" void* _calloc_base(size_t const count, size_t const size);
+extern "C" void* _realloc_base(void* block, size_t size);
+extern "C" void* _recalloc_base(void* const block, size_t const count, size_t const size);
+extern "C" void _free_base(void* const block);
+extern "C" char* _strdup_base(const char* src);
+
+void* operator new(size_t n);
+void operator delete(void* p) noexcept;
+
+// void* malloc(size_t n);
+
+class SourceAllocator
+{
+public:
+ static const bool kNeedFree = true;
+ void* Malloc(size_t size)
+ {
+ if (size) // behavior of malloc(0) is implementation defined.
+ return _malloc_base(size);
+ else
+ return NULL; // standardize to returning NULL.
+ }
+ void* Realloc(void* originalPtr, size_t originalSize, size_t newSize)
+ {
+ (void)originalSize;
+ if (newSize == 0)
+ {
+ _free_base(originalPtr);
+ return NULL;
+ }
+ return _realloc_base(originalPtr, newSize);
+ }
+ static void Free(void* ptr)
+ {
+ _free_base(ptr);
+ }
+};
+
+typedef rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::MemoryPoolAllocator<SourceAllocator>, SourceAllocator> rapidjson_document;
+// typedef rapidjson::GenericDocument<rapidjson::UTF8<>, SourceAllocator, SourceAllocator> rapidjson_document;
+// typedef rapidjson::Document rapidjson_document;
+// using MyDocument = rapidjson::GenericDocument<rapidjson::UTF8<>, MemoryAllocator>;
+// using rapidjson_document = rapidjson::GenericDocument<rapidjson::UTF8<>, SourceAllocator, SourceAllocator>;
diff --git a/primedev/core/memory.cpp b/primedev/core/memory.cpp
new file mode 100644
index 00000000..3770586f
--- /dev/null
+++ b/primedev/core/memory.cpp
@@ -0,0 +1,347 @@
+#include "memory.h"
+
+CMemoryAddress::CMemoryAddress() : m_nAddress(0) {}
+CMemoryAddress::CMemoryAddress(const uintptr_t nAddress) : m_nAddress(nAddress) {}
+CMemoryAddress::CMemoryAddress(const void* pAddress) : m_nAddress(reinterpret_cast<uintptr_t>(pAddress)) {}
+
+// operators
+CMemoryAddress::operator uintptr_t() const
+{
+ return m_nAddress;
+}
+
+CMemoryAddress::operator void*() const
+{
+ return reinterpret_cast<void*>(m_nAddress);
+}
+
+CMemoryAddress::operator bool() const
+{
+ return m_nAddress != 0;
+}
+
+bool CMemoryAddress::operator==(const CMemoryAddress& other) const
+{
+ return m_nAddress == other.m_nAddress;
+}
+
+bool CMemoryAddress::operator!=(const CMemoryAddress& other) const
+{
+ return m_nAddress != other.m_nAddress;
+}
+
+bool CMemoryAddress::operator==(const uintptr_t& addr) const
+{
+ return m_nAddress == addr;
+}
+
+bool CMemoryAddress::operator!=(const uintptr_t& addr) const
+{
+ return m_nAddress != addr;
+}
+
+CMemoryAddress CMemoryAddress::operator+(const CMemoryAddress& other) const
+{
+ return Offset(other.m_nAddress);
+}
+
+CMemoryAddress CMemoryAddress::operator-(const CMemoryAddress& other) const
+{
+ return CMemoryAddress(m_nAddress - other.m_nAddress);
+}
+
+CMemoryAddress CMemoryAddress::operator+(const uintptr_t& addr) const
+{
+ return Offset(addr);
+}
+
+CMemoryAddress CMemoryAddress::operator-(const uintptr_t& addr) const
+{
+ return CMemoryAddress(m_nAddress - addr);
+}
+
+CMemoryAddress CMemoryAddress::operator*() const
+{
+ return Deref();
+}
+
+// traversal
+CMemoryAddress CMemoryAddress::Offset(const uintptr_t nOffset) const
+{
+ return CMemoryAddress(m_nAddress + nOffset);
+}
+
+CMemoryAddress CMemoryAddress::Deref(const int nNumDerefs) const
+{
+ uintptr_t ret = m_nAddress;
+ for (int i = 0; i < nNumDerefs; i++)
+ ret = *reinterpret_cast<uintptr_t*>(ret);
+
+ return CMemoryAddress(ret);
+}
+
+// patching
+void CMemoryAddress::Patch(const uint8_t* pBytes, const size_t nSize)
+{
+ if (nSize)
+ WriteProcessMemory(GetCurrentProcess(), reinterpret_cast<LPVOID>(m_nAddress), pBytes, nSize, NULL);
+}
+
+void CMemoryAddress::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_msg(false, "Failed to parse invalid hex string.");
+ val = -1;
+ }
+
+ result += (j == 0) ? val * 16 : val;
+ }
+ ret.push_back(result);
+ }
+
+ i++;
+ }
+
+ return ret;
+}
+
+void CMemoryAddress::Patch(const char* pBytes)
+{
+ std::vector<uint8_t> vBytes = HexBytesToString(pBytes);
+ Patch(vBytes.data(), vBytes.size());
+}
+
+void CMemoryAddress::NOP(const size_t nSize)
+{
+ uint8_t* pBytes = new uint8_t[nSize];
+
+ memset(pBytes, 0x90, nSize);
+ Patch(pBytes, nSize);
+
+ delete[] pBytes;
+}
+
+bool CMemoryAddress::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)) {}
+
+CMemoryAddress CModule::GetExport(const char* pExportName)
+{
+ return CMemoryAddress(reinterpret_cast<uintptr_t>(GetProcAddress(reinterpret_cast<HMODULE>(m_nAddress), pExportName)));
+}
+
+CMemoryAddress CModule::FindPattern(const uint8_t* pPattern, const char* pMask)
+{
+ if (!m_ExecutableCode.IsSectionValid())
+ return CMemoryAddress();
+
+ 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 CMemoryAddress(const_cast<uint8_t*>(pData));
+ }
+ }
+ else
+ goto CONTINUE;
+ }
+
+ return CMemoryAddress((&*(const_cast<uint8_t*>(pData))));
+ }
+ }
+
+ CONTINUE:;
+ }
+
+ return CMemoryAddress();
+}
+
+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_msg(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);
+}
+
+CMemoryAddress CModule::FindPattern(const char* pPattern)
+{
+ const auto pattern = MaskedBytesFromPattern(pPattern);
+ return FindPattern(pattern.first.data(), pattern.second.c_str());
+}
diff --git a/primedev/core/memory.h b/primedev/core/memory.h
new file mode 100644
index 00000000..a978963e
--- /dev/null
+++ b/primedev/core/memory.h
@@ -0,0 +1,90 @@
+#pragma once
+
+class CMemoryAddress
+{
+public:
+ uintptr_t m_nAddress;
+
+public:
+ CMemoryAddress();
+ CMemoryAddress(const uintptr_t nAddress);
+ CMemoryAddress(const void* pAddress);
+
+ // operators
+ operator uintptr_t() const;
+ operator void*() const;
+ operator bool() const;
+
+ bool operator==(const CMemoryAddress& other) const;
+ bool operator!=(const CMemoryAddress& other) const;
+ bool operator==(const uintptr_t& addr) const;
+ bool operator!=(const uintptr_t& addr) const;
+
+ CMemoryAddress operator+(const CMemoryAddress& other) const;
+ CMemoryAddress operator-(const CMemoryAddress& other) const;
+ CMemoryAddress operator+(const uintptr_t& other) const;
+ CMemoryAddress operator-(const uintptr_t& other) const;
+ CMemoryAddress operator*() const;
+
+ template <typename T> T RCast()
+ {
+ return reinterpret_cast<T>(m_nAddress);
+ }
+
+ // traversal
+ CMemoryAddress Offset(const uintptr_t nOffset) const;
+ CMemoryAddress 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 CMemoryAddress
+{
+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);
+
+ CMemoryAddress GetExport(const char* pExportName);
+ CMemoryAddress FindPattern(const uint8_t* pPattern, const char* pMask);
+ CMemoryAddress FindPattern(const char* pPattern);
+};
diff --git a/primedev/core/sourceinterface.cpp b/primedev/core/sourceinterface.cpp
new file mode 100644
index 00000000..5a72beb0
--- /dev/null
+++ b/primedev/core/sourceinterface.cpp
@@ -0,0 +1,48 @@
+#include "sourceinterface.h"
+#include "logging/sourceconsole.h"
+
+AUTOHOOK_INIT()
+
+// really wanted to do a modular callback system here but honestly couldn't be bothered so hardcoding stuff for now: todo later
+
+// clang-format off
+AUTOHOOK_PROCADDRESS(ClientCreateInterface, client.dll, CreateInterface,
+void*, __fastcall, (const char* pName, const int* pReturnCode))
+// clang-format on
+{
+ void* ret = ClientCreateInterface(pName, pReturnCode);
+ spdlog::info("CreateInterface CLIENT {}", pName);
+
+ if (!strcmp(pName, "GameClientExports001"))
+ InitialiseConsoleOnInterfaceCreation();
+
+ return ret;
+}
+
+// clang-format off
+AUTOHOOK_PROCADDRESS(ServerCreateInterface, server.dll, CreateInterface,
+void*, __fastcall, (const char* pName, const int* pReturnCode))
+// clang-format on
+{
+ void* ret = ServerCreateInterface(pName, pReturnCode);
+ spdlog::info("CreateInterface SERVER {}", pName);
+
+ return ret;
+}
+
+// clang-format off
+AUTOHOOK_PROCADDRESS(EngineCreateInterface, engine.dll, CreateInterface,
+void*, __fastcall, (const char* pName, const int* pReturnCode))
+// clang-format on
+{
+ void* ret = EngineCreateInterface(pName, pReturnCode);
+ spdlog::info("CreateInterface ENGINE {}", pName);
+
+ return ret;
+}
+
+// clang-format off
+ON_DLL_LOAD("client.dll", ClientInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(client.dll)}
+ON_DLL_LOAD("server.dll", ServerInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(server.dll)}
+ON_DLL_LOAD("engine.dll", EngineInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(engine.dll)}
+// clang-format on
diff --git a/primedev/core/sourceinterface.h b/primedev/core/sourceinterface.h
new file mode 100644
index 00000000..7b5e81f3
--- /dev/null
+++ b/primedev/core/sourceinterface.h
@@ -0,0 +1,31 @@
+#pragma once
+#include <string>
+
+// literally just copied from ttf2sdk definition
+typedef void* (*CreateInterfaceFn)(const char* pName, int* pReturnCode);
+
+template <typename T> class SourceInterface
+{
+private:
+ T* m_interface;
+
+public:
+ SourceInterface(const std::string& moduleName, const std::string& interfaceName)
+ {
+ HMODULE handle = GetModuleHandleA(moduleName.c_str());
+ CreateInterfaceFn createInterface = (CreateInterfaceFn)GetProcAddress(handle, "CreateInterface");
+ m_interface = (T*)createInterface(interfaceName.c_str(), NULL);
+ if (m_interface == nullptr)
+ spdlog::error("Failed to call CreateInterface for %s in %s", interfaceName, moduleName);
+ }
+
+ T* operator->() const
+ {
+ return m_interface;
+ }
+
+ operator T*() const
+ {
+ return m_interface;
+ }
+};
diff --git a/primedev/core/structs.h b/primedev/core/structs.h
new file mode 100644
index 00000000..037233a6
--- /dev/null
+++ b/primedev/core/structs.h
@@ -0,0 +1,77 @@
+#pragma once
+//clang-format off
+// About this file:
+// This file contains several macros used to define reversed structs
+// The reason we use these macros is to make it easier to update existing structs
+// when new fields are reversed
+// This means we dont have to manually add padding, and recalculate when updating
+
+// Technical note:
+// While functionally, these structs act like a regular struct, they are actually
+// defined as unions with anonymous structs in them.
+// This means that each field is essentially an offset into a union.
+// We acknowledge that this goes against C++'s active-member guideline for unions
+// However, this is not such a big deal here as these structs will not be constructed
+
+// Usage:
+// To use these macros, define a struct like so:
+/*
+OFFSET_STRUCT(Name)
+{
+ STRUCT_SIZE(0x100) // Total in-memory struct size
+ FIELD(0x0, int field) // offset, signature
+}
+*/
+
+#define OFFSET_STRUCT(name) union name
+#define STRUCT_SIZE(size) char __size[size];
+#define STRUCT_FIELD_OFFSET(offset, signature) \
+ struct \
+ { \
+ char CONCAT2(pad, __LINE__)[offset]; \
+ signature; \
+ };
+
+// Special case for a 0-offset field
+#define STRUCT_FIELD_NOOFFSET(offset, signature) signature;
+
+// Just puts two tokens next to each other, but
+// allows us to force the preprocessor to do another pass
+#define FX(f, x) f x
+
+// Macro used to detect if the given offset is 0 or not
+#define TEST_0 ,
+// MSVC does no preprocessing of integer literals.
+// On other compilers `0x0` gets processed into `0`
+#define TEST_0x0 ,
+
+// Concats the first and third argument and drops everything else
+// Used with preprocessor expansion in later passes to move the third argument to the fourth and change the value
+#define ZERO_P_I(a, b, c, ...) a##c
+
+// We use FX to prepare to use ZERO_P_I.
+// The right block contains 3 arguments:
+// NIF_
+// CONCAT2(TEST_, offset)
+// 1
+//
+// If offset is not 0 (or 0x0) the preprocessor replaces
+// it with nothing and the third argument stays 1
+//
+// If the offset is 0, TEST_0 expands to , and 1 becomes the fourth argument
+//
+// With those arguments we call ZERO_P_I and the first and third arugment get concat.
+// We either end up with:
+// NIF_ (if offset is 0) or
+// NIF_1 (if offset is not 0)
+#define IF_ZERO(m) FX(ZERO_P_I, (NIF_, CONCAT2(TEST_, m), 1))
+
+// These macros are used to branch after we processed if the offset is zero or not
+#define NIF_(t, ...) t
+#define NIF_1(t, ...) __VA_ARGS__
+
+// FIELD(S), generates an anonymous struct when a non 0 offset is given, otherwise just a signature
+#define FIELD(offset, signature) IF_ZERO(offset)(STRUCT_FIELD_NOOFFSET, STRUCT_FIELD_OFFSET)(offset, signature)
+#define FIELDS FIELD
+
+//clang-format on
diff --git a/primedev/core/tier0.cpp b/primedev/core/tier0.cpp
new file mode 100644
index 00000000..1f59722c
--- /dev/null
+++ b/primedev/core/tier0.cpp
@@ -0,0 +1,30 @@
+#include "tier0.h"
+
+IMemAlloc* g_pMemAllocSingleton = nullptr;
+
+CommandLineType CommandLine;
+Plat_FloatTimeType Plat_FloatTime;
+ThreadInServerFrameThreadType ThreadInServerFrameThread;
+
+typedef IMemAlloc* (*CreateGlobalMemAllocType)();
+CreateGlobalMemAllocType CreateGlobalMemAlloc;
+
+// needs to be a seperate function, since memalloc.cpp calls it
+void TryCreateGlobalMemAlloc()
+{
+ // init memalloc stuff
+ CreateGlobalMemAlloc =
+ reinterpret_cast<CreateGlobalMemAllocType>(GetProcAddress(GetModuleHandleA("tier0.dll"), "CreateGlobalMemAlloc"));
+ g_pMemAllocSingleton = CreateGlobalMemAlloc(); // if it already exists, this returns the preexisting IMemAlloc instance
+}
+
+ON_DLL_LOAD("tier0.dll", Tier0GameFuncs, (CModule module))
+{
+ // shouldn't be necessary, but do this just in case
+ TryCreateGlobalMemAlloc();
+
+ // setup tier0 funcs
+ CommandLine = module.GetExport("CommandLine").RCast<CommandLineType>();
+ Plat_FloatTime = module.GetExport("Plat_FloatTime").RCast<Plat_FloatTimeType>();
+ ThreadInServerFrameThread = module.GetExport("ThreadInServerFrameThread").RCast<ThreadInServerFrameThreadType>();
+}
diff --git a/primedev/core/tier0.h b/primedev/core/tier0.h
new file mode 100644
index 00000000..cc9af39e
--- /dev/null
+++ b/primedev/core/tier0.h
@@ -0,0 +1,63 @@
+#pragma once
+
+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;
+};
+
+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) = 0;
+ virtual void CreateCmdLine(int argc, char** argv) = 0;
+ virtual void unknown() = 0;
+ virtual const char* GetCmdLine(void) const = 0;
+
+ virtual const char* CheckParm(const char* psz, const char** ppszValue = 0) const = 0;
+ virtual void RemoveParm() const = 0;
+ virtual void AppendParm(const char* pszParm, const char* pszValues) = 0;
+
+ virtual const char* ParmValue(const char* psz, const char* pDefaultVal = 0) const = 0;
+ virtual int ParmValue(const char* psz, int nDefaultVal) const = 0;
+ virtual float ParmValue(const char* psz, float flDefaultVal) const = 0;
+
+ virtual int ParmCount() const = 0;
+ virtual int FindParm(const char* psz) const = 0;
+ virtual const char* GetParm(int nIndex) const = 0;
+ virtual void SetParm(int nIndex, char const* pParm) = 0;
+
+ // virtual const char** GetParms() const {}
+};
+
+extern IMemAlloc* g_pMemAllocSingleton;
+
+typedef CCommandLine* (*CommandLineType)();
+extern CommandLineType CommandLine;
+
+typedef double (*Plat_FloatTimeType)();
+extern Plat_FloatTimeType Plat_FloatTime;
+
+typedef bool (*ThreadInServerFrameThreadType)();
+extern ThreadInServerFrameThreadType ThreadInServerFrameThread;
+
+void TryCreateGlobalMemAlloc();
diff --git a/primedev/core/vanilla.h b/primedev/core/vanilla.h
new file mode 100644
index 00000000..b0797803
--- /dev/null
+++ b/primedev/core/vanilla.h
@@ -0,0 +1,29 @@
+#pragma once
+
+/// Determines if we are in vanilla-compatibility mode.
+/// In this mode we shouldn't auth with Atlas, which prevents users from joining a
+/// non-trusted server. This means that we can unrestrict client/server commands
+/// as well as various other small changes for compatibility
+class VanillaCompatibility
+{
+public:
+ void SetVanillaCompatibility(bool isVanilla)
+ {
+ static bool bInitialised = false;
+ if (bInitialised)
+ return;
+
+ bInitialised = true;
+ m_bIsVanillaCompatible = isVanilla;
+ }
+
+ bool GetVanillaCompatibility()
+ {
+ return m_bIsVanillaCompatible;
+ }
+
+private:
+ bool m_bIsVanillaCompatible = false;
+};
+
+inline VanillaCompatibility* g_pVanillaCompatibility;