OpenTTD Source  20241120-master-g6d3adc6169
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 
51 enum 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 
66 struct GRFTextEntry {
67  GRFTextList textholder;
68  StringID def_string;
69  uint32_t grfid;
70  uint16_t stringid;
71 };
72 
73 
74 static std::vector<GRFTextEntry> _grf_text;
75 static uint8_t _currentLangID = GRFLX_ENGLISH;
76 
83 int 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 
98 int 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 
116  {
117  }
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 
236 std::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;
364  case 0x0C: Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_STATION_NAME); 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 
470 string_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 
485 static 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 
508 void 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 
522 void AddGRFTextToList(GRFTextWrapper &list, uint8_t langid, uint32_t grfid, bool allow_newlines, std::string_view text_to_add)
523 {
524  if (!list) list.reset(new GRFTextList());
525  AddGRFTextToList(*list, langid, grfid, allow_newlines, text_to_add);
526 }
527 
534 void AddGRFTextToList(GRFTextWrapper &list, std::string_view text_to_add)
535 {
536  if (!list) list.reset(new GRFTextList());
537  AddGRFTextToList(*list, GRFLX_UNSPECIFIED, text_to_add);
538 }
539 
543 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)
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::find_if(std::begin(_grf_text), std::end(_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 
587 StringID GetGRFStringID(uint32_t grfid, StringID stringid)
588 {
589  auto it = std::find_if(std::begin(_grf_text), std::end(_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 
606 const 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 
631 const char *GetGRFStringFromGRFText(const GRFTextWrapper &text)
632 {
633  return text ? GetGRFStringFromGRFText(*text) : nullptr;
634 }
635 
639 const 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 
659 void SetCurrentGrfLangID(uint8_t language_id)
660 {
661  _currentLangID = language_id;
662 }
663 
664 bool 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 
687 struct TextRefStack {
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 
766 {
768 }
769 
775 {
776  _newgrf_textrefstack = *backup;
777  delete backup;
778 }
779 
798 void 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 
828 uint RemapNewGRFStringControlCode(uint 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. */
1012  case SCC_NEWGRF_PUSH_WORD:
1013  return 0;
1014  }
1015 }
Helper types related to the allocation of memory.
constexpr static debug_inline 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...
Definition: control_codes.h:17
@ 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.
Definition: control_codes.h:30
@ 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.
Definition: control_codes.h:31
@ 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.
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.
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.
struct TextRefStack * CreateTextRefStackBackup()
Create a backup of the current NewGRF text stack.
static uint8_t _currentLangID
by default, english is used.
Definition: newgrf_text.cpp:75
uint RemapNewGRFStringControlCode(uint scc, const char **str, StringParameters &parameters, bool modify_parameters)
FormatString for NewGRF specific "magic" string control codes.
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.
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.
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.
Definition: newgrf_text.cpp:42
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.
Definition: string_func.h:124
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition: string_type.h:25
StringID MakeStringID(StringTab tab, uint index)
Create a StringID.
Definition: strings_func.h:49
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
Definition: strings_type.h:16
static const uint TAB_SIZE_NEWGRF
Number of strings for NewGRFs.
Definition: strings_type.h:52
@ TEXT_TAB_NEWGRF_START
Start of NewGRF supplied strings.
Definition: strings_type.h:40
Dynamic data of a loaded NewGRF.
Definition: newgrf.h:108
Holder of the above structure.
Definition: newgrf_text.cpp:66
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.
Definition: newgrf_text.cpp:98
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:2679
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.
Definition: newgrf_text.cpp:83
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.