Repository navigation
bugfix(worldbuilder): Fix script player import #3408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Roll back document changes when an import fails. Both parsers can add waypoint links before the new post-parse name-count check rejects an import. Restoring
📍 Affects 2 files
|
||
| 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; i<count; i++) { | ||
| if (scripts[i]->getScript() == nullptr && scripts[i]->getScriptGroup()==nullptr) continue; | ||
|
|
@@ -1410,8 +1429,8 @@ void ScriptDialog::OnLoad() | |
| } else { | ||
| Int j; | ||
| for (j=0; j<m_sides.getNumSides(); j++) { | ||
| AsciiString name = m_sides.getSideInfo(i)->getDict()->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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: rg -n '#define DEBUG_ASSERTCRASH|DEBUG_ASSERTCRASH\(' Generals/Code/GameEngine/Include Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp | head -35
sed -n '1400,1480p' Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cppRepository: TheSuperHackers/GeneralsGameCode Length of output: 7578 Clear pending script lists after a failed import. If |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -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))); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Reject invalid serialized player counts in both parsers. Clamping a negative count to zero lets a one-list import pass validation with invalid player data.
📍 Affects 2 files
|
||
| Int i; | ||
| for (i=0; i<numNames; i++) { | ||
| if (i>=MAX_PLAYER_COUNT) break; | ||
| pThis->m_readPlayerNames[i] = file.readAsciiString(); | ||
| } | ||
| pThis->m_numReadPlayerNames = numNames; | ||
| DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over.")); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was this removed? Many other functions also do it like that. |
||
| return true; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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); | ||||||||||||
|
Comment on lines
+1555
to
+1556
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If this new check rejects a file containing a |
||||||||||||
| } | ||||||||||||
| } catch(...) { | ||||||||||||
| if (count == 0) { | ||||||||||||
| count = ScriptList::getReadScripts(scripts); | ||||||||||||
| } | ||||||||||||
| for (Int i = 0; i < count; i++) { | ||||||||||||
| deleteInstance(scripts[i]); | ||||||||||||
| } | ||||||||||||
| m_sides = sidesBeforeImport; | ||||||||||||
| throw; | ||||||||||||
|
greptile-apps[bot] marked this conversation as resolved.
|
||||||||||||
| } | ||||||||||||
| 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; i<count; i++) { | ||||||||||||
| if (scripts[i]->getScript() == nullptr && scripts[i]->getScriptGroup()==nullptr) continue; | ||||||||||||
|
|
@@ -1577,9 +1600,8 @@ void ScriptDialog::OnLoad() | |||||||||||
| } else { | ||||||||||||
| Int j; | ||||||||||||
| for (j=0; j<m_sides.getNumSides(); j++) { | ||||||||||||
| // Using i as an index assumes that i < m_sides.getNumSides. Is that safe??? | ||||||||||||
| AsciiString name = m_sides.getSideInfo(i)->getDict()->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); | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: rg -n '#define DEBUG_ASSERTCRASH|DEBUG_ASSERTCRASH\(' GeneralsMD/Code/GameEngine/Include GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp | head -35
sed -n '1580,1660p' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cppRepository: TheSuperHackers/GeneralsGameCode Length of output: 7582 🏁 Script executed: printf '%s\n' '--- WorldBuilder OnLoad ---'
sed -n '1485,1665p' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp | nl -ba -v1485
printf '%s\n' '--- Script producer/getter ---'
sed -n '520,645p' GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp | nl -ba -v520
sed -n '645,695p' GeneralsMD/Code/GameEngine/Source/Common/System/DataChunk.cpp | nl -ba -v645
printf '%s\n' '--- DataChunk parse ---'
rg -n 'DataChunkInput::parse|DEBUG_ASSERTCRASH' GeneralsMD/Code/GameEngine/Source/Common/System/DataChunk.cpp GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp GeneralsMD/Code/GameEngine/Include GeneralsMD/Code/GameEngine/Source/Common | head -100
printf '%s\n' '--- Macro definitions ---'
rg -n '#\\s*define\\s+DEBUG_ASSERTCRASH|DEBUG_ASSERTCRASH' GeneralsMD/Code/GameEngine/Include/Common GeneralsMD/Code/GameEngine/Include | head -80Repository: TheSuperHackers/GeneralsGameCode Length of output: 32896 🏁 Script executed: printf '%s\n' '--- PR diff for OnLoad ---'
git diff --unified=8 f8ba7eb44b688db14e2ff24d7172474f70be1587 d8e91982809a3fe8e244cff45739e4608dbd0b3d -- GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp | sed -n '1,240p'
printf '%s\n' '--- OnLoad bindings and player-name writes ---'
rg -n -C 3 'OnLoad\\(|m_readPlayerNames|ParsePlayersDataChunk|ON_COMMAND.*(LOAD|IMPORT)|ID_.*(LOAD|IMPORT)' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.h
printf '%s\n' '--- DEBUG_ASSERTCRASH macro definition repository-wide ---'
rg -n '#\\s*define\\s+DEBUG_ASSERTCRASH' GeneralsMD/Code | head -30Repository: TheSuperHackers/GeneralsGameCode Length of output: 6779 🏁 Script executed: printf '%s\n' '--- ScriptDialog message map and OnLoad references ---'
rg -n -C 4 'OnLoad|BEGIN_MESSAGE_MAP|ON_COMMAND|ON_BN_CLICKED' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.h | head -150
printf '%s\n' '--- ScriptPlayers parser and m_readPlayerNames ---'
rg -n -C 3 'm_readPlayerNames|ParsePlayersDataChunk' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.hRepository: TheSuperHackers/GeneralsGameCode Length of output: 9620 Clear pending script lists when parsing fails. If Suggested fix } catch(...) {
m_sides = sidesBeforeImport;
+ ScriptList *pendingScripts[MAX_PLAYER_COUNT];
+ Int pendingCount = ScriptList::getReadScripts(pendingScripts);
+ for (Int pendingIndex = 0; pendingIndex < pendingCount; pendingIndex++) {
+ deleteInstance(pendingScripts[pendingIndex]);
+ }
throw;
} |
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
|
@@ -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))); | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium Over-capacity files are accepted as valid, with player records beyond
Suggested change
🤖 Copy this AI Prompt to have your agent fix this:There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a script file declares more than |
||||||||||||
| Int i; | ||||||||||||
| for (i=0; i<numNames; i++) { | ||||||||||||
| if (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; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This try catch looks unnecessary. Unless DataChunkInput::parse throws?