OpenTTD Source 20250312-master-gcdcc6b491d
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"
30#include "language.h"
31#include <sstream>
32
33#include "table/strings.h"
34#include "table/control_codes.h"
35
36#include "safeguards.h"
37
43enum GRFBaseLanguages : uint8_t {
44 GRFLB_AMERICAN = 0x01,
45 GRFLB_ENGLISH = 0x02,
46 GRFLB_GERMAN = 0x04,
47 GRFLB_FRENCH = 0x08,
48 GRFLB_SPANISH = 0x10,
49 GRFLB_GENERIC = 0x80,
50};
51
52enum GRFExtendedLanguages : uint8_t {
53 GRFLX_AMERICAN = 0x00,
54 GRFLX_ENGLISH = 0x01,
55 GRFLX_GERMAN = 0x02,
56 GRFLX_FRENCH = 0x03,
57 GRFLX_SPANISH = 0x04,
58 GRFLX_UNSPECIFIED = 0x7F,
59};
60
61
68 GRFTextList textholder;
69 StringID def_string;
70 uint32_t grfid;
71 GRFStringID stringid;
72};
73
74
76static uint8_t _currentLangID = GRFLX_ENGLISH;
77
84int LanguageMap::GetMapping(int newgrf_id, bool gender) const
85{
86 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
87 for (const Mapping &m : map) {
88 if (m.newgrf_id == newgrf_id) return m.openttd_id;
89 }
90 return -1;
91}
92
99int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
100{
101 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
102 for (const Mapping &m : map) {
103 if (m.openttd_id == openttd_id) return m.newgrf_id;
104 }
105 return -1;
106}
107
119
121 int offset;
122
124 std::map<uint8_t, std::stringstream> strings;
125
131 void Flush(const LanguageMap *lm, std::ostringstream &dest)
132 {
133 if (this->strings.find(0) == this->strings.end()) {
134 /* In case of a (broken) NewGRF without a default,
135 * assume an empty string. */
136 GrfMsg(1, "choice list misses default value");
137 this->strings[0] = std::stringstream();
138 }
139
140 std::ostreambuf_iterator<char> d(dest);
141
142 if (lm == nullptr) {
143 /* In case there is no mapping, just ignore everything but the default.
144 * A probable cause for this happening is when the language file has
145 * been removed by the user and as such no mapping could be made. */
146 dest << this->strings[0].rdbuf();
147 return;
148 }
149
150 Utf8Encode(d, this->type);
151
152 if (this->type == SCC_SWITCH_CASE) {
153 /*
154 * Format for case switch:
155 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
156 * Each LEN is printed using 2 bytes in big endian order.
157 */
158
159 /* "<NUM CASES>" */
160 int count = 0;
161 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
162 /* Count the ones we have a mapped string for. */
163 if (this->strings.find(lm->GetReverseMapping(i, false)) != this->strings.end()) count++;
164 }
165 *d++ = count;
166
167 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
168 /* Resolve the string we're looking for. */
169 int idx = lm->GetReverseMapping(i, false);
170 if (this->strings.find(idx) == this->strings.end()) continue;
171 auto str = this->strings[idx].str();
172
173 /* "<CASEn>" */
174 *d++ = i + 1;
175
176 /* "<LENn>": Limit the length of the string to 0xFFFE to leave space for the '\0'. */
177 size_t len = std::min<size_t>(0xFFFE, str.size());
178 *d++ = GB(len + 1, 8, 8);
179 *d++ = GB(len + 1, 0, 8);
180
181 /* "<STRINGn>" */
182 dest.write(str.c_str(), len);
183 *d++ = '\0';
184 }
185
186 /* "<STRINGDEFAULT>" */
187 dest << this->strings[0].rdbuf() << '\0';
188 } else {
189 if (this->type == SCC_PLURAL_LIST) {
190 *d++ = lm->plural_form;
191 }
192
193 /*
194 * Format for choice list:
195 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
196 */
197
198 /* "<OFFSET>" */
199 *d++ = this->offset - 0x80;
200
201 /* "<NUM CHOICES>" */
202 int count = (this->type == SCC_GENDER_LIST ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
203 *d++ = count;
204
205 /* "<LENs>" */
206 for (int i = 0; i < count; i++) {
207 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
208 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0].str();
209 size_t len = str.size() + 1;
210 if (len > 0xFF) GrfMsg(1, "choice list string is too long");
211 *d++ = GB(len, 0, 8);
212 }
213
214 /* "<STRINGs>" */
215 for (int i = 0; i < count; i++) {
216 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
217 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0].str();
218 /* Limit the length of the string we copy to 0xFE. The length is written above
219 * as a byte and we need room for the final '\0'. */
220 size_t len = std::min<size_t>(0xFE, str.size());
221 dest.write(str.c_str(), len);
222 *d++ = '\0';
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 std::string_view::const_iterator src = str.cbegin();
243
244 /* Is this an unicode string? */
245 bool unicode = false;
246 char32_t marker;
247 size_t len = Utf8Decode(&marker, &*src);
248
249 if (marker == NFO_UTF8_IDENTIFIER) {
250 unicode = true;
251 src += len;
252 }
253
254 /* Helper variable for a possible (string) mapping. */
255 UnmappedChoiceList *mapping = nullptr;
256
257 std::ostringstream dest;
258 std::ostreambuf_iterator<char> d(dest);
259 while (src != str.cend()) {
260 char32_t c;
261
262 if (unicode && Utf8EncodedCharLen(*src) != 0) {
263 c = Utf8Consume(src);
264 /* 'Magic' range of control codes. */
265 if (GB(c, 8, 8) == 0xE0) {
266 c = GB(c, 0, 8);
267 } else if (c >= 0x20) {
268 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
269 Utf8Encode(d, c);
270 continue;
271 }
272 } else {
273 c = static_cast<uint8_t>(*src++);
274 }
275
276 if (c == '\0') break;
277
278 switch (c) {
279 case 0x01:
280 if (*src == '\0') goto string_end;
281 Utf8Encode(d, ' ');
282 src++;
283 break;
284 case 0x0A: break;
285 case 0x0D:
286 if (allow_newlines) {
287 *d++ = 0x0A;
288 } else {
289 GrfMsg(1, "Detected newline in string that does not allow one");
290 }
291 break;
292 case 0x0E: Utf8Encode(d, SCC_TINYFONT); break;
293 case 0x0F: Utf8Encode(d, SCC_BIGFONT); break;
294 case 0x1F:
295 if (src[0] == '\0' || src[1] == '\0') goto string_end;
296 Utf8Encode(d, ' ');
297 src += 2;
298 break;
299 case 0x7B:
300 case 0x7C:
301 case 0x7D:
302 case 0x7E:
303 case 0x7F: Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break;
304 case 0x80: Utf8Encode(d, byte80); break;
305 case 0x81:
306 {
307 if (src[0] == '\0' || src[1] == '\0') goto string_end;
308 uint16_t string;
309 string = static_cast<uint8_t>(*src++);
310 string |= static_cast<uint8_t>(*src++) << 8;
312 Utf8Encode(d, MapGRFStringID(grfid, GRFStringID{string}));
313 break;
314 }
315 case 0x82:
316 case 0x83:
317 case 0x84: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_DATE_LONG + c - 0x82); break;
318 case 0x85: Utf8Encode(d, SCC_NEWGRF_DISCARD_WORD); break;
319 case 0x86: Utf8Encode(d, SCC_NEWGRF_ROTATE_TOP_4_WORDS); break;
320 case 0x87: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_VOLUME_LONG); break;
321 case 0x88: Utf8Encode(d, SCC_BLUE); break;
322 case 0x89: Utf8Encode(d, SCC_SILVER); break;
323 case 0x8A: Utf8Encode(d, SCC_GOLD); break;
324 case 0x8B: Utf8Encode(d, SCC_RED); break;
325 case 0x8C: Utf8Encode(d, SCC_PURPLE); break;
326 case 0x8D: Utf8Encode(d, SCC_LTBROWN); break;
327 case 0x8E: Utf8Encode(d, SCC_ORANGE); break;
328 case 0x8F: Utf8Encode(d, SCC_GREEN); break;
329 case 0x90: Utf8Encode(d, SCC_YELLOW); break;
330 case 0x91: Utf8Encode(d, SCC_DKGREEN); break;
331 case 0x92: Utf8Encode(d, SCC_CREAM); break;
332 case 0x93: Utf8Encode(d, SCC_BROWN); break;
333 case 0x94: Utf8Encode(d, SCC_WHITE); break;
334 case 0x95: Utf8Encode(d, SCC_LTBLUE); break;
335 case 0x96: Utf8Encode(d, SCC_GRAY); break;
336 case 0x97: Utf8Encode(d, SCC_DKBLUE); break;
337 case 0x98: Utf8Encode(d, SCC_BLACK); break;
338 case 0x9A:
339 {
340 int code = *src++;
341 switch (code) {
342 case 0x00: goto string_end;
343 case 0x01: Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_CURRENCY); break;
344 /* 0x02: ignore next colour byte is not supported. It works on the final
345 * string and as such hooks into the string drawing routine. At that
346 * point many things already happened, such as splitting up of strings
347 * when drawn over multiple lines or right-to-left translations, which
348 * make the behaviour peculiar, e.g. only happening at specific width
349 * of windows. Or we need to add another pass over the string to just
350 * support this. As such it is not implemented in OpenTTD. */
351 case 0x03:
352 {
353 if (src[0] == '\0' || src[1] == '\0') goto string_end;
354 uint16_t tmp = static_cast<uint8_t>(*src++);
355 tmp |= static_cast<uint8_t>(*src++) << 8;
357 Utf8Encode(d, tmp);
358 break;
359 }
360 case 0x06: Utf8Encode(d, SCC_NEWGRF_PRINT_BYTE_HEX); break;
361 case 0x07: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_HEX); break;
362 case 0x08: Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_HEX); break;
363 /* 0x09, 0x0A are TTDPatch internal use only string codes. */
364 case 0x0B: Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_HEX); break;
366 case 0x0D: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG); break;
367 case 0x0E:
368 case 0x0F:
369 {
370 if (str[0] == '\0') goto string_end;
371 const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
372 int index = *src++;
373 int mapped = lm != nullptr ? lm->GetMapping(index, code == 0x0E) : -1;
374 if (mapped >= 0) {
375 Utf8Encode(d, code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
376 Utf8Encode(d, code == 0x0E ? mapped : mapped + 1);
377 }
378 break;
379 }
380
381 case 0x10:
382 case 0x11:
383 if (str[0] == '\0') goto string_end;
384 if (mapping == nullptr) {
385 if (code == 0x10) src++; // Skip the index
386 GrfMsg(1, "choice list {} marker found when not expected", code == 0x10 ? "next" : "default");
387 break;
388 } else {
389 int index = (code == 0x10 ? *src++ : 0);
390 if (mapping->strings.find(index) != mapping->strings.end()) {
391 GrfMsg(1, "duplicate choice list string, ignoring");
392 } else {
393 d = std::ostreambuf_iterator<char>(mapping->strings[index]);
394 }
395 }
396 break;
397
398 case 0x12:
399 if (mapping == nullptr) {
400 GrfMsg(1, "choice list end marker found when not expected");
401 } else {
402 /* Now we can start flushing everything and clean everything up. */
403 mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id), dest);
404 delete mapping;
405 mapping = nullptr;
406
407 d = std::ostreambuf_iterator<char>(dest);
408 }
409 break;
410
411 case 0x13:
412 case 0x14:
413 case 0x15:
414 if (src[0] == '\0') goto string_end;
415 if (mapping != nullptr) {
416 GrfMsg(1, "choice lists can't be stacked, it's going to get messy now...");
417 if (code != 0x14) src++;
418 } else {
419 static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
420 mapping = new UnmappedChoiceList(mp[code - 0x13], code == 0x14 ? 0 : *src++);
421 }
422 break;
423
424 case 0x16:
425 case 0x17:
426 case 0x18:
427 case 0x19:
428 case 0x1A:
429 case 0x1B:
430 case 0x1C:
431 case 0x1D:
432 case 0x1E:
434 break;
435
436 case 0x1F: Utf8Encode(d, SCC_PUSH_COLOUR); break;
437 case 0x20: Utf8Encode(d, SCC_POP_COLOUR); break;
438
439 case 0x21: Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_FORCE); break;
440
441 default:
442 GrfMsg(1, "missing handler for extended format code");
443 break;
444 }
445 break;
446 }
447
448 case 0x9E: Utf8Encode(d, 0x20AC); break; // Euro
449 case 0x9F: Utf8Encode(d, 0x0178); break; // Y with diaeresis
450 case 0xA0: Utf8Encode(d, SCC_UP_ARROW); break;
451 case 0xAA: Utf8Encode(d, SCC_DOWN_ARROW); break;
452 case 0xAC: Utf8Encode(d, SCC_CHECKMARK); break;
453 case 0xAD: Utf8Encode(d, SCC_CROSS); break;
454 case 0xAF: Utf8Encode(d, SCC_RIGHT_ARROW); break;
455 case 0xB4: Utf8Encode(d, SCC_TRAIN); break;
456 case 0xB5: Utf8Encode(d, SCC_LORRY); break;
457 case 0xB6: Utf8Encode(d, SCC_BUS); break;
458 case 0xB7: Utf8Encode(d, SCC_PLANE); break;
459 case 0xB8: Utf8Encode(d, SCC_SHIP); break;
460 case 0xB9: Utf8Encode(d, SCC_SUPERSCRIPT_M1); break;
461 case 0xBC: Utf8Encode(d, SCC_SMALL_UP_ARROW); break;
462 case 0xBD: Utf8Encode(d, SCC_SMALL_DOWN_ARROW); break;
463 default:
464 /* Validate any unhandled character */
465 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
466 Utf8Encode(d, c);
467 break;
468 }
469 }
470
471string_end:
472 if (mapping != nullptr) {
473 GrfMsg(1, "choice list was incomplete, the whole list is ignored");
474 delete mapping;
475 }
476
477 return dest.str();
478}
479
486static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
487{
488 /* Loop through all languages and see if we can replace a string */
489 for (auto &text : list) {
490 if (text.langid == langid) {
491 text.text = text_to_add;
492 return;
493 }
494 }
495
496 /* If a string wasn't replaced, then we must append the new string */
497 list.push_back(GRFText{ langid, std::string(text_to_add) });
498}
499
509void AddGRFTextToList(GRFTextList &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
510{
511 AddGRFTextToList(list, langid, TranslateTTDPatchCodes(grfid, langid, allow_newlines, text_to_add));
512}
513
523void AddGRFTextToList(GRFTextWrapper &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
524{
525 if (list == nullptr) list = std::make_shared<GRFTextList>();
526 AddGRFTextToList(*list, langid, grfid, allow_newlines, text_to_add);
527}
528
535void AddGRFTextToList(GRFTextWrapper &list, std::string_view text_to_add)
536{
537 if (list == nullptr) list = std::make_shared<GRFTextList>();
538 AddGRFTextToList(*list, GRFLX_UNSPECIFIED, text_to_add);
539}
540
544StringID 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)
545{
546 /* When working with the old language scheme (grf_version is less than 7) and
547 * English or American is among the set bits, simply add it as English in
548 * the new scheme, i.e. as langid = 1.
549 * If English is set, it is pretty safe to assume the translations are not
550 * actually translated.
551 */
552 if (!new_scheme) {
553 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
554 langid_to_add = GRFLX_ENGLISH;
555 } else {
556 StringID ret = STR_EMPTY;
557 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, true, allow_newlines, text_to_add, def_string);
558 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, true, allow_newlines, text_to_add, def_string);
559 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, true, allow_newlines, text_to_add, def_string);
560 return ret;
561 }
562 }
563
564 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
565 if (it == std::end(_grf_text)) {
566 /* Too many strings allocated, return empty. */
567 if (_grf_text.size() == TAB_SIZE_NEWGRF) return STR_EMPTY;
568
569 /* We didn't find our stringid and grfid in the list, allocate a new id. */
570 it = _grf_text.emplace(std::end(_grf_text));
571 it->grfid = grfid;
572 it->stringid = stringid;
573 it->def_string = def_string;
574 }
575 StringIndexInTab id(it - std::begin(_grf_text));
576
577 std::string newtext = TranslateTTDPatchCodes(grfid, langid_to_add, allow_newlines, text_to_add);
578 AddGRFTextToList(it->textholder, langid_to_add, newtext);
579
580 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));
581
583}
584
588StringID GetGRFStringID(uint32_t grfid, GRFStringID stringid)
589{
590 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
591 if (it != std::end(_grf_text)) {
592 StringIndexInTab id(it - std::begin(_grf_text));
594 }
595
596 return STR_UNDEFINED;
597}
598
599
607const char *GetGRFStringFromGRFText(const GRFTextList &text_list)
608{
609 const char *default_text = nullptr;
610
611 /* Search the list of lang-strings of this stringid for current lang */
612 for (const auto &text : text_list) {
613 if (text.langid == _currentLangID) return text.text.c_str();
614
615 /* If the current string is English or American, set it as the
616 * fallback language if the specific language isn't available. */
617 if (text.langid == GRFLX_UNSPECIFIED || (default_text == nullptr && (text.langid == GRFLX_ENGLISH || text.langid == GRFLX_AMERICAN))) {
618 default_text = text.text.c_str();
619 }
620 }
621
622 return default_text;
623}
624
633{
634 return text ? GetGRFStringFromGRFText(*text) : nullptr;
635}
636
641{
642 assert(stringid.base() < _grf_text.size());
643 assert(_grf_text[stringid].grfid != 0);
644
645 const char *str = GetGRFStringFromGRFText(_grf_text[stringid].textholder);
646 if (str != nullptr) return str;
647
648 /* Use the default string ID if the fallback string isn't available */
649 return GetStringPtr(_grf_text[stringid].def_string);
650}
651
660void SetCurrentGrfLangID(uint8_t language_id)
661{
662 _currentLangID = language_id;
663}
664
665bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
666{
667 if (grf_version < 7) {
668 switch (_currentLangID) {
669 case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0;
670 case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0;
671 case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
672 default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
673 }
674 }
675
676 return (lang_id == _currentLangID || lang_id == GRFLX_UNSPECIFIED);
677}
678
684{
685 _grf_text.clear();
686}
687
689 std::array<uint8_t, 0x30> stack{};
690 uint8_t position = 0;
691 const GRFFile *grffile = nullptr;
692
693 TextRefStack(const GRFFile *grffile, uint8_t num_entries) : grffile(grffile)
694 {
695 extern TemporaryStorageArray<int32_t, 0x110> _temp_store;
696
697 assert(num_entries < sizeof(uint32_t) * std::size(stack));
698
699 auto stack_it = this->stack.begin();
700 for (uint i = 0; i < num_entries; i++) {
701 uint32_t value = _temp_store.GetValue(0x100 + i);
702 for (uint j = 0; j < 32; j += 8) {
703 *stack_it++ = GB(value, j, 8);
704 }
705 }
706 }
707
708 uint8_t PopUnsignedByte() { assert(this->position < this->stack.size()); return this->stack[this->position++]; }
709 int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
710
711 uint16_t PopUnsignedWord()
712 {
713 uint16_t val = this->PopUnsignedByte();
714 return val | (this->PopUnsignedByte() << 8);
715 }
716 int16_t PopSignedWord() { return (int32_t)this->PopUnsignedWord(); }
717
718 uint32_t PopUnsignedDWord()
719 {
720 uint32_t val = this->PopUnsignedWord();
721 return val | (this->PopUnsignedWord() << 16);
722 }
723 int32_t PopSignedDWord() { return (int32_t)this->PopUnsignedDWord(); }
724
725 uint64_t PopUnsignedQWord()
726 {
727 uint64_t val = this->PopUnsignedDWord();
728 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
729 }
730 int64_t PopSignedQWord() { return (int64_t)this->PopUnsignedQWord(); }
731
734 {
735 uint8_t tmp[2];
736 for (int i = 0; i < 2; i++) tmp[i] = this->stack[this->position + i + 6];
737 for (int i = 5; i >= 0; i--) this->stack[this->position + i + 2] = this->stack[this->position + i];
738 for (int i = 0; i < 2; i++) this->stack[this->position + i] = tmp[i];
739 }
740
741 void PushWord(uint16_t word)
742 {
743 if (this->position >= 2) {
744 this->position -= 2;
745 } else {
746 // Rotate right 2 positions
747 std::rotate(this->stack.rbegin(), this->stack.rbegin() + 2, this->stack.rend());
748 }
749 this->stack[this->position] = GB(word, 0, 8);
750 this->stack[this->position + 1] = GB(word, 8, 8);
751 }
752};
753
754static void HandleNewGRFStringControlCodes(const char *str, TextRefStack &stack, std::vector<StringParameter> &params);
755
763static void RemapNewGRFStringControlCode(char32_t scc, const char **str, 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 case SCC_NEWGRF_PRINT_BYTE_SIGNED: params.emplace_back(stack.PopSignedByte()); break;
770 case SCC_NEWGRF_PRINT_QWORD_CURRENCY: params.emplace_back(stack.PopSignedQWord()); break;
771
773 case SCC_NEWGRF_PRINT_DWORD_SIGNED: params.emplace_back(stack.PopSignedDWord()); break;
774
775 case SCC_NEWGRF_PRINT_BYTE_HEX: params.emplace_back(stack.PopUnsignedByte()); break;
776 case SCC_NEWGRF_PRINT_QWORD_HEX: params.emplace_back(stack.PopUnsignedQWord()); break;
777
781 case SCC_NEWGRF_PRINT_WORD_SIGNED: params.emplace_back(stack.PopSignedWord()); break;
782
788 case SCC_NEWGRF_PRINT_WORD_UNSIGNED: params.emplace_back(stack.PopUnsignedWord()); break;
789
793 case SCC_NEWGRF_PRINT_DWORD_HEX: params.emplace_back(stack.PopUnsignedDWord()); break;
794
795 /* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
797 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: params.emplace_back(CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR + stack.PopUnsignedWord()); break;
798
799 case SCC_NEWGRF_DISCARD_WORD: stack.PopUnsignedWord(); break;
800
802 case SCC_NEWGRF_PUSH_WORD: stack.PushWord(Utf8Consume(str)); break;
803
807 params.emplace_back(GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile));
808 params.emplace_back(stack.PopUnsignedWord());
809 break;
810
812 StringID stringid = MapGRFStringID(stack.grffile->grfid, GRFStringID{stack.PopUnsignedWord()});
813 params.emplace_back(stringid);
814 /* We also need to handle the substring's stack usage. */
815 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
816 break;
817 }
818
820 CargoType cargo = GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile);
821 params.emplace_back(cargo < NUM_CARGO ? 1ULL << cargo : 0);
822 break;
823 }
824 }
825}
826
833char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str)
834{
835 switch (scc) {
836 default:
837 return scc;
838
843 return SCC_COMMA;
844
849 return SCC_HEX;
850
853 return SCC_CURRENCY_LONG;
854
857
860 return SCC_DATE_LONG;
861
864 return SCC_DATE_SHORT;
865
867 return SCC_VELOCITY;
868
870 return SCC_VOLUME_LONG;
871
873 return SCC_VOLUME_SHORT;
874
876 return SCC_WEIGHT_LONG;
877
879 return SCC_WEIGHT_SHORT;
880
882 return SCC_POWER;
883
885 return SCC_FORCE;
886
888 return SCC_CARGO_LONG;
889
891 return SCC_CARGO_SHORT;
892
894 return SCC_CARGO_TINY;
895
897 return SCC_CARGO_LIST;
898
900 return SCC_STATION_NAME;
901
902 /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
904 Utf8Consume(str);
905 return 0;
906
909 return 0;
910 }
911}
912
919static void HandleNewGRFStringControlCodes(const char *str, TextRefStack &stack, std::vector<StringParameter> &params)
920{
921 if (str == nullptr) return;
922
923 for (const char *p = str; *p != '\0'; /* nothing */) {
924 char32_t scc;
925 p += Utf8Decode(&scc, p);
926 RemapNewGRFStringControlCode(scc, &p, stack, params);
927 }
928}
929
937std::vector<StringParameter> GetGRFSringTextStackParameters(const GRFFile *grffile, StringID stringid, uint8_t num_entries)
938{
939 if (stringid == INVALID_STRING_ID) return {};
940
941 const char *str = GetStringPtr(stringid);
942 if (str == nullptr) return {};
943
944 std::vector<StringParameter> params;
945 params.reserve(20);
946
947 TextRefStack stack{grffile, num_entries};
948 HandleNewGRFStringControlCodes(str, stack, params);
949
950 return params;
951}
952
960std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, uint8_t num_entries)
961{
962 StringID stringid = GetGRFStringID(grffile->grfid, grfstringid);
963 auto params = GetGRFSringTextStackParameters(grffile, stringid, num_entries);
964 return GetStringWithArgs(stringid, params);
965}
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
A sort-of mixin that adds 'at(pos)' and 'operator[](pos)' implementations for 'ConvertibleThroughBase...
static constexpr TimerGame< struct Calendar >::Date DAYS_TILL_ORIGINAL_BASE_YEAR
The date of the first day of the original base year.
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
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 ...
Definition newgrf.cpp:563
Base for the NewGRF implementation.
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.
static void HandleNewGRFStringControlCodes(const char *str, TextRefStack &stack, std::vector< StringParameter > &params)
Handle control codes in a NewGRF string, processing the stack and filling parameters.
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.
GRFBaseLanguages
Explains the newgrf shift bit positioning.
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).
const char * GetGRFStringPtr(StringIndexInTab stringid)
Get a C-string from a stringid set by a newgrf.
static void RemapNewGRFStringControlCode(char32_t scc, const char **str, TextRefStack &stack, std::vector< StringParameter > &params)
Process NewGRF string control code instructions.
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.
const char * GetGRFStringFromGRFText(const GRFTextList &text_list)
Get a C-string from a GRFText-list.
static uint8_t _currentLangID
by default, english is used.
std::vector< StringParameter > GetGRFSringTextStackParameters(const GRFFile *grffile, StringID stringid, uint8_t num_entries)
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.
std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, uint8_t num_entries)
Format a GRF string using the text ref stack for parameters.
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:414
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition string.cpp:437
size_t Utf8Encode(T buf, char32_t c)
Encode a unicode character and place it in the buffer.
Definition string.cpp:478
Functions related to low-level strings.
int8_t Utf8EncodedCharLen(char c)
Return the length of an UTF-8 encoded value based on a single char.
@ 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:338
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:111
Holder of the above structure.
A GRF text with associated language ID.
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:2784
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
Class for temporary storage of data.
void RotateTop4Words()
Rotate the top four words down: W1, W2, W3, W4 -> W4, W1, W2, W3.
Helper structure for mapping choice lists.
std::map< uint8_t, std::stringstream > strings
Mapping of NewGRF supplied ID to the different strings in the choice list.
StringControlCode type
The type of choice list.
int offset
The offset for the plural/gender form.
UnmappedChoiceList(StringControlCode type, int offset)
Initialise the mapping.
void Flush(const LanguageMap *lm, std::ostringstream &dest)
Flush this choice list into the destination string.
Definition of the game-calendar-timer.