OpenTTD Source 20250716-master-g6b6caa6fa8
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 <http://www.gnu.org/licenses/>.
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"
29#include "core/alloc_type.hpp"
32#include "language.h"
33#include <ranges>
34
35#include "table/strings.h"
36#include "table/control_codes.h"
37
38#include "safeguards.h"
39
45enum GRFBaseLanguages : uint8_t {
46 GRFLB_AMERICAN = 0x01,
47 GRFLB_ENGLISH = 0x02,
48 GRFLB_GERMAN = 0x04,
49 GRFLB_FRENCH = 0x08,
50 GRFLB_SPANISH = 0x10,
51 GRFLB_GENERIC = 0x80,
52};
53
54enum GRFExtendedLanguages : uint8_t {
55 GRFLX_AMERICAN = 0x00,
56 GRFLX_ENGLISH = 0x01,
57 GRFLX_GERMAN = 0x02,
58 GRFLX_FRENCH = 0x03,
59 GRFLX_SPANISH = 0x04,
60 GRFLX_UNSPECIFIED = 0x7F,
61};
62
63
70 GRFTextList textholder;
71 StringID def_string;
72 uint32_t grfid;
73 GRFStringID stringid;
74};
75
76
78static uint8_t _current_lang_id = GRFLX_ENGLISH;
79
86int LanguageMap::GetMapping(int newgrf_id, bool gender) const
87{
88 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
89 for (const Mapping &m : map) {
90 if (m.newgrf_id == newgrf_id) return m.openttd_id;
91 }
92 return -1;
93}
94
101int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
102{
103 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
104 for (const Mapping &m : map) {
105 if (m.openttd_id == openttd_id) return m.newgrf_id;
106 }
107 return -1;
108}
109
121
123 int offset;
124
126 std::map<int, std::string> strings;
127
133 void Flush(const LanguageMap *lm, std::string &dest)
134 {
135 if (this->strings.find(0) == this->strings.end()) {
136 /* In case of a (broken) NewGRF without a default,
137 * assume an empty string. */
138 GrfMsg(1, "choice list misses default value");
139 this->strings[0] = std::string();
140 }
141
142 StringBuilder builder(dest);
143
144 if (lm == nullptr) {
145 /* In case there is no mapping, just ignore everything but the default.
146 * A probable cause for this happening is when the language file has
147 * been removed by the user and as such no mapping could be made. */
148 builder += this->strings[0];
149 return;
150 }
151
152 builder.PutUtf8(this->type);
153
154 if (this->type == SCC_SWITCH_CASE) {
155 /*
156 * Format for case switch:
157 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
158 * Each LEN is printed using 2 bytes in big endian order.
159 */
160
161 /* "<NUM CASES>" */
162 int count = 0;
163 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
164 /* Count the ones we have a mapped string for. */
165 if (this->strings.find(lm->GetReverseMapping(i, false)) != this->strings.end()) count++;
166 }
167 builder.PutUint8(count);
168
169 auto add_case = [&](std::string_view str) {
170 /* "<LENn>" */
171 uint16_t len = ClampTo<uint16_t>(str.size());
172 builder.PutUint16LE(len);
173
174 /* "<STRINGn>" */
175 builder += str.substr(0, len);
176 };
177
178 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
179 /* Resolve the string we're looking for. */
180 int idx = lm->GetReverseMapping(i, false);
181 if (this->strings.find(idx) == this->strings.end()) continue;
182 auto &str = this->strings[idx];
183
184 /* "<CASEn>" */
185 builder.PutUint8(i + 1);
186
187 add_case(str);
188 }
189
190 /* "<STRINGDEFAULT>" */
191 add_case(this->strings[0]);
192 } else {
193 if (this->type == SCC_PLURAL_LIST) {
194 builder.PutUint8(lm->plural_form);
195 }
196
197 /*
198 * Format for choice list:
199 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
200 */
201
202 /* "<OFFSET>" */
203 builder.PutUint8(this->offset - 0x80);
204
205 /* "<NUM CHOICES>" */
206 int count = (this->type == SCC_GENDER_LIST ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
207 builder.PutUint8(count);
208
209 /* "<LENs>" */
210 for (int i = 0; i < count; i++) {
211 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
212 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
213 size_t len = str.size();
214 if (len > UINT8_MAX) GrfMsg(1, "choice list string is too long");
215 builder.PutUint8(ClampTo<uint8_t>(len));
216 }
217
218 /* "<STRINGs>" */
219 for (int i = 0; i < count; i++) {
220 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
221 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
222 uint8_t len = ClampTo<uint8_t>(str.size());
223 builder += str.substr(0, len);
224 }
225 }
226 }
227};
228
238std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool allow_newlines, std::string_view str, StringControlCode byte80)
239{
240 /* Empty input string? Nothing to do here. */
241 if (str.empty()) return {};
242
243 StringConsumer consumer(str);
244
245 /* Is this an unicode string? */
246 bool unicode = consumer.ReadUtf8If(NFO_UTF8_IDENTIFIER);
247
248 /* Helper variable for a possible (string) mapping of plural/gender and cases. */
249 std::optional<UnmappedChoiceList> mapping_pg, mapping_c;
250 std::optional<std::reference_wrapper<std::string>> dest_c;
251
252 std::string dest;
253 StringBuilder builder(dest);
254 while (consumer.AnyBytesLeft()) {
255 char32_t c;
256 if (auto u = unicode ? consumer.TryReadUtf8() : std::nullopt; u.has_value()) {
257 c = *u;
258 /* 'Magic' range of control codes. */
259 if (0xE000 <= c && c <= 0xE0FF) {
260 c -= 0xE000;
261 } else if (c >= 0x20) {
262 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
263 builder.PutUtf8(c);
264 continue;
265 }
266 } else {
267 c = consumer.ReadUint8(); // read as unsigned, otherwise integer promotion breaks it
268 }
269 assert(c <= 0xFF);
270 if (c == '\0') break;
271
272 switch (c) {
273 case 0x01:
274 consumer.SkipUint8();
275 builder.PutChar(' ');
276 break;
277 case 0x0A: break;
278 case 0x0D:
279 if (allow_newlines) {
280 builder.PutChar(0x0A);
281 } else {
282 GrfMsg(1, "Detected newline in string that does not allow one");
283 }
284 break;
285 case 0x0E: builder.PutUtf8(SCC_TINYFONT); break;
286 case 0x0F: builder.PutUtf8(SCC_BIGFONT); break;
287 case 0x1F:
288 consumer.SkipUint8();
289 consumer.SkipUint8();
290 builder.PutChar(' ');
291 break;
292 case 0x7B:
293 case 0x7C:
294 case 0x7D:
295 case 0x7E:
296 case 0x7F: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break;
297 case 0x80: builder.PutUtf8(byte80); break;
298 case 0x81:
299 {
300 uint16_t string = consumer.ReadUint16LE();
301 builder.PutUtf8(SCC_NEWGRF_STRINL);
302 builder.PutUtf8(MapGRFStringID(grfid, GRFStringID{string}));
303 break;
304 }
305 case 0x82:
306 case 0x83:
307 case 0x84: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_DATE_LONG + c - 0x82); break;
308 case 0x85: builder.PutUtf8(SCC_NEWGRF_DISCARD_WORD); break;
309 case 0x86: builder.PutUtf8(SCC_NEWGRF_ROTATE_TOP_4_WORDS); break;
310 case 0x87: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_VOLUME_LONG); break;
311 case 0x88: builder.PutUtf8(SCC_BLUE); break;
312 case 0x89: builder.PutUtf8(SCC_SILVER); break;
313 case 0x8A: builder.PutUtf8(SCC_GOLD); break;
314 case 0x8B: builder.PutUtf8(SCC_RED); break;
315 case 0x8C: builder.PutUtf8(SCC_PURPLE); break;
316 case 0x8D: builder.PutUtf8(SCC_LTBROWN); break;
317 case 0x8E: builder.PutUtf8(SCC_ORANGE); break;
318 case 0x8F: builder.PutUtf8(SCC_GREEN); break;
319 case 0x90: builder.PutUtf8(SCC_YELLOW); break;
320 case 0x91: builder.PutUtf8(SCC_DKGREEN); break;
321 case 0x92: builder.PutUtf8(SCC_CREAM); break;
322 case 0x93: builder.PutUtf8(SCC_BROWN); break;
323 case 0x94: builder.PutUtf8(SCC_WHITE); break;
324 case 0x95: builder.PutUtf8(SCC_LTBLUE); break;
325 case 0x96: builder.PutUtf8(SCC_GRAY); break;
326 case 0x97: builder.PutUtf8(SCC_DKBLUE); break;
327 case 0x98: builder.PutUtf8(SCC_BLACK); break;
328 case 0x9A:
329 {
330 uint8_t code = consumer.ReadUint8();
331 switch (code) {
332 case 0x00: goto string_end;
333 case 0x01: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_CURRENCY); break;
334 /* 0x02: ignore next colour byte is not supported. It works on the final
335 * string and as such hooks into the string drawing routine. At that
336 * point many things already happened, such as splitting up of strings
337 * when drawn over multiple lines or right-to-left translations, which
338 * make the behaviour peculiar, e.g. only happening at specific width
339 * of windows. Or we need to add another pass over the string to just
340 * support this. As such it is not implemented in OpenTTD. */
341 case 0x03:
342 {
343 uint16_t tmp = consumer.ReadUint16LE();
345 builder.PutUtf8(tmp);
346 break;
347 }
348 case 0x06: builder.PutUtf8(SCC_NEWGRF_PRINT_BYTE_HEX); break;
349 case 0x07: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_HEX); break;
350 case 0x08: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_HEX); break;
351 /* 0x09, 0x0A are TTDPatch internal use only string codes. */
352 case 0x0B: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_HEX); break;
353 case 0x0C: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_STATION_NAME); break;
354 case 0x0D: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG); break;
355 case 0x0E:
356 case 0x0F:
357 {
358 const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
359 int index = consumer.ReadUint8();
360 int mapped = lm != nullptr ? lm->GetMapping(index, code == 0x0E) : -1;
361 if (mapped >= 0) {
362 builder.PutUtf8(code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
363 builder.PutUtf8(code == 0x0E ? mapped : mapped + 1);
364 }
365 break;
366 }
367
368 case 0x10:
369 case 0x11:
370 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
371 if (code == 0x10) consumer.SkipUint8(); // Skip the index
372 GrfMsg(1, "choice list {} marker found when not expected", code == 0x10 ? "next" : "default");
373 break;
374 } else {
375 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
376 int index = (code == 0x10 ? consumer.ReadUint8() : 0);
377 if (mapping->strings.find(index) != mapping->strings.end()) {
378 GrfMsg(1, "duplicate choice list string, ignoring");
379 } else {
380 builder = StringBuilder(mapping->strings[index]);
381 if (!mapping_pg) dest_c = mapping->strings[index];
382 }
383 }
384 break;
385
386 case 0x12:
387 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
388 GrfMsg(1, "choice list end marker found when not expected");
389 } else {
390 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
391 auto &new_dest = mapping_pg && dest_c ? dest_c->get() : dest;
392 /* Now we can start flushing everything and clean everything up. */
393 mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id), new_dest);
394 if (!mapping_pg) dest_c.reset();
395 mapping.reset();
396
397 builder = StringBuilder(new_dest);
398 }
399 break;
400
401 case 0x13:
402 case 0x14:
403 case 0x15: {
404 auto &mapping = code == 0x14 ? mapping_c : mapping_pg;
405 /* Case mapping can have nested plural/gender mapping. Otherwise nesting is invalid. */
406 if (mapping.has_value() || mapping_pg.has_value()) {
407 GrfMsg(1, "choice lists can't be stacked, it's going to get messy now...");
408 if (code != 0x14) consumer.SkipUint8();
409 } else {
410 static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
411 mapping.emplace(mp[code - 0x13], code == 0x14 ? 0 : consumer.ReadUint8());
412 }
413 break;
414 }
415
416 case 0x16:
417 case 0x17:
418 case 0x18:
419 case 0x19:
420 case 0x1A:
421 case 0x1B:
422 case 0x1C:
423 case 0x1D:
424 case 0x1E:
425 builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_DATE_LONG + code - 0x16);
426 break;
427
428 case 0x1F: builder.PutUtf8(SCC_PUSH_COLOUR); break;
429 case 0x20: builder.PutUtf8(SCC_POP_COLOUR); break;
430
431 case 0x21: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_FORCE); break;
432
433 default:
434 GrfMsg(1, "missing handler for extended format code");
435 break;
436 }
437 break;
438 }
439
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
535StringID 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)
536{
537 /* When working with the old language scheme (grf_version is less than 7) and
538 * English or American is among the set bits, simply add it as English in
539 * the new scheme, i.e. as langid = 1.
540 * If English is set, it is pretty safe to assume the translations are not
541 * actually translated.
542 */
543 if (!new_scheme) {
544 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
545 langid_to_add = GRFLX_ENGLISH;
546 } else {
547 StringID ret = STR_EMPTY;
548 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, true, allow_newlines, text_to_add, def_string);
549 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, true, allow_newlines, text_to_add, def_string);
550 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, true, allow_newlines, text_to_add, def_string);
551 return ret;
552 }
553 }
554
555 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
556 if (it == std::end(_grf_text)) {
557 /* Too many strings allocated, return empty. */
558 if (_grf_text.size() == TAB_SIZE_NEWGRF) return STR_EMPTY;
559
560 /* We didn't find our stringid and grfid in the list, allocate a new id. */
561 it = _grf_text.emplace(std::end(_grf_text));
562 it->grfid = grfid;
563 it->stringid = stringid;
564 it->def_string = def_string;
565 }
566 StringIndexInTab id(it - std::begin(_grf_text));
567
568 std::string newtext = TranslateTTDPatchCodes(grfid, langid_to_add, allow_newlines, text_to_add);
569 AddGRFTextToList(it->textholder, langid_to_add, newtext);
570
571 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));
572
574}
575
579StringID GetGRFStringID(uint32_t grfid, GRFStringID stringid)
580{
581 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
582 if (it != std::end(_grf_text)) {
583 StringIndexInTab id(it - std::begin(_grf_text));
585 }
586
587 return STR_UNDEFINED;
588}
589
590
598std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextList &text_list)
599{
600 std::optional<std::string_view> default_text;
601
602 /* Search the list of lang-strings of this stringid for current lang */
603 for (const auto &text : text_list) {
604 if (text.langid == _current_lang_id) return text.text;
605
606 /* If the current string is English or American, set it as the
607 * fallback language if the specific language isn't available. */
608 if (text.langid == GRFLX_UNSPECIFIED || (!default_text.has_value() && (text.langid == GRFLX_ENGLISH || text.langid == GRFLX_AMERICAN))) {
609 default_text = text.text;
610 }
611 }
612
613 return default_text;
614}
615
623std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextWrapper &text)
624{
625 return text ? GetGRFStringFromGRFText(*text) : std::nullopt;
626}
627
631std::string_view GetGRFStringPtr(StringIndexInTab stringid)
632{
633 assert(stringid.base() < _grf_text.size());
634 assert(_grf_text[stringid].grfid != 0);
635
636 auto str = GetGRFStringFromGRFText(_grf_text[stringid].textholder);
637 if (str.has_value()) return *str;
638
639 /* Use the default string ID if the fallback string isn't available */
640 return GetStringPtr(_grf_text[stringid].def_string);
641}
642
651void SetCurrentGrfLangID(uint8_t language_id)
652{
653 _current_lang_id = language_id;
654}
655
656bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
657{
658 if (grf_version < 7) {
659 switch (_current_lang_id) {
660 case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0;
661 case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0;
662 case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
663 default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
664 }
665 }
666
667 return (lang_id == _current_lang_id || lang_id == GRFLX_UNSPECIFIED);
668}
669
675{
676 _grf_text.clear();
677}
678
680private:
681 std::vector<uint8_t> stack;
682public:
683 const GRFFile *grffile = nullptr;
684
685 TextRefStack(const GRFFile *grffile, std::span<const int32_t> textstack) : grffile(grffile)
686 {
687 this->stack.reserve(textstack.size() * 4 + 6); // for translations it is reasonable to push 3 word and rotate them
688 for (int32_t value : std::ranges::reverse_view(textstack)) {
689 this->PushDWord(value);
690 }
691 }
692
693 uint8_t PopUnsignedByte()
694 {
695 if (this->stack.empty()) return 0;
696 auto res = this->stack.back();
697 this->stack.pop_back();
698 return res;
699 }
700 int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
701
702 uint16_t PopUnsignedWord()
703 {
704 uint16_t val = this->PopUnsignedByte();
705 return val | (this->PopUnsignedByte() << 8);
706 }
707 int16_t PopSignedWord() { return (int32_t)this->PopUnsignedWord(); }
708
709 uint32_t PopUnsignedDWord()
710 {
711 uint32_t val = this->PopUnsignedWord();
712 return val | (this->PopUnsignedWord() << 16);
713 }
714 int32_t PopSignedDWord() { return (int32_t)this->PopUnsignedDWord(); }
715
716 uint64_t PopUnsignedQWord()
717 {
718 uint64_t val = this->PopUnsignedDWord();
719 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
720 }
721 int64_t PopSignedQWord() { return (int64_t)this->PopUnsignedQWord(); }
722
725 {
726 auto w1 = this->PopUnsignedWord();
727 auto w2 = this->PopUnsignedWord();
728 auto w3 = this->PopUnsignedWord();
729 auto w4 = this->PopUnsignedWord();
730 this->PushWord(w3);
731 this->PushWord(w2);
732 this->PushWord(w1);
733 this->PushWord(w4);
734 }
735
736 void PushByte(uint8_t b)
737 {
738 this->stack.push_back(b);
739 }
740
741 void PushWord(uint16_t w)
742 {
743 this->PushByte(GB(w, 8, 8));
744 this->PushByte(GB(w, 0, 8));
745 }
746
747 void PushDWord(uint32_t dw)
748 {
749 this->PushWord(GB(dw, 16, 16));
750 this->PushWord(GB(dw, 0, 16));
751 }
752};
753
754static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params);
755
763static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector<StringParameter> &params)
764{
765 /* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
766 * After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
767 switch (scc) {
768 default: return;
769
770 case SCC_PLURAL_LIST:
771 consumer.SkipUint8(); // plural form
772 [[fallthrough]];
773 case SCC_GENDER_LIST: {
774 consumer.SkipUint8(); // offset
775 /* plural and gender choices cannot contain any string commands, so just skip the whole thing */
776 uint num = consumer.ReadUint8();
777 uint total_len = 0;
778 for (uint i = 0; i != num; i++) {
779 total_len += consumer.ReadUint8();
780 }
781 consumer.Skip(total_len);
782 break;
783 }
784
785 case SCC_SWITCH_CASE: {
786 /* skip all cases and continue with default case */
787 uint num = consumer.ReadUint8();
788 for (uint i = 0; i != num; i++) {
789 consumer.SkipUint8();
790 auto len = consumer.ReadUint16LE();
791 consumer.Skip(len);
792 }
793 consumer.SkipUint16LE(); // length of default
794 break;
795 }
796
797 case SCC_GENDER_INDEX:
798 case SCC_SET_CASE:
799 consumer.SkipUint8();
800 break;
801
802 case SCC_ARG_INDEX:
803 NOT_REACHED();
804 break;
805
806 case SCC_NEWGRF_PRINT_BYTE_SIGNED: params.emplace_back(stack.PopSignedByte()); break;
807 case SCC_NEWGRF_PRINT_QWORD_CURRENCY: params.emplace_back(stack.PopSignedQWord()); break;
808
810 case SCC_NEWGRF_PRINT_DWORD_SIGNED: params.emplace_back(stack.PopSignedDWord()); break;
811
812 case SCC_NEWGRF_PRINT_BYTE_HEX: params.emplace_back(stack.PopUnsignedByte()); break;
813 case SCC_NEWGRF_PRINT_QWORD_HEX: params.emplace_back(stack.PopUnsignedQWord()); break;
814
818 case SCC_NEWGRF_PRINT_WORD_SIGNED: params.emplace_back(stack.PopSignedWord()); break;
819
825 case SCC_NEWGRF_PRINT_WORD_UNSIGNED: params.emplace_back(stack.PopUnsignedWord()); break;
826
830 case SCC_NEWGRF_PRINT_DWORD_HEX: params.emplace_back(stack.PopUnsignedDWord()); break;
831
832 /* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
834 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: params.emplace_back(CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR + stack.PopUnsignedWord()); break;
835
836 case SCC_NEWGRF_DISCARD_WORD: stack.PopUnsignedWord(); break;
837
839 case SCC_NEWGRF_PUSH_WORD: stack.PushWord(consumer.ReadUtf8(0)); break;
840
844 params.emplace_back(GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile));
845 params.emplace_back(stack.PopUnsignedWord());
846 break;
847
848 case SCC_NEWGRF_STRINL: {
849 StringID stringid = consumer.ReadUtf8(STR_NULL);
850 /* We also need to handle the substring's stack usage. */
851 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
852 break;
853 }
854
856 StringID stringid = MapGRFStringID(stack.grffile->grfid, GRFStringID{stack.PopUnsignedWord()});
857 params.emplace_back(stringid);
858 /* We also need to handle the substring's stack usage. */
859 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
860 break;
861 }
862
864 CargoType cargo = GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile);
865 params.emplace_back(cargo < NUM_CARGO ? 1ULL << cargo : 0);
866 break;
867 }
868 }
869}
870
877char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
878{
879 switch (scc) {
880 default:
881 return scc;
882
887 return SCC_COMMA;
888
893 return SCC_HEX;
894
897 return SCC_CURRENCY_LONG;
898
901 return SCC_DATE_LONG;
902
905 return SCC_DATE_SHORT;
906
908 return SCC_VELOCITY;
909
911 return SCC_VOLUME_LONG;
912
914 return SCC_VOLUME_SHORT;
915
917 return SCC_WEIGHT_LONG;
918
920 return SCC_WEIGHT_SHORT;
921
923 return SCC_POWER;
924
926 return SCC_FORCE;
927
929 return SCC_CARGO_LONG;
930
932 return SCC_CARGO_SHORT;
933
935 return SCC_CARGO_TINY;
936
938 return SCC_CARGO_LIST;
939
941 return SCC_STATION_NAME;
942
943 /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
945 consumer.SkipUtf8();
946 return 0;
947
950 return 0;
951 }
952}
953
960static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params)
961{
962 StringConsumer consumer(str);
963 while (consumer.AnyBytesLeft()) {
964 char32_t scc = consumer.ReadUtf8();
965 ProcessNewGRFStringControlCode(scc, consumer, stack, params);
966 }
967}
968
976std::vector<StringParameter> GetGRFSringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span<const int32_t> textstack)
977{
978 if (stringid == INVALID_STRING_ID) return {};
979
980 auto str = GetStringPtr(stringid);
981
982 std::vector<StringParameter> params;
983 params.reserve(20);
984
985 TextRefStack stack{grffile, textstack};
986 HandleNewGRFStringControlCodes(str, stack, params);
987
988 return params;
989}
990
998std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span<const int32_t> textstack)
999{
1000 StringID stringid = GetGRFStringID(grffile->grfid, grfstringid);
1001 auto params = GetGRFSringTextStackParameters(grffile, stringid, textstack);
1002 return GetStringWithArgs(stringid, params);
1003}
Helper types related to the allocation of memory.
debug_inline 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:23
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:75
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:55
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::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.
std::vector< StringParameter > GetGRFSringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span< const int32_t > textstack)
Process the text ref stack for a GRF String and return its parameters.
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:371
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:337
StringID MakeStringID(StringTab tab, StringIndexInTab index)
Create a StringID.
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:115
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:353
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.