OpenTTD Source 20260621-master-g720d10536d
newgrf_text.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
17
18#include "stdafx.h"
19
20#include "debug.h"
21#include "newgrf.h"
22#include "strings_internal.h"
23#include "newgrf_storage.h"
24#include "newgrf_text.h"
25#include "newgrf_cargo.h"
26#include "string_func.h"
28#include "debug.h"
31#include "language.h"
32#include <ranges>
33
34#include "table/strings.h"
35#include "table/control_codes.h"
36
37#include "safeguards.h"
38
44enum GRFBaseLanguages : uint8_t {
45 GRFLB_AMERICAN = 0x01,
46 GRFLB_ENGLISH = 0x02,
47 GRFLB_GERMAN = 0x04,
48 GRFLB_FRENCH = 0x08,
49 GRFLB_SPANISH = 0x10,
50 GRFLB_GENERIC = 0x80,
51};
52
53enum GRFExtendedLanguages : uint8_t {
54 GRFLX_AMERICAN = 0x00,
55 GRFLX_ENGLISH = 0x01,
56 GRFLX_GERMAN = 0x02,
57 GRFLX_FRENCH = 0x03,
58 GRFLX_SPANISH = 0x04,
59 GRFLX_UNSPECIFIED = 0x7F,
60};
61
62
69 GRFTextList textholder;
70 StringID def_string;
71 uint32_t grfid;
72 GRFStringID stringid;
73};
74
75
77
84int LanguageMap::GetMapping(int newgrf_id, bool gender) const
85{
86 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
87 for (const Mapping &m : map) {
88 if (m.newgrf_id == newgrf_id) return m.openttd_id;
89 }
90 return -1;
91}
92
99int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
100{
101 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
102 for (const Mapping &m : map) {
103 if (m.openttd_id == openttd_id) return m.newgrf_id;
104 }
105 return -1;
106}
107
119
121 int offset;
122
124 std::map<int, std::string> strings;
125
131 void Flush(const LanguageMap *lm, std::string &dest)
132 {
133 if (this->strings.find(0) == this->strings.end()) {
134 /* In case of a (broken) NewGRF without a default,
135 * assume an empty string. */
136 GrfMsg(1, "choice list misses default value");
137 this->strings[0] = std::string();
138 }
139
140 StringBuilder builder(dest);
141
142 if (lm == nullptr) {
143 /* In case there is no mapping, just ignore everything but the default.
144 * A probable cause for this happening is when the language file has
145 * been removed by the user and as such no mapping could be made. */
146 builder += this->strings[0];
147 return;
148 }
149
150 builder.PutUtf8(this->type);
151
152 if (this->type == SCC_SWITCH_CASE) {
153 /*
154 * Format for case switch:
155 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
156 * Each LEN is printed using 2 bytes in big endian order.
157 */
158
159 /* "<NUM CASES>" */
160 int count = 0;
161 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
162 /* Count the ones we have a mapped string for. */
163 if (this->strings.find(lm->GetReverseMapping(i, false)) != this->strings.end()) count++;
164 }
165 builder.PutUint8(count);
166
167 auto add_case = [&](std::string_view str) {
168 /* "<LENn>" */
169 uint16_t len = ClampTo<uint16_t>(str.size());
170 builder.PutUint16LE(len);
171
172 /* "<STRINGn>" */
173 builder += str.substr(0, len);
174 };
175
176 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
177 /* Resolve the string we're looking for. */
178 int idx = lm->GetReverseMapping(i, false);
179 if (this->strings.find(idx) == this->strings.end()) continue;
180 auto &str = this->strings[idx];
181
182 /* "<CASEn>" */
183 builder.PutUint8(i + 1);
184
185 add_case(str);
186 }
187
188 /* "<STRINGDEFAULT>" */
189 add_case(this->strings[0]);
190 } else {
191 if (this->type == SCC_PLURAL_LIST) {
192 builder.PutUint8(lm->plural_form);
193 }
194
195 /*
196 * Format for choice list:
197 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
198 */
199
200 /* "<OFFSET>" */
201 builder.PutUint8(this->offset - 0x80);
202
203 /* "<NUM CHOICES>" */
204 int count = (this->type == SCC_GENDER_LIST ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
205 builder.PutUint8(count);
206
207 /* "<LENs>" */
208 for (int i = 0; i < count; i++) {
209 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
210 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
211 size_t len = str.size();
212 if (len > UINT8_MAX) GrfMsg(1, "choice list string is too long");
213 builder.PutUint8(ClampTo<uint8_t>(len));
214 }
215
216 /* "<STRINGs>" */
217 for (int i = 0; i < count; i++) {
218 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
219 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
220 uint8_t len = ClampTo<uint8_t>(str.size());
221 builder += str.substr(0, len);
222 }
223 }
224 }
225};
226
236std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool allow_newlines, std::string_view str, StringControlCode byte80)
237{
238 /* Empty input string? Nothing to do here. */
239 if (str.empty()) return {};
240
241 StringConsumer consumer(str);
242
243 /* Is this an unicode string? */
244 bool unicode = consumer.ReadUtf8If(NFO_UTF8_IDENTIFIER);
245
246 /* Helper variable for a possible (string) mapping of plural/gender and cases. */
247 std::optional<UnmappedChoiceList> mapping_pg, mapping_c;
248 std::optional<std::reference_wrapper<std::string>> dest_c;
249
250 std::string dest;
251 StringBuilder builder(dest);
252 while (consumer.AnyBytesLeft()) {
253 char32_t c;
254 if (auto u = unicode ? consumer.TryReadUtf8() : std::nullopt; u.has_value()) {
255 c = *u;
256 /* 'Magic' range of control codes. */
257 if (0xE000 <= c && c <= 0xE0FF) {
258 c -= 0xE000;
259 } else if (c >= 0x20) {
260 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
261 builder.PutUtf8(c);
262 continue;
263 }
264 } else {
265 c = consumer.ReadUint8(); // read as unsigned, otherwise integer promotion breaks it
266 }
267 assert(c <= 0xFF);
268 if (c == '\0') break;
269
270 switch (c) {
271 case 0x01:
272 consumer.SkipUint8();
273 builder.PutChar(' ');
274 break;
275 case 0x0A: break;
276 case 0x0D:
277 if (allow_newlines) {
278 builder.PutChar(0x0A);
279 } else {
280 GrfMsg(1, "Detected newline in string that does not allow one");
281 }
282 break;
283 case 0x0E: builder.PutUtf8(SCC_TINYFONT); break;
284 case 0x0F: builder.PutUtf8(SCC_BIGFONT); break;
285 case 0x1F:
286 consumer.SkipUint8();
287 consumer.SkipUint8();
288 builder.PutChar(' ');
289 break;
290 case 0x7B:
291 case 0x7C:
292 case 0x7D:
293 case 0x7E:
294 case 0x7F: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break;
295 case 0x80: builder.PutUtf8(byte80); break;
296 case 0x81:
297 {
298 uint16_t string = consumer.ReadUint16LE();
299 builder.PutUtf8(SCC_NEWGRF_STRINL);
300 builder.PutUtf8(MapGRFStringID(grfid, GRFStringID{string}));
301 break;
302 }
303 case 0x82:
304 case 0x83:
305 case 0x84: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_DATE_LONG + c - 0x82); break;
306 case 0x85: builder.PutUtf8(SCC_NEWGRF_DISCARD_WORD); break;
307 case 0x86: builder.PutUtf8(SCC_NEWGRF_ROTATE_TOP_4_WORDS); break;
308 case 0x87: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_VOLUME_LONG); break;
309 case 0x88: builder.PutUtf8(SCC_BLUE); break;
310 case 0x89: builder.PutUtf8(SCC_SILVER); break;
311 case 0x8A: builder.PutUtf8(SCC_GOLD); break;
312 case 0x8B: builder.PutUtf8(SCC_RED); break;
313 case 0x8C: builder.PutUtf8(SCC_PURPLE); break;
314 case 0x8D: builder.PutUtf8(SCC_LTBROWN); break;
315 case 0x8E: builder.PutUtf8(SCC_ORANGE); break;
316 case 0x8F: builder.PutUtf8(SCC_GREEN); break;
317 case 0x90: builder.PutUtf8(SCC_YELLOW); break;
318 case 0x91: builder.PutUtf8(SCC_DKGREEN); break;
319 case 0x92: builder.PutUtf8(SCC_CREAM); break;
320 case 0x93: builder.PutUtf8(SCC_BROWN); break;
321 case 0x94: builder.PutUtf8(SCC_WHITE); break;
322 case 0x95: builder.PutUtf8(SCC_LTBLUE); break;
323 case 0x96: builder.PutUtf8(SCC_GRAY); break;
324 case 0x97: builder.PutUtf8(SCC_DKBLUE); break;
325 case 0x98: builder.PutUtf8(SCC_BLACK); break;
326 case 0x9A:
327 {
328 uint8_t code = consumer.ReadUint8();
329 switch (code) {
330 case 0x00: goto string_end;
331 case 0x01: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_CURRENCY); break;
332 /* 0x02: ignore next colour byte is not supported. It works on the final
333 * string and as such hooks into the string drawing routine. At that
334 * point many things already happened, such as splitting up of strings
335 * when drawn over multiple lines or right-to-left translations, which
336 * make the behaviour peculiar, e.g. only happening at specific width
337 * of windows. Or we need to add another pass over the string to just
338 * support this. As such it is not implemented in OpenTTD. */
339 case 0x03:
340 {
341 uint16_t tmp = consumer.ReadUint16LE();
343 builder.PutUtf8(tmp);
344 break;
345 }
346 case 0x06: builder.PutUtf8(SCC_NEWGRF_PRINT_BYTE_HEX); break;
347 case 0x07: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_HEX); break;
348 case 0x08: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_HEX); break;
349 /* 0x09, 0x0A are TTDPatch internal use only string codes. */
350 case 0x0B: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_HEX); break;
351 case 0x0C: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_STATION_NAME); break;
352 case 0x0D: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG); break;
353 case 0x0E:
354 case 0x0F:
355 {
356 const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
357 int index = consumer.ReadUint8();
358 int mapped = lm != nullptr ? lm->GetMapping(index, code == 0x0E) : -1;
359 if (mapped >= 0) {
360 builder.PutUtf8(code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
361 builder.PutUtf8(code == 0x0E ? mapped : mapped + 1);
362 }
363 break;
364 }
365
366 case 0x10:
367 case 0x11:
368 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
369 if (code == 0x10) consumer.SkipUint8(); // Skip the index
370 GrfMsg(1, "choice list {} marker found when not expected", code == 0x10 ? "next" : "default");
371 break;
372 } else {
373 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
374 int index = (code == 0x10 ? consumer.ReadUint8() : 0);
375 if (mapping->strings.find(index) != mapping->strings.end()) {
376 GrfMsg(1, "duplicate choice list string, ignoring");
377 } else {
378 builder = StringBuilder(mapping->strings[index]);
379 if (!mapping_pg) dest_c = mapping->strings[index];
380 }
381 }
382 break;
383
384 case 0x12:
385 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
386 GrfMsg(1, "choice list end marker found when not expected");
387 } else {
388 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
389 auto &new_dest = mapping_pg && dest_c ? dest_c->get() : dest;
390 /* Now we can start flushing everything and clean everything up. */
391 mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id), new_dest);
392 if (!mapping_pg) dest_c.reset();
393 mapping.reset();
394
395 builder = StringBuilder(new_dest);
396 }
397 break;
398
399 case 0x13:
400 case 0x14:
401 case 0x15: {
402 auto &mapping = code == 0x14 ? mapping_c : mapping_pg;
403 /* Case mapping can have nested plural/gender mapping. Otherwise nesting is invalid. */
404 if (mapping.has_value() || mapping_pg.has_value()) {
405 GrfMsg(1, "choice lists can't be stacked, it's going to get messy now...");
406 if (code != 0x14) consumer.SkipUint8();
407 } else {
408 static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
409 mapping.emplace(mp[code - 0x13], code == 0x14 ? 0 : consumer.ReadUint8());
410 }
411 break;
412 }
413
414 case 0x16:
415 case 0x17:
416 case 0x18:
417 case 0x19:
418 case 0x1A:
419 case 0x1B:
420 case 0x1C:
421 case 0x1D:
422 case 0x1E:
423 builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_DATE_LONG + code - 0x16);
424 break;
425
426 case 0x1F: builder.PutUtf8(SCC_PUSH_COLOUR); break;
427 case 0x20: builder.PutUtf8(SCC_POP_COLOUR); break;
428
429 case 0x21: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_FORCE); break;
430
431 default:
432 GrfMsg(1, "missing handler for extended format code");
433 break;
434 }
435 break;
436 }
437
438 case 0x9B: builder.PutUtf8(SCC_TOWN); break;
439 case 0x9C: builder.PutUtf8(SCC_CITY); break;
440 case 0x9E: builder.PutUtf8(0x20AC); break; // Euro
441 case 0x9F: builder.PutUtf8(0x0178); break; // Y with diaeresis
442 case 0xA0: builder.PutUtf8(SCC_UP_ARROW); break;
443 case 0xAA: builder.PutUtf8(SCC_DOWN_ARROW); break;
444 case 0xAC: builder.PutUtf8(SCC_CHECKMARK); break;
445 case 0xAD: builder.PutUtf8(SCC_CROSS); break;
446 case 0xAF: builder.PutUtf8(SCC_RIGHT_ARROW); break;
447 case 0xB4: builder.PutUtf8(SCC_TRAIN); break;
448 case 0xB5: builder.PutUtf8(SCC_LORRY); break;
449 case 0xB6: builder.PutUtf8(SCC_BUS); break;
450 case 0xB7: builder.PutUtf8(SCC_PLANE); break;
451 case 0xB8: builder.PutUtf8(SCC_SHIP); break;
452 case 0xB9: builder.PutUtf8(SCC_SUPERSCRIPT_M1); break;
453 case 0xBC: builder.PutUtf8(SCC_SMALL_UP_ARROW); break;
454 case 0xBD: builder.PutUtf8(SCC_SMALL_DOWN_ARROW); break;
455 default:
456 /* Validate any unhandled character */
457 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
458 builder.PutUtf8(c);
459 break;
460 }
461 }
462
463string_end:
464 if (mapping_pg.has_value() || mapping_c.has_value()) {
465 GrfMsg(1, "choice list was incomplete, the whole list is ignored");
466 }
467
468 return dest;
469}
470
477static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
478{
479 /* Loop through all languages and see if we can replace a string */
480 for (auto &text : list) {
481 if (text.langid == langid) {
482 text.text = text_to_add;
483 return;
484 }
485 }
486
487 /* If a string wasn't replaced, then we must append the new string */
488 list.emplace_back(langid, std::string{text_to_add});
489}
490
500void AddGRFTextToList(GRFTextList &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
501{
502 AddGRFTextToList(list, langid, TranslateTTDPatchCodes(grfid, langid, allow_newlines, text_to_add));
503}
504
514void AddGRFTextToList(GRFTextWrapper &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
515{
516 if (list == nullptr) list = std::make_shared<GRFTextList>();
517 AddGRFTextToList(*list, langid, grfid, allow_newlines, text_to_add);
518}
519
526void AddGRFTextToList(GRFTextWrapper &list, std::string_view text_to_add)
527{
528 if (list == nullptr) list = std::make_shared<GRFTextList>();
529 AddGRFTextToList(*list, GRFLX_UNSPECIFIED, text_to_add);
530}
531
542static StringID AddGRFString(uint32_t grfid, GRFStringID stringid, uint8_t langid_to_add, bool allow_newlines, std::string_view text_to_add, StringID def_string)
543{
544 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
545 if (it == std::end(_grf_text)) {
546 /* Too many strings allocated, return empty. */
547 if (_grf_text.size() == TAB_SIZE_NEWGRF) return STR_EMPTY;
548
549 /* We didn't find our stringid and grfid in the list, allocate a new id. */
550 it = _grf_text.emplace(std::end(_grf_text));
551 it->grfid = grfid;
552 it->stringid = stringid;
553 it->def_string = def_string;
554 }
555 StringIndexInTab id(it - std::begin(_grf_text));
556
557 std::string newtext = TranslateTTDPatchCodes(grfid, langid_to_add, allow_newlines, text_to_add);
558 AddGRFTextToList(it->textholder, langid_to_add, newtext);
559
560 GrfMsg(3, "Added 0x{:X} grfid {:08X} string 0x{:X} lang 0x{:X} string '{}' ({:X})", id, grfid, stringid, langid_to_add, newtext, MakeStringID(TEXT_TAB_NEWGRF_START, id));
561
563}
564
576StringID AddGRFString(uint32_t grfid, GRFStringID stringid, uint8_t langid_to_add, bool new_scheme, bool allow_newlines, std::string_view text_to_add, StringID def_string)
577{
578 if (new_scheme) return AddGRFString(grfid, stringid, langid_to_add, allow_newlines, text_to_add, def_string);
579
580 /* When working with the old language scheme (grf_version is less than 7) and
581 * English or American is among the set bits, simply add it as English in
582 * the new scheme, i.e. as langid = 1.
583 * If English is set, it is pretty safe to assume the translations are not
584 * actually translated.
585 */
586 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) return AddGRFString(grfid, stringid, GRFLX_ENGLISH, allow_newlines, text_to_add, def_string);
587
588 StringID ret = STR_EMPTY;
589 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, allow_newlines, text_to_add, def_string);
590 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, allow_newlines, text_to_add, def_string);
591 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, allow_newlines, text_to_add, def_string);
592 return ret;
593}
594
601StringID GetGRFStringID(uint32_t grfid, GRFStringID stringid)
602{
603 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
604 if (it != std::end(_grf_text)) {
605 StringIndexInTab id(it - std::begin(_grf_text));
607 }
608
609 return STR_UNDEFINED;
610}
611
612
621std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextList &text_list)
622{
623 std::optional<std::string_view> default_text;
624
625 /* Search the list of lang-strings of this stringid for current lang */
626 for (const auto &text : text_list) {
627 if (text.langid == _current_language->newgrflangid) return text.text;
628
629 /* If the current string is English or American, set it as the
630 * fallback language if the specific language isn't available. */
631 if (text.langid == GRFLX_UNSPECIFIED || (!default_text.has_value() && (text.langid == GRFLX_ENGLISH || text.langid == GRFLX_AMERICAN))) {
632 default_text = text.text;
633 }
634 }
635
636 return default_text;
637}
638
647std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextWrapper &text)
648{
649 return text ? GetGRFStringFromGRFText(*text) : std::nullopt;
650}
651
657std::string_view GetGRFStringPtr(StringIndexInTab stringid)
658{
659 assert(stringid.base() < _grf_text.size());
660 assert(_grf_text[stringid].grfid != 0);
661
662 auto str = GetGRFStringFromGRFText(_grf_text[stringid].textholder);
663 if (str.has_value()) return *str;
664
665 /* Use the default string ID if the fallback string isn't available */
666 return GetStringPtr(_grf_text[stringid].def_string);
667}
668
669bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
670{
671 if (grf_version < 7) {
672 switch (_current_language->newgrflangid) {
673 case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0;
674 case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0;
675 case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
676 default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
677 }
678 }
679
680 return (lang_id == _current_language->newgrflangid || lang_id == GRFLX_UNSPECIFIED);
681}
682
688{
689 _grf_text.clear();
690}
691
692struct TextRefStack {
693private:
694 std::vector<uint8_t> stack;
695public:
696 const GRFFile *grffile = nullptr;
697
698 TextRefStack(const GRFFile *grffile, std::span<const int32_t> textstack) : grffile(grffile)
699 {
700 this->stack.reserve(textstack.size() * 4 + 6); // for translations it is reasonable to push 3 word and rotate them
701 for (int32_t value : std::ranges::reverse_view(textstack)) {
702 this->PushDWord(value);
703 }
704 }
705
706 uint8_t PopUnsignedByte()
707 {
708 if (this->stack.empty()) return 0;
709 auto res = this->stack.back();
710 this->stack.pop_back();
711 return res;
712 }
713 int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
714
715 uint16_t PopUnsignedWord()
716 {
717 uint16_t val = this->PopUnsignedByte();
718 return val | (this->PopUnsignedByte() << 8);
719 }
720 int16_t PopSignedWord() { return (int32_t)this->PopUnsignedWord(); }
721
722 uint32_t PopUnsignedDWord()
723 {
724 uint32_t val = this->PopUnsignedWord();
725 return val | (this->PopUnsignedWord() << 16);
726 }
727 int32_t PopSignedDWord() { return (int32_t)this->PopUnsignedDWord(); }
728
729 uint64_t PopUnsignedQWord()
730 {
731 uint64_t val = this->PopUnsignedDWord();
732 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
733 }
734 int64_t PopSignedQWord() { return (int64_t)this->PopUnsignedQWord(); }
735
738 {
739 auto w1 = this->PopUnsignedWord();
740 auto w2 = this->PopUnsignedWord();
741 auto w3 = this->PopUnsignedWord();
742 auto w4 = this->PopUnsignedWord();
743 this->PushWord(w3);
744 this->PushWord(w2);
745 this->PushWord(w1);
746 this->PushWord(w4);
747 }
748
749 void PushByte(uint8_t b)
750 {
751 this->stack.push_back(b);
752 }
753
754 void PushWord(uint16_t w)
755 {
756 this->PushByte(GB(w, 8, 8));
757 this->PushByte(GB(w, 0, 8));
758 }
759
760 void PushDWord(uint32_t dw)
761 {
762 this->PushWord(GB(dw, 16, 16));
763 this->PushWord(GB(dw, 0, 16));
764 }
765};
766
767static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params);
768
776static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector<StringParameter> &params)
777{
778 /* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
779 * After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
780 switch (scc) {
781 default: return;
782
783 case SCC_PLURAL_LIST:
784 consumer.SkipUint8(); // plural form
785 [[fallthrough]];
786 case SCC_GENDER_LIST: {
787 consumer.SkipUint8(); // offset
788 /* plural and gender choices cannot contain any string commands, so just skip the whole thing */
789 uint num = consumer.ReadUint8();
790 uint total_len = 0;
791 for (uint i = 0; i != num; i++) {
792 total_len += consumer.ReadUint8();
793 }
794 consumer.Skip(total_len);
795 break;
796 }
797
798 case SCC_SWITCH_CASE: {
799 /* skip all cases and continue with default case */
800 uint num = consumer.ReadUint8();
801 for (uint i = 0; i != num; i++) {
802 consumer.SkipUint8();
803 auto len = consumer.ReadUint16LE();
804 consumer.Skip(len);
805 }
806 consumer.SkipUint16LE(); // length of default
807 break;
808 }
809
810 case SCC_GENDER_INDEX:
811 case SCC_SET_CASE:
812 consumer.SkipUint8();
813 break;
814
815 case SCC_ARG_INDEX:
816 NOT_REACHED();
817 break;
818
819 case SCC_NEWGRF_PRINT_BYTE_SIGNED: params.emplace_back(stack.PopSignedByte()); break;
820 case SCC_NEWGRF_PRINT_QWORD_CURRENCY: params.emplace_back(stack.PopSignedQWord()); break;
821
823 case SCC_NEWGRF_PRINT_DWORD_SIGNED: params.emplace_back(stack.PopSignedDWord()); break;
824
825 case SCC_NEWGRF_PRINT_BYTE_HEX: params.emplace_back(stack.PopUnsignedByte()); break;
826 case SCC_NEWGRF_PRINT_QWORD_HEX: params.emplace_back(stack.PopUnsignedQWord()); break;
827
831 case SCC_NEWGRF_PRINT_WORD_SIGNED: params.emplace_back(stack.PopSignedWord()); break;
832
838 case SCC_NEWGRF_PRINT_WORD_UNSIGNED: params.emplace_back(stack.PopUnsignedWord()); break;
839
843 case SCC_NEWGRF_PRINT_DWORD_HEX: params.emplace_back(stack.PopUnsignedDWord()); break;
844
845 /* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
847 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: params.emplace_back(CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR + stack.PopUnsignedWord()); break;
848
849 case SCC_NEWGRF_DISCARD_WORD: stack.PopUnsignedWord(); break;
850
852 case SCC_NEWGRF_PUSH_WORD: stack.PushWord(consumer.ReadUtf8(0)); break;
853
857 params.emplace_back(GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile));
858 params.emplace_back(stack.PopUnsignedWord());
859 break;
860
861 case SCC_NEWGRF_STRINL: {
862 StringID stringid = consumer.ReadUtf8(STR_NULL);
863 /* We also need to handle the substring's stack usage. */
864 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
865 break;
866 }
867
869 StringID stringid = MapGRFStringID(stack.grffile->grfid, GRFStringID{stack.PopUnsignedWord()});
870 params.emplace_back(stringid);
871 /* We also need to handle the substring's stack usage. */
872 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
873 break;
874 }
875
877 CargoType cargo = GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile);
878 params.emplace_back(cargo < NUM_CARGO ? 1ULL << cargo : 0);
879 break;
880 }
881 }
882}
883
890char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
891{
892 switch (scc) {
893 default:
894 return scc;
895
900 return SCC_COMMA;
901
906 return SCC_HEX;
907
910 return SCC_CURRENCY_LONG;
911
914 return SCC_DATE_LONG;
915
918 return SCC_DATE_SHORT;
919
921 return SCC_VELOCITY;
922
924 return SCC_VOLUME_LONG;
925
927 return SCC_VOLUME_SHORT;
928
930 return SCC_WEIGHT_LONG;
931
933 return SCC_WEIGHT_SHORT;
934
936 return SCC_POWER;
937
939 return SCC_FORCE;
940
942 return SCC_CARGO_LONG;
943
945 return SCC_CARGO_SHORT;
946
948 return SCC_CARGO_TINY;
949
951 return SCC_CARGO_LIST;
952
954 return SCC_STATION_NAME;
955
956 /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
958 consumer.SkipUtf8();
959 return 0;
960
963 return 0;
964 }
965}
966
973static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params)
974{
975 StringConsumer consumer(str);
976 while (consumer.AnyBytesLeft()) {
977 char32_t scc = consumer.ReadUtf8();
978 ProcessNewGRFStringControlCode(scc, consumer, stack, params);
979 }
980}
981
989std::vector<StringParameter> GetGRFStringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span<const int32_t> textstack)
990{
991 if (stringid == INVALID_STRING_ID) return {};
992
993 auto str = GetStringPtr(stringid);
994
995 std::vector<StringParameter> params;
996 params.reserve(20);
997
998 TextRefStack stack{grffile, textstack};
999 HandleNewGRFStringControlCodes(str, stack, params);
1000
1001 return params;
1002}
1003
1011std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span<const int32_t> textstack)
1012{
1013 StringID stringid = GetGRFStringID(grffile->grfid, grfstringid);
1014 auto params = GetGRFStringTextStackParameters(grffile, stringid, textstack);
1015 return GetStringWithArgs(stringid, params);
1016}
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
static constexpr CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:75
CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:22
void PutUtf8(char32_t c)
Append UTF-8 char.
void PutUint16LE(uint16_t value)
Append binary uint16 using little endian.
void PutChar(char c)
Append 8-bit char.
void PutUint8(uint8_t value)
Append binary uint8.
Compose data into a growing std::string.
Parse data from a string / buffer.
char32_t ReadUtf8(char32_t def='?')
Read UTF-8 character, and advance reader.
void SkipUtf8()
Skip UTF-8 character, and advance reader.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
uint8_t ReadUint8(uint8_t def=0)
Read binary uint8, and advance reader.
void SkipUint16LE()
Skip binary uint16, and advance reader.
uint16_t ReadUint16LE(uint16_t def=0)
Read binary uint16 using little endian, and advance reader.
void SkipUint8()
Skip binary uint8.
std::optional< char32_t > TryReadUtf8()
Try to read a UTF-8 character, and then advance reader.
bool ReadUtf8If(char32_t c)
Check whether the next UTF-8 char matches 'c', and skip it.
void Skip(size_type len)
Discard some bytes.
static constexpr TimerGame< struct Calendar >::Date DAYS_TILL_ORIGINAL_BASE_YEAR
A sort-of mixin that implements 'at(pos)' and 'operator[](pos)' only for a specific type.
Control codes that are embedded in the translation strings.
StringControlCode
List of string control codes used for string formatting, displaying, and by strgen to generate the la...
@ SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT
9A 19: Read 2 bytes from the stack as short signed volume
@ SCC_NEWGRF_PRINT_DWORD_CURRENCY
8F: Read 4 bytes from the stack as currency
@ SCC_NEWGRF_PRINT_WORD_HEX
9A 07: Read 2 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT
9A 1A: Read 2 bytes from the stack as short unsigned weight
@ SCC_NEWGRF_ROTATE_TOP_4_WORDS
86: Rotate the top 4 words of the stack (W4 W1 W2 W3)
@ SCC_TINYFONT
Switch to small font.
@ SCC_NEWGRF_PRINT_DWORD_DATE_LONG
9A 16: Read 4 bytes from the stack as base 0 date
@ SCC_NEWGRF_PRINT_DWORD_HEX
9A 08: Read 4 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_DATE_SHORT
83: Read 2 bytes from the stack as base 1920 date
@ SCC_BIGFONT
Switch to large font.
@ SCC_NEWGRF_PRINT_BYTE_HEX
9A 06: Read 1 byte from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_UNSIGNED
7E: Read 2 bytes from the stack as unsigned value
@ SCC_NEWGRF_PRINT_BYTE_SIGNED
7D: Read 1 byte from the stack as signed value
@ SCC_NEWGRF_PRINT_WORD_CARGO_NAME
9A 1E: Read 2 bytes from the stack as cargo name
@ SCC_NEWGRF_PRINT_DWORD_SIGNED
7B: Read 4 bytes from the stack
@ SCC_NEWGRF_STRINL
Inline another string at the current position, StringID is encoded in the string.
@ SCC_NEWGRF_PRINT_WORD_CARGO_SHORT
9A 1C: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_WORD_STATION_NAME
9A 0C: Read 2 bytes from the stack as station name
@ SCC_NEWGRF_PRINT_WORD_DATE_LONG
82: Read 2 bytes from the stack as base 1920 date
@ SCC_NEWGRF_PRINT_WORD_SIGNED
7C: Read 2 bytes from the stack as signed value
@ SCC_NEWGRF_PRINT_WORD_POWER
9A 18: Read 2 bytes from the stack as unsigned power
@ SCC_NEWGRF_PUSH_WORD
9A 03: Pushes 2 bytes onto the stack
@ SCC_NEWGRF_PRINT_WORD_STRING_ID
81: Read 2 bytes from the stack as String ID
@ SCC_NEWGRF_PRINT_WORD_VOLUME_LONG
87: Read 2 bytes from the stack as long signed volume
@ SCC_NEWGRF_DISCARD_WORD
85: Discard the next two bytes
@ SCC_NEWGRF_PRINT_DWORD_FORCE
9A 21: Read 4 bytes from the stack as unsigned force
@ SCC_NEWGRF_PRINT_WORD_CARGO_TINY
9A 1D: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_QWORD_HEX
9A 0B: Read 8 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_CARGO_LONG
9A 1B: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_WORD_SPEED
84: Read 2 bytes from the stack as signed speed
@ SCC_NEWGRF_PRINT_DWORD_DATE_SHORT
9A 17: Read 4 bytes from the stack as base 0 date
@ SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG
9A 0D: Read 2 bytes from the stack as long unsigned weight
@ SCC_NEWGRF_PRINT_QWORD_CURRENCY
9A 01: Read 8 bytes from the stack as currency
Functions related to debugging.
Information about languages and their files.
const LanguageMetadata * _current_language
The currently loaded language.
Definition strings.cpp:54
constexpr To ClampTo(From value)
Clamp the given value down to lie within the requested type.
Base for the NewGRF implementation.
StringID MapGRFStringID(uint32_t grfid, GRFStringID str)
Used when setting an object's property to map to the GRF's strings while taking in consideration the ...
CargoType GetCargoTranslation(uint8_t cargo, const GRFFile *grffile, bool usebit)
Translate a GRF-local cargo slot/bitnum into a CargoType.
Cargo support for NewGRFs.
Functionality related to the temporary and persistent storage arrays for NewGRFs.
void CleanUpStrings()
House cleaning.
static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
Add a new text to a GRFText list.
std::string_view GetGRFStringPtr(StringIndexInTab stringid)
Get a C-string from a stringid set by a newgrf.
GRFBaseLanguages
Explains the newgrf shift bit positioning.
static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector< StringParameter > &params)
Handle control codes in a NewGRF string, processing the stack and filling parameters.
std::vector< StringParameter > GetGRFStringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span< const int32_t > textstack)
Process the text ref stack for a GRF String and return its parameters.
std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool allow_newlines, std::string_view str, StringControlCode byte80)
Translate TTDPatch string codes into something OpenTTD can handle (better).
std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span< const int32_t > textstack)
Format a GRF string using the text ref stack for parameters.
StringID GetGRFStringID(uint32_t grfid, GRFStringID stringid)
Returns the index for this stringid associated with its grfID.
std::optional< std::string_view > GetGRFStringFromGRFText(const GRFTextList &text_list)
Get a C-string from a GRFText-list.
static StringID AddGRFString(uint32_t grfid, GRFStringID stringid, uint8_t langid_to_add, bool allow_newlines, std::string_view text_to_add, StringID def_string)
Add the new read string into our structure.
static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector< StringParameter > &params)
Process NewGRF string control code instructions.
char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
Emit OpenTTD's internal string code for the different NewGRF string codes.
Header of Action 04 "universal holder" structure and functions.
StrongType::Typedef< uint32_t, struct GRFStringIDTag, StrongType::Compare, StrongType::Integer > GRFStringID
Type for GRF-internal string IDs.
std::shared_ptr< GRFTextList > GRFTextWrapper
Reference counted wrapper around a GRFText pointer.
std::vector< GRFText > GRFTextList
A GRF text with a list of translations.
static const char32_t NFO_UTF8_IDENTIFIER
This character (thorn) indicates a unicode string to NFO.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
Definition string.cpp:375
Compose strings from textual and binary data.
Parse strings.
Functions related to low-level strings.
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition string_type.h:25
void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
Get a parsed string with most special stringcodes replaced by the string parameters.
Definition strings.cpp:336
StringID MakeStringID(StringTab tab, StringIndexInTab index)
Create a StringID.
Types and functions related to the internal workings of formatting OpenTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
StrongType::Typedef< uint32_t, struct StringIndexInTabTag, StrongType::Compare, StrongType::Integer > StringIndexInTab
The index/offset of a string within a StringTab.
static const StringID INVALID_STRING_ID
Constant representing an invalid string (16bit in case it is used in savegames).
static const uint TAB_SIZE_NEWGRF
Number of strings for NewGRFs.
@ TEXT_TAB_NEWGRF_START
Start of NewGRF supplied strings.
Dynamic data of a loaded NewGRF.
Definition newgrf.h:128
Holder of the above structure.
Mapping between NewGRF and OpenTTD IDs.
Mapping of language data between a NewGRF and OpenTTD.
std::vector< Mapping > gender_map
Mapping of NewGRF and OpenTTD IDs for genders.
int GetReverseMapping(int openttd_id, bool gender) const
Get the mapping from OpenTTD's internal ID to the NewGRF supplied ID.
int plural_form
The plural form used for this language.
static const LanguageMap * GetLanguageMap(uint32_t grfid, uint8_t language_id)
Get the language map associated with a given NewGRF and language.
Definition newgrf.cpp:363
std::vector< Mapping > case_map
Mapping of NewGRF and OpenTTD IDs for cases.
int GetMapping(int newgrf_id, bool gender) const
Get the mapping from the NewGRF supplied ID to OpenTTD's internal ID.
void RotateTop4Words()
Rotate the top four words down: W1, W2, W3, W4 -> W4, W1, W2, W3.
StringControlCode type
The type of choice list.
int offset
The offset for the plural/gender form.
std::map< int, std::string > strings
Mapping of NewGRF supplied ID to the different strings in the choice list.
UnmappedChoiceList(StringControlCode type, int offset)
Initialise the mapping.
void Flush(const LanguageMap *lm, std::string &dest)
Flush this choice list into the destination string.
Definition of the game-calendar-timer.