diff --git a/src/openrct2/localisation/Formatting.cpp b/src/openrct2/localisation/Formatting.cpp index 107ea57774..7d9b972648 100644 --- a/src/openrct2/localisation/Formatting.cpp +++ b/src/openrct2/localisation/Formatting.cpp @@ -495,9 +495,33 @@ namespace OpenRCT2 return FmtString(std::move(fmtc)); } + std::stringstream& GetThreadFormatStream() + { + thread_local std::stringstream ss; + // Reset the buffer (reported as most efficient way) + std::stringstream().swap(ss); + return ss; + } + + size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, std::stringstream& ss) + { + auto stringLen = ss.tellp(); + auto copyLen = std::min(bufferLen - 1, stringLen); + + ss.seekg(0, std::ios::beg); + ss.read(buffer, copyLen); + buffer[copyLen] = '\0'; + + return stringLen; + } + void FormatArgumentAny(std::stringstream& ss, FormatToken token, const std::any& value) { - if (value.type() == typeid(int32_t)) + if (value.type() == typeid(uint16_t)) + { + FormatArgument(ss, token, std::any_cast(value)); + } + else if (value.type() == typeid(int32_t)) { FormatArgument(ss, token, std::any_cast(value)); } @@ -549,11 +573,81 @@ namespace OpenRCT2 std::string FormatStringAny(const FmtString& fmt, const std::vector& args) { - thread_local std::stringstream ss; - // Reset the buffer (reported as most efficient way) - std::stringstream().swap(ss); + auto& ss = GetThreadFormatStream(); size_t argIndex = 0; FormatStringAny(ss, fmt, args, argIndex); return ss.str(); } + + size_t FormatStringAny(char* buffer, size_t bufferLen, const FmtString& fmt, const std::vector& args) + { + auto& ss = GetThreadFormatStream(); + size_t argIndex = 0; + FormatStringAny(ss, fmt, args, argIndex); + return CopyStringStreamToBuffer(buffer, bufferLen, ss); + } + + template static T ReadFromArgs(const void*& args) + { + T value; + std::memcpy(&value, args, sizeof(T)); + args = reinterpret_cast(reinterpret_cast(args) + sizeof(T)); + return value; + } + + static void BuildAnyArgListFromLegacyArgBuffer(const FmtString& fmt, std::vector& anyArgs, const void* args) + { + for (const auto& t : fmt) + { + switch (t.kind) + { + case FORMAT_COMMA32: + case FORMAT_INT32: + case FORMAT_COMMA2DP32: + case FORMAT_CURRENCY2DP: + case FORMAT_CURRENCY: + anyArgs.push_back(ReadFromArgs(args)); + break; + case FORMAT_COMMA16: + case FORMAT_UINT16: + case FORMAT_MONTHYEAR: + case FORMAT_MONTH: + case FORMAT_VELOCITY: + case FORMAT_DURATION: + case FORMAT_REALTIME: + case FORMAT_LENGTH: + anyArgs.push_back(ReadFromArgs(args)); + break; + case FORMAT_STRINGID: + case FORMAT_STRINGID2: + { + auto stringId = ReadFromArgs(args); + anyArgs.push_back(stringId); + BuildAnyArgListFromLegacyArgBuffer(GetFmtStringById(stringId), anyArgs, args); + break; + } + case FORMAT_STRING: + { + auto sz = ReadFromArgs(args); + anyArgs.push_back(sz); + BuildAnyArgListFromLegacyArgBuffer(sz, anyArgs, args); + break; + } + case FORMAT_POP16: + args = reinterpret_cast(reinterpret_cast(args) + 2); + break; + case FORMAT_PUSH16: + args = reinterpret_cast(reinterpret_cast(args) - 2); + break; + } + } + } + + size_t FormatStringLegacy(char* buffer, size_t bufferLen, rct_string_id id, const void* args) + { + std::vector anyArgs; + auto fmt = GetFmtStringById(id); + BuildAnyArgListFromLegacyArgBuffer(fmt, anyArgs, args); + return FormatStringAny(buffer, bufferLen, fmt, anyArgs); + } } // namespace OpenRCT2 diff --git a/src/openrct2/localisation/Formatting.h b/src/openrct2/localisation/Formatting.h index f254542c77..727d371738 100644 --- a/src/openrct2/localisation/Formatting.h +++ b/src/openrct2/localisation/Formatting.h @@ -76,6 +76,8 @@ namespace OpenRCT2 bool CanFormatToken(FormatToken t); FmtString GetFmtStringById(rct_string_id id); + std::stringstream& GetThreadFormatStream(); + size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, std::stringstream& ss); inline void FormatString(std::stringstream& ss, std::stack stack) { @@ -137,9 +139,7 @@ namespace OpenRCT2 template std::string FormatString(const FmtString& fmt, TArgs&&... argN) { - thread_local std::stringstream ss; - // Reset the buffer (reported as most efficient way) - std::stringstream().swap(ss); + auto& ss = GetThreadFormatStream(); FormatString(ss, fmt, argN...); return ss.str(); } @@ -156,5 +156,14 @@ namespace OpenRCT2 return FormatString(fmt, argN...); } + template size_t FormatStringId(char* buffer, size_t bufferLen, rct_string_id id, TArgs&&... argN) + { + auto& ss = GetThreadFormatStream(); + FormatStringId(ss, id, argN...); + return CopyStringStreamToBuffer(buffer, bufferLen, ss); + } + std::string FormatStringAny(const FmtString& fmt, const std::vector& args); + size_t FormatStringAny(char* buffer, size_t bufferLen, const FmtString& fmt, const std::vector& args); + size_t FormatStringLegacy(char* buffer, size_t bufferLen, rct_string_id id, const void* args); } // namespace OpenRCT2 diff --git a/test/tests/FormattingTests.cpp b/test/tests/FormattingTests.cpp index e70289aae9..de36f1c6d5 100644 --- a/test/tests/FormattingTests.cpp +++ b/test/tests/FormattingTests.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include using namespace OpenRCT2; @@ -280,3 +281,31 @@ TEST_F(FormattingTests, any_two_level_format) auto actual = FormatStringAny("Queuing for {STRINGID}", { strDefault, strBoatHire, 2 }); ASSERT_EQ("Queuing for Boat Hire 2", actual); } + +TEST_F(FormattingTests, to_fixed_buffer) +{ + char buffer[16]; + std::memset(buffer, '\xFF', sizeof(buffer)); + auto len = FormatStringId(buffer, 8, STR_GUEST_X, 123); + ASSERT_EQ(len, 9); + ASSERT_STREQ("Guest 1", buffer); + + // Ensure rest of the buffer was not overwritten + for (size_t i = 8; i < sizeof(buffer); i++) + { + ASSERT_EQ('\xFF', buffer[i]); + } +} + +TEST_F(FormattingTests, using_legacy_buffer_args) +{ + auto ft = Formatter(); + ft.Add(STR_RIDE_NAME_DEFAULT); + ft.Add(STR_RIDE_NAME_BOAT_HIRE); + ft.Add(2); + + char buffer[32]{}; + auto len = FormatStringLegacy(buffer, sizeof(buffer), STR_QUEUING_FOR, ft.Data()); + ASSERT_EQ(len, 23); + ASSERT_STREQ("Queuing for Boat Hire 2", buffer); +}