Files
RP412/MUNGA/NETWORK.cpp
T
CydandClaude Opus 5 06d4c91dad Playback plays
Two faults, and playback runs.

The spooler task was reading before the header came off the stream. The
header's length depends on the egg's host count, so it can only be taken
off once the mission exists - which is
L4PlaybackNetworkManager::StartConnecting - and until then the cursor sits
on the header. The task parsed it as packet one: our application ID is 0,
which is NetworkManagerClientID exactly, and the two host IDs behind it
read as a message length of 1 and a message ID of 3. Message 3 on the
network manager is ReceiveEggFile, so it built a Mission from the header
and died inside it. The task now waits for SpoolHeaderConsumed, set only
after the header has been read AND passed its sanity check, so a spool that
does not add up is never played at all.

The arcade's WaitingForEgg case, which replays an egg carried in the spool
as network manager packets, cannot work with this format in any case:
reading those packets means passing the header first, and passing the
header means already knowing the egg. A recording made by this build keeps
its egg beside it, so there is no such circle.

And last.egg had three other writers, not one. Every network manager dumps
the egg it loaded to that name as a debug aid -
networkEggNotationFile->WriteFile - and it writes a notation image, 101,920
bytes of it, straight over the 9,470 byte text egg saved with the
recording. Playback then read that as its egg, found no map entry, and
Mission::Mission carried on past its own PostQuitMessage with an
uninitialised map name to dereference a NULL resource. All three dumps are
last-loaded.egg now, and last.egg belongs to the recording alone.

Worth noting the copy was never wrong: SPOOLS\<stamp>.egg is byte-identical
to the frontend.egg it came from. Only the last.egg convenience copy was
being overwritten, which is the sort of thing that looks like a corrupt
recording and is not.

Where it stands: -pb loads the spool, takes the egg saved beside it, lays
the cockpit out as the Live Cam it was recorded from, consumes the header,
and dispatches the race's packets to the interest manager with sane
lengths, without crashing. Whether the pods MOVE on screen is the next
thing to look at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:18:45 -05:00

473 lines
12 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "hostmgr.h"
#include "interest.h"
#include "icom.h"
#include "app.h"
#include "spooler.h"
#include "notation.h"
#include "nttmgr.h"
#if defined(TRACE_ROUTE_PACKET)
static BitTrace Route_Packet("Route Packet");
# define SET_ROUTE_PACKET() Route_Packet.Set()
# define CLEAR_ROUTE_PACKET() Route_Packet.Clear()
#else
# define SET_ROUTE_PACKET()
# define CLEAR_ROUTE_PACKET()
#endif
const NetworkAddress NullNetworkAddress = 0x00000000;// 0.0.0.0 (in TCP land);
//#############################################################################
// Shared Data Support
//
NetworkClient::SharedData
NetworkClient::DefaultData(
NetworkClient::GetClassDerivations(),
NetworkClient::GetMessageHandlers()
);
//
//#############################################################################
// Code for the network client class
//#############################################################################
//
Derivation* NetworkClient::GetClassDerivations()
{
static Derivation classDerivations(Receiver::GetClassDerivations(), "NetworkClient");
return &classDerivations;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constructor for the NetworkClient
//
NetworkClient::NetworkClient(
ClassID class_ID,
SharedData &virtual_data,
ClientID client_ID
):
Receiver(class_ID, virtual_data)
{
clientID = client_ID;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Destructor for the NetworkClient
//
NetworkClient::~NetworkClient()
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Destructor for the NetworkClient
//
Logical
NetworkClient::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Receive packet for the NetworkClient
//
void
NetworkClient::ReceiveNetworkPacket(
NetworkPacket *packet,
Receiver::Message *packet_message
)
{
//
// The recording tee.
//
// Every network client comes through here - the interest manager
// carrying entity updates and the network manager carrying mission
// control - which are the two places the review build spools
// separately. One hook covers both, and it sits before Dispatch so a
// packet is kept whether or not anything downstream makes use of it.
//
// The recorder arms itself on the first packet rather than at the
// green light: playback rebuilds the world from the LoadMission and
// RunMission packets, so a spool that starts at the flag cannot be
// replayed.
//
if (Application::IsRecording())
{
SpoolRecorder *recorder = SpoolRecorder_Get();
if (!recorder->IsArmed())
{
if (!recorder->Arm())
{
//
// Could not reserve the buffer - say so once, by turning
// the request off, rather than asking again every packet.
//
Application::SetRecording(False);
}
}
recorder->Record(packet);
}
Dispatch(packet_message);
}
//
//#############################################################################
// Code for the network manager class
//#############################################################################
const Receiver::HandlerEntry
NetworkManager::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(NetworkManager,ReceiveEggFile)
};
Receiver::MessageHandlerSet& NetworkManager::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(NetworkManager::MessageHandlerEntries), NetworkManager::MessageHandlerEntries, NetworkClient::GetMessageHandlers());
return messageHandlers;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
NetworkManager::SharedData
NetworkManager::DefaultData(
NetworkManager::GetClassDerivations(),
NetworkManager::GetMessageHandlers()
);
Derivation* NetworkManager::GetClassDerivations()
{
static Derivation classDerivations(NetworkClient::GetClassDerivations(), "NetworkManager");
return &classDerivations;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constructor for the NetworkManager
//
NetworkManager::NetworkManager(SharedData &shared_data):
NetworkClient(NetworkManagerClassID, shared_data, NetworkManagerClientID)
{
// basic init goes here
gameID = 0;
addresses = NULL;
num_addresses = 0;
eggTempBuffer = NULL;
networkEggNotationFile = NULL;
eggTempNext = 0;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Creates a host for us. Any derived class must deal with reading the
// notation file to get the network nodes to connect to. This start is
// designed for STAND-ALONE MODE ONLY and should not be inherited for use by
//
void
NetworkManager::StartConnecting(Mission *)
{
Check(this);
//
//---------------------------------------------------------
// Make the local host, and get the application to adopt it
//---------------------------------------------------------
//
SOCKADDR_IN address;
address.sin_family = AF_INET;
address.sin_addr.S_un.S_addr = 1;
Host *local_host = new Host(1, GameMachineHostType, &address);
Register_Object(local_host);
Check(application);
Check(application->GetHostManager());
application->GetHostManager()->AdoptLocalHost(local_host);
//
//--------------------------------------------------------------------
// 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);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::EndMission This routine is called when a mission ends to
// allow the network management system to do stuff (like disconnecting network
// connections between pods and so on)
//
Logical
NetworkManager::Shutdown()
{
//
// If there is a notation file floating about, kill it!
//
if (networkEggNotationFile)
{
Unregister_Object(networkEggNotationFile);
delete networkEggNotationFile;
}
//
// Deregister local host
//
Host *local_host;
local_host = application->GetHostManager()->OrphanLocalHost();
Unregister_Object(local_host);
delete local_host;
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Destructor for the NetworkManager
//
NetworkManager::~NetworkManager()
{
// shutdown net goes here
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::ReceiveEggFileMessageHandler
//
void
NetworkManager::ReceiveEggFileMessageHandler(
ReceiveEggFileMessage* EggMessage
)
{
//
// 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-loaded.egg");
//
// 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;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::Send Handles sending a message to a specific network address
// which can NOT be us. Default behavior is to do nothing, as no network is
// hooked up if we get here
//
void
NetworkManager::Send(
Message *,
ClientID,
HostID
)
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::Broadcast Handles broadcasting a message to everyone,
// including ourselves. !!!! Reliable broadcasting is currently used,
// implimented by sending a point-to-point message to every destination.
//
void
NetworkManager::Broadcast(
Message *message,
ClientID client_id
)
{
//
// Send this message back to ourselves
//
NetworkClient *client = GetNetworkClientPointer(client_id);
Check(client);
client->ReceiveNetworkPacket(NULL, message);
//
// Broadcast the message to everyone else on the network
//
ExclusiveBroadcast(message,client_id);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::ExclusiveBroadcast Broadcasts a message to everyone on the
// network execpt us. Default behavior sends it to nobody, as there is really
// no network
//
void
NetworkManager::ExclusiveBroadcast(
Message *,
ClientID
)
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::GetNetworkClientPointer Determine who the client of a
// message is and return a pointer to them.
//
NetworkClient*
NetworkManager::GetNetworkClientPointer(ClientID client_id)
{
switch (client_id)
{
case NetworkClient::NetworkManagerClientID:
return this;
case NetworkClient::EntityManagerClientID:
Check(application);
return application->GetEntityManager();
case NetworkClient::HostManagerClientID:
Check(application);
return application->GetHostManager();
case NetworkClient::InterestManagerClientID:
Check(application);
return application->GetInterestManager();
case NetworkClient::IcomManagerClientID:
Check(application);
return application->GetIntercomManager();
case NetworkClient::ApplicationClientID:
Check(application);
return application;
default:
DEBUG_STREAM << "------UNKNOWN NETWORK CLIENT ID " << client_id << std::endl << std::flush;
Fail("Unknown Client ID");
break;
}
return NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::RoutePacket
//
Logical
NetworkManager::RoutePacket()
{
Check(this);
Byte
message_buffer[NETWORKMANAGER_BUFFER_SIZE];
NetworkPacket
*p;
//
// Return if check buffers doesn't have a packet for us
//
if (!CheckBuffers((NetworkPacket*)message_buffer))
{
return False;
}
//
// Process and route the packet properly
//
p = (NetworkPacket*)message_buffer;
if (p->gameID == gameID)
{
// cout<<"++++++++client "<<p->clientID<<" gameID "<<p->gameID<<" fromHost "<<p->fromHost<<"\n";
NetworkClient *client = GetNetworkClientPointer(p->clientID);
Check(client);
SET_ROUTE_PACKET();
client->ReceiveNetworkPacket(p, &p->messageData);
CLEAR_ROUTE_PACKET();
}
else
{
DEBUG_STREAM << "+++++++++ client " << p->clientID << " gameID " << p->gameID << " fromHost " << p->fromHost << std::endl << std::flush;
Fail("######### NOT A PACKET FOR THIS GAME!");
}
RemovePacket(p);
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::ExecuteBackground Allows the network to perform tasks in
// the applications background. The method should return True if the manager
// is busy, false otherwise. For example, if the input and output buffers of
// the network driver are not empty then return True, otherwise return False.
//
Logical
NetworkManager::ExecuteBackground()
{
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::CheckBuffers Checks to see if there is a message for us
// buffered up in the network interface card and returns a pointer to it.
//
Logical
NetworkManager::CheckBuffers(NetworkPacket*)
{
Fail("NetworkManager::CheckBuffers - should never reach here!");
return False;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NetworkManager::RemovePacket If the network implimentation requires us to
// free up a packet buffer, this routine would do it.
//
void
NetworkManager::RemovePacket(NetworkPacket*)
{
Fail("NetworkManager::RemovePacket - should never reach here!");
}