use core::mem::MaybeUninit;
#[cfg(feature = "std")]
use crate::util::byte_parsing::big_endian::strip_u32;
#[cfg(feature = "std")]
use crate::util::xml::helper::text_as_str_err;
use crate::{
platform::prelude::*,
util::xml::{
helper::{
end_tag, optional_attribute_escaped_err, parse_base, parse_children, single_child,
text, text_err, text_parsed, Error as XmlError,
},
Reader,
},
RealTime, Run, Segment, Time, TimeSpan,
};
#[cfg(feature = "std")]
use image::{codecs::png, ColorType, ImageEncoder};
#[cfg(feature = "std")]
use snafu::OptionExt;
#[derive(Debug, snafu::Snafu)]
#[snafu(context(suffix(false)))]
pub enum Error {
Xml {
source: XmlError,
},
Int {
source: core::num::ParseIntError,
},
LengthOutOfBounds,
Image,
}
impl From<XmlError> for Error {
fn from(source: XmlError) -> Self {
Self::Xml { source }
}
}
impl From<core::num::ParseIntError> for Error {
fn from(source: core::num::ParseIntError) -> Self {
Self::Int { source }
}
}
pub type Result<T> = core::result::Result<T, Error>;
#[inline]
const fn type_hint<T>(v: Result<T>) -> Result<T> {
v
}
fn time_span<F>(reader: &mut Reader<'_>, f: F) -> Result<()>
where
F: FnOnce(TimeSpan),
{
text_err(reader, |text| {
let milliseconds = text.parse::<i64>()?;
f(TimeSpan::from_milliseconds(milliseconds as f64));
Ok(())
})
}
fn time<F>(reader: &mut Reader<'_>, f: F) -> Result<()>
where
F: FnOnce(Time),
{
time_span(reader, |t| f(RealTime(Some(t)).into()))
}
#[cfg(feature = "std")]
fn image<F>(
reader: &mut Reader<'_>,
raw_buf: &mut Vec<MaybeUninit<u8>>,
png_buf: &mut Vec<u8>,
mut f: F,
) -> Result<()>
where
F: FnMut(&[u8]),
{
single_child(reader, "ImageIcon", |reader, _| {
let (width, height, image) = text_as_str_err::<_, _, Error>(reader, |t| {
let src = t.as_bytes();
raw_buf.resize(
base64_simd::STANDARD.estimated_decoded_length(src.len()),
MaybeUninit::uninit(),
);
let decoded = base64_simd::STANDARD
.decode(src, base64_simd::Out::from_uninit_slice(raw_buf))
.map_err(|_| Error::Image)?;
let (width, height);
let mut cursor = decoded.get(0xD1..).ok_or(Error::Image)?;
height = strip_u32(&mut cursor).ok_or(Error::Image)?;
width = strip_u32(&mut cursor).ok_or(Error::Image)?;
let len = (width as usize)
.checked_mul(height as usize)
.and_then(|b| b.checked_mul(4))
.context(LengthOutOfBounds)?;
Ok((
width,
height,
decoded.get(0xFE..0xFE + len).ok_or(Error::Image)?,
))
})?;
png_buf.clear();
png::PngEncoder::new(&mut *png_buf)
.write_image(image, width, height, ColorType::Rgba8)
.map_err(|_| Error::Image)?;
f(png_buf);
Ok(())
})
}
fn parse_segment(
total_time: &mut TimeSpan,
reader: &mut Reader<'_>,
_raw_buf: &mut Vec<MaybeUninit<u8>>,
_png_buf: &mut Vec<u8>,
) -> Result<Segment> {
single_child(reader, "Segment", |reader, _| {
single_child(reader, "default", |reader, _| {
let mut segment = Segment::new("");
let mut defer_setting_run_time = false;
parse_children(reader, |reader, tag, attributes| match tag.name() {
"name" => text(reader, |t| segment.set_name(t)),
"bestTime" => single_child(reader, "milliseconds", |reader, _| {
time(reader, |t| segment.set_best_segment_time(t))
}),
"runTime" => {
type_hint(optional_attribute_escaped_err(
attributes,
"reference",
|reference| {
if reference == "../bestTime" {
defer_setting_run_time = true;
}
Ok(())
},
))?;
if !defer_setting_run_time {
single_child(reader, "milliseconds", |reader, _| {
time_span(reader, |t| {
*total_time += t;
})
})?;
segment.set_personal_best_split_time(RealTime(Some(*total_time)).into());
Ok(())
} else {
end_tag(reader)
}
}
#[cfg(feature = "std")]
"icon" => image(reader, _raw_buf, _png_buf, |i| segment.set_icon(i)),
_ => end_tag(reader),
})?;
if defer_setting_run_time {
*total_time += segment.best_segment_time().real_time.ok_or(Error::Xml {
source: XmlError::ElementNotFound,
})?;
segment.set_personal_best_split_time(RealTime(Some(*total_time)).into());
}
Ok(segment)
})
})
}
pub fn parse(source: &str) -> Result<Run> {
let reader = &mut Reader::new(source);
let mut raw_buf = Vec::new();
let mut png_buf = Vec::new();
let mut run = Run::new();
parse_base(reader, "Run", |reader, _| {
single_child(reader, "Run", |reader, _| {
single_child(reader, "default", |reader, _| {
parse_children(reader, |reader, tag, _| match tag.name() {
"name" => text(reader, |t| run.set_game_name(t)),
"subTitle" => text(reader, |t| run.set_category_name(t)),
"delayedStart" => time_span(reader, |t| run.set_offset(TimeSpan::zero() - t)),
"numberOfAttempts" => text_parsed(reader, |t| run.set_attempt_count(t)),
"segments" => {
let mut total_time = TimeSpan::zero();
parse_children(reader, |reader, _, _| {
let segment =
parse_segment(&mut total_time, reader, &mut raw_buf, &mut png_buf)?;
run.push_segment(segment);
Ok(())
})
}
_ => end_tag(reader),
})
})
})
})?;
Ok(run)
}