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>
1816 lines
52 KiB
C++
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, ®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<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--;
|
|
}
|
|
}
|