Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/mono/mono_gd/gd_mono.cpp
10278 views
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
31
#include "gd_mono.h"
32
33
#include "../csharp_script.h"
34
#include "../glue/runtime_interop.h"
35
#include "../godotsharp_dirs.h"
36
#include "../thirdparty/coreclr_delegates.h"
37
#include "../thirdparty/hostfxr.h"
38
#include "../utils/path_utils.h"
39
#include "gd_mono_cache.h"
40
41
#ifdef TOOLS_ENABLED
42
#include "../editor/hostfxr_resolver.h"
43
#include "../editor/semver.h"
44
#endif
45
46
#include "core/config/project_settings.h"
47
#include "core/debugger/engine_debugger.h"
48
#include "core/io/dir_access.h"
49
#include "core/io/file_access.h"
50
#include "core/os/os.h"
51
#include "core/os/thread.h"
52
53
#ifdef UNIX_ENABLED
54
#include <dlfcn.h>
55
#endif
56
57
#ifndef TOOLS_ENABLED
58
#ifdef ANDROID_ENABLED
59
#include "../thirdparty/mono_delegates.h"
60
#endif
61
#endif
62
63
GDMono *GDMono::singleton = nullptr;
64
65
namespace {
66
hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = nullptr;
67
hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = nullptr;
68
hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr;
69
hostfxr_close_fn hostfxr_close = nullptr;
70
71
#ifndef TOOLS_ENABLED
72
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);
73
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);
74
75
coreclr_create_delegate_fn coreclr_create_delegate = nullptr;
76
coreclr_initialize_fn coreclr_initialize = nullptr;
77
78
#ifdef ANDROID_ENABLED
79
mono_install_assembly_preload_hook_fn mono_install_assembly_preload_hook = nullptr;
80
mono_assembly_name_get_name_fn mono_assembly_name_get_name = nullptr;
81
mono_assembly_name_get_culture_fn mono_assembly_name_get_culture = nullptr;
82
mono_image_open_from_data_with_name_fn mono_image_open_from_data_with_name = nullptr;
83
mono_assembly_load_from_full_fn mono_assembly_load_from_full = nullptr;
84
#endif
85
#endif
86
87
#ifdef _WIN32
88
static_assert(sizeof(char_t) == sizeof(char16_t));
89
using HostFxrCharString = Char16String;
90
#define HOSTFXR_STR(m_str) L##m_str
91
#else
92
static_assert(sizeof(char_t) == sizeof(char));
93
using HostFxrCharString = CharString;
94
#define HOSTFXR_STR(m_str) m_str
95
#endif
96
97
HostFxrCharString str_to_hostfxr(const String &p_str) {
98
#ifdef _WIN32
99
return p_str.utf16();
100
#else
101
return p_str.utf8();
102
#endif
103
}
104
105
const char_t *get_data(const HostFxrCharString &p_char_str) {
106
return (const char_t *)p_char_str.get_data();
107
}
108
109
#ifdef TOOLS_ENABLED
110
bool try_get_dotnet_root_from_command_line(String &r_dotnet_root) {
111
String pipe;
112
List<String> args;
113
args.push_back("--list-sdks");
114
115
int exitcode;
116
Error err = OS::get_singleton()->execute("dotnet", args, &pipe, &exitcode, true);
117
118
ERR_FAIL_COND_V_MSG(err != OK, false, String(".NET failed to get list of installed SDKs. Error: ") + error_names[err]);
119
ERR_FAIL_COND_V_MSG(exitcode != 0, false, pipe);
120
121
Vector<String> sdks = pipe.strip_edges().replace("\r\n", "\n").split("\n", false);
122
123
godotsharp::SemVerParser sem_ver_parser;
124
125
godotsharp::SemVer latest_sdk_version;
126
String latest_sdk_path;
127
128
for (const String &sdk : sdks) {
129
// The format of the SDK lines is:
130
// 8.0.401 [/usr/share/dotnet/sdk]
131
String version_string = sdk.get_slice(" ", 0);
132
String path = sdk.get_slice(" ", 1);
133
path = path.substr(1, path.length() - 2);
134
135
godotsharp::SemVer version;
136
if (!sem_ver_parser.parse(version_string, version)) {
137
WARN_PRINT("Unable to parse .NET SDK version '" + version_string + "'.");
138
continue;
139
}
140
141
if (!DirAccess::exists(path)) {
142
WARN_PRINT("Found .NET SDK version '" + version_string + "' with invalid path '" + path + "'.");
143
continue;
144
}
145
146
if (version > latest_sdk_version) {
147
latest_sdk_version = version;
148
latest_sdk_path = path;
149
}
150
}
151
152
if (!latest_sdk_path.is_empty()) {
153
print_verbose("Found .NET SDK at " + latest_sdk_path);
154
// The `dotnet_root` is the parent directory.
155
r_dotnet_root = latest_sdk_path.path_join("..").simplify_path();
156
return true;
157
}
158
159
return false;
160
}
161
#endif
162
163
String find_hostfxr() {
164
#ifdef TOOLS_ENABLED
165
String dotnet_root;
166
String fxr_path;
167
if (godotsharp::hostfxr_resolver::try_get_path(dotnet_root, fxr_path)) {
168
return fxr_path;
169
}
170
171
// hostfxr_resolver doesn't look for dotnet in `PATH`. If it fails, we try to use the dotnet
172
// executable in `PATH` to find the `dotnet_root` and get the `hostfxr_path` from there.
173
if (try_get_dotnet_root_from_command_line(dotnet_root)) {
174
if (godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(dotnet_root, fxr_path)) {
175
return fxr_path;
176
}
177
}
178
179
ERR_PRINT(String() + ".NET: One of the dependent libraries is missing. " +
180
"Typically when the `hostfxr`, `hostpolicy` or `coreclr` dynamic " +
181
"libraries are not present in the expected locations.");
182
183
return String();
184
#else
185
186
#if defined(WINDOWS_ENABLED)
187
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
188
.path_join("hostfxr.dll");
189
#elif defined(MACOS_ENABLED)
190
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
191
.path_join("libhostfxr.dylib");
192
#elif defined(UNIX_ENABLED)
193
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
194
.path_join("libhostfxr.so");
195
#else
196
#error "Platform not supported (yet?)"
197
#endif
198
199
if (FileAccess::exists(probe_path)) {
200
return probe_path;
201
}
202
203
return String();
204
205
#endif
206
}
207
208
#ifndef TOOLS_ENABLED
209
String find_monosgen() {
210
#if defined(ANDROID_ENABLED)
211
// Android includes all native libraries in the libs directory of the APK
212
// so we assume it exists and use only the name to dlopen it.
213
return "libmonosgen-2.0.so";
214
#else
215
#if defined(WINDOWS_ENABLED)
216
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
217
.path_join("monosgen-2.0.dll");
218
#elif defined(MACOS_ENABLED)
219
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
220
.path_join("libmonosgen-2.0.dylib");
221
#elif defined(UNIX_ENABLED)
222
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
223
.path_join("libmonosgen-2.0.so");
224
#else
225
#error "Platform not supported (yet?)"
226
#endif
227
228
if (FileAccess::exists(probe_path)) {
229
return probe_path;
230
}
231
232
return String();
233
#endif
234
}
235
236
String find_coreclr() {
237
#if defined(WINDOWS_ENABLED)
238
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
239
.path_join("coreclr.dll");
240
#elif defined(MACOS_ENABLED)
241
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
242
.path_join("libcoreclr.dylib");
243
#elif defined(UNIX_ENABLED)
244
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
245
.path_join("libcoreclr.so");
246
#else
247
#error "Platform not supported (yet?)"
248
#endif
249
250
if (FileAccess::exists(probe_path)) {
251
return probe_path;
252
}
253
254
return String();
255
}
256
#endif
257
258
bool load_hostfxr(void *&r_hostfxr_dll_handle) {
259
String hostfxr_path = find_hostfxr();
260
261
if (hostfxr_path.is_empty()) {
262
return false;
263
}
264
265
print_verbose("Found hostfxr: " + hostfxr_path);
266
267
Error err = OS::get_singleton()->open_dynamic_library(hostfxr_path, r_hostfxr_dll_handle);
268
269
if (err != OK) {
270
return false;
271
}
272
273
void *lib = r_hostfxr_dll_handle;
274
275
void *symbol = nullptr;
276
277
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_dotnet_command_line", symbol);
278
ERR_FAIL_COND_V(err != OK, false);
279
hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)symbol;
280
281
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_runtime_config", symbol);
282
ERR_FAIL_COND_V(err != OK, false);
283
hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)symbol;
284
285
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_get_runtime_delegate", symbol);
286
ERR_FAIL_COND_V(err != OK, false);
287
hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)symbol;
288
289
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_close", symbol);
290
ERR_FAIL_COND_V(err != OK, false);
291
hostfxr_close = (hostfxr_close_fn)symbol;
292
293
return (hostfxr_initialize_for_runtime_config &&
294
hostfxr_get_runtime_delegate &&
295
hostfxr_close);
296
}
297
298
#ifndef TOOLS_ENABLED
299
bool load_coreclr(void *&r_coreclr_dll_handle) {
300
String coreclr_path = find_coreclr();
301
302
bool is_monovm = false;
303
if (coreclr_path.is_empty()) {
304
// Fallback to MonoVM (should have the same API as CoreCLR).
305
coreclr_path = find_monosgen();
306
is_monovm = true;
307
}
308
309
if (coreclr_path.is_empty()) {
310
return false;
311
}
312
313
const String coreclr_name = is_monovm ? "monosgen" : "coreclr";
314
print_verbose("Found " + coreclr_name + ": " + coreclr_path);
315
316
Error err = OS::get_singleton()->open_dynamic_library(coreclr_path, r_coreclr_dll_handle);
317
318
if (err != OK) {
319
return false;
320
}
321
322
void *lib = r_coreclr_dll_handle;
323
324
void *symbol = nullptr;
325
326
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_initialize", symbol);
327
ERR_FAIL_COND_V(err != OK, false);
328
coreclr_initialize = (coreclr_initialize_fn)symbol;
329
330
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_create_delegate", symbol);
331
ERR_FAIL_COND_V(err != OK, false);
332
coreclr_create_delegate = (coreclr_create_delegate_fn)symbol;
333
334
#ifdef ANDROID_ENABLED
335
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_install_assembly_preload_hook", symbol);
336
ERR_FAIL_COND_V(err != OK, false);
337
mono_install_assembly_preload_hook = (mono_install_assembly_preload_hook_fn)symbol;
338
339
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_name_get_name", symbol);
340
ERR_FAIL_COND_V(err != OK, false);
341
mono_assembly_name_get_name = (mono_assembly_name_get_name_fn)symbol;
342
343
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_name_get_culture", symbol);
344
ERR_FAIL_COND_V(err != OK, false);
345
mono_assembly_name_get_culture = (mono_assembly_name_get_culture_fn)symbol;
346
347
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_image_open_from_data_with_name", symbol);
348
ERR_FAIL_COND_V(err != OK, false);
349
mono_image_open_from_data_with_name = (mono_image_open_from_data_with_name_fn)symbol;
350
351
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "mono_assembly_load_from_full", symbol);
352
ERR_FAIL_COND_V(err != OK, false);
353
mono_assembly_load_from_full = (mono_assembly_load_from_full_fn)symbol;
354
#endif
355
356
return (coreclr_initialize &&
357
coreclr_create_delegate);
358
}
359
#endif
360
361
#ifdef TOOLS_ENABLED
362
load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) {
363
hostfxr_handle cxt = nullptr;
364
int rc = hostfxr_initialize_for_runtime_config(p_config_path, nullptr, &cxt);
365
if (rc != 0 || cxt == nullptr) {
366
hostfxr_close(cxt);
367
ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_runtime_config failed with code: " + itos(rc));
368
}
369
370
void *load_assembly_and_get_function_pointer = nullptr;
371
372
rc = hostfxr_get_runtime_delegate(cxt,
373
hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer);
374
if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) {
375
ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc));
376
}
377
378
hostfxr_close(cxt);
379
380
return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
381
}
382
#else
383
load_assembly_and_get_function_pointer_fn initialize_hostfxr_self_contained(
384
const char_t *p_main_assembly_path) {
385
hostfxr_handle cxt = nullptr;
386
387
List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
388
389
List<HostFxrCharString> argv_store;
390
Vector<const char_t *> argv;
391
argv.resize(cmdline_args.size() + 1);
392
393
argv.write[0] = p_main_assembly_path;
394
395
int i = 1;
396
for (const String &E : cmdline_args) {
397
HostFxrCharString &stored = argv_store.push_back(str_to_hostfxr(E))->get();
398
argv.write[i] = get_data(stored);
399
i++;
400
}
401
402
int rc = hostfxr_initialize_for_dotnet_command_line(argv.size(), argv.ptrw(), nullptr, &cxt);
403
if (rc != 0 || cxt == nullptr) {
404
hostfxr_close(cxt);
405
ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_dotnet_command_line failed with code: " + itos(rc));
406
}
407
408
void *load_assembly_and_get_function_pointer = nullptr;
409
410
rc = hostfxr_get_runtime_delegate(cxt,
411
hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer);
412
if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) {
413
ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc));
414
}
415
416
hostfxr_close(cxt);
417
418
return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
419
}
420
#endif
421
422
#ifdef TOOLS_ENABLED
423
using godot_plugins_initialize_fn = bool (*)(void *, bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *, const void **, int32_t);
424
#else
425
using godot_plugins_initialize_fn = bool (*)(void *, GDMonoCache::ManagedCallbacks *, const void **, int32_t);
426
#endif
427
428
#ifdef TOOLS_ENABLED
429
godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
430
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
431
432
HostFxrCharString godot_plugins_path = str_to_hostfxr(
433
GodotSharpDirs::get_api_assemblies_dir().path_join("GodotPlugins.dll"));
434
435
HostFxrCharString config_path = str_to_hostfxr(
436
GodotSharpDirs::get_api_assemblies_dir().path_join("GodotPlugins.runtimeconfig.json"));
437
438
load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer =
439
initialize_hostfxr_for_config(get_data(config_path));
440
441
if (load_assembly_and_get_function_pointer == nullptr) {
442
// Show a message box to the user to make the problem explicit (and explain a potential crash).
443
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"));
444
ERR_FAIL_V_MSG(nullptr, ".NET: Failed to load compatible .NET runtime");
445
}
446
447
r_runtime_initialized = true;
448
449
print_verbose(".NET: hostfxr initialized");
450
451
int rc = load_assembly_and_get_function_pointer(get_data(godot_plugins_path),
452
HOSTFXR_STR("GodotPlugins.Main, GodotPlugins"),
453
HOSTFXR_STR("InitializeFromEngine"),
454
UNMANAGEDCALLERSONLY_METHOD,
455
nullptr,
456
(void **)&godot_plugins_initialize);
457
ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");
458
459
return godot_plugins_initialize;
460
}
461
#else
462
godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
463
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
464
465
String assembly_name = Path::get_csharp_project_name();
466
467
HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir()
468
.path_join(assembly_name + ".dll"));
469
470
load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer =
471
initialize_hostfxr_self_contained(get_data(assembly_path));
472
ERR_FAIL_NULL_V(load_assembly_and_get_function_pointer, nullptr);
473
474
r_runtime_initialized = true;
475
476
print_verbose(".NET: hostfxr initialized");
477
478
int rc = load_assembly_and_get_function_pointer(get_data(assembly_path),
479
get_data(str_to_hostfxr("GodotPlugins.Game.Main, " + assembly_name)),
480
HOSTFXR_STR("InitializeFromGameProject"),
481
UNMANAGEDCALLERSONLY_METHOD,
482
nullptr,
483
(void **)&godot_plugins_initialize);
484
ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");
485
486
return godot_plugins_initialize;
487
}
488
489
godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) {
490
String assembly_name = Path::get_csharp_project_name();
491
492
#if defined(WINDOWS_ENABLED)
493
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll");
494
#elif defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
495
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dylib");
496
#elif defined(ANDROID_ENABLED)
497
String native_aot_so_path = "lib" + assembly_name + ".so";
498
#elif defined(UNIX_ENABLED)
499
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".so");
500
#else
501
#error "Platform not supported (yet?)"
502
#endif
503
504
Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle);
505
506
if (err != OK) {
507
return nullptr;
508
}
509
510
void *lib = r_aot_dll_handle;
511
512
void *symbol = nullptr;
513
514
err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol);
515
ERR_FAIL_COND_V(err != OK, nullptr);
516
return (godot_plugins_initialize_fn)symbol;
517
}
518
#endif
519
520
#ifndef TOOLS_ENABLED
521
#ifdef ANDROID_ENABLED
522
MonoAssembly *load_assembly_from_pck(MonoAssemblyName *p_assembly_name, char **p_assemblies_path, void *p_user_data) {
523
constexpr bool ref_only = false;
524
525
const char *name = mono_assembly_name_get_name(p_assembly_name);
526
const char *culture = mono_assembly_name_get_culture(p_assembly_name);
527
528
String assembly_name;
529
if (culture && strcmp(culture, "")) {
530
assembly_name += culture;
531
assembly_name += "/";
532
}
533
assembly_name += name;
534
if (!assembly_name.ends_with(".dll")) {
535
assembly_name += ".dll";
536
}
537
538
String path = GodotSharpDirs::get_api_assemblies_dir();
539
path = path.path_join(assembly_name);
540
541
print_verbose(".NET: Loading assembly '" + assembly_name + "' from '" + path + "'.");
542
543
if (!FileAccess::exists(path)) {
544
// We could not find the assembly, return null so another hook may find it.
545
return nullptr;
546
}
547
548
Vector<uint8_t> data = FileAccess::get_file_as_bytes(path);
549
ERR_FAIL_COND_V_MSG(data.is_empty(), nullptr, ".NET: Could not read assembly in '" + path + "'.");
550
551
MonoImageOpenStatus status = MONO_IMAGE_OK;
552
553
MonoImage *image = mono_image_open_from_data_with_name(
554
reinterpret_cast<char *>(data.ptrw()), data.size(),
555
/*need_copy*/ true,
556
&status,
557
ref_only,
558
assembly_name.utf8().get_data());
559
560
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || image == nullptr, nullptr, ".NET: Failed to open assembly image.");
561
562
status = MONO_IMAGE_OK;
563
564
MonoAssembly *assembly = mono_assembly_load_from_full(
565
image, assembly_name.utf8().get_data(),
566
&status,
567
ref_only);
568
569
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || assembly == nullptr, nullptr, ".NET: Failed to load assembly from image.");
570
571
return assembly;
572
}
573
#endif
574
575
godot_plugins_initialize_fn initialize_coreclr_and_godot_plugins(bool &r_runtime_initialized) {
576
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
577
578
String assembly_name = Path::get_csharp_project_name();
579
580
#ifdef ANDROID_ENABLED
581
// Android requires installing a preload hook to load assemblies from inside the APK,
582
// other platforms can find the assemblies with the default lookup.
583
if (mono_install_assembly_preload_hook != nullptr) {
584
mono_install_assembly_preload_hook(&load_assembly_from_pck, nullptr);
585
}
586
#endif
587
588
void *coreclr_handle = nullptr;
589
unsigned int domain_id = 0;
590
int rc = coreclr_initialize(nullptr, nullptr, 0, nullptr, nullptr, &coreclr_handle, &domain_id);
591
ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to initialize CoreCLR.");
592
593
r_runtime_initialized = true;
594
595
print_verbose(".NET: CoreCLR initialized");
596
597
coreclr_create_delegate(coreclr_handle, domain_id,
598
assembly_name.utf8().get_data(),
599
"GodotPlugins.Game.Main",
600
"InitializeFromGameProject",
601
(void **)&godot_plugins_initialize);
602
ERR_FAIL_NULL_V_MSG(godot_plugins_initialize, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");
603
604
return godot_plugins_initialize;
605
}
606
#endif
607
608
} // namespace
609
610
bool GDMono::should_initialize() {
611
#ifdef TOOLS_ENABLED
612
// The editor always needs to initialize the .NET module for now.
613
return true;
614
#else
615
return OS::get_singleton()->has_feature("dotnet");
616
#endif
617
}
618
619
static bool _on_core_api_assembly_loaded() {
620
if (!GDMonoCache::godot_api_cache_updated) {
621
return false;
622
}
623
624
bool debug;
625
#ifdef DEBUG_ENABLED
626
debug = true;
627
#else
628
debug = false;
629
#endif // DEBUG_ENABLED
630
631
GDMonoCache::managed_callbacks.GD_OnCoreApiAssemblyLoaded(debug);
632
633
return true;
634
}
635
636
void GDMono::initialize() {
637
print_verbose(".NET: Initializing module...");
638
639
_init_godot_api_hashes();
640
641
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
642
643
#if !defined(APPLE_EMBEDDED_ENABLED)
644
// Check that the .NET assemblies directory exists before trying to use it.
645
if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) {
646
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"));
647
ERR_FAIL_MSG(".NET: Assemblies not found");
648
}
649
#endif
650
651
if (load_hostfxr(hostfxr_dll_handle)) {
652
godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized);
653
ERR_FAIL_NULL(godot_plugins_initialize);
654
} else {
655
#if !defined(TOOLS_ENABLED)
656
if (load_coreclr(coreclr_dll_handle)) {
657
godot_plugins_initialize = initialize_coreclr_and_godot_plugins(runtime_initialized);
658
} else {
659
void *dll_handle = nullptr;
660
godot_plugins_initialize = try_load_native_aot_library(dll_handle);
661
if (godot_plugins_initialize != nullptr) {
662
runtime_initialized = true;
663
}
664
}
665
666
if (godot_plugins_initialize == nullptr) {
667
ERR_FAIL_MSG(".NET: Failed to load hostfxr");
668
}
669
#else
670
671
// Show a message box to the user to make the problem explicit (and explain a potential crash).
672
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"));
673
ERR_FAIL_MSG(".NET: Failed to load hostfxr");
674
#endif
675
}
676
677
int32_t interop_funcs_size = 0;
678
const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size);
679
680
GDMonoCache::ManagedCallbacks managed_callbacks{};
681
682
void *godot_dll_handle = nullptr;
683
684
#if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(APPLE_EMBEDDED_ENABLED)
685
// Managed code can access it on its own on other platforms
686
godot_dll_handle = dlopen(nullptr, RTLD_NOW);
687
#endif
688
689
#ifdef TOOLS_ENABLED
690
gdmono::PluginCallbacks plugin_callbacks_res;
691
bool init_ok = godot_plugins_initialize(godot_dll_handle,
692
Engine::get_singleton()->is_editor_hint(),
693
&plugin_callbacks_res, &managed_callbacks,
694
interop_funcs, interop_funcs_size);
695
ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
696
697
plugin_callbacks = plugin_callbacks_res;
698
#else
699
bool init_ok = godot_plugins_initialize(godot_dll_handle, &managed_callbacks,
700
interop_funcs, interop_funcs_size);
701
ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
702
#endif
703
704
GDMonoCache::update_godot_api_cache(managed_callbacks);
705
706
print_verbose(".NET: GodotPlugins initialized");
707
708
_on_core_api_assembly_loaded();
709
710
#ifdef TOOLS_ENABLED
711
_try_load_project_assembly();
712
#endif
713
714
initialized = true;
715
}
716
717
#ifdef TOOLS_ENABLED
718
void GDMono::_try_load_project_assembly() {
719
if (Engine::get_singleton()->is_project_manager_hint()) {
720
return;
721
}
722
723
// Load the project's main assembly. This doesn't necessarily need to succeed.
724
// The game may not be using .NET at all, or if the project does use .NET and
725
// we're running in the editor, it may just happen to be it wasn't built yet.
726
if (!_load_project_assembly()) {
727
if (OS::get_singleton()->is_stdout_verbose()) {
728
print_error(".NET: Failed to load project assembly");
729
}
730
}
731
}
732
#endif
733
734
void GDMono::_init_godot_api_hashes() {
735
#ifdef DEBUG_ENABLED
736
get_api_core_hash();
737
738
#ifdef TOOLS_ENABLED
739
get_api_editor_hash();
740
#endif // TOOLS_ENABLED
741
#endif // DEBUG_ENABLED
742
}
743
744
#ifdef TOOLS_ENABLED
745
bool GDMono::_load_project_assembly() {
746
String assembly_name = Path::get_csharp_project_name();
747
748
String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
749
.path_join(assembly_name + ".dll");
750
assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
751
752
if (!FileAccess::exists(assembly_path)) {
753
return false;
754
}
755
756
String loaded_assembly_path;
757
bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16().get_data(), &loaded_assembly_path);
758
759
if (success) {
760
project_assembly_path = loaded_assembly_path.simplify_path();
761
project_assembly_modified_time = FileAccess::get_modified_time(loaded_assembly_path);
762
}
763
764
return success;
765
}
766
#endif
767
768
#ifdef GD_MONO_HOT_RELOAD
769
void GDMono::reload_failure() {
770
if (++project_load_failure_count >= (int)GLOBAL_GET("dotnet/project/assembly_reload_attempts")) {
771
// After reloading a project has failed n times in a row, update the path and modification time
772
// to stop any further attempts at loading this assembly, which probably is never going to work anyways.
773
project_load_failure_count = 0;
774
775
ERR_PRINT_ED(".NET: Giving up on assembly reloading. Please restart the editor if unloading was failing.");
776
777
String assembly_name = Path::get_csharp_project_name();
778
String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir().path_join(assembly_name + ".dll");
779
assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
780
project_assembly_path = assembly_path.simplify_path();
781
project_assembly_modified_time = FileAccess::get_modified_time(assembly_path);
782
}
783
}
784
785
Error GDMono::reload_project_assemblies() {
786
ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
787
788
finalizing_scripts_domain = true;
789
790
if (!get_plugin_callbacks().UnloadProjectPluginCallback()) {
791
ERR_PRINT_ED(".NET: Failed to unload assemblies. Please check https://github.com/godotengine/godot/issues/78513 for more information.");
792
reload_failure();
793
return FAILED;
794
}
795
796
finalizing_scripts_domain = false;
797
798
// Load the project's main assembly. Here, during hot-reloading, we do
799
// consider failing to load the project's main assembly to be an error.
800
if (!_load_project_assembly()) {
801
ERR_PRINT_ED(".NET: Failed to load project assembly.");
802
reload_failure();
803
return ERR_CANT_OPEN;
804
}
805
806
if (project_load_failure_count > 0) {
807
project_load_failure_count = 0;
808
ERR_PRINT_ED(".NET: Assembly reloading succeeded after failures.");
809
}
810
811
return OK;
812
}
813
#endif
814
815
GDMono::GDMono() {
816
singleton = this;
817
}
818
819
GDMono::~GDMono() {
820
finalizing_scripts_domain = true;
821
822
if (hostfxr_dll_handle) {
823
OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle);
824
}
825
if (coreclr_dll_handle) {
826
OS::get_singleton()->close_dynamic_library(coreclr_dll_handle);
827
}
828
829
finalizing_scripts_domain = false;
830
runtime_initialized = false;
831
832
singleton = nullptr;
833
}
834
835
namespace MonoBind {
836
837
GodotSharp *GodotSharp::singleton = nullptr;
838
839
void GodotSharp::reload_assemblies(bool p_soft_reload) {
840
#ifdef GD_MONO_HOT_RELOAD
841
CRASH_COND(CSharpLanguage::get_singleton() == nullptr);
842
// This method may be called more than once with `call_deferred`, so we need to check
843
// again if reloading is needed to avoid reloading multiple times unnecessarily.
844
if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
845
CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload);
846
}
847
#endif
848
}
849
850
GodotSharp::GodotSharp() {
851
singleton = this;
852
}
853
854
GodotSharp::~GodotSharp() {
855
singleton = nullptr;
856
}
857
858
} // namespace MonoBind
859
860