Internal memalloc replacement

This commit replaces the standard memalloc system with that of the game. This means we can share the same allocated memory objects between the game and SDK without having to use 'MemAllocSingleton()' and manually call the constructors/destructors. This also means we can create ConVar's and ConCommands in the global scope, and a bunch more cool stuff. The explanation in 'r5dev\loader\loader.cpp' documents the new loading system.
This commit is contained in:
Kawe Mazidjatari 2023-06-25 11:37:52 +02:00
parent b76f4aa3bd
commit 708a249507
11 changed files with 265 additions and 93 deletions

View File

@ -57,6 +57,7 @@ add_subdirectory( localize )
add_subdirectory( engine )
add_subdirectory( vguimatsurface )
add_subdirectory( vgui )
add_subdirectory( loader )
set( FOLDER_CONTEXT "Plugins" )
add_subdirectory( pluginsdk )

View File

@ -59,6 +59,8 @@ void Tier0_Init()
void SDK_Init()
{
Tier0_Init();
if (strstr(GetCommandLineA(), "-launcher"))
{
g_svCmdLine = GetCommandLineA();
@ -86,7 +88,7 @@ void SDK_Init()
// Log the SDK's 'build_id' under the emblem.
spdlog::info("{:s}+------------------------------------------------[{:010d}]-+{:s}\n",
g_svRedF, g_SDKDll.m_pNTHeaders->FileHeader.TimeDateStamp, g_svReset);
g_svRedF, g_SDKDll.GetNTHeaders()->FileHeader.TimeDateStamp, g_svReset);
spdlog::info("\n");
Systems_Init();
@ -153,37 +155,17 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
NOTE_UNUSED(hModule);
NOTE_UNUSED(lpReserved);
#if !defined (DEDICATED) && !defined (CLIENT_DLL)
// This dll is imported by the game executable, we cannot circumvent it.
// To solve the recursive init problem, we check if -noworkerdll is passed.
// If this is passed, the worker dll will not be initialized, which allows
// us to load the client dll (or any other dll) instead, or load the game
// without the SDK.
s_bNoWorkerDll = !!strstr(GetCommandLineA(), "-noworkerdll");
#endif // !DEDICATED && CLIENT_DLL
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
{
if (!s_bNoWorkerDll)
{
Tier0_Init();
SDK_Init();
}
else // Destroy crash handler.
{
g_CrashHandler->~CCrashHandler();
g_CrashHandler = nullptr;
}
SDK_Init();
break;
}
case DLL_PROCESS_DETACH:
{
if (!s_bNoWorkerDll)
{
SDK_Shutdown();
}
SDK_Shutdown();
break;
}
}

View File

@ -389,7 +389,6 @@ void DetourRegister() // Register detour classes to be searched and hooked.
REGISTER(VJobThread);
REGISTER(VThreadTools);
REGISTER(VTSListBase);
REGISTER(VMemStd);
// Tier1
REGISTER(VCommandLine);

View File

@ -19,5 +19,3 @@ static const char* const R5R_EMBLEM[] =
R"(| |)"/*,
R"(+-------------------------------------------------------------+)"*/
};
static bool s_bNoWorkerDll = false;

View File

@ -12,19 +12,6 @@
#include "launcher/launcher.h"
#include <eiface.h>
int HWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
// !TODO [AMOS]: 'RemoveSpuriousGameParameters()' is inline with 'LauncherMain()' in S0 and S1,
// and its the only function where we could append our own command line parameters early enough
// programatically (has to be after 'CommandLine()->CreateCmdLine()', but before 'SetPriorityClass()')
// For S0 and S1 we should modify the command line buffer passed to the entry point instead (here).
#if defined (GAMEDLL_S0) || defined (GAMEDLL_S1)
return v_WinMain(hInstance, hPrevInstance, const_cast<LPSTR>(g_svCmdLine.c_str()), nShowCmd);
#else
return v_WinMain(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
#endif
}
int LauncherMain(HINSTANCE hInstance)
{
SpdLog_PostInit();
@ -175,7 +162,6 @@ LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS* pExceptionPointers)
void VLauncher::Attach(void) const
{
DetourAttach((LPVOID*)&v_WinMain, &HWinMain);
DetourAttach((LPVOID*)&v_LauncherMain, &LauncherMain);
DetourAttach((LPVOID*)&v_TopLevelExceptionFilter, &TopLevelExceptionFilter);
#if !defined (GAMEDLL_S0) && !defined (GAMEDLL_S1)
@ -184,7 +170,6 @@ void VLauncher::Attach(void) const
}
void VLauncher::Detach(void) const
{
DetourDetach((LPVOID*)&v_WinMain, &HWinMain);
DetourDetach((LPVOID*)&v_LauncherMain, &LauncherMain);
DetourDetach((LPVOID*)&v_TopLevelExceptionFilter, &TopLevelExceptionFilter);
#if !defined (GAMEDLL_S0) && !defined (GAMEDLL_S1)

View File

@ -1,9 +1,6 @@
#ifndef LAUNCHER_H
#define LAUNCHER_H
inline CMemory p_WinMain;
inline auto v_WinMain = p_WinMain.RCast<int (*)(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)>();
inline CMemory p_LauncherMain;
inline auto v_LauncherMain = p_LauncherMain.RCast<int(*)(HINSTANCE hInstance)>();
@ -25,7 +22,6 @@ class VLauncher : public IDetour
{
virtual void GetAdr(void) const
{
LogFunAdr("WinMain", p_WinMain.GetPtr());
LogFunAdr("LauncherMain", p_LauncherMain.GetPtr());
LogFunAdr("TopLevelExceptionFilter", p_TopLevelExceptionFilter.GetPtr());
#if !defined (GAMEDLL_S0) && !defined (GAMEDLL_S1)
@ -34,10 +30,7 @@ class VLauncher : public IDetour
}
virtual void GetFun(void) const
{
p_WinMain = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 41 8B D9 49 8B F8");
v_WinMain = p_WinMain.RCast<int (*)(HINSTANCE, HINSTANCE, LPSTR, int)>();
p_LauncherMain = g_GameDll.GetExportedSymbol("LauncherMain");
p_LauncherMain = g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 8B C8 E8 ?? ?? ?? ?? CC").FollowNearCallSelf();
v_LauncherMain = p_LauncherMain.RCast<int(*)(HINSTANCE)>();
p_TopLevelExceptionFilter = g_GameDll.FindPatternSIMD("40 53 48 83 EC 20 48 8B 05 ?? ?? ?? ?? 48 8B D9 48 85 C0 74 06");

View File

@ -0,0 +1,37 @@
cmake_minimum_required( VERSION 3.16 )
add_module( "shared_lib" "loader" "vpc" ${FOLDER_CONTEXT} TRUE TRUE )
start_sources()
add_sources( SOURCE_GROUP "Private"
"loader.cpp"
"loader.h"
)
target_link_libraries( ${PROJECT_NAME} PRIVATE
"advapi32.lib"
"bcrypt.lib"
"crypt32.lib"
"dbghelp.lib"
"wldap32.lib"
"ws2_32.lib"
"Rpcrt4.lib"
"vpc"
"tier0"
# TODO: these have to be removed, currently
# linked as it would otherwise throw linker
# errors due to undefined external symbols.
"libdetours"
"liblzham"
"libprotobuf"
"libspdlog"
"SigCache_Pb"
)
end_sources()
target_link_options( ${PROJECT_NAME} PRIVATE
"/STACK:8000000" # Match game executable stack reserve size
)

158
r5dev/loader/loader.cpp Normal file
View File

@ -0,0 +1,158 @@
//===========================================================================//
//
// Purpose: SDK loader stub
//
// --------------------------------------------------------------------------
// The game module cannot be imported directly by the executable, as the
// internal memalloc system is not initialized before the entry point is
// called. So we need to load the SDK dll after the entry point is called,
// but before LauncherMain is called.
//
// The executable exports table has been restructured; the exported function
// 'GetDenuvoTimeTicketRequest' has been swapped with 'CreateGlobalMemAlloc',
// the exported 'IEngineAPI' interface accessor has been replaced with
// 'g_pMemAllocSingleton', and the exported function 'LauncherMain' has been
// swapped with 'WinMain', so we can obtain the addresses without hardcoding.
//
// These changes allow us to load the SDK in the following order:
// - Create game process
// - Import this loader stub by its dummy export.
// - Immediately hook 'WinMain', by getting a pointer to it from its exports.
// - Determine if, and which SDK module to load.
//
// Since WinMain is called before anything of the game is, we can still hook
// and modify anything of the game before it starts. With the above order of
// initialization, we can now replace the standard memalloc system with that
// of the game, by:
//
// - Redefining the standard C functions to use the internal memalloc system.
// - Checking if the memalloc system has been initialized, and create if not.
//===========================================================================//
#include "loader.h"
#include "tier0/module.h"
//-----------------------------------------------------------------------------
// Image statics
//-----------------------------------------------------------------------------
static const PEB64* s_ProcessEnvironmentBlock = nullptr;
static const IMAGE_DOS_HEADER* s_DosHeader = nullptr;
static const IMAGE_NT_HEADERS64* s_NtHeaders = nullptr;
static HMODULE s_SdkModule = NULL;
//-----------------------------------------------------------------------------
// WinMain function pointer
//-----------------------------------------------------------------------------
static int (*v_WinMain)(HINSTANCE, HINSTANCE, LPSTR, int) = nullptr;
//-----------------------------------------------------------------------------
// Purpose: Terminates the process with an error when called
//-----------------------------------------------------------------------------
void FatalError(const char* fmt, ...)
{
va_list vArgs;
va_start(vArgs, fmt);
char errorBuf[1024];
vsnprintf(errorBuf, sizeof(errorBuf), fmt, vArgs);
errorBuf[sizeof(errorBuf) - 1] = '\0';
va_end(vArgs);
MessageBoxA(NULL, errorBuf, "Loader Error", MB_ICONERROR | MB_OK);
TerminateProcess(GetCurrentProcess(), 0xBAD0C0DE);
}
//-----------------------------------------------------------------------------
// Purpose: Loads the SDK module
//-----------------------------------------------------------------------------
void InitGameSDK(const LPSTR lpCmdLine)
{
if (V_strstr(lpCmdLine, "-noworkerdll"))
return;
char moduleName[MAX_PATH];
if (!GetModuleFileNameA((HMODULE)s_DosHeader,
moduleName, sizeof(moduleName)))
return;
// Prune the path.
const char* pModuleName = strrchr(moduleName, '\\') + 1;
// The dedicated server has its own SDK module,
// so we need to check whether we are running
// the base game or the dedicated server.
if (V_stricmp(pModuleName, "r5apex.exe") == NULL)
s_SdkModule = LoadLibraryA("GameSDK.dll");
else if (V_stricmp(pModuleName, "r5apex_ds.exe") == NULL)
s_SdkModule = LoadLibraryA("Dedicated.dll");
if (!s_SdkModule)
{
Assert(0);
FatalError("Failed to load SDK: error code = %08x\n", GetLastError());
}
}
//-----------------------------------------------------------------------------
// Purpose: WinMain hook; loads the SDK before LauncherMain
//-----------------------------------------------------------------------------
int WINAPI hWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
InitGameSDK(lpCmdLine); // Init GameSDK, internal function calls LauncherMain.
return v_WinMain(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
}
//-----------------------------------------------------------------------------
// Purpose: APIENTRY
//-----------------------------------------------------------------------------
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
{
s_ProcessEnvironmentBlock = (PEB64*)__readgsqword(0x60);
s_DosHeader = (IMAGE_DOS_HEADER*)s_ProcessEnvironmentBlock->ImageBaseAddress;
s_NtHeaders = (IMAGE_NT_HEADERS64*)((uintptr_t)s_DosHeader
+ (uintptr_t)s_DosHeader->e_lfanew);
v_WinMain = CModule::GetExportedSymbol((QWORD)s_DosHeader, "WinMain")
.RCast<int (*)(HINSTANCE, HINSTANCE, LPSTR, int)>();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&v_WinMain, &hWinMain);
HRESULT hr = DetourTransactionCommit();
if (hr != NO_ERROR) // Failed to hook into the process, terminate...
{
Assert(0);
FatalError("Failed to detour process: error code = %08x\n", hr);
}
break;
}
case DLL_PROCESS_DETACH:
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&v_WinMain, &hWinMain);
HRESULT hr = DetourTransactionCommit();
Assert(hr != NO_ERROR);
NOTE_UNUSED(hr);
if (s_SdkModule)
FreeLibrary(s_SdkModule);
break;
}
}
return TRUE;
}

9
r5dev/loader/loader.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef LOADER_H
#define LOADER_H
__declspec(dllexport) void DummyExport()
{
// Required for detours.
}
#endif // LOADER_H

View File

@ -5,33 +5,49 @@
//=============================================================================//
#include "memstd.h"
static bool s_bAllocatorInitialized = false;
static void InitAllocator()
{
if (!s_bAllocatorInitialized)
{
s_bAllocatorInitialized = true;
// https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
const PEB64* processEnvBlock = reinterpret_cast<PEB64*>(__readgsqword(0x60));
const QWORD imageBase = processEnvBlock->ImageBaseAddress;
CreateGlobalMemAlloc = CModule::GetExportedSymbol(imageBase,
"CreateGlobalMemAlloc").RCast<CStdMemAlloc* (*)(void)>();
g_pMemAllocSingleton = CModule::GetExportedSymbol(imageBase,
"g_pMemAllocSingleton").DerefSelf().RCast<CStdMemAlloc*>();
}
}
//=============================================================================//
// reimplementation of standard C functions for callbacks
// ----------------------------------------------------------------------------
//
// Ideally this didn't exist, but since 'CreateGlobalMemAlloc' is part of the
// monolithic game executable, it couldn't be imported early enough to bind
// the C functions to feature the internal memalloc system instead. This code
// basically replicates the compiled code in the executable, and can be used
// to set callbacks up to allow hooking external code with internal without
// having to change the source code.
//
// Reimplementation of standard C functions for memalloc callbacks
// ---------------------------------------------------------------------------
// The replacement functions use the game's internal memalloc system instead
//=============================================================================//
extern "C" void* R_malloc(size_t nSize)
{
Assert(nSize);
InitAllocator();
return MemAllocSingleton()->Alloc<void>(nSize);
}
extern "C" void R_free(void* pBlock)
{
//Assert(pBlock);
InitAllocator();
MemAllocSingleton()->Free(pBlock);
}
extern "C" void* R_realloc(void* pBlock, size_t nSize)
{
Assert(pBlock && nSize);
//Assert(pBlock && nSize);
InitAllocator();
if (nSize)
return MemAllocSingleton()->Realloc<void>(pBlock, nSize);
@ -46,6 +62,8 @@ extern "C" char* R_strdup(const char* pString)
{
Assert(pString);
InitAllocator();
const size_t nLen = strlen(pString) + 1;
void* pNew = MemAllocSingleton()->Alloc<char>(nLen);
@ -59,9 +77,21 @@ extern "C" void* R_calloc(size_t nCount, size_t nSize)
{
Assert(nCount && nSize);
InitAllocator();
const size_t nTotal = nCount * nSize;
void* pNew = MemAllocSingleton()->Alloc<void>(nTotal);
memset(pNew, NULL, nTotal);
return pNew;
}
// !TODO: other 'new' operators introduced in C++17.
void* operator new(std::size_t n) noexcept(false)
{
return malloc(n);
}
void operator delete(void* p) throw()
{
return free(p);
}

View File

@ -2,11 +2,18 @@
#define MEMSTD_H
extern "C" void* R_malloc(size_t nSize);
extern "C" void R_free(void* pBlock);
extern "C" void R_free(void* pBlock);
extern "C" void* R_realloc(void* pBlock, size_t nSize);
extern "C" char* R_strdup(const char* pString);
extern "C" void* R_calloc(size_t nCount, size_t nSize);
// Shadow standard implementation with ours.
#define malloc(nSize) R_malloc(nSize)
#define free(pBlock) R_free(pBlock)
#define realloc(pBlock, nSize) R_realloc(pBlock, nSize)
#define strdup(pString) R_strdup(pString)
#define calloc(nCount, nSize) R_calloc(nCount, nSize)
class IMemAlloc
{
public:
@ -47,43 +54,16 @@ public:
//-----------------------------------------------------------------------------
class CStdMemAlloc : public IMemAlloc{};
inline CMemory p_CreateGlobalMemAlloc;
inline auto v_CreateGlobalMemAlloc = p_CreateGlobalMemAlloc.RCast<CStdMemAlloc* (*)(void)>();
inline CStdMemAlloc** g_pMemAllocSingleton = nullptr;
inline CStdMemAlloc* (*CreateGlobalMemAlloc)() = nullptr;
inline CStdMemAlloc* g_pMemAllocSingleton = nullptr;
inline IMemAlloc* MemAllocSingleton()
{
if (!(*g_pMemAllocSingleton))
if (!g_pMemAllocSingleton)
{
(*g_pMemAllocSingleton) = v_CreateGlobalMemAlloc();
g_pMemAllocSingleton = CreateGlobalMemAlloc();
}
return (*g_pMemAllocSingleton);
return g_pMemAllocSingleton;
}
///////////////////////////////////////////////////////////////////////////////
class VMemStd : public IDetour
{
virtual void GetAdr(void) const
{
LogFunAdr("CreateGlobalMemAlloc", p_CreateGlobalMemAlloc.GetPtr());
LogVarAdr("g_pMemAllocSingleton", reinterpret_cast<uintptr_t>(g_pMemAllocSingleton));
}
virtual void GetFun(void) const
{
p_CreateGlobalMemAlloc = g_GameDll.FindPatternSIMD("40 53 48 83 EC 20 BB ?? ?? ?? ?? 33 C0");
v_CreateGlobalMemAlloc = p_CreateGlobalMemAlloc.RCast<CStdMemAlloc* (*)(void)>(); /*40 53 48 83 EC 20 BB ?? ?? ?? ?? 33 C0*/
}
virtual void GetVar(void) const
{
g_pMemAllocSingleton = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 41 8B D8")
.OffsetSelf(0x5A).FindPatternSelf("48 8B", CMemory::Direction::DOWN, 100).ResolveRelativeAddressSelf(0x3, 0x7).RCast<CStdMemAlloc**>();
}
virtual void GetCon(void) const { }
virtual void Attach(void) const { }
virtual void Detach(void) const { }
};
///////////////////////////////////////////////////////////////////////////////
#endif // MEMSTD_H