mirror of https://github.com/OpenTTD/OpenTTD
Codechange: Encode case/gender/plural choice lists without null termination, only length prefix. (#13876)
parent
25005cff16
commit
c105adcd96
|
@ -152,7 +152,7 @@ struct UnmappedChoiceList {
|
||||||
if (this->type == SCC_SWITCH_CASE) {
|
if (this->type == SCC_SWITCH_CASE) {
|
||||||
/*
|
/*
|
||||||
* Format for case switch:
|
* Format for case switch:
|
||||||
* <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
|
* <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
|
||||||
* Each LEN is printed using 2 bytes in big endian order.
|
* Each LEN is printed using 2 bytes in big endian order.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -164,6 +164,16 @@ struct UnmappedChoiceList {
|
||||||
}
|
}
|
||||||
*d++ = count;
|
*d++ = count;
|
||||||
|
|
||||||
|
auto add_case = [&](std::string_view str) {
|
||||||
|
/* "<LENn>" */
|
||||||
|
uint16_t len = ClampTo<uint16_t>(str.size());
|
||||||
|
*d++ = GB(len, 0, 8);
|
||||||
|
*d++ = GB(len, 8, 8);
|
||||||
|
|
||||||
|
/* "<STRINGn>" */
|
||||||
|
dest.write(str.data(), len);
|
||||||
|
};
|
||||||
|
|
||||||
for (uint8_t i = 0; i < _current_language->num_cases; i++) {
|
for (uint8_t i = 0; i < _current_language->num_cases; i++) {
|
||||||
/* Resolve the string we're looking for. */
|
/* Resolve the string we're looking for. */
|
||||||
int idx = lm->GetReverseMapping(i, false);
|
int idx = lm->GetReverseMapping(i, false);
|
||||||
|
@ -173,18 +183,11 @@ struct UnmappedChoiceList {
|
||||||
/* "<CASEn>" */
|
/* "<CASEn>" */
|
||||||
*d++ = i + 1;
|
*d++ = i + 1;
|
||||||
|
|
||||||
/* "<LENn>": Limit the length of the string to 0xFFFE to leave space for the '\0'. */
|
add_case(str);
|
||||||
size_t len = std::min<size_t>(0xFFFE, str.size());
|
|
||||||
*d++ = GB(len + 1, 8, 8);
|
|
||||||
*d++ = GB(len + 1, 0, 8);
|
|
||||||
|
|
||||||
/* "<STRINGn>" */
|
|
||||||
dest.write(str.c_str(), len);
|
|
||||||
*d++ = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "<STRINGDEFAULT>" */
|
/* "<STRINGDEFAULT>" */
|
||||||
dest << this->strings[0].rdbuf();
|
add_case(this->strings[0].view());
|
||||||
} else {
|
} else {
|
||||||
if (this->type == SCC_PLURAL_LIST) {
|
if (this->type == SCC_PLURAL_LIST) {
|
||||||
*d++ = lm->plural_form;
|
*d++ = lm->plural_form;
|
||||||
|
@ -206,20 +209,17 @@ struct UnmappedChoiceList {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
|
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();
|
const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0].str();
|
||||||
size_t len = str.size() + 1;
|
size_t len = str.size();
|
||||||
if (len > 0xFF) GrfMsg(1, "choice list string is too long");
|
if (len > UINT8_MAX) GrfMsg(1, "choice list string is too long");
|
||||||
*d++ = GB(len, 0, 8);
|
*d++ = ClampTo<uint8_t>(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "<STRINGs>" */
|
/* "<STRINGs>" */
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
|
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();
|
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
|
uint8_t len = ClampTo<uint8_t>(str.size());
|
||||||
* as a byte and we need room for the final '\0'. */
|
|
||||||
size_t len = std::min<size_t>(0xFE, str.size());
|
|
||||||
dest.write(str.c_str(), len);
|
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 */
|
/* skip all cases and continue with default case */
|
||||||
uint num = static_cast<uint8_t>(*str++);
|
uint num = static_cast<uint8_t>(*str++);
|
||||||
for (uint i = 0; i != num; i++) {
|
for (uint i = 0; i != num; i++) {
|
||||||
str += 3 + (static_cast<uint8_t>(str[1]) << 8) + static_cast<uint8_t>(str[2]);
|
str += 3 + static_cast<uint8_t>(str[1]) + (static_cast<uint8_t>(str[2]) << 8);
|
||||||
}
|
}
|
||||||
|
str += 2; // length of default
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -292,18 +292,14 @@ std::optional<std::string_view> ParseWord(const char **buf)
|
||||||
* CommandByte <ARG#> <NUM> {Length of each string} {each string} */
|
* CommandByte <ARG#> <NUM> {Length of each string} {each string} */
|
||||||
static void EmitWordList(Buffer *buffer, const std::vector<std::string> &words)
|
static void EmitWordList(Buffer *buffer, const std::vector<std::string> &words)
|
||||||
{
|
{
|
||||||
/* Maximum word length in bytes, excluding trailing NULL. */
|
|
||||||
constexpr size_t MAX_WORD_LENGTH = UINT8_MAX - 2;
|
|
||||||
|
|
||||||
buffer->AppendByte(static_cast<uint8_t>(words.size()));
|
buffer->AppendByte(static_cast<uint8_t>(words.size()));
|
||||||
for (size_t i = 0; i < words.size(); i++) {
|
for (size_t i = 0; i < words.size(); i++) {
|
||||||
size_t len = words[i].size() + 1;
|
size_t len = words[i].size();
|
||||||
if (len >= UINT8_MAX) StrgenFatal("WordList {}/{} string '{}' too long, max bytes {}", i + 1, words.size(), words[i], MAX_WORD_LENGTH);
|
if (len > UINT8_MAX) StrgenFatal("WordList {}/{} string '{}' too long, max bytes {}", i + 1, words.size(), words[i], UINT8_MAX);
|
||||||
buffer->AppendByte(static_cast<uint8_t>(len));
|
buffer->AppendByte(static_cast<uint8_t>(len));
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < words.size(); i++) {
|
for (size_t i = 0; i < words.size(); i++) {
|
||||||
buffer->append(words[i]);
|
buffer->append(words[i]);
|
||||||
buffer->AppendByte(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -900,11 +896,12 @@ void LanguageWriter::WriteLang(const StringData &data)
|
||||||
|
|
||||||
_translated = cmdp != &ls->english;
|
_translated = cmdp != &ls->english;
|
||||||
|
|
||||||
|
std::optional<size_t> default_case_pos;
|
||||||
if (!ls->translated_cases.empty()) {
|
if (!ls->translated_cases.empty()) {
|
||||||
/* Need to output a case-switch.
|
/* Need to output a case-switch.
|
||||||
* It has this format
|
* It has this format
|
||||||
* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
|
* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
|
||||||
* Each LEN is printed using 2 bytes in big endian order. */
|
* Each LEN is printed using 2 bytes in little endian order. */
|
||||||
buffer.AppendUtf8(SCC_SWITCH_CASE);
|
buffer.AppendUtf8(SCC_SWITCH_CASE);
|
||||||
buffer.AppendByte(static_cast<uint8_t>(ls->translated_cases.size()));
|
buffer.AppendByte(static_cast<uint8_t>(ls->translated_cases.size()));
|
||||||
|
|
||||||
|
@ -917,16 +914,25 @@ void LanguageWriter::WriteLang(const StringData &data)
|
||||||
buffer.AppendByte(0);
|
buffer.AppendByte(0);
|
||||||
/* Write string */
|
/* Write string */
|
||||||
PutCommandString(&buffer, c.string.c_str());
|
PutCommandString(&buffer, c.string.c_str());
|
||||||
buffer.AppendByte(0); // terminate with a zero
|
|
||||||
/* Fill in the length */
|
/* Fill in the length */
|
||||||
size_t size = buffer.size() - (pos + 2);
|
size_t size = buffer.size() - (pos + 2);
|
||||||
buffer[pos + 0] = GB(size, 8, 8);
|
buffer[pos + 0] = GB(size, 0, 8);
|
||||||
buffer[pos + 1] = 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 (!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->WriteLength(buffer.size());
|
||||||
this->Write(buffer.data(), buffer.size());
|
this->Write(buffer.data(), buffer.size());
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
|
|
@ -790,8 +790,7 @@ static const char *ParseStringChoice(const char *b, uint form, StringBuilder &bu
|
||||||
total_len += len;
|
total_len += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(form_len > 0); // len includes a null terminator
|
builder += std::string_view(b + form_offset, form_len);
|
||||||
builder += std::string_view(b + form_offset, form_len - 1);
|
|
||||||
return b + total_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}
|
case SCC_SWITCH_CASE: { // {Used to implement case switching}
|
||||||
/* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
|
/* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
|
||||||
* Each LEN is printed using 2 bytes in big endian order. */
|
* Each LEN is printed using 2 bytes in little endian order. */
|
||||||
uint num = (uint8_t)*str++;
|
uint num = (uint8_t)*str++;
|
||||||
std::optional<std::string_view> found;
|
std::optional<std::string_view> found;
|
||||||
for (; num > 0; --num) {
|
for (; num > 0; --num) {
|
||||||
uint8_t index = static_cast<uint8_t>(str[0]);
|
uint8_t index = static_cast<uint8_t>(str[0]);
|
||||||
uint16_t len = (static_cast<uint8_t>(str[1]) << 8) + static_cast<uint8_t>(str[2]);
|
uint16_t len = static_cast<uint8_t>(str[1]) + (static_cast<uint8_t>(str[2]) << 8);
|
||||||
assert(len > 0); // len includes a null terminator
|
|
||||||
str += 3;
|
str += 3;
|
||||||
if (index == case_index) {
|
if (index == case_index) {
|
||||||
/* Found the case */
|
/* Found the case */
|
||||||
found.emplace(str, len - 1);
|
found.emplace(str, len);
|
||||||
}
|
}
|
||||||
str += len;
|
str += len;
|
||||||
}
|
}
|
||||||
const char *end = str_stack.top().end;
|
uint16_t default_len = static_cast<uint8_t>(str[0]) + (static_cast<uint8_t>(str[1]) << 8);
|
||||||
if (!found.has_value()) found.emplace(str, end - str);
|
str += 2;
|
||||||
str = end;
|
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"
|
str_stack.emplace(*found, ref_param_offset, case_index); // this may invalidate "str"
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue