livesplit_core/run/parser/
llanfair_gered.rs1use 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#[derive(Debug, snafu::Snafu)]
28#[snafu(context(suffix(false)))]
29pub enum Error {
30 Xml {
32 source: XmlError,
34 },
35 Int {
37 source: core::num::ParseIntError,
39 },
40 LengthOutOfBounds,
42 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
58pub type Result<T> = core::result::Result<T, Error>;
60
61#[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
192pub 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}