diff options
Diffstat (limited to 'core/io')
-rw-r--r-- | core/io/dir_access.cpp | 14 | ||||
-rw-r--r-- | core/io/dir_access.h | 6 | ||||
-rw-r--r-- | core/io/file_access.h | 4 | ||||
-rw-r--r-- | core/io/image.cpp | 8 | ||||
-rw-r--r-- | core/io/image.h | 16 | ||||
-rw-r--r-- | core/io/ip.cpp | 8 | ||||
-rw-r--r-- | core/io/ip.h | 2 | ||||
-rw-r--r-- | core/io/logger.cpp | 10 | ||||
-rw-r--r-- | core/io/resource.cpp | 20 | ||||
-rw-r--r-- | core/io/resource_format_binary.cpp | 22 | ||||
-rw-r--r-- | core/io/translation_loader_po.cpp | 439 |
11 files changed, 340 insertions, 209 deletions
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 86d8dea3d9..73efdeb38e 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -250,6 +250,14 @@ DirAccess *DirAccess::create(AccessType p_access) { DirAccess *da = create_func[p_access] ? create_func[p_access]() : nullptr; if (da) { da->_access_type = p_access; + + // for ACCESS_RESOURCES and ACCESS_FILESYSTEM, current_dir already defaults to where game was started + // in case current directory is force changed elsewhere for ACCESS_RESOURCES + if (p_access == ACCESS_RESOURCES) { + da->change_dir("res://"); + } else if (p_access == ACCESS_USERDATA) { + da->change_dir("user://"); + } } return da; @@ -414,8 +422,6 @@ Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags, bool p_ } bool DirAccess::exists(String p_dir) { - DirAccess *da = DirAccess::create_for_path(p_dir); - bool valid = da->change_dir(p_dir) == OK; - memdelete(da); - return valid; + DirAccessRef da = DirAccess::create_for_path(p_dir); + return da->change_dir(p_dir) == OK; } diff --git a/core/io/dir_access.h b/core/io/dir_access.h index 8154f5366c..b97d097842 100644 --- a/core/io/dir_access.h +++ b/core/io/dir_access.h @@ -134,9 +134,13 @@ struct DirAccessRef { operator bool() const { return f != nullptr; } - DirAccess *f; + DirAccess *f = nullptr; DirAccessRef(DirAccess *fa) { f = fa; } + DirAccessRef(DirAccessRef &&other) { + f = other.f; + other.f = nullptr; + } ~DirAccessRef() { if (f) { memdelete(f); diff --git a/core/io/file_access.h b/core/io/file_access.h index 5413665440..a6cb5d9fc6 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -188,6 +188,10 @@ struct FileAccessRef { operator FileAccess *() { return f; } FileAccessRef(FileAccess *fa) { f = fa; } + FileAccessRef(FileAccessRef &&other) { + f = other.f; + other.f = nullptr; + } ~FileAccessRef() { if (f) { memdelete(f); diff --git a/core/io/image.cpp b/core/io/image.cpp index 577fc59807..5376b78a89 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -1465,8 +1465,8 @@ template <class Component, int CC, bool renormalize, void (*renormalize_func)(Component *)> static void _generate_po2_mipmap(const Component *p_src, Component *p_dst, uint32_t p_width, uint32_t p_height) { //fast power of 2 mipmap generation - uint32_t dst_w = MAX(p_width >> 1, 1); - uint32_t dst_h = MAX(p_height >> 1, 1); + uint32_t dst_w = MAX(p_width >> 1, 1u); + uint32_t dst_h = MAX(p_height >> 1, 1u); int right_step = (p_width == 1) ? 0 : CC; int down_step = (p_height == 1) ? 0 : (p_width * CC); @@ -3112,8 +3112,8 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_mipmaps", "renormalize"), &Image::generate_mipmaps, DEFVAL(false)); ClassDB::bind_method(D_METHOD("clear_mipmaps"), &Image::clear_mipmaps); - ClassDB::bind_method(D_METHOD("create", "width", "height", "use_mipmaps", "format"), &Image::_create_empty); - ClassDB::bind_method(D_METHOD("create_from_data", "width", "height", "use_mipmaps", "format", "data"), &Image::_create_from_data); + ClassDB::bind_method(D_METHOD("create", "width", "height", "use_mipmaps", "format"), &Image::create_empty); + ClassDB::bind_method(D_METHOD("create_from_data", "width", "height", "use_mipmaps", "format", "data"), &Image::create_from_data); ClassDB::bind_method(D_METHOD("is_empty"), &Image::is_empty); diff --git a/core/io/image.h b/core/io/image.h index 53bfa0881f..39c700565b 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -155,14 +155,6 @@ protected: static void _bind_methods(); private: - void _create_empty(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { - create(p_width, p_height, p_use_mipmaps, p_format); - } - - void _create_from_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data) { - create(p_width, p_height, p_use_mipmaps, p_format, p_data); - } - Format format = FORMAT_L8; Vector<uint8_t> data; int width = 0; @@ -289,6 +281,14 @@ public: Vector<uint8_t> save_png_to_buffer() const; Error save_exr(const String &p_path, bool p_grayscale) const; + void create_empty(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { + create(p_width, p_height, p_use_mipmaps, p_format); + } + + void create_from_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data) { + create(p_width, p_height, p_use_mipmaps, p_format, p_data); + } + /** * create an empty image */ diff --git a/core/io/ip.cpp b/core/io/ip.cpp index 8e0d47e762..2f88307d94 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -186,7 +186,7 @@ IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Typ } IP::ResolverStatus IP::get_resolve_item_status(ResolverID p_id) const { - ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, IP::RESOLVER_STATUS_NONE); + ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, IP::RESOLVER_STATUS_NONE, vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES)); IP::ResolverStatus res = resolver->queue[p_id].status.get(); if (res == IP::RESOLVER_STATUS_NONE) { @@ -197,7 +197,7 @@ IP::ResolverStatus IP::get_resolve_item_status(ResolverID p_id) const { } IPAddress IP::get_resolve_item_address(ResolverID p_id) const { - ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, IPAddress()); + ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, IPAddress(), vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES)); MutexLock lock(resolver->mutex); @@ -217,7 +217,7 @@ IPAddress IP::get_resolve_item_address(ResolverID p_id) const { } Array IP::get_resolve_item_addresses(ResolverID p_id) const { - ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, Array()); + ERR_FAIL_INDEX_V_MSG(p_id, IP::RESOLVER_MAX_QUERIES, Array(), vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES)); MutexLock lock(resolver->mutex); if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) { @@ -237,7 +237,7 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const { } void IP::erase_resolve_item(ResolverID p_id) { - ERR_FAIL_INDEX(p_id, IP::RESOLVER_MAX_QUERIES); + ERR_FAIL_INDEX_MSG(p_id, IP::RESOLVER_MAX_QUERIES, vformat("Too many concurrent DNS resolver queries (%d, but should be %d at most). Try performing less network requests at once.", p_id, IP::RESOLVER_MAX_QUERIES)); resolver->queue[p_id].status.set(IP::RESOLVER_STATUS_NONE); } diff --git a/core/io/ip.h b/core/io/ip.h index 5602710550..ab00b7b0a6 100644 --- a/core/io/ip.h +++ b/core/io/ip.h @@ -56,7 +56,7 @@ public: }; enum { - RESOLVER_MAX_QUERIES = 32, + RESOLVER_MAX_QUERIES = 256, RESOLVER_INVALID_ID = -1 }; diff --git a/core/io/logger.cpp b/core/io/logger.cpp index cb6369ae3d..2b6f230434 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -128,7 +128,7 @@ void RotatedFileLogger::clear_old_backups() { String basename = base_path.get_file().get_basename(); String extension = base_path.get_extension(); - DirAccess *da = DirAccess::open(base_path.get_base_dir()); + DirAccessRef da = DirAccess::open(base_path.get_base_dir()); if (!da) { return; } @@ -152,8 +152,6 @@ void RotatedFileLogger::clear_old_backups() { da->remove(E->get()); } } - - memdelete(da); } void RotatedFileLogger::rotate_file() { @@ -167,18 +165,16 @@ void RotatedFileLogger::rotate_file() { backup_name += "." + base_path.get_extension(); } - DirAccess *da = DirAccess::open(base_path.get_base_dir()); + DirAccessRef da = DirAccess::open(base_path.get_base_dir()); if (da) { da->copy(base_path, backup_name); - memdelete(da); } clear_old_backups(); } } else { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_USERDATA); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_USERDATA); if (da) { da->make_dir_recursive(base_path.get_base_dir()); - memdelete(da); } } diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 66d5c54b53..f90a6e9304 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -290,6 +290,21 @@ void Resource::_take_over_path(const String &p_path) { } RID Resource::get_rid() const { + if (get_script_instance()) { + Callable::CallError ce; + RID ret = get_script_instance()->callp(SNAME("_get_rid"), nullptr, 0, ce); + if (ce.error == Callable::CallError::CALL_OK && ret.is_valid()) { + return ret; + } + } + if (_get_extension() && _get_extension()->get_rid) { + RID ret; + ret.from_uint64(_get_extension()->get_rid(_get_extension_instance())); + if (ret.is_valid()) { + return ret; + } + } + return RID(); } @@ -428,6 +443,11 @@ void Resource::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resource_local_to_scene"), "set_local_to_scene", "is_local_to_scene"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_path", "get_path"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "resource_name"), "set_name", "get_name"); + + MethodInfo get_rid_bind("_get_rid"); + get_rid_bind.return_val.type = Variant::RID; + + ::ClassDB::add_virtual_method(get_class_static(), get_rid_bind, true, Vector<String>(), true); } Resource::Resource() : diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index ee59a916f1..b65993e3dd 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -1032,7 +1032,6 @@ RES ResourceFormatLoaderBinary::load(const String &p_path, const String &p_origi String path = !p_original_path.is_empty() ? p_original_path : p_path; loader.local_path = ProjectSettings::get_singleton()->localize_path(path); loader.res_path = loader.local_path; - //loader.set_local_path( Globals::get_singleton()->localize_path(p_path) ); loader.open(f); err = loader.load(); @@ -1086,17 +1085,14 @@ void ResourceFormatLoaderBinary::get_dependencies(const String &p_path, List<Str ResourceLoaderBinary loader; loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); loader.res_path = loader.local_path; - //loader.set_local_path( Globals::get_singleton()->localize_path(p_path) ); loader.get_dependencies(f, p_dependencies, p_add_types); } Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, const Map<String, String> &p_map) { - //Error error=OK; - FileAccess *f = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, "Cannot open file '" + p_path + "'."); - FileAccess *fw = nullptr; //=FileAccess::open(p_path+".depren"); + FileAccess *fw = nullptr; String local_path = p_path.get_base_dir(); @@ -1158,10 +1154,12 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons if (ver_format < FORMAT_VERSION_CAN_RENAME_DEPS) { memdelete(f); memdelete(fw); - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - da->remove(p_path + ".depren"); - memdelete(da); - //use the old approach + { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + da->remove(p_path + ".depren"); + } + + // Use the old approach. WARN_PRINT("This file is old, so it can't refactor dependencies, opening and resaving '" + p_path + "'."); @@ -1174,7 +1172,6 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); loader.res_path = loader.local_path; loader.remaps = p_map; - //loader.set_local_path( Globals::get_singleton()->localize_path(p_path) ); loader.open(f); err = loader.load(); @@ -1304,10 +1301,9 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons return ERR_CANT_CREATE; } - DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); da->remove(p_path); da->rename(p_path + ".depren", p_path); - memdelete(da); return OK; } @@ -1320,7 +1316,6 @@ String ResourceFormatLoaderBinary::get_resource_type(const String &p_path) const ResourceLoaderBinary loader; loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); loader.res_path = loader.local_path; - //loader.set_local_path( Globals::get_singleton()->localize_path(p_path) ); String r = loader.recognize(f); return ClassDB::get_compatibility_remapped_class(r); } @@ -1339,7 +1334,6 @@ ResourceUID::ID ResourceFormatLoaderBinary::get_resource_uid(const String &p_pat ResourceLoaderBinary loader; loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); loader.res_path = loader.local_path; - //loader.set_local_path( Globals::get_singleton()->localize_path(p_path) ); loader.open(f, true); if (loader.error != OK) { return ResourceUID::INVALID_ID; //could not read diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp index 8d3e58cad1..801bd8b0bf 100644 --- a/core/io/translation_loader_po.cpp +++ b/core/io/translation_loader_po.cpp @@ -35,98 +35,160 @@ #include "core/string/translation_po.h" RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { - enum Status { - STATUS_NONE, - STATUS_READING_ID, - STATUS_READING_STRING, - STATUS_READING_CONTEXT, - STATUS_READING_PLURAL, - }; - - Status status = STATUS_NONE; - - String msg_id; - String msg_str; - String msg_context; - Vector<String> msgs_plural; - String config; - if (r_error) { *r_error = ERR_FILE_CORRUPT; } - Ref<TranslationPO> translation = Ref<TranslationPO>(memnew(TranslationPO)); - int line = 1; - int plural_forms = 0; - int plural_index = -1; - bool entered_context = false; - bool skip_this = false; - bool skip_next = false; - bool is_eof = false; const String path = f->get_path(); + Ref<TranslationPO> translation = Ref<TranslationPO>(memnew(TranslationPO)); + String config; - while (!is_eof) { - String l = f->get_line().strip_edges(); - is_eof = f->eof_reached(); + uint32_t magic = f->get_32(); + if (magic == 0x950412de) { + // Load binary MO file. - // If we reached last line and it's not a content line, break, otherwise let processing that last loop - if (is_eof && l.is_empty()) { - if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || (status == STATUS_READING_PLURAL && plural_index != plural_forms - 1)) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Unexpected EOF while reading PO file at: " + path + ":" + itos(line)); - } else { - break; - } + uint16_t version_maj = f->get_16(); + uint16_t version_min = f->get_16(); + if (version_maj > 1) { + ERR_FAIL_V_MSG(RES(), vformat("Unsupported MO file %s, version %d.%d.", path, version_maj, version_min)); } - if (l.begins_with("msgctxt")) { - if (status != STATUS_READING_STRING && status != STATUS_READING_PLURAL) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: " + path + ":" + itos(line)); + uint32_t num_strings = f->get_32(); + uint32_t id_table_offset = f->get_32(); + uint32_t trans_table_offset = f->get_32(); + + // Read string tables. + for (uint32_t i = 0; i < num_strings; i++) { + String msg_id; + String msg_id_plural; + String msg_context; + + // Read id strings and context. + { + Vector<uint8_t> data; + f->seek(id_table_offset + i * 8); + uint32_t str_start = 0; + uint32_t str_len = f->get_32(); + uint32_t str_offset = f->get_32(); + + data.resize(str_len + 1); + f->seek(str_offset); + f->get_buffer(data.ptrw(), str_len); + data.write[str_len] = 0; + + bool is_plural = false; + for (uint32_t j = 0; j < str_len + 1; j++) { + if (data[j] == 0x04) { + msg_context.parse_utf8((const char *)data.ptr(), j); + str_start = j + 1; + } + if (data[j] == 0x00) { + if (is_plural) { + msg_id_plural.parse_utf8((const char *)(data.ptr() + str_start), j - str_start); + } else { + msg_id.parse_utf8((const char *)(data.ptr() + str_start), j - str_start); + is_plural = true; + } + str_start = j + 1; + } + } } - // In PO file, "msgctxt" appears before "msgid". If we encounter a "msgctxt", we add what we have read - // and set "entered_context" to true to prevent adding twice. - if (!skip_this && !msg_id.is_empty()) { - if (status == STATUS_READING_STRING) { - translation->add_message(msg_id, msg_str, msg_context); - } else if (status == STATUS_READING_PLURAL) { - if (plural_index != plural_forms - 1) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line)); + // Read translated strings. + { + Vector<uint8_t> data; + f->seek(trans_table_offset + i * 8); + uint32_t str_start = 0; + uint32_t str_len = f->get_32(); + uint32_t str_offset = f->get_32(); + + data.resize(str_len + 1); + f->seek(str_offset); + f->get_buffer(data.ptrw(), str_len); + data.write[str_len] = 0; + + if (msg_id.is_empty()) { + config = String::utf8((const char *)data.ptr(), str_len); + // Record plural rule. + int p_start = config.find("Plural-Forms"); + if (p_start != -1) { + int p_end = config.find("\n", p_start); + translation->set_plural_rule(config.substr(p_start, p_end - p_start)); + } + } else { + Vector<String> plural_msg; + for (uint32_t j = 0; j < str_len + 1; j++) { + if (data[j] == 0x00) { + if (msg_id_plural.is_empty()) { + translation->add_message(msg_id, String::utf8((const char *)(data.ptr() + str_start), j - str_start), msg_context); + } else { + plural_msg.push_back(String::utf8((const char *)(data.ptr() + str_start), j - str_start)); + } + str_start = j + 1; + } + } + if (!plural_msg.is_empty()) { + translation->add_plural_message(msg_id, plural_msg, msg_context); } - translation->add_plural_message(msg_id, msgs_plural, msg_context); } } - msg_context = ""; - l = l.substr(7, l.length()).strip_edges(); - status = STATUS_READING_CONTEXT; - entered_context = true; } - if (l.begins_with("msgid_plural")) { - if (plural_forms == 0) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: " + path + ":" + itos(line)); - } else if (status != STATUS_READING_ID) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: " + path + ":" + itos(line)); - } - // We don't record the message in "msgid_plural" itself as tr_n(), TTRN(), RTRN() interfaces provide the plural string already. - // We just have to reset variables related to plurals for "msgstr[]" later on. - l = l.substr(12, l.length()).strip_edges(); - plural_index = -1; - msgs_plural.clear(); - msgs_plural.resize(plural_forms); - status = STATUS_READING_PLURAL; - } else if (l.begins_with("msgid")) { - if (status == STATUS_READING_ID) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Unexpected 'msgid', was expecting 'msgstr' while parsing: " + path + ":" + itos(line)); + memdelete(f); + } else { + // Try to load as text PO file. + f->seek(0); + + enum Status { + STATUS_NONE, + STATUS_READING_ID, + STATUS_READING_STRING, + STATUS_READING_CONTEXT, + STATUS_READING_PLURAL, + }; + + Status status = STATUS_NONE; + + String msg_id; + String msg_str; + String msg_context; + Vector<String> msgs_plural; + + if (r_error) { + *r_error = ERR_FILE_CORRUPT; + } + + int line = 1; + int plural_forms = 0; + int plural_index = -1; + bool entered_context = false; + bool skip_this = false; + bool skip_next = false; + bool is_eof = false; + + while (!is_eof) { + String l = f->get_line().strip_edges(); + is_eof = f->eof_reached(); + + // If we reached last line and it's not a content line, break, otherwise let processing that last loop + if (is_eof && l.is_empty()) { + if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || (status == STATUS_READING_PLURAL && plural_index != plural_forms - 1)) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Unexpected EOF while reading PO file at: " + path + ":" + itos(line)); + } else { + break; + } } - if (!msg_id.is_empty()) { - if (!skip_this && !entered_context) { + if (l.begins_with("msgctxt")) { + if (status != STATUS_READING_STRING && status != STATUS_READING_PLURAL) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: " + path + ":" + itos(line)); + } + + // In PO file, "msgctxt" appears before "msgid". If we encounter a "msgctxt", we add what we have read + // and set "entered_context" to true to prevent adding twice. + if (!skip_this && !msg_id.is_empty()) { if (status == STATUS_READING_STRING) { translation->add_message(msg_id, msg_str, msg_context); } else if (status == STATUS_READING_PLURAL) { @@ -137,119 +199,163 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { translation->add_plural_message(msg_id, msgs_plural, msg_context); } } - } else if (config.is_empty()) { - config = msg_str; - // Record plural rule. - int p_start = config.find("Plural-Forms"); - if (p_start != -1) { - int p_end = config.find("\n", p_start); - translation->set_plural_rule(config.substr(p_start, p_end - p_start)); - plural_forms = translation->get_plural_forms(); + msg_context = ""; + l = l.substr(7, l.length()).strip_edges(); + status = STATUS_READING_CONTEXT; + entered_context = true; + } + + if (l.begins_with("msgid_plural")) { + if (plural_forms == 0) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: " + path + ":" + itos(line)); + } else if (status != STATUS_READING_ID) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: " + path + ":" + itos(line)); + } + // We don't record the message in "msgid_plural" itself as tr_n(), TTRN(), RTRN() interfaces provide the plural string already. + // We just have to reset variables related to plurals for "msgstr[]" later on. + l = l.substr(12, l.length()).strip_edges(); + plural_index = -1; + msgs_plural.clear(); + msgs_plural.resize(plural_forms); + status = STATUS_READING_PLURAL; + } else if (l.begins_with("msgid")) { + if (status == STATUS_READING_ID) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Unexpected 'msgid', was expecting 'msgstr' while parsing: " + path + ":" + itos(line)); } + + if (!msg_id.is_empty()) { + if (!skip_this && !entered_context) { + if (status == STATUS_READING_STRING) { + translation->add_message(msg_id, msg_str, msg_context); + } else if (status == STATUS_READING_PLURAL) { + if (plural_index != plural_forms - 1) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line)); + } + translation->add_plural_message(msg_id, msgs_plural, msg_context); + } + } + } else if (config.is_empty()) { + config = msg_str; + // Record plural rule. + int p_start = config.find("Plural-Forms"); + if (p_start != -1) { + int p_end = config.find("\n", p_start); + translation->set_plural_rule(config.substr(p_start, p_end - p_start)); + plural_forms = translation->get_plural_forms(); + } + } + + l = l.substr(5, l.length()).strip_edges(); + status = STATUS_READING_ID; + // If we did not encounter msgctxt, we reset context to empty to reset it. + if (!entered_context) { + msg_context = ""; + } + msg_id = ""; + msg_str = ""; + skip_this = skip_next; + skip_next = false; + entered_context = false; } - l = l.substr(5, l.length()).strip_edges(); - status = STATUS_READING_ID; - // If we did not encounter msgctxt, we reset context to empty to reset it. - if (!entered_context) { - msg_context = ""; + if (l.begins_with("msgstr[")) { + if (status != STATUS_READING_PLURAL) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: " + path + ":" + itos(line)); + } + plural_index++; // Increment to add to the next slot in vector msgs_plural. + l = l.substr(9, l.length()).strip_edges(); + } else if (l.begins_with("msgstr")) { + if (status != STATUS_READING_ID) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: " + path + ":" + itos(line)); + } + + l = l.substr(6, l.length()).strip_edges(); + status = STATUS_READING_STRING; } - msg_id = ""; - msg_str = ""; - skip_this = skip_next; - skip_next = false; - entered_context = false; - } - if (l.begins_with("msgstr[")) { - if (status != STATUS_READING_PLURAL) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: " + path + ":" + itos(line)); + if (l.is_empty() || l.begins_with("#")) { + if (l.contains("fuzzy")) { + skip_next = true; + } + line++; + continue; // Nothing to read or comment. } - plural_index++; // Increment to add to the next slot in vector msgs_plural. - l = l.substr(9, l.length()).strip_edges(); - } else if (l.begins_with("msgstr")) { - if (status != STATUS_READING_ID) { + + if (!l.begins_with("\"") || status == STATUS_NONE) { memdelete(f); - ERR_FAIL_V_MSG(RES(), "Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: " + path + ":" + itos(line)); + ERR_FAIL_V_MSG(RES(), "Invalid line '" + l + "' while parsing: " + path + ":" + itos(line)); } - l = l.substr(6, l.length()).strip_edges(); - status = STATUS_READING_STRING; - } - - if (l.is_empty() || l.begins_with("#")) { - if (l.contains("fuzzy")) { - skip_next = true; - } - line++; - continue; // Nothing to read or comment. - } + l = l.substr(1, l.length()); + // Find final quote, ignoring escaped ones (\"). + // The escape_next logic is necessary to properly parse things like \\" + // where the backslash is the one being escaped, not the quote. + int end_pos = -1; + bool escape_next = false; + for (int i = 0; i < l.length(); i++) { + if (l[i] == '\\' && !escape_next) { + escape_next = true; + continue; + } - if (!l.begins_with("\"") || status == STATUS_NONE) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Invalid line '" + l + "' while parsing: " + path + ":" + itos(line)); - } + if (l[i] == '"' && !escape_next) { + end_pos = i; + break; + } - l = l.substr(1, l.length()); - // Find final quote, ignoring escaped ones (\"). - // The escape_next logic is necessary to properly parse things like \\" - // where the backslash is the one being escaped, not the quote. - int end_pos = -1; - bool escape_next = false; - for (int i = 0; i < l.length(); i++) { - if (l[i] == '\\' && !escape_next) { - escape_next = true; - continue; + escape_next = false; } - if (l[i] == '"' && !escape_next) { - end_pos = i; - break; + if (end_pos == -1) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Expected '\"' at end of message while parsing: " + path + ":" + itos(line)); } - escape_next = false; - } + l = l.substr(0, end_pos); + l = l.c_unescape(); - if (end_pos == -1) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Expected '\"' at end of message while parsing: " + path + ":" + itos(line)); - } + if (status == STATUS_READING_ID) { + msg_id += l; + } else if (status == STATUS_READING_STRING) { + msg_str += l; + } else if (status == STATUS_READING_CONTEXT) { + msg_context += l; + } else if (status == STATUS_READING_PLURAL && plural_index >= 0) { + if (plural_index >= plural_forms) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Unexpected plural form while parsing: " + path + ":" + itos(line)); + } + msgs_plural.write[plural_index] = msgs_plural[plural_index] + l; + } - l = l.substr(0, end_pos); - l = l.c_unescape(); - - if (status == STATUS_READING_ID) { - msg_id += l; - } else if (status == STATUS_READING_STRING) { - msg_str += l; - } else if (status == STATUS_READING_CONTEXT) { - msg_context += l; - } else if (status == STATUS_READING_PLURAL && plural_index >= 0) { - msgs_plural.write[plural_index] = msgs_plural[plural_index] + l; + line++; } - line++; - } - - memdelete(f); + memdelete(f); - // Add the last set of data from last iteration. - if (status == STATUS_READING_STRING) { - if (!msg_id.is_empty()) { - if (!skip_this) { - translation->add_message(msg_id, msg_str, msg_context); + // Add the last set of data from last iteration. + if (status == STATUS_READING_STRING) { + if (!msg_id.is_empty()) { + if (!skip_this) { + translation->add_message(msg_id, msg_str, msg_context); + } + } else if (config.is_empty()) { + config = msg_str; } - } else if (config.is_empty()) { - config = msg_str; - } - } else if (status == STATUS_READING_PLURAL) { - if (!skip_this && !msg_id.is_empty()) { - if (plural_index != plural_forms - 1) { - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line)); + } else if (status == STATUS_READING_PLURAL) { + if (!skip_this && !msg_id.is_empty()) { + if (plural_index != plural_forms - 1) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line)); + } + translation->add_plural_message(msg_id, msgs_plural, msg_context); } - translation->add_plural_message(msg_id, msgs_plural, msg_context); } } @@ -290,6 +396,7 @@ RES TranslationLoaderPO::load(const String &p_path, const String &p_original_pat void TranslationLoaderPO::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("po"); + p_extensions->push_back("mo"); } bool TranslationLoaderPO::handles_type(const String &p_type) const { @@ -297,7 +404,7 @@ bool TranslationLoaderPO::handles_type(const String &p_type) const { } String TranslationLoaderPO::get_resource_type(const String &p_path) const { - if (p_path.get_extension().to_lower() == "po") { + if (p_path.get_extension().to_lower() == "po" || p_path.get_extension().to_lower() == "mo") { return "Translation"; } return ""; |