r5sdk/r5dev/loader/loader.cpp
Kawe Mazidjatari 708a249507 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.
2023-06-25 11:37:52 +02:00

159 lines
5.4 KiB
C++

//===========================================================================//
//
// 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;
}