diff --git a/src/newgrf_text.cpp b/src/newgrf_text.cpp index f90310911b..30a07a5ed5 100644 --- a/src/newgrf_text.cpp +++ b/src/newgrf_text.cpp @@ -152,7 +152,7 @@ struct UnmappedChoiceList { if (this->type == SCC_SWITCH_CASE) { /* * Format for case switch: - * + * * Each LEN is printed using 2 bytes in big endian order. */ @@ -164,6 +164,16 @@ struct UnmappedChoiceList { } *d++ = count; + auto add_case = [&](std::string_view str) { + /* "" */ + uint16_t len = ClampTo(str.size()); + *d++ = GB(len, 0, 8); + *d++ = GB(len, 8, 8); + + /* "" */ + dest.write(str.data(), len); + }; + for (uint8_t i = 0; i < _current_language->num_cases; i++) { /* Resolve the string we're looking for. */ int idx = lm->GetReverseMapping(i, false); @@ -173,18 +183,11 @@ struct UnmappedChoiceList { /* "" */ *d++ = i + 1; - /* "": Limit the length of the string to 0xFFFE to leave space for the '\0'. */ - size_t len = std::min(0xFFFE, str.size()); - *d++ = GB(len + 1, 8, 8); - *d++ = GB(len + 1, 0, 8); - - /* "" */ - dest.write(str.c_str(), len); - *d++ = '\0'; + add_case(str); } /* "" */ - dest << this->strings[0].rdbuf(); + add_case(this->strings[0].view()); } else { if (this->type == SCC_PLURAL_LIST) { *d++ = lm->plural_form; @@ -206,20 +209,17 @@ struct UnmappedChoiceList { for (int i = 0; i < count; i++) { int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1); const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0].str(); - size_t len = str.size() + 1; - if (len > 0xFF) GrfMsg(1, "choice list string is too long"); - *d++ = GB(len, 0, 8); + size_t len = str.size(); + if (len > UINT8_MAX) GrfMsg(1, "choice list string is too long"); + *d++ = ClampTo(len); } /* "" */ for (int i = 0; i < count; i++) { int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1); const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0].str(); - /* Limit the length of the string we copy to 0xFE. The length is written above - * as a byte and we need room for the final '\0'. */ - size_t len = std::min(0xFE, str.size()); + uint8_t len = ClampTo(str.size()); dest.write(str.c_str(), len); - *d++ = '\0'; } } } @@ -793,8 +793,9 @@ static void ProcessNewGRFStringControlCode(char32_t scc, const char *&str, TextR /* skip all cases and continue with default case */ uint num = static_cast(*str++); for (uint i = 0; i != num; i++) { - str += 3 + (static_cast(str[1]) << 8) + static_cast(str[2]); + str += 3 + static_cast(str[1]) + (static_cast(str[2]) << 8); } + str += 2; // length of default break; } diff --git a/src/strgen/strgen_base.cpp b/src/strgen/strgen_base.cpp index b4964cb4a7..8e94acb91c 100644 --- a/src/strgen/strgen_base.cpp +++ b/src/strgen/strgen_base.cpp @@ -292,18 +292,14 @@ std::optional ParseWord(const char **buf) * CommandByte {Length of each string} {each string} */ static void EmitWordList(Buffer *buffer, const std::vector &words) { - /* Maximum word length in bytes, excluding trailing NULL. */ - constexpr size_t MAX_WORD_LENGTH = UINT8_MAX - 2; - buffer->AppendByte(static_cast(words.size())); for (size_t i = 0; i < words.size(); i++) { - size_t len = words[i].size() + 1; - if (len >= UINT8_MAX) StrgenFatal("WordList {}/{} string '{}' too long, max bytes {}", i + 1, words.size(), words[i], MAX_WORD_LENGTH); + size_t len = words[i].size(); + if (len > UINT8_MAX) StrgenFatal("WordList {}/{} string '{}' too long, max bytes {}", i + 1, words.size(), words[i], UINT8_MAX); buffer->AppendByte(static_cast(len)); } for (size_t i = 0; i < words.size(); i++) { buffer->append(words[i]); - buffer->AppendByte(0); } } @@ -900,11 +896,12 @@ void LanguageWriter::WriteLang(const StringData &data) _translated = cmdp != &ls->english; + std::optional default_case_pos; if (!ls->translated_cases.empty()) { /* Need to output a case-switch. * It has this format - * <0x9E> - * Each LEN is printed using 2 bytes in big endian order. */ + * <0x9E> + * Each LEN is printed using 2 bytes in little endian order. */ buffer.AppendUtf8(SCC_SWITCH_CASE); buffer.AppendByte(static_cast(ls->translated_cases.size())); @@ -917,16 +914,25 @@ void LanguageWriter::WriteLang(const StringData &data) buffer.AppendByte(0); /* Write string */ PutCommandString(&buffer, c.string.c_str()); - buffer.AppendByte(0); // terminate with a zero /* Fill in the length */ size_t size = buffer.size() - (pos + 2); - buffer[pos + 0] = GB(size, 8, 8); - buffer[pos + 1] = GB(size, 0, 8); + buffer[pos + 0] = GB(size, 0, 8); + buffer[pos + 1] = GB(size, 8, 8); } + + default_case_pos = buffer.size(); + buffer.AppendByte(0); + buffer.AppendByte(0); } if (!cmdp->empty()) PutCommandString(&buffer, cmdp->c_str()); + if (default_case_pos.has_value()) { + size_t size = buffer.size() - (*default_case_pos + 2); + buffer[*default_case_pos + 0] = GB(size, 0, 8); + buffer[*default_case_pos + 1] = GB(size, 8, 8); + } + this->WriteLength(buffer.size()); this->Write(buffer.data(), buffer.size()); buffer.clear(); diff --git a/src/strings.cpp b/src/strings.cpp index 83164dfa06..27192746f2 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -790,8 +790,7 @@ static const char *ParseStringChoice(const char *b, uint form, StringBuilder &bu total_len += len; } - assert(form_len > 0); // len includes a null terminator - builder += std::string_view(b + form_offset, form_len - 1); + builder += std::string_view(b + form_offset, form_len); return b + total_len; } @@ -1231,24 +1230,25 @@ static void FormatString(StringBuilder &builder, std::string_view str_arg, Strin } case SCC_SWITCH_CASE: { // {Used to implement case switching} - /* <0x9E> - * Each LEN is printed using 2 bytes in big endian order. */ + /* <0x9E> + * Each LEN is printed using 2 bytes in little endian order. */ uint num = (uint8_t)*str++; std::optional found; for (; num > 0; --num) { uint8_t index = static_cast(str[0]); - uint16_t len = (static_cast(str[1]) << 8) + static_cast(str[2]); - assert(len > 0); // len includes a null terminator + uint16_t len = static_cast(str[1]) + (static_cast(str[2]) << 8); str += 3; if (index == case_index) { /* Found the case */ - found.emplace(str, len - 1); + found.emplace(str, len); } str += len; } - const char *end = str_stack.top().end; - if (!found.has_value()) found.emplace(str, end - str); - str = end; + uint16_t default_len = static_cast(str[0]) + (static_cast(str[1]) << 8); + str += 2; + if (!found.has_value()) found.emplace(str, default_len); + str += default_len; + assert(str <= str_stack.top().end); str_stack.emplace(*found, ref_param_offset, case_index); // this may invalidate "str" break; }