OpenTTD Source 20250524-master-gc366e6a48e
newgrf_text.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
18#include "stdafx.h"
19
20#include "debug.h"
21#include "newgrf.h"
22#include "strings_internal.h"
23#include "newgrf_storage.h"
24#include "newgrf_text.h"
25#include "newgrf_cargo.h"
26#include "string_func.h"
28#include "debug.h"
29#include "core/alloc_type.hpp"
32#include "language.h"
33#include <ranges>
34
35#include "table/strings.h"
36#include "table/control_codes.h"
37
38#include "safeguards.h"
39
45enum GRFBaseLanguages : uint8_t {
46 GRFLB_AMERICAN = 0x01,
47 GRFLB_ENGLISH = 0x02,
48 GRFLB_GERMAN = 0x04,
49 GRFLB_FRENCH = 0x08,
50 GRFLB_SPANISH = 0x10,
51 GRFLB_GENERIC = 0x80,
52};
53
54enum GRFExtendedLanguages : uint8_t {
55 GRFLX_AMERICAN = 0x00,
56 GRFLX_ENGLISH = 0x01,
57 GRFLX_GERMAN = 0x02,
58 GRFLX_FRENCH = 0x03,
59 GRFLX_SPANISH = 0x04,
60 GRFLX_UNSPECIFIED = 0x7F,
61};
62
63
70 GRFTextList textholder;
71 StringID def_string;
72 uint32_t grfid;
73 GRFStringID stringid;
74};
75
76
78static uint8_t _current_lang_id = GRFLX_ENGLISH;
79
86int LanguageMap::GetMapping(int newgrf_id, bool gender) const
87{
88 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
89 for (const Mapping &m : map) {
90 if (m.newgrf_id == newgrf_id) return m.openttd_id;
91 }
92 return -1;
93}
94
101int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
102{
103 const std::vector<Mapping> &map = gender ? this->gender_map : this->case_map;
104 for (const Mapping &m : map) {
105 if (m.openttd_id == openttd_id) return m.newgrf_id;
106 }
107 return -1;
108}
109
121
123 int offset;
124
126 std::map<int, std::string> strings;
127
133 void Flush(const LanguageMap *lm, std::string &dest)
134 {
135 if (this->strings.find(0) == this->strings.end()) {
136 /* In case of a (broken) NewGRF without a default,
137 * assume an empty string. */
138 GrfMsg(1, "choice list misses default value");
139 this->strings[0] = std::string();
140 }
141
142 StringBuilder builder(dest);
143
144 if (lm == nullptr) {
145 /* In case there is no mapping, just ignore everything but the default.
146 * A probable cause for this happening is when the language file has
147 * been removed by the user and as such no mapping could be made. */
148 builder += this->strings[0];
149 return;
150 }
151
152 builder.PutUtf8(this->type);
153
154 if (this->type == SCC_SWITCH_CASE) {
155 /*
156 * Format for case switch:
157 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
158 * Each LEN is printed using 2 bytes in big endian order.
159 */
160
161 /* "<NUM CASES>" */
162 int count = 0;
163 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
164 /* Count the ones we have a mapped string for. */
165 if (this->strings.find(lm->GetReverseMapping(i, false)) != this->strings.end()) count++;
166 }
167 builder.PutUint8(count);
168
169 auto add_case = [&](std::string_view str) {
170 /* "<LENn>" */
171 uint16_t len = ClampTo<uint16_t>(str.size());
172 builder.PutUint16LE(len);
173
174 /* "<STRINGn>" */
175 builder += str.substr(0, len);
176 };
177
178 for (uint8_t i = 0; i < _current_language->num_cases; i++) {
179 /* Resolve the string we're looking for. */
180 int idx = lm->GetReverseMapping(i, false);
181 if (this->strings.find(idx) == this->strings.end()) continue;
182 auto &str = this->strings[idx];
183
184 /* "<CASEn>" */
185 builder.PutUint8(i + 1);
186
187 add_case(str);
188 }
189
190 /* "<STRINGDEFAULT>" */
191 add_case(this->strings[0]);
192 } else {
193 if (this->type == SCC_PLURAL_LIST) {
194 builder.PutUint8(lm->plural_form);
195 }
196
197 /*
198 * Format for choice list:
199 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
200 */
201
202 /* "<OFFSET>" */
203 builder.PutUint8(this->offset - 0x80);
204
205 /* "<NUM CHOICES>" */
206 int count = (this->type == SCC_GENDER_LIST ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
207 builder.PutUint8(count);
208
209 /* "<LENs>" */
210 for (int i = 0; i < count; i++) {
211 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
212 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
213 size_t len = str.size();
214 if (len > UINT8_MAX) GrfMsg(1, "choice list string is too long");
215 builder.PutUint8(ClampTo<uint8_t>(len));
216 }
217
218 /* "<STRINGs>" */
219 for (int i = 0; i < count; i++) {
220 int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
221 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
222 uint8_t len = ClampTo<uint8_t>(str.size());
223 builder += str.substr(0, len);
224 }
225 }
226 }
227};
228
238std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool allow_newlines, std::string_view str, StringControlCode byte80)
239{
240 /* Empty input string? Nothing to do here. */
241 if (str.empty()) return {};
242
243 StringConsumer consumer(str);
244
245 /* Is this an unicode string? */
246 bool unicode = consumer.ReadUtf8If(NFO_UTF8_IDENTIFIER);
247
248 /* Helper variable for a possible (string) mapping of plural/gender and cases. */
249 std::optional<UnmappedChoiceList> mapping_pg, mapping_c;
250 std::optional<std::reference_wrapper<std::string>> dest_c;
251
252 std::string dest;
253 StringBuilder builder(dest);
254 while (consumer.AnyBytesLeft()) {
255 char32_t c;
256 if (auto u = unicode ? consumer.TryReadUtf8() : std::nullopt; u.has_value()) {
257 c = *u;
258 /* 'Magic' range of control codes. */
259 if (0xE000 <= c && c <= 0xE0FF) {
260 c -= 0xE000;
261 } else if (c >= 0x20) {
262 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
263 builder.PutUtf8(c);
264 continue;
265 }
266 } else {
267 c = consumer.ReadUint8(); // read as unsigned, otherwise integer promotion breaks it
268 }
269 assert(c <= 0xFF);
270 if (c == '\0') break;
271
272 switch (c) {
273 case 0x01:
274 consumer.SkipUint8();
275 builder.PutChar(' ');
276 break;
277 case 0x0A: break;
278 case 0x0D:
279 if (allow_newlines) {
280 builder.PutChar(0x0A);
281 } else {
282 GrfMsg(1, "Detected newline in string that does not allow one");
283 }
284 break;
285 case 0x0E: builder.PutUtf8(SCC_TINYFONT); break;
286 case 0x0F: builder.PutUtf8(SCC_BIGFONT); break;
287 case 0x1F:
288 consumer.SkipUint8();
289 consumer.SkipUint8();
290 builder.PutChar(' ');
291 break;
292 case 0x7B:
293 case 0x7C:
294 case 0x7D:
295 case 0x7E:
296 case 0x7F: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break;
297 case 0x80: builder.PutUtf8(byte80); break;
298 case 0x81:
299 {
300 uint16_t string = consumer.ReadUint16LE();
301 builder.PutUtf8(SCC_NEWGRF_STRINL);
302 builder.PutUtf8(MapGRFStringID(grfid, GRFStringID{string}));
303 break;
304 }
305 case 0x82:
306 case 0x83:
307 case 0x84: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_DATE_LONG + c - 0x82); break;
308 case 0x85: builder.PutUtf8(SCC_NEWGRF_DISCARD_WORD); break;
309 case 0x86: builder.PutUtf8(SCC_NEWGRF_ROTATE_TOP_4_WORDS); break;
310 case 0x87: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_VOLUME_LONG); break;
311 case 0x88: builder.PutUtf8(SCC_BLUE); break;
312 case 0x89: builder.PutUtf8(SCC_SILVER); break;
313 case 0x8A: builder.PutUtf8(SCC_GOLD); break;
314 case 0x8B: builder.PutUtf8(SCC_RED); break;
315 case 0x8C: builder.PutUtf8(SCC_PURPLE); break;
316 case 0x8D: builder.PutUtf8(SCC_LTBROWN); break;
317 case 0x8E: builder.PutUtf8(SCC_ORANGE); break;
318 case 0x8F: builder.PutUtf8(SCC_GREEN); break;
319 case 0x90: builder.PutUtf8(SCC_YELLOW); break;
320 case 0x91: builder.PutUtf8(SCC_DKGREEN); break;
321 case 0x92: builder.PutUtf8(SCC_CREAM); break;
322 case 0x93: builder.PutUtf8(SCC_BROWN); break;
323 case 0x94: builder.PutUtf8(SCC_WHITE); break;
324 case 0x95: builder.PutUtf8(SCC_LTBLUE); break;
325 case 0x96: builder.PutUtf8(SCC_GRAY); break;
326 case 0x97: builder.PutUtf8(SCC_DKBLUE); break;
327 case 0x98: builder.PutUtf8(SCC_BLACK); break;
328 case 0x9A:
329 {
330 uint8_t code = consumer.ReadUint8();
331 switch (code) {
332 case 0x00: goto string_end;
333 case 0x01: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_CURRENCY); break;
334 /* 0x02: ignore next colour byte is not supported. It works on the final
335 * string and as such hooks into the string drawing routine. At that
336 * point many things already happened, such as splitting up of strings
337 * when drawn over multiple lines or right-to-left translations, which
338 * make the behaviour peculiar, e.g. only happening at specific width
339 * of windows. Or we need to add another pass over the string to just
340 * support this. As such it is not implemented in OpenTTD. */
341 case 0x03:
342 {
343 uint16_t tmp = consumer.ReadUint16LE();
345 builder.PutUtf8(tmp);
346 break;
347 }
348 case 0x06: builder.PutUtf8(SCC_NEWGRF_PRINT_BYTE_HEX); break;
349 case 0x07: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_HEX); break;
350 case 0x08: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_HEX); break;
351 /* 0x09, 0x0A are TTDPatch internal use only string codes. */
352 case 0x0B: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_HEX); break;
353 case 0x0C: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_STATION_NAME); break;
354 case 0x0D: builder.PutUtf8(SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG); break;
355 case 0x0E:
356 case 0x0F:
357 {
358 const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
359 int index = consumer.ReadUint8();
360 int mapped = lm != nullptr ? lm->GetMapping(index, code == 0x0E) : -1;
361 if (mapped >= 0) {
362 builder.PutUtf8(code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
363 builder.PutUtf8(code == 0x0E ? mapped : mapped + 1);
364 }
365 break;
366 }
367
368 case 0x10:
369 case 0x11:
370 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
371 if (code == 0x10) consumer.SkipUint8(); // Skip the index
372 GrfMsg(1, "choice list {} marker found when not expected", code == 0x10 ? "next" : "default");
373 break;
374 } else {
375 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
376 int index = (code == 0x10 ? consumer.ReadUint8() : 0);
377 if (mapping->strings.find(index) != mapping->strings.end()) {
378 GrfMsg(1, "duplicate choice list string, ignoring");
379 } else {
380 builder = StringBuilder(mapping->strings[index]);
381 if (!mapping_pg) dest_c = mapping->strings[index];
382 }
383 }
384 break;
385
386 case 0x12:
387 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
388 GrfMsg(1, "choice list end marker found when not expected");
389 } else {
390 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
391 auto &new_dest = mapping_pg && dest_c ? dest_c->get() : dest;
392 /* Now we can start flushing everything and clean everything up. */
393 mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id), new_dest);
394 if (!mapping_pg) dest_c.reset();
395 mapping.reset();
396
397 builder = StringBuilder(new_dest);
398 }
399 break;
400
401 case 0x13:
402 case 0x14:
403 case 0x15: {
404 auto &mapping = code == 0x14 ? mapping_c : mapping_pg;
405 /* Case mapping can have nested plural/gender mapping. Otherwise nesting is invalid. */
406 if (mapping.has_value() || mapping_pg.has_value()) {
407 GrfMsg(1, "choice lists can't be stacked, it's going to get messy now...");
408 if (code != 0x14) consumer.SkipUint8();
409 } else {
410 static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
411 mapping.emplace(mp[code - 0x13], code == 0x14 ? 0 : consumer.ReadUint8());
412 }
413 break;
414 }
415
416 case 0x16:
417 case 0x17:
418 case 0x18:
419 case 0x19:
420 case 0x1A:
421 case 0x1B:
422 case 0x1C:
423 case 0x1D:
424 case 0x1E:
425 builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_DATE_LONG + code - 0x16);
426 break;
427
428 case 0x1F: builder.PutUtf8(SCC_PUSH_COLOUR); break;
429 case 0x20: builder.PutUtf8(SCC_POP_COLOUR); break;
430
431 case 0x21: builder.PutUtf8(SCC_NEWGRF_PRINT_DWORD_FORCE); break;
432
433 default:
434 GrfMsg(1, "missing handler for extended format code");
435 break;
436 }
437 break;
438 }
439
440 case 0x9E: builder.PutUtf8(0x20AC); break; // Euro
441 case 0x9F: builder.PutUtf8(0x0178); break; // Y with diaeresis
442 case 0xA0: builder.PutUtf8(SCC_UP_ARROW); break;
443 case 0xAA: builder.PutUtf8(SCC_DOWN_ARROW); break;
444 case 0xAC: builder.PutUtf8(SCC_CHECKMARK); break;
445 case 0xAD: builder.PutUtf8(SCC_CROSS); break;
446 case 0xAF: builder.PutUtf8(SCC_RIGHT_ARROW); break;
447 case 0xB4: builder.PutUtf8(SCC_TRAIN); break;
448 case 0xB5: builder.PutUtf8(SCC_LORRY); break;
449 case 0xB6: builder.PutUtf8(SCC_BUS); break;
450 case 0xB7: builder.PutUtf8(SCC_PLANE); break;
451 case 0xB8: builder.PutUtf8(SCC_SHIP); break;
452 case 0xB9: builder.PutUtf8(SCC_SUPERSCRIPT_M1); break;
453 case 0xBC: builder.PutUtf8(SCC_SMALL_UP_ARROW); break;
454 case 0xBD: builder.PutUtf8(SCC_SMALL_DOWN_ARROW); break;
455 default:
456 /* Validate any unhandled character */
457 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
458 builder.PutUtf8(c);
459 break;
460 }
461 }
462
463string_end:
464 if (mapping_pg.has_value() || mapping_c.has_value()) {
465 GrfMsg(1, "choice list was incomplete, the whole list is ignored");
466 }
467
468 return dest;
469}
470
477static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
478{
479 /* Loop through all languages and see if we can replace a string */
480 for (auto &text : list) {
481 if (text.langid == langid) {
482 text.text = text_to_add;
483 return;
484 }
485 }
486
487 /* If a string wasn't replaced, then we must append the new string */
488 list.emplace_back(langid, std::string{text_to_add});
489}
490
500void AddGRFTextToList(GRFTextList &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
501{
502 AddGRFTextToList(list, langid, TranslateTTDPatchCodes(grfid, langid, allow_newlines, text_to_add));
503}
504
514void AddGRFTextToList(GRFTextWrapper &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
515{
516 if (list == nullptr) list = std::make_shared<GRFTextList>();
517 AddGRFTextToList(*list, langid, grfid, allow_newlines, text_to_add);
518}
519
526void AddGRFTextToList(GRFTextWrapper &list, std::string_view text_to_add)
527{
528 if (list == nullptr) list = std::make_shared<GRFTextList>();
529 AddGRFTextToList(*list, GRFLX_UNSPECIFIED, text_to_add);
530}
531
535StringID AddGRFString(uint32_t grfid, GRFStringID stringid, uint8_t langid_to_add, bool new_scheme, bool allow_newlines, std::string_view text_to_add, StringID def_string)
536{
537 /* When working with the old language scheme (grf_version is less than 7) and
538 * English or American is among the set bits, simply add it as English in
539 * the new scheme, i.e. as langid = 1.
540 * If English is set, it is pretty safe to assume the translations are not
541 * actually translated.
542 */
543 if (!new_scheme) {
544 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
545 langid_to_add = GRFLX_ENGLISH;
546 } else {
547 StringID ret = STR_EMPTY;
548 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, true, allow_newlines, text_to_add, def_string);
549 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, true, allow_newlines, text_to_add, def_string);
550 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, true, allow_newlines, text_to_add, def_string);
551 return ret;
552 }
553 }
554
555 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
556 if (it == std::end(_grf_text)) {
557 /* Too many strings allocated, return empty. */
558 if (_grf_text.size() == TAB_SIZE_NEWGRF) return STR_EMPTY;
559
560 /* We didn't find our stringid and grfid in the list, allocate a new id. */
561 it = _grf_text.emplace(std::end(_grf_text));
562 it->grfid = grfid;
563 it->stringid = stringid;
564 it->def_string = def_string;
565 }
566 StringIndexInTab id(it - std::begin(_grf_text));
567
568 std::string newtext = TranslateTTDPatchCodes(grfid, langid_to_add, allow_newlines, text_to_add);
569 AddGRFTextToList(it->textholder, langid_to_add, newtext);
570
571 GrfMsg(3, "Added 0x{:X} grfid {:08X} string 0x{:X} lang 0x{:X} string '{}' ({:X})", id, grfid, stringid, langid_to_add, newtext, MakeStringID(TEXT_TAB_NEWGRF_START, id));
572
574}
575
579StringID GetGRFStringID(uint32_t grfid, GRFStringID stringid)
580{
581 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](const GRFTextEntry &grf_text) { return grf_text.grfid == grfid && grf_text.stringid == stringid; });
582 if (it != std::end(_grf_text)) {
583 StringIndexInTab id(it - std::begin(_grf_text));
585 }
586
587 return STR_UNDEFINED;
588}
589
590
598std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextList &text_list)
599{
600 std::optional<std::string_view> default_text;
601
602 /* Search the list of lang-strings of this stringid for current lang */
603 for (const auto &text : text_list) {
604 if (text.langid == _current_lang_id) return text.text;
605
606 /* If the current string is English or American, set it as the
607 * fallback language if the specific language isn't available. */
608 if (text.langid == GRFLX_UNSPECIFIED || (!default_text.has_value() && (text.langid == GRFLX_ENGLISH || text.langid == GRFLX_AMERICAN))) {
609 default_text = text.text;
610 }
611 }
612
613 return default_text;
614}
615
623std::optional<std::string_view> GetGRFStringFromGRFText(const GRFTextWrapper &text)
624{
625 return text ? GetGRFStringFromGRFText(*text) : std::nullopt;
626}
627
631std::string_view GetGRFStringPtr(StringIndexInTab stringid)
632{
633 assert(stringid.base() < _grf_text.size());
634 assert(_grf_text[stringid].grfid != 0);
635
636 auto str = GetGRFStringFromGRFText(_grf_text[stringid].textholder);
637 if (str.has_value()) return *str;
638
639 /* Use the default string ID if the fallback string isn't available */
640 return GetStringPtr(_grf_text[stringid].def_string);
641}
642
651void SetCurrentGrfLangID(uint8_t language_id)
652{
653 _current_lang_id = language_id;
654}
655
656bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
657{
658 if (grf_version < 7) {
659 switch (_current_lang_id) {
660 case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0;
661 case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0;
662 case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
663 default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
664 }
665 }
666
667 return (lang_id == _current_lang_id || lang_id == GRFLX_UNSPECIFIED);
668}
669
675{
676 _grf_text.clear();
677}
678
680private:
681 std::vector<uint8_t> stack;
682public:
683 const GRFFile *grffile = nullptr;
684
685 TextRefStack(const GRFFile *grffile, std::span<const int32_t> textstack) : grffile(grffile)
686 {
687 this->stack.reserve(textstack.size() * 4 + 6); // for translations it is reasonable to push 3 word and rotate them
688 for (int32_t value : std::ranges::reverse_view(textstack)) {
689 this->PushDWord(value);
690 }
691 }
692
693 uint8_t PopUnsignedByte()
694 {
695 if (this->stack.empty()) return 0;
696 auto res = this->stack.back();
697 this->stack.pop_back();
698 return res;
699 }
700 int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
701
702 uint16_t PopUnsignedWord()
703 {
704 uint16_t val = this->PopUnsignedByte();
705 return val | (this->PopUnsignedByte() << 8);
706 }
707 int16_t PopSignedWord() { return (int32_t)this->PopUnsignedWord(); }
708
709 uint32_t PopUnsignedDWord()
710 {
711 uint32_t val = this->PopUnsignedWord();
712 return val | (this->PopUnsignedWord() << 16);
713 }
714 int32_t PopSignedDWord() { return (int32_t)this->PopUnsignedDWord(); }
715
716 uint64_t PopUnsignedQWord()
717 {
718 uint64_t val = this->PopUnsignedDWord();
719 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
720 }
721 int64_t PopSignedQWord() { return (int64_t)this->PopUnsignedQWord(); }
722
725 {
726 auto w1 = this->PopUnsignedWord();
727 auto w2 = this->PopUnsignedWord();
728 auto w3 = this->PopUnsignedWord();
729 auto w4 = this->PopUnsignedWord();
730 this->PushWord(w3);
731 this->PushWord(w2);
732 this->PushWord(w1);
733 this->PushWord(w4);
734 }
735
736 void PushByte(uint8_t b)
737 {
738 this->stack.push_back(b);
739 }
740
741 void PushWord(uint16_t w)
742 {
743 this->PushByte(GB(w, 8, 8));
744 this->PushByte(GB(w, 0, 8));
745 }
746
747 void PushDWord(uint32_t dw)
748 {
749 this->PushWord(GB(dw, 16, 16));
750 this->PushWord(GB(dw, 0, 16));
751 }
752};
753
754static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params);
755
763static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector<StringParameter> &params)
764{
765 /* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
766 * After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
767 switch (scc) {
768 default: return;
769
770 case SCC_PLURAL_LIST:
771 consumer.SkipUint8(); // plural form
772 [[fallthrough]];
773 case SCC_GENDER_LIST: {
774 consumer.SkipUint8(); // offset
775 /* plural and gender choices cannot contain any string commands, so just skip the whole thing */
776 uint num = consumer.ReadUint8();
777 uint total_len = 0;
778 for (uint i = 0; i != num; i++) {
779 total_len += consumer.ReadUint8();
780 }
781 consumer.Skip(total_len);
782 break;
783 }
784
785 case SCC_SWITCH_CASE: {
786 /* skip all cases and continue with default case */
787 uint num = consumer.ReadUint8();
788 for (uint i = 0; i != num; i++) {
789 consumer.SkipUint8();
790 auto len = consumer.ReadUint16LE();
791 consumer.Skip(len);
792 }
793 consumer.SkipUint16LE(); // length of default
794 break;
795 }
796
797 case SCC_GENDER_INDEX:
798 case SCC_SET_CASE:
799 consumer.SkipUint8();
800 break;
801
802 case SCC_ARG_INDEX:
803 NOT_REACHED();
804 break;
805
806 case SCC_NEWGRF_PRINT_BYTE_SIGNED: params.emplace_back(stack.PopSignedByte()); break;
807 case SCC_NEWGRF_PRINT_QWORD_CURRENCY: params.emplace_back(stack.PopSignedQWord()); break;
808
810 case SCC_NEWGRF_PRINT_DWORD_SIGNED: params.emplace_back(stack.PopSignedDWord()); break;
811
812 case SCC_NEWGRF_PRINT_BYTE_HEX: params.emplace_back(stack.PopUnsignedByte()); break;
813 case SCC_NEWGRF_PRINT_QWORD_HEX: params.emplace_back(stack.PopUnsignedQWord()); break;
814
818 case SCC_NEWGRF_PRINT_WORD_SIGNED: params.emplace_back(stack.PopSignedWord()); break;
819
825 case SCC_NEWGRF_PRINT_WORD_UNSIGNED: params.emplace_back(stack.PopUnsignedWord()); break;
826
830 case SCC_NEWGRF_PRINT_DWORD_HEX: params.emplace_back(stack.PopUnsignedDWord()); break;
831
832 /* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
834 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: params.emplace_back(CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR + stack.PopUnsignedWord()); break;
835
836 case SCC_NEWGRF_DISCARD_WORD: stack.PopUnsignedWord(); break;
837
839 case SCC_NEWGRF_PUSH_WORD: stack.PushWord(consumer.ReadUtf8(0)); break;
840
844 params.emplace_back(GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile));
845 params.emplace_back(stack.PopUnsignedWord());
846 break;
847
848 case SCC_NEWGRF_STRINL: {
849 StringID stringid = consumer.ReadUtf8(STR_NULL);
850 /* We also need to handle the substring's stack usage. */
851 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
852 break;
853 }
854
856 StringID stringid = MapGRFStringID(stack.grffile->grfid, GRFStringID{stack.PopUnsignedWord()});
857 params.emplace_back(stringid);
858 /* We also need to handle the substring's stack usage. */
859 HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params);
860 break;
861 }
862
864 CargoType cargo = GetCargoTranslation(stack.PopUnsignedWord(), stack.grffile);
865 params.emplace_back(cargo < NUM_CARGO ? 1ULL << cargo : 0);
866 break;
867 }
868 }
869}
870
877char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
878{
879 switch (scc) {
880 default:
881 return scc;
882
887 return SCC_COMMA;
888
893 return SCC_HEX;
894
897 return SCC_CURRENCY_LONG;
898
901 return SCC_DATE_LONG;
902
905 return SCC_DATE_SHORT;
906
908 return SCC_VELOCITY;
909
911 return SCC_VOLUME_LONG;
912
914 return SCC_VOLUME_SHORT;
915
917 return SCC_WEIGHT_LONG;
918
920 return SCC_WEIGHT_SHORT;
921
923 return SCC_POWER;
924
926 return SCC_FORCE;
927
929 return SCC_CARGO_LONG;
930
932 return SCC_CARGO_SHORT;
933
935 return SCC_CARGO_TINY;
936
938 return SCC_CARGO_LIST;
939
941 return SCC_STATION_NAME;
942
943 /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
945 consumer.SkipUtf8();
946 return 0;
947
950 return 0;
951 }
952}
953
960static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector<StringParameter> &params)
961{
962 StringConsumer consumer(str);
963 while (consumer.AnyBytesLeft()) {
964 char32_t scc = consumer.ReadUtf8();
965 ProcessNewGRFStringControlCode(scc, consumer, stack, params);
966 }
967}
968
976std::vector<StringParameter> GetGRFSringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span<const int32_t> textstack)
977{
978 if (stringid == INVALID_STRING_ID) return {};
979
980 auto str = GetStringPtr(stringid);
981
982 std::vector<StringParameter> params;
983 params.reserve(20);
984
985 TextRefStack stack{grffile, textstack};
986 HandleNewGRFStringControlCodes(str, stack, params);
987
988 return params;
989}
990
998std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span<const int32_t> textstack)
999{
1000 StringID stringid = GetGRFStringID(grffile->grfid, grfstringid);
1001 auto params = GetGRFSringTextStackParameters(grffile, stringid, textstack);
1002 return GetStringWithArgs(stringid, params);
1003}
Helper types related to the allocation of memory.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:75
void PutUtf8(char32_t c)
Append UTF.8 char.
void PutUint16LE(uint16_t value)
Append binary uint16 using little endian.
void PutChar(char c)
Append 8-bit char.
void PutUint8(uint8_t value)
Append binary uint8.
A sort-of mixin that adds 'at(pos)' and 'operator[](pos)' implementations for 'ConvertibleThroughBase...
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.
Control codes that are embedded in the translation strings.
StringControlCode
List of string control codes used for string formatting, displaying, and by strgen to generate the la...
@ SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT
9A 19: Read 2 bytes from the stack as short signed volume
@ SCC_NEWGRF_PRINT_DWORD_CURRENCY
8F: Read 4 bytes from the stack as currency
@ SCC_NEWGRF_PRINT_WORD_HEX
9A 07: Read 2 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT
9A 1A: Read 2 bytes from the stack as short unsigned weight
@ SCC_NEWGRF_ROTATE_TOP_4_WORDS
86: Rotate the top 4 words of the stack (W4 W1 W2 W3)
@ SCC_TINYFONT
Switch to small font.
@ SCC_NEWGRF_PRINT_DWORD_DATE_LONG
9A 16: Read 4 bytes from the stack as base 0 date
@ SCC_NEWGRF_PRINT_DWORD_HEX
9A 08: Read 4 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_DATE_SHORT
83: Read 2 bytes from the stack as base 1920 date
@ SCC_BIGFONT
Switch to large font.
@ SCC_NEWGRF_PRINT_BYTE_HEX
9A 06: Read 1 byte from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_UNSIGNED
7E: Read 2 bytes from the stack as unsigned value
@ SCC_NEWGRF_PRINT_BYTE_SIGNED
7D: Read 1 byte from the stack as signed value
@ SCC_NEWGRF_PRINT_WORD_CARGO_NAME
9A 1E: Read 2 bytes from the stack as cargo name
@ SCC_NEWGRF_PRINT_DWORD_SIGNED
7B: Read 4 bytes from the stack
@ SCC_NEWGRF_STRINL
Inline another string at the current position, StringID is encoded in the string.
@ SCC_NEWGRF_PRINT_WORD_CARGO_SHORT
9A 1C: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_WORD_STATION_NAME
9A 0C: Read 2 bytes from the stack as station name
@ SCC_NEWGRF_PRINT_WORD_DATE_LONG
82: Read 2 bytes from the stack as base 1920 date
@ SCC_NEWGRF_PRINT_WORD_SIGNED
7C: Read 2 bytes from the stack as signed value
@ SCC_NEWGRF_PRINT_WORD_POWER
9A 18: Read 2 bytes from the stack as unsigned power
@ SCC_NEWGRF_PUSH_WORD
9A 03: Pushes 2 bytes onto the stack
@ SCC_NEWGRF_PRINT_WORD_STRING_ID
81: Read 2 bytes from the stack as String ID
@ SCC_NEWGRF_PRINT_WORD_VOLUME_LONG
87: Read 2 bytes from the stack as long signed volume
@ SCC_NEWGRF_DISCARD_WORD
85: Discard the next two bytes
@ SCC_NEWGRF_PRINT_DWORD_FORCE
9A 21: Read 4 bytes from the stack as unsigned force
@ SCC_NEWGRF_PRINT_WORD_CARGO_TINY
9A 1D: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_QWORD_HEX
9A 0B: Read 8 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_CARGO_LONG
9A 1B: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_WORD_SPEED
84: Read 2 bytes from the stack as signed speed
@ SCC_NEWGRF_PRINT_DWORD_DATE_SHORT
9A 17: Read 4 bytes from the stack as base 0 date
@ SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG
9A 0D: Read 2 bytes from the stack as long unsigned weight
@ SCC_NEWGRF_PRINT_QWORD_CURRENCY
9A 01: Read 8 bytes from the stack as currency
Functions related to debugging.
Information about languages and their files.
const LanguageMetadata * _current_language
The currently loaded language.
Definition strings.cpp:55
Base for the NewGRF implementation.
StringID MapGRFStringID(uint32_t grfid, GRFStringID str)
Used when setting an object's property to map to the GRF's strings while taking in consideration the ...
CargoType GetCargoTranslation(uint8_t cargo, const GRFFile *grffile, bool usebit)
Translate a GRF-local cargo slot/bitnum into a CargoType.
Cargo support for NewGRFs.
Functionality related to the temporary and persistent storage arrays for NewGRFs.
void CleanUpStrings()
House cleaning.
static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
Add a new text to a GRFText list.
std::string_view GetGRFStringPtr(StringIndexInTab stringid)
Get a C-string from a stringid set by a newgrf.
GRFBaseLanguages
Explains the newgrf shift bit positioning.
static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector< StringParameter > &params)
Handle control codes in a NewGRF string, processing the stack and filling parameters.
std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool allow_newlines, std::string_view str, StringControlCode byte80)
Translate TTDPatch string codes into something OpenTTD can handle (better).
std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span< const int32_t > textstack)
Format a GRF string using the text ref stack for parameters.
void SetCurrentGrfLangID(uint8_t language_id)
Equivalence Setter function between game and newgrf langID.
StringID GetGRFStringID(uint32_t grfid, GRFStringID stringid)
Returns the index for this stringid associated with its grfID.
std::optional< std::string_view > GetGRFStringFromGRFText(const GRFTextList &text_list)
Get a C-string from a GRFText-list.
std::vector< StringParameter > GetGRFSringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span< const int32_t > textstack)
Process the text ref stack for a GRF String and return its parameters.
StringID AddGRFString(uint32_t grfid, GRFStringID stringid, uint8_t langid_to_add, bool new_scheme, bool allow_newlines, std::string_view text_to_add, StringID def_string)
Add the new read string into our structure.
static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector< StringParameter > &params)
Process NewGRF string control code instructions.
static uint8_t _current_lang_id
by default, english is used.
char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
Emit OpenTTD's internal string code for the different NewGRF string codes.
Header of Action 04 "universal holder" structure and functions.
std::shared_ptr< GRFTextList > GRFTextWrapper
Reference counted wrapper around a GRFText pointer.
std::vector< GRFText > GRFTextList
A GRF text with a list of translations.
static const char32_t NFO_UTF8_IDENTIFIER
This character (thorn) indicates a unicode string to NFO.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
Definition string.cpp:371
Compose strings from textual and binary data.
Parse strings.
Functions related to low-level strings.
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition string_type.h:25
void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
Get a parsed string with most special stringcodes replaced by the string parameters.
Definition strings.cpp:327
StringID MakeStringID(StringTab tab, StringIndexInTab index)
Create a StringID.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
static const StringID INVALID_STRING_ID
Constant representing an invalid string (16bit in case it is used in savegames)
static const uint TAB_SIZE_NEWGRF
Number of strings for NewGRFs.
@ TEXT_TAB_NEWGRF_START
Start of NewGRF supplied strings.
Dynamic data of a loaded NewGRF.
Definition newgrf.h:115
Holder of the above structure.
Mapping between NewGRF and OpenTTD IDs.
Mapping of language data between a NewGRF and OpenTTD.
std::vector< Mapping > gender_map
Mapping of NewGRF and OpenTTD IDs for genders.
int GetReverseMapping(int openttd_id, bool gender) const
Get the mapping from OpenTTD's internal ID to the NewGRF supplied ID.
int plural_form
The plural form used for this language.
static const LanguageMap * GetLanguageMap(uint32_t grfid, uint8_t language_id)
Get the language map associated with a given NewGRF and language.
Definition newgrf.cpp:351
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.