NetTransport seam: the wire moves behind an interface
The Steam-multiplayer prerequisite from the design doc: L4NET keeps hosts, message queues, and the deterministic mesh ordering, while connect/listen/accept/close, send/receive, startup/cleanup, the local interface list, and ip[:port] parsing move behind NetTransport (L4NETTRANSPORT.h). WinsockNetTransport carries the existing TCP behavior over verbatim - including the connect retry-while-refused loop the egg-ACK ordering relies on - and is the process default; NetTransport_Set installs a replacement before the network manager comes up. L4STEAMTRANSPORT.h documents the ISteamNetworkingSockets mapping per method (FakeIP keeps [pilots] entries as IPv4 strings) behind RP412_STEAM until the Steamworks SDK lands. Also fixed in passing: the ExclusiveBroadcast path sent sizeof(network_packet) - four bytes of pointer - instead of the message size, which would have sheared the stream framing had it ever fired. Verified: single-player race cycle (menu, race, results, menu, race) green; -net 8000 boots, listens for a console through the transport, and idles stable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+41
-245
@@ -19,6 +19,7 @@
|
||||
#include "l4app.h"
|
||||
#include "l4host.h"
|
||||
#include "l4net.h"
|
||||
#include "l4nettransport.h"
|
||||
#include "..\munga\mission.h"
|
||||
#include "..\munga\notation.h"
|
||||
//#include <netnub.hpp>
|
||||
@@ -226,11 +227,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 +271,7 @@ L4NetworkManager::L4NetworkManager():
|
||||
if(addresses == NULL)
|
||||
{
|
||||
DEBUG_STREAM<<"ERROR: GetMyAddress() failed!\n";
|
||||
WSACleanup();
|
||||
NetTransport_Get()->Cleanup();
|
||||
Fail("Unable to initialize the network");
|
||||
}
|
||||
|
||||
@@ -319,39 +319,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;
|
||||
}
|
||||
@@ -490,7 +462,6 @@ void
|
||||
//
|
||||
listen = False;
|
||||
remoteHostCount = 0;
|
||||
int bufferSize = sizeof(SOCKADDR_IN);
|
||||
while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL)
|
||||
{
|
||||
Check(mission_host_data);
|
||||
@@ -503,7 +474,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);
|
||||
|
||||
@@ -760,7 +731,7 @@ L4NetworkManager::~L4NetworkManager()
|
||||
delete my_l4host;
|
||||
}
|
||||
|
||||
WSACleanup();
|
||||
NetTransport_Get()->Cleanup();
|
||||
delete wsaData;
|
||||
wsaData = NULL;
|
||||
//Unregister_Pointer(Net_Common_Ptr);
|
||||
@@ -992,8 +963,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,
|
||||
@@ -1166,7 +1136,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)
|
||||
@@ -1330,7 +1300,7 @@ Logical L4NetworkManager::SendMessageToNetnub(
|
||||
//Net_Common_Ptr->Function =
|
||||
// NETNUB_SEND_PACKET;
|
||||
//NetNub::SendCommand();
|
||||
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)
|
||||
@@ -1876,7 +1846,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);
|
||||
|
||||
@@ -2117,15 +2090,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;
|
||||
}
|
||||
@@ -2255,45 +2228,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(
|
||||
@@ -2585,30 +2534,20 @@ 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
NetworkAddress* myAddresses = new NetworkAddress[count];
|
||||
for (int i=0; i<count; i++)
|
||||
myAddresses[i] = (NetworkAddress) local_addresses[i];
|
||||
num_addresses = count;
|
||||
|
||||
return myAddresses;
|
||||
}
|
||||
@@ -2620,55 +2559,12 @@ 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);
|
||||
// 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;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -2680,9 +2576,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;
|
||||
@@ -2738,122 +2632,25 @@ 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;
|
||||
@@ -2885,8 +2682,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)
|
||||
//{
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
#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;
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
int wsa_error = 0, result;
|
||||
do
|
||||
{
|
||||
result = connect(sock, (sockaddr *) remote, sizeof(SOCKADDR_IN));
|
||||
if (result != 0)
|
||||
{
|
||||
wsa_error = WSAGetLastError();
|
||||
}
|
||||
} while (result != 0 && wsa_error == WSAECONNREFUSED);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: connect() failed with "
|
||||
<< wsa_error << "!\n" << std::flush;
|
||||
closesocket(sock);
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return (Connection) sock;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
//===========================================================================//
|
||||
// File: l4nettransport.h //
|
||||
// Project: MUNGA Brick: Network Transport Seam //
|
||||
// Contents: The wire interface under the network manager //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
|
||||
// PROPRIETARY AND CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "..\munga\style.h"
|
||||
#include <Winsock2.h>
|
||||
|
||||
//########################################################################
|
||||
// NetTransport - the seam between the network manager's mesh/console
|
||||
// logic and the wire (docs/RP412-FRONTEND-DESIGN.md section 3). Mirrors
|
||||
// the RIOBase pattern: L4NET keeps hosts, message queues, and the
|
||||
// deterministic mesh ordering; only connect/listen/send/recv live
|
||||
// behind this interface.
|
||||
//
|
||||
// Implementations:
|
||||
// WinsockNetTransport (l4nettransport.cpp) - TCP; the arcade/LAN wire
|
||||
// SteamNetTransport (future) - ISteamNetworkingSockets
|
||||
// P2P over SDR
|
||||
//
|
||||
// Addresses stay SOCKADDR_IN-shaped on purpose: Steam's FakeIP system
|
||||
// hands out fake IPv4 addresses for exactly this kind of engine, so
|
||||
// the [pilots] list keeps working as ip[:port] strings in both worlds.
|
||||
//########################################################################
|
||||
|
||||
class NetTransport
|
||||
{
|
||||
public:
|
||||
// Opaque connection handle. SOCKET for Winsock (SOCKET is UINT_PTR),
|
||||
// HSteamNetConnection for Steam - both fit; callers must not
|
||||
// interpret it.
|
||||
typedef UINT_PTR Connection;
|
||||
static const Connection InvalidConnection = (Connection) ~0; // == INVALID_SOCKET
|
||||
|
||||
// Receive() results when no payload came back
|
||||
enum
|
||||
{
|
||||
ReceiveDisconnected = 0, // orderly close or connection reset
|
||||
ReceiveNoData = -1 // nothing pending (connections are nonblocking)
|
||||
};
|
||||
|
||||
virtual ~NetTransport()
|
||||
{
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Lifecycle
|
||||
//---------------------------------------------------------------
|
||||
virtual Logical
|
||||
Startup() = 0;
|
||||
virtual void
|
||||
Cleanup() = 0;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Addressing: the local interface list (the mesh identifies
|
||||
// "which [pilots] entry is me" against it) and numeric ip[:port]
|
||||
// parsing. Port 0 in the result means "caller applies default".
|
||||
//---------------------------------------------------------------
|
||||
virtual int
|
||||
GetLocalAddresses(
|
||||
unsigned long *addresses,
|
||||
int max_count
|
||||
) = 0;
|
||||
virtual Logical
|
||||
Resolve(
|
||||
const char *host_name,
|
||||
SOCKADDR_IN *address
|
||||
) = 0;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Connections (the deterministic mesh + the console channel).
|
||||
// Connect blocks until the remote end accepts (the mesh relies
|
||||
// on retry-until-up ordering), then goes nonblocking. Listeners
|
||||
// are nonblocking from the start; Accept polls one.
|
||||
//---------------------------------------------------------------
|
||||
virtual Connection
|
||||
Connect(
|
||||
const SOCKADDR_IN *remote,
|
||||
int local_port
|
||||
) = 0;
|
||||
virtual Connection
|
||||
Listen(
|
||||
int local_port,
|
||||
int backlog
|
||||
) = 0;
|
||||
virtual Connection
|
||||
Accept(Connection listener) = 0;
|
||||
virtual void
|
||||
Close(Connection connection) = 0;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Data plane (nonblocking)
|
||||
//---------------------------------------------------------------
|
||||
virtual int
|
||||
Send(
|
||||
Connection connection,
|
||||
const void *data,
|
||||
int size
|
||||
) = 0;
|
||||
virtual int
|
||||
Receive(
|
||||
Connection connection,
|
||||
void *buffer,
|
||||
int size
|
||||
) = 0;
|
||||
|
||||
// remote endpoint of a live connection (mesh identity checks)
|
||||
virtual Logical
|
||||
GetRemoteAddress(
|
||||
Connection connection,
|
||||
SOCKADDR_IN *address
|
||||
) = 0;
|
||||
};
|
||||
|
||||
// The process-wide transport. Defaults to Winsock TCP; a Steam build
|
||||
// installs its transport BEFORE the network manager comes up.
|
||||
NetTransport *
|
||||
NetTransport_Get();
|
||||
void
|
||||
NetTransport_Set(NetTransport *transport);
|
||||
@@ -0,0 +1,128 @@
|
||||
//===========================================================================//
|
||||
// File: l4steamtransport.h //
|
||||
// Project: MUNGA Brick: Steam Network Transport (skeleton) //
|
||||
// Contents: NetTransport over ISteamNetworkingSockets - awaiting the SDK //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
|
||||
// PROPRIETARY AND CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#pragma once
|
||||
|
||||
//########################################################################
|
||||
// SteamNetTransport - the retail wire. NOT BUILT YET: everything below
|
||||
// is guarded by RP412_STEAM, which stays undefined until the Steamworks
|
||||
// SDK is dropped into the tree (needs a Steamworks partner login to
|
||||
// download; put it at extern\steamworks and add steam_api.lib +
|
||||
// steam_api.dll to the link/dist).
|
||||
//
|
||||
// Method mapping (see also docs/RP412-FRONTEND-DESIGN.md section 3):
|
||||
//
|
||||
// Startup SteamAPI_Init + ISteamNetworkingUtils::InitRelay
|
||||
// NetworkAccessAuthorization; then
|
||||
// BeginAsyncRequestFakeIP so this pod has a fake
|
||||
// IPv4 identity to put in the [pilots] list.
|
||||
// Cleanup SteamAPI_Shutdown.
|
||||
// GetLocalAddresses our allocated FakeIP (the mesh identifies
|
||||
// "which pilots entry is me" against it, same as
|
||||
// the LAN build matches its interface list).
|
||||
// Resolve parse ip[:port] exactly like Winsock - FakeIPs
|
||||
// ARE IPv4 strings, so egg text stays unchanged.
|
||||
// Connect ConnectP2PByFakeIP (SDR picks the route, NAT
|
||||
// traversal and IP privacy come free); block on
|
||||
// the connection state callback until Connected,
|
||||
// mirroring the TCP retry-until-accepted loop.
|
||||
// Listen CreateListenSocketP2PFakeIP(port index); the
|
||||
// game port / console port pair becomes fake-port
|
||||
// index 0 / 1.
|
||||
// Accept poll the connection-request callback queue,
|
||||
// AcceptConnection, return the HSteamNetConnection
|
||||
// (it fits the NetTransport::Connection handle).
|
||||
// Close CloseConnection(reason 0, linger enabled).
|
||||
// Send SendMessageToConnection with
|
||||
// k_nSteamNetworkingSend_Reliable: the engine's
|
||||
// stream framing (NetworkPacketHeader + payload)
|
||||
// assumes ordered reliable delivery, exactly what
|
||||
// a reliable Steam message lane provides.
|
||||
// Receive ReceiveMessagesOnConnection; copy into the
|
||||
// host's pad buffer, normalize "no messages" to
|
||||
// ReceiveNoData and connection-closed callbacks
|
||||
// to ReceiveDisconnected.
|
||||
// GetRemoteAddress GetConnectionInfo -> m_addrRemote (the FakeIP),
|
||||
// so the mesh's identity checks keep working on
|
||||
// SOCKADDR_IN values.
|
||||
//
|
||||
// Lobby flow that drives this transport (front-end work, not here):
|
||||
// the lobby owner collects members' FakeIPs + loadouts via lobby data,
|
||||
// builds the canonical egg with the ordered [pilots] list of FakeIPs,
|
||||
// distributes it, and doubles as the console (RPL4CONSOLE marshals the
|
||||
// mission; StopMissionMessage goes out over the console channel).
|
||||
//
|
||||
// Install with NetTransport_Set(new SteamNetTransport) BEFORE the
|
||||
// network manager constructs (front of WinMain, when -steam / a Steam
|
||||
// launch is detected).
|
||||
//########################################################################
|
||||
|
||||
#ifdef RP412_STEAM
|
||||
|
||||
#include "l4nettransport.h"
|
||||
|
||||
class SteamNetTransport:
|
||||
public NetTransport
|
||||
{
|
||||
public:
|
||||
SteamNetTransport();
|
||||
~SteamNetTransport();
|
||||
|
||||
Logical
|
||||
Startup();
|
||||
void
|
||||
Cleanup();
|
||||
|
||||
int
|
||||
GetLocalAddresses(
|
||||
unsigned long *addresses,
|
||||
int max_count
|
||||
);
|
||||
Logical
|
||||
Resolve(
|
||||
const char *host_name,
|
||||
SOCKADDR_IN *address
|
||||
);
|
||||
|
||||
Connection
|
||||
Connect(
|
||||
const SOCKADDR_IN *remote,
|
||||
int local_port
|
||||
);
|
||||
Connection
|
||||
Listen(
|
||||
int local_port,
|
||||
int backlog
|
||||
);
|
||||
Connection
|
||||
Accept(Connection listener);
|
||||
void
|
||||
Close(Connection connection);
|
||||
|
||||
int
|
||||
Send(
|
||||
Connection connection,
|
||||
const void *data,
|
||||
int size
|
||||
);
|
||||
int
|
||||
Receive(
|
||||
Connection connection,
|
||||
void *buffer,
|
||||
int size
|
||||
);
|
||||
|
||||
Logical
|
||||
GetRemoteAddress(
|
||||
Connection connection,
|
||||
SOCKADDR_IN *address
|
||||
);
|
||||
};
|
||||
|
||||
#endif // RP412_STEAM
|
||||
@@ -248,6 +248,7 @@
|
||||
<ClCompile Include=".\L4MOUSE.cpp" />
|
||||
<ClCompile Include=".\L4MPPR.cpp" />
|
||||
<ClCompile Include=".\L4NET.CPP" />
|
||||
<ClCompile Include=".\L4NETTRANSPORT.cpp" />
|
||||
<ClCompile Include=".\L4PARTICLES.cpp" />
|
||||
<ClCompile Include=".\L4MFDVIEW.cpp" />
|
||||
<ClCompile Include=".\L4PADRIO.cpp" />
|
||||
@@ -438,6 +439,7 @@
|
||||
<ClInclude Include=".\L4MOUSE.h" />
|
||||
<ClInclude Include=".\L4MPPR.h" />
|
||||
<ClInclude Include=".\L4NET.H" />
|
||||
<ClInclude Include=".\L4NETTRANSPORT.h" />
|
||||
<ClInclude Include=".\L4PARTICLES.h" />
|
||||
<ClInclude Include=".\L4MFDVIEW.h" />
|
||||
<ClInclude Include=".\L4PADRIO.h" />
|
||||
|
||||
@@ -92,6 +92,19 @@ Transport seam: mirror the `RIOBase` pattern at the L4NET layer — a
|
||||
`L4Host` keeps its identity/queue role; only connect/listen/send/recv move
|
||||
behind the interface.
|
||||
|
||||
**Status (2026-07-12): the seam is IN.** `MUNGA_L4/L4NETTRANSPORT.h/.cpp`
|
||||
defines `NetTransport` (startup/cleanup, local-address list, ip[:port]
|
||||
resolve, connect/listen/accept/close, send/receive, remote-address) with
|
||||
`WinsockNetTransport` as the process default; L4NET.CPP contains no raw
|
||||
Winsock calls anymore. `MUNGA_L4/L4STEAMTRANSPORT.h` documents the
|
||||
per-method ISteamNetworkingSockets mapping (FakeIP keeps the `[pilots]`
|
||||
list as IPv4 strings) and stays behind `RP412_STEAM` until the Steamworks
|
||||
SDK is dropped at `extern\steamworks` (partner-login download). Remaining
|
||||
Steam work: SDK drop → implement `SteamNetTransport` → lobby UI in the
|
||||
front end (owner collects FakeIPs/loadouts via lobby data, builds and
|
||||
distributes the canonical egg, runs the RPL4CONSOLE marshal) → install
|
||||
with `NetTransport_Set` at WinMain when launched under Steam.
|
||||
|
||||
## 4. Implementation options (decide here)
|
||||
|
||||
**A. In-engine front end (recommended).** Port the egg builder (~300 lines:
|
||||
|
||||
Reference in New Issue
Block a user