22#include "strings_internal.h"
35#include "table/strings.h"
46 GRFLB_AMERICAN = 0x01,
54enum GRFExtendedLanguages : uint8_t {
55 GRFLX_AMERICAN = 0x00,
60 GRFLX_UNSPECIFIED = 0x7F,
90 if (m.newgrf_id == newgrf_id)
return m.openttd_id;
105 if (m.openttd_id == openttd_id)
return m.newgrf_id;
135 if (this->strings.find(0) == this->strings.end()) {
138 GrfMsg(1,
"choice list misses default value");
139 this->strings[0] = std::string();
148 builder += this->strings[0];
154 if (this->type == SCC_SWITCH_CASE) {
165 if (this->strings.find(lm->
GetReverseMapping(i,
false)) != this->strings.end()) count++;
169 auto add_case = [&](std::string_view str) {
171 uint16_t len = ClampTo<uint16_t>(str.size());
175 builder += str.substr(0, len);
181 if (this->strings.find(idx) == this->strings.end())
continue;
182 auto &str = this->strings[idx];
191 add_case(this->strings[0]);
193 if (this->type == SCC_PLURAL_LIST) {
203 builder.
PutUint8(this->offset - 0x80);
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));
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);
241 if (str.empty())
return {};
249 std::optional<UnmappedChoiceList> mapping_pg, mapping_c;
250 std::optional<std::reference_wrapper<std::string>> dest_c;
256 if (
auto u = unicode ? consumer.
TryReadUtf8() : std::nullopt; u.has_value()) {
259 if (0xE000 <= c && c <= 0xE0FF) {
261 }
else if (c >= 0x20) {
270 if (c ==
'\0')
break;
279 if (allow_newlines) {
282 GrfMsg(1,
"Detected newline in string that does not allow one");
297 case 0x80: builder.
PutUtf8(byte80);
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;
332 case 0x00:
goto string_end;
360 int mapped = lm !=
nullptr ? lm->
GetMapping(index, code == 0x0E) : -1;
362 builder.
PutUtf8(code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
363 builder.
PutUtf8(code == 0x0E ? mapped : mapped + 1);
370 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
372 GrfMsg(1,
"choice list {} marker found when not expected", code == 0x10 ?
"next" :
"default");
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");
381 if (!mapping_pg) dest_c = mapping->strings[index];
387 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
388 GrfMsg(1,
"choice list end marker found when not expected");
390 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
391 auto &new_dest = mapping_pg && dest_c ? dest_c->get() : dest;
394 if (!mapping_pg) dest_c.reset();
404 auto &mapping = code == 0x14 ? mapping_c : mapping_pg;
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...");
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());
428 case 0x1F: builder.
PutUtf8(SCC_PUSH_COLOUR);
break;
429 case 0x20: builder.
PutUtf8(SCC_POP_COLOUR);
break;
434 GrfMsg(1,
"missing handler for extended format code");
440 case 0x9E: builder.
PutUtf8(0x20AC);
break;
441 case 0x9F: builder.
PutUtf8(0x0178);
break;
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;
464 if (mapping_pg.has_value() || mapping_c.has_value()) {
465 GrfMsg(1,
"choice list was incomplete, the whole list is ignored");
480 for (
auto &text : list) {
481 if (text.langid == langid) {
482 text.text = text_to_add;
488 list.emplace_back(langid, std::string{text_to_add});
516 if (list ==
nullptr) list = std::make_shared<GRFTextList>();
528 if (list ==
nullptr) list = std::make_shared<GRFTextList>();
544 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
545 langid_to_add = GRFLX_ENGLISH;
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);
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)) {
561 it = _grf_text.emplace(std::end(_grf_text));
563 it->stringid = stringid;
564 it->def_string = def_string;
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));
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)) {
587 return STR_UNDEFINED;
600 std::optional<std::string_view> default_text;
603 for (
const auto &text : text_list) {
608 if (text.langid == GRFLX_UNSPECIFIED || (!default_text.has_value() && (text.langid == GRFLX_ENGLISH || text.langid == GRFLX_AMERICAN))) {
609 default_text = text.text;
633 assert(stringid.base() < _grf_text.size());
634 assert(_grf_text[stringid].grfid != 0);
637 if (str.has_value())
return *str;
640 return GetStringPtr(_grf_text[stringid].def_string);
656bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
658 if (grf_version < 7) {
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;
681 std::vector<uint8_t> stack;
683 const GRFFile *grffile =
nullptr;
685 TextRefStack(
const GRFFile *grffile, std::span<const int32_t> textstack) : grffile(grffile)
687 this->stack.reserve(textstack.size() * 4 + 6);
688 for (int32_t value : std::ranges::reverse_view(textstack)) {
689 this->PushDWord(value);
693 uint8_t PopUnsignedByte()
695 if (this->stack.empty())
return 0;
696 auto res = this->stack.back();
697 this->stack.pop_back();
700 int8_t PopSignedByte() {
return (int8_t)this->PopUnsignedByte(); }
702 uint16_t PopUnsignedWord()
704 uint16_t val = this->PopUnsignedByte();
705 return val | (this->PopUnsignedByte() << 8);
707 int16_t PopSignedWord() {
return (int32_t)this->PopUnsignedWord(); }
709 uint32_t PopUnsignedDWord()
711 uint32_t val = this->PopUnsignedWord();
712 return val | (this->PopUnsignedWord() << 16);
714 int32_t PopSignedDWord() {
return (int32_t)this->PopUnsignedDWord(); }
716 uint64_t PopUnsignedQWord()
718 uint64_t val = this->PopUnsignedDWord();
719 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
721 int64_t PopSignedQWord() {
return (int64_t)this->PopUnsignedQWord(); }
726 auto w1 = this->PopUnsignedWord();
727 auto w2 = this->PopUnsignedWord();
728 auto w3 = this->PopUnsignedWord();
729 auto w4 = this->PopUnsignedWord();
736 void PushByte(uint8_t b)
738 this->stack.push_back(b);
741 void PushWord(uint16_t w)
743 this->PushByte(
GB(w, 8, 8));
744 this->PushByte(
GB(w, 0, 8));
747 void PushDWord(uint32_t dw)
749 this->PushWord(
GB(dw, 16, 16));
750 this->PushWord(
GB(dw, 0, 16));
770 case SCC_PLURAL_LIST:
773 case SCC_GENDER_LIST: {
778 for (uint i = 0; i != num; i++) {
781 consumer.
Skip(total_len);
785 case SCC_SWITCH_CASE: {
788 for (uint i = 0; i != num; i++) {
797 case SCC_GENDER_INDEX:
845 params.emplace_back(stack.PopUnsignedWord());
857 params.emplace_back(stringid);
865 params.emplace_back(cargo <
NUM_CARGO ? 1ULL << cargo : 0);
897 return SCC_CURRENCY_LONG;
901 return SCC_DATE_LONG;
905 return SCC_DATE_SHORT;
911 return SCC_VOLUME_LONG;
914 return SCC_VOLUME_SHORT;
917 return SCC_WEIGHT_LONG;
920 return SCC_WEIGHT_SHORT;
929 return SCC_CARGO_LONG;
932 return SCC_CARGO_SHORT;
935 return SCC_CARGO_TINY;
938 return SCC_CARGO_LIST;
941 return SCC_STATION_NAME;
980 auto str = GetStringPtr(stringid);
982 std::vector<StringParameter> params;
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.
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
void PutUtf8(char32_t c)
Append UTF.8 char.
void PutUint16LE(uint16_t value)
Append binary uint16 using little endian.
void PutChar(char c)
Append 8-bit char.
void PutUint8(uint8_t value)
Append binary uint8.
Compose data into a growing std::string.
Parse data from a string / buffer.
char32_t ReadUtf8(char32_t def='?')
Read UTF-8 character, and advance reader.
void SkipUtf8()
Skip UTF-8 character, and advance reader.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
uint8_t ReadUint8(uint8_t def=0)
Read binary uint8, and advance reader.
void SkipUint16LE()
Skip binary uint16, and advance reader.
uint16_t ReadUint16LE(uint16_t def=0)
Read binary uint16 using little endian, and advance reader.
void SkipUint8()
Skip binary uint8.
std::optional< char32_t > TryReadUtf8()
Try to read a UTF-8 character, and then advance reader.
bool ReadUtf8If(char32_t c)
Check whether the next UTF-8 char matches 'c', and skip it.
void Skip(size_type len)
Discard some bytes.
static constexpr TimerGame< struct Calendar >::Date DAYS_TILL_ORIGINAL_BASE_YEAR
The date of the first day of the original base year.
A sort-of mixin that implements 'at(pos)' and 'operator[](pos)' only for a specific type.
Control codes that are embedded in the translation strings.
StringControlCode
List of string control codes used for string formatting, displaying, and by strgen to generate the la...
@ SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT
9A 19: Read 2 bytes from the stack as short signed volume
@ SCC_NEWGRF_PRINT_DWORD_CURRENCY
8F: Read 4 bytes from the stack as currency
@ SCC_NEWGRF_PRINT_WORD_HEX
9A 07: Read 2 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT
9A 1A: Read 2 bytes from the stack as short unsigned weight
@ SCC_NEWGRF_ROTATE_TOP_4_WORDS
86: Rotate the top 4 words of the stack (W4 W1 W2 W3)
@ SCC_TINYFONT
Switch to small font.
@ SCC_NEWGRF_PRINT_DWORD_DATE_LONG
9A 16: Read 4 bytes from the stack as base 0 date
@ SCC_NEWGRF_PRINT_DWORD_HEX
9A 08: Read 4 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_DATE_SHORT
83: Read 2 bytes from the stack as base 1920 date
@ SCC_BIGFONT
Switch to large font.
@ SCC_NEWGRF_PRINT_BYTE_HEX
9A 06: Read 1 byte from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_UNSIGNED
7E: Read 2 bytes from the stack as unsigned value
@ SCC_NEWGRF_PRINT_BYTE_SIGNED
7D: Read 1 byte from the stack as signed value
@ SCC_NEWGRF_PRINT_WORD_CARGO_NAME
9A 1E: Read 2 bytes from the stack as cargo name
@ SCC_NEWGRF_PRINT_DWORD_SIGNED
7B: Read 4 bytes from the stack
@ SCC_NEWGRF_STRINL
Inline another string at the current position, StringID is encoded in the string.
@ SCC_NEWGRF_PRINT_WORD_CARGO_SHORT
9A 1C: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_WORD_STATION_NAME
9A 0C: Read 2 bytes from the stack as station name
@ SCC_NEWGRF_PRINT_WORD_DATE_LONG
82: Read 2 bytes from the stack as base 1920 date
@ SCC_NEWGRF_PRINT_WORD_SIGNED
7C: Read 2 bytes from the stack as signed value
@ SCC_NEWGRF_PRINT_WORD_POWER
9A 18: Read 2 bytes from the stack as unsigned power
@ SCC_NEWGRF_PUSH_WORD
9A 03: Pushes 2 bytes onto the stack
@ SCC_NEWGRF_PRINT_WORD_STRING_ID
81: Read 2 bytes from the stack as String ID
@ SCC_NEWGRF_PRINT_WORD_VOLUME_LONG
87: Read 2 bytes from the stack as long signed volume
@ SCC_NEWGRF_DISCARD_WORD
85: Discard the next two bytes
@ SCC_NEWGRF_PRINT_DWORD_FORCE
9A 21: Read 4 bytes from the stack as unsigned force
@ SCC_NEWGRF_PRINT_WORD_CARGO_TINY
9A 1D: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_QWORD_HEX
9A 0B: Read 8 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_CARGO_LONG
9A 1B: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_WORD_SPEED
84: Read 2 bytes from the stack as signed speed
@ SCC_NEWGRF_PRINT_DWORD_DATE_SHORT
9A 17: Read 4 bytes from the stack as base 0 date
@ SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG
9A 0D: Read 2 bytes from the stack as long unsigned weight
@ SCC_NEWGRF_PRINT_QWORD_CURRENCY
9A 01: Read 8 bytes from the stack as currency
Functions related to debugging.
Information about languages and their files.
const LanguageMetadata * _current_language
The currently loaded language.
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 > ¶ms)
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 > ¶ms)
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.
Compose strings from textual and binary data.
Functions related to low-level strings.
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
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.
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.
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.
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.
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.