Files
TeslaRel410/restoration/source410/MUNGA_L4/L4NET.CPP
T
CydandClaude Fable 5 b01db52d43 BT410 5.3.117: the pod talks -- the entire NetNub networked layer reconstructed, and the last runtime stub falls
Twenty-two Fail sites in L4NET.CPP -- the whole console/mesh transport --
replaced with decomp-verified bodies (part_007.c @004501d4..@00452378),
every error string extracted byte-for-byte from the shipped exe, every
magic number resolved to a macro from the AUTHENTIC 1995 NETNUB.HPP that
survives in the archive (NETCOM_VERSION 11; 0x63c == MAX_SEND_DATA_SIZE).

The real-mode bridge came free: the 1995 netshare.OBJ has been linking
into mungal4.lib all along via the prebuilt fallback -- this file only had
to define the six globals it imports and call it. NetNub::SendCommand is
the gateway (setRMBuff, DPMI int 31h/0300h on the TSR's vector, getRMBuff);
BC4.52's <dos.h> really has int386x -- the binary's own "int386x():ERROR="
text named the RTL call.

The topology, decomp-proven: console listens on 1501, the pod mesh on
1502; hosts before ours in the egg get an OPEN, ours becomes the local
host by address match, hosts after ours get a LISTEN -- and CheckBuffers
heals the ordering race by SWAPPING sockets when a peer lands on the wrong
listen. The console's listen is a wildcard that learns its address; the
console connection SURVIVES mission teardown (Shutdown zeroes both
counters anyway -- reproduced faithfully). Send discipline: direct while
the retry buffer is empty, everything queues behind a backlog in per-host
FIFO, one round-robin drain per idle slot; only a dumped-to-avoid-blocking
send is retried, and RemovePacket is empty in the binary because delivery
already compacted the pad buffer. Internal connect/disconnect events ride
the GetNetworkClientPointer(0)->ReceiveNetworkPacket self-dispatch (the
decomp's FUN_0041d7f0 resolves to the ClientID-0-is-me idiom).

Ghidra had dropped register and trailing arguments at half a dozen call
sites (GetRemoteHost's HostID among them) -- the surviving HPP signatures
arbitrated each.

Stubs: 8 across 8 files -- ALL RUNTIME STUBS ARE NOW CLEAR; what remains
is the authoring-side CreateStreamedSubsystem family, which missions never
call. Single-user regression clean on the rig; the networked path awaits
its console + NetNub-TSR live session (two-agreeing-runs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 10:05:13 -05:00

1816 lines
52 KiB
C++

//===========================================================================//
// File: l4net.cpp //
// Project: MUNGA Brick: Network Manager //
// Contents: Implementation details for the L4 network manager //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 03/02/95 GAC Initial coding. //
// 04/08/95 GAC Netnow Removed, TCP/NetNub support added //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#include <mungal4.hpp>
#pragma hdrstop
#if !defined(L4NET_HPP)
# include <l4net.hpp>
#endif
#if !defined(L4APP_HPP)
# include <l4app.hpp>
#endif
#if !defined(L4HOST_HPP)
# include <l4host.hpp>
#endif
#if !defined(MISSION_HPP)
# include <mission.hpp>
#endif
#if !defined(NOTATION_HPP)
# include <notation.hpp>
#endif
#include <dos.h> // REGS / SREGS / segread / int386x (DPMI real-mode call)
//
//#############################################################################
// NetNub client-side globals. Net_Common_Ptr NULL = no NetNub TSR loaded;
// L4File then takes its plain-DOS path (L4FILE.CPP:32) and the network
// manager runs in single user mode.
//#############################################################################
//
Netcom_Ptr
Net_Common_Ptr = NULL;
//
// The protected-mode globals the NETSHARE.ASM bridge imports (EXTRN C):
// Function_Ptr / Buffer_Length_Ptr aim at the * fields of the protected-mode
// Netcom copy; baseadr:offs hold the REAL-MODE segment/offset of the shared
// structure; the RM*_Off words are the real-mode offsets of the Function and
// Buffer_Length fields inside it. The bridge links as the SURVIVING 1995
// netshare.OBJ (CODE/RP/MUNGA_L4, already a mungal4.lib member -- TASM
// uppercased its symbols; tlink32 matches caselessly). Binary homes
// 0x522b84..0x522b98, in this order.
//
extern "C"
{
unsigned short
*Buffer_Length_Ptr;
short
*Function_Ptr;
short
offs,
baseadr;
}
static short
intno; // the netnub's real-mode interrupt vector
extern "C"
{
short
RMFunction_Off,
RMBuffer_Length_Off;
}
static long
RMverno; // the resident netnub's version stamp
extern "C"
{
void mapRMBuff();
void getRMBuff();
void setRMBuff();
void getRMNumbers();
}
//
//#############################################################################
// NetNub::SendCommand -- binary @00452100 (authentic assert line 2470).
//
// The protected->real gateway: push the PM Netcom's * fields + buffer down
// to the real-mode shared block (setRMBuff), fire the netnub's real-mode
// interrupt through DPMI function 0300h (simulate real-mode interrupt), and
// pull the reply fields back (getRMBuff). The register image is zeroed
// exactly where the binary zeroes it (reserved/flags/fs/gs/ss:sp -- DPMI
// supplies a real-mode stack; the ISR takes its data from the shared block,
// not from registers).
//#############################################################################
//
void
NetNub::SendCommand()
{
//
// The DPMI 0.9 int 31h/0300h real-mode register image.
//
struct RealModeRegisters
{
long
edi, esi, ebp, reserved, ebx, edx, ecx, eax;
short
flags, es, ds, fs, gs, ip, cs, sp, ss;
}
rm_regs;
union REGS
regs;
struct SREGS
sregs;
setRMBuff();
regs.x.eax = 0x300; // DPMI: simulate real mode interrupt
regs.h.bl = (unsigned char)intno; // the netnub's vector
regs.h.bh = 0;
regs.x.ecx = 0; // no stack words to copy
regs.x.edi = (unsigned long)&rm_regs;
segread(&sregs); // es:edi -> the image (flat ds)
rm_regs.reserved = 0;
rm_regs.flags = 0;
rm_regs.fs = 0;
rm_regs.gs = 0;
rm_regs.sp = 0; // 0:0 -- DPMI provides the stack
rm_regs.ss = 0;
int386x(0x31, &regs, &regs, &sregs);
if (regs.x.cflag != 0)
{
DEBUG_STREAM << "int386x():ERROR=" << regs.x.eax << endl;
Fail("Error calling netnub\n");
}
getRMBuff();
}
//
//#############################################################################
// Shared data support
//#############################################################################
//
Derivation
L4NetworkManager::ClassDerivations(
NetworkManager::ClassDerivations,
"L4NetworkManager"
);
const Receiver::HandlerEntry
L4NetworkManager::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(L4NetworkManager,ReceiveEggFile),
MESSAGE_ENTRY(L4NetworkManager,AcknowledgeEggFile),
MESSAGE_ENTRY(L4NetworkManager,HostConnected),
MESSAGE_ENTRY(L4NetworkManager,HostDisconnected)
};
L4NetworkManager::MessageHandlerSet
L4NetworkManager::MessageHandlers(
ELEMENTS(L4NetworkManager::MessageHandlerEntries),
L4NetworkManager::MessageHandlerEntries,
NetworkManager::MessageHandlers
);
L4NetworkManager::SharedData
L4NetworkManager::DefaultData(
L4NetworkManager::ClassDerivations,
L4NetworkManager::MessageHandlers
);
//
//#############################################################################
// Code for the network manager class
//#############################################################################
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constructor for the L4NetworkManager
//
L4NetworkManager::L4NetworkManager():
NetworkManager(DefaultData),
messageBuffer(this)
{
unsigned long
network_common_flat_address;
network_common_flat_address =
L4Application::GetNetworkCommonFlatAddress();
lastHostIteratorPosition = 0;
nextOpenHostID = FirstLegalHostID+1; // Reserve first legal id for console
currentNetworkState = NormalState;
myConsoleHost = NULL;
eggAcknowledged = False;
networkStartupMode = SlaveMode;
numberOfMungaHostsConnected = 0;
numberOfConsoleHostsConnected = 0;
remoteHostCount = 0x7FFFFFFF;
//
// If there is no net common, set things up for single user mode
//
if (network_common_flat_address == 0)
{
Net_Common_Ptr = NULL;
//
//---------------------------------------------------------------------
// Retrieve the egg as specified on the command line, and send it as an
// egg message
//---------------------------------------------------------------------
//
CString egg_name = L4Application::GetEggNotationFileName();
if (!strlen(egg_name))
{
Fail("ERROR: No source exists for egg!");
}
networkEggNotationFile = new NotationFile(egg_name);
Register_Object(networkEggNotationFile);
networkEggNotationFile->WriteFile("last.egg");
currentNetworkState = NormalState;
ReceiveEggFileMessage egg_message(-1, 10, "local egg", 10);
application->Post(DefaultEventPriority, this, &egg_message);
return;
}
//
// Setup the local netcom structure and some pointers to things inside it
// (binary @004501d4; the RM plumbing follows the surviving RP comments
// line for line).
//
Net_Common_Ptr = new Netcom;
Register_Pointer(Net_Common_Ptr);
Function_Ptr = &Net_Common_Ptr->Function;
Buffer_Length_Ptr = &Net_Common_Ptr->Buffer_Length;
//
// Setup the global pointers that the netnub real/protected mode
// interface requires to work.
//
offs = (short)(network_common_flat_address & 0x0000000f);
baseadr = (short)(network_common_flat_address >> 4);
mapRMBuff(); // get selector for baseadr
RMFunction_Off = (short)(offs
+ sizeof(Net_Common_Ptr->Version_Number)
+ sizeof(Net_Common_Ptr->Interrupt_Number));
RMBuffer_Length_Off = (short)(RMFunction_Off
+ sizeof(Net_Common_Ptr->Function)
+ sizeof(Net_Common_Ptr->Status));
getRMNumbers(); // get Interrupt_Number, Version_Number
intno = Net_Common_Ptr->Interrupt_Number;
RMverno = Net_Common_Ptr->Version_Number;
//
// Check for right network common number
//
if (RMverno != NETCOM_VERSION)
{
DEBUG_STREAM << "Netnub version " << RMverno
<< ", code version " << NETCOM_VERSION << endl;
Fail("MUNGA was compiled with an old version of netnub.h");
}
//
// Get my network address from the netnub (also verifies we are connected)
//
address = GetMyAddress();
if (address == 0)
{
Fail("Unable to initialize the network");
}
//
// Force us into reliable mode
//
Mode(NetworkManager::ReliableMode);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4NetworkManager::~L4NetworkManager()
{
if (networkEggNotationFile)
{
Unregister_Object(networkEggNotationFile);
delete networkEggNotationFile;
networkEggNotationFile = NULL;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::CreateConsoleHost This routine creates the console remote
// host and posts a listen for the console connection.
//
void
L4NetworkManager::CreateConsoleHost()
{
//
// Die if there is no network common around
//
if (Net_Common_Ptr == NULL)
{
return;
}
//
// Initialize a bunch of stuff to sane states
//
nextOpenHostID = FirstLegalHostID+1; // Reserve first legal id for console
networkStartupMode = SlaveMode; // We're a slave, (not a simulated console)
currentNetworkState = ConsoleOnly; // Only accept messages from the console
numberOfConsoleHostsConnected = 0; // Shouldn't be any consoles yet
numberOfMungaHostsConnected = 0; // No game machines either
//
// Listen for the console (TCP port 1501) and stand up its host record
// (binary @0045046c). The address starts null -- CheckBuffers learns
// the console's real address when it connects (any console may claim
// the pod).
//
unsigned long
console_socket = OpenConnection(NETNUB_TCP_LISTEN, 1501, 0, 0);
myConsoleHost =
new L4Host(
FirstLegalHostID,
ConsoleHostType,
NullNetworkAddress,
console_socket,
CString("Console")
);
Register_Object(myConsoleHost);
myConsoleHost->SetConnectStatus(L4Host::ListeningConnectionStatus);
Check(application);
Check(application->GetHostManager());
application->GetHostManager()->AdoptRemoteHost(myConsoleHost);
//
// A fresh console session starts with no egg. The binary drops the
// pointer without deleting (reproduced; the normal path already tore
// the old egg down in Shutdown).
//
networkEggNotationFile = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::StartConnecting This routine is called when a mission
// starts to allow the network management system to establish communications
// with all the participants.
//
void
L4NetworkManager::StartConnecting(Mission *mission)
{
L4Host *my_l4host;
//
// This should be entered with no network connections to other game hosts
// up, so we initialize this count to zero.
//
numberOfMungaHostsConnected = 0;
//
// Create the host iterator for the mission egg
//
Mission::HostIterator mission_host_iterator(mission);
MissionHostData *mission_host_data;
//
// If there is no net common, set things up for single user mode
//
if (Net_Common_Ptr == NULL)
{
//
// Make the local host for single user testing, we give the host ID#1
// and assign the first symbolic name in the egg to it.
//
mission_host_data = mission_host_iterator.ReadAndNext();
Check(mission_host_data);
my_l4host = new L4Host(
1, // Host ID
mission_host_data->GetHostType(), // Host Type
NullNetworkAddress, // Net Address
0, // Socket address
mission_host_data->GetAddressString()
);
Register_Object(my_l4host);
Check(application);
Check(application->GetHostManager());
application->GetHostManager()->AdoptLocalHost(my_l4host);
//
//--------------------------------------------------------------------
// Now, since this host creator is for stand-alone mode, send the load
// mission message to the application
//--------------------------------------------------------------------
//
Check(application);
Application::Message
load_message(
Application::LoadMissionMessageID,
sizeof(Application::Message)
);
application->Post(DefaultEventPriority, application, &load_message);
return;
}
//
// Networked (binary @00450554). Walk the egg's host list: every host
// BEFORE ours gets an active open to its game port (it is already
// listening for us); our own entry is recognized by address and becomes
// the local host; every host AFTER ours gets a listen (it will open to
// us). This ordering rule forms the pod-to-pod mesh without collisions.
// Game port = 1502.
//
Logical
found_self = False;
remoteHostCount = 0;
while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL)
{
CString
host_name = mission_host_data->GetAddressString();
unsigned long
host_address = ResolveAddress(host_name);
if (host_address == GetAddress())
{
//
// This entry is me.
//
my_l4host =
new L4Host(
nextOpenHostID,
mission_host_data->GetHostType(),
host_address,
0,
host_name
);
Register_Object(my_l4host);
application->GetHostManager()->AdoptLocalHost(my_l4host);
found_self = True;
}
else
{
remoteHostCount++;
unsigned long
host_socket;
if (!found_self)
{
host_socket =
OpenConnection(NETNUB_TCP_OPEN, 0, 1502, host_address);
}
else
{
host_socket =
OpenConnection(NETNUB_TCP_LISTEN, 1502, 0, host_address);
}
L4Host
*remote_host =
new L4Host(
nextOpenHostID,
mission_host_data->GetHostType(),
host_address,
host_socket,
host_name
);
Register_Object(remote_host);
remote_host->SetConnectStatus(
found_self
? L4Host::ListeningConnectionStatus
: L4Host::OpeningConnectionStatus);
application->GetHostManager()->AdoptRemoteHost(remote_host);
}
nextOpenHostID++;
}
if (networkStartupMode == SlaveMode)
{
//
// Tell the console its egg took (it moves on to the next pod); if
// every remote is somehow already online, go straight to load.
//
AcknowledgeEggFileMessage
acknowledge_message;
Check(myConsoleHost);
Send(&acknowledge_message, 0, myConsoleHost->GetHostID());
if (remoteHostCount <= numberOfMungaHostsConnected)
{
Application::Message
all_connected_message(
Application::LoadMissionMessageID,
sizeof(Application::Message)
);
application->Post(
DefaultEventPriority, application, &all_connected_message);
}
}
else
{
Fail("host is in an illegal startup mode\n");
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::Shutdown This routine is called when a mission ends to
// allow the network management system to drop all the connections.
//
Logical
L4NetworkManager::Shutdown()
{
Host
*base_host;
L4Host
*my_l4host;
unsigned long
network_common_flat_address;
network_common_flat_address =
L4Application::GetNetworkCommonFlatAddress();
if (networkEggNotationFile)
{
Unregister_Object(networkEggNotationFile);
delete networkEggNotationFile;
networkEggNotationFile = NULL;
}
//
// If there is no net common, all we do is deregister the local host
//
if (network_common_flat_address == 0)
{
base_host = application->GetHostManager()->OrphanLocalHost();
my_l4host = Cast_Object(L4Host*, base_host);
Check(my_l4host);
Unregister_Object(my_l4host);
delete my_l4host;
return True;
}
//
// Networked (binary @0045084c): close and destroy every GAME host; the
// console connection and its host record survive across missions (the
// pod drops back to the console-only lobby). The local host is orphaned
// and destroyed like the single-user path.
//
HostManager
*host_manager = application->GetHostManager();
Check(host_manager);
HostManager::RemoteHostIterator
remote_iterator(host_manager);
L4Host
*remote_host;
while ((remote_host = (L4Host *)remote_iterator.ReadAndNext()) != NULL)
{
if (remote_host->GetHostType() != ConsoleHostType)
{
if (remote_host->GetConnectStatus()
!= L4Host::NoNetworkConnectionStatus)
{
CloseConnection(remote_host->GetNetworkSocket());
}
host_manager->OrphanRemoteHost(remote_host);
Unregister_Object(remote_host);
delete remote_host;
}
}
base_host = host_manager->OrphanLocalHost();
if (base_host != NULL)
{
my_l4host = Cast_Object(L4Host*, base_host);
Unregister_Object(my_l4host);
delete my_l4host;
}
//
// Back to the lobby. NOTE: the binary zeroes BOTH connection counters
// even though the console host stays online -- reproduced faithfully
// (the console's own disconnect path recreates its listener rather than
// decrementing through zero).
//
remoteHostCount = 0x7FFFFFFF;
nextOpenHostID = FirstLegalHostID+1;
networkStartupMode = SlaveMode;
currentNetworkState = ConsoleOnly;
numberOfConsoleHostsConnected = 0;
numberOfMungaHostsConnected = 0;
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::Send Sends a message to a single host.
//
void
L4NetworkManager::Send(Message *message, ClientID to, HostID host_ID)
{
Check_Pointer(message);
//
// Binary @00450f48. While the retry buffer is empty, try the wire
// directly; a send the netnub dumped to avoid blocking joins the buffer
// for retry. Once anything is buffered, every new message must queue
// BEHIND it (per-host FIFO order) and we take a drain attempt instead.
// (The offline-host and size checks live inside SendMessageToNetnub.)
//
if (messageBuffer.IsEmpty())
{
if (!SendMessageToNetnub(message, to, host_ID))
{
messageBuffer.AddSendRequest(host_ID, to, message);
}
}
else
{
messageBuffer.AddSendRequest(host_ID, to, message);
messageBuffer.AttemptToSend();
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::ExclusiveBroadcast Sends a message to every online host
// except the named client.
//
void
L4NetworkManager::ExclusiveBroadcast(Message *message, ClientID to)
{
Check_Pointer(message);
Check(application);
//
// Binary @0045134c. Empty buffer: one batched multiple-send on the
// wire; hosts whose send the netnub dumped join the retry buffer.
// Non-empty: everything queues behind the backlog (per-host FIFO) and
// we take a drain attempt.
//
if (messageBuffer.IsEmpty())
{
DroppedMessageHostSocket
dropped_hosts(NULL);
SendBatchedMessageToNetnub(message, to, &dropped_hosts);
DroppedMessageHostIterator
dropped_iterator(&dropped_hosts);
L4Host
*dropped_host;
while ((dropped_host = dropped_iterator.ReadAndNext()) != NULL)
{
messageBuffer.AddSendRequest(dropped_host->GetHostID(), to, message);
}
}
else
{
HostManager::RemoteHostIterator
remote_iterator(application->GetHostManager());
L4Host
*remote_host;
while ((remote_host = (L4Host *)remote_iterator.ReadAndNext()) != NULL)
{
if (
remote_host->GetHostType() != ConsoleHostType &&
remote_host->GetConnectStatus() == L4Host::OnLineConnectionStatus
)
{
messageBuffer.AddSendRequest(remote_host->GetHostID(), to, message);
}
}
messageBuffer.AttemptToSend();
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::CheckBuffers Checks to see if there is a message for us
// buffered up in the network interface card.
//
Logical
L4NetworkManager::CheckBuffers(NetworkPacket *packet)
{
//
// No netnub present? Nothing can have arrived.
//
if (Net_Common_Ptr == NULL)
{
return False;
}
//
// Binary @004518b8, the receive pump. Deliver a buffered packet if one
// is whole; otherwise advance every pending connection state machine
// (posting at most ONE internal connect event per call), then drain the
// wire into the per-host pad buffers with one MULTIPLE_RECEIVE, and try
// the delivery again.
//
HostManager::RemoteHostIterator
host_iterator(application->GetHostManager());
if (GetNextMungaPacket(packet, &host_iterator))
{
return True;
}
MultipleReceivePacketRequestPtr
receive_request =
(MultipleReceivePacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
L4Host
*receive_hosts[MULTIPLE_RECEIVE_PACKET_MAX];
int
receive_count = 0;
L4Host
*l4host;
host_iterator.First();
while ((l4host = (L4Host *)host_iterator.ReadAndNext()) != NULL)
{
switch (l4host->GetConnectStatus())
{
case L4Host::NoNetworkConnectionStatus:
break;
case L4Host::OpeningConnectionStatus:
{
unsigned long
peer_address = CheckSocket(l4host->GetNetworkSocket());
if (peer_address != 0)
{
if (peer_address != l4host->GetNetworkAddress())
{
DEBUG_STREAM << "Host " << l4host->GetNetworkAddress()
<< " client " << peer_address << endl;
Fail("For an OPEN the host MUST match the client or something is really messed up\n");
}
HostConnectedMessage
connect_message(
l4host->GetHostID(),
peer_address,
l4host->GetNetworkSocket());
GetNetworkClientPointer(0)->ReceiveNetworkPacket(
NULL, &connect_message);
return False;
}
break;
}
case L4Host::ListeningConnectionStatus:
{
unsigned long
peer_address = CheckSocket(l4host->GetNetworkSocket());
if (peer_address != 0)
{
unsigned long
listen_socket = l4host->GetNetworkSocket();
if (peer_address == l4host->GetNetworkAddress())
{
//
// The host this listen was reserved for connected.
//
HostConnectedMessage
connect_message(
l4host->GetHostID(), peer_address, listen_socket);
GetNetworkClientPointer(0)->ReceiveNetworkPacket(
NULL, &connect_message);
}
else if (l4host->GetHostType() == ConsoleHostType)
{
//
// The console listen is a wildcard: learn the console's
// address from whoever claimed the pod.
//
l4host->SetNetworkAddress(peer_address);
HostConnectedMessage
connect_message(
l4host->GetHostID(), peer_address, listen_socket);
GetNetworkClientPointer(0)->ReceiveNetworkPacket(
NULL, &connect_message);
}
else
{
//
// Someone ELSE'S peer connected on this listen (mesh
// peers racing the ordering rule): find the listening
// host that expects this address and SWAP sockets so
// each record owns its own stream.
//
HostManager::RemoteHostIterator
other_iterator(application->GetHostManager());
L4Host
*other_host;
while ((other_host =
(L4Host *)other_iterator.ReadAndNext()) != NULL)
{
if (
other_host->GetConnectStatus()
== L4Host::ListeningConnectionStatus &&
other_host->GetNetworkAddress() == peer_address
)
{
l4host->SetNetworkSocket(
other_host->GetNetworkSocket());
other_host->SetNetworkSocket(listen_socket);
HostConnectedMessage
connect_message(
other_host->GetHostID(),
peer_address,
listen_socket);
GetNetworkClientPointer(0)->ReceiveNetworkPacket(
NULL, &connect_message);
return False;
}
}
}
return False;
}
break;
}
case L4Host::OnLineConnectionStatus:
//
// Only ask for data a full max-size receive can fit.
//
if (
(short)(l4host->pad_size - l4host->pad_tail)
>= MAX_RECEIVE_DATA_SIZE
)
{
receive_hosts[receive_count] = l4host;
receive_request->Socket_Ptrs[receive_count] =
l4host->GetNetworkSocket();
receive_count++;
}
break;
default:
Fail("Host had illegal connection status\n");
break;
}
}
if (receive_count == 0)
{
return False;
}
receive_request->Socket_Count = receive_count;
Net_Common_Ptr->Function = NETNUB_MULTIPLE_RECEIVE;
Net_Common_Ptr->Buffer_Length = sizeof(MultipleReceivePacketRequest);
NetNub::SendCommand();
if (Net_Common_Ptr->Status == NETNUB_OK)
{
return False;
}
MultipleReceivePacketReturnPtr
receive_return =
(MultipleReceivePacketReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer;
char
*received_data = receive_return->Received_Data;
int
receive_index;
for (receive_index = 0; receive_index < receive_count; receive_index++)
{
long
receive_status = (long)receive_return->Status[receive_index];
l4host = receive_hosts[receive_index];
if (receive_status == NETNUB_STREAM_DISCONNECTED)
{
HostDisconnectedMessage
disconnect_message(
l4host->GetHostID(), l4host->GetNetworkSocket());
GetNetworkClientPointer(0)->ReceiveNetworkPacket(
NULL, &disconnect_message);
}
else if (receive_status < 0)
{
DEBUG_STREAM << "MUNGA reported network error "
<< receive_status << endl;
Fail("MUNGA network error");
}
else if (receive_status > 0)
{
memcpy(
l4host->pad_buffer + l4host->pad_tail,
received_data,
(size_t)receive_status);
l4host->pad_tail =
(unsigned short)(l4host->pad_tail + receive_status);
received_data += receive_status;
}
}
return GetNextMungaPacket(packet, &host_iterator);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
L4NetworkManager::RemovePacket(NetworkPacket *)
{
//
// No netnub present? Just return
//
if (Net_Common_Ptr == NULL)
{
return;
}
//
// Nothing to do (binary @004520f8 is an EMPTY function): the L4 pump
// dequeues and compacts the pad buffer inside GetNextMungaPacket, so a
// delivered packet is already gone.
//
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
L4NetworkManager::ExecuteBackground()
{
Check(this);
if (!messageBuffer.IsEmpty())
{
messageBuffer.AttemptToSend();
return True;
}
return False;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::Marker Sends a text marker down the netnub spool for
// mission review annotation.
//
void
L4NetworkManager::Marker(char *marker_text)
{
//
// No netnub present? Just return
//
if (Net_Common_Ptr == NULL)
{
return;
}
//
// Binary @004525bc: hand the text (with its terminator) to the netnub's
// MARKER_TEXT function -- it lands in the console spool for review
// annotation.
//
int
marker_length = strlen(marker_text) + 1;
memcpy(Net_Common_Ptr->Shared_Memory_Buffer, marker_text, marker_length);
Net_Common_Ptr->Function = NETNUB_MARKER_TEXT;
Net_Common_Ptr->Buffer_Length = (unsigned short)marker_length;
NetNub::SendCommand();
if (Net_Common_Ptr->Status != NETNUB_OK)
{
DEBUG_STREAM << "NetNub marker text call failed\n";
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::Mode Switches the netnub between reliable and unreliable
// delivery.
//
void
L4NetworkManager::Mode(NetworkMode myMode)
{
//
// No netnub present? Just return
//
if (Net_Common_Ptr == NULL)
{
return;
}
//
// Binary @0045255c: reliable delivery = the netnub BLOCKS in send until
// the stream takes the bytes; unreliable = non-blocking, and a send that
// would block comes back NETNUB_DATA_DUMPED (which the send paths then
// buffer for retry).
//
SetSwitchesRequestPtr
switches_request =
(SetSwitchesRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
switches_request->blocking_switch = (myMode == ReliableMode) ? 1 : 0;
Net_Common_Ptr->Function = NETNUB_SET_SWITCHES;
Net_Common_Ptr->Buffer_Length = sizeof(SetSwitchesRequest);
NetNub::SendCommand();
if (Net_Common_Ptr->Status != NETNUB_OK)
{
DEBUG_STREAM << "NetNub mode call failed\n";
}
}
//
//#############################################################################
// Message handlers
//#############################################################################
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::ReceiveEggFileMessageHandler
//
void
L4NetworkManager::ReceiveEggFileMessageHandler(
ReceiveEggFileMessage *EggMessage
)
{
#if defined(LAB_ONLY)
DEBUG_STREAM << "Received Egg message #" << EggMessage->sequenceNumber
<< endl;
#endif
//
// An egg sequence number of -1 means that the egg is posted locally, and
// should just call create mission
//
if (EggMessage->sequenceNumber == -1)
{
application->CreateMission(networkEggNotationFile);
return;
}
if (EggMessage->sequenceNumber == 0)
{
//
// Make a buffer
//
eggTempBuffer = new char[EggMessage->notationFileLength];
Register_Pointer(eggTempBuffer);
eggTempNext = 0;
}
//
// Write the egg data into the buffer
//
memcpy(
(eggTempBuffer+eggTempNext),
EggMessage->notationData,
EggMessage->thisMessageLength
);
eggTempNext += EggMessage->thisMessageLength;
//
// If we don't have all the data, return and wait for more
//
if (eggTempNext < EggMessage->notationFileLength)
{
return;
}
//
// We've got all the data, make the notation file
//
networkEggNotationFile = new NotationFile();
Register_Object(networkEggNotationFile);
networkEggNotationFile->ReadText(eggTempBuffer, eggTempNext);
networkEggNotationFile->WriteFile("last.egg");
currentNetworkState = NormalState;
//
// Now turn the notation file into a mission
//
application->CreateMission(networkEggNotationFile);
//
// Get rid of the ram buffer now that we're done with it
//
Unregister_Pointer(eggTempBuffer);
delete eggTempBuffer;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::AcknowledgeEggFileMessageHandler
//
void
L4NetworkManager::AcknowledgeEggFileMessageHandler(
AcknowledgeEggFileMessage *
)
{
#if defined(LAB_ONLY)
DEBUG_STREAM << "\nReceived egg acknowledged message\n";
#endif
eggAcknowledged = True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::HostConnectedMessageHandler
//
void
L4NetworkManager::HostConnectedMessageHandler(
HostConnectedMessage *HostConnected
)
{
//
// Get a pointer to the L4 host
//
Check(application);
Check(application->GetHostManager());
Host *connected_host =
application->GetHostManager()->GetRemoteHost(HostConnected->hostID);
L4Host *l4connected_host = Cast_Object(L4Host*, connected_host);
Check(l4connected_host);
//
// Set the host connection status to on line and increment the
// counter of hosts online.
//
l4connected_host->SetConnectStatus(L4Host::OnLineConnectionStatus);
switch (l4connected_host->GetHostType())
{
case GameMachineHostType:
numberOfMungaHostsConnected++;
DEBUG_STREAM << "Connected to GameMachineHost at ";
break;
case CameraShipHostType:
numberOfMungaHostsConnected++;
DEBUG_STREAM << "Connected to CameraShipHost at ";
break;
case MissionReviewHostType:
numberOfMungaHostsConnected++;
DEBUG_STREAM << "Connected to MissionReviewHost at ";
break;
case ConsoleHostType:
numberOfConsoleHostsConnected++;
DEBUG_STREAM << "Connected to ConsoleHost at ";
break;
default:
Fail("L4NetworkManager::HostConnectedMessageHandler - unknown host type");
break;
}
DEBUG_STREAM << HostConnected->networkAddress << endl;
if (numberOfMungaHostsConnected >= remoteHostCount)
{
DEBUG_STREAM << "All connections completed!\n";
Marker("MUNGA MARKER--All connections completed!\n");
//
//--------------------------------------------------------------------
// All hosts are up; send the load mission message to the application
//--------------------------------------------------------------------
//
Check(application);
Application::Message
load_message(
Application::LoadMissionMessageID,
sizeof(Application::Message)
);
application->Post(DefaultEventPriority, application, &load_message);
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager::HostDisconnectedMessageHandler
// This message is assumed only to be sent by the console just prior to it
// disconnecting. It clues us to locally close and destroy the console host
// so the game will start normally even if the console isn't around.
//
void
L4NetworkManager::HostDisconnectedMessageHandler(
HostDisconnectedMessage *HostDisconnected
)
{
//
// Get a pointer to the L4 host
//
Check(application->GetHostManager());
Host *connected_host =
application->GetHostManager()->GetRemoteHost(HostDisconnected->hostID);
L4Host *l4connected_host = Cast_Object(L4Host*, connected_host);
Check(l4connected_host);
//
// Set the host connection status to off line, then close the connection.
//
l4connected_host->SetConnectStatus(L4Host::NoNetworkConnectionStatus);
NetworkAddress temp_net_address = l4connected_host->GetNetworkAddress();
CloseConnection(HostDisconnected->streamPointer);
//
// See what type of host it was that disconnected
//
switch (l4connected_host->GetHostType())
{
case GameMachineHostType:
numberOfMungaHostsConnected--;
DEBUG_STREAM << "\nDisconnected from GameMachineHost at ";
break;
case CameraShipHostType:
numberOfMungaHostsConnected--;
DEBUG_STREAM << "\nDisconnected from CameraShipHost at ";
break;
case MissionReviewHostType:
numberOfMungaHostsConnected--;
DEBUG_STREAM << "\nDisconnected from MissionReviewHost at ";
break;
case ConsoleHostType:
//
// Knock down the number of consoles online, then destroy the
// console host and recreate it; this resets all the internal
// buffers and pointers so check_buffers won't go crazy
//
numberOfConsoleHostsConnected--;
DEBUG_STREAM << "\nDisconnected from ConsoleHost at ";
application->GetHostManager()->OrphanRemoteHost(l4connected_host);
Unregister_Object(l4connected_host);
delete l4connected_host;
CreateConsoleHost();
break;
default:
Fail("L4NetworkManager::HostDisconnectedMessageHandler - unknown host type");
break;
}
DEBUG_STREAM << temp_net_address << "\n";
}
//
//#############################################################################
// Netnub wire primitives (decomp-verified, part_007.c; every error string is
// the shipped binary's own text).
//#############################################################################
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CheckSocket -- binary @00452318. Polls a pending open/listen: 0 while
// nothing has connected; the PEER'S ADDRESS once the stream is up.
//
unsigned long
L4NetworkManager::CheckSocket(unsigned long socket)
{
ReceivePacketRequestPtr
check_request =
(ReceivePacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
check_request->Socket_Ptr = socket;
Net_Common_Ptr->Function = NETNUB_CHECK_SOCKET;
Net_Common_Ptr->Buffer_Length = sizeof(ReceivePacketRequest);
NetNub::SendCommand();
if (Net_Common_Ptr->Status == NETNUB_OK)
{
return 0;
}
if (Net_Common_Ptr->Status == NETNUB_TCP_CONNECTED)
{
return *(unsigned long *)Net_Common_Ptr->Shared_Memory_Buffer;
}
Fail("CheckSocket returned an illegal status code\n");
return 0;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ResolveAddress -- binary @00452244. Hand the symbolic name (dotted quad
// or hostname) to the netnub's resolver; returns the 32-bit address.
//
unsigned long
L4NetworkManager::ResolveAddress(CString host_name)
{
int
name_length = strlen(host_name) + 1;
memcpy(Net_Common_Ptr->Shared_Memory_Buffer, (const char *)host_name,
name_length);
Net_Common_Ptr->Function = NETNUB_RESOLVE_ADDRESS;
Net_Common_Ptr->Buffer_Length = (unsigned short)strlen(host_name);
NetNub::SendCommand();
if (Net_Common_Ptr->Status != NETNUB_OK)
{
DEBUG_STREAM << "Couldn't resolve " << (const char *)host_name
<< " to a net address\n";
Fail("unresolvable network address\n");
}
return ((ResolveReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer)
->Internet_Address;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// GetMyAddress -- binary @004521d0. A STATUS round trip; doubles as the
// "is the netnub alive" probe (the ctor Fails on 0).
//
unsigned long
L4NetworkManager::GetMyAddress()
{
Net_Common_Ptr->Function = NETNUB_STATUS;
Net_Common_Ptr->Buffer_Length = 0;
NetNub::SendCommand();
if (Net_Common_Ptr->Status == NETNUB_OK)
{
return ((StatusReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer)
->My_Net_Address;
}
DEBUG_STREAM << "Status not ok, it's " << Net_Common_Ptr->Status << endl;
return 0;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// OpenConnection -- binary @00452378. connection_type is passed straight
// through as the netnub function (NETNUB_TCP_OPEN or NETNUB_TCP_LISTEN);
// the netnub allocates the socket (Socket_Ptr 0) and returns its handle.
//
unsigned long
L4NetworkManager::OpenConnection(
int connection_type,
int local_port,
int remote_port,
int internet_address
)
{
TCPOpenRequestPtr
open_request =
(TCPOpenRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
open_request->Local_Port = (short)local_port;
open_request->Remote_Port = (short)remote_port;
open_request->Internet_Address = (unsigned long)internet_address;
open_request->Socket_Ptr = 0;
Net_Common_Ptr->Function = (short)connection_type;
Net_Common_Ptr->Buffer_Length = sizeof(TCPOpenRequest);
NetNub::SendCommand();
if (Net_Common_Ptr->Status == NETNUB_ERROR)
{
DEBUG_STREAM << "NetNub TCP Open/Listen error #"
<< *(unsigned long *)Net_Common_Ptr->Shared_Memory_Buffer
<< " listening for " << internet_address << endl;
Fail("Netnub error during open/listen\n");
}
return ((TCPOpenReturn *)Net_Common_Ptr->Shared_Memory_Buffer)->Socket_Ptr;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CloseConnection -- binary @004524c8. Best-effort: a close error only
// logs (the stream is going away regardless).
//
void
L4NetworkManager::CloseConnection(unsigned long socket_ptr)
{
*(unsigned long *)Net_Common_Ptr->Shared_Memory_Buffer = socket_ptr;
Net_Common_Ptr->Function = NETNUB_TCP_CLOSE;
Net_Common_Ptr->Buffer_Length = 4;
NetNub::SendCommand();
if (Net_Common_Ptr->Status != NETNUB_OK)
{
DEBUG_STREAM << "NetNub close error " << Net_Common_Ptr->Status
<< " on stream " << socket_ptr << endl;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// GetNextMungaPacket -- binary @00452000. Round-robin over the hosts
// (resuming at lastHostIteratorPosition so no host starves): deliver the
// first WHOLE packet from a pad buffer -- copy it out, compact the pad, and
// shrink the tail. In ConsoleOnly state only console packets deliver.
//
Logical
L4NetworkManager::GetNextMungaPacket(
NetworkPacket *network_packet,
HostManager::RemoteHostIterator *all_iterator
)
{
short
host_count = (short)all_iterator->GetSize();
short
hosts_checked;
for (hosts_checked = 0; hosts_checked < host_count; hosts_checked++)
{
lastHostIteratorPosition++;
if ((int)all_iterator->GetSize() <= lastHostIteratorPosition)
{
lastHostIteratorPosition = 0;
}
L4Host
*l4host =
(L4Host *)all_iterator->GetNthItem(lastHostIteratorPosition);
if (l4host == NULL)
{
return False;
}
if (
(currentNetworkState != ConsoleOnly ||
l4host->GetHostType() == ConsoleHostType) &&
l4host->pad_tail >=
sizeof(NetworkPacketHeader) + sizeof(Receiver::Message)
)
{
short
packet_length =
(short)(*(short *)(l4host->pad_buffer
+ sizeof(NetworkPacketHeader))
+ sizeof(NetworkPacketHeader));
if ((int)packet_length <= (int)l4host->pad_tail)
{
memcpy(network_packet, l4host->pad_buffer,
(size_t)packet_length);
if ((short)(l4host->pad_tail - packet_length) != 0)
{
memmove(
l4host->pad_buffer,
l4host->pad_buffer + packet_length,
(size_t)(l4host->pad_tail - packet_length));
}
l4host->pad_tail =
(unsigned short)(l4host->pad_tail - packet_length);
return True;
}
}
}
return False;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SendMessageToNetnub -- binary @00450fa8. One SEND_PACKET: the wire frame
// is a NetworkPacketHeader {clientID, gameID, fromHost, timeStamp} plus the
// message bytes; the netnub strips the leading socket handle for routing.
// Returns False ONLY when the netnub dumped the send to avoid blocking
// (the callers buffer it for retry); every other outcome -- including a
// detected disconnect, which posts the internal event -- is "done".
//
Logical
L4NetworkManager::SendMessageToNetnub(
Message *message,
ClientID client_ID,
HostID host_ID
)
{
Check_Pointer(message);
Check(application);
Check(application->GetHostManager());
Host
*base_host = application->GetHostManager()->GetRemoteHost(host_ID);
if (base_host == NULL)
{
return True;
}
L4Host
*l4host = Cast_Object(L4Host*, base_host);
if (l4host->GetConnectStatus() != L4Host::OnLineConnectionStatus)
{
return True;
}
HostID
my_host_ID = 0;
Host
*local_host = application->GetHostManager()->GetLocalHost();
if (local_host != NULL)
{
my_host_ID = local_host->GetHostID();
}
int
send_length = message->messageLength + sizeof(NetworkPacketHeader);
if (message->messageLength < 8 || send_length > MAX_SEND_DATA_SIZE)
{
DEBUG_STREAM << "L4NetworkManager::SendMessageToNetnub - "
<< l4host->GetNetworkAddress()
<< "Requested to send a " << send_length << " size message\n";
Fail("L4NetworkManager::SendMessageToNetnub - Illegal transmit size\n");
return True;
}
SendPacketRequestPtr
send_request =
(SendPacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
NetworkPacketHeader
*wire_header = (NetworkPacketHeader *)send_request->Send_Data;
send_request->Socket_Ptr = l4host->GetNetworkSocket();
wire_header->clientID = client_ID;
wire_header->gameID = gameID;
wire_header->fromHost = my_host_ID;
wire_header->timeStamp = Now();
memcpy(
send_request->Send_Data + sizeof(NetworkPacketHeader),
message,
message->messageLength);
Net_Common_Ptr->Function = NETNUB_SEND_PACKET;
Net_Common_Ptr->Buffer_Length =
(unsigned short)SEND_BUFFER_SIZE(send_length);
NetNub::SendCommand();
switch (Net_Common_Ptr->Status)
{
case NETNUB_DATA_DUMPED:
return False;
case NETNUB_STREAM_DISCONNECTED:
{
DEBUG_STREAM << "L4NetworkManager::SendMessageToNetnub - "
<< "Disconnect detected in send\n";
HostDisconnectedMessage
disconnect_message(
l4host->GetHostID(), l4host->GetNetworkSocket());
GetNetworkClientPointer(0)->ReceiveNetworkPacket(
NULL, &disconnect_message);
return True;
}
case NETNUB_OK:
return True;
default:
DEBUG_STREAM << "L4NetworkManager::SendMessageToNetnub - "
<< "Error " << Net_Common_Ptr->Status << " "
<< l4host->GetNetworkAddress() << endl;
Fail("NetNub error in transmit\n");
return True;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SendBatchedMessageToNetnub -- binary @00451448. One MULTIPLE_SEND frame
// to every online game host (the console is excluded from broadcasts; ten
// destinations is the netnub's frame limit). Per-destination statuses come
// back in place of the socket list: a dumped send adds its host to the
// caller's dropped chain for buffering, a disconnect posts the internal
// event, anything else negative is fatal.
//
void
L4NetworkManager::SendBatchedMessageToNetnub(
Message *message,
ClientID client,
DroppedMessageHostSocket *dropped_message_host_socket
)
{
Check_Pointer(message);
Check_Pointer(dropped_message_host_socket);
if (Net_Common_Ptr == NULL)
{
return;
}
HostID
my_host_ID = 0;
Host
*local_host = application->GetHostManager()->GetLocalHost();
if (local_host != NULL)
{
my_host_ID = local_host->GetHostID();
}
int
send_length = message->messageLength + sizeof(NetworkPacketHeader);
if (message->messageLength < 8 || send_length > MAX_SEND_DATA_SIZE)
{
DEBUG_STREAM << "L4NetworkManager::SendBatchedMessageToNetnub - "
<< "Requested to send a " << send_length << " size message\n";
Fail("L4NetworkManager::SendBatchedMessageToNetnub - Illegal transmit size\n");
return;
}
MultipleSendPacketRequestPtr
batch_request =
(MultipleSendPacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
L4Host
*batch_hosts[MULTIPLE_SEND_PACKET_MAX];
int
batch_count = 0;
HostManager::RemoteHostIterator
remote_iterator(application->GetHostManager());
L4Host
*remote_host;
while ((remote_host = (L4Host *)remote_iterator.ReadAndNext()) != NULL)
{
if (
remote_host->GetHostType() != ConsoleHostType &&
remote_host->GetConnectStatus() == L4Host::OnLineConnectionStatus
)
{
if (batch_count >= MULTIPLE_SEND_PACKET_MAX)
{
DEBUG_STREAM
<< "L4NetworkManager::SendBatchedMessageToNetnub - ";
Fail("Tried to send to too many destinations\n");
return;
}
batch_hosts[batch_count] = remote_host;
batch_request->Socket_Ptrs[batch_count] =
remote_host->GetNetworkSocket();
batch_count++;
}
}
batch_request->Socket_Count = batch_count;
if (batch_count == 0)
{
return;
}
NetworkPacketHeader
*wire_header = (NetworkPacketHeader *)batch_request->Send_Data;
wire_header->clientID = client;
wire_header->gameID = gameID;
wire_header->fromHost = my_host_ID;
wire_header->timeStamp = Now();
memcpy(
batch_request->Send_Data + sizeof(NetworkPacketHeader),
message,
message->messageLength);
Net_Common_Ptr->Function = NETNUB_MULTIPLE_SEND;
Net_Common_Ptr->Buffer_Length =
(unsigned short)MULTIPLE_SEND_BUFFER_SIZE(send_length);
NetNub::SendCommand();
if (Net_Common_Ptr->Status == NETNUB_OK)
{
return;
}
//
// Per-destination statuses overwrite the socket list.
//
MultipleSendPacketReturnPtr
batch_return =
(MultipleSendPacketReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer;
int
batch_index;
for (batch_index = 0; batch_index < batch_count; batch_index++)
{
long
send_status = (long)batch_return->Errors[batch_index];
L4Host
*batched_host = batch_hosts[batch_index];
if (send_status == NETNUB_DATA_DUMPED)
{
dropped_message_host_socket->Add(batched_host);
}
else if (send_status == NETNUB_STREAM_DISCONNECTED)
{
DEBUG_STREAM << "L4NetworkManager::SendBatchedMessageToNetnub - "
<< "Disconnect detected in send\n";
HostDisconnectedMessage
disconnect_message(
batched_host->GetHostID(),
batched_host->GetNetworkSocket());
GetNetworkClientPointer(0)->ReceiveNetworkPacket(
NULL, &disconnect_message);
}
else if (send_status != NETNUB_OK)
{
DEBUG_STREAM << "L4NetworkManager::SendBatchedMessageToNetnub - "
<< "Error " << send_status << " "
<< batched_host->GetNetworkAddress() << endl;
Fail("NetNub error in transmit\n");
}
}
}
//
//#############################################################################
// L4NetworkManager__MessageBuffer
//#############################################################################
//
L4NetworkManager__MessageBuffer::L4NetworkManager__MessageBuffer(
L4NetworkManager *network_manager
):
messageQueueSocket(NULL, 1)
{
networkManager = network_manager;
currentQueueIndex = 0;
bufferSize = 0;
}
L4NetworkManager__MessageBuffer::~L4NetworkManager__MessageBuffer()
{
}
//
// The queued-send plumbing (binary @00452640..@004528dc): a SendRequest owns
// a COPY of the message; a MessageQueue chains one host's requests in FIFO
// order; the buffer tables the queues by host and drains them round-robin,
// one request per attempt.
//
MessageQueue__SendRequest::MessageQueue__SendRequest(
NetworkClient::ClientID client_ID,
Receiver::Message *message
)
{
Check_Pointer(message);
clientID = client_ID;
messageToSend = (Receiver::Message *)new char[message->messageLength];
Register_Pointer(messageToSend);
memcpy(messageToSend, message, message->messageLength);
}
MessageQueue__SendRequest::~MessageQueue__SendRequest()
{
Unregister_Pointer(messageToSend);
delete [] (char *)messageToSend;
}
Logical
MessageQueue__SendRequest::TestInstance() const
{
return True;
}
HostMessageBuffer__MessageQueue::HostMessageBuffer__MessageQueue(
HostID host_ID
):
sendRequestSocket(NULL)
{
hostID = host_ID;
}
HostMessageBuffer__MessageQueue::~HostMessageBuffer__MessageQueue()
{
ChainIteratorOf<SendRequest*>
request_iterator(&sendRequestSocket);
request_iterator.DeletePlugs();
}
Logical
HostMessageBuffer__MessageQueue::TestInstance() const
{
return True;
}
void
HostMessageBuffer__MessageQueue::AddSendRequest(
NetworkClient::ClientID client_ID,
Receiver::Message *message
)
{
SendRequest
*send_request = new SendRequest(client_ID, message);
Register_Object(send_request);
sendRequestSocket.Add(send_request);
}
Logical
L4NetworkManager__MessageBuffer::TestInstance() const
{
return True;
}
void
L4NetworkManager__MessageBuffer::AddSendRequest(
HostID host_ID,
NetworkClient::ClientID client_ID,
Receiver::Message *message
)
{
Check(this);
Check_Pointer(message);
MessageQueue
*message_queue = messageQueueSocket.Find(host_ID);
if (message_queue == NULL)
{
message_queue = new MessageQueue(host_ID);
Register_Object(message_queue);
messageQueueSocket.AddValue(message_queue, host_ID);
}
message_queue->AddSendRequest(client_ID, message);
bufferSize++;
}
void
L4NetworkManager__MessageBuffer::AttemptToSend()
{
Check(this);
//
// Round-robin the host queues from where the last attempt stopped and
// take the FIRST request of the first non-empty queue (per-host FIFO is
// preserved; the caller guarantees bufferSize > 0). A send that got
// through -- or died on a disconnect -- retires the request; only a
// dumped-to-avoid-blocking send keeps it for the next attempt.
//
TableIteratorOf<MessageQueue*, HostID>
queue_iterator(&messageQueueSocket);
MessageQueue
*message_queue;
MessageQueue__SendRequest
*send_request;
do
{
currentQueueIndex++;
if ((int)queue_iterator.GetSize() <= currentQueueIndex)
{
currentQueueIndex = 0;
}
message_queue =
(MessageQueue *)queue_iterator.GetNthItem(currentQueueIndex);
Check(message_queue);
ChainIteratorOf<MessageQueue__SendRequest*>
request_iterator(&message_queue->sendRequestSocket);
send_request = request_iterator.GetCurrent();
}
while (send_request == NULL);
if (
networkManager->SendMessageToNetnub(
send_request->messageToSend,
send_request->clientID,
message_queue->hostID)
)
{
Unregister_Object(send_request);
delete send_request;
bufferSize--;
}
}