diff --git a/core/config/engine.h b/core/config/engine.h index 7e617d8773a0..67b02dba2f35 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -83,6 +83,7 @@ class Engine { bool editor_hint = false; bool project_manager_hint = false; bool extension_reloading = false; + bool safe_mode_hint = false; bool _print_header = true; @@ -152,6 +153,9 @@ class Engine { _FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) { extension_reloading = p_enabled; } _FORCE_INLINE_ bool is_extension_reloading_enabled() const { return extension_reloading; } + + _FORCE_INLINE_ void set_safe_mode_hint(bool p_enabled) { safe_mode_hint = p_enabled; } + _FORCE_INLINE_ bool is_safe_mode_hint() const { return safe_mode_hint; } #else _FORCE_INLINE_ void set_editor_hint(bool p_enabled) {} _FORCE_INLINE_ bool is_editor_hint() const { return false; } @@ -161,6 +165,9 @@ class Engine { _FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) {} _FORCE_INLINE_ bool is_extension_reloading_enabled() const { return false; } + + _FORCE_INLINE_ void set_safe_mode_hint(bool p_enabled) {} + _FORCE_INLINE_ bool is_safe_mode_hint() const { return false; } #endif Dictionary get_version_info() const; diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index 1ee9de077689..ac46e71b67c0 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -69,6 +69,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(co } GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return LOAD_STATUS_OK; + } + if (gdextension_map.has(p_path)) { return LOAD_STATUS_ALREADY_LOADED; } @@ -92,6 +96,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String #else ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_extension_reloading_enabled(), LOAD_STATUS_FAILED, "GDExtension reloading is disabled."); + if (Engine::get_singleton()->is_safe_mode_hint()) { + return LOAD_STATUS_OK; + } + if (!gdextension_map.has(p_path)) { return LOAD_STATUS_NOT_LOADED; } @@ -134,6 +142,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String } GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return LOAD_STATUS_OK; + } + if (!gdextension_map.has(p_path)) { return LOAD_STATUS_NOT_LOADED; } @@ -180,6 +192,10 @@ String GDExtensionManager::class_get_icon_path(const String &p_class) const { } void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } + ERR_FAIL_COND(int32_t(p_level) - 1 != level); for (KeyValue> &E : gdextension_map) { E.value->initialize_library(p_level); @@ -188,6 +204,10 @@ void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel } void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLevel p_level) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } + ERR_FAIL_COND(int32_t(p_level) != level); for (KeyValue> &E : gdextension_map) { E.value->deinitialize_library(p_level); @@ -226,6 +246,10 @@ void GDExtensionManager::_reload_all_scripts() { #endif // TOOLS_ENABLED void GDExtensionManager::load_extensions() { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } + Ref f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ); while (f.is_valid() && !f->eof_reached()) { String s = f->get_line().strip_edges(); @@ -240,6 +264,9 @@ void GDExtensionManager::load_extensions() { void GDExtensionManager::reload_extensions() { #ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } bool reloaded = false; for (const KeyValue> &E : gdextension_map) { if (!E.value->is_reloadable()) { diff --git a/core/os/os.cpp b/core/os/os.cpp index 642de11a9f10..323c7c240877 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -287,10 +287,14 @@ String OS::get_bundle_icon_path() const { } // OS specific path for user:// -String OS::get_user_data_dir() const { +String OS::get_user_data_dir(const String &p_appname) const { return "."; } +String OS::get_user_data_dir() const { + return get_user_data_dir(get_safe_dir_name(GLOBAL_GET("application/config/name"))); +} + // Absolute path to res:// String OS::get_resource_dir() const { return ProjectSettings::get_singleton()->get_resource_path(); @@ -301,6 +305,23 @@ String OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { return "."; } +void OS::create_lock_file() { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } + + String lock_file_path = get_user_data_dir().path_join(".safe_mode_lock"); + Ref lock_file = FileAccess::open(lock_file_path, FileAccess::WRITE); + if (lock_file.is_valid()) { + lock_file->close(); + } +} + +void OS::remove_lock_file() { + String lock_file_path = get_user_data_dir().path_join(".safe_mode_lock"); + DirAccess::remove_absolute(lock_file_path); +} + Error OS::shell_open(const String &p_uri) { return ERR_UNAVAILABLE; } diff --git a/core/os/os.h b/core/os/os.h index 91e0ce937944..be4bac17ee9b 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -274,6 +274,7 @@ class OS { virtual String get_bundle_resource_dir() const; virtual String get_bundle_icon_path() const; + virtual String get_user_data_dir(const String &p_appname) const; virtual String get_user_data_dir() const; virtual String get_resource_dir() const; @@ -292,6 +293,9 @@ class OS { virtual Error move_to_trash(const String &p_path) { return FAILED; } + void create_lock_file(); + void remove_lock_file(); + virtual int get_exit_code() const; // `set_exit_code` should only be used from `SceneTree` (or from a similar // level, e.g. from the `Main::start` if leaving without creating a `SceneTree`). diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index ce2553456d91..8ac42b1a256a 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -872,18 +872,17 @@ void OS_Unix::unset_environment(const String &p_var) const { unsetenv(p_var.utf8().get_data()); } -String OS_Unix::get_user_data_dir() const { - String appname = get_safe_dir_name(GLOBAL_GET("application/config/name")); - if (!appname.is_empty()) { +String OS_Unix::get_user_data_dir(const String &p_appname) const { + if (!p_appname.is_empty()) { bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir"); if (use_custom_dir) { String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true); if (custom_dir.is_empty()) { - custom_dir = appname; + custom_dir = p_appname; } return get_data_path().path_join(custom_dir); } else { - return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname); + return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(p_appname); } } diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index df269a59d32a..a9bc61aa0f01 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -100,7 +100,7 @@ class OS_Unix : public OS { virtual void initialize_debugging() override; virtual String get_executable_path() const override; - virtual String get_user_data_dir() const override; + virtual String get_user_data_dir(const String &p_appname) const override; }; class UnixTerminalLogger : public StdLogger { diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index d3bd18c0e81f..ba490585ae32 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -91,6 +91,10 @@ EditorDebuggerNode::EditorDebuggerNode() { remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval"); inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval"); + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } + EditorRunBar::get_singleton()->get_pause_button()->connect(SceneStringName(pressed), callable_mp(this, &EditorDebuggerNode::_paused)); } @@ -262,6 +266,10 @@ void EditorDebuggerNode::set_keep_open(bool p_keep_open) { } Error EditorDebuggerNode::start(const String &p_uri) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return OK; + } + ERR_FAIL_COND_V(!p_uri.contains("://"), ERR_INVALID_PARAMETER); if (keep_open && current_uri == p_uri && server.is_valid()) { return OK; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index cb647ffc35d6..602fa8d8e3da 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -723,6 +723,10 @@ void EditorNode::_notification(int p_what) { CanvasItemEditor::ThemePreviewMode theme_preview_mode = (CanvasItemEditor::ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", CanvasItemEditor::THEME_PREVIEW_PROJECT); update_preview_themes(theme_preview_mode); + if (Engine::get_singleton()->is_safe_mode_hint()) { + EditorToaster::get_singleton()->popup_str(TTR("Safe Mode is enabled. Editor functionality has been restricted."), EditorToaster::SEVERITY_WARNING); + } + /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */ } break; @@ -1116,9 +1120,15 @@ void EditorNode::_sources_changed(bool p_exist) { if (!singleton->cmdline_export_mode) { EditorResourcePreview::get_singleton()->start(); } + + get_tree()->create_timer(1.0f)->connect("timeout", callable_mp(this, &EditorNode::_remove_lock_file)); } } +void EditorNode::_remove_lock_file() { + OS::get_singleton()->remove_lock_file(); +} + void EditorNode::_scan_external_changes() { disk_changed_list->clear(); TreeItem *r = disk_changed_list->create_item(); @@ -5249,6 +5259,10 @@ void EditorNode::_load_central_editor_layout_from_config(Ref p_confi } void EditorNode::_load_open_scenes_from_config(Ref p_layout) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } + if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) { return; } diff --git a/editor/editor_node.h b/editor/editor_node.h index 4d55eaf1b2ab..544a3d2f128a 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -151,20 +151,6 @@ class EditorNode : public Node { ACTION_ON_STOP_CLOSE_BUTTOM_PANEL, }; - struct ExecuteThreadArgs { - String path; - List args; - String output; - Thread execute_output_thread; - Mutex execute_output_mutex; - int exitcode = 0; - SafeFlag done; - }; - -private: - friend class EditorSceneTabs; - friend class SurfaceUpgradeTool; - enum MenuOptions { FILE_NEW_SCENE, FILE_NEW_INHERITED_SCENE, @@ -254,6 +240,20 @@ class EditorNode : public Node { TOOL_MENU_BASE = 1000 }; + struct ExecuteThreadArgs { + String path; + List args; + String output; + Thread execute_output_thread; + Mutex execute_output_mutex; + int exitcode = 0; + SafeFlag done; + }; + +private: + friend class EditorSceneTabs; + friend class SurfaceUpgradeTool; + enum { MAX_INIT_CALLBACKS = 128, MAX_BUILD_CALLBACKS = 128 @@ -559,6 +559,7 @@ class EditorNode : public Node { void _resources_reimporting(const Vector &p_resources); void _resources_reimported(const Vector &p_resources); void _sources_changed(bool p_exist); + void _remove_lock_file(); void _node_renamed(); void _editor_select_next(); diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp index 4cc2d1145ed9..828c2cd35fb2 100644 --- a/editor/gui/editor_run_bar.cpp +++ b/editor/gui/editor_run_bar.cpp @@ -38,6 +38,7 @@ #include "editor/editor_run_native.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" +#include "editor/gui/editor_toaster.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/panel_container.h" @@ -50,7 +51,35 @@ void EditorRunBar::_notification(int p_what) { _reset_play_buttons(); } break; + case NOTIFICATION_READY: { + if (Engine::get_singleton()->is_safe_mode_hint()) { + safe_mode_show_dialog(); + } + } break; + case NOTIFICATION_THEME_CHANGED: { + if (Engine::get_singleton()->is_safe_mode_hint()) { + main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadSafeMode"), EditorStringName(EditorStyles))); + safe_mode_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("SafeModeButton"), EditorStringName(EditorStyles))); + safe_mode_button->add_theme_style_override("hover", get_theme_stylebox(SNAME("SafeModeButton"), EditorStringName(EditorStyles))); + + safe_mode_button->set_icon(get_editor_theme_icon(SNAME("NodeWarning"))); + safe_mode_reload_button->set_icon(get_editor_theme_icon(SNAME("Reload"))); + + safe_mode_button->begin_bulk_theme_override(); + safe_mode_button->add_theme_color_override("icon_normal_color", Color(0.3, 0.3, 0.3, 1)); + safe_mode_button->add_theme_color_override("icon_pressed_color", Color(0.4, 0.4, 0.4, 1)); + safe_mode_button->add_theme_color_override("icon_hover_color", Color(0.6, 0.6, 0.6, 1)); + Color dark_color = get_theme_color("safe_mode_text_color", EditorStringName(Editor)); + safe_mode_button->add_theme_color_override(SceneStringName(font_color), dark_color); + safe_mode_button->add_theme_color_override("font_pressed_color", dark_color.lightened(0.2)); + safe_mode_button->add_theme_color_override("font_hover_color", dark_color.lightened(0.4)); + safe_mode_button->add_theme_color_override("font_hover_pressed_color", dark_color.lightened(0.2)); + safe_mode_button->end_bulk_theme_override(); + + return; + } + _update_play_buttons(); pause_button->set_icon(get_editor_theme_icon(SNAME("Pause"))); stop_button->set_icon(get_editor_theme_icon(SNAME("Stop"))); @@ -76,6 +105,10 @@ void EditorRunBar::_notification(int p_what) { } void EditorRunBar::_reset_play_buttons() { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } + play_button->set_pressed(false); play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay"))); play_button->set_tooltip_text(TTR("Play the project.")); @@ -90,6 +123,10 @@ void EditorRunBar::_reset_play_buttons() { } void EditorRunBar::_update_play_buttons() { + if (Engine::get_singleton()->is_safe_mode_hint()) { + return; + } + _reset_play_buttons(); if (!is_playing()) { return; @@ -262,7 +299,20 @@ void EditorRunBar::_run_native(const Ref &p_preset) { } } +void EditorRunBar::safe_mode_show_dialog() { + safe_mode_popup->popup_centered(); +} + +void EditorRunBar::safe_mode_reload_project() { + EditorNode::get_singleton()->trigger_menu_option(EditorNode::RELOAD_CURRENT_PROJECT, false); +} + void EditorRunBar::play_main_scene(bool p_from_native) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + EditorToaster::get_singleton()->popup_str(TTR("Safe Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING); + return; + } + if (p_from_native) { run_native->resume_run_native(); } else { @@ -274,6 +324,11 @@ void EditorRunBar::play_main_scene(bool p_from_native) { } void EditorRunBar::play_current_scene(bool p_reload) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + EditorToaster::get_singleton()->popup_str(TTR("Safe Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING); + return; + } + String last_current_scene = run_current_filename; // This is necessary to have a copy of the string. EditorNode::get_singleton()->save_default_environment(); @@ -288,6 +343,11 @@ void EditorRunBar::play_current_scene(bool p_reload) { } void EditorRunBar::play_custom_scene(const String &p_custom) { + if (Engine::get_singleton()->is_safe_mode_hint()) { + EditorToaster::get_singleton()->popup_str(TTR("Safe Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING); + return; + } + stop_playing(); current_mode = RunMode::RUN_CUSTOM; @@ -371,6 +431,35 @@ EditorRunBar::EditorRunBar() { main_hbox = memnew(HBoxContainer); main_panel->add_child(main_hbox); + if (Engine::get_singleton()->is_safe_mode_hint()) { + safe_mode_popup = memnew(AcceptDialog); + safe_mode_popup->set_min_size(Size2(550, 70)); + safe_mode_popup->set_title(TTR("Safe Mode")); + safe_mode_popup->set_text(TTR("Godot opened the project in Safe Mode, which is a special mode that can help recover projects that crash the engine upon initialization. The following features have been temporarily disabled:\n\n- Tool scripts\n- Editor plugins\n- GDExtension addons\n- Automatic scene restoring\n\nIf the project cannot be opened outside of this mode, then it's very likely any of these components is preventing this project from launching. This mode is intended only for basic editing to troubleshoot such issues, and therefore it is not possible to run a project in this mode.\n\nTo disable Safe Mode, reload the project by pressing the Reload button next to the Safe Mode banner, or by reopening the project normally.")); + safe_mode_popup->set_autowrap(true); + add_child(safe_mode_popup); + + safe_mode_reload_button = memnew(Button); + main_hbox->add_child(safe_mode_reload_button); + safe_mode_reload_button->set_theme_type_variation("RunBarButton"); + safe_mode_reload_button->set_focus_mode(Control::FOCUS_NONE); + safe_mode_reload_button->set_tooltip_text(TTR("Disable safe mode and reload the project.")); + safe_mode_reload_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::safe_mode_reload_project)); + + safe_mode_panel = memnew(PanelContainer); + main_hbox->add_child(safe_mode_panel); + + safe_mode_button = memnew(Button); + safe_mode_panel->add_child(safe_mode_button); + safe_mode_button->set_theme_type_variation("RunBarButton"); + safe_mode_button->set_focus_mode(Control::FOCUS_NONE); + safe_mode_button->set_text(TTR("Safe Mode")); + safe_mode_button->set_tooltip_text(TTR("Safe Mode is enabled. Click for more details.")); + safe_mode_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::safe_mode_show_dialog)); + + return; + } + play_button = memnew(Button); main_hbox->add_child(play_button); play_button->set_theme_type_variation("RunBarButton"); diff --git a/editor/gui/editor_run_bar.h b/editor/gui/editor_run_bar.h index 1cb999612a0c..961abb9d3da1 100644 --- a/editor/gui/editor_run_bar.h +++ b/editor/gui/editor_run_bar.h @@ -40,6 +40,7 @@ class EditorRunNative; class EditorQuickOpen; class PanelContainer; class HBoxContainer; +class AcceptDialog; class EditorRunBar : public MarginContainer { GDCLASS(EditorRunBar, MarginContainer); @@ -56,6 +57,11 @@ class EditorRunBar : public MarginContainer { PanelContainer *main_panel = nullptr; HBoxContainer *main_hbox = nullptr; + PanelContainer *safe_mode_panel = nullptr; + Button *safe_mode_button = nullptr; + Button *safe_mode_reload_button = nullptr; + AcceptDialog *safe_mode_popup = nullptr; + Button *play_button = nullptr; Button *pause_button = nullptr; Button *stop_button = nullptr; @@ -93,6 +99,9 @@ class EditorRunBar : public MarginContainer { public: static EditorRunBar *get_singleton() { return singleton; } + void safe_mode_show_dialog(); + void safe_mode_reload_project(); + void play_main_scene(bool p_from_native = false); void play_current_scene(bool p_reload = false); void play_custom_scene(const String &p_custom); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index a3e62c298f79..a36ea16a5d1c 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -387,8 +387,13 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { Color button_color = Color(1, 1, 1); // Can't set tooltip after adding button, need to do it before. if (scr->is_tool()) { - additional_notes += "\n" + TTR("This script is currently running in the editor."); - button_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); + if (Engine::get_singleton()->is_safe_mode_hint()) { + additional_notes += "\n" + TTR("This script can run in the editor.\nIt is currently disabled due to safe mode."); + button_color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor)); + } else { + additional_notes += "\n" + TTR("This script is currently running in the editor."); + button_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); + } } if (EditorNode::get_singleton()->get_object_custom_type_base(p_node) == scr) { additional_notes += "\n" + TTR("This script is a custom type."); diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index e6fe5549235f..9b0223579db0 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -1779,7 +1779,7 @@ bool AssetLibraryEditorPlugin::is_available() { // directly from GitHub which does not set CORS. return false; #else - return StreamPeerTLS::is_available(); + return StreamPeerTLS::is_available() && !Engine::get_singleton()->is_safe_mode_hint(); #endif } diff --git a/editor/plugins/editor_plugin_settings.cpp b/editor/plugins/editor_plugin_settings.cpp index afc60b09d613..6d4153b15d93 100644 --- a/editor/plugins/editor_plugin_settings.cpp +++ b/editor/plugins/editor_plugin_settings.cpp @@ -39,6 +39,7 @@ #include "editor/editor_string_names.h" #include "editor/themes/editor_scale.h" #include "scene/gui/margin_container.h" +#include "scene/gui/separator.h" #include "scene/gui/tree.h" void EditorPluginSettings::_notification(int p_what) { @@ -51,6 +52,12 @@ void EditorPluginSettings::_notification(int p_what) { plugin_config_dialog->connect("plugin_ready", callable_mp(EditorNode::get_singleton(), &EditorNode::_on_plugin_ready)); plugin_list->connect("button_clicked", callable_mp(this, &EditorPluginSettings::_cell_button_pressed)); } break; + + case NOTIFICATION_THEME_CHANGED: { + if (Engine::get_singleton()->is_safe_mode_hint()) { + safe_mode_icon->set_texture(get_editor_theme_icon(SNAME("NodeWarning"))); + } + } break; } } @@ -209,6 +216,23 @@ EditorPluginSettings::EditorPluginSettings() { plugin_config_dialog->config(""); add_child(plugin_config_dialog); + if (Engine::get_singleton()->is_safe_mode_hint()) { + HBoxContainer *c = memnew(HBoxContainer); + add_child(c); + + safe_mode_icon = memnew(TextureRect); + safe_mode_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + c->add_child(safe_mode_icon); + + Label *safe_mode_label = memnew(Label(TTR("Safe mode is enabled. Enabled plugins will not run while this mode is active."))); + safe_mode_label->set_theme_type_variation("HeaderSmall"); + safe_mode_label->set_h_size_flags(SIZE_EXPAND_FILL); + c->add_child(safe_mode_label); + + HSeparator *sep = memnew(HSeparator); + add_child(sep); + } + HBoxContainer *title_hb = memnew(HBoxContainer); Label *label = memnew(Label(TTR("Installed Plugins:"))); label->set_theme_type_variation("HeaderSmall"); diff --git a/editor/plugins/editor_plugin_settings.h b/editor/plugins/editor_plugin_settings.h index 5b470b3e5835..3f29277b4c5e 100644 --- a/editor/plugins/editor_plugin_settings.h +++ b/editor/plugins/editor_plugin_settings.h @@ -55,6 +55,7 @@ class EditorPluginSettings : public VBoxContainer { }; PluginConfigDialog *plugin_config_dialog = nullptr; + TextureRect *safe_mode_icon = nullptr; Tree *plugin_list = nullptr; bool updating = false; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 59f45ef5dbd7..a417fb55fa4a 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -249,6 +249,7 @@ void ProjectManager::_update_theme(bool p_skip_creation) { import_btn->set_icon(get_editor_theme_icon(SNAME("Load"))); scan_btn->set_icon(get_editor_theme_icon(SNAME("Search"))); open_btn->set_icon(get_editor_theme_icon(SNAME("Edit"))); + open_options_btn->set_icon(get_editor_theme_icon(SNAME("Collapse"))); run_btn->set_icon(get_editor_theme_icon(SNAME("Play"))); rename_btn->set_icon(get_editor_theme_icon(SNAME("Rename"))); manage_tags_btn->set_icon(get_editor_theme_icon("Script")); @@ -268,6 +269,9 @@ void ProjectManager::_update_theme(bool p_skip_creation) { manage_tags_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager"))); erase_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager"))); erase_missing_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager"))); + + open_btn_container->add_theme_constant_override("separation", 0); + open_options_popup->set_item_icon(0, get_editor_theme_icon(SNAME("NodeWarning"))); } // Asset library popup. @@ -478,7 +482,7 @@ void ProjectManager::_run_project_confirm() { } } -void ProjectManager::_open_selected_projects() { +void ProjectManager::_open_selected_projects(bool p_safe_mode) { // Show loading text to tell the user that the project manager is busy loading. // This is especially important for the Web project manager. loading_label->show(); @@ -506,6 +510,10 @@ void ProjectManager::_open_selected_projects() { args.push_back("--editor"); + if (p_safe_mode) { + args.push_back("--safe-mode"); + } + Error err = OS::get_singleton()->create_instance(args); if (err != OK) { loading_label->hide(); @@ -521,7 +529,7 @@ void ProjectManager::_open_selected_projects() { get_tree()->quit(); } -void ProjectManager::_open_selected_projects_ask() { +void ProjectManager::_open_selected_projects_ask(bool p_safe_mode) { const HashSet &selected_list = project_list->get_selected_project_keys(); if (selected_list.size() < 1) { return; @@ -605,8 +613,14 @@ void ProjectManager::_open_selected_projects_ask() { return; } + // Check if the project failed to load during last startup. + if (!p_safe_mode && project.safe_mode) { + open_safe_mode_ask->popup_centered(); + return; + } + // Open if the project is up-to-date. - _open_selected_projects(); + _open_selected_projects(p_safe_mode); } void ProjectManager::_install_project(const String &p_zip_path, const String &p_title) { @@ -691,6 +705,7 @@ void ProjectManager::_update_project_buttons() { erase_btn->set_disabled(empty_selection); open_btn->set_disabled(empty_selection || is_missing_project_selected); + open_options_btn->set_disabled(empty_selection || is_missing_project_selected); rename_btn->set_disabled(empty_selection || is_missing_project_selected); manage_tags_btn->set_disabled(empty_selection || is_missing_project_selected || selected_projects.size() > 1); run_btn->set_disabled(empty_selection || is_missing_project_selected); @@ -698,6 +713,19 @@ void ProjectManager::_update_project_buttons() { erase_missing_btn->set_disabled(!project_list->is_any_project_missing()); } +void ProjectManager::_open_options_popup() { + Rect2 rect = open_btn_container->get_screen_rect(); + rect.position.y += rect.size.height; + open_options_popup->set_size(Size2(rect.size.width, 0)); + open_options_popup->set_position(rect.position); + + open_options_popup->popup(); +} + +void ProjectManager::_open_safe_mode_ask() { + open_safe_mode_ask->popup_centered(); +} + void ProjectManager::_on_projects_updated() { Vector selected_projects = project_list->get_selected_projects(); int index = 0; @@ -711,6 +739,14 @@ void ProjectManager::_on_projects_updated() { project_list->update_dock_menu(); } +void ProjectManager::_on_open_options_selected(int p_option) { + switch (p_option) { + case 0: // Edit in safe mode + _open_safe_mode_ask(); + break; + } +} + void ProjectManager::_on_project_created(const String &dir) { project_list->add_project(dir, false); project_list->save_config(); @@ -718,7 +754,7 @@ void ProjectManager::_on_project_created(const String &dir) { int i = project_list->refresh_project(dir); project_list->select_project(i); project_list->ensure_project_visible(i); - _open_selected_projects_ask(); + _open_selected_projects_ask(false); project_list->update_dock_menu(); } @@ -745,7 +781,7 @@ void ProjectManager::_on_search_term_submitted(const String &p_text) { return; } - _open_selected_projects_ask(); + _open_selected_projects_ask(false); } LineEdit *ProjectManager::get_search_box() { @@ -934,7 +970,7 @@ void ProjectManager::shortcut_input(const Ref &p_ev) { switch (k->get_keycode()) { case Key::ENTER: { - _open_selected_projects_ask(); + _open_selected_projects_ask(false); } break; case Key::HOME: { if (project_list->get_project_count() > 0) { @@ -1291,7 +1327,7 @@ ProjectManager::ProjectManager() { project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons)); project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_list_placeholder)); project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons)); - project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask)); + project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask).bind(false)); // Empty project list placeholder. { @@ -1350,11 +1386,30 @@ ProjectManager::ProjectManager() { project_list_sidebar->add_child(memnew(HSeparator)); + open_btn_container = memnew(HBoxContainer); + open_btn_container->set_anchors_preset(Control::PRESET_FULL_RECT); + project_list_sidebar->add_child(open_btn_container); + open_btn = memnew(Button); open_btn->set_text(TTR("Edit")); open_btn->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTR("Edit Project"), KeyModifierMask::CMD_OR_CTRL | Key::E)); - open_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_ask)); - project_list_sidebar->add_child(open_btn); + open_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_ask).bind(false)); + open_btn->set_h_size_flags(Control::SIZE_EXPAND_FILL); + open_btn_container->add_child(open_btn); + + open_btn_container->add_child(memnew(VSeparator)); + + open_options_btn = memnew(Button); + open_options_btn->set_icon_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER); + open_options_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_options_popup)); + open_btn_container->add_child(open_options_btn); + + open_options_popup = memnew(PopupMenu); + open_options_popup->add_item(TTR("Edit in safe mode")); + open_options_popup->connect(SceneStringName(id_pressed), callable_mp(this, &ProjectManager::_on_open_options_selected)); + open_options_btn->add_child(open_options_popup); + + open_btn_container->set_custom_minimum_size(Size2(120, open_btn->get_combined_minimum_size().y)); run_btn = memnew(Button); run_btn->set_text(TTR("Run")); @@ -1476,17 +1531,26 @@ ProjectManager::ProjectManager() { multi_open_ask = memnew(ConfirmationDialog); multi_open_ask->set_ok_button_text(TTR("Edit")); - multi_open_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects)); + multi_open_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects).bind(false)); add_child(multi_open_ask); multi_run_ask = memnew(ConfirmationDialog); multi_run_ask->set_ok_button_text(TTR("Run")); - multi_run_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project_confirm)); + multi_run_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project_confirm).bind(false)); add_child(multi_run_ask); + open_safe_mode_ask = memnew(ConfirmationDialog); + open_safe_mode_ask->set_min_size(Size2(550, 70)); + open_safe_mode_ask->set_ok_button_text(TTR("Edit in Safe Mode")); + open_safe_mode_ask->set_text(TTR("It looks like Godot crashed when opening this project the last time. If you're having problems editing this project, you can try to open it in Safe Mode.\n\nThis is a special mode that may help to recover projects that crash the engine during initialization. This mode temporarily disables the following features:\n\n- Tool scripts\n- Editor plugins\n- GDExtension addons\n- Automatic scene restoring\n\nThis mode is intended only for basic editing to troubleshoot such issues, and therefore it will not possible to run the project during this mode. It is also a good idea to make a backup of your project before proceeding.\n\nEdit the project in Safe Mode?")); + open_safe_mode_ask->set_autowrap(true); + open_safe_mode_ask->add_button(TTR("Edit normally"))->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects).bind(false)); + open_safe_mode_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects).bind(true)); + add_child(open_safe_mode_ask); + ask_update_settings = memnew(ConfirmationDialog); ask_update_settings->set_autowrap(true); - ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects)); + ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects).bind(false)); full_convert_button = ask_update_settings->add_button(TTR("Convert Full Project"), !GLOBAL_GET("gui/common/swap_cancel_ok")); full_convert_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_full_convert_button_pressed)); add_child(ask_update_settings); diff --git a/editor/project_manager.h b/editor/project_manager.h index 669b5d8b6c57..c69a4af579be 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -45,6 +45,7 @@ class LinkButton; class MarginContainer; class OptionButton; class PanelContainer; +class PopupMenu; class ProjectDialog; class ProjectList; class QuickSettingsDialog; @@ -152,12 +153,16 @@ class ProjectManager : public Control { Button *import_btn = nullptr; Button *scan_btn = nullptr; Button *open_btn = nullptr; + Button *open_options_btn = nullptr; Button *run_btn = nullptr; Button *rename_btn = nullptr; Button *manage_tags_btn = nullptr; Button *erase_btn = nullptr; Button *erase_missing_btn = nullptr; + HBoxContainer *open_btn_container = nullptr; + PopupMenu *open_options_popup = nullptr; + EditorFileDialog *scan_dir = nullptr; ConfirmationDialog *erase_ask = nullptr; @@ -168,14 +173,15 @@ class ProjectManager : public Control { ConfirmationDialog *erase_missing_ask = nullptr; ConfirmationDialog *multi_open_ask = nullptr; ConfirmationDialog *multi_run_ask = nullptr; + ConfirmationDialog *open_safe_mode_ask = nullptr; ProjectDialog *project_dialog = nullptr; void _scan_projects(); void _run_project(); void _run_project_confirm(); - void _open_selected_projects(); - void _open_selected_projects_ask(); + void _open_selected_projects(bool p_safe_mode); + void _open_selected_projects_ask(bool p_safe_mode); void _install_project(const String &p_zip_path, const String &p_title); void _import_project(); @@ -186,9 +192,12 @@ class ProjectManager : public Control { void _erase_project_confirm(); void _erase_missing_projects_confirm(); void _update_project_buttons(); + void _open_options_popup(); + void _open_safe_mode_ask(); void _on_project_created(const String &dir); void _on_projects_updated(); + void _on_open_options_selected(int p_option); void _on_order_option_changed(int p_idx); void _on_search_term_changed(const String &p_term); diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index 092a6a1a1852..ec40c28b9c50 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -417,14 +417,16 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa String conf = p_path.path_join("project.godot"); bool grayed = false; bool missing = false; + bool safe_mode = false; Ref cf = memnew(ConfigFile); Error cf_err = cf->load(conf); int config_version = 0; + String cf_project_name; String project_name = TTR("Unnamed Project"); if (cf_err == OK) { - String cf_project_name = cf->get_value("application", "config/name", ""); + cf_project_name = cf->get_value("application", "config/name", ""); if (!cf_project_name.is_empty()) { project_name = cf_project_name.xml_unescape(); } @@ -486,7 +488,13 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa ProjectManager::get_singleton()->add_new_tag(tag); } - return Item(project_name, description, project_version, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version); + // We can't use globalize_path because there is no loaded project. So we replicate the behavior of accessing user:// + if (!cf_project_name.is_empty()) { + String safe_mode_lock_file = OS::get_singleton()->get_user_data_dir(cf_project_name).path_join(".safe_mode_lock"); + safe_mode = FileAccess::exists(safe_mode_lock_file); + } + + return Item(project_name, description, project_version, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, safe_mode, config_version); } void ProjectList::_update_icons_async() { diff --git a/editor/project_manager/project_list.h b/editor/project_manager/project_list.h index 6e0f5830ac31..be986d4187d1 100644 --- a/editor/project_manager/project_list.h +++ b/editor/project_manager/project_list.h @@ -115,6 +115,7 @@ class ProjectList : public ScrollContainer { bool favorite = false; bool grayed = false; bool missing = false; + bool safe_mode = false; int version = 0; ProjectListItemControl *control = nullptr; @@ -133,6 +134,7 @@ class ProjectList : public ScrollContainer { bool p_favorite, bool p_grayed, bool p_missing, + bool p_safe_mode, int p_version) { project_name = p_name; description = p_description; @@ -146,6 +148,7 @@ class ProjectList : public ScrollContainer { favorite = p_favorite; grayed = p_grayed; missing = p_missing; + safe_mode = p_safe_mode; version = p_version; control = nullptr; diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 610ad3efdf16..85d6e37b5421 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -210,7 +210,9 @@ void register_editor_types() { EditorPlugins::add_by_type(); EditorPlugins::add_by_type(); EditorPlugins::add_by_type(); - EditorPlugins::add_by_type(); + if (!Engine::get_singleton()->is_safe_mode_hint()) { + EditorPlugins::add_by_type(); + } EditorPlugins::add_by_type(); EditorPlugins::add_by_type(); EditorPlugins::add_by_type(); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index a63b6d4e140a..0812a8f9bf21 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1867,6 +1867,11 @@ void EditorThemeManager::_populate_editor_styles(const Ref &p_theme style_launch_pad_movie->set_border_color(p_config.accent_color); style_launch_pad_movie->set_border_width_all(Math::round(2 * EDSCALE)); p_theme->set_stylebox("LaunchPadMovieMode", EditorStringName(EditorStyles), style_launch_pad_movie); + Ref style_launch_pad_safe_mode = style_launch_pad->duplicate(); + style_launch_pad_safe_mode->set_bg_color(p_config.accent_color * Color(1, 1, 1, 0.1)); + style_launch_pad_safe_mode->set_border_color(p_config.warning_color); + style_launch_pad_safe_mode->set_border_width_all(Math::round(2 * EDSCALE)); + p_theme->set_stylebox("LaunchPadSafeMode", EditorStringName(EditorStyles), style_launch_pad_safe_mode); p_theme->set_stylebox("MovieWriterButtonNormal", EditorStringName(EditorStyles), make_empty_stylebox(0, 0, 0, 0)); Ref style_write_movie_button = p_config.button_style_pressed->duplicate(); @@ -1884,6 +1889,16 @@ void EditorThemeManager::_populate_editor_styles(const Ref &p_theme p_theme->set_color("movie_writer_icon_pressed", EditorStringName(EditorStyles), Color(0, 0, 0, 0.84)); p_theme->set_color("movie_writer_icon_hover", EditorStringName(EditorStyles), Color(1, 1, 1, 0.9)); p_theme->set_color("movie_writer_icon_hover_pressed", EditorStringName(EditorStyles), Color(0, 0, 0, 0.84)); + + Ref style_safe_mode_button = p_config.button_style_pressed->duplicate(); + style_safe_mode_button->set_bg_color(p_config.warning_color); + style_safe_mode_button->set_corner_radius_all(p_config.corner_radius * EDSCALE); + style_safe_mode_button->set_content_margin_all(0); + // Safe mode button is implicitly styled from the panel's background. + // So, remove any existing borders. (e.g. from draw_extra_borders config) + style_safe_mode_button->set_border_width_all(0); + style_safe_mode_button->set_expand_margin(SIDE_RIGHT, 2 * EDSCALE); + p_theme->set_stylebox("SafeModeButton", EditorStringName(EditorStyles), style_safe_mode_button); } // Standard GUI variations. diff --git a/main/main.cpp b/main/main.cpp index 870f7d31b83e..c10fbac0ddf6 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -194,6 +194,7 @@ static uint64_t quit_after = 0; static OS::ProcessID editor_pid = 0; #ifdef TOOLS_ENABLED static bool found_project = false; +static bool safe_mode = false; static bool auto_build_solutions = false; static String debug_server_uri; static bool wait_for_import = false; @@ -527,6 +528,7 @@ void Main::print_help(const char *p_binary) { #ifdef TOOLS_ENABLED print_help_option("-e, --editor", "Start the editor instead of running the scene.\n", CLI_OPTION_AVAILABILITY_EDITOR); print_help_option("-p, --project-manager", "Start the project manager, even if a project is auto-detected.\n", CLI_OPTION_AVAILABILITY_EDITOR); + print_help_option("--safe-mode", "Start the editor in safe mode, which disables typical features that can cause startup crashes, such as tool scripts, editor plugins, GDExtension addons, and others.\n", CLI_OPTION_AVAILABILITY_EDITOR); print_help_option("--debug-server ", "Start the editor debug server (://[:port], e.g. tcp://127.0.0.1:6007)\n", CLI_OPTION_AVAILABILITY_EDITOR); print_help_option("--dap-port ", "Use the specified port for the GDScript Debugger Adaptor protocol. Recommended port range [1024, 49151].\n", CLI_OPTION_AVAILABILITY_EDITOR); #if defined(MODULE_GDSCRIPT_ENABLED) && !defined(GDSCRIPT_NO_LSP) @@ -1379,6 +1381,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph editor = true; } else if (arg == "-p" || arg == "--project-manager") { // starts project manager project_manager = true; + } else if (arg == "--safe-mode") { // Enables safe mode. + safe_mode = true; } else if (arg == "--debug-server") { if (N) { debug_server_uri = N->get(); @@ -1812,6 +1816,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (editor) { Engine::get_singleton()->set_editor_hint(true); Engine::get_singleton()->set_extension_reloading_enabled(true); + + // Create initialization lock file to detect crashes during startup. + OS::get_singleton()->create_lock_file(); } #endif @@ -1857,6 +1864,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (project_manager) { Engine::get_singleton()->set_project_manager_hint(true); } + + if (safe_mode) { + Engine::get_singleton()->set_safe_mode_hint(true); + } #endif GLOBAL_DEF("debug/file_logging/enable_file_logging", false); @@ -2482,6 +2493,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph print_help(execpath); } + if (editor) { + OS::get_singleton()->remove_lock_file(); + } + EngineDebugger::deinitialize(); if (performance) { @@ -3284,6 +3299,8 @@ int Main::start() { editor = true; } else if (E->get() == "-p" || E->get() == "--project-manager") { project_manager = true; + } else if (E->get() == "--safe-mode") { + safe_mode = true; } else if (E->get() == "--install-android-build-template") { install_android_build_template = true; #endif // TOOLS_ENABLED @@ -3858,7 +3875,7 @@ int Main::start() { #ifdef TOOLS_ENABLED if (editor) { - if (game_path != String(GLOBAL_GET("application/run/main_scene")) || !editor_node->has_scenes_in_session()) { + if (!safe_mode && (game_path != String(GLOBAL_GET("application/run/main_scene")) || !editor_node->has_scenes_in_session())) { Error serr = editor_node->load_scene(local_game_path); if (serr != OK) { ERR_PRINT("Failed to load scene"); @@ -3935,6 +3952,10 @@ int Main::start() { Crypto::load_default_certificates( EditorSettings::get_singleton()->get_setting("network/tls/editor_tls_certificates").operator String()); } + + if (safe_mode) { + Engine::get_singleton()->set_safe_mode_hint(true); + } #endif } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index eaf2565e6920..913a2effd1c7 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -247,7 +247,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr bool GDScript::can_instantiate() const { #ifdef TOOLS_ENABLED - return valid && (tool || ScriptServer::is_scripting_enabled()); + return valid && (tool || ScriptServer::is_scripting_enabled()) && !Engine::get_singleton()->is_safe_mode_hint(); #else return valid; #endif diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 36c8a40ed9a6..47db5097682f 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -2291,7 +2291,7 @@ void CSharpScript::update_script_class_info(Ref p_script) { bool CSharpScript::can_instantiate() const { #ifdef TOOLS_ENABLED - bool extra_cond = type_info.is_tool || ScriptServer::is_scripting_enabled(); + bool extra_cond = (type_info.is_tool || ScriptServer::is_scripting_enabled()) && !Engine::get_singleton()->is_safe_mode_hint(); #else bool extra_cond = true; #endif diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 49913b9c30ad..62c62818de7d 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -105,7 +105,7 @@ String GodotIOJavaWrapper::get_cache_dir() { } } -String GodotIOJavaWrapper::get_user_data_dir() { +String GodotIOJavaWrapper::get_user_data_dir(const String &p_appname) { if (_get_data_dir) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, String()); diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index c113a13040d9..94390422c05e 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -70,7 +70,7 @@ class GodotIOJavaWrapper { Error open_uri(const String &p_uri); String get_cache_dir(); - String get_user_data_dir(); + String get_user_data_dir(const String &p_appname); String get_locale(); String get_model(); int get_screen_dpi(); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 764959eef358..a965455baf04 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -413,7 +413,7 @@ String OS_Android::get_model_name() const { } String OS_Android::get_data_path() const { - return get_user_data_dir(); + return OS::get_user_data_dir(); } void OS_Android::_load_system_font_config() { @@ -647,12 +647,12 @@ String OS_Android::get_executable_path() const { return OS::get_executable_path(); } -String OS_Android::get_user_data_dir() const { +String OS_Android::get_user_data_dir(const String &p_appname) const { if (!data_dir_cache.is_empty()) { return data_dir_cache; } - String data_dir = godot_io_java->get_user_data_dir(); + String data_dir = godot_io_java->get_user_data_dir(p_appname); if (!data_dir.is_empty()) { data_dir_cache = _remove_symlink(data_dir); return data_dir_cache; @@ -751,7 +751,7 @@ void OS_Android::vibrate_handheld(int p_duration_ms, float p_amplitude) { } String OS_Android::get_config_path() const { - return get_user_data_dir().path_join("config"); + return OS::get_user_data_dir().path_join("config"); } void OS_Android::benchmark_begin_measure(const String &p_context, const String &p_what) { @@ -871,7 +871,7 @@ String OS_Android::get_system_ca_certificates() { } Error OS_Android::setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) { - r_project_path = get_user_data_dir(); + r_project_path = OS::get_user_data_dir(); Error err = OS_Unix::setup_remote_filesystem(p_server_host, p_port, p_password, r_project_path); if (err == OK) { remote_fs_dir = r_project_path; diff --git a/platform/android/os_android.h b/platform/android/os_android.h index b150ef4f6161..16851cb46f9d 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -140,7 +140,7 @@ class OS_Android : public OS_Unix { virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual Vector get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual String get_executable_path() const override; - virtual String get_user_data_dir() const override; + virtual String get_user_data_dir(const String &p_appname) const override; virtual String get_data_path() const override; virtual String get_cache_path() const override; virtual String get_resource_dir() const override; diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index b7c5a7306560..63dd7a51263f 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -114,7 +114,7 @@ class OS_IOS : public OS_Unix { virtual Error shell_open(const String &p_uri) override; - virtual String get_user_data_dir() const override; + virtual String get_user_data_dir(const String &p_appname) const override; virtual String get_cache_path() const override; diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index 35b87ea64703..9c64aa3fa701 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -318,7 +318,7 @@ void register_dynamic_symbol(char *name, void *address) { return OK; } -String OS_IOS::get_user_data_dir() const { +String OS_IOS::get_user_data_dir(const String &p_appname) const { static String ret; if (ret.is_empty()) { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index ef8f90421ba0..311bd2b9f9ec 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -178,19 +178,18 @@ void OS_Web::vibrate_handheld(int p_duration_ms, float p_amplitude) { godot_js_input_vibrate_handheld(p_duration_ms); } -String OS_Web::get_user_data_dir() const { +String OS_Web::get_user_data_dir(const String &p_appname) const { String userfs = "/userfs"; - String appname = get_safe_dir_name(GLOBAL_GET("application/config/name")); - if (!appname.is_empty()) { + if (!p_appname.is_empty()) { bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir"); if (use_custom_dir) { String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true); if (custom_dir.is_empty()) { - custom_dir = appname; + custom_dir = p_appname; } return userfs.path_join(custom_dir).replace("\\", "/"); } else { - return userfs.path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname).replace("\\", "/"); + return userfs.path_join(get_godot_dir_name()).path_join("app_userdata").path_join(p_appname).replace("\\", "/"); } } diff --git a/platform/web/os_web.h b/platform/web/os_web.h index 55a5fcc6c62f..de1c7e7a47ae 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -103,7 +103,7 @@ class OS_Web : public OS_Unix { String get_cache_path() const override; String get_config_path() const override; String get_data_path() const override; - String get_user_data_dir() const override; + String get_user_data_dir(const String &p_appname) const override; bool is_userfs_persistent() const override; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 9025f53f42b2..13de29f16b13 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1797,18 +1797,17 @@ String OS_Windows::get_system_dir(SystemDir p_dir, bool p_shared_storage) const return path; } -String OS_Windows::get_user_data_dir() const { - String appname = get_safe_dir_name(GLOBAL_GET("application/config/name")); - if (!appname.is_empty()) { +String OS_Windows::get_user_data_dir(const String &p_appname) const { + if (!p_appname.is_empty()) { bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir"); if (use_custom_dir) { String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true); if (custom_dir.is_empty()) { - custom_dir = appname; + custom_dir = p_appname; } return get_data_path().path_join(custom_dir).replace("\\", "/"); } else { - return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname).replace("\\", "/"); + return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(p_appname).replace("\\", "/"); } } diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index b6a21ed42dde..8ea3aa645710 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -217,7 +217,7 @@ class OS_Windows : public OS { virtual String get_godot_dir_name() const override; virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; - virtual String get_user_data_dir() const override; + virtual String get_user_data_dir(const String &p_appname) const override; virtual String get_unique_id() const override;