|
|
- /*
- * main.cpp -- The main "entry point" and the main logic of the DLL.
- *
- * Here, we define and initialize struct Main that contains the main code of this DLL.
- *
- * The main procedure goes as follows:
- * 1. The loader checks that PatchLoader.dll and mono.dll exist
- * 2. mono.dll is loaded into memory and some of its functions are looked up
- * 3. mono_jit_init_version is hooked with the help of MinHook
- *
- * Then, the loader waits until Unity creates its root domain for mono (which is done with mono_jit_init_version).
- *
- * Inside mono_jit_init_version hook:
- * 1. Call the original mono_jit_init_version to get the Unity root domain
- * 2. Load PatchLoader.dll into the root domain
- * 3. Find and invoke PatchLoader.Loader.Run()
- *
- * Rest of the work is done on the managed side.
- *
- */
-
- #pragma warning( disable : 4267 100 152 6387 4456 6011 )
-
- #include "winapi_util.h"
- #include <Windows.h>
-
- #include "config.h"
- #include "mono.h"
- #include "hook.h"
- #include "assert_util.h"
- #include "proxy.h"
- #include <synchapi.h>
-
- #include <intrin.h>
-
- EXTERN_C IMAGE_DOS_HEADER __ImageBase; // This is provided by MSVC with the infomration about this DLL
-
- HANDLE unhandledMutex;
- void ownMonoJitParseOptions(int argc, char * argv[]);
- BOOL setOptions = FALSE;
- BOOL shouldBreakOnUnhandledException = TRUE;
-
- __declspec(dllexport) void SetIgnoreUnhandledExceptions(BOOL ignore)
- {
- shouldBreakOnUnhandledException = ignore;
- }
-
- void unhandledException(void* exc, void* data)
- {
- WaitForSingleObject(unhandledMutex, INFINITE);
-
- void* exception = NULL;
- void* mstr = mono_object_to_string(exc, &exception);
-
- if (exception != NULL)
- {
- #ifdef _VERBOSE
- void* monostr = mono_object_to_string(exception, &exception);
- if (exception != NULL)
- {
- DEBUG_BREAK;
- LOG("An error occurred while stringifying uncaught error, but the error could not be stringified.\n");
- ASSERT(FALSE, L"Uncaught exception; could not stringify");
- }
- else
- {
- char* str = mono_string_to_utf8(monostr);
- DEBUG_BREAK;
- LOG("An error occurred stringifying uncaught error: %s\n", str);
-
- /*size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
- wchar_t* wstr = memalloc(sizeof(wchar_t) * len);
- MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);*/
- wchar_t* wstr = mono_string_to_utf16(monostr);
-
- ASSERT_F(FALSE, L"Uncaught exception; stringify failed: %wS", wstr);
-
- mono_free(wstr);
- mono_free(str);
- }
- #else
- ASSERT(FALSE, L"Could not stringify uncaught exception");
- #endif
- }
-
- char* str = mono_string_to_utf8(mstr);
- DEBUG_BREAK;
- LOG("Uncaught exception: %s\n", str);
-
- /*size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
- wchar_t* wstr = memalloc(sizeof(wchar_t) * len);
- MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);*/
- wchar_t* wstr = mono_string_to_utf16(mstr);
-
- if (shouldBreakOnUnhandledException)
- {
- #ifdef _VERBOSE
- ASSERT(FALSE, L"Uncaught exception; see doorstop.log for details");
- #else
- ASSERT_F(FALSE, L"Uncaught exception: %wS", wstr);
- #endif
- }
-
- mono_free(wstr);
- mono_free(str);
-
- ReleaseMutex(unhandledMutex);
- }
-
- // The hook for mono_jit_init_version
- // We use this since it will always be called once to initialize Mono's JIT
- void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_version)
- {
- const BOOL debugger_already_initialized = mono_debug_enabled();
-
- if(debugger_already_initialized)
- {
- LOG("Debugger was already initialized\n");
- }
-
- // Call the original mono_jit_init_version to initialize the Unity Root Domain
- if (debug) {
- char* opts[1];
- opts[0] = "";
- ownMonoJitParseOptions(0, opts);
- }
- #ifdef WIN32
- if (debug_info && !debugger_already_initialized) {
- mono_debug_init(MONO_DEBUG_FORMAT_MONO);
- }
- #endif
-
- void *domain = mono_jit_init_version(root_domain_name, runtime_version);
-
- if (debug_info && !debugger_already_initialized) {
- #ifdef WIN64
- mono_debug_init(MONO_DEBUG_FORMAT_MONO);
- #endif
- mono_debug_domain_create(domain);
- }
-
- DWORD len = GetFullPathName(targetAssembly, 0, NULL, NULL);
- wchar_t *full_path = memalloc(sizeof(wchar_t) * len);
- GetFullPathName(targetAssembly, len, full_path, NULL);
- size_t path_len = WideCharToMultiByte(CP_UTF8, 0, full_path, -1, NULL, 0, NULL, NULL);
- char *dll_path = memalloc(sizeof(char) * path_len);
- WideCharToMultiByte(CP_UTF8, 0, full_path, -1, dll_path, path_len, NULL, NULL);
-
- memfree(full_path);
-
- LOG("Loading assembly: %s\n", dll_path);
- // Load our custom assembly into the domain
- void *assembly = mono_domain_assembly_open(domain, dll_path);
-
- if (assembly == NULL)
- LOG("Failed to load assembly\n");
-
- memfree(dll_path);
- ASSERT_SOFT(assembly != NULL, domain);
-
- // Get assembly's image that contains CIL code
- void *image = mono_assembly_get_image(assembly);
- ASSERT_SOFT(image != NULL, domain);
-
- // Note: we use the runtime_invoke route since jit_exec will not work on DLLs
-
- // Create a descriptor for a random Main method
- void *desc = mono_method_desc_new("*:Main", FALSE);
-
- // Find the first possible Main method in the assembly
- void *method = mono_method_desc_search_in_image(desc, image);
- ASSERT_SOFT(method != NULL, domain);
-
- void *signature = mono_method_signature(method);
-
- // Get the number of parameters in the signature
- UINT32 params = mono_signature_get_param_count(signature);
-
- void **args = NULL;
- wchar_t *app_path = NULL;
- if (params == 1)
- {
- // If there is a parameter, it's most likely a string[].
- // Populate it as follows
- // 0 => path to the game's executable
- // 1 => --doorstop-invoke
-
- get_module_path(NULL, &app_path, NULL, 0);
-
- void *exe_path = MONO_STRING(app_path);
- void *doorstop_handle = MONO_STRING(L"--doorstop-invoke");
-
- void *args_array = mono_array_new(domain, mono_get_string_class(), 2);
-
- SET_ARRAY_REF(args_array, 0, exe_path);
- SET_ARRAY_REF(args_array, 1, doorstop_handle);
-
- args = memalloc(sizeof(void*) * 1);
- _ASSERTE(args != nullptr);
- args[0] = args_array;
- }
-
- LOG("Installing uncaught exception handler\n");
-
- mono_install_unhandled_exception_hook(unhandledException, NULL);
-
- wchar_t* dll_path_w; // self path
- size_t dll_path_len = get_module_path((HINSTANCE)&__ImageBase, &dll_path_w, NULL, 0);
- size_t multibyte_path_len = WideCharToMultiByte(CP_UTF8, 0, dll_path_w, dll_path_len, NULL, 0, NULL, NULL);
- char* self_dll_path = memalloc(multibyte_path_len + 1);
- WideCharToMultiByte(CP_UTF8, 0, dll_path_w, dll_path_len, self_dll_path, multibyte_path_len + 1, NULL, NULL);
- self_dll_path[multibyte_path_len] = 0;
-
- mono_dllmap_insert(NULL, "i:bsipa-doorstop", NULL, self_dll_path, NULL); // remap `bsipa-doorstop` to this assembly
-
- memfree(self_dll_path);
- memfree(dll_path_w);
-
-
- unhandledMutex = CreateMutexW(NULL, FALSE, NULL);
-
- LOG("Invoking method!\n");
-
- void* exception = NULL;
- mono_runtime_invoke(method, NULL, args, &exception);
-
- WaitForSingleObject(unhandledMutex, INFINITE); // if the EH is triggered, wait for it
-
- if (args != NULL)
- {
- memfree(app_path);
- memfree(args);
- NULL;
- }
-
- #ifdef _VERBOSE
- if (exception != NULL)
- {
- void* monostr = mono_object_to_string(exception, &exception);
- if (exception != NULL)
- LOG("An error occurred while invoking the injector, but the error could not be stringified.\n")
- else
- {
- char* str = mono_string_to_utf8(monostr);
- LOG("An error occurred invoking the injector: %s\n", str);
- mono_free(str);
- }
- }
- #endif
-
- cleanupConfig();
-
- free_logger();
-
- ReleaseMutex(unhandledMutex);
-
- return domain;
- }
-
- void ownMonoJitParseOptions(int argc, char * argv[])
- {
- setOptions = TRUE;
-
- int size = argc;
- #ifdef WIN64
- if (debug) size += 2;
- #elif defined(WIN32)
- if (debug) size += 1;
- #endif
-
- char** arguments = memalloc(sizeof(char*) * size);
- _ASSERTE(arguments != nullptr);
- memcpy(arguments, argv, sizeof(char*) * argc);
- if (debug) {
- //arguments[argc++] = "--debug";
- #ifdef WIN64
- arguments[argc++] = "--soft-breakpoints";
- #endif
- if (debug_server)
- arguments[argc] = "--debugger-agent=transport=dt_socket,address=0.0.0.0:10000,server=y";
- else
- arguments[argc] = "--debugger-agent=transport=dt_socket,address=127.0.0.1:10000,server=n";
- }
-
- mono_jit_parse_options(size, arguments);
-
- memfree(arguments);
- }
-
- BOOL initialized = FALSE;
-
- void init(HMODULE module)
- {
- if (!initialized)
- {
- initialized = TRUE;
- LOG("Got mono.dll at %p\n", module);
- loadMonoFunctions(module);
- }
- }
-
- void * WINAPI hookGetProcAddress(HMODULE module, char const *name)
- {
- if (lstrcmpA(name, "mono_jit_init_version") == 0)
- {
- init(module);
- return (void*)&ownMonoJitInitVersion;
- }
- if (lstrcmpA(name, "mono_jit_parse_options") == 0 && debug)
- {
- init(module);
- return (void*)&ownMonoJitParseOptions;
- }
- return (void*)GetProcAddress(module, name);
- }
-
- BOOL hookGetMessage(
- BOOL isW,
- LPMSG msg,
- HWND hwnd,
- UINT wMsgFilterMin,
- UINT wMsgFilterMax
- );
-
- BOOL WINAPI hookGetMessageA(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax)
- {
- return hookGetMessage(FALSE, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
- }
- BOOL WINAPI hookGetMessageW(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax)
- {
- return hookGetMessage(TRUE, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
- }
-
- typedef BOOL(*GetMessageHook)(BOOL isW, BOOL result, LPMSG msg, HWND hwnd, UINT filterMin, UINT filterMax);
-
- GetMessageHook getMessageHook = NULL;
-
- __declspec(dllexport) void __stdcall SetGetMessageHook(GetMessageHook hook) {
- getMessageHook = hook;
- }
-
- BOOL hookGetMessage(
- BOOL isW,
- LPMSG msg,
- HWND hwnd,
- UINT wMsgFilterMin,
- UINT wMsgFilterMax
- )
- {
- BOOL loop = FALSE;
-
- BOOL result;
-
- do {
- if (isW) {
- result = GetMessageW(msg, hwnd, wMsgFilterMin, wMsgFilterMax);
- } else {
- result = GetMessageA(msg, hwnd, wMsgFilterMin, wMsgFilterMax);
- }
-
- if (getMessageHook) {
- loop = getMessageHook(isW, result, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
- }
- } while (loop);
-
- return result;
- }
-
- BOOL hookPeekMessage(
- BOOL isW,
- LPMSG msg,
- HWND hwnd,
- UINT wMsgFilterMin,
- UINT wMsgFilterMax,
- UINT wRemoveMsg
- );
-
- BOOL WINAPI hookPeekMessageA(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
- {
- return hookPeekMessage(FALSE, msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
- }
- BOOL WINAPI hookPeekMessageW(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
- {
- return hookPeekMessage(TRUE, msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
- }
-
- typedef BOOL(*PeekMessageHook)(BOOL isW, BOOL result, LPMSG msg, HWND hwnd, UINT filterMin, UINT filterMax, UINT* wRemoveMsg);
-
- PeekMessageHook peekMessageHook = NULL;
-
- __declspec(dllexport) void __stdcall SetPeekMessageHook(PeekMessageHook hook) {
- peekMessageHook = hook;
- }
-
- BOOL hookPeekMessage(
- BOOL isW,
- LPMSG msg,
- HWND hwnd,
- UINT wMsgFilterMin,
- UINT wMsgFilterMax,
- UINT wRemoveMsg
- )
- {
- BOOL loop = FALSE;
-
- BOOL result;
-
- do {
- if (isW) {
- result = PeekMessageW(msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
- }
- else {
- result = PeekMessageA(msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
- }
-
- if (peekMessageHook) {
- loop = peekMessageHook(isW, result, msg, hwnd, wMsgFilterMin, wMsgFilterMax, &wRemoveMsg);
- }
- } while (loop);
-
- return result;
- }
-
- BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reasonForDllLoad, LPVOID reserved)
- {
- if (reasonForDllLoad != DLL_PROCESS_ATTACH)
- return TRUE;
-
- hHeap = GetProcessHeap();
-
- init_logger();
-
- LOG("Doorstop started!\n");
-
- wchar_t *dll_path = NULL;
- size_t dll_path_len = get_module_path((HINSTANCE)&__ImageBase, &dll_path, NULL, 0);
-
- LOG("DLL Path: %S\n", dll_path);
-
- wchar_t *dll_name = get_file_name_no_ext(dll_path, dll_path_len);
-
- LOG("Doorstop DLL Name: %S\n", dll_name);
-
- loadProxy(dll_name);
- loadConfig();
-
- // If the loader is disabled, don't inject anything.
- if (enabled)
- {
- LOG("Doorstop enabled!\n");
- ASSERT_SOFT(GetFileAttributesW(targetAssembly) != INVALID_FILE_ATTRIBUTES, TRUE);
-
- HMODULE targetModule = GetModuleHandleA("UnityPlayer");
-
- if(targetModule == NULL)
- {
- LOG("No UnityPlayer.dll; using EXE as the hook target.");
- targetModule = GetModuleHandleA(NULL);
- }
-
- LOG("Installing IAT hook\n");
- if (!iat_hook(targetModule, "kernel32.dll", &GetProcAddress, &hookGetProcAddress))
- {
- LOG("Failed to install IAT hook!\n");
- free_logger();
- }
-
- LOG("Hook installed!\n");
-
- LOG("Attempting to install GetMessageA and GetMessageW hooks\n");
-
- if (!iat_hook(targetModule, "user32.dll", &GetMessageA, &hookGetMessageA)) {
- LOG("Could not hook GetMessageA! (not an error)\n");
- }
- if (!iat_hook(targetModule, "user32.dll", &GetMessageW, &hookGetMessageW)) {
- LOG("Could not hook GetMessageW! (not an error)\n");
- }
-
- LOG("Attempting to install PeekMessageA and PeekMessageW hooks\n");
-
- if (!iat_hook(targetModule, "user32.dll", &PeekMessageA, &hookPeekMessageA)) {
- LOG("Could not hook PeekMessageA! (not an error)\n");
- }
- if (!iat_hook(targetModule, "user32.dll", &PeekMessageW, &hookPeekMessageW)) {
- LOG("Could not hook PeekMessageW! (not an error)\n");
- }
- }
- else
- {
- LOG("Doorstop disabled! memfreeing resources\n");
- free_logger();
- }
-
- memfree(dll_name);
- memfree(dll_path);
-
- return TRUE;
- }
|