livesplit_core/run/parser/
llanfair_gered.rs

1//! Provides the parser for splits files used by Gered's Llanfair fork.
2
3use core::mem::MaybeUninit;
4
5#[cfg(feature = "std")]
6use crate::util::byte_parsing::big_endian::strip_u32;
7#[cfg(feature = "std")]
8use crate::util::xml::helper::text_as_str_err;
9use crate::{
10    platform::prelude::*,
11    util::xml::{
12        helper::{
13            end_tag, optional_attribute_escaped_err, parse_base, parse_children, single_child,
14            text, text_err, text_parsed, Error as XmlError,
15        },
16        Reader,
17    },
18    RealTime, Run, Segment, Time, TimeSpan,
19};
20#[cfg(feature = "std")]
21use image::{codecs::png, ColorType, ImageEncoder};
22#[cfg(feature = "std")]
23use snafu::OptionExt;
24
25/// The Error type for splits files that couldn't be parsed by the Llanfair (Gered)
26/// Parser.
27#[derive(Debug, snafu::Snafu)]
28#[snafu(context(suffix(false)))]
29pub enum Error {
30    /// The underlying XML format couldn't be parsed.
31    Xml {
32        /// The underlying error.
33        source: XmlError,
34    },
35    /// Failed to parse an integer.
36    Int {
37        /// The underlying error.
38        source: core::num::ParseIntError,
39    },
40    /// The length of a buffer was too large.
41    LengthOutOfBounds,
42    /// Failed to parse an image.
43    Image,
44}
45
46impl From<XmlError> for Error {
47    fn from(source: XmlError) -> Self {
48        Self::Xml { source }
49    }
50}
51
52impl From<core::num::ParseIntError> for Error {
53    fn from(source: core::num::ParseIntError) -> Self {
54        Self::Int { source }
55    }
56}
57
58/// The Result type for the Llanfair (Gered) Parser.
59pub type Result<T> = core::result::Result<T, Error>;
60
61// FIXME: Generalized Type Ascription (GTA 6)
62#[inline]
63const fn type_hint<T>(v: Result<T>) -> Result<T> {
64    v
65}
66
67fn time_span<F>(reader: &mut Reader<'_>, f: F) -> Result<()>
68where
69    F: FnOnce(TimeSpan),
70{
71    text_err(reader, |text| {
72        let milliseconds = text.parse::<i64>()?;
73        f(TimeSpan::from_milliseconds(milliseconds as f64));
74        Ok(())
75    })
76}
77
78fn time<F>(reader: &mut Reader<'_>, f: F) -> Result<()>
79where
80    F: FnOnce(Time),
81{
82    time_span(reader, |t| f(RealTime(Some(t)).into()))
83}
84
85#[cfg(feature = "std")]
86fn image<F>(
87    reader: &mut Reader<'_>,
88    raw_buf: &mut Vec<MaybeUninit<u8>>,
89    png_buf: &mut Vec<u8>,
90    mut f: F,
91) -> Result<()>
92where
93    F: FnMut(&[u8]),
94{
95    single_child(reader, "ImageIcon", |reader, _| {
96        let (width, height, image) = text_as_str_err::<_, _, Error>(reader, |t| {
97            let src = t.as_bytes();
98
99            raw_buf.resize(
100                base64_simd::STANDARD.estimated_decoded_length(src.len()),
101                MaybeUninit::uninit(),
102            );
103
104            let decoded = base64_simd::STANDARD
105                .decode(src, base64_simd::Out::from_uninit_slice(raw_buf))
106                .map_err(|_| Error::Image)?;
107
108            let (width, height);
109            let mut cursor = decoded.get(0xD1..).ok_or(Error::Image)?;
110            height = strip_u32(&mut cursor).ok_or(Error::Image)?;
111            width = strip_u32(&mut cursor).ok_or(Error::Image)?;
112
113            let len = (width as usize)
114                .checked_mul(height as usize)
115                .and_then(|b| b.checked_mul(4))
116                .context(LengthOutOfBounds)?;
117
118            Ok((
119                width,
120                height,
121                decoded.get(0xFE..0xFE + len).ok_or(Error::Image)?,
122            ))
123        })?;
124
125        png_buf.clear();
126        png::PngEncoder::new(&mut *png_buf)
127            .write_image(image, width, height, ColorType::Rgba8)
128            .map_err(|_| Error::Image)?;
129
130        f(png_buf);
131
132        Ok(())
133    })
134}
135
136fn parse_segment(
137    total_time: &mut TimeSpan,
138    reader: &mut Reader<'_>,
139    _raw_buf: &mut Vec<MaybeUninit<u8>>,
140    _png_buf: &mut Vec<u8>,
141) -> Result<Segment> {
142    single_child(reader, "Segment", |reader, _| {
143        single_child(reader, "default", |reader, _| {
144            let mut segment = Segment::new("");
145            let mut defer_setting_run_time = false;
146
147            parse_children(reader, |reader, tag, attributes| match tag.name() {
148                "name" => text(reader, |t| segment.set_name(t)),
149                "bestTime" => single_child(reader, "milliseconds", |reader, _| {
150                    time(reader, |t| segment.set_best_segment_time(t))
151                }),
152                "runTime" => {
153                    type_hint(optional_attribute_escaped_err(
154                        attributes,
155                        "reference",
156                        |reference| {
157                            if reference == "../bestTime" {
158                                defer_setting_run_time = true;
159                            }
160                            Ok(())
161                        },
162                    ))?;
163                    if !defer_setting_run_time {
164                        single_child(reader, "milliseconds", |reader, _| {
165                            time_span(reader, |t| {
166                                *total_time += t;
167                            })
168                        })?;
169                        segment.set_personal_best_split_time(RealTime(Some(*total_time)).into());
170                        Ok(())
171                    } else {
172                        end_tag(reader)
173                    }
174                }
175                #[cfg(feature = "std")]
176                "icon" => image(reader, _raw_buf, _png_buf, |i| segment.set_icon(i)),
177                _ => end_tag(reader),
178            })?;
179
180            if defer_setting_run_time {
181                *total_time += segment.best_segment_time().real_time.ok_or(Error::Xml {
182                    source: XmlError::ElementNotFound,
183                })?;
184                segment.set_personal_best_split_time(RealTime(Some(*total_time)).into());
185            }
186
187            Ok(segment)
188        })
189    })
190}
191
192/// Attempts to parse a splits file used by Gered's Llanfair fork.
193pub fn parse(source: &str) -> Result<Run> {
194    let reader = &mut Reader::new(source);
195
196    let mut raw_buf = Vec::new();
197    let mut png_buf = Vec::new();
198
199    let mut run = Run::new();
200
201    parse_base(reader, "Run", |reader, _| {
202        single_child(reader, "Run", |reader, _| {
203            single_child(reader, "default", |reader, _| {
204                parse_children(reader, |reader, tag, _| match tag.name() {
205                    "name" => text(reader, |t| run.set_game_name(t)),
206                    "subTitle" => text(reader, |t| run.set_category_name(t)),
207                    "delayedStart" => time_span(reader, |t| run.set_offset(TimeSpan::zero() - t)),
208                    "numberOfAttempts" => text_parsed(reader, |t| run.set_attempt_count(t)),
209                    "segments" => {
210                        let mut total_time = TimeSpan::zero();
211                        parse_children(reader, |reader, _, _| {
212                            let segment =
213                                parse_segment(&mut total_time, reader, &mut raw_buf, &mut png_buf)?;
214                            run.push_segment(segment);
215                            Ok(())
216                        })
217                    }
218                    _ => end_tag(reader),
219                })
220            })
221        })
222    })?;
223
224    Ok(run)
225}