vault 11.0.1

A parser for Company of Heroes replay files.
Documentation
use crate::data::chunks::{Chunk, Chunk::DataData, Header, TrashDataChunk};
use crate::data::parser::parse_utf8_variable;
use crate::data::{ParserResult, Player, Span};
use byteorder::{LittleEndian, ReadBytesExt};
use nom::bytes::complete::{tag, take, take_while};
use nom::character::{is_digit, is_hex_digit};
use nom::combinator::{cut, map, map_parser, peek, verify};
use nom::multi::{length_count, length_data, length_value, many_m_n, many_till};
use nom::number::complete::{le_u32, le_u64};
use nom::sequence::{separated_pair, tuple};
use nom_tracable::tracable_parser;
use uuid::Uuid;

#[derive(Debug)]
pub struct Option {
    _name: String,
    _value: u32,
}

impl Option {
    #[tracable_parser]
    pub fn parse_option(input: Span) -> ParserResult<Option> {
        map(
            tuple((parse_utf8_variable(le_u32), le_u32)),
            |((_, name), value)| Option {
                _name: name,
                _value: value,
            },
        )(input)
    }
}

#[derive(Debug)]
pub struct DataDataChunk {
    _header: Header,
    _opponent_type: u32,
    pub players: Vec<Player>,
    pub skirmish: bool,
    pub matchhistory_id: u64,
    _options: Vec<Option>,
    pub mod_uuid: Uuid,
    _unknown_number: u32,
}

impl DataDataChunk {
    #[tracable_parser]
    pub fn parse(input: Span, header: Header) -> ParserResult<Chunk> {
        if header.version == 1 {
            return TrashDataChunk::parse(input, header);
        }

        cut(map_parser(
            take(header.length),
            map(
                tuple((
                    Self::parse_opponent_type,
                    take(6u32),
                    Self::parse_players(header.clone()),
                    length_data(le_u32),
                    Self::parse_skirmish_flag,
                    le_u64,
                    take(24u32),
                    Self::parse_options,
                    take(12u32),
                    Self::parse_mod_info,
                )),
                |(
                    opponent_type,
                    _,
                    players,
                    _,
                    skirmish,
                    matchhistory_id,
                    _,
                    options,
                    _,
                    (mod_uuid, unknown_number),
                )| {
                    DataData(DataDataChunk {
                        _header: header.clone(),
                        _opponent_type: opponent_type,
                        players,
                        skirmish,
                        matchhistory_id,
                        _options: options,
                        mod_uuid,
                        _unknown_number: unknown_number,
                    })
                },
            ),
        ))(input)
    }

    #[tracable_parser]
    fn parse_opponent_type(input: Span) -> ParserResult<u32> {
        le_u32(input)
    }

    fn parse_players(header: Header) -> impl FnMut(Span) -> ParserResult<Vec<Player>> {
        move |input: Span| length_count(le_u32, Player::parse_player(header.clone()))(input)
    }

    #[tracable_parser]
    fn parse_options(input: Span) -> ParserResult<Vec<Option>> {
        map(
            many_till(
                Option::parse_option,
                map_parser(
                    peek(take(12u32)),
                    many_m_n(3, 3, verify(le_u32, |n| *n == 0 || *n == 1)),
                ),
            ),
            |(options, _)| options,
        )(input)
    }

    #[tracable_parser]
    fn parse_skirmish_flag(input: Span) -> ParserResult<bool> {
        map(parse_utf8_variable(le_u32), |(_, id)| !id.is_empty())(input)
    }

    #[tracable_parser]
    fn parse_mod_info(input: Span) -> ParserResult<(Uuid, u32)> {
        length_value(
            le_u32,
            map(
                separated_pair(take_while(is_hex_digit), tag(":"), take_while(is_digit)),
                |(mod_uuid, unknown_number): (Span, Span)| {
                    (
                        Uuid::try_parse_ascii(&mod_uuid).unwrap(),
                        unknown_number
                            .into_fragment()
                            .read_u32::<LittleEndian>()
                            .unwrap(),
                    )
                },
            ),
        )(input)
    }
}