gd_mono_assembly.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. /*************************************************************************/
  2. /* gd_mono_assembly.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
  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_assembly.h"
  31. #include <mono/metadata/mono-debug.h>
  32. #include <mono/metadata/tokentype.h>
  33. #include "core/config/project_settings.h"
  34. #include "core/io/file_access_pack.h"
  35. #include "core/os/file_access.h"
  36. #include "core/os/os.h"
  37. #include "core/templates/list.h"
  38. #include "../godotsharp_dirs.h"
  39. #include "gd_mono_cache.h"
  40. #include "gd_mono_class.h"
  41. Vector<String> GDMonoAssembly::search_dirs;
  42. void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) {
  43. String framework_dir;
  44. if (!p_custom_bcl_dir.is_empty()) {
  45. framework_dir = p_custom_bcl_dir;
  46. } else if (mono_assembly_getrootdir()) {
  47. framework_dir = String::utf8(mono_assembly_getrootdir()).plus_file("mono").plus_file("4.5");
  48. }
  49. if (!framework_dir.is_empty()) {
  50. r_search_dirs.push_back(framework_dir);
  51. r_search_dirs.push_back(framework_dir.plus_file("Facades"));
  52. }
  53. #if !defined(TOOLS_ENABLED)
  54. String data_game_assemblies_dir = GodotSharpDirs::get_data_game_assemblies_dir();
  55. if (!data_game_assemblies_dir.is_empty()) {
  56. r_search_dirs.push_back(data_game_assemblies_dir);
  57. }
  58. #endif
  59. if (p_custom_config.length()) {
  60. r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(p_custom_config));
  61. } else {
  62. r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir());
  63. }
  64. if (p_custom_config.is_empty()) {
  65. r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir());
  66. } else {
  67. String api_config = p_custom_config == "ExportRelease" ? "Release" : "Debug";
  68. r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config));
  69. }
  70. r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir());
  71. r_search_dirs.push_back(OS::get_singleton()->get_resource_dir());
  72. r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir());
  73. #ifdef TOOLS_ENABLED
  74. r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir());
  75. // For GodotTools to find the api assemblies
  76. r_search_dirs.push_back(GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"));
  77. #endif
  78. }
  79. // This is how these assembly loading hooks work:
  80. //
  81. // - The 'search' hook checks if the assembly has already been loaded, to avoid loading again.
  82. // - The 'preload' hook does the actual loading and is only called if the
  83. // 'search' hook didn't find the assembly in the list of loaded assemblies.
  84. // - The 'load' hook is called after the assembly has been loaded. Its job is to add the
  85. // assembly to the list of loaded assemblies so that the 'search' hook can look it up.
  86. void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) {
  87. String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
  88. MonoImage *image = mono_assembly_get_image(assembly);
  89. GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, image, assembly));
  90. #ifdef GD_MONO_HOT_RELOAD
  91. String path = String::utf8(mono_image_get_filename(image));
  92. if (FileAccess::exists(path)) {
  93. gdassembly->modified_time = FileAccess::get_modified_time(path);
  94. }
  95. #endif
  96. MonoDomain *domain = mono_domain_get();
  97. GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly);
  98. }
  99. MonoAssembly *GDMonoAssembly::assembly_search_hook(MonoAssemblyName *aname, void *user_data) {
  100. return GDMonoAssembly::_search_hook(aname, user_data, false);
  101. }
  102. MonoAssembly *GDMonoAssembly::assembly_refonly_search_hook(MonoAssemblyName *aname, void *user_data) {
  103. return GDMonoAssembly::_search_hook(aname, user_data, true);
  104. }
  105. MonoAssembly *GDMonoAssembly::assembly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data) {
  106. return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, false);
  107. }
  108. MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data) {
  109. return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true);
  110. }
  111. MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) {
  112. String name = String::utf8(mono_assembly_name_get_name(aname));
  113. bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
  114. GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
  115. if (loaded_asm) {
  116. return loaded_asm->get_assembly();
  117. }
  118. return nullptr;
  119. }
  120. MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) {
  121. String name = String::utf8(mono_assembly_name_get_name(aname));
  122. return _load_assembly_search(name, aname, refonly, search_dirs);
  123. }
  124. MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
  125. MonoAssembly *res = nullptr;
  126. String path;
  127. bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe");
  128. for (int i = 0; i < p_search_dirs.size(); i++) {
  129. const String &search_dir = p_search_dirs[i];
  130. if (has_extension) {
  131. path = search_dir.plus_file(p_name);
  132. if (FileAccess::exists(path)) {
  133. res = _real_load_assembly_from(path, p_refonly, p_aname);
  134. if (res != nullptr) {
  135. return res;
  136. }
  137. }
  138. } else {
  139. path = search_dir.plus_file(p_name + ".dll");
  140. if (FileAccess::exists(path)) {
  141. res = _real_load_assembly_from(path, p_refonly, p_aname);
  142. if (res != nullptr) {
  143. return res;
  144. }
  145. }
  146. path = search_dir.plus_file(p_name + ".exe");
  147. if (FileAccess::exists(path)) {
  148. res = _real_load_assembly_from(path, p_refonly, p_aname);
  149. if (res != nullptr) {
  150. return res;
  151. }
  152. }
  153. }
  154. }
  155. return nullptr;
  156. }
  157. String GDMonoAssembly::find_assembly(const String &p_name) {
  158. String path;
  159. bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe");
  160. for (int i = 0; i < search_dirs.size(); i++) {
  161. const String &search_dir = search_dirs[i];
  162. if (has_extension) {
  163. path = search_dir.plus_file(p_name);
  164. if (FileAccess::exists(path)) {
  165. return path;
  166. }
  167. } else {
  168. path = search_dir.plus_file(p_name + ".dll");
  169. if (FileAccess::exists(path)) {
  170. return path;
  171. }
  172. path = search_dir.plus_file(p_name + ".exe");
  173. if (FileAccess::exists(path)) {
  174. return path;
  175. }
  176. }
  177. }
  178. return String();
  179. }
  180. void GDMonoAssembly::initialize() {
  181. fill_search_dirs(search_dirs);
  182. mono_install_assembly_search_hook(&assembly_search_hook, nullptr);
  183. mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, nullptr);
  184. mono_install_assembly_preload_hook(&assembly_preload_hook, nullptr);
  185. mono_install_assembly_refonly_preload_hook(&assembly_refonly_preload_hook, nullptr);
  186. mono_install_assembly_load_hook(&assembly_load_hook, nullptr);
  187. }
  188. MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) {
  189. Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
  190. ERR_FAIL_COND_V_MSG(data.is_empty(), nullptr, "Could read the assembly in the specified location");
  191. String image_filename;
  192. #ifdef ANDROID_ENABLED
  193. if (p_path.begins_with("res://")) {
  194. image_filename = p_path.substr(6, p_path.length());
  195. } else {
  196. image_filename = ProjectSettings::get_singleton()->globalize_path(p_path);
  197. }
  198. #else
  199. // FIXME: globalize_path does not work on exported games
  200. image_filename = ProjectSettings::get_singleton()->globalize_path(p_path);
  201. #endif
  202. MonoImageOpenStatus status = MONO_IMAGE_OK;
  203. MonoImage *image = mono_image_open_from_data_with_name(
  204. (char *)&data[0], data.size(),
  205. true, &status, p_refonly,
  206. image_filename.utf8());
  207. ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from memory: '" + p_path + "'.");
  208. if (p_aname != nullptr) {
  209. // Check assembly version
  210. const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY);
  211. ERR_FAIL_NULL_V(table, nullptr);
  212. if (mono_table_info_get_rows(table)) {
  213. uint32_t cols[MONO_ASSEMBLY_SIZE];
  214. mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE);
  215. // Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision.
  216. uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION];
  217. uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION];
  218. uint16_t required_minor;
  219. uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr);
  220. if (required_major != 0) {
  221. if (major != required_major && minor != required_minor) {
  222. mono_image_close(image);
  223. return nullptr;
  224. }
  225. }
  226. }
  227. }
  228. #ifdef DEBUG_ENABLED
  229. Vector<uint8_t> pdb_data;
  230. String pdb_path(p_path + ".pdb");
  231. if (!FileAccess::exists(pdb_path)) {
  232. pdb_path = p_path.get_basename() + ".pdb"; // without .dll
  233. if (!FileAccess::exists(pdb_path)) {
  234. goto no_pdb;
  235. }
  236. }
  237. pdb_data = FileAccess::get_file_as_array(pdb_path);
  238. // mono_debug_close_image doesn't seem to be needed
  239. mono_debug_open_image_from_memory(image, &pdb_data[0], pdb_data.size());
  240. no_pdb:
  241. #endif
  242. bool need_manual_load_hook = mono_image_get_assembly(image) != nullptr; // Re-using an existing image with an assembly loaded
  243. status = MONO_IMAGE_OK;
  244. MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_refonly);
  245. ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, nullptr, "Failed to load assembly for image");
  246. if (need_manual_load_hook) {
  247. // For some reason if an assembly survived domain reloading (maybe because it's referenced somewhere else),
  248. // the mono internal search hook don't detect it, yet mono_image_open_from_data_with_name re-uses the image
  249. // and assembly, and mono_assembly_load_from_full doesn't call the load hook. We need to call it manually.
  250. String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
  251. bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
  252. GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
  253. if (!loaded_asm) {
  254. assembly_load_hook(assembly, nullptr);
  255. }
  256. }
  257. // Decrement refcount which was previously incremented by mono_image_open_from_data_with_name
  258. mono_image_close(image);
  259. return assembly;
  260. }
  261. void GDMonoAssembly::unload() {
  262. ERR_FAIL_NULL(image); // Should not be called if already unloaded
  263. for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) {
  264. memdelete(E->value());
  265. }
  266. cached_classes.clear();
  267. cached_raw.clear();
  268. assembly = nullptr;
  269. image = nullptr;
  270. }
  271. String GDMonoAssembly::get_path() const {
  272. return String::utf8(mono_image_get_filename(image));
  273. }
  274. GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) {
  275. ERR_FAIL_NULL_V(image, nullptr);
  276. ClassKey key(p_namespace, p_name);
  277. GDMonoClass **match = cached_classes.getptr(key);
  278. if (match) {
  279. return *match;
  280. }
  281. MonoClass *mono_class = mono_class_from_name(image, String(p_namespace).utf8(), String(p_name).utf8());
  282. if (!mono_class) {
  283. return nullptr;
  284. }
  285. GDMonoClass *wrapped_class = memnew(GDMonoClass(p_namespace, p_name, mono_class, this));
  286. cached_classes[key] = wrapped_class;
  287. cached_raw[mono_class] = wrapped_class;
  288. return wrapped_class;
  289. }
  290. GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
  291. ERR_FAIL_NULL_V(image, nullptr);
  292. Map<MonoClass *, GDMonoClass *>::Element *match = cached_raw.find(p_mono_class);
  293. if (match) {
  294. return match->value();
  295. }
  296. StringName namespace_name = String::utf8(mono_class_get_namespace(p_mono_class));
  297. StringName class_name = String::utf8(mono_class_get_name(p_mono_class));
  298. GDMonoClass *wrapped_class = memnew(GDMonoClass(namespace_name, class_name, p_mono_class, this));
  299. cached_classes[ClassKey(namespace_name, class_name)] = wrapped_class;
  300. cached_raw[p_mono_class] = wrapped_class;
  301. return wrapped_class;
  302. }
  303. GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) {
  304. GDMonoClass *match = nullptr;
  305. if (gdobject_class_cache_updated) {
  306. Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class);
  307. if (result) {
  308. match = result->get();
  309. }
  310. } else {
  311. List<GDMonoClass *> nested_classes;
  312. int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);
  313. for (int i = 1; i < rows; i++) {
  314. MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);
  315. if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) {
  316. continue;
  317. }
  318. GDMonoClass *current = get_class(mono_class);
  319. if (!current) {
  320. continue;
  321. }
  322. nested_classes.push_back(current);
  323. if (!match && current->get_name() == p_class) {
  324. match = current;
  325. }
  326. while (!nested_classes.is_empty()) {
  327. GDMonoClass *current_nested = nested_classes.front()->get();
  328. nested_classes.pop_front();
  329. void *iter = nullptr;
  330. while (true) {
  331. MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter);
  332. if (!raw_nested) {
  333. break;
  334. }
  335. GDMonoClass *nested_class = get_class(raw_nested);
  336. if (nested_class) {
  337. gdobject_class_cache.insert(nested_class->get_name(), nested_class);
  338. nested_classes.push_back(nested_class);
  339. }
  340. }
  341. }
  342. gdobject_class_cache.insert(current->get_name(), current);
  343. }
  344. gdobject_class_cache_updated = true;
  345. }
  346. return match;
  347. }
  348. GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
  349. if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) {
  350. return GDMono::get_singleton()->get_corlib_assembly();
  351. }
  352. // We need to manually call the search hook in this case, as it won't be called in the next step
  353. MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname);
  354. if (!assembly) {
  355. assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs);
  356. if (!assembly) {
  357. return nullptr;
  358. }
  359. }
  360. GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
  361. ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?");
  362. ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr);
  363. return loaded_asm;
  364. }
  365. GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
  366. if (p_name == "mscorlib" || p_name == "mscorlib.dll") {
  367. return GDMono::get_singleton()->get_corlib_assembly();
  368. }
  369. // We need to manually call the search hook in this case, as it won't be called in the next step
  370. MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
  371. MonoAssembly *assembly = mono_assembly_invoke_search_hook(aname);
  372. mono_assembly_name_free(aname);
  373. mono_free(aname);
  374. if (!assembly) {
  375. assembly = _real_load_assembly_from(p_path, p_refonly);
  376. if (!assembly) {
  377. return nullptr;
  378. }
  379. }
  380. GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
  381. ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?");
  382. return loaded_asm;
  383. }
  384. GDMonoAssembly::~GDMonoAssembly() {
  385. if (image) {
  386. unload();
  387. }
  388. }