You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

498 lines
13 KiB

  1. /*
  2. * main.cpp -- The main "entry point" and the main logic of the DLL.
  3. *
  4. * Here, we define and initialize struct Main that contains the main code of this DLL.
  5. *
  6. * The main procedure goes as follows:
  7. * 1. The loader checks that PatchLoader.dll and mono.dll exist
  8. * 2. mono.dll is loaded into memory and some of its functions are looked up
  9. * 3. mono_jit_init_version is hooked with the help of MinHook
  10. *
  11. * Then, the loader waits until Unity creates its root domain for mono (which is done with mono_jit_init_version).
  12. *
  13. * Inside mono_jit_init_version hook:
  14. * 1. Call the original mono_jit_init_version to get the Unity root domain
  15. * 2. Load PatchLoader.dll into the root domain
  16. * 3. Find and invoke PatchLoader.Loader.Run()
  17. *
  18. * Rest of the work is done on the managed side.
  19. *
  20. */
  21. #pragma warning( disable : 4267 100 152 6387 4456 6011 )
  22. #include "winapi_util.h"
  23. #include <Windows.h>
  24. #include "config.h"
  25. #include "mono.h"
  26. #include "hook.h"
  27. #include "assert_util.h"
  28. #include "proxy.h"
  29. #include <synchapi.h>
  30. #include <intrin.h>
  31. EXTERN_C IMAGE_DOS_HEADER __ImageBase; // This is provided by MSVC with the infomration about this DLL
  32. HANDLE unhandledMutex;
  33. void ownMonoJitParseOptions(int argc, char * argv[]);
  34. BOOL setOptions = FALSE;
  35. BOOL shouldBreakOnUnhandledException = TRUE;
  36. __declspec(dllexport) void SetIgnoreUnhandledExceptions(BOOL ignore)
  37. {
  38. shouldBreakOnUnhandledException = ignore;
  39. }
  40. void unhandledException(void* exc, void* data)
  41. {
  42. WaitForSingleObject(unhandledMutex, INFINITE);
  43. void* exception = NULL;
  44. void* mstr = mono_object_to_string(exc, &exception);
  45. if (exception != NULL)
  46. {
  47. #ifdef _VERBOSE
  48. void* monostr = mono_object_to_string(exception, &exception);
  49. if (exception != NULL)
  50. {
  51. DEBUG_BREAK;
  52. LOG("An error occurred while stringifying uncaught error, but the error could not be stringified.\n");
  53. ASSERT(FALSE, L"Uncaught exception; could not stringify");
  54. }
  55. else
  56. {
  57. char* str = mono_string_to_utf8(monostr);
  58. DEBUG_BREAK;
  59. LOG("An error occurred stringifying uncaught error: %s\n", str);
  60. /*size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
  61. wchar_t* wstr = memalloc(sizeof(wchar_t) * len);
  62. MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);*/
  63. wchar_t* wstr = mono_string_to_utf16(monostr);
  64. ASSERT_F(FALSE, L"Uncaught exception; stringify failed: %wS", wstr);
  65. mono_free(wstr);
  66. mono_free(str);
  67. }
  68. #else
  69. ASSERT(FALSE, L"Could not stringify uncaught exception");
  70. #endif
  71. }
  72. char* str = mono_string_to_utf8(mstr);
  73. DEBUG_BREAK;
  74. LOG("Uncaught exception: %s\n", str);
  75. /*size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
  76. wchar_t* wstr = memalloc(sizeof(wchar_t) * len);
  77. MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);*/
  78. wchar_t* wstr = mono_string_to_utf16(mstr);
  79. if (shouldBreakOnUnhandledException)
  80. {
  81. #ifdef _VERBOSE
  82. ASSERT(FALSE, L"Uncaught exception; see doorstop.log for details");
  83. #else
  84. ASSERT_F(FALSE, L"Uncaught exception: %wS", wstr);
  85. #endif
  86. }
  87. mono_free(wstr);
  88. mono_free(str);
  89. ReleaseMutex(unhandledMutex);
  90. }
  91. // The hook for mono_jit_init_version
  92. // We use this since it will always be called once to initialize Mono's JIT
  93. void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_version)
  94. {
  95. const BOOL debugger_already_initialized = mono_debug_enabled();
  96. if(debugger_already_initialized)
  97. {
  98. LOG("Debugger was already initialized\n");
  99. }
  100. // Call the original mono_jit_init_version to initialize the Unity Root Domain
  101. if (debug) {
  102. char* opts[1];
  103. opts[0] = "";
  104. ownMonoJitParseOptions(0, opts);
  105. }
  106. #ifdef WIN32
  107. if (debug_info && !debugger_already_initialized) {
  108. mono_debug_init(MONO_DEBUG_FORMAT_MONO);
  109. }
  110. #endif
  111. void *domain = mono_jit_init_version(root_domain_name, runtime_version);
  112. if (debug_info && !debugger_already_initialized) {
  113. #ifdef WIN64
  114. mono_debug_init(MONO_DEBUG_FORMAT_MONO);
  115. #endif
  116. mono_debug_domain_create(domain);
  117. }
  118. DWORD len = GetFullPathName(targetAssembly, 0, NULL, NULL);
  119. wchar_t *full_path = memalloc(sizeof(wchar_t) * len);
  120. GetFullPathName(targetAssembly, len, full_path, NULL);
  121. size_t path_len = WideCharToMultiByte(CP_UTF8, 0, full_path, -1, NULL, 0, NULL, NULL);
  122. char *dll_path = memalloc(sizeof(char) * path_len);
  123. WideCharToMultiByte(CP_UTF8, 0, full_path, -1, dll_path, path_len, NULL, NULL);
  124. memfree(full_path);
  125. LOG("Loading assembly: %s\n", dll_path);
  126. // Load our custom assembly into the domain
  127. void *assembly = mono_domain_assembly_open(domain, dll_path);
  128. if (assembly == NULL)
  129. LOG("Failed to load assembly\n");
  130. memfree(dll_path);
  131. ASSERT_SOFT(assembly != NULL, domain);
  132. // Get assembly's image that contains CIL code
  133. void *image = mono_assembly_get_image(assembly);
  134. ASSERT_SOFT(image != NULL, domain);
  135. // Note: we use the runtime_invoke route since jit_exec will not work on DLLs
  136. // Create a descriptor for a random Main method
  137. void *desc = mono_method_desc_new("*:Main", FALSE);
  138. // Find the first possible Main method in the assembly
  139. void *method = mono_method_desc_search_in_image(desc, image);
  140. ASSERT_SOFT(method != NULL, domain);
  141. void *signature = mono_method_signature(method);
  142. // Get the number of parameters in the signature
  143. UINT32 params = mono_signature_get_param_count(signature);
  144. void **args = NULL;
  145. wchar_t *app_path = NULL;
  146. if (params == 1)
  147. {
  148. // If there is a parameter, it's most likely a string[].
  149. // Populate it as follows
  150. // 0 => path to the game's executable
  151. // 1 => --doorstop-invoke
  152. get_module_path(NULL, &app_path, NULL, 0);
  153. void *exe_path = MONO_STRING(app_path);
  154. void *doorstop_handle = MONO_STRING(L"--doorstop-invoke");
  155. void *args_array = mono_array_new(domain, mono_get_string_class(), 2);
  156. SET_ARRAY_REF(args_array, 0, exe_path);
  157. SET_ARRAY_REF(args_array, 1, doorstop_handle);
  158. args = memalloc(sizeof(void*) * 1);
  159. _ASSERTE(args != nullptr);
  160. args[0] = args_array;
  161. }
  162. LOG("Installing uncaught exception handler\n");
  163. mono_install_unhandled_exception_hook(unhandledException, NULL);
  164. wchar_t* dll_path_w; // self path
  165. size_t dll_path_len = get_module_path((HINSTANCE)&__ImageBase, &dll_path_w, NULL, 0);
  166. size_t multibyte_path_len = WideCharToMultiByte(CP_UTF8, 0, dll_path_w, dll_path_len, NULL, 0, NULL, NULL);
  167. char* self_dll_path = memalloc(multibyte_path_len + 1);
  168. WideCharToMultiByte(CP_UTF8, 0, dll_path_w, dll_path_len, self_dll_path, multibyte_path_len + 1, NULL, NULL);
  169. self_dll_path[multibyte_path_len] = 0;
  170. mono_dllmap_insert(NULL, "i:bsipa-doorstop", NULL, self_dll_path, NULL); // remap `bsipa-doorstop` to this assembly
  171. memfree(self_dll_path);
  172. memfree(dll_path_w);
  173. unhandledMutex = CreateMutexW(NULL, FALSE, NULL);
  174. LOG("Invoking method!\n");
  175. void* exception = NULL;
  176. mono_runtime_invoke(method, NULL, args, &exception);
  177. WaitForSingleObject(unhandledMutex, INFINITE); // if the EH is triggered, wait for it
  178. if (args != NULL)
  179. {
  180. memfree(app_path);
  181. memfree(args);
  182. NULL;
  183. }
  184. #ifdef _VERBOSE
  185. if (exception != NULL)
  186. {
  187. void* monostr = mono_object_to_string(exception, &exception);
  188. if (exception != NULL)
  189. LOG("An error occurred while invoking the injector, but the error could not be stringified.\n")
  190. else
  191. {
  192. char* str = mono_string_to_utf8(monostr);
  193. LOG("An error occurred invoking the injector: %s\n", str);
  194. mono_free(str);
  195. }
  196. }
  197. #endif
  198. cleanupConfig();
  199. free_logger();
  200. ReleaseMutex(unhandledMutex);
  201. return domain;
  202. }
  203. void ownMonoJitParseOptions(int argc, char * argv[])
  204. {
  205. setOptions = TRUE;
  206. int size = argc;
  207. #ifdef WIN64
  208. if (debug) size += 2;
  209. #elif defined(WIN32)
  210. if (debug) size += 1;
  211. #endif
  212. char** arguments = memalloc(sizeof(char*) * size);
  213. _ASSERTE(arguments != nullptr);
  214. memcpy(arguments, argv, sizeof(char*) * argc);
  215. if (debug) {
  216. //arguments[argc++] = "--debug";
  217. #ifdef WIN64
  218. arguments[argc++] = "--soft-breakpoints";
  219. #endif
  220. if (debug_server)
  221. arguments[argc] = "--debugger-agent=transport=dt_socket,address=0.0.0.0:10000,server=y";
  222. else
  223. arguments[argc] = "--debugger-agent=transport=dt_socket,address=127.0.0.1:10000,server=n";
  224. }
  225. mono_jit_parse_options(size, arguments);
  226. memfree(arguments);
  227. }
  228. BOOL initialized = FALSE;
  229. void init(HMODULE module)
  230. {
  231. if (!initialized)
  232. {
  233. initialized = TRUE;
  234. LOG("Got mono.dll at %p\n", module);
  235. loadMonoFunctions(module);
  236. }
  237. }
  238. void * WINAPI hookGetProcAddress(HMODULE module, char const *name)
  239. {
  240. if (lstrcmpA(name, "mono_jit_init_version") == 0)
  241. {
  242. init(module);
  243. return (void*)&ownMonoJitInitVersion;
  244. }
  245. if (lstrcmpA(name, "mono_jit_parse_options") == 0 && debug)
  246. {
  247. init(module);
  248. return (void*)&ownMonoJitParseOptions;
  249. }
  250. return (void*)GetProcAddress(module, name);
  251. }
  252. BOOL hookGetMessage(
  253. BOOL isW,
  254. LPMSG msg,
  255. HWND hwnd,
  256. UINT wMsgFilterMin,
  257. UINT wMsgFilterMax
  258. );
  259. BOOL WINAPI hookGetMessageA(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax)
  260. {
  261. return hookGetMessage(FALSE, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
  262. }
  263. BOOL WINAPI hookGetMessageW(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax)
  264. {
  265. return hookGetMessage(TRUE, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
  266. }
  267. typedef BOOL(*GetMessageHook)(BOOL isW, BOOL result, LPMSG msg, HWND hwnd, UINT filterMin, UINT filterMax);
  268. GetMessageHook getMessageHook = NULL;
  269. __declspec(dllexport) void __stdcall SetGetMessageHook(GetMessageHook hook) {
  270. getMessageHook = hook;
  271. }
  272. BOOL hookGetMessage(
  273. BOOL isW,
  274. LPMSG msg,
  275. HWND hwnd,
  276. UINT wMsgFilterMin,
  277. UINT wMsgFilterMax
  278. )
  279. {
  280. BOOL loop = FALSE;
  281. BOOL result;
  282. do {
  283. if (isW) {
  284. result = GetMessageW(msg, hwnd, wMsgFilterMin, wMsgFilterMax);
  285. } else {
  286. result = GetMessageA(msg, hwnd, wMsgFilterMin, wMsgFilterMax);
  287. }
  288. if (getMessageHook) {
  289. loop = getMessageHook(isW, result, msg, hwnd, wMsgFilterMin, wMsgFilterMax);
  290. }
  291. } while (loop);
  292. return result;
  293. }
  294. BOOL hookPeekMessage(
  295. BOOL isW,
  296. LPMSG msg,
  297. HWND hwnd,
  298. UINT wMsgFilterMin,
  299. UINT wMsgFilterMax,
  300. UINT wRemoveMsg
  301. );
  302. BOOL WINAPI hookPeekMessageA(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
  303. {
  304. return hookPeekMessage(FALSE, msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
  305. }
  306. BOOL WINAPI hookPeekMessageW(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
  307. {
  308. return hookPeekMessage(TRUE, msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
  309. }
  310. typedef BOOL(*PeekMessageHook)(BOOL isW, BOOL result, LPMSG msg, HWND hwnd, UINT filterMin, UINT filterMax, UINT* wRemoveMsg);
  311. PeekMessageHook peekMessageHook = NULL;
  312. __declspec(dllexport) void __stdcall SetPeekMessageHook(PeekMessageHook hook) {
  313. peekMessageHook = hook;
  314. }
  315. BOOL hookPeekMessage(
  316. BOOL isW,
  317. LPMSG msg,
  318. HWND hwnd,
  319. UINT wMsgFilterMin,
  320. UINT wMsgFilterMax,
  321. UINT wRemoveMsg
  322. )
  323. {
  324. BOOL loop = FALSE;
  325. BOOL result;
  326. do {
  327. if (isW) {
  328. result = PeekMessageW(msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
  329. }
  330. else {
  331. result = PeekMessageA(msg, hwnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
  332. }
  333. if (peekMessageHook) {
  334. loop = peekMessageHook(isW, result, msg, hwnd, wMsgFilterMin, wMsgFilterMax, &wRemoveMsg);
  335. }
  336. } while (loop);
  337. return result;
  338. }
  339. BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reasonForDllLoad, LPVOID reserved)
  340. {
  341. if (reasonForDllLoad != DLL_PROCESS_ATTACH)
  342. return TRUE;
  343. hHeap = GetProcessHeap();
  344. init_logger();
  345. LOG("Doorstop started!\n");
  346. wchar_t *dll_path = NULL;
  347. size_t dll_path_len = get_module_path((HINSTANCE)&__ImageBase, &dll_path, NULL, 0);
  348. LOG("DLL Path: %S\n", dll_path);
  349. wchar_t *dll_name = get_file_name_no_ext(dll_path, dll_path_len);
  350. LOG("Doorstop DLL Name: %S\n", dll_name);
  351. loadProxy(dll_name);
  352. loadConfig();
  353. // If the loader is disabled, don't inject anything.
  354. if (enabled)
  355. {
  356. LOG("Doorstop enabled!\n");
  357. ASSERT_SOFT(GetFileAttributesW(targetAssembly) != INVALID_FILE_ATTRIBUTES, TRUE);
  358. HMODULE targetModule = GetModuleHandleA("UnityPlayer");
  359. if(targetModule == NULL)
  360. {
  361. LOG("No UnityPlayer.dll; using EXE as the hook target.");
  362. targetModule = GetModuleHandleA(NULL);
  363. }
  364. LOG("Installing IAT hook\n");
  365. if (!iat_hook(targetModule, "kernel32.dll", &GetProcAddress, &hookGetProcAddress))
  366. {
  367. LOG("Failed to install IAT hook!\n");
  368. free_logger();
  369. }
  370. LOG("Hook installed!\n");
  371. LOG("Attempting to install GetMessageA and GetMessageW hooks\n");
  372. if (!iat_hook(targetModule, "user32.dll", &GetMessageA, &hookGetMessageA)) {
  373. LOG("Could not hook GetMessageA! (not an error)\n");
  374. }
  375. if (!iat_hook(targetModule, "user32.dll", &GetMessageW, &hookGetMessageW)) {
  376. LOG("Could not hook GetMessageW! (not an error)\n");
  377. }
  378. LOG("Attempting to install PeekMessageA and PeekMessageW hooks\n");
  379. if (!iat_hook(targetModule, "user32.dll", &PeekMessageA, &hookPeekMessageA)) {
  380. LOG("Could not hook PeekMessageA! (not an error)\n");
  381. }
  382. if (!iat_hook(targetModule, "user32.dll", &PeekMessageW, &hookPeekMessageW)) {
  383. LOG("Could not hook PeekMessageW! (not an error)\n");
  384. }
  385. }
  386. else
  387. {
  388. LOG("Doorstop disabled! memfreeing resources\n");
  389. free_logger();
  390. }
  391. memfree(dll_name);
  392. memfree(dll_path);
  393. return TRUE;
  394. }