Files
BT411/engine/MUNGA_L4/L4NET.H
T
arcattackandClaude Fable 5 f04e8019c2 D1: relay-assigned seats -- players never need a player number
A pod launched with no BT_SELF asks the relay for a seat before joining:
new control frames SEAT_REQUEST (-6) -> relay reserves the lowest roster
seat not claimed or reserved (60s reservation so simultaneous joiners
can't race onto one seat) -> SEAT_ASSIGN (-7, int32 hostID + NUL tag)
becomes relaySelf; everything downstream (egg self-match, HELLO) runs
exactly as if BT_SELF had been set. Roster full -> SEAT_FULL (-8) ->
clean Fail(). A real HELLO pops the reservation; a pod drop frees the
seat. Explicit BT_SELF still claims a specific seat (the operator's
local launches use it).

Client: L4NetworkManager::RelayRequestSeat (throwaway TCP dial to the
relay game port, 10s reply window). Relay: SEAT_REQUEST branch in
_handle_game_frame. Operator exporter collapsed from per-seat
join_as_playerN.bat to ONE universal join.bat (+ join_lan.bat with
BT_RELAY=auto) -- every player gets the same file, first come first
served, the arcade walk-up-to-a-pod model. players/ regenerated.

Verified: 8/8 seat stub tests (distinct seats in roster order, FULL on
exhaustion, claimed+reserved stays FULL, HELLO claim accepted, duplicate
HELLO refused); 2-node localhost e2e with NO BT_SELF on either pod ->
both seated, full ladder, RunMission pair, UDP flowing post-launch;
regression smoke: explicit-BT_SELF relay session clean with zero seat
requests, classic mesh (no BT_RELAY) un-regressed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:11:03 -05:00

487 lines
14 KiB
C++

//===========================================================================//
// File: l4net.hh //
// Project: MUNGA Brick: Network Manager //
// Contents: Interface specification for network brick //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 03/02/95 GAC Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
#pragma once
#include "l4host.h"
#include "..\munga\network.h"
#include "..\munga\hostmgr.h"
//WinSock support :ADB 01/06/07
//#include "..\munga\netnub.h"
#define NETNUB_TCP_OPEN 3 // Opens a TCP stream to another computer
#define NETNUB_TCP_LISTEN 4 // Queue's a TCP listen that we can accept a connection on
#define MULTIPLE_SEND_PACKET_MAX 10
#define MAX_RECEIVE_DATA_SIZE 1600
#include <Winsock2.h>
#include <Ws2tcpip.h>
class NotationFile;
class L4NetworkManager__ReceiveEggFileMessage;
class L4NetworkManager__AcknowledgeEggFileMessage;
class L4NetworkManager__HostConnectedMessage;
class L4NetworkManager__HostDisconnectedMessage;
class NetNub
{
friend int
Netnub_Open_File(
const char* filename,
int access,
unsigned int model
);
friend int
Netnub_Write_File(
int handle,
void *buffer,
size_t length
);
friend int
Netnub_Close_File(int handle);
public:
//WinSock support :ADB 01/06/07
//static void SendCommand();
};
//extern Netcom_Ptr
// Net_Common_Ptr;
//~~~~~~~~~~~~~~~~~~~~~ MessageQueue__SendRequest ~~~~~~~~~~~~~~~~~~~~~~~~~~
class MessageQueue__SendRequest:
public Plug
{
friend class HostMessageBuffer__MessageQueue;
friend class L4NetworkManager__MessageBuffer;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private methods
//
private:
MessageQueue__SendRequest(
NetworkClient::ClientID client_ID,
Receiver::Message *message
);
~MessageQueue__SendRequest();
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
private:
NetworkClient::ClientID
clientID;
Receiver::Message
*messageToSend;
};
//~~~~~~~~~~~~~~~~~~~ HostMessageBuffer__MessageQueue ~~~~~~~~~~~~~~~~~~~~~~
class HostMessageBuffer__MessageQueue:
public Node
{
friend class L4NetworkManager__MessageBuffer;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private methods
//
private:
HostMessageBuffer__MessageQueue(HostID host_ID);
~HostMessageBuffer__MessageQueue();
Logical
TestInstance() const;
void
AddSendRequest(
NetworkClient::ClientID client_ID,
Receiver::Message *message
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
private:
HostID
hostID;
typedef MessageQueue__SendRequest
SendRequest;
ChainOf<SendRequest*>
sendRequestSocket;
};
//~~~~~~~~~~~~~~~~~~ L4NetworkManager__MessageBuffer ~~~~~~~~~~~~~~~~~~~~~~~
class L4NetworkManager__MessageBuffer:
public Node
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Public methods
//
public:
L4NetworkManager__MessageBuffer(L4NetworkManager *network_manager);
~L4NetworkManager__MessageBuffer();
Logical
TestInstance() const;
Logical
IsEmpty();
void
AddSendRequest(
HostID host_ID,
NetworkClient::ClientID client_ID,
Receiver::Message *message
);
void
AttemptToSend();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
private:
L4NetworkManager
*networkManager;
typedef HostMessageBuffer__MessageQueue
MessageQueue;
TableOf<MessageQueue*, HostID>
messageQueueSocket;
IteratorPosition
currentQueueIndex;
CollectionSize
bufferSize;
#ifdef LAB_ONLY
CollectionSize
messageCount,
maxBufferSize;
#endif
};
inline Logical
L4NetworkManager__MessageBuffer::IsEmpty()
{
Check(this);
return (bufferSize == 0);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~ l4Network manager~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class L4NetworkManager: public NetworkManager
{
friend class L4NetworkManager__MessageBuffer;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constructor, Destructor, Testing
//
public:
L4NetworkManager();
~L4NetworkManager();
static Logical TestClass();
Logical TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Network message methods
//
public:
void Send(
Message *what,
ClientID to,
HostID host_ID);
void ExclusiveBroadcast(
Message *what,
ClientID to);
void StartConnecting(Mission *mission);
Logical Shutdown();
Logical CheckBuffers(NetworkPacket *packet);
void RemovePacket(NetworkPacket *packet);
Logical ExecuteBackground();
void Marker(char *marker_text);
void Mode(NetworkMode myMode);
void CreateConsoleHost();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Network maintenance support
//
public:
int netPlayerCount;
enum NetworkState
{
NormalState,
ConsoleOnly
};
enum NetworkStartupMode
{
SlaveMode,
MasterMode
};
bool CheckSocket(SOCKET socket, SOCKADDR_IN *remoteEndpoint);
bool ResolveAddress(CString host_name, SOCKADDR_IN *address);
//WinSock support :ADB 01/06/07
NetworkAddress* GetMyAddress();
//WinSock support :ADB 01/06/07
SOCKET OpenConnection(
int connection_type, // NETNUB_TCP_LISTEN or NETNUB_TCP_OPEN
int local_port,
int remote_port,
int internet_address);
void CloseConnection(SOCKET socket_ptr); // socket address from netnub (to close)
Logical GetNextMungaPacket(
NetworkPacket *network_packet,
HostManager::RemoteHostIterator* all_iterator);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data support
//
public:
static Derivation *GetClassDerivations();
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support
//
public:
//
// Message IDs
//
enum
{
AcknowledgeEggFileMessageID = NetworkManager::NextMessageID,
HostConnectedMessageID,
HostDisconnectedMessageID,
NextMessageID
};
//
// Message types
//
typedef L4NetworkManager__AcknowledgeEggFileMessage
AcknowledgeEggFileMessage;
typedef L4NetworkManager__HostConnectedMessage
HostConnectedMessage;
typedef L4NetworkManager__HostDisconnectedMessage
HostDisconnectedMessage;
//
// Message table
//
static const HandlerEntry MessageHandlerEntries[];
//static MessageHandlerSet MessageHandlers;
static MessageHandlerSet& GetMessageHandlers();
void ReceiveEggFileMessageHandler(
ReceiveEggFileMessage *EggMessage);
void AcknowledgeEggFileMessageHandler(
AcknowledgeEggFileMessage *AcknowledgeEgg);
void HostConnectedMessageHandler(
HostConnectedMessage *HostConnected);
void HostDisconnectedMessageHandler(
HostDisconnectedMessage *ConsoleDisconnect);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private methods
//
private:
typedef ChainOf<L4Host*>
DroppedMessageHostSocket;
typedef ChainIteratorOf<L4Host*>
DroppedMessageHostIterator;
Logical
SendMessageToNetnub(
Message *message,
ClientID client_ID,
HostID host_ID
);
void
SendBatchedMessageToNetnub(
Message *message,
ClientID client,
DroppedMessageHostSocket *dropped_message_host_socket
);
//
// D1 RELAY MODE (BT_RELAY=host:port + BT_SELF=<[pilots] entry>): outbound-
// only topology -- the pod dials the relay (extended btconsole.py) for BOTH
// the console protocol (egg/launch; legacy raw frames, direction flipped)
// and the game traffic (envelope-framed, one TCP connection multiplexing
// every peer; UDP for the unreliable channel in a later phase). BT_RELAY
// unset => relayMode False => every relay branch is dead and the classic
// mesh is untouched.
//
Logical
RelayDiscover(SOCKADDR_IN *console_endpoint);
Logical
RelayRequestSeat();
SOCKET
RelayDialTcp(const SOCKADDR_IN &endpoint, int timeout_seconds);
Logical
RelaySendAll(SOCKET sock, const char *buffer, int length);
NetworkPacket *
BuildRelayPacket(Message *message, ClientID client, int *packet_size);
Logical
RelaySendFrame(int route, const NetworkPacket *packet, int packet_size);
void
ConnectRelayGame(HostID local_host_ID);
void
RelayGameDown(const char *why);
Logical
CheckRelay(NetworkPacket *network_packet);
//
// UDP unreliable channel (restores the 1995 NETNUB reliable/unreliable
// split): the ~60Hz update records ride UDP so a lost/late datagram is
// dropped (dead reckoning absorbs the gap) instead of head-of-line-
// blocking the reliable stream. Reliable traffic + control stay on TCP.
//
void
ConnectRelayUdp(HostID local_host_ID);
Logical
RelayUdpSendFrame(int route, const NetworkPacket *packet, int packet_size);
void
RelayUdpKeepalive(HostID local_host_ID);
Logical
RelayShouldUseUdp(const Message *message) const;
Logical
CheckRelayUdp(NetworkPacket *network_packet);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private Data
//
private:
int
numberOfMungaHostsConnected,
numberOfConsoleHostsConnected,
remoteHostCount;
NetworkStartupMode networkStartupMode;
Logical eggAcknowledged;
NetworkState currentNetworkState;
L4Host *myConsoleHost;
HostID nextOpenHostID;
IteratorPosition lastHostIteratorPosition;
typedef L4NetworkManager__MessageBuffer
MessageBuffer;
MessageBuffer
messageBuffer;
//WinSock Support
WSADATA* wsaData;
//hostent* thisHost;
addrinfo* thisHost;
SOCKET gameListenerSocket;
SOCKET consoleListenerSocket;
// D1 relay mode state (see the private-methods block above). All inert
// unless BT_RELAY is set.
Logical relayMode;
char relaySelf[64]; // BT_SELF -- our [pilots] entry string
SOCKADDR_IN relayConsoleAddress; // relay console endpoint (BT_RELAY port)
SOCKADDR_IN relayGameAddress; // relay game endpoint (port + 1, tcp+udp)
SOCKET relayGameSocket; // the ONE multiplexed game connection
SOCKET relayUdpSocket; // unreliable channel (later phase)
char relayPad[16384]; // relay-connection reassembly buffer
int relayPadTail;
Logical relayConsoleDialedOnce; // first dial gets the long timeout
Logical udpUp; // UDP HELLO acked by the relay
HostID relayLocalHostID; // our hostID (for UDP HELLO/keepalive/tx)
unsigned long lastUdpKeepaliveTick;
unsigned long lastUdpHelloTick; // HELLO retry until acked
unsigned long udpTxSeq;
unsigned long udpRxSeq[64]; // per-fromHost last-seen sequence
NetworkMode currentNetworkMode; // Reliable/Unreliable (Mode() stores)
};
//~~~~~~~~~~~~~~~~~~~~~~ L4NetworkManager inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager__AcknowledgeEggFileMessage
// This message is sent back to the console to acknowledge receipt of the
// egg, it indicates this computer has established all it's connections and
// the console can procede with the next host
//
class L4NetworkManager__AcknowledgeEggFileMessage:
public NetworkManager::Message
{
public:
L4NetworkManager__AcknowledgeEggFileMessage():
NetworkManager::Message(
L4NetworkManager::AcknowledgeEggFileMessageID,
sizeof(L4NetworkManager__AcknowledgeEggFileMessage)
){}
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager__HostConnectedMessage
// This message is generated internally by CheckBuffers and indicates that
// a host has connected up to us. This is an INTERNAL message only and is
// not ment to be sent on the network.
class L4NetworkManager__HostConnectedMessage :
public NetworkManager::Message
{
public:
L4NetworkManager__HostConnectedMessage(HostID host_id, const SOCKADDR_IN &network_address, unsigned long stream_pointer) :
NetworkManager::Message(L4NetworkManager::HostConnectedMessageID, sizeof(L4NetworkManager__HostConnectedMessage)),
hostID(host_id),
networkAddress(network_address),
streamPointer(stream_pointer)
{
}
HostID hostID;
SOCKADDR_IN networkAddress;
unsigned long streamPointer;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager__HostDisconnectedMessage
// The console is expected to send us this message if it disconnects from us
// for any reason. This allows the local host to setup the internal state
// so the game will auto start even if the console can't stay connected to us
// for some reason (for example, the console simulator in the game code can't
// connect to more than one host at once).
// !!! CheckBuffers should poll the connection state of all the streams and
// generate this message automatically whenever a host drops off the net.
// !!! HACK At the moment this message is always treated as if it came from
// the console host, so it should only be sent down the console stream.
//
class L4NetworkManager__HostDisconnectedMessage :
public NetworkManager::Message
{
public:
L4NetworkManager__HostDisconnectedMessage(HostID host_id, unsigned long stream_pointer) :
NetworkManager::Message(L4NetworkManager::HostDisconnectedMessageID, sizeof(L4NetworkManager__HostDisconnectedMessage)),
hostID(host_id),
streamPointer(stream_pointer)
{
}
HostID hostID;
unsigned long streamPointer;
};