feat(cli): Play a replay file from the command line - #3227
Conversation
PR Summary by QodoAdd visual replay playback and absolute file loading to CLI
AI Description
Diagram
High-Level Assessment
Files changed (15)
|
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Source/Common/CommandLine.cpp | Adds -loadreplay parsing and validates replay/save extensions before queuing startup work. |
| Core/GameEngine/Source/Common/System/FileSystem.cpp | Adds platform-aware recognition of rooted Windows and POSIX paths. |
| Generals/Code/GameEngine/Source/Common/Recorder.cpp | Supports absolute replay paths and validates queued replay headers and map availability before playback. |
| GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp | Mirrors the queued replay and absolute-path behavior for Zero Hour. |
| Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp | Resolves absolute save paths in place while retaining managed Save-directory lookup for relative names. |
| GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp | Mirrors the command-line save-path resolution behavior for Zero Hour. |
| Generals/Code/GameEngine/Source/GameClient/GameClient.cpp | Starts queued replay playback after shell initialization, following the existing queued-save lifecycle point. |
| GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp | Mirrors the post-shell queued replay startup hook for Zero Hour. |
Sequence Diagram
sequenceDiagram
participant CLI as Command line
participant Client as GameClient
participant Recorder as RecorderClass
participant FS as FileSystem
participant Maps as MapCache
CLI->>Client: Queue -loadreplay path
Client->>Client: Initialize and show shell
Client->>Recorder: loadQueuedReplay()
Recorder->>FS: Resolve absolute path or Replay directory
FS-->>Recorder: Replay header
Recorder->>Maps: Validate replay map
Maps-->>Recorder: Map available
Recorder->>Recorder: playbackFile(path)
Recorder-->>Client: Start visual replay playback
Reviews (1): Last reviewed commit: "feat(cli): Play a replay file from the c..." | Re-trigger Greptile
Code Review by Qodo
1. -ignoreReplaySyncErrors is unregistered
|
|
|
||
| // TheSuperHackers @feature bobtista 22/07/2026 Load a save game file from the command line. | ||
| { "-loadsave", parseLoadSave }, | ||
| { "-loadreplay", parseLoadReplay }, |
There was a problem hiding this comment.
1. -ignorereplaysyncerrors is unregistered 📎 Requirement gap ≡ Correctness
The PR registers -loadreplay but does not register the required -ignoreReplaySyncErrors option, so invoking the mandated suppression flag cannot set TheDebugIgnoreSyncErrors. Only the differently named legacy -ignoresync option reaches parseSync.
Agent Prompt
## Issue description
`-loadreplay` must support the explicit `-ignoreReplaySyncErrors` command-line option, but that name is not registered.
## Issue Context
The existing `parseSync` handler already enables `TheDebugIgnoreSyncErrors`, and the legacy `-ignoresync` registration should remain compatible. Register the required option name as an alias to the same handler.
## Fix Focus Areas
- Core/GameEngine/Source/Common/CommandLine.cpp[1205-1208]
- Core/GameEngine/Source/Common/CommandLine.cpp[1319-1323]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| return; | ||
| } | ||
|
|
||
| if (!playbackFile(filename)) |
There was a problem hiding this comment.
2. Playerless replay crashes startup 🐞 Bug ≡ Correctness
loadQueuedReplay() passes headers with localPlayerIndex == -1 into playbackFile(), which dereferences getSlot(-1) and crashes instead of playing or cleanly rejecting the replay. The recorder itself can write -1 for non-network single-player recordings, so -loadreplay can hit this with a generated replay file.
Agent Prompt
## Issue description
Queued playback can receive a valid replay header with `localPlayerIndex == -1`, but `playbackFile()` unconditionally dereferences that slot and crashes. Handle the no-local-player case without calling `getSlot(-1)`, and apply the equivalent fix to both game variants.
## Issue Context
`readReplayHeader()` explicitly accepts `-1`, and `startRecording()` can serialize `-1` for non-network single-player recordings. The multiplayer flag should only inspect a slot when the index is nonnegative; otherwise use the appropriate non-multiplayer default.
## Fix Focus Areas
- Generals/Code/GameEngine/Source/Common/Recorder.cpp[1117-1120]
- Generals/Code/GameEngine/Source/Common/Recorder.cpp[1191-1200]
- GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp[1120-1123]
- GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp[1194-1203]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There is no way to play a replay visually from the command line.
-replaysimulates headlessly, so an externally supplied.repcannot be launched from an operating-system file handler and watched.Now
-loadreplay <file>plays a replay through the normal client, and an absolute path is opened in place while a relative name still resolves from the Replay directory.RecorderClass::readReplayHeaderusesFileSystem::isAbsolutePathfrom #3226 for that, andloadQueuedReplayruns at the point-loadsavealready uses, once the client has initialized the shell, so the menus the playback returns to are on the stack.A replay whose map is unavailable starts a game that cannot load, so
loadQueuedReplayrejects it up front and quits rather than failing deep in map loading.Verified with a bogus path as a control so a pass is distinguishable from "the game started anyway":
\\localhost\C$\...)InGame:D9C721A5 Replay:D8A198C0 Frame:110)Todo:
z_generalsandg_generals)