Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,13 @@ void AICommandParmsStorage::reconstitute(AICommandParms& parms) const
void AICommandParmsStorage::doXfer(Xfer *xfer)
{
xfer->xferUser(&m_cmd, sizeof(m_cmd));
xfer->xferUser(&m_cmd, sizeof(m_cmdSource));
// TheSuperHackers @bugfix Transfer the command source instead of the command twice.
xfer->xferUser(&m_cmdSource, sizeof(m_cmdSource));
Comment on lines +129 to +130

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Invalid cmdsource from old saves 🐞 Bug ☼ Reliability

When loading a save created before this fix, the slot now read into m_cmdSource contains the old
duplicated m_cmd bytes, producing out-of-range CommandSourceType values. That invalid value is
later used unchecked (e.g., (1 << cmdSource) in weapon selection), which can cause undefined
behavior/crashes and incorrect command-source filtering.
Agent Prompt
### Issue description
After this PR, `AICommandParmsStorage::doXfer` correctly transfers `m_cmdSource`, but **old saves written by the previous code** stored a duplicate of `m_cmd` in the second field. Loading those saves will therefore set `m_cmdSource` to an invalid `CommandSourceType` value.

This becomes dangerous because other code paths assume `CommandSourceType` is in-range and use it in bit operations (e.g., `1 << cmdSource`), which can become undefined behavior if `cmdSource` is large.

### Issue Context
- `CommandSourceType` has only a few valid values (`CMD_FROM_PLAYER`..`CMD_DEFAULT_SWITCH_WEAPON`, then `COMMAND_SOURCE_TYPE_COUNT`).
- `AICommandType` has many values and can exceed the bit-width safe range for shifting.
- `AICommandParmsStorage::doXfer` currently does not validate `m_cmdSource` after loading.

### Fix Focus Areas
- Add a post-load validation step in `AICommandParmsStorage::doXfer` to clamp/normalize `m_cmdSource` when `xfer->getXferMode() == XFER_LOAD`.
  - Example approach: after `xferUser(&m_cmdSource, ...)`, check `static_cast<Int>(m_cmdSource)` is within `[0, COMMAND_SOURCE_TYPE_COUNT)`; if not, set to a safe default (likely `CMD_FROM_AI`).
- Apply the same fix in both game variants.

Recommended code shape (illustrative):
```cpp
xfer->xferUser(&m_cmdSource, sizeof(m_cmdSource));
if (xfer->getXferMode() == XFER_LOAD) {
  const Int cs = static_cast<Int>(m_cmdSource);
  if (cs < 0 || cs >= COMMAND_SOURCE_TYPE_COUNT) {
    m_cmdSource = CMD_FROM_AI;
  }
}
```

### Fix Focus Areas (exact locations)
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[126-133]
- GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[129-136]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

if (xfer->getXferMode() == XFER_LOAD && (m_cmdSource < CMD_FROM_PLAYER || m_cmdSource >= COMMAND_SOURCE_TYPE_COUNT))
{
// TheSuperHackers @info Saves from before this fix contain a copy of the command in the command source slot.
m_cmdSource = CMD_FROM_AI;
}
xfer->xferCoord3D(&m_pos);
xfer->xferObjectID(&m_obj);
xfer->xferObjectID(&m_otherObj);
Expand Down
8 changes: 7 additions & 1 deletion GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,13 @@ void AICommandParmsStorage::reconstitute(AICommandParms& parms) const
void AICommandParmsStorage::doXfer(Xfer *xfer)
{
xfer->xferUser(&m_cmd, sizeof(m_cmd));
xfer->xferUser(&m_cmd, sizeof(m_cmdSource));
// TheSuperHackers @bugfix Transfer the command source instead of the command twice.
xfer->xferUser(&m_cmdSource, sizeof(m_cmdSource));
if (xfer->getXferMode() == XFER_LOAD && (m_cmdSource < CMD_FROM_PLAYER || m_cmdSource >= COMMAND_SOURCE_TYPE_COUNT))
{
// TheSuperHackers @info Saves from before this fix contain a copy of the command in the command source slot.
m_cmdSource = CMD_FROM_AI;
}
xfer->xferCoord3D(&m_pos);
xfer->xferObjectID(&m_obj);
xfer->xferObjectID(&m_otherObj);
Expand Down
Loading