summaryrefslogtreecommitdiffstats
path: root/platform/ios/export/export_plugin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/export/export_plugin.cpp')
-rw-r--r--platform/ios/export/export_plugin.cpp682
1 files changed, 619 insertions, 63 deletions
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index 33389129b7..5b65c8b485 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -40,6 +40,8 @@
#include "editor/editor_paths.h"
#include "editor/editor_string_names.h"
#include "editor/export/editor_export.h"
+#include "editor/export/lipo.h"
+#include "editor/export/macho.h"
#include "editor/import/resource_importer_texture_settings.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/themes/editor_scale.h"
@@ -106,6 +108,94 @@ static const IconInfo icon_infos[] = {
{ PNAME("icons/notification_60x60"), "iphone", "Icon-60.png", "60", "3x", "20x20", false }
};
+struct APIAccessInfo {
+ String prop_name;
+ String type_name;
+ Vector<String> prop_flag_value;
+ Vector<String> prop_flag_name;
+ int default_value;
+};
+
+static const APIAccessInfo api_info[] = {
+ { "file_timestamp",
+ "NSPrivacyAccessedAPICategoryFileTimestamp",
+ { "DDA9.1", "C617.1", "3B52.1" },
+ { "Display to user on-device:", "Inside app or group container", "Files provided to app by user" },
+ 3 },
+ { "system_boot_time",
+ "NSPrivacyAccessedAPICategorySystemBootTime",
+ { "35F9.1", "8FFB.1", "3D61.1" },
+ { "Measure time on-device", "Calculate absolute event timestamps", "User-initiated bug report" },
+ 1 },
+ { "disk_space",
+ "NSPrivacyAccessedAPICategoryDiskSpace",
+ { "E174.1", "85F4.1", "7D9E.1", "B728.1" },
+ { "Write or delete file on-device", "Display to user on-device", "User-initiated bug report", "Health research app" },
+ 3 },
+ { "active_keyboard",
+ "NSPrivacyAccessedAPICategoryActiveKeyboards",
+ { "3EC4.1", "54BD.1" },
+ { "Custom keyboard app on-device", "Customize UI on-device:2" },
+ 0 },
+ { "user_defaults",
+ "NSPrivacyAccessedAPICategoryUserDefaults",
+ { "1C8F.1", "AC6B.1", "CA92.1" },
+ { "Access info from same App Group", "Access managed app configuration", "Access info from same app" },
+ 0 }
+};
+
+struct DataCollectionInfo {
+ String prop_name;
+ String type_name;
+};
+
+static const DataCollectionInfo data_collect_type_info[] = {
+ { "name", "NSPrivacyCollectedDataTypeName" },
+ { "email_address", "NSPrivacyCollectedDataTypeEmailAddress" },
+ { "phone_number", "NSPrivacyCollectedDataTypePhoneNumber" },
+ { "physical_address", "NSPrivacyCollectedDataTypePhysicalAddress" },
+ { "other_contact_info", "NSPrivacyCollectedDataTypeOtherUserContactInfo" },
+ { "health", "NSPrivacyCollectedDataTypeHealth" },
+ { "fitness", "NSPrivacyCollectedDataTypeFitness" },
+ { "payment_info", "NSPrivacyCollectedDataTypePaymentInfo" },
+ { "credit_info", "NSPrivacyCollectedDataTypeCreditInfo" },
+ { "other_financial_info", "NSPrivacyCollectedDataTypeOtherFinancialInfo" },
+ { "precise_location", "NSPrivacyCollectedDataTypePreciseLocation" },
+ { "coarse_location", "NSPrivacyCollectedDataTypeCoarseLocation" },
+ { "sensitive_info", "NSPrivacyCollectedDataTypeSensitiveInfo" },
+ { "contacts", "NSPrivacyCollectedDataTypeContacts" },
+ { "emails_or_text_messages", "NSPrivacyCollectedDataTypeEmailsOrTextMessages" },
+ { "photos_or_videos", "NSPrivacyCollectedDataTypePhotosorVideos" },
+ { "audio_data", "NSPrivacyCollectedDataTypeAudioData" },
+ { "gameplay_content", "NSPrivacyCollectedDataTypeGameplayContent" },
+ { "customer_support", "NSPrivacyCollectedDataTypeCustomerSupport" },
+ { "other_user_content", "NSPrivacyCollectedDataTypeOtherUserContent" },
+ { "browsing_history", "NSPrivacyCollectedDataTypeBrowsingHistory" },
+ { "search_hhistory", "NSPrivacyCollectedDataTypeSearchHistory" },
+ { "user_id", "NSPrivacyCollectedDataTypeUserID" },
+ { "device_id", "NSPrivacyCollectedDataTypeDeviceID" },
+ { "purchase_history", "NSPrivacyCollectedDataTypePurchaseHistory" },
+ { "product_interaction", "NSPrivacyCollectedDataTypeProductInteraction" },
+ { "advertising_data", "NSPrivacyCollectedDataTypeAdvertisingData" },
+ { "other_usage_data", "NSPrivacyCollectedDataTypeOtherUsageData" },
+ { "crash_data", "NSPrivacyCollectedDataTypeCrashData" },
+ { "performance_data", "NSPrivacyCollectedDataTypePerformanceData" },
+ { "other_diagnostic_data", "NSPrivacyCollectedDataTypeOtherDiagnosticData" },
+ { "environment_scanning", "NSPrivacyCollectedDataTypeEnvironmentScanning" },
+ { "hands", "NSPrivacyCollectedDataTypeHands" },
+ { "head", "NSPrivacyCollectedDataTypeHead" },
+ { "other_data_types", "NSPrivacyCollectedDataTypeOtherDataTypes" },
+};
+
+static const DataCollectionInfo data_collect_purpose_info[] = {
+ { "Analytics", "NSPrivacyCollectedDataTypePurposeAnalytics" },
+ { "App Functionality", "NSPrivacyCollectedDataTypePurposeAppFunctionality" },
+ { "Developer Advertising", "NSPrivacyCollectedDataTypePurposeDeveloperAdvertising" },
+ { "Third-party Advertising", "NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising" },
+ { "Product Personalization", "NSPrivacyCollectedDataTypePurposeProductPersonalization" },
+ { "Other", "NSPrivacyCollectedDataTypePurposeOther" },
+};
+
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") {
@@ -119,6 +209,21 @@ String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPres
if (!is_package_name_valid(identifier, &pn_err)) {
return TTR("Invalid Identifier:") + " " + pn_err;
}
+ } else if (p_name == "privacy/file_timestamp_access_reasons") {
+ int access = p_preset->get("privacy/file_timestamp_access_reasons");
+ if (access == 0) {
+ return TTR("At least one file timestamp access reason should be selected.");
+ }
+ } else if (p_name == "privacy/disk_space_access_reasons") {
+ int access = p_preset->get("privacy/disk_space_access_reasons");
+ if (access == 0) {
+ return TTR("At least one disk space access reason should be selected.");
+ }
+ } else if (p_name == "privacy/system_boot_time_access_reasons") {
+ int access = p_preset->get("privacy/system_boot_time_access_reasons");
+ if (access == 0) {
+ return TTR("At least one system boot time access reason should be selected.");
+ }
}
}
return String();
@@ -140,6 +245,15 @@ bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPre
return false;
}
+ if (p_preset == nullptr) {
+ return true;
+ }
+
+ bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
+ if (p_option.begins_with("privacy") || p_option == "application/generate_simulator_library_if_missing") {
+ return advanced_options_enabled;
+ }
+
return true;
}
@@ -175,6 +289,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/export_project_only"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/delete_old_export_files_unconditionally"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/generate_simulator_library_if_missing"), true));
Vector<PluginConfigIOS> found_plugins = get_plugins();
for (int i = 0; i < found_plugins.size(); i++) {
@@ -220,6 +336,37 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
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"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ for (uint64_t i = 0; i < sizeof(api_info) / sizeof(api_info[0]); ++i) {
+ String prop_name = vformat("privacy/%s_access_reasons", api_info[i].prop_name);
+ String hint;
+ for (int j = 0; j < api_info[i].prop_flag_value.size(); j++) {
+ if (j != 0) {
+ hint += ",";
+ }
+ hint += vformat("%s - %s:%d", api_info[i].prop_flag_value[j], api_info[i].prop_flag_name[j], (1 << j));
+ }
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, prop_name, PROPERTY_HINT_FLAGS, hint), api_info[i].default_value));
+ }
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "privacy/tracking_enabled"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "privacy/tracking_domains"), Vector<String>()));
+
+ {
+ String hint;
+ for (uint64_t i = 0; i < sizeof(data_collect_purpose_info) / sizeof(data_collect_purpose_info[0]); ++i) {
+ if (i != 0) {
+ hint += ",";
+ }
+ hint += vformat("%s:%d", data_collect_purpose_info[i].prop_name, (1 << i));
+ }
+ for (uint64_t i = 0; i < sizeof(data_collect_type_info) / sizeof(data_collect_type_info[0]); ++i) {
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/collected", data_collect_type_info[i].prop_name)), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/linked_to_user", data_collect_type_info[i].prop_name)), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/used_for_tracking", data_collect_type_info[i].prop_name)), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, vformat("privacy/collected_data/%s/collection_purposes", data_collect_type_info[i].prop_name), PROPERTY_HINT_FLAGS, hint), 0));
+ }
+ }
+
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)) {
@@ -256,65 +403,65 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
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) {
+ if (lines[i].contains("$binary")) {
strnew += lines[i].replace("$binary", p_config.binary_name) + "\n";
- } else if (lines[i].find("$modules_buildfile") != -1) {
+ } else if (lines[i].contains("$modules_buildfile")) {
strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n";
- } else if (lines[i].find("$modules_fileref") != -1) {
+ } else if (lines[i].contains("$modules_fileref")) {
strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n";
- } else if (lines[i].find("$modules_buildphase") != -1) {
+ } else if (lines[i].contains("$modules_buildphase")) {
strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n";
- } else if (lines[i].find("$modules_buildgrp") != -1) {
+ } else if (lines[i].contains("$modules_buildgrp")) {
strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n";
- } else if (lines[i].find("$name") != -1) {
+ } else if (lines[i].contains("$name")) {
strnew += lines[i].replace("$name", p_config.pkg_name) + "\n";
- } else if (lines[i].find("$bundle_identifier") != -1) {
+ } else if (lines[i].contains("$bundle_identifier")) {
strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n";
- } else if (lines[i].find("$short_version") != -1) {
+ } else if (lines[i].contains("$short_version")) {
strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n";
- } else if (lines[i].find("$version") != -1) {
+ } else if (lines[i].contains("$version")) {
strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n";
- } else if (lines[i].find("$min_version") != -1) {
+ } else if (lines[i].contains("$min_version")) {
strnew += lines[i].replace("$min_version", p_preset->get("application/min_ios_version")) + "\n";
- } else if (lines[i].find("$signature") != -1) {
+ } else if (lines[i].contains("$signature")) {
strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n";
- } else if (lines[i].find("$team_id") != -1) {
+ } else if (lines[i].contains("$team_id")) {
strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n";
- } else if (lines[i].find("$default_build_config") != -1) {
+ } else if (lines[i].contains("$default_build_config")) {
strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n";
- } else if (lines[i].find("$export_method") != -1) {
+ } else if (lines[i].contains("$export_method")) {
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) {
+ } else if (lines[i].contains("$provisioning_profile_uuid_release")) {
strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE)) + "\n";
- } else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) {
+ } else if (lines[i].contains("$provisioning_profile_uuid_debug")) {
strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG)) + "\n";
- } else if (lines[i].find("$code_sign_style_debug") != -1) {
+ } else if (lines[i].contains("$code_sign_style_debug")) {
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) {
+ } else if (lines[i].contains("$code_sign_style_release")) {
if (rel_manual) {
strnew += lines[i].replace("$code_sign_style_release", "Manual") + "\n";
} else {
strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n";
}
- } else if (lines[i].find("$provisioning_profile_uuid") != -1) {
+ } else if (lines[i].contains("$provisioning_profile_uuid")) {
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);
strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n";
- } else if (lines[i].find("$code_sign_identity_debug") != -1) {
+ } else if (lines[i].contains("$code_sign_identity_debug")) {
strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n";
- } else if (lines[i].find("$code_sign_identity_release") != -1) {
+ } else if (lines[i].contains("$code_sign_identity_release")) {
strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n";
- } else if (lines[i].find("$additional_plist_content") != -1) {
+ } else if (lines[i].contains("$additional_plist_content")) {
strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n";
- } else if (lines[i].find("$godot_archs") != -1) {
+ } else if (lines[i].contains("$godot_archs")) {
strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n";
- } else if (lines[i].find("$linker_flags") != -1) {
+ } else if (lines[i].contains("$linker_flags")) {
strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n";
- } else if (lines[i].find("$targeted_device_family") != -1) {
+ } else if (lines[i].contains("$targeted_device_family")) {
String xcode_value;
switch ((int)p_preset->get("application/targeted_device_family")) {
case 0: // iPhone
@@ -328,16 +475,16 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
break;
}
strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n";
- } else if (lines[i].find("$cpp_code") != -1) {
+ } else if (lines[i].contains("$cpp_code")) {
strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n";
- } else if (lines[i].find("$docs_in_place") != -1) {
+ } else if (lines[i].contains("$docs_in_place")) {
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) {
+ } else if (lines[i].contains("$docs_sharing")) {
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) {
+ } else if (lines[i].contains("$entitlements_push_notifications")) {
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) {
+ } else if (lines[i].contains("$required_device_capabilities")) {
String capabilities;
// I've removed armv7 as we can run on 64bit only devices
@@ -359,7 +506,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
}
strnew += lines[i].replace("$required_device_capabilities", capabilities);
- } else if (lines[i].find("$interface_orientations") != -1) {
+ } else if (lines[i].contains("$interface_orientations")) {
String orientations;
const DisplayServer::ScreenOrientation screen_orientation =
DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation")));
@@ -397,35 +544,73 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
}
strnew += lines[i].replace("$interface_orientations", orientations);
- } else if (lines[i].find("$camera_usage_description") != -1) {
+ } else if (lines[i].contains("$ipad_interface_orientations")) {
+ 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>UIInterfaceOrientationLandscapeRight</string>\n";
+ break;
+ case DisplayServer::SCREEN_PORTRAIT:
+ orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
+ break;
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
+ orientations += "<string>UIInterfaceOrientationLandscapeLeft</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("$ipad_interface_orientations", orientations);
+ } else if (lines[i].contains("$camera_usage_description")) {
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) {
+ } else if (lines[i].contains("$microphone_usage_description")) {
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) {
+ } else if (lines[i].contains("$photolibrary_usage_description")) {
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) {
+ } else if (lines[i].contains("$plist_launch_screen_name")) {
String value = "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>";
strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n";
- } else if (lines[i].find("$pbx_launch_screen_file_reference") != -1) {
+ } else if (lines[i].contains("$pbx_launch_screen_file_reference")) {
String value = "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };";
strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n";
- } else if (lines[i].find("$pbx_launch_screen_copy_files") != -1) {
+ } else if (lines[i].contains("$pbx_launch_screen_copy_files")) {
String value = "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */,";
strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n";
- } else if (lines[i].find("$pbx_launch_screen_build_phase") != -1) {
+ } else if (lines[i].contains("$pbx_launch_screen_build_phase")) {
String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */,";
strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n";
- } else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) {
+ } else if (lines[i].contains("$pbx_launch_screen_build_reference")) {
String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };";
strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n";
#ifndef DISABLE_DEPRECATED
- } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) {
+ } else if (lines[i].contains("$pbx_launch_image_usage_setting")) {
strnew += lines[i].replace("$pbx_launch_image_usage_setting", "") + "\n";
#endif
- } else if (lines[i].find("$launch_screen_image_mode") != -1) {
+ } else if (lines[i].contains("$launch_screen_image_mode")) {
int image_scale_mode = p_preset->get("storyboard/image_scale_mode");
String value;
@@ -442,7 +627,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
}
strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n";
- } else if (lines[i].find("$launch_screen_background_color") != -1) {
+ } else if (lines[i].contains("$launch_screen_background_color")) {
bool use_custom = p_preset->get("storyboard/use_custom_bg_color");
Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : GLOBAL_GET("application/boot_splash/bg_color");
const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\"";
@@ -455,7 +640,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
String value = value_format.format(value_dictionary, "$_");
strnew += lines[i].replace("$launch_screen_background_color", value) + "\n";
- } else if (lines[i].find("$pbx_locale_file_reference") != -1) {
+ } else if (lines[i].contains("$pbx_locale_file_reference")) {
String locale_files;
Vector<String> translations = GLOBAL_GET("internationalization/locale/translations");
if (translations.size() > 0) {
@@ -474,7 +659,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
}
}
strnew += lines[i].replace("$pbx_locale_file_reference", locale_files);
- } else if (lines[i].find("$pbx_locale_build_reference") != -1) {
+ } else if (lines[i].contains("$pbx_locale_build_reference")) {
String locale_files;
Vector<String> translations = GLOBAL_GET("internationalization/locale/translations");
if (translations.size() > 0) {
@@ -493,10 +678,10 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
}
}
strnew += lines[i].replace("$pbx_locale_build_reference", locale_files);
- } else if (lines[i].find("$swift_runtime_migration") != -1) {
+ } else if (lines[i].contains("$swift_runtime_migration")) {
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) {
+ } else if (lines[i].contains("$swift_runtime_build_settings")) {
String value = !p_config.use_swift_runtime ? "" : R"(
CLANG_ENABLE_MODULES = YES;
SWIFT_OBJC_BRIDGING_HEADER = "$binary/dummy.h";
@@ -504,24 +689,105 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
)";
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) {
+ } else if (lines[i].contains("$swift_runtime_fileref")) {
String value = !p_config.use_swift_runtime ? "" : R"(
90B4C2AA2680BC560039117A /* dummy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "dummy.h"; sourceTree = "<group>"; };
90B4C2B52680C7E90039117A /* 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) {
+ } else if (lines[i].contains("$swift_runtime_binary_files")) {
String value = !p_config.use_swift_runtime ? "" : R"(
90B4C2AA2680BC560039117A /* dummy.h */,
90B4C2B52680C7E90039117A /* dummy.swift */,
)";
strnew += lines[i].replace("$swift_runtime_binary_files", value) + "\n";
- } else if (lines[i].find("$swift_runtime_buildfile") != -1) {
+ } else if (lines[i].contains("$swift_runtime_buildfile")) {
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) {
+ } else if (lines[i].contains("$swift_runtime_build_phase")) {
String value = !p_config.use_swift_runtime ? "" : "90B4C2B62680C7E90039117A /* dummy.swift */,";
strnew += lines[i].replace("$swift_runtime_build_phase", value) + "\n";
+ } else if (lines[i].contains("$priv_collection")) {
+ bool section_opened = false;
+ for (uint64_t j = 0; j < sizeof(data_collect_type_info) / sizeof(data_collect_type_info[0]); ++j) {
+ bool data_collected = p_preset->get(vformat("privacy/collected_data/%s/collected", data_collect_type_info[j].prop_name));
+ bool linked = p_preset->get(vformat("privacy/collected_data/%s/linked_to_user", data_collect_type_info[j].prop_name));
+ bool tracking = p_preset->get(vformat("privacy/collected_data/%s/used_for_tracking", data_collect_type_info[j].prop_name));
+ int purposes = p_preset->get(vformat("privacy/collected_data/%s/collection_purposes", data_collect_type_info[j].prop_name));
+ if (data_collected) {
+ if (!section_opened) {
+ section_opened = true;
+ strnew += "\t<key>NSPrivacyCollectedDataTypes</key>\n";
+ strnew += "\t<array>\n";
+ }
+ strnew += "\t\t<dict>\n";
+ strnew += "\t\t\t<key>NSPrivacyCollectedDataType</key>\n";
+ strnew += vformat("\t\t\t<string>%s</string>\n", data_collect_type_info[j].type_name);
+ strnew += "\t\t\t\t<key>NSPrivacyCollectedDataTypeLinked</key>\n";
+ if (linked) {
+ strnew += "\t\t\t\t<true/>\n";
+ } else {
+ strnew += "\t\t\t\t<false/>\n";
+ }
+ strnew += "\t\t\t\t<key>NSPrivacyCollectedDataTypeTracking</key>\n";
+ if (tracking) {
+ strnew += "\t\t\t\t<true/>\n";
+ } else {
+ strnew += "\t\t\t\t<false/>\n";
+ }
+ if (purposes != 0) {
+ strnew += "\t\t\t\t<key>NSPrivacyCollectedDataTypePurposes</key>\n";
+ strnew += "\t\t\t\t<array>\n";
+ for (uint64_t k = 0; k < sizeof(data_collect_purpose_info) / sizeof(data_collect_purpose_info[0]); ++k) {
+ if (purposes & (1 << k)) {
+ strnew += vformat("\t\t\t\t\t<string>%s</string>\n", data_collect_purpose_info[k].type_name);
+ }
+ }
+ strnew += "\t\t\t\t</array>\n";
+ }
+ strnew += "\t\t\t</dict>\n";
+ }
+ }
+ if (section_opened) {
+ strnew += "\t</array>\n";
+ }
+ } else if (lines[i].contains("$priv_tracking")) {
+ bool tracking = p_preset->get("privacy/tracking_enabled");
+ strnew += "\t<key>NSPrivacyTracking</key>\n";
+ if (tracking) {
+ strnew += "\t<true/>\n";
+ } else {
+ strnew += "\t<false/>\n";
+ }
+ Vector<String> tracking_domains = p_preset->get("privacy/tracking_domains");
+ if (!tracking_domains.is_empty()) {
+ strnew += "\t<key>NSPrivacyTrackingDomains</key>\n";
+ strnew += "\t<array>\n";
+ for (const String &E : tracking_domains) {
+ strnew += "\t\t<string>" + E + "</string>\n";
+ }
+ strnew += "\t</array>\n";
+ }
+ } else if (lines[i].contains("$priv_api_types")) {
+ strnew += "\t<array>\n";
+ for (uint64_t j = 0; j < sizeof(api_info) / sizeof(api_info[0]); ++j) {
+ int api_access = p_preset->get(vformat("privacy/%s_access_reasons", api_info[j].prop_name));
+ if (api_access != 0) {
+ strnew += "\t\t<dict>\n";
+ strnew += "\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n";
+ strnew += "\t\t\t<array>\n";
+ for (int k = 0; k < api_info[j].prop_flag_value.size(); k++) {
+ if (api_access & (1 << k)) {
+ strnew += vformat("\t\t\t\t<string>%s</string>\n", api_info[j].prop_flag_value[k]);
+ }
+ }
+ strnew += "\t\t\t</array>\n";
+ strnew += "\t\t\t<key>NSPrivacyAccessedAPIType</key>\n";
+ strnew += vformat("\t\t\t<string>%s</string>\n", api_info[j].type_name);
+ strnew += "\t\t</dict>\n";
+ }
+ }
+ strnew += "\t</array>\n";
} else {
strnew += lines[i] + "\n";
}
@@ -887,6 +1153,167 @@ struct ExportLibsData {
String dest_dir;
};
+bool EditorExportPlatformIOS::_archive_has_arm64(const String &p_path, uint32_t *r_cputype, uint32_t *r_cpusubtype) const {
+ bool has_arm64_image = false;
+ if (FileAccess::exists(p_path)) {
+ if (LipO::is_lipo(p_path)) {
+ // LipO.
+ Ref<LipO> lipo;
+ lipo.instantiate();
+ if (lipo->open_file(p_path)) {
+ for (int i = 0; i < lipo->get_arch_count(); i++) {
+ if (lipo->get_arch_cputype(i) == 0x100000c && lipo->get_arch_cpusubtype(i) == 0) {
+ has_arm64_image = true;
+ break;
+ }
+ }
+ }
+ lipo->close();
+ } else {
+ // Single architecture archive.
+ Ref<FileAccess> sim_f = FileAccess::open(p_path, FileAccess::READ);
+ if (sim_f.is_valid()) {
+ char magic[9] = {};
+ sim_f->get_buffer((uint8_t *)&magic[0], 8);
+ if (String(magic) == String("!<arch>\n")) {
+ while (!sim_f->eof_reached()) {
+ // Read file metadata.
+ char name_short[17] = {};
+ char size_short[11] = {};
+ sim_f->get_buffer((uint8_t *)&name_short[0], 16);
+ sim_f->seek(sim_f->get_position() + 12 + 6 + 6 + 8); // Skip modification time, owner ID, group ID, file mode.
+ sim_f->get_buffer((uint8_t *)&size_short[0], 10);
+ sim_f->seek(sim_f->get_position() + 2); // Skip end marker.
+
+ int64_t file_size = String(size_short).to_int();
+ int64_t next_off = sim_f->get_position() + file_size;
+
+ String name = String(name_short); // Skip extended name.
+ if (name.is_empty() || file_size == 0) {
+ break;
+ }
+ if (name.begins_with("#1/")) {
+ int64_t name_len = String(name_short).replace("#1/", "").to_int();
+ sim_f->seek(sim_f->get_position() + name_len);
+ }
+
+ // Read file content.
+ uint32_t obj_magic = sim_f->get_32();
+
+ bool swap = (obj_magic == 0xcffaedfe || obj_magic == 0xcefaedfe);
+ if (obj_magic == 0xcefaedfe || obj_magic == 0xfeedface || obj_magic == 0xcffaedfe || obj_magic == 0xfeedfacf) {
+ uint32_t cputype = sim_f->get_32();
+ uint32_t cpusubtype = sim_f->get_32();
+ if (swap) {
+ cputype = BSWAP32(cputype);
+ cpusubtype = BSWAP32(cpusubtype);
+ }
+ if (r_cputype) {
+ *r_cputype = cputype;
+ }
+ if (r_cpusubtype) {
+ *r_cpusubtype = cpusubtype;
+ }
+ if (cputype == 0x100000c && cpusubtype == 0) {
+ has_arm64_image = true;
+ }
+ break;
+ }
+ sim_f->seek(next_off);
+ }
+ }
+ sim_f->close();
+ }
+ }
+ }
+ return has_arm64_image;
+}
+
+int EditorExportPlatformIOS::_archive_convert_to_simulator(const String &p_path) const {
+ int commands_patched = 0;
+ Ref<FileAccess> sim_f = FileAccess::open(p_path, FileAccess::READ_WRITE);
+ if (sim_f.is_valid()) {
+ char magic[9] = {};
+ sim_f->get_buffer((uint8_t *)&magic[0], 8);
+ if (String(magic) == String("!<arch>\n")) {
+ while (!sim_f->eof_reached()) {
+ // Read file metadata.
+ char name_short[17] = {};
+ char size_short[11] = {};
+ sim_f->get_buffer((uint8_t *)&name_short[0], 16);
+ sim_f->seek(sim_f->get_position() + 12 + 6 + 6 + 8); // Skip modification time, owner ID, group ID, file mode.
+ sim_f->get_buffer((uint8_t *)&size_short[0], 10);
+ sim_f->seek(sim_f->get_position() + 2); // Skip end marker.
+
+ int64_t file_size = String(size_short).to_int();
+ int64_t next_off = sim_f->get_position() + file_size;
+
+ String name = String(name_short); // Skip extended name.
+ if (name.is_empty() || file_size == 0) {
+ break;
+ }
+ if (name.begins_with("#1/")) {
+ int64_t name_len = String(name_short).replace("#1/", "").to_int();
+ sim_f->seek(sim_f->get_position() + name_len);
+ }
+
+ // Read file content.
+ uint32_t obj_magic = sim_f->get_32();
+
+ bool swap = (obj_magic == 0xcffaedfe || obj_magic == 0xcefaedfe);
+ if (obj_magic == 0xcefaedfe || obj_magic == 0xfeedface || obj_magic == 0xcffaedfe || obj_magic == 0xfeedfacf) {
+ uint32_t cputype = sim_f->get_32();
+ uint32_t cpusubtype = sim_f->get_32();
+ uint32_t filetype = sim_f->get_32();
+ uint32_t ncmds = sim_f->get_32();
+ sim_f->get_32(); // Commands total size.
+ sim_f->get_32(); // Commands flags.
+ if (obj_magic == 0xcffaedfe || obj_magic == 0xfeedfacf) {
+ sim_f->get_32(); // Reserved, 64-bit only.
+ }
+ if (swap) {
+ ncmds = BSWAP32(ncmds);
+ cputype = BSWAP32(cputype);
+ cpusubtype = BSWAP32(cpusubtype);
+ filetype = BSWAP32(filetype);
+ }
+ if (cputype == 0x100000C && cpusubtype == 0 && filetype == 1) {
+ // ARM64, object file.
+ for (uint32_t i = 0; i < ncmds; i++) {
+ int64_t cmdofs = sim_f->get_position();
+ uint32_t cmdid = sim_f->get_32();
+ uint32_t cmdsize = sim_f->get_32();
+ if (swap) {
+ cmdid = BSWAP32(cmdid);
+ cmdsize = BSWAP32(cmdsize);
+ }
+ if (cmdid == MachO::LoadCommandID::LC_BUILD_VERSION) {
+ int64_t platform = sim_f->get_32();
+ if (swap) {
+ platform = BSWAP32(platform);
+ }
+ if (platform == MachO::PlatformID::PLATFORM_IOS) {
+ sim_f->seek(cmdofs + 4 + 4);
+ uint32_t new_id = MachO::PlatformID::PLATFORM_IOSSIMULATOR;
+ if (swap) {
+ new_id = BSWAP32(new_id);
+ }
+ sim_f->store_32(new_id);
+ commands_patched++;
+ }
+ }
+ sim_f->seek(cmdofs + cmdsize);
+ }
+ }
+ }
+ sim_f->seek(next_off);
+ }
+ }
+ sim_f->close();
+ }
+ return commands_patched;
+}
+
void EditorExportPlatformIOS::_check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const {
Ref<PList> plist;
plist.instantiate();
@@ -1094,12 +1521,7 @@ void EditorExportPlatformIOS::_add_assets_to_project(const String &p_out_dir, co
String type;
if (asset.exported_path.ends_with(".framework")) {
- int total_libs = 0;
- int static_libs = 0;
- int dylibs = 0;
- int frameworks = 0;
- _check_xcframework_content(p_out_dir.path_join(asset.exported_path), total_libs, static_libs, dylibs, frameworks);
- if (asset.should_embed && (static_libs != total_libs)) {
+ 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";
@@ -1107,7 +1529,12 @@ void EditorExportPlatformIOS::_add_assets_to_project(const String &p_out_dir, co
type = "wrapper.framework";
} else if (asset.exported_path.ends_with(".xcframework")) {
- if (asset.should_embed) {
+ int total_libs = 0;
+ int static_libs = 0;
+ int dylibs = 0;
+ int frameworks = 0;
+ _check_xcframework_content(p_out_dir.path_join(asset.exported_path), total_libs, static_libs, dylibs, frameworks);
+ if (asset.should_embed && static_libs != total_libs) {
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";
@@ -1632,19 +2059,72 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
}
{
+ bool delete_old = p_preset->get("application/delete_old_export_files_unconditionally");
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
if (da.is_valid()) {
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
+ // Remove leftovers from last export so they don't interfere in case some files are no longer needed.
if (da->change_dir(binary_dir + ".xcodeproj") == OK) {
- da->erase_contents_recursive();
+ // Check directory content before deleting.
+ int expected_files = 0;
+ int total_files = 0;
+ if (!delete_old) {
+ da->list_dir_begin();
+ for (String n = da->get_next(); !n.is_empty(); n = da->get_next()) {
+ if (!n.begins_with(".")) { // Ignore ".", ".." and hidden files.
+ if (da->current_is_dir()) {
+ if (n == "xcshareddata" || n == "project.xcworkspace") {
+ expected_files++;
+ }
+ } else {
+ if (n == "project.pbxproj") {
+ expected_files++;
+ }
+ }
+ total_files++;
+ }
+ }
+ da->list_dir_end();
+ }
+ if ((total_files == 0) || (expected_files >= Math::floor(total_files * 0.8))) {
+ da->erase_contents_recursive();
+ } else {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Unexpected files found in the export destination directory \"%s.xcodeproj\", delete it manually or select another destination."), binary_dir));
+ return ERR_CANT_CREATE;
+ }
}
+ da->change_dir(current_dir);
+
if (da->change_dir(binary_dir) == OK) {
- da->erase_contents_recursive();
+ // Check directory content before deleting.
+ int expected_files = 0;
+ int total_files = 0;
+ if (!delete_old) {
+ da->list_dir_begin();
+ for (String n = da->get_next(); !n.is_empty(); n = da->get_next()) {
+ if (!n.begins_with(".")) { // Ignore ".", ".." and hidden files.
+ if (da->current_is_dir()) {
+ if (n == "dylibs" || n == "Images.xcassets" || n.ends_with(".lproj") || n == "godot-publish-dotnet" || n.ends_with(".xcframework") || n.ends_with(".framework")) {
+ expected_files++;
+ }
+ } else {
+ if (n == binary_name + "-Info.plist" || n == binary_name + ".entitlements" || n == "Launch Screen.storyboard" || n == "export_options.plist" || n.begins_with("dummy.") || n.ends_with(".gdip")) {
+ expected_files++;
+ }
+ }
+ total_files++;
+ }
+ }
+ da->list_dir_end();
+ }
+ if ((total_files == 0) || (expected_files >= Math::floor(total_files * 0.8))) {
+ da->erase_contents_recursive();
+ } else {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Unexpected files found in the export destination directory \"%s\", delete it manually or select another destination."), binary_dir));
+ return ERR_CANT_CREATE;
+ }
}
-
da->change_dir(current_dir);
if (!da->dir_exists(binary_dir)) {
@@ -1694,6 +2174,7 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
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");
+ files_to_parse.insert("PrivacyInfo.xcprivacy");
IOSConfigData config_data = {
pkg_name,
@@ -1829,7 +2310,7 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
ret = unzGoToNextFile(src_pkg_zip);
}
- /* we're done with our source zip */
+ // We're done with our source zip.
unzClose(src_pkg_zip);
if (!found_library) {
@@ -1837,6 +2318,80 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
return ERR_FILE_NOT_FOUND;
}
+ // Check and generate missing ARM64 simulator library.
+ if (p_preset->get("application/generate_simulator_library_if_missing").operator bool()) {
+ String sim_lib_path = dest_dir + String(binary_name + ".xcframework").path_join("ios-arm64_x86_64-simulator").path_join("libgodot.a");
+ String dev_lib_path = dest_dir + String(binary_name + ".xcframework").path_join("ios-arm64").path_join("libgodot.a");
+ String tmp_lib_path = EditorPaths::get_singleton()->get_cache_dir().path_join(binary_name + "_lipo_");
+ uint32_t cputype = 0;
+ uint32_t cpusubtype = 0;
+ if (!_archive_has_arm64(sim_lib_path, &cputype, &cpusubtype) && _archive_has_arm64(dev_lib_path) && FileAccess::exists(dev_lib_path)) {
+ add_message(EXPORT_MESSAGE_INFO, TTR("Export"), TTR("ARM64 simulator library, generating from device library."));
+
+ Vector<String> tmp_lib_files;
+ Vector<Vector2i> tmp_lib_cputypes;
+ // Extract/copy simulator lib.
+ if (FileAccess::exists(sim_lib_path)) {
+ if (LipO::is_lipo(sim_lib_path)) {
+ Ref<LipO> lipo;
+ lipo.instantiate();
+ if (lipo->open_file(sim_lib_path)) {
+ for (int i = 0; i < lipo->get_arch_count(); i++) {
+ const String &f_name = tmp_lib_path + itos(tmp_lib_files.size());
+ lipo->extract_arch(i, f_name);
+ tmp_lib_files.push_back(f_name);
+ tmp_lib_cputypes.push_back(Vector2i(lipo->get_arch_cputype(i), lipo->get_arch_cpusubtype(i)));
+ }
+ }
+ } else {
+ const String &f_name = tmp_lib_path + itos(tmp_lib_files.size());
+ tmp_app_path->copy(sim_lib_path, f_name);
+ tmp_lib_files.push_back(f_name);
+ tmp_lib_cputypes.push_back(Vector2i(cputype, cpusubtype));
+ }
+ }
+ // Copy device lib.
+ if (LipO::is_lipo(dev_lib_path)) {
+ Ref<LipO> lipo;
+ lipo.instantiate();
+ if (lipo->open_file(dev_lib_path)) {
+ for (int i = 0; i < lipo->get_arch_count(); i++) {
+ if (lipo->get_arch_cputype(i) == 0x100000c && lipo->get_arch_cpusubtype(i) == 0) {
+ const String &f_name = tmp_lib_path + itos(tmp_lib_files.size());
+ lipo->extract_arch(i, f_name);
+ tmp_lib_files.push_back(f_name);
+ tmp_lib_cputypes.push_back(Vector2i(0x100000c, 0)); // ARM64.
+ break;
+ }
+ }
+ }
+ } else {
+ const String &f_name = tmp_lib_path + itos(tmp_lib_files.size());
+ tmp_app_path->copy(dev_lib_path, f_name);
+ tmp_lib_files.push_back(f_name);
+ tmp_lib_cputypes.push_back(Vector2i(0x100000c, 0)); // ARM64.
+ }
+
+ // Patch device lib.
+ int patch_count = _archive_convert_to_simulator(tmp_lib_path + itos(tmp_lib_files.size() - 1));
+ if (patch_count == 0) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Export"), TTR("Unable to generate ARM64 simulator library."));
+ } else {
+ // Repack.
+ Ref<LipO> lipo;
+ lipo.instantiate();
+ lipo->create_file(sim_lib_path, tmp_lib_files, tmp_lib_cputypes);
+ }
+
+ // Cleanup.
+ for (const String &E : tmp_lib_files) {
+ tmp_app_path->remove(E);
+ }
+ }
+ }
+
+ // Generate translations files.
+
Dictionary appnames = GLOBAL_GET("application/config/name_localized");
Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized");
Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized");
@@ -2416,6 +2971,7 @@ void EditorExportPlatformIOS::_update_preset_status() {
} else {
has_runnable_preset.clear();
}
+ devices_changed.set();
}
#endif