use crate::error::{Error, Result};
use crate::make_tag;
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Cursor, Read, Seek, SeekFrom};
const METADATA_HEADER_SIZE: usize = 16;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
#[derive(FromPrimitive, Copy, Clone)]
#[repr(u32)]
pub enum KnownMetadata {
Wildcard = 0,
HardDisk = make_tag(b"GDDD"),
HardDiskIdent = make_tag(b"IDNT"),
HardDiskKey = make_tag(b"KEY "),
PcmciaCIS = make_tag(b"CIS "),
CdRomOld = make_tag(b"CHCD"),
CdRomTrack = make_tag(b"CHTR"),
CdRomTrack2 = make_tag(b"CHT2"),
GdRomOld = make_tag(b"CHGT"),
GdRomTrack = make_tag(b"CHGD"),
AudioVideo = make_tag(b"AVAV"),
AudioVideoLaserDisc = make_tag(b"AVLD"),
}
impl KnownMetadata {
pub fn is_cdrom(tag: u32) -> bool {
if let Some(tag) = FromPrimitive::from_u32(tag) {
return matches!(
tag,
KnownMetadata::CdRomOld
| KnownMetadata::CdRomTrack
| KnownMetadata::CdRomTrack2
| KnownMetadata::GdRomOld
| KnownMetadata::GdRomTrack
);
}
false
}
}
pub trait MetadataTag {
fn metatag(&self) -> u32;
}
impl MetadataTag for KnownMetadata {
fn metatag(&self) -> u32 {
*self as u32
}
}
#[derive(Debug)]
pub struct Metadata {
pub metatag: u32,
pub value: Vec<u8>,
pub flags: u8,
pub index: u32,
pub length: u32,
}
impl MetadataTag for Metadata {
fn metatag(&self) -> u32 {
self.metatag
}
}
#[derive(Clone)]
pub struct MetadataRef {
offset: u64,
length: u32,
flags: u8,
index: u32,
metatag: u32,
}
impl MetadataRef {
fn read_into<F: Read + Seek>(&self, file: &mut F, buf: &mut [u8]) -> Result<()> {
file.seek(SeekFrom::Start(self.offset + METADATA_HEADER_SIZE as u64))?;
file.read_exact(buf)?;
Ok(())
}
pub fn read<F: Read + Seek>(&self, file: &mut F) -> Result<Metadata> {
let mut buf = vec![0u8; self.length as usize];
self.read_into(file, &mut buf)?;
Ok(Metadata {
metatag: self.metatag,
value: buf,
flags: self.flags,
index: self.index,
length: self.length,
})
}
}
impl MetadataTag for MetadataRef {
#[inline(always)]
fn metatag(&self) -> u32 {
self.metatag
}
}
pub struct MetadataRefs<'a, F: Read + Seek + 'a> {
pub(crate) file: &'a mut F,
curr_offset: u64,
curr: Option<MetadataRef>,
indices: Vec<(u32, u32)>,
}
impl<'a, F: Read + Seek + 'a> MetadataRefs<'a, F> {
pub(crate) fn from_stream(file: &'a mut F, initial_offset: u64) -> Self {
MetadataRefs {
file,
curr_offset: initial_offset,
curr: None,
indices: Vec::new(),
}
}
pub(crate) fn dead(file: &'a mut F) -> Self {
MetadataRefs {
file,
curr_offset: 0,
curr: None,
indices: Vec::new(),
}
}
}
impl<'a, F: Read + Seek + 'a> TryFrom<MetadataRefs<'a, F>> for Vec<Metadata> {
type Error = Error;
fn try_from(mut value: MetadataRefs<'a, F>) -> std::result::Result<Self, Self::Error> {
let metas = &mut value;
let metas: Vec<_> = metas.collect();
metas.iter().map(|e| e.read(&mut value.file)).collect()
}
}
impl<'a, F: Read + Seek + 'a> Iterator for MetadataRefs<'a, F> {
type Item = MetadataRef;
fn next(&mut self) -> Option<Self::Item> {
if self.curr_offset == 0 {
return None;
}
fn next_inner<'a, F: Read + Seek + 'a>(s: &mut MetadataRefs<'a, F>) -> Result<MetadataRef> {
let mut raw_header: [u8; METADATA_HEADER_SIZE] = [0; METADATA_HEADER_SIZE];
s.file.seek(SeekFrom::Start(s.curr_offset))?;
let count = s.file.read(&mut raw_header)?;
if count != METADATA_HEADER_SIZE {
return Err(Error::MetadataNotFound);
}
let mut cursor = Cursor::new(raw_header);
cursor.seek(SeekFrom::Start(0))?;
let metatag = cursor.read_u32::<BigEndian>()?;
let length = cursor.read_u32::<BigEndian>()?;
let next = cursor.read_u64::<BigEndian>()?;
let flags = length >> 24;
let length = length & 0x00ffffff;
let mut index = 0;
for indice in s.indices.iter_mut() {
if indice.0 == metatag {
index = indice.1;
indice.1 += 1;
break;
}
}
if index == 0 {
s.indices.push((metatag, 1))
}
let new = MetadataRef {
offset: s.curr_offset,
length,
metatag,
flags: flags as u8,
index,
};
s.curr_offset = next;
s.curr = Some(new.clone());
Ok(new)
}
next_inner(self).ok()
}
}