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.

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