Skip to content

Commit 756de02

Browse files
committed
Replicated in Generals.
Resolved merge conflicts in 'Object.cpp'.
1 parent d49db75 commit 756de02

2 files changed

Lines changed: 56 additions & 10 deletions

File tree

Generals/Code/GameEngine/Include/GameLogic/Object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ class Object : public Thing, public Snapshot
717717

718718
Object* m_containedBy; /**< an object can only be contained by at most one
719719
other object, this is that object (if present) */
720-
ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after
720+
ObjectID m_containedByID; ///< ID of the object we're contained by; only to be used when m_containedBy cannot be used
721721
UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy
722722

723723
Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0)

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

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu
173173
m_physics(nullptr),
174174
m_geometryInfo(tt->getTemplateGeometryInfo()),
175175
m_containedBy(nullptr),
176-
m_xferContainedByID(INVALID_ID),
176+
m_containedByID(INVALID_ID),
177177
m_containedByFrame(0),
178178
m_behaviors(nullptr),
179179
m_body(nullptr),
@@ -621,6 +621,22 @@ void Object::onContainedBy( Object *containedBy )
621621
clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
622622
m_containedBy = containedBy;
623623
m_containedByFrame = TheGameLogic->getFrame();
624+
625+
#if RETAIL_COMPATIBLE_CRC
626+
// TheSuperHackers @info Set INVALID_ID if the container object was destroyed
627+
// to indicate that the pointer will become a dangling pointer in the next frame.
628+
if (containedBy && !containedBy->isDestroyed())
629+
{
630+
m_containedByID = containedBy->getID();
631+
}
632+
else
633+
{
634+
m_containedByID = INVALID_ID;
635+
}
636+
#else
637+
DEBUG_ASSERTCRASH(containedBy == nullptr || !containedBy->isDestroyed(),
638+
("Object::onContainedBy - Adding into a destroyed container"));
639+
#endif
624640
}
625641

626642
//-------------------------------------------------------------------------------------------------
@@ -631,6 +647,10 @@ void Object::onRemovedFrom( Object *removedFrom )
631647
clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) );
632648
m_containedBy = nullptr;
633649
m_containedByFrame = 0;
650+
651+
#if RETAIL_COMPATIBLE_CRC
652+
m_containedByID = INVALID_ID;
653+
#endif
634654
}
635655

636656
//-------------------------------------------------------------------------------------------------
@@ -681,9 +701,33 @@ void Object::onDestroy()
681701
{
682702

683703
// This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up.
684-
if( m_containedBy && m_containedBy->getContain() )
704+
if (m_containedBy)
685705
{
686-
m_containedBy->getContain()->removeFromContain( this );
706+
#if RETAIL_COMPATIBLE_CRC
707+
if (m_containedByID == INVALID_ID)
708+
{
709+
// TheSuperHackers @bugfix Caball009 25/05/2026 Due to a potential use-after-free bug that cannot be fixed
710+
// with retail compatibility, the 'contained by' pointer of this object may point to an already destroyed object.
711+
// Avoid removing this object from the contain list, because it could crash the game,
712+
// as the begin / end iterator for STLPort and MSVC std::list implementations depends on dynamically allocated memory.
713+
DEBUG_CRASH(("container object must be valid; this looks like use-after-free"));
714+
}
715+
else
716+
{
717+
DEBUG_ASSERTCRASH(TheGameLogic->findObjectByID(m_containedByID) == m_containedBy,
718+
("contained by pointer is out of sync with contained by ID"));
719+
720+
if (ContainModuleInterface* contain = m_containedBy->getContain())
721+
{
722+
contain->removeFromContain(this);
723+
}
724+
}
725+
#else
726+
if (ContainModuleInterface* contain = m_containedBy->getContain())
727+
{
728+
contain->removeFromContain(this);
729+
}
730+
#endif
687731
}
688732

689733
//
@@ -3727,16 +3771,18 @@ void Object::xfer( Xfer *xfer )
37273771
// No, the contain module is just going to friend_ reach in and set this for us.
37283772
// Containers more complicated than Open (like Tunnel) can't do that. Our variable,
37293773
// our responsibility.
3774+
#if !RETAIL_COMPATIBLE_CRC
3775+
// TheSuperHackers @tweak Contained by ID is already set with retail compatibility; don't overwrite it.
37303776
if( xfer->getXferMode() == XFER_SAVE )
37313777
{
37323778
if( m_containedBy != nullptr )
3733-
m_xferContainedByID = m_containedBy->getID();
3779+
m_containedByID = m_containedBy->getID();
37343780
else
3735-
m_xferContainedByID = INVALID_ID;
3781+
m_containedByID = INVALID_ID;
37363782
}
3783+
#endif
37373784

3738-
3739-
xfer->xferObjectID( &m_xferContainedByID );
3785+
xfer->xferObjectID( &m_containedByID );
37403786
}
37413787

37423788
// contained by frame
@@ -3961,8 +4007,8 @@ void Object::xfer( Xfer *xfer )
39614007
//-------------------------------------------------------------------------------------------------
39624008
void Object::loadPostProcess()
39634009
{
3964-
if( m_xferContainedByID != INVALID_ID )
3965-
m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID);
4010+
if( m_containedByID != INVALID_ID )
4011+
m_containedBy = TheGameLogic->findObjectByID(m_containedByID);
39664012
else
39674013
m_containedBy = nullptr;
39684014

0 commit comments

Comments
 (0)