Files
RP412/RP_L4/RPL4PB.cpp
T
CydandClaude Opus 5 d00e03ae20 Playback reads its own header as the first packet
The heap corruption is explained, and it was never a memory bug - it was a
mis-parse that corrupted the heap downstream.

One line of instrumentation in DispatchPacket did it:

    Playback: packet client=0 message=3 length=1 -> the NETWORK MANAGER

Against the spool's header - appID 0, major 3, (remote 0, id 2),
(remote 1, id 3) - every field lines up:

    clientID      0   <- appID
    gameID        3   <- major version
    fromHost      0   <- host 1 remote
    timeStamp     2   <- host 1 id
    messageLength 1   <- host 2 remote
    messageID     3   <- host 2 id

So the spooler task is parsing the 24 byte header as packet one. Client 0
message 3 is ReceiveEggFile, which builds a Mission - a SECOND one, from a
packet - and that is the crash in Mission::Mission.

The cursor is at byte zero because L4PlaybackNetworkManager::StartConnecting,
which is what consumes the header, had not run yet. Its own log line for a
camera station never appeared, and Application::CreateMission shows up in
the stack UNDER SpoolerTask::Execute rather than under the egg message
posted from the constructor. The task begins dispatching before the mission
exists, and nothing stops it.

That also explains why the wrong-egg guard stayed silent: it inspects the
first packet in StartConnecting, which is correct when it runs, and it
simply never got the chance.

Left in: DispatchPacket names the client ID, message ID, length and the
client each packet resolved to, for the first two dozen packets. Client IDs
and message IDs are both small integers counted from the same base -
NetworkClient::NextMessageID is 3, so the interest manager's
NewDynamicEntity and the network manager's ReceiveEggFile are BOTH message
3 - which is exactly why a mis-routed packet looked valid to whoever
received it. A NULL client is now returned from rather than called through,
since Check() is a no-op in release.

Next: the spooler task must not dispatch until the mission is loaded and
the header consumed.

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

620 lines
17 KiB
C++

#include "rpl4.h"
#pragma hdrstop
#include "rpl4pb.h"
#include "..\munga_l4\l4video.h"
#include "..\munga\mission.h"
#include "..\munga_l4\l4net.h"
#include "..\munga\update.h"
#include "..\munga\renderer.h"
#include "..\munga\cammppr.h"
#include "..\munga\icom.h"
#include "..\munga\nttmgr.h"
#include "..\rp\rpplayer.h"
#include "..\munga\dropzone.h"
#include "..\rp\vtv.h"
#include "..\munga\director.h"
// RB 1/14/07
//#include <dpl\matrix.h>
//#############################################################################
//########################## RPSpoolerTask ##############################
//#############################################################################
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RPSpoolerTask::RPSpoolerTask()
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RPSpoolerTask::DispatchPacket(NetworkPacket *packet)
{
RPL4PlaybackApplication *playback_app =
Cast_Object(RPL4PlaybackApplication*,application);
playback_app->DispatchPacket(packet);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
long
RPSpoolerTask::GetTimeBias()
{
RPL4PlaybackApplication *playback_app =
Cast_Object(RPL4PlaybackApplication*,application);
Check_Fpu();
return playback_app->GetTimeBias();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RPSpoolerTask::~RPSpoolerTask()
{
Check_Fpu();
}
RPL4IdleApplication::RPL4IdleApplication(HINSTANCE hInstance, HWND hWnd, ResourceFile *resource)
:RPL4Application(hInstance, hWnd, resource)
{
}
RPL4IdleApplication::~RPL4IdleApplication()
{
}
NetworkManager *RPL4IdleApplication::MakeNetworkManager()
{
Check_Fpu();
return new L4PlaybackNetworkManager;
}
Registry *RPL4IdleApplication::MakeRegistry()
{
return NULL;
}
void RPL4IdleApplication::LoadBackgroundTasks()
{
Check(this);
ApplicationTask *application_task;
application_task = new GaugeRendererTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
}
Logical RPL4IdleApplication::ExecuteForeground(Time start_of_frame, Scalar frame_duration)
{
RPL4Application::ExecuteForeground(start_of_frame, frame_duration);
return 0;
}
Logical RPL4IdleApplication::Shutdown(int remainingApps)
{
if (remainingApps > 1)
{
return 1;
} else
{
RPL4Application::Shutdown(remainingApps);
return 0;
}
}
void
RPL4IdleApplication::Initialize()
{
Check(this);
L4Application::InitializeTillConsole();
}
//#############################################################################
//####################### RPL4PlaybackApplication #######################
//#############################################################################
const Receiver::HandlerEntry
RPL4PlaybackApplication::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(RPL4PlaybackApplication, RunMission),
MESSAGE_ENTRY(RPL4PlaybackApplication, StopMission)
};
Receiver::MessageHandlerSet& RPL4PlaybackApplication::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(RPL4PlaybackApplication::MessageHandlerEntries), RPL4PlaybackApplication::MessageHandlerEntries, RPL4Application::GetMessageHandlers());
return messageHandlers;
}
Derivation* RPL4PlaybackApplication::GetClassDerivations()
{
static Derivation classDerivations(RPL4Application::GetClassDerivations(), "RPL4PlaybackApplication");
return &classDerivations;
}
RPL4PlaybackApplication::SharedData
RPL4PlaybackApplication::DefaultData(
RPL4PlaybackApplication::GetClassDerivations(),
RPL4PlaybackApplication::GetMessageHandlers()
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RPL4PlaybackApplication::RPL4PlaybackApplication(
HINSTANCE hInstance,
HWND hWnd,
ResourceFile *resource_file,
SpoolFile *spool_file
):
RPL4Application(hInstance, hWnd, resource_file, L4ApplicationClassID, DefaultData)
{
Check(spool_file);
spoolFile = spool_file;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
NetworkManager*
RPL4PlaybackApplication::MakeNetworkManager()
{
Check_Fpu();
return new L4PlaybackNetworkManager;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RPL4PlaybackApplication::Initialize()
{
Check(this);
L4Application::InitializeTillConsole();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RPL4PlaybackApplication::LoadBackgroundTasks()
{
Check(this);
//
//--------------------------------------------------------------------------
// Add background tasks
//--------------------------------------------------------------------------
//
ApplicationTask *application_task;
application_task = new RPSpoolerTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new ProcessEventTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new GaugeRendererTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new CompleteCyclesTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new FryDeathRowTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new AudioRendererTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
RPL4PlaybackApplication::ExecuteForeground(
Time start_of_frame,
Scalar frame_duration
)
{
SET_FOREGROUND_PROCESSING();
Check(this);
Verify(application == this);
if (!executeFrames || GetApplicationState() == WaitingForEgg)
{
videoRenderer->ExecuteIdle();
CLEAR_FOREGROUND_PROCESSING();
return executeFrames && !Exit_Code;
}
Time
frame_ticks;
frame_ticks = frame_duration;
if (RPL4Application::GetMissionReviewMode() == 2)
{
//
//--------------------------------------------------------------------------
// Controls Manager
//
// Poll all devices, update all control variables.
//
// This is executed before the update manager so that the
// models have valid control values. It is not necessary for
// the controls to operate at the frame rate of this loop. If
// the controls manager can run run at a lower rate it can
// throttle itself internally.
//--------------------------------------------------------------------------
//
Check(controlsManager);
controlsManager->Execute();
}
//
//--------------------------------------------------------------------------
// Update Manager
//
// Execute replicants.
// Execute master entities if they are interesting here or
// elsewhere.
// Inform interest manager of possible interest zone change.
// If the master entity has provided and update message then
// send it to replicants.
//
// This is executed before the interest manager so that the
// interest manager can merge all interest zone changes before
// broadcasting interest arena deltas and building interesting
// entity lists.
//
// This is executed before the renderers so that the watchers
// have executed on the model and are ready for use by the
// renderers.
//--------------------------------------------------------------------------
//
SET_UPDATE_MANAGER();
Check(updateManager);
updateManager->Execute(start_of_frame);
CLEAR_UPDATE_MANAGER();
//
//--------------------------------------------------------------------------
// Interest Manager
//
// Update the net interest arena.
// Calculate which interest zones should be released and which
// interest zones should be loaded
// Send a becoming uninteresting and becoming interesting
// message to the entity.
// Broadcast the interest arena change
//
// The renderer manager call, update interest origins will
// update only those interest origins required by the renderers
// executing this frame.
//
// This is executed before the renderer manager so that when
// the renderers execute they execute upon interest lists that
// are valid as of the end of a model/update frame.
//
// In theory the interest manager does not have to run every
// frame. If throttled less, this would cause fuzz at interest
// zone borders, which may or may not be acceptable depending
// on the size of the interest arena.
//--------------------------------------------------------------------------
//
Check(rendererManager);
rendererManager->UpdateInterestOrigins(start_of_frame);
Check(interestManager);
interestManager->Execute();
//
//--------------------------------------------------------------------------
// Renderer Manager
//
// Execute renderers.
// Get interest list from interest manager.
// Poll watchers.
//
// It is not necessary for all renderers to operate at the frame
// rate of this loop. If a renderer can run at a lower rate it
// does so via the renderer manager which will govern if a
// renderer executes this frame.
//--------------------------------------------------------------------------
//
SET_RENDERER_MANAGER();
Check(rendererManager);
rendererManager->Execute(start_of_frame, frame_ticks, frame_ticks);
CLEAR_RENDERER_MANAGER();
//
//--------------------------------------------------------------------------
// Intercom Manager
//--------------------------------------------------------------------------
//
Check(intercomManager);
intercomManager->Execute();
//
//--------------------------------------------------------------------------
// Execution statistics
//--------------------------------------------------------------------------
//
if (GetApplicationState() == RunningMission)
{
// same rule as Application::ExecuteForeground - the console's
// countdown when there is one, our own reckoning otherwise. There
// is no console in mission review, so this takes the fallback.
Scalar console_remaining;
if (gMissionClockHook != NULL &&
(*gMissionClockHook)(&console_remaining))
{
secondsRemainingInGame = console_remaining;
}
else
{
secondsRemainingInGame =
currentMission->GetGameLength() - (Now() - gameStarted);
}
}
CLEAR_FOREGROUND_PROCESSING();
Check_Fpu();
return executeFrames && !Exit_Code;
}
//
//#############################################################################
// ExecuteBackgroundTask
//#############################################################################
//
void
RPL4PlaybackApplication::ExecuteBackgroundTask()
{
Check(this);
//
// If there exists a high priority event, execute it
//
if (ProcessOneEvent(HighEventPriority))
{
return;
}
//
// Execute lower priority tasks
//
Check(backgroundTasks);
backgroundTasks->Execute();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RPL4PlaybackApplication::~RPL4PlaybackApplication()
{
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RPL4PlaybackApplication::DispatchPacket(NetworkPacket *packet)
{
Check(this);
NetworkManager *net_mgr = GetNetworkManager();
Check(net_mgr);
NetworkClient *client = net_mgr->GetNetworkClientPointer(packet->clientID);
//
// Say what is being routed where, for the first few.
//
// Playback dies inside Mission::Mission, reached through the egg file
// handler, while dispatching spooled packets - so a packet is arriving
// at a client that is not the one it was recorded for. Client IDs and
// message IDs are both small integers counted from the same base
// (NetworkClient::NextMessageID is 3, so the interest manager's
// NewDynamicEntity and the network manager's ReceiveEggFile are BOTH
// message 3), which makes a mis-routed packet look perfectly valid to
// whoever receives it. Naming the pair and the client it resolved to
// ends the guessing.
//
// A NULL client is the other candidate: Check() is a no-op in a
// release build, so a missing interest manager would be a call through
// nothing rather than a complaint.
//
{
static int said = 0;
if (said < 24)
{
++said;
DEBUG_STREAM << "Playback: packet client=" << (int) packet->clientID
<< " message=" << (int) packet->messageData.messageID
<< " length=" << (int) packet->messageData.messageLength
<< " -> client " << (void *) client
<< (client == NULL ? " (NULL!)" : "")
<< (client == (NetworkClient *) net_mgr ? " = the NETWORK MANAGER" : "")
<< (client == (NetworkClient *) this ? " = the application" : "")
<< (client == (NetworkClient *) GetInterestManager()
? " = the interest manager" : "")
<< "\n" << std::flush;
}
}
if (client == NULL)
{
return;
}
Check(client);
client->ReceiveNetworkPacket(packet, &packet->messageData);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RPL4PlaybackApplication::RunMissionMessageHandler(
RunMissionMessage *message
)
{
Check(this);
//
//------------------------------------------------------------------------
// If the application is waiting to launch, synchronize the clock stuff up
//------------------------------------------------------------------------
//
if (GetApplicationState() == WaitingForLaunch)
{
SpoolFile *spool = GetSpoolFile();
Check(spool);
NetworkPacket *packet = (NetworkPacket*)spool->GetPointer();
Verify(packet->messageData.messageID == RunMissionMessageID);
timeBias = Now().ticks - packet->timeStamp.ticks;
spool->NextPacket();
}
RPL4Application::RunMissionMessageHandler(message);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
char winners_spot[]="win?";
void
RPL4PlaybackApplication::StopMissionMessageHandler(
StopMissionMessage *message
)
{
//STUBBED: DPL RB 1/14/07
MissionReviewApplicationManager *app_mgr = GetApplicationManager();
Check(app_mgr);
SpoolFile *spool = GetSpoolFile();
if (spool)
{
Check(spool);
app_mgr->ReleaseSpoolFile(spool);
spoolFile = NULL;
//
//------------------------
// Find the dropzone group
//------------------------
//
EntityManager *entity_manager = application->GetEntityManager();
Check(entity_manager);
EntityGroup *dropzones = entity_manager->FindGroup("DropZones");
Check(dropzones);
char *place = winners_spot+3;
Player *p;
//
//----------------------------------------------------------------------
// Move all the players into their final positions in the winners circle
//----------------------------------------------------------------------
//
for (
int rank=0;
(p = CameraDirector::FindPlayerByRank(rank)) != NULL;
++rank
)
{
//
//-----------------------------------------------------------------
// Now find the dropzone appropriate to the rank. If one cannot be
// found, ignore this vehicle
//-----------------------------------------------------------------
//
Verify(rank<10);
*place = (char)('1' + rank);
ChainIteratorOf<Node*> iterator(dropzones->groupMembers);
DropZone *dropzone;
while ((dropzone = (DropZone*)iterator.ReadAndNext()) != NULL)
{
if (!strcmp(dropzone->GetDropZoneName(), winners_spot))
{
break;
}
}
if (!dropzone)
{
continue;
}
//
//---------------------------------------------------------------------
// Get the location of this dropzone, and reset the vehicle to place it
// where specified
//---------------------------------------------------------------------
//
VTV *vtv = (VTV*)p->GetPlayerVehicle();
if (vtv->GetClassID() == VTVClassID)
{
vtv->Reset(dropzone->localOrigin, VTV::MissionReviewReset);
vtv->SetPerformance(&VTV::DoNothing);
vtv->FlushEvents();
}
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Reload the NameBitmaps for the Winners Circle
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DPLRenderer *dpl_renderer = GetVideoRenderer();
Check(dpl_renderer);
dpl_renderer->SortAndReloadNameBitmaps();
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Set the Viewing Angle to 45 Degrees for Winners Circle
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
dpl_renderer->SetViewAngle(Degree(45.0f));
//
//~~~~~~~~~~~~~~~~~~~~~~~
// Set the Lighting Angle
//~~~~~~~~~~~~~~~~~~~~~~~
//
for(int ii=0;ii<dpl_renderer->sceneLightCount;++ii)
{
//dpl_RotateDCS(dpl_renderer->sceneLightDCS[ii], 0.0, dpl_Z);
//dpl_RotateDCS(dpl_renderer->sceneLightDCS[ii], 150.0, dpl_Y);
//dpl_RotateDCS(dpl_renderer->sceneLightDCS[ii], -30.0, dpl_X);
//dpl_FlushDCS (dpl_renderer->sceneLightDCS[ii]);
}
}
//
//---------------------------------------
// Let the application stop regularly now
//---------------------------------------
//
RPL4Application::StopMissionMessageHandler(message);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
RPL4PlaybackApplication::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}