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>
This commit is contained in:
@@ -0,0 +1,905 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user