diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 542835fb19e..5e4ae2fce77 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -32,69 +32,71 @@ #include +#include "core/io/file_access_pack.h" #include "core/os/os.h" +#include "core/project_settings.h" #include "../mono_gd/gd_mono.h" #include "../mono_gd/gd_mono_assembly.h" #include "../mono_gd/gd_mono_cache.h" +#include "../utils/macros.h" namespace GodotSharpExport { -String get_assemblyref_name(MonoImage *p_image, int index) { +struct AssemblyRefInfo { + String name; + uint16_t major; + uint16_t minor; + uint16_t build; + uint16_t revision; +}; + +AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) { const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF); uint32_t cols[MONO_ASSEMBLYREF_SIZE]; mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE); - return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])); + return { + String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])), + (uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION], + (uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION], + (uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER], + (uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER] + }; } Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector &p_search_dirs, Dictionary &r_assembly_dependencies) { MonoImage *image = p_assembly->get_image(); for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { - String ref_name = get_assemblyref_name(image, i); + AssemblyRefInfo ref_info = get_assemblyref_name(image, i); + + const String &ref_name = ref_info.name; if (r_assembly_dependencies.has(ref_name)) continue; GDMonoAssembly *ref_assembly = NULL; - String path; - bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe"); - for (int j = 0; j < p_search_dirs.size(); j++) { - const String &search_dir = p_search_dirs[j]; + { + MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A" + CRASH_COND(ref_aname == nullptr); + SCOPE_EXIT { + mono_assembly_name_free(ref_aname); + mono_free(ref_aname); + }; - if (has_extension) { - path = search_dir.plus_file(ref_name); - if (FileAccess::exists(path)) { - GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true); - if (ref_assembly != NULL) - break; - } - } else { - path = search_dir.plus_file(ref_name + ".dll"); - if (FileAccess::exists(path)) { - GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); - if (ref_assembly != NULL) - break; - } + mono_assembly_get_assemblyref(image, i, ref_aname); - path = search_dir.plus_file(ref_name + ".exe"); - if (FileAccess::exists(path)) { - GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); - if (ref_assembly != NULL) - break; - } + if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) { + ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); } + + r_assembly_dependencies[ref_name] = ref_assembly->get_path(); } - ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); - - // Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir. - r_assembly_dependencies[ref_name] = path; - Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies); ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); } @@ -113,6 +115,11 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, Vector search_dirs; GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir); + if (p_custom_bcl_dir.length()) { + // Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory. + r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path(); + } + for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) { String assembly_name = *key; String assembly_path = p_initial_assemblies[*key]; diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 4fbd74ba672..a3c04fe58d9 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -521,8 +521,8 @@ void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) { GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) { - if (p_name == "mscorlib") - return get_corlib_assembly(); + if (p_name == "mscorlib" && corlib_assembly) + return corlib_assembly; MonoDomain *domain = mono_domain_get(); uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0; @@ -532,7 +532,9 @@ GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) { bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) { +#ifdef DEBUG_ENABLED CRASH_COND(!r_assembly); +#endif MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); bool result = load_assembly(p_name, aname, r_assembly, p_refonly); @@ -544,26 +546,27 @@ bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bo bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) { +#ifdef DEBUG_ENABLED CRASH_COND(!r_assembly); +#endif + + return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs()); +} + +bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector &p_search_dirs) { + +#ifdef DEBUG_ENABLED + CRASH_COND(!r_assembly); +#endif print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); - MonoImageOpenStatus status = MONO_IMAGE_OK; - MonoAssembly *assembly = mono_assembly_load_full(p_aname, NULL, &status, p_refonly); + GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs); if (!assembly) return false; - ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false); - - uint32_t domain_id = mono_domain_get_id(mono_domain_get()); - - GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name); - - ERR_FAIL_COND_V(stored_assembly == NULL, false); - ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false); - - *r_assembly = *stored_assembly; + *r_assembly = assembly; print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index c8135c6ca49..8e54ff9bbcd 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -241,6 +241,7 @@ public: bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false); bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false); + bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector &p_search_dirs); bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false); Error finalize_and_unload_domain(MonoDomain *p_domain); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 3e55c136615..d87fc1e91be 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -33,6 +33,7 @@ #include #include +#include "core/io/file_access_pack.h" #include "core/list.h" #include "core/os/file_access.h" #include "core/os/os.h" @@ -99,7 +100,7 @@ void GDMonoAssembly::fill_search_dirs(Vector &r_search_dirs, const Strin // - The 'load' hook is called after the assembly has been loaded. Its job is to add the // assembly to the list of loaded assemblies so that the 'search' hook can look it up. -void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) { +void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) { String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); @@ -133,9 +134,7 @@ MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *an return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true); } -MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly) { - - (void)user_data; // UNUSED +MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) { String name = String::utf8(mono_assembly_name_get_name(aname)); bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); @@ -147,15 +146,13 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d return NULL; } -MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) { - - (void)user_data; // UNUSED +MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) { String name = String::utf8(mono_assembly_name_get_name(aname)); - return _load_assembly_search(name, search_dirs, refonly); + return _load_assembly_search(name, aname, refonly, search_dirs); } -MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector &p_search_dirs, bool p_refonly) { +MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector &p_search_dirs) { MonoAssembly *res = NULL; String path; @@ -168,21 +165,21 @@ MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const if (has_extension) { path = search_dir.plus_file(p_name); if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly); + res = _real_load_assembly_from(path, p_refonly, p_aname); if (res != NULL) return res; } } else { path = search_dir.plus_file(p_name + ".dll"); if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly); + res = _real_load_assembly_from(path, p_refonly, p_aname); if (res != NULL) return res; } path = search_dir.plus_file(p_name + ".exe"); if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly); + res = _real_load_assembly_from(path, p_refonly, p_aname); if (res != NULL) return res; } @@ -230,7 +227,7 @@ void GDMonoAssembly::initialize() { mono_install_assembly_load_hook(&assembly_load_hook, NULL); } -MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) { +MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) { Vector data = FileAccess::get_file_as_array(p_path); ERR_FAIL_COND_V_MSG(data.empty(), NULL, "Could read the assembly in the specified location"); @@ -255,7 +252,33 @@ MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, boo true, &status, p_refonly, image_filename.utf8()); - ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, NULL, "Failed to open assembly image from the loaded data"); + ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, NULL, "Failed to open assembly image from memory: '" + p_path + "'."); + + if (p_aname != nullptr) { + // Check assembly version + const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY); + + ERR_FAIL_NULL_V(table, nullptr); + + if (mono_table_info_get_rows(table)) { + uint32_t cols[MONO_ASSEMBLY_SIZE]; + mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE); + + // Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision. + uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION]; + uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION]; + + uint16_t required_minor; + uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr); + + if (required_major != 0) { + if (major != required_major && minor != required_minor) { + mono_image_close(image); + return nullptr; + } + } + } + } #ifdef DEBUG_ENABLED Vector pdb_data; @@ -283,7 +306,7 @@ no_pdb: MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_refonly); - ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, NULL, "Failed to load assembly for image"); + ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, nullptr, "Failed to load assembly for image"); if (need_manual_load_hook) { // For some reason if an assembly survived domain reloading (maybe because it's referenced somewhere else), @@ -425,6 +448,26 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) return match; } +GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector &p_search_dirs) { + + if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) + return GDMono::get_singleton()->get_corlib_assembly(); + + // We need to manually call the search hook in this case, as it won't be called in the next step + MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname); + + if (!assembly) { + assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs); + ERR_FAIL_NULL_V(assembly, nullptr); + } + + GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); + ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?"); + ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr); + + return loaded_asm; +} + GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) { if (p_name == "mscorlib" || p_name == "mscorlib.dll") diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 43c8225b744..7fc1817993f 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -93,8 +93,8 @@ class GDMonoAssembly { static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly); static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly); - static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly); - static MonoAssembly *_load_assembly_search(const String &p_name, const Vector &p_search_dirs, bool p_refonly); + static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname = nullptr); + static MonoAssembly *_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector &p_search_dirs); friend class GDMono; static void initialize(); @@ -120,7 +120,9 @@ public: static String find_assembly(const String &p_name); static void fill_search_dirs(Vector &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String()); + static const Vector &get_default_search_dirs() { return search_dirs; } + static GDMonoAssembly *load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector &p_search_dirs); static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly); diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index 70ce4b3bb93..86ab57c42a9 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -175,7 +175,7 @@ void GDMonoLog::initialize() { log_level_id = get_log_level_id(log_level.get_data()); if (log_file) { - OS::get_singleton()->print("Mono: Logfile is: %s\n", log_file_path.utf8().get_data()); + OS::get_singleton()->print("Mono: Log file is: '%s'\n", log_file_path.utf8().get_data()); mono_trace_set_log_handler(mono_log_callback, this); } else { OS::get_singleton()->printerr("Mono: No log file, using default log handler\n"); diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h index 754000dc143..4e245716e05 100644 --- a/modules/mono/utils/macros.h +++ b/modules/mono/utils/macros.h @@ -81,4 +81,25 @@ } while (true); #endif +namespace gdmono { + +template +struct ScopeExit { + ScopeExit(F p_exit_func) : + exit_func(p_exit_func) {} + ~ScopeExit() { exit_func(); } + F exit_func; +}; + +class ScopeExitAux { +public: + template + ScopeExit operator+(F p_exit_func) { return ScopeExit(p_exit_func); } +}; + +} // namespace gdmono + +#define SCOPE_EXIT \ + auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]() -> void + #endif // UTIL_MACROS_H