Files
BT412/engine/MUNGA_L4/L4NETTRANSPORT.cpp
T
CydandClaude Fable 5 503536f1b3 Merge BT411 audio-fidelity + combat work (4e72f0c..abed41e) into BT412
Brings the post-fork BT411 line forward via a local-path merge (never
touches the BT411 gitea remote): the full audio-fidelity system (engine
AUD*/L4AUD* + audiopresets.cpp + ~600 content wavs + AUDIO_FIDELITY.md),
missiles/rear-fire/HUD/gyro/gait tasks (#66-68), FOGDAY.EGG, and
refreshed context docs -- 91 commits, ~688 files clean.

Only 5 files overlapped the steamification; resolved keeping BOTH:
- L4NET.CPP: took BT411's task-#50 fix (don't close the game listener on
  console loss) over the seam's adaptation of the old buggy close; sends
  stay on NetTransport_Get().
- L4NETTRANSPORT.cpp: folded BT411's TCP_NODELAY latency fix into
  WinsockNetTransport::Connect (the seam already had retry + nonblocking).
- mechmppr.cpp: combined the device_owns_input gating with BT411's
  task-#68 look-behind, gating the lookBehind write too.
- .gitignore / CMakeLists.txt / mech4.cpp: trivial / auto-merged
  (deviceOwnsInput gating preserved).

Verified: clean build (default + implicitly the Steam TU untouched);
solo front-end mode; loopback MP through the seam (mesh completes, both
tick, replication works, no NODELAY warnings).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 19:34:54 -05:00

344 lines
9.1 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4nettransport.h"
#include <Ws2tcpip.h>
//########################################################################
// WinsockNetTransport - the TCP wire the arcade always used, moved
// verbatim out of L4NET.CPP behind the NetTransport seam. Behavior
// notes preserved from the original:
// - Connect retries while the remote refuses (WSAECONNREFUSED): the
// console feeds eggs in [pilots] order with ACKs, so earlier pods
// are listening before later pods open - the retry covers the race.
// - Sockets go nonblocking once connected; listeners are nonblocking
// from creation.
//########################################################################
namespace
{
class WinsockNetTransport:
public NetTransport
{
public:
WinsockNetTransport():
started(False)
{
}
Logical
Startup()
{
if (started)
{
return True;
}
int result = WSAStartup(MAKEWORD(2, 2), &winsockData);
if (result != NO_ERROR)
{
DEBUG_STREAM << "ERROR: WSAStartup() failed with " << result
<< "!\n" << std::flush;
return False;
}
started = True;
return True;
}
void
Cleanup()
{
if (started)
{
WSACleanup();
started = False;
}
}
int
GetLocalAddresses(
unsigned long *addresses,
int max_count
)
{
char name[255];
PHOSTENT hostinfo;
if (gethostname(name, sizeof(name)) != 0)
{
DEBUG_STREAM << "ERROR: gethostname() failed!" << std::endl << std::flush;
return 0;
}
if ((hostinfo = gethostbyname(name)) == NULL)
{
DEBUG_STREAM << "ERROR: gethostbyname() failed!" << std::endl << std::flush;
return 0;
}
int count = 0;
for (int i = 0; hostinfo->h_addr_list[i] != NULL && count < max_count; ++i)
{
addresses[count++] = *((unsigned long *) hostinfo->h_addr_list[i]);
}
// loopback rounds out the list (single-machine testing)
if (count < max_count)
{
addresses[count++] = 0x0100007F;
}
return count;
}
Logical
Resolve(
const char *host_name,
SOCKADDR_IN *address
)
{
// numeric ip[:port] only - the egg carries addresses, not
// names; port stays 0 when absent (caller applies default)
int buffer_size = sizeof(SOCKADDR_IN);
return WSAStringToAddressA(
(LPSTR) host_name, AF_INET, NULL,
(LPSOCKADDR) address, &buffer_size) == 0;
}
Connection
Connect(
const SOCKADDR_IN *remote,
int local_port
)
{
DEBUG_STREAM << "Opening connection to "
<< inet_ntoa(remote->sin_addr) << ":" << ntohs(remote->sin_port)
<< "...\n" << std::flush;
//
// Retry-while-refused, bounded: the egg-ACK ordering means
// the peer may not be listening yet. A refused TCP socket
// is dead - every attempt needs a fresh one.
//
DWORD deadline = GetTickCount() + 120 * 1000;
for (;;)
{
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET)
{
DEBUG_STREAM << "ERROR: socket() failed with "
<< WSAGetLastError() << "!\n";
return InvalidConnection;
}
bool reuse_address = true;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(char *) &reuse_address, sizeof(bool)))
{
DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on socket; setsockopt() failed with "
<< WSAGetLastError() << "!\n" << std::flush;
closesocket(sock);
return InvalidConnection;
}
// bind the game port: the mesh identifies peers by
// address AND port, so the source port must be ours
// (port 0 = ephemeral, for console channels)
sockaddr_in local_endpoint;
memset(&local_endpoint, 0, sizeof(local_endpoint));
local_endpoint.sin_family = AF_INET;
local_endpoint.sin_port = htons((unsigned short) local_port);
local_endpoint.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(sock, (sockaddr *) &local_endpoint, sizeof(local_endpoint)))
{
DEBUG_STREAM << "ERROR: Could not bind local socket; bind() failed with "
<< WSAGetLastError() << "!\n" << std::flush;
closesocket(sock);
return InvalidConnection;
}
if (connect(sock, (sockaddr *) remote, sizeof(SOCKADDR_IN)) == 0)
{
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;
closesocket(sock);
return InvalidConnection;
}
// TCP_NODELAY (BT411 task #50, folded into the seam): disable
// Nagle so the small per-frame update records ship immediately
// instead of being coalesced. Nagle + delayed-ACK batch the
// tiny position packets into ~40-200ms bursts, so a peer moving
// at a steady speed is RECEIVED in lurches (dead-reckoned ground
// speed swinging 3x per 0.25s window). The pod net carries
// steady real-time state where latency, not throughput, matters.
BOOL no_delay = TRUE;
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
(char *) &no_delay, sizeof(no_delay)))
DEBUG_STREAM << "WARN: could not set TCP_NODELAY on active socket; setsockopt() failed with "
<< WSAGetLastError() << "\n" << std::flush;
return (Connection) sock;
}
int wsa_error = WSAGetLastError();
closesocket(sock);
if (wsa_error != WSAECONNREFUSED ||
(LONG)(GetTickCount() - deadline) >= 0)
{
DEBUG_STREAM << "ERROR: connect() failed with "
<< wsa_error << "!\n" << std::flush;
return InvalidConnection;
}
Sleep(250); // peer not up yet - redial
}
}
Connection
Listen(
int local_port,
int backlog
)
{
DEBUG_STREAM << "Starting to listen on port " << local_port
<< "...\n" << std::flush;
SOCKET listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listener == INVALID_SOCKET)
{
DEBUG_STREAM << "ERROR: Could not create listener socket; socket() failed with "
<< WSAGetLastError() << "!\n" << std::flush;
return InvalidConnection;
}
bool reuse_address = true;
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,
(char *) &reuse_address, sizeof(bool)))
{
DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on listener socket; setsockopt() failed with "
<< WSAGetLastError() << "!\n" << std::flush;
closesocket(listener);
return InvalidConnection;
}
sockaddr_in local_endpoint;
memset(&local_endpoint, 0, sizeof(local_endpoint));
local_endpoint.sin_family = AF_INET;
local_endpoint.sin_port = htons((unsigned short) local_port);
local_endpoint.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(listener, (sockaddr *) &local_endpoint, sizeof(local_endpoint)))
{
DEBUG_STREAM << "ERROR: Could not bind listener socket; bind() failed with "
<< WSAGetLastError() << "!\n" << std::flush;
closesocket(listener);
return InvalidConnection;
}
if (listen(listener, backlog))
{
DEBUG_STREAM << "ERROR: Could not listen on listener socket; listen() failed with "
<< WSAGetLastError() << "!\n" << std::flush;
closesocket(listener);
return InvalidConnection;
}
unsigned long enable = 1;
if (ioctlsocket(listener, FIONBIO, &enable))
{
DEBUG_STREAM << "ERROR: Could not set listener socket to nonblocking; ioctlsocket() failed with "
<< WSAGetLastError() << "!\n" << std::flush;
closesocket(listener);
return InvalidConnection;
}
return (Connection) listener;
}
Connection
Accept(Connection listener)
{
SOCKET accepted = accept((SOCKET) listener, NULL, 0);
if (accepted == INVALID_SOCKET)
{
return InvalidConnection;
}
return (Connection) accepted;
}
void
Close(Connection connection)
{
shutdown((SOCKET) connection, SD_BOTH);
closesocket((SOCKET) connection);
}
int
Send(
Connection connection,
const void *data,
int size
)
{
return send((SOCKET) connection, (const char *) data, size, 0);
}
int
Receive(
Connection connection,
void *buffer,
int size
)
{
int received = recv((SOCKET) connection, (char *) buffer, size, 0);
if (received > 0)
{
return received;
}
if (received == 0)
{
return ReceiveDisconnected;
}
DWORD error = WSAGetLastError();
switch (error)
{
case WSAECONNRESET:
// hard drop reads the same as an orderly close upstairs
return ReceiveDisconnected;
case WSAEWOULDBLOCK:
return ReceiveNoData;
default:
DEBUG_STREAM << "WinsockNetTransport::Receive: recv returned an unexpected error: WSAGetLastError = "
<< error << std::endl << std::flush;
return ReceiveNoData;
}
}
Logical
GetRemoteAddress(
Connection connection,
SOCKADDR_IN *address
)
{
int size = sizeof(SOCKADDR_IN);
memset(address, 0, size);
return getpeername((SOCKET) connection, (sockaddr *) address, &size) == 0;
}
private:
Logical started;
WSADATA winsockData;
};
WinsockNetTransport gWinsockTransport;
NetTransport *gTransport = &gWinsockTransport;
}
NetTransport *
NetTransport_Get()
{
return gTransport;
}
void
NetTransport_Set(NetTransport *transport)
{
gTransport = (transport != NULL) ? transport : &gWinsockTransport;
}