From 4041844ea997bc3c73d13721b7fe2fb43e493520 Mon Sep 17 00:00:00 2001 From: beerpsi Date: Mon, 15 Apr 2024 19:30:28 +0000 Subject: [PATCH] mai2/mu3/cm: Integrate UnityDoorstop with segatools (#11) [UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) is a tool to execute managed code (.NET DLLs) before Unity does, useful for modding frameworks such as BepInEx. This PR integrates parts of its code into segatools, so loading BepInEx is as simple as adding 2 lines to `segatools.ini`: ```ini [unity] targetAssembly=BepInEx\core\BepInEx.Preloader.dll ``` This PR also factors out the Unity path redirection hooks to its own module. Reviewed-on: https://gitea.tendokyu.moe/Dniel97/segatools/pulls/11 Co-authored-by: beerpsi Co-committed-by: beerpsi --- cmhook/config.c | 1 + cmhook/config.h | 3 + cmhook/dllmain.c | 5 +- cmhook/meson.build | 3 +- cmhook/unity.c | 95 --------------- cmhook/unity.h | 3 - dist/cm/segatools.ini | 5 + dist/mai2/segatools.ini | 9 ++ dist/mu3/segatools.ini | 5 + mai2hook/config.c | 1 + mai2hook/config.h | 3 + mai2hook/dllmain.c | 5 +- mai2hook/meson.build | 3 +- mai2hook/unity.h | 3 - meson.build | 2 + mu3hook/config.c | 1 + mu3hook/config.h | 3 + mu3hook/dllmain.c | 7 +- mu3hook/meson.build | 3 +- mu3hook/unity.c | 95 --------------- mu3hook/unity.h | 3 - unityhook/config.c | 14 +++ unityhook/config.h | 12 ++ unityhook/doorstop.c | 173 +++++++++++++++++++++++++++ unityhook/doorstop.h | 5 + mai2hook/unity.c => unityhook/hook.c | 59 +++++---- unityhook/hook.h | 7 ++ unityhook/meson.build | 20 ++++ unityhook/mono.h | 100 ++++++++++++++++ unityhook/util.h | 20 ++++ 30 files changed, 436 insertions(+), 232 deletions(-) delete mode 100644 cmhook/unity.c delete mode 100644 cmhook/unity.h delete mode 100644 mai2hook/unity.h delete mode 100644 mu3hook/unity.c delete mode 100644 mu3hook/unity.h create mode 100644 unityhook/config.c create mode 100644 unityhook/config.h create mode 100644 unityhook/doorstop.c create mode 100644 unityhook/doorstop.h rename mai2hook/unity.c => unityhook/hook.c (70%) create mode 100644 unityhook/hook.h create mode 100644 unityhook/meson.build create mode 100644 unityhook/mono.h create mode 100644 unityhook/util.h diff --git a/cmhook/config.c b/cmhook/config.c index fd9186c8..4c0a8082 100644 --- a/cmhook/config.c +++ b/cmhook/config.c @@ -40,4 +40,5 @@ void cm_hook_config_load( vfd_config_load(&cfg->vfd, filename); touch_screen_config_load(&cfg->touch, filename); cm_dll_config_load(&cfg->dll, filename); + unity_config_load(&cfg->unity, filename); } diff --git a/cmhook/config.h b/cmhook/config.h index cf39c704..a8403585 100644 --- a/cmhook/config.h +++ b/cmhook/config.h @@ -11,6 +11,8 @@ #include "platform/config.h" +#include "unityhook/config.h" + struct cm_hook_config { struct platform_config platform; struct aime_config aime; @@ -19,6 +21,7 @@ struct cm_hook_config { struct vfd_config vfd; struct cm_dll_config dll; struct touch_screen_config touch; + struct unity_config unity; }; void cm_dll_config_load( diff --git a/cmhook/dllmain.c b/cmhook/dllmain.c index 0e12adbd..687601a8 100644 --- a/cmhook/dllmain.c +++ b/cmhook/dllmain.c @@ -16,10 +16,11 @@ #include "cmhook/config.h" #include "cmhook/io4.h" #include "cmhook/cm-dll.h" -#include "cmhook/unity.h" #include "platform/platform.h" +#include "unityhook/hook.h" + #include "util/dprintf.h" static HMODULE cm_hook_mod; @@ -83,7 +84,7 @@ static DWORD CALLBACK cm_pre_startup(void) There seems to be an issue with other DLL hooks if `LoadLibraryW` is hooked earlier in the `cmhook` initialization. */ - unity_hook_init(); + unity_hook_init(&cm_hook_cfg.unity, cm_hook_mod); /* Initialize debug helpers */ diff --git a/cmhook/meson.build b/cmhook/meson.build index 338f773a..6c645c29 100644 --- a/cmhook/meson.build +++ b/cmhook/meson.build @@ -16,6 +16,7 @@ shared_library( hooklib_lib, cmio_lib, platform_lib, + unityhook_lib, util_lib, ], sources : [ @@ -26,7 +27,5 @@ shared_library( 'io4.h', 'cm-dll.c', 'cm-dll.h', - 'unity.h', - 'unity.c', ], ) diff --git a/cmhook/unity.c b/cmhook/unity.c deleted file mode 100644 index efefc32a..00000000 --- a/cmhook/unity.c +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include - -#include "hook/table.h" - -#include "hooklib/dll.h" -#include "hooklib/path.h" - -#include "util/dprintf.h" - -static void dll_hook_insert_hooks(HMODULE target); - -static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name); -static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); - -static const struct hook_symbol unity_kernel32_syms[] = { - { - .name = "LoadLibraryW", - .patch = my_LoadLibraryW, - .link = (void **) &next_LoadLibraryW, - }, -}; - -static const wchar_t *target_modules[] = { - L"mono.dll", - L"cri_ware_unity.dll", -}; -static const size_t target_modules_len = _countof(target_modules); - -void unity_hook_init(void) -{ - dll_hook_insert_hooks(NULL); -} - -static void dll_hook_insert_hooks(HMODULE target) -{ - hook_table_apply( - target, - "kernel32.dll", - unity_kernel32_syms, - _countof(unity_kernel32_syms)); -} - -static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) -{ - const wchar_t *name_end; - const wchar_t *target_module; - bool already_loaded; - HMODULE result; - size_t name_len; - size_t target_module_len; - - if (name == NULL) { - SetLastError(ERROR_INVALID_PARAMETER); - - return NULL; - } - - // Check if the module is already loaded - already_loaded = GetModuleHandleW(name) != NULL; - - // Must call the next handler so the DLL reference count is incremented - result = next_LoadLibraryW(name); - - if (!already_loaded && result != NULL) { - name_len = wcslen(name); - - for (size_t i = 0; i < target_modules_len; i++) { - target_module = target_modules[i]; - target_module_len = wcslen(target_module); - - // Check if the newly loaded library is at least the length of - // the name of the target module - if (name_len < target_module_len) { - continue; - } - - name_end = &name[name_len - target_module_len]; - - // Check if the name of the newly loaded library is one of the - // modules the path hooks should be injected into - if (_wcsicmp(name_end, target_module) != 0) { - continue; - } - - dprintf("Unity: Loaded %S\n", target_module); - - dll_hook_insert_hooks(result); - path_hook_insert_hooks(result); - } - } - - return result; -} diff --git a/cmhook/unity.h b/cmhook/unity.h deleted file mode 100644 index 99c3bd93..00000000 --- a/cmhook/unity.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void unity_hook_init(void); diff --git a/dist/cm/segatools.ini b/dist/cm/segatools.ini index 37f57086..181c7852 100644 --- a/dist/cm/segatools.ini +++ b/dist/cm/segatools.ini @@ -68,6 +68,11 @@ dipsw1=0 ; Enable/Disable WinTouch emulation enable=0 +[unity] +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +targetAssembly= + ; ----------------------------------------------------------------------------- ; Custom IO settings ; ----------------------------------------------------------------------------- diff --git a/dist/mai2/segatools.ini b/dist/mai2/segatools.ini index 3aa8758c..ed8cc0a1 100644 --- a/dist/mai2/segatools.ini +++ b/dist/mai2/segatools.ini @@ -83,6 +83,15 @@ path= ; Leave empty if you want to use Segatools built-in keyboard input. path= +; ----------------------------------------------------------------------------- +; Misc. hook settings +; ----------------------------------------------------------------------------- + +[unity] +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +targetAssembly= + ; ----------------------------------------------------------------------------- ; Input settings ; ----------------------------------------------------------------------------- diff --git a/dist/mu3/segatools.ini b/dist/mu3/segatools.ini index ac897df6..c55cbbb3 100644 --- a/dist/mu3/segatools.ini +++ b/dist/mu3/segatools.ini @@ -72,6 +72,11 @@ dipsw1=1 [gfx] enable=1 +[unity] +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +targetAssembly= + ; ----------------------------------------------------------------------------- ; Custom IO settings ; ----------------------------------------------------------------------------- diff --git a/mai2hook/config.c b/mai2hook/config.c index 86b97644..d41a1d6c 100644 --- a/mai2hook/config.c +++ b/mai2hook/config.c @@ -39,4 +39,5 @@ void mai2_hook_config_load( io4_config_load(&cfg->io4, filename); vfd_config_load(&cfg->vfd, filename); mai2_dll_config_load(&cfg->dll, filename); + unity_config_load(&cfg->unity, filename); } diff --git a/mai2hook/config.h b/mai2hook/config.h index 2def3a74..7e868225 100644 --- a/mai2hook/config.h +++ b/mai2hook/config.h @@ -10,6 +10,8 @@ #include "platform/config.h" +#include "unityhook/config.h" + struct mai2_hook_config { struct platform_config platform; struct aime_config aime; @@ -17,6 +19,7 @@ struct mai2_hook_config { struct io4_config io4; struct vfd_config vfd; struct mai2_dll_config dll; + struct unity_config unity; }; void mai2_dll_config_load( diff --git a/mai2hook/dllmain.c b/mai2hook/dllmain.c index 5d533687..1d10c607 100644 --- a/mai2hook/dllmain.c +++ b/mai2hook/dllmain.c @@ -12,10 +12,11 @@ #include "mai2hook/config.h" #include "mai2hook/io4.h" #include "mai2hook/mai2-dll.h" -#include "mai2hook/unity.h" #include "platform/platform.h" +#include "unityhook/hook.h" + #include "util/dprintf.h" static HMODULE mai2_hook_mod; @@ -80,7 +81,7 @@ static DWORD CALLBACK mai2_pre_startup(void) There seems to be an issue with other DLL hooks if `LoadLibraryW` is hooked earlier in the `mai2hook` initialization. */ - unity_hook_init(); + unity_hook_init(&mai2_hook_cfg.unity, mai2_hook_mod); /* Initialize debug helpers */ diff --git a/mai2hook/meson.build b/mai2hook/meson.build index 8578c51b..f43fccc6 100644 --- a/mai2hook/meson.build +++ b/mai2hook/meson.build @@ -15,6 +15,7 @@ shared_library( hooklib_lib, mai2io_lib, platform_lib, + unityhook_lib, util_lib, ], sources : [ @@ -25,7 +26,5 @@ shared_library( 'io4.h', 'mai2-dll.c', 'mai2-dll.h', - 'unity.h', - 'unity.c', ], ) diff --git a/mai2hook/unity.h b/mai2hook/unity.h deleted file mode 100644 index 99c3bd93..00000000 --- a/mai2hook/unity.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void unity_hook_init(void); diff --git a/meson.build b/meson.build index a0db24aa..90ac2927 100644 --- a/meson.build +++ b/meson.build @@ -42,6 +42,7 @@ shlwapi_lib = cc.find_library('shlwapi') dinput8_lib = cc.find_library('dinput8') dxguid_lib = cc.find_library('dxguid') xinput_lib = cc.find_library('xinput') +pathcch_lib = cc.find_library('pathcch') inc = include_directories('.') capnhook = subproject('capnhook') @@ -55,6 +56,7 @@ subdir('platform') subdir('util') subdir('gfxhook') +subdir('unityhook') subdir('aimeio') subdir('chuniio') diff --git a/mu3hook/config.c b/mu3hook/config.c index bb51103a..290d5aef 100644 --- a/mu3hook/config.c +++ b/mu3hook/config.c @@ -42,4 +42,5 @@ void mu3_hook_config_load( gfx_config_load(&cfg->gfx, filename); vfd_config_load(&cfg->vfd, filename); mu3_dll_config_load(&cfg->dll, filename); + unity_config_load(&cfg->unity, filename); } diff --git a/mu3hook/config.h b/mu3hook/config.h index f1b2cdfc..930a766f 100644 --- a/mu3hook/config.h +++ b/mu3hook/config.h @@ -13,6 +13,8 @@ #include "platform/config.h" +#include "unityhook/config.h" + struct mu3_hook_config { struct platform_config platform; struct aime_config aime; @@ -22,6 +24,7 @@ struct mu3_hook_config { // struct led15093_config led15093; struct vfd_config vfd; struct mu3_dll_config dll; + struct unity_config unity; }; void mu3_dll_config_load( diff --git a/mu3hook/dllmain.c b/mu3hook/dllmain.c index 3f940ddb..bd1ec2bc 100644 --- a/mu3hook/dllmain.c +++ b/mu3hook/dllmain.c @@ -2,7 +2,6 @@ #include -#include "board/io4.h" #include "board/sg-reader.h" #include "board/vfd.h" @@ -20,10 +19,12 @@ #include "mu3hook/config.h" #include "mu3hook/io4.h" #include "mu3hook/mu3-dll.h" -#include "mu3hook/unity.h" #include "platform/platform.h" +#include "unityhook/config.h" +#include "unityhook/hook.h" + #include "util/dprintf.h" static HMODULE mu3_hook_mod; @@ -99,7 +100,7 @@ static DWORD CALLBACK mu3_pre_startup(void) There seems to be an issue with other DLL hooks if `LoadLibraryW` is hooked earlier in the `mu3hook` initialization. */ - unity_hook_init(); + unity_hook_init(&mu3_hook_cfg.unity, mu3_hook_mod); /* Initialize debug helpers */ diff --git a/mu3hook/meson.build b/mu3hook/meson.build index 27ba7f74..a722428f 100644 --- a/mu3hook/meson.build +++ b/mu3hook/meson.build @@ -17,6 +17,7 @@ shared_library( hooklib_lib, mu3io_lib, platform_lib, + unityhook_lib, util_lib, ], sources : [ @@ -27,7 +28,5 @@ shared_library( 'io4.h', 'mu3-dll.c', 'mu3-dll.h', - 'unity.h', - 'unity.c', ], ) diff --git a/mu3hook/unity.c b/mu3hook/unity.c deleted file mode 100644 index efefc32a..00000000 --- a/mu3hook/unity.c +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include - -#include "hook/table.h" - -#include "hooklib/dll.h" -#include "hooklib/path.h" - -#include "util/dprintf.h" - -static void dll_hook_insert_hooks(HMODULE target); - -static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name); -static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); - -static const struct hook_symbol unity_kernel32_syms[] = { - { - .name = "LoadLibraryW", - .patch = my_LoadLibraryW, - .link = (void **) &next_LoadLibraryW, - }, -}; - -static const wchar_t *target_modules[] = { - L"mono.dll", - L"cri_ware_unity.dll", -}; -static const size_t target_modules_len = _countof(target_modules); - -void unity_hook_init(void) -{ - dll_hook_insert_hooks(NULL); -} - -static void dll_hook_insert_hooks(HMODULE target) -{ - hook_table_apply( - target, - "kernel32.dll", - unity_kernel32_syms, - _countof(unity_kernel32_syms)); -} - -static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) -{ - const wchar_t *name_end; - const wchar_t *target_module; - bool already_loaded; - HMODULE result; - size_t name_len; - size_t target_module_len; - - if (name == NULL) { - SetLastError(ERROR_INVALID_PARAMETER); - - return NULL; - } - - // Check if the module is already loaded - already_loaded = GetModuleHandleW(name) != NULL; - - // Must call the next handler so the DLL reference count is incremented - result = next_LoadLibraryW(name); - - if (!already_loaded && result != NULL) { - name_len = wcslen(name); - - for (size_t i = 0; i < target_modules_len; i++) { - target_module = target_modules[i]; - target_module_len = wcslen(target_module); - - // Check if the newly loaded library is at least the length of - // the name of the target module - if (name_len < target_module_len) { - continue; - } - - name_end = &name[name_len - target_module_len]; - - // Check if the name of the newly loaded library is one of the - // modules the path hooks should be injected into - if (_wcsicmp(name_end, target_module) != 0) { - continue; - } - - dprintf("Unity: Loaded %S\n", target_module); - - dll_hook_insert_hooks(result); - path_hook_insert_hooks(result); - } - } - - return result; -} diff --git a/mu3hook/unity.h b/mu3hook/unity.h deleted file mode 100644 index 99c3bd93..00000000 --- a/mu3hook/unity.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void unity_hook_init(void); diff --git a/unityhook/config.c b/unityhook/config.c new file mode 100644 index 00000000..6f0a0cc2 --- /dev/null +++ b/unityhook/config.c @@ -0,0 +1,14 @@ +#include "config.h" + +void unity_config_load(struct unity_config *cfg, const wchar_t *filename) { + cfg->enable = GetPrivateProfileIntW(L"unity", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"unity", + L"targetAssembly", + L"", + cfg->target_assembly, + _countof(cfg->target_assembly), + filename + ); +} diff --git a/unityhook/config.h b/unityhook/config.h new file mode 100644 index 00000000..6fca14a7 --- /dev/null +++ b/unityhook/config.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include + +struct unity_config { + bool enable; + wchar_t target_assembly[MAX_PATH]; +}; + +void unity_config_load(struct unity_config *cfg, const wchar_t *filename); diff --git a/unityhook/doorstop.c b/unityhook/doorstop.c new file mode 100644 index 00000000..07b6799f --- /dev/null +++ b/unityhook/doorstop.c @@ -0,0 +1,173 @@ +// A simplified version of NeighTools' UnityDoorstop, allowing mod loaders +// like BepInEx to be loaded into Unity games. +// +// SPDX-License-Identifier: CC0 +// https://github.com/NeighTools/UnityDoorstop +#include + +#include +#include + +#include "hooklib/procaddr.h" +#include "util/dprintf.h" + +#include "doorstop.h" +#include "mono.h" +#include "util.h" + +static void * my_mono_jit_init_version(const char *root_domain_name, const char *runtime_version); +void doorstop_invoke(void *domain); + +static char module_name[MAX_PATH]; +static bool doorstop_hook_initted; +static struct unity_config unity_config; +static struct hook_symbol unity_mono_syms[] = { +{ + .name = "mono_jit_init_version", + .patch = my_mono_jit_init_version, + } +}; + +void doorstop_mono_hook_init(const struct unity_config *cfg, HINSTANCE module) { + if (doorstop_hook_initted || cfg->target_assembly[0] == 0) { + return; + } + + GetModuleBaseNameA(GetCurrentProcess(), module, module_name, MAX_PATH); + + memcpy(&unity_config, cfg, sizeof(*cfg)); + load_mono_functions(module); + proc_addr_table_push(module_name, unity_mono_syms, _countof(unity_mono_syms)); + + doorstop_hook_initted = true; +} + +static void * my_mono_jit_init_version(const char *root_domain_name, const char *runtime_version) { + dprintf("Unity: Starting Mono domain \"%s\"\n", root_domain_name); + + SetEnvironmentVariableW(L"DOORSTOP_DLL_SEARCH_DIRS", widen(mono_assembly_getrootdir())); + + void* domain = mono_jit_init_version(root_domain_name, runtime_version); + + doorstop_invoke(domain); + return domain; +} + +void doorstop_invoke(void* domain) { + if (GetEnvironmentVariableW(L"DOORSTOP_INITIALIZED", NULL, 0) != 0) { + dprintf("Unity: Doorstop is already initialized.\n"); + return; + } + + SetEnvironmentVariableW(L"DOORSTOP_INITIALIZED", L"TRUE"); + + mono_thread_set_main(mono_thread_current()); + + if (mono_domain_set_config) { +#define CONFIG_EXT L".config" + + wchar_t exe_path[MAX_PATH]; + size_t exe_path_len = GetModuleFileNameW(NULL, exe_path, MAX_PATH); + wchar_t *folder_name = wcsdup(exe_path); + + PathCchRemoveFileSpec(folder_name, exe_path_len + 1); + + char *exe_path_n = narrow(exe_path); + char *folder_name_n = narrow(folder_name); + + dprintf("Unity: Setting config paths: base dir: %s; config path: %s\n", folder_name_n, exe_path_n); + + mono_domain_set_config(domain, folder_name_n, exe_path_n); + + free(folder_name); + free(exe_path_n); + free(folder_name_n); + +#undef CONFIG_EXT + } + + SetEnvironmentVariableW(L"DOORSTOP_INVOKE_DLL_PATH", unity_config.target_assembly); + + char *assembly_dir = mono_assembly_getrootdir(); + dprintf("Unity: Assembly directory: %s\n", assembly_dir); + + SetEnvironmentVariableA("DOORSTOP_MANAGED_FOLDER_DIR", assembly_dir); + + wchar_t app_path[MAX_PATH]; + GetModuleFileNameW(NULL, app_path, MAX_PATH); + SetEnvironmentVariableW(L"DOORSTOP_PROCESS_PATH", app_path); + + char* dll_path = narrow(unity_config.target_assembly); + + dprintf("Unity: Loading assembly: %s\n", dll_path); + + void* assembly = mono_domain_assembly_open(domain, dll_path); + + if (!assembly) { + dprintf("Unity: Failed to load assembly\n"); + free(dll_path); + return; + } + + void *image = mono_assembly_get_image(assembly); + + if (!image) { + dprintf("Unity: Assembly image doesn't exist\n"); + free(dll_path); + return; + } + + void *desc = mono_method_desc_new("*:Main", FALSE); + void *method = mono_method_desc_search_in_image(desc, image); + + if (!method) { + dprintf("Unity: Assembly does not have a valid entrypoint.\n"); + free(dll_path); + return; + } + + void *signature = mono_method_signature(method); + UINT32 params = mono_signature_get_param_count(signature); + void **args = NULL; + + if (params == 1) { + // If there is a parameter, it's most likely a string[]. + void *args_array = mono_array_new(domain, mono_get_string_class(), 0); + args = malloc(sizeof(void*) * 1); + args[0] = args_array; + } + + dprintf("Unity: Invoking method %p\n", method); + + void *exc = NULL; + mono_runtime_invoke(method, NULL, args, &exc); + + if (exc) { + dprintf("Unity: Error invoking method!\n"); + + void *ex_class = mono_get_exception_class(); + void *to_string_desc = mono_method_desc_new("*:ToString()", FALSE); + void* to_string_method = mono_method_desc_search_in_class(to_string_desc, ex_class); + + mono_method_desc_free(to_string_desc); + + if (to_string_method) { + void* real_to_string_method = mono_object_get_virtual_method(exc, to_string_method); + void* exc2 = NULL; + void* str = mono_runtime_invoke(real_to_string_method, exc, NULL, &exc2); + + if (!exc2) { + char* exc_str = mono_string_to_utf8(str); + dprintf("Unity: Error message: %s\n", exc_str); + } + } + } + + mono_method_desc_free(desc); + free(dll_path); + + if (args) { + free(args); + args = NULL; + } +} diff --git a/unityhook/doorstop.h b/unityhook/doorstop.h new file mode 100644 index 00000000..8cc961e8 --- /dev/null +++ b/unityhook/doorstop.h @@ -0,0 +1,5 @@ +#pragma once + +#include "config.h" + +void doorstop_mono_hook_init(const struct unity_config *cfg, HINSTANCE module); \ No newline at end of file diff --git a/mai2hook/unity.c b/unityhook/hook.c similarity index 70% rename from mai2hook/unity.c rename to unityhook/hook.c index 64195be8..060233b9 100644 --- a/mai2hook/unity.c +++ b/unityhook/hook.c @@ -1,14 +1,23 @@ +#include #include -#include - #include "hook/table.h" - -#include "hooklib/dll.h" #include "hooklib/path.h" - #include "util/dprintf.h" +#include "doorstop.h" +#include "hook.h" + +static bool unity_hook_initted; +static struct unity_config unity_config; + +static const wchar_t *target_modules[] = { + L"mono.dll", + L"mono-2.0-bdwgc.dll", + L"cri_ware_unity.dll", +}; +static const size_t target_modules_len = _countof(target_modules); + static void dll_hook_insert_hooks(HMODULE target); static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name); @@ -22,28 +31,33 @@ static const struct hook_symbol unity_kernel32_syms[] = { }, }; -static const wchar_t *target_modules[] = { - L"mono-2.0-bdwgc.dll", - L"cri_ware_unity.dll", -}; -static const size_t target_modules_len = _countof(target_modules); +void unity_hook_init(const struct unity_config *cfg, HINSTANCE self) { + assert(cfg != NULL); -void unity_hook_init(void) -{ + if (!cfg->enable) { + return; + } + + if (unity_hook_initted) { + return; + } + + memcpy(&unity_config, cfg, sizeof(*cfg)); dll_hook_insert_hooks(NULL); + + unity_hook_initted = true; + dprintf("Unity: Hook enabled.\n"); } -static void dll_hook_insert_hooks(HMODULE target) -{ +static void dll_hook_insert_hooks(HMODULE target) { hook_table_apply( - target, - "kernel32.dll", - unity_kernel32_syms, - _countof(unity_kernel32_syms)); + target, + "kernel32.dll", + unity_kernel32_syms, + _countof(unity_kernel32_syms)); } -static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) -{ +static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) { const wchar_t *name_end; const wchar_t *target_module; bool already_loaded; @@ -66,6 +80,11 @@ static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) if (!already_loaded && result != NULL) { name_len = wcslen(name); + // mono entrypoint for injecting target_assembly + if (GetProcAddress(result, "mono_jit_init_version")) { + doorstop_mono_hook_init(&unity_config, result); + } + for (size_t i = 0; i < target_modules_len; i++) { target_module = target_modules[i]; target_module_len = wcslen(target_module); diff --git a/unityhook/hook.h b/unityhook/hook.h new file mode 100644 index 00000000..684868cf --- /dev/null +++ b/unityhook/hook.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "config.h" + +void unity_hook_init(const struct unity_config *cfg, HINSTANCE self); diff --git a/unityhook/meson.build b/unityhook/meson.build new file mode 100644 index 00000000..9fd7a7b4 --- /dev/null +++ b/unityhook/meson.build @@ -0,0 +1,20 @@ +unityhook_lib = static_library( + 'unityhook', + include_directories: inc, + implicit_include_directories: false, + c_pch: '../precompiled.h', + dependencies: [ + capnhook.get_variable('hook_dep'), + pathcch_lib + ], + sources: [ + 'mono.h', + 'config.c', + 'config.h', + 'doorstop.c', + 'doorstop.h', + 'hook.c', + 'hook.h', + 'util.h' + ], +) diff --git a/unityhook/mono.h b/unityhook/mono.h new file mode 100644 index 00000000..7056e75f --- /dev/null +++ b/unityhook/mono.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: CC0 +// https://github.com/NeighTools/UnityDoorstop +#pragma once + +#include + +// Here we define the pointers to some functions within mono.dll +// Note to C learners: these are not signature definitions, but rather "variable" +// definitions with the function pointer type. + +// Note: we use void* instead of the real intented structs defined in mono API +// This way we don't need to include or define any of Mono's structs, which saves space +// This, obviously, comes with a drawback of not being able to easily access the contents of the structs + +void * (*mono_thread_current)(); +void (*mono_thread_set_main)(void *); + +void *(*mono_jit_init_version)(const char *root_domain_name, const char *runtime_version); +void *(*mono_domain_assembly_open)(void *domain, const char *name); +void *(*mono_assembly_get_image)(void *assembly); +void *(*mono_runtime_invoke)(void *method, void *obj, void **params, void **exc); + +void *(*mono_method_desc_new)(const char *name, int include_namespace); +void* (*mono_method_desc_search_in_image)(void* desc, void* image); +void *(*mono_method_desc_search_in_class)(void *desc, void *klass); +void (*mono_method_desc_free)(void *desc); +void *(*mono_method_signature)(void *method); +UINT32 (*mono_signature_get_param_count)(void *sig); + +void (*mono_domain_set_config)(void *domain, char *base_dir, char *config_file_name); +void *(*mono_array_new)(void *domain, void *eclass, uintptr_t n); +void *(*mono_get_string_class)(); + +char *(*mono_assembly_getrootdir)(); + +// Additional funcs to bootstrap custom MONO +void (*mono_set_dirs)(const char* assembly_dir, const char* config_dir); +void (*mono_config_parse)(const char* filename); +void (*mono_set_assemblies_path)(const char* path); +void *(*mono_object_to_string)(void* obj, void** exc); +char *(*mono_string_to_utf8)(void* s); + +void *(*mono_image_open_from_data_with_name)(void *data, DWORD data_len, int need_copy, void *status, int refonly, + const char *name); + +void* (*mono_get_exception_class)(); +void* (*mono_object_get_virtual_method)(void* obj_raw, void* method); + +void* (*mono_jit_parse_options)(int argc, const char** argv); + +typedef enum { + MONO_DEBUG_FORMAT_NONE, + MONO_DEBUG_FORMAT_MONO, + /* Deprecated, the mdb debugger is not longer supported. */ + MONO_DEBUG_FORMAT_DEBUGGER +} MonoDebugFormat; + +void* (*mono_debug_init)(MonoDebugFormat format); +void* (*mono_debug_domain_create)(void* domain); + +/** +* \brief Loads Mono C API function pointers so that the above definitions can be called. +* \param mono_lib Mono.dll module. +*/ +void load_mono_functions(HMODULE mono_lib) { + // Enjoy the fact that C allows such sloppy casting + // In C++ you would have to cast to the precise function pointer type +#define GET_MONO_PROC(name) name = (void*)GetProcAddress(mono_lib, #name) + + // Find and assign all our functions that we are going to use + GET_MONO_PROC(mono_domain_assembly_open); + GET_MONO_PROC(mono_assembly_get_image); + GET_MONO_PROC(mono_runtime_invoke); + GET_MONO_PROC(mono_jit_init_version); + GET_MONO_PROC(mono_method_desc_new); + GET_MONO_PROC(mono_method_desc_search_in_class); + GET_MONO_PROC(mono_method_desc_search_in_image); + GET_MONO_PROC(mono_method_desc_free); + GET_MONO_PROC(mono_method_signature); + GET_MONO_PROC(mono_signature_get_param_count); + GET_MONO_PROC(mono_array_new); + GET_MONO_PROC(mono_get_string_class); + GET_MONO_PROC(mono_assembly_getrootdir); + GET_MONO_PROC(mono_thread_current); + GET_MONO_PROC(mono_thread_set_main); + GET_MONO_PROC(mono_domain_set_config); + GET_MONO_PROC(mono_set_dirs); + GET_MONO_PROC(mono_config_parse); + GET_MONO_PROC(mono_set_assemblies_path); + GET_MONO_PROC(mono_object_to_string); + GET_MONO_PROC(mono_string_to_utf8); + GET_MONO_PROC(mono_image_open_from_data_with_name); + GET_MONO_PROC(mono_get_exception_class); + GET_MONO_PROC(mono_object_get_virtual_method); + GET_MONO_PROC(mono_jit_parse_options); + GET_MONO_PROC(mono_debug_init); + GET_MONO_PROC(mono_debug_domain_create); + +#undef GET_MONO_PROC +} \ No newline at end of file diff --git a/unityhook/util.h b/unityhook/util.h new file mode 100644 index 00000000..41def7b7 --- /dev/null +++ b/unityhook/util.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +wchar_t *widen(const char *str) { + const int reqsz = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + wchar_t *result = malloc(reqsz * sizeof(wchar_t)); + + MultiByteToWideChar(CP_UTF8, 0, str, -1, result, reqsz); + return result; +} + +char *narrow(const wchar_t *str) { + const int reqsz = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + char *result = malloc(reqsz * sizeof(char)); + + WideCharToMultiByte(CP_UTF8, 0, str, -1, result, reqsz, NULL, NULL); + return result; +} +