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 ¤t;
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