Skip to content

Commit eea0e7e

Browse files
authored
[GEN][ZH] Fix crash in OpenContain::processDamageToContained() when killed occupants kill the host container (#1019)
1 parent e8b55f5 commit eea0e7e

File tree

2 files changed

+158
-18
lines changed

2 files changed

+158
-18
lines changed

Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,20 +1274,17 @@ void OpenContain::orderAllPassengersToExit( CommandSourceType commandSource )
12741274
//-------------------------------------------------------------------------------------------------
12751275
void OpenContain::processDamageToContained()
12761276
{
1277+
#if RETAIL_COMPATIBLE_CRC
1278+
12771279
const ContainedItemsList* items = getContainedItemsList();
12781280
if( items )
12791281
{
1280-
ContainedItemsList::const_iterator it;
1281-
it = items->begin();
1282+
ContainedItemsList::const_iterator it = items->begin();
1283+
const size_t listSize = items->size();
12821284

12831285
while( it != items->end() )
12841286
{
1285-
Object *object = *it;
1286-
1287-
//Advance to the next iterator before we apply the damage.
1288-
//It's possible that the damage will kill the unit and foobar
1289-
//the iterator list.
1290-
++it;
1287+
Object *object = *it++;
12911288

12921289
//Calculate the damage to be inflicted on each unit.
12931290
Real damage = object->getBodyModule()->getMaxHealth() * getOpenContainModuleData()->m_damagePercentageToUnits;
@@ -1300,9 +1297,82 @@ void OpenContain::processDamageToContained()
13001297
object->attemptDamage( &damageInfo );
13011298

13021299
if( !object->isEffectivelyDead() && getOpenContainModuleData()->m_damagePercentageToUnits == 1.0f )
1303-
object->kill(); // in case we are carrying flame proof troops we have been asked to kill
1300+
object->kill(); // in case we are carrying flame proof troops we have been asked to kill
1301+
1302+
// TheSuperHackers @info Calls to Object::attemptDamage and Object::kill will not remove
1303+
// the occupant from the host container straight away. Instead it will be removed when the
1304+
// Object deletion is finalized in a Game Logic update. This will lead to strange behavior
1305+
// where the occupant will be removed after death with a delay. This behavior cannot be
1306+
// changed without breaking retail compatibility.
1307+
1308+
// TheSuperHackers @bugfix xezon 05/06/2025 Stop iterating when the list was cleared.
1309+
// This scenario can happen if the killed occupant(s) apply deadly damage on death
1310+
// to the host container, which then attempts to remove all remaining occupants
1311+
// on the death of the host container. This is reproducible by destroying a
1312+
// GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1313+
if (listSize != items->size())
1314+
{
1315+
DEBUG_ASSERTCRASH( listSize == 0, ("List is expected empty\n") );
1316+
break;
1317+
}
1318+
}
1319+
}
1320+
1321+
#else
1322+
1323+
// TheSuperHackers @bugfix xezon 05/06/2025 Temporarily empty the m_containList
1324+
// to prevent a potential child call to catastrophically modify the m_containList.
1325+
// This scenario can happen if the killed occupant(s) apply deadly damage on death
1326+
// to the host container, which then attempts to remove all remaining occupants
1327+
// on the death of the host container. This is reproducible by destroying a
1328+
// GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1329+
1330+
// Caveat: While the m_containList is empty, it will not be possible to apply damage
1331+
// on death of a unit to another unit in the host container. If this functionality
1332+
// is desired, then this implementation needs to be revisited.
1333+
1334+
ContainedItemsList list;
1335+
m_containList.swap(list);
1336+
m_containListSize = 0;
1337+
1338+
ContainedItemsList::iterator it = list.begin();
1339+
1340+
while ( it != list.end() )
1341+
{
1342+
Object *object = *it;
1343+
1344+
DEBUG_ASSERTCRASH( object, ("Contain list must not contain NULL element\n") );
1345+
1346+
// Calculate the damage to be inflicted on each unit.
1347+
Real damage = object->getBodyModule()->getMaxHealth() * percentDamage;
1348+
1349+
DamageInfo damageInfo;
1350+
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
1351+
damageInfo.in.m_deathType = data->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL;
1352+
damageInfo.in.m_sourceID = getObject()->getID();
1353+
damageInfo.in.m_amount = damage;
1354+
object->attemptDamage( &damageInfo );
1355+
1356+
if( !object->isEffectivelyDead() && percentDamage == 1.0f )
1357+
object->kill(); // in case we are carrying flame proof troops we have been asked to kill
1358+
1359+
if ( object->isEffectivelyDead() )
1360+
{
1361+
onRemoving( object );
1362+
object->onRemovedFrom( getObject() );
1363+
it = list.erase( it );
1364+
}
1365+
else
1366+
{
1367+
++it;
13041368
}
13051369
}
1370+
1371+
// Swap the list back where it belongs.
1372+
m_containList.swap(list);
1373+
m_containListSize = (UnsignedInt)m_containList.size();
1374+
1375+
#endif // RETAIL_COMPATIBLE_CRC
13061376
}
13071377

13081378
//-------------------------------------------------------------------------------------------------

GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,20 +1452,17 @@ void OpenContain::processDamageToContained(Real percentDamage)
14521452
{
14531453
const OpenContainModuleData *data = getOpenContainModuleData();
14541454

1455+
#if RETAIL_COMPATIBLE_CRC
1456+
14551457
const ContainedItemsList* items = getContainedItemsList();
14561458
if( items )
14571459
{
1458-
ContainedItemsList::const_iterator it;
1459-
it = items->begin();
1460+
ContainedItemsList::const_iterator it = items->begin();
1461+
const size_t listSize = items->size();
14601462

14611463
while( it != items->end() )
14621464
{
1463-
Object *object = *it;
1464-
1465-
//Advance to the next iterator before we apply the damage.
1466-
//It's possible that the damage will kill the unit and foobar
1467-
//the iterator list.
1468-
++it;
1465+
Object *object = *it++;
14691466

14701467
//Calculate the damage to be inflicted on each unit.
14711468
Real damage = object->getBodyModule()->getMaxHealth() * percentDamage;
@@ -1478,9 +1475,82 @@ void OpenContain::processDamageToContained(Real percentDamage)
14781475
object->attemptDamage( &damageInfo );
14791476

14801477
if( !object->isEffectivelyDead() && percentDamage == 1.0f )
1481-
object->kill(); // in case we are carrying flame proof troops we have been asked to kill
1478+
object->kill(); // in case we are carrying flame proof troops we have been asked to kill
1479+
1480+
// TheSuperHackers @info Calls to Object::attemptDamage and Object::kill will not remove
1481+
// the occupant from the host container straight away. Instead it will be removed when the
1482+
// Object deletion is finalized in a Game Logic update. This will lead to strange behavior
1483+
// where the occupant will be removed after death with a delay. This behavior cannot be
1484+
// changed without breaking retail compatibility.
1485+
1486+
// TheSuperHackers @bugfix xezon 05/06/2025 Stop iterating when the list was cleared.
1487+
// This scenario can happen if the killed occupant(s) apply deadly damage on death
1488+
// to the host container, which then attempts to remove all remaining occupants
1489+
// on the death of the host container. This is reproducible by destroying a
1490+
// GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1491+
if (listSize != items->size())
1492+
{
1493+
DEBUG_ASSERTCRASH( listSize == 0, ("List is expected empty\n") );
1494+
break;
1495+
}
1496+
}
1497+
}
1498+
1499+
#else
1500+
1501+
// TheSuperHackers @bugfix xezon 05/06/2025 Temporarily empty the m_containList
1502+
// to prevent a potential child call to catastrophically modify the m_containList.
1503+
// This scenario can happen if the killed occupant(s) apply deadly damage on death
1504+
// to the host container, which then attempts to remove all remaining occupants
1505+
// on the death of the host container. This is reproducible by destroying a
1506+
// GLA Battle Bus with at least 2 half damaged GLA Terrorists inside.
1507+
1508+
// Caveat: While the m_containList is empty, it will not be possible to apply damage
1509+
// on death of a unit to another unit in the host container. If this functionality
1510+
// is desired, then this implementation needs to be revisited.
1511+
1512+
ContainedItemsList list;
1513+
m_containList.swap(list);
1514+
m_containListSize = 0;
1515+
1516+
ContainedItemsList::iterator it = list.begin();
1517+
1518+
while ( it != list.end() )
1519+
{
1520+
Object *object = *it;
1521+
1522+
DEBUG_ASSERTCRASH( object, ("Contain list must not contain NULL element\n") );
1523+
1524+
// Calculate the damage to be inflicted on each unit.
1525+
Real damage = object->getBodyModule()->getMaxHealth() * percentDamage;
1526+
1527+
DamageInfo damageInfo;
1528+
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
1529+
damageInfo.in.m_deathType = data->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL;
1530+
damageInfo.in.m_sourceID = getObject()->getID();
1531+
damageInfo.in.m_amount = damage;
1532+
object->attemptDamage( &damageInfo );
1533+
1534+
if( !object->isEffectivelyDead() && percentDamage == 1.0f )
1535+
object->kill(); // in case we are carrying flame proof troops we have been asked to kill
1536+
1537+
if ( object->isEffectivelyDead() )
1538+
{
1539+
onRemoving( object );
1540+
object->onRemovedFrom( getObject() );
1541+
it = list.erase( it );
1542+
}
1543+
else
1544+
{
1545+
++it;
14821546
}
14831547
}
1548+
1549+
// Swap the list back where it belongs.
1550+
m_containList.swap(list);
1551+
m_containListSize = (UnsignedInt)m_containList.size();
1552+
1553+
#endif // RETAIL_COMPATIBLE_CRC
14841554
}
14851555

14861556
//-------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)