OpenTTD Source 20241224-master-gf74b0cf984
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 "newgrf.h"
21#include "strings_internal.h"
22#include "newgrf_storage.h"
23#include "newgrf_text.h"
24#include "newgrf_cargo.h"
25#include "string_func.h"
27#include "debug.h"
28#include "core/alloc_type.hpp"
29#include "language.h"
30#include <sstream>
31
32#include "table/strings.h"
33#include "table/control_codes.h"
34
35#include "safeguards.h"
36
43 GRFLB_AMERICAN = 0x01,
44 GRFLB_ENGLISH = 0x02,
45 GRFLB_GERMAN = 0x04,
46 GRFLB_FRENCH = 0x08,
47 GRFLB_SPANISH = 0x10,
48 GRFLB_GENERIC = 0x80,
49};
50
51enum GRFExtendedLanguages {
52 GRFLX_AMERICAN = 0x00,
53 GRFLX_ENGLISH = 0x01,
54 GRFLX_GERMAN = 0x02,
55 GRFLX_FRENCH = 0x03,
56 GRFLX_SPANISH = 0x04,
57 GRFLX_UNSPECIFIED = 0x7F,
58};
59
60
67 GRFTextList textholder;
68 StringID def_string;
69 uint32_t grfid;
70 uint16_t stringid;
71};
72
73
74static std::vector<GRFTextEntry> _grf_text;
75static uint8_t _currentLangID = GRFLX_ENGLISH;
76
83int LanguageMap::GetMapping(int newgrf_id, bool gender) const
84{
85 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
86 for (const Mapping &m : map) {
87 if (m.newgrf_id == newgrf_id) return m.openttd_id;
88 }
89 return -1;
90}
91
98int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
99{
100 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
101 for (const Mapping &m : map) {
102 if (m.openttd_id == openttd_id) return m.newgrf_id;
103 }
104 return -1;
105}
106
118
120 int offset;
121
123 std::map<uint8_t, std::stringstream> strings;
124
130 void Flush(const LanguageMap *lm, std::ostringstream &dest)
131 {
132 if (this->strings.find(0) == this->strings.end()) {
133 /* In case of a (broken) NewGRF without a default,
134 * assume an empty string. */
135 GrfMsg(1, "choice list misses default value");
136 this->strings[0] = std::stringstream();
137 }
138
139 std::ostreambuf_iterator<char> d(dest);
140
141 if (lm == nullptr) {
142 /* In case there is no mapping, just ignore everything but the default.
143 * A probable cause for this happening is when the language file has
144 * been removed by the user and as such no mapping could be made. */
145 dest << this->strings[0].rdbuf();
146 return;
147 }
148
149 Utf8Encode(d, this->type);
150
151 if (this->type == SCC_SWITCH_CASE) {
152 /*
153 * Format for case switch:
154 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
155 * Each LEN is printed using 2 bytes in big endian order.
156 */
157
158 /* "<NUM CASES>" */
159 int count = 0;
160 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
161 /* Count the ones we have a mapped string for. */
162 if (this->strings.find(lm->GetReverseMapping(i, false)) != this->strings.end()) count++;
163 }
164 *d++ = count;
165
166 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
167 /* Resolve the string we're looking for. */
168 int idx = lm->GetReverseMapping(i, false);
169 if (this->strings.find(idx) == this->strings.end()) continue;
170 auto str = this->strings[idx].str();
171
172 /* "<CASEn>" */
173 *d++ = i + 1;
174
175 /* "<LENn>": Limit the length of the string to 0xFFFE to leave space for the '\0'. */
176 size_t len = std::min<size_t>(0xFFFE, str.size());
177 *d++ = GB(len + 1, 8, 8);
178 *d++ = GB(len + 1, 0, 8);
179
180 /* "<STRINGn>" */
181 dest.write(str.c_str(), len);
182 *d++ = '\0';
183 }
184
185 /* "<STRINGDEFAULT>" */
186 dest << this->strings[0].rdbuf() << '\0';
187 } else {
188 if (this->type == SCC_PLURAL_LIST) {
189 *d++ = lm->plural_form;
190 }
191
192 /*
193 * Format for choice list:
194 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
195 */
196
197 /* "<OFFSET>" */
198 *d++ = this->offset - 0x80;
199
200 /* "<NUM CHOICES>" */
201 int count = (this->type == SCC_GENDER_LIST ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
202 *d++ = count;
203
204 /* "<LENs>" */
205 for (int i = 0; i < count; i++) {
206 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
207 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0].str();
208 size_t len = str.size() + 1;
209 if (len > 0xFF) GrfMsg(1, "choice list string is too long");
210 *d++ = GB(len, 0, 8);
211 }
212
213 /* "<STRINGs>" */
214 for (int i = 0; i < count; i++) {
215 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
216 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0].str();
217 /* Limit the length of the string we copy to 0xFE. The length is written above
218 * as a byte and we need room for the final '\0'. */
219 size_t len = std::min<size_t>(0xFE, str.size());
220 dest.write(str.c_str(), len);
221 *d++ = '\0';
222 }
223 }
224 }
225};
226
236std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool allow_newlines, std::string_view str, StringControlCode byte80)
237{
238 /* Empty input string? Nothing to do here. */
239 if (str.empty()) return {};
240
241 std::string_view::const_iterator src = str.cbegin();
242
243 /* Is this an unicode string? */
244 bool unicode = false;
245 char32_t marker;
246 size_t len = Utf8Decode(&marker, &*src);
247
248 if (marker == NFO_UTF8_IDENTIFIER) {
249 unicode = true;
250 src += len;
251 }
252
253 /* Helper variable for a possible (string) mapping. */
254 UnmappedChoiceList *mapping = nullptr;
255
256 std::ostringstream dest;
257 std::ostreambuf_iterator<char> d(dest);
258 while (src != str.cend()) {
259 char32_t c;
260
261 if (unicode && Utf8EncodedCharLen(*src) != 0) {
262 c = Utf8Consume(src);
263 /* 'Magic' range of control codes. */
264 if (GB(c, 8, 8) == 0xE0) {
265 c = GB(c, 0, 8);
266 } else if (c >= 0x20) {
267 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
268 Utf8Encode(d, c);
269 continue;
270 }
271 } else {
272 c = static_cast<uint8_t>(*src++);
273 }
274
275 if (c == '\0') break;
276
277 switch (c) {
278 case 0x01:
279 if (*src == '\0') goto string_end;
280 Utf8Encode(d, ' ');
281 src++;
282 break;
283 case 0x0A: break;
284 case 0x0D:
285 if (allow_newlines) {
286 *d++ = 0x0A;
287 } else {
288 GrfMsg(1, "Detected newline in string that does not allow one");
289 }
290 break;
291 case 0x0E: Utf8Encode(d, SCC_TINYFONT); break;
292 case 0x0F: Utf8Encode(d, SCC_BIGFONT); break;
293 case 0x1F:
294 if (src[0] == '\0' || src[1] == '\0') goto string_end;
295 Utf8Encode(d, ' ');
296 src += 2;
297 break;
298 case 0x7B:
299 case 0x7C:
300 case 0x7D:
301 case 0x7E:
302 case 0x7F: Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break;
303 case 0x80: Utf8Encode(d, byte80); break;
304 case 0x81:
305 {
306 if (src[0] == '\0' || src[1] == '\0') goto string_end;
307 StringID string;
308 string = static_cast<uint8_t>(*src++);
309 string |= static_cast<uint8_t>(*src++) << 8;
311 Utf8Encode(d, MapGRFStringID(grfid, string));
312 break;
313 }
314 case 0x82:
315 case 0x83:
316 case 0x84: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_DATE_LONG + c - 0x82); break;
317 case 0x85: Utf8Encode(d, SCC_NEWGRF_DISCARD_WORD); break;
318 case 0x86: Utf8Encode(d, SCC_NEWGRF_ROTATE_TOP_4_WORDS); break;
319 case 0x87: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_VOLUME_LONG); break;
320 case 0x88: Utf8Encode(d, SCC_BLUE); break;
321 case 0x89: Utf8Encode(d, SCC_SILVER); break;
322 case 0x8A: Utf8Encode(d, SCC_GOLD); break;
323 case 0x8B: Utf8Encode(d, SCC_RED); break;
324 case 0x8C: Utf8Encode(d, SCC_PURPLE); break;
325 case 0x8D: Utf8Encode(d, SCC_LTBROWN); break;
326 case 0x8E: Utf8Encode(d, SCC_ORANGE); break;
327 case 0x8F: Utf8Encode(d, SCC_GREEN); break;
328 case 0x90: Utf8Encode(d, SCC_YELLOW); break;
329 case 0x91: Utf8Encode(d, SCC_DKGREEN); break;
330 case 0x92: Utf8Encode(d, SCC_CREAM); break;
331 case 0x93: Utf8Encode(d, SCC_BROWN); break;
332 case 0x94: Utf8Encode(d, SCC_WHITE); break;
333 case 0x95: Utf8Encode(d, SCC_LTBLUE); break;
334 case 0x96: Utf8Encode(d, SCC_GRAY); break;
335 case 0x97: Utf8Encode(d, SCC_DKBLUE); break;
336 case 0x98: Utf8Encode(d, SCC_BLACK); break;
337 case 0x9A:
338 {
339 int code = *src++;
340 switch (code) {
341 case 0x00: goto string_end;
342 case 0x01: Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_CURRENCY); break;
343 /* 0x02: ignore next colour byte is not supported. It works on the final
344 * string and as such hooks into the string drawing routine. At that
345 * point many things already happened, such as splitting up of strings
346 * when drawn over multiple lines or right-to-left translations, which
347 * make the behaviour peculiar, e.g. only happening at specific width
348 * of windows. Or we need to add another pass over the string to just
349 * support this. As such it is not implemented in OpenTTD. */
350 case 0x03:
351 {
352 if (src[0] == '\0' || src[1] == '\0') goto string_end;
353 uint16_t tmp = static_cast<uint8_t>(*src++);
354 tmp |= static_cast<uint8_t>(*src++) << 8;
356 Utf8Encode(d, tmp);
357 break;
358 }
359 case 0x06: Utf8Encode(d, SCC_NEWGRF_PRINT_BYTE_HEX); break;
360 case 0x07: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_HEX); break;
361 case 0x08: Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_HEX); break;
362 /* 0x09, 0x0A are TTDPatch internal use only string codes. */
363 case 0x0B: Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_HEX); break;
365 case 0x0D: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG); break;
366 case 0x0E:
367 case 0x0F:
368 {
369 if (str[0] == '\0') goto string_end;
370 const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
371 int index = *src++;
372 int mapped = lm != nullptr ? lm->GetMapping(index, code == 0x0E) : -1;
373 if (mapped >= 0) {
374 Utf8Encode(d, code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
375 Utf8Encode(d, code == 0x0E ? mapped : mapped + 1);
376 }
377 break;
378 }
379
380 case 0x10:
381 case 0x11:
382 if (str[0] == '\0') goto string_end;
383 if (mapping == nullptr) {
384 if (code == 0x10) src++; // Skip the index
385 GrfMsg(1, "choice list {} marker found when not expected", code == 0x10 ? "next" : "default");
386 break;
387 } else {
388 int index = (code == 0x10 ? *src++ : 0);
389 if (mapping->strings.find(index) != mapping->strings.end()) {
390 GrfMsg(1, "duplicate choice list string, ignoring");
391 } else {
392 d = std::ostreambuf_iterator<char>(mapping->strings[index]);
393 }
394 }
395 break;
396
397 case 0x12:
398 if (mapping == nullptr) {
399 GrfMsg(1, "choice list end marker found when not expected");
400 } else {
401 /* Now we can start flushing everything and clean everything up. */
402 mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id), dest);
403 delete mapping;
404 mapping = nullptr;
405
406 d = std::ostreambuf_iterator<char>(dest);
407 }
408 break;
409
410 case 0x13:
411 case 0x14:
412 case 0x15:
413 if (src[0] == '\0') goto string_end;
414 if (mapping != nullptr) {
415 GrfMsg(1, "choice lists can't be stacked, it's going to get messy now...");
416 if (code != 0x14) src++;
417 } else {
418 static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
419 mapping = new UnmappedChoiceList(mp[code - 0x13], code == 0x14 ? 0 : *src++);
420 }
421 break;
422
423 case 0x16:
424 case 0x17:
425 case 0x18:
426 case 0x19:
427 case 0x1A:
428 case 0x1B:
429 case 0x1C:
430 case 0x1D:
431 case 0x1E:
433 break;
434
435 case 0x1F: Utf8Encode(d, SCC_PUSH_COLOUR); break;
436 case 0x20: Utf8Encode(d, SCC_POP_COLOUR); break;
437
438 case 0x21: Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_FORCE); break;
439
440 default:
441 GrfMsg(1, "missing handler for extended format code");
442 break;
443 }
444 break;
445 }
446
447 case 0x9E: Utf8Encode(d, 0x20AC); break; // Euro
448 case 0x9F: Utf8Encode(d, 0x0178); break; // Y with diaeresis
449 case 0xA0: Utf8Encode(d, SCC_UP_ARROW); break;
450 case 0xAA: Utf8Encode(d, SCC_DOWN_ARROW); break;
451 case 0xAC: Utf8Encode(d, SCC_CHECKMARK); break;
452 case 0xAD: Utf8Encode(d, SCC_CROSS); break;
453 case 0xAF: Utf8Encode(d, SCC_RIGHT_ARROW); break;
454 case 0xB4: Utf8Encode(d, SCC_TRAIN); break;
455 case 0xB5: Utf8Encode(d, SCC_LORRY); break;
456 case 0xB6: Utf8Encode(d, SCC_BUS); break;
457 case 0xB7: Utf8Encode(d, SCC_PLANE); break;
458 case 0xB8: Utf8Encode(d, SCC_SHIP); break;
459 case 0xB9: Utf8Encode(d, SCC_SUPERSCRIPT_M1); break;
460 case 0xBC: Utf8Encode(d, SCC_SMALL_UP_ARROW); break;
461 case 0xBD: Utf8Encode(d, SCC_SMALL_DOWN_ARROW); break;
462 default:
463 /* Validate any unhandled character */
464 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
465 Utf8Encode(d, c);
466 break;
467 }
468 }
469
470string_end:
471 if (mapping != nullptr) {
472 GrfMsg(1, "choice list was incomplete, the whole list is ignored");
473 delete mapping;
474 }
475
476 return dest.str();
477}
478
485static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
486{
487 /* Loop through all languages and see if we can replace a string */
488 for (auto &text : list) {
489 if (text.langid == langid) {
490 text.text = text_to_add;
491 return;
492 }
493 }
494
495 /* If a string wasn't replaced, then we must append the new string */
496 list.push_back(GRFText{ langid, std::string(text_to_add) });
497}
498
508void AddGRFTextToList(GRFTextList &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
509{
510 AddGRFTextToList(list, langid, TranslateTTDPatchCodes(grfid, langid, allow_newlines, text_to_add));
511}
512
522void AddGRFTextToList(GRFTextWrapper &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
523{
524 if (list == nullptr) list = std::make_shared<GRFTextList>();
525 AddGRFTextToList(*list, langid, grfid, allow_newlines, text_to_add);
526}
527
534void AddGRFTextToList(GRFTextWrapper &list, std::string_view text_to_add)
535{
536 if (list == nullptr) list = std::make_shared<GRFTextList>();
537 AddGRFTextToList(*list, GRFLX_UNSPECIFIED, text_to_add);
538}
539
543StringID AddGRFString(uint32_t grfid, uint16_t stringid, uint8_t langid_to_add, bool new_scheme, bool allow_newlines, std::string_view text_to_add, StringID def_string)
544{
545 /* When working with the old language scheme (grf_version is less than 7) and
546 * English or American is among the set bits, simply add it as English in
547 * the new scheme, i.e. as langid = 1.
548 * If English is set, it is pretty safe to assume the translations are not
549 * actually translated.
550 */
551 if (!new_scheme) {
552 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
553 langid_to_add = GRFLX_ENGLISH;
554 } else {
555 StringID ret = STR_EMPTY;
556 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, true, allow_newlines, text_to_add, def_string);
557 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, true, allow_newlines, text_to_add, def_string);
558 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, true, allow_newlines, text_to_add, def_string);
559 return ret;
560 }
561 }
562
563 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
564 if (it == std::end(_grf_text)) {
565 /* Too many strings allocated, return empty. */
566 if (_grf_text.size() == TAB_SIZE_NEWGRF) return STR_EMPTY;
567
568 /* We didn't find our stringid and grfid in the list, allocate a new id. */
569 it = _grf_text.emplace(std::end(_grf_text));
570 it->grfid = grfid;
571 it->stringid = stringid;
572 it->def_string = def_string;
573 }
574 uint id = static_cast<uint>(it - std::begin(_grf_text));
575
576 std::string newtext = TranslateTTDPatchCodes(grfid, langid_to_add, allow_newlines, text_to_add);
577 AddGRFTextToList(it->textholder, langid_to_add, newtext);
578
579 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));
580
582}
583
587StringID GetGRFStringID(uint32_t grfid, StringID stringid)
588{
589 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
590 if (it != std::end(_grf_text)) {
591 uint id = static_cast<uint>(it - std::begin(_grf_text));
593 }
594
595 return STR_UNDEFINED;
596}
597
598
606const char *GetGRFStringFromGRFText(const GRFTextList &text_list)
607{
608 const char *default_text = nullptr;
609
610 /* Search the list of lang-strings of this stringid for current lang */
611 for (const auto &text : text_list) {
612 if (text.langid == _currentLangID) return text.text.c_str();
613
614 /* If the current string is English or American, set it as the
615 * fallback language if the specific language isn't available. */
616 if (text.langid == GRFLX_UNSPECIFIED || (default_text == nullptr && (text.langid == GRFLX_ENGLISH || text.langid == GRFLX_AMERICAN))) {
617 default_text = text.text.c_str();
618 }
619 }
620
621 return default_text;
622}
623
632{
633 return text ? GetGRFStringFromGRFText(*text) : nullptr;
634}
635
639const char *GetGRFStringPtr(uint32_t stringid)
640{
641 assert(stringid < _grf_text.size());
642 assert(_grf_text[stringid].grfid != 0);
643
644 const char *str = GetGRFStringFromGRFText(_grf_text[stringid].textholder);
645 if (str != nullptr) return str;
646
647 /* Use the default string ID if the fallback string isn't available */
648 return GetStringPtr(_grf_text[stringid].def_string);
649}
650
659void SetCurrentGrfLangID(uint8_t language_id)
660{
661 _currentLangID = language_id;
662}
663
664bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
665{
666 if (grf_version < 7) {
667 switch (_currentLangID) {
668 case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0;
669 case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0;
670 case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
671 default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
672 }
673 }
674
675 return (lang_id == _currentLangID || lang_id == GRFLX_UNSPECIFIED);
676}
677
683{
684 _grf_text.clear();
685}
686
688 std::array<uint8_t, 0x30> stack;
689 uint8_t position;
690 const GRFFile *grffile;
691 bool used;
692
693 TextRefStack() : position(0), grffile(nullptr), used(false) {}
694
695 uint8_t PopUnsignedByte() { assert(this->position < this->stack.size()); return this->stack[this->position++]; }
696 int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
697
698 uint16_t PopUnsignedWord()
699 {
700 uint16_t val = this->PopUnsignedByte();
701 return val | (this->PopUnsignedByte() << 8);
702 }
703 int16_t PopSignedWord() { return (int32_t)this->PopUnsignedWord(); }
704
705 uint32_t PopUnsignedDWord()
706 {
707 uint32_t val = this->PopUnsignedWord();
708 return val | (this->PopUnsignedWord() << 16);
709 }
710 int32_t PopSignedDWord() { return (int32_t)this->PopUnsignedDWord(); }
711
712 uint64_t PopUnsignedQWord()
713 {
714 uint64_t val = this->PopUnsignedDWord();
715 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
716 }
717 int64_t PopSignedQWord() { return (int64_t)this->PopUnsignedQWord(); }
718
721 {
722 uint8_t tmp[2];
723 for (int i = 0; i < 2; i++) tmp[i] = this->stack[this->position + i + 6];
724 for (int i = 5; i >= 0; i--) this->stack[this->position + i + 2] = this->stack[this->position + i];
725 for (int i = 0; i < 2; i++) this->stack[this->position + i] = tmp[i];
726 }
727
728 void PushWord(uint16_t word)
729 {
730 if (this->position >= 2) {
731 this->position -= 2;
732 } else {
733 // Rotate right 2 positions
734 std::rotate(this->stack.rbegin(), this->stack.rbegin() + 2, this->stack.rend());
735 }
736 this->stack[this->position] = GB(word, 0, 8);
737 this->stack[this->position + 1] = GB(word, 8, 8);
738 }
739
740 void ResetStack(const GRFFile *grffile)
741 {
742 assert(grffile != nullptr);
743 this->position = 0;
744 this->grffile = grffile;
745 this->used = true;
746 }
747};
748
751
757{
758 return _newgrf_textrefstack.used;
759}
760
769
775{
776 _newgrf_textrefstack = *backup;
777 delete backup;
778}
779
798void StartTextRefStackUsage(const GRFFile *grffile, uint8_t numEntries, const uint32_t *values)
799{
800 extern TemporaryStorageArray<int32_t, 0x110> _temp_store;
801
802 _newgrf_textrefstack.ResetStack(grffile);
803
804 auto stack_it = _newgrf_textrefstack.stack.begin();
805 for (uint i = 0; i < numEntries; i++) {
806 uint32_t value = values != nullptr ? values[i] : _temp_store.GetValue(0x100 + i);
807 for (uint j = 0; j < 32; j += 8) {
808 *stack_it = GB(value, j, 8);
809 stack_it++;
810 }
811 }
812}
813
816{
817 _newgrf_textrefstack.used = false;
818}
819
828char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str, StringParameters &parameters, bool modify_parameters)
829{
830 switch (scc) {
831 default: break;
832
833 /* These control codes take one string parameter, check there are at least that many available. */
858 if (parameters.GetDataLeft() < 1) {
859 Debug(misc, 0, "Too many NewGRF string parameters.");
860 return 0;
861 }
862 break;
863
864 /* These string code take two string parameters, check there are at least that many available. */
868 if (parameters.GetDataLeft() < 2) {
869 Debug(misc, 0, "Too many NewGRF string parameters.");
870 return 0;
871 }
872 break;
873 }
874
875 if (_newgrf_textrefstack.used && modify_parameters) {
876 /* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
877 * After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
878 switch (scc) {
879 default: NOT_REACHED();
880 case SCC_NEWGRF_PRINT_BYTE_SIGNED: parameters.SetParam(0, _newgrf_textrefstack.PopSignedByte()); break;
881 case SCC_NEWGRF_PRINT_QWORD_CURRENCY: parameters.SetParam(0, _newgrf_textrefstack.PopSignedQWord()); break;
882
884 case SCC_NEWGRF_PRINT_DWORD_SIGNED: parameters.SetParam(0, _newgrf_textrefstack.PopSignedDWord()); break;
885
886 case SCC_NEWGRF_PRINT_BYTE_HEX: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedByte()); break;
887 case SCC_NEWGRF_PRINT_QWORD_HEX: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedQWord()); break;
888
892 case SCC_NEWGRF_PRINT_WORD_SIGNED: parameters.SetParam(0, _newgrf_textrefstack.PopSignedWord()); break;
893
899 case SCC_NEWGRF_PRINT_WORD_UNSIGNED: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedWord()); break;
900
904 case SCC_NEWGRF_PRINT_DWORD_HEX: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedDWord()); break;
905
906 /* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
908 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: parameters.SetParam(0, _newgrf_textrefstack.PopUnsignedWord() + CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR); break;
909
910 case SCC_NEWGRF_DISCARD_WORD: _newgrf_textrefstack.PopUnsignedWord(); break;
911
913 case SCC_NEWGRF_PUSH_WORD: _newgrf_textrefstack.PushWord(Utf8Consume(str)); break;
914
918 parameters.SetParam(0, GetCargoTranslation(_newgrf_textrefstack.PopUnsignedWord(), _newgrf_textrefstack.grffile));
919 parameters.SetParam(1, _newgrf_textrefstack.PopUnsignedWord());
920 break;
921
923 parameters.SetParam(0, MapGRFStringID(_newgrf_textrefstack.grffile->grfid, _newgrf_textrefstack.PopUnsignedWord()));
924 break;
925
927 CargoID cargo = GetCargoTranslation(_newgrf_textrefstack.PopUnsignedWord(), _newgrf_textrefstack.grffile);
928 parameters.SetParam(0, cargo < NUM_CARGO ? 1ULL << cargo : 0);
929 break;
930 }
931 }
932 } else {
933 /* Consume additional parameter characters that follow the NewGRF string code. */
934 switch (scc) {
935 default: break;
936
938 Utf8Consume(str);
939 break;
940 }
941 }
942
943 /* Emit OpenTTD's internal string code for the different NewGRF variants. */
944 switch (scc) {
945 default: NOT_REACHED();
950 return SCC_COMMA;
951
956 return SCC_HEX;
957
960 return SCC_CURRENCY_LONG;
961
964
967 return SCC_DATE_LONG;
968
971 return SCC_DATE_SHORT;
972
974 return SCC_VELOCITY;
975
977 return SCC_VOLUME_LONG;
978
980 return SCC_VOLUME_SHORT;
981
983 return SCC_WEIGHT_LONG;
984
986 return SCC_WEIGHT_SHORT;
987
989 return SCC_POWER;
990
992 return SCC_FORCE;
993
995 return SCC_CARGO_LONG;
996
998 return SCC_CARGO_SHORT;
999
1001 return SCC_CARGO_TINY;
1002
1004 return SCC_CARGO_LIST;
1005
1007 return SCC_STATION_NAME;
1008
1009 /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
1013 return 0;
1014 }
1015}
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 CargoID
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:22
static const CargoID NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:74
size_t GetDataLeft() const
Return the amount of elements which can still be read.
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.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
Information about languages and their files.
const LanguageMetadata * _current_language
The currently loaded language.
Definition strings.cpp:54
StringID MapGRFStringID(uint32_t grfid, StringID str)
Used when setting an object's property to map to the GRF's strings while taking in consideration the ...
Definition newgrf.cpp:560
Base for the NewGRF implementation.
CargoID GetCargoTranslation(uint8_t cargo, const GRFFile *grffile, bool usebit)
Translate a GRF-local cargo slot/bitnum into a CargoID.
Cargo support for NewGRFs.
Functionality related to the temporary and persistent storage arrays for NewGRFs.
struct TextRefStack * CreateTextRefStackBackup()
Create a backup of the current NewGRF text stack.
void CleanUpStrings()
House cleaning.
static TextRefStack _newgrf_textrefstack
The stack that is used for TTDP compatible string code parsing.
static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
Add a new text to a GRFText list.
char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str, StringParameters &parameters, bool modify_parameters)
FormatString for NewGRF specific "magic" string control codes.
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).
void SetCurrentGrfLangID(uint8_t language_id)
Equivalence Setter function between game and newgrf langID.
void StartTextRefStackUsage(const GRFFile *grffile, uint8_t numEntries, const uint32_t *values)
Start using the TTDP compatible string code parsing.
const char * GetGRFStringPtr(uint32_t stringid)
Get a C-string from a stringid set by a newgrf.
const char * GetGRFStringFromGRFText(const GRFTextList &text_list)
Get a C-string from a GRFText-list.
static uint8_t _currentLangID
by default, english is used.
void RestoreTextRefStackBackup(struct TextRefStack *backup)
Restore a copy of the text stack to the used stack.
bool UsingNewGRFTextStack()
Check whether the NewGRF text stack is in use.
StringID AddGRFString(uint32_t grfid, uint16_t 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.
StringID GetGRFStringID(uint32_t grfid, StringID stringid)
Returns the index for this stringid associated with its grfID.
GRFBaseLanguages
Explains the newgrf shift bit positioning.
void StopTextRefStackUsage()
Stop using the TTDP compatible string code parsing.
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:396
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition string.cpp:419
size_t Utf8Encode(T buf, char32_t c)
Encode a unicode character and place it in the buffer.
Definition string.cpp:460
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
StringID MakeStringID(StringTab tab, uint index)
Create a StringID.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
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:108
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:2687
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.