docs: document replay system architecture and spacebar advance mechanism
Browse filesFully traced the spacebar-to-advance-replay chain through Ghidra:
keyboard -> TurnGame+0x1F4 -> HurryHandler -> DDGame+0x7E41 -> GameFrameEndProcessor -> local Hurry message (0x17).
Adds 11 replay/turn function addresses and 6 DDGame replay offsets to address.rs.
Creates docs/re-notes/replay-system.md with full findings including
the Option A direct control strategy (set DDGame+0x7E41 from DLL).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
crates/openwa-types/src/address.rs
CHANGED
|
@@ -114,6 +114,36 @@ pub mod va {
|
|
| 114 |
pub const CTASK_TEAM_CTOR: u32 = 0x0055_5BB0;
|
| 115 |
pub const CTASK_TURNGAME_CTOR: u32 = 0x0055_B280;
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
// === Gameplay functions ===
|
| 118 |
|
| 119 |
pub const CREATE_EXPLOSION: u32 = 0x0054_8080;
|
|
@@ -544,5 +574,23 @@ pub mod va {
|
|
| 544 |
pub const RENDER_QUEUE: u32 = 0x524;
|
| 545 |
/// Offset to weapon panel pointer
|
| 546 |
pub const WEAPON_PANEL: u32 = 0x548;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
}
|
| 548 |
}
|
|
|
|
| 114 |
pub const CTASK_TEAM_CTOR: u32 = 0x0055_5BB0;
|
| 115 |
pub const CTASK_TURNGAME_CTOR: u32 = 0x0055_B280;
|
| 116 |
|
| 117 |
+
// === Replay / turn management ===
|
| 118 |
+
|
| 119 |
+
/// Loads .WAgame replay file, validates magic 0x4157, stores payload at DDGame+0xDB1C.
|
| 120 |
+
/// stdcall(this, mode) where mode: 1=play, 2=getmap, 3=getscheme, 4=repair. ~12KB function.
|
| 121 |
+
pub const REPLAY_LOADER: u32 = 0x0046_2DF0;
|
| 122 |
+
/// Parses "MM:SS.FF" time string → frame number. Returns -1 on failure.
|
| 123 |
+
pub const PARSE_REPLAY_POSITION: u32 = 0x004E_3490;
|
| 124 |
+
/// Routes game messages through the task handler tree.
|
| 125 |
+
pub const GAME_MESSAGE_ROUTER: u32 = 0x0055_3BD0;
|
| 126 |
+
/// TurnGame message dispatcher. Case 2=FrameFinish, Case 4=ProcessInput, Case 0x28=SkipGo.
|
| 127 |
+
/// thiscall + 4 stack params.
|
| 128 |
+
pub const TURNGAME_HANDLE_MESSAGE: u32 = 0x0055_DC00;
|
| 129 |
+
/// Checks TurnGame+0x1F4 (hurry requested). Normal game: sends packet 0x17.
|
| 130 |
+
/// Replay mode (DB08 && DB0A): sets DDGame+0x7E41 instead. Uses ESI (__usercall).
|
| 131 |
+
pub const TURNGAME_HURRY_HANDLER: u32 = 0x0055_E5F0;
|
| 132 |
+
/// Per-frame turn timer: decrements turn timer by 0x14 (20ms) each frame.
|
| 133 |
+
pub const TURN_MANAGER_PROCESS_FRAME: u32 = 0x0055_FDA0;
|
| 134 |
+
/// Iterates teams during ProcessInput, sends packet 0x2B for valid teams.
|
| 135 |
+
pub const TURNGAME_AUTO_SELECT_TEAMS: u32 = 0x0056_11E0;
|
| 136 |
+
/// Control task HandleMessage (vtable 0x669C28). Translates keyboard input (msg 0xC)
|
| 137 |
+
/// into game messages. Case 9 toggles pause flag.
|
| 138 |
+
pub const CONTROL_TASK_HANDLE_MESSAGE: u32 = 0x0054_51F0;
|
| 139 |
+
/// End-of-frame processing. Reads DDGame+0x7E41 (deferred hurry flag),
|
| 140 |
+
/// converts it to local Hurry message (0x17). Also handles frame counters.
|
| 141 |
+
pub const GAME_FRAME_END_PROCESSOR: u32 = 0x0053_1960;
|
| 142 |
+
/// Main frame loop. Processes message queue, calls GameFrameEndProcessor.
|
| 143 |
+
pub const GAME_FRAME_DISPATCHER: u32 = 0x0053_1D00;
|
| 144 |
+
/// Sends game packet if network buffer capacity allows. Checks DDGame+0x98A4.
|
| 145 |
+
pub const SEND_GAME_PACKET_CONDITIONAL: u32 = 0x0053_1880;
|
| 146 |
+
|
| 147 |
// === Gameplay functions ===
|
| 148 |
|
| 149 |
pub const CREATE_EXPLOSION: u32 = 0x0054_8080;
|
|
|
|
| 574 |
pub const RENDER_QUEUE: u32 = 0x524;
|
| 575 |
/// Offset to weapon panel pointer
|
| 576 |
pub const WEAPON_PANEL: u32 = 0x548;
|
| 577 |
+
|
| 578 |
+
// --- Replay state ---
|
| 579 |
+
|
| 580 |
+
/// Deferred hurry flag. Set to 1 during replay instead of sending network
|
| 581 |
+
/// packet. GameFrameEndProcessor (0x531960) reads this and converts it to
|
| 582 |
+
/// a local Hurry message (TaskMessage 0x17 = 23).
|
| 583 |
+
pub const DEFERRED_HURRY_FLAG: u32 = 0x7E41;
|
| 584 |
+
/// Replay state flag A — checked by TurnGame_HurryHandler and FrameFinish.
|
| 585 |
+
/// Both DB08 and DB0A must be non-zero for replay-mode hurry path.
|
| 586 |
+
pub const REPLAY_STATE_FLAG_A: u32 = 0xDB08;
|
| 587 |
+
/// Replay state flag B — checked together with flag A for replay mode.
|
| 588 |
+
pub const REPLAY_STATE_FLAG_B: u32 = 0xDB0A;
|
| 589 |
+
/// Replay active flag — set to 1 by ReplayLoader when loading a .WAgame file.
|
| 590 |
+
pub const REPLAY_ACTIVE: u32 = 0xDB48;
|
| 591 |
+
/// Input replay file path (string buffer, 0x400 bytes).
|
| 592 |
+
pub const REPLAY_INPUT_PATH: u32 = 0xDB60;
|
| 593 |
+
/// Output replay file path (for recording, 0x400 bytes).
|
| 594 |
+
pub const REPLAY_OUTPUT_PATH: u32 = 0xDF60;
|
| 595 |
}
|
| 596 |
}
|
docs/re-notes/replay-system.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Replay System
|
| 2 |
+
|
| 3 |
+
WA's replay system records and plays back game inputs from `.WAgame` files.
|
| 4 |
+
|
| 5 |
+
## Command-Line Entry Points
|
| 6 |
+
|
| 7 |
+
In `Frontend__MainNavigationLoop` (0x4E6440):
|
| 8 |
+
- `/play <file>` -- play replay from start
|
| 9 |
+
- `/playat <file> [MM:SS.FF]` -- play from specific time offset
|
| 10 |
+
|
| 11 |
+
Globals set by arg parsing:
|
| 12 |
+
- `0x0088AF58` -- replay filename buffer
|
| 13 |
+
- `0x0088C77C` -- playback position (frame number)
|
| 14 |
+
- `0x0088C778` -- position valid flag (0 or 1)
|
| 15 |
+
|
| 16 |
+
## DDGame Replay Offsets
|
| 17 |
+
|
| 18 |
+
| Offset | Type | Description |
|
| 19 |
+
|----------|-------|-------------|
|
| 20 |
+
| +0x7E41 | byte | Deferred hurry flag. Set to 1 during replay when spacebar triggers a hurry. Consumed by GameFrameEndProcessor which converts it to a local Hurry message (0x17). |
|
| 21 |
+
| +0xDB08 | byte | Replay state flag A. Both A and B must be non-zero for the replay hurry path. |
|
| 22 |
+
| +0xDB0A | byte | Replay state flag B. |
|
| 23 |
+
| +0xDB0B | byte | Replay state flag C (checked by SendGamePacketConditional). |
|
| 24 |
+
| +0xDB1C | ptr | Replay payload pointer (malloc'd from file data). |
|
| 25 |
+
| +0xDB48 | dword | Replay active flag (set to 1 by ReplayLoader). |
|
| 26 |
+
| +0xDB50 | dword | Replay frame pointer/state. |
|
| 27 |
+
| +0xDB54 | dword | Replay frame pointer/state. |
|
| 28 |
+
| +0xDB58 | dword | Replay frame pointer/state. |
|
| 29 |
+
| +0xDB60 | char[0x400] | Input replay file path. |
|
| 30 |
+
| +0xDF60 | char[0x400] | Output replay file path (for recording). |
|
| 31 |
+
| +0xEF60 | dword | Init counter (set to 0 by loader). |
|
| 32 |
+
|
| 33 |
+
## Spacebar Advance Mechanism (Fully Traced)
|
| 34 |
+
|
| 35 |
+
When spacebar is pressed during replay playback, this chain fires:
|
| 36 |
+
|
| 37 |
+
```
|
| 38 |
+
Keyboard input
|
| 39 |
+
-> Control task (0x5451F0) translates keyboard action
|
| 40 |
+
-> Sets TurnGame+0x1F4 = 1 ("hurry requested")
|
| 41 |
+
-> TurnGame ProcessInput (msg 4)
|
| 42 |
+
-> TurnGame_HurryHandler (0x55E5F0)
|
| 43 |
+
checks TurnGame+0x1F4
|
| 44 |
+
Normal game: SendGamePacketWrapped(0x17, 0)
|
| 45 |
+
Replay mode: DDGame+0x7E41 = 1 (deferred hurry)
|
| 46 |
+
-> GameFrameEndProcessor (0x531960) end-of-frame
|
| 47 |
+
reads DDGame+0x7E41
|
| 48 |
+
if set: sends msg 0x17 (Hurry) locally + via SendGamePacketConditional
|
| 49 |
+
clears flag
|
| 50 |
+
-> Turn ends, next turn begins from replay data
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
Packet 0x17 = TaskMessage_Hurry (value 23 from wkJellyWorm enum). In replay mode
|
| 54 |
+
this is processed locally, not over the network. The deferred flag (0x7E41) bridges
|
| 55 |
+
the gap between "input detected during ProcessInput" and "end-of-frame processing."
|
| 56 |
+
|
| 57 |
+
## Key Functions
|
| 58 |
+
|
| 59 |
+
| Address | Name | Description |
|
| 60 |
+
|----------|------|-------------|
|
| 61 |
+
| 0x462DF0 | ReplayLoader | Loads .WAgame file. Validates magic 0x4157 ("WA"), stores payload at DDGame+0xDB1C. param_2: 1=play, 2=getmap, 3=getscheme, 4=repair. |
|
| 62 |
+
| 0x4E3490 | ParseReplayPosition | Parses "MM:SS.FF" -> frame number. Returns -1 on failure. |
|
| 63 |
+
| 0x531880 | SendGamePacketConditional | Sends packet if network buffer allows. |
|
| 64 |
+
| 0x531960 | GameFrameEndProcessor | End-of-frame. Reads deferred hurry flag, sends Hurry message. |
|
| 65 |
+
| 0x531D00 | GameFrameDispatcher | Main frame loop. Message queue + GameFrameEndProcessor. |
|
| 66 |
+
| 0x5451F0 | ControlTask_HandleMessage | Translates keyboard input (msg 0xC) -> game messages. |
|
| 67 |
+
| 0x553BD0 | GameMessageRouter | Routes messages through task handler tree. |
|
| 68 |
+
| 0x55DC00 | TurnGame_HandleMessage | Message dispatcher. Case 2=FrameFinish, 4=ProcessInput. |
|
| 69 |
+
| 0x55E5F0 | TurnGame_HurryHandler | Hurry logic. Normal: packet 0x17. Replay: deferred flag. __usercall(ESI). |
|
| 70 |
+
| 0x55FDA0 | TurnManager_ProcessFrame | Per-frame turn timer, decrements by 0x14 (20ms). |
|
| 71 |
+
| 0x5611E0 | TurnGame_AutoSelectTeams | Iterates teams during ProcessInput, sends packet 0x2B. |
|
| 72 |
+
|
| 73 |
+
## Replay File Format
|
| 74 |
+
|
| 75 |
+
- Magic: 4 bytes, value 0x4157 ("WA" in little-endian)
|
| 76 |
+
- Version in upper 16 bits of first dword
|
| 77 |
+
- Size: 4 bytes (payload length)
|
| 78 |
+
- Payload: binary game state + recorded inputs
|
| 79 |
+
|
| 80 |
+
## Direct Replay Control (Future)
|
| 81 |
+
|
| 82 |
+
To bypass keyboard simulation entirely, set DDGame+0x7E41 = 1 from our DLL thread.
|
| 83 |
+
The game's existing GameFrameEndProcessor converts it to a local Hurry message every
|
| 84 |
+
frame. This is the simplest approach and faithfully replicates what spacebar does.
|
| 85 |
+
|
| 86 |
+
Prerequisites: replay flags DB08 and DB0A must both be non-zero (true during normal
|
| 87 |
+
replay playback).
|