Skip to content

fix: Cross-platform wide string formatting for replay/UI text truncation#94

Merged
fbraz3 merged 3 commits into
mainfrom
fix/64bit-string-truncation-replay-construction
Apr 19, 2026
Merged

fix: Cross-platform wide string formatting for replay/UI text truncation#94
fbraz3 merged 3 commits into
mainfrom
fix/64bit-string-truncation-replay-construction

Conversation

@fbraz3

@fbraz3 fbraz3 commented Apr 19, 2026

Copy link
Copy Markdown
Owner

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:

  • MSVC: %s = wide string (wchar_t), %S = narrow string (char)
  • POSIX: %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

  • UnicodeString::format_va(): Added normalizeWidePrintfFormatForPosix() helper
  • LocalFile::writeFormat(): Route through UnicodeString::format_va
  • simpleplayer.cpp: Use explicit %ls specifier for wide strings

2. In-Game UI Formatting

Refactored message methods to use UnicodeString::format_va:

  • InGameUI::message(UnicodeString, ...)
  • InGameUI::messageColor(RGBColor*, UnicodeString, ...)
  • Applied to both GeneralsMD (Zero Hour) and Generals (base game)

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

fbraz3 added 3 commits April 19, 2026 16:04
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 printf format normalization in UnicodeString::format_va() to make MSVC-style %s/%S usage behave consistently.
  • Refactor InGameUI message formatting (Generals + Zero Hour) to route through UnicodeString::format_va() instead of calling vswprintf directly.
  • Route LocalFile::writeFormat(const WideChar*, ...) through UnicodeString::format_va() so replay header serialization benefits from the same normalization; update one swprintf callsite 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).

Comment thread docs/WORKDIR/lessons/2026-04-LESSONS.md
@fbraz3 fbraz3 merged commit 653c770 into main Apr 19, 2026
12 checks passed
@fbraz3 fbraz3 deleted the fix/64bit-string-truncation-replay-construction branch April 20, 2026 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants