OpenTTD Source 20251213-master-g1091fa6071
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
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
77static uint8_t _current_lang_id = GRFLX_ENGLISH;
78
85int LanguageMap::GetMapping(int newgrf_id, bool gender) const
86{
87 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
88 for (const Mapping &m : map) {
89 if (m.newgrf_id == newgrf_id) return m.openttd_id;
90 }
91 return -1;
92}
93
100int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
101{
102 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
103 for (const Mapping &m : map) {
104 if (m.openttd_id == openttd_id) return m.newgrf_id;
105 }
106 return -1;
107}
108
120
122 int offset;
123
125 std::map<int, std::string> strings;
126
132 void Flush(const LanguageMap *lm, std::string &dest)
133 {
134 if (this->strings.find(0) == this->strings.end()) {
135 /* In case of a (broken) NewGRF without a default,
136 * assume an empty string. */
137 GrfMsg(1, "choice list misses default value");
138 this->strings[0] = std::string();
139 }
140
141 StringBuilder builder(dest);
142
143 if (lm == nullptr) {
144 /* In case there is no mapping, just ignore everything but the default.
145 * A probable cause for this happening is when the language file has
146 * been removed by the user and as such no mapping could be made. */
147 builder += this->strings[0];
148 return;
149 }
150
151 builder.PutUtf8(this->type);
152
153 if (this->type == SCC_SWITCH_CASE) {
154 /*
155 * Format for case switch:
156 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
157 * Each LEN is printed using 2 bytes in big endian order.
158 */
159
160 /* "<NUM CASES>" */
161 int count = 0;
162 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
163 /* Count the ones we have a mapped string for. */
164 if (this->strings.find(lm->GetReverseMapping(i, false)) != this->strings.end()) count++;
165 }
166 builder.PutUint8(count);
167
168 auto add_case = [&](std::string_view str) {
169 /* "<LENn>" */
170 uint16_t len = ClampTo<uint16_t>(str.size());
171 builder.PutUint16LE(len);
172
173 /* "<STRINGn>" */
174 builder += str.substr(0, len);
175 };
176
177 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
178 /* Resolve the string we're looking for. */
179 int idx = lm->GetReverseMapping(i, false);
180 if (this->strings.find(idx) == this->strings.end()) continue;
181 auto &str = this->strings[idx];
182
183 /* "<CASEn>" */
184 builder.PutUint8(i + 1);
185
186 add_case(str);
187 }
188
189 /* "<STRINGDEFAULT>" */
190 add_case(this->strings[0]);
191 } else {
192 if (this->type == SCC_PLURAL_LIST) {
193 builder.PutUint8(lm->plural_form);
194 }
195
196 /*
197 * Format for choice list:
198 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
199 */
200
201 /* "<OFFSET>" */
202 builder.PutUint8(this->offset - 0x80);
203
204 /* "<NUM CHOICES>" */
205 int count = (this->type == SCC_GENDER_LIST ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
206 builder.PutUint8(count);
207
208 /* "<LENs>" */
209 for (int i = 0; i < count; i++) {
210 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
211 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
212 size_t len = str.size();
213 if (len > UINT8_MAX) GrfMsg(1, "choice list string is too long");
214 builder.PutUint8(ClampTo<uint8_t>(len));
215 }
216
217 /* "<STRINGs>" */
218 for (int i = 0; i < count; i++) {
219 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
220 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
221 uint8_t len = ClampTo<uint8_t>(str.size());
222 builder += str.substr(0, len);
223 }
224 }
225 }
226};
227
237std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool allow_newlines, std::string_view str, StringControlCode byte80)
238{
239 /* Empty input string? Nothing to do here. */
240 if (str.empty()) return {};
241
242 StringConsumer consumer(str);
243
244 /* Is this an unicode string? */
245 bool unicode = consumer.ReadUtf8If(NFO_UTF8_IDENTIFIER);
246
247 /* Helper variable for a possible (string) mapping of plural/gender and cases. */
248 std::optional<UnmappedChoiceList> mapping_pg, mapping_c;
249 std::optional<std::reference_wrapper<std::string>> dest_c;
250
251 std::string dest;
252 StringBuilder builder(dest);
253 while (consumer.AnyBytesLeft()) {
254 char32_t c;
255 if (auto u = unicode ? consumer.TryReadUtf8() : std::nullopt; u.has_value()) {
256 c = *u;
257 /* 'Magic' range of control codes. */
258 if (0xE000 <= c && c <= 0xE0FF) {
259 c -= 0xE000;
260 } else if (c >= 0x20) {
261 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
262 builder.PutUtf8(c);
263 continue;
264 }
265 } else {
266 c = consumer.ReadUint8(); // read as unsigned, otherwise integer promotion breaks it
267 }
268 assert(c <= 0xFF);
269 if (c == '\0') break;
270
271 switch (c) {
272 case 0x01:
273 consumer.SkipUint8();
274 builder.PutChar(' ');
275 break;
276 case 0x0A: break;
277 case 0x0D:
278 if (allow_newlines) {
279 builder.PutChar(0x0A);
280 } else {
281 GrfMsg(1, "Detected newline in string that does not allow one");
282 }
283 break;
284 case 0x0E: builder.PutUtf8(SCC_TINYFONT); break;
285 case 0x0F: builder.PutUtf8(SCC_BIGFONT); break;
286 case 0x1F:
287 consumer.SkipUint8();
288 consumer.SkipUint8();
289 builder.PutChar(' ');
290 break;
291 case 0x7B:
292 case 0x7C:
293 case 0x7D:
294 case 0x7E:
295 case 0x7F: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break;
296 case 0x80: builder.PutUtf8(byte80); break;
297 case 0x81:
298 {
299 uint16_t string = consumer.ReadUint16LE();
300 builder.PutUtf8(SCC_NEWGRF_STRINL);
301 builder.PutUtf8(MapGRFStringID(grfid, GRFStringID{string}));
302 break;
303 }
304 case 0x82:
305 case 0x83:
306 case 0x84: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_DATE_LONG + c - 0x82); break;
307 case 0x85: builder.PutUtf8(SCC_NEWGRF_DISCARD_WORD); break;
308 case 0x86: builder.PutUtf8(SCC_NEWGRF_ROTATE_TOP_4_WORDS); break;
309 case 0x87: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_VOLUME_LONG); break;
310 case 0x88: builder.PutUtf8(SCC_BLUE); break;
311 case 0x89: builder.PutUtf8(SCC_SILVER); break;
312 case 0x8A: builder.PutUtf8(SCC_GOLD); break;
313 case 0x8B: builder.PutUtf8(SCC_RED); break;
314 case 0x8C: builder.PutUtf8(SCC_PURPLE); break;
315 case 0x8D: builder.PutUtf8(SCC_LTBROWN); break;
316 case 0x8E: builder.PutUtf8(SCC_ORANGE); break;
317 case 0x8F: builder.PutUtf8(SCC_GREEN); break;
318 case 0x90: builder.PutUtf8(SCC_YELLOW); break;
319 case 0x91: builder.PutUtf8(SCC_DKGREEN); break;
320 case 0x92: builder.PutUtf8(SCC_CREAM); break;
321 case 0x93: builder.PutUtf8(SCC_BROWN); break;
322 case 0x94: builder.PutUtf8(SCC_WHITE); break;
323 case 0x95: builder.PutUtf8(SCC_LTBLUE); break;
324 case 0x96: builder.PutUtf8(SCC_GRAY); break;
325 case 0x97: builder.PutUtf8(SCC_DKBLUE); break;
326 case 0x98: builder.PutUtf8(SCC_BLACK); break;
327 case 0x9A:
328 {
329 uint8_t code = consumer.ReadUint8();
330 switch (code) {
331 case 0x00: goto string_end;
332 case 0x01: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_CURRENCY); break;
333 /* 0x02: ignore next colour byte is not supported. It works on the final
334 * string and as such hooks into the string drawing routine. At that
335 * point many things already happened, such as splitting up of strings
336 * when drawn over multiple lines or right-to-left translations, which
337 * make the behaviour peculiar, e.g. only happening at specific width
338 * of windows. Or we need to add another pass over the string to just
339 * support this. As such it is not implemented in OpenTTD. */
340 case 0x03:
341 {
342 uint16_t tmp = consumer.ReadUint16LE();
344 builder.PutUtf8(tmp);
345 break;
346 }
347 case 0x06: builder.PutUtf8(SCC_NEWGRF_PRINT_BYTE_HEX); break;
348 case 0x07: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_HEX); break;
349 case 0x08: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_HEX); break;
350 /* 0x09, 0x0A are TTDPatch internal use only string codes. */
351 case 0x0B: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_HEX); break;
352 case 0x0C: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_STATION_NAME); break;
353 case 0x0D: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG); break;
354 case 0x0E:
355 case 0x0F:
356 {
357 const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
358 int index = consumer.ReadUint8();
359 int mapped = lm != nullptr ? lm->GetMapping(index, code == 0x0E) : -1;
360 if (mapped >= 0) {
361 builder.PutUtf8(code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
362 builder.PutUtf8(code == 0x0E ? mapped : mapped + 1);
363 }
364 break;
365 }
366
367 case 0x10:
368 case 0x11:
369 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
370 if (code == 0x10) consumer.SkipUint8(); // Skip the index
371 GrfMsg(1, "choice list {} marker found when not expected", code == 0x10 ? "next" : "default");
372 break;
373 } else {
374 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
375 int index = (code == 0x10 ? consumer.ReadUint8() : 0);
376 if (mapping->strings.find(index) != mapping->strings.end()) {
377 GrfMsg(1, "duplicate choice list string, ignoring");
378 } else {
379 builder = StringBuilder(mapping->strings[index]);
380 if (!mapping_pg) dest_c = mapping->strings[index];
381 }
382 }
383 break;
384
385 case 0x12:
386 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
387 GrfMsg(1, "choice list end marker found when not expected");
388 } else {
389 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
390 auto &new_dest = mapping_pg && dest_c ? dest_c->get() : dest;
391 /* Now we can start flushing everything and clean everything up. */
392 mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id), new_dest);
393 if (!mapping_pg) dest_c.reset();
394 mapping.reset();
395
396 builder = StringBuilder(new_dest);
397 }
398 break;
399
400 case 0x13:
401 case 0x14:
402 case 0x15: {
403 auto &mapping = code == 0x14 ? mapping_c : mapping_pg;
404 /* Case mapping can have nested plural/gender mapping. Otherwise nesting is invalid. */
405 if (mapping.has_value() || mapping_pg.has_value()) {
406 GrfMsg(1, "choice lists can't be stacked, it's going to get messy now...");
407 if (code != 0x14) consumer.SkipUint8();
408 } else {
409 static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
410 mapping.emplace(mp[code - 0x13], code == 0x14 ? 0 : consumer.ReadUint8());
411 }
412 break;
413 }
414
415 case 0x16:
416 case 0x17:
417 case 0x18:
418 case 0x19:
419 case 0x1A:
420 case 0x1B:
421 case 0x1C:
422 case 0x1D:
423 case 0x1E:
424 builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_DATE_LONG + code - 0x16);
425 break;
426
427 case 0x1F: builder.PutUtf8(SCC_PUSH_COLOUR); break;
428 case 0x20: builder.PutUtf8(SCC_POP_COLOUR); break;
429
430 case 0x21: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_FORCE); break;
431
432 default:
433 GrfMsg(1, "missing handler for extended format code");
434 break;
435 }
436 break;
437 }
438
439 case 0x9B: builder.PutUtf8(SCC_TOWN); break;
440 case 0x9C: builder.PutUtf8(SCC_CITY); break;
441 case 0x9E: builder.PutUtf8(0x20AC); break; // Euro
442 case 0x9F: builder.PutUtf8(0x0178); break; // Y with diaeresis
443 case 0xA0: builder.PutUtf8(SCC_UP_ARROW); break;
444 case 0xAA: builder.PutUtf8(SCC_DOWN_ARROW); break;
445 case 0xAC: builder.PutUtf8(SCC_CHECKMARK); break;
446 case 0xAD: builder.PutUtf8(SCC_CROSS); break;
447 case 0xAF: builder.PutUtf8(SCC_RIGHT_ARROW); break;
448 case 0xB4: builder.PutUtf8(SCC_TRAIN); break;
449 case 0xB5: builder.PutUtf8(SCC_LORRY); break;
450 case 0xB6: builder.PutUtf8(SCC_BUS); break;
451 case 0xB7: builder.PutUtf8(SCC_PLANE); break;
452 case 0xB8: builder.PutUtf8(SCC_SHIP); break;
453 case 0xB9: builder.PutUtf8(SCC_SUPERSCRIPT_M1); break;
454 case 0xBC: builder.PutUtf8(SCC_SMALL_UP_ARROW); break;
455 case 0xBD: builder.PutUtf8(SCC_SMALL_DOWN_ARROW); break;
456 default:
457 /* Validate any unhandled character */
458 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
459 builder.PutUtf8(c);
460 break;
461 }
462 }
463
464string_end:
465 if (mapping_pg.has_value() || mapping_c.has_value()) {
466 GrfMsg(1, "choice list was incomplete, the whole list is ignored");
467 }
468
469 return dest;
470}
471
478static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
479{
480 /* Loop through all languages and see if we can replace a string */
481 for (auto &text : list) {
482 if (text.langid == langid) {
483 text.text = text_to_add;
484 return;
485 }
486 }
487
488 /* If a string wasn't replaced, then we must append the new string */
489 list.emplace_back(langid, std::string{text_to_add});
490}
491
501void AddGRFTextToList(GRFTextList &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
502{
503 AddGRFTextToList(list, langid, TranslateTTDPatchCodes(grfid, langid, allow_newlines, text_to_add));
504}
505
515void AddGRFTextToList(GRFTextWrapper &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
516{
517 if (list == nullptr) list = std::make_shared<GRFTextList>();
518 AddGRFTextToList(*list, langid, grfid, allow_newlines, text_to_add);
519}
520
527void AddGRFTextToList(GRFTextWrapper &list, std::string_view text_to_add)
528{
529 if (list == nullptr) list = std::make_shared<GRFTextList>();
530 AddGRFTextToList(*list, GRFLX_UNSPECIFIED, text_to_add);
531}
532
536StringID 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)
537{
538 /* When working with the old language scheme (grf_version is less than 7) and
539 * English or American is among the set bits, simply add it as English in
540 * the new scheme, i.e. as langid = 1.
541 * If English is set, it is pretty safe to assume the translations are not
542 * actually translated.
543 */
544 if (!new_scheme) {
545 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
546 langid_to_add = GRFLX_ENGLISH;
547 } else {
548 StringID ret = STR_EMPTY;
549 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, true, allow_newlines, text_to_add, def_string);
550 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, true, allow_newlines, text_to_add, def_string);
551 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, true, allow_newlines, text_to_add, def_string);
552 return ret;
553 }
554 }
555
556 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
557 if (it == std::end(_grf_text)) {
558 /* Too many strings allocated, return empty. */
559 if (_grf_text.size() == TAB_SIZE_NEWGRF) return STR_EMPTY;
560
561 /* We didn't find our stringid and grfid in the list, allocate a new id. */
562 it = _grf_text.emplace(std::end(_grf_text));
563 it->grfid = grfid;
564 it->stringid = stringid;
565 it->def_string = def_string;
566 }
567 StringIndexInTab id(it - std::begin(_grf_text));
568
569 std::string newtext = TranslateTTDPatchCodes(grfid, langid_to_add, allow_newlines, text_to_add);
570 AddGRFTextToList(it->textholder, langid_to_add, newtext);
571
572 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));
573
575}
576
580StringID GetGRFStringID(uint32_t grfid, GRFStringID stringid)
581{
582 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
583 if (it != std::end(_grf_text)) {
584 StringIndexInTab id(it - std::begin(_grf_text));
586 }
587
588 return STR_UNDEFINED;
589}
590
591
599std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextList &text_list)
600{
601 std::optional<std::string_view> default_text;
602
603 /* Search the list of lang-strings of this stringid for current lang */
604 for (const auto &text : text_list) {
605 if (text.langid == _current_lang_id) return text.text;
606
607 /* If the current string is English or American, set it as the
608 * fallback language if the specific language isn't available. */
609 if (text.langid == GRFLX_UNSPECIFIED || (!default_text.has_value() && (text.langid == GRFLX_ENGLISH || text.langid == GRFLX_AMERICAN))) {
610 default_text = text.text;
611 }
612 }
613
614 return default_text;
615}
616
624std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextWrapper &text)
625{
626 return text ? GetGRFStringFromGRFText(*text) : std::nullopt;
627}
628
632std::string_view GetGRFStringPtr(StringIndexInTab stringid)
633{
634 assert(stringid.base() < _grf_text.size());
635 assert(_grf_text[stringid].grfid != 0);
636
637 auto str = GetGRFStringFromGRFText(_grf_text[stringid].textholder);
638 if (str.has_value()) return *str;
639
640 /* Use the default string ID if the fallback string isn't available */
641 return GetStringPtr(_grf_text[stringid].def_string);
642}
643
652void SetCurrentGrfLangID(uint8_t language_id)
653{
654 _current_lang_id = language_id;
655}
656
657bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
658{
659 if (grf_version < 7) {
660 switch (_current_lang_id) {
661 case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0;
662 case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0;
663 case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
664 default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
665 }
666 }
667
668 return (lang_id == _current_lang_id || lang_id == GRFLX_UNSPECIFIED);
669}
670
676{
677 _grf_text.clear();
678}
679
681private:
682 std::vector<uint8_t> stack;
683public:
684 const GRFFile *grffile = nullptr;
685
686 TextRefStack(const GRFFile *grffile, std::span<const int32_t> textstack) : grffile(grffile)
687 {
688 this->stack.reserve(textstack.size() * 4 + 6); // for translations it is reasonable to push 3 word and rotate them
689 for (int32_t value : std::ranges::reverse_view(textstack)) {
690 this->PushDWord(value);
691 }
692 }
693
694 uint8_t PopUnsignedByte()
695 {
696 if (this->stack.empty()) return 0;
697 auto res = this->stack.back();
698 this->stack.pop_back();
699 return res;
700 }
701 int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
702
703 uint16_t PopUnsignedWord()
704 {
705 uint16_t val = this->PopUnsignedByte();
706 return val | (this->PopUnsignedByte() << 8);
707 }
708 int16_t PopSignedWord() { return (int32_t)this->PopUnsignedWord(); }
709
710 uint32_t PopUnsignedDWord()
711 {
712 uint32_t val = this->PopUnsignedWord();
713 return val | (this->PopUnsignedWord() << 16);
714 }
715 int32_t PopSignedDWord() { return (int32_t)this->PopUnsignedDWord(); }
716
717 uint64_t PopUnsignedQWord()
718 {
719 uint64_t val = this->PopUnsignedDWord();
720 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
721 }
722 int64_t PopSignedQWord() { return (int64_t)this->PopUnsignedQWord(); }
723
726 {
727 auto w1 = this->PopUnsignedWord();
728 auto w2 = this->PopUnsignedWord();
729 auto w3 = this->PopUnsignedWord();
730 auto w4 = this->PopUnsignedWord();
731 this->PushWord(w3);
732 this->PushWord(w2);
733 this->PushWord(w1);
734 this->PushWord(w4);
735 }
736
737 void PushByte(uint8_t b)
738 {
739 this->stack.push_back(b);
740 }
741
742 void PushWord(uint16_t w)
743 {
744 this->PushByte(GB(w, 8, 8));
745 this->PushByte(GB(w, 0, 8));
746 }
747
748 void PushDWord(uint32_t dw)
749 {
750 this->PushWord(GB(dw, 16, 16));
751 this->PushWord(GB(dw, 0, 16));
752 }
753};
754
755static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params);
756
764static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector<StringParameter> &params)
765{
766 /* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
767 * After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
768 switch (scc) {
769 default: return;
770
771 case SCC_PLURAL_LIST:
772 consumer.SkipUint8(); // plural form
773 [[fallthrough]];
774 case SCC_GENDER_LIST: {
775 consumer.SkipUint8(); // offset
776 /* plural and gender choices cannot contain any string commands, so just skip the whole thing */
777 uint num = consumer.ReadUint8();
778 uint total_len = 0;
779 for (uint i = 0; i != num; i++) {
780 total_len += consumer.ReadUint8();
781 }
782 consumer.Skip(total_len);
783 break;
784 }
785
786 case SCC_SWITCH_CASE: {
787 /* skip all cases and continue with default case */
788 uint num = consumer.ReadUint8();
789 for (uint i = 0; i != num; i++) {
790 consumer.SkipUint8();
791 auto len = consumer.ReadUint16LE();
792 consumer.Skip(len);
793 }
794 consumer.SkipUint16LE(); // length of default
795 break;
796 }
797
798 case SCC_GENDER_INDEX:
799 case SCC_SET_CASE:
800 consumer.SkipUint8();
801 break;
802
803 case SCC_ARG_INDEX:
804 NOT_REACHED();
805 break;
806
807 case SCC_NEWGRF_PRINT_BYTE_SIGNED: params.emplace_back(stack.PopSignedByte()); break;
808 case SCC_NEWGRF_PRINT_QWORD_CURRENCY: params.emplace_back(stack.PopSignedQWord()); break;
809
811 case SCC_NEWGRF_PRINT_DWORD_SIGNED: params.emplace_back(stack.PopSignedDWord()); break;
812
813 case SCC_NEWGRF_PRINT_BYTE_HEX: params.emplace_back(stack.PopUnsignedByte()); break;
814 case SCC_NEWGRF_PRINT_QWORD_HEX: params.emplace_back(stack.PopUnsignedQWord()); break;
815
819 case SCC_NEWGRF_PRINT_WORD_SIGNED: params.emplace_back(stack.PopSignedWord()); break;
820
826 case SCC_NEWGRF_PRINT_WORD_UNSIGNED: params.emplace_back(stack.PopUnsignedWord()); break;
827
831 case SCC_NEWGRF_PRINT_DWORD_HEX: params.emplace_back(stack.PopUnsignedDWord()); break;
832
833 /* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
835 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: params.emplace_back(CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR + stack.PopUnsignedWord()); break;
836
837 case SCC_NEWGRF_DISCARD_WORD: stack.PopUnsignedWord(); break;
838
840 case SCC_NEWGRF_PUSH_WORD: stack.PushWord(consumer.ReadUtf8(0)); break;
841
845 params.emplace_back(GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile));
846 params.emplace_back(stack.PopUnsignedWord());
847 break;
848
849 case SCC_NEWGRF_STRINL: {
850 StringID stringid = consumer.ReadUtf8(STR_NULL);
851 /* We also need to handle the substring's stack usage. */
852 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
853 break;
854 }
855
857 StringID stringid = MapGRFStringID(stack.grffile->grfid, GRFStringID{stack.PopUnsignedWord()});
858 params.emplace_back(stringid);
859 /* We also need to handle the substring's stack usage. */
860 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
861 break;
862 }
863
865 CargoType cargo = GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile);
866 params.emplace_back(cargo < NUM_CARGO ? 1ULL << cargo : 0);
867 break;
868 }
869 }
870}
871
878char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
879{
880 switch (scc) {
881 default:
882 return scc;
883
888 return SCC_COMMA;
889
894 return SCC_HEX;
895
898 return SCC_CURRENCY_LONG;
899
902 return SCC_DATE_LONG;
903
906 return SCC_DATE_SHORT;
907
909 return SCC_VELOCITY;
910
912 return SCC_VOLUME_LONG;
913
915 return SCC_VOLUME_SHORT;
916
918 return SCC_WEIGHT_LONG;
919
921 return SCC_WEIGHT_SHORT;
922
924 return SCC_POWER;
925
927 return SCC_FORCE;
928
930 return SCC_CARGO_LONG;
931
933 return SCC_CARGO_SHORT;
934
936 return SCC_CARGO_TINY;
937
939 return SCC_CARGO_LIST;
940
942 return SCC_STATION_NAME;
943
944 /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
946 consumer.SkipUtf8();
947 return 0;
948
951 return 0;
952 }
953}
954
961static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params)
962{
963 StringConsumer consumer(str);
964 while (consumer.AnyBytesLeft()) {
965 char32_t scc = consumer.ReadUtf8();
966 ProcessNewGRFStringControlCode(scc, consumer, stack, params);
967 }
968}
969
977std::vector<StringParameter> GetGRFStringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span<const int32_t> textstack)
978{
979 if (stringid == INVALID_STRING_ID) return {};
980
981 auto str = GetStringPtr(stringid);
982
983 std::vector<StringParameter> params;
984 params.reserve(20);
985
986 TextRefStack stack{grffile, textstack};
987 HandleNewGRFStringControlCodes(str, stack, params);
988
989 return params;
990}
991
999std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span<const int32_t> textstack)
1000{
1001 StringID stringid = GetGRFStringID(grffile->grfid, grfstringid);
1002 auto params = GetGRFStringTextStackParameters(grffile, stringid, textstack);
1003 return GetStringWithArgs(stringid, params);
1004}
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:21
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:73
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
The date of the first day of the 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
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.
void SetCurrentGrfLangID(uint8_t language_id)
Equivalence Setter function between game and newgrf langID.
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.
StringID 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)
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.
static uint8_t _current_lang_id
by default, english is used.
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.
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:373
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.
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:114
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:357
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.
uint8_t num_genders
the number of genders of this language
Definition language.h:53
uint8_t num_cases
the number of cases of this language
Definition language.h:54
void RotateTop4Words()
Rotate the top four words down: W1, W2, W3, W4 -> W4, W1, W2, W3.
Helper structure for mapping choice lists.
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.