diff --git a/Generals/Code/GameEngine/Include/Common/TunnelTracker.h b/Generals/Code/GameEngine/Include/Common/TunnelTracker.h index f66b7285ee6..50241906ab8 100644 --- a/Generals/Code/GameEngine/Include/Common/TunnelTracker.h +++ b/Generals/Code/GameEngine/Include/Common/TunnelTracker.h @@ -51,7 +51,7 @@ class TunnelTracker : public MemoryPoolObject, Bool isValidContainerFor(const Object* obj, Bool checkCapacity) const; void addToContainList( Object *obj ); ///< add 'obj' to contain list - void removeFromContain( Object *obj, Bool exposeStealthUnits = FALSE ); ///< remove 'obj' from contain list + Bool removeFromContain( Object *obj, Bool exposeStealthUnits = FALSE ); ///< remove 'obj' from contain list Bool isInContainer( Object *obj ); ///< Is this thing inside? void onTunnelCreated( const Object *newTunnel ); ///< A tunnel was made diff --git a/Generals/Code/GameEngine/Include/GameLogic/Object.h b/Generals/Code/GameEngine/Include/GameLogic/Object.h index 54ca8613dc8..ea38ddbc835 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Object.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Object.h @@ -648,6 +648,10 @@ class Object : public Thing, public Snapshot virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) override; +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC + void removeFromTunnelContain(); +#endif + private: // yes, private. No, really. Private. Don't expose. diff --git a/Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp b/Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp index 2a79732cb85..a8561f39c36 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp @@ -197,7 +197,7 @@ void TunnelTracker::addToContainList( Object *obj ) } // ------------------------------------------------------------------------ -void TunnelTracker::removeFromContain( Object *obj, Bool exposeStealthUnits ) +Bool TunnelTracker::removeFromContain( Object *obj, Bool exposeStealthUnits ) { ContainedItemsList::iterator it = std::find(m_containList.begin(), m_containList.end(), obj); @@ -212,8 +212,11 @@ void TunnelTracker::removeFromContain( Object *obj, Bool exposeStealthUnits ) DEBUG_ASSERTCRASH(m_heroUnitsContained > 0, ("TunnelTracker::removeFromContain - Removing hero but hero count is %d", m_heroUnitsContained)); --m_heroUnitsContained; } + + return true; } + return false; } // ------------------------------------------------------------------------ diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index afd6ee9660d..587f1f9b387 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -44,6 +44,9 @@ #include "Common/Team.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC +#include "Common/TunnelTracker.h" +#endif #include "Common/Upgrade.h" #include "Common/WellKnownKeys.h" #include "Common/Xfer.h" @@ -636,6 +639,23 @@ void Object::onRemovedFrom( Object *removedFrom ) m_containedByFrame = 0; } +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC +//------------------------------------------------------------------------------------------------- +void Object::removeFromTunnelContain() +{ + for (Int i = 0; i < ThePlayerList->getPlayerCount(); ++i) + { + TunnelTracker* tracker = ThePlayerList->getNthPlayer(i)->getTunnelSystem(); + if (tracker && tracker->removeFromContain(this)) + { + break; + } + } + + onRemovedFrom(nullptr); +} +#endif + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Int Object::getTransportSlotCount() const @@ -695,7 +715,21 @@ void Object::onDestroy() // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. if( m_containedBy && m_containedBy->getContain() ) { - m_containedBy->getContain()->removeFromContain( this ); +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix bobtista / Caball009 17/09/2026 Remove stranded tunnel occupants during destruction. + // Surrendering can transfer a tunnel without updating its tunnel tracker. If that tunnel is destroyed, + // m_containedBy still points to the freed tunnel, and its ID is read here through that stale pointer. + // An unregistered ID means the tunnel is gone, so this object is removed from the tunnel trackers directly. + // This is a limited workaround that keeps retail compatibility. It cannot detect reuse of the freed memory. + if (!TheGameLogic->findObjectByID(m_containedBy->getID())) + { + removeFromTunnelContain(); + } + else +#endif + { + m_containedBy->getContain()->removeFromContain(this); + } } // diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 932caa24f82..7c4a66ae604 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -410,14 +410,6 @@ void GameLogic::reset() m_thingTemplateBuildableOverrides.clear(); m_controlBarOverrides.clear(); - // set the hash to be rather large. We need to optimize this value later. - m_objHash.clear(); -#if USING_STLPORT - m_objHash.resize(OBJ_HASH_SIZE); -#else - m_objHash.reserve(OBJ_HASH_SIZE); -#endif - m_pauseFrame = 0; m_pauseSound = FALSE; m_pauseMusic = FALSE; @@ -432,6 +424,14 @@ void GameLogic::reset() // destroy all objects destroyAllObjectsImmediate(); + // set the hash to be rather large. We need to optimize this value later. + m_objHash.clear(); +#if USING_STLPORT + m_objHash.resize(OBJ_HASH_SIZE); +#else + m_objHash.reserve(OBJ_HASH_SIZE); +#endif + m_nextObjID = (ObjectID)1; m_frameObjectsChangedTriggerAreas = 0; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h b/GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h index 722aab0a54c..851ae5bef26 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h @@ -51,7 +51,7 @@ class TunnelTracker : public MemoryPoolObject, Bool isValidContainerFor(const Object* obj, Bool checkCapacity) const; void addToContainList( Object *obj ); ///< add 'obj' to contain list - void removeFromContain( Object *obj, Bool exposeStealthUnits = FALSE ); ///< remove 'obj' from contain list + Bool removeFromContain( Object *obj, Bool exposeStealthUnits = FALSE ); ///< remove 'obj' from contain list Bool isInContainer( Object *obj ); ///< Is this thing inside? void onTunnelCreated( const Object *newTunnel ); ///< A tunnel was made diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 13019ab3fa7..214abbf58f8 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -684,6 +684,10 @@ class Object : public Thing, public Snapshot virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) override; +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC + void removeFromTunnelContain(); +#endif + private: // yes, private. No, really. Private. Don't expose. diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp index 11d987d0570..62a7bd1a112 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp @@ -198,7 +198,7 @@ void TunnelTracker::addToContainList( Object *obj ) } // ------------------------------------------------------------------------ -void TunnelTracker::removeFromContain( Object *obj, Bool exposeStealthUnits ) +Bool TunnelTracker::removeFromContain( Object *obj, Bool exposeStealthUnits ) { ContainedItemsList::iterator it = std::find(m_containList.begin(), m_containList.end(), obj); @@ -213,8 +213,11 @@ void TunnelTracker::removeFromContain( Object *obj, Bool exposeStealthUnits ) DEBUG_ASSERTCRASH(m_heroUnitsContained > 0, ("TunnelTracker::removeFromContain - Removing hero but hero count is %d", m_heroUnitsContained)); --m_heroUnitsContained; } + + return true; } + return false; } // ------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index f99f83dfd27..e32873ffad2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -44,6 +44,9 @@ #include "Common/Team.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC +#include "Common/TunnelTracker.h" +#endif #include "Common/Upgrade.h" #include "Common/WellKnownKeys.h" #include "Common/Xfer.h" @@ -711,6 +714,23 @@ void Object::onRemovedFrom( Object *removedFrom ) } +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC +//------------------------------------------------------------------------------------------------- +void Object::removeFromTunnelContain() +{ + for (Int i = 0; i < ThePlayerList->getPlayerCount(); ++i) + { + TunnelTracker* tracker = ThePlayerList->getNthPlayer(i)->getTunnelSystem(); + if (tracker && tracker->removeFromContain(this)) + { + break; + } + } + + onRemovedFrom(nullptr); +} +#endif + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Int Object::getTransportSlotCount() const @@ -770,7 +790,21 @@ void Object::onDestroy() // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. if( m_containedBy && m_containedBy->getContain() ) { - m_containedBy->getContain()->removeFromContain( this ); +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix bobtista / Caball009 17/09/2026 Remove stranded tunnel occupants during destruction. + // Surrendering can transfer a tunnel without updating its tunnel tracker. If that tunnel is destroyed, + // m_containedBy still points to the freed tunnel, and its ID is read here through that stale pointer. + // An unregistered ID means the tunnel is gone, so this object is removed from the tunnel trackers directly. + // This is a limited workaround that keeps retail compatibility. It cannot detect reuse of the freed memory. + if (!TheGameLogic->findObjectByID(m_containedBy->getID())) + { + removeFromTunnelContain(); + } + else +#endif + { + m_containedBy->getContain()->removeFromContain(this); + } } // diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 60bae04f186..e7bea4711ea 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -420,12 +420,6 @@ void GameLogic::reset() m_thingTemplateBuildableOverrides.clear(); m_controlBarOverrides.clear(); - // set the hash to be rather large. We need to optimize this value later. -// m_objHash.clear(); -// m_objHash.resize(OBJ_HASH_SIZE); - m_objVector.clear(); - m_objVector.resize(OBJ_HASH_SIZE, nullptr); - m_pauseFrame = 0; m_pauseSound = FALSE; m_pauseMusic = FALSE; @@ -440,6 +434,12 @@ void GameLogic::reset() // destroy all objects destroyAllObjectsImmediate(); + // set the hash to be rather large. We need to optimize this value later. +// m_objHash.clear(); +// m_objHash.resize(OBJ_HASH_SIZE); + m_objVector.clear(); + m_objVector.resize(OBJ_HASH_SIZE, nullptr); + m_nextObjID = (ObjectID)1; m_frameObjectsChangedTriggerAreas = 0;