From b01db52d43c5bc001f3ea38fd58b09a5cc0a0a45 Mon Sep 17 00:00:00 2001 From: Cyd Date: Tue, 4 Aug 2026 10:05:13 -0500 Subject: [PATCH] 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 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 --- restoration/source410/MUNGA_L4/L4NET.CPP | 2562 ++++++++++++----- restoration/source410/MUNGA_L4/L4NET.NOTES.md | 87 + 2 files changed, 1902 insertions(+), 747 deletions(-) create mode 100644 restoration/source410/MUNGA_L4/L4NET.NOTES.md diff --git a/restoration/source410/MUNGA_L4/L4NET.CPP b/restoration/source410/MUNGA_L4/L4NET.CPP index dac9482d..0275618e 100644 --- a/restoration/source410/MUNGA_L4/L4NET.CPP +++ b/restoration/source410/MUNGA_L4/L4NET.CPP @@ -1,747 +1,1815 @@ -//===========================================================================// -// 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 -#pragma hdrstop - -#if !defined(L4NET_HPP) -# include -#endif - -#if !defined(L4APP_HPP) -# include -#endif - -#if !defined(L4HOST_HPP) -# include -#endif - -#if !defined(MISSION_HPP) -# include -#endif - -#if !defined(NOTATION_HPP) -# include -#endif - -// -//############################################################################# -// 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; - -void - NetNub::SendCommand() -{ - Fail("NetNub::SendCommand -- netnub interface not yet reconstructed"); -} - -// -//############################################################################# -// 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 - // - Fail("L4NetworkManager -- netnub (networked) path not yet reconstructed"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -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 - - Fail("L4NetworkManager::CreateConsoleHost -- netnub path not yet reconstructed"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// 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; - } - - Fail("L4NetworkManager::StartConnecting -- netnub path not yet reconstructed"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// 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; - } - - Fail("L4NetworkManager::Shutdown -- netnub path not yet reconstructed"); - return False; -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// L4NetworkManager::Send Sends a message to a single host. -// -void - L4NetworkManager::Send(Message *message, ClientID, HostID host_ID) -{ - Host *base_host; - L4Host *l4host; - - Check_Pointer(message); - Check(application); - Check(application->GetHostManager()); - base_host = application->GetHostManager()->GetRemoteHost(host_ID); - if (base_host == NULL) - { - return; - } - l4host = Cast_Object(L4Host*, base_host); - if (l4host->GetConnectStatus() != L4Host::OnLineConnectionStatus) - { - return; - } - - Fail("L4NetworkManager::Send -- netnub path not yet reconstructed"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// L4NetworkManager::ExclusiveBroadcast Sends a message to every online host -// except the named client. -// -void - L4NetworkManager::ExclusiveBroadcast(Message *message, ClientID) -{ - Check_Pointer(message); - Check(application); - - // - // With no hosts online there is no one to broadcast to - // - if (numberOfMungaHostsConnected == 0 && numberOfConsoleHostsConnected == 0) - { - return; - } - - Fail("L4NetworkManager::ExclusiveBroadcast -- netnub path not yet reconstructed"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// L4NetworkManager::CheckBuffers Checks to see if there is a message for us -// buffered up in the network interface card. -// -Logical - L4NetworkManager::CheckBuffers(NetworkPacket *) -{ - // - // No netnub present? Nothing can have arrived. - // - if (Net_Common_Ptr == NULL) - { - return False; - } - - Fail("L4NetworkManager::CheckBuffers -- netnub path not yet reconstructed"); - return False; -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -void - L4NetworkManager::RemovePacket(NetworkPacket *) -{ - // - // No netnub present? Just return - // - if (Net_Common_Ptr == NULL) - { - return; - } - - Fail("L4NetworkManager::RemovePacket -- netnub path not yet reconstructed"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -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 *) -{ - // - // No netnub present? Just return - // - if (Net_Common_Ptr == NULL) - { - return; - } - - Fail("L4NetworkManager::Marker -- netnub path not yet reconstructed"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// L4NetworkManager::Mode Switches the netnub between reliable and unreliable -// delivery. -// -void - L4NetworkManager::Mode(NetworkMode) -{ - // - // No netnub present? Just return - // - if (Net_Common_Ptr == NULL) - { - return; - } - - Fail("L4NetworkManager::Mode -- netnub path not yet reconstructed"); -} - -// -//############################################################################# -// 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 -- not yet reconstructed -//############################################################################# -// -unsigned long - L4NetworkManager::CheckSocket(unsigned long) -{ - Fail("L4NetworkManager::CheckSocket -- netnub path not yet reconstructed"); - return 0; -} - -unsigned long - L4NetworkManager::ResolveAddress(CString) -{ - Fail("L4NetworkManager::ResolveAddress -- netnub path not yet reconstructed"); - return 0; -} - -unsigned long - L4NetworkManager::GetMyAddress() -{ - Fail("L4NetworkManager::GetMyAddress -- netnub path not yet reconstructed"); - return 0; -} - -unsigned long - L4NetworkManager::OpenConnection(int, int, int, int) -{ - Fail("L4NetworkManager::OpenConnection -- netnub path not yet reconstructed"); - return 0; -} - -void - L4NetworkManager::CloseConnection(unsigned long) -{ - Fail("L4NetworkManager::CloseConnection -- netnub path not yet reconstructed"); -} - -Logical - L4NetworkManager::GetNextMungaPacket( - NetworkPacket *, - HostManager::RemoteHostIterator * - ) -{ - Fail("L4NetworkManager::GetNextMungaPacket -- netnub path not yet reconstructed"); - return False; -} - -Logical - L4NetworkManager::SendMessageToNetnub(Message *, ClientID, HostID) -{ - Fail("L4NetworkManager::SendMessageToNetnub -- netnub path not yet reconstructed"); - return False; -} - -void - L4NetworkManager::SendBatchedMessageToNetnub( - Message *, - ClientID, - DroppedMessageHostSocket * - ) -{ - Fail("L4NetworkManager::SendBatchedMessageToNetnub -- netnub path not yet reconstructed"); -} - -// -//############################################################################# -// L4NetworkManager__MessageBuffer -//############################################################################# -// -L4NetworkManager__MessageBuffer::L4NetworkManager__MessageBuffer( - L4NetworkManager *network_manager -): - messageQueueSocket(NULL, 1) -{ - networkManager = network_manager; - currentQueueIndex = 0; - bufferSize = 0; -} - -L4NetworkManager__MessageBuffer::~L4NetworkManager__MessageBuffer() -{ -} - -void - L4NetworkManager__MessageBuffer::AddSendRequest( - HostID, - NetworkClient::ClientID, - Receiver::Message * - ) -{ - Fail("L4NetworkManager__MessageBuffer::AddSendRequest -- netnub path not yet reconstructed"); -} - -void - L4NetworkManager__MessageBuffer::AttemptToSend() -{ - Fail("L4NetworkManager__MessageBuffer::AttemptToSend -- netnub path not yet reconstructed"); -} +//===========================================================================// +// 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 +#pragma hdrstop + +#if !defined(L4NET_HPP) +# include +#endif + +#if !defined(L4APP_HPP) +# include +#endif + +#if !defined(L4HOST_HPP) +# include +#endif + +#if !defined(MISSION_HPP) +# include +#endif + +#if !defined(NOTATION_HPP) +# include +#endif + +#include // 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, ®s, ®s, &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 + 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 + 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 + 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--; + } +} diff --git a/restoration/source410/MUNGA_L4/L4NET.NOTES.md b/restoration/source410/MUNGA_L4/L4NET.NOTES.md new file mode 100644 index 00000000..14250db2 --- /dev/null +++ b/restoration/source410/MUNGA_L4/L4NET.NOTES.md @@ -0,0 +1,87 @@ +# L4NET.CPP — the NetNub networked layer (reconstructed 2026-08-04, 5.3.117) + +The pod's entire console/mesh transport: the protected-mode client of the +real-mode NetNub TSR. Every body verified against the shipped binary +(part_007.c cluster @004501d4..@00452378); every error string extracted +byte-for-byte from BTL4OPT.EXE; the authentic interface header +(CODE/RP/MUNGA_L4/NetNub/NETNUB.HPP, NETCOM_VERSION 11) supplied every +struct and function code, and each binary literal resolved to one of its +macros (0x63c == MAX_SEND_DATA_SIZE, 0x2c == MULTIPLE_SEND_BUFFER_SIZE(0), +0x640 == MAX_RECEIVE_DATA_SIZE, 10 == the frame's destination limit). + +## The real-mode bridge + +`NetNub::SendCommand` (@00452100) is the gateway: `setRMBuff()` pushes the +PM Netcom's Function/Status/Buffer_Length + buffer down to the real-mode +shared block, DPMI int 31h/0300h fires the TSR's interrupt (vector from +`getRMNumbers()`), `getRMBuff()` pulls the reply back. The bridge routines +are NOT recompiled — the SURVIVING 1995 `netshare.OBJ` +(CODE/RP/MUNGA_L4) has been a mungal4.lib member all along via the +prebuilt-fallback (`netshare` in ORDER_mungal4); this file only had to +supply the C-side globals it imports (`Function_Ptr`, `Buffer_Length_Ptr`, +`baseadr:offs`, `RMFunction_Off`, `RMBuffer_Length_Off` — binary homes +0x522b84..0x522b98) and `extern "C"` prototypes (TASM uppercased the OBJ's +symbols; tlink32 matches caselessly — no `/c`). BC4.52's 32-bit +really does have `int386x`/`segread` — the binary's own error text +("int386x():ERROR=") named the RTL call. The register image zeroes exactly +the fields the binary zeroes (reserved/flags/fs/gs/ss:sp). + +## The topology (all decomp-proven) + +- Console listens on TCP **1501**, pod mesh on TCP **1502**. +- `StartConnecting`: hosts BEFORE ours in the egg list get an active OPEN + (they already listen); our entry is recognized by address and adopted as + the local host; hosts AFTER ours get a LISTEN. That ordering rule forms + the peer-to-peer mesh without collisions. A slave then Sends + AcknowledgeEggFile to the console; MasterMode Fails ("host is in an + illegal startup mode" — 4.10 pods are always slaves). +- `CheckBuffers` (@004518b8) is the pump: deliver a whole buffered packet; + else advance ONE connection state machine per call (OPEN must match its + address — Verify; the console LISTEN is a wildcard that learns its + address; a mesh peer landing on the WRONG listen gets its socket SWAPPED + to the record that expects it — the race healer), then one + MULTIPLE_RECEIVE drains the wire into per-host pad buffers + (pad_size/pad_head/pad_tail — the surviving L4HOST.HPP names them). +- Wire frame = `NetworkPacketHeader{clientID, gameID, fromHost, timeStamp}` + + message; the netnub strips the leading socket handle. + `GetNextMungaPacket` round-robins hosts from `lastHostIteratorPosition`, + copies out one whole packet, memmove-compacts the pad. `RemovePacket` + is EMPTY in the binary (@004520f8) — delivery already dequeued. +- Send discipline: buffer empty → direct send, and only a + NETNUB_DATA_DUMPED (would-block) result queues the message; buffer + non-empty → everything queues BEHIND (per-host FIFO) + one drain attempt. + `ExecuteBackground` drains one request per idle slot. The MessageBuffer + copies each message (SendRequest owns the copy), tables queues by host, + and round-robins them. A disconnect during send posts the internal + HostDisconnected event through `GetNetworkClientPointer(0)-> + ReceiveNetworkPacket(NULL, &msg)` — the self-dispatch idiom the decomp's + `FUN_0041d7f0(this,0)` resolves to (ClientID 0 == the manager). +- `Shutdown`: game hosts close + destroy; THE CONSOLE CONNECTION SURVIVES + the mission (pod returns to the console-only lobby). The binary zeroes + BOTH connection counters even though the console stays online — + reproduced faithfully (the console's own disconnect path recreates its + listener rather than decrementing through zero). +- `Mode`: reliable == the netnub BLOCKS in send (SET_SWITCHES + blocking_switch=1); unreliable == non-blocking, dumps join the retry + buffer. The ctor forces ReliableMode; the engine flips to Unreliable + during map load. + +## Sources & drift notes + +- RP411's L4NET.CPP is the 2007 WinSock port — its 1995 NetNub code + survives only in comment blocks (used line-for-line for the ctor RM + plumbing). CODE/RP's L4NET.HPP + NETNUB.HPP + NETSHARE.ASM are + authentic-form. +- Ghidra dropped register/trailing args at several sites + (GetRemoteHost's HostID, SendMessageToNetnub's host argument, + CreateWeaponExplosions-style stack args) — the HPP signatures and + HostManager's Find-based GetRemoteHost settle each. + +## Runtime status + +Single-user path regression-clean on the rig (mission soak, no faults). +The NETWORKED path compiles + links (0 unresolved) but awaits its live +test: a console + NetNub-TSR session (net_loop-style conf) driving OUR +BTL4REC — the console handshake (egg → Ack → connections → LoadMission), +a two-pod mesh, and mission teardown back to the lobby. Two-agreeing-runs +rule applies.