fix: Cross-platform wide string formatting for replay/UI text truncation#94
Merged
Merged
Conversation
Add centralized format string normalization in UnicodeString::format_va to handle semantic differences between MSVC and POSIX libc: - MSVC: %s = wide, %S = narrow - POSIX: %s = narrow, %ls = wide This fixes one-character truncation of wide strings on Linux/macOS when using formatted output in replays, UI messages, and file I/O. Changes: - UnicodeString: Added normalizeWidePrintfFormatForPosix() helper (POSIX-guarded) - LocalFile::writeFormat: Route through UnicodeString for normalized output - simpleplayer: Use explicit %ls specifier for wide strings Fixes #XX (replay truncation, UI text truncation on macOS/Linux)
Refactor message(), messageColor(), and message(AsciiString) methods to use UnicodeString::format_va instead of direct vswprintf calls. This ensures consistent wide string handling across all platforms and applies the POSIX-compatible format normalization. Changes applied to both GeneralsMD (Zero Hour) and Generals (base game) for consistency. Affected methods: - InGameUI::message(UnicodeString, ...) - InGameUI::messageColor(RGBColor*, UnicodeString, ...) - InGameUI::message(AsciiString, ...) [GeneralsMD/Zero Hour only] Fixes UI text truncation (tooltips, construction status, game messages).
Session 2026-04-19: Documented cross-platform wide string formatting issue and its solution for future reference. Root cause: MSVC and POSIX libc have incompatible semantics for wide printf format specifiers (%s means different things on each platform). Prevention guidance: - Always use UnicodeString::format for wide string formatting (platform-agnostic) - Avoid direct vswprintf/swprintf calls for wide strings - Use explicit specifiers (%ls for wide, %hs for narrow) in format strings - Guard any platform-specific formatting with #ifdef/#ifndef Reference: See UnicodeString::normalizeWidePrintfFormatForPosix() for implementation details.
There was a problem hiding this comment.
Pull request overview
This PR fixes cross-platform wide-string formatting so UI messages and replay metadata are no longer truncated on POSIX platforms due to differing wide printf format semantics.
Changes:
- Add POSIX-side wide
printfformat normalization inUnicodeString::format_va()to make MSVC-style%s/%Susage behave consistently. - Refactor
InGameUImessage formatting (Generals + Zero Hour) to route throughUnicodeString::format_va()instead of callingvswprintfdirectly. - Route
LocalFile::writeFormat(const WideChar*, ...)throughUnicodeString::format_va()so replay header serialization benefits from the same normalization; update oneswprintfcallsite to use explicit%ls.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| docs/WORKDIR/lessons/2026-04-LESSONS.md | Documents the root cause and the fix strategy for the POSIX wide-format mismatch. |
| GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp | Routes UI message formatting through UnicodeString::format_va() for consistent wide formatting on POSIX. |
| Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp | Same UI formatting refactor as Zero Hour for consistency across both games. |
| Core/GameEngine/Source/Common/System/UnicodeString.cpp | Adds POSIX-only format-string normalization and applies it inside UnicodeString::format_va(). |
| Core/GameEngine/Source/Common/System/LocalFile.cpp | Makes wide writeFormat() use UnicodeString::format_va() so file/replay formatting is normalized on POSIX. |
| Core/GameEngine/Source/Common/Audio/simpleplayer.cpp | Updates a swprintf format string to use explicit wide specifiers (%ls). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fix one-character truncation of wide strings on Linux/macOS in replay headers, UI text, and in-game messages.
Root Cause: MSVC and POSIX libc have incompatible semantics for wide printf format specifiers:
%s= wide string (wchar_t),%S= narrow string (char)%s= narrow string (char),%ls= wide string (wchar_t)The codebase was using MSVC semantics universally, causing one-character truncation when strings were interpreted as narrow instead of wide on POSIX systems.
Changes
1. Core Wide String Formatting
normalizeWidePrintfFormatForPosix()helperUnicodeString::format_va%lsspecifier for wide strings2. In-Game UI Formatting
Refactored message methods to use
UnicodeString::format_va:InGameUI::message(UnicodeString, ...)InGameUI::messageColor(RGBColor*, UnicodeString, ...)3. Documentation
Updated lesson learned in docs/WORKDIR/lessons/2026-04-LESSONS.md
Validation
✅ Cross-platform: Linux, macOS ARM64
✅ No compilation errors
✅ All unsafe printf calls addressed
✅ Backward compatible with Windows build
Total: 3 commits, 189 insertions, 84 deletions