From 03065d045a8692bb0882a7e6ed16b67380dea7db Mon Sep 17 00:00:00 2001 From: Omar Aglan Date: Sat, 3 Oct 2026 18:42:34 +0300 Subject: [PATCH] bugfix(worldbuilder): Fix script player import (#3408) --- .../Tools/WorldBuilder/include/ScriptDialog.h | 1 + .../Tools/WorldBuilder/src/ScriptDialog.cpp | 36 +++++++++--- .../Tools/WorldBuilder/include/ScriptDialog.h | 1 + .../Tools/WorldBuilder/src/ScriptDialog.cpp | 56 +++++++++++++++---- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/Generals/Code/Tools/WorldBuilder/include/ScriptDialog.h b/Generals/Code/Tools/WorldBuilder/include/ScriptDialog.h index 34737b5c09a..ff210a41d8a 100644 --- a/Generals/Code/Tools/WorldBuilder/include/ScriptDialog.h +++ b/Generals/Code/Tools/WorldBuilder/include/ScriptDialog.h @@ -102,6 +102,7 @@ class ScriptDialog : public CDialog Int m_maxWaypoint; AsciiString m_readPlayerNames[MAX_PLAYER_COUNT]; + Int m_numReadPlayerNames; protected: HTREEITEM addPlayer(Int playerIndx); diff --git a/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp b/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp index 2d88de1aa93..c74fe8f95de 100644 --- a/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp @@ -153,6 +153,7 @@ ScriptDialog::ScriptDialog(CWnd* pParent /*=nullptr*/) : CDialog(ScriptDialog::IDD, pParent) { m_draggingTreeView = false; + m_numReadPlayerNames = 0; //{{AFX_DATA_INIT(ScriptDialog) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT @@ -1368,14 +1369,34 @@ void ScriptDialog::OnLoad() m_firstTrigger = nullptr; m_waypointBase = pDoc->getNextWaypointID(); m_maxWaypoint = m_waypointBase; + m_numReadPlayerNames = 0; file.registerParser( "PlayerScriptsList", AsciiString::TheEmptyString, ScriptList::ParseScriptsDataChunk ); file.registerParser( "ObjectsList", AsciiString::TheEmptyString, ParseObjectsDataChunk ); file.registerParser( "PolygonTriggers", AsciiString::TheEmptyString, ParsePolygonTriggersDataChunk ); file.registerParser( "WaypointsList", AsciiString::TheEmptyString, ParseWaypointDataChunk ); file.registerParser( "ScriptTeams", AsciiString::TheEmptyString, ParseTeamsDataChunk ); file.registerParser( "ScriptsPlayers", AsciiString::TheEmptyString, ParsePlayersDataChunk ); - if (!file.parse(this)) { - throw(ERROR_CORRUPT_FILE_FORMAT); + SidesList sidesBeforeImport; + sidesBeforeImport = m_sides; + ScriptList *scripts[MAX_PLAYER_COUNT]; + Int count = 0; + try { + if (!file.parse(this)) { + throw(ERROR_CORRUPT_FILE_FORMAT); + } + count = ScriptList::getReadScripts(scripts); + if (count > 1 && m_numReadPlayerNames < count) { + throw(ERROR_CORRUPT_FILE_FORMAT); + } + } catch(...) { + if (count == 0) { + count = ScriptList::getReadScripts(scripts); + } + for (Int i = 0; i < count; i++) { + deleteInstance(scripts[i]); + } + m_sides = sidesBeforeImport; + throw; } pDoc->setNextWaypointID(m_maxWaypoint); @@ -1399,8 +1420,6 @@ void ScriptDialog::OnLoad() PolygonTrigger::addPolygonTrigger(pTrig); } - ScriptList *scripts[MAX_PLAYER_COUNT]; - Int count = ScriptList::getReadScripts(scripts); Int i; for (i=0; igetScript() == nullptr && scripts[i]->getScriptGroup()==nullptr) continue; @@ -1410,8 +1429,8 @@ void ScriptDialog::OnLoad() } else { Int j; for (j=0; jgetDict()->getAsciiString(TheKey_playerName); - if (name == m_readPlayerNames[j]) { + AsciiString name = m_sides.getSideInfo(j)->getDict()->getAsciiString(TheKey_playerName); + if (name == m_readPlayerNames[i]) { curSide = j; break; } @@ -1463,7 +1482,7 @@ void ScriptDialog::OnLoad() } } catch(...) { - DEBUG_CRASH(("threw exception in ScriptDialog::OnLoad")); + ::AfxMessageBox("Unable to import scripts. The file contains invalid data or exceeds the player limit.", MB_OK); } } @@ -1633,11 +1652,12 @@ Bool ScriptDialog::ParsePlayersDataChunk(DataChunkInput &file, DataChunkInfo *in { ScriptDialog *pThis = (ScriptDialog *)userData; Int numNames = file.readInt(); + numNames = max(0, min(numNames, Int(MAX_PLAYER_COUNT))); Int i; for (i=0; i=MAX_PLAYER_COUNT) break; pThis->m_readPlayerNames[i] = file.readAsciiString(); } + pThis->m_numReadPlayerNames = numNames; DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over.")); return true; } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/ScriptDialog.h b/GeneralsMD/Code/Tools/WorldBuilder/include/ScriptDialog.h index d7f51f2d782..92a0626bb29 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/ScriptDialog.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/ScriptDialog.h @@ -108,6 +108,7 @@ class ScriptDialog : public CDialog Int m_maxWaypoint; AsciiString m_readPlayerNames[MAX_PLAYER_COUNT]; + Int m_numReadPlayerNames; protected: HTREEITEM addPlayer(Int playerIndx); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp index 13715f4a395..9317838bed6 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp @@ -154,6 +154,7 @@ ScriptDialog::ScriptDialog(CWnd* pParent /*=nullptr*/) : CDialog(ScriptDialog::IDD, pParent) { m_draggingTreeView = false; + m_numReadPlayerNames = 0; m_autoUpdateWarnings = true; //{{AFX_DATA_INIT(ScriptDialog) // NOTE: the ClassWizard will add member initialization here @@ -1535,14 +1536,34 @@ void ScriptDialog::OnLoad() m_firstTrigger = nullptr; m_waypointBase = pDoc->getNextWaypointID(); m_maxWaypoint = m_waypointBase; + m_numReadPlayerNames = 0; file.registerParser( "PlayerScriptsList", AsciiString::TheEmptyString, ScriptList::ParseScriptsDataChunk ); file.registerParser( "ObjectsList", AsciiString::TheEmptyString, ParseObjectsDataChunk ); file.registerParser( "PolygonTriggers", AsciiString::TheEmptyString, ParsePolygonTriggersDataChunk ); file.registerParser( "WaypointsList", AsciiString::TheEmptyString, ParseWaypointDataChunk ); file.registerParser( "ScriptTeams", AsciiString::TheEmptyString, ParseTeamsDataChunk ); file.registerParser( "ScriptsPlayers", AsciiString::TheEmptyString, ParsePlayersDataChunk ); - if (!file.parse(this)) { - throw(ERROR_CORRUPT_FILE_FORMAT); + SidesList sidesBeforeImport; + sidesBeforeImport = m_sides; + ScriptList *scripts[MAX_PLAYER_COUNT]; + Int count = 0; + try { + if (!file.parse(this)) { + throw(ERROR_CORRUPT_FILE_FORMAT); + } + count = ScriptList::getReadScripts(scripts); + if (count > 1 && m_numReadPlayerNames < count) { + throw(ERROR_CORRUPT_FILE_FORMAT); + } + } catch(...) { + if (count == 0) { + count = ScriptList::getReadScripts(scripts); + } + for (Int i = 0; i < count; i++) { + deleteInstance(scripts[i]); + } + m_sides = sidesBeforeImport; + throw; } pDoc->setNextWaypointID(m_maxWaypoint); @@ -1552,6 +1573,10 @@ void ScriptDialog::OnLoad() REF_PTR_RELEASE(pUndo); // belongs to pDoc now. m_sides = *TheSidesList; + for (Int sideIndex = sidesBeforeImport.getNumSides(); sideIndex < m_sides.getNumSides(); sideIndex++) { + addPlayer(sideIndex); + } + if (m_firstReadObject) { AddObjectUndoable *pUndo = new AddObjectUndoable(pDoc, m_firstReadObject); pDoc->AddAndDoUndoable(pUndo); @@ -1566,8 +1591,6 @@ void ScriptDialog::OnLoad() PolygonTrigger::addPolygonTrigger(pTrig); } - ScriptList *scripts[MAX_PLAYER_COUNT]; - Int count = ScriptList::getReadScripts(scripts); Int i; for (i=0; igetScript() == nullptr && scripts[i]->getScriptGroup()==nullptr) continue; @@ -1577,9 +1600,8 @@ void ScriptDialog::OnLoad() } else { Int j; for (j=0; jgetDict()->getAsciiString(TheKey_playerName); - if (name == m_readPlayerNames[j]) { + AsciiString name = m_sides.getSideInfo(j)->getDict()->getAsciiString(TheKey_playerName); + if (name == m_readPlayerNames[i]) { curSide = j; break; } @@ -1639,7 +1661,7 @@ void ScriptDialog::OnLoad() } catch(...) { - DEBUG_CRASH(("threw exception in ScriptDialog::OnLoad")); + ::AfxMessageBox("Unable to import scripts. The file contains invalid data or exceeds the player limit.", MB_OK); } } @@ -1817,12 +1839,17 @@ Bool ScriptDialog::ParsePlayersDataChunk(DataChunkInput &file, DataChunkInfo *in readDicts = file.readInt(); } Int numNames = file.readInt(); + numNames = max(0, min(numNames, Int(MAX_PLAYER_COUNT))); Int i; for (i=0; i=MAX_PLAYER_COUNT) break; pThis->m_readPlayerNames[i] = file.readAsciiString(); if (readDicts) { Dict sideDict = file.readDict(); + Bool hasPlayerName; + AsciiString playerName = sideDict.getAsciiString(TheKey_playerName, &hasPlayerName); + if (!hasPlayerName || playerName != pThis->m_readPlayerNames[i]) { + return false; + } bool nameFound = false; for (Int j=0; j < pThis->m_sides.getNumSides(); j++) { AsciiString name = pThis->m_sides.getSideInfo(j)->getDict()->getAsciiString(TheKey_playerName); @@ -1834,17 +1861,22 @@ Bool ScriptDialog::ParsePlayersDataChunk(DataChunkInput &file, DataChunkInfo *in } } if (nameFound == false) { + if (pThis->m_sides.getNumSides() >= MAX_PLAYER_COUNT) { + return false; + } // This side doesn't currently exist, so add it. pThis->m_sides.addSide(&sideDict); + SidesInfo* sides = pThis->m_sides.findSideInfo(playerName); + if (sides == nullptr) { + return false; + } ScriptList* pList = newInstance(ScriptList); - SidesInfo* sides = pThis->m_sides.findSideInfo(pThis->m_readPlayerNames[i]); // A script list must be created. sides->setScriptList(pList); - // Update the dialog. - pThis->addPlayer(i); } } } + pThis->m_numReadPlayerNames = numNames; DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over.")); return true; }