vault 11.0.1

A parser for Company of Heroes replay files.
Documentation
use crate::data::chunks::Header;
use crate::data::parser::{parse_utf16_variable, parse_utf8_variable};
use crate::data::Item;
use crate::data::{ParserResult, Span};
use nom::bytes::complete::take;
use nom::combinator::{cond, cut, map};
use nom::multi::length_count;
use nom::number::complete::{le_u32, le_u64, le_u8};
use nom::sequence::tuple;
use nom::IResult;
use nom_tracable::tracable_parser;

#[derive(Debug)]
pub struct Player {
    pub id: u32,
    pub human: u8,
    pub name: String,
    pub team: u32,
    pub faction: String,
    _ai_type: String,
    pub steam_id: String,
    pub profile_id: u64,
    _items: Vec<Item>,
}

impl Player {
    pub fn parse_player(header: Header) -> impl FnMut(Span) -> ParserResult<Player> {
        move |input: Span| {
            let (input, player) = cut(map(
                tuple((
                    le_u8,
                    Self::parse_name,
                    Self::parse_team,
                    le_u32,
                    take(1u32),
                    Self::parse_faction,
                    take(8u32),
                    Self::parse_ai,
                    take(40u32),
                    le_u64,
                    take(1u32),
                    Self::parse_steam_id,
                    take(18u32),
                )),
                |(human, name, team, id, _, faction, _, ai_type, _, profile_id, _, steam_id, _)| {
                    Player {
                        id,
                        human,
                        name,
                        team,
                        faction,
                        _ai_type: ai_type,
                        steam_id,
                        profile_id,
                        _items: vec![],
                    }
                },
            ))(input)?;

            let (input, items) = Self::parse_items(input, &player)?;

            // players in this chunk version or later have an extra 4 bytes between them for some reason
            let (input, _) = cond(header.version >= 4595383, take(4u32))(input)?;

            Ok((
                input,
                Player {
                    _items: items,
                    ..player
                },
            ))
        }
    }

    #[tracable_parser]
    fn parse_name(input: Span) -> ParserResult<String> {
        let (input, (_, name)) = parse_utf16_variable(le_u32)(input)?;
        Ok((input, name))
    }
    #[tracable_parser]
    fn parse_team(input: Span) -> ParserResult<u32> {
        le_u32(input)
    }
    #[tracable_parser]
    fn parse_faction(input: Span) -> ParserResult<String> {
        let (input, (_, faction)) = parse_utf8_variable(le_u32)(input)?;
        Ok((input, faction))
    }
    #[tracable_parser]
    fn parse_ai(input: Span) -> ParserResult<String> {
        let (input, (_, ai)) = parse_utf8_variable(le_u32)(input)?;
        Ok((input, ai))
    }
    #[tracable_parser]
    fn parse_steam_id(input: Span) -> ParserResult<String> {
        let (input, (_, steam_id)) = parse_utf16_variable(le_u32)(input)?;
        Ok((input, steam_id))
    }

    fn item_parser_for(player: &Player) -> impl FnMut(Span) -> ParserResult<Item> {
        if player.human == 0 {
            Item::parse_cpu_item
        } else {
            Item::parse_player_item
        }
    }

    #[tracable_parser]
    fn parse_items<'a>(input: Span<'a>, player: &Player) -> IResult<Span<'a>, Vec<Item>> {
        cut(map(
            tuple((
                length_count(le_u32, Self::item_parser_for(player)),
                take(4u32),
                length_count(le_u32, Self::item_parser_for(player)),
            )),
            |(mut battlegroup_items, _, mut cosmetic_items)| {
                battlegroup_items.append(&mut cosmetic_items);
                battlegroup_items
            },
        ))(input)
    }
}