Quellcode durchsuchen

[macOS export] Simplify code signing options, add support for rcodesign tool for signing and notarization.

bruvzg vor 3 Jahren
Ursprung
Commit
d91cb1d5d5

+ 1 - 0
core/core_constants.cpp

@@ -613,6 +613,7 @@ void register_global_constants() {
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_HIDE_QUATERNION_EDIT);
+	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_PASSWORD);
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX);
 
 	BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_NONE);

+ 1 - 0
core/object/object.h

@@ -95,6 +95,7 @@ enum PropertyHint {
 	PROPERTY_HINT_LOCALIZABLE_STRING,
 	PROPERTY_HINT_NODE_TYPE, ///< a node object type
 	PROPERTY_HINT_HIDE_QUATERNION_EDIT, /// Only Node3D::transform should hide the quaternion editor.
+	PROPERTY_HINT_PASSWORD,
 	PROPERTY_HINT_MAX,
 	// When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit
 };

+ 4 - 1
doc/classes/@GlobalScope.xml

@@ -2711,7 +2711,10 @@
 		<constant name="PROPERTY_HINT_HIDE_QUATERNION_EDIT" value="45" enum="PropertyHint">
 			Hints that a quaternion property should disable the temporary euler editor.
 		</constant>
-		<constant name="PROPERTY_HINT_MAX" value="46" enum="PropertyHint">
+		<constant name="PROPERTY_HINT_PASSWORD" value="46" enum="PropertyHint">
+			Hints that a string property is a password, and every character is replaced with the secret character.
+		</constant>
+		<constant name="PROPERTY_HINT_MAX" value="47" enum="PropertyHint">
 		</constant>
 		<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags">
 		</constant>

+ 10 - 0
editor/editor_properties.cpp

@@ -99,6 +99,10 @@ void EditorPropertyText::set_string_name(bool p_enabled) {
 	string_name = p_enabled;
 }
 
+void EditorPropertyText::set_secret(bool p_enabled) {
+	text->set_secret(p_enabled);
+}
+
 void EditorPropertyText::set_placeholder(const String &p_string) {
 	text->set_placeholder(p_string);
 }
@@ -4408,6 +4412,9 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 				EditorPropertyText *editor = memnew(EditorPropertyText);
 				if (p_hint == PROPERTY_HINT_PLACEHOLDER_TEXT) {
 					editor->set_placeholder(p_hint_text);
+				} else if (p_hint == PROPERTY_HINT_PASSWORD) {
+					editor->set_secret(true);
+					editor->set_placeholder(p_hint_text);
 				}
 				return editor;
 			}
@@ -4532,6 +4539,9 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 				EditorPropertyText *editor = memnew(EditorPropertyText);
 				if (p_hint == PROPERTY_HINT_PLACEHOLDER_TEXT) {
 					editor->set_placeholder(p_hint_text);
+				} else if (p_hint == PROPERTY_HINT_PASSWORD) {
+					editor->set_secret(true);
+					editor->set_placeholder(p_hint_text);
 				}
 				editor->set_string_name(true);
 				return editor;

+ 1 - 0
editor/editor_properties.h

@@ -67,6 +67,7 @@ public:
 	void set_string_name(bool p_enabled);
 	virtual void update_property() override;
 	void set_placeholder(const String &p_string);
+	void set_secret(bool p_enabled);
 	EditorPropertyText();
 };
 

+ 6 - 0
editor/editor_property_name_processor.cpp

@@ -106,6 +106,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	capitalize_string_remaps["aabb"] = "AABB";
 	capitalize_string_remaps["adb"] = "ADB";
 	capitalize_string_remaps["ao"] = "AO";
+	capitalize_string_remaps["api"] = "API";
 	capitalize_string_remaps["apk"] = "APK";
 	capitalize_string_remaps["arm64-v8a"] = "arm64-v8a";
 	capitalize_string_remaps["armeabi-v7a"] = "armeabi-v7a";
@@ -191,11 +192,14 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	capitalize_string_remaps["opengl"] = "OpenGL";
 	capitalize_string_remaps["opentype"] = "OpenType";
 	capitalize_string_remaps["openxr"] = "OpenXR";
+	capitalize_string_remaps["osslsigncode"] = "osslsigncode";
 	capitalize_string_remaps["pck"] = "PCK";
 	capitalize_string_remaps["png"] = "PNG";
 	capitalize_string_remaps["po2"] = "(Power of 2)"; // Unit.
 	capitalize_string_remaps["pvrtc"] = "PVRTC";
 	capitalize_string_remaps["pvs"] = "PVS";
+	capitalize_string_remaps["rcedit"] = "rcedit";
+	capitalize_string_remaps["rcodesign"] = "rcodesign";
 	capitalize_string_remaps["rgb"] = "RGB";
 	capitalize_string_remaps["rid"] = "RID";
 	capitalize_string_remaps["rmb"] = "RMB";
@@ -205,6 +209,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	capitalize_string_remaps["sdfgi"] = "SDFGI";
 	capitalize_string_remaps["sdk"] = "SDK";
 	capitalize_string_remaps["sec"] = "(sec)"; // Unit.
+	capitalize_string_remaps["signtool"] = "signtool";
 	capitalize_string_remaps["sms"] = "SMS";
 	capitalize_string_remaps["srgb"] = "sRGB";
 	capitalize_string_remaps["ssao"] = "SSAO";
@@ -237,6 +242,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	capitalize_string_remaps["webp"] = "WebP";
 	capitalize_string_remaps["webrtc"] = "WebRTC";
 	capitalize_string_remaps["websocket"] = "WebSocket";
+	capitalize_string_remaps["wine"] = "wine";
 	capitalize_string_remaps["wifi"] = "Wi-Fi";
 	capitalize_string_remaps["x86"] = "x86";
 	capitalize_string_remaps["xr"] = "XR";

+ 2 - 0
editor/export/editor_export.cpp

@@ -312,12 +312,14 @@ void EditorExport::update_export_presets() {
 			// Clear the preset properties and values prior to reloading
 			preset->properties.clear();
 			preset->values.clear();
+			preset->update_visibility.clear();
 
 			for (const EditorExportPlatform::ExportOption &E : options) {
 				preset->properties.push_back(E.option);
 
 				StringName option_name = E.option.name;
 				preset->values[option_name] = previous_values.has(option_name) ? previous_values[option_name] : E.default_value;
+				preset->update_visibility[option_name] = E.update_visibility;
 			}
 		}
 	}

+ 1 - 0
editor/export/editor_export_platform.cpp

@@ -323,6 +323,7 @@ Ref<EditorExportPreset> EditorExportPlatform::create_preset() {
 	for (const ExportOption &E : options) {
 		preset->properties.push_back(E.option);
 		preset->values[E.option.name] = E.default_value;
+		preset->update_visibility[E.option.name] = E.update_visibility;
 	}
 
 	return preset;

+ 8 - 6
editor/export/editor_export_platform.h

@@ -117,10 +117,12 @@ public:
 	struct ExportOption {
 		PropertyInfo option;
 		Variant default_value;
+		bool update_visibility = false;
 
-		ExportOption(const PropertyInfo &p_info, const Variant &p_default) :
+		ExportOption(const PropertyInfo &p_info, const Variant &p_default, bool p_update_visibility = false) :
 				option(p_info),
-				default_value(p_default) {
+				default_value(p_default),
+				update_visibility(p_update_visibility) {
 		}
 		ExportOption() {}
 	};
@@ -136,13 +138,13 @@ public:
 		messages.push_back(msg);
 		switch (p_type) {
 			case EXPORT_MESSAGE_INFO: {
-				print_line(vformat("%s: %s\n", msg.category, msg.text));
+				print_line(vformat("%s: %s", msg.category, msg.text));
 			} break;
 			case EXPORT_MESSAGE_WARNING: {
-				WARN_PRINT(vformat("%s: %s\n", msg.category, msg.text));
+				WARN_PRINT(vformat("%s: %s", msg.category, msg.text));
 			} break;
 			case EXPORT_MESSAGE_ERROR: {
-				ERR_PRINT(vformat("%s: %s\n", msg.category, msg.text));
+				ERR_PRINT(vformat("%s: %s", msg.category, msg.text));
 			} break;
 			default:
 				break;
@@ -170,7 +172,7 @@ public:
 
 	virtual void get_export_options(List<ExportOption> *r_options) = 0;
 	virtual bool should_update_export_options() { return false; }
-	virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const { return true; }
+	virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const { return true; }
 
 	virtual String get_os_name() const = 0;
 	virtual String get_name() const = 0;

+ 4 - 1
editor/export/editor_export_preset.cpp

@@ -34,6 +34,9 @@ bool EditorExportPreset::_set(const StringName &p_name, const Variant &p_value)
 	if (values.has(p_name)) {
 		values[p_name] = p_value;
 		EditorExport::singleton->save_presets();
+		if (update_visibility[p_name]) {
+			notify_property_list_changed();
+		}
 		return true;
 	}
 
@@ -51,7 +54,7 @@ bool EditorExportPreset::_get(const StringName &p_name, Variant &r_ret) const {
 
 void EditorExportPreset::_get_property_list(List<PropertyInfo> *p_list) const {
 	for (const PropertyInfo &E : properties) {
-		if (platform->get_export_option_visibility(E.name, values)) {
+		if (platform->get_export_option_visibility(this, E.name, values)) {
 			p_list->push_back(E);
 		}
 	}

+ 1 - 0
editor/export/editor_export_preset.h

@@ -67,6 +67,7 @@ private:
 
 	List<PropertyInfo> properties;
 	HashMap<StringName, Variant> values;
+	HashMap<StringName, bool> update_visibility;
 
 	String name;
 

+ 6 - 2
platform/macos/export/export.cpp

@@ -33,8 +33,12 @@
 #include "export_plugin.h"
 
 void register_macos_exporter() {
-	EDITOR_DEF("export/macos/force_builtin_codesign", false);
-	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::BOOL, "export/macos/force_builtin_codesign", PROPERTY_HINT_NONE));
+	EDITOR_DEF("export/macos/rcodesign", "");
+#ifdef WINDOWS_ENABLED
+	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/macos/rcodesign", PROPERTY_HINT_GLOBAL_FILE, "*.exe"));
+#else
+	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/macos/rcodesign", PROPERTY_HINT_GLOBAL_FILE));
+#endif
 
 	Ref<EditorExportPlatformMacOS> platform;
 	platform.instantiate();

+ 432 - 207
platform/macos/export/export_plugin.cpp

@@ -51,11 +51,51 @@ void EditorExportPlatformMacOS::get_preset_features(const Ref<EditorExportPreset
 	r_features->push_back(p_preset->get("binary_format/architecture"));
 }
 
-bool EditorExportPlatformMacOS::get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const {
-	// These options are not supported by built-in codesign, used on non macOS host.
-	if (!OS::get_singleton()->has_feature("macos")) {
-		if (p_option == "codesign/identity" || p_option == "codesign/timestamp" || p_option == "codesign/hardened_runtime" || p_option == "codesign/custom_options" || p_option.begins_with("notarization/")) {
-			return false;
+bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+	// Hide irrelevant code signing options.
+	if (p_preset) {
+		int codesign_tool = p_preset->get("codesign/codesign");
+		switch (codesign_tool) {
+			case 1: { // built-in ad-hoc
+				if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options") {
+					return false;
+				}
+			} break;
+			case 2: { // "rcodesign"
+				if (p_option == "codesign/identity") {
+					return false;
+				}
+			} break;
+#ifdef MACOS_ENABLED
+			case 3: { // "codesign"
+				if (p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password") {
+					return false;
+				}
+			} break;
+#endif
+			default: { // disabled
+				if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options" || p_option.begins_with("codesign/entitlements")) {
+					return false;
+				}
+			} break;
+		}
+
+		// Hide irrelevant notarization options.
+		int notary_tool = p_preset->get("notarization/notarization");
+		switch (notary_tool) {
+			case 1: { // "rcodesign"
+				if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id") {
+					return false;
+				}
+			} break;
+			case 2: { // "altool"
+				// All options are visible.
+			} break;
+			default: { // disabled
+				if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key") {
+					return false;
+				}
+			} break;
 		}
 	}
 
@@ -83,40 +123,22 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false));
-	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"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	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"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/location_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/address_book_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/calendar_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photos_library_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/desktop_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/documents_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/downloads_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/network_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/removable_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
 
-	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), true));
+#ifdef MACOS_ENABLED
+	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign,Xcode codesign"), 3, true));
+#else
+	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign"), 1, true));
+#endif
+	// "codesign" only options:
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/replace_existing_signature"), true));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true));
+	// "rcodesign" only options:
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_file", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_password", PROPERTY_HINT_PASSWORD), ""));
+	// "codesign" and "rcodesign" only options:
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
-
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false));
-
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/disable_library_validation"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/audio_input"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/camera"), false));
@@ -126,7 +148,6 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/photos_library"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/apple_events"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/debugging"), false));
-
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_server"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_client"), false));
@@ -137,13 +158,43 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
 	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array()));
-
 	r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
 
-	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false));
+#ifdef MACOS_ENABLED
+	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign,Xcode altool"), 0, true));
+#else
+	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign"), 0, true));
+#endif
+	// "altool" only options:
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PASSWORD, "Enable two-factor authentication and provide app-specific password"), ""));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), ""));
+	// "altool" and "rcodesign" only options:
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), ""));
+
+	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"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	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"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/location_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/address_book_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/calendar_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photos_library_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/desktop_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/documents_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/downloads_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/network_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/removable_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
 
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false));
@@ -415,156 +466,284 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres
  */
 
 Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+	int notary_tool = p_preset->get("notarization/notarization");
+	switch (notary_tool) {
+		case 1: { // "rcodesign"
+			print_verbose("using rcodesign notarization...");
+
+			String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
+			if (rcodesign.is_empty()) {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign)."));
+				return Error::FAILED;
+			}
+
+			List<String> args;
+
+			args.push_back("notary-submit");
+
+			if (p_preset->get("notarization/api_uuid") == "") {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect issuer ID name not specified."));
+				return Error::FAILED;
+			}
+			if (p_preset->get("notarization/api_key") == "") {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified."));
+				return Error::FAILED;
+			}
+
+			args.push_back("--api-issuer");
+			args.push_back(p_preset->get("notarization/api_uuid"));
+
+			args.push_back("--api-key");
+			args.push_back(p_preset->get("notarization/api_key"));
+
+			args.push_back(p_path);
+
+			String str;
+			int exitcode = 0;
+
+			Error err = OS::get_singleton()->execute(rcodesign, args, &str, &exitcode, true);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start rcodesign executable."));
+				return err;
+			}
+
+			int rq_offset = str.find("created submission ID:");
+			if (exitcode != 0 || rq_offset == -1) {
+				print_line("rcodesign (" + p_path + "):\n" + str);
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details."));
+				return Error::FAILED;
+			} else {
+				print_verbose("rcodesign (" + p_path + "):\n" + str);
+				int next_nl = str.find("\n", rq_offset);
+				String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid));
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour."));
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:"));
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"rcodesign notary-log --api-issuer <api uuid> --api-key <api key> <request uuid>\"");
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):"));
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"rcodesign staple <app path>\"");
+			}
+		} break;
 #ifdef MACOS_ENABLED
-	List<String> args;
+		case 2: { // "altool"
+			print_verbose("using altool notarization...");
 
-	args.push_back("altool");
-	args.push_back("--notarize-app");
+			if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Xcode command line tools are not installed."));
+				return Error::FAILED;
+			}
 
-	args.push_back("--primary-bundle-id");
-	args.push_back(p_preset->get("application/bundle_identifier"));
+			List<String> args;
 
-	args.push_back("--username");
-	args.push_back(p_preset->get("notarization/apple_id_name"));
+			args.push_back("altool");
+			args.push_back("--notarize-app");
 
-	args.push_back("--password");
-	args.push_back(p_preset->get("notarization/apple_id_password"));
+			args.push_back("--primary-bundle-id");
+			args.push_back(p_preset->get("application/bundle_identifier"));
 
-	args.push_back("--type");
-	args.push_back("osx");
+			if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified."));
+				return Error::FAILED;
+			}
+			if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time."));
+				return Error::FAILED;
+			}
 
-	if (p_preset->get("notarization/apple_team_id")) {
-		args.push_back("--asc-provider");
-		args.push_back(p_preset->get("notarization/apple_team_id"));
-	}
+			if (p_preset->get("notarization/apple_id_name") != "") {
+				if (p_preset->get("notarization/apple_id_password") == "") {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Apple ID password not specified."));
+					return Error::FAILED;
+				}
+				args.push_back("--username");
+				args.push_back(p_preset->get("notarization/apple_id_name"));
 
-	args.push_back("--file");
-	args.push_back(p_path);
+				args.push_back("--password");
+				args.push_back(p_preset->get("notarization/apple_id_password"));
+			} else {
+				if (p_preset->get("notarization/api_key") == "") {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified."));
+					return Error::FAILED;
+				}
+				args.push_back("--apiIssuer");
+				args.push_back(p_preset->get("notarization/api_uuid"));
 
-	String str;
-	Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true);
-	if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
-		add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable."));
-		return err;
-	}
+				args.push_back("--apiKey");
+				args.push_back(p_preset->get("notarization/api_key"));
+			}
 
-	print_verbose("altool (" + p_path + "):\n" + str);
-	int rq_offset = str.find("RequestUUID");
-	if (rq_offset == -1) {
-		add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed."));
-		return FAILED;
-	} else {
-		int next_nl = str.find("\n", rq_offset);
-		String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
-		add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid));
-		add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."));
-		add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:"));
-		add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
-		add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):"));
-		add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple <app path>\"");
-	}
+			args.push_back("--type");
+			args.push_back("osx");
 
-#endif
+			if (p_preset->get("notarization/apple_team_id")) {
+				args.push_back("--asc-provider");
+				args.push_back(p_preset->get("notarization/apple_team_id"));
+			}
 
+			args.push_back("--file");
+			args.push_back(p_path);
+
+			String str;
+			int exitcode = 0;
+			Error err = OS::get_singleton()->execute("xcrun", args, &str, &exitcode, true);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable."));
+				return err;
+			}
+
+			int rq_offset = str.find("RequestUUID");
+			if (exitcode != 0 || rq_offset == -1) {
+				print_line("xcrun altool (" + p_path + "):\n" + str);
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details."));
+				return Error::FAILED;
+			} else {
+				print_verbose("xcrun altool (" + p_path + "):\n" + str);
+				int next_nl = str.find("\n", rq_offset);
+				String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid));
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."));
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:"));
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):"));
+				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple <app path>\"");
+			}
+		} break;
+#endif
+		default: {
+		};
+	}
 	return OK;
 }
 
 Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn) {
-	bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign");
-	bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
-
-	if ((!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) || force_builtin_codesign) {
-		print_verbose("using built-in codesign...");
+	int codesign_tool = p_preset->get("codesign/codesign");
+	switch (codesign_tool) {
+		case 1: { // built-in ad-hoc
+			print_verbose("using built-in codesign...");
 #ifdef MODULE_REGEX_ENABLED
-
-#ifdef MACOS_ENABLED
-		if (p_preset->get("codesign/timestamp") && p_warn) {
-			add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!"));
-		}
-		if (p_preset->get("codesign/hardened_runtime") && p_warn) {
-			add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"));
-		}
-#endif
-
-		String error_msg;
-		Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg);
-		if (err != OK) {
-			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg));
-			return FAILED;
-		}
+			String error_msg;
+			Error err = CodeSign::codesign(false, true, p_path, p_ent_path, error_msg);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg));
+				return Error::FAILED;
+			}
 #else
-		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module."));
+			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module."));
 #endif
-		return OK;
-	} else {
-		print_verbose("using external codesign...");
-		List<String> args;
-		if (p_preset->get("codesign/timestamp")) {
-			if (ad_hoc) {
-				if (p_warn) {
-					add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!"));
-				}
-			} else {
-				args.push_back("--timestamp");
+		} break;
+		case 2: { // "rcodesign"
+			print_verbose("using rcodesign codesign...");
+
+			String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
+			if (rcodesign.is_empty()) {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Xrcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign)."));
+				return Error::FAILED;
 			}
-		}
-		if (p_preset->get("codesign/hardened_runtime")) {
-			if (ad_hoc) {
-				if (p_warn) {
-					add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"));
-				}
+
+			List<String> args;
+			args.push_back("sign");
+
+			if (p_path.get_extension() != "dmg") {
+				args.push_back("--entitlements-xml-path");
+				args.push_back(p_ent_path);
+			}
+
+			String certificate_file = p_preset->get("codesign/certificate_file");
+			String certificate_pass = p_preset->get("codesign/certificate_password");
+			if (!certificate_file.is_empty() && !certificate_file.is_empty()) {
+				args.push_back("--p12-file");
+				args.push_back(certificate_file);
+				args.push_back("--p12-password");
+				args.push_back(certificate_pass);
+			}
+
+			args.push_back("-v"); /* provide some more feedback */
+
+			args.push_back(p_path);
+
+			String str;
+			int exitcode = 0;
+
+			Error err = OS::get_singleton()->execute(rcodesign, args, &str, &exitcode, true);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start rcodesign executable."));
+				return err;
+			}
+
+			if (exitcode != 0) {
+				print_line("rcodesign (" + p_path + "):\n" + str);
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Code signing failed, see editor log for details."));
+				return Error::FAILED;
 			} else {
+				print_verbose("rcodesign (" + p_path + "):\n" + str);
+			}
+		} break;
+#ifdef MACOS_ENABLED
+		case 3: { // "codesign"
+			print_verbose("using xcode codesign...");
+
+			if (!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Xcode command line tools are not installed."));
+				return Error::FAILED;
+			}
+
+			bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
+
+			List<String> args;
+			if (!ad_hoc) {
+				args.push_back("--timestamp");
 				args.push_back("--options");
 				args.push_back("runtime");
 			}
-		}
-
-		if (p_path.get_extension() != "dmg") {
-			args.push_back("--entitlements");
-			args.push_back(p_ent_path);
-		}
 
-		PackedStringArray user_args = p_preset->get("codesign/custom_options");
-		for (int i = 0; i < user_args.size(); i++) {
-			String user_arg = user_args[i].strip_edges();
-			if (!user_arg.is_empty()) {
-				args.push_back(user_arg);
+			if (p_path.get_extension() != "dmg") {
+				args.push_back("--entitlements");
+				args.push_back(p_ent_path);
 			}
-		}
 
-		args.push_back("-s");
-		if (ad_hoc) {
-			args.push_back("-");
-		} else {
-			args.push_back(p_preset->get("codesign/identity"));
-		}
+			PackedStringArray user_args = p_preset->get("codesign/custom_options");
+			for (int i = 0; i < user_args.size(); i++) {
+				String user_arg = user_args[i].strip_edges();
+				if (!user_arg.is_empty()) {
+					args.push_back(user_arg);
+				}
+			}
 
-		args.push_back("-v"); /* provide some more feedback */
+			args.push_back("-s");
+			if (ad_hoc) {
+				args.push_back("-");
+			} else {
+				args.push_back(p_preset->get("codesign/identity"));
+			}
 
-		if (p_preset->get("codesign/replace_existing_signature")) {
+			args.push_back("-v"); /* provide some more feedback */
 			args.push_back("-f");
-		}
 
-		args.push_back(p_path);
+			args.push_back(p_path);
 
-		String str;
-		Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
-		if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
-			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed."));
-			return err;
-		}
+			String str;
+			int exitcode = 0;
 
-		print_verbose("codesign (" + p_path + "):\n" + str);
-		if (str.find("no identity found") != -1) {
-			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
-			return FAILED;
-		}
-		if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) {
-			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid entitlements file."));
-			return FAILED;
-		}
-		return OK;
+			Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed."));
+				return err;
+			}
+
+			if (exitcode != 0) {
+				print_line("codesign (" + p_path + "):\n" + str);
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Code signing failed, see editor log for details."));
+				return Error::FAILED;
+			} else {
+				print_verbose("codesign (" + p_path + "):\n" + str);
+			}
+		} break;
+#endif
+		default: {
+		};
 	}
+
+	return OK;
 }
 
 Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
@@ -816,7 +995,9 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
 		err = ERR_CANT_CREATE;
 	}
 
-	DirAccess::remove_file_or_error(scr_path);
+	if (FileAccess::exists(scr_path)) {
+		DirAccess::remove_file_or_error(scr_path);
+	}
 	if (DirAccess::exists(tmp_app_path_name)) {
 		String old_dir = tmp_app_dir->get_current_dir();
 		if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) {
@@ -1148,7 +1329,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
 		err = save_pack(p_preset, p_debug, pack_path, &shared_objects);
 
 		// See if we can code sign our new package.
-		bool sign_enabled = p_preset->get("codesign/enable");
+		bool sign_enabled = (p_preset->get("codesign/codesign").operator int() > 0);
 
 		String ent_path = p_preset->get("codesign/entitlements/custom_file");
 		String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + "_helper.entitlements");
@@ -1314,14 +1495,25 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
 			}
 		}
 
-		bool ad_hoc = true;
-		if (err == OK) {
+		bool ad_hoc = false;
+		int codesign_tool = p_preset->get("codesign/codesign");
+		switch (codesign_tool) {
+			case 1: { // built-in ad-hoc
+				ad_hoc = true;
+			} break;
+			case 2: { // "rcodesign"
+				ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty();
+			} break;
 #ifdef MACOS_ENABLED
-			String sign_identity = p_preset->get("codesign/identity");
-#else
-			String sign_identity = "-";
+			case 3: { // "codesign"
+				ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
+			} break;
 #endif
-			ad_hoc = (sign_identity == "" || sign_identity == "-");
+			default: {
+			};
+		}
+
+		if (err == OK) {
 			bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation");
 			if ((!dylibs_found.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) {
 				add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries."));
@@ -1404,8 +1596,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
 			}
 		}
 
-#ifdef MACOS_ENABLED
-		bool noto_enabled = p_preset->get("notarization/enable");
+		bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0);
 		if (err == OK && noto_enabled) {
 			if (export_format == "app") {
 				add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead."));
@@ -1416,10 +1607,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
 				err = _notarize(p_preset, p_path);
 			}
 		}
-#endif
 
 		// Clean up temporary entitlements files.
-		DirAccess::remove_file_or_error(hlp_ent_path);
+		if (FileAccess::exists(hlp_ent_path)) {
+			DirAccess::remove_file_or_error(hlp_ent_path);
+		}
 
 		// Clean up temporary .app dir and generated entitlements.
 		if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") {
@@ -1602,65 +1794,98 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor
 		valid = false;
 	}
 
-	bool sign_enabled = p_preset->get("codesign/enable");
-
+	bool ad_hoc = false;
+	int codesign_tool = p_preset->get("codesign/codesign");
+	switch (codesign_tool) {
+		case 1: { // built-in ad-hoc
+			ad_hoc = true;
+		} break;
+		case 2: { // "rcodesign"
+			ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty();
+		} break;
 #ifdef MACOS_ENABLED
-	bool noto_enabled = p_preset->get("notarization/enable");
-	bool ad_hoc = ((p_preset->get("codesign/identity") == "") || (p_preset->get("codesign/identity") == "-"));
-
-	if (!ad_hoc && (bool)EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign")) {
-		err += TTR("Warning: Built-in \"codesign\" is selected in the Editor Settings. Code signing is limited to ad-hoc signature only.") + "\n";
-	}
-	if (!ad_hoc && !FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
-		err += TTR("Warning: Xcode command line tools are not installed, using built-in \"codesign\". Code signing is limited to ad-hoc signature only.") + "\n";
+		case 3: { // "codesign"
+			ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
+		} break;
+#endif
+		default: {
+		};
 	}
+	int notary_tool = p_preset->get("notarization/notarization");
 
-	if (noto_enabled) {
+	if (notary_tool > 0) {
 		if (ad_hoc) {
 			err += TTR("Notarization: Notarization with an ad-hoc signature is not supported.") + "\n";
 			valid = false;
 		}
-		if (!sign_enabled) {
+		if (codesign_tool == 0) {
 			err += TTR("Notarization: Code signing is required for notarization.") + "\n";
 			valid = false;
 		}
-		if (!(bool)p_preset->get("codesign/hardened_runtime")) {
-			err += TTR("Notarization: Hardened runtime is required for notarization.") + "\n";
-			valid = false;
-		}
-		if (!(bool)p_preset->get("codesign/timestamp")) {
-			err += TTR("Notarization: Timestamping is required for notarization.") + "\n";
-			valid = false;
-		}
-		if (p_preset->get("notarization/apple_id_name") == "") {
-			err += TTR("Notarization: Apple ID name not specified.") + "\n";
-			valid = false;
-		}
-		if (p_preset->get("notarization/apple_id_password") == "") {
-			err += TTR("Notarization: Apple ID password not specified.") + "\n";
-			valid = false;
+		if (notary_tool == 2) {
+			if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) {
+				err += TTR("Notarization: Xcode command line tools are not installed.") + "\n";
+				valid = false;
+			}
+			if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") {
+				err += TTR("Notarization: Neither Apple ID name nor App Store Connect issuer ID name not specified.") + "\n";
+				valid = false;
+			} else if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") {
+				err += TTR("Notarization: Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time.") + "\n";
+				valid = false;
+			} else {
+				if (p_preset->get("notarization/apple_id_name") != "") {
+					if (p_preset->get("notarization/apple_id_password") == "") {
+						err += TTR("Notarization: Apple ID password not specified.") + "\n";
+					}
+					valid = false;
+				}
+				if (p_preset->get("notarization/api_uuid") != "") {
+					if (p_preset->get("notarization/api_key") == "") {
+						err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n";
+						valid = false;
+					}
+				}
+			}
+		} else if (notary_tool == 1) {
+			if (p_preset->get("notarization/api_uuid") == "") {
+				err += TTR("Notarization: App Store Connect issuer ID name not specified.") + "\n";
+				valid = false;
+			}
+			if (p_preset->get("notarization/api_key") == "") {
+				err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n";
+				valid = false;
+			}
+
+			String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
+			if (rcodesign.is_empty()) {
+				err += TTR("Notarization: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign).") + "\n";
+				valid = false;
+			}
 		}
 	} else {
 		err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n";
-		if (!sign_enabled) {
+		if (codesign_tool == 0) {
 			err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
-		} else {
-			if ((bool)p_preset->get("codesign/hardened_runtime") && ad_hoc) {
-				err += TTR("Hardened Runtime is not compatible with ad-hoc signature, and will be disabled!") + "\n";
-			}
-			if ((bool)p_preset->get("codesign/timestamp") && ad_hoc) {
-				err += TTR("Timestamping is not compatible with ad-hoc signature, and will be disabled!") + "\n";
-			}
 		}
 	}
-#else
-	err += TTR("Warning: Notarization is not supported from this OS. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n";
-	if (!sign_enabled) {
-		err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
-	}
-#endif
 
-	if (sign_enabled) {
+	if (codesign_tool > 0) {
+		if (ad_hoc) {
+			err += TTR("Code signing: Using ad-hoc signature. The exported project will be blocked by Gatekeeper") + "\n";
+		}
+		if (codesign_tool == 3) {
+			if (!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
+				err += TTR("Code signing: Xcode command line tools are not installed.") + "\n";
+				valid = false;
+			}
+		} else if (codesign_tool == 2) {
+			String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
+			if (rcodesign.is_empty()) {
+				err += TTR("Code signing: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign).") + "\n";
+				valid = false;
+			}
+		}
 		if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) {
 			err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n";
 			valid = false;

+ 1 - 1
platform/macos/export/export_plugin.h

@@ -101,7 +101,7 @@ class EditorExportPlatformMacOS : public EditorExportPlatform {
 protected:
 	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
 	virtual void get_export_options(List<ExportOption> *r_options) override;
-	virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
+	virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
 
 public:
 	virtual String get_name() const override { return "macOS"; }

+ 9 - 5
platform/windows/export/export_plugin.cpp

@@ -113,7 +113,7 @@ List<String> EditorExportPlatformWindows::get_binary_extensions(const Ref<Editor
 	return list;
 }
 
-bool EditorExportPlatformWindows::get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
 	// This option is not supported by "osslsigncode", used on non-Windows host.
 	if (!OS::get_singleton()->has_feature("windows") && p_option == "codesign/identity_type") {
 		return false;
@@ -128,7 +128,7 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA1 hash)"), 0));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), ""));
-	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password", PROPERTY_HINT_PASSWORD), ""));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/timestamp_server_url"), ""));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/digest_algorithm", PROPERTY_HINT_ENUM, "SHA1,SHA256"), 1));
@@ -231,7 +231,7 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset
 	String str;
 	Error err = OS::get_singleton()->execute(rcedit_path, args, &str, nullptr, true);
 	if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
-		add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable. Configure rcedit path in the Editor Settings (Export > Windows > Rcedit), or disable \"Application > Modify Resources\" in the export preset."));
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable. Configure rcedit path in the Editor Settings (Export > Windows > rcedit), or disable \"Application > Modify Resources\" in the export preset."));
 		return err;
 	}
 	print_line("rcedit (" + p_path + "): " + str);
@@ -379,7 +379,11 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 	String str;
 	Error err = OS::get_singleton()->execute(signtool_path, args, &str, nullptr, true);
 	if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
-		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start signtool executable. Configure signtool path in the Editor Settings (Export > Windows > Signtool), or disable \"Codesign\" in the export preset."));
+#ifndef WINDOWS_ENABLED
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start signtool executable. Configure signtool path in the Editor Settings (Export > Windows > signtool), or disable \"Codesign\" in the export preset."));
+#else
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start osslsigncode executable. Configure signtool path in the Editor Settings (Export > Windows > osslsigncode), or disable \"Codesign\" in the export preset."));
+#endif
 		return err;
 	}
 
@@ -418,7 +422,7 @@ bool EditorExportPlatformWindows::has_valid_export_configuration(const Ref<Edito
 
 	String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
 	if (p_preset->get("application/modify_resources") && rcedit_path.is_empty()) {
-		err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n";
+		err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > rcedit) to change the icon or app information data.") + "\n";
 	}
 
 	if (!err.is_empty()) {

+ 1 - 1
platform/windows/export/export_plugin.h

@@ -48,9 +48,9 @@ public:
 	virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override;
 	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
 	virtual void get_export_options(List<ExportOption> *r_options) override;
-	virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
 	virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
 	virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
+	virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
 	virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
 	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
 };