faf_replay_parser/
parser.rs

1//! Parse replays from generic `Read`ers
2
3use bytes::{Buf, BufMut, BytesMut};
4use ordered_hash_map::OrderedHashMap;
5
6use std::collections::HashSet;
7use std::convert::TryFrom;
8use std::io::{BufRead, Cursor, Read};
9
10use crate::parser_builder::ParserBuilder;
11use crate::reader::{ReplayBufReadExt, ReplayReadError, ReplayReadExt, ReplayResult};
12use crate::replay::*;
13use crate::version::{Command, CommandId, Version};
14
15/// Configuration options for [`Parser`].
16#[derive(Debug)]
17pub struct ParserOptions<V: Version> {
18    /// Which commands should be fully parsed
19    pub commands: HashSet<V::CommandId>,
20    /// The maximum number of commands to parse
21    pub limit: Option<usize>,
22    /// Whether or not to add the parsed commands to the replay body
23    pub save_commands: bool,
24    /// Whether or not to return an error if a desync is detected
25    pub stop_on_desync: bool,
26}
27
28// Implemented by hand so that Version doesn't need Clone
29impl<V: Version> Clone for ParserOptions<V> {
30    fn clone(&self) -> Self {
31        Self {
32            commands: self.commands.clone(),
33            limit: self.limit.clone(),
34            save_commands: self.save_commands.clone(),
35            stop_on_desync: self.stop_on_desync.clone(),
36        }
37    }
38}
39
40/// A configurable replay parser. Construct a new parser with default paramaters or use the
41/// [`ParserBuilder`] for fine grained control.
42///
43/// # Example
44///
45/// ```rust
46/// use faf_replay_parser::{Parser, SCFA};
47/// use std::io::Read;
48///
49/// let mut data: &[u8] = b"Some replay data";
50///
51/// let parser = Parser::<SCFA>::new();
52/// let result = parser.parse(&mut data)
53///     .expect_err("Sadly not a valid replay");
54/// ```
55#[derive(Debug)]
56pub struct Parser<V: Version> {
57    options: ParserOptions<V>,
58}
59
60impl<V: Version> Parser<V> {
61    /// Constructs a new parser with default settings. This will only parse the most essential
62    /// commands needed to determine the number of participants, the length of the replay, and
63    /// whether or not the replay desynced.
64    pub fn new() -> Parser<V> {
65        ParserBuilder::new().commands_default().build()
66    }
67
68    /// Constructs a new [`Parser`] with given [`ParserOptions`]
69    pub fn with_options(options: ParserOptions<V>) -> Parser<V> {
70        Parser { options }
71    }
72
73    /// Creates a new [`StreamParser`] with the same options as `self`.
74    pub fn new_stream(&self) -> StreamParser<V> {
75        StreamParser::with_options(self.options.clone())
76    }
77
78    /// Fully parses a stream of bytes into a `Replay` struct.
79    pub fn parse(&self, reader: &mut (impl Read + BufRead)) -> ReplayResult<Replay<V>> {
80        self.parse_with_callback(reader, V::Command::process_command)
81    }
82
83    /// Like [`parse`] but using a custom command processing function. This can be useful for
84    /// aggregating replay results on the fly without saving the whole command stream to a `Vec`
85    /// first.
86    ///
87    /// # Example
88    /// ```rust
89    /// use faf_replay_parser::{ParserBuilder, SCFA};
90    /// use faf_replay_parser::scfa::ReplayCommand;
91    ///
92    /// let mut data: &[u8] = b"Some replay data";
93    ///
94    /// let parser = ParserBuilder::<SCFA>::new()
95    ///     .save_commands(false)
96    ///     .build();
97    ///
98    /// let replay = parser.parse_with_callback(&mut data, |sim, command| {
99    ///     if let ReplayCommand::Advance { ticks } = command {
100    ///         println!("Advancing {}!", ticks);
101    ///     }
102    ///     Ok(())
103    /// });
104    /// ```
105    ///
106    /// [`parse`]: struct.Parser.html#method.parse
107    pub fn parse_with_callback(
108        &self,
109        reader: &mut (impl Read + BufRead),
110        callback: impl Fn(&mut SimData, &V::Command) -> ReplayResult<()>,
111    ) -> ReplayResult<Replay<V>> {
112        let mut buf = Vec::new();
113        Ok(Replay {
114            header: parse_header_with_buf(reader, &mut buf)?,
115            body: parse_body_with_callback(reader, &self.options, callback, &mut buf)?,
116        })
117    }
118
119    /// Parses a stream of bytes into a `ReplayHeader` struct.
120    pub fn parse_header(&self, reader: &mut (impl Read + BufRead)) -> ReplayResult<ReplayHeader> {
121        Ok(parse_header(reader)?)
122    }
123
124    /// Parses a stream of bytes into a `ReplayBody` struct. Usually this means the header has
125    /// already been parsed so that `reader` starts at the correct offset for the replay body.
126    pub fn parse_body(&self, reader: &mut impl Read) -> ReplayResult<ReplayBody<V>> {
127        self.parse_body_with_callback(reader, V::Command::process_command)
128    }
129
130    /// Like [`parse_body`] but using a custom command processing function. See
131    /// [`parse_with_callback`] for an example callback function.
132    ///
133    /// [`parse_body`]: struct.Parser.html#method.parse_body
134    /// [`parse_with_callback`]: struct.Parser.html#method.parse_with_callback
135    pub fn parse_body_with_callback(
136        &self,
137        reader: &mut impl Read,
138        callback: impl Fn(&mut SimData, &V::Command) -> ReplayResult<()>,
139    ) -> ReplayResult<ReplayBody<V>> {
140        parse_body_with_callback(reader, &self.options, callback, &mut vec![])
141    }
142}
143
144/// Like [`Parser`] but for incremental parsing of streamed data. Useful when data is being
145/// received over a network.
146///
147/// Unlike [`Parser`], a `StreamParser` should only be used to parse a single replay stream at a
148/// time.
149///
150/// # Example
151/// ```rust
152/// use faf_replay_parser::{StreamParser, ReplayReadError, SCFA};
153/// use std::io::Read;
154/// use std::fs::File;
155///
156/// let mut file = File::open("tests/data/6176549.scfareplay").unwrap();
157/// let mut stream = StreamParser::<SCFA>::new();
158///
159/// while stream.feed_reader(&mut file, 8192).unwrap() != 0 {
160///     match stream.parse() {
161///         Err(ReplayReadError::IO(_)) => continue,
162///         res => res.expect("Replay file ok")
163///     }
164/// }
165/// let replay = stream.finalize().unwrap();
166/// ```
167#[derive(Debug)]
168pub struct StreamParser<V: Version> {
169    options: ParserOptions<V>,
170    /// Captures incoming unparsed data.
171    buffer: BytesMut,
172    /// A re-usable buffer for reading command data.
173    reuse_buf: Vec<u8>,
174    header: Option<ReplayHeader>,
175    body: Option<ReplayBody<V>>,
176}
177
178impl<V: Version> StreamParser<V> {
179    /// Constructs a new `StreamParser` with default settings.
180    /// See [`ParserBuilder::commands_default`].
181    pub fn new() -> StreamParser<V> {
182        ParserBuilder::new().commands_default().build_stream()
183    }
184
185    /// Constructs a new `StreamParser` with given [`ParserOptions`].
186    pub fn with_options(options: ParserOptions<V>) -> StreamParser<V> {
187        StreamParser {
188            options,
189            buffer: BytesMut::new(),
190            reuse_buf: Vec::new(),
191            header: None,
192            body: None,
193        }
194    }
195
196    /// Returns a reference to the stored [`ReplayHeader`].
197    pub fn header(&self) -> Option<&ReplayHeader> {
198        self.header.as_ref()
199    }
200
201    /// Returns a reference to the stored [`ReplayBody`].
202    pub fn body(&self) -> Option<&ReplayBody<V>> {
203        self.body.as_ref()
204    }
205
206    /// Adds some replay data to the buffer from an existing slice.
207    ///
208    /// If data is coming from a [`std::io::Read`], then
209    /// [`feed_reader`](struct.StreamParser.html#method.feed_reader) should be used instead to
210    /// avoid double allocation and double copying.
211    ///
212    /// # Examples
213    /// ```rust
214    /// use faf_replay_parser::{StreamParser, SCFA};
215    ///
216    /// let mut stream = StreamParser::<SCFA>::new();
217    /// stream.feed(&[0u8; 10]);
218    /// ```
219    pub fn feed(&mut self, data: &[u8]) {
220        self.buffer.extend_from_slice(data)
221    }
222
223    /// Pulls data from a [`std::io::Read`] and adds it directly to the buffer. This will only make
224    /// one call to `read` reading up to `max` bytes. Calling this in a loop acts similar to a
225    /// [`std::io::BufReader`] with a buffer size of `max`. Setting `max` to a small number could
226    /// cause excessive calls to `read`.
227    ///
228    /// # Returns
229    /// The number of bytes read.
230    ///
231    /// # Examples
232    /// ```rust
233    /// use faf_replay_parser::{StreamParser, SCFA};
234    /// use std::io::Cursor;
235    ///
236    /// let mut stream = StreamParser::<SCFA>::new();
237    /// let mut reader = Cursor::new(vec![0u8; 10]);
238    ///
239    /// assert_eq!(stream.feed_reader(&mut reader, 3).unwrap(), 3);
240    /// assert_eq!(stream.feed_reader(&mut reader, 10).unwrap(), 7);
241    /// ```
242    pub fn feed_reader(&mut self, reader: &mut impl Read, max: usize) -> std::io::Result<usize> {
243        // TODO: Should this even have a `max` option? Using a small number could be inefficient.
244        self.buffer.reserve(max);
245        let uninit_slice = self.buffer.chunk_mut();
246
247        unsafe {
248            let buf = std::slice::from_raw_parts_mut(uninit_slice.as_mut_ptr(), max);
249            // `read` may read the contents of buf, so we need to initialize it.
250            // There is a nightly feature `read_initializer` that can be used to avoid this.
251            buf.fill(0);
252
253            let n = reader.read(buf)?;
254
255            self.buffer.advance_mut(n);
256            Ok(n)
257        }
258    }
259
260    /// Parse as much of the data in the buffer as possible and advance the internal state. Calling
261    /// this function a second time without calling [`feed`](struct.StreamParser.html#method.feed)
262    /// or [`feed_reader`](struct.StreamParser.html#method.feed_reader) will return an error.
263    ///
264    /// # Errors
265    /// Returns a [`ReplayReadError::IO`] if there is not enough data available, and other variants
266    /// of [`ReplayReadError`] if the data stream is corrupt.
267    pub fn parse(&mut self) -> ReplayResult<()> {
268        if self.header.is_none() {
269            self.parse_header()
270        } else {
271            self.parse_body()
272        }
273    }
274
275    /// Parse a [`ReplayHeader`] from the buffer and advance the internal state. If `Ok(())` is
276    /// returned, the header can be accessed by calling
277    /// [`header`](struct.StreamParser.html#method.header).
278    ///
279    /// # Errors
280    /// Returns a [`ReplayReadError::IO`] if there is not enough data available, and other variants
281    /// of [`ReplayReadError`] if the data stream is corrupt.
282    pub fn parse_header(&mut self) -> ReplayResult<()> {
283        let mut cur = Cursor::new(&self.buffer[..]);
284        let result = parse_header_with_buf(&mut cur, &mut self.reuse_buf);
285
286        let result = match result {
287            Err(ReplayReadError::IO(_)) => {
288                // IO operations on BytesMut are infallable, so we must be missing data.
289                // In this case we just return the error without advancing the buffer, and let the
290                // caller try again later.
291                return Err(result.unwrap_err());
292            }
293            Ok(header) => {
294                self.header.replace(header);
295                Ok(())
296            }
297            err => Err(err.unwrap_err()),
298        };
299
300        let n = cur.position() as usize;
301        self.buffer.advance(n);
302
303        return result;
304    }
305
306    /// Parse as many commands as possible from the buffer and add them to the internal
307    /// [`ReplayBody`] command stream. The [`ReplayBody`] can be accessed by calling
308    /// [`body`](struct.StreamParser.html#method.body).
309    ///
310    /// # Errors
311    /// Returns a [`ReplayReadError::IO`] if there is not enough data available, and other variants
312    /// of [`ReplayReadError`] if the data stream is corrupt.
313    pub fn parse_body(&mut self) -> ReplayResult<()> {
314        self.parse_body_with_callback(V::Command::process_command)
315    }
316
317    /// Like [`parse_body`](struct.StreamParser.html#method.parse_body) but applying a custom
318    /// processing function to each parsed command.
319    pub fn parse_body_with_callback(
320        &mut self,
321        callback: impl Fn(&mut SimData, &V::Command) -> ReplayResult<()>,
322    ) -> ReplayResult<()> {
323        self.body.get_or_insert_with(|| ReplayBody {
324            commands: Vec::new(),
325            sim: SimData::new(),
326        });
327
328        while self.options.limit.is_none()
329            || self.body.as_ref().unwrap().commands.len() < self.options.limit.unwrap()
330        {
331            // This duplicates a small amount of work, but saves one allocation.
332            if !self.has_frame()? {
333                return Err(ReplayReadError::IO(std::io::Error::new(
334                    std::io::ErrorKind::UnexpectedEof,
335                    "partial frame",
336                )));
337            }
338            match self.parse_command() {
339                Ok(Some(cmd)) => {
340                    let result = callback(&mut self.body.as_mut().unwrap().sim, &cmd);
341
342                    // Deciding not to combine these with #![feature(let_chains)]
343                    if let Err(ReplayReadError::Desynced(_)) = result {
344                        if self.options.stop_on_desync {
345                            result?;
346                        }
347                    } else {
348                        result?;
349                    }
350
351                    if self.options.save_commands {
352                        self.body.as_mut().unwrap().commands.push(cmd);
353                    }
354
355                    if self.buffer.is_empty() {
356                        return Ok(());
357                    }
358                }
359                Ok(None) => {}
360                err => return Err(err.unwrap_err()),
361            }
362        }
363
364        // Skip commands without parsing
365        loop {
366            self.parse_command_frame().map(|_| ())?;
367            if self.buffer.is_empty() {
368                return Ok(());
369            }
370        }
371    }
372
373    /// Checks if there is enough data available to parse an additional command from the replay
374    /// body.
375    ///
376    /// # Errors
377    /// If the command ID or the frame size are invalid, a [`ReplayReadError::Malformed`] is
378    /// returned.
379    ///
380    /// # Examples
381    /// ```rust
382    /// use faf_replay_parser::{StreamParser, SCFA};
383    ///
384    /// let mut stream = StreamParser::<SCFA>::new();
385    /// assert!(!stream.has_frame().unwrap());
386    ///
387    /// stream.feed(&[0, 3, 0]);
388    /// assert!(stream.has_frame().unwrap());
389    ///
390    /// stream.reset();
391    /// stream.feed(&[100]); // Not a valid command id
392    /// stream.has_frame().unwrap_err();
393    /// ```
394    pub fn has_frame(&self) -> ReplayResult<bool> {
395        // TODO: Refactor. Should not be calling into scfa crate here
396        crate::scfa::bytes::has_frame(&self.buffer[..])
397    }
398
399    /// Parses a command from the buffer and consumes the read data. If the command type is not
400    /// present in the parser options then returns `Ok(None)`.
401    ///
402    /// It is recommended to call [`has_frame`](struct.StreamParser.html#method.has_frame) first to
403    /// ensure that enough data is available.
404    ///
405    /// # Errors
406    /// If an error occurrs, it may be that the replay data is corrupted or there is not enough
407    /// data available to fully parse a command. In case of missing data, the error will always
408    /// be a [`ReplayReadError::IO`] and the inner error will be a [`std::io::Error`] with kind
409    /// [`std::io::ErrorKind::UnexpectedEof`]. Any other error likely means that the stream became
410    /// corrupted and that future calls to `parse_command` are unlikely to succeed.
411    ///
412    /// # Examples
413    /// ```rust
414    /// use faf_replay_parser::{StreamParser, SCFA};
415    /// use faf_replay_parser::scfa::ReplayCommand;
416    ///
417    /// let mut stream = StreamParser::<SCFA>::new();
418    ///
419    /// stream.feed(&[0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00]);
420    ///
421    /// assert_eq!(
422    ///     stream.parse_command().unwrap(),
423    ///     Some(ReplayCommand::Advance { ticks: 1 })
424    /// );
425    /// ```
426    pub fn parse_command(&mut self) -> ReplayResult<Option<V::Command>> {
427        // Might be able to do this using BytesMut functionality alone. Not sure.
428        let mut cur = Cursor::new(&self.buffer[..]);
429        let result = parse_command(&mut cur, &self.options, &mut self.reuse_buf);
430
431        if let Err(ReplayReadError::IO(_)) = &result {
432            // IO operations on BytesMut are infallable, so we must be missing data.
433            // In this case we just return the error without advancing the buffer, and let the
434            // caller try again later.
435            return result;
436        } else {
437            let n = cur.position() as usize;
438            self.buffer.advance(n);
439
440            return result;
441        }
442    }
443
444    /// Parses a command frame, but doesn't parse the command data.
445    ///
446    /// # Errors
447    /// Returns a [`ReplayReadError::IO`] if there is not enough data available, and other variants
448    /// of [`ReplayReadError`] if either the command id or the frame size are invalid.
449    pub fn parse_command_frame(&mut self) -> ReplayResult<ReplayCommandFrame<V>> {
450        let mut cur = Cursor::new(&self.buffer[..]);
451        let result = parse_command_frame(&mut cur);
452
453        if let Err(ReplayReadError::IO(_)) = &result {
454            return result;
455        } else {
456            let n = cur.position() as usize;
457            self.buffer.advance(n);
458
459            return result;
460        }
461    }
462
463    /// Returns whether or not a call to [`finalize`](struct.StreamParser.html#method.finalize)
464    /// will succeed.
465    pub fn can_finalize(&self) -> bool {
466        self.buffer.is_empty()
467    }
468
469    /// Signals that the stream has ended and returns the parsed [`StreamReplay`].
470    ///
471    /// # Errors
472    /// Returns a [`ReplayReadError::IO`] if there is any unparsed data in the buffer.
473    pub fn finalize(&mut self) -> ReplayResult<StreamReplay<V>> {
474        match self.buffer.is_empty() {
475            true => Ok(self.force_finalize()),
476            false => Err(ReplayReadError::IO(std::io::Error::new(
477                std::io::ErrorKind::UnexpectedEof,
478                "replay finalized before all data was consumed",
479            ))),
480        }
481    }
482
483    /// Returns the currently parsed replay, and discards any excess data from the buffer.
484    pub fn force_finalize(&mut self) -> StreamReplay<V> {
485        let result = StreamReplay {
486            header: self.header.take(),
487            body: self.body.take(),
488        };
489        self.buffer.clear();
490
491        result
492    }
493
494    /// Throws away all internal state. After resetting, `self` can be used to start parsing a new
495    /// data stream.
496    ///
497    /// Note that this will not deallocate the internal buffer, so it is slightly more efficient to
498    /// reset and reuse a [`StreamParser`] than to create a new one.
499    pub fn reset(&mut self) {
500        self.header.take();
501        self.body.take();
502        self.buffer.clear();
503    }
504}
505
506/// Like [`Replay`] but fields are optional.
507#[derive(Debug)]
508pub struct StreamReplay<V: Version> {
509    pub header: Option<ReplayHeader>,
510    pub body: Option<ReplayBody<V>>,
511}
512
513impl<V: Version> StreamReplay<V> {
514    /// Unwrap the optional fields.
515    pub fn unwrap(self) -> Replay<V> {
516        Replay {
517            header: self.header.unwrap(),
518            body: self.body.unwrap(),
519        }
520    }
521}
522
523/// A hand optimized function for extracting the tick count as quickly and with as little
524/// overhead as possible. Does not check for desyncs.
525pub fn parse_body_ticks<V: Version>(reader: &mut impl Read) -> ReplayResult<u32> {
526    let mut ticks = 0;
527    let mut buf = vec![0u8; 4096];
528
529    // Will not throw an error on missing data
530    loop {
531        unsafe {
532            ticks += match parse_body_ticks_command::<V>(reader, buf.as_mut_slice()) {
533                Err(ReplayReadError::IO(err))
534                    if err.kind() == std::io::ErrorKind::UnexpectedEof =>
535                {
536                    return Ok(ticks);
537                }
538                other => other?,
539            }
540        }
541    }
542}
543
544/// Calling this with a buffer shorter than 4 bytes is undefined behavior.
545#[inline]
546unsafe fn parse_body_ticks_command<V: Version>(
547    reader: &mut impl Read,
548    buf: &mut [u8],
549) -> ReplayResult<u32> {
550    debug_assert!(buf.len() >= 4);
551
552    // Size is guaranteed to be at least 3.
553    let (command_id, size) = parse_command_frame_header::<V>(reader)?;
554
555    if command_id != V::CommandId::advance() {
556        reader.skip_buf(size as usize - 3, buf)?;
557        return Ok(0);
558    }
559    if size != 7 {
560        reader.skip_buf(size as usize - 3, buf)?;
561        return Err(ReplayReadError::Malformed("invalid command size"));
562    }
563    // We are assuming that buf is large enough to hold the contents of an ADVANCE command frame.
564    reader.read_exact(buf.get_unchecked_mut(0..4))?;
565    let ticks = buf.get_unchecked(0..4).read_u32_le().unwrap();
566
567    Ok(ticks)
568}
569
570/// Parses a stream of bytes into a `ReplayHeader` struct.
571pub fn parse_header(reader: &mut (impl Read + BufRead)) -> ReplayResult<ReplayHeader> {
572    let mut buf = Vec::new();
573    parse_header_with_buf(reader, &mut buf)
574}
575
576fn parse_header_with_buf(
577    reader: &mut (impl Read + BufRead),
578    buf: &mut Vec<u8>,
579) -> ReplayResult<ReplayHeader> {
580    // Format will be very close to "Supreme Commander v1.50.3701"
581    let scfa_version = reader.read_string_with_capacity(28)?;
582    // Skip the string "\r\n\x00"
583    // Even though we expect exactly this string, we still allow for longer strings just in case.
584    reader.read_string()?;
585    let version_and_mapname = reader.read_string()?;
586    let mut version_and_mapname = version_and_mapname.splitn(2, "\r\n");
587    let replay_version = match version_and_mapname.next() {
588        Some(s) => s.to_string(),
589        None => return Err(ReplayReadError::Malformed("missing replay version")),
590    };
591    let map_file = match version_and_mapname.next() {
592        Some(s) => s.to_string(),
593        None => return Err(ReplayReadError::Malformed("missing map name")),
594    };
595    // Skip the string "\r\n\x1a\x00"
596    reader.read_string()?;
597
598    let mods_size = reader.read_u32_le()? as usize;
599    reader.read_exact_to_vec(mods_size, buf)?;
600    let mods = (&buf[..mods_size]).read_lua_object()?;
601
602    let scenario_size = reader.read_u32_le()? as usize;
603    reader.read_exact_to_vec(scenario_size, buf)?;
604    let scenario = (&buf[..scenario_size]).read_lua_object()?;
605    let num_sources = reader.read_u8()? as usize;
606
607    let mut players = OrderedHashMap::new();
608    for _ in 0..num_sources {
609        let name = reader.read_string()?;
610        let player_id = reader.read_i32_le()?;
611        players.insert(name, player_id);
612    }
613
614    let cheats_enabled = reader.read_bool()?;
615    let army_count = reader.read_u8()? as usize;
616
617    let mut armies = OrderedHashMap::new();
618    for _ in 0..army_count {
619        let player_data_size = reader.read_u32_le()? as usize;
620        reader.read_exact_to_vec(player_data_size, buf)?;
621        let player_data = (&buf[..player_data_size]).read_lua_object()?;
622        let player_source = reader.read_u8()?;
623        armies.insert(player_source, player_data);
624
625        if player_source != 255 {
626            reader.skip(1)?;
627        }
628    }
629
630    let seed = reader.read_u32_le()?;
631
632    Ok(ReplayHeader {
633        scfa_version,
634        replay_version,
635        map_file,
636        mods,
637        scenario,
638        players,
639        cheats_enabled,
640        armies,
641        seed,
642    })
643}
644
645/// Parse replay command stream
646fn parse_body_with_callback<V: Version>(
647    reader: &mut impl Read,
648    options: &ParserOptions<V>,
649    callback: impl Fn(&mut SimData, &V::Command) -> ReplayResult<()>,
650    buf: &mut Vec<u8>,
651) -> ReplayResult<ReplayBody<V>> {
652    let mut commands = Vec::new();
653    let mut sim = SimData::new();
654
655    while options.limit.is_none() || commands.len() < options.limit.unwrap() {
656        match parse_command(reader, options, buf) {
657            Err(ReplayReadError::IO(ref e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
658                break
659            }
660            Err(e) => return Err(e),
661            Ok(opt) => {
662                if let Some(cmd) = opt {
663                    let result = callback(&mut sim, &cmd);
664
665                    // Deciding not to combine these with #![feature(let_chains)]
666                    if let Err(ReplayReadError::Desynced(_)) = result {
667                        if options.stop_on_desync {
668                            result?;
669                        }
670                    } else {
671                        result?;
672                    }
673
674                    if options.save_commands {
675                        commands.push(cmd);
676                    }
677                }
678            }
679        }
680    }
681
682    Ok(ReplayBody { commands, sim })
683}
684
685fn parse_command_frame<V: Version>(
686    reader: &mut (impl Read + BufRead),
687) -> ReplayResult<ReplayCommandFrame<V>> {
688    let (command_id, size) = parse_command_frame_header::<V>(reader)?;
689    let data = reader.vec_read_exact((size - 3) as usize)?;
690
691    Ok(ReplayCommandFrame {
692        command_id,
693        size,
694        data,
695    })
696}
697
698fn parse_command_frame_header<V: Version>(
699    reader: &mut impl Read,
700) -> ReplayResult<(V::CommandId, u16)> {
701    let command_id = reader.read_u8()?;
702    let command_id = match V::CommandId::try_from(command_id) {
703        Err(_) => return Err(ReplayReadError::Malformed("invalid command")),
704        Ok(c) => c,
705    };
706
707    let size = reader.read_u16_le()?;
708    if size < 3 {
709        return Err(ReplayReadError::Malformed("invalid command size"));
710    }
711
712    Ok((command_id, size))
713}
714
715/// Parses one command
716///
717/// Packet structure in bytestream
718/// ```text
719///     4   7      7      7
720///     TLLDTLLDDDDTLLDDDDTLLDDDD
721///     =========================
722/// ```
723/// Where:
724/// ```text
725///     T - byte - defines command type
726///     L - short - defines command length of T + L + D
727///     D - variable length - binary data of size `command length`, may be empty
728/// ```
729pub fn parse_command<V: Version>(
730    reader: &mut impl Read,
731    ctx: &ParserOptions<V>,
732    buf: &mut Vec<u8>,
733) -> ReplayResult<Option<V::Command>> {
734    let (command_id, size) = parse_command_frame_header::<V>(reader)?;
735    let len = size as usize - 3;
736
737    // Pull command data from the reader.
738    reader.read_exact_to_vec(len, buf)?;
739
740    if !ctx.commands.contains(&command_id) {
741        return Ok(None);
742    }
743
744    Ok(Some(V::Command::parse_command_data(
745        command_id.as_u8(),
746        &buf[..len],
747    )?))
748}
749
750#[cfg(test)]
751mod tests {
752    use super::*;
753    use pretty_assertions::assert_eq;
754
755    use crate::lua::LuaObject;
756    use crate::scfa::replay::*;
757    use crate::scfa::SCFA;
758    use std::io::Write;
759
760    #[test]
761    fn parse_issue_command() {
762        let mut data: &[u8] = &[
763            // cmd header -- |  -- entities len -- |   -- entity id: 0 --  |   -- command id  ...
764            0x0C, 0x40, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
765            //  |      -- arg1 --       | type |      -- arg2 --      | ttype|     -- x (f32) ...
766            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x00, 0x60, 0x28,
767            //-- |    -- y (f32)  --    |     -- z (f32) --     | arg3 | -- formation (i32) -- |
768            0x44, 0x00, 0x70, 0x95, 0x41, 0x00, 0x40, 0xA9, 0x43, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
769            //             -- blueprint id --             |        -- arg4 --     | -- arg5   ...
770            0x75, 0x72, 0x62, 0x30, 0x31, 0x30, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
771            // arg5 -- |       -- arg6 --      | Nil |
772            0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x04, 0x00, 0x01,
773        ];
774        let ctx = ParserOptions::<SCFA> {
775            commands: [ReplayCommandId::try_from(replay_command::ISSUE_COMMAND).unwrap()]
776                .iter()
777                .cloned()
778                .collect(),
779            limit: None,
780            save_commands: true,
781            stop_on_desync: true,
782        };
783
784        let issue_command = parse_command(&mut data, &ctx, &mut vec![])
785            .unwrap()
786            .unwrap();
787
788        // I'm so glad that Rust doesn't implement Eq for floats
789        let command = GameCommand {
790            entity_ids: vec![0],
791            id: 0,
792            coordinated_attack_cmd_id: -1,
793            type_: game_command::BUILD_MOBILE,
794            arg2: -1,
795            target: Target::Position(Position {
796                x: 0.0,
797                y: 0.0,
798                z: 0.0,
799            }),
800            arg3: 0,
801            formation: None,
802            blueprint: "urb0101".to_string(),
803            arg4: 0,
804            arg5: 1,
805            arg6: 1,
806            upgrades: LuaObject::Nil,
807            clear_queue: None,
808        };
809        match issue_command {
810            ReplayCommand::IssueCommand(issue_command) => {
811                assert_eq!(command.entity_ids, issue_command.entity_ids);
812                assert_eq!(command.id, issue_command.id);
813                assert_eq!(
814                    command.coordinated_attack_cmd_id,
815                    issue_command.coordinated_attack_cmd_id
816                );
817                assert_eq!(command.type_, issue_command.type_);
818                assert_eq!(command.arg2, issue_command.arg2);
819                assert_eq!(command.arg3, issue_command.arg3);
820                assert_eq!(command.blueprint, issue_command.blueprint);
821                assert_eq!(command.arg4, issue_command.arg4);
822                assert_eq!(command.arg5, issue_command.arg5);
823                assert_eq!(command.arg6, issue_command.arg6);
824                assert_eq!(command.upgrades, issue_command.upgrades);
825                assert_eq!(command.clear_queue, issue_command.clear_queue);
826            }
827            _ => panic!("Wrong command type produced"),
828        }
829    }
830
831    #[test]
832    fn parse_lua_sim_callback() {
833        let mut data: &[u8] = &[
834            0x16, 0x1A, 0x00, 0x43, 0x6C, 0x65, 0x61, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,
835            0x73, 0x00, 0x04, 0x05, 0x01, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x40, 0x00,
836        ];
837        let ctx = ParserOptions::<SCFA> {
838            commands: [ReplayCommandId::try_from(replay_command::LUA_SIM_CALLBACK).unwrap()]
839                .iter()
840                .cloned()
841                .collect(),
842            limit: None,
843            save_commands: true,
844            stop_on_desync: true,
845        };
846
847        let command = parse_command(&mut data, &ctx, &mut vec![])
848            .unwrap()
849            .unwrap();
850
851        match command {
852            ReplayCommand::LuaSimCallback {
853                func,
854                args,
855                selection,
856            } => {
857                assert_eq!(func, "ClearTargets");
858                assert!(args.as_hashmap().unwrap().is_empty());
859                assert_eq!(selection, vec![0x4000E0]);
860            }
861            _ => panic!("Wrong command type produced"),
862        }
863    }
864
865    #[test]
866    fn test_parser_init() {
867        let _ = Parser::<SCFA>::new();
868    }
869
870    #[test]
871    fn test_stream_init() {
872        let stream = StreamParser::<SCFA>::new();
873
874        assert_eq!(stream.header().is_none(), true);
875        assert_eq!(stream.body().is_none(), true);
876    }
877
878    #[test]
879    fn test_stream_parse_command() {
880        let mut stream = StreamParser::<SCFA>::new();
881
882        assert_eq!(stream.has_frame().unwrap(), false);
883
884        // Advance is a default command
885        stream.feed(&[0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00]);
886
887        assert_eq!(stream.has_frame().unwrap(), true);
888        assert_eq!(
889            stream.parse_command().unwrap(),
890            Some(ReplayCommand::Advance { ticks: 1 })
891        );
892        assert_eq!(stream.has_frame().unwrap(), false);
893    }
894
895    #[test]
896    fn test_stream_feed_reader() {
897        let mut stream = StreamParser::<SCFA>::new();
898        let mut data: &[u8] = &[0x0, 0x07];
899
900        // Feed from one type of `Read`
901        assert_eq!(stream.feed_reader(&mut data, 10).unwrap(), 2);
902
903        // Feed from another reader
904        let mut cur = Cursor::new(vec![0x00, 0x01, 0x00]);
905        assert_eq!(stream.feed_reader(&mut cur, 1).unwrap(), 1);
906        assert_eq!(stream.feed_reader(&mut cur, 10).unwrap(), 2);
907
908        // Some more bytes from the same reader again
909        cur.write(&[0x00, 0x00]).unwrap();
910        cur.set_position(cur.position() - 2);
911        assert_eq!(stream.feed_reader(&mut cur, 10).unwrap(), 2);
912
913        assert_eq!(
914            stream.parse_command().unwrap(),
915            Some(ReplayCommand::Advance { ticks: 1 })
916        );
917    }
918}