Ver código fonte

Merge pull request #41230 from naithar/feature/pluggable-ios-modules

[iOS] [4.0] iOS Plugins
Rémi Verschelde 4 anos atrás
pai
commit
1626cfd937
47 arquivos alterados com 1341 adições e 411 exclusões
  1. 2 77
      misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
  2. 8 1
      modules/SCsub
  3. 18 0
      modules/arkit/arkit.gdip
  4. 2 2
      modules/arkit/arkit_module.cpp
  5. 1 1
      modules/arkit/arkit_module.h
  6. 1 11
      modules/camera/SCsub
  7. 1 1
      modules/camera/config.py
  8. 0 6
      modules/camera/register_types.cpp
  9. 15 0
      modules/camera_iphone/SCsub
  10. 18 0
      modules/camera_iphone/camera.gdip
  11. 0 0
      modules/camera_iphone/camera_ios.h
  12. 0 0
      modules/camera_iphone/camera_ios.mm
  13. 40 0
      modules/camera_iphone/camera_module.cpp
  14. 32 0
      modules/camera_iphone/camera_module.h
  15. 6 0
      modules/camera_iphone/config.py
  16. 15 0
      modules/gamecenter/SCsub
  17. 6 0
      modules/gamecenter/config.py
  18. 0 4
      modules/gamecenter/game_center.h
  19. 14 23
      modules/gamecenter/game_center.mm
  20. 35 0
      modules/gamecenter/game_center_delegate.h
  21. 45 0
      modules/gamecenter/game_center_delegate.mm
  22. 48 0
      modules/gamecenter/game_center_module.cpp
  23. 32 0
      modules/gamecenter/game_center_module.h
  24. 17 0
      modules/gamecenter/gamecenter.gdip
  25. 15 0
      modules/icloud/SCsub
  26. 6 0
      modules/icloud/config.py
  27. 17 0
      modules/icloud/icloud.gdip
  28. 0 4
      modules/icloud/icloud.h
  29. 1 13
      modules/icloud/icloud.mm
  30. 48 0
      modules/icloud/icloud_module.cpp
  31. 32 0
      modules/icloud/icloud_module.h
  32. 15 0
      modules/inappstore/SCsub
  33. 6 0
      modules/inappstore/config.py
  34. 0 4
      modules/inappstore/in_app_store.h
  35. 0 4
      modules/inappstore/in_app_store.mm
  36. 48 0
      modules/inappstore/in_app_store_module.cpp
  37. 32 0
      modules/inappstore/in_app_store_module.h
  38. 17 0
      modules/inappstore/inappstore.gdip
  39. 0 3
      platform/iphone/SCsub
  40. 1 0
      platform/iphone/app_delegate.mm
  41. 0 15
      platform/iphone/detect.py
  42. 474 186
      platform/iphone/export/export.cpp
  43. 3 12
      platform/iphone/os_iphone.h
  44. 3 32
      platform/iphone/os_iphone.mm
  45. 265 0
      platform/iphone/plugin/godot_plugin_config.h
  46. 1 2
      platform/iphone/view_controller.h
  47. 1 10
      platform/iphone/view_controller.mm

+ 2 - 77
misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj

@@ -153,81 +153,6 @@
 						DevelopmentTeam = $team_id;
 						ProvisioningStyle = Automatic;
 						SystemCapabilities = {
-							com.apple.AccessWiFi = {
-								enabled = $access_wifi;
-							};
-							com.apple.ApplePay = {
-								enabled = 0;
-							};
-							com.apple.ApplicationGroups.iOS = {
-								enabled = 0;
-							};
-							com.apple.AutoFillCredentialProvider = {
-								enabled = 0;
-							};
-							com.apple.BackgroundModes = {
-								enabled = 0;
-							};
-							com.apple.ClassKit = {
-								enabled = 0;
-							};
-							com.apple.DataProtection = {
-								enabled = 0;
-							};
-							com.apple.GameCenter.iOS = {
-								enabled = $game_center;
-							};
-							com.apple.HealthKit = {
-								enabled = 0;
-							};
-							com.apple.HomeKit = {
-								enabled = 0;
-							};
-							com.apple.HotspotConfiguration = {
-								enabled = 0;
-							};
-							com.apple.InAppPurchase = {
-								enabled = $in_app_purchases;
-							};
-							com.apple.InterAppAudio = {
-								enabled = 0;
-							};
-							com.apple.Keychain = {
-								enabled = 0;
-							};
-							com.apple.Maps.iOS = {
-								enabled = 0;
-							};
-							com.apple.Multipath = {
-								enabled = 0;
-							};
-							com.apple.NearFieldCommunicationTagReading = {
-								enabled = 0;
-							};
-							com.apple.NetworkExtensions.iOS = {
-								enabled = 0;
-							};
-							com.apple.Push = {
-								enabled = $push_notifications;
-							};
-							com.apple.SafariKeychain = {
-								enabled = 0;
-							};
-							com.apple.Siri = {
-								enabled = 0;
-							};
-							com.apple.VPNLite = {
-								enabled = 0;
-							};
-							com.apple.WAC = {
-								enabled = 0;
-							};
-							com.apple.Wallet = {
-								enabled = 0;
-							};
-							com.apple.iCloud = {
-								enabled = 0;
-							};
 						};
 					};
 				};
@@ -393,7 +318,7 @@
 				);
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
-					"$(PROJECT_DIR)",
+					"$(PROJECT_DIR)/**",
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = $bundle_identifier;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -423,7 +348,7 @@
 				);
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
-					"$(PROJECT_DIR)",
+					"$(PROJECT_DIR)/**",
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = $bundle_identifier;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 8 - 1
modules/SCsub

@@ -47,7 +47,14 @@ for name, path in env.module_list.items():
 
     # Some modules are not linked automatically but can be enabled optionally
     # on iOS, so we handle those specially.
-    if env["platform"] == "iphone" and name in ["arkit", "camera"]:
+    if env["platform"] == "iphone" and name in [
+        "arkit",
+        "camera",
+        "camera_iphone",
+        "gamecenter",
+        "inappstore",
+        "icloud",
+    ]:
         continue
 
     lib = env_modules.add_library("module_%s" % name, env.modules_sources)

+ 18 - 0
modules/arkit/arkit.gdip

@@ -0,0 +1,18 @@
+[config]
+name="ARKit"
+binary="arkit_lib.a"
+
+initialization="register_arkit_types"
+deinitialization="unregister_arkit_types"
+
+[dependencies]
+linked=[]
+embedded=[]
+system=["AVFoundation.framework", "ARKit.framework"]
+
+capabilities=["arkit"]
+
+files=[]
+
+[plist]
+NSCameraUsageDescription="Device camera is used for some functionality"

+ 2 - 2
modules/arkit/register_types.cpp → modules/arkit/arkit_module.cpp

@@ -1,5 +1,5 @@
 /*************************************************************************/
-/*  register_types.cpp                                                   */
+/*  arkit_module.cpp                                                     */
 /*************************************************************************/
 /*                       This file is part of:                           */
 /*                           GODOT ENGINE                                */
@@ -28,7 +28,7 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#include "register_types.h"
+#include "arkit_module.h"
 
 #include "arkit_interface.h"
 

+ 1 - 1
modules/arkit/register_types.h → modules/arkit/arkit_module.h

@@ -1,5 +1,5 @@
 /*************************************************************************/
-/*  register_types.h                                                     */
+/*  arkit_module.h                                                       */
 /*************************************************************************/
 /*                       This file is part of:                           */
 /*                           GODOT ENGINE                                */

+ 1 - 11
modules/camera/SCsub

@@ -5,17 +5,7 @@ Import("env_modules")
 
 env_camera = env_modules.Clone()
 
-if env["platform"] == "iphone":
-    # (iOS) Enable module support
-    env_camera.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
-
-    # (iOS) Build as separate static library
-    modules_sources = []
-    env_camera.add_source_files(modules_sources, "register_types.cpp")
-    env_camera.add_source_files(modules_sources, "camera_ios.mm")
-    mod_lib = env_modules.add_library("#bin/libgodot_camera_module" + env["LIBSUFFIX"], modules_sources)
-
-elif env["platform"] == "windows":
+if env["platform"] == "windows":
     env_camera.add_source_files(env.modules_sources, "register_types.cpp")
     env_camera.add_source_files(env.modules_sources, "camera_win.cpp")
 

+ 1 - 1
modules/camera/config.py

@@ -1,5 +1,5 @@
 def can_build(env, platform):
-    return platform == "iphone" or platform == "osx" or platform == "windows"
+    return platform == "osx" or platform == "windows"
 
 
 def configure(env):

+ 0 - 6
modules/camera/register_types.cpp

@@ -33,9 +33,6 @@
 #if defined(WINDOWS_ENABLED)
 #include "camera_win.h"
 #endif
-#if defined(IPHONE_ENABLED)
-#include "camera_ios.h"
-#endif
 #if defined(OSX_ENABLED)
 #include "camera_osx.h"
 #endif
@@ -44,9 +41,6 @@ void register_camera_types() {
 #if defined(WINDOWS_ENABLED)
 	CameraServer::make_default<CameraWindows>();
 #endif
-#if defined(IPHONE_ENABLED)
-	CameraServer::make_default<CameraIOS>();
-#endif
 #if defined(OSX_ENABLED)
 	CameraServer::make_default<CameraOSX>();
 #endif

+ 15 - 0
modules/camera_iphone/SCsub

@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_camera = env_modules.Clone()
+
+# (iOS) Enable module support
+env_camera.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
+# (iOS) Build as separate static library
+modules_sources = []
+env_camera.add_source_files(modules_sources, "*.cpp")
+env_camera.add_source_files(modules_sources, "*.mm")
+mod_lib = env_modules.add_library("#bin/libgodot_camera_module" + env["LIBSUFFIX"], modules_sources)

+ 18 - 0
modules/camera_iphone/camera.gdip

@@ -0,0 +1,18 @@
+[config]
+name="Camera"
+binary="camera_lib.a"
+
+initialization="register_camera_types"
+deinitialization="unregister_camera_types"
+
+[dependencies]
+linked=[]
+embedded=[]
+system=["AVFoundation.framework"]
+
+capabilities=[]
+
+files=[]
+
+[plist]
+NSCameraUsageDescription="Device camera is used for some functionality"

+ 0 - 0
modules/camera/camera_ios.h → modules/camera_iphone/camera_ios.h


+ 0 - 0
modules/camera/camera_ios.mm → modules/camera_iphone/camera_ios.mm


+ 40 - 0
modules/camera_iphone/camera_module.cpp

@@ -0,0 +1,40 @@
+/*************************************************************************/
+/*  camera_module.cpp                                                    */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+#include "camera_module.h"
+
+#include "camera_ios.h"
+
+void register_camera_types() {
+	CameraServer::make_default<CameraIOS>();
+}
+
+void unregister_camera_types() {
+}

+ 32 - 0
modules/camera_iphone/camera_module.h

@@ -0,0 +1,32 @@
+/*************************************************************************/
+/*  camera_module.h                                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+void register_camera_types();
+void unregister_camera_types();

+ 6 - 0
modules/camera_iphone/config.py

@@ -0,0 +1,6 @@
+def can_build(env, platform):
+    return platform == "iphone"
+
+
+def configure(env):
+    pass

+ 15 - 0
modules/gamecenter/SCsub

@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_gamecenter = env_modules.Clone()
+
+# (iOS) Enable module support
+env_gamecenter.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
+# (iOS) Build as separate static library
+modules_sources = []
+env_gamecenter.add_source_files(modules_sources, "*.cpp")
+env_gamecenter.add_source_files(modules_sources, "*.mm")
+mod_lib = env_modules.add_library("#bin/libgodot_gamecenter_module" + env["LIBSUFFIX"], modules_sources)

+ 6 - 0
modules/gamecenter/config.py

@@ -0,0 +1,6 @@
+def can_build(env, platform):
+    return platform == "iphone"
+
+
+def configure(env):
+    pass

+ 0 - 4
platform/iphone/game_center.h → modules/gamecenter/game_center.h

@@ -28,8 +28,6 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifdef GAME_CENTER_ENABLED
-
 #ifndef GAME_CENTER_H
 #define GAME_CENTER_H
 
@@ -71,5 +69,3 @@ public:
 };
 
 #endif
-
-#endif

+ 14 - 23
platform/iphone/game_center.mm → modules/gamecenter/game_center.mm

@@ -28,28 +28,15 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifdef GAME_CENTER_ENABLED
-
 #include "game_center.h"
+#import "platform/iphone/app_delegate.h"
 
-#ifdef __IPHONE_9_0
-
-#import <GameKit/GameKit.h>
-extern "C" {
-
-#else
-
-extern "C" {
+#import "game_center_delegate.h"
+#import "platform/iphone/view_controller.h"
 #import <GameKit/GameKit.h>
 
-#endif
-
-#import "app_delegate.h"
-};
-
-#import "view_controller.h"
-
 GameCenter *GameCenter::instance = NULL;
+GodotGameCenterDelegate *gameCenterDelegate = nil;
 
 void GameCenter::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("authenticate"), &GameCenter::authenticate);
@@ -76,7 +63,7 @@ Error GameCenter::authenticate() {
 	GKLocalPlayer *player = [GKLocalPlayer localPlayer];
 	ERR_FAIL_COND_V(![player respondsToSelector:@selector(authenticateHandler)], ERR_UNAVAILABLE);
 
-	ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
+	UIViewController *root_controller = [[UIApplication sharedApplication] delegate].window.rootViewController;
 	ERR_FAIL_COND_V(!root_controller, FAILED);
 
 	// This handler is called several times.  First when the view needs to be shown, then again
@@ -305,10 +292,10 @@ Error GameCenter::show_game_center(Dictionary p_params) {
 	GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
 	ERR_FAIL_COND_V(!controller, FAILED);
 
-	ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
+	UIViewController *root_controller = [[UIApplication sharedApplication] delegate].window.rootViewController;
 	ERR_FAIL_COND_V(!root_controller, FAILED);
 
-	controller.gameCenterDelegate = root_controller;
+	controller.gameCenterDelegate = gameCenterDelegate;
 	controller.viewState = view_state;
 	if (view_state == GKGameCenterViewControllerStateLeaderboards) {
 		controller.leaderboardIdentifier = nil;
@@ -382,8 +369,12 @@ GameCenter::GameCenter() {
 	ERR_FAIL_COND(instance != NULL);
 	instance = this;
 	authenticated = false;
-};
 
-GameCenter::~GameCenter() {}
+	gameCenterDelegate = [[GodotGameCenterDelegate alloc] init];
+};
 
-#endif
+GameCenter::~GameCenter() {
+	if (gameCenterDelegate) {
+		gameCenterDelegate = nil;
+	}
+}

+ 35 - 0
modules/gamecenter/game_center_delegate.h

@@ -0,0 +1,35 @@
+/*************************************************************************/
+/*  game_center_delegate.h                                               */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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 <GameKit/GameKit.h>
+
+@interface GodotGameCenterDelegate : NSObject <GKGameCenterControllerDelegate>
+
+@end

+ 45 - 0
modules/gamecenter/game_center_delegate.mm

@@ -0,0 +1,45 @@
+/*************************************************************************/
+/*  game_center_delegate.mm                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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 "game_center_delegate.h"
+
+#include "game_center.h"
+
+@implementation GodotGameCenterDelegate
+
+- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
+	//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
+	if (GameCenter::get_singleton()) {
+		GameCenter::get_singleton()->game_center_closed();
+	}
+	[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
+}
+
+@end

+ 48 - 0
modules/gamecenter/game_center_module.cpp

@@ -0,0 +1,48 @@
+/*************************************************************************/
+/*  game_center_module.cpp                                               */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+#include "game_center_module.h"
+
+#include "core/config/engine.h"
+
+#include "game_center.h"
+
+GameCenter *game_center;
+
+void register_gamecenter_types() {
+	game_center = memnew(GameCenter);
+	Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
+}
+
+void unregister_gamecenter_types() {
+	if (game_center) {
+		memdelete(game_center);
+	}
+}

+ 32 - 0
modules/gamecenter/game_center_module.h

@@ -0,0 +1,32 @@
+/*************************************************************************/
+/*  game_center_module.h                                                 */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+void register_gamecenter_types();
+void unregister_gamecenter_types();

+ 17 - 0
modules/gamecenter/gamecenter.gdip

@@ -0,0 +1,17 @@
+[config]
+name="GameCenter"
+binary="gamecenter_lib.a"
+
+initialization="register_gamecenter_types"
+deinitialization="unregister_gamecenter_types"
+
+[dependencies]
+linked=[]
+embedded=[]
+system=["GameKit.framework"]
+
+capabilities=["gamekit"]
+
+files=[]
+
+[plist]

+ 15 - 0
modules/icloud/SCsub

@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_icloud = env_modules.Clone()
+
+# (iOS) Enable module support
+env_icloud.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
+# (iOS) Build as separate static library
+modules_sources = []
+env_icloud.add_source_files(modules_sources, "*.cpp")
+env_icloud.add_source_files(modules_sources, "*.mm")
+mod_lib = env_modules.add_library("#bin/libgodot_icloud_module" + env["LIBSUFFIX"], modules_sources)

+ 6 - 0
modules/icloud/config.py

@@ -0,0 +1,6 @@
+def can_build(env, platform):
+    return platform == "iphone"
+
+
+def configure(env):
+    pass

+ 17 - 0
modules/icloud/icloud.gdip

@@ -0,0 +1,17 @@
+[config]
+name="iCloud"
+binary="icloud_lib.a"
+
+initialization="register_icloud_types"
+deinitialization="unregister_icloud_types"
+
+[dependencies]
+linked=[]
+embedded=[]
+system=[]
+
+capabilities=[]
+
+files=[]
+
+[plist]

+ 0 - 4
platform/iphone/icloud.h → modules/icloud/icloud.h

@@ -28,8 +28,6 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifdef ICLOUD_ENABLED
-
 #ifndef ICLOUD_H
 #define ICLOUD_H
 
@@ -60,5 +58,3 @@ public:
 };
 
 #endif
-
-#endif

+ 1 - 13
platform/iphone/icloud.mm → modules/icloud/icloud.mm

@@ -28,22 +28,12 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifdef ICLOUD_ENABLED
-
 #include "icloud.h"
 
-#ifndef __IPHONE_9_0
-extern "C" {
-#endif
-
-#import "app_delegate.h"
+#import "platform/iphone/app_delegate.h"
 
 #import <Foundation/Foundation.h>
 
-#ifndef __IPHONE_9_0
-};
-#endif
-
 ICloud *ICloud::instance = NULL;
 
 void ICloud::_bind_methods() {
@@ -353,5 +343,3 @@ ICloud::ICloud() {
 }
 
 ICloud::~ICloud() {}
-
-#endif

+ 48 - 0
modules/icloud/icloud_module.cpp

@@ -0,0 +1,48 @@
+/*************************************************************************/
+/*  icloud_module.cpp                                                    */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+#include "icloud_module.h"
+
+#include "core/config/engine.h"
+
+#include "icloud.h"
+
+ICloud *icloud;
+
+void register_icloud_types() {
+	icloud = memnew(ICloud);
+	Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
+}
+
+void unregister_icloud_types() {
+	if (icloud) {
+		memdelete(icloud);
+	}
+}

+ 32 - 0
modules/icloud/icloud_module.h

@@ -0,0 +1,32 @@
+/*************************************************************************/
+/*  icloud_module.h                                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+void register_icloud_types();
+void unregister_icloud_types();

+ 15 - 0
modules/inappstore/SCsub

@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_inappstore = env_modules.Clone()
+
+# (iOS) Enable module support
+env_inappstore.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
+# (iOS) Build as separate static library
+modules_sources = []
+env_inappstore.add_source_files(modules_sources, "*.cpp")
+env_inappstore.add_source_files(modules_sources, "*.mm")
+mod_lib = env_modules.add_library("#bin/libgodot_inappstore_module" + env["LIBSUFFIX"], modules_sources)

+ 6 - 0
modules/inappstore/config.py

@@ -0,0 +1,6 @@
+def can_build(env, platform):
+    return platform == "iphone"
+
+
+def configure(env):
+    pass

+ 0 - 4
platform/iphone/in_app_store.h → modules/inappstore/in_app_store.h

@@ -28,8 +28,6 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifdef STOREKIT_ENABLED
-
 #ifndef IN_APP_STORE_H
 #define IN_APP_STORE_H
 
@@ -77,5 +75,3 @@ public:
 };
 
 #endif
-
-#endif

+ 0 - 4
platform/iphone/in_app_store.mm → modules/inappstore/in_app_store.mm

@@ -28,8 +28,6 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifdef STOREKIT_ENABLED
-
 #include "in_app_store.h"
 
 #import <Foundation/Foundation.h>
@@ -411,5 +409,3 @@ InAppStore::~InAppStore() {
 	[[SKPaymentQueue defaultQueue] removeTransactionObserver:transactions_observer];
 	transactions_observer = nil;
 }
-
-#endif

+ 48 - 0
modules/inappstore/in_app_store_module.cpp

@@ -0,0 +1,48 @@
+/*************************************************************************/
+/*  in_app_store_module.cpp                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+#include "in_app_store_module.h"
+
+#include "core/config/engine.h"
+
+#include "in_app_store.h"
+
+InAppStore *store_kit;
+
+void register_inappstore_types() {
+	store_kit = memnew(InAppStore);
+	Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
+}
+
+void unregister_inappstore_types() {
+	if (store_kit) {
+		memdelete(store_kit);
+	}
+}

+ 32 - 0
modules/inappstore/in_app_store_module.h

@@ -0,0 +1,32 @@
+/*************************************************************************/
+/*  in_app_store_module.h                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+void register_inappstore_types();
+void unregister_inappstore_types();

+ 17 - 0
modules/inappstore/inappstore.gdip

@@ -0,0 +1,17 @@
+[config]
+name="InAppStore"
+binary="inappstore_lib.a"
+
+initialization="register_inappstore_types"
+deinitialization="unregister_inappstore_types"
+
+[dependencies]
+linked=[]
+embedded=[]
+system=["StoreKit.framework"]
+
+capabilities=[]
+
+files=[]
+
+[plist]

+ 0 - 3
platform/iphone/SCsub

@@ -8,9 +8,6 @@ iphone_lib = [
     "main.m",
     "app_delegate.mm",
     "view_controller.mm",
-    "game_center.mm",
-    "in_app_store.mm",
-    "icloud.mm",
     "ios.mm",
     "vulkan_context_iphone.mm",
     "display_server_iphone.mm",

+ 1 - 0
platform/iphone/app_delegate.mm

@@ -36,6 +36,7 @@
 #include "os_iphone.h"
 #import "view_controller.h"
 
+#import <AVFoundation/AVFoundation.h>
 #import <AudioToolbox/AudioServices.h>
 
 #define kRenderingFrequency 60

+ 0 - 15
platform/iphone/detect.py

@@ -35,9 +35,6 @@ def get_opts():
             " validation layers)",
             False,
         ),
-        BoolVariable("game_center", "Support for game center", True),
-        BoolVariable("store_kit", "Support for in-app store", True),
-        BoolVariable("icloud", "Support for iCloud", True),
         BoolVariable("ios_exceptions", "Enable exceptions", False),
         ("ios_triple", "Triple for ios toolchain", ""),
     ]
@@ -222,18 +219,6 @@ def configure(env):
         ]
     )
 
-    # Feature options
-    if env["game_center"]:
-        env.Append(CPPDEFINES=["GAME_CENTER_ENABLED"])
-        env.Append(LINKFLAGS=["-framework", "GameKit"])
-
-    if env["store_kit"]:
-        env.Append(CPPDEFINES=["STOREKIT_ENABLED"])
-        env.Append(LINKFLAGS=["-framework", "StoreKit"])
-
-    if env["icloud"]:
-        env.Append(CPPDEFINES=["ICLOUD_ENABLED"])
-
     env.Prepend(
         CPPPATH=[
             "$IPHONESDK/usr/include",

+ 474 - 186
platform/iphone/export/export.cpp

@@ -43,6 +43,7 @@
 #include "editor/editor_settings.h"
 #include "main/splash.gen.h"
 #include "platform/iphone/logo.gen.h"
+#include "platform/iphone/plugin/godot_plugin_config.h"
 #include "string.h"
 
 #include <sys/stat.h>
@@ -54,6 +55,13 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
 
 	Ref<ImageTexture> logo;
 
+	// Plugins
+	volatile bool plugins_changed;
+	Thread *check_for_changes_thread;
+	volatile bool quit_request;
+	Mutex plugins_lock;
+	Vector<PluginConfig> plugins;
+
 	typedef Error (*FileHandler)(String p_file, void *p_userdata);
 	static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata);
 	static Error _codesign(String p_file, void *p_userdata);
@@ -70,6 +78,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
 		String modules_fileref;
 		String modules_buildphase;
 		String modules_buildgrp;
+		Vector<String> capabilities;
 	};
 	struct ExportArchitecture {
 		String name;
@@ -102,7 +111,9 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
 
 	void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, 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, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
+	Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
 	Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
+	Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
 
 	bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
 		String pname = p_package;
@@ -127,6 +138,40 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
 		return true;
 	}
 
+	static void _check_for_changes_poll_thread(void *ud) {
+		EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud;
+
+		while (!ea->quit_request) {
+			// Nothing to do if we already know the plugins have changed.
+			if (!ea->plugins_changed) {
+				MutexLock lock(ea->plugins_lock);
+
+				Vector<PluginConfig> loaded_plugins = get_plugins();
+
+				if (ea->plugins.size() != loaded_plugins.size()) {
+					ea->plugins_changed = true;
+				} else {
+					for (int i = 0; i < ea->plugins.size(); i++) {
+						if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) {
+							ea->plugins_changed = true;
+							break;
+						}
+					}
+				}
+			}
+
+			uint64_t wait = 3000000;
+			uint64_t time = OS::get_singleton()->get_ticks_usec();
+			while (OS::get_singleton()->get_ticks_usec() - time < wait) {
+				OS::get_singleton()->delay_usec(300000);
+
+				if (ea->quit_request) {
+					break;
+				}
+			}
+		}
+	}
+
 protected:
 	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
 	virtual void get_export_options(List<ExportOption> *r_options) override;
@@ -136,13 +181,21 @@ public:
 	virtual String get_os_name() const override { return "iOS"; }
 	virtual Ref<Texture2D> get_logo() const override { return logo; }
 
+	virtual bool should_update_export_options() override {
+		bool export_options_changed = plugins_changed;
+		if (export_options_changed) {
+			// don't clear unless we're reporting true, to avoid race
+			plugins_changed = false;
+		}
+		return export_options_changed;
+	}
+
 	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
 		List<String> list;
 		list.push_back("ipa");
 		return list;
 	}
 	virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
-	virtual void add_module_code(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &p_name, const String &p_fid, const String &p_gid);
 
 	virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
 
@@ -156,6 +209,85 @@ public:
 
 	EditorExportPlatformIOS();
 	~EditorExportPlatformIOS();
+
+	/// List the gdip files in the directory specified by the p_path parameter.
+	static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) {
+		Vector<String> dir_files;
+		DirAccessRef da = DirAccess::open(p_path);
+		if (da) {
+			da->list_dir_begin();
+			while (true) {
+				String file = da->get_next();
+				if (file.empty()) {
+					break;
+				}
+
+				if (file == "." || file == "..") {
+					continue;
+				}
+
+				if (da->current_is_hidden()) {
+					continue;
+				}
+
+				if (da->current_is_dir()) {
+					if (p_check_directories) {
+						Vector<String> directory_files = list_plugin_config_files(p_path.plus_file(file), false);
+						for (int i = 0; i < directory_files.size(); ++i) {
+							dir_files.push_back(file.plus_file(directory_files[i]));
+						}
+					}
+
+					continue;
+				}
+
+				if (file.ends_with(PLUGIN_CONFIG_EXT)) {
+					dir_files.push_back(file);
+				}
+			}
+			da->list_dir_end();
+		}
+
+		return dir_files;
+	}
+
+	static Vector<PluginConfig> get_plugins() {
+		Vector<PluginConfig> loaded_plugins;
+
+		String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins");
+
+		if (DirAccess::exists(plugins_dir)) {
+			Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true);
+
+			if (!plugins_filenames.empty()) {
+				Ref<ConfigFile> config_file = memnew(ConfigFile);
+				for (int i = 0; i < plugins_filenames.size(); i++) {
+					PluginConfig config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
+					if (config.valid_config) {
+						loaded_plugins.push_back(config);
+					} else {
+						print_error("Invalid plugin config file " + plugins_filenames[i]);
+					}
+				}
+			}
+		}
+
+		return loaded_plugins;
+	}
+
+	static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
+		Vector<PluginConfig> enabled_plugins;
+		Vector<PluginConfig> all_plugins = get_plugins();
+		for (int i = 0; i < all_plugins.size(); i++) {
+			PluginConfig plugin = all_plugins[i];
+			bool enabled = p_presets->get("plugins/" + plugin.name);
+			if (enabled) {
+				enabled_plugins.push_back(plugin);
+			}
+		}
+
+		return enabled_plugins;
+	}
 };
 
 void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
@@ -224,12 +356,16 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
 	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::BOOL, "capabilities/arkit"), false));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/camera"), false));
+	Vector<PluginConfig> found_plugins = get_plugins();
+
+	for (int i = 0; i < found_plugins.size(); i++) {
+		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false));
+	}
+
+	plugins_changed = false;
+	plugins = found_plugins;
 
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/game_center"), true));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/in_app_purchases"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false));
 
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false));
@@ -345,18 +481,6 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
 			strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "<true/>" : "<false/>") + "\n";
 		} else if (lines[i].find("$docs_sharing") != -1) {
 			strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "<true/>" : "<false/>") + "\n";
-		} else if (lines[i].find("$access_wifi") != -1) {
-			bool is_on = p_preset->get("capabilities/access_wifi");
-			strnew += lines[i].replace("$access_wifi", is_on ? "1" : "0") + "\n";
-		} else if (lines[i].find("$game_center") != -1) {
-			bool is_on = p_preset->get("capabilities/game_center");
-			strnew += lines[i].replace("$game_center", is_on ? "1" : "0") + "\n";
-		} else if (lines[i].find("$in_app_purchases") != -1) {
-			bool is_on = p_preset->get("capabilities/in_app_purchases");
-			strnew += lines[i].replace("$in_app_purchases", is_on ? "1" : "0") + "\n";
-		} else if (lines[i].find("$push_notifications") != -1) {
-			bool is_on = p_preset->get("capabilities/push_notifications");
-			strnew += lines[i].replace("$push_notifications", is_on ? "1" : "0") + "\n";
 		} else if (lines[i].find("$entitlements_push_notifications") != -1) {
 			bool is_on = p_preset->get("capabilities/push_notifications");
 			strnew += lines[i].replace("$entitlements_push_notifications", is_on ? "<key>aps-environment</key><string>development</string>" : "") + "\n";
@@ -366,15 +490,14 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
 			// I've removed armv7 as we can run on 64bit only devices
 			// Note that capabilities listed here are requirements for the app to be installed.
 			// They don't enable anything.
+			Vector<String> capabilities_list = p_config.capabilities;
 
-			if ((bool)p_preset->get("capabilities/arkit")) {
-				capabilities += "<string>arkit</string>\n";
-			}
-			if ((bool)p_preset->get("capabilities/game_center")) {
-				capabilities += "<string>gamekit</string>\n";
+			if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) {
+				capabilities_list.push_back("wifi");
 			}
-			if ((bool)p_preset->get("capabilities/access_wifi")) {
-				capabilities += "<string>wifi</string>\n";
+
+			for (int idx = 0; idx < capabilities_list.size(); idx++) {
+				capabilities += "<string>" + capabilities_list[idx] + "</string>\n";
 			}
 
 			strnew += lines[i].replace("$required_device_capabilities", capabilities);
@@ -984,28 +1107,6 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
 	// Note, frameworks like gamekit are always included in our project.pbxprof file
 	// even if turned off in capabilities.
 
-	// We do need our ARKit framework
-	if ((bool)p_preset->get("capabilities/arkit")) {
-		String build_id = (++current_id).str();
-		String ref_id = (++current_id).str();
-
-		if (pbx_frameworks_build.length() > 0) {
-			pbx_frameworks_build += ",\n";
-			pbx_frameworks_refs += ",\n";
-		}
-
-		pbx_frameworks_build += build_id;
-		pbx_frameworks_refs += ref_id;
-
-		Dictionary format_dict;
-		format_dict["build_id"] = build_id;
-		format_dict["ref_id"] = ref_id;
-		format_dict["name"] = "ARKit.framework";
-		format_dict["file_path"] = "System/Library/Frameworks/ARKit.framework";
-		format_dict["file_type"] = "wrapper.framework";
-		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);
@@ -1021,142 +1122,179 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
 	}
 }
 
-Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
+Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
 	DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
+
 	String binary_name = p_out_dir.get_file().get_basename();
 
-	ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
-	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, p_should_embed };
-			r_exported_assets.push_back(exported_asset);
+	DirAccess *da = DirAccess::create_for_path(p_asset);
+	if (!da) {
+		memdelete(filesystem_da);
+		ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + ".");
+	}
+	bool file_exists = da->file_exists(p_asset);
+	bool dir_exists = da->dir_exists(p_asset);
+	if (!file_exists && !dir_exists) {
+		memdelete(da);
+		memdelete(filesystem_da);
+		return ERR_FILE_NOT_FOUND;
+	}
+
+	String base_dir = p_asset.get_base_dir().replace("res://", "");
+	String destination_dir;
+	String destination;
+	String asset_path;
+
+	bool create_framework = false;
+
+	if (p_is_framework && p_asset.ends_with(".dylib")) {
+		// For iOS we need to turn .dylib into .framework
+		// to be able to send application to AppStore
+		asset_path = String("dylibs").plus_file(base_dir);
+
+		String file_name;
+
+		if (!p_custom_file_name) {
+			file_name = p_asset.get_basename().get_file();
 		} else {
-			DirAccess *da = DirAccess::create_for_path(asset);
-			if (!da) {
-				memdelete(filesystem_da);
-				ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + asset + ".");
-			}
-			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;
-			}
+			file_name = *p_custom_file_name;
+		}
 
-			String base_dir = asset.get_base_dir().replace("res://", "");
-			String destination_dir;
-			String destination;
-			String asset_path;
+		String framework_name = file_name + ".framework";
 
-			bool create_framework = false;
+		asset_path = asset_path.plus_file(framework_name);
+		destination_dir = p_out_dir.plus_file(asset_path);
+		destination = destination_dir.plus_file(file_name);
+		create_framework = true;
+	} else if (p_is_framework && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) {
+		asset_path = String("dylibs").plus_file(base_dir);
 
-			if (p_is_framework && asset.ends_with(".dylib")) {
-				// For iOS we need to turn .dylib into .framework
-				// to be able to send application to AppStore
-				asset_path = String("dylibs").plus_file(base_dir);
+		String file_name;
 
-				String file_name = asset.get_basename().get_file();
-				String framework_name = file_name + ".framework";
+		if (!p_custom_file_name) {
+			file_name = p_asset.get_file();
+		} else {
+			file_name = *p_custom_file_name;
+		}
 
-				asset_path = asset_path.plus_file(framework_name);
-				destination_dir = p_out_dir.plus_file(asset_path);
-				destination = destination_dir.plus_file(file_name);
-				create_framework = true;
-			} else if (p_is_framework && (asset.ends_with(".framework") || asset.ends_with(".xcframework"))) {
-				asset_path = String("dylibs").plus_file(base_dir);
+		asset_path = asset_path.plus_file(file_name);
+		destination_dir = p_out_dir.plus_file(asset_path);
+		destination = destination_dir;
+	} else {
+		asset_path = base_dir;
 
-				String file_name = asset.get_file();
-				asset_path = asset_path.plus_file(file_name);
-				destination_dir = p_out_dir.plus_file(asset_path);
-				destination = destination_dir;
-			} else {
-				asset_path = base_dir;
+		String file_name;
 
-				String file_name = asset.get_file();
-				destination_dir = p_out_dir.plus_file(asset_path);
-				asset_path = asset_path.plus_file(file_name);
-				destination = p_out_dir.plus_file(asset_path);
-			}
+		if (!p_custom_file_name) {
+			file_name = p_asset.get_file();
+		} else {
+			file_name = *p_custom_file_name;
+		}
 
-			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;
-				}
-			}
+		destination_dir = p_out_dir.plus_file(asset_path);
+		asset_path = asset_path.plus_file(file_name);
+		destination = p_out_dir.plus_file(asset_path);
+	}
 
-			Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination);
+	if (!filesystem_da->dir_exists(destination_dir)) {
+		Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+		if (make_dir_err) {
 			memdelete(da);
-			if (err) {
-				memdelete(filesystem_da);
-				return err;
-			}
-			IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed };
-			r_exported_assets.push_back(exported_asset);
+			memdelete(filesystem_da);
+			return make_dir_err;
+		}
+	}
 
-			if (create_framework) {
-				String file_name = asset.get_basename().get_file();
-				String framework_name = file_name + ".framework";
+	Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+	memdelete(da);
+	if (err) {
+		memdelete(filesystem_da);
+		return err;
+	}
+	IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed };
+	r_exported_assets.push_back(exported_asset);
 
-				// Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
-				{
-					List<String> install_name_args;
-					install_name_args.push_back("-id");
-					install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name));
-					install_name_args.push_back(destination);
+	if (create_framework) {
+		String file_name;
 
-					OS::get_singleton()->execute("install_name_tool", install_name_args, true);
-				}
+		if (!p_custom_file_name) {
+			file_name = p_asset.get_basename().get_file();
+		} else {
+			file_name = *p_custom_file_name;
+		}
 
-				// Creating Info.plist
-				{
-					String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-											   "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
-											   "<plist version=\"1.0\">\n"
-											   "<dict>\n"
-											   "<key>CFBundleShortVersionString</key>\n"
-											   "<string>1.0</string>\n"
-											   "<key>CFBundleIdentifier</key>\n"
-											   "<string>com.gdnative.framework.$name</string>\n"
-											   "<key>CFBundleName</key>\n"
-											   "<string>$name</string>\n"
-											   "<key>CFBundleExecutable</key>\n"
-											   "<string>$name</string>\n"
-											   "<key>DTPlatformName</key>\n"
-											   "<string>iphoneos</string>\n"
-											   "<key>CFBundleInfoDictionaryVersion</key>\n"
-											   "<string>6.0</string>\n"
-											   "<key>CFBundleVersion</key>\n"
-											   "<string>1</string>\n"
-											   "<key>CFBundlePackageType</key>\n"
-											   "<string>FMWK</string>\n"
-											   "<key>MinimumOSVersion</key>\n"
-											   "<string>10.0</string>\n"
-											   "</dict>\n"
-											   "</plist>";
-
-					String info_plist = info_plist_format.replace("$name", file_name);
-
-					FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE);
-					if (f) {
-						f->store_string(info_plist);
-						f->close();
-						memdelete(f);
-					}
-				}
+		String framework_name = file_name + ".framework";
+
+		// Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
+		{
+			List<String> install_name_args;
+			install_name_args.push_back("-id");
+			install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name));
+			install_name_args.push_back(destination);
+
+			OS::get_singleton()->execute("install_name_tool", install_name_args, true);
+		}
+
+		// Creating Info.plist
+		{
+			String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+									   "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+									   "<plist version=\"1.0\">\n"
+									   "<dict>\n"
+									   "<key>CFBundleShortVersionString</key>\n"
+									   "<string>1.0</string>\n"
+									   "<key>CFBundleIdentifier</key>\n"
+									   "<string>com.gdnative.framework.$name</string>\n"
+									   "<key>CFBundleName</key>\n"
+									   "<string>$name</string>\n"
+									   "<key>CFBundleExecutable</key>\n"
+									   "<string>$name</string>\n"
+									   "<key>DTPlatformName</key>\n"
+									   "<string>iphoneos</string>\n"
+									   "<key>CFBundleInfoDictionaryVersion</key>\n"
+									   "<string>6.0</string>\n"
+									   "<key>CFBundleVersion</key>\n"
+									   "<string>1</string>\n"
+									   "<key>CFBundlePackageType</key>\n"
+									   "<string>FMWK</string>\n"
+									   "<key>MinimumOSVersion</key>\n"
+									   "<string>10.0</string>\n"
+									   "</dict>\n"
+									   "</plist>";
+
+			String info_plist = info_plist_format.replace("$name", file_name);
+
+			FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE);
+			if (f) {
+				f->store_string(info_plist);
+				f->close();
+				memdelete(f);
 			}
 		}
 	}
+
 	memdelete(filesystem_da);
 
 	return OK;
 }
 
+Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
+	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, p_should_embed };
+			r_exported_assets.push_back(exported_asset);
+		} else {
+			Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
+			ERR_FAIL_COND_V(err, err);
+		}
+	}
+
+	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++) {
@@ -1202,20 +1340,173 @@ Vector<String> EditorExportPlatformIOS::_get_preset_architectures(const Ref<Edit
 	return enabled_archs;
 }
 
-void EditorExportPlatformIOS::add_module_code(const Ref<EditorExportPreset> &p_preset, EditorExportPlatformIOS::IOSConfigData &p_config_data, const String &p_name, const String &p_fid, const String &p_gid) {
-	if ((bool)p_preset->get("capabilities/" + p_name)) {
-		//add module static library
-		print_line("ADDING MODULE: " + p_name);
+Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug) {
+	String plugin_definition_cpp_code;
+	String plugin_initialization_cpp_code;
+	String plugin_deinitialization_cpp_code;
 
-		p_config_data.modules_buildfile += p_gid + " /* libgodot_" + p_name + "_module.a in Frameworks */ = {isa = PBXBuildFile; fileRef = " + p_fid + " /* libgodot_" + p_name + "_module.a */; };\n\t\t";
-		p_config_data.modules_fileref += p_fid + " /* libgodot_" + p_name + "_module.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = godot_" + p_name + "_module ; path = \"libgodot_" + p_name + "_module.a\"; sourceTree = \"<group>\"; };\n\t\t";
-		p_config_data.modules_buildphase += p_gid + " /* libgodot_" + p_name + "_module.a */,\n\t\t\t\t";
-		p_config_data.modules_buildgrp += p_fid + " /* libgodot_" + p_name + "_module.a */,\n\t\t\t\t";
-	} else {
-		//add stub function for disabled module
-		p_config_data.cpp_code += "void register_" + p_name + "_types() { /*stub*/ };\n";
-		p_config_data.cpp_code += "void unregister_" + p_name + "_types() { /*stub*/ };\n";
+	Vector<String> plugin_linked_dependencies;
+	Vector<String> plugin_embedded_dependencies;
+	Vector<String> plugin_files;
+
+	Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
+
+	Vector<String> added_linked_dependenciy_names;
+	Vector<String> added_embedded_dependenciy_names;
+	HashMap<String, String> plist_values;
+
+	Error err;
+
+	for (int i = 0; i < enabled_plugins.size(); i++) {
+		PluginConfig plugin = enabled_plugins[i];
+
+		// Export plugin binary.
+		if (!plugin.supports_targets) {
+			err = _copy_asset(dest_dir, plugin.binary, nullptr, true, true, r_exported_assets);
+		} else {
+			String plugin_binary_dir = plugin.binary.get_base_dir();
+			String plugin_name_prefix = plugin.binary.get_basename().get_file();
+			String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + ".a";
+			String result_file_name = plugin.binary.get_file();
+
+			err = _copy_asset(dest_dir, plugin_binary_dir.plus_file(plugin_file), &result_file_name, true, true, r_exported_assets);
+		}
+
+		ERR_FAIL_COND_V(err, err);
+
+		// Adding dependencies.
+		// Use separate container for names to check for duplicates.
+		for (int j = 0; j < plugin.linked_dependencies.size(); j++) {
+			String dependency = plugin.linked_dependencies[j];
+			String name = dependency.get_file();
+
+			if (added_linked_dependenciy_names.has(name)) {
+				continue;
+			}
+
+			added_linked_dependenciy_names.push_back(name);
+			plugin_linked_dependencies.push_back(dependency);
+		}
+
+		for (int j = 0; j < plugin.system_dependencies.size(); j++) {
+			String dependency = plugin.system_dependencies[j];
+			String name = dependency.get_file();
+
+			if (added_linked_dependenciy_names.has(name)) {
+				continue;
+			}
+
+			added_linked_dependenciy_names.push_back(name);
+			plugin_linked_dependencies.push_back(dependency);
+		}
+
+		for (int j = 0; j < plugin.embedded_dependencies.size(); j++) {
+			String dependency = plugin.embedded_dependencies[j];
+			String name = dependency.get_file();
+
+			if (added_embedded_dependenciy_names.has(name)) {
+				continue;
+			}
+
+			added_embedded_dependenciy_names.push_back(name);
+			plugin_embedded_dependencies.push_back(dependency);
+		}
+
+		plugin_files.append_array(plugin.files_to_copy);
+
+		// Capabilities
+		// Also checking for duplicates.
+		for (int j = 0; j < plugin.capabilities.size(); j++) {
+			String capability = plugin.capabilities[j];
+
+			if (p_config_data.capabilities.has(capability)) {
+				continue;
+			}
+
+			p_config_data.capabilities.push_back(capability);
+		}
+
+		// Plist
+		// Using hash map container to remove duplicates
+		const String *K = nullptr;
+
+		while ((K = plugin.plist.next(K))) {
+			String key = *K;
+			String value = plugin.plist[key];
+
+			if (key.empty() || value.empty()) {
+				continue;
+			}
+
+			plist_values[key] = value;
+		}
+
+		// CPP Code
+		String definition_comment = "// Plugin: " + plugin.name + "\n";
+		String initialization_method = plugin.initialization_method + "();\n";
+		String deinitialization_method = plugin.deinitialization_method + "();\n";
+
+		plugin_definition_cpp_code += definition_comment +
+									  "extern void " + initialization_method +
+									  "extern void " + deinitialization_method + "\n";
+
+		plugin_initialization_cpp_code += "\t" + initialization_method;
+		plugin_deinitialization_cpp_code += "\t" + deinitialization_method;
+	}
+
+	// Updating `Info.plist`
+	{
+		const String *K = nullptr;
+		while ((K = plist_values.next(K))) {
+			String key = *K;
+			String value = plist_values[key];
+
+			if (key.empty() || value.empty()) {
+				continue;
+			}
+
+			p_config_data.plist_content += "<key>" + key + "</key><string>" + value + "</string>\n";
+		}
 	}
+
+	// Export files
+	{
+		// Export linked plugin dependency
+		err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+
+		// Export embedded plugin dependency
+		err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+
+		// Export plugin files
+		err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+	}
+
+	// Update CPP
+	{
+		Dictionary plugin_format;
+		plugin_format["definition"] = plugin_definition_cpp_code;
+		plugin_format["initialization"] = plugin_initialization_cpp_code;
+		plugin_format["deinitialization"] = plugin_deinitialization_cpp_code;
+
+		String plugin_cpp_code = "\n// Godot Plugins\n"
+								 "void godot_ios_plugins_initialize();\n"
+								 "void godot_ios_plugins_deinitialize();\n"
+								 "// Exported Plugins\n\n"
+								 "$definition"
+								 "// Use Plugins\n"
+								 "void godot_ios_plugins_initialize() {\n"
+								 "$initialization"
+								 "}\n\n"
+								 "void godot_ios_plugins_deinitialize() {\n"
+								 "$deinitialization"
+								 "}\n";
+
+		p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_");
+	}
+	return OK;
 }
 
 Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
@@ -1324,9 +1615,12 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 		"",
 		"",
 		"",
-		""
+		"",
+		Vector<String>()
 	};
 
+	Vector<IOSExportAsset> assets;
+
 	DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir);
 	ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE);
 
@@ -1339,8 +1633,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 		return ERR_CANT_OPEN;
 	}
 
-	add_module_code(p_preset, config_data, "arkit", "F9B95E6E2391205500AF0000", "F9C95E812391205C00BF0000");
-	add_module_code(p_preset, config_data, "camera", "F9B95E6E2391205500AF0001", "F9C95E812391205C00BF0001");
+	err = _export_ios_plugins(p_preset, config_data, dest_dir + binary_name, assets, p_debug);
+	ERR_FAIL_COND_V(err, err);
 
 	//export rest of the files
 	int ret = unzGoToFirstFile(src_pkg_zip);
@@ -1382,21 +1676,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 			is_execute = true;
 #endif
 			file = "godot_ios.a";
-		} else if (file.begins_with("libgodot_arkit")) {
-			if ((bool)p_preset->get("capabilities/arkit") && file.ends_with(String(p_debug ? "debug" : "release") + ".fat.a")) {
-				file = "libgodot_arkit_module.a";
-			} else {
-				ret = unzGoToNextFile(src_pkg_zip);
-				continue; //ignore!
-			}
-		} else if (file.begins_with("libgodot_camera")) {
-			if ((bool)p_preset->get("capabilities/camera") && file.ends_with(String(p_debug ? "debug" : "release") + ".fat.a")) {
-				file = "libgodot_camera_module.a";
-			} else {
-				ret = unzGoToNextFile(src_pkg_zip);
-				continue; //ignore!
-			}
 		}
+
 		if (file == project_file) {
 			project_file_data = data;
 		}
@@ -1530,7 +1811,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 	}
 
 	print_line("Exporting additional assets");
-	Vector<IOSExportAsset> assets;
 	_export_additional_assets(dest_dir + binary_name, libraries, assets);
 	_add_assets_to_project(p_preset, project_file_data, assets);
 	String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj";
@@ -1665,9 +1945,17 @@ EditorExportPlatformIOS::EditorExportPlatformIOS() {
 	Ref<Image> img = memnew(Image(_iphone_logo));
 	logo.instance();
 	logo->create_from_image(img);
+
+	plugins_changed = true;
+	quit_request = false;
+
+	check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this);
 }
 
 EditorExportPlatformIOS::~EditorExportPlatformIOS() {
+	quit_request = true;
+	Thread::wait_to_finish(check_for_changes_thread);
+	memdelete(check_for_changes_thread);
 }
 
 void register_iphone_exporter() {

+ 3 - 12
platform/iphone/os_iphone.h

@@ -35,9 +35,6 @@
 
 #include "drivers/coreaudio/audio_driver_coreaudio.h"
 #include "drivers/unix/os_unix.h"
-#include "game_center.h"
-#include "icloud.h"
-#include "in_app_store.h"
 #include "ios.h"
 #include "joypad_iphone.h"
 #include "servers/audio_server.h"
@@ -48,6 +45,9 @@
 #include "platform/iphone/vulkan_context_iphone.h"
 #endif
 
+extern void godot_ios_plugins_initialize();
+extern void godot_ios_plugins_deinitialize();
+
 class OSIPhone : public OS_Unix {
 private:
 	static HashMap<String, void *> dynamic_symbol_lookup_table;
@@ -55,15 +55,6 @@ private:
 
 	AudioDriverCoreAudio audio_driver;
 
-#ifdef GAME_CENTER_ENABLED
-	GameCenter *game_center;
-#endif
-#ifdef STOREKIT_ENABLED
-	InAppStore *store_kit;
-#endif
-#ifdef ICLOUD_ENABLED
-	ICloud *icloud;
-#endif
 	iOS *ios;
 
 	JoypadIPhone *joypad_iphone;

+ 3 - 32
platform/iphone/os_iphone.mm

@@ -125,21 +125,6 @@ void OSIPhone::initialize() {
 }
 
 void OSIPhone::initialize_modules() {
-#ifdef GAME_CENTER_ENABLED
-	game_center = memnew(GameCenter);
-	Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
-#endif
-
-#ifdef STOREKIT_ENABLED
-	store_kit = memnew(InAppStore);
-	Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
-#endif
-
-#ifdef ICLOUD_ENABLED
-	icloud = memnew(ICloud);
-	Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
-#endif
-
 	ios = memnew(iOS);
 	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
 
@@ -155,26 +140,12 @@ void OSIPhone::deinitialize_modules() {
 		memdelete(ios);
 	}
 
-#ifdef GAME_CENTER_ENABLED
-	if (game_center) {
-		memdelete(game_center);
-	}
-#endif
-
-#ifdef STOREKIT_ENABLED
-	if (store_kit) {
-		memdelete(store_kit);
-	}
-#endif
-
-#ifdef ICLOUD_ENABLED
-	if (icloud) {
-		memdelete(icloud);
-	}
-#endif
+	godot_ios_plugins_deinitialize();
 }
 
 void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
+	godot_ios_plugins_initialize();
+
 	main_loop = p_main_loop;
 
 	if (main_loop) {

+ 265 - 0
platform/iphone/plugin/godot_plugin_config.h

@@ -0,0 +1,265 @@
+/*************************************************************************/
+/*  godot_plugin_config.h                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+#ifndef GODOT_PLUGIN_CONFIG_H
+#define GODOT_PLUGIN_CONFIG_H
+
+#include "core/error/error_list.h"
+#include "core/io/config_file.h"
+#include "core/string/ustring.h"
+
+static const char *PLUGIN_CONFIG_EXT = ".gdip";
+
+static const char *CONFIG_SECTION = "config";
+static const char *CONFIG_NAME_KEY = "name";
+static const char *CONFIG_BINARY_KEY = "binary";
+static const char *CONFIG_INITIALIZE_KEY = "initialization";
+static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization";
+
+static const char *DEPENDENCIES_SECTION = "dependencies";
+static const char *DEPENDENCIES_LINKED_KEY = "linked";
+static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded";
+static const char *DEPENDENCIES_SYSTEM_KEY = "system";
+static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities";
+static const char *DEPENDENCIES_FILES_KEY = "files";
+
+static const char *PLIST_SECTION = "plist";
+
+/*
+ The `config` section and fields are required and defined as follow:
+- **name**: name of the plugin
+- **binary**: path to static `.a` library
+
+The `dependencies` and fields are optional.
+- **linked**: dependencies that should only be linked.
+- **embedded**: dependencies that should be linked and embedded into application.
+- **system**: system dependencies that should be linked.
+- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file.
+- **files**: files that would be copied into application
+
+The `plist` section are optional.
+- **key**: key and value that would be added in Info.plist file.
+ */
+
+struct PluginConfig {
+	// Set to true when the config file is properly loaded.
+	bool valid_config = false;
+	bool supports_targets = false;
+	// Unix timestamp of last change to this plugin.
+	uint64_t last_updated = 0;
+
+	// Required config section
+	String name;
+	String binary;
+	String initialization_method;
+	String deinitialization_method;
+
+	// Optional dependencies section
+	Vector<String> linked_dependencies;
+	Vector<String> embedded_dependencies;
+	Vector<String> system_dependencies;
+
+	Vector<String> files_to_copy;
+	Vector<String> capabilities;
+
+	// Optional plist section
+	// Supports only string types for now
+	HashMap<String, String> plist;
+};
+
+static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+	String absolute_path;
+
+	if (dependency_path.empty()) {
+		return absolute_path;
+	}
+
+	if (dependency_path.is_abs_path()) {
+		return dependency_path;
+	}
+
+	String res_path = ProjectSettings::get_singleton()->globalize_path("res://");
+	absolute_path = plugin_config_dir.plus_file(dependency_path);
+
+	return absolute_path.replace(res_path, "res://");
+}
+
+static inline String resolve_system_dependency_path(String dependency_path) {
+	String absolute_path;
+
+	if (dependency_path.empty()) {
+		return absolute_path;
+	}
+
+	if (dependency_path.is_abs_path()) {
+		return dependency_path;
+	}
+
+	String system_path = "/System/Library/Frameworks";
+
+	return system_path.plus_file(dependency_path);
+}
+
+static inline Vector<String> resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths) {
+	Vector<String> paths;
+
+	for (int i = 0; i < p_paths.size(); i++) {
+		String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]);
+
+		if (path.empty()) {
+			continue;
+		}
+
+		paths.push_back(path);
+	}
+
+	return paths;
+}
+
+static inline Vector<String> resolve_system_dependencies(Vector<String> p_paths) {
+	Vector<String> paths;
+
+	for (int i = 0; i < p_paths.size(); i++) {
+		String path = resolve_system_dependency_path(p_paths[i]);
+
+		if (path.empty()) {
+			continue;
+		}
+
+		paths.push_back(path);
+	}
+
+	return paths;
+}
+
+static inline bool validate_plugin(PluginConfig &plugin_config) {
+	bool valid_name = !plugin_config.name.empty();
+	bool valid_binary_name = !plugin_config.binary.empty();
+	bool valid_initialize = !plugin_config.initialization_method.empty();
+	bool valid_deinitialize = !plugin_config.deinitialization_method.empty();
+
+	bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize;
+
+	if (fields_value && FileAccess::exists(plugin_config.binary)) {
+		plugin_config.valid_config = true;
+		plugin_config.supports_targets = false;
+	} else if (fields_value) {
+		String file_path = plugin_config.binary.get_base_dir();
+		String file_name = plugin_config.binary.get_basename().get_file();
+		String release_file_name = file_path.plus_file(file_name + ".release.a");
+		String debug_file_name = file_path.plus_file(file_name + ".debug.a");
+
+		if (FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) {
+			plugin_config.valid_config = true;
+			plugin_config.supports_targets = true;
+		}
+	}
+
+	return plugin_config.valid_config;
+}
+
+static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_config, const String &config_path) {
+	uint64_t last_updated = FileAccess::get_modified_time(config_path);
+
+	if (!plugin_config.supports_targets) {
+		last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
+	} else {
+		String file_path = plugin_config.binary.get_base_dir();
+		String file_name = plugin_config.binary.get_basename().get_file();
+		String release_file_name = file_path.plus_file(file_name + ".release.a");
+		String debug_file_name = file_path.plus_file(file_name + ".debug.a");
+
+		last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name));
+		last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name));
+	}
+
+	return last_updated;
+}
+
+static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
+	PluginConfig plugin_config = {};
+
+	if (!config_file.is_valid()) {
+		return plugin_config;
+	}
+
+	Error err = config_file->load(path);
+
+	if (err != OK) {
+		return plugin_config;
+	}
+
+	String config_base_dir = path.get_base_dir();
+
+	plugin_config.name = config_file->get_value(CONFIG_SECTION, CONFIG_NAME_KEY, String());
+	plugin_config.initialization_method = config_file->get_value(CONFIG_SECTION, CONFIG_INITIALIZE_KEY, String());
+	plugin_config.deinitialization_method = config_file->get_value(CONFIG_SECTION, CONFIG_DEINITIALIZE_KEY, String());
+
+	String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String());
+	plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path);
+
+	if (config_file->has_section(DEPENDENCIES_SECTION)) {
+		Vector<String> linked_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LINKED_KEY, Vector<String>());
+		Vector<String> embedded_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_EMBEDDED_KEY, Vector<String>());
+		Vector<String> system_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_SYSTEM_KEY, Vector<String>());
+		Vector<String> files = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_FILES_KEY, Vector<String>());
+
+		plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies);
+		plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies);
+		plugin_config.system_dependencies = resolve_system_dependencies(system_dependencies);
+
+		plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files);
+
+		plugin_config.capabilities = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_CAPABILITIES_KEY, Vector<String>());
+	}
+
+	if (config_file->has_section(PLIST_SECTION)) {
+		List<String> keys;
+		config_file->get_section_keys(PLIST_SECTION, &keys);
+
+		for (int i = 0; i < keys.size(); i++) {
+			String value = config_file->get_value(PLIST_SECTION, keys[i], String());
+
+			if (value.empty()) {
+				continue;
+			}
+
+			plugin_config.plist[keys[i]] = value;
+		}
+	}
+
+	if (validate_plugin(plugin_config)) {
+		plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
+	}
+
+	return plugin_config;
+}
+
+#endif // GODOT_PLUGIN_CONFIG_H

+ 1 - 2
platform/iphone/view_controller.h

@@ -28,13 +28,12 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#import <GameKit/GameKit.h>
 #import <UIKit/UIKit.h>
 
 @class GodotView;
 @class GodotNativeVideoView;
 
-@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
+@interface ViewController : UIViewController
 
 @property(nonatomic, readonly, strong) GodotView *godotView;
 @property(nonatomic, readonly, strong) GodotNativeVideoView *videoView;

+ 1 - 10
platform/iphone/view_controller.mm

@@ -36,6 +36,7 @@
 #import "native_video_view.h"
 #include "os_iphone.h"
 
+#import <AVFoundation/AVFoundation.h>
 #import <GameController/GameController.h>
 
 @interface ViewController ()
@@ -214,14 +215,4 @@
 	}
 }
 
-// MARK: Delegates
-
-#ifdef GAME_CENTER_ENABLED
-- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
-	//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
-	GameCenter::get_singleton()->game_center_closed();
-	[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
-}
-#endif
-
 @end