From 52b6eb06689a0230c1e407582b159d9b68103e40 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 10:40:36 +0200 Subject: [PATCH 01/55] [GEN][ZH] Replace TheGlobalData macro with inline variable to make debugging easier (#1118) --- Generals/Code/GameEngine/Include/Common/GlobalData.h | 5 +++++ GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Generals/Code/GameEngine/Include/Common/GlobalData.h b/Generals/Code/GameEngine/Include/Common/GlobalData.h index dbf13303402..4d06757f7f3 100644 --- a/Generals/Code/GameEngine/Include/Common/GlobalData.h +++ b/Generals/Code/GameEngine/Include/Common/GlobalData.h @@ -560,6 +560,11 @@ class GlobalData : public SubsystemInterface // singleton extern GlobalData* TheWritableGlobalData; +// use TheGlobalData for all read-only accesses +#if __cplusplus >= 201703L +inline const GlobalData* const& TheGlobalData = TheWritableGlobalData; +#else #define TheGlobalData ((const GlobalData*)TheWritableGlobalData) +#endif #endif diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 9dc544d9b2e..fba180d1238 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -573,6 +573,11 @@ class GlobalData : public SubsystemInterface // singleton extern GlobalData* TheWritableGlobalData; +// use TheGlobalData for all read-only accesses +#if __cplusplus >= 201703L +inline const GlobalData* const& TheGlobalData = TheWritableGlobalData; +#else #define TheGlobalData ((const GlobalData*)TheWritableGlobalData) +#endif #endif From cc7dbee99b1e664f43ef1b270bc248dd74d9f398 Mon Sep 17 00:00:00 2001 From: Paul Levlin Date: Sat, 21 Jun 2025 10:59:25 +0200 Subject: [PATCH 02/55] [GEN][ZH] Downgrade debug assertion to log for isPathInDirectory (#1128) --- Generals/Code/GameEngine/Source/Common/System/FileSystem.cpp | 1 - .../Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp | 1 + GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp | 1 - .../Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp | 1 + 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/System/FileSystem.cpp b/Generals/Code/GameEngine/Source/Common/System/FileSystem.cpp index 65e31318951..1952bcd1589 100644 --- a/Generals/Code/GameEngine/Source/Common/System/FileSystem.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/FileSystem.cpp @@ -367,7 +367,6 @@ Bool FileSystem::isPathInDirectory(const AsciiString& testPath, const AsciiStrin if (!testPathNormalized.startsWith(basePathNormalized)) #endif { - DEBUG_CRASH(("Normalized file path for '%s': '%s' was outside the expected base path of '%s' (normalized: '%s').\n", testPath.str(), testPathNormalized.str(), basePath.str(), basePathNormalized.str())); return false; } diff --git a/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp index 91e4f9aa315..cb85e81dbcb 100644 --- a/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp @@ -936,6 +936,7 @@ AsciiString GameState::portableMapPathToRealMapPath(const AsciiString& in) const if (!FileSystem::isPathInDirectory(prefix, containingBasePath)) { + DEBUG_LOG(("Normalized file path for '%s' was outside the expected base path of '%s'.\n", prefix.str(), containingBasePath.str())); return AsciiString::TheEmptyString; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp index 4f8a7d63b3f..aed60a6b17c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp @@ -384,7 +384,6 @@ Bool FileSystem::isPathInDirectory(const AsciiString& testPath, const AsciiStrin if (!testPathNormalized.startsWith(basePathNormalized)) #endif { - DEBUG_CRASH(("Normalized file path for '%s': '%s' was outside the expected base path of '%s' (normalized: '%s').\n", testPath.str(), testPathNormalized.str(), basePath.str(), basePathNormalized.str())); return false; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp index ffc33ea9abc..fd2de28eb16 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp @@ -936,6 +936,7 @@ AsciiString GameState::portableMapPathToRealMapPath(const AsciiString& in) const if (!FileSystem::isPathInDirectory(prefix, containingBasePath)) { + DEBUG_LOG(("Normalized file path for '%s' was outside the expected base path of '%s'.\n", prefix.str(), containingBasePath.str())); return AsciiString::TheEmptyString; } From 2a179d8add10ce05c386c5358aeb7add631f73a4 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:02:39 +0200 Subject: [PATCH 03/55] [ZH] Fix heap-use-after-free in LayersList::updateUIFromList() when creating a new map in World Builder (#1098) --- GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp index eeb1bbf5f3a..0929a4d6b62 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp @@ -1271,7 +1271,6 @@ BOOL CWorldBuilderDoc::OnNewDocument() m_waypointTableNeedsUpdate = true; m_curWaypointID = 0; WbApp()->selectPointerTool(); - PolygonTrigger::deleteTriggers(); // Make sure that all the old units are removed from the list. // Bug fix by MLL 1/14/03 @@ -1279,6 +1278,9 @@ BOOL CWorldBuilderDoc::OnNewDocument() TheLayersList->resetLayers(); TheLayersList->disableUpdates(); + // TheSuperHackers @bugfix Caball009 20/06/2025 Must not delete polygon triggers before calling enableUpdates. + PolygonTrigger::deleteTriggers(); + TheSidesList->clear(); TheSidesList->validateSides(); From a6e408b8b8e9aac5b80abc006adbb5ef2352107b Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:04:06 +0200 Subject: [PATCH 04/55] [GEN] Backport bug fixes in CWorldBuilderDoc::OnNewDocument from Zero Hour (#1099) --- Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp b/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp index ba03053650c..e3be4773473 100644 --- a/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp @@ -1210,7 +1210,16 @@ BOOL CWorldBuilderDoc::OnNewDocument() m_waypointTableNeedsUpdate = true; m_curWaypointID = 0; WbApp()->selectPointerTool(); + + // Make sure that all the old units are removed from the list. + // Bug fix by MLL 1/14/03 + TheLayersList->enableUpdates(); + TheLayersList->resetLayers(); + TheLayersList->disableUpdates(); + + // TheSuperHackers @bugfix Caball009 20/06/2025 Must not delete polygon triggers before calling enableUpdates. PolygonTrigger::deleteTriggers(); + TheSidesList->clear(); TheSidesList->validateSides(); From 1c8b33d308c55c5009cbcbe9b97b5bb4c3df652c Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:07:13 +0200 Subject: [PATCH 05/55] [GEN][ZH] Annotate fallthrough between various switch case labels (#1085) --- Core/Libraries/Source/WWVegas/WWLib/always.h | 1 - Dependencies/Utility/Utility/CppMacros.h | 10 ++++------ Generals/Code/GameEngine/Source/Common/RandomValue.cpp | 2 ++ .../GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp | 7 +++++-- .../GameClient/GUI/GameWindowTransitionsStyles.cpp | 6 ++++-- .../Code/GameEngine/Source/GameClient/GameText.cpp | 2 ++ .../Source/GameClient/MessageStream/WindowXlat.cpp | 2 +- .../Object/Update/AIUpdate/HackInternetAIUpdate.cpp | 8 ++++---- .../GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp | 2 +- .../Object/Update/AIUpdate/MissileAIUpdate.cpp | 2 ++ .../Source/GameLogic/ScriptEngine/Scripts.cpp | 2 +- .../Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp | 6 +++--- .../Code/GameEngine/Source/Common/RandomValue.cpp | 2 ++ .../GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp | 7 +++++-- .../GameClient/GUI/GameWindowTransitionsStyles.cpp | 6 ++++-- .../Code/GameEngine/Source/GameClient/GameText.cpp | 2 ++ .../Source/GameClient/MessageStream/CommandXlat.cpp | 4 ++-- .../Source/GameClient/MessageStream/WindowXlat.cpp | 2 +- .../Object/Update/AIUpdate/HackInternetAIUpdate.cpp | 8 ++++---- .../GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp | 2 +- .../Object/Update/AIUpdate/MissileAIUpdate.cpp | 2 ++ .../GameLogic/Object/Update/SpecialAbilityUpdate.cpp | 4 ++-- .../Source/GameLogic/ScriptEngine/Scripts.cpp | 2 +- .../Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp | 6 +++--- .../Libraries/Source/WWVegas/WW3D2/textureloader.cpp | 5 ++++- 25 files changed, 62 insertions(+), 40 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WWLib/always.h b/Core/Libraries/Source/WWVegas/WWLib/always.h index 7439bc9e628..255a306647f 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/always.h +++ b/Core/Libraries/Source/WWVegas/WWLib/always.h @@ -46,7 +46,6 @@ // TheSuperHackers @compile feliwir 17/04/2025 include utility macros for cross-platform compatibility #include #include - #include // Disable warning about exception handling not being enabled. It's used as part of STL - in a part of STL we don't use. diff --git a/Dependencies/Utility/Utility/CppMacros.h b/Dependencies/Utility/Utility/CppMacros.h index 57aaa75abc8..9c0ed332cb2 100644 --- a/Dependencies/Utility/Utility/CppMacros.h +++ b/Dependencies/Utility/Utility/CppMacros.h @@ -21,8 +21,12 @@ #if __cplusplus >= 201703L #define NOEXCEPT_17 noexcept +#define REGISTER +#define FALLTHROUGH [[fallthrough]] #else #define NOEXCEPT_17 +#define REGISTER register +#define FALLTHROUGH #endif // noexcept for methods of IUNKNOWN interface @@ -38,12 +42,6 @@ #define CPP_11(code) #endif -#if __cplusplus >= 201703L -#define REGISTER -#else -#define REGISTER register -#endif - #if __cplusplus < 201103L #define static_assert(expr, msg) #endif diff --git a/Generals/Code/GameEngine/Source/Common/RandomValue.cpp b/Generals/Code/GameEngine/Source/Common/RandomValue.cpp index 194ec0cea78..8b2f5dafcce 100644 --- a/Generals/Code/GameEngine/Source/Common/RandomValue.cpp +++ b/Generals/Code/GameEngine/Source/Common/RandomValue.cpp @@ -383,6 +383,7 @@ Real GameClientRandomVariable::getValue( void ) const if (m_low == m_high) { return m_low; } // else return as though a UNIFORM. + FALLTHROUGH; case UNIFORM: return GameClientRandomValueReal( m_low, m_high ); @@ -427,6 +428,7 @@ Real GameLogicRandomVariable::getValue( void ) const if (m_low == m_high) { return m_low; } // else return as though a UNIFORM. + FALLTHROUGH; case UNIFORM: return GameLogicRandomValueReal( m_low, m_high ); diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index 0f9492d7515..59f9c092c80 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -1877,20 +1877,23 @@ void grabSinglePlayerInfo( void ) { Bool isFriend = TRUE; - // set the string we'll be compairing to + // set the string we'll be comparing to switch (j) { case USA_ENEMY: isFriend = FALSE; + FALLTHROUGH; case USA_FRIEND: side.set("America"); break; case CHINA_ENEMY: isFriend = FALSE; + FALLTHROUGH; case CHINA_FRIEND: side.set("China"); break; case GLA_ENEMY: - isFriend = FALSE; + isFriend = FALSE; + FALLTHROUGH; case GLA_FRIEND: side.set("GLA"); break; diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp index c968b80232f..b95f23b4929 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp @@ -142,6 +142,8 @@ void FlashTransition::update( Int frame ) } // end if } + FALLTHROUGH; + case FLASHTRANSITION_FADE_IN_2: case FLASHTRANSITION_FADE_IN_3: { @@ -810,8 +812,8 @@ void ScaleUpTransition::update( Int frame ) TheAudio->addAudioEvent( &buttonClick ); } // end if - } + FALLTHROUGH; case SCALEUPTRANSITION_2: case SCALEUPTRANSITION_3: @@ -933,8 +935,8 @@ void ScoreScaleUpTransition::update( Int frame ) TheAudio->addAudioEvent( &buttonClick ); } // end if - } + FALLTHROUGH; case SCORESCALEUPTRANSITION_2: case SCORESCALEUPTRANSITION_3: diff --git a/Generals/Code/GameEngine/Source/GameClient/GameText.cpp b/Generals/Code/GameEngine/Source/GameClient/GameText.cpp index 6a8f1f485b3..f720c480dc2 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameText.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameText.cpp @@ -630,6 +630,7 @@ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *w } state = 1; + FALLTHROUGH; case 1: if ( ( ch >= 'a' && ch <= 'z') || ( ch >= 'A' && ch <='Z') || (ch >= '0' && ch <= '9') || ch == '_' ) { @@ -638,6 +639,7 @@ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *w break; } state = 2; + FALLTHROUGH; case 2: break; } diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp index 0241f4fa5bf..dfb78fa691f 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp @@ -204,7 +204,7 @@ GameMessageDisposition WindowTranslator::translateGameMessage(const GameMessage //If we release the button outside forceKeepMessage = TRUE; } - //FALL THROUGH INTENTIONALLY! + FALLTHROUGH; //FALL THROUGH INTENTIONALLY! } case GameMessage::MSG_RAW_MOUSE_POSITION: case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp index d69e1f7a228..db4372c1530 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp @@ -494,28 +494,28 @@ StateReturnType HackInternetState::update() { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_ELITE: amount = ai->getEliteCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_VETERAN: amount = ai->getVeteranCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_REGULAR: amount = ai->getRegularCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! default: amount = 1; break; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index 12d0b138d19..9f1764699cf 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -2216,7 +2216,7 @@ void JetAIUpdate::aiDoCommand(const AICommandParms* parms) if (isParkedAt(parms->m_obj)) return; - // else fall thru to the default case! + FALLTHROUGH; // else fall thru to the default case! default: { diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 725840f56db..774913b42b2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -652,6 +652,8 @@ UpdateSleepTime MissileAIUpdate::update() { break; } + FALLTHROUGH; + case IGNITION: doIgnitionState(); break; diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp index 0499d5b508d..b5ca0f05743 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp @@ -1776,7 +1776,7 @@ void Parameter::qualify(const AsciiString& qualifier, if (m_string == THIS_TEAM) { break; } - /// otherwise drop down & qualify. + FALLTHROUGH; /// otherwise drop down & qualify. case SCRIPT: case COUNTER: case FLAG: diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp index 4d72ffacfef..90cecd7e811 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp @@ -344,7 +344,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_LIGHTMAP_MULTI_TEXTURE, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... case WW3D::PRELIT_MODE_LIGHTMAP_MULTI_PASS: if (context->Header.Attributes & W3D_MESH_FLAG_PRELIT_LIGHTMAP_MULTI_PASS) { @@ -352,7 +352,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_LIGHTMAP_MULTI_PASS, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... case WW3D::PRELIT_MODE_VERTEX: if (context->Header.Attributes & W3D_MESH_FLAG_PRELIT_VERTEX) { @@ -360,7 +360,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_VERTEX, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... default: diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp index 5fa23da03b4..c1b41acd5b9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp @@ -383,6 +383,7 @@ Real GameClientRandomVariable::getValue( void ) const if (m_low == m_high) { return m_low; } // else return as though a UNIFORM. + FALLTHROUGH; case UNIFORM: return GameClientRandomValueReal( m_low, m_high ); @@ -427,6 +428,7 @@ Real GameLogicRandomVariable::getValue( void ) const if (m_low == m_high) { return m_low; } // else return as though a UNIFORM. + FALLTHROUGH; case UNIFORM: return GameLogicRandomValueReal( m_low, m_high ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index 786ba75d532..23e3960ebc4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -2145,20 +2145,23 @@ void grabSinglePlayerInfo( void ) { Bool isFriend = TRUE; - // set the string we'll be compairing to + // set the string we'll be comparing to switch (j) { case USA_ENEMY: isFriend = FALSE; + FALLTHROUGH; case USA_FRIEND: side.set("USA"); break; case CHINA_ENEMY: isFriend = FALSE; + FALLTHROUGH; case CHINA_FRIEND: side.set("China"); break; case GLA_ENEMY: - isFriend = FALSE; + isFriend = FALSE; + FALLTHROUGH; case GLA_FRIEND: side.set("GLA"); break; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp index f9795b2e5e0..20ea8ea96a7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp @@ -142,6 +142,8 @@ void FlashTransition::update( Int frame ) } // end if } + FALLTHROUGH; + case FLASHTRANSITION_FADE_IN_2: case FLASHTRANSITION_FADE_IN_3: { @@ -810,8 +812,8 @@ void ScaleUpTransition::update( Int frame ) TheAudio->addAudioEvent( &buttonClick ); } // end if - } + FALLTHROUGH; case SCALEUPTRANSITION_2: case SCALEUPTRANSITION_3: @@ -933,8 +935,8 @@ void ScoreScaleUpTransition::update( Int frame ) TheAudio->addAudioEvent( &buttonClick ); } // end if - } + FALLTHROUGH; case SCORESCALEUPTRANSITION_2: case SCORESCALEUPTRANSITION_3: diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameText.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameText.cpp index 9b6e207f9e5..163018fbd2e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameText.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameText.cpp @@ -630,6 +630,7 @@ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *w } state = 1; + FALLTHROUGH; case 1: if ( ( ch >= 'a' && ch <= 'z') || ( ch >= 'A' && ch <='Z') || (ch >= '0' && ch <= '9') || ch == '_' ) { @@ -638,6 +639,7 @@ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *w break; } state = 2; + FALLTHROUGH; case 2: break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 69cd51cf2f1..c19fad2b61f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3704,7 +3704,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage break; } - //intentional fall through + FALLTHROUGH; //intentional fall through } case GameMessage::MSG_MOUSE_RIGHT_CLICK: { @@ -3767,7 +3767,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage break; } - //intentional fall through + FALLTHROUGH; //intentional fall through } case GameMessage::MSG_MOUSE_LEFT_CLICK: { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp index cce35480d41..8a934a8f7ef 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp @@ -222,7 +222,7 @@ GameMessageDisposition WindowTranslator::translateGameMessage(const GameMessage //If we release the button outside forceKeepMessage = TRUE; } - //FALL THROUGH INTENTIONALLY! + FALLTHROUGH; //FALL THROUGH INTENTIONALLY! } case GameMessage::MSG_RAW_MOUSE_POSITION: case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp index 0b1f90090a0..4599b4b94c0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp @@ -506,28 +506,28 @@ StateReturnType HackInternetState::update() { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_ELITE: amount = ai->getEliteCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_VETERAN: amount = ai->getVeteranCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_REGULAR: amount = ai->getRegularCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! default: amount = 1; break; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index cca2a49e8d7..b37edd7dc80 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -2450,7 +2450,7 @@ void JetAIUpdate::aiDoCommand(const AICommandParms* parms) if (isParkedAt(parms->m_obj)) return; - // else fall thru to the default case! + FALLTHROUGH; // else fall thru to the default case! default: { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 657593267d9..8975acb9d5a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -707,6 +707,8 @@ UpdateSleepTime MissileAIUpdate::update() { break; } + FALLTHROUGH; + case IGNITION: doIgnitionState(); break; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp index f38cf423b8c..b089734ad9d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp @@ -280,7 +280,7 @@ UpdateSleepTime SpecialAbilityUpdate::update( void ) // it's been captured by a colleague! we should stop. shouldAbort = TRUE; } - //deliberately falling through... + FALLTHROUGH; //deliberately falling through... } case SPECIAL_BLACKLOTUS_STEAL_CASH_HACK: case SPECIAL_BOOBY_TRAP: @@ -309,7 +309,7 @@ UpdateSleepTime SpecialAbilityUpdate::update( void ) { if ( target->isKindOf( KINDOF_STRUCTURE ) ) shouldAbort = TRUE; - //deliberately falling through + FALLTHROUGH; //deliberately falling through } case SPECIAL_BLACKLOTUS_DISABLE_VEHICLE_HACK: { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp index 90ec1bdcd23..c90e2a4acb7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp @@ -1837,7 +1837,7 @@ void Parameter::qualify(const AsciiString& qualifier, if (m_string == THIS_TEAM) { break; } - /// otherwise drop down & qualify. + FALLTHROUGH; /// otherwise drop down & qualify. case SCRIPT: case COUNTER: case FLAG: diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp index 9cfcad01d74..4ebff7316d7 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp @@ -344,7 +344,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_LIGHTMAP_MULTI_TEXTURE, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... case WW3D::PRELIT_MODE_LIGHTMAP_MULTI_PASS: if (context->Header.Attributes & W3D_MESH_FLAG_PRELIT_LIGHTMAP_MULTI_PASS) { @@ -352,7 +352,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_LIGHTMAP_MULTI_PASS, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... case WW3D::PRELIT_MODE_VERTEX: if (context->Header.Attributes & W3D_MESH_FLAG_PRELIT_VERTEX) { @@ -360,7 +360,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_VERTEX, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... default: diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp index 7982141ca39..92e3a66df4c 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp @@ -895,7 +895,7 @@ void TextureLoader::Process_Foreground_Thumbnail(TextureLoadTaskClass *task) switch (task->Get_State()) { case TextureLoadTaskClass::STATE_NONE: Load_Thumbnail(task->Peek_Texture()); - // NOTE: fall-through is intentional + FALLTHROUGH; // NOTE: fall-through is intentional case TextureLoadTaskClass::STATE_COMPLETE: task->Destroy(); @@ -1260,12 +1260,15 @@ void TextureLoadTaskClass::Finish_Load(void) Apply_Missing_Texture(); break; } + FALLTHROUGH; case STATE_LOAD_BEGUN: Load(); + FALLTHROUGH; case STATE_LOAD_MIPMAP: End_Load(); + FALLTHROUGH; default: break; From 093416a8a0eafc265852587e77b6bfa6d70e1cf9 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:08:07 +0200 Subject: [PATCH 06/55] [GEN][ZH] Prevent using uninitialized memory 'offset_u' in W3DTankDraw, W3DTankTruckDraw (#1086) --- .../W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp | 7 ++++++- .../GameClient/Drawable/Draw/W3DTankTruckDraw.cpp | 7 ++++++- .../W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp | 7 ++++++- .../GameClient/Drawable/Draw/W3DTankTruckDraw.cpp | 7 ++++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index c409c8610db..afd5ef7d28d 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -234,7 +234,12 @@ void W3DTankDraw::updateTreadPositions(Real uvDelta) else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; - + else + { + DEBUG_CRASH(("Unhandled case in W3DTankDraw::updateTreadPositions")); + offset_u = 0.0f; + } + // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 6b5df1a2494..95e1fa16e40 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -414,7 +414,12 @@ void W3DTankTruckDraw::updateTreadPositions(Real uvDelta) else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; - + else + { + DEBUG_CRASH(("Unhandled case in W3DTankTruckDraw::updateTreadPositions")); + offset_u = 0.0f; + } + // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index 269a56b0336..28f7c332368 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -234,7 +234,12 @@ void W3DTankDraw::updateTreadPositions(Real uvDelta) else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; - + else + { + DEBUG_CRASH(("Unhandled case in W3DTankDraw::updateTreadPositions")); + offset_u = 0.0f; + } + // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 0a2687eb944..37e4763c692 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -414,7 +414,12 @@ void W3DTankTruckDraw::updateTreadPositions(Real uvDelta) else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; - + else + { + DEBUG_CRASH(("Unhandled case in W3DTankTruckDraw::updateTreadPositions")); + offset_u = 0.0f; + } + // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); From 0ebdeebbb2dee1bf53de3b0a9c0162f3e9ae615b Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:08:43 +0200 Subject: [PATCH 07/55] [GEN][ZH] Prevent using uninitialized memory 'start', 'end' in BridgeScaffoldBehavior::update() (#1088) --- .../GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp | 4 ++++ .../GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp index 25b9a985d5a..bc705ce7001 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp @@ -192,6 +192,10 @@ UpdateSleepTime BridgeScaffoldBehavior::update( void ) end = &m_riseToPos; break; + default: + DEBUG_CRASH(("Unhandled case in BridgeScaffoldBehavior::update()")); + return UPDATE_SLEEP_NONE; + } // end switch // adjust speed so it's slower at the end of motion diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp index 4ea04722caa..b7c9b6ada10 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp @@ -192,6 +192,10 @@ UpdateSleepTime BridgeScaffoldBehavior::update( void ) end = &m_riseToPos; break; + default: + DEBUG_CRASH(("Unhandled case in BridgeScaffoldBehavior::update()")); + return UPDATE_SLEEP_NONE; + } // end switch // adjust speed so it's slower at the end of motion From 915bd64982845035cdc05cd4257d4a12e1324181 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:11:12 +0200 Subject: [PATCH 08/55] [GEN][ZH] Prevent dereferencing NULL pointer 'tex' in WW3DAssetManager::Get_Texture() (#1089) --- Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp | 6 ++++++ GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp index 50740c98052..a3712b62d42 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp @@ -1145,6 +1145,12 @@ TextureClass * WW3DAssetManager::Get_Texture { tex = NEW_REF (TextureClass, (lower_case_name, NULL, mip_level_count, texture_format, allow_compression)); } + else + { + WWASSERT_PRINT(false, ("Unhandled case")); + return NULL; + } + TextureHash.Insert(tex->Get_Texture_Name(),tex); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp index 4d00d891145..f963b6d4fbd 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp @@ -1122,6 +1122,12 @@ TextureClass * WW3DAssetManager::Get_Texture { tex = NEW_REF (VolumeTextureClass, (lower_case_name, NULL, mip_level_count, texture_format, allow_compression, allow_reduction)); } + else + { + WWASSERT_PRINT(false, ("Unhandled case")); + return NULL; + } + TextureHash.Insert(tex->Get_Texture_Name(),tex); } From af35ed98b026ee8cda13da8d17fef2541d89145a Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:11:24 +0200 Subject: [PATCH 09/55] [GEN][ZH] Prevent reading invalid data in LoadScreen (#1090) --- Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp | 3 +++ .../Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index a2de250a1e7..149a8e37a08 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -886,6 +886,7 @@ void MultiPlayerLoadScreen::processProgress(Int playerId, Int percentage) if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } //DEBUG_LOG(("Percentage %d was passed in for Player %d (in loadscreen position %d)\n", percentage, playerId, m_playerLookup[playerId])); if(m_progressBars[m_playerLookup[playerId]]) @@ -1213,6 +1214,7 @@ void GameSpyLoadScreen::processProgress(Int playerId, Int percentage) if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } //DEBUG_LOG(("Percentage %d was passed in for Player %d (in loadscreen position %d)\n", percentage, playerId, m_playerLookup[playerId])); if(m_progressBars[m_playerLookup[playerId]]) @@ -1365,6 +1367,7 @@ void MapTransferLoadScreen::processProgress(Int playerId, Int percentage, AsciiS if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } if (m_oldProgress[playerId] == percentage) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index 3ceaf51bd7b..b3434558440 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -1476,6 +1476,7 @@ void MultiPlayerLoadScreen::processProgress(Int playerId, Int percentage) if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } //DEBUG_LOG(("Percentage %d was passed in for Player %d (in loadscreen position %d)\n", percentage, playerId, m_playerLookup[playerId])); if(m_progressBars[m_playerLookup[playerId]]) @@ -1843,6 +1844,7 @@ void GameSpyLoadScreen::processProgress(Int playerId, Int percentage) if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } //DEBUG_LOG(("Percentage %d was passed in for Player %d (in loadscreen position %d)\n", percentage, playerId, m_playerLookup[playerId])); if(m_progressBars[m_playerLookup[playerId]]) @@ -1995,6 +1997,7 @@ void MapTransferLoadScreen::processProgress(Int playerId, Int percentage, AsciiS if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } if (m_oldProgress[playerId] == percentage) From 9d64a430cd31293603ed280f35084676bb254302 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:12:14 +0200 Subject: [PATCH 10/55] [GEN][ZH] Fix using uninitialized memory 'curVictim' in EMPUpdate (#1094) --- .../Source/GameLogic/Object/Update/EMPUpdate.cpp | 4 ++-- .../Source/GameLogic/Object/Update/EMPUpdate.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index 33b7fc8aae6..df2df552a75 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -180,8 +180,8 @@ void EMPUpdate::doDisableAttack( void ) Real curVictimDistSqr; const Coord3D *pos = object->getPosition(); - SimpleObjectIterator *iter; - Object *curVictim; + SimpleObjectIterator *iter = NULL; + Object *curVictim = NULL; if (radius > 0.0f) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index 0873b59b777..c155714faec 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -198,8 +198,8 @@ void EMPUpdate::doDisableAttack( void ) } } - SimpleObjectIterator *iter; - Object *curVictim; + SimpleObjectIterator *iter = NULL; + Object *curVictim = NULL; if (radius > 0.0f) { @@ -499,8 +499,8 @@ void LeafletDropBehavior::doDisableAttack( void ) Real curVictimDistSqr; const Coord3D *pos = object->getPosition(); - SimpleObjectIterator *iter; - Object *curVictim; + SimpleObjectIterator *iter = NULL; + Object *curVictim = NULL; if (radius > 0.0f) { From 9615d216986be31c0718ee98c827d562fdeb332d Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:12:34 +0200 Subject: [PATCH 11/55] [GEN][ZH] Prevent dereferencing NULL pointer 'TheAI' in AI::parseAiDataDefinition() (#1096) --- Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp | 7 +++---- GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp index 987fe75ea11..0ec8efe442b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -427,11 +427,10 @@ void AI::parseAiDataDefinition( INI* ini ) if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES ) TheAI->newOverride(); - } // end if - - // parse the ini weapon definition - ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable ); + // parse the ini weapon definition + ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable ); + } // end if } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp index 2900f19d9f1..d1fa826a4d5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -430,11 +430,10 @@ void AI::parseAiDataDefinition( INI* ini ) if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES ) TheAI->newOverride(); - } // end if - - // parse the ini weapon definition - ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable ); + // parse the ini weapon definition + ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable ); + } // end if } From adacc2292d839593da7c14c9cdb04ecc2f7fba95 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:13:54 +0200 Subject: [PATCH 12/55] [GEN][ZH] Prevent dereferencing NULL pointer 'that' in PhysicsBehavior::transferVelocityTo() (#1101) --- .../Source/GameLogic/Object/Update/PhysicsUpdate.cpp | 4 +++- .../Source/GameLogic/Object/Update/PhysicsUpdate.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 6ac17ecd7c6..66feeb1ef21 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -942,8 +942,10 @@ void PhysicsBehavior::addOverlap(Object *obj) void PhysicsBehavior::transferVelocityTo(PhysicsBehavior* that) const { if (that != NULL) + { that->m_vel.add(&m_vel); - that->m_velMag = INVALID_VEL_MAG; + that->m_velMag = INVALID_VEL_MAG; + } } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index dff994e3d9d..91e4a0b7f1c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -1067,8 +1067,10 @@ void PhysicsBehavior::addOverlap(Object *obj) void PhysicsBehavior::transferVelocityTo(PhysicsBehavior* that) const { if (that != NULL) + { that->m_vel.add(&m_vel); - that->m_velMag = INVALID_VEL_MAG; + that->m_velMag = INVALID_VEL_MAG; + } } //------------------------------------------------------------------------------------------------- From 246b43f080d1f5a2043e790e388632295d27b268 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:14:39 +0200 Subject: [PATCH 13/55] [GEN][ZH] Fix heap-use-after-free in DisconnectManager::update() (#1103) --- .../GameEngine/Source/GameNetwork/DisconnectManager.cpp | 7 +++++-- .../GameEngine/Source/GameNetwork/DisconnectManager.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Generals/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp index 6daed7ef83a..5f8bc102726 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -139,10 +139,13 @@ void DisconnectManager::update(ConnectionManager *conMgr) { //use next ping server static size_t serverIndex = 0; serverIndex++; - if( serverIndex >= TheGameSpyConfig->getPingServers().size() ) + + AsciiStringList pingServers = TheGameSpyConfig->getPingServers(); + + if( serverIndex >= pingServers.size() ) serverIndex = 0; //wrap back to first ping server - std::list::iterator it = TheGameSpyConfig->getPingServers().begin(); + std::list::iterator it = pingServers.begin(); for( size_t i = 0; i < serverIndex; i++ ) it++; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp index bbecd950156..632c92dc3e2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -139,10 +139,13 @@ void DisconnectManager::update(ConnectionManager *conMgr) { //use next ping server static size_t serverIndex = 0; serverIndex++; - if( serverIndex >= TheGameSpyConfig->getPingServers().size() ) + + AsciiStringList pingServers = TheGameSpyConfig->getPingServers(); + + if( serverIndex >= pingServers.size() ) serverIndex = 0; //wrap back to first ping server - std::list::iterator it = TheGameSpyConfig->getPingServers().begin(); + std::list::iterator it = pingServers.begin(); for( size_t i = 0; i < serverIndex; i++ ) it++; From c8625aeb5740f61c6d1caf0d345f8c6a72ede3c1 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:15:41 +0200 Subject: [PATCH 14/55] [GEN][ZH] Prevent buffer overrun while writing to 'new_types', 'new_lensflares' in DazzleRenderObjClass (#1111) --- Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp | 6 ++++-- GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp index 61156e1580c..a9a6365b05f 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp @@ -669,7 +669,8 @@ void DazzleRenderObjClass::Init_Type(const DazzleInitClass& i) unsigned new_count=i.type+1; DazzleTypeClass** new_types=W3DNEWARRAY DazzleTypeClass*[new_count]; unsigned a=0; - for (;a Date: Sat, 21 Jun 2025 11:16:37 +0200 Subject: [PATCH 15/55] [GEN][ZH] Fix dereferencing NULL pointer 'ext' in DX8Wrapper::_Create_DX8_Surface() (#1112) --- Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | 5 +++-- .../Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index ee887eb836c..003d6d69615 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -2403,9 +2403,10 @@ IDirect3DSurface8 * DX8Wrapper::_Create_DX8_Surface(const char *filename_) // If file not found, try the dds format // else create a surface with missing texture in it char compressed_name[200]; - strncpy(compressed_name,filename_, 200); + strncpy(compressed_name,filename_, ARRAY_SIZE(compressed_name)); + compressed_name[ARRAY_SIZE(compressed_name)-1] = '\0'; char *ext = strstr(compressed_name, "."); - if ( (strlen(ext)==4) && + if ( ext && (strlen(ext)==4) && ( (ext[1] == 't') || (ext[1] == 'T') ) && ( (ext[2] == 'g') || (ext[2] == 'G') ) && ( (ext[3] == 'a') || (ext[3] == 'A') ) ) { diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index 2a9b3cf0636..db6491a29d2 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -2942,9 +2942,10 @@ IDirect3DSurface8 * DX8Wrapper::_Create_DX8_Surface(const char *filename_) // If file not found, try the dds format // else create a surface with missing texture in it char compressed_name[200]; - strncpy(compressed_name,filename_, 200); + strncpy(compressed_name,filename_, ARRAY_SIZE(compressed_name)); + compressed_name[ARRAY_SIZE(compressed_name)-1] = '\0'; char *ext = strstr(compressed_name, "."); - if ( (strlen(ext)==4) && + if ( ext && (strlen(ext)==4) && ( (ext[1] == 't') || (ext[1] == 'T') ) && ( (ext[2] == 'g') || (ext[2] == 'G') ) && ( (ext[3] == 'a') || (ext[3] == 'A') ) ) { From d96ccec5c69e866f654512dadff6a2f984d94ae8 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:17:01 +0200 Subject: [PATCH 16/55] [GEN][ZH] Write correct call to super class in constructor of GameSpyGameSlot (#1113) --- .../Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp | 2 +- .../Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp b/Generals/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp index 4e8ad212e0c..372c2ac4ff1 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp @@ -57,8 +57,8 @@ // GameSpyGameSlot ------------------------------------------- GameSpyGameSlot::GameSpyGameSlot() + : GameSlot() { - GameSlot(); m_gameSpyLogin.clear(); m_gameSpyLocale.clear(); m_profileID = 0; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp index 514076fc435..85a01bcc33d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp @@ -57,8 +57,8 @@ // GameSpyGameSlot ------------------------------------------- GameSpyGameSlot::GameSpyGameSlot() + : GameSlot() { - GameSlot(); m_gameSpyLogin.clear(); m_gameSpyLocale.clear(); m_profileID = 0; From b8685612e6efd509d5a99ce2f21364240bbec75b Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:18:19 +0200 Subject: [PATCH 17/55] [GEN][ZH] Remove unnecessary NULL pointer test in UpdateSlotList() (#1120) --- Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp | 7 ++++--- Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp | 2 ++ GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp | 7 ++++--- GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp | 2 ++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp b/Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp index f7eb013b315..29ba9158aa6 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp @@ -339,13 +339,14 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], for( int i =0; i < MAX_SLOTS; i++ ) { GameSlot * slot = myGame->getSlot(i); + // if i'm host, enable the controls for AI - if(myGame->amIHost() && slot && slot->isAI()) + if(myGame->amIHost() && slot->isAI()) { EnableAcceptControls(TRUE, myGame, comboPlayer, comboColor, comboPlayerTemplate, comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i); } - else if (slot && myGame->getLocalSlotNum() == i) + else if (myGame->getLocalSlotNum() == i) { if(slot->isAccepted() && !myGame->amIHost()) { @@ -371,7 +372,7 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], EnableAcceptControls(FALSE, myGame, comboPlayer, comboColor, comboPlayerTemplate, comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i); } - if(slot && slot->isHuman()) + if(slot->isHuman()) { UnicodeString newName = slot->getName(); UnicodeString oldName = GadgetComboBoxGetText(comboPlayer[i]); diff --git a/Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp b/Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp index 0d564421d8b..903a2957434 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp @@ -449,6 +449,7 @@ GameSlot* GameInfo::getSlot( Int slotNum ) if (slotNum < 0 || slotNum >= MAX_SLOTS) return NULL; + DEBUG_ASSERTCRASH( m_slot[slotNum], ("NULL slot pointer") ); return m_slot[slotNum]; } @@ -458,6 +459,7 @@ const GameSlot* GameInfo::getConstSlot( Int slotNum ) const if (slotNum < 0 || slotNum >= MAX_SLOTS) return NULL; + DEBUG_ASSERTCRASH( m_slot[slotNum], ("NULL slot pointer") ); return m_slot[slotNum]; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp index 6ecd086d4f2..409a8d47ff0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp @@ -393,13 +393,14 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], for( int i =0; i < MAX_SLOTS; i++ ) { GameSlot * slot = myGame->getSlot(i); + // if i'm host, enable the controls for AI - if(myGame->amIHost() && slot && slot->isAI()) + if(myGame->amIHost() && slot->isAI()) { EnableAcceptControls(TRUE, myGame, comboPlayer, comboColor, comboPlayerTemplate, comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i); } - else if (slot && myGame->getLocalSlotNum() == i) + else if (myGame->getLocalSlotNum() == i) { if(slot->isAccepted() && !myGame->amIHost()) { @@ -425,7 +426,7 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], EnableAcceptControls(FALSE, myGame, comboPlayer, comboColor, comboPlayerTemplate, comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i); } - if(slot && slot->isHuman()) + if(slot->isHuman()) { UnicodeString newName = slot->getName(); UnicodeString oldName = GadgetComboBoxGetText(comboPlayer[i]); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp index 8d6b22755f5..3c86422d6f7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp @@ -454,6 +454,7 @@ GameSlot* GameInfo::getSlot( Int slotNum ) if (slotNum < 0 || slotNum >= MAX_SLOTS) return NULL; + DEBUG_ASSERTCRASH( m_slot[slotNum], ("NULL slot pointer") ); return m_slot[slotNum]; } @@ -463,6 +464,7 @@ const GameSlot* GameInfo::getConstSlot( Int slotNum ) const if (slotNum < 0 || slotNum >= MAX_SLOTS) return NULL; + DEBUG_ASSERTCRASH( m_slot[slotNum], ("NULL slot pointer") ); return m_slot[slotNum]; } From 896c0bc3219d4f3103a4e0dd6d0ab824f8e60a0b Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:18:48 +0200 Subject: [PATCH 18/55] [GEN][ZH] Prevent dereferencing NULL pointer 'button' in ScriptActions::doCameoFlash() (#1121) --- .../GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp | 1 + .../GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index e07e6bc95b9..ffc82e81363 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -2551,6 +2551,7 @@ void ScriptActions::doCameoFlash(const AsciiString& name, Int timeInSeconds) if( button == NULL ) { DEBUG_CRASH(( "ScriptActions::doCameoFlash can't find AsciiString cameoflash" )); + return; } Int frames = LOGICFRAMES_PER_SECOND * timeInSeconds; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index b91f81e880f..bdff64698ad 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -2628,6 +2628,7 @@ void ScriptActions::doCameoFlash(const AsciiString& name, Int timeInSeconds) if( button == NULL ) { DEBUG_CRASH(( "ScriptActions::doCameoFlash can't find AsciiString cameoflash" )); + return; } Int frames = LOGICFRAMES_PER_SECOND * timeInSeconds; From 42bf7b9db93ebf1c3c6d006fedab9bb963526c50 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:19:08 +0200 Subject: [PATCH 19/55] [GEN][ZH] Prevent using uninitialized memory 'hr' in W3DShaderManager::LoadAndCreateD3DShader() (#1123) --- .../Source/W3DDevice/GameClient/W3DShaderManager.cpp | 4 ++-- .../Source/W3DDevice/GameClient/W3DShaderManager.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index f562493b036..6fe7a765013 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -2773,11 +2773,11 @@ HRESULT W3DShaderManager::LoadAndCreateD3DShader(const char* strFilePath, const file->close(); file = NULL; - if (ShaderType == TRUE)//SHADERTYPE_VERTEX) + if (ShaderType) // SHADERTYPE_VERTEX { hr = DX8Wrapper::_Get_D3D_Device8()->CreateVertexShader(pDeclaration, pShader, pHandle, Usage); } - else if (ShaderType == FALSE)//SHADERTYPE_PIXEL) + else // SHADERTYPE_PIXEL { hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader(pShader, pHandle); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index 3d746a5abbc..460c28714c2 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -3043,11 +3043,11 @@ HRESULT W3DShaderManager::LoadAndCreateD3DShader(const char* strFilePath, const file->close(); file = NULL; - if (ShaderType == TRUE)//SHADERTYPE_VERTEX) + if (ShaderType) // SHADERTYPE_VERTEX { hr = DX8Wrapper::_Get_D3D_Device8()->CreateVertexShader(pDeclaration, pShader, pHandle, Usage); } - else if (ShaderType == FALSE)//SHADERTYPE_PIXEL) + else // SHADERTYPE_PIXEL { hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader(pShader, pHandle); } From ef82fdf1e509f87de872c106fa33be3c8e4c86e8 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:19:22 +0200 Subject: [PATCH 20/55] [GEN][ZH] Add missing break keywords between switch labels in W3DProjectedShadow, W3DDisplay, W3DWater (#1125) --- .../W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp | 5 +++++ .../Source/W3DDevice/GameClient/W3DDisplay.cpp | 1 + .../Source/W3DDevice/GameClient/Water/W3DWater.cpp | 1 + .../W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp | 5 +++++ .../Source/W3DDevice/GameClient/W3DDisplay.cpp | 1 + .../Source/W3DDevice/GameClient/Water/W3DWater.cpp | 1 + 6 files changed, 14 insertions(+) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index bbd0b18ae2f..5ac331ddc94 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -1575,6 +1575,7 @@ Shadow* W3DProjectedShadowManager::addDecal(Shadow::ShadowTypeInfo *shadowInfo) break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -1711,6 +1712,7 @@ Shadow* W3DProjectedShadowManager::addDecal(RenderObjClass *robj, Shadow::Shadow break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -1907,6 +1909,7 @@ W3DProjectedShadow* W3DProjectedShadowManager::addShadow(RenderObjClass *robj, S break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -2037,6 +2040,7 @@ void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) break; case SHADOW_PROJECTION: m_numProjectionShadows--; + break; default: break; } @@ -2062,6 +2066,7 @@ void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) break; case SHADOW_PROJECTION: m_numProjectionShadows--; + break; default: break; } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 6794944a86d..8a5f3c0a3a6 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -2525,6 +2525,7 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, m_2DRender->Enable_Additive(false); m_2DRender->Enable_Alpha(false); doAlphaReset = TRUE; + break; default: break; } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 7167fb16331..857064fe16b 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -1695,6 +1695,7 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) renderWater(); } //WATER_TYPE_1 + break; default: break; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index 84b7380769c..2a0cdcad82a 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -1575,6 +1575,7 @@ Shadow* W3DProjectedShadowManager::addDecal(Shadow::ShadowTypeInfo *shadowInfo) break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -1711,6 +1712,7 @@ Shadow* W3DProjectedShadowManager::addDecal(RenderObjClass *robj, Shadow::Shadow break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -1907,6 +1909,7 @@ W3DProjectedShadow* W3DProjectedShadowManager::addShadow(RenderObjClass *robj, S break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -2037,6 +2040,7 @@ void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) break; case SHADOW_PROJECTION: m_numProjectionShadows--; + break; default: break; } @@ -2062,6 +2066,7 @@ void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) break; case SHADOW_PROJECTION: m_numProjectionShadows--; + break; default: break; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 3c8ddf6c904..1a12f98a3cd 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -2662,6 +2662,7 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, m_2DRender->Enable_Additive(false); m_2DRender->Enable_Alpha(false); doAlphaReset = TRUE; + break; default: break; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index f880e070de9..60dad81fecf 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -1718,6 +1718,7 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) renderWater(); } //WATER_TYPE_1 + break; default: break; From 80e8b23eea8f5e05e83f7b15beb289658af41fa5 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:19:39 +0200 Subject: [PATCH 21/55] [GEN][ZH] Fix dereferencing NULL pointer 'CBScheme->m_buttonQueueImage' in ControlBarSchemeManager::preloadAssets() (#1126) --- .../Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp | 2 +- .../Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp index 423ea8559a7..0ddac645277 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp @@ -991,7 +991,7 @@ void ControlBarSchemeManager::preloadAssets( TimeOfDay timeOfDay ) if (CBScheme->m_rightHUDImage) { - TheDisplay->preloadTextureAssets(CBScheme->m_buttonQueueImage->getFilename()); + TheDisplay->preloadTextureAssets(CBScheme->m_rightHUDImage->getFilename()); } for (Int layer = 0; layer < MAX_CONTROL_BAR_SCHEME_IMAGE_LAYERS; ++layer) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp index 22e49b08fe4..2684160703b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp @@ -1004,7 +1004,7 @@ void ControlBarSchemeManager::preloadAssets( TimeOfDay timeOfDay ) if (CBScheme->m_rightHUDImage) { - TheDisplay->preloadTextureAssets(CBScheme->m_buttonQueueImage->getFilename()); + TheDisplay->preloadTextureAssets(CBScheme->m_rightHUDImage->getFilename()); } for (Int layer = 0; layer < MAX_CONTROL_BAR_SCHEME_IMAGE_LAYERS; ++layer) From e83038f7b943eb76663a94c9ecf82e19362cabd4 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:20:26 +0200 Subject: [PATCH 22/55] [GEN][ZH] Fix using uninitialized memory 'window' in GameWindowManagerScript's createGadget (#1127) --- .../Source/GameClient/GUI/GameWindowManagerScript.cpp | 2 +- .../Source/GameClient/GUI/GameWindowManagerScript.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index 420b5b87db8..2bb9320edd7 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -1621,7 +1621,7 @@ static GameWindow *createGadget( char *type, WinInstanceData *instData, void *data ) { - GameWindow *window; + GameWindow *window = NULL; instData->m_owner = parent; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index 9a2176e1ece..4c22f9c081b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -1629,7 +1629,7 @@ static GameWindow *createGadget( char *type, WinInstanceData *instData, void *data ) { - GameWindow *window; + GameWindow *window = NULL; instData->m_owner = parent; From 37b73d72fc92ab98eb7a73a18b136fe3a5f29157 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 12:57:21 +0200 Subject: [PATCH 23/55] [GEN][ZH] Fix incorrect default Resolution selection in the Options Menu when the loaded Resolution does not match the active Display Resolution (#1056) --- .../GUI/GUICallbacks/Menus/OptionsMenu.cpp | 14 ++++++++++---- .../GUI/GUICallbacks/Menus/OptionsMenu.cpp | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 4bd172b22d9..c80a6595162 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -1578,21 +1578,27 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) // populate resolution modes GadgetComboBoxReset(comboBoxResolution); Int numResolutions = TheDisplay->getDisplayModeCount(); + UnsignedInt displayWidth = TheDisplay->getWidth(); + UnsignedInt displayHeight = TheDisplay->getHeight(); + for( i = 0; i < numResolutions; ++i ) { Int xres,yres,bitDepth; TheDisplay->getDisplayModeDescription(i,&xres,&yres,&bitDepth); str.format(L"%d x %d",xres,yres); GadgetComboBoxAddEntry( comboBoxResolution, str, color); - if (xres == selectedXRes && yres == selectedYRes) + // TheSuperHackers @bugfix xezon 12/06/2025 Now makes a selection with the active display resolution + // instead of the resolution read from the Option Preferences, because the active display resolution + // is the most relevant to make a selection with and the Option Preferences could be wrong. + if ( xres == displayWidth && yres == displayHeight ) selectedResIndex=i; } - if (selectedResIndex == -1) //check if saved mode no longer available + if (selectedResIndex == -1) { // TheSuperHackers @bugfix xezon 08/06/2025 Now adds the current resolution instead of defaulting to 800 x 600. // This avoids force changing the resolution when the user has set a custom resolution in the Option Preferences. - Int xres = TheDisplay->getWidth(); - Int yres = TheDisplay->getHeight(); + Int xres = displayWidth; + Int yres = displayHeight; str.format(L"%d x %d",xres,yres); GadgetComboBoxAddEntry( comboBoxResolution, str, color ); selectedResIndex = GadgetComboBoxGetLength( comboBoxResolution ) - 1; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 29d0afddb71..b2c132d0c98 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -1644,21 +1644,27 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) // populate resolution modes GadgetComboBoxReset(comboBoxResolution); Int numResolutions = TheDisplay->getDisplayModeCount(); + UnsignedInt displayWidth = TheDisplay->getWidth(); + UnsignedInt displayHeight = TheDisplay->getHeight(); + for( i = 0; i < numResolutions; ++i ) { Int xres,yres,bitDepth; TheDisplay->getDisplayModeDescription(i,&xres,&yres,&bitDepth); str.format(L"%d x %d",xres,yres); GadgetComboBoxAddEntry( comboBoxResolution, str, color); - if (xres == selectedXRes && yres == selectedYRes) + // TheSuperHackers @bugfix xezon 12/06/2025 Now makes a selection with the active display resolution + // instead of the resolution read from the Option Preferences, because the active display resolution + // is the most relevant to make a selection with and the Option Preferences could be wrong. + if ( xres == displayWidth && yres == displayHeight ) selectedResIndex=i; } - if (selectedResIndex == -1) //check if saved mode no longer available + if (selectedResIndex == -1) { // TheSuperHackers @bugfix xezon 08/06/2025 Now adds the current resolution instead of defaulting to 800 x 600. // This avoids force changing the resolution when the user has set a custom resolution in the Option Preferences. - Int xres = TheDisplay->getWidth(); - Int yres = TheDisplay->getHeight(); + Int xres = displayWidth; + Int yres = displayHeight; str.format(L"%d x %d",xres,yres); GadgetComboBoxAddEntry( comboBoxResolution, str, color ); selectedResIndex = GadgetComboBoxGetLength( comboBoxResolution ) - 1; From fd0cb36b2e48b8741b23f801c91aaf3f5d2ce468 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 13:53:37 +0200 Subject: [PATCH 24/55] [CORE] Fix copying of RefCountClass, RefCountValue (#1081) --- Core/Libraries/Source/WWVegas/WWLib/refcount.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Core/Libraries/Source/WWVegas/WWLib/refcount.h b/Core/Libraries/Source/WWVegas/WWLib/refcount.h index c7d1a702936..6ee4f8f9b60 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/refcount.h +++ b/Core/Libraries/Source/WWVegas/WWLib/refcount.h @@ -123,6 +123,9 @@ class RefCountClass #endif } + /* + ** The reference counter value cannot be copied. + */ RefCountClass(const RefCountClass & ) : NumRefs(1) #ifndef NDEBUG @@ -135,6 +138,8 @@ class RefCountClass #endif } + RefCountClass& operator=(const RefCountClass&) { return *this; } + /* ** Add_Ref, call this function if you are going to keep a pointer ** to this object. @@ -280,6 +285,12 @@ class RefCountValue WWASSERT(NumRefs == IntegerType(0)); } + /* + ** The reference counter value cannot be copied. + */ + RefCountValue(const RefCountValue&) : NumRefs(1) {} + RefCountValue& operator=(const RefCountValue&) { return *this; } + /* ** Add_Ref, call this function if you are going to keep a pointer to this object. */ From 795c025b74ee3df88a0d7748d337142c47199fce Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 23:12:02 +0200 Subject: [PATCH 25/55] [GEN][ZH] Prevent unlikely buffer overrun while writing to 'newPts' in PolygonTrigger::reallocate() (#1091) --- .../Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp | 4 ++++ .../Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp index e2092633986..2841377d9e2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp @@ -95,6 +95,10 @@ void PolygonTrigger::reallocate(void) { DEBUG_ASSERTCRASH(m_numPoints <= m_sizePoints, ("Invalid m_numPoints.")); if (m_numPoints == m_sizePoints) { + if (m_sizePoints > INT_MAX / 2) { + DEBUG_CRASH(("Too many points to allocate.")); + return; + } // Reallocate. m_sizePoints += m_sizePoints; ICoord3D *newPts = NEW ICoord3D[m_sizePoints]; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp index 5e1dc0fb657..a0878a76b43 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp @@ -97,6 +97,10 @@ void PolygonTrigger::reallocate(void) { DEBUG_ASSERTCRASH(m_numPoints <= m_sizePoints, ("Invalid m_numPoints.")); if (m_numPoints == m_sizePoints) { + if (m_sizePoints > INT_MAX / 2) { + DEBUG_CRASH(("Too many points to allocate.")); + return; + } // Reallocate. m_sizePoints += m_sizePoints; ICoord3D *newPts = NEW ICoord3D[m_sizePoints]; From f7f26a4fa15c47c1f038b7b23a8493bb22ac76f7 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 23:13:22 +0200 Subject: [PATCH 26/55] [GEN][ZH] Fix dereferencing NULL pointer 'victimObj' in WeaponTemplate::estimateWeaponTemplateDamage() (#1093) --- .../Source/GameLogic/Object/Weapon.cpp | 53 +++++++----------- .../Source/GameLogic/Object/Weapon.cpp | 55 ++++++++----------- 2 files changed, 43 insertions(+), 65 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 31757c6f4a5..d1ed003577a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -567,10 +567,16 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( return 0.0f; } + const Real damageAmount = getPrimaryDamage(bonus); + if ( victimObj == NULL ) + { + return damageAmount; + } + DamageType damageType = getDamageType(); DeathType deathType = getDeathType(); - if (victimObj && victimObj->isKindOf(KINDOF_SHRUBBERY)) + if ( victimObj->isKindOf(KINDOF_SHRUBBERY) ) { if (deathType == DEATH_BURNED) { @@ -584,7 +590,7 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( } // this stays, even if ALLOW_SURRENDER is not defed, since flashbangs still use 'em - if (damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs) + if ( damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs ) { ContainModuleInterface* contain = victimObj->getContain(); if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) @@ -594,42 +600,25 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( } } - if( victimObj ) + if( victimObj->isKindOf(KINDOF_MINE) && damageType == DAMAGE_DISARM ) { - if( victimObj->isKindOf(KINDOF_MINE) && damageType == DAMAGE_DISARM ) - { - // this is just a nonzero value, to ensure we can target mines with disarm weapons, regardless... - return 1.0f; - } - if( damageType == DAMAGE_DEPLOY && !victimObj->isAirborneTarget() ) - { - return 1.0f; - } + // this is just a nonzero value, to ensure we can target mines with disarm weapons, regardless... + return 1.0f; + } + if( damageType == DAMAGE_DEPLOY && !victimObj->isAirborneTarget() ) + { + return 1.0f; } //@todo Kris need to examine the DAMAGE_HACK type for damage estimation purposes. //Likely this damage type will have threat implications that won't properly be dealt with until resolved. -// const Coord3D* sourcePos = sourceObj->getPosition(); - if (victimPos == NULL) - { - victimPos = victimObj->getPosition(); - } - - Real damageAmount = getPrimaryDamage(bonus); - if (victimObj == NULL) - { - return damageAmount; - } - else - { - DamageInfoInput damageInfo; - damageInfo.m_damageType = damageType; - damageInfo.m_deathType = deathType; - damageInfo.m_sourceID = sourceObj->getID(); - damageInfo.m_amount = damageAmount; - return victimObj->estimateDamage(damageInfo); - } + DamageInfoInput damageInfo; + damageInfo.m_damageType = damageType; + damageInfo.m_deathType = deathType; + damageInfo.m_sourceID = sourceObj->getID(); + damageInfo.m_amount = damageAmount; + return victimObj->estimateDamage(damageInfo); } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 2686ad2798a..8342e12133c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -580,10 +580,16 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( return 0.0f; } + const Real damageAmount = getPrimaryDamage(bonus); + if ( victimObj == NULL ) + { + return damageAmount; + } + DamageType damageType = getDamageType(); DeathType deathType = getDeathType(); - if (victimObj && victimObj->isKindOf(KINDOF_SHRUBBERY)) + if ( victimObj->isKindOf(KINDOF_SHRUBBERY) ) { if (deathType == DEATH_BURNED) { @@ -610,7 +616,7 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( - if (damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs) + if ( damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs ) { ContainModuleInterface* contain = victimObj->getContain(); if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) @@ -620,46 +626,29 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( } } - if( victimObj ) + if( damageType == DAMAGE_DISARM ) { - if( damageType == DAMAGE_DISARM ) - { - if( victimObj->isKindOf( KINDOF_MINE ) || victimObj->isKindOf( KINDOF_BOOBY_TRAP ) || victimObj->isKindOf( KINDOF_DEMOTRAP ) ) - { - // this is just a nonzero value, to ensure we can target mines with disarm weapons, regardless... - return 1.0f; - } - return 0.0f; - } - if( damageType == DAMAGE_DEPLOY && !victimObj->isAirborneTarget() ) + if( victimObj->isKindOf( KINDOF_MINE ) || victimObj->isKindOf( KINDOF_BOOBY_TRAP ) || victimObj->isKindOf( KINDOF_DEMOTRAP ) ) { + // this is just a nonzero value, to ensure we can target mines with disarm weapons, regardless... return 1.0f; } + return 0.0f; + } + if( damageType == DAMAGE_DEPLOY && !victimObj->isAirborneTarget() ) + { + return 1.0f; } //@todo Kris need to examine the DAMAGE_HACK type for damage estimation purposes. //Likely this damage type will have threat implications that won't properly be dealt with until resolved. -// const Coord3D* sourcePos = sourceObj->getPosition(); - if (victimPos == NULL) - { - victimPos = victimObj->getPosition(); - } - - Real damageAmount = getPrimaryDamage(bonus); - if (victimObj == NULL) - { - return damageAmount; - } - else - { - DamageInfoInput damageInfo; - damageInfo.m_damageType = damageType; - damageInfo.m_deathType = deathType; - damageInfo.m_sourceID = sourceObj->getID(); - damageInfo.m_amount = damageAmount; - return victimObj->estimateDamage(damageInfo); - } + DamageInfoInput damageInfo; + damageInfo.m_damageType = damageType; + damageInfo.m_deathType = deathType; + damageInfo.m_sourceID = sourceObj->getID(); + damageInfo.m_amount = damageAmount; + return victimObj->estimateDamage(damageInfo); } //------------------------------------------------------------------------------------------------- From 0279a8cbfaba5af092fc1e39bbce7b6fd50e684f Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 21 Jun 2025 23:14:01 +0200 Subject: [PATCH 27/55] [GEN][ZH] Fix using uninitialized memory 'buff' in INI::getNextQuotedAsciiString() (#1097) --- Generals/Code/GameEngine/Source/Common/INI/INI.cpp | 1 + GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp index ec573971627..5fa23ac345a 100644 --- a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp @@ -725,6 +725,7 @@ AsciiString INI::getNextQuotedAsciiString() { AsciiString result; char buff[INI_MAX_CHARS_PER_LINE]; + buff[0] = '\0'; const char *token = getNextTokenOrNull(); // if null, just leave an empty string if (token != NULL) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index 1634d019aec..bc9569238f1 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -717,6 +717,7 @@ AsciiString INI::getNextQuotedAsciiString() { AsciiString result; char buff[INI_MAX_CHARS_PER_LINE]; + buff[0] = '\0'; const char *token = getNextTokenOrNull(); // if null, just leave an empty string if (token != NULL) From 5104ace041ab46c2fb032d98acdad3f62aa18db2 Mon Sep 17 00:00:00 2001 From: helmutbuhler Date: Sun, 22 Jun 2025 06:44:52 +0200 Subject: [PATCH 28/55] [GEN][ZH] Add Replay Simulation feature (#923) Invoked with command line arguments -headless -jobs 1..N -replay ReplayName1 -replay ReplayName2 --- Core/GameEngine/CMakeLists.txt | 4 + .../Include/Common/ReplaySimulation.h | 48 ++++ .../GameEngine/Include/Common/WorkerProcess.h | 56 ++++ .../Source/Common/ReplaySimulation.cpp | 255 ++++++++++++++++++ .../Source/Common/WorkerProcess.cpp | 231 ++++++++++++++++ Dependencies/Utility/Utility/CppMacros.h | 6 +- Generals/Code/GameEngine/CMakeLists.txt | 3 + .../GameEngine/Include/Common/GameEngine.h | 3 +- .../GameEngine/Include/Common/GlobalData.h | 8 +- .../Code/GameEngine/Include/Common/Recorder.h | 12 +- .../Include/GameClient/ClientInstance.h | 10 + .../Include/GameClient/GameClient.h | 2 + .../GameEngine/Source/Common/CommandLine.cpp | 73 ++++- .../GameEngine/Source/Common/GameEngine.cpp | 9 +- .../GameEngine/Source/Common/GameMain.cpp | 20 +- .../GameEngine/Source/Common/GlobalData.cpp | 3 + .../GameEngine/Source/Common/Recorder.cpp | 64 +++-- .../GameEngine/Source/Common/System/Debug.cpp | 4 +- .../Source/GameClient/ClientInstance.cpp | 84 ++++-- .../GUI/GUICallbacks/Menus/ReplayMenu.cpp | 6 +- .../GUI/GUICallbacks/Menus/ScoreScreen.cpp | 47 +++- .../Source/GameClient/GUI/Shell/Shell.cpp | 4 +- .../Source/GameClient/GameClient.cpp | 23 +- .../Source/GameLogic/System/GameLogic.cpp | 41 +-- .../GameLogic/System/GameLogicDispatch.cpp | 6 +- .../Source/GameNetwork/IPEnumeration.cpp | 17 +- .../Source/WWVegas/WW3D2/textureloader.cpp | 2 +- Generals/Code/Main/WinMain.cpp | 9 +- GeneralsMD/Code/GameEngine/CMakeLists.txt | 4 + .../GameEngine/Include/Common/GameEngine.h | 3 +- .../GameEngine/Include/Common/GlobalData.h | 8 +- .../Code/GameEngine/Include/Common/Recorder.h | 12 +- .../Include/GameClient/ClientInstance.h | 10 + .../Include/GameClient/GameClient.h | 2 + .../GameEngine/Source/Common/CommandLine.cpp | 73 ++++- .../GameEngine/Source/Common/GameEngine.cpp | 9 +- .../GameEngine/Source/Common/GameMain.cpp | 20 +- .../GameEngine/Source/Common/GlobalData.cpp | 3 + .../GameEngine/Source/Common/Recorder.cpp | 64 +++-- .../GameEngine/Source/Common/System/Debug.cpp | 4 +- .../Source/GameClient/ClientInstance.cpp | 86 ++++-- .../GUI/GUICallbacks/Menus/ReplayMenu.cpp | 6 +- .../GUI/GUICallbacks/Menus/ScoreScreen.cpp | 47 +++- .../Source/GameClient/GUI/Shell/Shell.cpp | 4 +- .../Source/GameClient/GameClient.cpp | 23 +- .../Source/GameLogic/System/GameLogic.cpp | 41 +-- .../GameLogic/System/GameLogicDispatch.cpp | 6 +- .../Source/GameNetwork/IPEnumeration.cpp | 17 +- GeneralsMD/Code/Main/WinMain.cpp | 9 +- 49 files changed, 1231 insertions(+), 270 deletions(-) create mode 100644 Core/GameEngine/Include/Common/ReplaySimulation.h create mode 100644 Core/GameEngine/Include/Common/WorkerProcess.h create mode 100644 Core/GameEngine/Source/Common/ReplaySimulation.cpp create mode 100644 Core/GameEngine/Source/Common/WorkerProcess.cpp diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 35fc9a97d1e..56b8b90cfac 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -95,6 +95,7 @@ set(GAMEENGINE_SRC # Include/Common/RandomValue.h # Include/Common/Recorder.h # Include/Common/Registry.h + Include/Common/ReplaySimulation.h # Include/Common/ResourceGatheringManager.h # Include/Common/Science.h # Include/Common/ScopedMutex.h @@ -129,6 +130,7 @@ set(GAMEENGINE_SRC # Include/Common/UserPreferences.h # Include/Common/version.h # Include/Common/WellKnownKeys.h + Include/Common/WorkerProcess.h Include/Common/Xfer.h Include/Common/XferCRC.h Include/Common/XferDeepCRC.h @@ -607,6 +609,7 @@ set(GAMEENGINE_SRC # Source/Common/PerfTimer.cpp # Source/Common/RandomValue.cpp # Source/Common/Recorder.cpp + Source/Common/ReplaySimulation.cpp # Source/Common/RTS/AcademyStats.cpp # Source/Common/RTS/ActionManager.cpp # Source/Common/RTS/Energy.cpp @@ -676,6 +679,7 @@ set(GAMEENGINE_SRC # Source/Common/Thing/ThingTemplate.cpp # Source/Common/UserPreferences.cpp # Source/Common/version.cpp + Source/Common/WorkerProcess.cpp # Source/GameClient/ClientInstance.cpp # Source/GameClient/Color.cpp # Source/GameClient/Credits.cpp diff --git a/Core/GameEngine/Include/Common/ReplaySimulation.h b/Core/GameEngine/Include/Common/ReplaySimulation.h new file mode 100644 index 00000000000..219f6233709 --- /dev/null +++ b/Core/GameEngine/Include/Common/ReplaySimulation.h @@ -0,0 +1,48 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +class ReplaySimulation +{ +public: + + // TheSuperHackers @feature helmutbuhler 13/04/2025 + // Simulate a list of replays without graphics. + // Returns exit code 1 if mismatch or other error occurred + // Returns exit code 0 if all replays were successfully simulated without mismatches + static int simulateReplays(const std::vector &filenames, int maxProcesses); + + static void stop() { s_isRunning = false; } + + static Bool isRunning() { return s_isRunning; } + static UnsignedInt getCurrentReplayIndex() { return s_replayIndex; } + static UnsignedInt getReplayCount() { return s_replayCount; } + +private: + + static int simulateReplaysInThisProcess(const std::vector &filenames); + static int simulateReplaysInWorkerProcesses(const std::vector &filenames, int maxProcesses); + static std::vector resolveFilenameWildcards(const std::vector &filenames); + +private: + + static Bool s_isRunning; + static UnsignedInt s_replayIndex; + static UnsignedInt s_replayCount; +}; diff --git a/Core/GameEngine/Include/Common/WorkerProcess.h b/Core/GameEngine/Include/Common/WorkerProcess.h new file mode 100644 index 00000000000..0a0b104f64b --- /dev/null +++ b/Core/GameEngine/Include/Common/WorkerProcess.h @@ -0,0 +1,56 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +// Helper class that allows you to start a worker process and retrieve its exit code +// and console output as a string. +// It also makes sure that the started process is killed in case our process exits in any way. +class WorkerProcess +{ +public: + WorkerProcess(); + + bool startProcess(UnicodeString command); + + void update(); + + bool isRunning() const; + + // returns true iff the process exited. + bool isDone() const; + + DWORD getExitCode() const; + AsciiString getStdOutput() const; + + // Terminate Process if it's running + void kill(); + +private: + // returns true if all output has been received + // returns false if the worker is still running + bool fetchStdOutput(); + +private: + HANDLE m_processHandle; + HANDLE m_readHandle; + HANDLE m_jobHandle; + AsciiString m_stdOutput; + DWORD m_exitcode; + bool m_isDone; +}; diff --git a/Core/GameEngine/Source/Common/ReplaySimulation.cpp b/Core/GameEngine/Source/Common/ReplaySimulation.cpp new file mode 100644 index 00000000000..354925c8fcd --- /dev/null +++ b/Core/GameEngine/Source/Common/ReplaySimulation.cpp @@ -0,0 +1,255 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#include "Common/ReplaySimulation.h" + +#include "Common/GameEngine.h" +#include "Common/LocalFileSystem.h" +#include "Common/Recorder.h" +#include "Common/WorkerProcess.h" +#include "GameLogic/GameLogic.h" +#include "GameClient/GameClient.h" + + +Bool ReplaySimulation::s_isRunning = false; +UnsignedInt ReplaySimulation::s_replayIndex = 0; +UnsignedInt ReplaySimulation::s_replayCount = 0; + +namespace +{ +int countProcessesRunning(const std::vector& processes) +{ + int numProcessesRunning = 0; + size_t i = 0; + for (; i < processes.size(); ++i) + { + if (processes[i].isRunning()) + ++numProcessesRunning; + } + return numProcessesRunning; +} +} // namespace + +int ReplaySimulation::simulateReplaysInThisProcess(const std::vector &filenames) +{ + int numErrors = 0; + + if (!TheGlobalData->m_headless) + { + s_isRunning = true; + s_replayIndex = 0; + s_replayCount = static_cast(filenames.size()); + + // If we are not in headless mode, we need to run the replay in the engine. + for (; s_replayIndex < s_replayCount; ++s_replayIndex) + { + TheRecorder->playbackFile(filenames[s_replayIndex]); + TheGameEngine->execute(); + if (TheRecorder->sawCRCMismatch()) + numErrors++; + if (!s_isRunning) + break; + TheGameEngine->setQuitting(FALSE); + } + s_isRunning = false; + s_replayIndex = 0; + s_replayCount = 0; + return numErrors != 0 ? 1 : 0; + } + // Note that we use printf here because this is run from cmd. + DWORD totalStartTimeMillis = GetTickCount(); + for (size_t i = 0; i < filenames.size(); i++) + { + AsciiString filename = filenames[i]; + printf("Simulating Replay \"%s\"\n", filename.str()); + fflush(stdout); + DWORD startTimeMillis = GetTickCount(); + if (TheRecorder->simulateReplay(filename)) + { + UnsignedInt totalTimeSec = TheRecorder->getPlaybackFrameCount() / LOGICFRAMES_PER_SECOND; + while (TheRecorder->isPlaybackInProgress()) + { + TheGameClient->updateHeadless(); + + const int progressFrameInterval = 10*60*LOGICFRAMES_PER_SECOND; + if (TheGameLogic->getFrame() != 0 && TheGameLogic->getFrame() % progressFrameInterval == 0) + { + // Print progress report + UnsignedInt gameTimeSec = TheGameLogic->getFrame() / LOGICFRAMES_PER_SECOND; + UnsignedInt realTimeSec = (GetTickCount()-startTimeMillis) / 1000; + printf("Elapsed Time: %02d:%02d Game Time: %02d:%02d/%02d:%02d\n", + realTimeSec/60, realTimeSec%60, gameTimeSec/60, gameTimeSec%60, totalTimeSec/60, totalTimeSec%60); + fflush(stdout); + } + TheGameLogic->UPDATE(); + if (TheRecorder->sawCRCMismatch()) + { + numErrors++; + break; + } + } + UnsignedInt gameTimeSec = TheGameLogic->getFrame() / LOGICFRAMES_PER_SECOND; + UnsignedInt realTimeSec = (GetTickCount()-startTimeMillis) / 1000; + printf("Elapsed Time: %02d:%02d Game Time: %02d:%02d/%02d:%02d\n", + realTimeSec/60, realTimeSec%60, gameTimeSec/60, gameTimeSec%60, totalTimeSec/60, totalTimeSec%60); + fflush(stdout); + } + else + { + printf("Cannot open replay\n"); + numErrors++; + } + } + if (filenames.size() > 1) + { + printf("Simulation of all replays completed. Errors occurred: %d\n", numErrors); + + UnsignedInt realTime = (GetTickCount()-totalStartTimeMillis) / 1000; + printf("Total Time: %d:%02d:%02d\n", realTime/60/60, realTime/60%60, realTime%60); + fflush(stdout); + } + + return numErrors != 0 ? 1 : 0; +} + +int ReplaySimulation::simulateReplaysInWorkerProcesses(const std::vector &filenames, int maxProcesses) +{ + DWORD totalStartTimeMillis = GetTickCount(); + + WideChar exePath[1024]; + GetModuleFileNameW(NULL, exePath, ARRAY_SIZE(exePath)); + + std::vector processes; + int filenamePositionStarted = 0; + int filenamePositionDone = 0; + int numErrors = 0; + + while (true) + { + int i; + for (i = 0; i < processes.size(); i++) + processes[i].update(); + + // Get result of finished processes and print output in order + while (!processes.empty()) + { + if (!processes[0].isDone()) + break; + AsciiString stdOutput = processes[0].getStdOutput(); + printf("%d/%d %s", filenamePositionDone+1, (int)filenames.size(), stdOutput.str()); + DWORD exitcode = processes[0].getExitCode(); + if (exitcode != 0) + printf("Error!\n"); + fflush(stdout); + numErrors += exitcode == 0 ? 0 : 1; + processes.erase(processes.begin()); + filenamePositionDone++; + } + + int numProcessesRunning = countProcessesRunning(processes); + + // Add new processes when we are below the limit and there are replays left + while (numProcessesRunning < maxProcesses && filenamePositionStarted < filenames.size()) + { + UnicodeString filenameWide; + filenameWide.translate(filenames[filenamePositionStarted]); + UnicodeString command; + command.format(L"\"%s\"%s%s -replay \"%s\"", + exePath, + TheGlobalData->m_windowed ? L" -win" : L"", + TheGlobalData->m_headless ? L" -headless" : L"", + filenameWide.str()); + + processes.push_back(WorkerProcess()); + processes.back().startProcess(command); + + filenamePositionStarted++; + numProcessesRunning++; + } + + if (processes.empty()) + break; + + // Don't waste CPU here, our workers need every bit of CPU time they can get + Sleep(100); + } + + DEBUG_ASSERTCRASH(filenamePositionStarted == filenames.size(), ("inconsistent file position 1")); + DEBUG_ASSERTCRASH(filenamePositionDone == filenames.size(), ("inconsistent file position 2")); + + printf("Simulation of all replays completed. Errors occurred: %d\n", numErrors); + + UnsignedInt realTime = (GetTickCount()-totalStartTimeMillis) / 1000; + printf("Total Wall Time: %d:%02d:%02d\n", realTime/60/60, realTime/60%60, realTime%60); + fflush(stdout); + + return numErrors != 0 ? 1 : 0; +} + +std::vector ReplaySimulation::resolveFilenameWildcards(const std::vector &filenames) +{ + // If some filename contains wildcards, search for actual filenames. + // Note that we cannot do this in parseReplay because we require TheLocalFileSystem initialized. + std::vector filenamesResolved; + for (std::vector::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) + { + if (filename->find('*') || filename->find('?')) + { + AsciiString dir1 = TheRecorder->getReplayDir(); + AsciiString dir2 = *filename; + AsciiString wildcard = *filename; + { + int len = dir2.getLength(); + while (len) + { + char c = dir2.getCharAt(len-1); + if (c == '/' || c == '\\') + { + wildcard.set(wildcard.str()+dir2.getLength()); + break; + } + dir2.removeLastChar(); + len--; + } + } + + FilenameList files; + TheLocalFileSystem->getFileListInDirectory(dir2.str(), dir1.str(), wildcard, files, FALSE); + for (FilenameList::iterator it = files.begin(); it != files.end(); ++it) + { + AsciiString file; + file.set(it->str() + dir1.getLength()); + filenamesResolved.push_back(file); + } + } + else + filenamesResolved.push_back(*filename); + } + return filenamesResolved; +} + +int ReplaySimulation::simulateReplays(const std::vector &filenames, int maxProcesses) +{ + std::vector filenamesResolved = resolveFilenameWildcards(filenames); + if (maxProcesses == SIMULATE_REPLAYS_SEQUENTIAL) + return simulateReplaysInThisProcess(filenamesResolved); + else + return simulateReplaysInWorkerProcesses(filenamesResolved, maxProcesses); +} diff --git a/Core/GameEngine/Source/Common/WorkerProcess.cpp b/Core/GameEngine/Source/Common/WorkerProcess.cpp new file mode 100644 index 00000000000..5514c4c2882 --- /dev/null +++ b/Core/GameEngine/Source/Common/WorkerProcess.cpp @@ -0,0 +1,231 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include "Common/WorkerProcess.h" + +// We need Job-related functions, but these aren't defined in the Windows-headers that VC6 uses. +// So we define them here and load them dynamically. +#if defined(_MSC_VER) && _MSC_VER < 1300 +struct JOBOBJECT_BASIC_LIMIT_INFORMATION2 +{ + LARGE_INTEGER PerProcessUserTimeLimit; + LARGE_INTEGER PerJobUserTimeLimit; + DWORD LimitFlags; + SIZE_T MinimumWorkingSetSize; + SIZE_T MaximumWorkingSetSize; + DWORD ActiveProcessLimit; + ULONG_PTR Affinity; + DWORD PriorityClass; + DWORD SchedulingClass; +}; +struct IO_COUNTERS +{ + ULONGLONG ReadOperationCount; + ULONGLONG WriteOperationCount; + ULONGLONG OtherOperationCount; + ULONGLONG ReadTransferCount; + ULONGLONG WriteTransferCount; + ULONGLONG OtherTransferCount; +}; +struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION +{ + JOBOBJECT_BASIC_LIMIT_INFORMATION2 BasicLimitInformation; + IO_COUNTERS IoInfo; + SIZE_T ProcessMemoryLimit; + SIZE_T JobMemoryLimit; + SIZE_T PeakProcessMemoryUsed; + SIZE_T PeakJobMemoryUsed; +}; + +#define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 0x00002000 +const int JobObjectExtendedLimitInformation = 9; + +typedef HANDLE (WINAPI *PFN_CreateJobObjectW)(LPSECURITY_ATTRIBUTES, LPCWSTR); +typedef BOOL (WINAPI *PFN_SetInformationJobObject)(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD); +typedef BOOL (WINAPI *PFN_AssignProcessToJobObject)(HANDLE, HANDLE); + +static PFN_CreateJobObjectW CreateJobObjectW = (PFN_CreateJobObjectW)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateJobObjectW"); +static PFN_SetInformationJobObject SetInformationJobObject = (PFN_SetInformationJobObject)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "SetInformationJobObject"); +static PFN_AssignProcessToJobObject AssignProcessToJobObject = (PFN_AssignProcessToJobObject)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "AssignProcessToJobObject"); +#endif + +WorkerProcess::WorkerProcess() +{ + m_processHandle = NULL; + m_readHandle = NULL; + m_jobHandle = NULL; + m_exitcode = 0; + m_isDone = false; +} + +bool WorkerProcess::startProcess(UnicodeString command) +{ + m_stdOutput.clear(); + m_isDone = false; + + // Create pipe for reading console output + SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) }; + saAttr.bInheritHandle = TRUE; + HANDLE writeHandle = NULL; + if (!CreatePipe(&m_readHandle, &writeHandle, &saAttr, 0)) + return false; + SetHandleInformation(m_readHandle, HANDLE_FLAG_INHERIT, 0); + + STARTUPINFOW si = { sizeof(STARTUPINFOW) }; + si.dwFlags = STARTF_FORCEOFFFEEDBACK; // Prevent cursor wait animation + si.dwFlags |= STARTF_USESTDHANDLES; + si.hStdError = writeHandle; + si.hStdOutput = writeHandle; + + PROCESS_INFORMATION pi = { 0 }; + + if (!CreateProcessW(NULL, (LPWSTR)command.str(), + NULL, NULL, /*bInheritHandles=*/TRUE, 0, + NULL, 0, &si, &pi)) + { + CloseHandle(writeHandle); + CloseHandle(m_readHandle); + m_readHandle = NULL; + return false; + } + + CloseHandle(pi.hThread); + CloseHandle(writeHandle); + m_processHandle = pi.hProcess; + + // We want to make sure that when our process is killed, our workers automatically terminate as well. + // In Windows, the way to do this is to attach the worker to a job we own. + m_jobHandle = CreateJobObjectW != NULL ? CreateJobObjectW(NULL, NULL) : NULL; + if (m_jobHandle != NULL) + { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = { 0 }; + jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + SetInformationJobObject(m_jobHandle, (JOBOBJECTINFOCLASS)JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo)); + AssignProcessToJobObject(m_jobHandle, m_processHandle); + } + + return true; +} + +bool WorkerProcess::isRunning() const +{ + return m_processHandle != NULL; +} + +bool WorkerProcess::isDone() const +{ + return m_isDone; +} + +DWORD WorkerProcess::getExitCode() const +{ + return m_exitcode; +} + +AsciiString WorkerProcess::getStdOutput() const +{ + return m_stdOutput; +} + +bool WorkerProcess::fetchStdOutput() +{ + while (true) + { + // Call PeekNamedPipe to make sure ReadFile won't block + DWORD bytesAvailable = 0; + DEBUG_ASSERTCRASH(m_readHandle != NULL, ("Is not expected NULL")); + BOOL success = PeekNamedPipe(m_readHandle, NULL, 0, NULL, &bytesAvailable, NULL); + if (!success) + return true; + if (bytesAvailable == 0) + { + // Child process is still running and we have all output so far + return false; + } + + DWORD readBytes = 0; + char buffer[1024]; + success = ReadFile(m_readHandle, buffer, ARRAY_SIZE(buffer)-1, &readBytes, NULL); + if (!success) + return true; + DEBUG_ASSERTCRASH(readBytes != 0, ("expected readBytes to be non null")); + + // Remove \r, otherwise each new line is doubled when we output it again + for (int i = 0; i < readBytes; i++) + if (buffer[i] == '\r') + buffer[i] = ' '; + buffer[readBytes] = 0; + m_stdOutput.concat(buffer); + } +} + +void WorkerProcess::update() +{ + if (!isRunning()) + return; + + if (!fetchStdOutput()) + { + // There is still potential output pending + return; + } + + // Pipe broke, that means the process already exited. But we call this just to make sure + WaitForSingleObject(m_processHandle, INFINITE); + GetExitCodeProcess(m_processHandle, &m_exitcode); + CloseHandle(m_processHandle); + m_processHandle = NULL; + + CloseHandle(m_readHandle); + m_readHandle = NULL; + + CloseHandle(m_jobHandle); + m_jobHandle = NULL; + + m_isDone = true; +} + +void WorkerProcess::kill() +{ + if (!isRunning()) + return; + + if (m_processHandle != NULL) + { + TerminateProcess(m_processHandle, 1); + CloseHandle(m_processHandle); + m_processHandle = NULL; + } + + if (m_readHandle != NULL) + { + CloseHandle(m_readHandle); + m_readHandle = NULL; + } + + if (m_jobHandle != NULL) + { + CloseHandle(m_jobHandle); + m_jobHandle = NULL; + } + + m_stdOutput.clear(); + m_isDone = false; +} + diff --git a/Dependencies/Utility/Utility/CppMacros.h b/Dependencies/Utility/Utility/CppMacros.h index 9c0ed332cb2..59a5ae09afa 100644 --- a/Dependencies/Utility/Utility/CppMacros.h +++ b/Dependencies/Utility/Utility/CppMacros.h @@ -37,9 +37,11 @@ #endif #if __cplusplus >= 201103L - #define CPP_11(code) code + #define CPP_11(code) code + #define CONSTEXPR constexpr #else - #define CPP_11(code) + #define CPP_11(code) + #define CONSTEXPR #endif #if __cplusplus < 201103L diff --git a/Generals/Code/GameEngine/CMakeLists.txt b/Generals/Code/GameEngine/CMakeLists.txt index f1fe224b5b2..eed55eae5b6 100644 --- a/Generals/Code/GameEngine/CMakeLists.txt +++ b/Generals/Code/GameEngine/CMakeLists.txt @@ -91,6 +91,7 @@ set(GAMEENGINE_SRC Include/Common/RAMFile.h Include/Common/RandomValue.h Include/Common/Recorder.h +# Include/Common/ReplaySimulation.h Include/Common/Registry.h Include/Common/ResourceGatheringManager.h Include/Common/Science.h @@ -123,6 +124,7 @@ set(GAMEENGINE_SRC Include/Common/UserPreferences.h Include/Common/version.h Include/Common/WellKnownKeys.h +# Include/Common/WorkerProcess.h # Include/Common/Xfer.h # Include/Common/XferCRC.h # Include/Common/XferDeepCRC.h @@ -633,6 +635,7 @@ set(GAMEENGINE_SRC Source/Common/Thing/ThingTemplate.cpp Source/Common/UserPreferences.cpp Source/Common/version.cpp +# Source/Common/WorkerProcess.cpp Source/GameClient/ClientInstance.cpp Source/GameClient/Color.cpp Source/GameClient/Credits.cpp diff --git a/Generals/Code/GameEngine/Include/Common/GameEngine.h b/Generals/Code/GameEngine/Include/Common/GameEngine.h index e483db6548d..0b2ab9f6137 100644 --- a/Generals/Code/GameEngine/Include/Common/GameEngine.h +++ b/Generals/Code/GameEngine/Include/Common/GameEngine.h @@ -67,7 +67,6 @@ class GameEngine : public SubsystemInterface virtual ~GameEngine(); virtual void init( void ); ///< Init engine by creating client and logic - virtual void init( int argc, char *argv[] ); ///< Init engine by creating client and logic virtual void reset( void ); ///< reset system to starting state virtual void update( void ); ///< per frame update @@ -115,6 +114,6 @@ extern GameEngine *TheGameEngine; extern GameEngine *CreateGameEngine( void ); /// The entry point for the game system -extern void GameMain( int argc, char *argv[] ); +extern Int GameMain(); #endif // _GAME_ENGINE_H_ diff --git a/Generals/Code/GameEngine/Include/Common/GlobalData.h b/Generals/Code/GameEngine/Include/Common/GlobalData.h index 4d06757f7f3..5cbd6975ef4 100644 --- a/Generals/Code/GameEngine/Include/Common/GlobalData.h +++ b/Generals/Code/GameEngine/Include/Common/GlobalData.h @@ -52,7 +52,8 @@ enum AIDebugOptions CPP_11(: Int); // PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// -const Int MAX_GLOBAL_LIGHTS = 3; +CONSTEXPR const Int MAX_GLOBAL_LIGHTS = 3; +CONSTEXPR const Int SIMULATE_REPLAYS_SEQUENTIAL = -1; //------------------------------------------------------------------------------------------------- class CommandLineData @@ -338,9 +339,12 @@ class GlobalData : public SubsystemInterface Real m_cameraAdjustSpeed; ///< Rate at which we adjust camera height Bool m_enforceMaxCameraHeight; ///< Enfoce max camera height while scrolling? Bool m_buildMapCache; - AsciiString m_initialFile; ///< If this is specified, load a specific map/replay from the command-line + AsciiString m_initialFile; ///< If this is specified, load a specific map from the command-line AsciiString m_pendingFile; ///< If this is specified, use this map at the next game start + std::vector m_simulateReplays; ///< If not empty, simulate this list of replays and exit. + Int m_simulateReplayJobs; ///< Maximum number of processes to use for simulation, or SIMULATE_REPLAYS_SEQUENTIAL for sequential simulation + Int m_maxParticleCount; ///< maximum number of particles that can exist Int m_maxFieldParticleCount; ///< maximum number of field-type particles that can exist (roughly) WeaponBonusSet* m_weaponBonusSet; diff --git a/Generals/Code/GameEngine/Include/Common/Recorder.h b/Generals/Code/GameEngine/Include/Common/Recorder.h index fefd82c89d4..7663b8dc919 100644 --- a/Generals/Code/GameEngine/Include/Common/Recorder.h +++ b/Generals/Code/GameEngine/Include/Common/Recorder.h @@ -48,6 +48,7 @@ class ReplayGameInfo : public GameInfo enum RecorderModeType CPP_11(: Int) { RECORDERMODETYPE_RECORD, RECORDERMODETYPE_PLAYBACK, + RECORDERMODETYPE_SIMULATION_PLAYBACK, // Play back replay without any graphics RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games }; @@ -74,11 +75,13 @@ class RecorderClass : public SubsystemInterface { Bool replayMatchesGameVersion(AsciiString filename); ///< Returns true if the playback is a valid playback file for this version. static Bool replayMatchesGameVersion(const ReplayHeader& header); ///< Returns true if the playback is a valid playback file for this version. AsciiString getCurrentReplayFilename( void ); ///< valid during playback only + UnsignedInt getPlaybackFrameCount() const { return m_playbackFrameCount; } ///< valid during playback only void stopPlayback(); ///< Stops playback. Its fine to call this even if not playing back a file. + Bool simulateReplay(AsciiString filename); #if defined RTS_DEBUG || defined RTS_INTERNAL Bool analyzeReplay( AsciiString filename ); - Bool isAnalysisInProgress( void ); #endif + Bool isPlaybackInProgress() const; public: void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); @@ -100,7 +103,7 @@ class RecorderClass : public SubsystemInterface { UnsignedInt iniCRC; time_t startTime; time_t endTime; - UnsignedInt frameDuration; + UnsignedInt frameCount; Bool quitEarly; Bool desyncGame; Bool playerDiscons[MAX_SLOTS]; @@ -110,10 +113,11 @@ class RecorderClass : public SubsystemInterface { Bool readReplayHeader( ReplayHeader& header ); RecorderModeType getMode(); ///< Returns the current operating mode. + Bool isPlaybackMode() const { return m_mode == RECORDERMODETYPE_PLAYBACK || m_mode == RECORDERMODETYPE_SIMULATION_PLAYBACK; } void initControls(); ///< Show or Hide the Replay controls AsciiString getReplayDir(); ///< Returns the directory that holds the replay files. - AsciiString getReplayExtention(); ///< Returns the file extention for replay files. + static AsciiString getReplayExtention(); ///< Returns the file extention for replay files. AsciiString getLastReplayFileName(); ///< Returns the filename used for the default replay. GameInfo *getGameInfo( void ) { return &m_gameInfo; } ///< Returns the slot list for playback game start @@ -124,6 +128,7 @@ class RecorderClass : public SubsystemInterface { void logPlayerDisconnect(UnicodeString player, Int slot); void logCRCMismatch( void ); + Bool sawCRCMismatch() const; void cleanUpReplayFile( void ); ///< after a crash, send replay/debug info to a central repository void stopRecording(); ///< Stop recording and close m_file. @@ -154,6 +159,7 @@ class RecorderClass : public SubsystemInterface { Int m_currentFilePosition; RecorderModeType m_mode; AsciiString m_currentReplayFilename; ///< valid during playback only + UnsignedInt m_playbackFrameCount; ReplayGameInfo m_gameInfo; Bool m_wasDesync; diff --git a/Generals/Code/GameEngine/Include/GameClient/ClientInstance.h b/Generals/Code/GameEngine/Include/GameClient/ClientInstance.h index 74e7add404b..1f1ada1daca 100644 --- a/Generals/Code/GameEngine/Include/GameClient/ClientInstance.h +++ b/Generals/Code/GameEngine/Include/GameClient/ClientInstance.h @@ -30,6 +30,15 @@ class ClientInstance static bool isInitialized(); + static bool isMultiInstance(); + + // Change multi instance on runtime. Must be called before initialize. + static void setMultiInstance(bool v); + + // Skips using the primary instance. Must be called before initialize. + // Useful when the new process is not meant to collide with another normal Generals process. + static void skipPrimaryInstance(); + // Returns the instance index of this game client. Starts at 0. static UnsignedInt getInstanceIndex(); @@ -42,6 +51,7 @@ class ClientInstance private: static HANDLE s_mutexHandle; static UnsignedInt s_instanceIndex; + static Bool s_isMultiInstance; }; } // namespace rts diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index d599305b21c..f0c6a708029 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -95,6 +95,8 @@ class GameClient : public SubsystemInterface, virtual void setFrame( UnsignedInt frame ) { m_frame = frame; } ///< Set the GameClient's internal frame number virtual void registerDrawable( Drawable *draw ); ///< Given a drawable, register it with the GameClient and give it a unique ID + void updateHeadless(); + void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table diff --git a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp index b01b0d10bdb..ec75d89b2fc 100644 --- a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp @@ -29,7 +29,9 @@ #include "Common/CommandLine.h" #include "Common/CRCDebug.h" #include "Common/LocalFileSystem.h" +#include "Common/Recorder.h" #include "Common/version.h" +#include "GameClient/ClientInstance.h" #include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition #include "GameClient/GameText.h" #include "GameNetwork/NetworkDefs.h" @@ -405,6 +407,56 @@ Int parseMapName(char *args[], int num) return 1; } +Int parseHeadless(char *args[], int num) +{ + TheWritableGlobalData->m_headless = TRUE; + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_afterIntro = TRUE; + TheWritableGlobalData->m_playSizzle = FALSE; + return 1; +} + +Int parseReplay(char *args[], int num) +{ + if (num > 1) + { + AsciiString filename = args[1]; + if (!filename.endsWithNoCase(RecorderClass::getReplayExtention())) + { + printf("Invalid replay name \"%s\"\n", filename.str()); + exit(1); + } + TheWritableGlobalData->m_simulateReplays.push_back(filename); + + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_afterIntro = TRUE; + TheWritableGlobalData->m_playSizzle = FALSE; + TheWritableGlobalData->m_shellMapOn = FALSE; + + // Make replay playback possible while other clients (possible retail) are running + rts::ClientInstance::setMultiInstance(TRUE); + rts::ClientInstance::skipPrimaryInstance(); + + return 2; + } + return 1; +} + +Int parseJobs(char *args[], int num) +{ + if (num > 1) + { + TheWritableGlobalData->m_simulateReplayJobs = atoi(args[1]); + if (TheGlobalData->m_simulateReplayJobs < SIMULATE_REPLAYS_SEQUENTIAL || TheGlobalData->m_simulateReplayJobs == 0) + { + printf("Invalid number of jobs: %d\n", TheGlobalData->m_simulateReplayJobs); + exit(1); + } + return 2; + } + return 1; +} + Int parseXRes(char *args[], int num) { if (num > 1) @@ -782,13 +834,6 @@ Int parseQuickStart( char *args[], int num ) return 1; } -Int parseHeadless( char *args[], int num ) -{ - TheWritableGlobalData->m_headless = TRUE; - - return 1; -} - Int parseConstantDebug( char *args[], int num ) { TheWritableGlobalData->m_constantDebugUpdate = TRUE; @@ -1103,8 +1148,20 @@ static CommandLineParam paramsForStartup[] = { "-fullscreen", parseNoWin }, // TheSuperHackers @feature helmutbuhler 11/04/2025 - // This runs the game without a window, graphics, input and audio. Used for testing. + // This runs the game without a window, graphics, input and audio. You can combine this with -replay { "-headless", parseHeadless }, + + // TheSuperHackers @feature helmutbuhler 13/04/2025 + // Play back a replay. Pass the filename including .rep afterwards. + // You can pass this multiple times to play back multiple replays. + // You can also include wildcards. The file must be in the replay folder or in a subfolder. + { "-replay", parseReplay }, + + // TheSuperHackers @feature helmutbuhler 23/05/2025 + // Simulate each replay in a separate process and use 1..N processes at the same time. + // (If you have 4 cores, call it with -jobs 4) + // If you do not call this, all replays will be simulated in sequence in the same process. + { "-jobs", parseJobs }, }; // These Params are parsed during Engine Init before INI data is loaded diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index f6a97e93b21..4542419d928 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -250,8 +250,7 @@ void GameEngine::setFramesPerSecondLimit( Int fps ) /** ----------------------------------------------------------------------------------------------- * Initialize the game engine by initializing the GameLogic and GameClient. */ -void GameEngine::init( void ) {} /// @todo: I changed this to take argc & argv so we can parse those after the GDF is loaded. We need to rethink this immediately as it is a nasty hack -void GameEngine::init( int argc, char *argv[] ) +void GameEngine::init() { try { //create an INI object to use for loading stuff @@ -442,7 +441,7 @@ void GameEngine::init( int argc, char *argv[] ) // load the initial shell screen //TheShell->push( AsciiString("Menus/MainMenu.wnd") ); - // This allows us to run a map/reply from the command line + // This allows us to run a map from the command line if (TheGlobalData->m_initialFile.isEmpty() == FALSE) { AsciiString fname = TheGlobalData->m_initialFile; @@ -464,10 +463,6 @@ void GameEngine::init( int argc, char *argv[] ) msg->appendIntegerArgument(0); InitRandom(0); } - else if (fname.endsWithNoCase(".rep")) - { - TheRecorder->playbackFile(fname); - } } // diff --git a/Generals/Code/GameEngine/Source/Common/GameMain.cpp b/Generals/Code/GameEngine/Source/Common/GameMain.cpp index a8cbc291f91..cf5f2e3be84 100644 --- a/Generals/Code/GameEngine/Source/Common/GameMain.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameMain.cpp @@ -29,23 +29,33 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Common/GameEngine.h" +#include "Common/ReplaySimulation.h" /** * This is the entry point for the game system. */ -void GameMain( int argc, char *argv[] ) +Int GameMain() { + int exitcode = 0; // initialize the game engine using factory function TheGameEngine = CreateGameEngine(); - TheGameEngine->init(argc, argv); - - // run it - TheGameEngine->execute(); + TheGameEngine->init(); + + if (!TheGlobalData->m_simulateReplays.empty()) + { + exitcode = ReplaySimulation::simulateReplays(TheGlobalData->m_simulateReplays, TheGlobalData->m_simulateReplayJobs); + } + else + { + // run it + TheGameEngine->execute(); + } // since execute() returned, we are exiting the game delete TheGameEngine; TheGameEngine = NULL; + return exitcode; } diff --git a/Generals/Code/GameEngine/Source/Common/GlobalData.cpp b/Generals/Code/GameEngine/Source/Common/GlobalData.cpp index b7f04ba32c2..5f9e9cd2942 100644 --- a/Generals/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/Generals/Code/GameEngine/Source/Common/GlobalData.cpp @@ -948,6 +948,9 @@ GlobalData::GlobalData() m_initialFile.clear(); m_pendingFile.clear(); + m_simulateReplays.clear(); + m_simulateReplayJobs = SIMULATE_REPLAYS_SEQUENTIAL; + for (i = LEVEL_FIRST; i <= LEVEL_LAST; ++i) m_healthBonus[i] = 1.0f; diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index dacd872a046..a1fa13adab6 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -67,8 +67,8 @@ typedef int32_t replay_time_t; static time_t startTime; static const UnsignedInt startTimeOffset = 6; static const UnsignedInt endTimeOffset = startTimeOffset + sizeof(replay_time_t); -static const UnsignedInt framesOffset = endTimeOffset + sizeof(replay_time_t); -static const UnsignedInt desyncOffset = framesOffset + sizeof(UnsignedInt); +static const UnsignedInt frameCountOffset = endTimeOffset + sizeof(replay_time_t); +static const UnsignedInt desyncOffset = frameCountOffset + sizeof(UnsignedInt); static const UnsignedInt quitEarlyOffset = desyncOffset + sizeof(Bool); static const UnsignedInt disconOffset = quitEarlyOffset + sizeof(Bool); @@ -232,7 +232,7 @@ void RecorderClass::logGameEnd( void ) time_t t; time(&t); - UnsignedInt duration = TheGameLogic->getFrame(); + UnsignedInt frameCount = TheGameLogic->getFrame(); UnsignedInt fileSize = ftell(m_file); // move to appropriate offset if (!fseek(m_file, endTimeOffset, SEEK_SET)) @@ -242,10 +242,10 @@ void RecorderClass::logGameEnd( void ) fwrite(&tmp, sizeof(replay_time_t), 1, m_file); } // move to appropriate offset - if (!fseek(m_file, framesOffset, SEEK_SET)) + if (!fseek(m_file, frameCountOffset, SEEK_SET)) { - // save off duration - fwrite(&duration, sizeof(UnsignedInt), 1, m_file); + // save off frameCount + fwrite(&frameCount, sizeof(UnsignedInt), 1, m_file); } // move back to end of stream #ifdef DEBUG_CRASHING @@ -272,7 +272,7 @@ void RecorderClass::logGameEnd( void ) if (logFP) { struct tm *t2 = localtime(&t); - duration = t - startTime; + time_t duration = t - startTime; Int minutes = duration/60; Int seconds = duration%60; fprintf(logFP, "Game end at %s(%d:%2.2d elapsed time)\n", asctime(t2), minutes, seconds); @@ -408,6 +408,7 @@ void RecorderClass::init() { m_gameInfo.setSeed(GetGameLogicRandomSeed()); m_wasDesync = FALSE; m_doingAnalysis = FALSE; + m_playbackFrameCount = 0; } /** @@ -430,7 +431,7 @@ void RecorderClass::reset() { void RecorderClass::update() { if (m_mode == RECORDERMODETYPE_RECORD || m_mode == RECORDERMODETYPE_NONE) { updateRecord(); - } else if (m_mode == RECORDERMODETYPE_PLAYBACK) { + } else if (isPlaybackMode()) { updatePlayback(); } } @@ -476,11 +477,11 @@ void RecorderClass::stopPlayback() { m_file = NULL; } m_fileName.clear(); - // Don't clear the game data if the replay is over - let things continue -//#ifdef DEBUG_CRC + if (!m_doingAnalysis) + { TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); -//#endif + } } /** @@ -852,7 +853,7 @@ Bool RecorderClass::readReplayHeader(ReplayHeader& header) fread(&tmp, sizeof(replay_time_t), 1, m_file); header.endTime = tmp; - fread(&header.frameDuration, sizeof(UnsignedInt), 1, m_file); + fread(&header.frameCount, sizeof(UnsignedInt), 1, m_file); fread(&header.desyncGame, sizeof(Bool), 1, m_file); fread(&header.quitEarly, sizeof(Bool), 1, m_file); @@ -915,6 +916,14 @@ Bool RecorderClass::readReplayHeader(ReplayHeader& header) return TRUE; } +Bool RecorderClass::simulateReplay(AsciiString filename) +{ + Bool success = playbackFile(filename); + if (success) + m_mode = RECORDERMODETYPE_SIMULATION_PLAYBACK; + return success; +} + #if defined RTS_DEBUG || defined RTS_INTERNAL Bool RecorderClass::analyzeReplay( AsciiString filename ) { @@ -922,15 +931,18 @@ Bool RecorderClass::analyzeReplay( AsciiString filename ) return playbackFile(filename); } -Bool RecorderClass::isAnalysisInProgress( void ) + + +#endif + +Bool RecorderClass::isPlaybackInProgress( void ) const { - return m_mode == RECORDERMODETYPE_PLAYBACK && m_nextFrame != -1; + return isPlaybackMode() && m_nextFrame != -1; } -#endif AsciiString RecorderClass::getCurrentReplayFilename( void ) { - if (m_mode == RECORDERMODETYPE_PLAYBACK) + if (isPlaybackMode()) { return m_currentReplayFilename; } @@ -964,7 +976,7 @@ class CRCInfo UnsignedInt getLocalPlayer(void) { return m_localPlayer; } void setSawCRCMismatch(void) { m_sawCRCMismatch = TRUE; } - Bool sawCRCMismatch(void) { return m_sawCRCMismatch; } + Bool sawCRCMismatch(void) const { return m_sawCRCMismatch; } protected: @@ -1012,6 +1024,11 @@ UnsignedInt CRCInfo::readCRC(void) return val; } +Bool RecorderClass::sawCRCMismatch() const +{ + return m_crcInfo->sawCRCMismatch(); +} + void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback) { if (fromPlayback) @@ -1058,6 +1075,9 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f DEBUG_LOG(("Replay has gone out of sync!\nInGame:%8.8X Replay:%8.8X\nFrame:%d\n", playbackCRC, newCRC, mismatchFrame)); + // Print Mismatch in case we are simulating replays from console. + printf("CRC Mismatch in Frame %d\n", mismatchFrame); + // TheSuperHackers @tweak Pause the game on mismatch. Bool pause = TRUE; Bool pauseMusic = FALSE; @@ -1212,17 +1232,23 @@ Bool RecorderClass::playbackFile(AsciiString filename) // send a message to the logic for a new game if (!m_doingAnalysis) { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME ); + // TheSuperHackers @info helmutbuhler 13/04/2025 + // We send the New Game message here directly to the command list and bypass the TheMessageStream. + // That's ok because Multiplayer is disabled during replay playback and is actually required + // during replay simulation because we don't update TheMessageStream during simulation. + GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_NEW_GAME); msg->appendIntegerArgument(GAME_REPLAY); msg->appendIntegerArgument(difficulty); msg->appendIntegerArgument(rankPoints); if( maxFPS != 0 ) msg->appendIntegerArgument(maxFPS); + TheCommandList->appendMessage( msg ); //InitGameLogicRandom( m_gameInfo.getSeed()); InitRandom( m_gameInfo.getSeed() ); } m_currentReplayFilename = filename; + m_playbackFrameCount = header.frameCount; return TRUE; } @@ -1660,7 +1686,7 @@ void RecorderClass::initControls() Bool RecorderClass::isMultiplayer( void ) { - if (m_mode == RECORDERMODETYPE_PLAYBACK) + if (isPlaybackMode()) { GameSlot *slot; for (int i=0; i 0u) - { - char idStr[33]; - itoa(s_instanceIndex, idStr, 10); - guidStr.push_back('-'); - guidStr.append(idStr); - } - s_mutexHandle = CreateMutex(NULL, FALSE, guidStr.c_str()); - if (GetLastError() == ERROR_ALREADY_EXISTS) + if (isMultiInstance()) { - if (s_mutexHandle != NULL) + std::string guidStr = getFirstInstanceName(); + if (s_instanceIndex > 0u) { - CloseHandle(s_mutexHandle); - s_mutexHandle = NULL; + char idStr[33]; + itoa(s_instanceIndex, idStr, 10); + guidStr.push_back('-'); + guidStr.append(idStr); + } + s_mutexHandle = CreateMutex(NULL, FALSE, guidStr.c_str()); + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + if (s_mutexHandle != NULL) + { + CloseHandle(s_mutexHandle); + s_mutexHandle = NULL; + } + // Try again with a new instance. + ++s_instanceIndex; + continue; } - // Try again with a new instance. - ++s_instanceIndex; - continue; } -#else - s_mutexHandle = CreateMutex(NULL, FALSE, getFirstInstanceName()); - if (GetLastError() == ERROR_ALREADY_EXISTS) + else { - if (s_mutexHandle != NULL) + s_mutexHandle = CreateMutex(NULL, FALSE, getFirstInstanceName()); + if (GetLastError() == ERROR_ALREADY_EXISTS) { - CloseHandle(s_mutexHandle); - s_mutexHandle = NULL; + if (s_mutexHandle != NULL) + { + CloseHandle(s_mutexHandle); + s_mutexHandle = NULL; + } + return false; } - return false; } -#endif break; } @@ -80,6 +89,31 @@ bool ClientInstance::isInitialized() return s_mutexHandle != NULL; } +bool ClientInstance::isMultiInstance() +{ + return s_isMultiInstance; +} + +void ClientInstance::setMultiInstance(bool v) +{ + if (isInitialized()) + { + DEBUG_CRASH(("ClientInstance::setMultiInstance(%d) - cannot set multi instance after initialization", (int)v)); + return; + } + s_isMultiInstance = v; +} + +void ClientInstance::skipPrimaryInstance() +{ + if (isInitialized()) + { + DEBUG_CRASH(("ClientInstance::skipPrimaryInstance() - cannot skip primary instance after initialization")); + return; + } + s_instanceIndex = 1; +} + UnsignedInt ClientInstance::getInstanceIndex() { DEBUG_ASSERTLOG(isInitialized(), ("ClientInstance::isInitialized() failed")); diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp index 93fbffe9039..21bad58720a 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp @@ -248,7 +248,7 @@ void PopulateReplayFileListbox(GameWindow *listbox) // time_t totalSeconds = header.endTime - header.startTime; // Int mins = totalSeconds/60; // Int secs = totalSeconds%60; -// Real fps = header.frameDuration/totalSeconds; +// Real fps = header.frameCount/totalSeconds; // extraStr.format(L"%d:%d (%g fps) %hs", mins, secs, fps, header.desyncGame?"OOS ":""); // // for (Int i=0; iupdate(); - } while (TheRecorder->isAnalysisInProgress()); + } while (TheRecorder->isPlaybackInProgress()); } } } diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index 59f9c092c80..cea961ebbd4 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -64,6 +64,7 @@ #include "Common/PlayerTemplate.h" #include "Common/RandomValue.h" #include "Common/Recorder.h" +#include "Common/ReplaySimulation.h" #include "Common/ScoreKeeper.h" #include "Common/SkirmishBattleHonors.h" #include "Common/ThingFactory.h" @@ -411,6 +412,11 @@ WindowMsgHandledType ScoreScreenInput( GameWindow *window, UnsignedInt msg, } // end MainMenuInput +static Bool showButtonContinue() +{ + return ReplaySimulation::getCurrentReplayIndex() != ReplaySimulation::getReplayCount()-1; +} + /** System Function for the ScoreScreen */ //------------------------------------------------------------------------------------------------- WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, @@ -449,23 +455,36 @@ WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, { TheShell->pop(); TheCampaignManager->setCampaign(AsciiString::TheEmptyString); + + if ( ReplaySimulation::getReplayCount() > 0 ) + { + ReplaySimulation::stop(); + TheGameEngine->setQuitting(TRUE); + } } else if ( controlID == buttonContinueID ) { - if(!buttonIsFinishCampaign) - ReplayWasPressed = TRUE; - if( screenType == SCORESCREEN_SINGLEPLAYER) + if( ReplaySimulation::getReplayCount() > 0 ) { - AsciiString mapName = TheCampaignManager->getCurrentMap(); - - if( mapName.isEmpty() ) - { - ReplayWasPressed = FALSE; - TheShell->pop(); - } - else + TheGameEngine->setQuitting(TRUE); + } + else + { + if(!buttonIsFinishCampaign) + ReplayWasPressed = TRUE; + if( screenType == SCORESCREEN_SINGLEPLAYER) { - CheckForCDAtGameStart( startNextCampaignGame ); + AsciiString mapName = TheCampaignManager->getCurrentMap(); + + if( mapName.isEmpty() ) + { + ReplayWasPressed = FALSE; + TheShell->pop(); + } + else + { + CheckForCDAtGameStart( startNextCampaignGame ); + } } } } @@ -800,7 +819,7 @@ void initReplaySinglePlayer( void ) if (chatBoxBorder) chatBoxBorder->winHide(TRUE); if (buttonContinue) - buttonContinue->winHide(TRUE); + buttonContinue->winHide(!showButtonContinue()); if (buttonBuddies) buttonBuddies->winHide(TRUE); if (listboxChatWindowScoreScreen) @@ -886,7 +905,7 @@ void initReplayMultiPlayer(void) if (chatBoxBorder) chatBoxBorder->winHide(TRUE); if (buttonContinue) - buttonContinue->winHide(TRUE); + buttonContinue->winHide(!showButtonContinue()); if (buttonBuddies) buttonBuddies->winHide(TRUE); // if (buttonRehost) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index 2633a328c8b..164dbc47d9b 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -408,7 +408,7 @@ void Shell::showShell( Bool runInit ) { DEBUG_LOG(("Shell:showShell() - %s (%s)\n", TheGlobalData->m_initialFile.str(), (top())?top()->getFilename().str():"no top screen")); - if(!TheGlobalData->m_initialFile.isEmpty()) + if(!TheGlobalData->m_initialFile.isEmpty() || !TheGlobalData->m_simulateReplays.empty()) { return; } @@ -465,7 +465,7 @@ void Shell::showShell( Bool runInit ) void Shell::showShellMap(Bool useShellMap ) { // we don't want any of this to show if we're loading straight into a file - if(TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic ) + if (TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic || !TheGlobalData->m_simulateReplays.empty()) return; if(useShellMap && TheGlobalData->m_shellMapOn) { diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 8964e037870..07ac79f01f5 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -517,7 +517,7 @@ void GameClient::update( void ) //Initial Game Codition. We must show the movie first and then we can display the shell if(TheGlobalData->m_afterIntro && !TheDisplay->isMoviePlaying()) { - if( playSizzle && TheGlobalData->m_playSizzle && !TheGlobalData->m_headless )// Remove headless-check with Replay Simulation PR) + if( playSizzle && TheGlobalData->m_playSizzle ) { TheWritableGlobalData->m_allowExitOutOfMovies = TRUE; if(TheGameLODManager && TheGameLODManager->didMemPass()) @@ -591,11 +591,8 @@ void GameClient::update( void ) if(TheGlobalData->m_playIntro || TheGlobalData->m_afterIntro) { // redraw all views, update the GUI - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR - { - TheDisplay->DRAW(); - TheDisplay->UPDATE(); - } + TheDisplay->DRAW(); + TheDisplay->UPDATE(); return; } @@ -713,12 +710,10 @@ void GameClient::update( void ) } // update display - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR { TheDisplay->UPDATE(); } - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR { USE_PERF_TIMER(GameClient_draw) @@ -744,6 +739,18 @@ void GameClient::update( void ) } } // end update +void GameClient::updateHeadless() +{ + // TheSuperHackers @info helmutbuhler 03/05/2025 + // When we play a replay back in headless mode, we want to skip the update of GameClient + // because it's not necessary for CRC checking. + // But we do reset the particles. The problem is that particles can be generated during + // GameLogic and are only cleaned up during rendering. If we don't clean this up here, + // the particles accumulate and slow things down a lot and can even cause a crash on + // longer replays. + TheParticleSystemManager->reset(); +} + /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index dcc23721d5b..e90f56366bf 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -287,7 +287,7 @@ void GameLogic::setDefaults( Bool saveGame ) Bool GameLogic::isInSinglePlayerGame( void ) { return (m_gameMode == GAME_SINGLE_PLAYER || - (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheRecorder->getGameMode() == GAME_SINGLE_PLAYER)); + (TheRecorder && TheRecorder->isPlaybackMode() && TheRecorder->getGameMode() == GAME_SINGLE_PLAYER)); } @@ -794,9 +794,11 @@ static void populateRandomStartPosition( GameInfo *game ) Int i; Int numPlayers = MAX_SLOTS; const MapMetaData *md = TheMapCache->findMap( game->getMap() ); - DEBUG_ASSERTCRASH( md , ("Could not find map %s in the mapcache", game->getMap().str())); if (md) numPlayers = md->m_numPlayers; + else + printf("Could not find map \"%s\"\n", game->getMap().str()); + DEBUG_ASSERTCRASH( md , ("Could not find map %s in the mapcache", game->getMap().str())); // generate a map of start spot distances Real startSpotDistance[MAX_SLOTS][MAX_SLOTS]; @@ -1086,7 +1088,7 @@ void GameLogic::startNewGame( Bool saveGame ) } else { - if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) + if (TheRecorder && TheRecorder->isPlaybackMode()) { TheGameInfo = game = TheRecorder->getGameInfo(); } @@ -1130,7 +1132,7 @@ void GameLogic::startNewGame( Bool saveGame ) //****************************// // Get the m_loadScreen for this kind of game - if(!m_loadScreen) + if(!m_loadScreen && !(TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_SIMULATION_PLAYBACK)) { m_loadScreen = getLoadScreen( saveGame ); if(m_loadScreen && !TheGlobalData->m_headless) @@ -1914,7 +1916,7 @@ void GameLogic::startNewGame( Bool saveGame ) } // if we're in a load game, don't fade yet - if(saveGame == FALSE && TheTransitionHandler != NULL) + if(saveGame == FALSE && TheTransitionHandler != NULL && m_loadScreen) { TheTransitionHandler->setGroup("FadeWholeScreen"); while(!TheTransitionHandler->isFinished()) @@ -3168,20 +3170,21 @@ void GameLogic::update( void ) if (generateForSolo || generateForMP) { m_CRC = getCRC( CRC_RECALC ); - if (isMPGameOrReplay) - { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); - msg->appendIntegerArgument( m_CRC ); - msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - DEBUG_LOG(("Appended CRC on frame %d: %8.8X\n", m_frame, m_CRC)); - } - else - { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); - msg->appendIntegerArgument( m_CRC ); - msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - DEBUG_LOG(("Appended Playback CRC on frame %d: %8.8X\n", m_frame, m_CRC)); - } + bool isPlayback = (TheRecorder && TheRecorder->isPlaybackMode()); + + GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_LOGIC_CRC); + msg->appendIntegerArgument(m_CRC); + msg->appendBooleanArgument(isPlayback); + + // TheSuperHackers @info helmutbuhler 13/04/2025 + // During replay simulation, we bypass TheMessageStream and instead put the CRC message + // directly into TheCommandList because we don't update TheMessageStream during simulation. + GameMessageList *messageList = TheMessageStream; + if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_SIMULATION_PLAYBACK) + messageList = TheCommandList; + messageList->appendMessage(msg); + + DEBUG_LOG(("Appended %sCRC on frame %d: %8.8X\n", isPlayback ? "Playback " : "", m_frame, m_CRC)); } // collect stats diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index cf835cb3733..eeeefd1b2a0 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1799,7 +1799,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) // -------------------------------------------------------------------------------------------- case GameMessage::MSG_SET_REPLAY_CAMERA: { - if (TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer) + if (TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer) { if (TheTacticalView->isCameraMovementFinished()) { @@ -1936,7 +1936,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) //thisPlayer->getPlayerDisplayName().str(), m_frame)); m_cachedCRCs[msg->getPlayerIndex()] = newCRC; // to mask problem: = (oldCRC < newCRC)?newCRC:oldCRC; } - else if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) + else if (TheRecorder && TheRecorder->isPlaybackMode()) { UnsignedInt newCRC = msg->getArgument(0)->integer; //DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d\n", @@ -1966,7 +1966,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) } // end switch /**/ /// @todo: multiplayer semantics - if (currentlySelectedGroup && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/) + if (currentlySelectedGroup && TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/) { const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs(); TheInGameUI->deselectAllDrawables(); diff --git a/Generals/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp b/Generals/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp index e6fab46eff0..72c556e5f15 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp @@ -97,15 +97,16 @@ EnumeratedIP * IPEnumeration::getAddresses( void ) return NULL; } -#if defined(RTS_MULTI_INSTANCE) // TheSuperHackers @feature Add one unique local host IP address for each multi client instance. - const UnsignedInt id = rts::ClientInstance::getInstanceId(); - addNewIP( - 127, - (UnsignedByte)(id >> 16), - (UnsignedByte)(id >> 8), - (UnsignedByte)(id)); -#endif + if (rts::ClientInstance::isMultiInstance()) + { + const UnsignedInt id = rts::ClientInstance::getInstanceId(); + addNewIP( + 127, + (UnsignedByte)(id >> 16), + (UnsignedByte)(id >> 8), + (UnsignedByte)(id)); + } // construct a list of addresses int numAddresses = 0; diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp index e24aef6a2bc..b8187e07502 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp @@ -979,7 +979,7 @@ void TextureLoadTaskClass::Init(TextureBaseClass* tc,bool high_priority) { // Make sure texture has a filename. REF_PTR_SET(Texture,tc); - WWASSERT(Texture->Get_Full_Path() != NULL); + //WWASSERT(Texture->Get_Full_Path() != NULL); Reduction=Texture->Get_Reduction(); HighPriorityRequested=high_priority; diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 1f959a5bdf9..8a3f00888c3 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -740,6 +740,7 @@ static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5; Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, Int nCmdShow ) { + Int exitcode = 1; try { _set_se_translator( DumpExceptionInfo ); // Hook that allows stack trace. @@ -794,7 +795,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) - return 0; + return exitcode; // save our application instance for future use ApplicationHInstance = hInstance; @@ -835,13 +836,13 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheVersion = NULL; shutdownMemoryManager(); DEBUG_SHUTDOWN(); - return 0; + return exitcode; } DEBUG_LOG(("Create Generals Mutex okay.\n")); DEBUG_LOG(("CRC message is %d\n", GameMessage::MSG_LOGIC_CRC)); // run the game main loop - GameMain(0, NULL); + exitcode = GameMain(); delete TheVersion; TheVersion = NULL; @@ -870,7 +871,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheDmaCriticalSection = NULL; TheMemoryPoolCriticalSection = NULL; - return 0; + return exitcode; } // end WinMain diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 343a0fe28f4..e2bea3ec545 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -94,6 +94,7 @@ set(GAMEENGINE_SRC Include/Common/RAMFile.h Include/Common/RandomValue.h Include/Common/Recorder.h +# Include/Common/ReplaySimulation.h Include/Common/Registry.h Include/Common/ResourceGatheringManager.h Include/Common/Science.h @@ -129,6 +130,7 @@ set(GAMEENGINE_SRC Include/Common/UserPreferences.h Include/Common/version.h Include/Common/WellKnownKeys.h +# Include/Common/WorkerProcess.h # Include/Common/Xfer.h # Include/Common/XferCRC.h # Include/Common/XferDeepCRC.h @@ -607,6 +609,7 @@ set(GAMEENGINE_SRC Source/Common/PerfTimer.cpp Source/Common/RandomValue.cpp Source/Common/Recorder.cpp +# Source/Common/ReplaySimulation.cpp Source/Common/RTS/AcademyStats.cpp Source/Common/RTS/ActionManager.cpp Source/Common/RTS/Energy.cpp @@ -676,6 +679,7 @@ set(GAMEENGINE_SRC Source/Common/Thing/ThingTemplate.cpp Source/Common/UserPreferences.cpp Source/Common/version.cpp +# Source/Common/WorkerProcess.cpp Source/GameClient/ClientInstance.cpp Source/GameClient/Color.cpp Source/GameClient/Credits.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h index 087e0e8aba7..995817c7a2b 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h @@ -67,7 +67,6 @@ class GameEngine : public SubsystemInterface virtual ~GameEngine(); virtual void init( void ); ///< Init engine by creating client and logic - virtual void init( int argc, char *argv[] ); ///< Init engine by creating client and logic virtual void reset( void ); ///< reset system to starting state virtual void update( void ); ///< per frame update @@ -114,6 +113,6 @@ extern GameEngine *TheGameEngine; extern GameEngine *CreateGameEngine( void ); /// The entry point for the game system -extern void GameMain( int argc, char *argv[] ); +extern Int GameMain(); #endif // _GAME_ENGINE_H_ diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index fba180d1238..c4e33a69620 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -54,7 +54,8 @@ enum AIDebugOptions CPP_11(: Int); // PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// -const Int MAX_GLOBAL_LIGHTS = 3; +CONSTEXPR const Int MAX_GLOBAL_LIGHTS = 3; +CONSTEXPR const Int SIMULATE_REPLAYS_SEQUENTIAL = -1; //------------------------------------------------------------------------------------------------- class CommandLineData @@ -347,8 +348,11 @@ class GlobalData : public SubsystemInterface Real m_cameraAdjustSpeed; ///< Rate at which we adjust camera height Bool m_enforceMaxCameraHeight; ///< Enfoce max camera height while scrolling? Bool m_buildMapCache; - AsciiString m_initialFile; ///< If this is specified, load a specific map/replay from the command-line + AsciiString m_initialFile; ///< If this is specified, load a specific map from the command-line AsciiString m_pendingFile; ///< If this is specified, use this map at the next game start + + std::vector m_simulateReplays; ///< If not empty, simulate this list of replays and exit. + Int m_simulateReplayJobs; ///< Maximum number of processes to use for simulation, or SIMULATE_REPLAYS_SEQUENTIAL for sequential simulation Int m_maxParticleCount; ///< maximum number of particles that can exist Int m_maxFieldParticleCount; ///< maximum number of field-type particles that can exist (roughly) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index b776e6acf4d..a29187c96a9 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -48,6 +48,7 @@ class ReplayGameInfo : public GameInfo enum RecorderModeType CPP_11(: Int) { RECORDERMODETYPE_RECORD, RECORDERMODETYPE_PLAYBACK, + RECORDERMODETYPE_SIMULATION_PLAYBACK, // Play back replay without any graphics RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games }; @@ -74,11 +75,13 @@ class RecorderClass : public SubsystemInterface { Bool replayMatchesGameVersion(AsciiString filename); ///< Returns true if the playback is a valid playback file for this version. static Bool replayMatchesGameVersion(const ReplayHeader& header); ///< Returns true if the playback is a valid playback file for this version. AsciiString getCurrentReplayFilename( void ); ///< valid during playback only + UnsignedInt getPlaybackFrameCount() const { return m_playbackFrameCount; } ///< valid during playback only void stopPlayback(); ///< Stops playback. Its fine to call this even if not playing back a file. + Bool simulateReplay(AsciiString filename); #if defined RTS_DEBUG || defined RTS_INTERNAL Bool analyzeReplay( AsciiString filename ); - Bool isAnalysisInProgress( void ); #endif + Bool isPlaybackInProgress() const; public: void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); @@ -100,7 +103,7 @@ class RecorderClass : public SubsystemInterface { UnsignedInt iniCRC; time_t startTime; time_t endTime; - UnsignedInt frameDuration; + UnsignedInt frameCount; Bool quitEarly; Bool desyncGame; Bool playerDiscons[MAX_SLOTS]; @@ -110,10 +113,11 @@ class RecorderClass : public SubsystemInterface { Bool readReplayHeader( ReplayHeader& header ); RecorderModeType getMode(); ///< Returns the current operating mode. + Bool isPlaybackMode() const { return m_mode == RECORDERMODETYPE_PLAYBACK || m_mode == RECORDERMODETYPE_SIMULATION_PLAYBACK; } void initControls(); ///< Show or Hide the Replay controls AsciiString getReplayDir(); ///< Returns the directory that holds the replay files. - AsciiString getReplayExtention(); ///< Returns the file extention for replay files. + static AsciiString getReplayExtention(); ///< Returns the file extention for replay files. AsciiString getLastReplayFileName(); ///< Returns the filename used for the default replay. GameInfo *getGameInfo( void ) { return &m_gameInfo; } ///< Returns the slot list for playback game start @@ -124,6 +128,7 @@ class RecorderClass : public SubsystemInterface { void logPlayerDisconnect(UnicodeString player, Int slot); void logCRCMismatch( void ); + Bool sawCRCMismatch() const; void cleanUpReplayFile( void ); ///< after a crash, send replay/debug info to a central repository void stopRecording(); ///< Stop recording and close m_file. @@ -154,6 +159,7 @@ class RecorderClass : public SubsystemInterface { Int m_currentFilePosition; RecorderModeType m_mode; AsciiString m_currentReplayFilename; ///< valid during playback only + UnsignedInt m_playbackFrameCount; ReplayGameInfo m_gameInfo; Bool m_wasDesync; diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h b/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h index 74e7add404b..1f1ada1daca 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h @@ -30,6 +30,15 @@ class ClientInstance static bool isInitialized(); + static bool isMultiInstance(); + + // Change multi instance on runtime. Must be called before initialize. + static void setMultiInstance(bool v); + + // Skips using the primary instance. Must be called before initialize. + // Useful when the new process is not meant to collide with another normal Generals process. + static void skipPrimaryInstance(); + // Returns the instance index of this game client. Starts at 0. static UnsignedInt getInstanceIndex(); @@ -42,6 +51,7 @@ class ClientInstance private: static HANDLE s_mutexHandle; static UnsignedInt s_instanceIndex; + static Bool s_isMultiInstance; }; } // namespace rts diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index 3411ab8defe..a6f60f36bf6 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -99,6 +99,8 @@ class GameClient : public SubsystemInterface, virtual void setFrame( UnsignedInt frame ) { m_frame = frame; } ///< Set the GameClient's internal frame number virtual void registerDrawable( Drawable *draw ); ///< Given a drawable, register it with the GameClient and give it a unique ID + void updateHeadless(); + void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index 1b31d5028a7..dc967b179c4 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -29,7 +29,9 @@ #include "Common/CommandLine.h" #include "Common/CRCDebug.h" #include "Common/LocalFileSystem.h" +#include "Common/Recorder.h" #include "Common/version.h" +#include "GameClient/ClientInstance.h" #include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition #include "GameClient/GameText.h" #include "GameNetwork/NetworkDefs.h" @@ -405,6 +407,56 @@ Int parseMapName(char *args[], int num) return 1; } +Int parseHeadless(char *args[], int num) +{ + TheWritableGlobalData->m_headless = TRUE; + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_afterIntro = TRUE; + TheWritableGlobalData->m_playSizzle = FALSE; + return 1; +} + +Int parseReplay(char *args[], int num) +{ + if (num > 1) + { + AsciiString filename = args[1]; + if (!filename.endsWithNoCase(RecorderClass::getReplayExtention())) + { + printf("Invalid replay name \"%s\"\n", filename.str()); + exit(1); + } + TheWritableGlobalData->m_simulateReplays.push_back(filename); + + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_afterIntro = TRUE; + TheWritableGlobalData->m_playSizzle = FALSE; + TheWritableGlobalData->m_shellMapOn = FALSE; + + // Make replay playback possible while other clients (possible retail) are running + rts::ClientInstance::setMultiInstance(TRUE); + rts::ClientInstance::skipPrimaryInstance(); + + return 2; + } + return 1; +} + +Int parseJobs(char *args[], int num) +{ + if (num > 1) + { + TheWritableGlobalData->m_simulateReplayJobs = atoi(args[1]); + if (TheGlobalData->m_simulateReplayJobs < SIMULATE_REPLAYS_SEQUENTIAL || TheGlobalData->m_simulateReplayJobs == 0) + { + printf("Invalid number of jobs: %d\n", TheGlobalData->m_simulateReplayJobs); + exit(1); + } + return 2; + } + return 1; +} + Int parseXRes(char *args[], int num) { if (num > 1) @@ -782,13 +834,6 @@ Int parseQuickStart( char *args[], int num ) return 1; } -Int parseHeadless( char *args[], int num ) -{ - TheWritableGlobalData->m_headless = TRUE; - - return 1; -} - Int parseConstantDebug( char *args[], int num ) { TheWritableGlobalData->m_constantDebugUpdate = TRUE; @@ -1103,8 +1148,20 @@ static CommandLineParam paramsForStartup[] = { "-fullscreen", parseNoWin }, // TheSuperHackers @feature helmutbuhler 11/04/2025 - // This runs the game without a window, graphics, input and audio. Used for testing. + // This runs the game without a window, graphics, input and audio. You can combine this with -replay { "-headless", parseHeadless }, + + // TheSuperHackers @feature helmutbuhler 13/04/2025 + // Play back a replay. Pass the filename including .rep afterwards. + // You can pass this multiple times to play back multiple replays. + // You can also include wildcards. The file must be in the replay folder or in a subfolder. + { "-replay", parseReplay }, + + // TheSuperHackers @feature helmutbuhler 23/05/2025 + // Simulate each replay in a separate process and use 1..N processes at the same time. + // (If you have 4 cores, call it with -jobs 4) + // If you do not call this, all replays will be simulated in sequence in the same process. + { "-jobs", parseJobs }, }; // These Params are parsed during Engine Init before INI data is loaded diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index e4382cf8138..f95a046a494 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -251,8 +251,7 @@ void GameEngine::setFramesPerSecondLimit( Int fps ) /** ----------------------------------------------------------------------------------------------- * Initialize the game engine by initializing the GameLogic and GameClient. */ -void GameEngine::init( void ) {} /// @todo: I changed this to take argc & argv so we can parse those after the GDF is loaded. We need to rethink this immediately as it is a nasty hack -void GameEngine::init( int argc, char *argv[] ) +void GameEngine::init() { try { //create an INI object to use for loading stuff @@ -613,7 +612,7 @@ void GameEngine::init( int argc, char *argv[] ) // load the initial shell screen //TheShell->push( AsciiString("Menus/MainMenu.wnd") ); - // This allows us to run a map/reply from the command line + // This allows us to run a map from the command line if (TheGlobalData->m_initialFile.isEmpty() == FALSE) { AsciiString fname = TheGlobalData->m_initialFile; @@ -635,10 +634,6 @@ void GameEngine::init( int argc, char *argv[] ) msg->appendIntegerArgument(0); InitRandom(0); } - else if (fname.endsWithNoCase(".rep")) - { - TheRecorder->playbackFile(fname); - } } // diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp index aef3d163e30..7ee88428c06 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp @@ -29,23 +29,33 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Common/GameEngine.h" +#include "Common/ReplaySimulation.h" /** * This is the entry point for the game system. */ -void GameMain( int argc, char *argv[] ) +Int GameMain() { + int exitcode = 0; // initialize the game engine using factory function TheGameEngine = CreateGameEngine(); - TheGameEngine->init(argc, argv); - - // run it - TheGameEngine->execute(); + TheGameEngine->init(); + + if (!TheGlobalData->m_simulateReplays.empty()) + { + exitcode = ReplaySimulation::simulateReplays(TheGlobalData->m_simulateReplays, TheGlobalData->m_simulateReplayJobs); + } + else + { + // run it + TheGameEngine->execute(); + } // since execute() returned, we are exiting the game delete TheGameEngine; TheGameEngine = NULL; + return exitcode; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 6f94d7d59a3..11af51a8109 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -956,6 +956,9 @@ GlobalData::GlobalData() m_buildMapCache = FALSE; m_initialFile.clear(); m_pendingFile.clear(); + + m_simulateReplays.clear(); + m_simulateReplayJobs = SIMULATE_REPLAYS_SEQUENTIAL; for (i = LEVEL_FIRST; i <= LEVEL_LAST; ++i) m_healthBonus[i] = 1.0f; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index a8873aefd58..8261cbbe7f5 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -67,8 +67,8 @@ typedef int32_t replay_time_t; static time_t startTime; static const UnsignedInt startTimeOffset = 6; static const UnsignedInt endTimeOffset = startTimeOffset + sizeof(replay_time_t); -static const UnsignedInt framesOffset = endTimeOffset + sizeof(replay_time_t); -static const UnsignedInt desyncOffset = framesOffset + sizeof(UnsignedInt); +static const UnsignedInt frameCountOffset = endTimeOffset + sizeof(replay_time_t); +static const UnsignedInt desyncOffset = frameCountOffset + sizeof(UnsignedInt); static const UnsignedInt quitEarlyOffset = desyncOffset + sizeof(Bool); static const UnsignedInt disconOffset = quitEarlyOffset + sizeof(Bool); @@ -232,7 +232,7 @@ void RecorderClass::logGameEnd( void ) time_t t; time(&t); - UnsignedInt duration = TheGameLogic->getFrame(); + UnsignedInt frameCount = TheGameLogic->getFrame(); UnsignedInt fileSize = ftell(m_file); // move to appropriate offset if (!fseek(m_file, endTimeOffset, SEEK_SET)) @@ -242,10 +242,10 @@ void RecorderClass::logGameEnd( void ) fwrite(&tmp, sizeof(replay_time_t), 1, m_file); } // move to appropriate offset - if (!fseek(m_file, framesOffset, SEEK_SET)) + if (!fseek(m_file, frameCountOffset, SEEK_SET)) { - // save off duration - fwrite(&duration, sizeof(UnsignedInt), 1, m_file); + // save off frameCount + fwrite(&frameCount, sizeof(UnsignedInt), 1, m_file); } // move back to end of stream #ifdef DEBUG_CRASHING @@ -272,7 +272,7 @@ void RecorderClass::logGameEnd( void ) if (logFP) { struct tm *t2 = localtime(&t); - duration = t - startTime; + time_t duration = t - startTime; Int minutes = duration/60; Int seconds = duration%60; fprintf(logFP, "Game end at %s(%d:%2.2d elapsed time)\n", asctime(t2), minutes, seconds); @@ -408,6 +408,7 @@ void RecorderClass::init() { m_gameInfo.setSeed(GetGameLogicRandomSeed()); m_wasDesync = FALSE; m_doingAnalysis = FALSE; + m_playbackFrameCount = 0; } /** @@ -430,7 +431,7 @@ void RecorderClass::reset() { void RecorderClass::update() { if (m_mode == RECORDERMODETYPE_RECORD || m_mode == RECORDERMODETYPE_NONE) { updateRecord(); - } else if (m_mode == RECORDERMODETYPE_PLAYBACK) { + } else if (isPlaybackMode()) { updatePlayback(); } } @@ -476,11 +477,11 @@ void RecorderClass::stopPlayback() { m_file = NULL; } m_fileName.clear(); - // Don't clear the game data if the replay is over - let things continue -//#ifdef DEBUG_CRC + if (!m_doingAnalysis) + { TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); -//#endif + } } /** @@ -854,7 +855,7 @@ Bool RecorderClass::readReplayHeader(ReplayHeader& header) fread(&tmp, sizeof(replay_time_t), 1, m_file); header.endTime = tmp; - fread(&header.frameDuration, sizeof(UnsignedInt), 1, m_file); + fread(&header.frameCount, sizeof(UnsignedInt), 1, m_file); fread(&header.desyncGame, sizeof(Bool), 1, m_file); fread(&header.quitEarly, sizeof(Bool), 1, m_file); @@ -917,6 +918,14 @@ Bool RecorderClass::readReplayHeader(ReplayHeader& header) return TRUE; } +Bool RecorderClass::simulateReplay(AsciiString filename) +{ + Bool success = playbackFile(filename); + if (success) + m_mode = RECORDERMODETYPE_SIMULATION_PLAYBACK; + return success; +} + #if defined RTS_DEBUG || defined RTS_INTERNAL Bool RecorderClass::analyzeReplay( AsciiString filename ) { @@ -924,15 +933,18 @@ Bool RecorderClass::analyzeReplay( AsciiString filename ) return playbackFile(filename); } -Bool RecorderClass::isAnalysisInProgress( void ) + + +#endif + +Bool RecorderClass::isPlaybackInProgress( void ) const { - return m_mode == RECORDERMODETYPE_PLAYBACK && m_nextFrame != -1; + return isPlaybackMode() && m_nextFrame != -1; } -#endif AsciiString RecorderClass::getCurrentReplayFilename( void ) { - if (m_mode == RECORDERMODETYPE_PLAYBACK) + if (isPlaybackMode()) { return m_currentReplayFilename; } @@ -966,7 +978,7 @@ class CRCInfo UnsignedInt getLocalPlayer(void) { return m_localPlayer; } void setSawCRCMismatch(void) { m_sawCRCMismatch = TRUE; } - Bool sawCRCMismatch(void) { return m_sawCRCMismatch; } + Bool sawCRCMismatch(void) const { return m_sawCRCMismatch; } protected: @@ -1014,6 +1026,11 @@ UnsignedInt CRCInfo::readCRC(void) return val; } +Bool RecorderClass::sawCRCMismatch() const +{ + return m_crcInfo->sawCRCMismatch(); +} + void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback) { if (fromPlayback) @@ -1061,6 +1078,9 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f DEBUG_LOG(("Replay has gone out of sync!\nInGame:%8.8X Replay:%8.8X\nFrame:%d\n", playbackCRC, newCRC, mismatchFrame)); + // Print Mismatch in case we are simulating replays from console. + printf("CRC Mismatch in Frame %d\n", mismatchFrame); + // TheSuperHackers @tweak Pause the game on mismatch. Bool pause = TRUE; Bool pauseMusic = FALSE; @@ -1215,17 +1235,23 @@ Bool RecorderClass::playbackFile(AsciiString filename) // send a message to the logic for a new game if (!m_doingAnalysis) { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME ); + // TheSuperHackers @info helmutbuhler 13/04/2025 + // We send the New Game message here directly to the command list and bypass the TheMessageStream. + // That's ok because Multiplayer is disabled during replay playback and is actually required + // during replay simulation because we don't update TheMessageStream during simulation. + GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_NEW_GAME); msg->appendIntegerArgument(GAME_REPLAY); msg->appendIntegerArgument(difficulty); msg->appendIntegerArgument(rankPoints); if( maxFPS != 0 ) msg->appendIntegerArgument(maxFPS); + TheCommandList->appendMessage( msg ); //InitGameLogicRandom( m_gameInfo.getSeed()); InitRandom( m_gameInfo.getSeed() ); } m_currentReplayFilename = filename; + m_playbackFrameCount = header.frameCount; return TRUE; } @@ -1663,7 +1689,7 @@ void RecorderClass::initControls() Bool RecorderClass::isMultiplayer( void ) { - if (m_mode == RECORDERMODETYPE_PLAYBACK) + if (isPlaybackMode()) { GameSlot *slot; for (int i=0; i 0u) - { - char idStr[33]; - itoa(s_instanceIndex, idStr, 10); - guidStr.push_back('-'); - guidStr.append(idStr); - } - s_mutexHandle = CreateMutex(NULL, FALSE, guidStr.c_str()); - if (GetLastError() == ERROR_ALREADY_EXISTS) + if (isMultiInstance()) { - if (s_mutexHandle != NULL) + std::string guidStr = getFirstInstanceName(); + if (s_instanceIndex > 0u) + { + char idStr[33]; + itoa(s_instanceIndex, idStr, 10); + guidStr.push_back('-'); + guidStr.append(idStr); + } + s_mutexHandle = CreateMutex(NULL, FALSE, guidStr.c_str()); + if (GetLastError() == ERROR_ALREADY_EXISTS) { - CloseHandle(s_mutexHandle); - s_mutexHandle = NULL; + if (s_mutexHandle != NULL) + { + CloseHandle(s_mutexHandle); + s_mutexHandle = NULL; + } + // Try again with a new instance. + ++s_instanceIndex; + continue; } - // Try again with a new instance. - ++s_instanceIndex; - continue; } -#else - s_mutexHandle = CreateMutex(NULL, FALSE, getFirstInstanceName()); - if (GetLastError() == ERROR_ALREADY_EXISTS) + else { - if (s_mutexHandle != NULL) + s_mutexHandle = CreateMutex(NULL, FALSE, getFirstInstanceName()); + if (GetLastError() == ERROR_ALREADY_EXISTS) { - CloseHandle(s_mutexHandle); - s_mutexHandle = NULL; + if (s_mutexHandle != NULL) + { + CloseHandle(s_mutexHandle); + s_mutexHandle = NULL; + } + return false; } - return false; } -#endif break; } @@ -80,6 +89,31 @@ bool ClientInstance::isInitialized() return s_mutexHandle != NULL; } +bool ClientInstance::isMultiInstance() +{ + return s_isMultiInstance; +} + +void ClientInstance::setMultiInstance(bool v) +{ + if (isInitialized()) + { + DEBUG_CRASH(("ClientInstance::setMultiInstance(%d) - cannot set multi instance after initialization", (int)v)); + return; + } + s_isMultiInstance = v; +} + +void ClientInstance::skipPrimaryInstance() +{ + if (isInitialized()) + { + DEBUG_CRASH(("ClientInstance::skipPrimaryInstance() - cannot skip primary instance after initialization")); + return; + } + s_instanceIndex = 1; +} + UnsignedInt ClientInstance::getInstanceIndex() { DEBUG_ASSERTLOG(isInitialized(), ("ClientInstance::isInitialized() failed")); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp index ab4aa1ad44d..caa355f0c3b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp @@ -248,7 +248,7 @@ void PopulateReplayFileListbox(GameWindow *listbox) // time_t totalSeconds = header.endTime - header.startTime; // Int mins = totalSeconds/60; // Int secs = totalSeconds%60; -// Real fps = header.frameDuration/totalSeconds; +// Real fps = header.frameCount/totalSeconds; // extraStr.format(L"%d:%d (%g fps) %hs", mins, secs, fps, header.desyncGame?"OOS ":""); // // for (Int i=0; iupdate(); - } while (TheRecorder->isAnalysisInProgress()); + } while (TheRecorder->isPlaybackInProgress()); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index 23e3960ebc4..5e6ad64c59d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -67,6 +67,7 @@ #include "Common/PlayerTemplate.h" #include "Common/RandomValue.h" #include "Common/Recorder.h" +#include "Common/ReplaySimulation.h" #include "Common/ScoreKeeper.h" #include "Common/SkirmishBattleHonors.h" #include "Common/ThingFactory.h" @@ -503,6 +504,11 @@ WindowMsgHandledType ScoreScreenInput( GameWindow *window, UnsignedInt msg, } // end MainMenuInput +static Bool showButtonContinue() +{ + return ReplaySimulation::getCurrentReplayIndex() != ReplaySimulation::getReplayCount()-1; +} + /** System Function for the ScoreScreen */ //------------------------------------------------------------------------------------------------- WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, @@ -541,23 +547,36 @@ WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, { TheShell->pop(); TheCampaignManager->setCampaign(AsciiString::TheEmptyString); + + if ( ReplaySimulation::getReplayCount() > 0 ) + { + ReplaySimulation::stop(); + TheGameEngine->setQuitting(TRUE); + } } else if ( controlID == buttonContinueID ) { - if(!buttonIsFinishCampaign) - ReplayWasPressed = TRUE; - if( screenType == SCORESCREEN_SINGLEPLAYER) + if( ReplaySimulation::getReplayCount() > 0 ) { - AsciiString mapName = TheCampaignManager->getCurrentMap(); - - if( mapName.isEmpty() ) - { - ReplayWasPressed = FALSE; - TheShell->pop(); - } - else + TheGameEngine->setQuitting(TRUE); + } + else + { + if(!buttonIsFinishCampaign) + ReplayWasPressed = TRUE; + if( screenType == SCORESCREEN_SINGLEPLAYER) { - CheckForCDAtGameStart( startNextCampaignGame ); + AsciiString mapName = TheCampaignManager->getCurrentMap(); + + if( mapName.isEmpty() ) + { + ReplayWasPressed = FALSE; + TheShell->pop(); + } + else + { + CheckForCDAtGameStart( startNextCampaignGame ); + } } } } @@ -984,7 +1003,7 @@ void initReplaySinglePlayer( void ) if (chatBoxBorder) chatBoxBorder->winHide(TRUE); if (buttonContinue) - buttonContinue->winHide(TRUE); + buttonContinue->winHide(!showButtonContinue()); if (buttonBuddies) buttonBuddies->winHide(TRUE); if (listboxChatWindowScoreScreen) @@ -1091,7 +1110,7 @@ void initReplayMultiPlayer(void) if (chatBoxBorder) chatBoxBorder->winHide(TRUE); if (buttonContinue) - buttonContinue->winHide(TRUE); + buttonContinue->winHide(!showButtonContinue()); if (buttonBuddies) buttonBuddies->winHide(TRUE); // if (buttonRehost) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index ea50ed4e8fd..0b7148098c2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -410,7 +410,7 @@ void Shell::showShell( Bool runInit ) { DEBUG_LOG(("Shell:showShell() - %s (%s)\n", TheGlobalData->m_initialFile.str(), (top())?top()->getFilename().str():"no top screen")); - if(!TheGlobalData->m_initialFile.isEmpty()) + if(!TheGlobalData->m_initialFile.isEmpty() || !TheGlobalData->m_simulateReplays.empty()) { return; } @@ -472,7 +472,7 @@ void Shell::showShell( Bool runInit ) void Shell::showShellMap(Bool useShellMap ) { // we don't want any of this to show if we're loading straight into a file - if(TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic ) + if (TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic || !TheGlobalData->m_simulateReplays.empty()) return; if(useShellMap && TheGlobalData->m_shellMapOn) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 8c6e4076c87..c072e3d0d03 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -538,7 +538,7 @@ void GameClient::update( void ) //Initial Game Codition. We must show the movie first and then we can display the shell if(TheGlobalData->m_afterIntro && !TheDisplay->isMoviePlaying()) { - if( playSizzle && TheGlobalData->m_playSizzle && !TheGlobalData->m_headless )// Remove headless-check with Replay Simulation PR + if( playSizzle && TheGlobalData->m_playSizzle ) { TheWritableGlobalData->m_allowExitOutOfMovies = TRUE; if(TheGameLODManager && TheGameLODManager->didMemPass()) @@ -629,11 +629,8 @@ void GameClient::update( void ) if(TheGlobalData->m_playIntro || TheGlobalData->m_afterIntro) { // redraw all views, update the GUI - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR - { - TheDisplay->DRAW(); - TheDisplay->UPDATE(); - } + TheDisplay->DRAW(); + TheDisplay->UPDATE(); return; } @@ -751,12 +748,10 @@ void GameClient::update( void ) } // update display - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR { TheDisplay->UPDATE(); } - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR { USE_PERF_TIMER(GameClient_draw) @@ -782,6 +777,18 @@ void GameClient::update( void ) } } // end update +void GameClient::updateHeadless() +{ + // TheSuperHackers @info helmutbuhler 03/05/2025 + // When we play a replay back in headless mode, we want to skip the update of GameClient + // because it's not necessary for CRC checking. + // But we do reset the particles. The problem is that particles can be generated during + // GameLogic and are only cleaned up during rendering. If we don't clean this up here, + // the particles accumulate and slow things down a lot and can even cause a crash on + // longer replays. + TheParticleSystemManager->reset(); +} + /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index dfc19096551..7c3b1e3dcf8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -302,7 +302,7 @@ void GameLogic::setDefaults( Bool loadingSaveGame ) Bool GameLogic::isInSinglePlayerGame( void ) { return (m_gameMode == GAME_SINGLE_PLAYER || - (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheRecorder->getGameMode() == GAME_SINGLE_PLAYER)); + (TheRecorder && TheRecorder->isPlaybackMode() && TheRecorder->getGameMode() == GAME_SINGLE_PLAYER)); } @@ -839,9 +839,11 @@ static void populateRandomStartPosition( GameInfo *game ) Int i; Int numPlayers = MAX_SLOTS; const MapMetaData *md = TheMapCache->findMap( game->getMap() ); - DEBUG_ASSERTCRASH( md , ("Could not find map %s in the mapcache", game->getMap().str())); if (md) numPlayers = md->m_numPlayers; + else + printf("Could not find map \"%s\"\n", game->getMap().str()); + DEBUG_ASSERTCRASH( md , ("Could not find map %s in the mapcache", game->getMap().str())); // generate a map of start spot distances Real startSpotDistance[MAX_SLOTS][MAX_SLOTS]; @@ -1228,7 +1230,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } else { - if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) + if (TheRecorder && TheRecorder->isPlaybackMode()) { TheGameInfo = game = TheRecorder->getGameInfo(); } @@ -1292,7 +1294,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) //****************************// // Get the m_loadScreen for this kind of game - if(!m_loadScreen && !TheGlobalData->m_headless) + if(!m_loadScreen && !(TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_SIMULATION_PLAYBACK)) { m_loadScreen = getLoadScreen( loadingSaveGame ); if(m_loadScreen) @@ -2218,7 +2220,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } // if we're in a load game, don't fade yet - if(loadingSaveGame == FALSE && TheTransitionHandler != NULL) + if(loadingSaveGame == FALSE && TheTransitionHandler != NULL && m_loadScreen) { TheTransitionHandler->setGroup("FadeWholeScreen"); while(!TheTransitionHandler->isFinished()) @@ -3706,20 +3708,21 @@ void GameLogic::update( void ) if (generateForSolo || generateForMP) { m_CRC = getCRC( CRC_RECALC ); - if (isMPGameOrReplay) - { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); - msg->appendIntegerArgument( m_CRC ); - msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - DEBUG_LOG(("Appended CRC on frame %d: %8.8X\n", m_frame, m_CRC)); - } - else - { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); - msg->appendIntegerArgument( m_CRC ); - msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - DEBUG_LOG(("Appended Playback CRC on frame %d: %8.8X\n", m_frame, m_CRC)); - } + bool isPlayback = (TheRecorder && TheRecorder->isPlaybackMode()); + + GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_LOGIC_CRC); + msg->appendIntegerArgument(m_CRC); + msg->appendBooleanArgument(isPlayback); + + // TheSuperHackers @info helmutbuhler 13/04/2025 + // During replay simulation, we bypass TheMessageStream and instead put the CRC message + // directly into TheCommandList because we don't update TheMessageStream during simulation. + GameMessageList *messageList = TheMessageStream; + if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_SIMULATION_PLAYBACK) + messageList = TheCommandList; + messageList->appendMessage(msg); + + DEBUG_LOG(("Appended %sCRC on frame %d: %8.8X\n", isPlayback ? "Playback " : "", m_frame, m_CRC)); } // collect stats diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 7003423f037..75ad17edb34 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1827,7 +1827,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) // -------------------------------------------------------------------------------------------- case GameMessage::MSG_SET_REPLAY_CAMERA: { - if (TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer) + if (TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer) { if (TheTacticalView->isCameraMovementFinished()) { @@ -1964,7 +1964,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) //thisPlayer->getPlayerDisplayName().str(), m_frame)); m_cachedCRCs[msg->getPlayerIndex()] = newCRC; // to mask problem: = (oldCRC < newCRC)?newCRC:oldCRC; } - else if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) + else if (TheRecorder && TheRecorder->isPlaybackMode()) { UnsignedInt newCRC = msg->getArgument(0)->integer; //DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d\n", @@ -1994,7 +1994,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) } // end switch /**/ /// @todo: multiplayer semantics - if (currentlySelectedGroup && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/) + if (currentlySelectedGroup && TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/) { const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs(); TheInGameUI->deselectAllDrawables(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp index f485c603417..0d0a43018fe 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp @@ -97,15 +97,16 @@ EnumeratedIP * IPEnumeration::getAddresses( void ) return NULL; } -#if defined(RTS_MULTI_INSTANCE) // TheSuperHackers @feature Add one unique local host IP address for each multi client instance. - const UnsignedInt id = rts::ClientInstance::getInstanceId(); - addNewIP( - 127, - (UnsignedByte)(id >> 16), - (UnsignedByte)(id >> 8), - (UnsignedByte)(id)); -#endif + if (rts::ClientInstance::isMultiInstance()) + { + const UnsignedInt id = rts::ClientInstance::getInstanceId(); + addNewIP( + 127, + (UnsignedByte)(id >> 16), + (UnsignedByte)(id >> 8), + (UnsignedByte)(id)); + } // construct a list of addresses int numAddresses = 0; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 95196922a87..c2754bc5417 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -762,6 +762,7 @@ static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5; Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, Int nCmdShow ) { + Int exitcode = 1; #ifdef RTS_PROFILE Profile::StartRange("init"); @@ -840,7 +841,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) - return 0; + return exitcode; // save our application instance for future use ApplicationHInstance = hInstance; @@ -881,14 +882,14 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheVersion = NULL; shutdownMemoryManager(); DEBUG_SHUTDOWN(); - return 0; + return exitcode; } DEBUG_LOG(("Create Generals Mutex okay.\n")); DEBUG_LOG(("CRC message is %d\n", GameMessage::MSG_LOGIC_CRC)); // run the game main loop - GameMain(0, NULL); + exitcode = GameMain(); delete TheVersion; TheVersion = NULL; @@ -916,7 +917,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheDmaCriticalSection = NULL; TheMemoryPoolCriticalSection = NULL; - return 0; + return exitcode; } // end WinMain From 1ebbf5c671e75749ecf211c35197a56733ff513b Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 07:30:54 +0200 Subject: [PATCH 29/55] [ZH] Prevent dereferencing NULL pointer 'state' in StateMachine::xfer() (#1100) --- .../GameEngine/Source/Common/StateMachine.cpp | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/StateMachine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/StateMachine.cpp index 7c4de27d48b..faa22d3c903 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/StateMachine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/StateMachine.cpp @@ -865,19 +865,24 @@ void StateMachine::xfer( Xfer *xfer ) } for( i = m_stateMap.begin(); i != m_stateMap.end(); ++i ) { State *state = (*i).second; - StateID id = state->getID(); - xfer->xferUnsignedInt(&id); - if (id!=state->getID()) { - DEBUG_CRASH(("State ID mismatch - %d expected, %d read", state->getID(), id)); - throw SC_INVALID_DATA; + if( state != NULL ) + { + StateID id = state->getID(); + xfer->xferUnsignedInt(&id); + if (id!=state->getID()) { + DEBUG_CRASH(("State ID mismatch - %d expected, %d read", state->getID(), id)); + throw SC_INVALID_DATA; + } } - - if( state == NULL ) + else { - DEBUG_ASSERTCRASH(state != NULL, ("state was NULL on xfer, trying to heal...")); + DEBUG_CRASH(("state was NULL on xfer, trying to heal...")); // Hmm... too late to find out why we are getting NULL in our state, but if we let it go, we will Throw in xferSnapshot. state = internalGetState(m_defaultStateID); + StateID id = state->getID(); + xfer->xferUnsignedInt(&id); } + xfer->xferSnapshot(state); } From 869822967a8085e6c1e8b03c7e90507572724afc Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 07:31:53 +0200 Subject: [PATCH 30/55] [GEN][ZH] Remove unnecessary NULL pointer tests in RTS3DScene::RTS3DScene() (#1105) --- .../Source/W3DDevice/GameClient/W3DScene.cpp | 22 ++++++++++++------ .../Source/W3DDevice/GameClient/W3DScene.cpp | 23 ++++++++++++------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index 2f8633aa94e..ccbc15d2daf 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -136,7 +136,7 @@ RTS3DScene::RTS3DScene() //Allocate memory to hold queue of visible renderobjects that need to be drawn last //because they are forced translucent. m_translucentObjectsCount = 0; - if (TheGlobalData && TheGlobalData->m_maxVisibleTranslucentObjects) + if (TheGlobalData->m_maxVisibleTranslucentObjects > 0) m_translucentObjectsBuffer = NEW RenderObjClass* [TheGlobalData->m_maxVisibleTranslucentObjects]; else m_translucentObjectsBuffer = NULL; @@ -146,18 +146,26 @@ RTS3DScene::RTS3DScene() m_numNonOccluderOrOccludee=0; m_occludedObjectsCount=0; - m_potentialOccluders=NULL; - m_potentialOccludees=NULL; - m_nonOccludersOrOccludees=NULL; + if (TheGlobalData->m_maxVisibleOccluderObjects > 0) + m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects]; + else + m_potentialOccluders = NULL; + + if (TheGlobalData->m_maxVisibleOccludeeObjects > 0) + m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects]; + else + m_potentialOccludees = NULL; + + if (TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects > 0) + m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects]; + else + m_nonOccludersOrOccludees = NULL; //Modify the shader to make occlusion transparent ShaderClass shader = PlayerColorShader; shader.Set_Src_Blend_Func(ShaderClass::SRCBLEND_SRC_ALPHA); shader.Set_Dst_Blend_Func(ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA); - m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects]; - m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects]; - m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects]; #ifdef USE_NON_STENCIL_OCCLUSION for (i=0; im_maxVisibleTranslucentObjects) + if (TheGlobalData->m_maxVisibleTranslucentObjects > 0) m_translucentObjectsBuffer = NEW RenderObjClass* [TheGlobalData->m_maxVisibleTranslucentObjects]; else m_translucentObjectsBuffer = NULL; @@ -162,19 +162,26 @@ RTS3DScene::RTS3DScene() m_numNonOccluderOrOccludee=0; m_occludedObjectsCount=0; - m_potentialOccluders=NULL; - m_potentialOccludees=NULL; - m_nonOccludersOrOccludees=NULL; + if (TheGlobalData->m_maxVisibleOccluderObjects > 0) + m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects]; + else + m_potentialOccluders = NULL; + + if (TheGlobalData->m_maxVisibleOccludeeObjects > 0) + m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects]; + else + m_potentialOccludees = NULL; + + if (TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects > 0) + m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects]; + else + m_nonOccludersOrOccludees = NULL; //Modify the shader to make occlusion transparent ShaderClass shader = PlayerColorShader; shader.Set_Src_Blend_Func(ShaderClass::SRCBLEND_SRC_ALPHA); shader.Set_Dst_Blend_Func(ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA); - m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects]; - m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects]; - m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects]; - #ifdef USE_NON_STENCIL_OCCLUSION for (i=0; i Date: Sun, 22 Jun 2025 07:37:15 +0200 Subject: [PATCH 31/55] [GEN][ZH] Suppress compiler warning about dereferencing NULL pointer 'builderObj' in PlaceEventTranslator::translateGameMessage() (#1106) --- .../GameClient/MessageStream/PlaceEventTranslator.cpp | 6 ++---- .../GameClient/MessageStream/PlaceEventTranslator.cpp | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp index 7ae460e94b7..4c1192f24f9 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp @@ -170,11 +170,7 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess // translate the screen position of start to world target location TheTacticalView->screenToTerrain( &anchorStart, &world ); - // get the source object ID of the thing that is "building" the object - ObjectID builderID = INVALID_ID; Object *builderObj = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() ); - if( builderObj ) - builderID = builderObj->getID(); //Kris: September 27, 2002 //Make sure we have enough CASH to build it! It's possible that between the @@ -209,6 +205,8 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess break; } + DEBUG_ASSERTCRASH(builderObj != NULL, ("builderObj is NULL")); + // check to see if this is a legal location to build something at LegalBuildCode lbc; lbc = TheBuildAssistant->isLocationLegalToBuild( &world, diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp index 16ba5e79e6f..6c750071fed 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp @@ -181,11 +181,7 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess // translate the screen position of start to world target location TheTacticalView->screenToTerrain( &anchorStart, &world ); - // get the source object ID of the thing that is "building" the object - ObjectID builderID = INVALID_ID; Object *builderObj = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() ); - if( builderObj ) - builderID = builderObj->getID(); //Kris: September 27, 2002 //Make sure we have enough CASH to build it! It's possible that between the @@ -220,6 +216,8 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess break; } + DEBUG_ASSERTCRASH(builderObj != NULL, ("builderObj is NULL")); + // check to see if this is a legal location to build something at LegalBuildCode lbc; lbc = TheBuildAssistant->isLocationLegalToBuild( &world, From d5e18c5dac9d66190ad3fbd84a175f5ea59e841f Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:33:58 +0200 Subject: [PATCH 32/55] [GEN][ZH] Remove unnecessary NULL pointer test in CrateCollide::isValidToExecute() (#1110) --- .../GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp | 2 +- .../GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp index 49945919baa..f9da65a67d0 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp @@ -163,7 +163,7 @@ Bool CrateCollide::isValidToExecute( const Object *other ) const return FALSE; // must match our kindof flags (if any) - if (md && !other->isKindOfMulti(md->m_kindof, md->m_kindofnot)) + if ( !other->isKindOfMulti(md->m_kindof, md->m_kindofnot) ) return FALSE; if( other->isEffectivelyDead() ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp index 0be6e01aa8e..b822e26a3cf 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp @@ -166,7 +166,7 @@ Bool CrateCollide::isValidToExecute( const Object *other ) const return FALSE; // must match our kindof flags (if any) - if (md && !other->isKindOfMulti(md->m_kindof, md->m_kindofnot)) + if ( !other->isKindOfMulti(md->m_kindof, md->m_kindofnot) ) return FALSE; if( other->isEffectivelyDead() ) From 1fbfbe7685c98f60c09d3e73d7f2e12ca4326942 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:42:11 +0200 Subject: [PATCH 33/55] [GEN][ZH] Prevent dereferencing NULL pointer 'pbmi' and memory leaks in CreateBMPFile() (#1124) --- .../W3DDevice/GameClient/W3DDisplay.cpp | 132 +++++++++--------- .../W3DDevice/GameClient/W3DDisplay.cpp | 132 +++++++++--------- 2 files changed, 134 insertions(+), 130 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 8a5f3c0a3a6..02177767862 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -2780,75 +2780,77 @@ void W3DDisplay::setShroudLevel( Int x, Int y, CellShroudStatus setting ) //============================================================================= ///Utility function to dump data into a .BMP file static void CreateBMPFile(LPTSTR pszFile, char *image, Int width, Int height) -{ - HANDLE hf; // file handle - BITMAPFILEHEADER hdr; // bitmap file-header - PBITMAPINFOHEADER pbih; // bitmap info-header - LPBYTE lpBits; // memory pointer - DWORD dwTotal; // total count of bytes - DWORD cb; // incremental count of bytes - BYTE *hp; // byte pointer - DWORD dwTmp; - - PBITMAPINFO pbmi; - - pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)); - pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - pbmi->bmiHeader.biWidth = width; - pbmi->bmiHeader.biHeight = height; - pbmi->bmiHeader.biPlanes = 1; - pbmi->bmiHeader.biBitCount = 24; - pbmi->bmiHeader.biCompression = BI_RGB; - pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * 24; - pbmi->bmiHeader.biClrImportant = 0; - - - pbih = (PBITMAPINFOHEADER) pbmi; - lpBits = (LPBYTE) image; - - // Create the .BMP file. - hf = CreateFile(pszFile, - GENERIC_READ | GENERIC_WRITE, - (DWORD) 0, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - (HANDLE) NULL); - if (hf == INVALID_HANDLE_VALUE) - return; - hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" - // Compute the size of the entire file. - hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + - pbih->biSize + pbih->biClrUsed - * sizeof(RGBQUAD) + pbih->biSizeImage); - hdr.bfReserved1 = 0; - hdr.bfReserved2 = 0; - - // Compute the offset to the array of color indices. - hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + - pbih->biSize + pbih->biClrUsed - * sizeof (RGBQUAD); - - // Copy the BITMAPFILEHEADER into the .BMP file. - if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), - (LPDWORD) &dwTmp, NULL)) - return; - - // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. - if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),(LPDWORD) &dwTmp, NULL)) +{ + HANDLE hf; // file handle + BITMAPFILEHEADER hdr; // bitmap file-header + PBITMAPINFOHEADER pbih; // bitmap info-header + LPBYTE lpBits; // memory pointer + DWORD dwTotal; // total count of bytes + DWORD cb; // incremental count of bytes + BYTE *hp; // byte pointer + DWORD dwTmp; + + PBITMAPINFO pbmi; + + pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)); + if (pbmi == NULL) return; - // Copy the array of color indices into the .BMP file. - dwTotal = cb = pbih->biSizeImage; - hp = lpBits; - if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL)) - return; + pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pbmi->bmiHeader.biWidth = width; + pbmi->bmiHeader.biHeight = height; + pbmi->bmiHeader.biPlanes = 1; + pbmi->bmiHeader.biBitCount = 24; + pbmi->bmiHeader.biCompression = BI_RGB; + pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * 24; + pbmi->bmiHeader.biClrImportant = 0; + + pbih = (PBITMAPINFOHEADER) pbmi; + lpBits = (LPBYTE) image; + + // Create the .BMP file. + hf = CreateFile(pszFile, + GENERIC_READ | GENERIC_WRITE, + (DWORD) 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + (HANDLE) NULL); + + if (hf != INVALID_HANDLE_VALUE) + { + hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" + // Compute the size of the entire file. + hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof(RGBQUAD) + pbih->biSizeImage); + hdr.bfReserved1 = 0; + hdr.bfReserved2 = 0; + + // Compute the offset to the array of color indices. + hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof (RGBQUAD); + + // Copy the BITMAPFILEHEADER into the .BMP file. + if (WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), + (LPDWORD) &dwTmp, NULL)) + { + // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. + if (WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),(LPDWORD) &dwTmp, NULL)) + { + // Copy the array of color indices into the .BMP file. + dwTotal = cb = pbih->biSizeImage; + hp = lpBits; + WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp, NULL); + } + } - // Close the .BMP file. - if (!CloseHandle(hf)) - return; + // Close the .BMP file. + CloseHandle(hf); + } - // Free memory. + // Free memory. LocalFree( (HLOCAL) pbmi); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 1a12f98a3cd..a55d561a941 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -2920,75 +2920,77 @@ void W3DDisplay::setShroudLevel( Int x, Int y, CellShroudStatus setting ) //============================================================================= ///Utility function to dump data into a .BMP file static void CreateBMPFile(LPTSTR pszFile, char *image, Int width, Int height) -{ - HANDLE hf; // file handle - BITMAPFILEHEADER hdr; // bitmap file-header - PBITMAPINFOHEADER pbih; // bitmap info-header - LPBYTE lpBits; // memory pointer - DWORD dwTotal; // total count of bytes - DWORD cb; // incremental count of bytes - BYTE *hp; // byte pointer - DWORD dwTmp; - - PBITMAPINFO pbmi; - - pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)); - pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - pbmi->bmiHeader.biWidth = width; - pbmi->bmiHeader.biHeight = height; - pbmi->bmiHeader.biPlanes = 1; - pbmi->bmiHeader.biBitCount = 24; - pbmi->bmiHeader.biCompression = BI_RGB; - pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * 24; - pbmi->bmiHeader.biClrImportant = 0; - - - pbih = (PBITMAPINFOHEADER) pbmi; - lpBits = (LPBYTE) image; - - // Create the .BMP file. - hf = CreateFile(pszFile, - GENERIC_READ | GENERIC_WRITE, - (DWORD) 0, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - (HANDLE) NULL); - if (hf == INVALID_HANDLE_VALUE) - return; - hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" - // Compute the size of the entire file. - hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + - pbih->biSize + pbih->biClrUsed - * sizeof(RGBQUAD) + pbih->biSizeImage); - hdr.bfReserved1 = 0; - hdr.bfReserved2 = 0; - - // Compute the offset to the array of color indices. - hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + - pbih->biSize + pbih->biClrUsed - * sizeof (RGBQUAD); - - // Copy the BITMAPFILEHEADER into the .BMP file. - if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), - (LPDWORD) &dwTmp, NULL)) - return; - - // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. - if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),(LPDWORD) &dwTmp, NULL)) +{ + HANDLE hf; // file handle + BITMAPFILEHEADER hdr; // bitmap file-header + PBITMAPINFOHEADER pbih; // bitmap info-header + LPBYTE lpBits; // memory pointer + DWORD dwTotal; // total count of bytes + DWORD cb; // incremental count of bytes + BYTE *hp; // byte pointer + DWORD dwTmp; + + PBITMAPINFO pbmi; + + pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)); + if (pbmi == NULL) return; - // Copy the array of color indices into the .BMP file. - dwTotal = cb = pbih->biSizeImage; - hp = lpBits; - if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL)) - return; + pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pbmi->bmiHeader.biWidth = width; + pbmi->bmiHeader.biHeight = height; + pbmi->bmiHeader.biPlanes = 1; + pbmi->bmiHeader.biBitCount = 24; + pbmi->bmiHeader.biCompression = BI_RGB; + pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * 24; + pbmi->bmiHeader.biClrImportant = 0; + + pbih = (PBITMAPINFOHEADER) pbmi; + lpBits = (LPBYTE) image; + + // Create the .BMP file. + hf = CreateFile(pszFile, + GENERIC_READ | GENERIC_WRITE, + (DWORD) 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + (HANDLE) NULL); + + if (hf != INVALID_HANDLE_VALUE) + { + hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" + // Compute the size of the entire file. + hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof(RGBQUAD) + pbih->biSizeImage); + hdr.bfReserved1 = 0; + hdr.bfReserved2 = 0; + + // Compute the offset to the array of color indices. + hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof (RGBQUAD); + + // Copy the BITMAPFILEHEADER into the .BMP file. + if (WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), + (LPDWORD) &dwTmp, NULL)) + { + // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. + if (WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),(LPDWORD) &dwTmp, NULL)) + { + // Copy the array of color indices into the .BMP file. + dwTotal = cb = pbih->biSizeImage; + hp = lpBits; + WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp, NULL); + } + } - // Close the .BMP file. - if (!CloseHandle(hf)) - return; + // Close the .BMP file. + CloseHandle(hf); + } - // Free memory. + // Free memory. LocalFree( (HLOCAL) pbmi); } From 75c40d04bf71249392ce4a31080240e8fc2afd55 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:42:45 +0200 Subject: [PATCH 34/55] [GEN][ZH] Prevent using uninitialized memory 'locked_rects' in TextureLoader::Load_Thumbnail() (#1129) --- Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp | 2 +- .../Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp index b8187e07502..417aecde43f 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp @@ -199,7 +199,7 @@ IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename,WW3 MIP_LEVELS_ALL); unsigned level=0; - D3DLOCKED_RECT locked_rects[12]; + D3DLOCKED_RECT locked_rects[12]={0}; WWASSERT(d3d_texture->GetLevelCount()<=12); // Lock all surfaces diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp index 92e3a66df4c..6e40fb487eb 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp @@ -452,7 +452,7 @@ IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename, co #endif unsigned level=0; - D3DLOCKED_RECT locked_rects[12]; + D3DLOCKED_RECT locked_rects[12]={0}; WWASSERT(sysmem_texture->GetLevelCount()<=12); // Lock all surfaces From 2e8a2d152b98d0e74ceafd40c8d304ac529e1f7c Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Sun, 22 Jun 2025 16:43:10 +1000 Subject: [PATCH 35/55] [GEN][ZH] Do not acquire money transaction audio if the play sound parameter is false (#1130) --- .../GameEngine/Source/Common/RTS/Money.cpp | 18 ++++++++++-------- .../GameEngine/Source/Common/RTS/Money.cpp | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Money.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Money.cpp index 13d219f0648..035033d723e 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Money.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Money.cpp @@ -59,13 +59,14 @@ UnsignedInt Money::withdraw(UnsignedInt amountToWithdraw, Bool playSound) if (amountToWithdraw == 0) return amountToWithdraw; - //@todo: Do we do this frequently enough that it is a performance hit? - AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyWithdrawSound; - event.setPlayerIndex(m_playerIndex); - // Play a sound if (playSound) + { + //@todo: Do we do this frequently enough that it is a performance hit? + AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyWithdrawSound; + event.setPlayerIndex(m_playerIndex); TheAudio->addAudioEvent(&event); + } m_money -= amountToWithdraw; @@ -78,13 +79,14 @@ void Money::deposit(UnsignedInt amountToDeposit, Bool playSound) if (amountToDeposit == 0) return; - //@todo: Do we do this frequently enough that it is a performance hit? - AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyDepositSound; - event.setPlayerIndex(m_playerIndex); - // Play a sound if (playSound) + { + //@todo: Do we do this frequently enough that it is a performance hit? + AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyDepositSound; + event.setPlayerIndex(m_playerIndex); TheAudio->addAudioEvent(&event); + } m_money += amountToDeposit; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp index 9c3fabc5813..b95f35a83fe 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp @@ -60,13 +60,14 @@ UnsignedInt Money::withdraw(UnsignedInt amountToWithdraw, Bool playSound) if (amountToWithdraw == 0) return amountToWithdraw; - //@todo: Do we do this frequently enough that it is a performance hit? - AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyWithdrawSound; - event.setPlayerIndex(m_playerIndex); - // Play a sound if (playSound) + { + //@todo: Do we do this frequently enough that it is a performance hit? + AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyWithdrawSound; + event.setPlayerIndex(m_playerIndex); TheAudio->addAudioEvent(&event); + } m_money -= amountToWithdraw; @@ -79,13 +80,14 @@ void Money::deposit(UnsignedInt amountToDeposit, Bool playSound) if (amountToDeposit == 0) return; - //@todo: Do we do this frequently enough that it is a performance hit? - AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyDepositSound; - event.setPlayerIndex(m_playerIndex); - // Play a sound if (playSound) + { + //@todo: Do we do this frequently enough that it is a performance hit? + AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyDepositSound; + event.setPlayerIndex(m_playerIndex); TheAudio->addAudioEvent(&event); + } m_money += amountToDeposit; From da3346591cfd0c6761a9ecffc2cc3ad23bbb9122 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:44:33 +0200 Subject: [PATCH 36/55] [GEN][ZH] Prevent using uninitialized memory 'delta' in Dict::ensureUnique() (#1131) --- Generals/Code/GameEngine/Source/Common/Dict.cpp | 2 +- GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/Dict.cpp b/Generals/Code/GameEngine/Source/Common/Dict.cpp index 3aaa9e272b2..c4bb7a2266a 100644 --- a/Generals/Code/GameEngine/Source/Common/Dict.cpp +++ b/Generals/Code/GameEngine/Source/Common/Dict.cpp @@ -182,7 +182,7 @@ Dict::DictPair *Dict::ensureUnique(int numPairsNeeded, Bool preserveData, DictPa } } - Int delta; + Int delta = 0; if (pairToTranslate && m_data) delta = pairToTranslate - m_data->peek(); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp index 6fed812fc98..65114fdbf14 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp @@ -182,7 +182,7 @@ Dict::DictPair *Dict::ensureUnique(int numPairsNeeded, Bool preserveData, DictPa } } - Int delta; + Int delta = 0; if (pairToTranslate && m_data) delta = pairToTranslate - m_data->peek(); From e42d4e43c115029a5c3b7a32be5603296ffee476 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:45:32 +0200 Subject: [PATCH 37/55] [GEN][ZH] Suppress compiler warning about buffer overrun while writing to 'newIndices' in PopulateLobbyPlayerListbox() (#1132) --- .../GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp | 14 +++++++++----- .../GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index 2a4c2f83ddc..680a9e9abc5 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -568,14 +568,18 @@ void PopulateLobbyPlayerListbox(void) // restore selection if (indicesToSelect.size()) { - std::set::const_iterator indexIt; - Int *newIndices = NEW Int[indicesToSelect.size()]; - for (i=0, indexIt = indicesToSelect.begin(); indexIt != indicesToSelect.end(); ++i, ++indexIt) + std::set::const_iterator indexIt = indicesToSelect.begin(); + const size_t count = indicesToSelect.size(); + size_t index = 0; + Int *newIndices = NEW Int[count]; + while (index < count) { - newIndices[i] = *indexIt; + newIndices[index] = *indexIt; DEBUG_LOG(("Queueing up index %d to re-select\n", *indexIt)); + ++index; + ++indexIt; } - GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, indicesToSelect.size()); + GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, count); delete[] newIndices; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index 760fbb362cf..c727c3d6684 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -585,14 +585,18 @@ void PopulateLobbyPlayerListbox(void) // restore selection if (indicesToSelect.size()) { - std::set::const_iterator indexIt; - Int *newIndices = NEW Int[indicesToSelect.size()]; - for (i=0, indexIt = indicesToSelect.begin(); indexIt != indicesToSelect.end(); ++i, ++indexIt) + std::set::const_iterator indexIt = indicesToSelect.begin(); + const size_t count = indicesToSelect.size(); + size_t index = 0; + Int *newIndices = NEW Int[count]; + while (index < count) { - newIndices[i] = *indexIt; + newIndices[index] = *indexIt; DEBUG_LOG(("Queueing up index %d to re-select\n", *indexIt)); + ++index; + ++indexIt; } - GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, indicesToSelect.size()); + GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, count); delete[] newIndices; } From be7f0d4e9237d44f47bbfdf062aca48aa1fd2b52 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:46:47 +0200 Subject: [PATCH 38/55] [GEN][ZH] Prevent using uninitialized memory 'result' in PartitionData::calcMaxCoiForShape() (#1133) --- .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 4 ++++ .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 9b98775f05f..5c033bc105c 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -2168,8 +2168,12 @@ Int PartitionData::calcMaxCoiForShape(GeometryType geom, Real majorRadius, Real Real diagonal = (Real)(sqrtf(majorRadius*majorRadius + minorRadius*minorRadius)); Int cells = ThePartitionManager->worldToCellDist(diagonal*2) + 1; result = cells * cells; + break; } + default: + return 4; }; + static_assert(GEOMETRY_NUM_TYPES == 3, "GEOMETRY_NUM_TYPES has changed"); } if (result < 4) result = 4; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index c7e6d4b5a0c..b2b5526c91f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -2175,8 +2175,12 @@ Int PartitionData::calcMaxCoiForShape(GeometryType geom, Real majorRadius, Real Real diagonal = (Real)(sqrtf(majorRadius*majorRadius + minorRadius*minorRadius)); Int cells = ThePartitionManager->worldToCellDist(diagonal*2) + 1; result = cells * cells; + break; } + default: + return 4; }; + static_assert(GEOMETRY_NUM_TYPES == 3, "GEOMETRY_NUM_TYPES has changed"); } if (result < 4) result = 4; From d7bbe6b4c1d2132388a8f1bfc924d05c1ade4ad6 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:47:28 +0200 Subject: [PATCH 39/55] [GEN][ZH] Prevent using uninitialized memory in PartitionData::updateCellsTouched() (#1134) --- .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 6 +++++- .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 5c033bc105c..aad49d0157d 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -2039,7 +2039,6 @@ void PartitionData::updateCellsTouched() Object *obj = getObject(); - DEBUG_ASSERTCRASH(obj != NULL || m_ghostObject != NULL, ("must be attached to an Object here 1")); if (obj) { @@ -2060,6 +2059,11 @@ void PartitionData::updateCellsTouched() majorRadius = m_ghostObject->getGeometryMajorRadius(); minorRadius = m_ghostObject->getGeometryMinorRadius(); } + else + { + DEBUG_CRASH(("must be attached to an Object here")); + return; + } removeAllTouchedCells(); if (isSmall) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index b2b5526c91f..375ee9b1779 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -2043,7 +2043,6 @@ void PartitionData::updateCellsTouched() Object *obj = getObject(); - DEBUG_ASSERTCRASH(obj != NULL || m_ghostObject != NULL, ("must be attached to an Object here 1")); if (obj) { @@ -2064,6 +2063,11 @@ void PartitionData::updateCellsTouched() majorRadius = m_ghostObject->getGeometryMajorRadius(); minorRadius = m_ghostObject->getGeometryMinorRadius(); } + else + { + DEBUG_CRASH(("must be attached to an Object here")); + return; + } removeAllTouchedCells(); if (isSmall) From 2a0f3c62a7ba418ed724853e167b0881662e83ba Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:48:16 +0200 Subject: [PATCH 40/55] [GEN][ZH] Fix GEOMETRY_SPHERE & GEOMETRY_CYLINDER treated as GEOMETRY_BOX in PartitionData::calcMaxCoiForShape() (#1135) --- .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 3 +++ .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index aad49d0157d..dbb33dcc1af 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -2166,6 +2166,9 @@ Int PartitionData::calcMaxCoiForShape(GeometryType geom, Real majorRadius, Real // this actually allocates a few too many, but that's ok. Int cells = ThePartitionManager->worldToCellDist(majorRadius*2) + 1; result = cells * cells; +#if !RETAIL_COMPATIBLE_CRC + break; +#endif } case GEOMETRY_BOX: { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 375ee9b1779..6a08638e180 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -2173,6 +2173,9 @@ Int PartitionData::calcMaxCoiForShape(GeometryType geom, Real majorRadius, Real // this actually allocates a few too many, but that's ok. Int cells = ThePartitionManager->worldToCellDist(majorRadius*2) + 1; result = cells * cells; +#if !RETAIL_COMPATIBLE_CRC + break; +#endif } case GEOMETRY_BOX: { From 460c73db4332f666f989dc51ebe35177f4545a33 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:48:59 +0200 Subject: [PATCH 41/55] [GEN][ZH] Remove unnecessary NULL pointer test in SaveLoadMenuSystem() (#1136) --- .../Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp | 3 +-- .../Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp index 1f93cecb7a0..ddad0deef71 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp @@ -772,8 +772,7 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg, // save the game AsciiString filename; - if( selectedGameInfo ) - filename = selectedGameInfo->filename; + filename = selectedGameInfo->filename; TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description, fileType ); /* diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp index 437bc9cdcb7..04703084d59 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp @@ -791,8 +791,7 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg, // save the game AsciiString filename; - if( selectedGameInfo ) - filename = selectedGameInfo->filename; + filename = selectedGameInfo->filename; TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description, fileType ); /* From c9a4e3a52b5b155e0922001bd688005356b87c21 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:49:17 +0200 Subject: [PATCH 42/55] [GEN][ZH] Prevent dereferencing NULL pointer 'player' in setObserverWindows() (#1137) --- .../GUI/GUICallbacks/Menus/ScoreScreen.cpp | 18 ++++++------------ .../GUI/GUICallbacks/Menus/ScoreScreen.cpp | 18 ++++++------------ 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index cea961ebbd4..e3ee2daf761 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -1178,7 +1178,7 @@ static void updateChallengeMedals(Int& medals) //------------------------------------------------------------------------------------------------- void populatePlayerInfo( Player *player, Int pos) { - if(!player || pos > MAX_SLOTS) + if(!player || pos < 0 || pos >= MAX_SLOTS) return; Color color = player->getPlayerColor(); ScoreKeeper *scoreKpr = player->getScoreKeeper(); @@ -2071,7 +2071,7 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", i); //------------------------------------------------------------------------------------------------- void setObserverWindows( Player *player, Int i ) { - if(i < 0 || i >= MAX_SLOTS) + if(!player || i < 0 || i >= MAX_SLOTS) return; AsciiString winName; GameWindow *win; @@ -2082,16 +2082,10 @@ void setObserverWindows( Player *player, Int i ) winName.format("ScoreScreen.wnd:StaticTextPlayer%d", i); win = TheWindowManager->winGetWindowFromId( parent, TheNameKeyGenerator->nameToKey( winName ) ); DEBUG_ASSERTCRASH(win,("Could not find window %s on the score screen", winName.str())); - if (player) - { - GadgetStaticTextSetText(win, player->getPlayerDisplayName()); - win->winHide(FALSE); - win->winSetEnabledTextColors(color, win->winGetEnabledTextBorderColor()); - } - else - { - win->winHide(TRUE); - } + + GadgetStaticTextSetText(win, player->getPlayerDisplayName()); + win->winHide(FALSE); + win->winSetEnabledTextColors(color, win->winGetEnabledTextBorderColor()); // set the player name winName.format("ScoreScreen.wnd:StaticTextObserver%d", i); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index 5e6ad64c59d..69c2c46fde6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -1412,7 +1412,7 @@ static inline int CheckForApocalypse( ScoreKeeper *s, const char* szWeapon ) //------------------------------------------------------------------------------------------------- void populatePlayerInfo( Player *player, Int pos) { - if(!player || pos > MAX_SLOTS) + if(!player || pos < 0 || pos >= MAX_SLOTS) return; Color color = player->getPlayerColor(); ScoreKeeper *scoreKpr = player->getScoreKeeper(); @@ -2339,7 +2339,7 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", i); //------------------------------------------------------------------------------------------------- void setObserverWindows( Player *player, Int i ) { - if(i < 0 || i >= MAX_SLOTS) + if(!player || i < 0 || i >= MAX_SLOTS) return; AsciiString winName; GameWindow *win; @@ -2350,16 +2350,10 @@ void setObserverWindows( Player *player, Int i ) winName.format("ScoreScreen.wnd:StaticTextPlayer%d", i); win = TheWindowManager->winGetWindowFromId( parent, TheNameKeyGenerator->nameToKey( winName ) ); DEBUG_ASSERTCRASH(win,("Could not find window %s on the score screen", winName.str())); - if (player) - { - GadgetStaticTextSetText(win, player->getPlayerDisplayName()); - win->winHide(FALSE); - win->winSetEnabledTextColors(color, win->winGetEnabledTextBorderColor()); - } - else - { - win->winHide(TRUE); - } + + GadgetStaticTextSetText(win, player->getPlayerDisplayName()); + win->winHide(FALSE); + win->winSetEnabledTextColors(color, win->winGetEnabledTextBorderColor()); // set the player name winName.format("ScoreScreen.wnd:StaticTextObserver%d", i); From c2b8a73a1056590cb7361fd1d9a0cbe78a996741 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:49:54 +0200 Subject: [PATCH 43/55] [GEN][ZH] Remove unnecessary NULL pointer test in CommandTranslator::evaluateContextCommand() (#1138) --- .../GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp | 2 +- .../GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 834751d52e1..0710af5eab8 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -2071,7 +2071,7 @@ GameMessage::Type CommandTranslator::evaluateContextCommand( Drawable *draw, } // end else if #endif // ******************************************************************************************** - else if ( pos && !draw && TheInGameUI->canSelectedObjectsDoAction( InGameUI::ACTIONTYPE_SET_RALLY_POINT, NULL, InGameUI::SELECTION_ALL, FALSE )) + else if ( !draw && TheInGameUI->canSelectedObjectsDoAction( InGameUI::ACTIONTYPE_SET_RALLY_POINT, NULL, InGameUI::SELECTION_ALL, FALSE )) { msgType = GameMessage::MSG_SET_RALLY_POINT; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index c19fad2b61f..2ed0568bde8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -2201,7 +2201,7 @@ GameMessage::Type CommandTranslator::evaluateContextCommand( Drawable *draw, } // end else if #endif // ******************************************************************************************** - else if ( pos && !draw && TheInGameUI->canSelectedObjectsDoAction( InGameUI::ACTIONTYPE_SET_RALLY_POINT, NULL, InGameUI::SELECTION_ALL, FALSE )) + else if ( !draw && TheInGameUI->canSelectedObjectsDoAction( InGameUI::ACTIONTYPE_SET_RALLY_POINT, NULL, InGameUI::SELECTION_ALL, FALSE )) { msgType = GameMessage::MSG_SET_RALLY_POINT; From b32b081e2cc2bc059e0ab3ad31b038f0b12b9af6 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:50:48 +0200 Subject: [PATCH 44/55] [GEN][ZH] Suppress compile warning about buffer overrun while writing to 'm_structuresToRepair' in AIPlayer::repairStructure() (#1139) --- Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp | 2 +- GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index d3563f3cfc1..f540d5c0eb7 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -1972,7 +1972,7 @@ void AIPlayer::repairStructure(ObjectID structure) return; } } - if (m_structuresInQueue==MAX_STRUCTURES_TO_REPAIR) { + if (m_structuresInQueue>=MAX_STRUCTURES_TO_REPAIR) { DEBUG_LOG(("Structure repair queue is full, ignoring repair request. JBA\n")); return; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index 700012ac947..388de4fbde4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -2301,7 +2301,7 @@ void AIPlayer::repairStructure(ObjectID structure) return; } } - if (m_structuresInQueue==MAX_STRUCTURES_TO_REPAIR) { + if (m_structuresInQueue>=MAX_STRUCTURES_TO_REPAIR) { DEBUG_LOG(("Structure repair queue is full, ignoring repair request. JBA\n")); return; } From 264de8db656f17ac2cafdee90f657ca6571d7124 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:51:20 +0200 Subject: [PATCH 45/55] [GEN][ZH] Prevent dereferencing NULL pointer 'damageSource' in TransitionDamageFX::onBodyDamageStateChange() (#1140) --- .../Source/GameLogic/Object/Damage/TransitionDamageFX.cpp | 2 +- .../Source/GameLogic/Object/Damage/TransitionDamageFX.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp index 7092f77281e..0a88f6b44d5 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp @@ -363,7 +363,7 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, } // end if // do any object creation list for our new state - if( modData->m_OCL[ newState ][ i ].ocl ) + if( damageSource && modData->m_OCL[ newState ][ i ].ocl ) { if( lastDamageInfo == NULL || diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp index dd4a8fb0a93..9e1a2bb3485 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp @@ -366,7 +366,7 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, } // end if // do any object creation list for our new state - if( modData->m_OCL[ newState ][ i ].ocl ) + if( damageSource && modData->m_OCL[ newState ][ i ].ocl ) { if( lastDamageInfo == NULL || From 3e33df8d1d257f7c828caf4451603f0f48bd7e36 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:55:22 +0200 Subject: [PATCH 46/55] [GEN][ZH] Prevent dereferencing NULL pointer 'drawable' in BoneFXUpdate::resolveBoneLocations() (#1142) --- .../GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp | 3 --- .../GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp | 3 --- 2 files changed, 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp index 693e8cf09bd..b2112f89e86 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp @@ -508,9 +508,6 @@ void BoneFXUpdate::resolveBoneLocations() { Drawable *drawable = building->getDrawable(); if (drawable == NULL) { DEBUG_ASSERTCRASH(drawable != NULL, ("There is no drawable?")); - } - - if (d == NULL) { return; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp index c7d817b5d75..ad8422c4760 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp @@ -511,9 +511,6 @@ void BoneFXUpdate::resolveBoneLocations() { Drawable *drawable = building->getDrawable(); if (drawable == NULL) { DEBUG_ASSERTCRASH(drawable != NULL, ("There is no drawable?")); - } - - if (d == NULL) { return; } From bbdfa50809b3085595f720af39e51f07fe5964c5 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:55:38 +0200 Subject: [PATCH 47/55] [GEN][ZH] Prevent dereferencing NULL pointer 'font' in W3DFontLibrary::releaseFontData() (#1143) --- .../Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp | 7 ++++--- .../Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp index def7a7bcc67..d45fe882500 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp @@ -137,10 +137,11 @@ void W3DFontLibrary::releaseFontData( GameFont *font ) if(((FontCharsClass *)(font->fontData))->AlternateUnicodeFont) ((FontCharsClass *)(font->fontData))->AlternateUnicodeFont->Release_Ref(); ((FontCharsClass *)(font->fontData))->Release_Ref(); + + font->fontData = NULL; } - font->fontData = NULL; - -} // end releaseFont + +} // end releaseFontData // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp index d28f21b2e89..62c95f7e8c4 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp @@ -137,10 +137,11 @@ void W3DFontLibrary::releaseFontData( GameFont *font ) if(((FontCharsClass *)(font->fontData))->AlternateUnicodeFont) ((FontCharsClass *)(font->fontData))->AlternateUnicodeFont->Release_Ref(); ((FontCharsClass *)(font->fontData))->Release_Ref(); + + font->fontData = NULL; } - font->fontData = NULL; - -} // end releaseFont + +} // end releaseFontData // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// From d1a8d65b22112109980d4af9c36525419378a837 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:56:12 +0200 Subject: [PATCH 48/55] [CORE] Prevent using uninitialized memory 'listline' in Cftp::FindFile() (#1144) --- Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP b/Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP index 1a55571639d..e847ce8691a 100644 --- a/Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP +++ b/Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP @@ -659,7 +659,7 @@ HRESULT Cftp::LogoffFromServer( void ) HRESULT Cftp::FindFile( LPCSTR szRemoteFileName, int * piSize ) { char command[ 256 ]; - static char listline[ 256 ]; + static char listline[ 256 ] = {0}; int i, iReply; char ext[ 10 ]; From 99003dce736831cabbafeb38d1ef55dfec1830d8 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:56:30 +0200 Subject: [PATCH 49/55] [CORE] Prevent using uninitialized memory 'name' in RenderObjPersistFactoryClass::Load() (#1145) --- Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp b/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp index 8e42e9f998b..0c9b8faad04 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp @@ -1221,6 +1221,7 @@ PersistClass * RenderObjPersistFactoryClass::Load(ChunkLoadClass & cload) const RenderObjClass * old_obj = NULL; Matrix3D tm(1); char name[64]; + name[0] = '\0'; while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { From b1e6792a0f924254296f9d2fff97d9ce3b2d546e Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:56:52 +0200 Subject: [PATCH 50/55] [GEN][ZH] Remove unnecessary NULL pointer test in RTS3DScene::Visibility_Check() (#1146) --- .../GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp | 3 +-- .../GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index ccbc15d2daf..83a39bce904 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -399,8 +399,7 @@ void RTS3DScene::Visibility_Check(CameraClass * camera) m_translucentObjectsCount=0; m_numNonOccluderOrOccludee=0; - Int currentFrame=0; - if (TheGameLogic) currentFrame = TheGameLogic->getFrame(); + Int currentFrame=TheGameLogic->getFrame(); if (currentFrame <= TheGlobalData->m_defaultOcclusionDelay) currentFrame = TheGlobalData->m_defaultOcclusionDelay+1; //make sure occlusion is enabled when game starts (frame 0). diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index 4434ac8fcea..691d043f45a 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -415,8 +415,7 @@ void RTS3DScene::Visibility_Check(CameraClass * camera) m_translucentObjectsCount=0; m_numNonOccluderOrOccludee=0; - Int currentFrame=0; - if (TheGameLogic) currentFrame = TheGameLogic->getFrame(); + Int currentFrame=TheGameLogic->getFrame(); if (currentFrame <= TheGlobalData->m_defaultOcclusionDelay) currentFrame = TheGlobalData->m_defaultOcclusionDelay+1; //make sure occlusion is enabled when game starts (frame 0). From b1b16b9232483e8d585d881f841b2682b84998eb Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:57:48 +0200 Subject: [PATCH 51/55] [GEN][ZH] Prevent dereferencing NULL pointer 'TheWritableGlobalData' in GameLODManager::applyStaticLODLevel() (#1149) --- Generals/Code/GameEngine/Source/Common/GameLOD.cpp | 7 ++++--- GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/GameLOD.cpp b/Generals/Code/GameEngine/Source/Common/GameLOD.cpp index 08600c85ce3..7667a46fb53 100644 --- a/Generals/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameLOD.cpp @@ -577,9 +577,10 @@ void GameLODManager::applyStaticLODLevel(StaticGameLODLevel level) TheWritableGlobalData->m_enableDynamicLOD = lodInfo->m_enableDynamicLOD; TheWritableGlobalData->m_useFpsLimit = lodInfo->m_useFpsLimit; TheWritableGlobalData->m_useTrees = requestedTrees; - } - if (!m_memPassed || isReallyLowMHz()) { - TheWritableGlobalData->m_shellMapOn = false; + + if (!m_memPassed || isReallyLowMHz()) { + TheWritableGlobalData->m_shellMapOn = false; + } } if (TheTerrainVisual) TheTerrainVisual->setTerrainTracksDetail(); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp index 725c75fc8ef..a994a4b64ac 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp @@ -582,9 +582,10 @@ void GameLODManager::applyStaticLODLevel(StaticGameLODLevel level) TheWritableGlobalData->m_enableDynamicLOD = lodInfo->m_enableDynamicLOD; TheWritableGlobalData->m_useFpsLimit = lodInfo->m_useFpsLimit; TheWritableGlobalData->m_useTrees = requestedTrees; - } - if (!m_memPassed || isReallyLowMHz()) { - TheWritableGlobalData->m_shellMapOn = false; + + if (!m_memPassed || isReallyLowMHz()) { + TheWritableGlobalData->m_shellMapOn = false; + } } if (TheTerrainVisual) TheTerrainVisual->setTerrainTracksDetail(); From 4c85403bb6b51d677be855763bb466389fe88acb Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 09:04:38 +0200 Subject: [PATCH 52/55] [GEN][ZH] Prevent reading invalid data from 'm_numLevelPresets' in GameLODManager::newLODPreset() (#1150) --- Generals/Code/GameEngine/Source/Common/GameLOD.cpp | 14 +++++++++----- .../Code/GameEngine/Source/Common/GameLOD.cpp | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/GameLOD.cpp b/Generals/Code/GameEngine/Source/Common/GameLOD.cpp index 7667a46fb53..a2eba657876 100644 --- a/Generals/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameLOD.cpp @@ -253,13 +253,17 @@ BenchProfile *GameLODManager::newBenchProfile(void) LODPresetInfo *GameLODManager::newLODPreset(StaticGameLODLevel index) { - if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL) - { - m_numLevelPresets[index]++; - return &m_lodPresets[index][m_numLevelPresets[index]-1]; + if (index >= 0 && index < STATIC_GAME_LOD_COUNT) + { + if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL) + { + m_numLevelPresets[index]++; + return &m_lodPresets[index][m_numLevelPresets[index]-1]; + } + + DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index))); } - DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index))); return NULL; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp index a994a4b64ac..c35f08d8455 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp @@ -255,13 +255,17 @@ BenchProfile *GameLODManager::newBenchProfile(void) LODPresetInfo *GameLODManager::newLODPreset(StaticGameLODLevel index) { - if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL) - { - m_numLevelPresets[index]++; - return &m_lodPresets[index][m_numLevelPresets[index]-1]; + if (index >= 0 && index < STATIC_GAME_LOD_COUNT) + { + if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL) + { + m_numLevelPresets[index]++; + return &m_lodPresets[index][m_numLevelPresets[index]-1]; + } + + DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index))); } - DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index))); return NULL; } From b9318ec91185e638b370a77857ee00af3419c09c Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 09:05:22 +0200 Subject: [PATCH 53/55] [GEN][ZH] Prevent reading invalid data from 'm_staticGameLODInfo' in GameLODManager::getRecommendedTextureReduction() (#1151) --- Generals/Code/GameEngine/Source/Common/GameLOD.cpp | 3 +++ GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Generals/Code/GameEngine/Source/Common/GameLOD.cpp b/Generals/Code/GameEngine/Source/Common/GameLOD.cpp index a2eba657876..0a6568d9f43 100644 --- a/Generals/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameLOD.cpp @@ -693,6 +693,9 @@ Int GameLODManager::getRecommendedTextureReduction(void) if (!m_memPassed) //if they have < 256 MB, force them to low res textures. return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction; + if (m_idealDetailLevel < 0 || m_idealDetailLevel >= STATIC_GAME_LOD_COUNT) + return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction; + return m_staticGameLODInfo[m_idealDetailLevel].m_textureReduction; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp index c35f08d8455..235c3a14a68 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp @@ -698,6 +698,9 @@ Int GameLODManager::getRecommendedTextureReduction(void) if (!m_memPassed) //if they have < 256 MB, force them to low res textures. return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction; + if (m_idealDetailLevel < 0 || m_idealDetailLevel >= STATIC_GAME_LOD_COUNT) + return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction; + return m_staticGameLODInfo[m_idealDetailLevel].m_textureReduction; } From 61cbc6f3c68643a293cf9b41da293bba21f9c23d Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Jun 2025 09:07:17 +0200 Subject: [PATCH 54/55] [GEN][ZH] Prevent dereferencing NULL pointer 'theTemplate' in UpgradeMuxData::getUpgradeActivationMasks() (#1141) --- .../Code/GameEngine/Source/Common/Thing/Module.cpp | 9 +++++---- .../Code/GameEngine/Source/Common/Thing/Module.cpp | 13 +++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/Thing/Module.cpp b/Generals/Code/GameEngine/Source/Common/Thing/Module.cpp index 34ef9d96f61..362a43657c5 100644 --- a/Generals/Code/GameEngine/Source/Common/Thing/Module.cpp +++ b/Generals/Code/GameEngine/Source/Common/Thing/Module.cpp @@ -261,9 +261,9 @@ void UpgradeMuxData::getUpgradeActivationMasks(UpgradeMaskType& activation, Upgr it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } @@ -275,11 +275,12 @@ void UpgradeMuxData::getUpgradeActivationMasks(UpgradeMaskType& activation, Upgr it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } + m_conflictingMask.set( theTemplate->getUpgradeMask() ); } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/Module.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/Module.cpp index 9932109bcbd..a5908421bcd 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/Module.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/Module.cpp @@ -257,9 +257,9 @@ void UpgradeMuxData::muxDataProcessUpgradeRemoval(Object* obj) const it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } @@ -299,9 +299,9 @@ void UpgradeMuxData::getUpgradeActivationMasks(UpgradeMaskType& activation, Upgr it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } @@ -313,11 +313,12 @@ void UpgradeMuxData::getUpgradeActivationMasks(UpgradeMaskType& activation, Upgr it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } + m_conflictingMask.set( theTemplate->getUpgradeMask() ); } From 09c1c2b2dc2290467a1b3ab6ed15ca39fd95d160 Mon Sep 17 00:00:00 2001 From: aliendroid1 <9aliquraishi9@gmail.com> Date: Sun, 22 Jun 2025 09:33:21 -0500 Subject: [PATCH 55/55] [CMAKE] Enable multithreaded compilation for VC6 builds Created a custom cmake toolchain file for using the vc6 compiler with the ninja generator Switching from nmake to multithreaded ninja reduces compile times by an order of magnitude Does not seem to work with program database (pdb) file generation enabled --- CMakePresets.json | 123 ++++++++++++++++++++++++++------------- cmake/compilers.cmake | 26 ++++----- cmake/vc6toolchain.cmake | 89 ++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 54 deletions(-) create mode 100644 cmake/vc6toolchain.cmake diff --git a/CMakePresets.json b/CMakePresets.json index feb76247721..987eb72f294 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,26 +1,33 @@ { "version": 6, - "cmakeMinimumRequired": { + "cmakeMinimumRequired": + { "major": 3, "minor": 25, "patch": 0 }, - "configurePresets": [ + "configurePresets": + [ { "name": "vc6", "displayName": "Windows 32bit VC6 Release", - "generator": "NMake Makefiles", + "generator": "Ninja", "hidden": false, "binaryDir": "${sourceDir}/build/${presetName}", - "cacheVariables": { + "toolchainFile": "${sourceDir}/cmake/vc6toolchain.cmake", + "cacheVariables": + { + "CMAKE_VERBOSE_MAKEFILE": "TRUE", "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", - "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:ProgramDatabase>", + "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:Embedded>", "CMAKE_BUILD_TYPE": "Release", "RTS_FLAGS": "/W3" }, - "vendor": { - "jetbrains.com/clion": { + "vendor": + { + "jetbrains.com/clion": + { "toolchain": "Visual Studio 6" } } @@ -30,7 +37,8 @@ "displayName": "Windows 32bit VC6 Profile", "hidden": false, "inherits": "vc6", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_PROFILE": "ON" } }, @@ -38,7 +46,8 @@ "name": "vc6-internal", "displayName": "Windows 32bit VC6 Internal", "inherits": "vc6", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_INTERNAL": "ON" } }, @@ -47,7 +56,8 @@ "displayName": "Windows 32bit VC6 Debug", "hidden": false, "inherits": "vc6", - "cacheVariables": { + "cacheVariables": + { "CMAKE_BUILD_TYPE": "Debug", "RTS_BUILD_OPTION_DEBUG": "ON" } @@ -58,7 +68,8 @@ "generator": "Ninja Multi-Config", "hidden": true, "binaryDir": "${sourceDir}/build/${presetName}", - "cacheVariables": { + "cacheVariables": + { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:Embedded>", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL" @@ -70,7 +81,8 @@ "generator": "Ninja Multi-Config", "hidden": true, "binaryDir": "${sourceDir}/build/${presetName}", - "cacheVariables": { + "cacheVariables": + { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:Embedded>", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", @@ -82,15 +94,19 @@ "inherits": "default", "hidden": false, "displayName": "Windows 32bit Release", - "architecture": { + "architecture": + { "value": "Win32", "strategy": "external" }, - "cacheVariables": { + "cacheVariables": + { "RTS_FLAGS": "/W3" }, - "vendor": { - "jetbrains.com/clion": { + "vendor": + { + "jetbrains.com/clion": + { "toolchain": "Visual Studio" } } @@ -99,7 +115,8 @@ "name": "win32-profile", "inherits": "win32", "displayName": "Windows 32bit Profile", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_PROFILE": "ON" } }, @@ -107,7 +124,8 @@ "name": "win32-internal", "inherits": "win32", "displayName": "Windows 32bit Internal", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_INTERNAL": "ON" } }, @@ -115,7 +133,8 @@ "name": "win32-debug", "inherits": "win32", "displayName": "Windows 32bit Debug", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_DEBUG": "ON" } }, @@ -124,15 +143,19 @@ "inherits": "default-vcpkg", "hidden": false, "displayName": "Windows 32bit VCPKG Release", - "architecture": { + "architecture": + { "value": "Win32", "strategy": "external" }, - "cacheVariables": { + "cacheVariables": + { "RTS_FLAGS": "/W3" }, - "vendor": { - "jetbrains.com/clion": { + "vendor": + { + "jetbrains.com/clion": + { "toolchain": "Visual Studio" } } @@ -141,7 +164,8 @@ "name": "win32-vcpkg-profile", "inherits": "win32-vcpkg", "displayName": "Windows 32bit VCPKG Profile", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_PROFILE": "ON" } }, @@ -149,7 +173,8 @@ "name": "win32-vcpkg-internal", "inherits": "win32-vcpkg", "displayName": "Windows 32bit VCPKG Internal", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_INTERNAL": "ON" } }, @@ -157,7 +182,8 @@ "name": "win32-vcpkg-debug", "inherits": "win32-vcpkg", "displayName": "Windows 32bit VCPKG Debug", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_DEBUG": "ON" } }, @@ -168,7 +194,8 @@ "displayName": "Unix 32bit VCPKG Release" } ], - "buildPresets": [ + "buildPresets": + [ { "name": "vc6", "configurePreset": "vc6", @@ -257,10 +284,12 @@ "configuration": "Release" } ], - "workflowPresets": [ + "workflowPresets": + [ { "name": "vc6", - "steps": [ + "steps": + [ { "type": "configure", "name": "vc6" @@ -273,7 +302,8 @@ }, { "name": "vc6-debug", - "steps": [ + "steps": + [ { "type": "configure", "name": "vc6-debug" @@ -286,7 +316,8 @@ }, { "name": "vc6-internal", - "steps": [ + "steps": + [ { "type": "configure", "name": "vc6-internal" @@ -299,7 +330,8 @@ }, { "name": "vc6-profile", - "steps": [ + "steps": + [ { "type": "configure", "name": "vc6-profile" @@ -312,7 +344,8 @@ }, { "name": "win32", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32" @@ -325,7 +358,8 @@ }, { "name": "win32-internal", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-internal" @@ -338,7 +372,8 @@ }, { "name": "win32-profile", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-profile" @@ -351,7 +386,8 @@ }, { "name": "win32-debug", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-debug" @@ -364,7 +400,8 @@ }, { "name": "win32-vcpkg", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-vcpkg" @@ -377,7 +414,8 @@ }, { "name": "win32-vcpkg-internal", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-vcpkg-internal" @@ -390,7 +428,8 @@ }, { "name": "win32-vcpkg-profile", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-vcpkg-profile" @@ -403,7 +442,8 @@ }, { "name": "win32-vcpkg-debug", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-vcpkg-debug" @@ -416,7 +456,8 @@ }, { "name": "unix", - "steps": [ + "steps": + [ { "type": "configure", "name": "unix" diff --git a/cmake/compilers.cmake b/cmake/compilers.cmake index 7d184f98672..f9df8a2b53c 100644 --- a/cmake/compilers.cmake +++ b/cmake/compilers.cmake @@ -15,21 +15,21 @@ else() set(IS_VS6_BUILD FALSE) endif() -# Make release builds have debug information too. -if(MSVC) - # Create PDB for Release as long as debug info was generated during compile. - string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /DEBUG /OPT:REF /OPT:ICF") - string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /DEBUG /OPT:REF /OPT:ICF") -else() - # We go a bit wild here and assume any other compiler we are going to use supports -g for debug info. - string(APPEND CMAKE_CXX_FLAGS_RELEASE " -g") - string(APPEND CMAKE_C_FLAGS_RELEASE " -g") -endif() +if (NOT IS_VS6_BUILD) + # Make release builds have debug information too. + if(MSVC) + # Create PDB for Release as long as debug info was generated during compile. + string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /DEBUG /OPT:REF /OPT:ICF") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /DEBUG /OPT:REF /OPT:ICF") + else() + # We go a bit wild here and assume any other compiler we are going to use supports -g for debug info. + string(APPEND CMAKE_CXX_FLAGS_RELEASE " -g") + string(APPEND CMAKE_C_FLAGS_RELEASE " -g") + endif() -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) # Ensures only ISO features are used + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) # Ensures only ISO features are used -if (NOT IS_VS6_BUILD) if (MSVC) # Multithreaded build. add_compile_options(/MP) diff --git a/cmake/vc6toolchain.cmake b/cmake/vc6toolchain.cmake new file mode 100644 index 00000000000..71901edd8b0 --- /dev/null +++ b/cmake/vc6toolchain.cmake @@ -0,0 +1,89 @@ +#Defining convenience vars + +set(VS_X86 "C:/PROGRA~2/MICROS~2") +set(VC6_ROOT "${VS_X86}/VC98") +set(VS_X86_COMMON "${VS_X86}/Common") + +set(VC6_INCLUDES "${VC6_ROOT}/INCLUDE;${VC6_ROOT}/MFC/INCLUDE;${VC6_ROOT}/ATL/INCLUDE") +set(VC6_LIBRARIES "${VC6_ROOT}/LIB;${VC6_ROOT}/MFC/LIB") +set(VC6_PATH "${VC6_ROOT}/BIN;${VS_X86_COMMON}/Tools;${VS_X86_COMMON}/Tools/WinNT;${VS_X86_COMMON}/MSDev98/Bin") +set(VC6_DEV "${VS_X86_COMMON}/MSDev98") + +set(STANDARD_LIBRARIES "kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib") + +#Setting toolchain vars to configure ninja for vc6 + +set(ENV{INCLUDE} "${VC6_INCLUDES};$ENV{INCLUDE}") +set(ENV{LIB} "${VC6_LIBRARIES};$ENV{LIB}") +set(ENV{PATH} "${VC6_PATH};$ENV{PATH}") +set(ENV{MSDEV_DIR} "${VC6_DEV}") + +set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES "$ENV{INCLUDE}") +set(CMAKE_CXX_STANDARD_LIBRARIES "${STANDARD_LIBRARIES}") +set(CMAKE_CXX_STANDARD_LINK_DIRECTORIES "$ENV{LIB}") + +set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES "$ENV{INCLUDE}") +set(CMAKE_C_STANDARD_LIBRARIES "${STANDARD_LIBRARIES}") +set(CMAKE_C_STANDARD_LINK_DIRECTORIES "$ENV{LIB}") + +# Setting C++ default compiler flags for VC6 + +string(JOIN " " VC6_CXX_FLAGS + "/DWIN32" # Define WIN32 macro + "/D_WINDOWS" # Define _WINDOWS macro + "/Zm800" # Cap precompiled header memory allocation to prevent running out of heap space + "/Gd" # __cdecl calling convention for all functions (C/C++ standard behaviour) + "/GR" # Enable RTTI (Run-Time Type Information, needed for things like dynamic_cast) + "/Op" # Improve floating point consistency (More ISO-compliant and closer to 64bit behaviour) + "/EHs" # Synchronous C++ exception handling (ISO-standard C++ exception handling) +# "/Za" # Disable Microsoft language extensions (enforce standard) +) +set(CMAKE_CXX_FLAGS "${VC6_CXX_FLAGS}" CACHE STRING "" FORCE) + +string(JOIN " " VC6_CXX_FLAGS_DEBUG + "/Od" # Disable optimizations + "/Ob0" # Disable inline expansion + "/EHc-" # Do NOT assume extern "C" functions never throw (less strict, useful for debugging) + "/Oy-" # Disable frame pointer omission (helps debugging) + "/GZ" # Helps Catch Release-Build Errors in Debug Build + "/Yd" # Store complete debug info in .obj (needed for embedded debug info from precompiled header to be accessible) +) +set(CMAKE_CXX_FLAGS_DEBUG "${VC6_CXX_FLAGS_DEBUG}" CACHE STRING "" FORCE) + +string(JOIN " " VC6_CXX_FLAGS_RELEASE + "/O2" # Optimize for speed + "/Ob2" # Inline any suitable function + "/EHc" # Assume extern "C" functions never throw (safe for release) + "/Oy" # Omit frame pointer (smaller/faster code) + "/nologo" # Suppress copyright message +) +set(CMAKE_CXX_FLAGS_RELEASE "${VC6_CXX_FLAGS_RELEASE}" CACHE STRING "" FORCE) + +# Setting C++ default linker flags for VC6 + +string(JOIN " " VC6_EXE_LINKER_FLAGS + "/machine:IX86" # Target x86 architecture + "/LARGEADDRESSAWARE" # Allow >2GB RAM on x64 OS (useful for large address space on 32-bit binaries) +) +set(CMAKE_EXE_LINKER_FLAGS "${VC6_EXE_LINKER_FLAGS}" CACHE STRING "" FORCE) + +string(JOIN " " VC6_EXE_LINKER_FLAGS_RELEASE + "/RELEASE" # Set the checksum in the header and mark as release + "/DEBUG:NONE" # Do not generate debug info + "/OPT:REF,ICF" # Remove unreferenced code/data, COMDAT folding + "/NOLOGO" # Suppress linker startup banner +) +set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${VC6_EXE_LINKER_FLAGS_RELEASE}" CACHE STRING "" FORCE) + +string(JOIN " " VC6_EXE_LINKER_FLAGS_DEBUG + #"/DEBUG" # Generate debug info + #"/PDB:NONE" # Do not generate a PDB file + "/DEBUGTYPE:CV" # Use CodeView format for debug info (VC6 default) + "/OPT:NOREF,NOICF" # Do not remove unreferenced code/data, COMDAT folding + "/PDBTYPE:SEPT" # Separate debug symbols (one .pdb per .obj, easier for incremental linking) + "/INCREMENTAL:YES" # Enable incremental linking + "/VERBOSE" # Enable verbose output from the linker +) +set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${VC6_EXE_LINKER_FLAGS_DEBUG}" CACHE STRING "" FORCE) + +# @todo: Set default flags for C \ No newline at end of file