poe_data_tools 1.0.0

A library for working with Path of Exile game data
Documentation
use anyhow::anyhow;
use winnow::{
    Parser,
    binary::{le_f32, le_i32, le_u8, le_u32, le_u64, length_repeat},
    combinator::{cond, dispatch, empty, fail},
    error::ContextError,
};

use super::types::*;
use crate::file_parsers::{VersionedResult, VersionedResultExt, shared::winnow::WinnowParser};

fn connection<'a>(poe_version: u32) -> impl WinnowParser<&'a [u8], Connection> {
    winnow::trace!(
        "connection",
        (
            le_u32, //
            cond(poe_version == 2, le_i32),
        )
            .map(|(passive_id, curvature)| Connection {
                passive_id,
                curvature,
            })
    )
}

fn passive<'a>(poe_version: u32) -> impl WinnowParser<&'a [u8], Passive> {
    winnow::trace!(
        "passive",
        (
            le_u32,
            le_i32,
            le_u32,
            length_repeat(le_u32, connection(poe_version)),
        )
            .map(|(id, orbit, orbit_position, connections)| Passive {
                id,
                orbit,
                orbit_position,
                connections,
            })
    )
}

fn group<'a>(poe_version: u32) -> impl WinnowParser<&'a [u8], Group> {
    winnow::trace!(
        "group",
        (
            le_f32,
            le_f32,
            le_u32,
            le_u32,
            le_u8,
            length_repeat(le_u32, passive(poe_version)),
        )
            .map(|(x, y, flags, unk1, unk2, passives)| Group {
                x,
                y,
                flags,
                unk1,
                unk2,
                passives,
            })
    )
}

pub fn parse_psg_bytes(mut contents: &[u8], poe_version: u32) -> VersionedResult<PSGFile> {
    let version = le_u8
        .parse_next(&mut contents)
        .map_err(|e: ContextError| anyhow!("Failed to parse file: {e:?}"))?;

    let mut parser = (
        le_u8,
        length_repeat(le_u8, le_u8),
        length_repeat(
            le_u32,
            dispatch! {
                empty.value(poe_version);
                1 => le_u32.map(u64::from),
                2 => le_u64,
                _ => fail,
            },
        ),
        length_repeat(le_u32, group(poe_version)),
    )
        .map(
            |(graph_type, passives_per_orbit, root_passives, groups)| PSGFile {
                version,
                root_passives,
                groups,
                graph_type,
                passives_per_orbit,
            },
        );

    parser
        .parse(contents)
        .map_err(|e| anyhow!("Failed to parse file: {e:?}"))
        .with_version(Some(version as u32))
}