1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "Formatting.h"
11 
12 #include "../config/Config.h"
13 #include "../util/Util.h"
14 #include "Localisation.h"
15 #include "StringIds.h"
16 
17 #include <cmath>
18 #include <cstdint>
19 
20 namespace OpenRCT2
21 {
22     static void FormatMonthYear(FormatBuffer& ss, int32_t month, int32_t year);
23 
ParseNumericToken(std::string_view s)24     static std::optional<int32_t> ParseNumericToken(std::string_view s)
25     {
26         if (s.size() >= 3 && s.size() <= 5 && s[0] == '{' && s[s.size() - 1] == '}')
27         {
28             char buffer[8]{};
29             std::memcpy(buffer, s.data() + 1, s.size() - 2);
30             return std::atoi(buffer);
31         }
32         return std::nullopt;
33     }
34 
ParseNumericToken(std::string_view str,size_t & i)35     static std::optional<int32_t> ParseNumericToken(std::string_view str, size_t& i)
36     {
37         if (i < str.size() && str[i] == '{')
38         {
39             auto parameterStart = i;
40             do
41             {
42                 i++;
43             } while (i < str.size() && str[i] != '}');
44             if (i < str.size() && str[i] == '}')
45             {
46                 i++;
47             }
48 
49             auto paramter = str.substr(parameterStart, i - parameterStart);
50             return ParseNumericToken(paramter);
51         }
52         return std::nullopt;
53     }
54 
token(FormatToken k,std::string_view s,uint32_t p)55     FmtString::token::token(FormatToken k, std::string_view s, uint32_t p)
56         : kind(k)
57         , text(s)
58         , parameter(p)
59     {
60     }
61 
IsLiteral() const62     bool FmtString::token::IsLiteral() const
63     {
64         return kind == FormatToken::Literal;
65     }
66 
IsCodepoint() const67     bool FmtString::token::IsCodepoint() const
68     {
69         return kind == FormatToken::Escaped;
70     }
71 
GetCodepoint() const72     codepoint_t FmtString::token::GetCodepoint() const
73     {
74         if (kind == FormatToken::Escaped)
75         {
76             // Assume text is only "{{" or "}}" for now
77             return text[0];
78         }
79         return 0;
80     }
81 
iterator(std::string_view s,size_t i)82     FmtString::iterator::iterator(std::string_view s, size_t i)
83         : str(s)
84         , index(i)
85     {
86         update();
87     }
88 
update()89     void FmtString::iterator::update()
90     {
91         auto i = index;
92         if (i >= str.size())
93         {
94             current = token();
95             return;
96         }
97 
98         if (str[i] == '\n' || str[i] == '\r')
99         {
100             i++;
101         }
102         else if (str[i] == '{' && i + 1 < str.size() && str[i + 1] == '{')
103         {
104             i += 2;
105         }
106         else if (str[i] == '}' && i + 1 < str.size() && str[i + 1] == '}')
107         {
108             i += 2;
109         }
110         else if (str[i] == '{' && i + 1 < str.size() && str[i + 1] != '{')
111         {
112             // Move to end brace
113             auto startIndex = i;
114             do
115             {
116                 i++;
117             } while (i < str.size() && str[i] != '}');
118             if (i < str.size() && str[i] == '}')
119             {
120                 i++;
121 
122                 auto inner = str.substr(startIndex + 1, i - startIndex - 2);
123                 if (inner == "MOVE_X")
124                 {
125                     uint32_t p = 0;
126                     auto p0 = ParseNumericToken(str, i);
127                     if (p0)
128                     {
129                         p = *p0;
130                     }
131                     current = token(FormatToken::Move, str.substr(startIndex, i - startIndex), p);
132                     return;
133                 }
134 
135                 if (inner == "INLINE_SPRITE")
136                 {
137                     uint32_t p = 0;
138                     auto p0 = ParseNumericToken(str, i);
139                     auto p1 = ParseNumericToken(str, i);
140                     auto p2 = ParseNumericToken(str, i);
141                     auto p3 = ParseNumericToken(str, i);
142                     if (p0 && p1 && p2 && p3)
143                     {
144                         p |= (*p0);
145                         p |= (*p1) << 8;
146                         p |= (*p2) << 16;
147                         p |= (*p3) << 24;
148                     }
149                     current = token(FormatToken::InlineSprite, str.substr(startIndex, i - startIndex), p);
150                     return;
151                 }
152             }
153         }
154         else
155         {
156             do
157             {
158                 i++;
159             } while (i < str.size() && str[i] != '{' && str[i] != '}' && str[i] != '\n' && str[i] != '\r');
160         }
161         current = CreateToken(i - index);
162     }
163 
operator ==(iterator & rhs)164     bool FmtString::iterator::operator==(iterator& rhs)
165     {
166         return index == rhs.index;
167     }
168 
operator !=(iterator & rhs)169     bool FmtString::iterator::operator!=(iterator& rhs)
170     {
171         return index != rhs.index;
172     }
173 
CreateToken(size_t len)174     FmtString::token FmtString::iterator::CreateToken(size_t len)
175     {
176         std::string_view sztoken = str.substr(index, len);
177 
178         if (sztoken.size() >= 2 && ((sztoken[0] == '{' && sztoken[1] == '{') || (sztoken[0] == '}' && sztoken[1] == '}')))
179         {
180             return token(FormatToken::Escaped, sztoken);
181         }
182         if (sztoken.size() >= 2 && sztoken[0] == '{' && sztoken[1] != '{')
183         {
184             auto kind = FormatTokenFromString(sztoken.substr(1, len - 2));
185             return token(kind, sztoken);
186         }
187         if (sztoken == "\n" || sztoken == "\r")
188         {
189             return token(FormatToken::Newline, sztoken);
190         }
191         return token(FormatToken::Literal, sztoken);
192     }
193 
operator ->() const194     const FmtString::token* FmtString::iterator::operator->() const
195     {
196         return &current;
197     }
198 
operator *()199     const FmtString::token& FmtString::iterator::operator*()
200     {
201         return current;
202     }
203 
operator ++()204     FmtString::iterator& FmtString::iterator::operator++()
205     {
206         if (index < str.size())
207         {
208             index += current.text.size();
209             update();
210         }
211         return *this;
212     }
213 
operator ++(int)214     FmtString::iterator FmtString::iterator::operator++(int)
215     {
216         auto result = *this;
217         if (index < str.size())
218         {
219             index += current.text.size();
220             update();
221         }
222         return result;
223     }
224 
eol() const225     bool FmtString::iterator::eol() const
226     {
227         return index >= str.size();
228     }
229 
FmtString(std::string && s)230     FmtString::FmtString(std::string&& s)
231     {
232         _strOwned = std::move(s);
233         _str = _strOwned;
234     }
235 
FmtString(std::string_view s)236     FmtString::FmtString(std::string_view s)
237         : _str(s)
238     {
239     }
240 
FmtString(const char * s)241     FmtString::FmtString(const char* s)
242         : FmtString(s == nullptr ? std::string_view() : std::string_view(s))
243     {
244     }
245 
begin() const246     FmtString::iterator FmtString::begin() const
247     {
248         return iterator(_str, 0);
249     }
250 
end() const251     FmtString::iterator FmtString::end() const
252     {
253         return iterator(_str, _str.size());
254     }
255 
WithoutFormatTokens() const256     std::string FmtString::WithoutFormatTokens() const
257     {
258         std::string result;
259         result.reserve(_str.size() * 4);
260         for (const auto& t : *this)
261         {
262             if (t.IsLiteral())
263             {
264                 result += t.text;
265             }
266         }
267         return result;
268     }
269 
GetDigitSeparator()270     static std::string_view GetDigitSeparator()
271     {
272         auto sz = language_get_string(STR_LOCALE_THOUSANDS_SEPARATOR);
273         return sz != nullptr ? sz : std::string_view();
274     }
275 
GetDecimalSeparator()276     static std::string_view GetDecimalSeparator()
277     {
278         auto sz = language_get_string(STR_LOCALE_DECIMAL_POINT);
279         return sz != nullptr ? sz : std::string_view();
280     }
281 
FormatRealName(FormatBuffer & ss,rct_string_id id)282     void FormatRealName(FormatBuffer& ss, rct_string_id id)
283     {
284         if (IsRealNameStringId(id))
285         {
286             auto realNameIndex = id - REAL_NAME_START;
287             ss << real_names[realNameIndex % std::size(real_names)];
288             ss << ' ';
289             ss << real_name_initials[(realNameIndex >> 10) % std::size(real_name_initials)];
290             ss << '.';
291         }
292     }
293 
AppendSeparator(char (& buffer)[TSize],TIndex & i,std::string_view sep)294     template<size_t TSize, typename TIndex> static void AppendSeparator(char (&buffer)[TSize], TIndex& i, std::string_view sep)
295     {
296         if (i < TSize)
297         {
298             auto remainingLen = TSize - i;
299             auto cpyLen = std::min(sep.size(), remainingLen);
300             std::memcpy(&buffer[i], sep.data(), cpyLen);
301             i += static_cast<TIndex>(cpyLen);
302         }
303     }
304 
FormatNumber(FormatBuffer & ss,T value)305     template<size_t TDecimalPlace, bool TDigitSep, typename T> void FormatNumber(FormatBuffer& ss, T value)
306     {
307         char buffer[32];
308         size_t i = 0;
309 
310         uint64_t num;
311         if constexpr (std::is_signed<T>::value)
312         {
313             if (value < 0)
314             {
315                 ss << '-';
316                 if (value == std::numeric_limits<int64_t>::min())
317                 {
318                     // Edge case: int64_t can not store this number so manually assign num to (int64_t::max + 1)
319                     num = static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1;
320                 }
321                 else
322                 {
323                     // Cast negative number to int64_t and then reverse sign
324                     num = -static_cast<int64_t>(value);
325                 }
326             }
327             else
328             {
329                 num = value;
330             }
331         }
332         else
333         {
334             num = value;
335         }
336 
337         // Decimal digits
338         if constexpr (TDecimalPlace > 0)
339         {
340             while (num != 0 && i < sizeof(buffer) && i < TDecimalPlace)
341             {
342                 buffer[i++] = static_cast<char>('0' + (num % 10));
343                 num /= 10;
344             }
345             // handle case where value has fewer sig figs than required decimal places
346             while (num == 0 && i < TDecimalPlace && i < sizeof(buffer))
347             {
348                 buffer[i++] = '0';
349             }
350 
351             auto decSep = GetDecimalSeparator();
352             AppendSeparator(buffer, i, decSep);
353         }
354 
355         // Whole digits
356         [[maybe_unused]] auto digitSep = GetDigitSeparator();
357         [[maybe_unused]] size_t groupLen = 0;
358         do
359         {
360             if constexpr (TDigitSep)
361             {
362                 if (groupLen >= 3)
363                 {
364                     groupLen = 0;
365                     AppendSeparator(buffer, i, digitSep);
366                 }
367             }
368             buffer[i++] = static_cast<char>('0' + (num % 10));
369             num /= 10;
370             if constexpr (TDigitSep)
371             {
372                 groupLen++;
373             }
374         } while (num != 0 && i < sizeof(buffer));
375 
376         // Finally reverse append the string
377         for (int32_t j = static_cast<int32_t>(i - 1); j >= 0; j--)
378         {
379             ss << buffer[j];
380         }
381     }
382 
FormatCurrency(FormatBuffer & ss,T rawValue)383     template<size_t TDecimalPlace, bool TDigitSep, typename T> void FormatCurrency(FormatBuffer& ss, T rawValue)
384     {
385         auto currencyDesc = &CurrencyDescriptors[EnumValue(gConfigGeneral.currency_format)];
386         auto value = static_cast<int64_t>(rawValue) * currencyDesc->rate;
387 
388         // Negative sign
389         if (value < 0)
390         {
391             ss << '-';
392             value = -value;
393         }
394 
395         // Round the value away from zero
396         if constexpr (TDecimalPlace < 2)
397         {
398             value = (value + 99) / 100;
399         }
400 
401         // Currency symbol
402         auto symbol = currencyDesc->symbol_unicode;
403         auto affix = currencyDesc->affix_unicode;
404         if (!font_supports_string(symbol, FONT_SIZE_MEDIUM))
405         {
406             symbol = currencyDesc->symbol_ascii;
407             affix = currencyDesc->affix_ascii;
408         }
409 
410         // Currency symbol prefix
411         if (affix == CurrencyAffix::Prefix)
412         {
413             ss << symbol;
414         }
415 
416         // Drop the pennies for "large" currencies
417         auto dropPennies = false;
418         if constexpr (TDecimalPlace >= 2)
419         {
420             dropPennies = currencyDesc->rate >= 100;
421         }
422         if (dropPennies)
423         {
424             FormatNumber<0, TDigitSep>(ss, value / 100);
425         }
426         else
427         {
428             FormatNumber<TDecimalPlace, TDigitSep>(ss, value);
429         }
430 
431         // Currency symbol suffix
432         if (affix == CurrencyAffix::Suffix)
433         {
434             ss << symbol;
435         }
436     }
437 
FormatMinutesSeconds(FormatBuffer & ss,T value)438     template<typename T> static void FormatMinutesSeconds(FormatBuffer& ss, T value)
439     {
440         static constexpr const rct_string_id Formats[][2] = {
441             { STR_DURATION_SEC, STR_DURATION_SECS },
442             { STR_DURATION_MIN_SEC, STR_DURATION_MIN_SECS },
443             { STR_DURATION_MINS_SEC, STR_DURATION_MINS_SECS },
444         };
445 
446         auto minutes = value / 60;
447         auto seconds = value % 60;
448         if (minutes == 0)
449         {
450             auto fmt = Formats[0][seconds == 1 ? 0 : 1];
451             FormatStringId(ss, fmt, seconds);
452         }
453         else
454         {
455             auto fmt = Formats[minutes == 1 ? 1 : 2][seconds == 1 ? 0 : 1];
456             FormatStringId(ss, fmt, minutes, seconds);
457         }
458     }
459 
FormatHoursMinutes(FormatBuffer & ss,T value)460     template<typename T> static void FormatHoursMinutes(FormatBuffer& ss, T value)
461     {
462         static constexpr const rct_string_id Formats[][2] = {
463             { STR_REALTIME_MIN, STR_REALTIME_MINS },
464             { STR_REALTIME_HOUR_MIN, STR_REALTIME_HOUR_MINS },
465             { STR_REALTIME_HOURS_MIN, STR_REALTIME_HOURS_MINS },
466         };
467 
468         auto hours = value / 60;
469         auto minutes = value % 60;
470         if (hours == 0)
471         {
472             auto fmt = Formats[0][minutes == 1 ? 0 : 1];
473             FormatStringId(ss, fmt, minutes);
474         }
475         else
476         {
477             auto fmt = Formats[hours == 1 ? 1 : 2][minutes == 1 ? 0 : 1];
478             FormatStringId(ss, fmt, hours, minutes);
479         }
480     }
481 
FormatArgument(FormatBuffer & ss,FormatToken token,T arg)482     template<typename T> void FormatArgument(FormatBuffer& ss, FormatToken token, T arg)
483     {
484         switch (token)
485         {
486             case FormatToken::UInt16:
487             case FormatToken::Int32:
488                 if constexpr (std::is_integral<T>())
489                 {
490                     FormatNumber<0, false>(ss, arg);
491                 }
492                 break;
493             case FormatToken::Comma16:
494             case FormatToken::Comma32:
495                 if constexpr (std::is_integral<T>())
496                 {
497                     FormatNumber<0, true>(ss, arg);
498                 }
499                 break;
500             case FormatToken::Comma1dp16:
501                 if constexpr (std::is_integral<T>())
502                 {
503                     FormatNumber<1, true>(ss, arg);
504                 }
505                 else if constexpr (std::is_floating_point<T>())
506                 {
507                     FormatNumber<1, true>(ss, std::round(arg * 10));
508                 }
509                 break;
510             case FormatToken::Comma2dp32:
511                 if constexpr (std::is_integral<T>())
512                 {
513                     FormatNumber<2, true>(ss, arg);
514                 }
515                 else if constexpr (std::is_floating_point<T>())
516                 {
517                     FormatNumber<2, true>(ss, std::round(arg * 100));
518                 }
519                 break;
520             case FormatToken::Currency2dp:
521                 if constexpr (std::is_integral<T>())
522                 {
523                     FormatCurrency<2, true>(ss, arg);
524                 }
525                 break;
526             case FormatToken::Currency:
527                 if constexpr (std::is_integral<T>())
528                 {
529                     FormatCurrency<0, true>(ss, arg);
530                 }
531                 break;
532             case FormatToken::Velocity:
533                 if constexpr (std::is_integral<T>())
534                 {
535                     switch (gConfigGeneral.measurement_format)
536                     {
537                         default:
538                         case MeasurementFormat::Imperial:
539                             FormatStringId(ss, STR_UNIT_SUFFIX_MILES_PER_HOUR, arg);
540                             break;
541                         case MeasurementFormat::Metric:
542                             FormatStringId(ss, STR_UNIT_SUFFIX_KILOMETRES_PER_HOUR, mph_to_kmph(arg));
543                             break;
544                         case MeasurementFormat::SI:
545                             FormatStringId(ss, STR_UNIT_SUFFIX_METRES_PER_SECOND, mph_to_dmps(arg));
546                             break;
547                     }
548                 }
549                 break;
550             case FormatToken::DurationShort:
551                 if constexpr (std::is_integral<T>())
552                 {
553                     FormatMinutesSeconds(ss, arg);
554                 }
555                 break;
556             case FormatToken::DurationLong:
557                 if constexpr (std::is_integral<T>())
558                 {
559                     FormatHoursMinutes(ss, arg);
560                 }
561                 break;
562             case FormatToken::Length:
563                 if constexpr (std::is_integral<T>())
564                 {
565                     switch (gConfigGeneral.measurement_format)
566                     {
567                         default:
568                         case MeasurementFormat::Imperial:
569                             FormatStringId(ss, STR_UNIT_SUFFIX_FEET, metres_to_feet(arg));
570                             break;
571                         case MeasurementFormat::Metric:
572                         case MeasurementFormat::SI:
573                             FormatStringId(ss, STR_UNIT_SUFFIX_METRES, arg);
574                             break;
575                     }
576                 }
577                 break;
578             case FormatToken::MonthYear:
579                 if constexpr (std::is_integral<T>())
580                 {
581                     auto month = date_get_month(arg);
582                     auto year = date_get_year(arg) + 1;
583                     FormatMonthYear(ss, month, year);
584                 }
585                 break;
586             case FormatToken::Month:
587                 if constexpr (std::is_integral<T>())
588                 {
589                     auto szMonth = language_get_string(DateGameMonthNames[date_get_month(arg)]);
590                     if (szMonth != nullptr)
591                     {
592                         ss << szMonth;
593                     }
594                 }
595                 break;
596             case FormatToken::String:
597                 ss << arg;
598                 break;
599             case FormatToken::Sprite:
600                 if constexpr (std::is_integral<T>())
601                 {
602                     auto idx = static_cast<uint32_t>(arg);
603                     char inlineBuf[64];
604                     size_t len = snprintf(
605                         inlineBuf, sizeof(inlineBuf), "{INLINE_SPRITE}{%u}{%u}{%u}{%u}", ((idx >> 0) & 0xFF),
606                         ((idx >> 8) & 0xFF), ((idx >> 16) & 0xFF), ((idx >> 24) & 0xFF));
607                     ss.append(inlineBuf, len);
608                 }
609                 break;
610             default:
611                 break;
612         }
613     }
614 
615     template void FormatArgument(FormatBuffer&, FormatToken, uint16_t);
616     template void FormatArgument(FormatBuffer&, FormatToken, int16_t);
617     template void FormatArgument(FormatBuffer&, FormatToken, int32_t);
618     template void FormatArgument(FormatBuffer&, FormatToken, int64_t);
619     template void FormatArgument(FormatBuffer&, FormatToken, uint32_t);
620     template void FormatArgument(FormatBuffer&, FormatToken, uint64_t);
621     template void FormatArgument(FormatBuffer&, FormatToken, const char*);
622     template void FormatArgument(FormatBuffer&, FormatToken, std::string_view);
623 
IsRealNameStringId(rct_string_id id)624     bool IsRealNameStringId(rct_string_id id)
625     {
626         return id >= REAL_NAME_START && id <= REAL_NAME_END;
627     }
628 
GetFmtStringById(rct_string_id id)629     FmtString GetFmtStringById(rct_string_id id)
630     {
631         auto fmtc = language_get_string(id);
632         return FmtString(fmtc);
633     }
634 
GetThreadFormatStream()635     FormatBuffer& GetThreadFormatStream()
636     {
637         thread_local FormatBuffer ss;
638         ss.clear();
639         return ss;
640     }
641 
CopyStringStreamToBuffer(char * buffer,size_t bufferLen,FormatBuffer & ss)642     size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, FormatBuffer& ss)
643     {
644         auto copyLen = std::min<size_t>(bufferLen - 1, ss.size());
645 
646         std::copy(ss.data(), ss.data() + copyLen, buffer);
647         buffer[copyLen] = '\0';
648 
649         return ss.size();
650     }
651 
FormatArgumentAny(FormatBuffer & ss,FormatToken token,const FormatArg_t & value)652     static void FormatArgumentAny(FormatBuffer& ss, FormatToken token, const FormatArg_t& value)
653     {
654         if (std::holds_alternative<uint16_t>(value))
655         {
656             FormatArgument(ss, token, std::get<uint16_t>(value));
657         }
658         else if (std::holds_alternative<int32_t>(value))
659         {
660             FormatArgument(ss, token, std::get<int32_t>(value));
661         }
662         else if (std::holds_alternative<int64_t>(value))
663         {
664             FormatArgument(ss, token, std::get<int64_t>(value));
665         }
666         else if (std::holds_alternative<const char*>(value))
667         {
668             FormatArgument(ss, token, std::get<const char*>(value));
669         }
670         else if (std::holds_alternative<std::string>(value))
671         {
672             FormatArgument(ss, token, std::get<std::string>(value));
673         }
674         else
675         {
676             throw std::runtime_error("No support for format argument type.");
677         }
678     }
679 
FormatStringAny(FormatBuffer & ss,const FmtString & fmt,const std::vector<FormatArg_t> & args,size_t & argIndex)680     static void FormatStringAny(FormatBuffer& ss, const FmtString& fmt, const std::vector<FormatArg_t>& args, size_t& argIndex)
681     {
682         for (const auto& token : fmt)
683         {
684             if (token.kind == FormatToken::StringId)
685             {
686                 if (argIndex < args.size())
687                 {
688                     const auto& arg = args[argIndex++];
689                     std::optional<rct_string_id> stringId;
690                     if (auto value16 = std::get_if<uint16_t>(&arg))
691                     {
692                         stringId = *value16;
693                     }
694                     else if (auto value32 = std::get_if<int32_t>(&arg))
695                     {
696                         stringId = *value32;
697                     }
698                     if (stringId)
699                     {
700                         if (IsRealNameStringId(*stringId))
701                         {
702                             FormatRealName(ss, *stringId);
703                         }
704                         else
705                         {
706                             auto subfmt = GetFmtStringById(*stringId);
707                             FormatStringAny(ss, subfmt, args, argIndex);
708                         }
709                     }
710                 }
711                 else
712                 {
713                     argIndex++;
714                 }
715             }
716             else if (FormatTokenTakesArgument(token.kind))
717             {
718                 if (argIndex < args.size())
719                 {
720                     FormatArgumentAny(ss, token.kind, args[argIndex]);
721                 }
722                 argIndex++;
723             }
724             else if (token.kind != FormatToken::Push16 && token.kind != FormatToken::Pop16)
725             {
726                 ss << token.text;
727             }
728         }
729     }
730 
FormatStringAny(const FmtString & fmt,const std::vector<FormatArg_t> & args)731     std::string FormatStringAny(const FmtString& fmt, const std::vector<FormatArg_t>& args)
732     {
733         auto& ss = GetThreadFormatStream();
734         size_t argIndex = 0;
735         FormatStringAny(ss, fmt, args, argIndex);
736         return ss.data();
737     }
738 
FormatStringAny(char * buffer,size_t bufferLen,const FmtString & fmt,const std::vector<FormatArg_t> & args)739     size_t FormatStringAny(char* buffer, size_t bufferLen, const FmtString& fmt, const std::vector<FormatArg_t>& args)
740     {
741         auto& ss = GetThreadFormatStream();
742         size_t argIndex = 0;
743         FormatStringAny(ss, fmt, args, argIndex);
744         return CopyStringStreamToBuffer(buffer, bufferLen, ss);
745     }
746 
ReadFromArgs(const void * & args)747     template<typename T> static T ReadFromArgs(const void*& args)
748     {
749         T value;
750         std::memcpy(&value, args, sizeof(T));
751         args = reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(args) + sizeof(T));
752         return value;
753     }
754 
BuildAnyArgListFromLegacyArgBuffer(const FmtString & fmt,std::vector<FormatArg_t> & anyArgs,const void * & args)755     static void BuildAnyArgListFromLegacyArgBuffer(const FmtString& fmt, std::vector<FormatArg_t>& anyArgs, const void*& args)
756     {
757         for (const auto& t : fmt)
758         {
759             switch (t.kind)
760             {
761                 case FormatToken::Comma32:
762                 case FormatToken::Int32:
763                 case FormatToken::Comma2dp32:
764                 case FormatToken::Sprite:
765                     anyArgs.push_back(ReadFromArgs<int32_t>(args));
766                     break;
767                 case FormatToken::Currency2dp:
768                 case FormatToken::Currency:
769                     anyArgs.push_back(ReadFromArgs<int64_t>(args));
770                     break;
771                 case FormatToken::UInt16:
772                 case FormatToken::MonthYear:
773                 case FormatToken::Month:
774                 case FormatToken::Velocity:
775                 case FormatToken::DurationShort:
776                 case FormatToken::DurationLong:
777                     anyArgs.push_back(ReadFromArgs<uint16_t>(args));
778                     break;
779                 case FormatToken::Comma16:
780                 case FormatToken::Length:
781                 case FormatToken::Comma1dp16:
782                     anyArgs.push_back(ReadFromArgs<int16_t>(args));
783                     break;
784                 case FormatToken::StringId:
785                 {
786                     auto stringId = ReadFromArgs<rct_string_id>(args);
787                     anyArgs.push_back(stringId);
788                     BuildAnyArgListFromLegacyArgBuffer(GetFmtStringById(stringId), anyArgs, args);
789                     break;
790                 }
791                 case FormatToken::String:
792                 {
793                     auto sz = ReadFromArgs<const char*>(args);
794                     anyArgs.push_back(sz);
795                     break;
796                 }
797                 case FormatToken::Pop16:
798                     args = reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(args) + 2);
799                     break;
800                 case FormatToken::Push16:
801                     args = reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(args) - 2);
802                     break;
803                 default:
804                     break;
805             }
806         }
807     }
808 
FormatStringLegacy(char * buffer,size_t bufferLen,rct_string_id id,const void * args)809     size_t FormatStringLegacy(char* buffer, size_t bufferLen, rct_string_id id, const void* args)
810     {
811         thread_local std::vector<FormatArg_t> anyArgs;
812         anyArgs.clear();
813         auto fmt = GetFmtStringById(id);
814         BuildAnyArgListFromLegacyArgBuffer(fmt, anyArgs, args);
815         return FormatStringAny(buffer, bufferLen, fmt, anyArgs);
816     }
817 
FormatMonthYear(FormatBuffer & ss,int32_t month,int32_t year)818     static void FormatMonthYear(FormatBuffer& ss, int32_t month, int32_t year)
819     {
820         thread_local std::vector<FormatArg_t> tempArgs;
821         tempArgs.clear();
822 
823         auto fmt = GetFmtStringById(STR_DATE_FORMAT_MY);
824         Formatter ft;
825         ft.Add<uint16_t>(month);
826         ft.Add<uint16_t>(year);
827         const void* legacyArgs = ft.Data();
828         BuildAnyArgListFromLegacyArgBuffer(fmt, tempArgs, legacyArgs);
829         size_t argIndex = 0;
830         FormatStringAny(ss, fmt, tempArgs, argIndex);
831     }
832 
833 } // namespace OpenRCT2
834 
format_string(utf8 * dest,size_t size,rct_string_id format,const void * args)835 void format_string(utf8* dest, size_t size, rct_string_id format, const void* args)
836 {
837     OpenRCT2::FormatStringLegacy(dest, size, format, args);
838 }
839