Conversation
Greptile SummaryThe PR prevents LAN lobby serialization from hanging on oversized player names by rebuilding oversized payloads with UTF-8-safe name limits.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Convert human names to UTF-8] --> B[Build complete LAN payload]
B --> C{Payload at most 400 bytes?}
C -- Yes --> D[Return payload]
C -- No --> E[Calculate fixed-field length]
E --> F[Allocate remaining bytes among human names]
F --> G[Truncate names at UTF-8 boundaries]
G --> H{Allocation succeeded?}
H -- No --> I[Return empty payload]
H -- Yes --> J[Rebuild payload]
J --> K{Rebuilt payload at most 400 bytes?}
K -- Yes --> D
K -- No --> I
Reviews (4): Last reviewed commit: "refactor(network): Address LAN serialize..." | Re-trigger Greptile |
Skyaero42
left a comment
There was a problem hiding this comment.
This feels very complicated for what is eventually just a hack. FIxing the 400 byte gameinfo byte limit should be the true goal.
Something as simple as: count the number of available bytes for player names and divide that by the number of players - this gives the number of bytes each player name can have. Yes, it is not exact (if there a players with shorter names, that would also allow players with longer names than the threshold).
In general, there is a lot of stuff added in GameInfo that doesn't belong there. Specific byte counts of characters belong in Asciistring. Such function can probably also be generalized instead of using First and Last in functions.
There was a problem hiding this comment.
Greptile has paused reviews on this repository — it used its 100 free open-source review credits for this billing period. Reviews resume automatically on August 26. To continue before then, an organization admin can keep reviews running past the free credits — those bill as normal usage.
7900833 to
85030a1
Compare
Agreed, but retail still needs something, even if it's hacky. The 400-byte limit is part of the packed retail LAN wire layout: LANMessage is sent by size and cast directly by receivers, so enlarging the options array would change field offsets and break retail compat. Removing that limit requires a versioned or chunked protocol extension and should be a separate change. This PR keeps the existing wire format and fixes the current infinite loop; it would remain necessary as the retail-compatible fallback even after an extended protocol is introduced. |
85030a1 to
61ce9f6
Compare
3497569 to
2f84d0f
Compare
xezon
left a comment
There was a problem hiding this comment.
All of this new code is AI generated right? So the human reviewer would now need to check that the code was generated with a good prompt right?
d0a25c9 to
a4e99ac
Compare
|
The approach is fine. In fact, it follows the same two-pass structure xezon proposed in #1119: serialize normally, calculate the exact excess only when oversized, truncate names, then rebuild. |
|
I reviewed the current code and tested it myself. With eight connected clients using 36-byte CJK names, the host payload stayed between 398 and 400 bytes, remained valid UTF-8, and the match started and ran. |
a4e99ac to
865c66e
Compare
WalkthroughThe change adds UTF-8-safe name truncation and shared byte budgeting to LAN game-information serialization. It also updates LAN option length validation and adds a compile-time buffer-capacity assertion. ChangesLAN UTF-8 serialization
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant LANGameRoom
participant GameInfoToAsciiString
participant Utf8_Truncate_Len
participant LANAPI
LANGameRoom->>GameInfoToAsciiString: serialize player names and game fields
GameInfoToAsciiString->>Utf8_Truncate_Len: calculate UTF-8-safe name lengths
Utf8_Truncate_Len-->>GameInfoToAsciiString: return complete-character lengths
GameInfoToAsciiString->>LANAPI: validate serialized game options
LANAPI-->>LANGameRoom: accept length at or below the LAN maximum
Merge Risk: 🟡 Moderate · up to Some oversized lobby configurations can publish an empty game-options announcement, causing clients to remove the lobby rather than showing valid state. This should be corrected before merge. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
c5f6fad to
845faf4
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🟡 Minor · Keep Utf8_Truncate_Len consistent with its documented contract.
Core/Libraries/Source/WWVegas/WWLib/utf8.cpp:291-304
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep
Utf8_Truncate_Lenconsistent with its documented contract. For{'A', 0xC2, 0xFF}withmaxLen == 2, it returns2, althoughUtf8_Decoderejects the retained incomplete sequence. Validate the retained prefix or narrow the helper's contract.The current LAN path does not produce this malformed input:
WideCharStringToMultiByteusesWide_To_Utf8, whoseWide_Readreplaces unrepresentable values with U+FFFD before serialization.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: a142095d-3428-4030-acb2-8a7b4f5b2b85
📒 Files selected for processing (1)
Core/GameEngine/Source/GameNetwork/GameInfo.cpp
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
| } | ||
| } | ||
|
|
||
| // TheSuperHackers @bugfix bobtista 23/08/2026 Prevent an infinite loop when player names exceed | ||
| // the LAN options limit by rebuilding the payload with bounded UTF-8 names. | ||
| AsciiString optionsString = buildGameInfoAsciiString(*game, playerNames); | ||
| Bool optionsFit = TheLAN == nullptr || optionsString.getLength() <= m_lanMaxOptionsLength; | ||
| if (!optionsFit) | ||
| { | ||
| const Int fixedLength = optionsString.getLength() - playerNamesLength; | ||
| const Int maxPlayerNamesLength = m_lanMaxOptionsLength - fixedLength; | ||
| if (truncatePlayerNames(*game, playerNames, maxPlayerNamesLength)) | ||
| { | ||
| optionsString = buildGameInfoAsciiString(*game, playerNames); | ||
| optionsFit = optionsString.getLength() <= m_lanMaxOptionsLength; | ||
| } | ||
| } | ||
|
|
||
| if (!optionsFit) | ||
| { | ||
| DEBUG_CRASH(("WARNING: options string cannot fit within the expected length! Length is %d, but max is %d!", | ||
| optionsString.getLength(), m_lanMaxOptionsLength)); | ||
| return AsciiString::TheEmptyString; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '870,1110p' Core/GameEngine/Source/GameNetwork/GameInfo.cpp
rg -n 'GameInfoToAsciiString|RequestGameOptions|GameOptions|options\[' Core/GameEngine/Source/GameNetwork Core/GameEngine/Include/GameNetwork
sed -n '240,280p' Core/GameEngine/Include/GameNetwork/LANAPI.h
sed -n '800,860p' Core/GameEngine/Source/GameNetwork/LANAPI.cppRepository: TheSuperHackers/GeneralsGameCode
Length of output: 17398
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- LANGameInfo.cpp ---'
sed -n '230,315p' Core/GameEngine/Source/GameNetwork/LANGameInfo.cpp
printf '%s\n' '--- LANAPI.cpp publication path ---'
sed -n '690,735p' Core/GameEngine/Source/GameNetwork/LANAPI.cpp
sed -n '825,875p' Core/GameEngine/Source/GameNetwork/LANAPI.cpp
printf '%s\n' '--- LANAPIhandlers.cpp relevant callers/consumer ---'
sed -n '45,160p' Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp
sed -n '180,210p' Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp
sed -n '420,455p' Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp
sed -n '515,545p' Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp
sed -n '660,690p' Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp
sed -n '710,730p' Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp
printf '%s\n' '--- LANAPICallbacks.cpp relevant callers ---'
sed -n '110,140p' Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp
sed -n '450,475p' Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp
sed -n '500,540p' Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp
sed -n '580,600p' Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cppRepository: TheSuperHackers/GeneralsGameCode
Length of output: 15987
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- GameInfo parser ---'
sed -n '1090,1325p' Core/GameEngine/Source/GameNetwork/GameInfo.cpp
printf '%s\n' '--- LANGameInfo parser return ---'
sed -n '245,330p' Core/GameEngine/Source/GameNetwork/LANGameInfo.cpp
printf '%s\n' '--- all direct publication call sites ---'
rg -n -C 4 'GameInfoToAsciiString\\(|GenerateGameOptionsString\\(' Core/GameEngine/Source/GameNetworkRepository: TheSuperHackers/GeneralsGameCode
Length of output: 9908
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- parser return and required fields ---'
rg -n -C 8 'sawMap|sawSlotlist|optionsOk|return ' Core/GameEngine/Source/GameNetwork/GameInfo.cpp | tail -n 180
printf '%s\n' '--- direct serialization callers ---'
rg -n -F -C 3 'GameInfoToAsciiString(' Core/GameEngine/Source/GameNetwork
printf '%s\n' '--- generated-options callers ---'
rg -n -F -C 3 'GenerateGameOptionsString(' Core/GameEngine/Source/GameNetworkRepository: TheSuperHackers/GeneralsGameCode
Length of output: 18314
Reject empty game-options payloads before LAN publication. When GameInfoToAsciiString cannot fit the payload, RequestGameAnnounce and handleRequestGameInfo copy the empty result into MSG_GAME_ANNOUNCE and send it. handleGameAnnounce rejects the empty string because the required fields are missing, then removes the discovered game. If that game already exists, this removes valid lobby state. Guard the empty result before sending the announcement or game-options update.
WWLib/utf8.h, as does feat(string): Implement UTF-8 string conversion and validation functions #2528; whichever lands second can rebaseGameInfoToAsciiStringserializes the LAN lobby state into a string with a 400-byte limit. The existing code truncates each player name while appending its slot:Once the fixed portion of the options string consumes the remaining budget,
lenMaxbecomes negative. The loop removes the entire name, after whichAsciiString::removeLastCharbecomes a no-op. Because0 > lenMaxremains true, the host spins forever.The serializer now builds the complete payload with full player names first. If it exceeds 400 bytes, a second pass:
A final guard rejects any result that remains oversized. Therefore every non-empty LAN options payload returned by
GameInfoToAsciiStringis at most 400 bytes.Truncation cuts only at UTF-8 character boundaries through
Utf8_Truncate_LeninWWLib/utf8.h, keeping encoding rules outsideGameInfo. Supporting changes add a compile-time check that theGameOptions.optionsbuffer exceedsm_lanMaxOptionsLengthand allow a payload of exactly 400 bytes, which fits the 401-byte null-terminated buffer.Truncation affects only the serialized LAN payload. The host retains the full player names, while remote clients may display their truncated forms. Names that truncate to the same prefix are left as-is; distinguishing them would require additional collision handling.
Verification
Runtime verification
Tested in-engine with eight real game processes connected through Direct Connect.
Test setup:
界characters.Results:
Follow-up: cache each converted player name in
GameSlotso it is not rebuilt on every room refresh, as suggested in #1119.Todo:
Core