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; +} +