OpenTTD Source 20250905-master-g122023be8d
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 0x9B: builder.PutUtf8(SCC_TOWN); break;
441 case 0x9C: builder.PutUtf8(SCC_CITY); break;
442 case 0x9E: builder.PutUtf8(0x20AC); break; // Euro
443 case 0x9F: builder.PutUtf8(0x0178); break; // Y with diaeresis
444 case 0xA0: builder.PutUtf8(SCC_UP_ARROW); break;
445 case 0xAA: builder.PutUtf8(SCC_DOWN_ARROW); break;
446 case 0xAC: builder.PutUtf8(SCC_CHECKMARK); break;
447 case 0xAD: builder.PutUtf8(SCC_CROSS); break;
448 case 0xAF: builder.PutUtf8(SCC_RIGHT_ARROW); break;
449 case 0xB4: builder.PutUtf8(SCC_TRAIN); break;
450 case 0xB5: builder.PutUtf8(SCC_LORRY); break;
451 case 0xB6: builder.PutUtf8(SCC_BUS); break;
452 case 0xB7: builder.PutUtf8(SCC_PLANE); break;
453 case 0xB8: builder.PutUtf8(SCC_SHIP); break;
454 case 0xB9: builder.PutUtf8(SCC_SUPERSCRIPT_M1); break;
455 case 0xBC: builder.PutUtf8(SCC_SMALL_UP_ARROW); break;
456 case 0xBD: builder.PutUtf8(SCC_SMALL_DOWN_ARROW); break;
457 default:
458 /* Validate any unhandled character */
459 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
460 builder.PutUtf8(c);
461 break;
462 }
463 }
464
465string_end:
466 if (mapping_pg.has_value() || mapping_c.has_value()) {
467 GrfMsg(1, "choice list was incomplete, the whole list is ignored");
468 }
469
470 return dest;
471}
472
479static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
480{
481 /* Loop through all languages and see if we can replace a string */
482 for (auto &text : list) {
483 if (text.langid == langid) {
484 text.text = text_to_add;
485 return;
486 }
487 }
488
489 /* If a string wasn't replaced, then we must append the new string */
490 list.emplace_back(langid, std::string{text_to_add});
491}
492
502void AddGRFTextToList(GRFTextList &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
503{
504 AddGRFTextToList(list, langid, TranslateTTDPatchCodes(grfid, langid, allow_newlines, text_to_add));
505}
506
516void AddGRFTextToList(GRFTextWrapper &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
517{
518 if (list == nullptr) list = std::make_shared<GRFTextList>();
519 AddGRFTextToList(*list, langid, grfid, allow_newlines, text_to_add);
520}
521
528void AddGRFTextToList(GRFTextWrapper &list, std::string_view text_to_add)
529{
530 if (list == nullptr) list = std::make_shared<GRFTextList>();
531 AddGRFTextToList(*list, GRFLX_UNSPECIFIED, text_to_add);
532}
533
537StringID 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)
538{
539 /* When working with the old language scheme (grf_version is less than 7) and
540 * English or American is among the set bits, simply add it as English in
541 * the new scheme, i.e. as langid = 1.
542 * If English is set, it is pretty safe to assume the translations are not
543 * actually translated.
544 */
545 if (!new_scheme) {
546 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
547 langid_to_add = GRFLX_ENGLISH;
548 } else {
549 StringID ret = STR_EMPTY;
550 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, true, allow_newlines, text_to_add, def_string);
551 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, true, allow_newlines, text_to_add, def_string);
552 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, true, allow_newlines, text_to_add, def_string);
553 return ret;
554 }
555 }
556
557 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
558 if (it == std::end(_grf_text)) {
559 /* Too many strings allocated, return empty. */
560 if (_grf_text.size() == TAB_SIZE_NEWGRF) return STR_EMPTY;
561
562 /* We didn't find our stringid and grfid in the list, allocate a new id. */
563 it = _grf_text.emplace(std::end(_grf_text));
564 it->grfid = grfid;
565 it->stringid = stringid;
566 it->def_string = def_string;
567 }
568 StringIndexInTab id(it - std::begin(_grf_text));
569
570 std::string newtext = TranslateTTDPatchCodes(grfid, langid_to_add, allow_newlines, text_to_add);
571 AddGRFTextToList(it->textholder, langid_to_add, newtext);
572
573 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));
574
576}
577
581StringID GetGRFStringID(uint32_t grfid, GRFStringID stringid)
582{
583 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
584 if (it != std::end(_grf_text)) {
585 StringIndexInTab id(it - std::begin(_grf_text));
587 }
588
589 return STR_UNDEFINED;
590}
591
592
600std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextList &text_list)
601{
602 std::optional<std::string_view> default_text;
603
604 /* Search the list of lang-strings of this stringid for current lang */
605 for (const auto &text : text_list) {
606 if (text.langid == _current_lang_id) return text.text;
607
608 /* If the current string is English or American, set it as the
609 * fallback language if the specific language isn't available. */
610 if (text.langid == GRFLX_UNSPECIFIED || (!default_text.has_value() && (text.langid == GRFLX_ENGLISH || text.langid == GRFLX_AMERICAN))) {
611 default_text = text.text;
612 }
613 }
614
615 return default_text;
616}
617
625std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextWrapper &text)
626{
627 return text ? GetGRFStringFromGRFText(*text) : std::nullopt;
628}
629
633std::string_view GetGRFStringPtr(StringIndexInTab stringid)
634{
635 assert(stringid.base() < _grf_text.size());
636 assert(_grf_text[stringid].grfid != 0);
637
638 auto str = GetGRFStringFromGRFText(_grf_text[stringid].textholder);
639 if (str.has_value()) return *str;
640
641 /* Use the default string ID if the fallback string isn't available */
642 return GetStringPtr(_grf_text[stringid].def_string);
643}
644
653void SetCurrentGrfLangID(uint8_t language_id)
654{
655 _current_lang_id = language_id;
656}
657
658bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
659{
660 if (grf_version < 7) {
661 switch (_current_lang_id) {
662 case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0;
663 case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0;
664 case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
665 default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
666 }
667 }
668
669 return (lang_id == _current_lang_id || lang_id == GRFLX_UNSPECIFIED);
670}
671
677{
678 _grf_text.clear();
679}
680
682private:
683 std::vector<uint8_t> stack;
684public:
685 const GRFFile *grffile = nullptr;
686
687 TextRefStack(const GRFFile *grffile, std::span<const int32_t> textstack) : grffile(grffile)
688 {
689 this->stack.reserve(textstack.size() * 4 + 6); // for translations it is reasonable to push 3 word and rotate them
690 for (int32_t value : std::ranges::reverse_view(textstack)) {
691 this->PushDWord(value);
692 }
693 }
694
695 uint8_t PopUnsignedByte()
696 {
697 if (this->stack.empty()) return 0;
698 auto res = this->stack.back();
699 this->stack.pop_back();
700 return res;
701 }
702 int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
703
704 uint16_t PopUnsignedWord()
705 {
706 uint16_t val = this->PopUnsignedByte();
707 return val | (this->PopUnsignedByte() << 8);
708 }
709 int16_t PopSignedWord() { return (int32_t)this->PopUnsignedWord(); }
710
711 uint32_t PopUnsignedDWord()
712 {
713 uint32_t val = this->PopUnsignedWord();
714 return val | (this->PopUnsignedWord() << 16);
715 }
716 int32_t PopSignedDWord() { return (int32_t)this->PopUnsignedDWord(); }
717
718 uint64_t PopUnsignedQWord()
719 {
720 uint64_t val = this->PopUnsignedDWord();
721 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
722 }
723 int64_t PopSignedQWord() { return (int64_t)this->PopUnsignedQWord(); }
724
727 {
728 auto w1 = this->PopUnsignedWord();
729 auto w2 = this->PopUnsignedWord();
730 auto w3 = this->PopUnsignedWord();
731 auto w4 = this->PopUnsignedWord();
732 this->PushWord(w3);
733 this->PushWord(w2);
734 this->PushWord(w1);
735 this->PushWord(w4);
736 }
737
738 void PushByte(uint8_t b)
739 {
740 this->stack.push_back(b);
741 }
742
743 void PushWord(uint16_t w)
744 {
745 this->PushByte(GB(w, 8, 8));
746 this->PushByte(GB(w, 0, 8));
747 }
748
749 void PushDWord(uint32_t dw)
750 {
751 this->PushWord(GB(dw, 16, 16));
752 this->PushWord(GB(dw, 0, 16));
753 }
754};
755
756static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params);
757
765static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector<StringParameter> &params)
766{
767 /* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
768 * After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
769 switch (scc) {
770 default: return;
771
772 case SCC_PLURAL_LIST:
773 consumer.SkipUint8(); // plural form
774 [[fallthrough]];
775 case SCC_GENDER_LIST: {
776 consumer.SkipUint8(); // offset
777 /* plural and gender choices cannot contain any string commands, so just skip the whole thing */
778 uint num = consumer.ReadUint8();
779 uint total_len = 0;
780 for (uint i = 0; i != num; i++) {
781 total_len += consumer.ReadUint8();
782 }
783 consumer.Skip(total_len);
784 break;
785 }
786
787 case SCC_SWITCH_CASE: {
788 /* skip all cases and continue with default case */
789 uint num = consumer.ReadUint8();
790 for (uint i = 0; i != num; i++) {
791 consumer.SkipUint8();
792 auto len = consumer.ReadUint16LE();
793 consumer.Skip(len);
794 }
795 consumer.SkipUint16LE(); // length of default
796 break;
797 }
798
799 case SCC_GENDER_INDEX:
800 case SCC_SET_CASE:
801 consumer.SkipUint8();
802 break;
803
804 case SCC_ARG_INDEX:
805 NOT_REACHED();
806 break;
807
808 case SCC_NEWGRF_PRINT_BYTE_SIGNED: params.emplace_back(stack.PopSignedByte()); break;
809 case SCC_NEWGRF_PRINT_QWORD_CURRENCY: params.emplace_back(stack.PopSignedQWord()); break;
810
812 case SCC_NEWGRF_PRINT_DWORD_SIGNED: params.emplace_back(stack.PopSignedDWord()); break;
813
814 case SCC_NEWGRF_PRINT_BYTE_HEX: params.emplace_back(stack.PopUnsignedByte()); break;
815 case SCC_NEWGRF_PRINT_QWORD_HEX: params.emplace_back(stack.PopUnsignedQWord()); break;
816
820 case SCC_NEWGRF_PRINT_WORD_SIGNED: params.emplace_back(stack.PopSignedWord()); break;
821
827 case SCC_NEWGRF_PRINT_WORD_UNSIGNED: params.emplace_back(stack.PopUnsignedWord()); break;
828
832 case SCC_NEWGRF_PRINT_DWORD_HEX: params.emplace_back(stack.PopUnsignedDWord()); break;
833
834 /* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
836 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: params.emplace_back(CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR + stack.PopUnsignedWord()); break;
837
838 case SCC_NEWGRF_DISCARD_WORD: stack.PopUnsignedWord(); break;
839
841 case SCC_NEWGRF_PUSH_WORD: stack.PushWord(consumer.ReadUtf8(0)); break;
842
846 params.emplace_back(GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile));
847 params.emplace_back(stack.PopUnsignedWord());
848 break;
849
850 case SCC_NEWGRF_STRINL: {
851 StringID stringid = consumer.ReadUtf8(STR_NULL);
852 /* We also need to handle the substring's stack usage. */
853 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
854 break;
855 }
856
858 StringID stringid = MapGRFStringID(stack.grffile->grfid, GRFStringID{stack.PopUnsignedWord()});
859 params.emplace_back(stringid);
860 /* We also need to handle the substring's stack usage. */
861 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
862 break;
863 }
864
866 CargoType cargo = GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile);
867 params.emplace_back(cargo < NUM_CARGO ? 1ULL << cargo : 0);
868 break;
869 }
870 }
871}
872
879char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
880{
881 switch (scc) {
882 default:
883 return scc;
884
889 return SCC_COMMA;
890
895 return SCC_HEX;
896
899 return SCC_CURRENCY_LONG;
900
903 return SCC_DATE_LONG;
904
907 return SCC_DATE_SHORT;
908
910 return SCC_VELOCITY;
911
913 return SCC_VOLUME_LONG;
914
916 return SCC_VOLUME_SHORT;
917
919 return SCC_WEIGHT_LONG;
920
922 return SCC_WEIGHT_SHORT;
923
925 return SCC_POWER;
926
928 return SCC_FORCE;
929
931 return SCC_CARGO_LONG;
932
934 return SCC_CARGO_SHORT;
935
937 return SCC_CARGO_TINY;
938
940 return SCC_CARGO_LIST;
941
943 return SCC_STATION_NAME;
944
945 /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
947 consumer.SkipUtf8();
948 return 0;
949
952 return 0;
953 }
954}
955
962static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params)
963{
964 StringConsumer consumer(str);
965 while (consumer.AnyBytesLeft()) {
966 char32_t scc = consumer.ReadUtf8();
967 ProcessNewGRFStringControlCode(scc, consumer, stack, params);
968 }
969}
970
978std::vector<StringParameter> GetGRFStringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span<const int32_t> textstack)
979{
980 if (stringid == INVALID_STRING_ID) return {};
981
982 auto str = GetStringPtr(stringid);
983
984 std::vector<StringParameter> params;
985 params.reserve(20);
986
987 TextRefStack stack{grffile, textstack};
988 HandleNewGRFStringControlCodes(str, stack, params);
989
990 return params;
991}
992
1000std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span<const int32_t> textstack)
1001{
1002 StringID stringid = GetGRFStringID(grffile->grfid, grfstringid);
1003 auto params = GetGRFStringTextStackParameters(grffile, stringid, textstack);
1004 return GetStringWithArgs(stringid, params);
1005}
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: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: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: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: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.