Skip to main content

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