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 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-04 11:33:53 -05:00
co-authored by Claude Fable 5
parent 22f732fcfb
commit d050db5cae
+33 -2
View File
@@ -96,6 +96,11 @@ static void
matchmaking->SetLobbyMemberData(currentLobby, "vh", vehicle); matchmaking->SetLobbyMemberData(currentLobby, "vh", vehicle);
matchmaking->SetLobbyMemberData(currentLobby, "cl", color); matchmaking->SetLobbyMemberData(currentLobby, "cl", color);
matchmaking->SetLobbyMemberData(currentLobby, "xp", experience); 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 static int
@@ -187,10 +192,17 @@ static void
int n = 0; int n = 0;
for (const char *s = member.name; *s && n < 60; ++s) name[n++] = (WCHAR)*s; for (const char *s = member.name; *s && n < 60; ++s) name[n++] = (WCHAR)*s;
name[n] = 0; 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.vehicle[0] ? member.vehicle : "mech",
member.color[0] ? member.color : "-", 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 else
{ {
@@ -378,6 +390,25 @@ int
{ {
continue; 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)) if ((pass == 0) == (member.isSelf != 0))
{ {
roster_out->members[roster_out->memberCount++] = member; roster_out->members[roster_out->memberCount++] = member;