mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-06 06:32:56 +01:00
Fix #13842: News is imported incorrectly
This commit is contained in:
@@ -562,7 +562,7 @@ static void window_game_bottom_toolbar_draw_news_item(rct_drawpixelinfo* dpi, rc
|
||||
INSET_RECT_F_30);
|
||||
|
||||
// Text
|
||||
utf8* newsItemText = newsItem->Text;
|
||||
const auto* newsItemText = newsItem->Text.c_str();
|
||||
auto screenCoords = w->windowPos + ScreenCoordsXY{ middleOutsetWidget->midX(), middleOutsetWidget->top + 11 };
|
||||
width = middleOutsetWidget->width() - 62;
|
||||
gfx_draw_string_centred_wrapped_partial(
|
||||
|
||||
@@ -248,7 +248,7 @@ static void window_news_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32
|
||||
// Item text
|
||||
{
|
||||
auto ft = Formatter();
|
||||
ft.Add<utf8*>(newsItem.Text);
|
||||
ft.Add<const char*>(newsItem.Text.c_str());
|
||||
gfx_draw_string_left_wrapped(
|
||||
dpi, ft.Data(), { 2, y + lineHeight }, 325, STR_BOTTOM_TOOLBAR_NEWS_TEXT, COLOUR_BRIGHT_GREEN);
|
||||
}
|
||||
|
||||
@@ -380,22 +380,6 @@ void game_convert_strings_to_utf8()
|
||||
gScenarioCompletedBy = rct2_to_utf8(gScenarioCompletedBy, RCT2LanguageId::EnglishUK);
|
||||
gScenarioName = rct2_to_utf8(gScenarioName, RCT2LanguageId::EnglishUK);
|
||||
gScenarioDetails = rct2_to_utf8(gScenarioDetails, RCT2LanguageId::EnglishUK);
|
||||
|
||||
// News items
|
||||
game_convert_news_items_to_utf8();
|
||||
}
|
||||
|
||||
void game_convert_news_items_to_utf8()
|
||||
{
|
||||
for (int32_t i = 0; i < News::MaxItems; i++)
|
||||
{
|
||||
News::Item* newsItem = News::GetItem(i);
|
||||
|
||||
if (!str_is_null_or_empty(newsItem->Text))
|
||||
{
|
||||
rct2_to_utf8_self(newsItem->Text, sizeof(newsItem->Text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,15 +400,6 @@ void game_convert_strings_to_rct2(rct_s6_data* s6)
|
||||
utf8_to_rct2_self(userString, RCT12_USER_STRING_MAX_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// News items
|
||||
for (auto& newsItem : s6->news_items)
|
||||
{
|
||||
if (!str_is_null_or_empty(newsItem.Text))
|
||||
{
|
||||
utf8_to_rct2_self(newsItem.Text, sizeof(newsItem.Text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OpenRCT2 workaround to recalculate some values which are saved redundantly in the save to fix corrupted files.
|
||||
|
||||
@@ -173,7 +173,6 @@ void save_game_cmd(const utf8* name = nullptr);
|
||||
void save_game_with_name(const utf8* name);
|
||||
void game_autosave();
|
||||
void game_convert_strings_to_utf8();
|
||||
void game_convert_news_items_to_utf8();
|
||||
void game_convert_strings_to_rct2(rct_s6_data* s6);
|
||||
void utf8_to_rct2_self(char* buffer, size_t length);
|
||||
void rct2_to_utf8_self(char* buffer, size_t length);
|
||||
|
||||
@@ -65,7 +65,7 @@ static const std::unordered_map<std::string_view, FormatToken> FormatTokenMap =
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static std::string_view GetFormatTokenStringWithBraces(FormatToken token)
|
||||
std::string_view GetFormatTokenStringWithBraces(FormatToken token)
|
||||
{
|
||||
// Ensure cache is thread safe
|
||||
static std::mutex mutex;
|
||||
|
||||
@@ -76,6 +76,7 @@ enum class FormatToken
|
||||
OutlineDisable,
|
||||
};
|
||||
|
||||
std::string_view GetFormatTokenStringWithBraces(FormatToken token);
|
||||
FormatToken FormatTokenFromString(std::string_view token);
|
||||
std::string_view FormatTokenToString(FormatToken token, bool withBraces = false);
|
||||
bool FormatTokenTakesArgument(FormatToken token);
|
||||
|
||||
@@ -319,7 +319,7 @@ News::Item* News::AddItemToQueue(News::ItemType type, const utf8* text, uint32_t
|
||||
newsItem->Ticks = 0;
|
||||
newsItem->MonthYear = static_cast<uint16_t>(gDateMonthsElapsed);
|
||||
newsItem->Day = ((days_in_month[date_get_month(newsItem->MonthYear)] * gDateMonthTicks) >> 16) + 1;
|
||||
safe_strcpy(newsItem->Text, text, sizeof(newsItem->Text));
|
||||
newsItem->Text = text;
|
||||
|
||||
return newsItem;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
struct CoordsXYZ;
|
||||
class Formatter;
|
||||
@@ -60,7 +61,7 @@ namespace News
|
||||
uint16_t Ticks;
|
||||
uint16_t MonthYear;
|
||||
uint8_t Day;
|
||||
utf8 Text[256];
|
||||
std::string Text;
|
||||
|
||||
constexpr bool IsEmpty() const noexcept
|
||||
{
|
||||
|
||||
@@ -208,7 +208,6 @@ public:
|
||||
SetDefaultNames();
|
||||
determine_ride_entrance_and_exit_locations();
|
||||
|
||||
game_convert_news_items_to_utf8();
|
||||
map_count_remaining_land_rights();
|
||||
research_determine_first_of_type();
|
||||
}
|
||||
@@ -2613,7 +2612,7 @@ private:
|
||||
dst->Ticks = src->Ticks;
|
||||
dst->MonthYear = src->MonthYear;
|
||||
dst->Day = src->Day;
|
||||
std::copy(std::begin(src->Text), std::end(src->Text), dst->Text);
|
||||
dst->Text = ConvertFormattedStringToOpenRCT2(std::string_view(src->Text, sizeof(src->Text)));
|
||||
|
||||
if (dst->Type == News::ItemType::Research)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "RCT12.h"
|
||||
|
||||
#include "../core/String.hpp"
|
||||
#include "../localisation/Formatting.h"
|
||||
#include "../localisation/Localisation.h"
|
||||
#include "../ride/Track.h"
|
||||
#include "../world/Banner.h"
|
||||
@@ -20,6 +21,8 @@
|
||||
#include "../world/TileElement.h"
|
||||
#include "../world/Wall.h"
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
uint8_t RCT12TileElementBase::GetType() const
|
||||
{
|
||||
return this->type & TILE_ELEMENT_TYPE_MASK;
|
||||
@@ -1101,3 +1104,188 @@ std::string RCT12RemoveFormattingUTF8(std::string_view s)
|
||||
result.shrink_to_fit();
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace RCT12FormatCode
|
||||
{
|
||||
constexpr codepoint_t Newline = 5;
|
||||
constexpr codepoint_t NewlineSmall = 6;
|
||||
constexpr codepoint_t ColourBlack = 142;
|
||||
constexpr codepoint_t ColourGrey = 143;
|
||||
constexpr codepoint_t ColourWhite = 144;
|
||||
constexpr codepoint_t ColourRed = 145;
|
||||
constexpr codepoint_t ColourGreen = 146;
|
||||
constexpr codepoint_t ColourYellow = 147;
|
||||
constexpr codepoint_t ColourTopaz = 148;
|
||||
constexpr codepoint_t ColourCeladon = 149;
|
||||
constexpr codepoint_t ColourBabyBlue = 150;
|
||||
constexpr codepoint_t ColourPaleLavender = 151;
|
||||
constexpr codepoint_t ColourPaleGold = 152;
|
||||
constexpr codepoint_t ColourLightPink = 153;
|
||||
constexpr codepoint_t ColourPearlAqua = 154;
|
||||
constexpr codepoint_t ColourPaleSilver = 155;
|
||||
} // namespace RCT12FormatCode
|
||||
|
||||
static FormatToken GetFormatTokenFromRCT12Code(codepoint_t codepoint)
|
||||
{
|
||||
switch (codepoint)
|
||||
{
|
||||
case RCT12FormatCode::Newline:
|
||||
return FormatToken::Newline;
|
||||
case RCT12FormatCode::NewlineSmall:
|
||||
return FormatToken::NewlineSmall;
|
||||
case RCT12FormatCode::ColourBlack:
|
||||
return FormatToken::ColourBlack;
|
||||
case RCT12FormatCode::ColourGrey:
|
||||
return FormatToken::ColourGrey;
|
||||
case RCT12FormatCode::ColourWhite:
|
||||
return FormatToken::ColourWhite;
|
||||
case RCT12FormatCode::ColourRed:
|
||||
return FormatToken::ColourRed;
|
||||
case RCT12FormatCode::ColourGreen:
|
||||
return FormatToken::ColourGreen;
|
||||
case RCT12FormatCode::ColourYellow:
|
||||
return FormatToken::ColourYellow;
|
||||
case RCT12FormatCode::ColourTopaz:
|
||||
return FormatToken::ColourTopaz;
|
||||
case RCT12FormatCode::ColourCeladon:
|
||||
return FormatToken::ColourCeladon;
|
||||
case RCT12FormatCode::ColourBabyBlue:
|
||||
return FormatToken::ColourBabyBlue;
|
||||
case RCT12FormatCode::ColourPaleLavender:
|
||||
return FormatToken::ColourPaleLavender;
|
||||
case RCT12FormatCode::ColourPaleGold:
|
||||
return FormatToken::ColourPaleGold;
|
||||
case RCT12FormatCode::ColourLightPink:
|
||||
return FormatToken::ColourLightPink;
|
||||
case RCT12FormatCode::ColourPearlAqua:
|
||||
return FormatToken::ColourPearlAqua;
|
||||
case RCT12FormatCode::ColourPaleSilver:
|
||||
return FormatToken::ColourPaleSilver;
|
||||
default:
|
||||
return FormatToken::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
static codepoint_t GetRCT12CodeFromFormatToken(FormatToken token)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case FormatToken::Newline:
|
||||
return RCT12FormatCode::Newline;
|
||||
case FormatToken::NewlineSmall:
|
||||
return RCT12FormatCode::NewlineSmall;
|
||||
case FormatToken::ColourBlack:
|
||||
return RCT12FormatCode::ColourBlack;
|
||||
case FormatToken::ColourGrey:
|
||||
return RCT12FormatCode::ColourGrey;
|
||||
case FormatToken::ColourWhite:
|
||||
return RCT12FormatCode::ColourWhite;
|
||||
case FormatToken::ColourRed:
|
||||
return RCT12FormatCode::ColourRed;
|
||||
case FormatToken::ColourGreen:
|
||||
return RCT12FormatCode::ColourGreen;
|
||||
case FormatToken::ColourYellow:
|
||||
return RCT12FormatCode::ColourYellow;
|
||||
case FormatToken::ColourTopaz:
|
||||
return RCT12FormatCode::ColourTopaz;
|
||||
case FormatToken::ColourCeladon:
|
||||
return RCT12FormatCode::ColourCeladon;
|
||||
case FormatToken::ColourBabyBlue:
|
||||
return RCT12FormatCode::ColourBabyBlue;
|
||||
case FormatToken::ColourPaleLavender:
|
||||
return RCT12FormatCode::ColourPaleLavender;
|
||||
case FormatToken::ColourPaleGold:
|
||||
return RCT12FormatCode::ColourPaleGold;
|
||||
case FormatToken::ColourLightPink:
|
||||
return RCT12FormatCode::ColourLightPink;
|
||||
case FormatToken::ColourPearlAqua:
|
||||
return RCT12FormatCode::ColourPearlAqua;
|
||||
case FormatToken::ColourPaleSilver:
|
||||
return RCT12FormatCode::ColourPaleSilver;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ConvertFormattedStringToOpenRCT2(std::string_view buffer)
|
||||
{
|
||||
auto nullTerminator = buffer.find('\0');
|
||||
if (nullTerminator != std::string::npos)
|
||||
{
|
||||
buffer = buffer.substr(0, nullTerminator);
|
||||
}
|
||||
auto asUtf8 = rct2_to_utf8(buffer, RCT2LanguageId::EnglishUK);
|
||||
|
||||
std::string result;
|
||||
CodepointView codepoints(asUtf8);
|
||||
for (auto codepoint : codepoints)
|
||||
{
|
||||
auto token = GetFormatTokenFromRCT12Code(codepoint);
|
||||
if (token != FormatToken::Unknown)
|
||||
{
|
||||
result += GetFormatTokenStringWithBraces(token);
|
||||
}
|
||||
else
|
||||
{
|
||||
String::AppendCodepoint(result, codepoint);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ConvertFormattedStringToRCT2(std::string_view buffer, size_t maxLength)
|
||||
{
|
||||
std::string result;
|
||||
FmtString fmt(buffer);
|
||||
for (const auto& token : fmt)
|
||||
{
|
||||
if (token.IsLiteral())
|
||||
{
|
||||
result += token.text;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto codepoint = GetRCT12CodeFromFormatToken(token.kind);
|
||||
if (codepoint == 0)
|
||||
{
|
||||
result += token.text;
|
||||
}
|
||||
else
|
||||
{
|
||||
String::AppendCodepoint(result, codepoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
return GetTruncatedRCT2String(result, maxLength);
|
||||
}
|
||||
|
||||
std::string GetTruncatedRCT2String(std::string_view src, size_t maxLength)
|
||||
{
|
||||
auto rct2encoded = utf8_to_rct2(src);
|
||||
if (rct2encoded.size() > maxLength - 1)
|
||||
{
|
||||
log_warning(
|
||||
"The user string '%s' is too long for the S6 file format and has therefore been truncated.",
|
||||
std::string(src).c_str());
|
||||
|
||||
rct2encoded.resize(maxLength - 1);
|
||||
for (size_t i = 0; i < rct2encoded.size(); i++)
|
||||
{
|
||||
if (rct2encoded[i] == static_cast<char>(static_cast<uint8_t>(0xFF)))
|
||||
{
|
||||
if (i > maxLength - 4)
|
||||
{
|
||||
// This codepoint was truncated, remove codepoint altogether
|
||||
rct2encoded.resize(i);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip the next two bytes which represent the unicode character
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rct2encoded;
|
||||
}
|
||||
|
||||
@@ -876,3 +876,6 @@ ride_id_t RCT12RideIdToOpenRCT2RideId(const RCT12RideId rideId);
|
||||
RCT12RideId OpenRCT2RideIdToRCT12RideId(const ride_id_t rideId);
|
||||
bool IsLikelyUTF8(std::string_view s);
|
||||
std::string RCT12RemoveFormattingUTF8(std::string_view s);
|
||||
std::string ConvertFormattedStringToOpenRCT2(std::string_view buffer);
|
||||
std::string ConvertFormattedStringToRCT2(std::string_view buffer, size_t maxLength);
|
||||
std::string GetTruncatedRCT2String(std::string_view src, size_t maxLength);
|
||||
|
||||
@@ -412,7 +412,9 @@ void S6Exporter::Export()
|
||||
dst->Ticks = src->Ticks;
|
||||
dst->MonthYear = src->MonthYear;
|
||||
dst->Day = src->Day;
|
||||
std::memcpy(dst->Text, src->Text, sizeof(dst->Text));
|
||||
|
||||
auto rct2text = ConvertFormattedStringToRCT2(src->Text, sizeof(dst->Text));
|
||||
std::memcpy(dst->Text, rct2text.c_str(), std::min(sizeof(dst->Text), rct2text.size()));
|
||||
}
|
||||
|
||||
// pad_13CE730
|
||||
@@ -1646,37 +1648,6 @@ std::optional<uint16_t> S6Exporter::AllocateUserString(std::string_view value)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::string GetTruncatedRCT2String(std::string_view src)
|
||||
{
|
||||
auto rct2encoded = utf8_to_rct2(src);
|
||||
if (rct2encoded.size() > RCT12_USER_STRING_MAX_LENGTH - 1)
|
||||
{
|
||||
log_warning(
|
||||
"The user string '%s' is too long for the S6 file format and has therefore been truncated.",
|
||||
std::string(src).c_str());
|
||||
|
||||
rct2encoded.resize(RCT12_USER_STRING_MAX_LENGTH - 1);
|
||||
for (size_t i = 0; i < rct2encoded.size(); i++)
|
||||
{
|
||||
if (rct2encoded[i] == static_cast<char>(static_cast<uint8_t>(0xFF)))
|
||||
{
|
||||
if (i > RCT12_USER_STRING_MAX_LENGTH - 4)
|
||||
{
|
||||
// This codepoint was truncated, remove codepoint altogether
|
||||
rct2encoded.resize(i);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip the next two bytes which represent the unicode character
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rct2encoded;
|
||||
}
|
||||
|
||||
void S6Exporter::ExportUserStrings()
|
||||
{
|
||||
auto numUserStrings = std::min<size_t>(_userStrings.size(), RCT12_MAX_USER_STRINGS);
|
||||
@@ -1684,7 +1655,7 @@ void S6Exporter::ExportUserStrings()
|
||||
{
|
||||
auto dst = _s6.custom_strings[i];
|
||||
const auto& src = _userStrings[i];
|
||||
auto encodedSrc = GetTruncatedRCT2String(src);
|
||||
auto encodedSrc = GetTruncatedRCT2String(src, RCT12_USER_STRING_MAX_LENGTH);
|
||||
auto stringLen = std::min<size_t>(encodedSrc.size(), RCT12_USER_STRING_MAX_LENGTH - 1);
|
||||
std::memcpy(dst, encodedSrc.data(), stringLen);
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ public:
|
||||
dst->Ticks = src->Ticks;
|
||||
dst->MonthYear = src->MonthYear;
|
||||
dst->Day = src->Day;
|
||||
std::memcpy(dst->Text, src->Text, sizeof(src->Text));
|
||||
dst->Text = ConvertFormattedStringToOpenRCT2(std::string_view(src->Text, sizeof(src->Text)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -63,9 +63,7 @@ namespace OpenRCT2::Scripting
|
||||
result.Ticks = value["tickCount"].as_int();
|
||||
result.MonthYear = value["month"].as_int();
|
||||
result.Day = value["day"].as_int();
|
||||
|
||||
auto text = value["text"].as_string();
|
||||
String::Set(result.Text, sizeof(result.Text), text.c_str());
|
||||
result.Text = value["text"].as_string();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -219,7 +217,7 @@ namespace OpenRCT2::Scripting
|
||||
auto msg = GetMessage();
|
||||
if (msg != nullptr)
|
||||
{
|
||||
String::Set(msg->Text, sizeof(msg->Text), value.c_str());
|
||||
msg->Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user