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