From ef0134c557a16ef3e01eafb277425885c0fa7d5b Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:22:27 +0200 Subject: [PATCH 1/6] Refactored portable object. --- .../Source/GameLogic/Object/Contain/HelixContain.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp index aff9d91941c..a50a276fe0e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp @@ -250,10 +250,10 @@ void HelixContain::addToContainList( Object *obj ) if ( portable ) TheGameLogic->destroyObject( portable ); - m_portableStructureID = obj->getID(); - obj->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container - + portable = obj; + m_portableStructureID = portable->getID(); + portable->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container } else TransportContain::addToContainList( obj ); @@ -268,10 +268,10 @@ void HelixContain::addToContain( Object *obj ) if ( portable ) TheGameLogic->destroyObject( portable ); - m_portableStructureID = obj->getID(); - obj->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container - + portable = obj; + m_portableStructureID = portable->getID(); + portable->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container } else TransportContain::addToContain( obj ); From ec3ec2541b3248a281cf9dfaee75329d8c495fe4 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:05:20 +0200 Subject: [PATCH 2/6] Added code to set containedByID member variable. --- .../GameEngine/Include/GameLogic/Object.h | 4 +++ .../GameLogic/Object/Contain/HelixContain.cpp | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 8e336c3cfa0..5496ad4b269 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -453,6 +453,10 @@ class Object : public Thing, public Snapshot const Object* getEnclosingContainedBy() const; ///< Find the first enclosing container in the containment chain. const Object* getOuterObject() const; ///< Get the top-level object +#if RETAIL_COMPATIBLE_CRC + void friend_setContainedByID(ObjectID id) { m_containedByID = id; } +#endif + // Special Powers ------------------------------------------------------------------------------- SpecialPowerModuleInterface *getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const; void doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced = false ); ///< execute power diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp index a50a276fe0e..6868066e00a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp @@ -254,6 +254,24 @@ void HelixContain::addToContainList( Object *obj ) m_portableStructureID = portable->getID(); portable->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container + +#if RETAIL_COMPATIBLE_CRC + Object* containedBy = getObject(); + + // TheSuperHackers @info Set INVALID_ID if the container object was destroyed + // to indicate that the pointer will become a dangling pointer in the next frame. + if (containedBy && !containedBy->isDestroyed()) + { + portable->friend_setContainedByID(containedBy->getID()); + } + else + { + portable->friend_setContainedByID(INVALID_ID); + } +#else + DEBUG_ASSERTCRASH(getObject() == nullptr || !getObject()->isDestroyed(), + ("HelixContain::addToContainList - Adding to a destroyed container")); +#endif } else TransportContain::addToContainList( obj ); @@ -272,6 +290,24 @@ void HelixContain::addToContain( Object *obj ) m_portableStructureID = portable->getID(); portable->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container + +#if RETAIL_COMPATIBLE_CRC + Object* containedBy = getObject(); + + // TheSuperHackers @info Set INVALID_ID if the container object was destroyed + // to indicate that the pointer will become a dangling pointer in the next frame. + if (containedBy && !containedBy->isDestroyed()) + { + portable->friend_setContainedByID(containedBy->getID()); + } + else + { + portable->friend_setContainedByID(INVALID_ID); + } +#else + DEBUG_ASSERTCRASH(getObject() == nullptr || !getObject()->isDestroyed(), + ("HelixContain::addToContain - Adding to a destroyed container")); +#endif } else TransportContain::addToContain( obj ); From 25f1ec37e87baf994efc20f63a27b0380fe6522c Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:19:37 +0200 Subject: [PATCH 3/6] Reset containedBy pointer and id on removal. --- GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h | 2 +- .../Source/GameLogic/Object/Contain/HelixContain.cpp | 8 +++++++- .../Code/GameEngine/Source/GameLogic/Object/Object.cpp | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 5496ad4b269..4e090771db3 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -449,7 +449,7 @@ class Object : public Thing, public Snapshot void onContainedBy( Object *containedBy ); void onRemovedFrom( Object *removedFrom ); Int getTransportSlotCount() const; - void friend_setContainedBy( Object *containedBy ) { m_containedBy = containedBy; } + void friend_setContainedBy( Object *containedBy ); const Object* getEnclosingContainedBy() const; ///< Find the first enclosing container in the containment chain. const Object* getOuterObject() const; ///< Get the top-level object diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp index 6868066e00a..c900e02d348 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp @@ -320,10 +320,16 @@ void HelixContain::removeFromContain( Object *obj, Bool exposeStealthUnits ) { Object *portable = getPortableStructure(); if ( portable ) + { +#if RETAIL_COMPATIBLE_CRC + portable->friend_setContainedByID(INVALID_ID); +#else + portable->friend_setContainedBy(nullptr); +#endif m_portableStructureID = INVALID_ID; //portable->kill(); - + } } else { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 0bd898aae75..a476abc66ac 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -749,6 +749,15 @@ Int Object::getTransportSlotCount() const return count; } +void Object::friend_setContainedBy(Object* containedBy) +{ + m_containedBy = containedBy; + +#if !RETAIL_COMPATIBLE_CRC + m_containedByFrame = containedBy ? TheGameLogic->getFrame() : 0; +#endif +} + const Object* Object::getEnclosingContainedBy() const { for (const Object* child = this, *container = getContainedBy(); container; child = container, container = container->getContainedBy()) From 7efaed83105eb6ba3b76682675447e7e5fbcf8c0 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:03:03 +0200 Subject: [PATCH 4/6] Tweaked older TSH comment. --- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index a476abc66ac..8ddeb128638 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -4299,8 +4299,9 @@ void Object::xfer( Xfer *xfer ) // No, the contain module is just going to friend_ reach in and set this for us. // Containers more complicated than Open (like Tunnel) can't do that. Our variable, // our responsibility. -#if !RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC // TheSuperHackers @tweak Contained by ID is already set with retail compatibility; don't overwrite it. +#else if( xfer->getXferMode() == XFER_SAVE ) { if( m_containedBy != nullptr ) From 59fc50ab714085795bb517a5d2979f8cccf3460d Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:18:14 +0200 Subject: [PATCH 5/6] Made function 'friend_setContainedByID' Zero Hour only. --- GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 4e090771db3..bdd19690f52 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -453,7 +453,7 @@ class Object : public Thing, public Snapshot const Object* getEnclosingContainedBy() const; ///< Find the first enclosing container in the containment chain. const Object* getOuterObject() const; ///< Get the top-level object -#if RETAIL_COMPATIBLE_CRC +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_CRC void friend_setContainedByID(ObjectID id) { m_containedByID = id; } #endif From 5bd8731f7296160576b60a493811efc3cba4d310 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:23:31 +0200 Subject: [PATCH 6/6] Replicated in Generals. --- Generals/Code/GameEngine/Include/GameLogic/Object.h | 6 +++++- .../GameEngine/Source/GameLogic/Object/Object.cpp | 12 +++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/Object.h b/Generals/Code/GameEngine/Include/GameLogic/Object.h index c94d61d4fcc..72e23377b5b 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Object.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Object.h @@ -424,10 +424,14 @@ class Object : public Thing, public Snapshot void onContainedBy( Object *containedBy ); void onRemovedFrom( Object *removedFrom ); Int getTransportSlotCount() const; - void friend_setContainedBy( Object *containedBy ) { m_containedBy = containedBy; } + void friend_setContainedBy( Object *containedBy ); const Object* getEnclosingContainedBy() const; ///< Find the first enclosing container in the containment chain. const Object* getOuterObject() const; ///< Get the top-level object +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_CRC + void friend_setContainedByID(ObjectID id) { m_containedByID = id; } +#endif + // Special Powers ------------------------------------------------------------------------------- SpecialPowerModuleInterface *getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const; void doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced = false ); ///< execute power diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 461e3495539..63b25332dc2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -674,6 +674,15 @@ Int Object::getTransportSlotCount() const return count; } +void Object::friend_setContainedBy(Object* containedBy) +{ + m_containedBy = containedBy; + +#if !RETAIL_COMPATIBLE_CRC + m_containedByFrame = containedBy ? TheGameLogic->getFrame() : 0; +#endif +} + const Object* Object::getEnclosingContainedBy() const { for (const Object* child = this, *container = getContainedBy(); container; child = container, container = container->getContainedBy()) @@ -3771,8 +3780,9 @@ void Object::xfer( Xfer *xfer ) // No, the contain module is just going to friend_ reach in and set this for us. // Containers more complicated than Open (like Tunnel) can't do that. Our variable, // our responsibility. -#if !RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC // TheSuperHackers @tweak Contained by ID is already set with retail compatibility; don't overwrite it. +#else if( xfer->getXferMode() == XFER_SAVE ) { if( m_containedBy != nullptr )