use crate::sync_helpers::OnceCell;
use binrw::{BinRead, BinResult, BinWrite, Endian};
use std::{fmt::Debug, io::SeekFrom};
#[derive(Default, PartialEq, Eq)]
pub struct PosMarker<T> {
pub pos: OnceCell<u64>,
pub value: T,
}
impl<T> PosMarker<T> {
pub fn new(value: T) -> Self {
Self {
pos: OnceCell::new(),
value,
}
}
pub fn seek_from(&self, offset: u64) -> SeekFrom {
SeekFrom::Start(self.pos.get().unwrap() + offset)
}
fn get_pos(&self) -> binrw::BinResult<u64> {
let value = self.pos.get().ok_or(binrw::error::Error::Custom {
pos: 0,
err: Box::new("PosMarker has not been written to yet"),
})?;
Ok(*value)
}
}
impl<T> PosMarker<T>
where
T: Into<u64> + Copy,
{
pub fn try_seek_relative(&self, zero_check: bool) -> BinResult<SeekFrom> {
let pos = SeekFrom::Start(self.get_pos()? + self.value.into());
Ok(if !zero_check || Into::<u64>::into(self.value) > 0 {
pos
} else {
SeekFrom::Current(0)
})
}
pub fn seek_relative(&self, zero_check: bool) -> SeekFrom {
self.try_seek_relative(zero_check).unwrap()
}
}
impl<T> BinRead for PosMarker<T>
where
T: BinRead,
{
type Args<'a> = T::Args<'a>;
fn read_options<R: binrw::io::Read + binrw::io::Seek>(
reader: &mut R,
endian: binrw::Endian,
args: Self::Args<'_>,
) -> BinResult<Self> {
let pos = reader.stream_position()?;
T::read_options(reader, endian, args).map(|value| Self {
pos: OnceCell::from(pos),
value,
})
}
}
impl<T> BinWrite for PosMarker<T>
where
T: BinWrite<Args<'static> = ()> + Default,
{
type Args<'a> = ();
fn write_options<W: binrw::io::Write + binrw::io::Seek>(
&self,
writer: &mut W,
endian: binrw::Endian,
args: Self::Args<'_>,
) -> BinResult<()> {
self.pos
.set(writer.stream_position()?)
.map_err(|_| binrw::error::Error::Custom {
pos: writer.stream_position().unwrap(),
err: Box::new("PosMarker has already been written to"),
})?;
T::default().write_options(writer, endian, args)
}
}
impl<T> PosMarker<T>
where
T: BinWrite<Args<'static> = ()> + TryFrom<u64>,
T::Error: binrw::error::CustomError + 'static,
{
pub fn write_back<V, W>(&self, value: V, writer: &mut W, endian: Endian) -> BinResult<()>
where
V: TryInto<T> + std::fmt::Debug,
W: binrw::io::Write + binrw::io::Seek,
{
let return_to = writer.stream_position()?;
writer.seek(SeekFrom::Start(self.get_pos()?))?;
value
.try_into()
.map_err(|_| binrw::error::Error::Custom {
pos: self.get_pos().unwrap(),
err: Box::new("Error converting value to T"),
})?
.write_options(writer, endian, ())?;
writer.seek(SeekFrom::Start(return_to))?;
Ok(())
}
#[inline]
#[binrw::writer(writer, endian)]
fn write_hero<V, S, B>(
value: &V,
write_size_to: Option<&Self>,
write_offset_to: Option<&PosMarker<S>>,
offset_relative_to: Option<&PosMarker<B>>,
value_args: V::Args<'_>,
) -> BinResult<()>
where
V: BinWrite,
S: BinWrite<Args<'static> = ()> + TryFrom<u64>,
S::Error: binrw::error::CustomError + 'static,
{
let start_offset = writer.stream_position()?;
match write_offset_to {
Some(write_offset_at) => {
let base_offset_val = match offset_relative_to {
Some(offset_base) => offset_base.get_pos()?,
None => 0,
};
let offset_to_write = start_offset - base_offset_val;
write_offset_at.write_back(offset_to_write, writer, endian)?;
}
None => (),
};
value.write_options(writer, endian, value_args)?;
let total_size = writer.stream_position()? - start_offset;
match write_size_to {
Some(write_size_to) => write_size_to.write_back(total_size, writer, endian)?,
None => (),
};
Ok(())
}
#[binrw::writer(writer, endian)]
pub fn write_roff<U>(value: &U, write_offset_to: &Self) -> BinResult<()>
where
U: BinWrite<Args<'static> = ()>,
{
let no_size: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(no_size, Some(write_offset_to), Some(write_offset_to), ()),
)
}
pub fn write_offset<W>(&self, writer: &mut W, endian: Endian) -> BinResult<u64>
where
W: binrw::io::Write + binrw::io::Seek,
{
let stream_position = writer.stream_position()?;
self.write_back(stream_position, writer, endian)?;
Ok(stream_position)
}
#[binrw::writer(writer, endian)]
pub fn write_roff_b<U, B>(
value: &U,
write_offset_to: &Self,
offset_relative_to: &PosMarker<B>,
) -> BinResult<()>
where
U: BinWrite<Args<'static> = ()>,
{
let no_size: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(no_size, Some(write_offset_to), Some(offset_relative_to), ()),
)
}
#[binrw::writer(writer, endian)]
pub fn write_roff_size_b<U, S, B>(
value: &U,
write_offset_to: &PosMarker<S>,
write_size_to: &Self,
offset_relative_to: &PosMarker<B>,
) -> BinResult<()>
where
U: BinWrite<Args<'static> = ()>,
S: BinWrite<Args<'static> = ()> + TryFrom<u64>,
S::Error: binrw::error::CustomError + 'static,
{
Self::write_hero(
value,
writer,
endian,
(
Some(write_size_to),
Some(write_offset_to),
Some(offset_relative_to),
(),
),
)
}
#[binrw::writer(writer, endian)]
pub fn write_roff_size_ba<U, B, S>(
value: &U,
write_offset_to: &Self,
write_size_to: &PosMarker<S>,
offset_relative_to: &PosMarker<B>,
value_args: U::Args<'_>,
) -> BinResult<()>
where
U: BinWrite,
S: BinWrite<Args<'static> = ()> + TryFrom<u64>,
S::Error: binrw::error::CustomError + 'static,
{
PosMarker::<S>::write_hero(
value,
writer,
endian,
(
Some(write_size_to),
Some(write_offset_to),
Some(offset_relative_to),
value_args,
),
)
}
#[binrw::writer(writer, endian)]
pub fn write_aoff<U>(value: &U, write_offset_to: &Self) -> BinResult<()>
where
U: BinWrite<Args<'static> = ()>,
{
let no_size: Option<&PosMarker<T>> = None;
let no_base: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(no_size, Some(write_offset_to), no_base, ()),
)
}
#[binrw::writer(writer, endian)]
pub fn write_aoff_m<U>(value: &U, write_offset_to: Option<&Self>) -> BinResult<()>
where
U: BinWrite<Args<'static> = ()>,
{
let no_size: Option<&PosMarker<T>> = None;
let no_base: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(no_size, write_offset_to, no_base, ()),
)
}
#[binrw::writer(writer, endian)]
pub fn write_aoff_a<U>(
value: &U,
write_offset_to: &Self,
value_args: U::Args<'_>,
) -> BinResult<()>
where
U: BinWrite,
{
let no_size: Option<&PosMarker<T>> = None;
let no_base: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(no_size, Some(write_offset_to), no_base, value_args),
)
}
#[binrw::writer(writer, endian)]
pub fn write_roff_size<U, S>(
value: &U,
write_offset_to: &PosMarker<S>,
write_size_to: &Self,
) -> BinResult<()>
where
U: BinWrite<Args<'static> = ()>,
S: BinWrite<Args<'static> = ()> + TryFrom<u64>,
S::Error: binrw::error::CustomError + 'static,
{
let no_base: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(Some(write_size_to), Some(write_offset_to), no_base, ()),
)
}
#[binrw::writer(writer, endian)]
pub fn write_roff_size_a<U, S>(
value: &U,
write_offset_to: &PosMarker<S>,
write_size_to: &Self,
value_args: U::Args<'_>,
) -> BinResult<()>
where
U: BinWrite,
S: BinWrite<Args<'static> = ()> + TryFrom<u64>,
S::Error: binrw::error::CustomError + 'static,
{
let no_base: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(
Some(write_size_to),
Some(write_offset_to),
no_base,
value_args,
),
)
}
#[binrw::writer(writer, endian)]
pub fn write_aoff_size_a<U, S>(
value: &U,
write_offset_to: &PosMarker<S>,
write_size_to: &Self,
value_args: U::Args<'_>,
) -> BinResult<()>
where
U: BinWrite,
S: BinWrite<Args<'static> = ()> + TryFrom<u64>,
S::Error: binrw::error::CustomError + 'static,
{
let no_base: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(
Some(write_size_to),
Some(write_offset_to),
no_base,
value_args,
),
)
}
#[binrw::writer(writer, endian)]
pub fn write_aoff_size<U, S>(
value: &U,
write_offset_to: &PosMarker<S>,
write_size_to: &Self,
) -> BinResult<()>
where
U: BinWrite<Args<'static> = ()>,
S: BinWrite<Args<'static> = ()> + TryFrom<u64>,
S::Error: binrw::error::CustomError + 'static,
{
let no_base: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(Some(write_size_to), Some(write_offset_to), no_base, ()),
)
}
#[binrw::writer(writer, endian)]
pub fn write_size<U>(value: &U, write_size_to: &Self) -> BinResult<()>
where
U: BinWrite<Args<'static> = ()>,
{
let no_offset: Option<&PosMarker<T>> = None;
Self::write_hero(
value,
writer,
endian,
(Some(write_size_to), no_offset, no_offset, ()),
)
}
}
impl<T> Debug for PosMarker<T>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PosMarker")
.field("pos", &self.pos)
.field("value", &self.value)
.finish()
}
}