diff options
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" |