diff --git a/src/language.h b/src/language.h index 6a208ab929..a8de8c0f7a 100644 --- a/src/language.h +++ b/src/language.h @@ -14,6 +14,10 @@ #include "core/smallvec_type.hpp" +static const uint8 CASE_GENDER_LEN = 16; ///< The (maximum) length of a case/gender string. +static const uint8 MAX_NUM_GENDERS = 8; ///< Maximum number of supported genders. +static const uint8 MAX_NUM_CASES = 16; ///< Maximum number of supported cases. + /** Header of a language file. */ struct LanguagePackHeader { static const uint32 IDENT = 0x474E414C; ///< Identifier for OpenTTD language files, big endian for "LANG" @@ -43,13 +47,44 @@ struct LanguagePackHeader { */ uint16 winlangid; ///< windows language id uint8 newgrflangid; ///< newgrf language id - byte pad[3]; ///< pad header to be a multiple of 4 + uint8 num_genders; ///< the number of genders of this language + uint8 num_cases; ///< the number of cases of this language + byte pad[1]; ///< pad header to be a multiple of 4 + + char genders[MAX_NUM_GENDERS][CASE_GENDER_LEN]; ///< the genders used by this translation + char cases[MAX_NUM_CASES][CASE_GENDER_LEN]; ///< the cases used by this translation /** * Check whether the header is a valid header for OpenTTD. * @return true iff the header is deemed valid. */ bool IsValid() const; + + /** + * Get the index for the given gender. + * @param gender_str The string representation of the gender. + * @return The index of the gender, or MAX_NUM_GENDERS when the gender is unknown. + */ + uint8 GetGenderIndex(const char *gender_str) const + { + for (uint8 i = 0; i < MAX_NUM_GENDERS; i++) { + if (strcmp(gender_str, this->genders[i]) == 0) return i; + } + return MAX_NUM_GENDERS; + } + + /** + * Get the index for the given case. + * @param case_str The string representation of the case. + * @return The index of the case, or MAX_NUM_CASES when the case is unknown. + */ + uint8 GetCaseIndex(const char *case_str) const + { + for (uint8 i = 0; i < MAX_NUM_CASES; i++) { + if (strcmp(case_str, this->cases[i]) == 0) return i; + } + return MAX_NUM_CASES; + } }; assert_compile(sizeof(LanguagePackHeader) % 4 == 0); diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp index 3497e76165..c216940c36 100644 --- a/src/strgen/strgen.cpp +++ b/src/strgen/strgen.cpp @@ -78,14 +78,6 @@ static int _put_pos; static int _next_string_id; static uint32 _hash; -#define MAX_NUM_GENDERS 8 -static char _genders[MAX_NUM_GENDERS][16]; -static uint _numgenders; - -/* contains the name of all cases. */ -#define MAX_NUM_CASES 50 -static char _cases[MAX_NUM_CASES][16]; -static uint _numcases; static const char *_cur_ident; @@ -377,10 +369,9 @@ static void EmitGender(char *buf, int value) buf++; /* This is a {G=DER} command */ - for (nw = 0; ; nw++) { - if (nw >= MAX_NUM_GENDERS) error("G argument '%s' invalid", buf); - if (strcmp(buf, _genders[nw]) == 0) break; - } + nw = _lang.GetGenderIndex(buf); + if (nw >= MAX_NUM_GENDERS) error("G argument '%s' invalid", buf); + /* now nw contains the gender index */ PutUtf8(SCC_GENDER_INDEX); PutByte(nw); @@ -400,7 +391,7 @@ static void EmitGender(char *buf, int value) words[nw] = ParseWord(&buf); if (words[nw] == NULL) break; } - if (nw != _numgenders) error("Bad # of arguments for gender command"); + if (nw != _lang.num_genders) error("Bad # of arguments for gender command"); assert(IsInsideBS(cmd->value, SCC_CONTROL_START, UINT8_MAX)); PutUtf8(SCC_GENDER_LIST); @@ -420,10 +411,14 @@ static const CmdStruct *FindCmd(const char *s, int len) static uint ResolveCaseName(const char *str, uint len) { - for (uint i = 0; i < MAX_NUM_CASES; i++) { - if (memcmp(_cases[i], str, len) == 0 && _cases[i][len] == 0) return i + 1; - } - error("Invalid case-name '%s'", str); + /* First get a clean copy of only the case name, then resolve it. */ + char case_str[CASE_GENDER_LEN]; + memcpy(case_str, str, len); + case_str[len] = '\0'; + + uint8 case_idx = _lang.GetCaseIndex(case_str); + if (case_idx >= MAX_NUM_CASES) error("Invalid case-name '%s'", case_str); + return case_idx + 1; } @@ -559,9 +554,9 @@ static void HandlePragma(char *str, bool master) const char *s = ParseWord(&buf); if (s == NULL) break; - if (_numgenders >= MAX_NUM_GENDERS) error("Too many genders, max %d", MAX_NUM_GENDERS); - strecpy(_genders[_numgenders], s, lastof(_genders[_numgenders])); - _numgenders++; + if (_lang.num_genders >= MAX_NUM_GENDERS) error("Too many genders, max %d", MAX_NUM_GENDERS); + strecpy(_lang.genders[_lang.num_genders], s, lastof(_lang.genders[_lang.num_genders])); + _lang.num_genders++; } } else if (!memcmp(str, "case ", 5)) { if (master) error("Cases are not allowed in the base translation."); @@ -571,9 +566,9 @@ static void HandlePragma(char *str, bool master) const char *s = ParseWord(&buf); if (s == NULL) break; - if (_numcases >= MAX_NUM_CASES) error("Too many cases, max %d", MAX_NUM_CASES); - strecpy(_cases[_numcases], s, lastof(_cases[_numcases])); - _numcases++; + if (_lang.num_cases >= MAX_NUM_CASES) error("Too many cases, max %d", MAX_NUM_CASES); + strecpy(_lang.cases[_lang.num_cases], s, lastof(_lang.cases[_lang.num_cases])); + _lang.num_cases++; } } else { error("unknown pragma '%s'", str); @@ -810,7 +805,6 @@ static void ParseFile(const char *file, bool english) /* For each new file we parse, reset the genders, and language codes */ MemSetT(&_lang, 0); - _numgenders = 0; strecpy(_lang.digit_group_separator, ",", lastof(_lang.digit_group_separator)); strecpy(_lang.digit_group_separator_currency, ",", lastof(_lang.digit_group_separator_currency)); strecpy(_lang.digit_decimal_separator, ".", lastof(_lang.digit_decimal_separator)); diff --git a/src/strings.cpp b/src/strings.cpp index 9bab8cb725..a51fb84d46 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -1315,6 +1315,8 @@ bool LanguagePackHeader::IsValid() const this->plural_form < LANGUAGE_MAX_PLURAL && this->text_dir <= 1 && this->newgrflangid < MAX_LANG && + this->num_genders < MAX_NUM_GENDERS && + this->num_cases < MAX_NUM_CASES && StrValid(this->name, lastof(this->name)) && StrValid(this->own_name, lastof(this->own_name)) && StrValid(this->isocode, lastof(this->isocode)) &&