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:
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user