gd_mono.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. /**************************************************************************/
  2. /* gd_mono.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "gd_mono.h"
  31. #include "../csharp_script.h"
  32. #include "../glue/runtime_interop.h"
  33. #include "../godotsharp_dirs.h"
  34. #include "../thirdparty/coreclr_delegates.h"
  35. #include "../thirdparty/hostfxr.h"
  36. #include "../utils/path_utils.h"
  37. #include "gd_mono_cache.h"
  38. #ifdef TOOLS_ENABLED
  39. #include "../editor/hostfxr_resolver.h"
  40. #endif
  41. #include "core/config/project_settings.h"
  42. #include "core/debugger/engine_debugger.h"
  43. #include "core/io/dir_access.h"
  44. #include "core/io/file_access.h"
  45. #include "core/os/os.h"
  46. #include "core/os/thread.h"
  47. #ifdef UNIX_ENABLED
  48. #include <dlfcn.h>
  49. #endif
  50. #ifndef TOOLS_ENABLED
  51. #ifdef ANDROID_ENABLED
  52. #include "../thirdparty/mono_delegates.h"
  53. #endif
  54. #endif
  55. GDMono *GDMono::singleton = nullptr;
  56. namespace {
  57. hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = nullptr;
  58. hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = nullptr;
  59. hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr;
  60. hostfxr_close_fn hostfxr_close = nullptr;
  61. #ifndef TOOLS_ENABLED
  62. typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_create_delegate_fn)(void *hostHandle, unsigned int domainId, const char *entryPointAssemblyName, const char *entryPointTypeName, const char *entryPointMethodName, void **delegate);
  63. typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_initialize_fn)(const char *exePath, const char *appDomainFriendlyName, int propertyCount, const char **propertyKeys, const char **propertyValues, void **hostHandle, unsigned int *domainId);
  64. coreclr_create_delegate_fn coreclr_create_delegate = nullptr;
  65. coreclr_initialize_fn coreclr_initialize = nullptr;
  66. #ifdef ANDROID_ENABLED
  67. mono_install_assembly_preload_hook_fn mono_install_assembly_preload_hook = nullptr;
  68. mono_assembly_name_get_name_fn mono_assembly_name_get_name = nullptr;
  69. mono_assembly_name_get_culture_fn mono_assembly_name_get_culture = nullptr;
  70. mono_image_open_from_data_with_name_fn mono_image_open_from_data_with_name = nullptr;
  71. mono_assembly_load_from_full_fn mono_assembly_load_from_full = nullptr;
  72. #endif
  73. #endif
  74. #ifdef _WIN32
  75. static_assert(sizeof(char_t) == sizeof(char16_t));
  76. using HostFxrCharString = Char16String;
  77. #define HOSTFXR_STR(m_str) L##m_str
  78. #else
  79. static_assert(sizeof(char_t) == sizeof(char));
  80. using HostFxrCharString = CharString;
  81. #define HOSTFXR_STR(m_str) m_str
  82. #endif
  83. HostFxrCharString str_to_hostfxr(const String &p_str) {
  84. #ifdef _WIN32
  85. return p_str.utf16();
  86. #else
  87. return p_str.utf8();
  88. #endif
  89. }
  90. const char_t *get_data(const HostFxrCharString &p_char_str) {
  91. return (const char_t *)p_char_str.get_data();
  92. }
  93. String find_hostfxr() {
  94. #ifdef TOOLS_ENABLED
  95. String dotnet_root;
  96. String fxr_path;
  97. if (godotsharp::hostfxr_resolver::try_get_path(dotnet_root, fxr_path)) {
  98. return fxr_path;
  99. }
  100. // hostfxr_resolver doesn't look for dotnet in `PATH`. If it fails, we try to find the dotnet
  101. // executable in `PATH` here and pass its location as `dotnet_root` to `get_hostfxr_path`.
  102. String dotnet_exe = Path::find_executable("dotnet");
  103. if (!dotnet_exe.is_empty()) {
  104. // The file found in PATH may be a symlink
  105. dotnet_exe = Path::abspath(Path::realpath(dotnet_exe));
  106. // TODO:
  107. // Sometimes, the symlink may not point to the dotnet executable in the dotnet root.
  108. // That's the case with snaps. The snap install should have been found with the
  109. // previous `get_hostfxr_path`, but it would still be better to do this properly
  110. // and use something like `dotnet --list-sdks/runtimes` to find the actual location.
  111. // This way we could also check if the proper sdk or runtime is installed. This would
  112. // allow us to fail gracefully and show some helpful information in the editor.
  113. dotnet_root = dotnet_exe.get_base_dir();
  114. if (godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(dotnet_root, fxr_path)) {
  115. return fxr_path;
  116. }
  117. }
  118. ERR_PRINT(String() + ".NET: One of the dependent libraries is missing. " +
  119. "Typically when the `hostfxr`, `hostpolicy` or `coreclr` dynamic " +
  120. "libraries are not present in the expected locations.");
  121. return String();
  122. #else
  123. #if defined(WINDOWS_ENABLED)
  124. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  125. .path_join("hostfxr.dll");
  126. #elif defined(MACOS_ENABLED)
  127. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  128. .path_join("libhostfxr.dylib");
  129. #elif defined(UNIX_ENABLED)
  130. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  131. .path_join("libhostfxr.so");
  132. #else
  133. #error "Platform not supported (yet?)"
  134. #endif
  135. if (FileAccess::exists(probe_path)) {
  136. return probe_path;
  137. }
  138. return String();
  139. #endif
  140. }
  141. #ifndef TOOLS_ENABLED
  142. String find_monosgen() {
  143. #if defined(ANDROID_ENABLED)
  144. // Android includes all native libraries in the libs directory of the APK
  145. // so we assume it exists and use only the name to dlopen it.
  146. return "libmonosgen-2.0.so";
  147. #else
  148. #if defined(WINDOWS_ENABLED)
  149. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  150. .path_join("monosgen-2.0.dll");
  151. #elif defined(MACOS_ENABLED)
  152. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  153. .path_join("libmonosgen-2.0.dylib");
  154. #elif defined(UNIX_ENABLED)
  155. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  156. .path_join("libmonosgen-2.0.so");
  157. #else
  158. #error "Platform not supported (yet?)"
  159. #endif
  160. if (FileAccess::exists(probe_path)) {
  161. return probe_path;
  162. }
  163. return String();
  164. #endif
  165. }
  166. String find_coreclr() {
  167. #if defined(WINDOWS_ENABLED)
  168. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  169. .path_join("coreclr.dll");
  170. #elif defined(MACOS_ENABLED)
  171. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  172. .path_join("libcoreclr.dylib");
  173. #elif defined(UNIX_ENABLED)
  174. String probe_path = GodotSharpDirs::get_api_assemblies_dir()
  175. .path_join("libcoreclr.so");
  176. #else
  177. #error "Platform not supported (yet?)"
  178. #endif
  179. if (FileAccess::exists(probe_path)) {
  180. return probe_path;
  181. }
  182. return String();
  183. }
  184. #endif
  185. bool load_hostfxr(void *&r_hostfxr_dll_handle) {
  186. String hostfxr_path = find_hostfxr();
  187. if (hostfxr_path.is_empty()) {
  188. return false;
  189. }
  190. print_verbose("Found hostfxr: " + hostfxr_path);
  191. Error err = OS::get_singleton()->open_dynamic_library(hostfxr_path, r_hostfxr_dll_handle);
  192. if (err != OK) {
  193. return false;
  194. }
  195. void *lib = r_hostfxr_dll_handle;
  196. void *symbol = nullptr;
  197. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_dotnet_command_line", symbol);
  198. ERR_FAIL_COND_V(err != OK, false);
  199. hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)symbol;
  200. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_runtime_config", symbol);
  201. ERR_FAIL_COND_V(err != OK, false);
  202. hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)symbol;
  203. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_get_runtime_delegate", symbol);
  204. ERR_FAIL_COND_V(err != OK, false);
  205. hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)symbol;
  206. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_close", symbol);
  207. ERR_FAIL_COND_V(err != OK, false);
  208. hostfxr_close = (hostfxr_close_fn)symbol;
  209. return (hostfxr_initialize_for_runtime_config &&
  210. hostfxr_get_runtime_delegate &&
  211. hostfxr_close);
  212. }
  213. #ifndef TOOLS_ENABLED
  214. bool load_coreclr(void *&r_coreclr_dll_handle) {
  215. String coreclr_path = find_coreclr();
  216. bool is_monovm = false;
  217. if (coreclr_path.is_empty()) {
  218. // Fallback to MonoVM (should have the same API as CoreCLR).
  219. coreclr_path = find_monosgen();
  220. is_monovm = true;
  221. }
  222. if (coreclr_path.is_empty()) {
  223. return false;
  224. }
  225. const String coreclr_name = is_monovm ? "monosgen" : "coreclr";
  226. print_verbose("Found " + coreclr_name + ": " + coreclr_path);
  227. Error err = OS::get_singleton()->open_dynamic_library(coreclr_path, r_coreclr_dll_handle);
  228. if (err != OK) {
  229. return false;
  230. }
  231. void *lib = r_coreclr_dll_handle;
  232. void *symbol = nullptr;
  233. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_initialize", symbol);
  234. ERR_FAIL_COND_V(err != OK, false);
  235. coreclr_initialize = (coreclr_initialize_fn)symbol;
  236. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_create_delegate", symbol);
  237. ERR_FAIL_COND_V(err != OK, false);
  238. coreclr_create_delegate = (coreclr_create_delegate_fn)symbol;
  239. #ifdef ANDROID_ENABLED
  240. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_install_assembly_preload_hook", symbol);
  241. ERR_FAIL_COND_V(err != OK, false);
  242. mono_install_assembly_preload_hook = (mono_install_assembly_preload_hook_fn)symbol;
  243. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_name_get_name", symbol);
  244. ERR_FAIL_COND_V(err != OK, false);
  245. mono_assembly_name_get_name = (mono_assembly_name_get_name_fn)symbol;
  246. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_name_get_culture", symbol);
  247. ERR_FAIL_COND_V(err != OK, false);
  248. mono_assembly_name_get_culture = (mono_assembly_name_get_culture_fn)symbol;
  249. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_image_open_from_data_with_name", symbol);
  250. ERR_FAIL_COND_V(err != OK, false);
  251. mono_image_open_from_data_with_name = (mono_image_open_from_data_with_name_fn)symbol;
  252. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_load_from_full", symbol);
  253. ERR_FAIL_COND_V(err != OK, false);
  254. mono_assembly_load_from_full = (mono_assembly_load_from_full_fn)symbol;
  255. #endif
  256. return (coreclr_initialize &&
  257. coreclr_create_delegate);
  258. }
  259. #endif
  260. #ifdef TOOLS_ENABLED
  261. load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) {
  262. hostfxr_handle cxt = nullptr;
  263. int rc = hostfxr_initialize_for_runtime_config(p_config_path, nullptr, &cxt);
  264. if (rc != 0 || cxt == nullptr) {
  265. hostfxr_close(cxt);
  266. ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_runtime_config failed with code: " + itos(rc));
  267. }
  268. void *load_assembly_and_get_function_pointer = nullptr;
  269. rc = hostfxr_get_runtime_delegate(cxt,
  270. hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer);
  271. if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) {
  272. ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc));
  273. }
  274. hostfxr_close(cxt);
  275. return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
  276. }
  277. #else
  278. load_assembly_and_get_function_pointer_fn initialize_hostfxr_self_contained(
  279. const char_t *p_main_assembly_path) {
  280. hostfxr_handle cxt = nullptr;
  281. List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
  282. List<HostFxrCharString> argv_store;
  283. Vector<const char_t *> argv;
  284. argv.resize(cmdline_args.size() + 1);
  285. argv.write[0] = p_main_assembly_path;
  286. int i = 1;
  287. for (const String &E : cmdline_args) {
  288. HostFxrCharString &stored = argv_store.push_back(str_to_hostfxr(E))->get();
  289. argv.write[i] = get_data(stored);
  290. i++;
  291. }
  292. int rc = hostfxr_initialize_for_dotnet_command_line(argv.size(), argv.ptrw(), nullptr, &cxt);
  293. if (rc != 0 || cxt == nullptr) {
  294. hostfxr_close(cxt);
  295. ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_dotnet_command_line failed with code: " + itos(rc));
  296. }
  297. void *load_assembly_and_get_function_pointer = nullptr;
  298. rc = hostfxr_get_runtime_delegate(cxt,
  299. hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer);
  300. if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) {
  301. ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc));
  302. }
  303. hostfxr_close(cxt);
  304. return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
  305. }
  306. #endif
  307. #ifdef TOOLS_ENABLED
  308. using godot_plugins_initialize_fn = bool (*)(void *, bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *, const void **, int32_t);
  309. #else
  310. using godot_plugins_initialize_fn = bool (*)(void *, GDMonoCache::ManagedCallbacks *, const void **, int32_t);
  311. #endif
  312. #ifdef TOOLS_ENABLED
  313. godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
  314. godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
  315. HostFxrCharString godot_plugins_path = str_to_hostfxr(
  316. GodotSharpDirs::get_api_assemblies_dir().path_join("GodotPlugins.dll"));
  317. HostFxrCharString config_path = str_to_hostfxr(
  318. GodotSharpDirs::get_api_assemblies_dir().path_join("GodotPlugins.runtimeconfig.json"));
  319. load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer =
  320. initialize_hostfxr_for_config(get_data(config_path));
  321. if (load_assembly_and_get_function_pointer == nullptr) {
  322. // Show a message box to the user to make the problem explicit (and explain a potential crash).
  323. OS::get_singleton()->alert(TTR("Unable to load .NET runtime, no compatible version was found.\nAttempting to create/edit a project will lead to a crash.\n\nPlease install the .NET SDK 8.0 or later from https://get.dot.net and restart Godot."), TTR("Failed to load .NET runtime"));
  324. ERR_FAIL_V_MSG(nullptr, ".NET: Failed to load compatible .NET runtime");
  325. }
  326. r_runtime_initialized = true;
  327. print_verbose(".NET: hostfxr initialized");
  328. int rc = load_assembly_and_get_function_pointer(get_data(godot_plugins_path),
  329. HOSTFXR_STR("GodotPlugins.Main, GodotPlugins"),
  330. HOSTFXR_STR("InitializeFromEngine"),
  331. UNMANAGEDCALLERSONLY_METHOD,
  332. nullptr,
  333. (void **)&godot_plugins_initialize);
  334. ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");
  335. return godot_plugins_initialize;
  336. }
  337. #else
  338. godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
  339. godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
  340. String assembly_name = Path::get_csharp_project_name();
  341. HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir()
  342. .path_join(assembly_name + ".dll"));
  343. load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer =
  344. initialize_hostfxr_self_contained(get_data(assembly_path));
  345. ERR_FAIL_NULL_V(load_assembly_and_get_function_pointer, nullptr);
  346. r_runtime_initialized = true;
  347. print_verbose(".NET: hostfxr initialized");
  348. int rc = load_assembly_and_get_function_pointer(get_data(assembly_path),
  349. get_data(str_to_hostfxr("GodotPlugins.Game.Main, " + assembly_name)),
  350. HOSTFXR_STR("InitializeFromGameProject"),
  351. UNMANAGEDCALLERSONLY_METHOD,
  352. nullptr,
  353. (void **)&godot_plugins_initialize);
  354. ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");
  355. return godot_plugins_initialize;
  356. }
  357. godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) {
  358. String assembly_name = Path::get_csharp_project_name();
  359. #if defined(WINDOWS_ENABLED)
  360. String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll");
  361. #elif defined(MACOS_ENABLED) || defined(IOS_ENABLED)
  362. String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dylib");
  363. #elif defined(ANDROID_ENABLED)
  364. String native_aot_so_path = "lib" + assembly_name + ".so";
  365. #elif defined(UNIX_ENABLED)
  366. String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".so");
  367. #else
  368. #error "Platform not supported (yet?)"
  369. #endif
  370. Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle);
  371. if (err != OK) {
  372. return nullptr;
  373. }
  374. void *lib = r_aot_dll_handle;
  375. void *symbol = nullptr;
  376. err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol);
  377. ERR_FAIL_COND_V(err != OK, nullptr);
  378. return (godot_plugins_initialize_fn)symbol;
  379. }
  380. #endif
  381. #ifndef TOOLS_ENABLED
  382. #ifdef ANDROID_ENABLED
  383. MonoAssembly *load_assembly_from_pck(MonoAssemblyName *p_assembly_name, char **p_assemblies_path, void *p_user_data) {
  384. constexpr bool ref_only = false;
  385. const char *name = mono_assembly_name_get_name(p_assembly_name);
  386. const char *culture = mono_assembly_name_get_culture(p_assembly_name);
  387. String assembly_name;
  388. if (culture && strcmp(culture, "")) {
  389. assembly_name += culture;
  390. assembly_name += "/";
  391. }
  392. assembly_name += name;
  393. if (!assembly_name.ends_with(".dll")) {
  394. assembly_name += ".dll";
  395. }
  396. String path = GodotSharpDirs::get_api_assemblies_dir();
  397. path = path.path_join(assembly_name);
  398. print_verbose(".NET: Loading assembly '" + assembly_name + "' from '" + path + "'.");
  399. if (!FileAccess::exists(path)) {
  400. // We could not find the assembly, return null so another hook may find it.
  401. return nullptr;
  402. }
  403. Vector<uint8_t> data = FileAccess::get_file_as_bytes(path);
  404. ERR_FAIL_COND_V_MSG(data.is_empty(), nullptr, ".NET: Could not read assembly in '" + path + "'.");
  405. MonoImageOpenStatus status = MONO_IMAGE_OK;
  406. MonoImage *image = mono_image_open_from_data_with_name(
  407. reinterpret_cast<char *>(data.ptrw()), data.size(),
  408. /*need_copy*/ true,
  409. &status,
  410. ref_only,
  411. assembly_name.utf8().get_data());
  412. ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || image == nullptr, nullptr, ".NET: Failed to open assembly image.");
  413. status = MONO_IMAGE_OK;
  414. MonoAssembly *assembly = mono_assembly_load_from_full(
  415. image, assembly_name.utf8().get_data(),
  416. &status,
  417. ref_only);
  418. ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || assembly == nullptr, nullptr, ".NET: Failed to load assembly from image.");
  419. return assembly;
  420. }
  421. #endif
  422. godot_plugins_initialize_fn initialize_coreclr_and_godot_plugins(bool &r_runtime_initialized) {
  423. godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
  424. String assembly_name = Path::get_csharp_project_name();
  425. #ifdef ANDROID_ENABLED
  426. // Android requires installing a preload hook to load assemblies from inside the APK,
  427. // other platforms can find the assemblies with the default lookup.
  428. if (mono_install_assembly_preload_hook != nullptr) {
  429. mono_install_assembly_preload_hook(&load_assembly_from_pck, nullptr);
  430. }
  431. #endif
  432. void *coreclr_handle = nullptr;
  433. unsigned int domain_id = 0;
  434. int rc = coreclr_initialize(nullptr, nullptr, 0, nullptr, nullptr, &coreclr_handle, &domain_id);
  435. ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to initialize CoreCLR.");
  436. r_runtime_initialized = true;
  437. print_verbose(".NET: CoreCLR initialized");
  438. coreclr_create_delegate(coreclr_handle, domain_id,
  439. assembly_name.utf8().get_data(),
  440. "GodotPlugins.Game.Main",
  441. "InitializeFromGameProject",
  442. (void **)&godot_plugins_initialize);
  443. ERR_FAIL_NULL_V_MSG(godot_plugins_initialize, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");
  444. return godot_plugins_initialize;
  445. }
  446. #endif
  447. } // namespace
  448. bool GDMono::should_initialize() {
  449. #ifdef TOOLS_ENABLED
  450. // The editor always needs to initialize the .NET module for now.
  451. return true;
  452. #else
  453. return OS::get_singleton()->has_feature("dotnet");
  454. #endif
  455. }
  456. static bool _on_core_api_assembly_loaded() {
  457. if (!GDMonoCache::godot_api_cache_updated) {
  458. return false;
  459. }
  460. bool debug;
  461. #ifdef DEBUG_ENABLED
  462. debug = true;
  463. #else
  464. debug = false;
  465. #endif
  466. GDMonoCache::managed_callbacks.GD_OnCoreApiAssemblyLoaded(debug);
  467. return true;
  468. }
  469. void GDMono::initialize() {
  470. print_verbose(".NET: Initializing module...");
  471. _init_godot_api_hashes();
  472. godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
  473. #if !defined(IOS_ENABLED)
  474. // Check that the .NET assemblies directory exists before trying to use it.
  475. if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) {
  476. OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found"));
  477. ERR_FAIL_MSG(".NET: Assemblies not found");
  478. }
  479. #endif
  480. if (load_hostfxr(hostfxr_dll_handle)) {
  481. godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized);
  482. ERR_FAIL_NULL(godot_plugins_initialize);
  483. } else {
  484. #if !defined(TOOLS_ENABLED)
  485. if (load_coreclr(coreclr_dll_handle)) {
  486. godot_plugins_initialize = initialize_coreclr_and_godot_plugins(runtime_initialized);
  487. } else {
  488. godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle);
  489. if (godot_plugins_initialize != nullptr) {
  490. runtime_initialized = true;
  491. }
  492. }
  493. if (godot_plugins_initialize == nullptr) {
  494. ERR_FAIL_MSG(".NET: Failed to load hostfxr");
  495. }
  496. #else
  497. // Show a message box to the user to make the problem explicit (and explain a potential crash).
  498. OS::get_singleton()->alert(TTR("Unable to load .NET runtime, specifically hostfxr.\nAttempting to create/edit a project will lead to a crash.\n\nPlease install the .NET SDK 8.0 or later from https://get.dot.net and restart Godot."), TTR("Failed to load .NET runtime"));
  499. ERR_FAIL_MSG(".NET: Failed to load hostfxr");
  500. #endif
  501. }
  502. int32_t interop_funcs_size = 0;
  503. const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size);
  504. GDMonoCache::ManagedCallbacks managed_callbacks{};
  505. void *godot_dll_handle = nullptr;
  506. #if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(IOS_ENABLED)
  507. // Managed code can access it on its own on other platforms
  508. godot_dll_handle = dlopen(nullptr, RTLD_NOW);
  509. #endif
  510. #ifdef TOOLS_ENABLED
  511. gdmono::PluginCallbacks plugin_callbacks_res;
  512. bool init_ok = godot_plugins_initialize(godot_dll_handle,
  513. Engine::get_singleton()->is_editor_hint(),
  514. &plugin_callbacks_res, &managed_callbacks,
  515. interop_funcs, interop_funcs_size);
  516. ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
  517. plugin_callbacks = plugin_callbacks_res;
  518. #else
  519. bool init_ok = godot_plugins_initialize(godot_dll_handle, &managed_callbacks,
  520. interop_funcs, interop_funcs_size);
  521. ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
  522. #endif
  523. GDMonoCache::update_godot_api_cache(managed_callbacks);
  524. print_verbose(".NET: GodotPlugins initialized");
  525. _on_core_api_assembly_loaded();
  526. #ifdef TOOLS_ENABLED
  527. _try_load_project_assembly();
  528. #endif
  529. initialized = true;
  530. }
  531. #ifdef TOOLS_ENABLED
  532. void GDMono::_try_load_project_assembly() {
  533. if (Engine::get_singleton()->is_project_manager_hint()) {
  534. return;
  535. }
  536. // Load the project's main assembly. This doesn't necessarily need to succeed.
  537. // The game may not be using .NET at all, or if the project does use .NET and
  538. // we're running in the editor, it may just happen to be it wasn't built yet.
  539. if (!_load_project_assembly()) {
  540. if (OS::get_singleton()->is_stdout_verbose()) {
  541. print_error(".NET: Failed to load project assembly");
  542. }
  543. }
  544. }
  545. #endif
  546. void GDMono::_init_godot_api_hashes() {
  547. #ifdef DEBUG_METHODS_ENABLED
  548. get_api_core_hash();
  549. #ifdef TOOLS_ENABLED
  550. get_api_editor_hash();
  551. #endif // TOOLS_ENABLED
  552. #endif // DEBUG_METHODS_ENABLED
  553. }
  554. #ifdef TOOLS_ENABLED
  555. bool GDMono::_load_project_assembly() {
  556. String assembly_name = Path::get_csharp_project_name();
  557. String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
  558. .path_join(assembly_name + ".dll");
  559. assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
  560. if (!FileAccess::exists(assembly_path)) {
  561. return false;
  562. }
  563. String loaded_assembly_path;
  564. bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16().get_data(), &loaded_assembly_path);
  565. if (success) {
  566. project_assembly_path = loaded_assembly_path.simplify_path();
  567. project_assembly_modified_time = FileAccess::get_modified_time(loaded_assembly_path);
  568. }
  569. return success;
  570. }
  571. #endif
  572. #ifdef GD_MONO_HOT_RELOAD
  573. void GDMono::reload_failure() {
  574. if (++project_load_failure_count >= (int)GLOBAL_GET("dotnet/project/assembly_reload_attempts")) {
  575. // After reloading a project has failed n times in a row, update the path and modification time
  576. // to stop any further attempts at loading this assembly, which probably is never going to work anyways.
  577. project_load_failure_count = 0;
  578. ERR_PRINT_ED(".NET: Giving up on assembly reloading. Please restart the editor if unloading was failing.");
  579. String assembly_name = Path::get_csharp_project_name();
  580. String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir().path_join(assembly_name + ".dll");
  581. assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
  582. project_assembly_path = assembly_path.simplify_path();
  583. project_assembly_modified_time = FileAccess::get_modified_time(assembly_path);
  584. }
  585. }
  586. Error GDMono::reload_project_assemblies() {
  587. ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
  588. finalizing_scripts_domain = true;
  589. if (!get_plugin_callbacks().UnloadProjectPluginCallback()) {
  590. ERR_PRINT_ED(".NET: Failed to unload assemblies. Please check https://github.com/godotengine/godot/issues/78513 for more information.");
  591. reload_failure();
  592. return FAILED;
  593. }
  594. finalizing_scripts_domain = false;
  595. // Load the project's main assembly. Here, during hot-reloading, we do
  596. // consider failing to load the project's main assembly to be an error.
  597. if (!_load_project_assembly()) {
  598. ERR_PRINT_ED(".NET: Failed to load project assembly.");
  599. reload_failure();
  600. return ERR_CANT_OPEN;
  601. }
  602. if (project_load_failure_count > 0) {
  603. project_load_failure_count = 0;
  604. ERR_PRINT_ED(".NET: Assembly reloading succeeded after failures.");
  605. }
  606. return OK;
  607. }
  608. #endif
  609. GDMono::GDMono() {
  610. singleton = this;
  611. }
  612. GDMono::~GDMono() {
  613. finalizing_scripts_domain = true;
  614. if (hostfxr_dll_handle) {
  615. OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle);
  616. }
  617. if (coreclr_dll_handle) {
  618. OS::get_singleton()->close_dynamic_library(coreclr_dll_handle);
  619. }
  620. finalizing_scripts_domain = false;
  621. runtime_initialized = false;
  622. singleton = nullptr;
  623. }
  624. namespace MonoBind {
  625. GodotSharp *GodotSharp::singleton = nullptr;
  626. void GodotSharp::reload_assemblies(bool p_soft_reload) {
  627. #ifdef GD_MONO_HOT_RELOAD
  628. CRASH_COND(CSharpLanguage::get_singleton() == nullptr);
  629. // This method may be called more than once with `call_deferred`, so we need to check
  630. // again if reloading is needed to avoid reloading multiple times unnecessarily.
  631. if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
  632. CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload);
  633. }
  634. #endif
  635. }
  636. GodotSharp::GodotSharp() {
  637. singleton = this;
  638. }
  639. GodotSharp::~GodotSharp() {
  640. singleton = nullptr;
  641. }
  642. } // namespace MonoBind