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_i8, le_u8, le_u16, le_u32, length_take},
    combinator::{cond, dispatch, empty, fail, repeat, seq},
    error::ContextError,
    token::take,
};

use super::types::*;
use crate::file_parsers::{
    VersionedResult, VersionedResultExt,
    dolm::{
        parser::{dolm, index_buffer},
        types::DolmVertex,
    },
    shared::winnow::{WinnowParser, le_f16, repeat_array, take_array},
};

fn v3_section<'a>() -> impl WinnowParser<&'a [u8], (u8, Section, [f32; 6])> {
    let parser = |input: &mut &[u8]| -> winnow::Result<_> {
        let (vertex_format, num_shapes, _bytes_name_section, bbox, dolm) =
            (le_u8, le_u16, le_u32, repeat_array(le_f32), dolm()).parse_next(input)?;

        let name_lengths: Vec<_> = repeat(num_shapes as usize, le_u32).parse_next(input)?;

        let shape_names = name_lengths
            .into_iter()
            .map(|len| take(len).try_map(String::from_utf16le).parse_next(input))
            .collect::<winnow::Result<_>>()?;

        Ok((
            vertex_format,
            Section::V3(V3Section { dolm, shape_names }),
            bbox,
        ))
    };

    winnow::trace!("v3_section", parser)
}

struct V2ShapeExtents {
    name_length: u32,
    triangle_index: u32,
}

fn vertex<'a>(vertex_format: u8) -> impl WinnowParser<&'a [u8], DolmVertex> {
    let parser = seq!(DolmVertex {
        pos: repeat_array(le_f32),
        normal: repeat_array(le_i8),
        tangent: repeat_array(le_i8),
        tex_coord0: repeat_array(le_f16).map(Some),
        skin_bones: take_array().map(Some),
        skin_weights: take_array().map(Some),
        skin_extra: cond((vertex_format >> 1) & 1 == 1, take_array()),
        tex_coord1: cond(vertex_format & 1 == 1, repeat_array(le_f16)),
        tail_float: empty::<_, ContextError>.value(None),
        extra_vformat_6: empty::<_, ContextError>.value(None),
    });

    winnow::trace!("vertex", parser)
}

fn v2_section<'a>(version: u8) -> impl WinnowParser<&'a [u8], (u8, Section, [f32; 6])> {
    let parser = move |input: &mut &[u8]| -> winnow::Result<_> {
        let (
            num_triangles,
            num_vertices,
            vertex_format,
            num_shapes,
            _bytes_name_section,
            bbox,
            c04_2,
        ) = (
            le_u32,
            le_u32,
            le_u8,
            le_u16,
            le_u32,
            repeat_array(le_f32),
            cond(version == 2, le_u32),
        )
            .parse_next(input)?;

        let shape_extents: Vec<_> = repeat(
            num_shapes as usize,
            (le_u32, le_u32).map(|(name_length, triangle_index)| V2ShapeExtents {
                name_length,
                triangle_index,
            }),
        )
        .parse_next(input)?;

        let shape_extents = shape_extents
            .into_iter()
            .map(|s| {
                let name = take(s.name_length)
                    .try_map(String::from_utf16le)
                    .parse_next(input)?;

                Ok(ShapeExtents {
                    name,
                    triangle_index: s.triangle_index,
                })
            })
            .collect::<winnow::Result<Vec<_>>>()?;

        let index_buffer = index_buffer(num_vertices, num_triangles).parse_next(input)?;

        let vertex_buffer =
            repeat(num_vertices as usize, vertex(vertex_format)).parse_next(input)?;

        Ok((
            vertex_format,
            Section::V2(V2Section {
                c04_2,
                shape_extents,
                index_buffer,
                vertex_buffer,
            }),
            bbox,
        ))
    };

    winnow::trace!("v2_section", parser)
}

fn v2_tail<'a>(version: u8) -> impl WinnowParser<&'a [u8], Tail> {
    let parser = move |input: &mut &[u8]| {
        let [
            num_ellipsoids,
            num_skinned_vertices,
            num_sv_refs1,
            num_sv_refs2,
        ] = repeat_array(le_u32).parse_next(input)?;

        let (skinned_vertices, sv_refs1, ellipsoids, sv_refs2) = (
            repeat(num_skinned_vertices as usize, skinned_vertex()),
            repeat(num_sv_refs1 as usize, le_u32),
            repeat(num_ellipsoids as usize, ellipsoid(version)),
            repeat(num_sv_refs2 as usize, le_u32),
        )
            .parse_next(input)?;

        Ok(Tail {
            tail_version: 2,
            ellipsoids,
            spheres: vec![],
            sphere_connections: vec![],
            skinned_vertices,
            t3s: vec![],
            sv_refs1,
            sv_refs2,
        })
    };

    winnow::trace!("v2_tail", parser)
}

fn v3_tail<'a>(version: u8) -> impl WinnowParser<&'a [u8], Tail> {
    let parser = move |input: &mut &[u8]| {
        let [
            num_ellipsoids,
            num_spheres,
            num_sphere_connections,
            num_t3s,
            num_skinned_vertices,
            num_sv_refs1,
            num_sv_refs2,
        ] = repeat_array(le_u32).parse_next(input)?;

        let (
            ellipsoids,
            spheres,
            sphere_connections,
            t3s,
            skinned_vertices, //
            sv_refs1,
            sv_refs2,
        ) = (
            repeat(num_ellipsoids as usize, ellipsoid(version)),
            repeat(num_spheres as usize, sphere(version)),
            repeat(num_sphere_connections as usize, sphere_connection()),
            repeat(num_t3s as usize, empty),
            repeat(num_skinned_vertices as usize, skinned_vertex()),
            repeat(num_sv_refs1 as usize, le_u32),
            repeat(num_sv_refs2 as usize, le_u32),
        )
            .parse_next(input)?;

        Ok(Tail {
            tail_version: 3,
            ellipsoids,
            spheres,
            sphere_connections,
            skinned_vertices,
            t3s,
            sv_refs1,
            sv_refs2,
        })
    };

    winnow::trace!("v3_tail", parser)
}

fn v4_tail<'a>(version: u8) -> impl WinnowParser<&'a [u8], Tail> {
    let parser = move |input: &mut &[u8]| {
        let [
            num_ellipsoids,
            num_spheres,
            num_sphere_connections,
            num_t3s,
            num_skinned_vertices,
            num_sv_refs1,
            num_sv_refs2,
        ] = repeat_array(le_u32).parse_next(input)?;

        let (
            skinned_vertices, //
            sv_refs1,
            ellipsoids,
            spheres,
            sphere_connections,
            t3s,
            sv_refs2,
        ) = (
            repeat(num_skinned_vertices as usize, skinned_vertex()),
            repeat(num_sv_refs1 as usize, le_u32),
            repeat(num_ellipsoids as usize, ellipsoid(version)),
            repeat(num_spheres as usize, sphere(version)),
            repeat(num_sphere_connections as usize, sphere_connection()),
            repeat(num_t3s as usize, empty),
            repeat(num_sv_refs2 as usize, le_u32),
        )
            .parse_next(input)?;

        Ok(Tail {
            tail_version: 4,
            ellipsoids,
            spheres,
            sphere_connections,
            skinned_vertices,
            t3s,
            sv_refs1,
            sv_refs2,
        })
    };

    winnow::trace!("v4_tail", parser)
}

fn ellipsoid<'a>(version: u8) -> impl WinnowParser<&'a [u8], Ellipsoid> {
    let parser = seq!(Ellipsoid {
        floats: repeat_array(le_f32),
        unk1: le_u32,
        name: cond(
            version >= 3,
            length_take(le_u32).try_map(String::from_utf16le)
        )
    });

    winnow::trace!("ellipsoid", parser)
}

fn skinned_vertex<'a>() -> impl WinnowParser<&'a [u8], SkinnedVertex> {
    let parser = seq!(SkinnedVertex {
        pos: repeat_array(le_f32),
        unk1: repeat_array(le_u32),
        unk2: repeat_array(le_f32),
    });

    winnow::trace!("skinned_vertex", parser)
}

fn sphere<'a>(version: u8) -> impl WinnowParser<&'a [u8], Sphere> {
    let parser = seq!(Sphere {
        centre: repeat_array(le_f32),
        radius: le_f32,
        unk1: le_u32,
        name: cond(
            version >= 3,
            length_take(le_u32).try_map(String::from_utf16le)
        )
    });

    winnow::trace!("sphere", parser)
}

fn sphere_connection<'a>() -> impl WinnowParser<&'a [u8], SphereConnection> {
    let parser = seq!(SphereConnection {
        s0_index: le_u32,
        s1_index: le_u32,
    });

    winnow::trace!("sphere_connection", parser)
}

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

    let ((vertex_format, section, bbox), tail) = (
        dispatch! {
            empty.value(version);
            ..3 => v2_section(version),
            3.. => v3_section(),
        },
        dispatch! {
            le_u32;
            2 => v2_tail(version),
            3 => v3_tail(version),
            4 => v4_tail(version),
            _ => fail,
        },
    )
        .parse_next(&mut contents)
        .map_err(|e: ContextError| anyhow!("Failed to parse file: {e:?}"))
        .with_version(Some(version as u32))?;

    Ok(SMDFile {
        version,
        vertex_format,
        section,
        bbox,
        tail,
    })
    .with_version(Some(version as u32))
}