From 376cb7980cbbdff75fabd68443f1e626570d0c3e Mon Sep 17 00:00:00 2001 From: Briar <72242853+QuestionableDeer@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:18:24 -0800 Subject: [PATCH] Fix #9999-#10003: translations have truncated strings This issue, along with several related language-specific trunctions, was traced back to the fact that ScenarioIndexEntry uses a fixed-length array of utf8 characters to store the name, internal name, and scenario details. In some cases, this does not provide enough characters to contain the full description and so the safe copy methods truncate them to fit in the available buffer. Since the use of fixed-size arrays is a holdover from earlier C code, this commit addresses the issue by changing ScenarioIndexEntry to use proper utf8 strings and string views, which do not require truncation. --- contributors.md | 1 + distribution/changelog.txt | 1 + src/openrct2-ui/windows/ScenarioSelect.cpp | 8 +++----- src/openrct2/park/ParkFile.cpp | 6 +++--- src/openrct2/rct1/S4Importer.cpp | 6 +++--- src/openrct2/rct2/S6Importer.cpp | 18 +++++++----------- src/openrct2/scenario/ScenarioRepository.cpp | 6 +++--- src/openrct2/scenario/ScenarioRepository.h | 8 ++++---- src/openrct2/scenario/ScenarioSources.cpp | 2 +- src/openrct2/scenario/ScenarioSources.h | 2 +- 10 files changed, 27 insertions(+), 31 deletions(-) diff --git a/contributors.md b/contributors.md index a7dfe0ed66..2e45dc504d 100644 --- a/contributors.md +++ b/contributors.md @@ -247,6 +247,7 @@ Appreciation for contributors who have provided substantial work, but are no lon * Robert Yan (lewyche) * Tom Matalenas (tmatale) * Brendan Heinonen (staticinvocation) +* (QuestionableDeer) ## Toolchain * (Balletie) - macOS diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 697c3df739..6d06a68865 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,6 +1,7 @@ 0.4.20 (in development) ------------------------------------------------------------------------ - Improved: [#23677] Building new ride track now inherits the colour scheme from the previous piece. +- Fix: [#9999, #10000, #10001, #10002, #10003] Truncated scenario strings when using Catalan, Czech, Japanese, Polish or Russian. - Fix: [#21768] Dirty blocks debug overlay is rendered incorrectly on high DPI screens. - Fix: [#22617] Sloped Wooden and Side-Friction supports draw out of order when built directly above diagonal track pieces. - Fix: [#23522] Diagonal sloped Steeplechase supports have glitched sprites at the base. diff --git a/src/openrct2-ui/windows/ScenarioSelect.cpp b/src/openrct2-ui/windows/ScenarioSelect.cpp index ef5e7e4003..6c45024ab6 100644 --- a/src/openrct2-ui/windows/ScenarioSelect.cpp +++ b/src/openrct2-ui/windows/ScenarioSelect.cpp @@ -239,7 +239,7 @@ namespace OpenRCT2::Ui::Windows + ScreenCoordsXY{ widgets[WIDX_SCENARIOLIST].right + 4, widgets[WIDX_TABCONTENT].top + 5 }; auto ft = Formatter(); ft.Add(STR_STRING); - ft.Add(scenario->Name); + ft.Add(scenario->Name.c_str()); DrawTextEllipsised( dpi, screenPos + ScreenCoordsXY{ 85, 0 }, 170, STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::CENTRE }); screenPos.y += 15; @@ -247,7 +247,7 @@ namespace OpenRCT2::Ui::Windows // Scenario details ft = Formatter(); ft.Add(STR_STRING); - ft.Add(scenario->Details); + ft.Add(scenario->Details.c_str()); screenPos.y += DrawTextWrapped(dpi, screenPos, 170, STR_BLACK_STRING, ft) + 5; // Scenario objective @@ -445,13 +445,11 @@ namespace OpenRCT2::Ui::Windows bool isDisabled = listItem.scenario.is_locked; // Draw scenario name - char buffer[64]; - String::safeUtf8Copy(buffer, scenario->Name, sizeof(buffer)); StringId format = isDisabled ? static_cast(STR_STRINGID) : (isHighlighted ? highlighted_format : unhighlighted_format); auto ft = Formatter(); ft.Add(STR_STRING); - ft.Add(buffer); + ft.Add(scenario->Name.c_str()); auto colour = isDisabled ? colours[1].withFlag(ColourFlag::inset, true) : ColourWithFlags{ COLOUR_BLACK }; auto darkness = isDisabled ? TextDarkness::Dark : TextDarkness::Regular; diff --git a/src/openrct2/park/ParkFile.cpp b/src/openrct2/park/ParkFile.cpp index 5ee0502be2..863281231d 100644 --- a/src/openrct2/park/ParkFile.cpp +++ b/src/openrct2/park/ParkFile.cpp @@ -215,15 +215,15 @@ namespace OpenRCT2 std::string name; ReadWriteStringTable(cs, name, "en-GB"); - String::set(entry.Name, sizeof(entry.Name), name.c_str()); - String::set(entry.InternalName, sizeof(entry.InternalName), name.c_str()); + entry.Name = name; + entry.InternalName = name; std::string parkName; ReadWriteStringTable(cs, parkName, "en-GB"); std::string scenarioDetails; ReadWriteStringTable(cs, scenarioDetails, "en-GB"); - String::set(entry.Details, sizeof(entry.Details), scenarioDetails.c_str()); + entry.Details = scenarioDetails; entry.ObjectiveType = cs.Read(); entry.ObjectiveArg1 = cs.Read(); diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index fcceed6923..1719032c5b 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -262,7 +262,7 @@ namespace OpenRCT2::RCT1 desc.title = name.c_str(); } - String::set(dst->InternalName, sizeof(dst->InternalName), desc.title); + dst->InternalName = desc.title; if (!desc.textObjectId.empty()) { @@ -284,8 +284,8 @@ namespace OpenRCT2::RCT1 } } - String::set(dst->Name, sizeof(dst->Name), name.c_str()); - String::set(dst->Details, sizeof(dst->Details), details.c_str()); + dst->Name = name; + dst->Details = details; return true; } diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index 332e5cad32..c06086cb4e 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -257,18 +257,17 @@ namespace OpenRCT2::RCT2 if (String::isNullOrEmpty(_s6.Info.Name)) { // If the scenario doesn't have a name, set it to the filename - String::set(dst->Name, sizeof(dst->Name), Path::GetFileNameWithoutExtension(dst->Path).c_str()); + dst->Name = Path::GetFileNameWithoutExtension(dst->Path); } else { // Normalise the name to make the scenario as recognisable as possible. - auto normalisedName = ScenarioSources::NormaliseName(_s6.Info.Name); - String::set(dst->Name, sizeof(dst->Name), normalisedName.c_str()); + dst->Name = ScenarioSources::NormaliseName(_s6.Info.Name); } // Look up and store information regarding the origins of this scenario. SourceDescriptor desc; - if (ScenarioSources::TryGetByName(dst->Name, &desc)) + if (ScenarioSources::TryGetByName(dst->Name.c_str(), &desc)) { dst->ScenarioId = desc.id; dst->SourceIndex = desc.index; @@ -290,8 +289,8 @@ namespace OpenRCT2::RCT2 } // dst->name will be translated later so keep the untranslated name here - String::set(dst->InternalName, sizeof(dst->InternalName), dst->Name); - String::set(dst->Details, sizeof(dst->Details), _s6.Info.Details); + dst->InternalName = dst->Name; + dst->Details = _s6.Info.Details; if (!desc.textObjectId.empty()) { @@ -308,11 +307,8 @@ namespace OpenRCT2::RCT2 if (auto* obj = objManager.LoadObject(desc.textObjectId); obj != nullptr) { auto* textObject = reinterpret_cast(obj); - auto name = textObject->GetScenarioName(); - auto details = textObject->GetScenarioDetails(); - - String::set(dst->Name, sizeof(dst->Name), name.c_str()); - String::set(dst->Details, sizeof(dst->Details), details.c_str()); + dst->Name = textObject->GetScenarioName(); + dst->Details = textObject->GetScenarioDetails(); } } diff --git a/src/openrct2/scenario/ScenarioRepository.cpp b/src/openrct2/scenario/ScenarioRepository.cpp index 6420759ea9..87fa8af2b6 100644 --- a/src/openrct2/scenario/ScenarioRepository.cpp +++ b/src/openrct2/scenario/ScenarioRepository.cpp @@ -71,10 +71,10 @@ static int32_t ScenarioIndexEntryCompareByCategory(const ScenarioIndexEntry& ent { return static_cast(entryA.SourceGame) - static_cast(entryB.SourceGame); } - return strcmp(entryA.Name, entryB.Name); + return strcmp(entryA.Name.c_str(), entryB.Name.c_str()); case SCENARIO_CATEGORY_REAL: case SCENARIO_CATEGORY_OTHER: - return strcmp(entryA.Name, entryB.Name); + return strcmp(entryA.Name.c_str(), entryB.Name.c_str()); } } @@ -317,7 +317,7 @@ public: return nullptr; } - const ScenarioIndexEntry* GetByInternalName(const utf8* name) const override + const ScenarioIndexEntry* GetByInternalName(u8string_view name) const override { for (size_t i = 0; i < _scenarios.size(); i++) { diff --git a/src/openrct2/scenario/ScenarioRepository.h b/src/openrct2/scenario/ScenarioRepository.h index dbe2966050..8099349477 100644 --- a/src/openrct2/scenario/ScenarioRepository.h +++ b/src/openrct2/scenario/ScenarioRepository.h @@ -58,9 +58,9 @@ struct ScenarioIndexEntry int16_t ObjectiveArg3; ScenarioHighscoreEntry* Highscore = nullptr; - utf8 InternalName[64]; // Untranslated name - utf8 Name[64]; // Translated name - utf8 Details[256]; + u8string InternalName; // Untranslated name + u8string Name; // Translated name + u8string Details; }; namespace OpenRCT2 @@ -83,7 +83,7 @@ struct IScenarioRepository /** * Does not return custom scenarios due to the fact that they may have the same name. */ - virtual const ScenarioIndexEntry* GetByInternalName(const utf8* name) const = 0; + virtual const ScenarioIndexEntry* GetByInternalName(u8string_view name) const = 0; virtual const ScenarioIndexEntry* GetByPath(const utf8* path) const = 0; virtual bool TryRecordHighscore(int32_t language, const utf8* scenarioFileName, money64 companyValue, const utf8* name) = 0; diff --git a/src/openrct2/scenario/ScenarioSources.cpp b/src/openrct2/scenario/ScenarioSources.cpp index 8b24eba611..69b5658c4a 100644 --- a/src/openrct2/scenario/ScenarioSources.cpp +++ b/src/openrct2/scenario/ScenarioSources.cpp @@ -357,7 +357,7 @@ namespace OpenRCT2::ScenarioSources #pragma endregion - bool TryGetByName(const utf8* name, SourceDescriptor* outDesc) + bool TryGetByName(u8string_view name, SourceDescriptor* outDesc) { Guard::ArgumentNotNull(outDesc, GUARD_LINE); diff --git a/src/openrct2/scenario/ScenarioSources.h b/src/openrct2/scenario/ScenarioSources.h index dec700be8c..460bee36bd 100644 --- a/src/openrct2/scenario/ScenarioSources.h +++ b/src/openrct2/scenario/ScenarioSources.h @@ -23,7 +23,7 @@ struct SourceDescriptor namespace OpenRCT2::ScenarioSources { - bool TryGetByName(const utf8* name, SourceDescriptor* outDesc); + bool TryGetByName(u8string_view name, SourceDescriptor* outDesc); bool TryGetById(uint8_t id, SourceDescriptor* outDesc); u8string NormaliseName(u8string_view input); } // namespace OpenRCT2::ScenarioSources