From d17ce4c37efccd6c1be89a66ff4dc14c60ff0111 Mon Sep 17 00:00:00 2001 From: Rie Date: Tue, 9 Apr 2024 10:31:47 +0200 Subject: [PATCH] Fix "res://" being replaced by resource packs in the editor and on Android --- core/config/project_settings.cpp | 41 +++++++++++++++++++++++--------- core/config/project_settings.h | 3 ++- core/io/file_access_pack.cpp | 38 +++++++++++++++++++++++++++++ core/io/file_access_pack.h | 8 +++++++ doc/classes/ProjectSettings.xml | 1 + 5 files changed, 79 insertions(+), 12 deletions(-) diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index eb8e80a31d89..fdcd4f9b6064 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -472,13 +472,30 @@ void ProjectSettings::_emit_changed() { emit_signal("settings_changed"); } -bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) { +bool ProjectSettings::load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) { + return ProjectSettings::_load_resource_pack(p_pack, p_replace_files, p_offset, false); +} + +bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset, bool p_main_pack) { if (PackedData::get_singleton()->is_disabled()) { return false; } - bool ok = PackedData::get_singleton()->add_pack(p_pack, p_replace_files, p_offset) == OK; + if (p_pack == "res://") { + // Loading the resource directory as a pack source is reserved for internal use only. + return false; + } + if (!p_main_pack && !using_datapack && !OS::get_singleton()->get_resource_dir().is_empty()) { + // Add the project's resource file system to PackedData so directory access keeps working when + // the game is running without a main pack, like in the editor or on Android. + PackedData::get_singleton()->add_pack_source(memnew(PackedSourceDirectory)); + PackedData::get_singleton()->add_pack("res://", false, 0); + DirAccess::make_default(DirAccess::ACCESS_RESOURCES); + using_datapack = true; + } + + bool ok = PackedData::get_singleton()->add_pack(p_pack, p_replace_files, p_offset) == OK; if (!ok) { return false; } @@ -491,9 +508,11 @@ bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_f ResourceUID::get_singleton()->load_from_cache(false); } - //if data.pck is found, all directory access will be from here - DirAccess::make_default(DirAccess::ACCESS_RESOURCES); - using_datapack = true; + // If the data pack was found, all directory access will be from here. + if (!using_datapack) { + DirAccess::make_default(DirAccess::ACCESS_RESOURCES); + using_datapack = true; + } return true; } @@ -572,7 +591,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b // Attempt with a user-defined main pack first if (!p_main_pack.is_empty()) { - bool ok = _load_resource_pack(p_main_pack); + bool ok = _load_resource_pack(p_main_pack, false, 0, true); ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, vformat("Cannot open resource pack '%s'.", p_main_pack)); Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary"); @@ -591,7 +610,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b // and if so, we attempt loading it at the end. // Attempt with PCK bundled into executable. - bool found = _load_resource_pack(exec_path); + bool found = _load_resource_pack(exec_path, false, 0, true); // Attempt with exec_name.pck. // (This is the usual case when distributing a Godot game.) @@ -607,20 +626,20 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b #ifdef MACOS_ENABLED if (!found) { // Attempt to load PCK from macOS .app bundle resources. - found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_basename + ".pck")) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_filename + ".pck")); + found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_basename + ".pck"), false, 0, true) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_filename + ".pck"), false, 0, true); } #endif if (!found) { // Try to load data pack at the location of the executable. // As mentioned above, we have two potential names to attempt. - found = _load_resource_pack(exec_dir.path_join(exec_basename + ".pck")) || _load_resource_pack(exec_dir.path_join(exec_filename + ".pck")); + found = _load_resource_pack(exec_dir.path_join(exec_basename + ".pck"), false, 0, true) || _load_resource_pack(exec_dir.path_join(exec_filename + ".pck"), false, 0, true); } if (!found) { // If we couldn't find them next to the executable, we attempt // the current working directory. Same story, two tests. - found = _load_resource_pack(exec_basename + ".pck") || _load_resource_pack(exec_filename + ".pck"); + found = _load_resource_pack(exec_basename + ".pck", false, 0, true) || _load_resource_pack(exec_filename + ".pck", false, 0, true); } // If we opened our package, try and load our project. @@ -1418,7 +1437,7 @@ void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("localize_path", "path"), &ProjectSettings::localize_path); ClassDB::bind_method(D_METHOD("globalize_path", "path"), &ProjectSettings::globalize_path); ClassDB::bind_method(D_METHOD("save"), &ProjectSettings::save); - ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::_load_resource_pack, DEFVAL(true), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::load_resource_pack, DEFVAL(true), DEFVAL(0)); ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index 72877526812c..02049290f6b2 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -135,7 +135,8 @@ class ProjectSettings : public Object { void _convert_to_last_version(int p_from_version); - bool _load_resource_pack(const String &p_pack, bool p_replace_files = true, int p_offset = 0); + bool load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset); + bool _load_resource_pack(const String &p_pack, bool p_replace_files = true, int p_offset = 0, bool p_main_pack = false); void _add_property_info_bind(const Dictionary &p_info); diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 1a0e0b06cc91..3fba2c70679a 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -331,6 +331,44 @@ Ref PackedSourcePCK::get_file(const String &p_path, PackedData::Pack ////////////////////////////////////////////////////////////////// +bool PackedSourceDirectory::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) { + // Load with offset feature only supported for PCK files. + ERR_FAIL_COND_V_MSG(p_offset != 0, false, "Invalid PCK data. Note that loading files with a non-zero offset isn't supported with directories."); + + if (p_path != "res://") { + return false; + } + add_directory(p_path, p_replace_files); + return true; +} + +Ref PackedSourceDirectory::get_file(const String &p_path, PackedData::PackedFile *p_file) { + Ref ret = FileAccess::create_for_path(p_path); + ret->reopen(p_path, FileAccess::READ); + return ret; +} + +void PackedSourceDirectory::add_directory(const String &p_path, bool p_replace_files) { + Ref da = DirAccess::open(p_path); + if (da.is_null()) { + return; + } + da->set_include_hidden(true); + + for (const String &file_name : da->get_files()) { + String file_path = p_path.path_join(file_name); + uint8_t md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + PackedData::get_singleton()->add_path(p_path, file_path, 0, 0, md5, this, p_replace_files, false); + } + + for (const String &sub_dir_name : da->get_directories()) { + String sub_dir_path = p_path.path_join(sub_dir_name); + add_directory(sub_dir_path, p_replace_files); + } +} + +////////////////////////////////////////////////////////////////// + Error FileAccessPack::open_internal(const String &p_path, int p_mode_flags) { ERR_PRINT("Can't open pack-referenced file."); return ERR_UNAVAILABLE; diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 80d69d7b541d..0e4e7cd2893a 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -147,6 +147,14 @@ class PackedSourcePCK : public PackSource { virtual Ref get_file(const String &p_path, PackedData::PackedFile *p_file) override; }; +class PackedSourceDirectory : public PackSource { + void add_directory(const String &p_path, bool p_replace_files); + +public: + virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) override; + virtual Ref get_file(const String &p_path, PackedData::PackedFile *p_file) override; +}; + class FileAccessPack : public FileAccess { PackedData::PackedFile pf; diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 742dcdabc1c6..1aece88f1ddc 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -151,6 +151,7 @@ Loads the contents of the .pck or .zip file specified by [param pack] into the resource filesystem ([code]res://[/code]). Returns [code]true[/code] on success. [b]Note:[/b] If a file from [param pack] shares the same path as a file already in the resource filesystem, any attempts to load that file will use the file from [param pack] unless [param replace_files] is set to [code]false[/code]. [b]Note:[/b] The optional [param offset] parameter can be used to specify the offset in bytes to the start of the resource pack. This is only supported for .pck files. + [b]Note:[/b] [DirAccess] will not show changes made to the contents of [code]res://[/code] after calling this function.