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:
@@ -5,6 +5,13 @@
|
||||
|
||||
HWND ghWnd = 0;
|
||||
|
||||
// BT412: losing the console mid-mission ends it (lobby-member / marshaled
|
||||
// races -- the in-process console owns the clock, so a dropped console
|
||||
// would otherwise count up forever). Arcade -net pods leave it False and
|
||||
// re-listen for their console to return, exactly as always. Set by the
|
||||
// front end / lobby member path (Phase 5/6).
|
||||
Logical gConsoleLossEndsMission = False;
|
||||
|
||||
ApplicationManager* ApplicationManager::CurrentAppManager = NULL;
|
||||
|
||||
ApplicationManager::ApplicationManager(HINSTANCE hInstance, HWND hWnd, Scalar frame_rate) : Node(ApplicationManagerClassID), runningApplications(this)
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
extern HWND ghWnd;
|
||||
|
||||
// BT412: when True, losing the console mid-race ends the mission (the
|
||||
// marshaled/lobby path where the in-process console owns the clock);
|
||||
// False = the arcade -net pod re-listens for its console. See APPMGR.cpp.
|
||||
extern Logical gConsoleLossEndsMission;
|
||||
|
||||
class ApplicationManager : public Node
|
||||
{
|
||||
public:
|
||||
|
||||
+93
-250
@@ -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)
|
||||
//{
|
||||
|
||||
@@ -296,6 +296,11 @@ public:
|
||||
//static MessageHandlerSet MessageHandlers;
|
||||
static MessageHandlerSet& GetMessageHandlers();
|
||||
|
||||
// Load a local egg file and kick the mission cycle - the same path
|
||||
// the single-user startup takes; used by the in-game front end /
|
||||
// local console for the race-after-race loop.
|
||||
void FeedLocalEgg(const char *egg_path);
|
||||
|
||||
void ReceiveEggFileMessageHandler(
|
||||
ReceiveEggFileMessage *EggMessage);
|
||||
void AcknowledgeEggFileMessageHandler(
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
#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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -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 (see context/steamification.md). 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,905 @@
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4steamtransport.h"
|
||||
|
||||
#ifdef BT412_STEAM
|
||||
|
||||
#include "l4nettransport.h"
|
||||
|
||||
// The engine builds with /Zp1; Valve's ABI expects default packing.
|
||||
// Callback structs carry their own pack pragmas, but everything else
|
||||
// must see the platform default.
|
||||
#pragma pack(push, 8)
|
||||
#include "steam/steam_api.h"
|
||||
#include "steam/isteamnetworkingsockets.h"
|
||||
#include "steam/isteamnetworkingutils.h"
|
||||
#include "steam/steamnetworkingfakeip.h"
|
||||
#pragma pack(pop)
|
||||
|
||||
//########################################################################
|
||||
// SteamNetTransport - see l4steamtransport.h for the design. The
|
||||
// engine is single threaded and so is this: every entry point pumps
|
||||
// SteamAPI_RunCallbacks, which is where the status-changed callback
|
||||
// fires (accepting incoming connections and marking drops).
|
||||
//########################################################################
|
||||
|
||||
namespace
|
||||
{
|
||||
enum
|
||||
{
|
||||
maxListeners = 4,
|
||||
maxConnections = 32,
|
||||
maxPeers = 16,
|
||||
maxPending = 8,
|
||||
leftoverSize = 4096
|
||||
};
|
||||
|
||||
struct ListenerRecord
|
||||
{
|
||||
HSteamListenSocket handle;
|
||||
int fakePortIndex;
|
||||
unsigned short enginePort; // host order
|
||||
HSteamNetConnection pending[maxPending];
|
||||
int pendingCount;
|
||||
// The engine closes listeners with TCP semantics (accepted
|
||||
// connections survive). Steam's CloseListenSocket kills the
|
||||
// accepted connections ungracefully - so an engine close only
|
||||
// marks the listener here; the real close waits for Cleanup.
|
||||
Logical closed;
|
||||
};
|
||||
|
||||
struct ConnectionRecord
|
||||
{
|
||||
HSteamNetConnection handle;
|
||||
unsigned long remoteIP; // network order
|
||||
unsigned short remoteEnginePort; // network order
|
||||
Logical connected;
|
||||
Logical dead;
|
||||
char leftover[leftoverSize];
|
||||
int leftoverCount;
|
||||
};
|
||||
|
||||
struct PeerRecord
|
||||
{
|
||||
unsigned long fakeIP; // host order
|
||||
unsigned short fakeConsolePort; // host order
|
||||
unsigned short fakeGamePort; // host order
|
||||
unsigned __int64 steamID; // identity -> global FakeIP on accept
|
||||
};
|
||||
|
||||
// engine-side port convention (lobby default; game port is +1)
|
||||
unsigned short gEngineConsolePort = 1501;
|
||||
|
||||
Logical gSteamReady = False;
|
||||
unsigned long gLocalFakeIP = 0; // host order
|
||||
unsigned short gLocalFakePorts[2] = { 0, 0 };
|
||||
char gLocalFakeAddressString[32] = "";
|
||||
|
||||
// engine ports in the order Listen sees them: [0] console, [1] game
|
||||
unsigned short gEnginePorts[2] = { 0, 0 };
|
||||
int gEnginePortCount = 0;
|
||||
|
||||
ListenerRecord gListeners[maxListeners];
|
||||
int gListenerCount = 0;
|
||||
ConnectionRecord gConnections[maxConnections];
|
||||
int gConnectionCount = 0;
|
||||
PeerRecord gPeers[maxPeers];
|
||||
int gPeerCount = 0;
|
||||
|
||||
ListenerRecord *
|
||||
FindListener(HSteamListenSocket handle)
|
||||
{
|
||||
for (int i = 0; i < gListenerCount; ++i)
|
||||
{
|
||||
if (gListeners[i].handle == handle)
|
||||
{
|
||||
return &gListeners[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConnectionRecord *
|
||||
FindConnection(HSteamNetConnection handle)
|
||||
{
|
||||
for (int i = 0; i < gConnectionCount; ++i)
|
||||
{
|
||||
if (gConnections[i].handle == handle)
|
||||
{
|
||||
return &gConnections[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConnectionRecord *
|
||||
AddConnection(
|
||||
HSteamNetConnection handle,
|
||||
unsigned long remote_ip_net,
|
||||
unsigned short remote_engine_port_net
|
||||
)
|
||||
{
|
||||
if (gConnectionCount >= maxConnections)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
ConnectionRecord *record = &gConnections[gConnectionCount++];
|
||||
memset(record, 0, sizeof(*record));
|
||||
record->handle = handle;
|
||||
record->remoteIP = remote_ip_net;
|
||||
record->remoteEnginePort = remote_engine_port_net;
|
||||
return record;
|
||||
}
|
||||
|
||||
void
|
||||
RemoveConnection(HSteamNetConnection handle)
|
||||
{
|
||||
for (int i = 0; i < gConnectionCount; ++i)
|
||||
{
|
||||
if (gConnections[i].handle == handle)
|
||||
{
|
||||
gConnections[i] = gConnections[gConnectionCount - 1];
|
||||
--gConnectionCount;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// engine port in a SOCKADDR_IN -> the peer's matching fake port
|
||||
unsigned short
|
||||
LookupPeerFakePort(unsigned long fake_ip_host, unsigned short engine_port)
|
||||
{
|
||||
for (int i = 0; i < gPeerCount; ++i)
|
||||
{
|
||||
if (gPeers[i].fakeIP == fake_ip_host)
|
||||
{
|
||||
return (engine_port == gEngineConsolePort)
|
||||
? gPeers[i].fakeConsolePort
|
||||
: gPeers[i].fakeGamePort;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Status-changed handler (fires inside SteamAPI_RunCallbacks on
|
||||
// the game thread): accept incoming, queue connected, mark drops.
|
||||
// Registered through the CCallback dispatcher below - the
|
||||
// config-value function pointer is NOT dispatched by the steam_api
|
||||
// flavor of the library (Valve's own example uses STEAM_CALLBACK).
|
||||
//---------------------------------------------------------------
|
||||
void
|
||||
OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *status)
|
||||
{
|
||||
switch (status->m_info.m_eState)
|
||||
{
|
||||
case k_ESteamNetworkingConnectionState_Connecting:
|
||||
if (status->m_info.m_hListenSocket != k_HSteamListenSocket_Invalid)
|
||||
{
|
||||
// no more callers once the engine closed the listener
|
||||
ListenerRecord *listener = FindListener(status->m_info.m_hListenSocket);
|
||||
if (listener == NULL || listener->closed)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: incoming ["
|
||||
<< status->m_info.m_szConnectionDescription
|
||||
<< "] on a closed listener - rejecting\n" << std::flush;
|
||||
SteamNetworkingSockets()->CloseConnection(
|
||||
status->m_hConn, 0, "listener closed", false);
|
||||
break;
|
||||
}
|
||||
|
||||
// incoming: accept immediately, queue it when Connected
|
||||
DEBUG_STREAM << "SteamNetTransport: incoming ["
|
||||
<< status->m_info.m_szConnectionDescription
|
||||
<< "] - accepting\n" << std::flush;
|
||||
EResult accepted = SteamNetworkingSockets()->AcceptConnection(status->m_hConn);
|
||||
if (accepted != k_EResultOK)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: AcceptConnection failed (EResult "
|
||||
<< (int) accepted << ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case k_ESteamNetworkingConnectionState_Connected:
|
||||
DEBUG_STREAM << "SteamNetTransport: connected ["
|
||||
<< status->m_info.m_szConnectionDescription << "]\n" << std::flush;
|
||||
if (status->m_info.m_hListenSocket != k_HSteamListenSocket_Invalid)
|
||||
{
|
||||
ListenerRecord *listener = FindListener(status->m_info.m_hListenSocket);
|
||||
if (listener != NULL && listener->pendingCount < maxPending)
|
||||
{
|
||||
listener->pending[listener->pendingCount++] = status->m_hConn;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ConnectionRecord *record = FindConnection(status->m_hConn);
|
||||
if (record != NULL)
|
||||
{
|
||||
record->connected = True;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
|
||||
case k_ESteamNetworkingConnectionState_ClosedByPeer:
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: dropped ["
|
||||
<< status->m_info.m_szConnectionDescription
|
||||
<< "] end reason " << status->m_info.m_eEndReason
|
||||
<< ": " << status->m_info.m_szEndDebug << "\n" << std::flush;
|
||||
ConnectionRecord *record = FindConnection(status->m_hConn);
|
||||
if (record != NULL)
|
||||
{
|
||||
record->dead = True;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// The callback listener: constructed after SteamAPI_Init so the
|
||||
// CCallback registration lands in a live callback manager
|
||||
//---------------------------------------------------------------
|
||||
class SteamTransportCallbacks
|
||||
{
|
||||
public:
|
||||
STEAM_CALLBACK(SteamTransportCallbacks, OnStatusChanged,
|
||||
SteamNetConnectionStatusChangedCallback_t);
|
||||
};
|
||||
|
||||
void SteamTransportCallbacks::OnStatusChanged(
|
||||
SteamNetConnectionStatusChangedCallback_t *status)
|
||||
{
|
||||
OnConnectionStatusChanged(status);
|
||||
}
|
||||
|
||||
SteamTransportCallbacks *gCallbacks = NULL;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// The transport
|
||||
//---------------------------------------------------------------
|
||||
class SteamNetTransport:
|
||||
public NetTransport
|
||||
{
|
||||
public:
|
||||
Logical
|
||||
Startup()
|
||||
{
|
||||
// Install() already brought Steam up; the network manager
|
||||
// just confirms the transport is live
|
||||
return gSteamReady;
|
||||
}
|
||||
|
||||
void
|
||||
Cleanup()
|
||||
{
|
||||
// mission teardown (single-binary race loop): drop the
|
||||
// wire but keep the Steam API up - the lobby lives on
|
||||
for (int i = 0; i < gConnectionCount; ++i)
|
||||
{
|
||||
SteamNetworkingSockets()->CloseConnection(
|
||||
gConnections[i].handle, 0, "mission teardown", true);
|
||||
}
|
||||
gConnectionCount = 0;
|
||||
for (int j = 0; j < gListenerCount; ++j)
|
||||
{
|
||||
SteamNetworkingSockets()->CloseListenSocket(gListeners[j].handle);
|
||||
}
|
||||
gListenerCount = 0;
|
||||
gEnginePortCount = 0;
|
||||
}
|
||||
|
||||
int
|
||||
GetLocalAddresses(
|
||||
unsigned long *addresses,
|
||||
int max_count
|
||||
)
|
||||
{
|
||||
if (!gSteamReady || max_count < 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
addresses[0] = htonl(gLocalFakeIP);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Logical
|
||||
Resolve(
|
||||
const char *host_name,
|
||||
SOCKADDR_IN *address
|
||||
)
|
||||
{
|
||||
memset(address, 0, sizeof(SOCKADDR_IN));
|
||||
address->sin_family = AF_INET;
|
||||
|
||||
SteamNetworkingIPAddr parsed;
|
||||
if (!parsed.ParseString(host_name))
|
||||
{
|
||||
return False;
|
||||
}
|
||||
address->sin_addr.S_un.S_addr = htonl(parsed.GetIPv4());
|
||||
address->sin_port = htons(parsed.m_port); // 0 when absent
|
||||
return True;
|
||||
}
|
||||
|
||||
Connection
|
||||
Connect(
|
||||
const SOCKADDR_IN *remote,
|
||||
int local_port
|
||||
)
|
||||
{
|
||||
unsigned long remote_fake_ip = ntohl(remote->sin_addr.S_un.S_addr);
|
||||
unsigned short fake_port =
|
||||
LookupPeerFakePort(remote_fake_ip, ntohs(remote->sin_port));
|
||||
if (fake_port == 0)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: no registered peer for "
|
||||
<< inet_ntoa(remote->sin_addr) << " - lobby did not feed it\n" << std::flush;
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
DEBUG_STREAM << "SteamNetTransport: connecting to "
|
||||
<< inet_ntoa(remote->sin_addr) << ":" << ntohs(remote->sin_port)
|
||||
<< " (fake port " << fake_port << ")...\n" << std::flush;
|
||||
|
||||
SteamNetworkingIPAddr target;
|
||||
target.Clear();
|
||||
target.SetIPv4(remote_fake_ip, fake_port);
|
||||
|
||||
// mirror the TCP retry-while-refused loop, bounded: the
|
||||
// egg-ACK ordering means the peer may not be listening yet
|
||||
DWORD deadline = GetTickCount() + 120 * 1000;
|
||||
int attempt = 0;
|
||||
for (;;)
|
||||
{
|
||||
++attempt;
|
||||
HSteamNetConnection handle =
|
||||
SteamNetworkingSockets()->ConnectByIPAddress(target, 0, NULL);
|
||||
if (handle == k_HSteamNetConnection_Invalid)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: ConnectByIPAddress refused the call\n" << std::flush;
|
||||
return InvalidConnection;
|
||||
}
|
||||
AddConnection(handle, remote->sin_addr.S_un.S_addr, remote->sin_port);
|
||||
|
||||
//
|
||||
// Poll the connection state directly (the callback also
|
||||
// runs, for the log and the accept queues)
|
||||
//
|
||||
ESteamNetworkingConnectionState state =
|
||||
k_ESteamNetworkingConnectionState_Connecting;
|
||||
while ((LONG)(GetTickCount() - deadline) < 0)
|
||||
{
|
||||
SteamAPI_RunCallbacks();
|
||||
|
||||
SteamNetConnectionInfo_t info;
|
||||
if (!SteamNetworkingSockets()->GetConnectionInfo(handle, &info))
|
||||
{
|
||||
state = k_ESteamNetworkingConnectionState_Dead;
|
||||
break;
|
||||
}
|
||||
state = info.m_eState;
|
||||
if (state == k_ESteamNetworkingConnectionState_Connected ||
|
||||
state == k_ESteamNetworkingConnectionState_ProblemDetectedLocally ||
|
||||
state == k_ESteamNetworkingConnectionState_ClosedByPeer ||
|
||||
state == k_ESteamNetworkingConnectionState_None)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Sleep(25);
|
||||
}
|
||||
if (state == k_ESteamNetworkingConnectionState_Connected)
|
||||
{
|
||||
ConnectionRecord *record = FindConnection(handle);
|
||||
if (record != NULL)
|
||||
{
|
||||
record->connected = True;
|
||||
}
|
||||
DEBUG_STREAM << "SteamNetTransport: connect succeeded (attempt "
|
||||
<< attempt << ")\n" << std::flush;
|
||||
return (Connection) handle;
|
||||
}
|
||||
|
||||
// attempt failed - drop it and retry until the deadline
|
||||
DEBUG_STREAM << "SteamNetTransport: attempt " << attempt
|
||||
<< " ended in state " << (int) state << "\n" << std::flush;
|
||||
SteamNetworkingSockets()->CloseConnection(handle, 0, "retry", false);
|
||||
RemoveConnection(handle);
|
||||
if ((LONG)(GetTickCount() - deadline) >= 0)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: connect timed out\n" << std::flush;
|
||||
return InvalidConnection;
|
||||
}
|
||||
Sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
Connection
|
||||
Listen(
|
||||
int local_port,
|
||||
int backlog
|
||||
)
|
||||
{
|
||||
if (gListenerCount >= maxListeners)
|
||||
{
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
// a logically-closed listener for this engine port reopens
|
||||
// (the Steam socket outlives engine closes; see Close)
|
||||
for (int existing = 0; existing < gListenerCount; ++existing)
|
||||
{
|
||||
if (gListeners[existing].enginePort == (unsigned short) local_port)
|
||||
{
|
||||
gListeners[existing].closed = False;
|
||||
gListeners[existing].pendingCount = 0;
|
||||
DEBUG_STREAM << "SteamNetTransport: reopened listener on engine port "
|
||||
<< local_port << "\n" << std::flush;
|
||||
return (Connection) gListeners[existing].handle;
|
||||
}
|
||||
}
|
||||
|
||||
// engine port -> fake port index, in order of appearance:
|
||||
// the console channel always listens first, the mesh second
|
||||
int index = -1;
|
||||
for (int i = 0; i < gEnginePortCount; ++i)
|
||||
{
|
||||
if (gEnginePorts[i] == (unsigned short) local_port)
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if (index < 0)
|
||||
{
|
||||
if (gEnginePortCount >= 2)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: only two fake ports allocated!\n" << std::flush;
|
||||
return InvalidConnection;
|
||||
}
|
||||
index = gEnginePortCount;
|
||||
gEnginePorts[gEnginePortCount++] = (unsigned short) local_port;
|
||||
}
|
||||
|
||||
DEBUG_STREAM << "SteamNetTransport: listening on engine port "
|
||||
<< local_port << " (fake port " << gLocalFakePorts[index]
|
||||
<< ")...\n" << std::flush;
|
||||
|
||||
HSteamListenSocket handle =
|
||||
SteamNetworkingSockets()->CreateListenSocketP2PFakeIP(index, 0, NULL);
|
||||
if (handle == k_HSteamListenSocket_Invalid)
|
||||
{
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
ListenerRecord *listener = &gListeners[gListenerCount++];
|
||||
memset(listener, 0, sizeof(*listener));
|
||||
listener->handle = handle;
|
||||
listener->fakePortIndex = index;
|
||||
listener->enginePort = (unsigned short) local_port;
|
||||
return (Connection) handle;
|
||||
}
|
||||
|
||||
Connection
|
||||
Accept(Connection listener_handle)
|
||||
{
|
||||
SteamAPI_RunCallbacks();
|
||||
|
||||
ListenerRecord *listener = FindListener((HSteamListenSocket) listener_handle);
|
||||
if (listener == NULL || listener->closed || listener->pendingCount == 0)
|
||||
{
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
HSteamNetConnection handle = listener->pending[0];
|
||||
for (int i = 1; i < listener->pendingCount; ++i)
|
||||
{
|
||||
listener->pending[i - 1] = listener->pending[i];
|
||||
}
|
||||
--listener->pendingCount;
|
||||
|
||||
//
|
||||
// The remote engine port is the same engine port we accept
|
||||
// on (all pods share the -net convention under Steam). The
|
||||
// caller's address: Steam reports incoming connections
|
||||
// under a locally-allocated ALIAS FakeIP, so resolve the
|
||||
// caller's identity through the peer table to the GLOBAL
|
||||
// FakeIP the egg's [pilots] list promised - the engine's
|
||||
// mesh identity checks compare against that.
|
||||
//
|
||||
unsigned long remote_ip_net = 0;
|
||||
SteamNetConnectionInfo_t info;
|
||||
if (SteamNetworkingSockets()->GetConnectionInfo(handle, &info))
|
||||
{
|
||||
unsigned __int64 caller_id = info.m_identityRemote.GetSteamID64();
|
||||
for (int p = 0; p < gPeerCount; ++p)
|
||||
{
|
||||
if (gPeers[p].steamID != 0 && gPeers[p].steamID == caller_id)
|
||||
{
|
||||
remote_ip_net = htonl(gPeers[p].fakeIP);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (remote_ip_net == 0)
|
||||
{
|
||||
// unregistered caller: fall back to the alias address
|
||||
SteamNetworkingIPAddr remote_fake;
|
||||
if (SteamNetworkingSockets()->GetRemoteFakeIPForConnection(
|
||||
handle, &remote_fake) == k_EResultOK)
|
||||
{
|
||||
remote_ip_net = htonl(remote_fake.GetIPv4());
|
||||
}
|
||||
}
|
||||
ConnectionRecord *record = AddConnection(
|
||||
handle, remote_ip_net, htons(listener->enginePort));
|
||||
if (record != NULL)
|
||||
{
|
||||
record->connected = True;
|
||||
}
|
||||
return (Connection) handle;
|
||||
}
|
||||
|
||||
void
|
||||
Close(Connection connection)
|
||||
{
|
||||
ListenerRecord *listener = FindListener((HSteamListenSocket) connection);
|
||||
if (listener != NULL)
|
||||
{
|
||||
//
|
||||
// TCP semantics: accepted connections must survive a
|
||||
// listener close. Steam's CloseListenSocket kills them
|
||||
// ungracefully, so only mark it closed (rejecting any
|
||||
// new callers) and destroy it for real in Cleanup.
|
||||
//
|
||||
listener->closed = True;
|
||||
for (int i = 0; i < listener->pendingCount; ++i)
|
||||
{
|
||||
SteamNetworkingSockets()->CloseConnection(
|
||||
listener->pending[i], 0, "listener closed", false);
|
||||
}
|
||||
listener->pendingCount = 0;
|
||||
return;
|
||||
}
|
||||
SteamNetworkingSockets()->CloseConnection(
|
||||
(HSteamNetConnection) connection, 0, "closed", true);
|
||||
RemoveConnection((HSteamNetConnection) connection);
|
||||
}
|
||||
|
||||
int
|
||||
Send(
|
||||
Connection connection,
|
||||
const void *data,
|
||||
int size
|
||||
)
|
||||
{
|
||||
EResult result = SteamNetworkingSockets()->SendMessageToConnection(
|
||||
(HSteamNetConnection) connection, data, (uint32) size,
|
||||
k_nSteamNetworkingSend_Reliable, NULL);
|
||||
return (result == k_EResultOK) ? size : -1;
|
||||
}
|
||||
|
||||
int
|
||||
Receive(
|
||||
Connection connection,
|
||||
void *buffer,
|
||||
int size
|
||||
)
|
||||
{
|
||||
SteamAPI_RunCallbacks();
|
||||
|
||||
ConnectionRecord *record = FindConnection((HSteamNetConnection) connection);
|
||||
if (record == NULL)
|
||||
{
|
||||
return ReceiveNoData;
|
||||
}
|
||||
|
||||
char *out = (char *) buffer;
|
||||
int copied = 0;
|
||||
|
||||
// leftover bytes from a message that outsized the last call
|
||||
if (record->leftoverCount > 0)
|
||||
{
|
||||
int take = (record->leftoverCount < size) ? record->leftoverCount : size;
|
||||
memcpy(out, record->leftover, take);
|
||||
memmove(record->leftover, record->leftover + take,
|
||||
record->leftoverCount - take);
|
||||
record->leftoverCount -= take;
|
||||
out += take;
|
||||
copied += take;
|
||||
}
|
||||
|
||||
// drain messages while they fit; stash any partial tail
|
||||
while (copied < size && record->leftoverCount == 0)
|
||||
{
|
||||
SteamNetworkingMessage_t *message = NULL;
|
||||
int count = SteamNetworkingSockets()->ReceiveMessagesOnConnection(
|
||||
record->handle, &message, 1);
|
||||
if (count <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
int room = size - copied;
|
||||
int take = ((int) message->m_cbSize < room) ? (int) message->m_cbSize : room;
|
||||
memcpy(out, message->m_pData, take);
|
||||
out += take;
|
||||
copied += take;
|
||||
|
||||
int rest = (int) message->m_cbSize - take;
|
||||
if (rest > 0)
|
||||
{
|
||||
if (rest > leftoverSize)
|
||||
{
|
||||
rest = leftoverSize; // cannot happen: engine packets < 2K
|
||||
}
|
||||
memcpy(record->leftover, (const char *) message->m_pData + take, rest);
|
||||
record->leftoverCount = rest;
|
||||
}
|
||||
message->Release();
|
||||
}
|
||||
|
||||
if (copied > 0)
|
||||
{
|
||||
return copied;
|
||||
}
|
||||
if (record->dead)
|
||||
{
|
||||
return ReceiveDisconnected;
|
||||
}
|
||||
return ReceiveNoData;
|
||||
}
|
||||
|
||||
Logical
|
||||
GetRemoteAddress(
|
||||
Connection connection,
|
||||
SOCKADDR_IN *address
|
||||
)
|
||||
{
|
||||
memset(address, 0, sizeof(SOCKADDR_IN));
|
||||
ConnectionRecord *record = FindConnection((HSteamNetConnection) connection);
|
||||
if (record == NULL || !record->connected)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
address->sin_family = AF_INET;
|
||||
address->sin_addr.S_un.S_addr = record->remoteIP;
|
||||
address->sin_port = record->remoteEnginePort;
|
||||
return True;
|
||||
}
|
||||
};
|
||||
|
||||
SteamNetTransport gSteamTransport;
|
||||
}
|
||||
|
||||
//########################################################################
|
||||
// Install: bring Steam up, get our FakeIP identity, take over the wire
|
||||
//########################################################################
|
||||
|
||||
Logical
|
||||
SteamNetTransport_Install()
|
||||
{
|
||||
if (gSteamReady)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
|
||||
if (!SteamAPI_Init())
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: SteamAPI_Init failed "
|
||||
<< "(Steam not running, or no steam_appid.txt) - staying on TCP\n" << std::flush;
|
||||
return False;
|
||||
}
|
||||
|
||||
// connection status arrives through the CCallback dispatcher
|
||||
if (gCallbacks == NULL)
|
||||
{
|
||||
gCallbacks = new SteamTransportCallbacks;
|
||||
}
|
||||
|
||||
SteamNetworkingUtils()->InitRelayNetworkAccess();
|
||||
|
||||
//
|
||||
// Mission load stalls the game thread for 10-30s with nothing
|
||||
// pumping - Steam's default 10s connected-timeout would shear
|
||||
// every connection mid-load (seen live: end reason 4001 with rx
|
||||
// ages right at load duration). TCP never timed out idle arcade
|
||||
// links; 90s keeps that spirit.
|
||||
//
|
||||
SteamNetworkingUtils()->SetGlobalConfigValueInt32(
|
||||
k_ESteamNetworkingConfig_TimeoutConnected, 90 * 1000);
|
||||
|
||||
if (!SteamNetworkingSockets()->BeginAsyncRequestFakeIP(2))
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: BeginAsyncRequestFakeIP failed - staying on TCP\n" << std::flush;
|
||||
return False;
|
||||
}
|
||||
|
||||
// FakeIP allocation is async: pump until it lands (or give up)
|
||||
SteamNetworkingFakeIPResult_t fake;
|
||||
memset(&fake, 0, sizeof(fake));
|
||||
DWORD deadline = GetTickCount() + 20 * 1000;
|
||||
for (;;)
|
||||
{
|
||||
SteamAPI_RunCallbacks();
|
||||
SteamNetworkingSockets()->GetFakeIP(0, &fake);
|
||||
if (fake.m_eResult == k_EResultOK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (fake.m_eResult != k_EResultBusy && fake.m_eResult != k_EResultNoMatch)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: FakeIP allocation failed (EResult "
|
||||
<< (int) fake.m_eResult << ") - staying on TCP\n" << std::flush;
|
||||
return False;
|
||||
}
|
||||
if ((LONG)(GetTickCount() - deadline) >= 0)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: FakeIP allocation timed out - staying on TCP\n" << std::flush;
|
||||
return False;
|
||||
}
|
||||
Sleep(50);
|
||||
}
|
||||
|
||||
gLocalFakeIP = fake.m_unIP;
|
||||
gLocalFakePorts[0] = fake.m_unPorts[0];
|
||||
gLocalFakePorts[1] = fake.m_unPorts[1];
|
||||
|
||||
SteamNetworkingIPAddr mine;
|
||||
mine.Clear();
|
||||
mine.SetIPv4(gLocalFakeIP, 0);
|
||||
mine.ToString(gLocalFakeAddressString, sizeof(gLocalFakeAddressString), false);
|
||||
|
||||
DEBUG_STREAM << "SteamNetTransport: up as " << gLocalFakeAddressString
|
||||
<< " (fake ports " << gLocalFakePorts[0] << " console, "
|
||||
<< gLocalFakePorts[1] << " game)\n" << std::flush;
|
||||
|
||||
gSteamReady = True;
|
||||
NetTransport_Set(&gSteamTransport);
|
||||
|
||||
//
|
||||
// dev: BT412STEAMSELFTEST=1 loops a connection back to ourselves,
|
||||
// proving the listen/accept/connect/send/receive machinery (and
|
||||
// the status-callback dispatch) without a second machine
|
||||
//
|
||||
const char *self_test = getenv("BT412STEAMSELFTEST");
|
||||
if (self_test != NULL && atoi(self_test) != 0)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: SELF TEST starting\n" << std::flush;
|
||||
SteamNetTransport_RegisterPeer(
|
||||
gLocalFakeAddressString, gLocalFakePorts[0], gLocalFakePorts[1],
|
||||
SteamUser()->GetSteamID().ConvertToUint64());
|
||||
|
||||
NetTransport *transport = &gSteamTransport;
|
||||
NetTransport::Connection listener = transport->Listen(1501, 1);
|
||||
|
||||
SOCKADDR_IN self_address;
|
||||
transport->Resolve(gLocalFakeAddressString, &self_address);
|
||||
self_address.sin_port = htons(1501);
|
||||
NetTransport::Connection outgoing = transport->Connect(&self_address, 0);
|
||||
|
||||
NetTransport::Connection incoming = NetTransport::InvalidConnection;
|
||||
DWORD accept_deadline = GetTickCount() + 10 * 1000;
|
||||
while (incoming == NetTransport::InvalidConnection &&
|
||||
(LONG)(GetTickCount() - accept_deadline) < 0)
|
||||
{
|
||||
incoming = transport->Accept(listener);
|
||||
Sleep(50);
|
||||
}
|
||||
|
||||
Logical ok = False;
|
||||
Logical survives_close = False;
|
||||
if (outgoing != NetTransport::InvalidConnection &&
|
||||
incoming != NetTransport::InvalidConnection)
|
||||
{
|
||||
transport->Send(outgoing, "PING", 4);
|
||||
char buffer[16];
|
||||
DWORD recv_deadline = GetTickCount() + 5 * 1000;
|
||||
while ((LONG)(GetTickCount() - recv_deadline) < 0)
|
||||
{
|
||||
if (transport->Receive(incoming, buffer, sizeof(buffer)) == 4)
|
||||
{
|
||||
ok = (memcmp(buffer, "PING", 4) == 0);
|
||||
break;
|
||||
}
|
||||
Sleep(25);
|
||||
}
|
||||
|
||||
//
|
||||
// The arcade engine closes a listener right after
|
||||
// accepting - the accepted connection MUST survive
|
||||
// (Steam kills children on CloseListenSocket; the
|
||||
// transport defers the real close to Cleanup)
|
||||
//
|
||||
transport->Close(listener);
|
||||
transport->Send(incoming, "PONG", 4);
|
||||
recv_deadline = GetTickCount() + 5 * 1000;
|
||||
while ((LONG)(GetTickCount() - recv_deadline) < 0)
|
||||
{
|
||||
if (transport->Receive(outgoing, buffer, sizeof(buffer)) == 4)
|
||||
{
|
||||
survives_close = (memcmp(buffer, "PONG", 4) == 0);
|
||||
break;
|
||||
}
|
||||
Sleep(25);
|
||||
}
|
||||
}
|
||||
DEBUG_STREAM << "SteamNetTransport: SELF TEST "
|
||||
<< ((ok && survives_close) ? "PASSED" : "FAILED")
|
||||
<< " (out " << (outgoing != NetTransport::InvalidConnection)
|
||||
<< ", in " << (incoming != NetTransport::InvalidConnection)
|
||||
<< ", ping " << ok
|
||||
<< ", survives listener close " << survives_close
|
||||
<< ")\n" << std::flush;
|
||||
|
||||
// leave the session pristine for the real race
|
||||
gSteamTransport.Cleanup();
|
||||
gPeerCount = 0;
|
||||
}
|
||||
return True;
|
||||
}
|
||||
|
||||
const char *
|
||||
SteamNetTransport_GetFakeAddressString()
|
||||
{
|
||||
return gLocalFakeAddressString;
|
||||
}
|
||||
|
||||
int
|
||||
SteamNetTransport_GetFakeConsolePort()
|
||||
{
|
||||
return gLocalFakePorts[0];
|
||||
}
|
||||
|
||||
int
|
||||
SteamNetTransport_GetFakeGamePort()
|
||||
{
|
||||
return gLocalFakePorts[1];
|
||||
}
|
||||
|
||||
void
|
||||
SteamNetTransport_SetEnginePorts(int console_port)
|
||||
{
|
||||
gEngineConsolePort = (unsigned short) console_port;
|
||||
}
|
||||
|
||||
void
|
||||
SteamNetTransport_RegisterPeer(
|
||||
const char *fake_ip,
|
||||
int fake_console_port,
|
||||
int fake_game_port,
|
||||
unsigned __int64 steam_id
|
||||
)
|
||||
{
|
||||
SteamNetworkingIPAddr parsed;
|
||||
if (!parsed.ParseString(fake_ip))
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < gPeerCount; ++i)
|
||||
{
|
||||
if (gPeers[i].fakeIP == parsed.GetIPv4())
|
||||
{
|
||||
gPeers[i].fakeConsolePort = (unsigned short) fake_console_port;
|
||||
gPeers[i].fakeGamePort = (unsigned short) fake_game_port;
|
||||
gPeers[i].steamID = steam_id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (gPeerCount >= maxPeers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
gPeers[gPeerCount].fakeIP = parsed.GetIPv4();
|
||||
gPeers[gPeerCount].fakeConsolePort = (unsigned short) fake_console_port;
|
||||
gPeers[gPeerCount].fakeGamePort = (unsigned short) fake_game_port;
|
||||
gPeers[gPeerCount].steamID = steam_id;
|
||||
++gPeerCount;
|
||||
DEBUG_STREAM << "SteamNetTransport: peer registered: " << fake_ip
|
||||
<< " (console " << fake_console_port << ", game "
|
||||
<< fake_game_port << ", id " << steam_id << ")\n" << std::flush;
|
||||
}
|
||||
|
||||
#endif // BT412_STEAM
|
||||
@@ -0,0 +1,97 @@
|
||||
//===========================================================================//
|
||||
// File: l4steamtransport.h //
|
||||
// Project: MUNGA Brick: Steam Network Transport //
|
||||
// Contents: NetTransport over ISteamNetworkingSockets (FakeIP) //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
|
||||
// PROPRIETARY AND CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "..\munga\style.h"
|
||||
|
||||
//########################################################################
|
||||
// SteamNetTransport - the retail wire (l4steamtransport.cpp). Built
|
||||
// only under BT412_STEAM (Steamworks SDK vendored at
|
||||
// extern\steamworks_sdk_164; steam_api.dll ships beside the exe).
|
||||
//
|
||||
// Method mapping onto ISteamNetworkingSockets:
|
||||
//
|
||||
// Install SteamAPI_Init + InitRelayNetworkAccess, then
|
||||
// BeginAsyncRequestFakeIP(2): fake port 0 is the
|
||||
// console channel, fake port 1 the game mesh.
|
||||
// On success installs itself via NetTransport_Set;
|
||||
// on any failure the process stays on Winsock TCP.
|
||||
// GetLocalAddresses our FakeIP (the mesh identifies "which [pilots]
|
||||
// entry is me" against it).
|
||||
// Resolve SteamNetworkingIPAddr::ParseString - FakeIPs ARE
|
||||
// IPv4 strings, so egg text stays unchanged.
|
||||
// Connect ConnectByIPAddress to the peer's FakeIP + fake
|
||||
// game port (SDR routes it; NAT traversal and IP
|
||||
// privacy come free); blocks pumping callbacks
|
||||
// until Connected, retrying like the TCP
|
||||
// connect-while-refused loop.
|
||||
// Listen CreateListenSocketP2PFakeIP - first engine port
|
||||
// seen maps to fake port 0, second to fake port 1.
|
||||
// Accept connection-request callback AcceptConnections
|
||||
// queue up; Accept() pops fully-connected ones.
|
||||
// Send SendMessageToConnection, reliable lane (the
|
||||
// engine's stream framing assumes ordered
|
||||
// reliable delivery).
|
||||
// Receive ReceiveMessagesOnConnection into the caller's
|
||||
// buffer; normalized to ReceiveNoData /
|
||||
// ReceiveDisconnected like the Winsock transport.
|
||||
// GetRemoteAddress GetRemoteFakeIPForConnection, with the fake
|
||||
// port translated back to the ENGINE port so the
|
||||
// mesh's SOCKADDR_IN identity checks keep working.
|
||||
//
|
||||
// Addressing convention under Steam: every pod launches with the same
|
||||
// -net port (the lobby fixes it), so the egg's [pilots] entries are
|
||||
// "<fakeip>:<engine game port>" - the engine's self-match and peer
|
||||
// checks all work on engine ports, and only this transport knows the
|
||||
// Steam-assigned fake port values. The lobby layer exchanges each
|
||||
// member's FakeIP + fake game port and feeds RegisterPeer before the
|
||||
// egg is distributed.
|
||||
//########################################################################
|
||||
|
||||
#ifdef BT412_STEAM
|
||||
|
||||
// Bring Steam up and make this the process transport. False (with the
|
||||
// reason logged) leaves the Winsock transport in place - callers just
|
||||
// carry on over TCP.
|
||||
Logical
|
||||
SteamNetTransport_Install();
|
||||
|
||||
// Our FakeIP as a dotted string ("169.254.x.y"), for lobby member data
|
||||
// and the [pilots] list. Empty until Install succeeds.
|
||||
const char *
|
||||
SteamNetTransport_GetFakeAddressString();
|
||||
|
||||
// Our Steam-assigned fake ports (lobby member data): the console
|
||||
// channel and the game mesh.
|
||||
int
|
||||
SteamNetTransport_GetFakeConsolePort();
|
||||
int
|
||||
SteamNetTransport_GetFakeGamePort();
|
||||
|
||||
// The engine-side port convention for this session (default 1501;
|
||||
// game port is +1). Connect() maps an engine port in a SOCKADDR_IN to
|
||||
// the peer's matching Steam fake port.
|
||||
void
|
||||
SteamNetTransport_SetEnginePorts(int console_port);
|
||||
|
||||
// The lobby feeds every member's FakeIP + fake ports + SteamID here
|
||||
// before anything dials out. The SteamID matters on accept: Steam
|
||||
// reports incoming callers under locally-allocated alias addresses,
|
||||
// so the transport maps the caller's identity back to the global
|
||||
// FakeIP the egg promised. Registering is idempotent per IP.
|
||||
void
|
||||
SteamNetTransport_RegisterPeer(
|
||||
const char *fake_ip,
|
||||
int fake_console_port,
|
||||
int fake_game_port,
|
||||
unsigned __int64 steam_id
|
||||
);
|
||||
|
||||
#endif // BT412_STEAM
|
||||
Reference in New Issue
Block a user