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:
Cyd
2026-07-14 08:33:34 -05:00
co-authored by Claude Fable 5
parent 5285117d3b
commit 5ebb9a5906
69 changed files with 34965 additions and 251 deletions
+93 -250
View File
@@ -19,6 +19,9 @@
#include "l4app.h"
#include "l4host.h"
#include "l4net.h"
#include "l4nettransport.h"
#include "..\munga\appmgr.h"
#include "..\munga\appmsg.h"
#include "..\munga\mission.h"
#include "..\munga\notation.h"
//#include <netnub.hpp>
@@ -226,11 +229,10 @@ L4NetworkManager::L4NetworkManager():
//Function_Ptr = &Net_Common_Ptr->Function;
//Buffer_Length_Ptr = &Net_Common_Ptr->Buffer_Length;
int iResult = WSAStartup(MAKEWORD(2,2), wsaData);
if(iResult != NO_ERROR)
if (!NetTransport_Get()->Startup())
{
DEBUG_STREAM << "ERROR: WSAStartup() failed with " << iResult << "!\n" << std::flush;
WSACleanup();
DEBUG_STREAM << "ERROR: network transport startup failed!\n" << std::flush;
NetTransport_Get()->Cleanup();
PostQuitMessage(AbortExitCodeID);
}
consoleListenerSocket = NULL;
@@ -271,7 +273,7 @@ L4NetworkManager::L4NetworkManager():
if(addresses == NULL)
{
DEBUG_STREAM<<"ERROR: GetMyAddress() failed!\n";
WSACleanup();
NetTransport_Get()->Cleanup();
Fail("Unable to initialize the network");
}
@@ -319,39 +321,11 @@ void L4NetworkManager::CreateConsoleHost()
// CONSOLE_NET_PORT, // Local port
// 0, // Remote port (don't care in this case)
// 0); // Network address (don't care in this case)
consoleListenerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
consoleListenerSocket = (SOCKET) NetTransport_Get()->Listen(networkPort, 1);
if(consoleListenerSocket == INVALID_SOCKET)
{
DEBUG_STREAM << "ERROR: Could not create console listener socket; socket() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return;
}
sockaddr_in localEndpoint;
memset(&localEndpoint, 0, sizeof(localEndpoint));
localEndpoint.sin_family = AF_INET;
localEndpoint.sin_port = htons(networkPort);
localEndpoint.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(consoleListenerSocket, (sockaddr*)&localEndpoint, sizeof(localEndpoint)))
{
DEBUG_STREAM << "ERROR: Could not bind console listener socket; bind() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return;
}
if(listen(consoleListenerSocket, 1))
{
DEBUG_STREAM << "ERROR: Could not listen on console listener socket; listen() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return;
}
//set to non blocking
unsigned long enable = 1;
if(ioctlsocket(consoleListenerSocket, FIONBIO, &enable))
{
DEBUG_STREAM << "ERROR: Could not set console listener socket to nonblocking; ioctlsocket() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
DEBUG_STREAM << "ERROR: Could not open the console listener!\n" << std::flush;
NetTransport_Get()->Cleanup();
PostQuitMessage(AbortExitCodeID);
return;
}
@@ -384,6 +358,37 @@ void L4NetworkManager::CreateConsoleHost()
networkEggNotationFile = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::FeedLocalEgg Load a local egg file and post the same
// 'local egg' message the single-user startup posts, kicking the next
// mission cycle. Used by the in-game front end / local console.
//
void
L4NetworkManager::FeedLocalEgg(const char *egg_path)
{
Check(this);
Check_Pointer(egg_path);
if (networkEggNotationFile != NULL)
{
Unregister_Object(networkEggNotationFile);
delete networkEggNotationFile;
networkEggNotationFile = NULL;
}
networkEggNotationFile = new NotationFile(egg_path);
Register_Object(networkEggNotationFile);
networkEggNotationFile->WriteFile("last.egg");
// In network mode (the owner pod of a multiplayer race) the state
// gate must open or CheckBuffers keeps dropping mesh packets - a
// console-fed pod gets this from the chunked egg path instead.
currentNetworkState = NormalState;
ReceiveEggFileMessage egg_message(-1, 10, "local egg", 10);
application->Post(DefaultEventPriority, this, &egg_message);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::LoadMission This routine is called when a mission starts to
// allow the network management system to do stuff (potentially establishing
@@ -464,7 +469,6 @@ void
//
listen = False;
remoteHostCount = 0;
int bufferSize = sizeof(SOCKADDR_IN);
while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL)
{
Check(mission_host_data);
@@ -477,7 +481,7 @@ void
//VERIFY: are these IP addresses?
//net_address = ResolveAddress(host_name);
//net_address = inet_addr((char *)host_name);
WSAStringToAddressA((LPSTR)host_name, AF_INET, NULL, (LPSOCKADDR)&net_address, &bufferSize);
NetTransport_Get()->Resolve((LPSTR)host_name, &net_address);
if (net_address.sin_port == 0)
net_address.sin_port = htons(localGamePort);
@@ -734,7 +738,7 @@ L4NetworkManager::~L4NetworkManager()
delete my_l4host;
}
WSACleanup();
NetTransport_Get()->Cleanup();
delete wsaData;
wsaData = NULL;
//Unregister_Pointer(Net_Common_Ptr);
@@ -966,8 +970,7 @@ void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* H
//
// Post a listen for a console so it can reconnect if it wants to.
//
shutdown(gameListenerSocket, SD_BOTH);
closesocket(gameListenerSocket);
NetTransport_Get()->Close(gameListenerSocket);
gameListenerSocket = NULL;
//unsigned long console_socket = OpenConnection(
// NETNUB_TCP_LISTEN,
@@ -999,6 +1002,20 @@ void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* H
#else
myConsoleHost = 0;
#endif
//
// Lobby-member races: the departed console was the race
// owner - without a console the mission clock counts up
// forever, so end the mission and get back to the lobby
// room. (Arcade pods keep the listen above and wait for
// their console to return.)
//
if (gConsoleLossEndsMission &&
application->GetApplicationState() == Application::RunningMission)
{
DEBUG_STREAM << "Console lost mid-race - ending the mission\n" << std::flush;
Application::StopMissionMessage stop_message(0);
application->Post(DefaultEventPriority, application, &stop_message);
}
break;
}
default:
@@ -1140,7 +1157,7 @@ void L4NetworkManager::Send(
//Net_Common_Ptr->Buffer_Length = (short)SEND_BUFFER_SIZE(packet_size);
//send_packet_request->Socket_Ptr = l4host->GetNetworkSocket();
//NetNub::SendCommand();
send(l4host->GetNetworkSocket(), (char *)my_temp_packet, packet_size, 0);
NetTransport_Get()->Send(l4host->GetNetworkSocket(), my_temp_packet, packet_size);
free(my_temp_packet);
// Check for errors and abort if there were any
//#if defined(TRACE_SEND_BUFFER)
@@ -1306,9 +1323,9 @@ Logical L4NetworkManager::SendMessageToNetnub(
// SendPacketReturnPtr send_packet_return =
// (SendPacketReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer;
//#endif
//Net_Common_Ptr->Buffer_Length =
//Net_Common_Ptr->Buffer_Length =
// (unsigned short)SEND_BUFFER_SIZE(munga_network_message_size);
//Net_Common_Ptr->Function =
//Net_Common_Ptr->Function =
// NETNUB_SEND_PACKET;
//NetNub::SendCommand();
// BT bring-up trace (env BT_NET_TRACE): every point-to-point game send.
@@ -1319,7 +1336,7 @@ Logical L4NetworkManager::SendMessageToNetnub(
<< " len=" << (int)message->messageLength
<< " -> host " << (int)host_ID << "\n" << std::flush;
}
send(receiving_host->GetNetworkSocket(), (char *)network_packet, munga_network_message_size, 0);
NetTransport_Get()->Send(receiving_host->GetNetworkSocket(), network_packet, munga_network_message_size);
free(network_packet);
#if defined(TRACE_SEND_BUFFER)
@@ -1865,7 +1882,10 @@ void
//NetNub::SendCommand();
for(int i=0; i<host_count; i++)
{
send(l4host_array[i]->GetNetworkSocket(), (char *)network_packet, sizeof(network_packet), 0);
// was send(..., sizeof(network_packet), 0): sizeof a POINTER -
// four bytes of packet, which would shear the stream framing
// if this path ever fired
NetTransport_Get()->Send(l4host_array[i]->GetNetworkSocket(), network_packet, munga_network_message_size);
}
free(network_packet);
@@ -2106,15 +2126,15 @@ Logical L4NetworkManager::CheckBuffers(NetworkPacket *network_packet)
if(remote_host->GetHostType() == ConsoleHostType)
{
if((tempSocket = accept(consoleListenerSocket, NULL, 0)) != INVALID_SOCKET)
if((tempSocket = (SOCKET) NetTransport_Get()->Accept(consoleListenerSocket)) != INVALID_SOCKET)
{
closesocket(consoleListenerSocket);
NetTransport_Get()->Close(consoleListenerSocket);
consoleListenerSocket = INVALID_SOCKET;
}
}
else
{
if((tempSocket = accept(gameListenerSocket, NULL, 0)) == INVALID_SOCKET)
if((tempSocket = (SOCKET) NetTransport_Get()->Accept(gameListenerSocket)) == INVALID_SOCKET)
{
continue;
}
@@ -2244,45 +2264,21 @@ Logical L4NetworkManager::CheckBuffers(NetworkPacket *network_packet)
// call the network server
//NetShare();
void* buffer = malloc(space_left);
int received = recv(remote_host->GetNetworkSocket(), (char*)buffer, space_left, 0);
// transport normalizes the would-block/reset cases:
// >0 = data, ReceiveDisconnected = drop, ReceiveNoData = nothing yet
int received = NetTransport_Get()->Receive(remote_host->GetNetworkSocket(), buffer, space_left);
// if we received data, copy it over into the buffer for this host
//switch(Net_Common_Ptr->Status)
if(received > 0)
{
//case NETNUB_RECEIVED_PACKET:
// copy the received data over to the host's data buffer
Mem_Copy(
&remote_host->pad_buffer[remote_host->pad_tail],
//Net_Common_Ptr->Shared_Memory_Buffer,
buffer,
//Net_Common_Ptr->Buffer_Length,
received,
space_left);
//remote_host->pad_tail += Net_Common_Ptr->Buffer_Length;
remote_host->pad_tail += received;
//break;
}
//case NETNUB_OK:
else if(received == SOCKET_ERROR)
{
//this indicates that no data is available and the socket is non blocking
//break;
DWORD error = WSAGetLastError();
switch (error)
{
case WSAECONNRESET:
// this will cause us to execute our disconnect code
received = 0;
break;
case WSAEWOULDBLOCK:
// this is expected when there is no data
break;
default:
DEBUG_STREAM << "L4NetworkManager::CheckBuffers: Socket recv returned an unexpected error: WSAGetLastError = " << error << std::endl << std::flush;
}
}
// case NETNUB_STREAM_DISCONNECTED:
if(received == 0)
if(received == NetTransport::ReceiveDisconnected)
{
{
HostDisconnectedMessage myHostDisconnected(
@@ -2582,31 +2578,21 @@ void
//WinSock support :ADB 01/06/07
NetworkAddress* L4NetworkManager::GetMyAddress()
{
char name[255];
PHOSTENT hostinfo;
// the transport owns interface enumeration (it appends loopback)
enum { maxLocalAddresses = 16 };
unsigned long local_addresses[maxLocalAddresses];
if (gethostname(name, sizeof(name)) != 0)
int count = NetTransport_Get()->GetLocalAddresses(local_addresses, maxLocalAddresses);
if (count <= 0)
{
DEBUG_STREAM << "ERROR: gethostname() failed!" << std::endl << std::flush;
return NULL;
}
if ((hostinfo = gethostbyname(name)) == NULL)
{
DEBUG_STREAM << "ERROR: gethostbyname() failed!" << std::endl << std::flush;
return NULL;
}
NetworkAddress* myAddresses = new NetworkAddress[count];
for (int i=0; i<count; i++)
myAddresses[i] = (NetworkAddress) local_addresses[i];
num_addresses = count;
// 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];
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;
return myAddresses;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2617,54 +2603,11 @@ NetworkAddress* L4NetworkManager::GetMyAddress()
//WinSock support :ADB 01/06/07
bool L4NetworkManager::ResolveAddress(CString host_name, SOCKADDR_IN *address)
{
//if (strstr(host_name, ":"))
{
// this address contains a colon so we'll do things a little differently
int bufferSize = sizeof(SOCKADDR_IN);
WSAStringToAddressA((LPSTR)host_name, AF_INET, NULL, (LPSOCKADDR)address, &bufferSize);
if (address->sin_port == 0)
address->sin_port = htons(GAME_NET_PORT);
return true;
}
addrinfo* hostAddrinfo = NULL;
addrinfo aiHints;
memset(&aiHints, 0, sizeof(aiHints));
aiHints.ai_family = AF_INET;
aiHints.ai_socktype = SOCK_STREAM;
aiHints.ai_protocol = IPPROTO_TCP;
int iResult = getaddrinfo((char*)host_name, NULL, &aiHints , &hostAddrinfo);
if(iResult != 0)
{
DEBUG_STREAM<<"ERROR: getaddrinfo() failed with " << WSAGetLastError() << "!\n";
return NULL;
}
addrinfo* addr = hostAddrinfo;
while(addr != NULL)
{
if(addr->ai_addr->sa_family == AF_INET)
break;
addr = addr->ai_next;
}
memset(address, 0, sizeof(SOCKADDR_IN));
*address = *((sockaddr_in*)&addr->ai_addr->sa_data);
if (address->sin_port == 32778)
address->sin_port = htons(CONSOLE_NET_PORT);
freeaddrinfo(hostAddrinfo);
//
// Check the status returns and return the address if we got one ok
//
if(!address || address->sin_addr.S_un.S_addr == 0 || address->sin_port == 0)
{
DEBUG_STREAM << "Couldn't resolve " << host_name << " to a net address\n";
Fail("unresolvable network address\n");
}
// numeric ip[:port]; the game port fills in when none was given
// (the old DNS/getaddrinfo path below the early return was dead)
NetTransport_Get()->Resolve((LPSTR)host_name, address);
if (address->sin_port == 0)
address->sin_port = htons(GAME_NET_PORT);
return true;
}
@@ -2677,9 +2620,7 @@ bool L4NetworkManager::CheckSocket(SOCKET socket, SOCKADDR_IN *remoteEndpoint)
{
if (remoteEndpoint)
{
int size = sizeof(SOCKADDR_IN);
memset(remoteEndpoint, 0, size);
if(!getpeername(socket, (sockaddr*)remoteEndpoint, &size))
if (NetTransport_Get()->GetRemoteAddress(socket, remoteEndpoint))
return true;
}
return false;
@@ -2735,126 +2676,29 @@ SOCKET L4NetworkManager::OpenConnection(
if(connection_type == NETNUB_TCP_OPEN)
{
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);
if(sock == INVALID_SOCKET)
{
DEBUG_STREAM << "ERROR: socket() failed with " << WSAGetLastError() << "!\n";
return INVALID_SOCKET;
}
bool reuseAddr = true;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseAddr, sizeof(bool)))
{
DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on socket; setsockopt() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return INVALID_SOCKET;
}
sockaddr_in localEndpoint;
memset(&localEndpoint, 0, sizeof(localEndpoint));
localEndpoint.sin_family = AF_INET;
localEndpoint.sin_port = htons(local_port);
localEndpoint.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(sock, (sockaddr*)&localEndpoint, sizeof(localEndpoint)))
{
DEBUG_STREAM << "ERROR: Could not bind local socket; bind() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return INVALID_SOCKET;
}
sockaddr_in remoteEndpoint;
memset(&remoteEndpoint, 0, sizeof(remoteEndpoint));
remoteEndpoint.sin_family = AF_INET;
//VERIFY: Network vs Host byte order?
remoteEndpoint.sin_addr.S_un.S_addr = internet_address;
remoteEndpoint.sin_port = htons(remote_port);
int wsaError, iResult;
do
{
iResult = connect(sock, (sockaddr*)&remoteEndpoint, sizeof(remoteEndpoint));
if (iResult != 0)
{
wsaError = WSAGetLastError();
}
} while (iResult != 0 && wsaError == 10061);
if(iResult != 0)
{
DEBUG_STREAM << "ERROR: connect() failed with " << wsaError << "!\n" << std::flush;
return INVALID_SOCKET;
}
//set to non blocking
unsigned long enable = 1;
if(ioctlsocket(sock, FIONBIO, &enable))
{
DEBUG_STREAM << "ERROR: Could not set actively opened socket to nonblocking; ioctlsocket() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
}
return sock;
return (SOCKET) NetTransport_Get()->Connect(&remoteEndpoint, local_port);
}
else if(connection_type == NETNUB_TCP_LISTEN)
{
if(gameListenerSocket == NULL)
{
DEBUG_STREAM << "Starting to listen on port " << local_port << "...\n" << std::flush;
gameListenerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
gameListenerSocket = (SOCKET) NetTransport_Get()->Listen(local_port, 25);
if(gameListenerSocket == INVALID_SOCKET)
{
DEBUG_STREAM << "ERROR: Could not create game listener socket; socket() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
DEBUG_STREAM << "ERROR: Could not open the game listener on port " << local_port << "!\n" << std::flush;
PostQuitMessage(AbortExitCodeID);
}
bool reuseAddr = true;
if (setsockopt(gameListenerSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseAddr, sizeof(bool)))
{
DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on game listener socket; setsockopt() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return INVALID_SOCKET;
}
sockaddr_in localEndpoint;
memset(&localEndpoint, 0, sizeof(localEndpoint));
localEndpoint.sin_family = AF_INET;
localEndpoint.sin_port = htons(local_port);
localEndpoint.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(gameListenerSocket, (sockaddr*)&localEndpoint, sizeof(localEndpoint)))
{
DEBUG_STREAM << "ERROR: Could not bind game listener socket; bind() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return INVALID_SOCKET;
}
if(listen(gameListenerSocket, 25))
{
DEBUG_STREAM << "ERROR: Could not listen on game listener socket; listen() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return INVALID_SOCKET;
}
//set to non blocking
unsigned long enable = 1;
if(ioctlsocket(gameListenerSocket, FIONBIO, &enable))
{
DEBUG_STREAM << "ERROR: Could not set game listener socket to nonblocking; ioctlsocket() failed with " << WSAGetLastError() << "!\n" << std::flush;
WSACleanup();
PostQuitMessage(AbortExitCodeID);
return INVALID_SOCKET;
}
}
else
DEBUG_STREAM << "Listen requested on port " << local_port << " but gameListenerSocket already existed!\n" << std::flush;
return INVALID_SOCKET;
}
else
@@ -2882,8 +2726,7 @@ void L4NetworkManager::CloseConnection(SOCKET socket_ptr) // socket address fro
//close_request = (TCPCloseRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
//close_request->Socket_Ptr = socket_ptr;
//NetNub::SendCommand();
shutdown(socket_ptr, SD_BOTH);
closesocket(socket_ptr);
NetTransport_Get()->Close(socket_ptr);
// Check for errors
//if(Net_Common_Ptr->Status != NETNUB_OK)
//{