Skip to content

bugfix(worldbuilder): Fix script player import - #3408

Open
OmarAglan wants to merge 1 commit into
TheSuperHackers:mainfrom
OmarAglan:bugfix/worldbuilder-script-player-matching
Open

OmarAglan wants to merge 1 commit into
TheSuperHackers:mainfrom
OmarAglan:bugfix/worldbuilder-script-player-matching

Conversation

@OmarAglan

@OmarAglan OmarAglan commented Oct 3, 2026 •

Copy link
Copy Markdown

Relates to #555.

Fixes player-name matching when importing scripts in both WorldBuilder versions.

The lookup previously compared current side i with imported player j, then selected side j. Different player ordering could assign scripts to the wrong player. More imported players than existing sides could also cause an invalid side access.

Compare current side j with imported player i instead. Single-list imports retain the existing selected-player behavior.

Also addresses the player-import findings raised on #3409:

  • Reject invalid player counts, mismatched dictionary names and side-capacity overflow.
  • Create new player tree entries using their map indices after parsing succeeds.
  • Restore the dialog's sides and teams if parsing fails.

The side-dictionary fixes apply to Zero Hour here; #3409 brings the corrected implementation to Generals.

Validation:

  • Both VC6 Release WorldBuilder targets build.
  • Extracted production code passes 2,433 routing checks per game.
  • Tests cover reordered players, missing players, unequal counts, neutral players and single-list selection.
  • Baseline tests reproduce the incorrect assignment and invalid side access.
  • 317 additional extracted-code cases cover validation, capacity, tree indices and parse-failure recovery across the two games.
  • The original Zero Hour importer reproduces access violations for mismatched names and exhausted capacity.
  • Full editor testing remains pending; game assets are unavailable locally.

AI assistance was used for implementation and local verification.

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Both WorldBuilder versions validate imported player data and restore side state when parsing fails. They add tree entries for new sides and match imported scripts to map sides by player name.

Changes

WorldBuilder script import

Layer / File(s) Summary
Validate imported player data
Generals/Code/Tools/WorldBuilder/include/ScriptDialog.h, Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp, GeneralsMD/Code/Tools/WorldBuilder/include/ScriptDialog.h, GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
The parsers clamp the imported player-name count to 0..MAX_PLAYER_COUNT and record how many names they read. They validate side dictionary names and reject additions that exceed the side limit.
Handle and reconcile imports
Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp, GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
The load paths check parsed script lists and player names, restore the prior sides after parse failure, add tree entries for new sides, and match imported scripts to sides by player name. Import exceptions display an error message instead of triggering the previous debug-crash path.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: xezon

Merge Risk: 🟡 Moderate · up to 03065

Script imports now match players by name and reject several kinds of malformed data, which fixes the original wrong-player assignment. However, a rejected import may still leave partial changes in the map document. Address this before merging. A malformed negative player count is also still accepted for single-list imports.

Architecture Summary

Architecture risk: 🔵 Low · up to d8e91

The change affects 2 systems.

Changed systems: Generals, GeneralsMD

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — Generals (service) was modified; 1 changed file maps to changed impact.
  • observed — GeneralsMD (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp: OnLoad snapshots m_sides before parsing and restores it if parsing fails or throws, then rethrows the exception for the outer handler.
  • observed — Modified behavior in Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp: When imported data contains multiple players, OnLoad now compares each imported name at index i with the current map side name at index j. Previously, the lookup used mismatched indices (i for the side and j for the imported name), which could associate or discard scripts incorrectly.
  • observed — Modified behavior in Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp: The import exception handler now displays an “Unable to import scripts” message identifying invalid data or an exceeded player limit instead of calling DEBUG_CRASH.
  • observed — Modified behavior in Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp: ParsePlayersDataChunk now rejects counts below zero or above MAX_PLAYER_COUNT by returning false. It reads all names for accepted counts and returns file.atEndOfChunk(); previously it stopped reading at the array limit, asserted on trailing data, and returned true.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the script player import fix, which is the main change.
Description check ✅ Passed The description explains the player-matching bug and the import validation and recovery changes across both WorldBuilder versions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

OmarAglan added a commit to OmarAglan/GeneralsGameCode that referenced this pull request Oct 4, 2026
@OmarAglan
OmarAglan force-pushed the bugfix/worldbuilder-script-player-matching branch from 6d06903 to d8e9198 Compare October 4, 2026 08:21
@OmarAglan OmarAglan changed the title bugfix(worldbuilder): Match imported scripts to the correct player bugfix(worldbuilder): Fix script player import Oct 4, 2026
@OmarAglan
OmarAglan marked this pull request as ready for review October 4, 2026 08:23
@greptile-apps

greptile-apps Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[Medium risk] Fixes script import validation and error recovery in the world builder tool.

The PR is not yet safe to merge because invalid player counts can be accepted and a rejected import can still alter waypoint links.

Findings

  1. P1 Invalid Player Counts Accepted ▶
  2. P1 Failed Imports Leave Waypoint Links ▶
Summary

This PR corrects player-name routing for script imports in both WorldBuilder versions and adds import validation, failure cleanup, and Zero Hour side-tree updates.

  • Out-of-range player counts are now clamped instead of rejected.
  • A newly rejected import can leave waypoint links in the map.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Parse script file] --> B[Add waypoint links to document]
  B --> C{Validate script-list and name counts}
  C -->|Pass| D[Apply imported scripts]
  C -->|Fail| E[Restore sides and delete pending script lists]
  E --> F[Waypoint links remain]
Loading

Reviews (2) · Last reviewed commit: "bugfix(worldbuilder): Fix script player ..."

Comment thread GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8bacc1f7-0461-4011-b086-f2fad5acd7c1
📥 Commits

Reviewing files that changed from the base of the PR and between 6d06903 and d8e9198.

📒 Files selected for processing (2)
  • Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
  • GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.


} 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.cpp

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 7578


Clear pending script lists after a failed import.

If PlayerScriptsList is parsed before ScriptsPlayers fails, the engine retains the pending lists because this OnLoad handles the error before calling ScriptList::getReadScripts(scripts). A later valid import without PlayerScriptsList can retrieve those stale lists and add a single list’s scripts to the selected side. Clear the pending lists at this failed-import boundary; the multi-list name check does not cover this path.

}
DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over."));
return true;
return file.atEndOfChunk();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1360,1490p' Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
sed -n '1530,1670p' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
sed -n '1635,1660p' Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
sed -n '1825,1885p' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 12572


🏁 Script executed:

#!/bin/bash
set -o pipefail
rg -n "m_readPlayerNames|ParsePlayersDataChunk|WritePlayersDataChunk|ParseScriptsDataChunk|PlayerScriptsList|ScriptsPlayers" Generals/Code/Tools/WorldBuilder GeneralsMD/Code/Tools/WorldBuilder
printf '\\n--- name/list writers elsewhere in the two code trees ---\\n'
rg -n "WritePlayersDataChunk|ScriptsPlayers|PlayerScriptsList|WriteScriptsDataChunk" Generals/Code GeneralsMD/Code
printf '\\n--- targeted diff ---\\n'
git diff --unified=4 f8ba7eb44b688db14e2ff24d7172474f70be1587 d8e91982809a3fe8e244cff45739e4608dbd0b3d -- Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp | rg -n -C 8 "ParsePlayersDataChunk|m_readPlayerNames|OnLoad|return file.atEndOfChunk|MAX_PLAYER_COUNT"

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 14515


🏁 Script executed:

#!/bin/bash
set -o pipefail
printf '%s\\n' '--- Generals export/import ---'
sed -n '1170,1245p' Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
sed -n '1390,1455p' Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
printf '%s\\n' '--- GeneralsMD export/import ---'
sed -n '1335,1415p' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
sed -n '1565,1625p' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
printf '%s\\n' '--- script list serialization implementations ---'
sed -n '565,660p' Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp
sed -n '570,675p' GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp
printf '%s\\n' '--- dialog constructors and name-array uses ---'
rg -n -C 5 "ScriptDialog::ScriptDialog|m_readPlayerNames" Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp Generals/Code/Tools/WorldBuilder/include/ScriptDialog.h GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp GeneralsMD/Code/Tools/WorldBuilder/include/ScriptDialog.h

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 26614


🏁 Script executed:

#!/bin/bash
set -o pipefail
printf '%s\\n' '--- import action wiring and function entry ---'
rg -n -C 4 "OnLoad|ON_.*(LOAD|IMPORT)|ID_.*(LOAD|IMPORT)" Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp Generals/Code/Tools/WorldBuilder/include/ScriptDialog.h GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp GeneralsMD/Code/Tools/WorldBuilder/include/ScriptDialog.h
sed -n '1300,1385p' Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
sed -n '1490,1555p' GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
printf '%s\\n' '--- script-list parser entrypoint ---'
rg -n -C 9 "Bool ScriptList::ParseScriptsDataChunk|ParseScriptsDataChunk\\(" Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 13983


🏁 Script executed:

#!/bin/bash
set -o pipefail
for f in Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp; do
  echo "--- $f ---"
  rg -n "ScriptDialog::OnLoad|ON_BN_CLICKED\\(IDC_LOAD|openDataChunk\\(\"ScriptsPlayers\"|m_readPlayerNames\\[i\\]|return file.atEndOfChunk|m_readPlayerNames\\[MAX_PLAYER_COUNT\\]" "$f" "${f/\\/src\\//\\/include\\/ScriptDialog.h}"
  if [[ "$f" == Generals/Code/* ]]; then
    nl -ba "$f" | sed -n '1208,1228p;1408,1438p;1641,1654p'
  else
    nl -ba "$f" | sed -n '1368,1395p;1580,1612p;1825,1868p'
  fi
done
for f in Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp; do
  echo "--- $f ---"
  rg -n "ScriptList::ParseScriptsDataChunk|ScriptList::ParseScriptListDataChunk|ScriptList::WriteScriptsDataChunk|ScriptList::getReadScripts" "$f"
  nl -ba "$f" | sed -n '495,635p'
done

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 22825


Reject multi-list imports with too few player names.

Both parsers accept a complete player-name chunk even when it contains fewer names than the imported script lists. When the same dialog loads another file, an unread name slot can retain a previous import’s name. The second list can then be matched to the wrong map side.

Reset and retain the parsed-name count for each load. Before committing the import or matching lists, reject when count > 1 && parsedNameCount < count. Clean up staged lists and restore parser-added sides on rejection. Keep the count == 1 path unchanged; GeneralsMD supports one selected list with multiple exported side names.


} 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.cpp

Repository: 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 -80

Repository: 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 -30

Repository: 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.h

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 9620


Clear pending script lists when parsing fails.

If PlayerScriptsList is parsed before a later chunk fails, ScriptList::ParseScriptsDataChunk has already stored its lists. The parse-error catch restores m_sides but leaves those lists pending. After the message box, OnLoad returns and can be called again. A successful import without PlayerScriptsList can then pass the stale list to getReadScripts and add its scripts to the selected side. Drain and delete pending lists in this failure catch.

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;
 		}

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change adds a lot of new comments. Are all of these warranted by user facing problems?

curSide = m_curSelection.m_playerIndex;
} else {
Int j;
// TheSuperHackers @bugfix OmarAglan Match each imported player to the current map side by name.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superfluous comment because no one will look back on this

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

if (i>=MAX_PLAYER_COUNT) break;
pThis->m_readPlayerNames[i] = file.readAsciiString();
}
DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over."));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed? Many other functions also do it like that.

// TheSuperHackers @bugfix OmarAglan Restore sides and teams when script parsing fails.
SidesList sidesBeforeImport;
sidesBeforeImport = m_sides;
try {

Copy link
Copy Markdown

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?

ScriptDialog *pThis = (ScriptDialog *)userData;
Int numNames = file.readInt();
// TheSuperHackers @bugfix OmarAglan Reject player counts that cannot fit in the import array.
if (numNames < 0 || numNames > MAX_PLAYER_COUNT) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously MAX_PLAYER_COUNT was a clamp, now it is a fail condition. Maybe just clamp numNames between 0 and MAX_PLAYER_COUNT?

@OmarAglan
OmarAglan force-pushed the bugfix/worldbuilder-script-player-matching branch from d8e9198 to 03065d0 Compare October 6, 2026 08:47
readDicts = file.readInt();
}
Int numNames = file.readInt();
numNames = max(0, min(numNames, Int(MAX_PLAYER_COUNT)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/ScriptDialog.cpp:1842

Over-capacity files are accepted as valid, with player records beyond MAX_PLAYER_COUNT silently discarded. Clamping numNames before the loop prevents the parser from detecting the declared excess, and the remaining-record check is debug-only; reject values above MAX_PLAYER_COUNT before clamping (while retaining the existing handling for negative counts).

Suggested change
numNames = max(0, min(numNames, Int(MAX_PLAYER_COUNT)));
if (numNames > MAX_PLAYER_COUNT) {
return false;
}
numNames = max(0, numNames);
🤖 Copy this AI Prompt to have your agent fix this:
In file @GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp around line 1842:

Over-capacity files are accepted as valid, with player records beyond `MAX_PLAYER_COUNT` silently discarded. Clamping `numNames` before the loop prevents the parser from detecting the declared excess, and the remaining-record check is debug-only; reject values above `MAX_PLAYER_COUNT` before clamping (while retaining the existing handling for negative counts).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a98255a8-b1a5-451f-92e3-d29b114c9b67
📥 Commits

Reviewing files that changed from the base of the PR and between d8e9198 and 03065d0.

📒 Files selected for processing (4)
  • Generals/Code/Tools/WorldBuilder/include/ScriptDialog.h
  • Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
  • GeneralsMD/Code/Tools/WorldBuilder/include/ScriptDialog.h
  • GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

for (Int i = 0; i < count; i++) {
deleteInstance(scripts[i]);
}
m_sides = sidesBeforeImport;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 m_sides leaves those links in the document without the imported objects.

  • Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp#L1398-L1398: stage or remove added waypoint links on failure, and dispose of staged objects and triggers.
  • GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp#L1565-L1565: apply the same rollback and staged-resource cleanup.
📍 Affects 2 files
  • Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp#L1398-L1398 (this comment)
  • GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp#L1565-L1565

{
ScriptDialog *pThis = (ScriptDialog *)userData;
Int numNames = file.readInt();
numNames = max(0, min(numNames, Int(MAX_PLAYER_COUNT)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

  • Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp#L1655-L1655: fail parsing when the declared count is outside 0..MAX_PLAYER_COUNT.
  • GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp#L1842-L1842: apply the same declared-count validation before reading names.
📍 Affects 2 files
  • Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp#L1655-L1655 (this comment)
  • GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp#L1842-L1842

readDicts = file.readInt();
}
Int numNames = file.readInt();
numNames = max(0, min(numNames, Int(MAX_PLAYER_COUNT)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Invalid Player Counts Accepted

When a script file declares more than MAX_PLAYER_COUNT player names, this clamp reads only the first 16, but the parser still reports success. The unread data can trigger an assertion in debug builds; in release builds, the malformed file can be imported silently. Negative counts are also accepted as zero. The Generals importer has the same issue. Reject counts outside the supported range instead.

Comment on lines +1555 to +1556
if (count > 1 && m_numReadPlayerNames < count) {
throw(ERROR_CORRUPT_FILE_FORMAT);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Failed Imports Leave Waypoint Links

If this new check rejects a file containing a WaypointsList, parsing has already added its links to the map. The catch restores sides and deletes pending script lists, but it does not remove those links. The import reports failure while still changing the map; the Generals importer has the same failure path. Add links only after validation succeeds, or remove them when import fails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants