gauges: colorMapperMultiArmor (worst-of-8-zones) + fix the interpreterTable overflow [widget recon 4]
Reconstructs colorMapperMultiArmor -- tints one schematic colour (a whole torso section) by the WORST of up to 8 damage zones: - MultiArmorConnection (@004c346c/@004c34f4): scans 8 zones via entity->damageZones[idx], keeps the max damageLevel, count==0 ? 100 : Round(worst*100). - ColorMapperMultiArmor::Make/ctor (@004c3c48/@004c3d60): resolves 8 named zones to indices via Entity::GetDamageZoneIndex (13-param methodDescription). ALSO fixes a latent HEAP CORRUPTION that registering this widget exposed (and that would have blocked every further widget): GaugeInterpreter's bytecode buffer is a fixed interpreterTableSize=46864 (GAUGREND.h) tuned for RP's config, and its Insert() bounds guard is a Verify() that compiles out at DEBUG_LEVEL 0. As each BT gauge widget is registered, more of BT's larger L4GAUGE.CFG resolves into bytecode (vs being parse-skipped); colorMapperMultiArmor's 13 params x hundreds of calls pushed the total past 46864 -> silent overflow past the char[] -> heap smash detected later in mission bitmap loading (not the gauge code). Fix: interpreterTableSize -> 262144 (sized for BT's full config). Verified: parses, builds, all zones resolve, no /FORCE unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes), stable. Details: docs/GAUGE_COMPOSITE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1d2ddd8399
commit
fb8c1d6ace
+23
-1
@@ -307,7 +307,29 @@ Ghidra dropped (`scratchpad/disas_hp.py`):
|
||||
(ColorMapperCritical, CriticalConnection @004c3598) are the same shape — easy follow-ups now the pattern
|
||||
+ the ColorMapper base facts are pinned.
|
||||
|
||||
**Next increments (priority order from the map):** `colorMapperMultiArmor`/`cmCrit` (same ColorMapper pattern), then the
|
||||
**✅ Increment 4 — `colorMapperMultiArmor` DONE — worst-of-N-zones tint.** Tints one schematic colour (a whole
|
||||
torso SECTION) by the worst of up to 8 damage zones. Reconstructed `MultiArmorConnection` (@004c346c ctor /
|
||||
@004c34f4 Transfer: scan 8 zones via `entity->damageZones[idx]`, keep max `damageLevel`, `count==0 ? 100 :
|
||||
Round(worst*100)`) + `ColorMapperMultiArmor::Make`/ctor (@004c3c48/@004c3d60 — resolves 8 zone names to
|
||||
indices via `GetDamageZoneIndex`, 13-param methodDescription). Verified: parses, builds, all zones resolve,
|
||||
combat un-regressed (TARGET DESTROYED, 0 crashes).
|
||||
|
||||
**⭐⭐ THE `interpreterTable` OVERFLOW — a latent heap-corruption fixed (every future widget needed this).**
|
||||
Registering colorMapperMultiArmor CRASHED — but not in the gauge code: a heap corruption surfaced later in
|
||||
mission bitmap loading (`ObjectNameList::AddEntry` → malloc AV). ROOT CAUSE: `GaugeInterpreter`'s bytecode
|
||||
buffer is a FIXED `interpreterTableSize = 46864` (GAUGREND.h) tuned for RP's config, and its `Insert()` bounds
|
||||
guard is a `Verify()` that COMPILES OUT at DEBUG_LEVEL 0 (the same dead-`Verify` class as the BNDGBOX/heat
|
||||
bugs). As each BT gauge widget is registered, more of BT's (larger) L4GAUGE.CFG resolves into bytecode instead
|
||||
of being parse-skipped; cmHeat+headingPointer+cmArmor got close, colorMapperMultiArmor (13 params × ~hundreds
|
||||
of calls) tipped it past 46864 → silent overflow past the `char[]` → heap smash detected at the next big alloc.
|
||||
FIX: `interpreterTableSize` → 262144 (sized for BT's full config). ⚠ This was going to block EVERY further
|
||||
widget — the table is cumulative across all registered gauges. **LESSON (add to the checklist): a heap
|
||||
corruption that surfaces in an INNOCENT later alloc, right after registering a gauge, is the interpreterTable
|
||||
overflow — the Insert bounds-`Verify` is dead at DEBUG_LEVEL 0.**
|
||||
|
||||
**Next increments (priority order from the map):** `cmCrit` (ColorMapperCritical -- needs the subsystem
|
||||
`damageZone@0xE0` shadow resolved; CriticalConnection = state==1?100:Round(subsys->damageZone->damageLevel*100)),
|
||||
then the
|
||||
`ArmorZoneConnection`/`MultiArmorConnection` classes reconstructed); then the attribute-table wave (`vertBar`/
|
||||
`segmentArcRatio` speed/`GeneratorCluster` — need `AttributePointers[]` on Mech/HeatableSubsystem, a separate
|
||||
pass); the XL items (`map`/`vehicleSubSystems`/`PlayerStatus`) last. The POD path needs the FULL 23-entry
|
||||
|
||||
@@ -206,7 +206,14 @@ protected:
|
||||
|
||||
enum
|
||||
{
|
||||
interpreterTableSize = 46864
|
||||
// The original (RP) value 46864 is too small for BattleTech's larger
|
||||
// gauge config: as more BT gauge widgets are registered, more of
|
||||
// L4GAUGE.CFG resolves into interpreter bytecode instead of being
|
||||
// parse-skipped, overflowing the fixed table. The Insert() bounds guard
|
||||
// is a Verify() that compiles out at DEBUG_LEVEL 0, so the overflow was
|
||||
// a SILENT heap corruption (detected later in mission bitmap loading).
|
||||
// Sized generously for BT's full config. (was 46864)
|
||||
interpreterTableSize = 262144
|
||||
};
|
||||
|
||||
enum
|
||||
|
||||
@@ -218,6 +218,59 @@ void
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// MultiArmorConnection (@004c346c ctor / @004c34f4 Transfer) -- drives a
|
||||
// ColorMapper's colour index from the WORST of up to 8 damage zones. The ctor
|
||||
// copies the 8 zone indices + the owner; Transfer scans them each frame and
|
||||
// feeds the maximum damage ratio. Used by ColorMapperMultiArmor.
|
||||
//
|
||||
class MultiArmorConnection : public GaugeConnection
|
||||
{
|
||||
public:
|
||||
MultiArmorConnection(int *destination, Entity *entity, const int *zone_indices)
|
||||
: GaugeConnection(0), // FUN_00444124(this,0)
|
||||
owner(entity),
|
||||
currentColorIndex(destination)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
zoneIndex[i] = zone_indices[i];
|
||||
}
|
||||
|
||||
protected:
|
||||
//
|
||||
// Per-frame feed (@004c34f4): scan the (up to 8) zones -- skipping index < 0
|
||||
// ("unused") and absent zones -- keep the WORST (max) damageLevel (@0x158),
|
||||
// scale to 0..100 and round; no zone present -> 100.
|
||||
//
|
||||
void Update(); // override
|
||||
|
||||
Entity *owner; // @0x10
|
||||
int zoneIndex[8]; // @0x18..0x34
|
||||
int *currentColorIndex; // @0x38
|
||||
};
|
||||
|
||||
void
|
||||
MultiArmorConnection::Update()
|
||||
{
|
||||
Scalar worst = 0.0f;
|
||||
int count = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (zoneIndex[i] >= 0)
|
||||
{
|
||||
DamageZone *zone = owner->damageZones[zoneIndex[i]];
|
||||
if (zone != NULL)
|
||||
{
|
||||
++count;
|
||||
if (worst < zone->damageLevel)
|
||||
worst = zone->damageLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
*currentColorIndex = (count == 0) ? 100 : HeatRound(worst * 100.0f);
|
||||
}
|
||||
|
||||
|
||||
//###########################################################################
|
||||
//###########################################################################
|
||||
// File-private GaugeConnection subclasses
|
||||
@@ -521,18 +574,112 @@ ColorMapperArmor::~ColorMapperArmor()
|
||||
// ColorMapperMultiArmor @004c3c48 Make / @004c3d60 ctor
|
||||
//###########################################################################
|
||||
//
|
||||
// @004c3c48 -- Make: probe an 8-entry table of damage-zone descriptors
|
||||
// (DAT_00514704 stride 0x44); requires at least one to resolve (else warns
|
||||
// "No Damage zones found"). Allocates (0x6c) and constructs.
|
||||
// Tints one schematic colour by the WORST of up to 8 damage zones (e.g. a whole
|
||||
// torso section). CFG shape (L4GAUGE.CFG:96):
|
||||
// colorMapperMultiArmor( rate, mode, colourSlot, paletteA, paletteB,
|
||||
// zone1, zone2, ... zone8 ) ("unused" for empty slots)
|
||||
//
|
||||
// @004c3d60 -- ctor: ColorMapper base (vtable PTR_FUN_00518d88) +
|
||||
// AddConnection(new MultiArmorConnection(¤tColorIndex, subsystem,
|
||||
// zoneIndices[8])) (connection ctor FUN_004c346c, 0x3c bytes).
|
||||
|
||||
MethodDescription
|
||||
ColorMapperMultiArmor::methodDescription =
|
||||
{
|
||||
"colorMapperMultiArmor",
|
||||
ColorMapperMultiArmor::Make,
|
||||
{
|
||||
{ ParameterDescription::typeRate, NULL }, // rate ID
|
||||
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
||||
{ ParameterDescription::typeColor, NULL }, // hardware colour slot
|
||||
{ ParameterDescription::typeString, NULL }, // palette name A
|
||||
{ ParameterDescription::typeString, NULL }, // palette name B
|
||||
{ ParameterDescription::typeString, NULL }, // zone 1
|
||||
{ ParameterDescription::typeString, NULL }, // zone 2
|
||||
{ ParameterDescription::typeString, NULL }, // zone 3
|
||||
{ ParameterDescription::typeString, NULL }, // zone 4
|
||||
{ ParameterDescription::typeString, NULL }, // zone 5
|
||||
{ ParameterDescription::typeString, NULL }, // zone 6
|
||||
{ ParameterDescription::typeString, NULL }, // zone 7
|
||||
{ ParameterDescription::typeString, NULL }, // zone 8
|
||||
PARAMETER_DESCRIPTION_END
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// MultiArmorConnection::Transfer @004c34f4:
|
||||
// scan the 8 zones; keep the worst (max) damage ratio (zone+0x158);
|
||||
// no zone present -> *destination = 100, else Round(worst).
|
||||
// @004c3c48 -- Make. Resolve up to 8 named zones to indices; build if any
|
||||
// resolves. (The binary flag test is `index != 0`, so a valid zone OR an
|
||||
// "unused"/-1 slot both count as "present" -- i.e. it always builds; the
|
||||
// Transfer simply skips index < 0.)
|
||||
//
|
||||
Logical
|
||||
ColorMapperMultiArmor::Make(
|
||||
int display_port_index,
|
||||
Vector2DOf<int> /*position*/,
|
||||
Entity *entity,
|
||||
GaugeRenderer *gauge_renderer
|
||||
)
|
||||
{
|
||||
ParameterDescription *p = methodDescription.parameterList;
|
||||
|
||||
int zone_indices[8];
|
||||
Logical any = False;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
zone_indices[i] = entity->GetDamageZoneIndex(CString(p[5 + i].data.string)); // FUN_0042076c
|
||||
if (zone_indices[i] != 0) // binary: iVar2 != 0 (quirk -- see note above)
|
||||
any = True;
|
||||
}
|
||||
if (!any)
|
||||
{
|
||||
DebugStream << "colorMapperMultiArmor: No Damage zones found\n";
|
||||
return False;
|
||||
}
|
||||
|
||||
ColorMapperMultiArmor *gauge = (ColorMapperMultiArmor *)operator new(0x6c); // FUN_00402298(0x6c)
|
||||
if (gauge != NULL)
|
||||
{
|
||||
new (gauge) ColorMapperMultiArmor( // FUN_004c3d60
|
||||
p[0].data.rate, p[1].data.modeMask,
|
||||
(L4GaugeRenderer *)gauge_renderer,
|
||||
display_port_index, // graphics port number
|
||||
p[2].data.color, // colour slot
|
||||
entity,
|
||||
p[3].data.string, // palette A
|
||||
p[4].data.string, // palette B
|
||||
zone_indices,
|
||||
"ColorMapperMultiArmor");
|
||||
}
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// @004c3d60 -- ctor: ColorMapper base + a MultiArmorConnection over the 8 zones.
|
||||
//
|
||||
ColorMapperMultiArmor::ColorMapperMultiArmor(
|
||||
GaugeRate rate,
|
||||
ModeMask mode_mask,
|
||||
L4GaugeRenderer *renderer,
|
||||
int graphics_port_number,
|
||||
int color_index,
|
||||
Entity *entity,
|
||||
const char *palette_a,
|
||||
const char *palette_b,
|
||||
const int *zone_indices,
|
||||
const char *identification_string
|
||||
):
|
||||
ColorMapper( // FUN_004c37dc
|
||||
rate, mode_mask, renderer, graphics_port_number,
|
||||
color_index, palette_a, palette_b, identification_string)
|
||||
{
|
||||
MultiArmorConnection *connection =
|
||||
(MultiArmorConnection *)operator new(0x3c); // FUN_004c346c (0x3c bytes)
|
||||
if (connection != NULL)
|
||||
new (connection) MultiArmorConnection(¤tColorIndex, entity, zone_indices);
|
||||
AddConnection(connection);
|
||||
}
|
||||
|
||||
ColorMapperMultiArmor::~ColorMapperMultiArmor()
|
||||
{
|
||||
// base-dtor chain (~ColorMapper -> ~Gauge) releases the connection + palettes.
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// ColorMapperCritical @004c3ddc Make / @004c3e40 ctor
|
||||
|
||||
@@ -165,6 +165,9 @@
|
||||
public ColorMapper
|
||||
{
|
||||
public:
|
||||
// Registered in BTL4MethodDescription[] (btl4grnd.cpp) as "colorMapperMultiArmor".
|
||||
static MethodDescription methodDescription;
|
||||
|
||||
static Logical Make(int, Vector2DOf<int>, Entity *, GaugeRenderer *); // @004c3c48
|
||||
ColorMapperMultiArmor( // @004c3d60
|
||||
GaugeRate, ModeMask, L4GaugeRenderer *, int, int,
|
||||
|
||||
@@ -132,6 +132,7 @@ MethodDescription
|
||||
&ColorMapperHeat::methodDescription, // "cmHeat" -- heat-driven palette tint
|
||||
&HeadingPointer::methodDescription, // "headingPointer" -- compass needle + heading
|
||||
&ColorMapperArmor::methodDescription, // "cmArmor" -- per-zone armor-damage tint
|
||||
&ColorMapperMultiArmor::methodDescription, // "colorMapperMultiArmor" -- worst-of-8-zones tint
|
||||
&BTL4ChainToPrevious
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user