Skip to main content

faf_replay_parser/scfa/
parser.rs

1use super::replay::*;
2use crate::reader::{ReplayBufReadExt, ReplayReadError, ReplayReadExt, ReplayResult};
3use crate::replay::SimData;
4use crate::version::Command;
5use std::io::{BufRead, Read};
6
7impl Command for ReplayCommand {
8    fn as_advance(&self) -> Option<crate::version::Advance> {
9        match self {
10            Self::Advance { ticks } => Some(crate::version::Advance { ticks: *ticks }),
11            _ => None,
12        }
13    }
14
15    fn parse_command_data(command_id: u8, mut reader: &[u8]) -> ReplayResult<ReplayCommand> {
16        use replay_command::*;
17        use ReplayCommand::*;
18
19        Ok(match command_id {
20            ADVANCE => Advance {
21                ticks: reader.read_u32_le()?,
22            },
23            SET_COMMAND_SOURCE => SetCommandSource {
24                id: reader.read_u8()?,
25            },
26            COMMAND_SOURCE_TERMINATED => CommandSourceTerminated,
27            VERIFY_CHECKSUM => {
28                let mut digest = [0; 16];
29                reader.read_exact(&mut digest)?;
30                VerifyChecksum {
31                    digest: digest,
32                    tick: reader.read_u32_le()?,
33                }
34            }
35            REQUEST_PAUSE => RequestPause,
36            RESUME => Resume,
37            SINGLE_STEP => SingleStep,
38            CREATE_UNIT => CreateUnit {
39                army: reader.read_u8()?,
40                blueprint: reader.read_string()?,
41                x: reader.read_f32_le()?,
42                z: reader.read_f32_le()?,
43                heading: reader.read_f32_le()?,
44            },
45            CREATE_PROP => CreateProp {
46                blueprint: reader.read_string()?,
47                position: read_position(&mut reader)?,
48            },
49            DESTROY_ENTITY => DestroyEntity {
50                unit: reader.read_u32_le()?,
51            },
52            WARP_ENTITY => WarpEntity {
53                unit: reader.read_u32_le()?,
54                x: reader.read_f32_le()?,
55                y: reader.read_f32_le()?,
56                z: reader.read_f32_le()?,
57            },
58            PROCESS_INFO_PAIR => ProcessInfoPair {
59                unit: reader.read_u32_le()?,
60                arg1: reader.read_string()?,
61                arg2: reader.read_string()?,
62            },
63            ISSUE_COMMAND => IssueCommand(read_game_command(&mut reader)?),
64            ISSUE_FACTORY_COMMAND => IssueFactoryCommand(read_game_command(&mut reader)?),
65            INCREASE_COMMAND_COUNT => IncreaseCommandCount {
66                id: reader.read_u32_le()?,
67                delta: reader.read_i32_le()?,
68            },
69            DECREASE_COMMAND_COUNT => DecreaseCommandCount {
70                id: reader.read_u32_le()?,
71                delta: reader.read_i32_le()?,
72            },
73            SET_COMMAND_TARGET => SetCommandTarget {
74                id: reader.read_u32_le()?,
75                target: read_target(&mut reader)?,
76            },
77            SET_COMMAND_TYPE => {
78                let id = reader.read_u32_le()?;
79                let type_ = reader.read_u32_le()? as u8;
80                if type_ > game_command::MAX {
81                    return Err(ReplayReadError::Malformed("invalid game command"));
82                }
83                SetCommandType { id, type_ }
84            }
85            SET_COMMAND_CELLS => {
86                // This function may or may not be correct. So far, we have not found any replays that
87                // contain this command.
88                let id = reader.read_u32_le()?;
89                let cells = reader.read_lua_object()?;
90                if cells.evaluate_as_bool() {
91                    reader.skip_buf(1, &mut [0])?;
92                }
93                SetCommandCells {
94                    id,
95                    cells,
96                    position: read_position(&mut reader)?,
97                }
98            }
99            REMOVE_COMMAND_FROM_QUEUE => RemoveCommandFromQueue {
100                id: reader.read_u32_le()?,
101                unit: reader.read_u32_le()?,
102            },
103            DEBUG_COMMAND => DebugCommand {
104                command: reader.read_string()?,
105                position: read_position(&mut reader)?,
106                focus_army: reader.read_u8()?,
107                selection: read_entity_list(&mut reader)?,
108            },
109            EXECUTE_LUA_IN_SIM => ExecuteLuaInSim {
110                code: reader.read_string()?,
111            },
112            LUA_SIM_CALLBACK => {
113                let func = reader.read_string()?;
114                let args = reader.read_lua_object()?;
115
116                let selection = read_entity_list(&mut reader)?;
117                LuaSimCallback {
118                    func,
119                    args,
120                    selection,
121                }
122            }
123            END_GAME => EndGame,
124            _ => {
125                return Err(ReplayReadError::Malformed(
126                    "encountered unrecognized command",
127                ))
128            }
129        })
130    }
131
132    /// Update `sim` given the new command.
133    fn process_command(sim: &mut SimData, command: &ReplayCommand) -> ReplayResult<()> {
134        use ReplayCommand::*;
135
136        match command {
137            // Increment the tick counter
138            Advance { ticks } => sim.tick += *ticks,
139            // Change which player the following commands apply to
140            SetCommandSource { id } => sim.command_source = *id,
141            // Record when command a command stream ends
142            CommandSourceTerminated => {
143                sim.players_last_tick.insert(sim.command_source, sim.tick);
144            }
145            // Make sure that the replay hasn't desynced. Every player sends a checksum once per 50
146            // ticks representing the game state of their local simulation. If one or more of the
147            // checksums don't match, then those players have desynced and we can no longer trust their
148            // replay streams.
149            VerifyChecksum { digest, tick } => {
150                if sim.checksum_tick < Some(*tick) {
151                    // First checksum submitted for this tick
152                    sim.checksum_tick.replace(*tick);
153                    sim.checksum.copy_from_slice(&digest[..16]);
154                    return Ok(());
155                }
156                debug_assert_eq!(
157                    sim.checksum_tick,
158                    Some(*tick),
159                    "Checksum received out of order!"
160                );
161
162                if sim.checksum != digest[..16] {
163                    if sim.desync_tick.is_none() {
164                        sim.desync_tick.replace(sim.tick);
165                        sim.desync_ticks.replace(vec![]);
166                    }
167
168                    sim.desync_ticks.as_mut().unwrap().push(sim.tick);
169
170                    // TODO: Use a different error type here? ParseError?
171                    return Err(ReplayReadError::Desynced(sim.tick));
172                }
173            }
174            _ => (),
175        };
176        Ok(())
177    }
178}
179
180#[inline]
181pub fn read_position(reader: &mut impl Read) -> ReplayResult<Position> {
182    Ok(Position {
183        x: reader.read_f32_le()?,
184        y: reader.read_f32_le()?,
185        z: reader.read_f32_le()?,
186    })
187}
188
189pub fn read_entity_list(reader: &mut impl Read) -> ReplayResult<Vec<u32>> {
190    let len = reader.read_u32_le()? as usize;
191    // 16 players at max unit cap selecting every entity at once.
192    // This should be way more than enough.
193    let mut result = Vec::with_capacity(std::cmp::min(len, 16_000));
194
195    for _ in 0..len {
196        result.push(reader.read_u32_le()?);
197    }
198
199    Ok(result)
200}
201
202pub fn read_target(reader: &mut impl Read) -> ReplayResult<Target> {
203    use target_type::*;
204
205    match reader.read_u8()? {
206        NONE => Ok(Target::None),
207        ENTITY => Ok(Target::Entity {
208            id: reader.read_u32_le()?,
209        }),
210        POSITION => Ok(Target::Position(read_position(reader)?)),
211        _ => Err(ReplayReadError::Malformed("invalid target type")),
212    }
213}
214
215pub fn read_formation(reader: &mut impl Read) -> ReplayResult<Option<Formation>> {
216    let type_ = reader.read_i32_le()?;
217    if type_ == -1 {
218        return Ok(None);
219    }
220
221    Ok(Some(Formation {
222        a: reader.read_f32_le()?,
223        b: reader.read_f32_le()?,
224        c: reader.read_f32_le()?,
225        d: reader.read_f32_le()?,
226        scale: reader.read_f32_le()?,
227    }))
228}
229
230pub fn read_cells(reader: &mut impl Read) -> ReplayResult<Vec<Cell>> {
231    let size = reader.read_u32_le()? as usize;
232    // Since we've never seen these, lets just limit to 1 MiB
233    let mut result = Vec::with_capacity(std::cmp::min(
234        size,
235        crate::MIB / std::mem::size_of::<Cell>(),
236    ));
237    for _ in 0..size {
238        result.push(Cell {
239            x: reader.read_i16_le()?,
240            z: reader.read_i16_le()?,
241        });
242    }
243    Ok(result)
244}
245
246pub fn read_game_command(reader: &mut (impl Read + BufRead)) -> ReplayResult<GameCommand> {
247    let entity_ids = read_entity_list(reader)?;
248    let id = reader.read_u32_le()?;
249
250    // Guessing that 0xFFFFFFFF for this field means it's a signed int
251    // Appears to be another command id referencing the target of a coordinated attack.
252    // When this is not -1, type_ is always 'ATTACK (10)'
253    let coordinated_attack_cmd_id = reader.read_i32_le()?;
254    let type_ = reader.read_u8()?;
255
256    if type_ > game_command::MAX {
257        return Err(ReplayReadError::Malformed("invalid game command"));
258    }
259    // Appears to be a command id or entity id
260    // When this is not -1, type_ is always "BUILD_FACTORY (7)"
261    let arg2 = reader.read_i32_le()?;
262
263    let target = read_target(reader)?;
264    let arg3 = reader.read_u8()?; // Always 0
265    let formation = read_formation(reader)?;
266
267    let blueprint = reader.by_ref().take(255).read_string()?;
268
269    // TODO: Are these signed or unsigned? What do they mean?
270    let arg4 = reader.read_u32_le()?; // Always 0
271    let arg5 = reader.read_u32_le()?; // Always 1
272    let arg6 = reader.read_u32_le()?; // Always 1
273
274    let mut clear_queue = None;
275
276    let upgrades = reader.read_lua_object()?;
277    if upgrades.evaluate_as_bool() {
278        clear_queue = Some(reader.read_bool()?);
279    }
280
281    Ok(GameCommand {
282        entity_ids,
283        id,
284        coordinated_attack_cmd_id,
285        type_,
286        arg2,
287        target,
288        arg3,
289        formation,
290        blueprint,
291        arg4,
292        arg5,
293        arg6,
294        upgrades,
295        clear_queue,
296    })
297}