Files
BT412/engine/MUNGA_L4/L4STEAMTRANSPORT.cpp
T
CydandClaude Fable 5 5ebb9a5906 Net: NetTransport seam + Steam transport (Workstream C.1)
The wire moves behind NetTransport (L4NETTRANSPORT): L4NET.CPP taken
from RP412 post-seam -- all ~24 Winsock call sites route through
NetTransport_Get() -- with BT's 3 BT_NET_TRACE blocks re-sited onto
their code anchors (they read message/packet metadata, not sockets, so
no collision). Default WinsockNetTransport = the arcade/LAN TCP wire.

SteamNetTransport (L4STEAMTRANSPORT, ISteamNetworkingSockets + FakeIP/
SDR) compiles under option(BT412_STEAM) (default OFF); Steamworks SDK
1.64 vendored at extern/steamworks_sdk_164. steam_appid.txt gitignored
(Spacewar 480 by hand until a real AppID). Ported gConsoleLossEndsMission
from RP412's APPMGR (default False = arcade re-listen).

Verified: default TCP build passes full loopback MP through the seam
(console -> egg msgID-3 chunks -> mesh complete -> both instances tick,
net-tx/net-rx traces fire through NetTransport_Get()); BT412_STEAM=ON
compiles + links against the SDK + boots solo. Live Steam session
deferred to Phase 6. (Phase 4 of docs/BT412-ROADMAP.md)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 08:33:34 -05:00

906 lines
25 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4steamtransport.h"
#ifdef BT412_STEAM
#include "l4nettransport.h"
// The engine builds with /Zp1; Valve's ABI expects default packing.
// Callback structs carry their own pack pragmas, but everything else
// must see the platform default.
#pragma pack(push, 8)
#include "steam/steam_api.h"
#include "steam/isteamnetworkingsockets.h"
#include "steam/isteamnetworkingutils.h"
#include "steam/steamnetworkingfakeip.h"
#pragma pack(pop)
//########################################################################
// SteamNetTransport - see l4steamtransport.h for the design. The
// engine is single threaded and so is this: every entry point pumps
// SteamAPI_RunCallbacks, which is where the status-changed callback
// fires (accepting incoming connections and marking drops).
//########################################################################
namespace
{
enum
{
maxListeners = 4,
maxConnections = 32,
maxPeers = 16,
maxPending = 8,
leftoverSize = 4096
};
struct ListenerRecord
{
HSteamListenSocket handle;
int fakePortIndex;
unsigned short enginePort; // host order
HSteamNetConnection pending[maxPending];
int pendingCount;
// The engine closes listeners with TCP semantics (accepted
// connections survive). Steam's CloseListenSocket kills the
// accepted connections ungracefully - so an engine close only
// marks the listener here; the real close waits for Cleanup.
Logical closed;
};
struct ConnectionRecord
{
HSteamNetConnection handle;
unsigned long remoteIP; // network order
unsigned short remoteEnginePort; // network order
Logical connected;
Logical dead;
char leftover[leftoverSize];
int leftoverCount;
};
struct PeerRecord
{
unsigned long fakeIP; // host order
unsigned short fakeConsolePort; // host order
unsigned short fakeGamePort; // host order
unsigned __int64 steamID; // identity -> global FakeIP on accept
};
// engine-side port convention (lobby default; game port is +1)
unsigned short gEngineConsolePort = 1501;
Logical gSteamReady = False;
unsigned long gLocalFakeIP = 0; // host order
unsigned short gLocalFakePorts[2] = { 0, 0 };
char gLocalFakeAddressString[32] = "";
// engine ports in the order Listen sees them: [0] console, [1] game
unsigned short gEnginePorts[2] = { 0, 0 };
int gEnginePortCount = 0;
ListenerRecord gListeners[maxListeners];
int gListenerCount = 0;
ConnectionRecord gConnections[maxConnections];
int gConnectionCount = 0;
PeerRecord gPeers[maxPeers];
int gPeerCount = 0;
ListenerRecord *
FindListener(HSteamListenSocket handle)
{
for (int i = 0; i < gListenerCount; ++i)
{
if (gListeners[i].handle == handle)
{
return &gListeners[i];
}
}
return NULL;
}
ConnectionRecord *
FindConnection(HSteamNetConnection handle)
{
for (int i = 0; i < gConnectionCount; ++i)
{
if (gConnections[i].handle == handle)
{
return &gConnections[i];
}
}
return NULL;
}
ConnectionRecord *
AddConnection(
HSteamNetConnection handle,
unsigned long remote_ip_net,
unsigned short remote_engine_port_net
)
{
if (gConnectionCount >= maxConnections)
{
return NULL;
}
ConnectionRecord *record = &gConnections[gConnectionCount++];
memset(record, 0, sizeof(*record));
record->handle = handle;
record->remoteIP = remote_ip_net;
record->remoteEnginePort = remote_engine_port_net;
return record;
}
void
RemoveConnection(HSteamNetConnection handle)
{
for (int i = 0; i < gConnectionCount; ++i)
{
if (gConnections[i].handle == handle)
{
gConnections[i] = gConnections[gConnectionCount - 1];
--gConnectionCount;
return;
}
}
}
// engine port in a SOCKADDR_IN -> the peer's matching fake port
unsigned short
LookupPeerFakePort(unsigned long fake_ip_host, unsigned short engine_port)
{
for (int i = 0; i < gPeerCount; ++i)
{
if (gPeers[i].fakeIP == fake_ip_host)
{
return (engine_port == gEngineConsolePort)
? gPeers[i].fakeConsolePort
: gPeers[i].fakeGamePort;
}
}
return 0;
}
//---------------------------------------------------------------
// Status-changed handler (fires inside SteamAPI_RunCallbacks on
// the game thread): accept incoming, queue connected, mark drops.
// Registered through the CCallback dispatcher below - the
// config-value function pointer is NOT dispatched by the steam_api
// flavor of the library (Valve's own example uses STEAM_CALLBACK).
//---------------------------------------------------------------
void
OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *status)
{
switch (status->m_info.m_eState)
{
case k_ESteamNetworkingConnectionState_Connecting:
if (status->m_info.m_hListenSocket != k_HSteamListenSocket_Invalid)
{
// no more callers once the engine closed the listener
ListenerRecord *listener = FindListener(status->m_info.m_hListenSocket);
if (listener == NULL || listener->closed)
{
DEBUG_STREAM << "SteamNetTransport: incoming ["
<< status->m_info.m_szConnectionDescription
<< "] on a closed listener - rejecting\n" << std::flush;
SteamNetworkingSockets()->CloseConnection(
status->m_hConn, 0, "listener closed", false);
break;
}
// incoming: accept immediately, queue it when Connected
DEBUG_STREAM << "SteamNetTransport: incoming ["
<< status->m_info.m_szConnectionDescription
<< "] - accepting\n" << std::flush;
EResult accepted = SteamNetworkingSockets()->AcceptConnection(status->m_hConn);
if (accepted != k_EResultOK)
{
DEBUG_STREAM << "SteamNetTransport: AcceptConnection failed (EResult "
<< (int) accepted << ")\n" << std::flush;
}
}
break;
case k_ESteamNetworkingConnectionState_Connected:
DEBUG_STREAM << "SteamNetTransport: connected ["
<< status->m_info.m_szConnectionDescription << "]\n" << std::flush;
if (status->m_info.m_hListenSocket != k_HSteamListenSocket_Invalid)
{
ListenerRecord *listener = FindListener(status->m_info.m_hListenSocket);
if (listener != NULL && listener->pendingCount < maxPending)
{
listener->pending[listener->pendingCount++] = status->m_hConn;
}
}
else
{
ConnectionRecord *record = FindConnection(status->m_hConn);
if (record != NULL)
{
record->connected = True;
}
}
break;
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
case k_ESteamNetworkingConnectionState_ClosedByPeer:
{
DEBUG_STREAM << "SteamNetTransport: dropped ["
<< status->m_info.m_szConnectionDescription
<< "] end reason " << status->m_info.m_eEndReason
<< ": " << status->m_info.m_szEndDebug << "\n" << std::flush;
ConnectionRecord *record = FindConnection(status->m_hConn);
if (record != NULL)
{
record->dead = True;
}
}
break;
}
}
//---------------------------------------------------------------
// The callback listener: constructed after SteamAPI_Init so the
// CCallback registration lands in a live callback manager
//---------------------------------------------------------------
class SteamTransportCallbacks
{
public:
STEAM_CALLBACK(SteamTransportCallbacks, OnStatusChanged,
SteamNetConnectionStatusChangedCallback_t);
};
void SteamTransportCallbacks::OnStatusChanged(
SteamNetConnectionStatusChangedCallback_t *status)
{
OnConnectionStatusChanged(status);
}
SteamTransportCallbacks *gCallbacks = NULL;
//---------------------------------------------------------------
// The transport
//---------------------------------------------------------------
class SteamNetTransport:
public NetTransport
{
public:
Logical
Startup()
{
// Install() already brought Steam up; the network manager
// just confirms the transport is live
return gSteamReady;
}
void
Cleanup()
{
// mission teardown (single-binary race loop): drop the
// wire but keep the Steam API up - the lobby lives on
for (int i = 0; i < gConnectionCount; ++i)
{
SteamNetworkingSockets()->CloseConnection(
gConnections[i].handle, 0, "mission teardown", true);
}
gConnectionCount = 0;
for (int j = 0; j < gListenerCount; ++j)
{
SteamNetworkingSockets()->CloseListenSocket(gListeners[j].handle);
}
gListenerCount = 0;
gEnginePortCount = 0;
}
int
GetLocalAddresses(
unsigned long *addresses,
int max_count
)
{
if (!gSteamReady || max_count < 1)
{
return 0;
}
addresses[0] = htonl(gLocalFakeIP);
return 1;
}
Logical
Resolve(
const char *host_name,
SOCKADDR_IN *address
)
{
memset(address, 0, sizeof(SOCKADDR_IN));
address->sin_family = AF_INET;
SteamNetworkingIPAddr parsed;
if (!parsed.ParseString(host_name))
{
return False;
}
address->sin_addr.S_un.S_addr = htonl(parsed.GetIPv4());
address->sin_port = htons(parsed.m_port); // 0 when absent
return True;
}
Connection
Connect(
const SOCKADDR_IN *remote,
int local_port
)
{
unsigned long remote_fake_ip = ntohl(remote->sin_addr.S_un.S_addr);
unsigned short fake_port =
LookupPeerFakePort(remote_fake_ip, ntohs(remote->sin_port));
if (fake_port == 0)
{
DEBUG_STREAM << "SteamNetTransport: no registered peer for "
<< inet_ntoa(remote->sin_addr) << " - lobby did not feed it\n" << std::flush;
return InvalidConnection;
}
DEBUG_STREAM << "SteamNetTransport: connecting to "
<< inet_ntoa(remote->sin_addr) << ":" << ntohs(remote->sin_port)
<< " (fake port " << fake_port << ")...\n" << std::flush;
SteamNetworkingIPAddr target;
target.Clear();
target.SetIPv4(remote_fake_ip, fake_port);
// mirror the TCP retry-while-refused loop, bounded: the
// egg-ACK ordering means the peer may not be listening yet
DWORD deadline = GetTickCount() + 120 * 1000;
int attempt = 0;
for (;;)
{
++attempt;
HSteamNetConnection handle =
SteamNetworkingSockets()->ConnectByIPAddress(target, 0, NULL);
if (handle == k_HSteamNetConnection_Invalid)
{
DEBUG_STREAM << "SteamNetTransport: ConnectByIPAddress refused the call\n" << std::flush;
return InvalidConnection;
}
AddConnection(handle, remote->sin_addr.S_un.S_addr, remote->sin_port);
//
// Poll the connection state directly (the callback also
// runs, for the log and the accept queues)
//
ESteamNetworkingConnectionState state =
k_ESteamNetworkingConnectionState_Connecting;
while ((LONG)(GetTickCount() - deadline) < 0)
{
SteamAPI_RunCallbacks();
SteamNetConnectionInfo_t info;
if (!SteamNetworkingSockets()->GetConnectionInfo(handle, &info))
{
state = k_ESteamNetworkingConnectionState_Dead;
break;
}
state = info.m_eState;
if (state == k_ESteamNetworkingConnectionState_Connected ||
state == k_ESteamNetworkingConnectionState_ProblemDetectedLocally ||
state == k_ESteamNetworkingConnectionState_ClosedByPeer ||
state == k_ESteamNetworkingConnectionState_None)
{
break;
}
Sleep(25);
}
if (state == k_ESteamNetworkingConnectionState_Connected)
{
ConnectionRecord *record = FindConnection(handle);
if (record != NULL)
{
record->connected = True;
}
DEBUG_STREAM << "SteamNetTransport: connect succeeded (attempt "
<< attempt << ")\n" << std::flush;
return (Connection) handle;
}
// attempt failed - drop it and retry until the deadline
DEBUG_STREAM << "SteamNetTransport: attempt " << attempt
<< " ended in state " << (int) state << "\n" << std::flush;
SteamNetworkingSockets()->CloseConnection(handle, 0, "retry", false);
RemoveConnection(handle);
if ((LONG)(GetTickCount() - deadline) >= 0)
{
DEBUG_STREAM << "SteamNetTransport: connect timed out\n" << std::flush;
return InvalidConnection;
}
Sleep(1000);
}
}
Connection
Listen(
int local_port,
int backlog
)
{
if (gListenerCount >= maxListeners)
{
return InvalidConnection;
}
// a logically-closed listener for this engine port reopens
// (the Steam socket outlives engine closes; see Close)
for (int existing = 0; existing < gListenerCount; ++existing)
{
if (gListeners[existing].enginePort == (unsigned short) local_port)
{
gListeners[existing].closed = False;
gListeners[existing].pendingCount = 0;
DEBUG_STREAM << "SteamNetTransport: reopened listener on engine port "
<< local_port << "\n" << std::flush;
return (Connection) gListeners[existing].handle;
}
}
// engine port -> fake port index, in order of appearance:
// the console channel always listens first, the mesh second
int index = -1;
for (int i = 0; i < gEnginePortCount; ++i)
{
if (gEnginePorts[i] == (unsigned short) local_port)
{
index = i;
}
}
if (index < 0)
{
if (gEnginePortCount >= 2)
{
DEBUG_STREAM << "SteamNetTransport: only two fake ports allocated!\n" << std::flush;
return InvalidConnection;
}
index = gEnginePortCount;
gEnginePorts[gEnginePortCount++] = (unsigned short) local_port;
}
DEBUG_STREAM << "SteamNetTransport: listening on engine port "
<< local_port << " (fake port " << gLocalFakePorts[index]
<< ")...\n" << std::flush;
HSteamListenSocket handle =
SteamNetworkingSockets()->CreateListenSocketP2PFakeIP(index, 0, NULL);
if (handle == k_HSteamListenSocket_Invalid)
{
return InvalidConnection;
}
ListenerRecord *listener = &gListeners[gListenerCount++];
memset(listener, 0, sizeof(*listener));
listener->handle = handle;
listener->fakePortIndex = index;
listener->enginePort = (unsigned short) local_port;
return (Connection) handle;
}
Connection
Accept(Connection listener_handle)
{
SteamAPI_RunCallbacks();
ListenerRecord *listener = FindListener((HSteamListenSocket) listener_handle);
if (listener == NULL || listener->closed || listener->pendingCount == 0)
{
return InvalidConnection;
}
HSteamNetConnection handle = listener->pending[0];
for (int i = 1; i < listener->pendingCount; ++i)
{
listener->pending[i - 1] = listener->pending[i];
}
--listener->pendingCount;
//
// The remote engine port is the same engine port we accept
// on (all pods share the -net convention under Steam). The
// caller's address: Steam reports incoming connections
// under a locally-allocated ALIAS FakeIP, so resolve the
// caller's identity through the peer table to the GLOBAL
// FakeIP the egg's [pilots] list promised - the engine's
// mesh identity checks compare against that.
//
unsigned long remote_ip_net = 0;
SteamNetConnectionInfo_t info;
if (SteamNetworkingSockets()->GetConnectionInfo(handle, &info))
{
unsigned __int64 caller_id = info.m_identityRemote.GetSteamID64();
for (int p = 0; p < gPeerCount; ++p)
{
if (gPeers[p].steamID != 0 && gPeers[p].steamID == caller_id)
{
remote_ip_net = htonl(gPeers[p].fakeIP);
break;
}
}
}
if (remote_ip_net == 0)
{
// unregistered caller: fall back to the alias address
SteamNetworkingIPAddr remote_fake;
if (SteamNetworkingSockets()->GetRemoteFakeIPForConnection(
handle, &remote_fake) == k_EResultOK)
{
remote_ip_net = htonl(remote_fake.GetIPv4());
}
}
ConnectionRecord *record = AddConnection(
handle, remote_ip_net, htons(listener->enginePort));
if (record != NULL)
{
record->connected = True;
}
return (Connection) handle;
}
void
Close(Connection connection)
{
ListenerRecord *listener = FindListener((HSteamListenSocket) connection);
if (listener != NULL)
{
//
// TCP semantics: accepted connections must survive a
// listener close. Steam's CloseListenSocket kills them
// ungracefully, so only mark it closed (rejecting any
// new callers) and destroy it for real in Cleanup.
//
listener->closed = True;
for (int i = 0; i < listener->pendingCount; ++i)
{
SteamNetworkingSockets()->CloseConnection(
listener->pending[i], 0, "listener closed", false);
}
listener->pendingCount = 0;
return;
}
SteamNetworkingSockets()->CloseConnection(
(HSteamNetConnection) connection, 0, "closed", true);
RemoveConnection((HSteamNetConnection) connection);
}
int
Send(
Connection connection,
const void *data,
int size
)
{
EResult result = SteamNetworkingSockets()->SendMessageToConnection(
(HSteamNetConnection) connection, data, (uint32) size,
k_nSteamNetworkingSend_Reliable, NULL);
return (result == k_EResultOK) ? size : -1;
}
int
Receive(
Connection connection,
void *buffer,
int size
)
{
SteamAPI_RunCallbacks();
ConnectionRecord *record = FindConnection((HSteamNetConnection) connection);
if (record == NULL)
{
return ReceiveNoData;
}
char *out = (char *) buffer;
int copied = 0;
// leftover bytes from a message that outsized the last call
if (record->leftoverCount > 0)
{
int take = (record->leftoverCount < size) ? record->leftoverCount : size;
memcpy(out, record->leftover, take);
memmove(record->leftover, record->leftover + take,
record->leftoverCount - take);
record->leftoverCount -= take;
out += take;
copied += take;
}
// drain messages while they fit; stash any partial tail
while (copied < size && record->leftoverCount == 0)
{
SteamNetworkingMessage_t *message = NULL;
int count = SteamNetworkingSockets()->ReceiveMessagesOnConnection(
record->handle, &message, 1);
if (count <= 0)
{
break;
}
int room = size - copied;
int take = ((int) message->m_cbSize < room) ? (int) message->m_cbSize : room;
memcpy(out, message->m_pData, take);
out += take;
copied += take;
int rest = (int) message->m_cbSize - take;
if (rest > 0)
{
if (rest > leftoverSize)
{
rest = leftoverSize; // cannot happen: engine packets < 2K
}
memcpy(record->leftover, (const char *) message->m_pData + take, rest);
record->leftoverCount = rest;
}
message->Release();
}
if (copied > 0)
{
return copied;
}
if (record->dead)
{
return ReceiveDisconnected;
}
return ReceiveNoData;
}
Logical
GetRemoteAddress(
Connection connection,
SOCKADDR_IN *address
)
{
memset(address, 0, sizeof(SOCKADDR_IN));
ConnectionRecord *record = FindConnection((HSteamNetConnection) connection);
if (record == NULL || !record->connected)
{
return False;
}
address->sin_family = AF_INET;
address->sin_addr.S_un.S_addr = record->remoteIP;
address->sin_port = record->remoteEnginePort;
return True;
}
};
SteamNetTransport gSteamTransport;
}
//########################################################################
// Install: bring Steam up, get our FakeIP identity, take over the wire
//########################################################################
Logical
SteamNetTransport_Install()
{
if (gSteamReady)
{
return True;
}
if (!SteamAPI_Init())
{
DEBUG_STREAM << "SteamNetTransport: SteamAPI_Init failed "
<< "(Steam not running, or no steam_appid.txt) - staying on TCP\n" << std::flush;
return False;
}
// connection status arrives through the CCallback dispatcher
if (gCallbacks == NULL)
{
gCallbacks = new SteamTransportCallbacks;
}
SteamNetworkingUtils()->InitRelayNetworkAccess();
//
// Mission load stalls the game thread for 10-30s with nothing
// pumping - Steam's default 10s connected-timeout would shear
// every connection mid-load (seen live: end reason 4001 with rx
// ages right at load duration). TCP never timed out idle arcade
// links; 90s keeps that spirit.
//
SteamNetworkingUtils()->SetGlobalConfigValueInt32(
k_ESteamNetworkingConfig_TimeoutConnected, 90 * 1000);
if (!SteamNetworkingSockets()->BeginAsyncRequestFakeIP(2))
{
DEBUG_STREAM << "SteamNetTransport: BeginAsyncRequestFakeIP failed - staying on TCP\n" << std::flush;
return False;
}
// FakeIP allocation is async: pump until it lands (or give up)
SteamNetworkingFakeIPResult_t fake;
memset(&fake, 0, sizeof(fake));
DWORD deadline = GetTickCount() + 20 * 1000;
for (;;)
{
SteamAPI_RunCallbacks();
SteamNetworkingSockets()->GetFakeIP(0, &fake);
if (fake.m_eResult == k_EResultOK)
{
break;
}
if (fake.m_eResult != k_EResultBusy && fake.m_eResult != k_EResultNoMatch)
{
DEBUG_STREAM << "SteamNetTransport: FakeIP allocation failed (EResult "
<< (int) fake.m_eResult << ") - staying on TCP\n" << std::flush;
return False;
}
if ((LONG)(GetTickCount() - deadline) >= 0)
{
DEBUG_STREAM << "SteamNetTransport: FakeIP allocation timed out - staying on TCP\n" << std::flush;
return False;
}
Sleep(50);
}
gLocalFakeIP = fake.m_unIP;
gLocalFakePorts[0] = fake.m_unPorts[0];
gLocalFakePorts[1] = fake.m_unPorts[1];
SteamNetworkingIPAddr mine;
mine.Clear();
mine.SetIPv4(gLocalFakeIP, 0);
mine.ToString(gLocalFakeAddressString, sizeof(gLocalFakeAddressString), false);
DEBUG_STREAM << "SteamNetTransport: up as " << gLocalFakeAddressString
<< " (fake ports " << gLocalFakePorts[0] << " console, "
<< gLocalFakePorts[1] << " game)\n" << std::flush;
gSteamReady = True;
NetTransport_Set(&gSteamTransport);
//
// dev: BT412STEAMSELFTEST=1 loops a connection back to ourselves,
// proving the listen/accept/connect/send/receive machinery (and
// the status-callback dispatch) without a second machine
//
const char *self_test = getenv("BT412STEAMSELFTEST");
if (self_test != NULL && atoi(self_test) != 0)
{
DEBUG_STREAM << "SteamNetTransport: SELF TEST starting\n" << std::flush;
SteamNetTransport_RegisterPeer(
gLocalFakeAddressString, gLocalFakePorts[0], gLocalFakePorts[1],
SteamUser()->GetSteamID().ConvertToUint64());
NetTransport *transport = &gSteamTransport;
NetTransport::Connection listener = transport->Listen(1501, 1);
SOCKADDR_IN self_address;
transport->Resolve(gLocalFakeAddressString, &self_address);
self_address.sin_port = htons(1501);
NetTransport::Connection outgoing = transport->Connect(&self_address, 0);
NetTransport::Connection incoming = NetTransport::InvalidConnection;
DWORD accept_deadline = GetTickCount() + 10 * 1000;
while (incoming == NetTransport::InvalidConnection &&
(LONG)(GetTickCount() - accept_deadline) < 0)
{
incoming = transport->Accept(listener);
Sleep(50);
}
Logical ok = False;
Logical survives_close = False;
if (outgoing != NetTransport::InvalidConnection &&
incoming != NetTransport::InvalidConnection)
{
transport->Send(outgoing, "PING", 4);
char buffer[16];
DWORD recv_deadline = GetTickCount() + 5 * 1000;
while ((LONG)(GetTickCount() - recv_deadline) < 0)
{
if (transport->Receive(incoming, buffer, sizeof(buffer)) == 4)
{
ok = (memcmp(buffer, "PING", 4) == 0);
break;
}
Sleep(25);
}
//
// The arcade engine closes a listener right after
// accepting - the accepted connection MUST survive
// (Steam kills children on CloseListenSocket; the
// transport defers the real close to Cleanup)
//
transport->Close(listener);
transport->Send(incoming, "PONG", 4);
recv_deadline = GetTickCount() + 5 * 1000;
while ((LONG)(GetTickCount() - recv_deadline) < 0)
{
if (transport->Receive(outgoing, buffer, sizeof(buffer)) == 4)
{
survives_close = (memcmp(buffer, "PONG", 4) == 0);
break;
}
Sleep(25);
}
}
DEBUG_STREAM << "SteamNetTransport: SELF TEST "
<< ((ok && survives_close) ? "PASSED" : "FAILED")
<< " (out " << (outgoing != NetTransport::InvalidConnection)
<< ", in " << (incoming != NetTransport::InvalidConnection)
<< ", ping " << ok
<< ", survives listener close " << survives_close
<< ")\n" << std::flush;
// leave the session pristine for the real race
gSteamTransport.Cleanup();
gPeerCount = 0;
}
return True;
}
const char *
SteamNetTransport_GetFakeAddressString()
{
return gLocalFakeAddressString;
}
int
SteamNetTransport_GetFakeConsolePort()
{
return gLocalFakePorts[0];
}
int
SteamNetTransport_GetFakeGamePort()
{
return gLocalFakePorts[1];
}
void
SteamNetTransport_SetEnginePorts(int console_port)
{
gEngineConsolePort = (unsigned short) console_port;
}
void
SteamNetTransport_RegisterPeer(
const char *fake_ip,
int fake_console_port,
int fake_game_port,
unsigned __int64 steam_id
)
{
SteamNetworkingIPAddr parsed;
if (!parsed.ParseString(fake_ip))
{
return;
}
for (int i = 0; i < gPeerCount; ++i)
{
if (gPeers[i].fakeIP == parsed.GetIPv4())
{
gPeers[i].fakeConsolePort = (unsigned short) fake_console_port;
gPeers[i].fakeGamePort = (unsigned short) fake_game_port;
gPeers[i].steamID = steam_id;
return;
}
}
if (gPeerCount >= maxPeers)
{
return;
}
gPeers[gPeerCount].fakeIP = parsed.GetIPv4();
gPeers[gPeerCount].fakeConsolePort = (unsigned short) fake_console_port;
gPeers[gPeerCount].fakeGamePort = (unsigned short) fake_game_port;
gPeers[gPeerCount].steamID = steam_id;
++gPeerCount;
DEBUG_STREAM << "SteamNetTransport: peer registered: " << fake_ip
<< " (console " << fake_console_port << ", game "
<< fake_game_port << ", id " << steam_id << ")\n" << std::flush;
}
#endif // BT412_STEAM