1 /*************************************************************************/
2 /* gdnative.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 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
31 #include "gdnative.h"
32
33 #include "core/global_constants.h"
34 #include "core/io/file_access_encrypted.h"
35 #include "core/os/file_access.h"
36 #include "core/os/os.h"
37 #include "core/project_settings.h"
38
39 #include "scene/main/scene_tree.h"
40
41 static const String init_symbol = "gdnative_init";
42 static const String terminate_symbol = "gdnative_terminate";
43 static const String default_symbol_prefix = "godot_";
44 static const bool default_singleton = false;
45 static const bool default_load_once = true;
46 static const bool default_reloadable = true;
47
48 // Defined in gdnative_api_struct.gen.cpp
49 extern const godot_gdnative_core_api_struct api_struct;
50
51 Map<String, Vector<Ref<GDNative> > > GDNativeLibrary::loaded_libraries;
52
GDNativeLibrary()53 GDNativeLibrary::GDNativeLibrary() {
54 config_file.instance();
55
56 symbol_prefix = default_symbol_prefix;
57 load_once = default_load_once;
58 singleton = default_singleton;
59 reloadable = default_reloadable;
60 }
61
~GDNativeLibrary()62 GDNativeLibrary::~GDNativeLibrary() {
63 }
64
_set(const StringName & p_name,const Variant & p_property)65 bool GDNativeLibrary::_set(const StringName &p_name, const Variant &p_property) {
66
67 String name = p_name;
68
69 if (name.begins_with("entry/")) {
70 String key = name.substr(6, name.length() - 6);
71
72 config_file->set_value("entry", key, p_property);
73
74 set_config_file(config_file);
75
76 return true;
77 }
78
79 if (name.begins_with("dependency/")) {
80 String key = name.substr(11, name.length() - 11);
81
82 config_file->set_value("dependencies", key, p_property);
83
84 set_config_file(config_file);
85
86 return true;
87 }
88
89 return false;
90 }
91
_get(const StringName & p_name,Variant & r_property) const92 bool GDNativeLibrary::_get(const StringName &p_name, Variant &r_property) const {
93 String name = p_name;
94
95 if (name.begins_with("entry/")) {
96 String key = name.substr(6, name.length() - 6);
97
98 r_property = config_file->get_value("entry", key);
99
100 return true;
101 }
102
103 if (name.begins_with("dependency/")) {
104 String key = name.substr(11, name.length() - 11);
105
106 r_property = config_file->get_value("dependencies", key);
107
108 return true;
109 }
110
111 return false;
112 }
113
_get_property_list(List<PropertyInfo> * p_list) const114 void GDNativeLibrary::_get_property_list(List<PropertyInfo> *p_list) const {
115 // set entries
116 List<String> entry_key_list;
117
118 if (config_file->has_section("entry"))
119 config_file->get_section_keys("entry", &entry_key_list);
120
121 for (List<String>::Element *E = entry_key_list.front(); E; E = E->next()) {
122 String key = E->get();
123
124 PropertyInfo prop;
125
126 prop.type = Variant::STRING;
127 prop.name = "entry/" + key;
128
129 p_list->push_back(prop);
130 }
131
132 // set dependencies
133 List<String> dependency_key_list;
134
135 if (config_file->has_section("dependencies"))
136 config_file->get_section_keys("dependencies", &dependency_key_list);
137
138 for (List<String>::Element *E = dependency_key_list.front(); E; E = E->next()) {
139 String key = E->get();
140
141 PropertyInfo prop;
142
143 prop.type = Variant::STRING;
144 prop.name = "dependency/" + key;
145
146 p_list->push_back(prop);
147 }
148 }
149
set_config_file(Ref<ConfigFile> p_config_file)150 void GDNativeLibrary::set_config_file(Ref<ConfigFile> p_config_file) {
151
152 set_singleton(p_config_file->get_value("general", "singleton", default_singleton));
153 set_load_once(p_config_file->get_value("general", "load_once", default_load_once));
154 set_symbol_prefix(p_config_file->get_value("general", "symbol_prefix", default_symbol_prefix));
155 set_reloadable(p_config_file->get_value("general", "reloadable", default_reloadable));
156
157 String entry_lib_path;
158 {
159
160 List<String> entry_keys;
161
162 if (p_config_file->has_section("entry"))
163 p_config_file->get_section_keys("entry", &entry_keys);
164
165 for (List<String>::Element *E = entry_keys.front(); E; E = E->next()) {
166 String key = E->get();
167
168 Vector<String> tags = key.split(".");
169
170 bool skip = false;
171 for (int i = 0; i < tags.size(); i++) {
172 bool has_feature = OS::get_singleton()->has_feature(tags[i]);
173
174 if (!has_feature) {
175 skip = true;
176 break;
177 }
178 }
179
180 if (skip) {
181 continue;
182 }
183
184 entry_lib_path = p_config_file->get_value("entry", key);
185 break;
186 }
187 }
188
189 Vector<String> dependency_paths;
190 {
191
192 List<String> dependency_keys;
193
194 if (p_config_file->has_section("dependencies"))
195 p_config_file->get_section_keys("dependencies", &dependency_keys);
196
197 for (List<String>::Element *E = dependency_keys.front(); E; E = E->next()) {
198 String key = E->get();
199
200 Vector<String> tags = key.split(".");
201
202 bool skip = false;
203 for (int i = 0; i < tags.size(); i++) {
204 bool has_feature = OS::get_singleton()->has_feature(tags[i]);
205
206 if (!has_feature) {
207 skip = true;
208 break;
209 }
210 }
211
212 if (skip) {
213 continue;
214 }
215
216 dependency_paths = p_config_file->get_value("dependencies", key);
217 break;
218 }
219 }
220
221 current_library_path = entry_lib_path;
222 current_dependencies = dependency_paths;
223 }
224
_bind_methods()225 void GDNativeLibrary::_bind_methods() {
226 ClassDB::bind_method(D_METHOD("get_config_file"), &GDNativeLibrary::get_config_file);
227 ClassDB::bind_method(D_METHOD("set_config_file", "config_file"), &GDNativeLibrary::set_config_file);
228
229 ClassDB::bind_method(D_METHOD("get_current_library_path"), &GDNativeLibrary::get_current_library_path);
230 ClassDB::bind_method(D_METHOD("get_current_dependencies"), &GDNativeLibrary::get_current_dependencies);
231
232 ClassDB::bind_method(D_METHOD("should_load_once"), &GDNativeLibrary::should_load_once);
233 ClassDB::bind_method(D_METHOD("is_singleton"), &GDNativeLibrary::is_singleton);
234 ClassDB::bind_method(D_METHOD("get_symbol_prefix"), &GDNativeLibrary::get_symbol_prefix);
235 ClassDB::bind_method(D_METHOD("is_reloadable"), &GDNativeLibrary::is_reloadable);
236
237 ClassDB::bind_method(D_METHOD("set_load_once", "load_once"), &GDNativeLibrary::set_load_once);
238 ClassDB::bind_method(D_METHOD("set_singleton", "singleton"), &GDNativeLibrary::set_singleton);
239 ClassDB::bind_method(D_METHOD("set_symbol_prefix", "symbol_prefix"), &GDNativeLibrary::set_symbol_prefix);
240 ClassDB::bind_method(D_METHOD("set_reloadable", "reloadable"), &GDNativeLibrary::set_reloadable);
241
242 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "config_file", PROPERTY_HINT_RESOURCE_TYPE, "ConfigFile", 0), "set_config_file", "get_config_file");
243
244 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "load_once"), "set_load_once", "should_load_once");
245 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "singleton"), "set_singleton", "is_singleton");
246 ADD_PROPERTY(PropertyInfo(Variant::STRING, "symbol_prefix"), "set_symbol_prefix", "get_symbol_prefix");
247 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reloadable"), "set_reloadable", "is_reloadable");
248 }
249
GDNative()250 GDNative::GDNative() {
251 native_handle = NULL;
252 initialized = false;
253 }
254
~GDNative()255 GDNative::~GDNative() {
256 }
257
_bind_methods()258 void GDNative::_bind_methods() {
259 ClassDB::bind_method(D_METHOD("set_library", "library"), &GDNative::set_library);
260 ClassDB::bind_method(D_METHOD("get_library"), &GDNative::get_library);
261
262 ClassDB::bind_method(D_METHOD("initialize"), &GDNative::initialize);
263 ClassDB::bind_method(D_METHOD("terminate"), &GDNative::terminate);
264
265 ClassDB::bind_method(D_METHOD("call_native", "calling_type", "procedure_name", "arguments"), &GDNative::call_native);
266
267 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library");
268 }
269
set_library(Ref<GDNativeLibrary> p_library)270 void GDNative::set_library(Ref<GDNativeLibrary> p_library) {
271 ERR_FAIL_COND_MSG(library.is_valid(), "Tried to change library of GDNative when it is already set.");
272 library = p_library;
273 }
274
get_library() const275 Ref<GDNativeLibrary> GDNative::get_library() const {
276 return library;
277 }
278
279 extern "C" void _gdnative_report_version_mismatch(const godot_object *p_library, const char *p_ext, godot_gdnative_api_version p_want, godot_gdnative_api_version p_have);
280 extern "C" void _gdnative_report_loading_error(const godot_object *p_library, const char *p_what);
281
initialize()282 bool GDNative::initialize() {
283 if (library.is_null()) {
284 ERR_PRINT("No library set, can't initialize GDNative object");
285 return false;
286 }
287
288 String lib_path = library->get_current_library_path();
289 if (lib_path.empty()) {
290 ERR_PRINT("No library set for this platform");
291 return false;
292 }
293 #ifdef IPHONE_ENABLED
294 // On iOS we use static linking by default.
295 String path = "";
296
297 // On iOS dylibs is not allowed, but can be replaced with .framework or .xcframework.
298 // If they are used, we can run dlopen on them.
299 // They should be located under Frameworks directory, so we need to replace library path.
300 if (!lib_path.ends_with(".a")) {
301 path = ProjectSettings::get_singleton()->globalize_path(lib_path);
302
303 if (!FileAccess::exists(path)) {
304 String lib_name = lib_path.get_basename().get_file();
305 String framework_path_format = "Frameworks/$name.framework/$name";
306
307 Dictionary format_dict;
308 format_dict["name"] = lib_name;
309 String framework_path = framework_path_format.format(format_dict, "$_");
310
311 path = OS::get_singleton()->get_executable_path().get_base_dir().plus_file(framework_path);
312 }
313 }
314 #elif defined(ANDROID_ENABLED)
315 // On Android dynamic libraries are located separately from resource assets,
316 // we should pass library name to dlopen(). The library name is flattened
317 // during export.
318 String path = lib_path.get_file();
319 #elif defined(UWP_ENABLED)
320 // On UWP we use a relative path from the app
321 String path = lib_path.replace("res://", "");
322 #elif defined(OSX_ENABLED)
323 // On OSX the exported libraries are located under the Frameworks directory.
324 // So we need to replace the library path.
325 String path = ProjectSettings::get_singleton()->globalize_path(lib_path);
326 if (!FileAccess::exists(path)) {
327 path = OS::get_singleton()->get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(lib_path.get_file());
328 }
329 #else
330 String path = ProjectSettings::get_singleton()->globalize_path(lib_path);
331 #endif
332
333 if (library->should_load_once()) {
334 if (GDNativeLibrary::loaded_libraries.has(lib_path)) {
335 // already loaded. Don't load again.
336 // copy some of the stuff instead
337 this->native_handle = GDNativeLibrary::loaded_libraries[lib_path][0]->native_handle;
338 initialized = true;
339 return true;
340 }
341 }
342
343 Error err = OS::get_singleton()->open_dynamic_library(path, native_handle, true);
344 if (err != OK) {
345 return false;
346 }
347
348 void *library_init;
349
350 // we cheat here a little bit. you saw nothing
351 initialized = true;
352
353 err = get_symbol(library->get_symbol_prefix() + init_symbol, library_init, false);
354
355 initialized = false;
356
357 if (err || !library_init) {
358 OS::get_singleton()->close_dynamic_library(native_handle);
359 native_handle = NULL;
360 ERR_PRINTS("Failed to obtain " + library->get_symbol_prefix() + "gdnative_init symbol");
361 return false;
362 }
363
364 godot_gdnative_init_fn library_init_fpointer;
365 library_init_fpointer = (godot_gdnative_init_fn)library_init;
366
367 static uint64_t core_api_hash = 0;
368 static uint64_t editor_api_hash = 0;
369 static uint64_t no_api_hash = 0;
370
371 if (!(core_api_hash || editor_api_hash || no_api_hash)) {
372 core_api_hash = ClassDB::get_api_hash(ClassDB::API_CORE);
373 editor_api_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR);
374 no_api_hash = ClassDB::get_api_hash(ClassDB::API_NONE);
375 }
376
377 godot_gdnative_init_options options;
378
379 options.api_struct = &api_struct;
380 options.in_editor = Engine::get_singleton()->is_editor_hint();
381 options.core_api_hash = core_api_hash;
382 options.editor_api_hash = editor_api_hash;
383 options.no_api_hash = no_api_hash;
384 options.report_version_mismatch = &_gdnative_report_version_mismatch;
385 options.report_loading_error = &_gdnative_report_loading_error;
386 options.gd_native_library = (godot_object *)(get_library().ptr());
387 options.active_library_path = (godot_string *)&path;
388
389 library_init_fpointer(&options);
390
391 initialized = true;
392
393 if (library->should_load_once() && !GDNativeLibrary::loaded_libraries.has(lib_path)) {
394 Vector<Ref<GDNative> > gdnatives;
395 gdnatives.resize(1);
396 gdnatives.write[0] = Ref<GDNative>(this);
397 GDNativeLibrary::loaded_libraries.insert(lib_path, gdnatives);
398 }
399
400 return true;
401 }
402
terminate()403 bool GDNative::terminate() {
404
405 if (!initialized) {
406 ERR_PRINT("No valid library handle, can't terminate GDNative object");
407 return false;
408 }
409
410 if (library->should_load_once()) {
411 Vector<Ref<GDNative> > *gdnatives = &GDNativeLibrary::loaded_libraries[library->get_current_library_path()];
412 if (gdnatives->size() > 1) {
413 // there are other GDNative's still using this library, so we actually don't terminate
414 gdnatives->erase(Ref<GDNative>(this));
415 initialized = false;
416 return true;
417 } else if (gdnatives->size() == 1) {
418 // we're the last one, terminate!
419 gdnatives->clear();
420 // whew this looks scary, but all it does is remove the entry completely
421 GDNativeLibrary::loaded_libraries.erase(GDNativeLibrary::loaded_libraries.find(library->get_current_library_path()));
422 }
423 }
424
425 void *library_terminate;
426 Error error = get_symbol(library->get_symbol_prefix() + terminate_symbol, library_terminate);
427 if (error || !library_terminate) {
428 OS::get_singleton()->close_dynamic_library(native_handle);
429 native_handle = NULL;
430 initialized = false;
431 return true;
432 }
433
434 godot_gdnative_terminate_fn library_terminate_pointer;
435 library_terminate_pointer = (godot_gdnative_terminate_fn)library_terminate;
436
437 godot_gdnative_terminate_options options;
438 options.in_editor = Engine::get_singleton()->is_editor_hint();
439
440 library_terminate_pointer(&options);
441
442 initialized = false;
443
444 // GDNativeScriptLanguage::get_singleton()->initialized_libraries.erase(p_native_lib->path);
445
446 OS::get_singleton()->close_dynamic_library(native_handle);
447 native_handle = NULL;
448
449 return true;
450 }
451
is_initialized() const452 bool GDNative::is_initialized() const {
453 return initialized;
454 }
455
register_native_call_type(StringName p_call_type,native_call_cb p_callback)456 void GDNativeCallRegistry::register_native_call_type(StringName p_call_type, native_call_cb p_callback) {
457 native_calls.insert(p_call_type, p_callback);
458 }
459
get_native_call_types()460 Vector<StringName> GDNativeCallRegistry::get_native_call_types() {
461 Vector<StringName> call_types;
462 call_types.resize(native_calls.size());
463
464 size_t idx = 0;
465 for (Map<StringName, native_call_cb>::Element *E = native_calls.front(); E; E = E->next(), idx++) {
466 call_types.write[idx] = E->key();
467 }
468
469 return call_types;
470 }
471
call_native(StringName p_native_call_type,StringName p_procedure_name,Array p_arguments)472 Variant GDNative::call_native(StringName p_native_call_type, StringName p_procedure_name, Array p_arguments) {
473
474 Map<StringName, native_call_cb>::Element *E = GDNativeCallRegistry::singleton->native_calls.find(p_native_call_type);
475 if (!E) {
476 ERR_PRINT((String("No handler for native call type \"" + p_native_call_type) + "\" found").utf8().get_data());
477 return Variant();
478 }
479
480 void *procedure_handle;
481
482 Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(
483 native_handle,
484 p_procedure_name,
485 procedure_handle);
486
487 if (err != OK || procedure_handle == NULL) {
488 return Variant();
489 }
490
491 godot_variant result = E->get()(procedure_handle, (godot_array *)&p_arguments);
492
493 Variant res = *(Variant *)&result;
494 godot_variant_destroy(&result);
495 return res;
496 }
497
get_symbol(StringName p_procedure_name,void * & r_handle,bool p_optional) const498 Error GDNative::get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional) const {
499
500 if (!initialized) {
501 ERR_PRINT("No valid library handle, can't get symbol from GDNative object");
502 return ERR_CANT_OPEN;
503 }
504
505 Error result = OS::get_singleton()->get_dynamic_library_symbol_handle(
506 native_handle,
507 p_procedure_name,
508 r_handle,
509 p_optional);
510
511 return result;
512 }
513
load(const String & p_path,const String & p_original_path,Error * r_error)514 RES GDNativeLibraryResourceLoader::load(const String &p_path, const String &p_original_path, Error *r_error) {
515 Ref<GDNativeLibrary> lib;
516 lib.instance();
517
518 Ref<ConfigFile> config = lib->get_config_file();
519
520 Error err = config->load(p_path);
521
522 if (r_error) {
523 *r_error = err;
524 }
525
526 lib->set_config_file(config);
527
528 return lib;
529 }
530
get_recognized_extensions(List<String> * p_extensions) const531 void GDNativeLibraryResourceLoader::get_recognized_extensions(List<String> *p_extensions) const {
532 p_extensions->push_back("gdnlib");
533 }
534
handles_type(const String & p_type) const535 bool GDNativeLibraryResourceLoader::handles_type(const String &p_type) const {
536 return p_type == "GDNativeLibrary";
537 }
538
get_resource_type(const String & p_path) const539 String GDNativeLibraryResourceLoader::get_resource_type(const String &p_path) const {
540 String el = p_path.get_extension().to_lower();
541 if (el == "gdnlib")
542 return "GDNativeLibrary";
543 return "";
544 }
545
save(const String & p_path,const RES & p_resource,uint32_t p_flags)546 Error GDNativeLibraryResourceSaver::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
547
548 Ref<GDNativeLibrary> lib = p_resource;
549
550 if (lib.is_null()) {
551 return ERR_INVALID_DATA;
552 }
553
554 Ref<ConfigFile> config = lib->get_config_file();
555
556 config->set_value("general", "singleton", lib->is_singleton());
557 config->set_value("general", "load_once", lib->should_load_once());
558 config->set_value("general", "symbol_prefix", lib->get_symbol_prefix());
559 config->set_value("general", "reloadable", lib->is_reloadable());
560
561 return config->save(p_path);
562 }
563
recognize(const RES & p_resource) const564 bool GDNativeLibraryResourceSaver::recognize(const RES &p_resource) const {
565 return Object::cast_to<GDNativeLibrary>(*p_resource) != NULL;
566 }
567
get_recognized_extensions(const RES & p_resource,List<String> * p_extensions) const568 void GDNativeLibraryResourceSaver::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const {
569 if (Object::cast_to<GDNativeLibrary>(*p_resource) != NULL) {
570 p_extensions->push_back("gdnlib");
571 }
572 }
573