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 fd9186c..4c0a808 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 cf39c70..a840358 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 0e12adb..687601a 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 338f773..6c645c2 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 efefc32..0000000 --- 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 99c3bd9..0000000 --- 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 37f5708..181c785 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 3aa8758..ed8cc0a 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 ac897df..c55cbbb 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 86b9764..d41a1d6 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 2def3a7..7e86822 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 5d53368..1d10c60 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 8578c51..f43fccc 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 99c3bd9..0000000 --- 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 a0db24a..90ac292 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 bb51103..290d5ae 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 f1b2cdf..930a766 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 3f940dd..bd1ec2b 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 27ba7f7..a722428 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 efefc32..0000000 --- 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 99c3bd9..0000000 --- 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 0000000..6f0a0cc --- /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 0000000..6fca14a --- /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 0000000..07b6799 --- /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 0000000..8cc961e --- /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 64195be..060233b 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 0000000..684868c --- /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 0000000..9fd7a7b --- /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 0000000..7056e75 --- /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 0000000..41def7b --- /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; +} +