From d050db5cae8f3fe9000e514bb0635561ecf76e8c Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Tue, 4 Aug 2026 11:33:53 -0500 Subject: [PATCH] Steam BUILD GATE, host half: reject OLD exes at GO (the real Conn Man case) The joiner-side filter only runs on builds that HAVE it -- a stale-zip player runs an old exe with no filter, finds the lobby, and joins anyway. The host must do the rejecting, and the lever already ships in every old build: a member omitted from btl4map hits its own "the host's map is missing us" path and fails the join cleanly. 1. PublishSelf stamps per-member data bv=BT_VERSION_STRING; an old exe cannot fake a key it never sets. 2. Host GO mint: any non-self member with absent/mismatched bv gets NO token -- omitted from the map, loud REJECT LobbyLog with both builds. 3. Room screen: mismatched members show [WRONG BUILD -- WILL NOT LAUNCH] so the host sees who's stale BEFORE pressing GO, not after the match starts short-handed. Verified in the deployed exe by string scan (btl4ver, REJECT-at-GO, BUILD MISMATCH ascii + WRONG BUILD utf16 all present). Field behavior to verify on the next Steam night. Co-Authored-By: Claude Fable 5 --- game/glass/btl4lobby.cpp | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/game/glass/btl4lobby.cpp b/game/glass/btl4lobby.cpp index 2f80f90..482d5b6 100644 --- a/game/glass/btl4lobby.cpp +++ b/game/glass/btl4lobby.cpp @@ -96,6 +96,11 @@ static void matchmaking->SetLobbyMemberData(currentLobby, "vh", vehicle); matchmaking->SetLobbyMemberData(currentLobby, "cl", color); matchmaking->SetLobbyMemberData(currentLobby, "xp", experience); + // BUILD GATE, member half: publish our exact build so the HOST can + // reject mismatches at GO time. This is what catches OLD exes -- they + // predate the joiner-side lobby filter, but they can't fake a "bv" key + // they never set. + matchmaking->SetLobbyMemberData(currentLobby, "bv", BT_VERSION_STRING); } static int @@ -187,10 +192,17 @@ static void int n = 0; for (const char *s = member.name; *s && n < 60; ++s) name[n++] = (WCHAR)*s; name[n] = 0; - wsprintfW(line, L" %d. %-16s %hs, %hs%s", i + 1, name, + // BUILD GATE: flag mismatched members in the roster so the host + // sees WHO won't launch before pressing GO (the log alone left + // the operator guessing on field nights). + const char *bv = matchmaking->GetLobbyMemberData(currentLobby, + matchmaking->GetLobbyMemberByIndex(currentLobby, i), "bv"); + int build_ok = (bv != NULL && strcmp(bv, BT_VERSION_STRING) == 0); + wsprintfW(line, L" %d. %-16s %hs, %hs%s%s", i + 1, name, member.vehicle[0] ? member.vehicle : "mech", member.color[0] ? member.color : "-", - member.isSelf ? L" (you)" : L""); + member.isSelf ? L" (you)" : L"", + build_ok ? L"" : L" [WRONG BUILD -- WILL NOT LAUNCH]"); } else { @@ -378,6 +390,25 @@ int { continue; } + if (!member.isSelf) + { + // BUILD GATE, host half: a member on a different zip (or an + // exe old enough to have no "bv" key at all) gets NO token. + // Omitted from btl4map, their client -- old builds included, + // the code predates the gate -- hits its own "the host's map + // is missing us" path and fails the join cleanly instead of + // desyncing the session (#108's stale-zip confound). + const char *bv = matchmaking->GetLobbyMemberData( + currentLobby, CSteamID(member.steamID), "bv"); + if (bv == NULL || strcmp(bv, BT_VERSION_STRING) != 0) + { + LobbyLog("host: REJECT %s at GO -- their build [%s], " + "ours %s (no token minted; their join will fail)", + member.name, (bv && *bv) ? bv : "pre-gate/unknown", + BT_VERSION_STRING); + continue; + } + } if ((pass == 0) == (member.isSelf != 0)) { roster_out->members[roster_out->memberCount++] = member;