Unity seems to use GetModuleFileNameA/GetModuleFileNameW to determine the “base directory” that it’ll search in for MonoBleedingEdge, D3D12, the crash handler, etc. You can modify the import address table (IAT) of UnityPlayer.dll before you call UnityMain to redirect those calls to your own function that provides a different path.
Assuming your executable is written in C++17, here’s some sample code that will change an entry in the IAT:
static PIMAGE_IMPORT_DESCRIPTOR GetImportDirectory(HMODULE module)
{
auto dosHeader = (PIMAGE_DOS_HEADER)module;
auto ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)module + dosHeader->e_lfanew);
auto directory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
return (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)module + directory.VirtualAddress);
}
static void SetModuleImport(HMODULE module, LPCSTR importModuleName, LPCSTR importName, DWORD_PTR function)
{
auto imports = GetImportDirectory(module);
auto importModule = GetModuleHandleA(importModuleName);
for (int i = 0; imports[i].Name != 0; i++)
{
if (auto handle = GetModuleHandleA((LPCSTR)((DWORD_PTR)module + imports[i].Name)); handle != importModule)
continue;
auto thunks = (PIMAGE_THUNK_DATA)((DWORD_PTR)module + imports[i].FirstThunk);
auto originalThunks = (PIMAGE_THUNK_DATA)((DWORD_PTR)module + imports[i].OriginalFirstThunk);
for (int j = 0; originalThunks[j].u1.AddressOfData != 0; j++)
{
auto import = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)module + originalThunks[j].u1.AddressOfData);
if (strcmp(import->Name, importName) == 0)
{
DWORD oldProtect;
VirtualProtect(&thunks[j].u1.Function, sizeof DWORD_PTR, PAGE_READWRITE, &oldProtect);
thunks[j].u1.Function = function;
VirtualProtect(&thunks[j].u1.Function, sizeof DWORD_PTR, oldProtect, &oldProtect);
return;
}
}
}
}
If you want the engine to search for MonoBleedingEdge etc next to UnityPlayer.dll, you can use these functions:
static DWORD WINAPI DGetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize)
{
if (hModule == GetModuleHandleA(nullptr))
hModule = GetModuleHandleA("UnityPlayer.dll");
return GetModuleFileNameA(hModule, lpFilename, nSize);
}
static DWORD WINAPI DGetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize)
{
if (hModule == GetModuleHandleA(nullptr))
hModule = GetModuleHandleA("UnityPlayer.dll");
return GetModuleFileNameW(hModule, lpFilename, nSize);
}
And enable them using:
auto module = LoadLibraryW(L"path/to/UnityPlayer.dll");
SetModuleImport(module, "KERNEL32", "GetModuleFileNameA", (DWORD_PTR)&DGetModuleFileNameA);
SetModuleImport(module, "KERNEL32", "GetModuleFileNameW", (DWORD_PTR)&DGetModuleFileNameW);