Browse Source

Merge pull request #11783 from endragor/ios-export-frameworks

Allow exporting third-party iOS Frameworks
Rémi Verschelde 7 năm trước cách đây
mục cha
commit
71a3e71b61

+ 97 - 0
core/os/dir_access.cpp

@@ -98,6 +98,7 @@ static Error _erase_recursive(DirAccess *da) {
 			err = _erase_recursive(da);
 			if (err) {
 				print_line("err recurso " + E->get());
+				da->change_dir("..");
 				return err;
 			}
 			err = da->change_dir("..");
@@ -340,6 +341,102 @@ Error DirAccess::copy(String p_from, String p_to, int chmod_flags) {
 	return err;
 }
 
+// Changes dir for the current scope, returning back to the original dir
+// when scope exits
+class DirChanger {
+	DirAccess *da;
+	String original_dir;
+
+public:
+	DirChanger(DirAccess *p_da, String p_dir) {
+		da = p_da;
+		original_dir = p_da->get_current_dir();
+		p_da->change_dir(p_dir);
+	}
+
+	~DirChanger() {
+		da->change_dir(original_dir);
+	}
+};
+
+Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags) {
+	List<String> dirs;
+
+	String curdir = get_current_dir();
+	list_dir_begin();
+	String n = get_next();
+	while (n != String()) {
+
+		if (n != "." && n != "..") {
+
+			if (current_is_dir())
+				dirs.push_back(n);
+			else {
+				String rel_path = n;
+				if (!n.is_rel_path()) {
+					list_dir_end();
+					return ERR_BUG;
+				}
+				Error err = copy(get_current_dir() + "/" + n, p_to + rel_path, p_chmod_flags);
+				if (err) {
+					list_dir_end();
+					return err;
+				}
+			}
+		}
+
+		n = get_next();
+	}
+
+	list_dir_end();
+
+	for (List<String>::Element *E = dirs.front(); E; E = E->next()) {
+		String rel_path = E->get();
+		String target_dir = p_to + rel_path;
+		if (!p_target_da->dir_exists(target_dir)) {
+			Error err = p_target_da->make_dir(target_dir);
+			ERR_FAIL_COND_V(err, err);
+		}
+
+		Error err = change_dir(E->get());
+		ERR_FAIL_COND_V(err, err);
+		err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags);
+		if (err) {
+			change_dir("..");
+			ERR_PRINT("Failed to copy recursively");
+			return err;
+		}
+		err = change_dir("..");
+		if (err) {
+			ERR_PRINT("Failed to go back");
+			return err;
+		}
+	}
+
+	return OK;
+}
+
+Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags) {
+	ERR_FAIL_COND_V(!dir_exists(p_from), ERR_FILE_NOT_FOUND);
+
+	DirAccess *target_da = DirAccess::create_for_path(p_to);
+	ERR_FAIL_COND_V(!target_da, ERR_CANT_CREATE);
+
+	if (!target_da->dir_exists(p_to)) {
+		Error err = target_da->make_dir_recursive(p_to);
+		if (err) {
+			memdelete(target_da);
+		}
+		ERR_FAIL_COND_V(err, err);
+	}
+
+	DirChanger dir_changer(this, p_from);
+	Error err = _copy_dir(target_da, p_to + "/", p_chmod_flags);
+	memdelete(target_da);
+
+	return err;
+}
+
 bool DirAccess::exists(String p_dir) {
 
 	DirAccess *da = DirAccess::create_for_path(p_dir);

+ 4 - 0
core/os/dir_access.h

@@ -52,6 +52,9 @@ public:
 private:
 	AccessType _access_type;
 	static CreateFunc create_func[ACCESS_MAX]; ///< set this to instance a filesystem object
+
+	Error _copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags);
+
 protected:
 	String _get_root_path() const;
 	String _get_root_string() const;
@@ -89,6 +92,7 @@ public:
 	static bool exists(String p_dir);
 	virtual size_t get_space_left() = 0;
 
+	Error copy_dir(String p_from, String p_to, int chmod_flags = -1);
 	virtual Error copy(String p_from, String p_to, int chmod_flags = -1);
 	virtual Error rename(String p_from, String p_to) = 0;
 	virtual Error remove(String p_name) = 0;

+ 27 - 0
core/os/os.cpp

@@ -548,6 +548,33 @@ bool OS::has_feature(const String &p_feature) {
 	if (sizeof(void *) == 4 && p_feature == "32") {
 		return true;
 	}
+#if defined(__x86_64) || defined(__x86_64__)
+	if (p_feature == "x86_64") {
+		return true;
+	}
+#elif (defined(__i386) || defined(__i386__))
+	if (p_feature == "x86") {
+		return true;
+	}
+#elif defined(__aarch64__)
+	if (p_feature == "arm64") {
+		return true;
+	}
+#elif defined(__arm__)
+#if defined(__ARM_ARCH_7A__)
+	if (p_feature == "armv7a" || p_feature == "armv7") {
+		return true;
+	}
+#endif
+#if defined(__ARM_ARCH_7S__)
+	if (p_feature == "armv7s" || p_feature == "armv7") {
+		return true;
+	}
+#endif
+	if (p_feature == "arm") {
+		return true;
+	}
+#endif
 
 	if (_check_internal_feature_support(p_feature))
 		return true;

+ 11 - 0
core/ustring.cpp

@@ -862,6 +862,17 @@ Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allo
 	return ret;
 }
 
+String String::join(Vector<String> parts) {
+	String ret;
+	for (int i = 0; i < parts.size(); ++i) {
+		if (i > 0) {
+			ret += *this;
+		}
+		ret += parts[i];
+	}
+	return ret;
+}
+
 CharType String::char_uppercase(CharType p_char) {
 
 	return _find_upper(p_char);

+ 2 - 0
core/ustring.h

@@ -169,6 +169,8 @@ public:
 	Vector<int> split_ints(const String &p_splitter, bool p_allow_empty = true) const;
 	Vector<int> split_ints_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const;
 
+	String join(Vector<String> parts);
+
 	static CharType char_uppercase(CharType p_char);
 	static CharType char_lowercase(CharType p_char);
 	String to_upper() const;

+ 100 - 22
editor/editor_export.cpp

@@ -470,9 +470,52 @@ void EditorExportPlugin::add_file(const String &p_path, const Vector<uint8_t> &p
 	extra_files.push_back(ef);
 }
 
-void EditorExportPlugin::add_shared_object(const String &p_path) {
+void EditorExportPlugin::add_shared_object(const String &p_path, const Vector<String> &tags) {
 
-	shared_objects.push_back(p_path);
+	shared_objects.push_back(SharedObject(p_path, tags));
+}
+
+void EditorExportPlugin::add_ios_framework(const String &p_path) {
+	ios_frameworks.push_back(p_path);
+}
+
+Vector<String> EditorExportPlugin::get_ios_frameworks() const {
+	return ios_frameworks;
+}
+
+void EditorExportPlugin::add_ios_plist_content(const String &p_plist_content) {
+	ios_plist_content += p_plist_content + "\n";
+}
+
+String EditorExportPlugin::get_ios_plist_content() const {
+	return ios_plist_content;
+}
+
+void EditorExportPlugin::add_ios_linker_flags(const String &p_flags) {
+	if (ios_linker_flags.length() > 0) {
+		ios_linker_flags += ' ';
+	}
+	ios_linker_flags += p_flags;
+}
+
+String EditorExportPlugin::get_ios_linker_flags() const {
+	return ios_linker_flags;
+}
+
+void EditorExportPlugin::add_ios_bundle_file(const String &p_path) {
+	ios_bundle_files.push_back(p_path);
+}
+
+Vector<String> EditorExportPlugin::get_ios_bundle_files() const {
+	return ios_bundle_files;
+}
+
+void EditorExportPlugin::add_ios_cpp_code(const String &p_code) {
+	ios_cpp_code += p_code;
+}
+
+String EditorExportPlugin::get_ios_cpp_code() const {
+	return ios_cpp_code;
 }
 
 void EditorExportPlugin::_export_file_script(const String &p_path, const String &p_type, const PoolVector<String> &p_features) {
@@ -482,17 +525,17 @@ void EditorExportPlugin::_export_file_script(const String &p_path, const String
 	}
 }
 
-void EditorExportPlugin::_export_begin_script(const PoolVector<String> &p_features) {
+void EditorExportPlugin::_export_begin_script(const PoolVector<String> &p_features, bool p_debug, const String &p_path, int p_flags) {
 
 	if (get_script_instance()) {
-		get_script_instance()->call("_export_begin", p_features);
+		get_script_instance()->call("_export_begin", p_features, p_debug, p_path, p_flags);
 	}
 }
 
 void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const Set<String> &p_features) {
 }
 
-void EditorExportPlugin::_export_begin(const Set<String> &p_features) {
+void EditorExportPlugin::_export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags) {
 }
 
 void EditorExportPlugin::skip() {
@@ -502,33 +545,58 @@ void EditorExportPlugin::skip() {
 
 void EditorExportPlugin::_bind_methods() {
 
-	ClassDB::bind_method(D_METHOD("add_shared_object", "path"), &EditorExportPlugin::add_shared_object);
+	ClassDB::bind_method(D_METHOD("add_shared_object", "path", "tags"), &EditorExportPlugin::add_shared_object);
 	ClassDB::bind_method(D_METHOD("add_file", "path", "file", "remap"), &EditorExportPlugin::add_file);
+	ClassDB::bind_method(D_METHOD("add_ios_framework", "path"), &EditorExportPlugin::add_ios_framework);
+	ClassDB::bind_method(D_METHOD("add_ios_plist_content", "plist_content"), &EditorExportPlugin::add_ios_plist_content);
+	ClassDB::bind_method(D_METHOD("add_ios_linker_flags", "flags"), &EditorExportPlugin::add_ios_linker_flags);
+	ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_ios_bundle_file);
+	ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_ios_cpp_code);
 	ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip);
 
 	BIND_VMETHOD(MethodInfo("_export_file", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::POOL_STRING_ARRAY, "features")));
-	BIND_VMETHOD(MethodInfo("_export_begin", PropertyInfo(Variant::POOL_STRING_ARRAY, "features")));
+	BIND_VMETHOD(MethodInfo("_export_begin", PropertyInfo(Variant::POOL_STRING_ARRAY, "features"), PropertyInfo(Variant::BOOL, "is_debug"), PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "flags")));
 }
 
 EditorExportPlugin::EditorExportPlugin() {
 	skipped = false;
 }
 
-Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) {
-
+EditorExportPlatform::FeatureContainers EditorExportPlatform::get_feature_containers(const Ref<EditorExportPreset> &p_preset) {
 	Ref<EditorExportPlatform> platform = p_preset->get_platform();
 	List<String> feature_list;
+	platform->get_platform_features(&feature_list);
 	platform->get_preset_features(p_preset, &feature_list);
-	//figure out features
-	Set<String> features;
-	PoolVector<String> features_pv;
+
+	FeatureContainers result;
 	for (List<String>::Element *E = feature_list.front(); E; E = E->next()) {
-		features.insert(E->get());
-		features_pv.push_back(E->get());
+		result.features.insert(E->get());
+		result.features_pv.push_back(E->get());
 	}
+	return result;
+}
 
+EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+	FeatureContainers features = p_platform.get_feature_containers(p_preset);
 	Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	//initial export plugin callback
+	for (int i = 0; i < export_plugins.size(); i++) {
+		if (export_plugins[i]->get_script_instance()) { //script based
+			export_plugins[i]->_export_begin_script(features.features_pv, p_debug, p_path, p_flags);
+		} else {
+			export_plugins[i]->_export_begin(features.features, p_debug, p_path, p_flags);
+		}
+	}
+}
+
+EditorExportPlatform::ExportNotifier::~ExportNotifier() {
+	Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	for (int i = 0; i < export_plugins.size(); i++) {
+		export_plugins[i]->_export_end();
+	}
+}
 
+Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) {
 	//figure out paths of files that will be exported
 	Set<String> paths;
 	Vector<String> path_remaps;
@@ -551,13 +619,8 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 	_edit_filter_list(paths, p_preset->get_include_filter(), false);
 	_edit_filter_list(paths, p_preset->get_exclude_filter(), true);
 
-	//initial export plugin callback
+	Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
 	for (int i = 0; i < export_plugins.size(); i++) {
-		if (export_plugins[i]->get_script_instance()) { //script based
-			export_plugins[i]->_export_begin_script(features_pv);
-		} else {
-			export_plugins[i]->_export_begin(features);
-		}
 		if (p_so_func) {
 			for (int j = 0; j < export_plugins[i]->shared_objects.size(); j++) {
 				p_so_func(p_udata, export_plugins[i]->shared_objects[j]);
@@ -570,6 +633,10 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 		export_plugins[i]->_clear();
 	}
 
+	FeatureContainers feature_containers = get_feature_containers(p_preset);
+	Set<String> &features = feature_containers.features;
+	PoolVector<String> &features_pv = feature_containers.features_pv;
+
 	//store everything in the export medium
 	int idx = 0;
 	int total = paths.size();
@@ -686,7 +753,16 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 	return OK;
 }
 
-Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+	PackData *pack_data = (PackData *)p_userdata;
+	if (pack_data->so_files) {
+		pack_data->so_files->push_back(p_so);
+	}
+
+	return OK;
+}
+
+Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, const String &p_path, Vector<SharedObject> *p_so_files) {
 
 	EditorProgress ep("savepack", TTR("Packing"), 102);
 
@@ -697,8 +773,9 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c
 	PackData pd;
 	pd.ep = &ep;
 	pd.f = ftmp;
+	pd.so_files = p_so_files;
 
-	Error err = export_project_files(p_preset, _save_pack_file, &pd);
+	Error err = export_project_files(p_preset, _save_pack_file, &pd, _add_shared_object);
 
 	memdelete(ftmp); //close tmp file
 
@@ -1203,6 +1280,7 @@ String EditorExportPlatformPC::get_binary_extension() const {
 }
 
 Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+	ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
 
 	String custom_debug = p_preset->get("custom_template/debug");
 	String custom_release = p_preset->get("custom_template/release");

+ 59 - 6
editor/editor_export.h

@@ -118,13 +118,24 @@ public:
 	EditorExportPreset();
 };
 
+struct SharedObject {
+	String path;
+	Vector<String> tags;
+
+	SharedObject(const String &p_path, const Vector<String> &p_tags)
+		: path(p_path), tags(p_tags) {
+	}
+
+	SharedObject() {}
+};
+
 class EditorExportPlatform : public Reference {
 
 	GDCLASS(EditorExportPlatform, Reference)
 
 public:
 	typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
-	typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const String &p_path);
+	typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so);
 
 private:
 	struct SavedData {
@@ -144,6 +155,7 @@ private:
 		FileAccess *f;
 		Vector<SavedData> file_ofs;
 		EditorProgress *ep;
+		Vector<SharedObject> *so_files;
 	};
 
 	struct ZipData {
@@ -152,6 +164,11 @@ private:
 		EditorProgress *ep;
 	};
 
+	struct FeatureContainers {
+		Set<String> features;
+		PoolVector<String> features_pv;
+	};
+
 	void _export_find_resources(EditorFileSystemDirectory *p_dir, Set<String> &p_paths);
 	void _export_find_dependencies(const String &p_path, Set<String> &p_paths);
 
@@ -162,7 +179,16 @@ private:
 	void _edit_files_with_filter(DirAccess *da, const Vector<String> &p_filters, Set<String> &r_list, bool exclude);
 	void _edit_filter_list(Set<String> &r_list, const String &p_filter, bool exclude);
 
+	static Error _add_shared_object(void *p_userdata, const SharedObject &p_so);
+
 protected:
+	struct ExportNotifier {
+		ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
+		~ExportNotifier();
+	};
+
+	FeatureContainers get_feature_containers(const Ref<EditorExportPreset> &p_preset);
+
 	bool exists_export_template(String template_file_name, String *err) const;
 	String find_export_template(String template_file_name, String *err = NULL) const;
 	void gen_export_flags(Vector<String> &r_flags, int p_flags);
@@ -192,7 +218,7 @@ public:
 
 	Error export_project_files(const Ref<EditorExportPreset> &p_preset, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = NULL);
 
-	Error save_pack(const Ref<EditorExportPreset> &p_preset, const String &p_path);
+	Error save_pack(const Ref<EditorExportPreset> &p_preset, const String &p_path, Vector<SharedObject> *p_so_files = NULL);
 	Error save_zip(const Ref<EditorExportPreset> &p_preset, const String &p_path);
 
 	virtual bool poll_devices() { return false; }
@@ -225,7 +251,7 @@ class EditorExportPlugin : public Reference {
 
 	friend class EditorExportPlatform;
 
-	Vector<String> shared_objects;
+	Vector<SharedObject> shared_objects;
 	struct ExtraFile {
 		String path;
 		Vector<uint8_t> data;
@@ -234,26 +260,53 @@ class EditorExportPlugin : public Reference {
 	Vector<ExtraFile> extra_files;
 	bool skipped;
 
+	Vector<String> ios_frameworks;
+	String ios_plist_content;
+	String ios_linker_flags;
+	Vector<String> ios_bundle_files;
+	String ios_cpp_code;
+
 	_FORCE_INLINE_ void _clear() {
 		shared_objects.clear();
 		extra_files.clear();
 		skipped = false;
 	}
 
+	_FORCE_INLINE_ void _export_end() {
+		ios_frameworks.clear();
+		ios_bundle_files.clear();
+		ios_plist_content = "";
+		ios_linker_flags = "";
+		ios_cpp_code = "";
+	}
+
 	void _export_file_script(const String &p_path, const String &p_type, const PoolVector<String> &p_features);
-	void _export_begin_script(const PoolVector<String> &p_features);
+	void _export_begin_script(const PoolVector<String> &p_features, bool p_debug, const String &p_path, int p_flags);
 
 protected:
 	void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap);
-	void add_shared_object(const String &p_path);
+	void add_shared_object(const String &p_path, const Vector<String> &tags);
+
+	void add_ios_framework(const String &p_path);
+	void add_ios_plist_content(const String &p_plist_content);
+	void add_ios_linker_flags(const String &p_flags);
+	void add_ios_bundle_file(const String &p_path);
+	void add_ios_cpp_code(const String &p_code);
+
 	void skip();
 
 	virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features);
-	virtual void _export_begin(const Set<String> &p_features);
+	virtual void _export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags);
 
 	static void _bind_methods();
 
 public:
+	Vector<String> get_ios_frameworks() const;
+	String get_ios_plist_content() const;
+	String get_ios_linker_flags() const;
+	Vector<String> get_ios_bundle_files() const;
+	String get_ios_cpp_code() const;
+
 	EditorExportPlugin();
 };
 

+ 0 - 0
misc/dist/ios_xcode/godot.iphone.debug.fat


+ 0 - 0
misc/dist/ios_xcode/godot.iphone.release.arm


+ 0 - 0
misc/dist/ios_xcode/godot.iphone.release.arm64


+ 0 - 0
misc/dist/ios_xcode/godot.iphone.release.fat


+ 68 - 23
misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj

@@ -8,8 +8,20 @@
 
 /* Begin PBXBuildFile section */
 		1F1575721F582BE20003B888 /* dylibs in Resources */ = {isa = PBXBuildFile; fileRef = 1F1575711F582BE20003B888 /* dylibs */; };
+		1FE926991FBBF85400F53A6F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE926961FBBF7D400F53A6F /* SystemConfiguration.framework */; };
+		1FE9269A1FBBF85F00F53A6F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE926951FBBF7C400F53A6F /* Security.framework */; };
+		1FE9269B1FBBF86200F53A6F /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE926941FBBF7BD00F53A6F /* QuartzCore.framework */; };
+		1FE9269C1FBBF86500F53A6F /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE926931FBBF7AD00F53A6F /* MediaPlayer.framework */; };
+		1FE9269D1FBBF86600F53A6F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE926921FBBF7A000F53A6F /* GameController.framework */; };
+		1FE9269E1FBBF86900F53A6F /* CoreMotion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE926911FBBF79500F53A6F /* CoreMotion.framework */; };
+		1FE9269F1FBBF86B00F53A6F /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE926901FBBF78E00F53A6F /* CoreMedia.framework */; };
+		1FE926A01FBBF86D00F53A6F /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE9268E1FBBF77300F53A6F /* AudioToolbox.framework */; };
+		1FE926A11FBBF86D00F53A6F /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FE9268F1FBBF77F00F53A6F /* CoreAudio.framework */; };
+		DEADBEEF2F582BE20003B888 /* $binary.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DEADBEEF1F582BE20003B888 /* $binary.a */; };
+		1FF8DBB11FBA9DE1009DE660 /* dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */; };
 		1FF4C1851F584E3F00A41E41 /* GameKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FF4C1841F584E3F00A41E41 /* GameKit.framework */; };
 		1FF4C1871F584E5600A41E41 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FF4C1861F584E5600A41E41 /* StoreKit.framework */; };
+		1FF4C1871F584E7600A41E41 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FF4C1881F584E7600A41E41 /* StoreKit.framework */; };
 		D07CD43F1C5D573600B7FB28 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D07CD4331C5D573600B7FB28 /* [email protected] */; };
 		D07CD4411C5D573600B7FB28 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D07CD4351C5D573600B7FB28 /* [email protected] */; };
 		D07CD4421C5D573600B7FB28 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = D07CD4361C5D573600B7FB28 /* [email protected] */; };
@@ -26,14 +38,25 @@
 		D0BCFE4018AEBDA2004A7AAE /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCFE3F18AEBDA2004A7AAE /* OpenGLES.framework */; };
 		D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */; };
 		D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE7718AEBFEB004A7AAE /* $binary.pck */; };
-		D0BCFE7A18AEC06A004A7AAE /* $binary.iphone in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE7918AEC06A004A7AAE /* $binary.iphone */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
-		1F1575711F582BE20003B888 /* dylibs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dylibs; path = dylibs; sourceTree = "<group>"; };
+		1F1575711F582BE20003B888 /* dylibs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dylibs; path = "$binary/dylibs"; sourceTree = "<group>"; };
+		1FE9268E1FBBF77300F53A6F /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
+		1FE9268F1FBBF77F00F53A6F /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
+		1FE926901FBBF78E00F53A6F /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
+		1FE926911FBBF79500F53A6F /* CoreMotion.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMotion.framework; path = System/Library/Frameworks/CoreMotion.framework; sourceTree = SDKROOT; };
+		1FE926921FBBF7A000F53A6F /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
+		1FE926931FBBF7AD00F53A6F /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
+		1FE926941FBBF7BD00F53A6F /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
+		1FE926951FBBF7C400F53A6F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
+		1FE926961FBBF7D400F53A6F /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
+		DEADBEEF1F582BE20003B888 /* $binary.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = godot; path = "$binary.a"; sourceTree = "<group>"; };
 		1FF4C1841F584E3F00A41E41 /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = System/Library/Frameworks/GameKit.framework; sourceTree = SDKROOT; };
 		1FF4C1861F584E5600A41E41 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
+		1FF4C1881F584E7600A41E41 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
 		1FF4C1881F584E6300A41E41 /* $binary.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = $binary.entitlements; sourceTree = "<group>"; };
+		1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dummy.cpp; sourceTree = "<group>"; };
 		D07CD4331C5D573600B7FB28 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
 		D07CD4351C5D573600B7FB28 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
 		D07CD4361C5D573600B7FB28 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
@@ -51,24 +74,35 @@
 		D0BCFE3F18AEBDA2004A7AAE /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; };
 		D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "$binary-Info.plist"; sourceTree = "<group>"; };
 		D0BCFE4518AEBDA2004A7AAE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
-		D0BCFE4918AEBDA2004A7AAE /* $binary-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "$binary-Prefix.pch"; sourceTree = "<group>"; };
-		D0BCFE6118AEBDA3004A7AAE /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
 		D0BCFE7718AEBFEB004A7AAE /* $binary.pck */ = {isa = PBXFileReference; lastKnownFileType = file; path = $binary.pck; sourceTree = "<group>"; };
-		D0BCFE7918AEC06A004A7AAE /* $binary.iphone */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = $binary.iphone; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
+		$additional_pbx_files
+
 /* Begin PBXFrameworksBuildPhase section */
 		D0BCFE3118AEBDA2004A7AAE /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D0BCFE3A18AEBDA2004A7AAE /* CoreGraphics.framework in Frameworks */,
+				1FE926991FBBF85400F53A6F /* SystemConfiguration.framework in Frameworks */,
+				1FE9269A1FBBF85F00F53A6F /* Security.framework in Frameworks */,
+				1FE9269B1FBBF86200F53A6F /* QuartzCore.framework in Frameworks */,
+				1FE9269C1FBBF86500F53A6F /* MediaPlayer.framework in Frameworks */,
+				1FE9269D1FBBF86600F53A6F /* GameController.framework in Frameworks */,
+				1FE9269E1FBBF86900F53A6F /* CoreMotion.framework in Frameworks */,
+				1FE9269F1FBBF86B00F53A6F /* CoreMedia.framework in Frameworks */,
+				1FE926A11FBBF86D00F53A6F /* CoreAudio.framework in Frameworks */,
+				1FE926A01FBBF86D00F53A6F /* AudioToolbox.framework in Frameworks */,
 				D0BCFE4018AEBDA2004A7AAE /* OpenGLES.framework in Frameworks */,
 				1FF4C1871F584E5600A41E41 /* StoreKit.framework in Frameworks */,
-				D0BCFE3A18AEBDA2004A7AAE /* CoreGraphics.framework in Frameworks */,
+				1FF4C1871F584E7600A41E41 /* AVFoundation.framework in Frameworks */,
 				D0BCFE3C18AEBDA2004A7AAE /* UIKit.framework in Frameworks */,
 				1FF4C1851F584E3F00A41E41 /* GameKit.framework in Frameworks */,
 				D0BCFE3E18AEBDA2004A7AAE /* GLKit.framework in Frameworks */,
 				D0BCFE3818AEBDA2004A7AAE /* Foundation.framework in Frameworks */,
+				DEADBEEF2F582BE20003B888 /* $binary.a */,
+				$additional_pbx_frameworks_build
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -79,11 +113,11 @@
 			isa = PBXGroup;
 			children = (
 				1F1575711F582BE20003B888 /* dylibs */,
-				D0BCFE7918AEC06A004A7AAE /* $binary.iphone */,
 				D0BCFE7718AEBFEB004A7AAE /* $binary.pck */,
 				D0BCFE4118AEBDA2004A7AAE /* $binary */,
 				D0BCFE3618AEBDA2004A7AAE /* Frameworks */,
 				D0BCFE3518AEBDA2004A7AAE /* Products */,
+				$additional_pbx_resources_refs
 			);
 			sourceTree = "<group>";
 		};
@@ -98,14 +132,25 @@
 		D0BCFE3618AEBDA2004A7AAE /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				1FE926961FBBF7D400F53A6F /* SystemConfiguration.framework */,
+				1FE926951FBBF7C400F53A6F /* Security.framework */,
+				1FE926941FBBF7BD00F53A6F /* QuartzCore.framework */,
+				1FE926931FBBF7AD00F53A6F /* MediaPlayer.framework */,
+				1FE926921FBBF7A000F53A6F /* GameController.framework */,
+				1FE926911FBBF79500F53A6F /* CoreMotion.framework */,
+				1FE926901FBBF78E00F53A6F /* CoreMedia.framework */,
+				1FE9268F1FBBF77F00F53A6F /* CoreAudio.framework */,
+				1FE9268E1FBBF77300F53A6F /* AudioToolbox.framework */,
 				1FF4C1861F584E5600A41E41 /* StoreKit.framework */,
 				1FF4C1841F584E3F00A41E41 /* GameKit.framework */,
+				1FF4C1881F584E7600A41E41 /* AVFoundation.framework */,
 				D0BCFE3718AEBDA2004A7AAE /* Foundation.framework */,
 				D0BCFE3918AEBDA2004A7AAE /* CoreGraphics.framework */,
 				D0BCFE3B18AEBDA2004A7AAE /* UIKit.framework */,
 				D0BCFE3D18AEBDA2004A7AAE /* GLKit.framework */,
 				D0BCFE3F18AEBDA2004A7AAE /* OpenGLES.framework */,
-				D0BCFE6118AEBDA3004A7AAE /* XCTest.framework */,
+				DEADBEEF1F582BE20003B888 /* $binary.a */,
+				$additional_pbx_frameworks_refs
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -124,6 +169,7 @@
 				D07CD43C1C5D573600B7FB28 /* [email protected] */,
 				D07CD44D1C5D589C00B7FB28 /* Images.xcassets */,
 				D0BCFE4218AEBDA2004A7AAE /* Supporting Files */,
+				1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */,
 			);
 			path = $binary;
 			sourceTree = "<group>";
@@ -133,7 +179,6 @@
 			children = (
 				D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */,
 				D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */,
-				D0BCFE4918AEBDA2004A7AAE /* $binary-Prefix.pch */,
 			);
 			name = "Supporting Files";
 			sourceTree = "<group>";
@@ -218,7 +263,7 @@
 				D07CD4421C5D573600B7FB28 /* [email protected] in Resources */,
 				D07CD4481C5D573600B7FB28 /* [email protected] in Resources */,
 				D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */,
-				D0BCFE7A18AEC06A004A7AAE /* $binary.iphone in Resources */,
+				$additional_pbx_resources_build
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -229,6 +274,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				1FF8DBB11FBA9DE1009DE660 /* dummy.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -250,7 +296,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
+				ARCHS = "$godot_archs";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
@@ -265,6 +311,8 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "$code_sign_identity_debug";
 				COPY_PHASE_STRIP = NO;
+				ENABLE_BITCODE = NO;
+				"FRAMEWORK_SEARCH_PATHS[arch=*]" = "$binary";
 				GCC_C_LANGUAGE_STANDARD = gnu99;
 				GCC_DYNAMIC_NO_PIC = NO;
 				GCC_OPTIMIZATION_LEVEL = 0;
@@ -280,7 +328,7 @@
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				ONLY_ACTIVE_ARCH = YES;
+				OTHER_LDFLAGS = "$linker_flags";
 				SDKROOT = iphoneos;
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};
@@ -290,7 +338,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
+				ARCHS = "$godot_archs";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
@@ -306,6 +354,8 @@
 				CODE_SIGN_IDENTITY = "$code_sign_identity_release";
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "$code_sign_identity_release";
 				COPY_PHASE_STRIP = YES;
+				ENABLE_BITCODE = NO;
+				"FRAMEWORK_SEARCH_PATHS[arch=*]" = "$binary";
 				ENABLE_NS_ASSERTIONS = NO;
 				GCC_C_LANGUAGE_STANDARD = gnu99;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -315,6 +365,7 @@
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				OTHER_LDFLAGS = "$linker_flags";
 				SDKROOT = iphoneos;
 				TARGETED_DEVICE_FAMILY = "1,2";
 				VALIDATE_PRODUCT = YES;
@@ -324,26 +375,23 @@
 		D0BCFE7218AEBDA3004A7AAE /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD)";
+				ARCHS = "$godot_archs";
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_ENTITLEMENTS = $binary/$binary.entitlements;
 				CODE_SIGN_IDENTITY = "$code_sign_identity_debug";
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "$code_sign_identity_debug";
 				CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)";
 				DEVELOPMENT_TEAM = $team_id;
-				GCC_PRECOMPILE_PREFIX_HEADER = YES;
-				GCC_PREFIX_HEADER = "$binary/$binary-Prefix.pch";
 				INFOPLIST_FILE = "$binary/$binary-Info.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
-					"$(PROJECT_DIR)/dylibs",
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = $identifier;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE = "$provisioning_profile_uuid_debug";
 				TARGETED_DEVICE_FAMILY = "1,2";
-				VALID_ARCHS = "armv7 armv7s";
+				VALID_ARCHS = "armv7 armv7s arm64 i386 x86_64";
 				WRAPPER_EXTENSION = app;
 			};
 			name = Debug;
@@ -351,26 +399,23 @@
 		D0BCFE7318AEBDA3004A7AAE /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD)";
+				ARCHS = "$godot_archs";
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_ENTITLEMENTS = $binary/$binary.entitlements;
 				CODE_SIGN_IDENTITY = "$code_sign_identity_release";
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "$code_sign_identity_release";
 				CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)";
 				DEVELOPMENT_TEAM = $team_id;
-				GCC_PRECOMPILE_PREFIX_HEADER = YES;
-				GCC_PREFIX_HEADER = "$binary/$binary-Prefix.pch";
 				INFOPLIST_FILE = "$binary/$binary-Info.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
-					"$(PROJECT_DIR)/dylibs",
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = $identifier;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE = "$provisioning_profile_uuid_release";
 				TARGETED_DEVICE_FAMILY = "1,2";
-				VALID_ARCHS = "armv7 armv7s";
+				VALID_ARCHS = "armv7 armv7s arm64 i386 x86_64";
 				WRAPPER_EXTENSION = app;
 			};
 			name = Release;

+ 1 - 0
misc/dist/ios_xcode/godot_ios/dummy.cpp

@@ -0,0 +1 @@
+$cpp_code

+ 1 - 0
misc/dist/ios_xcode/godot_ios/dylibs/empty

@@ -0,0 +1 @@
+Dummy file to make dylibs folder exported

+ 10 - 0
misc/dist/ios_xcode/export_options.plist → misc/dist/ios_xcode/godot_ios/export_options.plist

@@ -4,7 +4,17 @@
 <dict>
     <key>method</key>
     <string>$export_method</string>
+
     <key>teamID</key>
     <string>$team_id</string>
+
+    <key>provisioningProfiles</key>
+    <dict>
+        <key>$identifier</key>
+        <string>$provisioning_profile_uuid</string>
+    </dict>
+
+    <key>compileBitcode</key>
+    <false/>
 </dict>
 </plist>

+ 2 - 1
misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist

@@ -7,7 +7,7 @@
 	<key>CFBundleDisplayName</key>
 	<string>$name</string>
 	<key>CFBundleExecutable</key>
-	<string>$binary.iphone</string>
+	<string>$binary</string>
 	<key>CFBundleIcons</key>
 	<dict/>
 	<key>CFBundleIcons~ipad</key>
@@ -47,5 +47,6 @@
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 	</array>
+	$additional_plist_content
 </dict>
 </plist>

+ 0 - 39
misc/dist/ios_xcode/godot_ios/main.m

@@ -1,39 +0,0 @@
-/*************************************************************************/
-/*  main.m                                                               */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md)    */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#import <UIKit/UIKit.h>
-
-#import "AppDelegate.h"
-
-int main(int argc, char *argv[]) {
-	@autoreleasepool {
-		return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
-	}
-}

+ 0 - 0
misc/dist/ios_xcode/godot.iphone.debug.arm → misc/dist/ios_xcode/libgodot.iphone.debug.fat.a


+ 0 - 0
misc/dist/ios_xcode/godot.iphone.debug.arm64 → misc/dist/ios_xcode/libgodot.iphone.release.fat.a


+ 4 - 4
modules/gdnative/gdnative.cpp

@@ -123,7 +123,7 @@ bool GDNative::initialize() {
 		return false;
 	}
 #ifdef IPHONE_ENABLED
-	String path = lib_path.replace("res://", "dylibs/");
+	String path = "";
 #else
 	String path = ProjectSettings::get_singleton()->globalize_path(lib_path);
 #endif
@@ -148,7 +148,7 @@ bool GDNative::initialize() {
 	// we cheat here a little bit. you saw nothing
 	initialized = true;
 
-	err = get_symbol(library->get_symbol_prefix() + init_symbol, library_init);
+	err = get_symbol(library->get_symbol_prefix() + init_symbol, library_init, false);
 
 	initialized = false;
 
@@ -280,7 +280,7 @@ Variant GDNative::call_native(StringName p_native_call_type, StringName p_proced
 	return *(Variant *)&result;
 }
 
-Error GDNative::get_symbol(StringName p_procedure_name, void *&r_handle) {
+Error GDNative::get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional) {
 
 	if (!initialized) {
 		ERR_PRINT("No valid library handle, can't get symbol from GDNative object");
@@ -291,7 +291,7 @@ Error GDNative::get_symbol(StringName p_procedure_name, void *&r_handle) {
 			native_handle,
 			p_procedure_name,
 			r_handle,
-			true);
+			p_optional);
 
 	return result;
 }

+ 1 - 1
modules/gdnative/gdnative.h

@@ -141,7 +141,7 @@ public:
 
 	Variant call_native(StringName p_native_call_type, StringName p_procedure_name, Array p_arguments = Array());
 
-	Error get_symbol(StringName p_procedure_name, void *&r_handle);
+	Error get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional = true);
 };
 
 class GDNativeLibraryResourceLoader : public ResourceFormatLoader {

+ 48 - 41
modules/gdnative/register_types.cpp

@@ -123,6 +123,11 @@ protected:
 	virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features);
 };
 
+struct LibrarySymbol {
+	char *name;
+	bool is_required;
+};
+
 void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_type, const Set<String> &p_features) {
 	if (p_type != "GDNativeLibrary") {
 		return;
@@ -136,7 +141,6 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
 
 	Ref<ConfigFile> config = lib->get_config_file();
 
-	String entry_lib_path;
 	{
 
 		List<String> entry_keys;
@@ -161,14 +165,12 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
 				continue;
 			}
 
-			entry_lib_path = config->get_value("entry", key);
-			break;
+			String entry_lib_path = config->get_value("entry", key);
+			add_shared_object(entry_lib_path, tags);
 		}
 	}
 
-	Vector<String> dependency_paths;
 	{
-
 		List<String> dependency_keys;
 		config->get_section_keys("dependencies", &dependency_keys);
 
@@ -191,47 +193,54 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
 				continue;
 			}
 
-			dependency_paths = config->get_value("dependencies", key);
-			break;
+			Vector<String> dependency_paths = config->get_value("dependencies", key);
+			for (int i = 0; i < dependency_paths.size(); i++) {
+				add_shared_object(dependency_paths[i], tags);
+			}
 		}
 	}
 
-	bool is_statically_linked = false;
-	{
-
-		List<String> static_linking_keys;
-		config->get_section_keys("static_linking", &static_linking_keys);
-
-		for (List<String>::Element *E = static_linking_keys.front(); E; E = E->next()) {
-			String key = E->get();
-
-			Vector<String> tags = key.split(".");
-
-			bool skip = false;
-
-			for (int i = 0; i < tags.size(); i++) {
-				bool has_feature = p_features.has(tags[i]);
-
-				if (!has_feature) {
-					skip = true;
-					break;
+	if (p_features.has("iOS")) {
+		// Register symbols in the "fake" dynamic lookup table, because dlsym does not work well on iOS.
+		LibrarySymbol expected_symbols[] = {
+			{ "gdnative_init", true },
+			{ "gdnative_terminate", false },
+			{ "nativescript_init", false },
+			{ "nativescript_frame", false },
+			{ "nativescript_thread_enter", false },
+			{ "nativescript_thread_exit", false },
+			{ "gdnative_singleton", false }
+		};
+		String declare_pattern = "extern \"C\" void $name(void)$weak;\n";
+		String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n"
+								 "extern void add_ios_init_callback(void (*cb)());\n";
+		String linker_flags = "";
+		for (int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) {
+			String full_name = lib->get_symbol_prefix() + expected_symbols[i].name;
+			String code = declare_pattern.replace("$name", full_name);
+			code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))");
+			additional_code += code;
+
+			if (!expected_symbols[i].is_required) {
+				if (linker_flags.length() > 0) {
+					linker_flags += " ";
 				}
+				linker_flags += "-Wl,-U,_" + full_name;
 			}
-
-			if (skip) {
-				continue;
-			}
-
-			is_statically_linked = config->get_value("static_linking", key);
-			break;
 		}
-	}
 
-	if (!is_statically_linked)
-		add_shared_object(entry_lib_path);
+		additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix());
+		String register_pattern = "  if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n";
+		for (int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) {
+			String full_name = lib->get_symbol_prefix() + expected_symbols[i].name;
+			additional_code += register_pattern.replace("$name", full_name);
+		}
+		additional_code += "}\n";
+		additional_code += String("struct $prefixstruct {$prefixstruct() {add_ios_init_callback($prefixinit);}};\n").replace("$prefix", lib->get_symbol_prefix());
+		additional_code += String("$prefixstruct $prefixstruct_instance;\n").replace("$prefix", lib->get_symbol_prefix());
 
-	for (int i = 0; i < dependency_paths.size(); i++) {
-		add_shared_object(dependency_paths[i]);
+		add_ios_cpp_code(additional_code);
+		add_ios_linker_flags(linker_flags);
 	}
 }
 
@@ -271,9 +280,7 @@ void register_gdnative_types() {
 
 #ifdef TOOLS_ENABLED
 
-	if (Engine::get_singleton()->is_editor_hint()) {
-		EditorNode::add_init_callback(editor_init_callback);
-	}
+	EditorNode::add_init_callback(editor_init_callback);
 #endif
 
 	ClassDB::register_class<GDNativeLibrary>();

+ 1 - 1
modules/webm/config.py

@@ -1,5 +1,5 @@
 def can_build(platform):
-    return True
+    return platform != 'iphone'
 
 def configure(env):
     pass

+ 18 - 1
platform/android/os_android.cpp

@@ -705,7 +705,24 @@ String OS_Android::get_joy_guid(int p_device) const {
 }
 
 bool OS_Android::_check_internal_feature_support(const String &p_feature) {
-	return p_feature == "mobile" || p_feature == "etc" || p_feature == "etc2"; //TODO support etc2 only if GLES3 driver is selected
+	if (p_feature == "mobile" || p_feature == "etc" || p_feature == "etc2") {
+		//TODO support etc2 only if GLES3 driver is selected
+		return true;
+	}
+#if defined(__aarch64__)
+	if (p_feature == "arm64-v8a") {
+		return true;
+	}
+#elif defined(__ARM_ARCH_7A__)
+	if (p_feature == "armeabi-v7a" || p_feature == "armeabi") {
+		return true;
+	}
+#elif defined(__arm__)
+	if (p_feature == "armeabi") {
+		return true;
+	}
+#endif
+	return false;
 }
 
 OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion) {

+ 6 - 6
platform/iphone/SCsub

@@ -3,7 +3,7 @@
 Import('env')
 
 iphone_lib = [
-
+    'godot_iphone.cpp',
     'os_iphone.cpp',
     'sem_iphone.cpp',
     'gl_view.mm',
@@ -17,10 +17,10 @@ iphone_lib = [
 ]
 
 env_ios = env.Clone()
+ios_lib = env_ios.Library('iphone', iphone_lib)
 
-obj = env_ios.Object('godot_iphone.cpp')
+def combine_libs(target=None, source=None, env=None):
+    lib_path = target[0].srcnode().abspath
+    env.Execute('$IPHONEPATH/usr/bin/libtool -static -o "' + lib_path + '" ' + ' '.join([('"' + lib.srcnode().abspath + '"') for lib in source]))
 
-prog = None
-prog = env_ios.Program('#bin/godot', [obj] + iphone_lib)
-action = "$IPHONEPATH/usr/bin/dsymutil " + File(prog)[0].path + " -o " + File(prog)[0].path + ".dSYM"
-env.AddPostAction(prog, action)
+combine_command = env_ios.Command('#bin/libgodot' + env_ios['LIBSUFFIX'], [ios_lib] + env_ios['LIBS'], combine_libs)

+ 12 - 7
platform/iphone/detect.py

@@ -61,10 +61,13 @@ def configure(env):
         env.Append(LINKFLAGS=['-flto'])
 
     ## Architecture
+    if env["ios_sim"] and not ("arch" in env):
+      env["arch"] = "x86"
 
-    if env["ios_sim"] or env["arch"] == "x86":  # i386, simulator
-        env["arch"] = "x86"
+    if env["arch"] == "x86":  # i386, simulator
         env["bits"] = "32"
+    elif env["arch"] == "x86_64":
+        env["bits"] = "64"
     elif (env["arch"] == "arm" or env["arch"] == "arm32" or env["arch"] == "armv7" or env["bits"] == "32"):  # arm
         env["arch"] = "arm"
         env["bits"] = "32"
@@ -95,10 +98,11 @@ def configure(env):
 
     ## Compile flags
 
-    if (env["arch"] == "x86"):
+    if (env["arch"] == "x86" or env["arch"] == "x86_64"):
         env['IPHONEPLATFORM'] = 'iPhoneSimulator'
-        env['ENV']['MACOSX_DEPLOYMENT_TARGET'] = '10.6'
-        env.Append(CCFLAGS='-arch i386 -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -D__IPHONE_OS_VERSION_MIN_REQUIRED=40100 -isysroot $IPHONESDK -mios-simulator-version-min=4.3 -DCUSTOM_MATRIX_TRANSFORM_H=\\\"build/iphone/matrix4_iphone.h\\\" -DCUSTOM_VECTOR3_TRANSFORM_H=\\\"build/iphone/vector3_iphone.h\\\"'.split())
+        env['ENV']['MACOSX_DEPLOYMENT_TARGET'] = '10.9'
+        arch_flag = "i386" if env["arch"] == "x86" else env["arch"]
+        env.Append(CCFLAGS=('-arch ' + arch_flag + ' -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=9.0 -DCUSTOM_MATRIX_TRANSFORM_H=\\\"build/iphone/matrix4_iphone.h\\\" -DCUSTOM_VECTOR3_TRANSFORM_H=\\\"build/iphone/vector3_iphone.h\\\"').split())
     elif (env["arch"] == "arm"):
         env.Append(CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=9.0 -MMD -MT dependencies'.split())
     elif (env["arch"] == "arm64"):
@@ -113,8 +117,9 @@ def configure(env):
 
     ## Link flags
 
-    if (env["arch"] == "x86"):
-        env.Append(LINKFLAGS=['-arch', 'i386', '-mios-simulator-version-min=4.3',
+    if (env["arch"] == "x86" or env["arch"] == "x86_64"):
+        arch_flag = "i386" if env["arch"] == "x86" else env["arch"]
+        env.Append(LINKFLAGS=['-arch', arch_flag, '-mios-simulator-version-min=9.0',
                               '-isysroot', '$IPHONESDK',
                               '-Xlinker',
                               '-objc_abi_version',

+ 382 - 69
platform/iphone/export/export.cpp

@@ -56,11 +56,48 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
 	static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata);
 	static Error _codesign(String p_file, void *p_userdata);
 
-	void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const String &p_name, const String &p_binary, bool p_debug);
-	static Error _export_dylibs(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total);
+	struct IOSConfigData {
+		String pkg_name;
+		String binary_name;
+		String plist_content;
+		String architectures;
+		String linker_flags;
+		String cpp_code;
+	};
+
+	struct ExportArchitecture {
+		String name;
+		bool is_default;
+
+		ExportArchitecture()
+			: name(""), is_default(false) {
+		}
+
+		ExportArchitecture(String p_name, bool p_is_default) {
+			name = p_name;
+			is_default = p_is_default;
+		}
+	};
+
+	struct IOSExportAsset {
+		String exported_path;
+		bool is_framework; // framework is anything linked to the binary, otherwise it's a resource
+	};
+
+	String _get_additional_plist_content();
+	String _get_linker_flags();
+	String _get_cpp_code();
+	void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug);
 	Error _export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
 	Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir);
 
+	Vector<ExportArchitecture> _get_supported_architectures();
+	Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset);
+
+	void _add_assets_to_project(Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
+	Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets);
+	Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
+
 protected:
 	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
 	virtual void get_export_options(List<ExportOption> *r_options);
@@ -96,6 +133,17 @@ void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset>
 	if (p_preset->get("texture_format/etc2")) {
 		r_features->push_back("etc2");
 	}
+	Vector<String> architectures = _get_preset_architectures(p_preset);
+	for (int i = 0; i < architectures.size(); ++i) {
+		r_features->push_back(architectures[i]);
+	}
+}
+
+Vector<EditorExportPlatformIOS::ExportArchitecture> EditorExportPlatformIOS::_get_supported_architectures() {
+	Vector<ExportArchitecture> archs;
+	archs.push_back(ExportArchitecture("armv7", true));
+	archs.push_back(ExportArchitecture("arm64", true));
+	return archs;
 }
 
 void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) {
@@ -120,7 +168,6 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0"));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0"));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/bits_mode", PROPERTY_HINT_ENUM, "Fat (32 & 64 bits),64 bits,32 bits"), 1));
 
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/iphone_120x120", PROPERTY_HINT_FILE, "png"), "")); // Home screen on iPhone/iPod Touch with retina display
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/ipad_76x76", PROPERTY_HINT_FILE, "png"), "")); // Home screen on iPad
@@ -145,10 +192,13 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), true));
 
-	/* probably need some more info */
+	Vector<ExportArchitecture> architectures = _get_supported_architectures();
+	for (int i = 0; i < architectures.size(); ++i) {
+		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + architectures[i].name), architectures[i].is_default));
+	}
 }
 
-void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const String &p_name, const String &p_binary, bool p_debug) {
+void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug) {
 	static const String export_method_string[] = {
 		"app-store",
 		"development",
@@ -158,13 +208,12 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
 	String str;
 	String strnew;
 	str.parse_utf8((const char *)pfile.ptr(), pfile.size());
-	print_line(str);
 	Vector<String> lines = str.split("\n");
 	for (int i = 0; i < lines.size(); i++) {
 		if (lines[i].find("$binary") != -1) {
-			strnew += lines[i].replace("$binary", p_binary) + "\n";
+			strnew += lines[i].replace("$binary", p_config.binary_name) + "\n";
 		} else if (lines[i].find("$name") != -1) {
-			strnew += lines[i].replace("$name", p_name) + "\n";
+			strnew += lines[i].replace("$name", p_config.pkg_name) + "\n";
 		} else if (lines[i].find("$info") != -1) {
 			strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n";
 		} else if (lines[i].find("$identifier") != -1) {
@@ -186,10 +235,21 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
 			strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get("application/provisioning_profile_uuid_release")) + "\n";
 		} else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) {
 			strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get("application/provisioning_profile_uuid_debug")) + "\n";
+		} else if (lines[i].find("$provisioning_profile_uuid") != -1) {
+			String uuid = p_debug ? p_preset->get("application/provisioning_profile_uuid_debug") : p_preset->get("application/provisioning_profile_uuid_release");
+			strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n";
 		} else if (lines[i].find("$code_sign_identity_debug") != -1) {
 			strnew += lines[i].replace("$code_sign_identity_debug", p_preset->get("application/code_sign_identity_debug")) + "\n";
 		} else if (lines[i].find("$code_sign_identity_release") != -1) {
 			strnew += lines[i].replace("$code_sign_identity_release", p_preset->get("application/code_sign_identity_release")) + "\n";
+		} else if (lines[i].find("$additional_plist_content") != -1) {
+			strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n";
+		} else if (lines[i].find("$godot_archs") != -1) {
+			strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n";
+		} else if (lines[i].find("$linker_flags") != -1) {
+			strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n";
+		} else if (lines[i].find("$cpp_code") != -1) {
+			strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n";
 		} else {
 			strnew += lines[i] + "\n";
 		}
@@ -204,27 +264,37 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
 	}
 }
 
-Error EditorExportPlatformIOS::_export_dylibs(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
-	if (!p_path.ends_with(".dylib")) return OK;
-	const String &dest_dir = *(String *)p_userdata;
-	String rel_path = p_path.replace_first("res://", "dylibs/");
-	DirAccess *dest_dir_access = DirAccess::open(dest_dir);
-	ERR_FAIL_COND_V(!dest_dir_access, ERR_CANT_OPEN);
-
-	String base_dir = rel_path.get_base_dir();
-	Error make_dir_err = OK;
-	if (!dest_dir_access->dir_exists(base_dir)) {
-		make_dir_err = dest_dir_access->make_dir_recursive(base_dir);
-	}
-	if (make_dir_err != OK) {
-		memdelete(dest_dir_access);
-		return make_dir_err;
+String EditorExportPlatformIOS::_get_additional_plist_content() {
+	Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	String result;
+	for (int i = 0; i < export_plugins.size(); ++i) {
+		result += export_plugins[i]->get_ios_plist_content();
 	}
+	return result;
+}
 
-	Error copy_err = dest_dir_access->copy(p_path, dest_dir + rel_path);
-	memdelete(dest_dir_access);
+String EditorExportPlatformIOS::_get_linker_flags() {
+	Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	String result;
+	for (int i = 0; i < export_plugins.size(); ++i) {
+		String flags = export_plugins[i]->get_ios_linker_flags();
+		if (flags.length() == 0) continue;
+		if (result.length() > 0) {
+			result += ' ';
+		}
+		result += flags;
+	}
+	// the flags will be enclosed in quotes, so need to escape them
+	return result.replace("\"", "\\\"");
+}
 
-	return copy_err;
+String EditorExportPlatformIOS::_get_cpp_code() {
+	Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	String result;
+	for (int i = 0; i < export_plugins.size(); ++i) {
+		result += export_plugins[i]->get_ios_cpp_code();
+	}
+	return result;
 }
 
 struct IconInfo {
@@ -402,7 +472,207 @@ Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) {
 	return OK;
 }
 
+struct PbxId {
+private:
+	static char _hex_char(uint8_t four_bits) {
+		if (four_bits < 10) {
+			return ('0' + four_bits);
+		}
+		return 'A' + (four_bits - 10);
+	}
+
+	static String _hex_pad(uint32_t num) {
+		Vector<char> ret;
+		ret.resize(sizeof(num) * 2);
+		for (int i = 0; i < sizeof(num) * 2; ++i) {
+			uint8_t four_bits = (num >> (sizeof(num) * 8 - (i + 1) * 4)) & 0xF;
+			ret[i] = _hex_char(four_bits);
+		}
+		return String::utf8(ret.ptr(), ret.size());
+	}
+
+public:
+	uint32_t high_bits;
+	uint32_t mid_bits;
+	uint32_t low_bits;
+
+	String str() const {
+		return _hex_pad(high_bits) + _hex_pad(mid_bits) + _hex_pad(low_bits);
+	}
+
+	PbxId &operator++() {
+		low_bits++;
+		if (!low_bits) {
+			mid_bits++;
+			if (!mid_bits) {
+				high_bits++;
+			}
+		}
+
+		return *this;
+	}
+};
+
+struct ExportLibsData {
+	Vector<String> lib_paths;
+	String dest_dir;
+};
+
+void EditorExportPlatformIOS::_add_assets_to_project(Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) {
+	Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	Vector<String> frameworks;
+	for (int i = 0; i < export_plugins.size(); ++i) {
+		Vector<String> plugin_frameworks = export_plugins[i]->get_ios_frameworks();
+		for (int j = 0; j < plugin_frameworks.size(); ++j) {
+			frameworks.push_back(plugin_frameworks[j]);
+		}
+	}
+
+	// that is just a random number, we just need Godot IDs not to clash with
+	// existing IDs in the project.
+	PbxId current_id = { 0x58938401, 0, 0 };
+	String pbx_files;
+	String pbx_frameworks_build;
+	String pbx_frameworks_refs;
+	String pbx_resources_build;
+	String pbx_resources_refs;
+
+	const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") +
+									"$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = $name; path = \"$file_path\"; sourceTree = \"<group>\"; };\n";
+	for (int i = 0; i < p_additional_assets.size(); ++i) {
+		String build_id = (++current_id).str();
+		String ref_id = (++current_id).str();
+		const IOSExportAsset &asset = p_additional_assets[i];
+
+		String type;
+		if (asset.exported_path.ends_with(".framework")) {
+			type = "wrapper.framework";
+		} else if (asset.exported_path.ends_with(".dylib")) {
+			type = "compiled.mach-o.dylib";
+		} else if (asset.exported_path.ends_with(".a")) {
+			type = "archive.ar";
+		} else {
+			type = "file";
+		}
+
+		String &pbx_build = asset.is_framework ? pbx_frameworks_build : pbx_resources_build;
+		String &pbx_refs = asset.is_framework ? pbx_frameworks_refs : pbx_resources_refs;
+
+		if (pbx_build.length() > 0) {
+			pbx_build += ",\n";
+			pbx_refs += ",\n";
+		}
+		pbx_build += build_id;
+		pbx_refs += ref_id;
+
+		Dictionary format_dict;
+		format_dict["build_id"] = build_id;
+		format_dict["ref_id"] = ref_id;
+		format_dict["name"] = asset.exported_path.get_file();
+		format_dict["file_path"] = asset.exported_path;
+		format_dict["file_type"] = type;
+		pbx_files += file_info_format.format(format_dict, "$_");
+	}
+
+	String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size());
+	str = str.replace("$additional_pbx_files", pbx_files);
+	str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build);
+	str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs);
+	str = str.replace("$additional_pbx_resources_build", pbx_resources_build);
+	str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs);
+
+	CharString cs = str.utf8();
+	p_project_data.resize(cs.size() - 1);
+	for (int i = 0; i < cs.size() - 1; i++) {
+		p_project_data[i] = cs[i];
+	}
+}
+
+Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets) {
+	DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	ERR_FAIL_COND_V(!filesystem_da, ERR_CANT_CREATE);
+	for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) {
+		String asset = p_assets[f_idx];
+		if (!asset.begins_with("res://")) {
+			// either SDK-builtin or already a part of the export template
+			IOSExportAsset exported_asset = { asset, p_is_framework };
+			r_exported_assets.push_back(exported_asset);
+		} else {
+			DirAccess *da = DirAccess::create_for_path(asset);
+			if (!da) {
+				memdelete(filesystem_da);
+				ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
+			}
+			bool file_exists = da->file_exists(asset);
+			bool dir_exists = da->dir_exists(asset);
+			if (!file_exists && !dir_exists) {
+				memdelete(da);
+				memdelete(filesystem_da);
+				return ERR_FILE_NOT_FOUND;
+			}
+			String additional_dir = p_is_framework && asset.ends_with(".dylib") ? "/dylibs/" : "/";
+			String destination_dir = p_out_dir + additional_dir + asset.get_base_dir().replace("res://", "");
+			if (!filesystem_da->dir_exists(destination_dir)) {
+				Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+				if (make_dir_err) {
+					memdelete(da);
+					memdelete(filesystem_da);
+					return make_dir_err;
+				}
+			}
+
+			String destination = destination_dir + "/" + asset.get_file();
+			Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination);
+			memdelete(da);
+			if (err) {
+				memdelete(filesystem_da);
+				return err;
+			}
+			IOSExportAsset exported_asset = { destination, p_is_framework };
+			r_exported_assets.push_back(exported_asset);
+		}
+	}
+	memdelete(filesystem_da);
+
+	return OK;
+}
+
+Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) {
+	Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	for (int i = 0; i < export_plugins.size(); i++) {
+		Vector<String> frameworks = export_plugins[i]->get_ios_frameworks();
+		Error err = _export_additional_assets(p_out_dir, frameworks, true, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+		Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files();
+		err = _export_additional_assets(p_out_dir, ios_bundle_files, false, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+	}
+
+	Vector<String> library_paths;
+	for (int i = 0; i < p_libraries.size(); ++i) {
+		library_paths.push_back(p_libraries[i].path);
+	}
+	Error err = _export_additional_assets(p_out_dir, library_paths, true, r_exported_assets);
+	ERR_FAIL_COND_V(err, err);
+
+	return OK;
+}
+
+Vector<String> EditorExportPlatformIOS::_get_preset_architectures(const Ref<EditorExportPreset> &p_preset) {
+	Vector<ExportArchitecture> all_archs = _get_supported_architectures();
+	Vector<String> enabled_archs;
+	for (int i = 0; i < all_archs.size(); ++i) {
+		bool is_enabled = p_preset->get("architectures/" + all_archs[i].name);
+		if (is_enabled) {
+			enabled_archs.push_back(all_archs[i].name);
+		}
+	}
+	return enabled_archs;
+}
+
 Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+	ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
 	String src_pkg_name;
 	String dest_dir = p_path.get_base_dir() + "/";
 	String binary_name = p_path.get_file().get_basename();
@@ -427,26 +697,43 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 		}
 	}
 
-	FileAccess *src_f = NULL;
-	zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+	DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	if (da) {
+		String current_dir = da->get_current_dir();
 
-	ep.step("Creating app", 0);
+		// remove leftovers from last export so they don't interfere
+		// in case some files are no longer needed
+		if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) {
+			da->erase_contents_recursive();
+		}
+		if (da->change_dir(dest_dir + binary_name) == OK) {
+			da->erase_contents_recursive();
+		}
 
-	unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
-	if (!src_pkg_zip) {
+		da->change_dir(current_dir);
 
-		EditorNode::add_io_error("Could not find template app to export:\n" + src_pkg_name);
-		return ERR_FILE_NOT_FOUND;
+		if (!da->dir_exists(dest_dir + binary_name)) {
+			Error err = da->make_dir(dest_dir + binary_name);
+			if (err) {
+				memdelete(da);
+				return err;
+			}
+		}
+		memdelete(da);
 	}
 
-	ERR_FAIL_COND_V(!src_pkg_zip, ERR_CANT_OPEN);
-	int ret = unzGoToFirstFile(src_pkg_zip);
+	ep.step("Making .pck", 0);
+	String pack_path = dest_dir + binary_name + ".pck";
+	Vector<SharedObject> libraries;
+	Error err = save_pack(p_preset, pack_path, &libraries);
+	if (err)
+		return err;
+
+	ep.step("Extracting and configuring Xcode project", 1);
 
-	String binary_to_use = "godot.iphone." + String(p_debug ? "debug" : "release") + ".";
-	int bits_mode = p_preset->get("application/bits_mode");
-	binary_to_use += String(bits_mode == 0 ? "fat" : bits_mode == 1 ? "arm64" : "armv7");
+	String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".fat.a";
 
-	print_line("binary: " + binary_to_use);
+	print_line("static library: " + library_to_use);
 	String pkg_name;
 	if (p_preset->get("application/name") != "")
 		pkg_name = p_preset->get("application/name"); // app_name
@@ -455,22 +742,41 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 	else
 		pkg_name = "Unnamed";
 
-	DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir);
-	ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE)
-
-	/* Now process our template */
-	bool found_binary = false;
+	bool found_library = false;
 	int total_size = 0;
 
+	const String project_file = "godot_ios.xcodeproj/project.pbxproj";
 	Set<String> files_to_parse;
 	files_to_parse.insert("godot_ios/godot_ios-Info.plist");
-	files_to_parse.insert("godot_ios.xcodeproj/project.pbxproj");
-	files_to_parse.insert("export_options.plist");
+	files_to_parse.insert(project_file);
+	files_to_parse.insert("godot_ios/export_options.plist");
+	files_to_parse.insert("godot_ios/dummy.cpp");
 	files_to_parse.insert("godot_ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata");
 	files_to_parse.insert("godot_ios.xcodeproj/xcshareddata/xcschemes/godot_ios.xcscheme");
 
-	print_line("Unzipping...");
+	IOSConfigData config_data = {
+		pkg_name,
+		binary_name,
+		_get_additional_plist_content(),
+		String(" ").join(_get_preset_architectures(p_preset)),
+		_get_linker_flags(),
+		_get_cpp_code()
+	};
+
+	DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir);
+	ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE)
 
+	print_line("Unzipping...");
+	FileAccess *src_f = NULL;
+	zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+	unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
+	if (!src_pkg_zip) {
+		EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name);
+		return ERR_CANT_OPEN;
+	}
+	ERR_FAIL_COND_V(!src_pkg_zip, ERR_CANT_OPEN);
+	int ret = unzGoToFirstFile(src_pkg_zip);
+	Vector<uint8_t> project_file_data;
 	while (ret == UNZ_OK) {
 		bool is_execute = false;
 
@@ -496,15 +802,18 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 
 		if (files_to_parse.has(file)) {
 			print_line(String("parse ") + file);
-			_fix_config_file(p_preset, data, pkg_name, binary_name, p_debug);
-		} else if (file.begins_with("godot.iphone")) {
-			if (file != binary_to_use) {
+			_fix_config_file(p_preset, data, config_data, p_debug);
+		} else if (file.begins_with("libgodot.iphone")) {
+			if (file != library_to_use) {
 				ret = unzGoToNextFile(src_pkg_zip);
 				continue; //ignore!
 			}
-			found_binary = true;
+			found_library = true;
 			is_execute = true;
-			file = "godot_ios.iphone";
+			file = "godot_ios.a";
+		}
+		if (file == project_file) {
+			project_file_data = data;
 		}
 
 		///@TODO need to parse logo files
@@ -557,16 +866,16 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 	/* we're done with our source zip */
 	unzClose(src_pkg_zip);
 
-	if (!found_binary) {
-		ERR_PRINTS("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive.");
+	if (!found_library) {
+		ERR_PRINTS("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive.");
 		memdelete(tmp_app_path);
 		return ERR_FILE_NOT_FOUND;
 	}
 
 	String iconset_dir = dest_dir + binary_name + "/Images.xcassets/AppIcon.appiconset/";
-	Error err = OK;
+	err = OK;
 	if (!tmp_app_path->dir_exists(iconset_dir)) {
-		Error err = tmp_app_path->make_dir_recursive(iconset_dir);
+		err = tmp_app_path->make_dir_recursive(iconset_dir);
 	}
 	memdelete(tmp_app_path);
 	if (err)
@@ -580,20 +889,23 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 	if (err)
 		return err;
 
-	ep.step("Making .pck", 1);
-
-	String pack_path = dest_dir + binary_name + ".pck";
-	err = save_pack(p_preset, pack_path);
-	if (err)
-		return err;
-
-	err = export_project_files(p_preset, _export_dylibs, &dest_dir);
-	if (err)
-		return err;
+	print_line("Exporting additional assets");
+	Vector<IOSExportAsset> assets;
+	_export_additional_assets(dest_dir + binary_name, libraries, assets);
+	_add_assets_to_project(project_file_data, assets);
+	String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj";
+	FileAccess *f = FileAccess::open(project_file_name, FileAccess::WRITE);
+	if (!f) {
+		ERR_PRINTS("Can't write '" + project_file_name + "'.");
+		return ERR_CANT_CREATE;
+	};
+	f->store_buffer(project_file_data.ptr(), project_file_data.size());
+	f->close();
+	memdelete(f);
 
 #ifdef OSX_ENABLED
 	ep.step("Code-signing dylibs", 2);
-	DirAccess *dylibs_dir = DirAccess::open(dest_dir + "dylibs");
+	DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs");
 	ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN);
 	CodesignData codesign_data(p_preset, p_debug);
 	err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data);
@@ -625,13 +937,14 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 	export_args.push_back("-archivePath");
 	export_args.push_back(archive_path);
 	export_args.push_back("-exportOptionsPlist");
-	export_args.push_back(dest_dir + "export_options.plist");
+	export_args.push_back(dest_dir + binary_name + "/export_options.plist");
+	export_args.push_back("-allowProvisioningUpdates");
 	export_args.push_back("-exportPath");
 	export_args.push_back(dest_dir);
 	err = OS::get_singleton()->execute("xcodebuild", export_args, true);
 	ERR_FAIL_COND_V(err, err);
 #else
-	print_line(".ipa can only be built on macOS. Leaving XCode project without building the package.");
+	print_line(".ipa can only be built on macOS. Leaving Xcode project without building the package.");
 #endif
 
 	return OK;

+ 7 - 7
platform/iphone/game_center.mm

@@ -109,7 +109,7 @@ void GameCenter::connect() {
 				GameCenter::get_singleton()->authenticated = true;
 			} else {
 				ret["result"] = "error";
-				ret["error_code"] = error.code;
+				ret["error_code"] = (int64_t)error.code;
 				ret["error_description"] = [error.localizedDescription UTF8String];
 				GameCenter::get_singleton()->authenticated = false;
 			};
@@ -145,7 +145,7 @@ Error GameCenter::post_score(Variant p_score) {
 					ret["result"] = "ok";
 				} else {
 					ret["result"] = "error";
-					ret["error_code"] = error.code;
+					ret["error_code"] = (int64_t)error.code;
 					ret["error_description"] = [error.localizedDescription UTF8String];
 				};
 
@@ -183,7 +183,7 @@ Error GameCenter::award_achievement(Variant p_params) {
 						ret["result"] = "ok";
 					} else {
 						ret["result"] = "error";
-						ret["error_code"] = error.code;
+						ret["error_code"] = (int64_t)error.code;
 					};
 
 					pending_events.push_back(ret);
@@ -241,7 +241,7 @@ void GameCenter::request_achievement_descriptions() {
 
 		} else {
 			ret["result"] = "error";
-			ret["error_code"] = error.code;
+			ret["error_code"] = (int64_t)error.code;
 		};
 
 		pending_events.push_back(ret);
@@ -273,7 +273,7 @@ void GameCenter::request_achievements() {
 
 		} else {
 			ret["result"] = "error";
-			ret["error_code"] = error.code;
+			ret["error_code"] = (int64_t)error.code;
 		};
 
 		pending_events.push_back(ret);
@@ -289,7 +289,7 @@ void GameCenter::reset_achievements() {
 			ret["result"] = "ok";
 		} else {
 			ret["result"] = "error";
-			ret["error_code"] = error.code;
+			ret["error_code"] = (int64_t)error.code;
 		};
 
 		pending_events.push_back(ret);
@@ -358,7 +358,7 @@ Error GameCenter::request_identity_verification_signature() {
 			ret["player_id"] = [player.playerID UTF8String];
 		} else {
 			ret["result"] = "error";
-			ret["error_code"] = error.code;
+			ret["error_code"] = (int64_t)error.code;
 			ret["error_description"] = [error.localizedDescription UTF8String];
 		};
 

+ 66 - 3
platform/iphone/os_iphone.cpp

@@ -46,6 +46,7 @@
 #include "sem_iphone.h"
 
 #include "ios.h"
+#include <dlfcn.h>
 
 int OSIPhone::get_video_driver_count() const {
 
@@ -99,9 +100,11 @@ void OSIPhone::initialize_core() {
 void OSIPhone::initialize_logger() {
 	Vector<Logger *> loggers;
 	loggers.push_back(memnew(SyslogLogger));
-	// FIXME: Reenable once we figure out how to get this properly in user://
-	// instead of littering the user's working dirs (res:// + pwd) with log files (GH-12277)
-	//loggers.push_back(memnew(RotatedFileLogger("user://logs/log.txt")));
+#ifdef DEBUG_ENABLED
+	// it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode
+	loggers.push_back(memnew(StdLogger));
+#endif
+	loggers.push_back(memnew(RotatedFileLogger("user://logs/log.txt")));
 	_set_logger(memnew(CompositeLogger(loggers)));
 }
 
@@ -402,6 +405,37 @@ void OSIPhone::alert(const String &p_alert, const String &p_title) {
 	iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
 }
 
+Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle) {
+	if (p_path.length() == 0) {
+		p_library_handle = RTLD_SELF;
+		return OK;
+	}
+	return OS_Unix::open_dynamic_library(p_path, p_library_handle);
+}
+
+Error OSIPhone::close_dynamic_library(void *p_library_handle) {
+	if (p_library_handle == RTLD_SELF) {
+		return OK;
+	}
+	return OS_Unix::close_dynamic_library(p_library_handle);
+}
+
+HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
+void register_dynamic_symbol(char *name, void *address) {
+	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
+	if (p_library_handle == RTLD_SELF) {
+		void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
+		if (ptr) {
+			p_symbol_handle = *ptr;
+			return OK;
+		}
+	}
+	return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
+}
+
 void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
 
 	video_mode = p_video_mode;
@@ -558,7 +592,36 @@ bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
 	return p_feature == "mobile" || p_feature == "etc" || p_feature == "pvrtc" || p_feature == "etc2";
 }
 
+// Initialization order between compilation units is not guaranteed,
+// so we use this as a hack to ensure certain code is called before
+// everything else, but after all units are initialized.
+typedef void (*init_callback)();
+static init_callback *ios_init_callbacks = NULL;
+static int ios_init_callbacks_count = 0;
+static int ios_init_callbacks_capacity = 0;
+
+void add_ios_init_callback(init_callback cb) {
+	if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
+		void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
+		if (new_ptr) {
+			ios_init_callbacks = (init_callback *)(new_ptr);
+			ios_init_callbacks_capacity += 32;
+		}
+	}
+	if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
+		ios_init_callbacks[ios_init_callbacks_count] = cb;
+		++ios_init_callbacks_count;
+	}
+}
+
 OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
+	for (int i = 0; i < ios_init_callbacks_count; ++i) {
+		ios_init_callbacks[i]();
+	}
+	free(ios_init_callbacks);
+	ios_init_callbacks = NULL;
+	ios_init_callbacks_count = 0;
+	ios_init_callbacks_capacity = 0;
 
 	main_loop = NULL;
 	visual_server = NULL;

+ 7 - 0
platform/iphone/os_iphone.h

@@ -60,6 +60,9 @@ private:
 		MAX_EVENTS = 64,
 	};
 
+	static HashMap<String, void *> dynamic_symbol_lookup_table;
+	friend void register_dynamic_symbol(char *name, void *address);
+
 	uint8_t supported_orientations;
 
 	VisualServer *visual_server;
@@ -153,6 +156,10 @@ public:
 
 	virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
 
+	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle);
+	virtual Error close_dynamic_library(void *p_library_handle);
+	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
+
 	virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
 	virtual VideoMode get_video_mode(int p_screen = 0) const;
 	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;

+ 1 - 0
platform/javascript/export/export.cpp

@@ -155,6 +155,7 @@ String EditorExportPlatformJavaScript::get_binary_extension() const {
 }
 
 Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+	ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
 
 	String custom_debug = p_preset->get("custom_template/debug");
 	String custom_release = p_preset->get("custom_template/release");

+ 1 - 0
platform/osx/export/export.cpp

@@ -288,6 +288,7 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
 }
 
 Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+	ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
 
 	String src_pkg_name;