use std::io::{Cursor, Seek};
use binrw::helpers::{count, until_eof};
use binrw::{binrw, BinRead, BinResult, BinWrite, Endian};
use crate::spec::KnownSize;
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct EFH {
pub efid: EFHID,
pub data_size: u16,
}
impl KnownSize for EFH {
const SIZE: usize = 4;
}
#[binrw]
#[brw(little)]
#[brw(repr = u16)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EFHID {
EI64,
IZUC,
IZUF,
Other(u16),
}
impl EFHID {
const KNOWN: &'static [(u16, EFHID)] = &[
(0x0001, Self::EI64),
(0x6375, Self::IZUC),
(0x7075, Self::IZUF),
];
}
impl From<u16> for EFHID {
fn from(value: u16) -> Self {
match Self::KNOWN.iter().find(|(tag, _)| *tag == value) {
Some((_, id)) => *id,
None => Self::Other(value),
}
}
}
impl From<&EFHID> for u16 {
fn from(id: &EFHID) -> Self {
match id {
EFHID::Other(v) => *v,
known => EFHID::KNOWN.iter().find(|(_, id)| id == known).map(|(tag, _)| *tag).unwrap(),
}
}
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct EF {
pub efh: EFH,
#[br(parse_with = ef_data, args(efh.efid, efh.data_size))]
pub efd: EFD,
}
#[derive(Clone, Debug)]
pub enum EFD {
EI64(EI64),
IZUF(IZUF),
IZUC(IZUC),
Unknown(Vec<u8>),
}
#[binrw]
#[brw(little)]
#[br(import(size: u16))]
#[derive(Clone, Debug)]
pub struct EI64 {
pub uncompressed_size: u64,
pub compressed_size: u64,
#[br(if(size >= 24))]
pub relative_offset: Option<u64>,
#[br(if(size >= 28))]
pub disk_number_start: Option<u32>,
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct IZUCH {
pub version: u8,
pub crc32: u32,
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct IZUC {
pub izuch: IZUCH,
#[br(parse_with = until_eof, try_map = String::from_utf8)]
#[bw(map = |comment: &String| comment.as_bytes().to_vec())]
pub comment: String,
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct IZUF {
pub izuch: IZUCH,
#[br(parse_with = until_eof, try_map = String::from_utf8)]
#[bw(map = |file_name: &String| file_name.as_bytes().to_vec())]
pub insecure_file_name: String,
}
#[binrw::parser(reader, endian)]
pub(crate) fn efs(len: u64) -> BinResult<Vec<EF>> {
let end = reader.stream_position()? + len;
let mut fields = Vec::new();
while reader.stream_position()? < end {
fields.push(EF::read_options(reader, endian, ())?);
}
let position = reader.stream_position()?;
if position != end {
return Err(binrw::Error::AssertFail {
pos: position,
message: format!("extra field overran its block by {} byte(s)", position - end),
});
}
Ok(fields)
}
#[binrw::parser(reader, endian)]
fn ef_data(tag: EFHID, size: u16) -> BinResult<EFD> {
let raw: Vec<u8> = count(size.into())(reader, endian, ())?;
Ok(match tag {
EFHID::EI64 => {
EFD::EI64(EI64::read_options(&mut Cursor::new(&raw), endian, (size,))?)
},
EFHID::IZUC => {
EFD::IZUC(IZUC::read_options(&mut Cursor::new(&raw), endian, ())?)
}
EFHID::IZUF => {
EFD::IZUF(IZUF::read_options(&mut Cursor::new(&raw), endian, ())?)
}
EFHID::Other(_) => EFD::Unknown(raw),
})
}
impl BinWrite for EFD {
type Args<'a> = ();
fn write_options<W: binrw::io::Write + Seek>(&self, writer: &mut W, endian: Endian, _: ()) -> BinResult<()> {
match self {
Self::EI64(field) => field.write_options(writer, endian, ()),
Self::IZUF(field) => field.write_options(writer, endian, ()),
Self::IZUC(field) => field.write_options(writer, endian, ()),
Self::Unknown(raw) => raw.write_options(writer, endian, ()),
}
}
}