Net: the Steam transport -- FakeIP + SDR behind the wire seam (step 4b)
NEW gated (BT_STEAM) TU L4STEAMNET: ISteamNetworkingSockets with FakeIP -- pseudo-SOCKET handles (0x5EA0xxxx) behind the L4NET BTNet* seam; the engine TCP byte stream rides reliable-NoNagle Steam messages re-assembled into per-connection rings (nonblocking recv semantics preserved: empty -> WSAEWOULDBLOCK, peer-closed -> 0); two FakeIP ports mirror the arcade console/game channel convention; listen sockets + accept queues via the global status-changed callback; graceful degrade to Winsock at every step. Seam completions in L4NET.CPP: CheckSocket reports a pseudo-socket peer as its FAKE ipv4:port (the lobby egg roster matches unchanged); OpenConnection TCP_OPEN delegates FakeIP targets to BTSteamNet_Connect. WinMain installs on BT_STEAM_NET=1 (lobby launch path / by hand). CMake: gated SDK include/ lib/dll-copy. Both pumps (SteamAPI_RunCallbacks + sockets RunCallbacks) drive it -- the FakeIP result rides the GENERAL dispatch (first attempt with only the sockets pump timed out). Verified live (ON/ON build, Steam client running): SteamAPI_Init OK, steam_appid.txt(480) auto-written, FakeIP allocated (169.254.36.58, console 54464 / game 54465); without BT_STEAM_NET or without Steam the game stays pure Winsock. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -371,6 +371,19 @@ target_link_libraries(btl4 PRIVATE
|
||||
# UNRESOLVED tolerates the dead offline-tool factory in mech3.cpp. See docs.
|
||||
target_link_options(btl4 PRIVATE /FORCE)
|
||||
|
||||
if(BT_STEAM)
|
||||
set(STEAMWORKS "${CMAKE_SOURCE_DIR}/extern/steamworks_sdk_164")
|
||||
target_sources(munga_engine PRIVATE "engine/MUNGA_L4/L4STEAMNET.cpp")
|
||||
target_include_directories(munga_engine PRIVATE "${STEAMWORKS}/public")
|
||||
target_include_directories(btl4 PRIVATE "${STEAMWORKS}/public")
|
||||
target_link_libraries(btl4 PRIVATE
|
||||
"${STEAMWORKS}/redistributable_bin/steam_api.lib")
|
||||
add_custom_command(TARGET btl4 POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${STEAMWORKS}/redistributable_bin/steam_api.dll"
|
||||
"$<TARGET_FILE_DIR:btl4>")
|
||||
endif()
|
||||
|
||||
# Copy the OpenAL runtime DLL next to the built exe. (libsndfile is gone: its
|
||||
# repo DLL was a no-op STUB -- L4AUDRES now loads the soundbank WAVs with an
|
||||
# in-tree RIFF/PCM reader, no external dependency.)
|
||||
|
||||
+4
-20
@@ -1,8 +1,8 @@
|
||||
[mission]
|
||||
adventure=BattleTech
|
||||
map=cavern
|
||||
map=grass
|
||||
scenario=freeforall
|
||||
time=night
|
||||
time=day
|
||||
weather=clear
|
||||
temperature=27
|
||||
length=600
|
||||
@@ -156,9 +156,8 @@ x=128
|
||||
y=32
|
||||
width=8
|
||||
[pilots]
|
||||
pilot=127.0.0.1:1502
|
||||
pilot=127.0.0.1:1602
|
||||
[127.0.0.1:1502]
|
||||
pilot=200.0.0.96
|
||||
[200.0.0.96]
|
||||
hostType=0
|
||||
advancedDamage=1
|
||||
loadzones=1
|
||||
@@ -172,20 +171,6 @@ dropzone=one
|
||||
vehicle=bhk1
|
||||
vehicleValue=1000
|
||||
color=White
|
||||
[127.0.0.1:1602]
|
||||
hostType=0
|
||||
advancedDamage=1
|
||||
loadzones=1
|
||||
name=Boreas
|
||||
bitmapindex=2
|
||||
experience=expert
|
||||
badge=VGL
|
||||
patch=Yellow
|
||||
role=Role::Default
|
||||
dropzone=one
|
||||
vehicle=ava1
|
||||
vehicleValue=1000
|
||||
color=Red
|
||||
[largebitmap]
|
||||
bitmap=BitMap::Large::Aeolus
|
||||
[BitMap::Large::Aeolus]
|
||||
@@ -242,4 +227,3 @@ width=4
|
||||
model=dfltrole
|
||||
[Role::NoReturn]
|
||||
model=noretun
|
||||
|
||||
|
||||
@@ -2760,6 +2760,17 @@ bool L4NetworkManager::CheckSocket(SOCKET socket, SOCKADDR_IN *remoteEndpoint)
|
||||
{
|
||||
if (remoteEndpoint)
|
||||
{
|
||||
#ifdef BT_STEAM
|
||||
//
|
||||
// A Steam pseudo-socket's peer is its FAKE ipv4:port -- the same
|
||||
// address the lobby-built egg carries in [pilots], so the swap/
|
||||
// match logic downstream works unchanged.
|
||||
//
|
||||
if (BTSteamNet_Owns(socket))
|
||||
{
|
||||
return BTSteamNet_PeerAddress(socket, remoteEndpoint) != 0;
|
||||
}
|
||||
#endif
|
||||
int size = sizeof(SOCKADDR_IN);
|
||||
memset(remoteEndpoint, 0, size);
|
||||
if(!getpeername(socket, (sockaddr*)remoteEndpoint, &size))
|
||||
@@ -2818,6 +2829,17 @@ SOCKET L4NetworkManager::OpenConnection(
|
||||
|
||||
if(connection_type == NETNUB_TCP_OPEN)
|
||||
{
|
||||
#ifdef BT_STEAM
|
||||
//
|
||||
// A FakeIP target travels the Steam wire (SDR); the returned
|
||||
// pseudo-SOCKET flows through the same L4Host bookkeeping.
|
||||
//
|
||||
if (BTSteamNet_IsFakeAddress((unsigned long)internet_address))
|
||||
{
|
||||
return BTSteamNet_Connect(
|
||||
(unsigned long)internet_address, remote_port);
|
||||
}
|
||||
#endif
|
||||
DEBUG_STREAM << "Opening connection to " << inet_ntoa(*(in_addr*)&internet_address) << ":" << remote_port << "...\n" << std::flush;
|
||||
|
||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
@@ -0,0 +1,584 @@
|
||||
//###########################################################################
|
||||
// L4STEAMNET -- the Steam internet transport (BT_STEAM only; this TU is
|
||||
// only in the build when the gate is on -- see CMakeLists.txt).
|
||||
// Design: L4STEAMNET.h.
|
||||
//###########################################################################
|
||||
|
||||
#include "l4steamnet.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#pragma pack(push, 8)
|
||||
#include "steam/steam_api.h"
|
||||
#include "steam/isteamnetworkingsockets.h"
|
||||
#include "steam/isteamnetworkingutils.h"
|
||||
#include "steam/steamnetworkingfakeip.h"
|
||||
#pragma pack(pop)
|
||||
|
||||
#include <iostream>
|
||||
#define STEAMNET_LOG(x) \
|
||||
do { std::cout << "[steamnet] " << x << "\n" << std::flush; } while (0)
|
||||
|
||||
//###########################################################################
|
||||
// State
|
||||
//###########################################################################
|
||||
|
||||
enum
|
||||
{
|
||||
MaxConnections = 16,
|
||||
RingCapacity = 256 * 1024, // per-connection reassembly ring
|
||||
PseudoSocketBase = 0x5EA00000,
|
||||
AcceptQueueSize = 8,
|
||||
FakePortCount = 2 // [0] console, [1] game
|
||||
};
|
||||
|
||||
struct SteamConnection
|
||||
{
|
||||
int inUse;
|
||||
HSteamNetConnection connection;
|
||||
SOCKADDR_IN peerAddress; // the peer's FAKE ipv4:port
|
||||
int connected; // handshake complete
|
||||
int closedByPeer;
|
||||
unsigned char *ring;
|
||||
int ringHead, ringTail; // [tail, head) valid
|
||||
};
|
||||
|
||||
static int steamActive = 0;
|
||||
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;
|
||||
|
||||
static SteamConnection *
|
||||
ConnectionOf(SOCKET wire_socket)
|
||||
{
|
||||
unsigned long value = (unsigned long)wire_socket;
|
||||
if ((value & 0xFFFF0000) != PseudoSocketBase)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
int index = (int)(value & 0xFFFF);
|
||||
if (index < 0 || index >= MaxConnections || !connections[index].inUse)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return &connections[index];
|
||||
}
|
||||
|
||||
static SOCKET
|
||||
PseudoSocketOf(SteamConnection *connection)
|
||||
{
|
||||
return (SOCKET)(PseudoSocketBase | (int)(connection - connections));
|
||||
}
|
||||
|
||||
static SteamConnection *
|
||||
AllocateConnection(HSteamNetConnection handle)
|
||||
{
|
||||
for (int i = 0; i < MaxConnections; ++i)
|
||||
{
|
||||
if (!connections[i].inUse)
|
||||
{
|
||||
SteamConnection &c = connections[i];
|
||||
memset(&c.peerAddress, 0, sizeof(c.peerAddress));
|
||||
c.inUse = 1;
|
||||
c.connection = handle;
|
||||
c.connected = 0;
|
||||
c.closedByPeer = 0;
|
||||
if (c.ring == NULL)
|
||||
{
|
||||
c.ring = (unsigned char *)malloc(RingCapacity);
|
||||
}
|
||||
c.ringHead = c.ringTail = 0;
|
||||
return &c;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
static void
|
||||
FillPeerAddress(SteamConnection *connection)
|
||||
{
|
||||
SteamNetConnectionInfo_t info;
|
||||
if (SteamNetworkingSockets()->GetConnectionInfo(
|
||||
connection->connection, &info) &&
|
||||
info.m_addrRemote.IsFakeIP())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// The status-changed callback (invoked from RunCallbacks on our thread).
|
||||
//###########################################################################
|
||||
|
||||
static void __cdecl
|
||||
OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *status)
|
||||
{
|
||||
ISteamNetworkingSockets *sockets = SteamNetworkingSockets();
|
||||
|
||||
switch (status->m_info.m_eState)
|
||||
{
|
||||
case k_ESteamNetworkingConnectionState_Connecting:
|
||||
//
|
||||
// Incoming connection on one of our listen sockets: accept it and
|
||||
// queue the pseudo-socket for BTNetAccept.
|
||||
//
|
||||
if (status->m_eOldState == k_ESteamNetworkingConnectionState_None &&
|
||||
status->m_info.m_hListenSocket != k_HSteamListenSocket_Invalid)
|
||||
{
|
||||
int channel = -1;
|
||||
for (int i = 0; i < FakePortCount; ++i)
|
||||
{
|
||||
if (listenSockets[i] == status->m_info.m_hListenSocket)
|
||||
{
|
||||
channel = i;
|
||||
}
|
||||
}
|
||||
if (channel < 0)
|
||||
{
|
||||
sockets->CloseConnection(status->m_hConn, 0, "unknown listener", false);
|
||||
return;
|
||||
}
|
||||
int next = (acceptHead[channel] + 1) % AcceptQueueSize;
|
||||
if (next == acceptTail[channel])
|
||||
{
|
||||
sockets->CloseConnection(status->m_hConn, 0, "accept queue full", false);
|
||||
return;
|
||||
}
|
||||
if (sockets->AcceptConnection(status->m_hConn) != k_EResultOK)
|
||||
{
|
||||
sockets->CloseConnection(status->m_hConn, 0, "accept failed", false);
|
||||
return;
|
||||
}
|
||||
SteamConnection *connection = AllocateConnection(status->m_hConn);
|
||||
if (connection == NULL)
|
||||
{
|
||||
sockets->CloseConnection(status->m_hConn, 0, "table full", false);
|
||||
return;
|
||||
}
|
||||
acceptQueue[channel][acceptHead[channel]] = PseudoSocketOf(connection);
|
||||
acceptHead[channel] = next;
|
||||
STEAMNET_LOG("incoming connection queued on channel " << channel);
|
||||
}
|
||||
break;
|
||||
|
||||
case k_ESteamNetworkingConnectionState_Connected:
|
||||
for (int i = 0; i < MaxConnections; ++i)
|
||||
{
|
||||
if (connections[i].inUse &&
|
||||
connections[i].connection == status->m_hConn)
|
||||
{
|
||||
connections[i].connected = 1;
|
||||
FillPeerAddress(&connections[i]);
|
||||
STEAMNET_LOG("connection " << i << " up");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case k_ESteamNetworkingConnectionState_ClosedByPeer:
|
||||
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
|
||||
for (int i = 0; i < MaxConnections; ++i)
|
||||
{
|
||||
if (connections[i].inUse &&
|
||||
connections[i].connection == status->m_hConn)
|
||||
{
|
||||
connections[i].closedByPeer = 1;
|
||||
STEAMNET_LOG("connection " << i << " closed ("
|
||||
<< status->m_info.m_szEndDebug << ")");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// The pump: Steam callbacks + draining every connection's messages into
|
||||
// its ring. Called lazily from the seam entries (the game thread), rate-
|
||||
// limited to once per millisecond tick.
|
||||
//###########################################################################
|
||||
|
||||
void
|
||||
BTSteamNet_Pump()
|
||||
{
|
||||
if (!steamActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
unsigned long now = GetTickCount();
|
||||
if (now == lastPumpTick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lastPumpTick = now;
|
||||
|
||||
//
|
||||
// Both pumps: the general Steam dispatch (FakeIP results, auth) AND
|
||||
// the networking-sockets callbacks (connection status).
|
||||
//
|
||||
SteamAPI_RunCallbacks();
|
||||
ISteamNetworkingSockets *sockets = SteamNetworkingSockets();
|
||||
sockets->RunCallbacks();
|
||||
|
||||
for (int i = 0; i < MaxConnections; ++i)
|
||||
{
|
||||
SteamConnection &c = connections[i];
|
||||
if (!c.inUse || c.connection == k_HSteamNetConnection_Invalid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
SteamNetworkingMessage_t *messages[16];
|
||||
int received = sockets->ReceiveMessagesOnConnection(
|
||||
c.connection, messages, 16);
|
||||
if (received <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
for (int m = 0; m < received; ++m)
|
||||
{
|
||||
int length = (int)messages[m]->m_cbSize;
|
||||
const unsigned char *data =
|
||||
(const unsigned char *)messages[m]->m_pData;
|
||||
int space = RingCapacity - 1 -
|
||||
((c.ringHead - c.ringTail + RingCapacity) % RingCapacity);
|
||||
if (length > space)
|
||||
{
|
||||
STEAMNET_LOG("ring overflow on connection " << i
|
||||
<< " -- dropping " << length << " bytes");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int b = 0; b < length; ++b)
|
||||
{
|
||||
c.ring[c.ringHead] = data[b];
|
||||
c.ringHead = (c.ringHead + 1) % RingCapacity;
|
||||
}
|
||||
}
|
||||
messages[m]->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// Install
|
||||
//###########################################################################
|
||||
|
||||
int
|
||||
BTSteamNet_Install()
|
||||
{
|
||||
if (steamActive)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// The 480 (Spacewar) test id if no appid file exists -- dev tooling.
|
||||
//
|
||||
FILE *appid = fopen("steam_appid.txt", "rt");
|
||||
if (appid == NULL)
|
||||
{
|
||||
appid = fopen("steam_appid.txt", "wt");
|
||||
if (appid != NULL)
|
||||
{
|
||||
fputs("480\n", appid);
|
||||
}
|
||||
}
|
||||
if (appid != NULL)
|
||||
{
|
||||
fclose(appid);
|
||||
}
|
||||
|
||||
if (!SteamAPI_Init())
|
||||
{
|
||||
STEAMNET_LOG("SteamAPI_Init failed (Steam not running / no login) "
|
||||
"-- staying on Winsock");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ISteamNetworkingSockets *sockets = SteamNetworkingSockets();
|
||||
SteamNetworkingUtils()->InitRelayNetworkAccess();
|
||||
SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(
|
||||
OnConnectionStatusChanged);
|
||||
|
||||
//
|
||||
// FakeIP: one allocation carrying both channel ports.
|
||||
//
|
||||
memset(&myFakeIP, 0, sizeof(myFakeIP));
|
||||
sockets->BeginAsyncRequestFakeIP(FakePortCount);
|
||||
for (int wait = 0; wait < 200; ++wait) // <= 20 s
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (myFakeIP.m_unIP == 0)
|
||||
{
|
||||
STEAMNET_LOG("FakeIP allocation timed out -- staying on Winsock");
|
||||
SteamAPI_Shutdown();
|
||||
return -1;
|
||||
}
|
||||
|
||||
//
|
||||
// Listen on both fake ports.
|
||||
//
|
||||
for (int i = 0; i < FakePortCount; ++i)
|
||||
{
|
||||
listenSockets[i] = sockets->CreateListenSocketP2PFakeIP(i, 0, NULL);
|
||||
acceptHead[i] = acceptTail[i] = 0;
|
||||
if (listenSockets[i] == k_HSteamListenSocket_Invalid)
|
||||
{
|
||||
STEAMNET_LOG("CreateListenSocketP2PFakeIP(" << 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]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
BTSteamNet_Active()
|
||||
{
|
||||
return steamActive;
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// The wire-seam surface
|
||||
//###########################################################################
|
||||
|
||||
int
|
||||
BTSteamNet_Owns(SOCKET wire_socket)
|
||||
{
|
||||
return steamActive && ConnectionOf(wire_socket) != NULL;
|
||||
}
|
||||
|
||||
int
|
||||
BTSteamNet_Send(SOCKET wire_socket, const char *data, int length)
|
||||
{
|
||||
BTSteamNet_Pump();
|
||||
SteamConnection *connection = ConnectionOf(wire_socket);
|
||||
if (connection == NULL || connection->closedByPeer)
|
||||
{
|
||||
WSASetLastError(WSAECONNRESET);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
EResult result = SteamNetworkingSockets()->SendMessageToConnection(
|
||||
connection->connection, data, (uint32)length,
|
||||
k_nSteamNetworkingSend_ReliableNoNagle, NULL);
|
||||
if (result != k_EResultOK)
|
||||
{
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
int
|
||||
BTSteamNet_Recv(SOCKET wire_socket, char *buffer, int capacity)
|
||||
{
|
||||
BTSteamNet_Pump();
|
||||
SteamConnection *connection = ConnectionOf(wire_socket);
|
||||
if (connection == NULL)
|
||||
{
|
||||
WSASetLastError(WSAENOTSOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
int available =
|
||||
(connection->ringHead - connection->ringTail + RingCapacity)
|
||||
% RingCapacity;
|
||||
if (available == 0)
|
||||
{
|
||||
if (connection->closedByPeer)
|
||||
{
|
||||
return 0; // the engine's disconnect path
|
||||
}
|
||||
WSASetLastError(WSAEWOULDBLOCK); // nonblocking no-data semantics
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
int count = (available < capacity) ? available : capacity;
|
||||
for (int b = 0; b < count; ++b)
|
||||
{
|
||||
buffer[b] = (char)connection->ring[connection->ringTail];
|
||||
connection->ringTail = (connection->ringTail + 1) % RingCapacity;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
SOCKET
|
||||
BTSteamNet_Accept(SOCKET listener_socket)
|
||||
{
|
||||
(void)listener_socket;
|
||||
if (!steamActive)
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
BTSteamNet_Pump();
|
||||
//
|
||||
// The engine's console/game listeners map onto our two fake-port
|
||||
// channels; either TCP listener polling through BTNetAccept also
|
||||
// offers whatever Steam has queued. Console arrivals are only
|
||||
// meaningful pre-mission, game arrivals during the mesh -- the
|
||||
// engine's own swap logic sorts them by peer address.
|
||||
//
|
||||
for (int channel = 0; channel < FakePortCount; ++channel)
|
||||
{
|
||||
if (acceptTail[channel] != acceptHead[channel])
|
||||
{
|
||||
SOCKET wire_socket = acceptQueue[channel][acceptTail[channel]];
|
||||
acceptTail[channel] = (acceptTail[channel] + 1) % AcceptQueueSize;
|
||||
return wire_socket;
|
||||
}
|
||||
}
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
void
|
||||
BTSteamNet_Close(SOCKET wire_socket)
|
||||
{
|
||||
SteamConnection *connection = ConnectionOf(wire_socket);
|
||||
if (connection == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SteamNetworkingSockets()->CloseConnection(
|
||||
connection->connection, 0, "closed", true);
|
||||
connection->inUse = 0;
|
||||
connection->connection = k_HSteamNetConnection_Invalid;
|
||||
}
|
||||
|
||||
int
|
||||
BTSteamNet_PeerAddress(SOCKET wire_socket, SOCKADDR_IN *out)
|
||||
{
|
||||
SteamConnection *connection = ConnectionOf(wire_socket);
|
||||
if (connection == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (connection->peerAddress.sin_family == 0)
|
||||
{
|
||||
FillPeerAddress(connection);
|
||||
}
|
||||
*out = connection->peerAddress;
|
||||
return connection->peerAddress.sin_family != 0;
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// Connection establishment + the lobby surface
|
||||
//###########################################################################
|
||||
|
||||
int
|
||||
BTSteamNet_IsFakeAddress(unsigned long internet_address_be)
|
||||
{
|
||||
if (!steamActive)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
SteamNetworkingIPAddr address;
|
||||
address.SetIPv4(ntohl(internet_address_be), 0);
|
||||
return address.IsFakeIP();
|
||||
}
|
||||
|
||||
SOCKET
|
||||
BTSteamNet_Connect(unsigned long internet_address_be, int remote_port)
|
||||
{
|
||||
if (!steamActive)
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
SteamNetworkingIPAddr address;
|
||||
address.SetIPv4(ntohl(internet_address_be), (uint16)remote_port);
|
||||
|
||||
HSteamNetConnection handle =
|
||||
SteamNetworkingSockets()->ConnectByIPAddress(address, 0, NULL);
|
||||
if (handle == k_HSteamNetConnection_Invalid)
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
SteamConnection *connection = AllocateConnection(handle);
|
||||
if (connection == NULL)
|
||||
{
|
||||
SteamNetworkingSockets()->CloseConnection(handle, 0, "table full", false);
|
||||
return INVALID_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);
|
||||
|
||||
//
|
||||
// The engine's OpenConnection is synchronous (blocking connect); wait
|
||||
// out the SDR handshake here, bounded.
|
||||
//
|
||||
for (int wait = 0; wait < 300; ++wait) // <= 30 s
|
||||
{
|
||||
BTSteamNet_Pump();
|
||||
if (connection->connected)
|
||||
{
|
||||
char dotted[48];
|
||||
address.ToString(dotted, sizeof(dotted), true);
|
||||
STEAMNET_LOG("connected to " << dotted);
|
||||
return PseudoSocketOf(connection);
|
||||
}
|
||||
if (connection->closedByPeer)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Sleep(100);
|
||||
}
|
||||
STEAMNET_LOG("connect failed/timed out");
|
||||
BTSteamNet_Close(PseudoSocketOf(connection));
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
int
|
||||
BTSteamNet_GetFakeAddress(char *dotted_out, int capacity,
|
||||
int *console_port, int *game_port)
|
||||
{
|
||||
if (!steamActive || myFakeIP.m_unIP == 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];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
//###########################################################################
|
||||
//
|
||||
// L4STEAMNET -- the Steam internet transport (BT_STEAM only).
|
||||
//
|
||||
// ISteamNetworkingSockets with FakeIP + SDR behind the L4NET.CPP wire
|
||||
// seam: connections carry the engine's exact TCP byte stream as reliable
|
||||
// Steam messages, re-assembled into per-connection rings so the engine's
|
||||
// nonblocking recv semantics hold (empty -> WSAEWOULDBLOCK, peer-closed
|
||||
// -> 0). Handles are pseudo-SOCKETs (0x5EA0xxxx) so L4Host and every
|
||||
// call site above the seam are identical on both wires; the peer address
|
||||
// reported through CheckSocket is the peer's FAKE IPv4:port, so the
|
||||
// engine's roster matching works against a lobby-built egg whose
|
||||
// [pilots] carry FakeIPs.
|
||||
//
|
||||
// Install: env BT_STEAM_NET=1 (set by the lobby launch path) -- needs the
|
||||
// Steam client running and content\steam_appid.txt (written with the 480
|
||||
// Spacewar test id if absent). Everything degrades gracefully: no Steam
|
||||
// -> inactive -> pure Winsock.
|
||||
//
|
||||
// Ports: FakeIP port index 0 = the CONSOLE channel, 1 = the GAME channel
|
||||
// (mirroring the arcade console-port / game-port=console+1 convention).
|
||||
//
|
||||
//###########################################################################
|
||||
|
||||
#include <winsock2.h>
|
||||
|
||||
int
|
||||
BTSteamNet_Install(); // 0 = up (FakeIP allocated)
|
||||
int
|
||||
BTSteamNet_Active();
|
||||
void
|
||||
BTSteamNet_Pump(); // callbacks + rx drain (game thread)
|
||||
|
||||
//
|
||||
// The wire-seam surface (BTNet* wrappers in L4NET.CPP + the marshal).
|
||||
//
|
||||
int
|
||||
BTSteamNet_Owns(SOCKET wire_socket);
|
||||
int
|
||||
BTSteamNet_Send(SOCKET wire_socket, const char *data, int length);
|
||||
int
|
||||
BTSteamNet_Recv(SOCKET wire_socket, char *buffer, int capacity);
|
||||
SOCKET
|
||||
BTSteamNet_Accept(SOCKET listener_socket);
|
||||
void
|
||||
BTSteamNet_Close(SOCKET wire_socket);
|
||||
int
|
||||
BTSteamNet_PeerAddress(SOCKET wire_socket, SOCKADDR_IN *out);
|
||||
|
||||
//
|
||||
// Connection establishment (the OpenConnection TCP_OPEN delegate + the
|
||||
// marshal): True when the IPv4 is a Steam FakeIP.
|
||||
//
|
||||
int
|
||||
BTSteamNet_IsFakeAddress(unsigned long internet_address_be);
|
||||
SOCKET
|
||||
BTSteamNet_Connect(unsigned long internet_address_be, int remote_port);
|
||||
|
||||
//
|
||||
// The lobby surface: our own fake address, dotted, plus its two ports.
|
||||
//
|
||||
int
|
||||
BTSteamNet_GetFakeAddress(char *dotted_out, int capacity,
|
||||
int *console_port, int *game_port);
|
||||
@@ -378,6 +378,18 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
}
|
||||
BTFE_RelaunchSelfAndExit(fe_arguments); // never returns
|
||||
}
|
||||
#ifdef BT_STEAM
|
||||
//
|
||||
// The Steam transport (step 4b): BT_STEAM_NET=1 (set by the lobby
|
||||
// launch path, or by hand for testing) brings up FakeIP + SDR
|
||||
// before the engine opens any connection. Degrades to Winsock.
|
||||
//
|
||||
if (getenv("BT_STEAM_NET") != NULL && *getenv("BT_STEAM_NET") != '0')
|
||||
{
|
||||
extern int BTSteamNet_Install();
|
||||
BTSteamNet_Install();
|
||||
}
|
||||
#endif
|
||||
//
|
||||
// A marshal-armed mission process: start the LocalConsole worker
|
||||
// (it retries until our own console listener is up).
|
||||
|
||||
Reference in New Issue
Block a user