Steam: the lobby + IDENTITY-TOKEN roster -- internet MP code-complete (step 4c)

NEW gated (BT_STEAM) game/glass/btl4lobby: an ISteamMatchmaking room replacing
the arcade Site-Management screen -- host creates (lobby data btl4=1), members
join by filter, pilots publish name/mech/color, the host ENTER mints the
roster and signals GO.

THE FAKEIP LESSON (live-verified, prior-art assumption DISPROVEN): a fresh
process gets a DIFFERENT FakeIP, so menu-time fake addresses go stale by
mission time -- the first cycle timed out connecting to its own stale address.
Redesign: roster addresses are opaque ipv4-shaped TOKENS (169.254.77.N with
ports 1501/1502) minted by the host at GO, mapped to Steam IDENTITIES;
connections ride ConnectP2P(identity, channel) with console=0/game=1 virtual
ports; the map reaches mission processes via env (BT_FE_MYFAKE +
BT_FE_STEAMMAP); the GetMyAddress seam feeds the pod its own token for roster
self-match; CheckSocket reports peers by token.  L4STEAMNET reworked (no
FakeIP allocation at all); the marshal gained the Steam-wire branch.

Verified single-machine (ON/ON, live Steam): menu -> HOST STEAM LOBBY ->
room -> GO -> token map minted -> egg roster carries the token -> mission
process up with 1 roster token incl. self -> pod self-matches -> stock
console ladder -> mission RUNS (46 ticks).  Remaining: the live 2-account
cross-machine session (docs/STEAM_TEST.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-18 00:03:17 -05:00
co-authored by Claude Fable 5
parent 48ede2f7d7
commit f703bb1d56
11 changed files with 875 additions and 124 deletions
+19 -3
View File
@@ -2682,14 +2682,30 @@ NetworkAddress* L4NetworkManager::GetMyAddress()
// count how many addresses we have
for (num_addresses = 0; hostinfo->h_addr_list[num_addresses]; num_addresses++);
NetworkAddress* myAddresses = new NetworkAddress[num_addresses + 1];
// (+2: the 127.0.0.1 slot, plus the Steam FakeIP slot when active)
NetworkAddress* myAddresses = new NetworkAddress[num_addresses + 2];
for (int i=0; i<num_addresses; i++)
myAddresses[i] = *((NetworkAddress*)hostinfo->h_addr_list[i]);
// add 127.0.0.1 to list
myAddresses[num_addresses++] = (NetworkAddress)0x0100007F;
#ifdef BT_STEAM
//
// The pod's own Steam FakeIP is also "me": a lobby-built egg's
// [pilots] roster carries FAKE addresses, and the self-identification
// walk matches the roster against this list.
//
{
unsigned long fake_address_be = 0;
if (BTSteamNet_GetFakeAddressRaw(&fake_address_be) == 0)
{
myAddresses[num_addresses++] = (NetworkAddress)fake_address_be;
}
}
#endif
return myAddresses;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+119 -69
View File
@@ -51,9 +51,29 @@ static SteamConnection connections[MaxConnections];
static HSteamListenSocket listenSockets[FakePortCount];
static SOCKET acceptQueue[FakePortCount][AcceptQueueSize];
static int acceptHead[FakePortCount], acceptTail[FakePortCount];
static SteamNetworkingFakeIPResult_t myFakeIP;
static unsigned long lastPumpTick = 0;
//
// The TOKEN TABLE (the FakeIP-stability lesson, live-verified: a fresh
// process gets a DIFFERENT FakeIP, so menu-time fake addresses go stale
// by mission time). Roster addresses are therefore opaque ipv4-shaped
// TOKENS minted by the lobby host and mapped to Steam IDENTITIES; the
// mission process receives the map via env:
// BT_FE_MYFAKE = my own token ip (joins GetMyAddress -- self-match)
// BT_FE_STEAMMAP = ip=steamid64;ip=steamid64;...
// Console channel = P2P virtual port 0 (token port 1501), game = virtual
// port 1 (token port 1502).
//
enum { TokenConsolePort = 1501, TokenGamePort = 1502 };
struct SteamToken
{
unsigned long addressBE;
unsigned long long steamID;
};
static SteamToken tokens[8];
static int tokenCount = 0;
static unsigned long myTokenBE = 0;
static SteamConnection *
ConnectionOf(SOCKET wire_socket)
{
@@ -101,22 +121,30 @@ static SteamConnection *
}
//
// The peer's fake ipv4:port as a SOCKADDR_IN (network byte order), from
// the connection info. The GAME port convention on the wire is the
// arcade's: whatever port the peer's fake address carries.
// The peer's TOKEN ipv4:port as a SOCKADDR_IN: the connection's remote
// Steam identity looked up in the token table; the port encodes the
// channel (console/game) the connection rides.
//
static void
FillPeerAddress(SteamConnection *connection)
FillPeerAddressFromIdentity(SteamConnection *connection, int channel)
{
SteamNetConnectionInfo_t info;
if (SteamNetworkingSockets()->GetConnectionInfo(
connection->connection, &info) &&
info.m_addrRemote.IsFakeIP())
if (!SteamNetworkingSockets()->GetConnectionInfo(
connection->connection, &info))
{
connection->peerAddress.sin_family = AF_INET;
connection->peerAddress.sin_addr.s_addr =
htonl(info.m_addrRemote.GetIPv4());
connection->peerAddress.sin_port = htons(info.m_addrRemote.m_port);
return;
}
unsigned long long remote_id = info.m_identityRemote.GetSteamID64();
for (int t = 0; t < tokenCount; ++t)
{
if (tokens[t].steamID == remote_id)
{
connection->peerAddress.sin_family = AF_INET;
connection->peerAddress.sin_addr.s_addr = tokens[t].addressBE;
connection->peerAddress.sin_port = htons((unsigned short)
((channel == 0) ? TokenConsolePort : TokenGamePort));
return;
}
}
}
@@ -169,6 +197,7 @@ static void __cdecl
sockets->CloseConnection(status->m_hConn, 0, "table full", false);
return;
}
FillPeerAddressFromIdentity(connection, channel);
acceptQueue[channel][acceptHead[channel]] = PseudoSocketOf(connection);
acceptHead[channel] = next;
STEAMNET_LOG("incoming connection queued on channel " << channel);
@@ -182,7 +211,6 @@ static void __cdecl
connections[i].connection == status->m_hConn)
{
connections[i].connected = 1;
FillPeerAddress(&connections[i]);
STEAMNET_LOG("connection " << i << " up");
}
}
@@ -319,49 +347,60 @@ int
OnConnectionStatusChanged);
//
// FakeIP: one allocation carrying both channel ports.
// The token handoff (lobby -> mission process): my own token + the
// token->identity map. A lobby/menu process has neither -- fine, it
// only needs matchmaking.
//
memset(&myFakeIP, 0, sizeof(myFakeIP));
sockets->BeginAsyncRequestFakeIP(FakePortCount);
for (int wait = 0; wait < 200; ++wait) // <= 20 s
tokenCount = 0;
myTokenBE = 0;
const char *my_token = getenv("BT_FE_MYFAKE");
if (my_token != NULL && my_token[0])
{
SteamAPI_RunCallbacks(); // FakeIP result rides the general dispatch
sockets->RunCallbacks();
sockets->GetFakeIP(0, &myFakeIP); // void: fills the struct
if (myFakeIP.m_eResult == k_EResultOK && myFakeIP.m_unIP != 0)
{
break;
}
Sleep(100);
myTokenBE = inet_addr(my_token);
}
if (myFakeIP.m_unIP == 0)
const char *map_text = getenv("BT_FE_STEAMMAP");
if (map_text != NULL && map_text[0])
{
STEAMNET_LOG("FakeIP allocation timed out -- staying on Winsock");
SteamAPI_Shutdown();
return -1;
char map_copy[512];
strncpy(map_copy, map_text, sizeof(map_copy) - 1);
map_copy[sizeof(map_copy) - 1] = 0;
char *cursor = strtok(map_copy, ";");
while (cursor != NULL && tokenCount < 8)
{
char *equals = strchr(cursor, '=');
if (equals != NULL)
{
*equals = 0;
tokens[tokenCount].addressBE = inet_addr(cursor);
tokens[tokenCount].steamID = _strtoui64(equals + 1, NULL, 10);
if (tokens[tokenCount].addressBE != INADDR_NONE &&
tokens[tokenCount].steamID != 0)
{
++tokenCount;
}
}
cursor = strtok(NULL, ";");
}
}
//
// Listen on both fake ports.
// Listen on both identity channels (console = 0, game = 1).
//
for (int i = 0; i < FakePortCount; ++i)
{
listenSockets[i] = sockets->CreateListenSocketP2PFakeIP(i, 0, NULL);
listenSockets[i] = sockets->CreateListenSocketP2P(i, 0, NULL);
acceptHead[i] = acceptTail[i] = 0;
if (listenSockets[i] == k_HSteamListenSocket_Invalid)
{
STEAMNET_LOG("CreateListenSocketP2PFakeIP(" << i << ") failed");
STEAMNET_LOG("CreateListenSocketP2P(" << i << ") failed");
}
}
steamActive = 1;
char dotted[32];
SteamNetworkingIPAddr address;
address.SetIPv4(myFakeIP.m_unIP, myFakeIP.m_unPorts[0]);
address.ToString(dotted, sizeof(dotted), false);
STEAMNET_LOG("up -- FakeIP " << dotted
<< " console-port " << myFakeIP.m_unPorts[0]
<< " game-port " << myFakeIP.m_unPorts[1]);
STEAMNET_LOG("up -- identity "
<< (unsigned long long)SteamUser()->GetSteamID().ConvertToUint64()
<< ", " << tokenCount << " roster token(s)"
<< (myTokenBE ? " incl. self" : ""));
return 0;
}
@@ -483,10 +522,6 @@ int
{
return 0;
}
if (connection->peerAddress.sin_family == 0)
{
FillPeerAddress(connection);
}
*out = connection->peerAddress;
return connection->peerAddress.sin_family != 0;
}
@@ -502,9 +537,14 @@ int
{
return 0;
}
SteamNetworkingIPAddr address;
address.SetIPv4(ntohl(internet_address_be), 0);
return address.IsFakeIP();
for (int t = 0; t < tokenCount; ++t)
{
if (tokens[t].addressBE == internet_address_be)
{
return 1;
}
}
return 0;
}
SOCKET
@@ -514,11 +554,25 @@ SOCKET
{
return INVALID_SOCKET;
}
SteamNetworkingIPAddr address;
address.SetIPv4(ntohl(internet_address_be), (uint16)remote_port);
unsigned long long steam_id = 0;
for (int t = 0; t < tokenCount; ++t)
{
if (tokens[t].addressBE == internet_address_be)
{
steam_id = tokens[t].steamID;
}
}
if (steam_id == 0)
{
return INVALID_SOCKET;
}
int channel = (remote_port == TokenConsolePort) ? 0 : 1;
SteamNetworkingIdentity identity;
identity.Clear();
identity.SetSteamID64(steam_id);
HSteamNetConnection handle =
SteamNetworkingSockets()->ConnectByIPAddress(address, 0, NULL);
SteamNetworkingSockets()->ConnectP2P(identity, channel, 0, NULL);
if (handle == k_HSteamNetConnection_Invalid)
{
return INVALID_SOCKET;
@@ -532,6 +586,7 @@ SOCKET
connection->peerAddress.sin_family = AF_INET;
connection->peerAddress.sin_addr.s_addr = internet_address_be;
connection->peerAddress.sin_port = htons((unsigned short)remote_port);
(void)identity;
//
// The engine's OpenConnection is synchronous (blocking connect); wait
@@ -542,9 +597,8 @@ SOCKET
BTSteamNet_Pump();
if (connection->connected)
{
char dotted[48];
address.ToString(dotted, sizeof(dotted), true);
STEAMNET_LOG("connected to " << dotted);
STEAMNET_LOG("connected to peer " << steam_id
<< " channel " << channel);
return PseudoSocketOf(connection);
}
if (connection->closedByPeer)
@@ -559,26 +613,22 @@ SOCKET
}
int
BTSteamNet_GetFakeAddress(char *dotted_out, int capacity,
int *console_port, int *game_port)
BTSteamNet_GetFakeAddressRaw(unsigned long *internet_address_be)
{
if (!steamActive || myFakeIP.m_unIP == 0)
if (!steamActive || myTokenBE == 0)
{
return -1;
}
SteamNetworkingIPAddr address;
address.SetIPv4(myFakeIP.m_unIP, 0);
char text[48];
address.ToString(text, sizeof(text), false);
strncpy(dotted_out, text, capacity - 1);
dotted_out[capacity - 1] = 0;
if (console_port != NULL)
{
*console_port = myFakeIP.m_unPorts[0];
}
if (game_port != NULL)
{
*game_port = myFakeIP.m_unPorts[1];
}
*internet_address_be = myTokenBE;
return 0;
}
unsigned long long
BTSteamNet_MySteamID()
{
if (!steamActive)
{
return 0;
}
return SteamUser()->GetSteamID().ConvertToUint64();
}
+7 -3
View File
@@ -59,8 +59,12 @@ SOCKET
BTSteamNet_Connect(unsigned long internet_address_be, int remote_port);
//
// The lobby surface: our own fake address, dotted, plus its two ports.
// The lobby surface: my identity; my roster token (raw, for the
// GetMyAddress self-match seam). Roster tokens + the token->identity map
// arrive in the MISSION process via env (BT_FE_MYFAKE / BT_FE_STEAMMAP --
// see the token-table note in the .cpp).
//
unsigned long long
BTSteamNet_MySteamID();
int
BTSteamNet_GetFakeAddress(char *dotted_out, int capacity,
int *console_port, int *game_port);
BTSteamNet_GetFakeAddressRaw(unsigned long *internet_address_be);