2021-07-13 21:46:21 +02:00
/**************************************************************************/
/* export_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "export_plugin.h"
2023-06-19 11:28:22 +02:00
# include "logo_svg.gen.h"
2022-12-10 17:59:09 +01:00
# include "run_icon_svg.gen.h"
2023-06-08 14:51:32 +02:00
2022-12-10 17:59:09 +01:00
# include "core/io/json.h"
2022-07-31 20:14:15 +02:00
# include "core/string/translation.h"
2022-02-12 02:46:22 +01:00
# include "editor/editor_node.h"
2022-12-10 17:59:09 +01:00
# include "editor/editor_paths.h"
2022-05-08 09:46:53 +02:00
# include "editor/editor_scale.h"
2023-08-13 02:33:39 +02:00
# include "editor/editor_string_names.h"
2023-04-12 21:02:28 +02:00
# include "editor/export/editor_export.h"
2023-06-20 04:50:09 +02:00
# include "editor/import/resource_importer_texture_settings.h"
2022-12-10 17:59:09 +01:00
# include "editor/plugins/script_editor_plugin.h"
2022-05-08 09:46:53 +02:00
2023-06-24 00:01:53 +02:00
# include "modules/modules_enabled.gen.h" // For mono and svg.
2022-05-08 09:46:53 +02:00
# ifdef MODULE_SVG_ENABLED
# include "modules/svg/image_loader_svg.h"
# endif
2022-02-12 02:46:22 +01:00
2022-07-28 00:34:12 +02:00
void EditorExportPlatformIOS : : get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) const {
2021-12-29 02:06:12 +01:00
// Vulkan and OpenGL ES 3.0 both mandate ETC2 support.
r_features - > push_back ( " etc2 " ) ;
2023-01-25 12:17:11 +01:00
r_features - > push_back ( " astc " ) ;
2021-07-13 21:46:21 +02:00
Vector < String > architectures = _get_preset_architectures ( p_preset ) ;
for ( int i = 0 ; i < architectures . size ( ) ; + + i ) {
r_features - > push_back ( architectures [ i ] ) ;
}
}
2022-07-28 00:34:12 +02:00
Vector < EditorExportPlatformIOS : : ExportArchitecture > EditorExportPlatformIOS : : _get_supported_architectures ( ) const {
2021-07-13 21:46:21 +02:00
Vector < ExportArchitecture > archs ;
archs . push_back ( ExportArchitecture ( " arm64 " , true ) ) ;
return archs ;
}
2022-11-17 10:53:04 +01:00
struct IconInfo {
const char * preset_key ;
const char * idiom ;
const char * export_name ;
const char * actual_size_side ;
const char * scale ;
const char * unscaled_size ;
const bool force_opaque ;
} ;
static const IconInfo icon_infos [ ] = {
// Home screen on iPhone
{ PNAME ( " icons/iphone_120x120 " ) , " iphone " , " Icon-120.png " , " 120 " , " 2x " , " 60x60 " , false } ,
{ PNAME ( " icons/iphone_120x120 " ) , " iphone " , " Icon-120.png " , " 120 " , " 3x " , " 40x40 " , false } ,
{ PNAME ( " icons/iphone_180x180 " ) , " iphone " , " Icon-180.png " , " 180 " , " 3x " , " 60x60 " , false } ,
// Home screen on iPad
{ PNAME ( " icons/ipad_76x76 " ) , " ipad " , " Icon-76.png " , " 76 " , " 1x " , " 76x76 " , false } ,
{ PNAME ( " icons/ipad_152x152 " ) , " ipad " , " Icon-152.png " , " 152 " , " 2x " , " 76x76 " , false } ,
{ PNAME ( " icons/ipad_167x167 " ) , " ipad " , " Icon-167.png " , " 167 " , " 2x " , " 83.5x83.5 " , false } ,
// App Store
{ PNAME ( " icons/app_store_1024x1024 " ) , " ios-marketing " , " Icon-1024.png " , " 1024 " , " 1x " , " 1024x1024 " , true } ,
// Spotlight
{ PNAME ( " icons/spotlight_40x40 " ) , " ipad " , " Icon-40.png " , " 40 " , " 1x " , " 40x40 " , false } ,
{ PNAME ( " icons/spotlight_80x80 " ) , " iphone " , " Icon-80.png " , " 80 " , " 2x " , " 40x40 " , false } ,
{ PNAME ( " icons/spotlight_80x80 " ) , " ipad " , " Icon-80.png " , " 80 " , " 2x " , " 40x40 " , false } ,
// Settings
{ PNAME ( " icons/settings_58x58 " ) , " iphone " , " Icon-58.png " , " 58 " , " 2x " , " 29x29 " , false } ,
{ PNAME ( " icons/settings_58x58 " ) , " ipad " , " Icon-58.png " , " 58 " , " 2x " , " 29x29 " , false } ,
{ PNAME ( " icons/settings_87x87 " ) , " iphone " , " Icon-87.png " , " 87 " , " 3x " , " 29x29 " , false } ,
// Notification
{ PNAME ( " icons/notification_40x40 " ) , " iphone " , " Icon-40.png " , " 40 " , " 2x " , " 20x20 " , false } ,
{ PNAME ( " icons/notification_40x40 " ) , " ipad " , " Icon-40.png " , " 40 " , " 2x " , " 20x20 " , false } ,
{ PNAME ( " icons/notification_60x60 " ) , " iphone " , " Icon-60.png " , " 60 " , " 3x " , " 20x20 " , false }
} ;
2023-03-09 09:41:52 +01:00
String EditorExportPlatformIOS : : get_export_option_warning ( const EditorExportPreset * p_preset , const StringName & p_name ) const {
if ( p_preset ) {
if ( p_name = = " application/app_store_team_id " ) {
String team_id = p_preset - > get ( " application/app_store_team_id " ) ;
if ( team_id . is_empty ( ) ) {
return TTR ( " App Store Team ID not specified. " ) + " \n " ;
}
} else if ( p_name = = " application/bundle_identifier " ) {
String identifier = p_preset - > get ( " application/bundle_identifier " ) ;
String pn_err ;
if ( ! is_package_name_valid ( identifier , & pn_err ) ) {
return TTR ( " Invalid Identifier: " ) + " " + pn_err ;
}
}
}
return String ( ) ;
}
bool EditorExportPlatformIOS : : get_export_option_visibility ( const EditorExportPreset * p_preset , const String & p_option ) const {
return true ;
}
void EditorExportPlatformIOS : : get_export_options ( List < ExportOption > * r_options ) const {
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " custom_template/debug " , PROPERTY_HINT_GLOBAL_FILE , " *.zip " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " custom_template/release " , PROPERTY_HINT_GLOBAL_FILE , " *.zip " ) , " " ) ) ;
Vector < ExportArchitecture > architectures = _get_supported_architectures ( ) ;
for ( int i = 0 ; i < architectures . size ( ) ; + + i ) {
2022-05-19 08:08:47 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , vformat ( " %s/%s " , PNAME ( " architectures " ) , architectures [ i ] . name ) ) , architectures [ i ] . is_default ) ) ;
2021-07-13 21:46:21 +02:00
}
2023-03-09 09:41:52 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/app_store_team_id " ) , " " , false , true ) ) ;
2021-07-13 21:46:21 +02:00
2023-05-10 11:40:17 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/provisioning_profile_uuid_debug " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET ) , " " ) ) ;
2022-01-25 16:12:50 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/code_sign_identity_debug " , PROPERTY_HINT_PLACEHOLDER_TEXT , " iPhone Developer " ) , " " ) ) ;
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " application/export_method_debug " , PROPERTY_HINT_ENUM , " App Store,Development,Ad-Hoc,Enterprise " ) , 1 ) ) ;
2023-05-10 11:40:17 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/provisioning_profile_uuid_release " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET ) , " " ) ) ;
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/code_sign_identity_release " , PROPERTY_HINT_PLACEHOLDER_TEXT , " iPhone Distribution " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " application/export_method_release " , PROPERTY_HINT_ENUM , " App Store,Development,Ad-Hoc,Enterprise " ) , 0 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " application/targeted_device_family " , PROPERTY_HINT_ENUM , " iPhone,iPad,iPhone & iPad " ) , 2 ) ) ;
2023-03-09 09:41:52 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/bundle_identifier " , PROPERTY_HINT_PLACEHOLDER_TEXT , " com.example.game " ) , " " , false , true ) ) ;
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/signature " ) , " " ) ) ;
2023-10-20 18:31:08 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/short_version " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Leave empty to use project version " ) , " " ) ) ;
2023-07-17 09:53:45 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/version " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Leave empty to use project version " ) , " " ) ) ;
2021-07-13 21:46:21 +02:00
2023-10-29 19:17:58 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/min_ios_version " ) , " 12.0 " ) ) ;
2022-11-17 22:46:48 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " application/icon_interpolation " , PROPERTY_HINT_ENUM , " Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos " ) , 4 ) ) ;
2023-06-24 10:38:08 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " application/export_project_only " ) , false ) ) ;
2021-07-13 21:46:21 +02:00
Vector < PluginConfigIOS > found_plugins = get_plugins ( ) ;
for ( int i = 0 ; i < found_plugins . size ( ) ; i + + ) {
2022-05-19 08:08:47 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , vformat ( " %s/%s " , PNAME ( " plugins " ) , found_plugins [ i ] . name ) ) , false ) ) ;
2021-07-13 21:46:21 +02:00
}
2022-05-19 17:00:06 +02:00
HashSet < String > plist_keys ;
2021-07-13 21:46:21 +02:00
for ( int i = 0 ; i < found_plugins . size ( ) ; i + + ) {
// Editable plugin plist values
PluginConfigIOS plugin = found_plugins [ i ] ;
2022-05-08 10:09:19 +02:00
for ( const KeyValue < String , PluginConfigIOS : : PlistItem > & E : plugin . plist ) {
switch ( E . value . type ) {
2021-07-13 21:46:21 +02:00
case PluginConfigIOS : : PlistItemType : : STRING_INPUT : {
2022-05-08 10:09:19 +02:00
String preset_name = " plugins_plist/ " + E . key ;
2021-07-13 21:46:21 +02:00
if ( ! plist_keys . has ( preset_name ) ) {
2022-05-08 10:09:19 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , preset_name ) , E . value . value ) ) ;
2021-07-13 21:46:21 +02:00
plist_keys . insert ( preset_name ) ;
}
} break ;
default :
continue ;
}
}
}
plugins_changed . clear ( ) ;
plugins = found_plugins ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " capabilities/access_wifi " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " capabilities/push_notifications " ) , false ) ) ;
2023-10-29 19:17:58 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " capabilities/performance_gaming_tier " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " capabilities/performance_a12 " ) , false ) ) ;
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " user_data/accessible_from_files_app " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " user_data/accessible_from_itunes_sharing " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/camera_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use the camera " ) , " " ) ) ;
2022-03-04 14:04:59 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/camera_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/microphone_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use the microphone " ) , " " ) ) ;
2022-03-04 14:04:59 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/microphone_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/photolibrary_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need access to the photo library " ) , " " ) ) ;
2022-03-04 14:04:59 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/photolibrary_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
2021-07-13 21:46:21 +02:00
2022-11-17 10:53:04 +01:00
HashSet < String > used_names ;
for ( uint64_t i = 0 ; i < sizeof ( icon_infos ) / sizeof ( icon_infos [ 0 ] ) ; + + i ) {
if ( ! used_names . has ( icon_infos [ i ] . preset_key ) ) {
used_names . insert ( icon_infos [ i ] . preset_key ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , icon_infos [ i ] . preset_key , PROPERTY_HINT_FILE , " *.png,*.jpg,*.jpeg " ) , " " ) ) ;
}
}
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " storyboard/image_scale_mode " , PROPERTY_HINT_ENUM , " Same as Logo,Center,Scale to Fit,Scale to Fill,Scale " ) , 0 ) ) ;
2022-08-19 18:51:06 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " storyboard/custom_image@2x " , PROPERTY_HINT_FILE , " *.png,*.jpg,*.jpeg " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " storyboard/custom_image@3x " , PROPERTY_HINT_FILE , " *.png,*.jpg,*.jpeg " ) , " " ) ) ;
2021-07-13 21:46:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " storyboard/use_custom_bg_color " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : COLOR , " storyboard/custom_bg_color " ) , Color ( ) ) ) ;
}
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 " ,
" ad-hoc " ,
" enterprise "
} ;
static const String storyboard_image_scale_mode [ ] = {
" center " ,
" scaleAspectFit " ,
" scaleAspectFill " ,
" scaleToFill "
} ;
2022-01-25 16:12:50 +01:00
String dbg_sign_id = p_preset - > get ( " application/code_sign_identity_debug " ) . operator String ( ) . is_empty ( ) ? " iPhone Developer " : p_preset - > get ( " application/code_sign_identity_debug " ) ;
String rel_sign_id = p_preset - > get ( " application/code_sign_identity_release " ) . operator String ( ) . is_empty ( ) ? " iPhone Distribution " : p_preset - > get ( " application/code_sign_identity_release " ) ;
2024-01-08 01:48:37 +01:00
bool dbg_manual = ! p_preset - > get_or_env ( " application/provisioning_profile_uuid_debug " , ENV_IOS_PROFILE_UUID_DEBUG ) . operator String ( ) . is_empty ( ) | | ( dbg_sign_id ! = " iPhone Developer " & & dbg_sign_id ! = " iPhone Distribution " ) ;
bool rel_manual = ! p_preset - > get_or_env ( " application/provisioning_profile_uuid_release " , ENV_IOS_PROFILE_UUID_RELEASE ) . operator String ( ) . is_empty ( ) | | ( rel_sign_id ! = " iPhone Developer " & & rel_sign_id ! = " iPhone Distribution " ) ;
2021-07-13 21:46:21 +02:00
String str ;
String strnew ;
str . parse_utf8 ( ( const char * ) pfile . ptr ( ) , pfile . size ( ) ) ;
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_config . binary_name ) + " \n " ;
} else if ( lines [ i ] . find ( " $modules_buildfile " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $modules_buildfile " , p_config . modules_buildfile ) + " \n " ;
} else if ( lines [ i ] . find ( " $modules_fileref " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $modules_fileref " , p_config . modules_fileref ) + " \n " ;
} else if ( lines [ i ] . find ( " $modules_buildphase " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $modules_buildphase " , p_config . modules_buildphase ) + " \n " ;
} else if ( lines [ i ] . find ( " $modules_buildgrp " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $modules_buildgrp " , p_config . modules_buildgrp ) + " \n " ;
} else if ( lines [ i ] . find ( " $name " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $name " , p_config . pkg_name ) + " \n " ;
} else if ( lines [ i ] . find ( " $bundle_identifier " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $bundle_identifier " , p_preset - > get ( " application/bundle_identifier " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $short_version " ) ! = - 1 ) {
2023-10-20 18:31:08 +02:00
strnew + = lines [ i ] . replace ( " $short_version " , p_preset - > get_version ( " application/short_version " ) ) + " \n " ;
2021-07-13 21:46:21 +02:00
} else if ( lines [ i ] . find ( " $version " ) ! = - 1 ) {
2023-07-17 09:53:45 +02:00
strnew + = lines [ i ] . replace ( " $version " , p_preset - > get_version ( " application/version " ) ) + " \n " ;
2023-10-29 19:17:58 +01:00
} else if ( lines [ i ] . find ( " $min_version " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $min_version " , p_preset - > get ( " application/min_ios_version " ) ) + " \n " ;
2021-07-13 21:46:21 +02:00
} else if ( lines [ i ] . find ( " $signature " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $signature " , p_preset - > get ( " application/signature " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $team_id " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $team_id " , p_preset - > get ( " application/app_store_team_id " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $default_build_config " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $default_build_config " , p_debug ? " Debug " : " Release " ) + " \n " ;
} else if ( lines [ i ] . find ( " $export_method " ) ! = - 1 ) {
int export_method = p_preset - > get ( p_debug ? " application/export_method_debug " : " application/export_method_release " ) ;
strnew + = lines [ i ] . replace ( " $export_method " , export_method_string [ export_method ] ) + " \n " ;
} else if ( lines [ i ] . find ( " $provisioning_profile_uuid_release " ) ! = - 1 ) {
2023-05-10 11:40:17 +02:00
strnew + = lines [ i ] . replace ( " $provisioning_profile_uuid_release " , p_preset - > get_or_env ( " application/provisioning_profile_uuid_release " , ENV_IOS_PROFILE_UUID_RELEASE ) ) + " \n " ;
2021-07-13 21:46:21 +02:00
} else if ( lines [ i ] . find ( " $provisioning_profile_uuid_debug " ) ! = - 1 ) {
2023-05-10 11:40:17 +02:00
strnew + = lines [ i ] . replace ( " $provisioning_profile_uuid_debug " , p_preset - > get_or_env ( " application/provisioning_profile_uuid_debug " , ENV_IOS_PROFILE_UUID_DEBUG ) ) + " \n " ;
2022-01-25 16:12:50 +01:00
} else if ( lines [ i ] . find ( " $code_sign_style_debug " ) ! = - 1 ) {
if ( dbg_manual ) {
strnew + = lines [ i ] . replace ( " $code_sign_style_debug " , " Manual " ) + " \n " ;
} else {
strnew + = lines [ i ] . replace ( " $code_sign_style_debug " , " Automatic " ) + " \n " ;
}
} else if ( lines [ i ] . find ( " $code_sign_style_release " ) ! = - 1 ) {
if ( rel_manual ) {
strnew + = lines [ i ] . replace ( " $code_sign_style_release " , " Manual " ) + " \n " ;
} else {
strnew + = lines [ i ] . replace ( " $code_sign_style_release " , " Automatic " ) + " \n " ;
}
2021-07-13 21:46:21 +02:00
} else if ( lines [ i ] . find ( " $provisioning_profile_uuid " ) ! = - 1 ) {
2023-05-10 11:40:17 +02:00
String uuid = p_debug ? p_preset - > get_or_env ( " application/provisioning_profile_uuid_debug " , ENV_IOS_PROFILE_UUID_DEBUG ) : p_preset - > get_or_env ( " application/provisioning_profile_uuid_release " , ENV_IOS_PROFILE_UUID_RELEASE ) ;
2021-07-13 21:46:21 +02:00
strnew + = lines [ i ] . replace ( " $provisioning_profile_uuid " , uuid ) + " \n " ;
} else if ( lines [ i ] . find ( " $code_sign_identity_debug " ) ! = - 1 ) {
2022-01-25 16:12:50 +01:00
strnew + = lines [ i ] . replace ( " $code_sign_identity_debug " , dbg_sign_id ) + " \n " ;
2021-07-13 21:46:21 +02:00
} else if ( lines [ i ] . find ( " $code_sign_identity_release " ) ! = - 1 ) {
2022-01-25 16:12:50 +01:00
strnew + = lines [ i ] . replace ( " $code_sign_identity_release " , rel_sign_id ) + " \n " ;
2021-07-13 21:46:21 +02:00
} 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 ( " $targeted_device_family " ) ! = - 1 ) {
String xcode_value ;
switch ( ( int ) p_preset - > get ( " application/targeted_device_family " ) ) {
case 0 : // iPhone
xcode_value = " 1 " ;
break ;
case 1 : // iPad
xcode_value = " 2 " ;
break ;
case 2 : // iPhone & iPad
xcode_value = " 1,2 " ;
break ;
}
strnew + = lines [ i ] . replace ( " $targeted_device_family " , xcode_value ) + " \n " ;
} else if ( lines [ i ] . find ( " $cpp_code " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $cpp_code " , p_config . cpp_code ) + " \n " ;
} else if ( lines [ i ] . find ( " $docs_in_place " ) ! = - 1 ) {
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 ( " $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 " ;
} else if ( lines [ i ] . find ( " $required_device_capabilities " ) ! = - 1 ) {
String capabilities ;
// 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/access_wifi " ) & & ! capabilities_list . has ( " wifi " ) ) {
capabilities_list . push_back ( " wifi " ) ;
}
2023-10-29 19:17:58 +01:00
if ( ( bool ) p_preset - > get ( " capabilities/performance_gaming_tier " ) & & ! capabilities_list . has ( " iphone-performance-gaming-tier " ) ) {
capabilities_list . push_back ( " iphone-performance-gaming-tier " ) ;
}
if ( ( bool ) p_preset - > get ( " capabilities/performance_a12 " ) & & ! capabilities_list . has ( " iphone-ipad-minimum-performance-a12 " ) ) {
capabilities_list . push_back ( " iphone-ipad-minimum-performance-a12 " ) ;
}
2021-07-13 21:46:21 +02:00
for ( int idx = 0 ; idx < capabilities_list . size ( ) ; idx + + ) {
capabilities + = " <string> " + capabilities_list [ idx ] + " </string> \n " ;
}
strnew + = lines [ i ] . replace ( " $required_device_capabilities " , capabilities ) ;
} else if ( lines [ i ] . find ( " $interface_orientations " ) ! = - 1 ) {
String orientations ;
const DisplayServer : : ScreenOrientation screen_orientation =
DisplayServer : : ScreenOrientation ( int ( GLOBAL_GET ( " display/window/handheld/orientation " ) ) ) ;
switch ( screen_orientation ) {
case DisplayServer : : SCREEN_LANDSCAPE :
orientations + = " <string>UIInterfaceOrientationLandscapeLeft</string> \n " ;
break ;
case DisplayServer : : SCREEN_PORTRAIT :
orientations + = " <string>UIInterfaceOrientationPortrait</string> \n " ;
break ;
case DisplayServer : : SCREEN_REVERSE_LANDSCAPE :
orientations + = " <string>UIInterfaceOrientationLandscapeRight</string> \n " ;
break ;
case DisplayServer : : SCREEN_REVERSE_PORTRAIT :
orientations + = " <string>UIInterfaceOrientationPortraitUpsideDown</string> \n " ;
break ;
case DisplayServer : : SCREEN_SENSOR_LANDSCAPE :
// Allow both landscape orientations depending on sensor direction.
orientations + = " <string>UIInterfaceOrientationLandscapeLeft</string> \n " ;
orientations + = " <string>UIInterfaceOrientationLandscapeRight</string> \n " ;
break ;
case DisplayServer : : SCREEN_SENSOR_PORTRAIT :
// Allow both portrait orientations depending on sensor direction.
orientations + = " <string>UIInterfaceOrientationPortrait</string> \n " ;
orientations + = " <string>UIInterfaceOrientationPortraitUpsideDown</string> \n " ;
break ;
case DisplayServer : : SCREEN_SENSOR :
// Allow all screen orientations depending on sensor direction.
orientations + = " <string>UIInterfaceOrientationLandscapeLeft</string> \n " ;
orientations + = " <string>UIInterfaceOrientationLandscapeRight</string> \n " ;
orientations + = " <string>UIInterfaceOrientationPortrait</string> \n " ;
orientations + = " <string>UIInterfaceOrientationPortraitUpsideDown</string> \n " ;
break ;
}
strnew + = lines [ i ] . replace ( " $interface_orientations " , orientations ) ;
} else if ( lines [ i ] . find ( " $camera_usage_description " ) ! = - 1 ) {
String description = p_preset - > get ( " privacy/camera_usage_description " ) ;
strnew + = lines [ i ] . replace ( " $camera_usage_description " , description ) + " \n " ;
} else if ( lines [ i ] . find ( " $microphone_usage_description " ) ! = - 1 ) {
String description = p_preset - > get ( " privacy/microphone_usage_description " ) ;
strnew + = lines [ i ] . replace ( " $microphone_usage_description " , description ) + " \n " ;
} else if ( lines [ i ] . find ( " $photolibrary_usage_description " ) ! = - 1 ) {
String description = p_preset - > get ( " privacy/photolibrary_usage_description " ) ;
strnew + = lines [ i ] . replace ( " $photolibrary_usage_description " , description ) + " \n " ;
} else if ( lines [ i ] . find ( " $plist_launch_screen_name " ) ! = - 1 ) {
2023-12-18 21:15:52 +01:00
String value = " <key>UILaunchStoryboardName</key> \n <string>Launch Screen</string> " ;
2021-07-13 21:46:21 +02:00
strnew + = lines [ i ] . replace ( " $plist_launch_screen_name " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $pbx_launch_screen_file_reference " ) ! = - 1 ) {
2023-12-18 21:15:52 +01:00
String value = " 90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \" Launch Screen.storyboard \" ; sourceTree = \" <group> \" ; }; " ;
2021-07-13 21:46:21 +02:00
strnew + = lines [ i ] . replace ( " $pbx_launch_screen_file_reference " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $pbx_launch_screen_copy_files " ) ! = - 1 ) {
2023-12-18 21:15:52 +01:00
String value = " 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */, " ;
2021-07-13 21:46:21 +02:00
strnew + = lines [ i ] . replace ( " $pbx_launch_screen_copy_files " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $pbx_launch_screen_build_phase " ) ! = - 1 ) {
2023-12-18 21:15:52 +01:00
String value = " 90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */, " ;
2021-07-13 21:46:21 +02:00
strnew + = lines [ i ] . replace ( " $pbx_launch_screen_build_phase " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $pbx_launch_screen_build_reference " ) ! = - 1 ) {
2023-12-18 21:15:52 +01:00
String value = " 90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; }; " ;
2021-07-13 21:46:21 +02:00
strnew + = lines [ i ] . replace ( " $pbx_launch_screen_build_reference " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $launch_screen_image_mode " ) ! = - 1 ) {
int image_scale_mode = p_preset - > get ( " storyboard/image_scale_mode " ) ;
String value ;
switch ( image_scale_mode ) {
case 0 : {
2022-10-18 16:43:37 +02:00
String logo_path = GLOBAL_GET ( " application/boot_splash/image " ) ;
bool is_on = GLOBAL_GET ( " application/boot_splash/fullsize " ) ;
2021-07-13 21:46:21 +02:00
// If custom logo is not specified, Godot does not scale default one, so we should do the same.
value = ( is_on & & logo_path . length ( ) > 0 ) ? " scaleAspectFit " : " center " ;
} break ;
default : {
value = storyboard_image_scale_mode [ image_scale_mode - 1 ] ;
}
}
strnew + = lines [ i ] . replace ( " $launch_screen_image_mode " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $launch_screen_background_color " ) ! = - 1 ) {
bool use_custom = p_preset - > get ( " storyboard/use_custom_bg_color " ) ;
2022-10-18 16:43:37 +02:00
Color color = use_custom ? p_preset - > get ( " storyboard/custom_bg_color " ) : GLOBAL_GET ( " application/boot_splash/bg_color " ) ;
2021-07-13 21:46:21 +02:00
const String value_format = " red= \" $red \" green= \" $green \" blue= \" $blue \" alpha= \" $alpha \" " ;
Dictionary value_dictionary ;
value_dictionary [ " red " ] = color . r ;
value_dictionary [ " green " ] = color . g ;
value_dictionary [ " blue " ] = color . b ;
value_dictionary [ " alpha " ] = color . a ;
String value = value_format . format ( value_dictionary , " $_ " ) ;
strnew + = lines [ i ] . replace ( " $launch_screen_background_color " , value ) + " \n " ;
2022-03-04 08:27:44 +01:00
} else if ( lines [ i ] . find ( " $pbx_locale_file_reference " ) ! = - 1 ) {
String locale_files ;
2022-10-18 16:43:37 +02:00
Vector < String > translations = GLOBAL_GET ( " internationalization/locale/translations " ) ;
2022-03-04 08:27:44 +01:00
if ( translations . size ( ) > 0 ) {
2022-08-17 15:11:56 +02:00
HashSet < String > languages ;
2022-03-04 08:27:44 +01:00
for ( const String & E : translations ) {
Ref < Translation > tr = ResourceLoader : : load ( E ) ;
2022-08-17 15:11:56 +02:00
if ( tr . is_valid ( ) & & tr - > get_locale ( ) ! = " en " ) {
languages . insert ( tr - > get_locale ( ) ) ;
2022-03-04 08:27:44 +01:00
}
2022-08-17 15:11:56 +02:00
}
int index = 0 ;
for ( const String & lang : languages ) {
locale_files + = " D0BCFE4518AEBDA2004A " + itos ( index ) . pad_zeros ( 4 ) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + " ; path = " + lang + " .lproj/InfoPlist.strings; sourceTree = \" <group> \" ; }; \n " ;
2022-03-04 08:27:44 +01:00
index + + ;
}
}
strnew + = lines [ i ] . replace ( " $pbx_locale_file_reference " , locale_files ) ;
} else if ( lines [ i ] . find ( " $pbx_locale_build_reference " ) ! = - 1 ) {
String locale_files ;
2022-10-18 16:43:37 +02:00
Vector < String > translations = GLOBAL_GET ( " internationalization/locale/translations " ) ;
2022-03-04 08:27:44 +01:00
if ( translations . size ( ) > 0 ) {
2022-08-17 15:11:56 +02:00
HashSet < String > languages ;
2022-03-04 08:27:44 +01:00
for ( const String & E : translations ) {
Ref < Translation > tr = ResourceLoader : : load ( E ) ;
2022-08-17 15:11:56 +02:00
if ( tr . is_valid ( ) & & tr - > get_locale ( ) ! = " en " ) {
languages . insert ( tr - > get_locale ( ) ) ;
2022-03-04 08:27:44 +01:00
}
2022-08-17 15:11:56 +02:00
}
int index = 0 ;
for ( const String & lang : languages ) {
locale_files + = " D0BCFE4518AEBDA2004A " + itos ( index ) . pad_zeros ( 4 ) + " /* " + lang + " */, \n " ;
2022-03-04 08:27:44 +01:00
index + + ;
}
}
strnew + = lines [ i ] . replace ( " $pbx_locale_build_reference " , locale_files ) ;
2021-06-21 15:37:41 +02:00
} else if ( lines [ i ] . find ( " $swift_runtime_migration " ) ! = - 1 ) {
String value = ! p_config . use_swift_runtime ? " " : " LastSwiftMigration = 1250; " ;
strnew + = lines [ i ] . replace ( " $swift_runtime_migration " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $swift_runtime_build_settings " ) ! = - 1 ) {
String value = ! p_config . use_swift_runtime ? " " : R " (
CLANG_ENABLE_MODULES = YES ;
SWIFT_OBJC_BRIDGING_HEADER = " $binary/dummy.h " ;
SWIFT_VERSION = 5.0 ;
) " ;
value = value . replace ( " $binary " , p_config . binary_name ) ;
strnew + = lines [ i ] . replace ( " $swift_runtime_build_settings " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $swift_runtime_fileref " ) ! = - 1 ) {
String value = ! p_config . use_swift_runtime ? " " : R " (
90 B4C2AA2680BC560039117A /* dummy.h */ = { isa = PBXFileReference ; lastKnownFileType = sourcecode . c . h ; path = " dummy.h " ; sourceTree = " <group> " ; } ;
90 B4C2B52680C7E90039117A /* dummy.swift */ = { isa = PBXFileReference ; lastKnownFileType = sourcecode . swift ; path = " dummy.swift " ; sourceTree = " <group> " ; } ;
) " ;
strnew + = lines [ i ] . replace ( " $swift_runtime_fileref " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $swift_runtime_binary_files " ) ! = - 1 ) {
String value = ! p_config . use_swift_runtime ? " " : R " (
90 B4C2AA2680BC560039117A /* dummy.h */ ,
90 B4C2B52680C7E90039117A /* dummy.swift */ ,
) " ;
strnew + = lines [ i ] . replace ( " $swift_runtime_binary_files " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $swift_runtime_buildfile " ) ! = - 1 ) {
String value = ! p_config . use_swift_runtime ? " " : " 90B4C2B62680C7E90039117A /* dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B4C2B52680C7E90039117A /* dummy.swift */; }; " ;
strnew + = lines [ i ] . replace ( " $swift_runtime_buildfile " , value ) + " \n " ;
} else if ( lines [ i ] . find ( " $swift_runtime_build_phase " ) ! = - 1 ) {
String value = ! p_config . use_swift_runtime ? " " : " 90B4C2B62680C7E90039117A /* dummy.swift */, " ;
strnew + = lines [ i ] . replace ( " $swift_runtime_build_phase " , value ) + " \n " ;
2021-07-13 21:46:21 +02:00
} else {
strnew + = lines [ i ] + " \n " ;
}
}
// !BAS! I'm assuming the 9 in the original code was a typo. I've added -1 or else it seems to also be adding our terminating zero...
2022-07-20 08:28:22 +02:00
// should apply the same fix in our macOS export.
2021-07-13 21:46:21 +02:00
CharString cs = strnew . utf8 ( ) ;
pfile . resize ( cs . size ( ) - 1 ) ;
for ( int i = 0 ; i < cs . size ( ) - 1 ; i + + ) {
pfile . write [ i ] = cs [ i ] ;
}
}
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 ;
}
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 ( " \" " , " \\ \" " ) ;
}
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 ;
}
void EditorExportPlatformIOS : : _blend_and_rotate ( Ref < Image > & p_dst , Ref < Image > & p_src , bool p_rot ) {
ERR_FAIL_COND ( p_dst . is_null ( ) ) ;
ERR_FAIL_COND ( p_src . is_null ( ) ) ;
int sw = p_rot ? p_src - > get_height ( ) : p_src - > get_width ( ) ;
int sh = p_rot ? p_src - > get_width ( ) : p_src - > get_height ( ) ;
int x_pos = ( p_dst - > get_width ( ) - sw ) / 2 ;
int y_pos = ( p_dst - > get_height ( ) - sh ) / 2 ;
int xs = ( x_pos > = 0 ) ? 0 : - x_pos ;
int ys = ( y_pos > = 0 ) ? 0 : - y_pos ;
if ( sw + x_pos > p_dst - > get_width ( ) ) {
sw = p_dst - > get_width ( ) - x_pos ;
}
if ( sh + y_pos > p_dst - > get_height ( ) ) {
sh = p_dst - > get_height ( ) - y_pos ;
}
for ( int y = ys ; y < sh ; y + + ) {
for ( int x = xs ; x < sw ; x + + ) {
Color sc = p_rot ? p_src - > get_pixel ( p_src - > get_width ( ) - y - 1 , x ) : p_src - > get_pixel ( x , y ) ;
Color dc = p_dst - > get_pixel ( x_pos + x , y_pos + y ) ;
dc . r = ( double ) ( sc . a * sc . r + dc . a * ( 1.0 - sc . a ) * dc . r ) ;
dc . g = ( double ) ( sc . a * sc . g + dc . a * ( 1.0 - sc . a ) * dc . g ) ;
dc . b = ( double ) ( sc . a * sc . b + dc . a * ( 1.0 - sc . a ) * dc . b ) ;
dc . a = ( double ) ( sc . a + dc . a * ( 1.0 - sc . a ) ) ;
p_dst - > set_pixel ( x_pos + x , y_pos + y , dc ) ;
}
}
}
Error EditorExportPlatformIOS : : _export_icons ( const Ref < EditorExportPreset > & p_preset , const String & p_iconset_dir ) {
String json_description = " { \" images \" :[ " ;
String sizes ;
2022-03-23 10:08:58 +01:00
Ref < DirAccess > da = DirAccess : : open ( p_iconset_dir ) ;
2023-12-06 15:18:35 +01:00
if ( da . is_null ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( TTR ( " Could not open a directory at path \" %s \" . " ) , p_iconset_dir ) ) ;
return ERR_CANT_OPEN ;
}
2021-07-13 21:46:21 +02:00
2022-11-17 10:53:04 +01:00
Color boot_bg_color = GLOBAL_GET ( " application/boot_splash/bg_color " ) ;
2021-07-13 21:46:21 +02:00
for ( uint64_t i = 0 ; i < ( sizeof ( icon_infos ) / sizeof ( icon_infos [ 0 ] ) ) ; + + i ) {
IconInfo info = icon_infos [ i ] ;
int side_size = String ( info . actual_size_side ) . to_int ( ) ;
String icon_path = p_preset - > get ( info . preset_key ) ;
if ( icon_path . length ( ) = = 0 ) {
2022-01-17 10:42:31 +01:00
// Resize main app icon
2022-10-18 16:43:37 +02:00
icon_path = GLOBAL_GET ( " application/config/icon " ) ;
2022-01-17 10:42:31 +01:00
Ref < Image > img = memnew ( Image ) ;
Error err = ImageLoader : : load_image ( icon_path , img ) ;
if ( err ! = OK ) {
2022-08-19 18:51:06 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( " Invalid icon (%s): '%s'. " , info . preset_key , icon_path ) ) ;
return ERR_UNCONFIGURED ;
2022-11-17 10:53:04 +01:00
} else if ( info . force_opaque & & img - > detect_alpha ( ) ! = Image : : ALPHA_NONE ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Export Icons " ) , vformat ( " Icon (%s) must be opaque. " , info . preset_key ) ) ;
2022-11-17 22:46:48 +01:00
img - > resize ( side_size , side_size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
2022-11-17 10:53:04 +01:00
Ref < Image > new_img = Image : : create_empty ( side_size , side_size , false , Image : : FORMAT_RGBA8 ) ;
new_img - > fill ( boot_bg_color ) ;
_blend_and_rotate ( new_img , img , false ) ;
err = new_img - > save_png ( p_iconset_dir + info . export_name ) ;
} else {
2022-11-17 22:46:48 +01:00
img - > resize ( side_size , side_size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
2022-11-17 10:53:04 +01:00
err = img - > save_png ( p_iconset_dir + info . export_name ) ;
2022-08-19 18:51:06 +02:00
}
2022-01-17 10:42:31 +01:00
if ( err ) {
2022-08-19 18:51:06 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( " Failed to export icon (%s): '%s'. " , info . preset_key , icon_path ) ) ;
2022-01-17 10:42:31 +01:00
return err ;
2021-07-13 21:46:21 +02:00
}
} else {
2022-01-17 10:42:31 +01:00
// Load custom icon and resize if required
2021-07-13 21:46:21 +02:00
Ref < Image > img = memnew ( Image ) ;
Error err = ImageLoader : : load_image ( icon_path , img ) ;
if ( err ! = OK ) {
2022-08-19 18:51:06 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( " Invalid icon (%s): '%s'. " , info . preset_key , icon_path ) ) ;
return ERR_UNCONFIGURED ;
2022-11-17 10:53:04 +01:00
} else if ( info . force_opaque & & img - > detect_alpha ( ) ! = Image : : ALPHA_NONE ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Export Icons " ) , vformat ( " Icon (%s) must be opaque. " , info . preset_key ) ) ;
2022-11-17 22:46:48 +01:00
img - > resize ( side_size , side_size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
2022-11-17 10:53:04 +01:00
Ref < Image > new_img = Image : : create_empty ( side_size , side_size , false , Image : : FORMAT_RGBA8 ) ;
new_img - > fill ( boot_bg_color ) ;
_blend_and_rotate ( new_img , img , false ) ;
err = new_img - > save_png ( p_iconset_dir + info . export_name ) ;
} else if ( img - > get_width ( ) ! = side_size | | img - > get_height ( ) ! = side_size ) {
2022-08-19 18:51:06 +02:00
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Export Icons " ) , vformat ( " Icon (%s): '%s' has incorrect size %s and was automatically resized to %s. " , info . preset_key , icon_path , img - > get_size ( ) , Vector2i ( side_size , side_size ) ) ) ;
2022-11-17 22:46:48 +01:00
img - > resize ( side_size , side_size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
2022-01-17 10:42:31 +01:00
err = img - > save_png ( p_iconset_dir + info . export_name ) ;
} else {
err = da - > copy ( icon_path , p_iconset_dir + info . export_name ) ;
2021-07-13 21:46:21 +02:00
}
if ( err ) {
2022-08-19 18:51:06 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( " Failed to export icon (%s): '%s'. " , info . preset_key , icon_path ) ) ;
2021-07-13 21:46:21 +02:00
return err ;
}
}
sizes + = String ( info . actual_size_side ) + " \n " ;
if ( i > 0 ) {
json_description + = " , " ;
}
json_description + = String ( " { " ) ;
json_description + = String ( " \" idiom \" : " ) + " \" " + info . idiom + " \" , " ;
json_description + = String ( " \" size \" : " ) + " \" " + info . unscaled_size + " \" , " ;
json_description + = String ( " \" scale \" : " ) + " \" " + info . scale + " \" , " ;
json_description + = String ( " \" filename \" : " ) + " \" " + info . export_name + " \" " ;
json_description + = String ( " } " ) ;
}
json_description + = " ]} " ;
2022-03-23 10:08:58 +01:00
Ref < FileAccess > json_file = FileAccess : : open ( p_iconset_dir + " Contents.json " , FileAccess : : WRITE ) ;
2023-12-06 15:18:35 +01:00
if ( json_file . is_null ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( TTR ( " Could not write to a file at path \" %s \" . " ) , p_iconset_dir + " Contents.json " ) ) ;
return ERR_CANT_CREATE ;
}
2021-07-13 21:46:21 +02:00
CharString json_utf8 = json_description . utf8 ( ) ;
json_file - > store_buffer ( ( const uint8_t * ) json_utf8 . get_data ( ) , json_utf8 . length ( ) ) ;
2022-03-23 10:08:58 +01:00
Ref < FileAccess > sizes_file = FileAccess : : open ( p_iconset_dir + " sizes " , FileAccess : : WRITE ) ;
2023-12-06 15:18:35 +01:00
if ( sizes_file . is_null ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export Icons " ) , vformat ( TTR ( " Could not write to a file at path \" %s \" . " ) , p_iconset_dir + " sizes " ) ) ;
return ERR_CANT_CREATE ;
}
2021-07-13 21:46:21 +02:00
CharString sizes_utf8 = sizes . utf8 ( ) ;
sizes_file - > store_buffer ( ( const uint8_t * ) sizes_utf8 . get_data ( ) , sizes_utf8 . length ( ) ) ;
return OK ;
}
Error EditorExportPlatformIOS : : _export_loading_screen_file ( const Ref < EditorExportPreset > & p_preset , const String & p_dest_dir ) {
const String custom_launch_image_2x = p_preset - > get ( " storyboard/custom_image@2x " ) ;
const String custom_launch_image_3x = p_preset - > get ( " storyboard/custom_image@3x " ) ;
if ( custom_launch_image_2x . length ( ) > 0 & & custom_launch_image_3x . length ( ) > 0 ) {
Ref < Image > image ;
2022-08-30 02:34:01 +02:00
String image_path = p_dest_dir . path_join ( " splash@2x.png " ) ;
2021-07-13 21:46:21 +02:00
image . instantiate ( ) ;
2022-11-20 12:16:16 +01:00
Error err = ImageLoader : : load_image ( custom_launch_image_2x , image ) ;
2021-07-13 21:46:21 +02:00
if ( err ) {
image . unref ( ) ;
return err ;
}
if ( image - > save_png ( image_path ) ! = OK ) {
return ERR_FILE_CANT_WRITE ;
}
image . unref ( ) ;
2022-08-30 02:34:01 +02:00
image_path = p_dest_dir . path_join ( " splash@3x.png " ) ;
2021-07-13 21:46:21 +02:00
image . instantiate ( ) ;
2022-11-20 12:16:16 +01:00
err = ImageLoader : : load_image ( custom_launch_image_3x , image ) ;
2021-07-13 21:46:21 +02:00
if ( err ) {
image . unref ( ) ;
return err ;
}
if ( image - > save_png ( image_path ) ! = OK ) {
return ERR_FILE_CANT_WRITE ;
}
} else {
Ref < Image > splash ;
2022-10-18 16:43:37 +02:00
const String splash_path = GLOBAL_GET ( " application/boot_splash/image " ) ;
2021-07-13 21:46:21 +02:00
if ( ! splash_path . is_empty ( ) ) {
splash . instantiate ( ) ;
2022-11-20 12:16:16 +01:00
const Error err = ImageLoader : : load_image ( splash_path , splash ) ;
2021-07-13 21:46:21 +02:00
if ( err ) {
splash . unref ( ) ;
}
}
if ( splash . is_null ( ) ) {
splash = Ref < Image > ( memnew ( Image ( boot_splash_png ) ) ) ;
}
// Using same image for both @2x and @3x
// because Godot's own boot logo uses single image for all resolutions.
// Also not using @1x image, because devices using this image variant
// are not supported by iOS 9, which is minimal target.
2022-08-30 02:34:01 +02:00
const String splash_png_path_2x = p_dest_dir . path_join ( " splash@2x.png " ) ;
const String splash_png_path_3x = p_dest_dir . path_join ( " splash@3x.png " ) ;
2021-07-13 21:46:21 +02:00
if ( splash - > save_png ( splash_png_path_2x ) ! = OK ) {
return ERR_FILE_CANT_WRITE ;
}
if ( splash - > save_png ( splash_png_path_3x ) ! = OK ) {
return ERR_FILE_CANT_WRITE ;
}
}
return OK ;
}
2022-03-23 10:08:58 +01:00
Error EditorExportPlatformIOS : : _walk_dir_recursive ( Ref < DirAccess > & p_da , FileHandler p_handler , void * p_userdata ) {
2021-07-13 21:46:21 +02:00
Vector < String > dirs ;
String current_dir = p_da - > get_current_dir ( ) ;
p_da - > list_dir_begin ( ) ;
2021-11-30 15:19:26 +01:00
String path = p_da - > get_next ( ) ;
while ( ! path . is_empty ( ) ) {
2021-07-13 21:46:21 +02:00
if ( p_da - > current_is_dir ( ) ) {
if ( path ! = " . " & & path ! = " .. " ) {
dirs . push_back ( path ) ;
}
} else {
2022-08-30 02:34:01 +02:00
Error err = p_handler ( current_dir . path_join ( path ) , p_userdata ) ;
2021-07-13 21:46:21 +02:00
if ( err ) {
p_da - > list_dir_end ( ) ;
return err ;
}
}
2021-11-30 15:19:26 +01:00
path = p_da - > get_next ( ) ;
2021-07-13 21:46:21 +02:00
}
p_da - > list_dir_end ( ) ;
for ( int i = 0 ; i < dirs . size ( ) ; + + i ) {
2023-12-13 17:06:14 +01:00
p_da - > change_dir ( dirs [ i ] ) ;
2021-07-13 21:46:21 +02:00
Error err = _walk_dir_recursive ( p_da , p_handler , p_userdata ) ;
p_da - > change_dir ( " .. " ) ;
if ( err ) {
return err ;
}
}
return OK ;
}
struct CodesignData {
const Ref < EditorExportPreset > & preset ;
bool debug = false ;
CodesignData ( const Ref < EditorExportPreset > & p_preset , bool p_debug ) :
preset ( p_preset ) ,
debug ( p_debug ) {
}
} ;
Error EditorExportPlatformIOS : : _codesign ( String p_file , void * p_userdata ) {
if ( p_file . ends_with ( " .dylib " ) ) {
2022-04-05 12:40:26 +02:00
CodesignData * data = static_cast < CodesignData * > ( p_userdata ) ;
2021-07-13 21:46:21 +02:00
print_line ( String ( " Signing " ) + p_file ) ;
2022-01-25 16:12:50 +01:00
String sign_id ;
if ( data - > debug ) {
sign_id = data - > preset - > get ( " application/code_sign_identity_debug " ) . operator String ( ) . is_empty ( ) ? " iPhone Developer " : data - > preset - > get ( " application/code_sign_identity_debug " ) ;
} else {
sign_id = data - > preset - > get ( " application/code_sign_identity_release " ) . operator String ( ) . is_empty ( ) ? " iPhone Distribution " : data - > preset - > get ( " application/code_sign_identity_release " ) ;
}
2021-07-13 21:46:21 +02:00
List < String > codesign_args ;
codesign_args . push_back ( " -f " ) ;
codesign_args . push_back ( " -s " ) ;
2022-01-25 16:12:50 +01:00
codesign_args . push_back ( sign_id ) ;
2021-07-13 21:46:21 +02:00
codesign_args . push_back ( p_file ) ;
2022-06-03 17:34:24 +02:00
String str ;
Error err = OS : : get_singleton ( ) - > execute ( " codesign " , codesign_args , & str , nullptr , true ) ;
print_verbose ( " codesign ( " + p_file + " ): \n " + str ) ;
return err ;
2021-07-13 21:46:21 +02:00
}
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 ( uint64_t i = 0 ; i < sizeof ( num ) * 2 ; + + i ) {
uint8_t four_bits = ( num > > ( sizeof ( num ) * 8 - ( i + 1 ) * 4 ) ) & 0xF ;
ret . write [ 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 ( const Ref < EditorExportPreset > & p_preset , Vector < uint8_t > & p_project_data , const Vector < IOSExportAsset > & p_additional_assets ) {
// 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 ;
String pbx_embeded_frameworks ;
const String file_info_format = String ( " $build_id = {isa = PBXBuildFile; fileRef = $ref_id; }; \n " ) +
2021-10-28 15:19:35 +02:00
" $ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \" $name \" ; path = \" $file_path \" ; sourceTree = \" <group> \" ; }; \n " ;
2021-07-13 21:46:21 +02:00
for ( int i = 0 ; i < p_additional_assets . size ( ) ; + + i ) {
String additional_asset_info_format = file_info_format ;
String build_id = ( + + current_id ) . str ( ) ;
String ref_id = ( + + current_id ) . str ( ) ;
String framework_id = " " ;
const IOSExportAsset & asset = p_additional_assets [ i ] ;
String type ;
if ( asset . exported_path . ends_with ( " .framework " ) ) {
if ( asset . should_embed ) {
additional_asset_info_format + = " $framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; \n " ;
framework_id = ( + + current_id ) . str ( ) ;
pbx_embeded_frameworks + = framework_id + " , \n " ;
}
type = " wrapper.framework " ;
} else if ( asset . exported_path . ends_with ( " .xcframework " ) ) {
if ( asset . should_embed ) {
additional_asset_info_format + = " $framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; \n " ;
framework_id = ( + + current_id ) . str ( ) ;
pbx_embeded_frameworks + = framework_id + " , \n " ;
}
type = " wrapper.xcframework " ;
} 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 ;
if ( framework_id . length ( ) > 0 ) {
format_dict [ " framework_id " ] = framework_id ;
}
pbx_files + = additional_asset_info_format . format ( format_dict , " $_ " ) ;
}
// Note, frameworks like gamekit are always included in our project.pbxprof file
// even if turned off in capabilities.
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 ) ;
str = str . replace ( " $pbx_embeded_frameworks " , pbx_embeded_frameworks ) ;
CharString cs = str . utf8 ( ) ;
p_project_data . resize ( cs . size ( ) - 1 ) ;
for ( int i = 0 ; i < cs . size ( ) - 1 ; i + + ) {
p_project_data . write [ i ] = cs [ i ] ;
}
}
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 ) {
String binary_name = p_out_dir . get_file ( ) . get_basename ( ) ;
2022-03-23 10:08:58 +01:00
Ref < DirAccess > da = DirAccess : : create_for_path ( p_asset ) ;
if ( da . is_null ( ) ) {
2021-07-13 21:46:21 +02:00
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 ) {
return ERR_FILE_NOT_FOUND ;
}
2023-11-15 18:29:19 +01:00
String base_dir = p_asset . get_base_dir ( ) . replace ( " res:// " , " " ) . replace ( " .godot/mono/temp/bin/ " , " " ) ;
String asset = p_asset . ends_with ( " / " ) ? p_asset . left ( p_asset . length ( ) - 1 ) : p_asset ;
2021-07-13 21:46:21 +02:00
String destination_dir ;
String destination ;
String asset_path ;
bool create_framework = false ;
2023-11-15 18:29:19 +01:00
if ( p_is_framework & & asset . ends_with ( " .dylib " ) ) {
2021-07-13 21:46:21 +02:00
// For iOS we need to turn .dylib into .framework
// to be able to send application to AppStore
2022-08-30 02:34:01 +02:00
asset_path = String ( " dylibs " ) . path_join ( base_dir ) ;
2021-07-13 21:46:21 +02:00
String file_name ;
if ( ! p_custom_file_name ) {
file_name = p_asset . get_basename ( ) . get_file ( ) ;
} else {
file_name = * p_custom_file_name ;
}
String framework_name = file_name + " .framework " ;
2022-08-30 02:34:01 +02:00
asset_path = asset_path . path_join ( framework_name ) ;
destination_dir = p_out_dir . path_join ( asset_path ) ;
destination = destination_dir . path_join ( file_name ) ;
2021-07-13 21:46:21 +02:00
create_framework = true ;
2023-11-15 18:29:19 +01:00
} else if ( p_is_framework & & ( asset . ends_with ( " .framework " ) | | asset . ends_with ( " .xcframework " ) ) ) {
2022-08-30 02:34:01 +02:00
asset_path = String ( " dylibs " ) . path_join ( base_dir ) ;
2021-07-13 21:46:21 +02:00
String file_name ;
if ( ! p_custom_file_name ) {
file_name = p_asset . get_file ( ) ;
} else {
file_name = * p_custom_file_name ;
}
2022-08-30 02:34:01 +02:00
asset_path = asset_path . path_join ( file_name ) ;
destination_dir = p_out_dir . path_join ( asset_path ) ;
2021-07-13 21:46:21 +02:00
destination = destination_dir ;
} else {
asset_path = base_dir ;
String file_name ;
if ( ! p_custom_file_name ) {
file_name = p_asset . get_file ( ) ;
} else {
file_name = * p_custom_file_name ;
}
2022-08-30 02:34:01 +02:00
destination_dir = p_out_dir . path_join ( asset_path ) ;
asset_path = asset_path . path_join ( file_name ) ;
destination = p_out_dir . path_join ( asset_path ) ;
2021-07-13 21:46:21 +02:00
}
2022-03-23 10:08:58 +01:00
Ref < DirAccess > filesystem_da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
ERR_FAIL_COND_V_MSG ( filesystem_da . is_null ( ) , ERR_CANT_CREATE , " Cannot create DirAccess for path ' " + p_out_dir + " '. " ) ;
2022-03-10 15:27:09 +01:00
2021-07-13 21:46:21 +02:00
if ( ! filesystem_da - > dir_exists ( destination_dir ) ) {
Error make_dir_err = filesystem_da - > make_dir_recursive ( destination_dir ) ;
if ( make_dir_err ) {
return make_dir_err ;
}
}
Error err = dir_exists ? da - > copy_dir ( p_asset , destination ) : da - > copy ( p_asset , destination ) ;
if ( err ) {
return err ;
}
2023-11-15 18:29:19 +01:00
if ( asset_path . ends_with ( " / " ) ) {
asset_path = asset_path . left ( asset_path . length ( ) - 1 ) ;
}
2022-08-30 02:34:01 +02:00
IOSExportAsset exported_asset = { binary_name . path_join ( asset_path ) , p_is_framework , p_should_embed } ;
2021-07-13 21:46:21 +02:00
r_exported_assets . push_back ( exported_asset ) ;
if ( create_framework ) {
String file_name ;
if ( ! p_custom_file_name ) {
file_name = p_asset . get_basename ( ) . get_file ( ) ;
} else {
file_name = * p_custom_file_name ;
}
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 " ) ;
2022-08-30 02:34:01 +02:00
install_name_args . push_back ( String ( " @rpath " ) . path_join ( framework_name ) . path_join ( file_name ) ) ;
2021-07-13 21:46:21 +02:00
install_name_args . push_back ( destination ) ;
OS : : get_singleton ( ) - > execute ( " install_name_tool " , install_name_args ) ;
}
// 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 "
2022-12-07 12:11:28 +01:00
" <string>com.gdextension.framework.$name</string> \n "
2021-07-13 21:46:21 +02:00
" <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 ) ;
2022-08-30 02:34:01 +02:00
Ref < FileAccess > f = FileAccess : : open ( destination_dir . path_join ( " Info.plist " ) , FileAccess : : WRITE ) ;
2022-03-23 10:08:58 +01:00
if ( f . is_valid ( ) ) {
2021-07-13 21:46:21 +02:00
f - > store_string ( info_plist ) ;
}
}
}
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 ) {
2023-11-18 23:40:56 +01:00
const String & asset = p_assets [ f_idx ] ;
2023-11-15 18:29:19 +01:00
if ( asset . begins_with ( " res:// " ) ) {
Error err = _copy_asset ( p_out_dir , asset , nullptr , p_is_framework , p_should_embed , r_exported_assets ) ;
2023-12-06 15:18:35 +01:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
2023-11-15 18:29:19 +01:00
} else if ( ProjectSettings : : get_singleton ( ) - > localize_path ( asset ) . begins_with ( " res:// " ) ) {
Error err = _copy_asset ( p_out_dir , ProjectSettings : : get_singleton ( ) - > localize_path ( asset ) , nullptr , p_is_framework , p_should_embed , r_exported_assets ) ;
2023-12-06 15:18:35 +01:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
2023-11-15 18:29:19 +01:00
} else {
2021-07-13 21:46:21 +02:00
// 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 ) ;
}
}
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 > linked_frameworks = export_plugins [ i ] - > get_ios_frameworks ( ) ;
Error err = _export_additional_assets ( p_out_dir , linked_frameworks , true , false , r_exported_assets ) ;
ERR_FAIL_COND_V ( err , err ) ;
Vector < String > embedded_frameworks = export_plugins [ i ] - > get_ios_embedded_frameworks ( ) ;
err = _export_additional_assets ( p_out_dir , embedded_frameworks , true , true , r_exported_assets ) ;
ERR_FAIL_COND_V ( err , err ) ;
Vector < String > project_static_libs = export_plugins [ i ] - > get_ios_project_static_libs ( ) ;
for ( int j = 0 ; j < project_static_libs . size ( ) ; j + + ) {
project_static_libs . write [ j ] = project_static_libs [ j ] . get_file ( ) ; // Only the file name as it's copied to the project
}
2021-10-17 22:38:03 +02:00
err = _export_additional_assets ( p_out_dir , project_static_libs , true , false , r_exported_assets ) ;
2021-07-13 21:46:21 +02:00
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 , 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 , true , r_exported_assets ) ;
ERR_FAIL_COND_V ( err , err ) ;
return OK ;
}
2022-07-28 00:34:12 +02:00
Vector < String > EditorExportPlatformIOS : : _get_preset_architectures ( const Ref < EditorExportPreset > & p_preset ) const {
2021-07-13 21:46:21 +02:00
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_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 ;
Vector < String > plugin_linked_dependencies ;
Vector < String > plugin_embedded_dependencies ;
Vector < String > plugin_files ;
Vector < PluginConfigIOS > enabled_plugins = get_enabled_plugins ( p_preset ) ;
Vector < String > added_linked_dependenciy_names ;
Vector < String > added_embedded_dependenciy_names ;
HashMap < String , String > plist_values ;
2022-05-19 17:00:06 +02:00
HashSet < String > plugin_linker_flags ;
2021-07-13 21:46:21 +02:00
Error err ;
for ( int i = 0 ; i < enabled_plugins . size ( ) ; i + + ) {
PluginConfigIOS plugin = enabled_plugins [ i ] ;
// Export plugin binary.
String plugin_main_binary = PluginConfigIOS : : get_plugin_main_binary ( plugin , p_debug ) ;
String plugin_binary_result_file = plugin . binary . get_file ( ) ;
// We shouldn't embed .xcframework that contains static libraries.
// Static libraries are not embedded anyway.
err = _copy_asset ( dest_dir , plugin_main_binary , & plugin_binary_result_file , true , false , r_exported_assets ) ;
2023-12-06 15:18:35 +01:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
2021-07-13 21:46:21 +02:00
// 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 ) ;
}
// Linker flags
// Checking duplicates
for ( int j = 0 ; j < plugin . linker_flags . size ( ) ; j + + ) {
String linker_flag = plugin . linker_flags [ j ] ;
plugin_linker_flags . insert ( linker_flag ) ;
}
// Plist
// Using hash map container to remove duplicates
2022-05-08 10:09:19 +02:00
for ( const KeyValue < String , PluginConfigIOS : : PlistItem > & E : plugin . plist ) {
String key = E . key ;
const PluginConfigIOS : : PlistItem & item = E . value ;
2021-07-13 21:46:21 +02:00
String value ;
switch ( item . type ) {
case PluginConfigIOS : : PlistItemType : : STRING_INPUT : {
String preset_name = " plugins_plist/ " + key ;
String input_value = p_preset - > get ( preset_name ) ;
value = " <string> " + input_value + " </string> " ;
} break ;
default :
value = item . value ;
break ;
}
if ( key . is_empty ( ) | | value . is_empty ( ) ) {
continue ;
}
String plist_key = " <key> " + key + " </key> " ;
plist_values [ plist_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 +
2021-10-28 15:19:35 +02:00
" extern void " + initialization_method +
" extern void " + deinitialization_method + " \n " ;
2021-07-13 21:46:21 +02:00
plugin_initialization_cpp_code + = " \t " + initialization_method ;
plugin_deinitialization_cpp_code + = " \t " + deinitialization_method ;
2021-06-21 15:37:41 +02:00
if ( plugin . use_swift_runtime ) {
p_config_data . use_swift_runtime = true ;
}
2021-07-13 21:46:21 +02:00
}
// Updating `Info.plist`
{
2022-05-08 10:09:19 +02:00
for ( const KeyValue < String , String > & E : plist_values ) {
String key = E . key ;
String value = E . value ;
2021-07-13 21:46:21 +02:00
if ( key . is_empty ( ) | | value . is_empty ( ) ) {
continue ;
}
p_config_data . plist_content + = key + value + " \n " ;
}
}
// Export files
{
// Export linked plugin dependency
err = _export_additional_assets ( dest_dir , plugin_linked_dependencies , true , false , r_exported_assets ) ;
2023-12-06 15:18:35 +01:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
2021-07-13 21:46:21 +02:00
// Export embedded plugin dependency
err = _export_additional_assets ( dest_dir , plugin_embedded_dependencies , true , true , r_exported_assets ) ;
2023-12-06 15:18:35 +01:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
2021-07-13 21:46:21 +02:00
// Export plugin files
err = _export_additional_assets ( dest_dir , plugin_files , false , false , r_exported_assets ) ;
2023-12-06 15:18:35 +01:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
2021-07-13 21:46:21 +02:00
}
// 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 , " $_ " ) ;
}
// Update Linker Flag Values
{
String result_linker_flags = " " ;
2022-05-19 01:43:40 +02:00
for ( const String & E : plugin_linker_flags ) {
const String & flag = E ;
2021-07-13 21:46:21 +02:00
if ( flag . length ( ) = = 0 ) {
continue ;
}
if ( result_linker_flags . length ( ) > 0 ) {
result_linker_flags + = ' ' ;
}
result_linker_flags + = flag ;
}
result_linker_flags = result_linker_flags . replace ( " \" " , " \\ \" " ) ;
p_config_data . linker_flags + = result_linker_flags ;
}
return OK ;
}
Error EditorExportPlatformIOS : : export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags ) {
2022-12-10 17:59:09 +01:00
return _export_project_helper ( p_preset , p_debug , p_path , p_flags , false , false ) ;
}
2023-11-30 15:57:38 +01:00
Error EditorExportPlatformIOS : : _export_project_helper ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags , bool p_simulator , bool p_oneclick ) {
2021-07-13 21:46:21 +02:00
ExportNotifier notifier ( * this , p_preset , p_debug , p_path , p_flags ) ;
2023-12-06 15:18:35 +01:00
const String dest_dir = p_path . get_base_dir ( ) + " / " ;
const String binary_name = p_path . get_file ( ) . get_basename ( ) ;
const String binary_dir = dest_dir + binary_name ;
if ( ! DirAccess : : exists ( dest_dir ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Target folder does not exist or is inaccessible: \" %s \" " ) , dest_dir ) ) ;
return ERR_FILE_BAD_PATH ;
}
2021-07-13 21:46:21 +02:00
2023-06-24 10:38:08 +02:00
bool export_project_only = p_preset - > get ( " application/export_project_only " ) ;
2023-11-30 15:57:38 +01:00
if ( p_oneclick ) {
export_project_only = false ; // Skip for one-click deploy.
}
2023-06-24 10:38:08 +02:00
EditorProgress ep ( " export " , export_project_only ? TTR ( " Exporting for iOS (Project Files Only) " ) : TTR( " Exporting for iOS " ), export_project_only ? 2 : 5, true) ;
2021-07-13 21:46:21 +02:00
String team_id = p_preset - > get ( " application/app_store_team_id " ) ;
ERR_FAIL_COND_V_MSG ( team_id . length ( ) = = 0 , ERR_CANT_OPEN , " App Store Team ID not specified - cannot configure the project. " ) ;
2023-12-06 15:18:35 +01:00
String src_pkg_name ;
2021-07-13 21:46:21 +02:00
if ( p_debug ) {
src_pkg_name = p_preset - > get ( " custom_template/debug " ) ;
} else {
src_pkg_name = p_preset - > get ( " custom_template/release " ) ;
}
2021-12-09 10:42:46 +01:00
if ( src_pkg_name . is_empty ( ) ) {
2021-07-13 21:46:21 +02:00
String err ;
2022-07-20 08:28:22 +02:00
src_pkg_name = find_export_template ( " ios.zip " , & err ) ;
2021-12-09 10:42:46 +01:00
if ( src_pkg_name . is_empty ( ) ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , TTR ( " Export template not found. " ) ) ;
2021-07-13 21:46:21 +02:00
return ERR_FILE_NOT_FOUND ;
}
}
2022-03-10 15:27:09 +01:00
{
2022-03-23 10:08:58 +01:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
if ( da . is_valid ( ) ) {
2022-03-10 15:27:09 +01:00
String current_dir = da - > get_current_dir ( ) ;
// remove leftovers from last export so they don't interfere
// in case some files are no longer needed
2023-12-06 15:18:35 +01:00
if ( da - > change_dir ( binary_dir + " .xcodeproj " ) = = OK ) {
2022-03-10 15:27:09 +01:00
da - > erase_contents_recursive ( ) ;
}
2023-12-06 15:18:35 +01:00
if ( da - > change_dir ( binary_dir ) = = OK ) {
2022-03-10 15:27:09 +01:00
da - > erase_contents_recursive ( ) ;
}
2021-07-13 21:46:21 +02:00
2022-03-10 15:27:09 +01:00
da - > change_dir ( current_dir ) ;
2021-07-13 21:46:21 +02:00
2023-12-06 15:18:35 +01:00
if ( ! da - > dir_exists ( binary_dir ) ) {
Error err = da - > make_dir ( binary_dir ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Failed to create the directory: \" %s \" " ) , binary_dir ) ) ;
2022-03-10 15:27:09 +01:00
return err ;
}
2021-07-13 21:46:21 +02:00
}
}
}
if ( ep . step ( " Making .pck " , 0 ) ) {
return ERR_SKIP ;
}
2023-12-06 15:18:35 +01:00
String pack_path = binary_dir + " .pck " ;
2021-07-13 21:46:21 +02:00
Vector < SharedObject > libraries ;
2022-03-10 08:48:25 +01:00
Error err = save_pack ( p_preset , p_debug , pack_path , & libraries ) ;
2021-07-13 21:46:21 +02:00
if ( err ) {
2023-12-06 15:18:35 +01:00
// Message is supplied by the subroutine method.
2021-07-13 21:46:21 +02:00
return err ;
}
if ( ep . step ( " Extracting and configuring Xcode project " , 1 ) ) {
return ERR_SKIP ;
}
2022-07-20 08:28:22 +02:00
String library_to_use = " libgodot.ios. " + String ( p_debug ? " debug " : " release " ) + " .xcframework " ;
2021-07-13 21:46:21 +02:00
print_line ( " Static framework: " + library_to_use ) ;
String pkg_name ;
2022-10-18 16:43:37 +02:00
if ( String ( GLOBAL_GET ( " application/config/name " ) ) ! = " " ) {
pkg_name = String ( GLOBAL_GET ( " application/config/name " ) ) ;
2021-07-13 21:46:21 +02:00
} else {
pkg_name = " Unnamed " ;
}
bool found_library = false ;
const String project_file = " godot_ios.xcodeproj/project.pbxproj " ;
2022-05-19 17:00:06 +02:00
HashSet < String > files_to_parse ;
2021-07-13 21:46:21 +02:00
files_to_parse . insert ( " godot_ios/godot_ios-Info.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 " ) ;
files_to_parse . insert ( " godot_ios/godot_ios.entitlements " ) ;
files_to_parse . insert ( " godot_ios/Launch Screen.storyboard " ) ;
IOSConfigData config_data = {
pkg_name ,
binary_name ,
_get_additional_plist_content ( ) ,
String ( " " ) . join ( _get_preset_architectures ( p_preset ) ) ,
_get_linker_flags ( ) ,
_get_cpp_code ( ) ,
" " ,
" " ,
" " ,
" " ,
2021-06-21 15:37:41 +02:00
Vector < String > ( ) ,
false
2021-07-13 21:46:21 +02:00
} ;
Vector < IOSExportAsset > assets ;
2022-03-23 10:08:58 +01:00
Ref < DirAccess > tmp_app_path = DirAccess : : create_for_path ( dest_dir ) ;
2023-12-06 15:18:35 +01:00
if ( tmp_app_path . is_null ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , vformat ( TTR ( " Could not create and open the directory: \" %s \" " ) , dest_dir ) ) ;
return ERR_CANT_CREATE ;
}
2021-07-13 21:46:21 +02:00
print_line ( " Unzipping... " ) ;
2022-05-11 14:15:58 +02:00
Ref < FileAccess > io_fa ;
zlib_filefunc_def io = zipio_create_io ( & io_fa ) ;
2021-07-13 21:46:21 +02:00
unzFile src_pkg_zip = unzOpen2 ( src_pkg_name . utf8 ( ) . get_data ( ) , & io ) ;
if ( ! src_pkg_zip ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , TTR ( " Could not open export template (not a zip file?): \" %s \" . " , src_pkg_name ) ) ;
2021-07-13 21:46:21 +02:00
return ERR_CANT_OPEN ;
}
2023-12-06 15:18:35 +01:00
err = _export_ios_plugins ( p_preset , config_data , binary_dir , assets , p_debug ) ;
if ( err ! = OK ) {
// TODO: Improve error reporting by using `add_message` throughout all methods called via `_export_ios_plugins`.
// For now a generic top level message would be fine, but we're ought to use proper reporting here instead of
// just fail macros and non-descriptive error return values.
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " iOS Plugins " ) , vformat ( TTR ( " Failed to export iOS plugins with code %d. Please check the output log. " ) , err ) ) ;
return err ;
}
2021-07-13 21:46:21 +02:00
//export rest of the files
int ret = unzGoToFirstFile ( src_pkg_zip ) ;
Vector < uint8_t > project_file_data ;
while ( ret = = UNZ_OK ) {
2023-01-16 17:01:35 +01:00
# if defined(MACOS_ENABLED) || defined(LINUXBSD_ENABLED)
2021-07-13 21:46:21 +02:00
bool is_execute = false ;
# endif
//get filename
unz_file_info info ;
char fname [ 16384 ] ;
ret = unzGetCurrentFileInfo ( src_pkg_zip , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
2022-04-05 12:40:26 +02:00
if ( ret ! = UNZ_OK ) {
break ;
}
2021-07-13 21:46:21 +02:00
2022-01-05 13:27:11 +01:00
String file = String : : utf8 ( fname ) ;
2021-07-13 21:46:21 +02:00
print_line ( " READ: " + file ) ;
Vector < uint8_t > data ;
data . resize ( info . uncompressed_size ) ;
//read
unzOpenCurrentFile ( src_pkg_zip ) ;
unzReadCurrentFile ( src_pkg_zip , data . ptrw ( ) , data . size ( ) ) ;
unzCloseCurrentFile ( src_pkg_zip ) ;
//write
if ( files_to_parse . has ( file ) ) {
_fix_config_file ( p_preset , data , config_data , p_debug ) ;
2022-07-20 08:28:22 +02:00
} else if ( file . begins_with ( " libgodot.ios " ) ) {
2021-07-13 21:46:21 +02:00
if ( ! file . begins_with ( library_to_use ) | | file . ends_with ( String ( " /empty " ) ) ) {
ret = unzGoToNextFile ( src_pkg_zip ) ;
continue ; //ignore!
}
found_library = true ;
2023-01-16 17:01:35 +01:00
# if defined(MACOS_ENABLED) || defined(LINUXBSD_ENABLED)
2021-07-13 21:46:21 +02:00
is_execute = true ;
# endif
file = file . replace ( library_to_use , binary_name + " .xcframework " ) ;
}
if ( file = = project_file ) {
project_file_data = data ;
}
///@TODO need to parse logo files
if ( data . size ( ) > 0 ) {
file = file . replace ( " godot_ios " , binary_name ) ;
print_line ( " ADDING: " + file + " size: " + itos ( data . size ( ) ) ) ;
/* write it into our folder structure */
file = dest_dir + file ;
/* make sure this folder exists */
String dir_name = file . get_base_dir ( ) ;
if ( ! tmp_app_path - > dir_exists ( dir_name ) ) {
print_line ( " Creating " + dir_name ) ;
Error dir_err = tmp_app_path - > make_dir_recursive ( dir_name ) ;
if ( dir_err ) {
unzClose ( src_pkg_zip ) ;
2023-12-06 15:18:35 +01:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create a directory at path \" %s \" . " ) , dir_name ) ) ;
2021-07-13 21:46:21 +02:00
return ERR_CANT_CREATE ;
}
}
/* write the file */
2022-04-12 09:12:40 +02:00
{
Ref < FileAccess > f = FileAccess : : open ( file , FileAccess : : WRITE ) ;
if ( f . is_null ( ) ) {
unzClose ( src_pkg_zip ) ;
2023-12-06 15:18:35 +01:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not write to a file at path \" %s \" . " ) , file ) ) ;
2022-04-12 09:12:40 +02:00
return ERR_CANT_CREATE ;
} ;
f - > store_buffer ( data . ptr ( ) , data . size ( ) ) ;
}
2021-07-13 21:46:21 +02:00
2023-01-16 17:01:35 +01:00
# if defined(MACOS_ENABLED) || defined(LINUXBSD_ENABLED)
2021-07-13 21:46:21 +02:00
if ( is_execute ) {
// we need execute rights on this file
chmod ( file . utf8 ( ) . get_data ( ) , 0755 ) ;
}
# endif
}
ret = unzGoToNextFile ( src_pkg_zip ) ;
}
/* we're done with our source zip */
unzClose ( src_pkg_zip ) ;
if ( ! found_library ) {
2023-12-06 15:18:35 +01:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Requested template library '%s' not found. It might be missing from your template archive. " ) , library_to_use ) ) ;
2021-07-13 21:46:21 +02:00
return ERR_FILE_NOT_FOUND ;
}
2022-10-18 16:43:37 +02:00
Dictionary appnames = GLOBAL_GET ( " application/config/name_localized " ) ;
2022-03-04 14:04:59 +01:00
Dictionary camera_usage_descriptions = p_preset - > get ( " privacy/camera_usage_description_localized " ) ;
Dictionary microphone_usage_descriptions = p_preset - > get ( " privacy/microphone_usage_description_localized " ) ;
Dictionary photolibrary_usage_descriptions = p_preset - > get ( " privacy/photolibrary_usage_description_localized " ) ;
2022-10-18 16:43:37 +02:00
Vector < String > translations = GLOBAL_GET ( " internationalization/locale/translations " ) ;
2022-03-04 08:27:44 +01:00
if ( translations . size ( ) > 0 ) {
{
2023-12-06 15:18:35 +01:00
String fname = binary_dir + " /en.lproj " ;
2022-03-04 08:27:44 +01:00
tmp_app_path - > make_dir_recursive ( fname ) ;
2022-03-23 10:08:58 +01:00
Ref < FileAccess > f = FileAccess : : open ( fname + " /InfoPlist.strings " , FileAccess : : WRITE ) ;
2022-03-04 14:04:59 +01:00
f - > store_line ( " /* Localized versions of Info.plist keys */ " ) ;
f - > store_line ( " " ) ;
2022-10-18 16:43:37 +02:00
f - > store_line ( " CFBundleDisplayName = \" " + GLOBAL_GET ( " application/config/name " ) . operator String ( ) + " \" ; " ) ;
2022-03-04 14:04:59 +01:00
f - > store_line ( " NSCameraUsageDescription = \" " + p_preset - > get ( " privacy/camera_usage_description " ) . operator String ( ) + " \" ; " ) ;
f - > store_line ( " NSMicrophoneUsageDescription = \" " + p_preset - > get ( " privacy/microphone_usage_description " ) . operator String ( ) + " \" ; " ) ;
f - > store_line ( " NSPhotoLibraryUsageDescription = \" " + p_preset - > get ( " privacy/photolibrary_usage_description " ) . operator String ( ) + " \" ; " ) ;
2022-03-04 08:27:44 +01:00
}
2022-08-17 15:11:56 +02:00
HashSet < String > languages ;
2022-03-04 08:27:44 +01:00
for ( const String & E : translations ) {
Ref < Translation > tr = ResourceLoader : : load ( E ) ;
2022-08-17 15:11:56 +02:00
if ( tr . is_valid ( ) & & tr - > get_locale ( ) ! = " en " ) {
languages . insert ( tr - > get_locale ( ) ) ;
}
}
for ( const String & lang : languages ) {
2023-12-06 15:18:35 +01:00
String fname = binary_dir + " / " + lang + " .lproj " ;
2022-08-17 15:11:56 +02:00
tmp_app_path - > make_dir_recursive ( fname ) ;
Ref < FileAccess > f = FileAccess : : open ( fname + " /InfoPlist.strings " , FileAccess : : WRITE ) ;
f - > store_line ( " /* Localized versions of Info.plist keys */ " ) ;
f - > store_line ( " " ) ;
if ( appnames . has ( lang ) ) {
f - > store_line ( " CFBundleDisplayName = \" " + appnames [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( camera_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSCameraUsageDescription = \" " + camera_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( microphone_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSMicrophoneUsageDescription = \" " + microphone_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( photolibrary_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSPhotoLibraryUsageDescription = \" " + photolibrary_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
2022-03-04 08:27:44 +01:00
}
}
}
2021-07-13 21:46:21 +02:00
// Copy project static libs to the project
Vector < Ref < EditorExportPlugin > > export_plugins = EditorExport : : get_singleton ( ) - > get_export_plugins ( ) ;
for ( int i = 0 ; i < export_plugins . size ( ) ; i + + ) {
Vector < String > project_static_libs = export_plugins [ i ] - > get_ios_project_static_libs ( ) ;
for ( int j = 0 ; j < project_static_libs . size ( ) ; j + + ) {
const String & static_lib_path = project_static_libs [ j ] ;
String dest_lib_file_path = dest_dir + static_lib_path . get_file ( ) ;
Error lib_copy_err = tmp_app_path - > copy ( static_lib_path , dest_lib_file_path ) ;
if ( lib_copy_err ! = OK ) {
2023-12-06 15:18:35 +01:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not copy a file at path \" %s \" to \" %s \" . " ) , static_lib_path , dest_lib_file_path ) ) ;
2021-07-13 21:46:21 +02:00
return lib_copy_err ;
}
}
}
2023-12-06 15:18:35 +01:00
String iconset_dir = binary_dir + " /Images.xcassets/AppIcon.appiconset/ " ;
2021-07-13 21:46:21 +02:00
err = OK ;
if ( ! tmp_app_path - > dir_exists ( iconset_dir ) ) {
err = tmp_app_path - > make_dir_recursive ( iconset_dir ) ;
}
2023-12-06 15:18:35 +01:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create a directory at path \" %s \" . " ) , iconset_dir ) ) ;
2021-07-13 21:46:21 +02:00
return err ;
}
err = _export_icons ( p_preset , iconset_dir ) ;
2023-12-06 15:18:35 +01:00
if ( err ! = OK ) {
// Message is supplied by the subroutine method.
2021-07-13 21:46:21 +02:00
return err ;
}
2022-03-10 15:27:09 +01:00
{
2023-12-06 15:18:35 +01:00
String splash_image_path = binary_dir + " /Images.xcassets/SplashImage.imageset/ " ;
2021-07-13 21:46:21 +02:00
2022-03-23 10:08:58 +01:00
Ref < DirAccess > launch_screen_da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
if ( launch_screen_da . is_null ( ) ) {
2023-12-06 15:18:35 +01:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , TTR ( " Could not access the filesystem. " ) ) ;
2022-03-10 15:27:09 +01:00
return ERR_CANT_CREATE ;
}
2021-07-13 21:46:21 +02:00
2023-12-18 21:15:52 +01:00
print_line ( " Exporting launch screen storyboard " ) ;
2021-07-13 21:46:21 +02:00
2023-12-18 21:15:52 +01:00
err = _export_loading_screen_file ( p_preset , splash_image_path ) ;
if ( err ! = OK ) {
2023-12-06 15:18:35 +01:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Failed to create a file at path \" %s \" with code %d. " ) , splash_image_path , err ) ) ;
2022-03-10 15:27:09 +01:00
}
2021-07-13 21:46:21 +02:00
}
2023-12-06 15:18:35 +01:00
if ( err ! = OK ) {
2021-07-13 21:46:21 +02:00
return err ;
}
print_line ( " Exporting additional assets " ) ;
2023-12-06 15:18:35 +01:00
_export_additional_assets ( binary_dir , libraries , assets ) ;
2021-07-13 21:46:21 +02:00
_add_assets_to_project ( p_preset , project_file_data , assets ) ;
2023-12-06 15:18:35 +01:00
String project_file_name = binary_dir + " .xcodeproj/project.pbxproj " ;
2022-04-12 09:12:40 +02:00
{
Ref < FileAccess > f = FileAccess : : open ( project_file_name , FileAccess : : WRITE ) ;
if ( f . is_null ( ) ) {
2023-12-06 15:18:35 +01:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not write to a file at path \" %s \" . " ) , project_file_name ) ) ;
2022-04-12 09:12:40 +02:00
return ERR_CANT_CREATE ;
} ;
f - > store_buffer ( project_file_data . ptr ( ) , project_file_data . size ( ) ) ;
}
2021-07-13 21:46:21 +02:00
2022-07-20 08:28:22 +02:00
# ifdef MACOS_ENABLED
2022-03-10 15:27:09 +01:00
{
if ( ep . step ( " Code-signing dylibs " , 2 ) ) {
return ERR_SKIP ;
}
2023-12-06 15:18:35 +01:00
Ref < DirAccess > dylibs_dir = DirAccess : : open ( binary_dir + " /dylibs " ) ;
2022-03-23 10:08:58 +01:00
ERR_FAIL_COND_V ( dylibs_dir . is_null ( ) , ERR_CANT_OPEN ) ;
2022-03-10 15:27:09 +01:00
CodesignData codesign_data ( p_preset , p_debug ) ;
err = _walk_dir_recursive ( dylibs_dir , _codesign , & codesign_data ) ;
2023-01-11 09:49:11 +01:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Code Signing " ) , TTR ( " Code signing failed, see editor log for details. " ) ) ;
return err ;
}
2021-07-13 21:46:21 +02:00
}
2023-06-24 10:38:08 +02:00
if ( export_project_only ) {
return OK ;
}
2021-07-13 21:46:21 +02:00
if ( ep . step ( " Making .xcarchive " , 3 ) ) {
return ERR_SKIP ;
}
2023-12-06 15:18:35 +01:00
2021-07-13 21:46:21 +02:00
String archive_path = p_path . get_basename ( ) + " .xcarchive " ;
List < String > archive_args ;
archive_args . push_back ( " -project " ) ;
2023-12-06 15:18:35 +01:00
archive_args . push_back ( binary_dir + " .xcodeproj " ) ;
2021-07-13 21:46:21 +02:00
archive_args . push_back ( " -scheme " ) ;
archive_args . push_back ( binary_name ) ;
archive_args . push_back ( " -sdk " ) ;
2022-12-10 17:59:09 +01:00
if ( p_simulator ) {
archive_args . push_back ( " iphonesimulator " ) ;
} else {
archive_args . push_back ( " iphoneos " ) ;
}
2021-07-13 21:46:21 +02:00
archive_args . push_back ( " -configuration " ) ;
archive_args . push_back ( p_debug ? " Debug " : " Release " ) ;
archive_args . push_back ( " -destination " ) ;
2022-12-10 17:59:09 +01:00
if ( p_simulator ) {
archive_args . push_back ( " generic/platform=iOS Simulator " ) ;
} else {
archive_args . push_back ( " generic/platform=iOS " ) ;
}
2021-07-13 21:46:21 +02:00
archive_args . push_back ( " archive " ) ;
2022-01-25 16:12:50 +01:00
archive_args . push_back ( " -allowProvisioningUpdates " ) ;
2021-07-13 21:46:21 +02:00
archive_args . push_back ( " -archivePath " ) ;
archive_args . push_back ( archive_path ) ;
2023-12-06 15:18:35 +01:00
2021-11-07 10:05:05 +01:00
String archive_str ;
err = OS : : get_singleton ( ) - > execute ( " xcodebuild " , archive_args , & archive_str , nullptr , true ) ;
2023-12-06 15:18:35 +01:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Xcode Build " ) , vformat ( TTR ( " Failed to run xcodebuild with code %d " ) , err ) ) ;
return err ;
}
2021-11-07 10:05:05 +01:00
print_line ( " xcodebuild (.xcarchive): \n " + archive_str ) ;
2023-01-11 09:49:11 +01:00
if ( ! archive_str . contains ( " ** ARCHIVE SUCCEEDED ** " ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Xcode Build " ) , TTR ( " Xcode project build failed, see editor log for details. " ) ) ;
return FAILED ;
}
2021-07-13 21:46:21 +02:00
2023-11-30 15:57:38 +01:00
if ( ! p_oneclick ) {
2022-12-10 17:59:09 +01:00
if ( ep . step ( " Making .ipa " , 4 ) ) {
return ERR_SKIP ;
}
2023-12-06 15:18:35 +01:00
2022-12-10 17:59:09 +01:00
List < String > export_args ;
export_args . push_back ( " -exportArchive " ) ;
export_args . push_back ( " -archivePath " ) ;
export_args . push_back ( archive_path ) ;
export_args . push_back ( " -exportOptionsPlist " ) ;
2023-12-06 15:18:35 +01:00
export_args . push_back ( binary_dir + " /export_options.plist " ) ;
2022-12-10 17:59:09 +01:00
export_args . push_back ( " -allowProvisioningUpdates " ) ;
export_args . push_back ( " -exportPath " ) ;
export_args . push_back ( dest_dir ) ;
2023-12-06 15:18:35 +01:00
2022-12-10 17:59:09 +01:00
String export_str ;
err = OS : : get_singleton ( ) - > execute ( " xcodebuild " , export_args , & export_str , nullptr , true ) ;
2023-12-06 15:18:35 +01:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Xcode Build " ) , vformat ( TTR ( " Failed to run xcodebuild with code %d " ) , err ) ) ;
return err ;
}
2022-12-10 17:59:09 +01:00
print_line ( " xcodebuild (.ipa): \n " + export_str ) ;
if ( ! export_str . contains ( " ** EXPORT SUCCEEDED ** " ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Xcode Build " ) , TTR ( " .ipa export failed, see editor log for details. " ) ) ;
return FAILED ;
}
2023-01-11 09:49:11 +01:00
}
2021-07-13 21:46:21 +02:00
# else
2023-01-11 09:49:11 +01:00
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Xcode Build " ) , TTR ( " .ipa can only be built on macOS. Leaving Xcode project without building the package. " ) ) ;
2021-07-13 21:46:21 +02:00
# endif
return OK ;
}
2023-06-11 19:01:39 +02:00
bool EditorExportPlatformIOS : : has_valid_export_configuration ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates , bool p_debug ) const {
2023-10-10 18:48:51 +02:00
# if defined(MODULE_MONO_ENABLED) && !defined(MACOS_ENABLED)
2023-10-02 18:07:20 +02:00
// TODO: Remove this restriction when we don't rely on macOS tools to package up the native libraries anymore.
r_error + = TTR ( " Exporting to iOS when using C#/.NET is experimental and requires macOS. " ) + " \n " ;
return false ;
2023-10-10 18:48:51 +02:00
# else
2023-07-11 22:11:51 +02:00
String err ;
bool valid = false ;
2023-06-24 00:01:53 +02:00
2023-10-10 18:48:51 +02:00
# if defined(MODULE_MONO_ENABLED)
// iOS export is still a work in progress, keep a message as a warning.
err + = TTR ( " Exporting to iOS when using C#/.NET is experimental. " ) + " \n " ;
# endif
2021-07-13 21:46:21 +02:00
// Look for export templates (first official, and if defined custom templates).
2022-07-20 08:28:22 +02:00
bool dvalid = exists_export_template ( " ios.zip " , & err ) ;
2021-07-13 21:46:21 +02:00
bool rvalid = dvalid ; // Both in the same ZIP.
if ( p_preset - > get ( " custom_template/debug " ) ! = " " ) {
dvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/debug " ) ) ;
if ( ! dvalid ) {
err + = TTR ( " Custom debug template not found. " ) + " \n " ;
}
}
if ( p_preset - > get ( " custom_template/release " ) ! = " " ) {
rvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/release " ) ) ;
if ( ! rvalid ) {
err + = TTR ( " Custom release template not found. " ) + " \n " ;
}
}
valid = dvalid | | rvalid ;
r_missing_templates = ! valid ;
2022-07-17 21:26:03 +02:00
if ( ! err . is_empty ( ) ) {
r_error = err ;
}
return valid ;
2023-10-10 18:48:51 +02:00
# endif // !(MODULE_MONO_ENABLED && !MACOS_ENABLED)
2022-07-17 21:26:03 +02:00
}
bool EditorExportPlatformIOS : : has_valid_project_configuration ( const Ref < EditorExportPreset > & p_preset , String & r_error ) const {
String err ;
bool valid = true ;
// Validate the project configuration.
2021-07-13 21:46:21 +02:00
2023-03-09 09:41:52 +01:00
List < ExportOption > options ;
get_export_options ( & options ) ;
for ( const EditorExportPlatform : : ExportOption & E : options ) {
if ( get_export_option_visibility ( p_preset . ptr ( ) , E . option . name ) ) {
String warn = get_export_option_warning ( p_preset . ptr ( ) , E . option . name ) ;
if ( ! warn . is_empty ( ) ) {
err + = warn + " \n " ;
if ( E . required ) {
valid = false ;
}
}
}
2021-07-13 21:46:21 +02:00
}
2023-06-20 04:50:09 +02:00
if ( ! ResourceImporterTextureSettings : : should_import_etc2_astc ( ) ) {
2021-07-13 21:46:21 +02:00
valid = false ;
}
if ( ! err . is_empty ( ) ) {
r_error = err ;
}
return valid ;
}
2022-12-10 17:59:09 +01:00
int EditorExportPlatformIOS : : get_options_count ( ) const {
MutexLock lock ( device_lock ) ;
return devices . size ( ) ;
}
String EditorExportPlatformIOS : : get_options_tooltip ( ) const {
return TTR ( " Select device from the list " ) ;
}
Ref < ImageTexture > EditorExportPlatformIOS : : get_option_icon ( int p_index ) const {
MutexLock lock ( device_lock ) ;
Ref < ImageTexture > icon ;
if ( p_index > = 0 | | p_index < devices . size ( ) ) {
Ref < Theme > theme = EditorNode : : get_singleton ( ) - > get_editor_theme ( ) ;
if ( theme . is_valid ( ) ) {
if ( devices [ p_index ] . simulator ) {
2023-08-13 02:33:39 +02:00
icon = theme - > get_icon ( " IOSSimulator " , EditorStringName ( EditorIcons ) ) ;
2022-12-10 17:59:09 +01:00
} else if ( devices [ p_index ] . wifi ) {
2023-08-13 02:33:39 +02:00
icon = theme - > get_icon ( " IOSDeviceWireless " , EditorStringName ( EditorIcons ) ) ;
2022-12-10 17:59:09 +01:00
} else {
2023-08-13 02:33:39 +02:00
icon = theme - > get_icon ( " IOSDeviceWired " , EditorStringName ( EditorIcons ) ) ;
2022-12-10 17:59:09 +01:00
}
}
}
return icon ;
}
String EditorExportPlatformIOS : : get_option_label ( int p_index ) const {
ERR_FAIL_INDEX_V ( p_index , devices . size ( ) , " " ) ;
MutexLock lock ( device_lock ) ;
return devices [ p_index ] . name ;
}
String EditorExportPlatformIOS : : get_option_tooltip ( int p_index ) const {
ERR_FAIL_INDEX_V ( p_index , devices . size ( ) , " " ) ;
MutexLock lock ( device_lock ) ;
return " UUID: " + devices [ p_index ] . id ;
}
bool EditorExportPlatformIOS : : is_package_name_valid ( const String & p_package , String * r_error ) const {
String pname = p_package ;
if ( pname . length ( ) = = 0 ) {
if ( r_error ) {
* r_error = TTR ( " Identifier is missing. " ) ;
}
return false ;
}
for ( int i = 0 ; i < pname . length ( ) ; i + + ) {
char32_t c = pname [ i ] ;
if ( ! ( is_ascii_alphanumeric_char ( c ) | | c = = ' - ' | | c = = ' . ' ) ) {
if ( r_error ) {
* r_error = vformat ( TTR ( " The character '%s' is not allowed in Identifier. " ) , String : : chr ( c ) ) ;
}
return false ;
}
}
return true ;
}
# ifdef MACOS_ENABLED
2023-11-21 10:43:29 +01:00
bool EditorExportPlatformIOS : : _check_xcode_install ( ) {
static bool xcode_found = false ;
if ( ! xcode_found ) {
2023-12-05 06:52:12 +01:00
Vector < String > mdfind_paths ;
List < String > mdfind_args ;
mdfind_args . push_back ( " kMDItemCFBundleIdentifier=com.apple.dt.Xcode " ) ;
String output ;
Error err = OS : : get_singleton ( ) - > execute ( " mdfind " , mdfind_args , & output ) ;
if ( err = = OK ) {
mdfind_paths = output . split ( " \n " ) ;
}
for ( const String & found_path : mdfind_paths ) {
xcode_found = ! found_path . is_empty ( ) & & DirAccess : : dir_exists_absolute ( found_path . strip_edges ( ) ) ;
if ( xcode_found ) {
break ;
}
2023-11-21 10:43:29 +01:00
}
}
return xcode_found ;
}
2022-12-10 17:59:09 +01:00
void EditorExportPlatformIOS : : _check_for_changes_poll_thread ( void * ud ) {
EditorExportPlatformIOS * ea = static_cast < EditorExportPlatformIOS * > ( ud ) ;
while ( ! ea - > quit_request . is_set ( ) ) {
// Nothing to do if we already know the plugins have changed.
if ( ! ea - > plugins_changed . is_set ( ) ) {
MutexLock lock ( ea - > plugins_lock ) ;
Vector < PluginConfigIOS > loaded_plugins = get_plugins ( ) ;
if ( ea - > plugins . size ( ) ! = loaded_plugins . size ( ) ) {
ea - > plugins_changed . set ( ) ;
} 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 . set ( ) ;
break ;
}
}
}
}
// Check for devices updates.
Vector < Device > ldevices ;
2023-11-30 15:57:38 +01:00
// Enum real devices (via ios_deploy, pre Xcode 15).
2022-12-10 17:59:09 +01:00
String idepl = EDITOR_GET ( " export/ios/ios_deploy " ) ;
2023-11-30 15:57:38 +01:00
if ( ! idepl . is_empty ( ) ) {
2022-12-10 17:59:09 +01:00
String devices ;
List < String > args ;
args . push_back ( " -c " ) ;
args . push_back ( " -timeout " ) ;
args . push_back ( " 1 " ) ;
args . push_back ( " -j " ) ;
args . push_back ( " -u " ) ;
args . push_back ( " -I " ) ;
int ec = 0 ;
Error err = OS : : get_singleton ( ) - > execute ( idepl , args , & devices , & ec , true ) ;
if ( err = = OK & & ec = = 0 ) {
Ref < JSON > json ;
json . instantiate ( ) ;
devices = " { \" devices \" :[ " + devices . replace ( " }{ " , " },{ " ) + " ]} " ;
err = json - > parse ( devices ) ;
if ( err = = OK ) {
Dictionary data = json - > get_data ( ) ;
Array devices = data [ " devices " ] ;
for ( int i = 0 ; i < devices . size ( ) ; i + + ) {
Dictionary device_event = devices [ i ] ;
if ( device_event [ " Event " ] = = " DeviceDetected " ) {
Dictionary device_info = device_event [ " Device " ] ;
Device nd ;
nd . id = device_info [ " DeviceIdentifier " ] ;
2023-11-30 15:57:38 +01:00
nd . name = device_info [ " DeviceName " ] . operator String ( ) + " (ios_deploy, " + ( ( device_event [ " Interface " ] = = " WIFI " ) ? " network " : " wired " ) + " ) " ;
2022-12-10 17:59:09 +01:00
nd . wifi = device_event [ " Interface " ] = = " WIFI " ;
2023-11-30 15:57:38 +01:00
nd . use_ios_deploy = true ;
2022-12-10 17:59:09 +01:00
nd . simulator = false ;
ldevices . push_back ( nd ) ;
}
}
}
}
}
2023-11-30 15:57:38 +01:00
// Enum simulators.
2023-11-21 10:43:29 +01:00
if ( _check_xcode_install ( ) & & ( FileAccess : : exists ( " /usr/bin/xcrun " ) | | FileAccess : : exists ( " /bin/xcrun " ) ) ) {
2023-11-30 15:57:38 +01:00
{
String devices ;
List < String > args ;
args . push_back ( " devicectl " ) ;
args . push_back ( " list " ) ;
args . push_back ( " devices " ) ;
args . push_back ( " -j " ) ;
args . push_back ( " - " ) ;
args . push_back ( " -q " ) ;
int ec = 0 ;
Error err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & devices , & ec , true ) ;
if ( err = = OK & & ec = = 0 ) {
Ref < JSON > json ;
json . instantiate ( ) ;
err = json - > parse ( devices ) ;
if ( err = = OK ) {
const Dictionary & data = json - > get_data ( ) ;
const Dictionary & result = data [ " result " ] ;
const Array & devices = result [ " devices " ] ;
for ( int i = 0 ; i < devices . size ( ) ; i + + ) {
const Dictionary & device_info = devices [ i ] ;
const Dictionary & conn_props = device_info [ " connectionProperties " ] ;
const Dictionary & dev_props = device_info [ " deviceProperties " ] ;
if ( conn_props [ " pairingState " ] = = " paired " & & dev_props [ " developerModeStatus " ] = = " enabled " ) {
2022-12-10 17:59:09 +01:00
Device nd ;
2023-11-30 15:57:38 +01:00
nd . id = device_info [ " identifier " ] ;
nd . name = dev_props [ " name " ] . operator String ( ) + " (devicectl, " + ( ( conn_props [ " transportType " ] = = " localNetwork " ) ? " network " : " wired " ) + " ) " ;
nd . wifi = conn_props [ " transportType " ] = = " localNetwork " ;
nd . simulator = false ;
2022-12-10 17:59:09 +01:00
ldevices . push_back ( nd ) ;
}
}
}
}
}
2023-11-30 15:57:38 +01:00
// Enum simulators.
{
String devices ;
List < String > args ;
args . push_back ( " simctl " ) ;
args . push_back ( " list " ) ;
args . push_back ( " devices " ) ;
args . push_back ( " -j " ) ;
int ec = 0 ;
Error err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & devices , & ec , true ) ;
if ( err = = OK & & ec = = 0 ) {
Ref < JSON > json ;
json . instantiate ( ) ;
err = json - > parse ( devices ) ;
if ( err = = OK ) {
const Dictionary & data = json - > get_data ( ) ;
const Dictionary & devices = data [ " devices " ] ;
for ( const Variant * key = devices . next ( nullptr ) ; key ; key = devices . next ( key ) ) {
const Array & os_devices = devices [ * key ] ;
for ( int i = 0 ; i < os_devices . size ( ) ; i + + ) {
const Dictionary & device_info = os_devices [ i ] ;
if ( device_info [ " isAvailable " ] . operator bool ( ) & & device_info [ " state " ] = = " Booted " ) {
Device nd ;
nd . id = device_info [ " udid " ] ;
nd . name = device_info [ " name " ] . operator String ( ) + " (simulator) " ;
nd . simulator = true ;
ldevices . push_back ( nd ) ;
}
}
}
}
}
}
2022-12-10 17:59:09 +01:00
}
// Update device list.
{
MutexLock lock ( ea - > device_lock ) ;
bool different = false ;
if ( ea - > devices . size ( ) ! = ldevices . size ( ) ) {
different = true ;
} else {
for ( int i = 0 ; i < ea - > devices . size ( ) ; i + + ) {
if ( ea - > devices [ i ] . id ! = ldevices [ i ] . id ) {
different = true ;
break ;
}
}
}
if ( different ) {
ea - > devices = ldevices ;
ea - > devices_changed . set ( ) ;
}
}
uint64_t sleep = 200 ;
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 ( 1000 * sleep ) ;
if ( ea - > quit_request . is_set ( ) ) {
break ;
}
}
}
}
# endif
Error EditorExportPlatformIOS : : run ( const Ref < EditorExportPreset > & p_preset , int p_device , int p_debug_flags ) {
# ifdef MACOS_ENABLED
ERR_FAIL_INDEX_V ( p_device , devices . size ( ) , ERR_INVALID_PARAMETER ) ;
String can_export_error ;
bool can_export_missing_templates ;
if ( ! can_export ( p_preset , can_export_error , can_export_missing_templates ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Run " ) , can_export_error ) ;
return ERR_UNCONFIGURED ;
}
MutexLock lock ( device_lock ) ;
EditorProgress ep ( " run " , vformat ( TTR ( " Running on %s " ) , devices [ p_device ] . name ) , 3 ) ;
String id = " tmpexport. " + uitos ( OS : : get_singleton ( ) - > get_unix_time ( ) ) ;
Ref < DirAccess > filesystem_da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
ERR_FAIL_COND_V_MSG ( filesystem_da . is_null ( ) , ERR_CANT_CREATE , " Cannot create DirAccess for path ' " + EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) + " '. " ) ;
filesystem_da - > make_dir_recursive ( EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( id ) ) ;
String tmp_export_path = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( id ) . path_join ( " export.ipa " ) ;
# define CLEANUP_AND_RETURN(m_err) \
{ \
if ( filesystem_da - > change_dir ( EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( id ) ) = = OK ) { \
filesystem_da - > erase_contents_recursive ( ) ; \
filesystem_da - > change_dir ( " .. " ) ; \
filesystem_da - > remove ( id ) ; \
} \
return m_err ; \
} \
( ( void ) 0 )
Device dev = devices [ p_device ] ;
// Export before sending to device.
Error err = _export_project_helper ( p_preset , true , tmp_export_path , p_debug_flags , dev . simulator , true ) ;
if ( err ! = OK ) {
CLEANUP_AND_RETURN ( err ) ;
}
Vector < String > cmd_args_list ;
String host = EDITOR_GET ( " network/debug/remote_host " ) ;
int remote_port = ( int ) EDITOR_GET ( " network/debug/remote_port " ) ;
if ( p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST ) {
host = " localhost " ;
}
if ( p_debug_flags & DEBUG_FLAG_DUMB_CLIENT ) {
int port = EDITOR_GET ( " filesystem/file_server/port " ) ;
String passwd = EDITOR_GET ( " filesystem/file_server/password " ) ;
cmd_args_list . push_back ( " --remote-fs " ) ;
cmd_args_list . push_back ( host + " : " + itos ( port ) ) ;
if ( ! passwd . is_empty ( ) ) {
cmd_args_list . push_back ( " --remote-fs-password " ) ;
cmd_args_list . push_back ( passwd ) ;
}
}
if ( p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG ) {
cmd_args_list . push_back ( " --remote-debug " ) ;
cmd_args_list . push_back ( get_debug_protocol ( ) + host + " : " + String : : num ( remote_port ) ) ;
List < String > breakpoints ;
ScriptEditor : : get_singleton ( ) - > get_breakpoints ( & breakpoints ) ;
if ( breakpoints . size ( ) ) {
cmd_args_list . push_back ( " --breakpoints " ) ;
String bpoints ;
for ( const List < String > : : Element * E = breakpoints . front ( ) ; E ; E = E - > next ( ) ) {
bpoints + = E - > get ( ) . replace ( " " , " %20 " ) ;
if ( E - > next ( ) ) {
bpoints + = " , " ;
}
}
cmd_args_list . push_back ( bpoints ) ;
}
}
if ( p_debug_flags & DEBUG_FLAG_VIEW_COLLISIONS ) {
cmd_args_list . push_back ( " --debug-collisions " ) ;
}
if ( p_debug_flags & DEBUG_FLAG_VIEW_NAVIGATION ) {
cmd_args_list . push_back ( " --debug-navigation " ) ;
}
if ( dev . simulator ) {
// Deploy and run on simulator.
if ( ep . step ( " Installing to simulator... " , 3 ) ) {
CLEANUP_AND_RETURN ( ERR_SKIP ) ;
} else {
List < String > args ;
args . push_back ( " simctl " ) ;
args . push_back ( " install " ) ;
args . push_back ( dev . id ) ;
args . push_back ( EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( id ) . path_join ( " export.xcarchive/Products/Applications/export.app " ) ) ;
String log ;
int ec ;
err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & log , & ec , true ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Run " ) , TTR ( " Could not start simctl executable. " ) ) ;
CLEANUP_AND_RETURN ( err ) ;
}
if ( ec ! = 0 ) {
print_line ( " simctl install: \n " + log ) ;
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Run " ) , TTR ( " Installation failed, see editor log for details. " ) ) ;
CLEANUP_AND_RETURN ( ERR_UNCONFIGURED ) ;
}
}
if ( ep . step ( " Running on simulator... " , 4 ) ) {
CLEANUP_AND_RETURN ( ERR_SKIP ) ;
} else {
List < String > args ;
args . push_back ( " simctl " ) ;
args . push_back ( " launch " ) ;
2023-11-30 15:57:38 +01:00
args . push_back ( " --terminate-running-process " ) ;
2022-12-10 17:59:09 +01:00
args . push_back ( dev . id ) ;
args . push_back ( p_preset - > get ( " application/bundle_identifier " ) ) ;
for ( const String & E : cmd_args_list ) {
args . push_back ( E ) ;
}
String log ;
int ec ;
err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & log , & ec , true ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Run " ) , TTR ( " Could not start simctl executable. " ) ) ;
CLEANUP_AND_RETURN ( err ) ;
}
if ( ec ! = 0 ) {
print_line ( " simctl launch: \n " + log ) ;
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Run " ) , TTR ( " Running failed, see editor log for details. " ) ) ;
}
}
2023-11-30 15:57:38 +01:00
} else if ( dev . use_ios_deploy ) {
// Deploy and run on real device (via ios-deploy).
2022-12-10 17:59:09 +01:00
if ( ep . step ( " Installing and running on device... " , 4 ) ) {
CLEANUP_AND_RETURN ( ERR_SKIP ) ;
} else {
List < String > args ;
args . push_back ( " -u " ) ;
args . push_back ( " -I " ) ;
args . push_back ( " --id " ) ;
args . push_back ( dev . id ) ;
args . push_back ( " --justlaunch " ) ;
args . push_back ( " --bundle " ) ;
args . push_back ( EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( id ) . path_join ( " export.xcarchive/Products/Applications/export.app " ) ) ;
String app_args ;
for ( const String & E : cmd_args_list ) {
app_args + = E + " " ;
}
if ( ! app_args . is_empty ( ) ) {
args . push_back ( " --args " ) ;
args . push_back ( app_args ) ;
}
String idepl = EDITOR_GET ( " export/ios/ios_deploy " ) ;
if ( idepl . is_empty ( ) ) {
idepl = " ios-deploy " ;
}
String log ;
int ec ;
err = OS : : get_singleton ( ) - > execute ( idepl , args , & log , & ec , true ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Run " ) , TTR ( " Could not start ios-deploy executable. " ) ) ;
CLEANUP_AND_RETURN ( err ) ;
}
if ( ec ! = 0 ) {
print_line ( " ios-deploy: \n " + log ) ;
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Run " ) , TTR ( " Installation/running failed, see editor log for details. " ) ) ;
CLEANUP_AND_RETURN ( ERR_UNCONFIGURED ) ;
}
}
2023-11-30 15:57:38 +01:00
} else {
// Deploy and run on real device.
if ( ep . step ( " Installing to device... " , 3 ) ) {
CLEANUP_AND_RETURN ( ERR_SKIP ) ;
} else {
List < String > args ;
args . push_back ( " devicectl " ) ;
args . push_back ( " device " ) ;
args . push_back ( " install " ) ;
args . push_back ( " app " ) ;
args . push_back ( " -d " ) ;
args . push_back ( dev . id ) ;
args . push_back ( EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( id ) . path_join ( " export.xcarchive/Products/Applications/export.app " ) ) ;
String log ;
int ec ;
err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & log , & ec , true ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Run " ) , TTR ( " Could not start device executable. " ) ) ;
CLEANUP_AND_RETURN ( err ) ;
}
if ( ec ! = 0 ) {
print_line ( " device install: \n " + log ) ;
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Run " ) , TTR ( " Installation failed, see editor log for details. " ) ) ;
CLEANUP_AND_RETURN ( ERR_UNCONFIGURED ) ;
}
}
if ( ep . step ( " Running on device... " , 4 ) ) {
CLEANUP_AND_RETURN ( ERR_SKIP ) ;
} else {
List < String > args ;
args . push_back ( " devicectl " ) ;
args . push_back ( " device " ) ;
args . push_back ( " process " ) ;
args . push_back ( " launch " ) ;
args . push_back ( " --terminate-existing " ) ;
args . push_back ( " -d " ) ;
args . push_back ( dev . id ) ;
args . push_back ( p_preset - > get ( " application/bundle_identifier " ) ) ;
for ( const String & E : cmd_args_list ) {
args . push_back ( E ) ;
}
String log ;
int ec ;
err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & log , & ec , true ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Run " ) , TTR ( " Could not start devicectl executable. " ) ) ;
CLEANUP_AND_RETURN ( err ) ;
}
if ( ec ! = 0 ) {
print_line ( " devicectl launch: \n " + log ) ;
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Run " ) , TTR ( " Running failed, see editor log for details. " ) ) ;
}
}
2022-12-10 17:59:09 +01:00
}
CLEANUP_AND_RETURN ( OK ) ;
# undef CLEANUP_AND_RETURN
# else
return ERR_UNCONFIGURED ;
# endif
}
2021-07-13 21:46:21 +02:00
EditorExportPlatformIOS : : EditorExportPlatformIOS ( ) {
2023-03-09 09:41:52 +01:00
if ( EditorNode : : get_singleton ( ) ) {
2022-05-08 09:46:53 +02:00
# ifdef MODULE_SVG_ENABLED
2023-03-09 09:41:52 +01:00
Ref < Image > img = memnew ( Image ) ;
const bool upsample = ! Math : : is_equal_approx ( Math : : round ( EDSCALE ) , EDSCALE ) ;
2022-05-08 09:46:53 +02:00
2023-06-15 01:27:56 +02:00
ImageLoaderSVG : : create_image_from_string ( img , _ios_logo_svg , EDSCALE , upsample , false ) ;
2023-03-09 09:41:52 +01:00
logo = ImageTexture : : create_from_image ( img ) ;
2022-12-10 17:59:09 +01:00
ImageLoaderSVG : : create_image_from_string ( img , _ios_run_icon_svg , EDSCALE , upsample , false ) ;
run_icon = ImageTexture : : create_from_image ( img ) ;
2022-05-08 09:46:53 +02:00
# endif
2023-03-09 09:41:52 +01:00
plugins_changed . set ( ) ;
2022-12-10 17:59:09 +01:00
devices_changed . set ( ) ;
# ifdef MACOS_ENABLED
2023-03-09 09:41:52 +01:00
check_for_changes_thread . start ( _check_for_changes_poll_thread , this ) ;
2022-08-15 11:30:43 +02:00
# endif
2023-03-09 09:41:52 +01:00
}
2021-07-13 21:46:21 +02:00
}
EditorExportPlatformIOS : : ~ EditorExportPlatformIOS ( ) {
2022-12-10 17:59:09 +01:00
# ifdef MACOS_ENABLED
2021-07-13 21:46:21 +02:00
quit_request . set ( ) ;
2023-04-27 18:34:30 +02:00
if ( check_for_changes_thread . is_started ( ) ) {
check_for_changes_thread . wait_to_finish ( ) ;
}
2022-08-15 11:30:43 +02:00
# endif
2021-07-13 21:46:21 +02:00
}