Fix std::format_error crash from Windows-1252 bytes in format strings - #400
Conversation
|
Compiling with |
There was a problem hiding this comment.
Pull request overview
This PR fixes a multiplayer crash caused by MSVC <format> throwing std::format_error when std::format format strings contain raw Windows-1252 bytes (e.g., \x95, \xA6). It preserves the exact rendered output/byte sequences required by the game’s bitmap fonts and network-visible chat prefixes by moving those bytes out of the format strings and into arguments / concatenation.
Changes:
- Reworks scoreboard header formatting to pass the Windows-1252 bullet (
\x95) as a separate argument instead of embedding it in thestd::formatformat string. - Replaces
std::formatusage for\xA6-prefixed automated chat messages with string concatenation to avoid UTF-8 validation of the format string.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
game_patch/multi/multi.cpp |
Avoids std::format with \xA6 in the format string by switching to concatenation for “Shot canceled” messages. |
game_patch/multi/alpine_packets.cpp |
Avoids std::format with \xA6 in the format string when sending legacy chat lines. |
game_patch/hud/multi_scoreboard.cpp |
Introduces a SEPARATOR constant (\x95) and updates scoreboard std::format calls to keep the bullet out of the format string. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
I cannot reproduce nor on godbolt.org. |
|
@jyh9521 When you have a chance, could you speak to the points Copilot and @is-this-c raised above? |
Reply 1 — main PR / issue commentThanks both. I ran a probe, and it turns out The variable is the ordinary literal encoding, which MSVC defaults to the build machine's #include <cstdio>
#include <format>
#include <stdexcept>
int main()
{
#ifdef _MSVC_EXECUTION_CHARACTER_SET
std::printf("_MSVC_EXECUTION_CHARACTER_SET = %d\n", _MSVC_EXECUTION_CHARACTER_SET);
#else
std::printf("_MSVC_EXECUTION_CHARACTER_SET = (not defined)\n");
#endif
try {
const std::string s = std::format("{} \x95 {}/{} PLAYING", "DEATHMATCH", 3, 8);
std::printf("ok, %zu bytes:", s.size());
for (unsigned char c : s) {
std::printf(" %02X", c);
}
std::printf("\n");
}
catch (const std::format_error& e) {
std::printf("std::format_error: %s\n", e.what());
return 1;
}
return 0;
}Same machine,
UTF-8 is fine too, so "the format string has to be valid UTF-8" — which I wrote in #399, and 932 is a DBCS, and So the trigger is the builder's system locale, not the code. Anyone building from source on a Given that, I agree
I'm happy to replace this PR with just the compiler flag, or keep both if you'd rather have the Reply 2 — inline, alpine_packets.cpp (Copilot: per-client allocation)Good catch, and it stands regardless of which fix we land: the pre-1.2.0 branch is inside the std::optional<std::string> legacy_msg;
for (rf::Player& player : SinglyLinkedList{rf::player_list}) {
...
} else {
if (!legacy_msg) {
legacy_msg = std::string{"\xA6 "} + std::string{msg};
}
send_chat_line_packet(*legacy_msg, &player);
}
}If we go with Reply 3 — inline, multi_scoreboard.cpp (Copilot: comment wording)The comment is wrong, but not in the way suggested — the replacement wording is wrong too. What actually breaks is a double-byte ordinary literal encoding, where // Windows-1252 bullet. Must stay a raw byte for the .vf bitmap fonts, and must stay out of
// any std::format format string: when the ordinary literal encoding is a double-byte charset
// (932 is the default on a Japanese Windows), MSVC decodes the format string and rejects
// 0x95 as a stray lead byte.That said, if we take |
|
|
7c29ce6 to
7219d41
Compare
Agreed. I've replaced the four code changes with the compiler flags — the PR is now a single if(MSVC)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
add_compile_options(/arch:SSE2)
# Pin both charsets. Sources are UTF-8; narrow string literals are Windows-1252,
# because the .vf bitmap fonts are indexed by Windows-1252 byte value and several
# literals embed those bytes directly (\x95 bullet, \xA6 chat prefix, ...).
# Without this the ordinary literal encoding follows the build machine's system
# code page: on a DBCS locale such as 932 those bytes become invalid multi-byte
# sequences and std::format throws std::format_error at runtime.
add_compile_options(/source-charset:utf-8 /execution-charset:windows-1252)
# Statically link Microsoft's CRT.
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
else()
add_compile_options(-msse2)
add_compile_options(-finput-charset=UTF-8 -fexec-charset=WINDOWS-1252)
endif()Two things I checked before proposing
Copilot's two comments are moot with this approach, since none of the three source files change
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
CMakeLists.txt:50
-finput-charset/-fexec-charsetare being applied to all non-MSVC builds (including Linux/macOS toolchains). That can unintentionally transcode any non-ASCII UTF-8 literals into Windows-1252 bytes, changing runtime strings and potentially breaking builds on compilers that don’t accept these flags. If this is meant to address Windows/MinGW-only behavior, scope it (e.g.if(MINGW)/ Windows) rather than globally for every non-MSVC target.
else()
add_compile_options(-msse2)
add_compile_options(-finput-charset=UTF-8 -fexec-charset=WINDOWS-1252)
endif()
docs/CHANGELOG.md:110
- This changelog entry describes the crash as limited to builds compiled on Windows with a Japanese/Chinese/Korean locale, but #399’s report reproduces on an English Windows 11 machine and attributes it to invalid Windows-1252 bytes inside
std::formatformat strings (toolset-dependent). Consider rewording to reflect the actual scope/cause so users don’t mistakenly assume they’re unaffected.
[@jyh9521](https://github.com/jyh9521)
- Fix crash on join for builds compiled on Windows systems using a Japanese, Chinese, or Korean locale
|
@jyh9521 Merged. Thank you for the contribution! |
Fixes #399
Four call sites embed raw Windows-1252 bytes (
\x95,\xA6) instd::formatformat strings, which MSVC's
<format>validates as UTF-8. Moves the bytesout of the format strings; rendered output is unchanged.
Details and the diagnosis are in #399.