r5sdk/r5dev/loader/loader.cpp

159 lines
5.4 KiB
C++
Raw Normal View History

//===========================================================================//
//
// 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, MAIN_GAME_DLL) == NULL)
s_SdkModule = LoadLibraryA(MAIN_WORKER_DLL);
else if (V_stricmp(pModuleName, SERVER_GAME_DLL) == NULL)
s_SdkModule = LoadLibraryA(SERVER_WORKER_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;
}