summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/actions/upload-artifact/action.yml3
-rw-r--r--core/io/dir_access.cpp2
-rw-r--r--core/io/resource_importer.cpp14
-rw-r--r--doc/classes/EditorScript.xml5
-rw-r--r--editor/editor_file_system.cpp13
-rw-r--r--editor/editor_interface.cpp2
-rw-r--r--editor/editor_script.cpp20
-rw-r--r--editor/export/codesign.cpp (renamed from platform/macos/export/codesign.cpp)0
-rw-r--r--editor/export/codesign.h (renamed from platform/macos/export/codesign.h)6
-rw-r--r--editor/export/lipo.cpp (renamed from platform/macos/export/lipo.cpp)108
-rw-r--r--editor/export/lipo.h (renamed from platform/macos/export/lipo.h)11
-rw-r--r--editor/export/macho.cpp (renamed from platform/macos/export/macho.cpp)0
-rw-r--r--editor/export/macho.h (renamed from platform/macos/export/macho.h)23
-rw-r--r--editor/project_manager.cpp2
-rw-r--r--editor/project_manager/quick_settings_dialog.cpp34
-rw-r--r--editor/project_manager/quick_settings_dialog.h3
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml2
-rw-r--r--modules/gdscript/gdscript_parser.cpp110
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.notest.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable_global.notest.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable_unnamed.notest.gd1
-rw-r--r--modules/navigation/nav_region.cpp4
-rw-r--r--platform/ios/doc_classes/EditorExportPlatformIOS.xml3
-rw-r--r--platform/ios/export/export_plugin.cpp242
-rw-r--r--platform/ios/export/export_plugin.h2
-rw-r--r--platform/macos/export/export_plugin.cpp6
28 files changed, 547 insertions, 85 deletions
diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml
index 8033839a7c..80c680103d 100644
--- a/.github/actions/upload-artifact/action.yml
+++ b/.github/actions/upload-artifact/action.yml
@@ -16,4 +16,5 @@ runs:
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}
- retention-days: 14
+ # Default is 90 days.
+ retention-days: 60
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index 5df67b1103..2f3fe4deeb 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -339,6 +339,8 @@ String DirAccess::get_full_path(const String &p_path, AccessType p_access) {
}
Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flags) {
+ ERR_FAIL_COND_V_MSG(p_from == p_to, ERR_INVALID_PARAMETER, "Source and destination path are equal.");
+
//printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data());
Error err;
{
diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp
index 4c16650439..9e6f3ba314 100644
--- a/core/io/resource_importer.cpp
+++ b/core/io/resource_importer.cpp
@@ -118,9 +118,21 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy
}
#endif
- if (r_path_and_type.path.is_empty() || r_path_and_type.type.is_empty()) {
+ if (r_path_and_type.type.is_empty()) {
return ERR_FILE_CORRUPT;
}
+ if (r_path_and_type.path.is_empty()) {
+ // Some importers may not write files to the .godot folder, so the path can be empty.
+ if (r_path_and_type.importer.is_empty()) {
+ return ERR_FILE_CORRUPT;
+ }
+
+ // It's only invalid if the extension for the importer is not empty.
+ Ref<ResourceImporter> importer = get_importer_by_name(r_path_and_type.importer);
+ if (importer.is_null() || !importer->get_save_extension().is_empty()) {
+ return ERR_FILE_CORRUPT;
+ }
+ }
return OK;
}
diff --git a/doc/classes/EditorScript.xml b/doc/classes/EditorScript.xml
index 24480437fd..bd18852dbc 100644
--- a/doc/classes/EditorScript.xml
+++ b/doc/classes/EditorScript.xml
@@ -44,8 +44,7 @@
<return type="void" />
<param index="0" name="node" type="Node" />
<description>
- Adds [param node] as a child of the root node in the editor context.
- [b]Warning:[/b] The implementation of this method is currently disabled.
+ Makes [param node] root of the currently opened scene. Only works if the scene is empty. If the [param node] is a scene instance, an inheriting scene will be created.
</description>
</method>
<method name="get_editor_interface" qualifiers="const" deprecated="[EditorInterface] is a global singleton and can be accessed directly by its name.">
@@ -57,7 +56,7 @@
<method name="get_scene" qualifiers="const">
<return type="Node" />
<description>
- Returns the Editor's currently active scene.
+ Returns the edited (current) scene's root [Node]. Equivalent of [method EditorInterface.get_edited_scene_root].
</description>
</method>
</methods>
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 4bca42bf8b..d84ccb0c03 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -2468,18 +2468,23 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
int from = 0;
for (int i = 0; i < reimport_files.size(); i++) {
if (groups_to_reimport.has(reimport_files[i].path)) {
+ from = i + 1;
continue;
}
if (use_multiple_threads && reimport_files[i].threaded) {
- if (i + 1 == reimport_files.size() || reimport_files[i + 1].importer != reimport_files[from].importer) {
+ if (i + 1 == reimport_files.size() || reimport_files[i + 1].importer != reimport_files[from].importer || groups_to_reimport.has(reimport_files[i + 1].path)) {
if (from - i == 0) {
// Single file, do not use threads.
pr.step(reimport_files[i].path.get_file(), i);
_reimport_file(reimport_files[i].path);
} else {
Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(reimport_files[from].importer);
- ERR_CONTINUE(!importer.is_valid());
+ if (importer.is_null()) {
+ ERR_PRINT(vformat("Invalid importer for \"%s\".", reimport_files[from].importer));
+ from = i + 1;
+ continue;
+ }
importer->import_threaded_begin();
@@ -2509,6 +2514,10 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
} else {
pr.step(reimport_files[i].path.get_file(), i);
_reimport_file(reimport_files[i].path);
+
+ // We need to increment the counter, maybe the next file is multithreaded
+ // and doesn't have the same importer.
+ from = i + 1;
}
}
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index 35e846b344..46113ab2cb 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -39,6 +39,7 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_run_bar.h"
+#include "editor/gui/editor_scene_tabs.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/node_3d_editor_plugin.h"
@@ -452,6 +453,7 @@ void EditorInterface::save_scene_as(const String &p_scene, bool p_with_preview)
void EditorInterface::mark_scene_as_unsaved() {
EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(EditorNode::get_editor_data().get_current_edited_scene_history_id());
+ EditorSceneTabs::get_singleton()->update_scene_tabs();
}
void EditorInterface::save_all_scenes() {
diff --git a/editor/editor_script.cpp b/editor/editor_script.cpp
index a2af5a760e..30a4b6811c 100644
--- a/editor/editor_script.cpp
+++ b/editor/editor_script.cpp
@@ -32,7 +32,10 @@
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
+#include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/editor_scene_tabs.h"
#include "scene/main/node.h"
+#include "scene/resources/packed_scene.h"
void EditorScript::add_root_node(Node *p_node) {
if (!EditorNode::get_singleton()) {
@@ -41,11 +44,24 @@ void EditorScript::add_root_node(Node *p_node) {
}
if (EditorNode::get_singleton()->get_edited_scene()) {
- EditorNode::add_io_error("EditorScript::add_root_node: " + TTR("There is an edited scene already."));
+ EditorNode::add_io_error("EditorScript::add_root_node: " + TTR("The current scene already has a root node."));
return;
}
- //editor->set_edited_scene(p_node);
+ const String &scene_path = p_node->get_scene_file_path();
+ if (!scene_path.is_empty()) {
+ Ref<PackedScene> scene = ResourceLoader::load(scene_path);
+ if (scene.is_valid()) {
+ memfree(scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE)); // Ensure node cache.
+
+ p_node->set_scene_inherited_state(scene->get_state());
+ p_node->set_scene_file_path(String());
+ }
+ }
+
+ EditorNode::get_singleton()->set_edited_scene(p_node);
+ EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(EditorNode::get_editor_data().get_current_edited_scene_history_id());
+ EditorSceneTabs::get_singleton()->update_scene_tabs();
}
Node *EditorScript::get_scene() const {
diff --git a/platform/macos/export/codesign.cpp b/editor/export/codesign.cpp
index 72d496b04d..72d496b04d 100644
--- a/platform/macos/export/codesign.cpp
+++ b/editor/export/codesign.cpp
diff --git a/platform/macos/export/codesign.h b/editor/export/codesign.h
index 49d53b376e..9a858c49ac 100644
--- a/platform/macos/export/codesign.h
+++ b/editor/export/codesign.h
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef MACOS_CODESIGN_H
-#define MACOS_CODESIGN_H
+#ifndef CODESIGN_H
+#define CODESIGN_H
// macOS code signature creation utility.
//
@@ -364,4 +364,4 @@ public:
#endif // MODULE_REGEX_ENABLED
-#endif // MACOS_CODESIGN_H
+#endif // CODESIGN_H
diff --git a/platform/macos/export/lipo.cpp b/editor/export/lipo.cpp
index 2d77e5960d..9038994b06 100644
--- a/platform/macos/export/lipo.cpp
+++ b/editor/export/lipo.cpp
@@ -37,7 +37,7 @@ bool LipO::is_lipo(const String &p_path) {
return (magic == 0xbebafeca || magic == 0xcafebabe || magic == 0xbfbafeca || magic == 0xcafebabf);
}
-bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_files) {
+bool LipO::create_file(const String &p_output_path, const Vector<String> &p_files) {
close();
fa = FileAccess::open(p_output_path, FileAccess::WRITE);
@@ -125,6 +125,100 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f
return true;
}
+bool LipO::create_file(const String &p_output_path, const Vector<String> &p_files, const Vector<Vector2i> &p_cputypes) {
+ close();
+
+ fa = FileAccess::open(p_output_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_output_path));
+ ERR_FAIL_COND_V(p_files.size() != p_cputypes.size(), false);
+
+ uint64_t max_size = 0;
+ for (int i = 0; i < p_files.size(); i++) {
+ Ref<FileAccess> fb = FileAccess::open(p_files[i], FileAccess::READ);
+ if (fb.is_null()) {
+ close();
+ ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s\".", p_files[i]));
+ }
+
+ {
+ FatArch arch;
+ MachO mh;
+ if (MachO::is_macho(p_files[i]) && mh.open_file(p_files[i])) {
+ arch.cputype = mh.get_cputype();
+ arch.cpusubtype = mh.get_cpusubtype();
+ arch.offset = 0;
+ arch.size = mh.get_size();
+ arch.align = mh.get_align();
+ ERR_FAIL_V_MSG(arch.cputype != (uint32_t)p_cputypes[i].x || arch.cpusubtype != (uint32_t)p_cputypes[i].y, vformat("Mismatching MachO architecture: \"%s\".", p_files[i]));
+ } else {
+ arch.cputype = (uint32_t)p_cputypes[i].x;
+ arch.cpusubtype = (uint32_t)p_cputypes[i].y;
+ arch.offset = 0;
+ arch.size = fb->get_length();
+ arch.align = 0x03;
+ }
+ max_size += arch.size;
+
+ archs.push_back(arch);
+ }
+ }
+
+ // Write header.
+ bool is_64 = (max_size >= std::numeric_limits<uint32_t>::max());
+ if (is_64) {
+ fa->store_32(0xbfbafeca);
+ } else {
+ fa->store_32(0xbebafeca);
+ }
+ fa->store_32(BSWAP32(archs.size()));
+ uint64_t offset = archs.size() * (is_64 ? 32 : 20) + 8;
+ for (int i = 0; i < archs.size(); i++) {
+ archs.write[i].offset = offset + PAD(offset, uint64_t(1) << archs[i].align);
+ if (is_64) {
+ fa->store_32(BSWAP32(archs[i].cputype));
+ fa->store_32(BSWAP32(archs[i].cpusubtype));
+ fa->store_64(BSWAP64(archs[i].offset));
+ fa->store_64(BSWAP64(archs[i].size));
+ fa->store_32(BSWAP32(archs[i].align));
+ fa->store_32(0);
+ } else {
+ fa->store_32(BSWAP32(archs[i].cputype));
+ fa->store_32(BSWAP32(archs[i].cpusubtype));
+ fa->store_32(BSWAP32(archs[i].offset));
+ fa->store_32(BSWAP32(archs[i].size));
+ fa->store_32(BSWAP32(archs[i].align));
+ }
+ offset = archs[i].offset + archs[i].size;
+ }
+
+ // Write files and padding.
+ for (int i = 0; i < archs.size(); i++) {
+ Ref<FileAccess> fb = FileAccess::open(p_files[i], FileAccess::READ);
+ if (fb.is_null()) {
+ close();
+ ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s\".", p_files[i]));
+ }
+ uint64_t cur = fa->get_position();
+ for (uint64_t j = cur; j < archs[i].offset; j++) {
+ fa->store_8(0);
+ }
+ int pages = archs[i].size / 4096;
+ int remain = archs[i].size % 4096;
+ unsigned char step[4096];
+ for (int j = 0; j < pages; j++) {
+ uint64_t br = fb->get_buffer(step, 4096);
+ if (br > 0) {
+ fa->store_buffer(step, br);
+ }
+ }
+ uint64_t br = fb->get_buffer(step, remain);
+ if (br > 0) {
+ fa->store_buffer(step, br);
+ }
+ }
+ return true;
+}
+
bool LipO::open_file(const String &p_path) {
close();
@@ -198,6 +292,18 @@ int LipO::get_arch_count() const {
return archs.size();
}
+uint32_t LipO::get_arch_cputype(int p_index) const {
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "LipO: File not opened.");
+ ERR_FAIL_INDEX_V(p_index, archs.size(), 0);
+ return archs[p_index].cputype;
+}
+
+uint32_t LipO::get_arch_cpusubtype(int p_index) const {
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "LipO: File not opened.");
+ ERR_FAIL_INDEX_V(p_index, archs.size(), 0);
+ return archs[p_index].cpusubtype;
+}
+
bool LipO::extract_arch(int p_index, const String &p_path) {
ERR_FAIL_COND_V_MSG(fa.is_null(), false, "LipO: File not opened.");
ERR_FAIL_INDEX_V(p_index, archs.size(), false);
diff --git a/platform/macos/export/lipo.h b/editor/export/lipo.h
index e375fc5a66..0b65959bc5 100644
--- a/platform/macos/export/lipo.h
+++ b/editor/export/lipo.h
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef MACOS_LIPO_H
-#define MACOS_LIPO_H
+#ifndef LIPO_H
+#define LIPO_H
// Universal / Universal 2 fat binary file creator and extractor.
@@ -57,10 +57,13 @@ class LipO : public RefCounted {
public:
static bool is_lipo(const String &p_path);
- bool create_file(const String &p_output_path, const PackedStringArray &p_files);
+ bool create_file(const String &p_output_path, const Vector<String> &p_files);
+ bool create_file(const String &p_output_path, const Vector<String> &p_files, const Vector<Vector2i> &p_cputypes);
bool open_file(const String &p_path);
int get_arch_count() const;
+ uint32_t get_arch_cputype(int p_index) const;
+ uint32_t get_arch_cpusubtype(int p_index) const;
bool extract_arch(int p_index, const String &p_path);
void close();
@@ -68,4 +71,4 @@ public:
~LipO();
};
-#endif // MACOS_LIPO_H
+#endif // LIPO_H
diff --git a/platform/macos/export/macho.cpp b/editor/export/macho.cpp
index a829774a88..a829774a88 100644
--- a/platform/macos/export/macho.cpp
+++ b/editor/export/macho.cpp
diff --git a/platform/macos/export/macho.h b/editor/export/macho.h
index a84de7de60..e7393a2fee 100644
--- a/platform/macos/export/macho.h
+++ b/editor/export/macho.h
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef MACOS_MACHO_H
-#define MACOS_MACHO_H
+#ifndef MACHO_H
+#define MACHO_H
// Mach-O binary object file format parser and editor.
@@ -39,6 +39,7 @@
#include "core/object/ref_counted.h"
class MachO : public RefCounted {
+public:
struct MachHeader {
uint32_t cputype;
uint32_t cpusubtype;
@@ -98,6 +99,21 @@ class MachO : public RefCounted {
LC_LINKER_OPTIMIZATION_HINT = 0x0000002e,
LC_VERSION_MIN_TVOS = 0x0000002f,
LC_VERSION_MIN_WATCHOS = 0x00000030,
+ LC_BUILD_VERSION = 0x00000032,
+ };
+
+ enum PlatformID {
+ PLATFORM_UNKNOWN = 0,
+ PLATFORM_MACOS = 1,
+ PLATFORM_IOS = 2,
+ PLATFORM_TVOS = 3,
+ PLATFORM_WATCHOS = 4,
+ PLATFORM_BRIDGEOS = 5,
+ PLATFORM_MACCATALYST = 6,
+ PLATFORM_IOSSIMULATOR = 7,
+ PLATFORM_TVOSSIMULATOR = 8,
+ PLATFORM_WATCHOSSIMULATOR = 9,
+ PLATFORM_DRIVERKIT = 10,
};
struct LoadCommandHeader {
@@ -158,6 +174,7 @@ class MachO : public RefCounted {
uint32_t reserved3;
};
+private:
Ref<FileAccess> fa;
bool swap = false;
@@ -208,4 +225,4 @@ public:
bool set_signature_size(uint64_t p_size);
};
-#endif // MACOS_MACHO_H
+#endif // MACHO_H
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index b7dce1a224..52d7b14b65 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -385,7 +385,7 @@ void ProjectManager::_dim_window() {
// Quick settings.
void ProjectManager::_show_quick_settings() {
- quick_settings_dialog->popup_centered(Size2(600, 200) * EDSCALE);
+ quick_settings_dialog->popup_centered(Size2(640, 200) * EDSCALE);
}
void ProjectManager::_restart_confirmed() {
diff --git a/editor/project_manager/quick_settings_dialog.cpp b/editor/project_manager/quick_settings_dialog.cpp
index 05e8523518..ee53bd7927 100644
--- a/editor/project_manager/quick_settings_dialog.cpp
+++ b/editor/project_manager/quick_settings_dialog.cpp
@@ -47,6 +47,7 @@ void QuickSettingsDialog::_fetch_setting_values() {
editor_themes.clear();
editor_scales.clear();
editor_network_modes.clear();
+ editor_directory_naming_conventions.clear();
{
List<PropertyInfo> editor_settings_properties;
@@ -61,6 +62,8 @@ void QuickSettingsDialog::_fetch_setting_values() {
editor_scales = pi.hint_string.split(",");
} else if (pi.name == "network/connection/network_mode") {
editor_network_modes = pi.hint_string.split(",");
+ } else if (pi.name == "project_manager/directory_naming_convention") {
+ editor_directory_naming_conventions = pi.hint_string.split(",");
}
}
}
@@ -120,6 +123,19 @@ void QuickSettingsDialog::_update_current_values() {
}
}
}
+
+ // Project directory naming options.
+ {
+ const int current_directory_naming = EDITOR_GET("project_manager/directory_naming_convention");
+
+ for (int i = 0; i < editor_directory_naming_conventions.size(); i++) {
+ const String &directory_naming_value = editor_directory_naming_conventions[i];
+ if (current_directory_naming == i) {
+ directory_naming_convention_button->set_text(directory_naming_value);
+ directory_naming_convention_button->select(i);
+ }
+ }
+ }
}
void QuickSettingsDialog::_add_setting_control(const String &p_text, Control *p_control) {
@@ -155,6 +171,10 @@ void QuickSettingsDialog::_network_mode_selected(int p_id) {
_set_setting_value("network/connection/network_mode", p_id);
}
+void QuickSettingsDialog::_directory_naming_convention_selected(int p_id) {
+ _set_setting_value("project_manager/directory_naming_convention", p_id);
+}
+
void QuickSettingsDialog::_set_setting_value(const String &p_setting, const Variant &p_value, bool p_restart_required) {
EditorSettings::get_singleton()->set(p_setting, p_value);
EditorSettings::get_singleton()->notify_changes();
@@ -284,6 +304,20 @@ QuickSettingsDialog::QuickSettingsDialog() {
_add_setting_control(TTR("Network Mode"), network_mode_option_button);
}
+ // Project directory naming options.
+ {
+ directory_naming_convention_button = memnew(OptionButton);
+ directory_naming_convention_button->set_fit_to_longest_item(false);
+ directory_naming_convention_button->connect(SceneStringName(item_selected), callable_mp(this, &QuickSettingsDialog::_directory_naming_convention_selected));
+
+ for (int i = 0; i < editor_directory_naming_conventions.size(); i++) {
+ const String &directory_naming_convention = editor_directory_naming_conventions[i];
+ directory_naming_convention_button->add_item(directory_naming_convention, i);
+ }
+
+ _add_setting_control(TTR("Directory Naming Convention"), directory_naming_convention_button);
+ }
+
_update_current_values();
#ifdef ANDROID_ENABLED
diff --git a/editor/project_manager/quick_settings_dialog.h b/editor/project_manager/quick_settings_dialog.h
index c4c4e601c9..7a03996934 100644
--- a/editor/project_manager/quick_settings_dialog.h
+++ b/editor/project_manager/quick_settings_dialog.h
@@ -47,6 +47,7 @@ class QuickSettingsDialog : public AcceptDialog {
Vector<String> editor_themes;
Vector<String> editor_scales;
Vector<String> editor_network_modes;
+ Vector<String> editor_directory_naming_conventions;
void _fetch_setting_values();
void _update_current_values();
@@ -60,6 +61,7 @@ class QuickSettingsDialog : public AcceptDialog {
OptionButton *theme_option_button = nullptr;
OptionButton *scale_option_button = nullptr;
OptionButton *network_mode_option_button = nullptr;
+ OptionButton *directory_naming_convention_button = nullptr;
Label *custom_theme_label = nullptr;
@@ -67,6 +69,7 @@ class QuickSettingsDialog : public AcceptDialog {
void _theme_selected(int p_id);
void _scale_selected(int p_id);
void _network_mode_selected(int p_id);
+ void _directory_naming_convention_selected(int p_id);
void _set_setting_value(const String &p_setting, const Variant &p_value, bool p_restart_required = false);
Label *restart_required_label = nullptr;
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 1909ca5ab5..6e7ac0dec9 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -314,7 +314,7 @@
@export var image_array: Array[Image]
@export var node_array: Array[Node]
[/codeblock]
- [b]Note:[/b] Custom resources and nodes must be registered as global classes using [code]class_name[/code].
+ [b]Note:[/b] Custom resources and nodes should be registered as global classes using [code]class_name[/code], since the Inspector currently only supports global classes. Otherwise, a less specific type will be exported instead.
[b]Note:[/b] Node export is only supported in [Node]-derived classes and has a number of other limitations.
</description>
</annotation>
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index f1a35c84b7..d0948e4831 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -4160,6 +4160,64 @@ static String _get_annotation_error_string(const StringName &p_annotation_name,
return vformat(R"("%s" annotation requires a variable of type %s, but type "%s" was given instead.)", p_annotation_name, string, p_provided_type.to_string());
}
+static StringName _find_narrowest_native_or_global_class(const GDScriptParser::DataType &p_type) {
+ switch (p_type.kind) {
+ case GDScriptParser::DataType::NATIVE: {
+ if (p_type.is_meta_type) {
+ return Object::get_class_static(); // `GDScriptNativeClass` is not an exposed class.
+ }
+ return p_type.native_type;
+ } break;
+ case GDScriptParser::DataType::SCRIPT: {
+ Ref<Script> script;
+ if (p_type.script_type.is_valid()) {
+ script = p_type.script_type;
+ } else {
+ script = ResourceLoader::load(p_type.script_path, SNAME("Script"));
+ }
+
+ if (p_type.is_meta_type) {
+ return script.is_valid() ? script->get_class() : Script::get_class_static();
+ }
+ if (script.is_null()) {
+ return p_type.native_type;
+ }
+ if (script->get_global_name() != StringName()) {
+ return script->get_global_name();
+ }
+
+ Ref<Script> base_script = script->get_base_script();
+ if (base_script.is_null()) {
+ return script->get_instance_base_type();
+ }
+
+ GDScriptParser::DataType base_type;
+ base_type.kind = GDScriptParser::DataType::SCRIPT;
+ base_type.builtin_type = Variant::OBJECT;
+ base_type.native_type = base_script->get_instance_base_type();
+ base_type.script_type = base_script;
+ base_type.script_path = base_script->get_path();
+
+ return _find_narrowest_native_or_global_class(base_type);
+ } break;
+ case GDScriptParser::DataType::CLASS: {
+ if (p_type.is_meta_type) {
+ return GDScript::get_class_static();
+ }
+ if (p_type.class_type == nullptr) {
+ return p_type.native_type;
+ }
+ if (p_type.class_type->get_global_name() != StringName()) {
+ return p_type.class_type->get_global_name();
+ }
+ return _find_narrowest_native_or_global_class(p_type.class_type->base_type);
+ } break;
+ default: {
+ ERR_FAIL_V(StringName());
+ } break;
+ }
+}
+
template <PropertyHint t_hint, Variant::Type t_type>
bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
@@ -4302,57 +4360,9 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = String();
break;
case GDScriptParser::DataType::NATIVE:
- if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
- variable->export_info.type = Variant::OBJECT;
- variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
- variable->export_info.hint_string = export_type.native_type;
- } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
- variable->export_info.type = Variant::OBJECT;
- variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
- variable->export_info.hint_string = export_type.native_type;
- } else {
- push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
- return false;
- }
- break;
+ case GDScriptParser::DataType::SCRIPT:
case GDScriptParser::DataType::CLASS: {
- StringName class_name;
- if (export_type.class_type) {
- class_name = export_type.class_type->get_global_name();
- }
- if (class_name == StringName()) {
- push_error(R"(Script export type must be a global class.)", p_annotation);
- return false;
- }
- if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
- variable->export_info.type = Variant::OBJECT;
- variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
- variable->export_info.hint_string = class_name;
- } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
- variable->export_info.type = Variant::OBJECT;
- variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
- variable->export_info.hint_string = class_name;
- } else {
- push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
- return false;
- }
- } break;
-
- case GDScriptParser::DataType::SCRIPT: {
- StringName class_name;
- if (export_type.script_type.is_valid()) {
- class_name = export_type.script_type->get_global_name();
- }
- if (class_name == StringName()) {
- Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script"));
- if (script.is_valid()) {
- class_name = script->get_global_name();
- }
- }
- if (class_name == StringName()) {
- push_error(R"(Script export type must be a global class.)", p_annotation);
- return false;
- }
+ const StringName class_name = _find_narrowest_native_or_global_class(export_type);
if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 483e6cab0d..8bcb2bcb9a 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -2,7 +2,8 @@ class_name ExportVariableTest
extends Node
const Utils = preload("../../utils.notest.gd")
-const PreloadedScript = preload("./export_variable.notest.gd")
+const PreloadedGlobalClass = preload("./export_variable_global.notest.gd")
+const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd")
# Built-in types.
@export var test_weak_int = 1
@@ -24,7 +25,8 @@ const PreloadedScript = preload("./export_variable.notest.gd")
# Global custom classes.
@export var test_global_class: ExportVariableTest
-@export var test_preloaded_script: PreloadedScript
+@export var test_preloaded_global_class: PreloadedGlobalClass
+@export var test_preloaded_unnamed_class: PreloadedUnnamedClass # GH-93168
# Arrays.
@export var test_array: Array
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.notest.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.notest.gd
deleted file mode 100644
index 6d064351c1..0000000000
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.notest.gd
+++ /dev/null
@@ -1,2 +0,0 @@
-class_name ExportPreloadedClassTest
-extends Node
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index bb094e14b4..d10462bb8d 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -25,8 +25,10 @@ var test_timer: Timer = null
hint=NODE_TYPE hint_string="Timer" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Timer"
var test_global_class: ExportVariableTest = null
hint=NODE_TYPE hint_string="ExportVariableTest" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportVariableTest"
-var test_preloaded_script: ExportPreloadedClassTest = null
- hint=NODE_TYPE hint_string="ExportPreloadedClassTest" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportPreloadedClassTest"
+var test_preloaded_global_class: ExportVariableTestGlobalClass = null
+ hint=NODE_TYPE hint_string="ExportVariableTestGlobalClass" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportVariableTestGlobalClass"
+var test_preloaded_unnamed_class: Node2D = null
+ hint=NODE_TYPE hint_string="Node2D" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Node2D"
var test_array: Array = []
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_array_bool: Array = Array[bool]([])
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable_global.notest.gd b/modules/gdscript/tests/scripts/parser/features/export_variable_global.notest.gd
new file mode 100644
index 0000000000..caa2ead214
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable_global.notest.gd
@@ -0,0 +1,2 @@
+class_name ExportVariableTestGlobalClass
+extends Node2D
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable_unnamed.notest.gd b/modules/gdscript/tests/scripts/parser/features/export_variable_unnamed.notest.gd
new file mode 100644
index 0000000000..e251cf8aee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable_unnamed.notest.gd
@@ -0,0 +1 @@
+extends Node2D
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index fc1db391ae..c91071a3ab 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -84,11 +84,11 @@ void NavRegion::set_transform(Transform3D p_transform) {
void NavRegion::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) {
#ifdef DEBUG_ENABLED
- if (map && !Math::is_equal_approx(double(map->get_cell_size()), double(p_navigation_mesh->get_cell_size()))) {
+ if (map && p_navigation_mesh.is_valid() && !Math::is_equal_approx(double(map->get_cell_size()), double(p_navigation_mesh->get_cell_size()))) {
ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_size()), double(map->get_cell_size())));
}
- if (map && !Math::is_equal_approx(double(map->get_cell_height()), double(p_navigation_mesh->get_cell_height()))) {
+ if (map && p_navigation_mesh.is_valid() && !Math::is_equal_approx(double(map->get_cell_height()), double(p_navigation_mesh->get_cell_height()))) {
ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_height()), double(map->get_cell_height())));
}
#endif // DEBUG_ENABLED
diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
index 20c1647843..87994ef22b 100644
--- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml
+++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
@@ -41,6 +41,9 @@
<member name="application/export_project_only" type="bool" setter="" getter="">
If [code]true[/code], exports iOS project files without building an XCArchive or [code].ipa[/code] file. If [code]false[/code], exports iOS project files and builds an XCArchive and [code].ipa[/code] file at the same time. When combining Godot with Fastlane or other build pipelines, you may want to set this to [code]true[/code].
</member>
+ <member name="application/generate_simulator_library_if_missing" type="bool" setter="" getter="">
+ If [code]true[/code], and ARM64 simulator library is missing from the export template, it is automatically generated from ARM64 device library.
+ </member>
<member name="application/icon_interpolation" type="int" setter="" getter="">
Interpolation method used to resize application icon.
</member>
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index 2194804b3b..490f73a36d 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"
@@ -248,7 +250,7 @@ bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPre
}
bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
- if (p_option.begins_with("privacy")) {
+ if (p_option.begins_with("privacy") || p_option == "application/generate_simulator_library_if_missing") {
return advanced_options_enabled;
}
@@ -288,6 +290,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
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++) {
@@ -1150,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();
@@ -2146,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) {
@@ -2154,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");
diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h
index 197f27da76..1964906c27 100644
--- a/platform/ios/export/export_plugin.h
+++ b/platform/ios/export/export_plugin.h
@@ -135,6 +135,8 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
Vector<ExportArchitecture> _get_supported_architectures() const;
Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset) const;
+ bool _archive_has_arm64(const String &p_path, uint32_t *r_cputype = nullptr, uint32_t *r_cpusubtype = nullptr) const;
+ int _archive_convert_to_simulator(const String &p_path) const;
void _check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const;
Error _convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const;
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 32278ba4a1..73e2f2d45b 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -30,10 +30,7 @@
#include "export_plugin.h"
-#include "codesign.h"
-#include "lipo.h"
#include "logo_svg.gen.h"
-#include "macho.h"
#include "run_icon_svg.gen.h"
#include "core/io/image_loader.h"
@@ -43,6 +40,9 @@
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_string_names.h"
+#include "editor/export/codesign.h"
+#include "editor/export/lipo.h"
+#include "editor/export/macho.h"
#include "editor/import/resource_importer_texture_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/resources/image_texture.h"