The camera HUD only reads the slots it filled

A Live Cam host launched into a real race took the process down with an
access violation. WER put the fault at image offset 0x76239, which with
this build's fixed base resolves against the shipped PDB to
CameraShipHUDRenderable::Execute - the dereference in the ranking loop.

Two defects, both older than Live Cam. playerRank is SPARSE: it is sized
for the racing players plus the camera players and filled by
playerBitmapIndex - 1, exactly as the constructor's own comment says. But
it was allocated with new[], which does not zero, and Execute walked it
densely to playerCount and dereferenced every slot. Any bitmap index that
nobody claimed was therefore uninitialised heap read as an int*.

Live Cam is simply the first thing that can leave a gap at the front. The
host takes bitmap index 1 and, being the camera, is not in the "Players"
group, so slot 0 was never claimed while playerCount was still 1 and the
loop still ran. The arcade never hit it because its camera cabinets sat
after the pods in the egg, so slot 0 belonged to a real racer and the
dense walk was accidentally safe.

So: clear the array on allocation, remember its real length as rankCount,
and iterate that while skipping the unclaimed slots. The playerCount
bound was wrong on its own terms too - a race whose bitmap indices run
past it would have missed the tail.

Everything before the crash worked on the first try, which is the other
half of the news: with a live racing peer the camera director builds, the
camera ship comes up and starts directing. The map-load hang that stopped
a camera host before was an artefact of it having no peers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-10 15:34:22 -05:00
co-authored by Claude Opus 5
parent 1a5a4a220c
commit aa2ca0376a
2 changed files with 33 additions and 1 deletions
+20 -1
View File
@@ -2805,6 +2805,7 @@ CameraShipHUDRenderable::CameraShipHUDRenderable(Entity *entity, ExecutionType e
//
playerRank = NULL;
oldPlayerRank = NULL;
rankCount = 0;
nameDCS = NULL;
rankDCS = NULL;
nameInstance = NULL;
@@ -2848,6 +2849,18 @@ CameraShipHUDRenderable::CameraShipHUDRenderable(Entity *entity, ExecutionType e
Verify(camera_player_count <= MAX_PLAYER_NAMES);
playerRank = new (int (*[camera_player_count]));
Register_Pointer(playerRank);
//
// Cleared, because the fill below is by bitmap index and leaves
// gaps: with a Live Cam host the racing players start at index 2
// and nothing ever claims slot 0. new[] does not zero, so those
// gaps were uninitialised heap read as int* - the access
// violation in Execute.
//
rankCount = camera_player_count;
for (int slot = 0; slot < rankCount; ++slot)
{
playerRank[slot] = NULL;
}
Player *active_player;
if(player_group)
{
@@ -3095,8 +3108,14 @@ void CameraShipHUDRenderable::Execute()
// whenever they change!
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
for(int ii=0; ii<playerCount; ++ii)
// Over the whole array, not playerCount, and skipping the slots no
// bitmap index claimed - see rankCount in the header.
for(int ii=0; ii<rankCount; ++ii)
{
if (playerRank[ii] == NULL)
{
continue;
}
if (oldPlayerRank[ii] != (*playerRank[ii]))
{
oldPlayerRank[ii] = *playerRank[ii];
+13
View File
@@ -846,6 +846,19 @@ class CameraShipHUDRenderable :
int
playerCount;
//
// How long playerRank/oldPlayerRank actually are. NOT playerCount:
// the arrays are keyed by playerBitmapIndex, which a CameraShip
// player takes a slot in too, so they are sized for the racing
// players plus the camera players and are SPARSE - a slot whose
// bitmap index nobody claimed stays NULL. Walking them to
// playerCount instead read past the last claimed slot, which is
// how a Live Cam host (bitmap index 1, and not in "Players") took
// the whole game down.
//
int
rankCount;
int
oldFollowedPlayerIndex,
*followedPlayerIndex;