use std::io::{self, BufRead, Read, Seek, SeekFrom};
use crate::sys;
use crate::{Chd, ChdError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Version {
V1,
V3,
V4,
V5,
Other(u32),
}
impl Version {
pub fn from_raw(v: u32) -> Self {
match v {
1 => Version::V1,
3 => Version::V3,
4 => Version::V4,
5 => Version::V5,
other => Version::Other(other),
}
}
pub fn raw(self) -> u32 {
match self {
Version::V1 => 1,
Version::V3 => 3,
Version::V4 => 4,
Version::V5 => 5,
Version::Other(v) => v,
}
}
}
impl Chd {
pub fn version_typed(&self) -> Version {
Version::from_raw(self.version())
}
pub fn hunks(&self) -> HunkIter<'_> {
HunkIter {
chd: self,
next: 0,
count: self.hunk_count(),
hunk_bytes: self.hunk_bytes() as usize,
}
}
pub fn metadata_iter(&self) -> MetadataIter<'_> {
MetadataIter { chd: self, next: 0 }
}
pub fn reader(&self) -> ChdReader<'_> {
ChdReader { chd: self, pos: 0 }
}
}
pub struct HunkIter<'a> {
chd: &'a Chd,
next: u32,
count: u32,
hunk_bytes: usize,
}
impl Iterator for HunkIter<'_> {
type Item = Result<Vec<u8>>;
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.count {
return None;
}
let hunknum = self.next;
self.next += 1;
let mut buf = vec![0u8; self.hunk_bytes];
Some(self.chd.read_hunk(hunknum, &mut buf).map(|_| buf))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.count - self.next) as usize;
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for HunkIter<'_> {}
#[derive(Debug, Clone)]
pub struct MetadataEntry {
pub tag: u32,
pub flags: u8,
pub data: Vec<u8>,
}
pub struct MetadataIter<'a> {
chd: &'a Chd,
next: u32,
}
impl Iterator for MetadataIter<'_> {
type Item = Result<MetadataEntry>;
fn next(&mut self) -> Option<Self::Item> {
let index = self.next;
let mut tag: u32 = 0;
let mut flags: u8 = 0;
let mut size: u32 = 0;
let err = unsafe {
sys::chd_shim_metadata_enum(
self.chd.raw(),
index,
&mut tag,
&mut flags,
std::ptr::null_mut(),
0,
&mut size,
)
};
match err {
ChdError::NoError => {}
ChdError::MetadataNotFound => return None,
other => return Some(Err(other)),
}
let mut data = vec![0u8; size as usize];
let err = unsafe {
sys::chd_shim_metadata_enum(
self.chd.raw(),
index,
&mut tag,
&mut flags,
data.as_mut_ptr() as *mut _,
size,
&mut size,
)
};
if err != ChdError::NoError {
return Some(Err(err));
}
self.next += 1;
Some(Ok(MetadataEntry { tag, flags, data }))
}
}
pub struct ChdReader<'a> {
chd: &'a Chd,
pos: u64,
}
impl ChdReader<'_> {
pub fn position(&self) -> u64 {
self.pos
}
}
impl Read for ChdReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let total = self.chd.logical_bytes();
if self.pos >= total {
return Ok(0);
}
let max = (total - self.pos).min(buf.len() as u64) as usize;
if max == 0 {
return Ok(0);
}
self.chd
.read_bytes(self.pos, &mut buf[..max])
.map_err(chd_err_to_io)?;
self.pos += max as u64;
Ok(max)
}
}
impl Seek for ChdReader<'_> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let total = self.chd.logical_bytes();
let new_pos: i128 = match pos {
SeekFrom::Start(p) => p as i128,
SeekFrom::End(off) => total as i128 + off as i128,
SeekFrom::Current(off) => self.pos as i128 + off as i128,
};
if new_pos < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"seek before start",
));
}
self.pos = new_pos as u64;
Ok(self.pos)
}
}
pub struct HunkReader {
data: Vec<u8>,
pos: usize,
}
impl HunkReader {
pub fn new(chd: &Chd, hunknum: u32) -> Result<Self> {
let mut data = vec![0u8; chd.hunk_bytes() as usize];
chd.read_hunk(hunknum, &mut data)?;
Ok(Self { data, pos: 0 })
}
pub fn into_inner(self) -> Vec<u8> {
self.data
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl Read for HunkReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let remaining = &self.data[self.pos..];
let n = remaining.len().min(buf.len());
buf[..n].copy_from_slice(&remaining[..n]);
self.pos += n;
Ok(n)
}
}
impl Seek for HunkReader {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let len = self.data.len() as i128;
let new_pos: i128 = match pos {
SeekFrom::Start(p) => p as i128,
SeekFrom::End(off) => len + off as i128,
SeekFrom::Current(off) => self.pos as i128 + off as i128,
};
if new_pos < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"seek before start",
));
}
self.pos = new_pos as usize;
Ok(self.pos as u64)
}
}
impl BufRead for HunkReader {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
Ok(&self.data[self.pos.min(self.data.len())..])
}
fn consume(&mut self, amt: usize) {
self.pos = (self.pos + amt).min(self.data.len());
}
}
pub mod metadata {
pub fn tag_to_chars(tag: u32) -> [char; 4] {
[
((tag >> 24) & 0xff) as u8 as char,
((tag >> 16) & 0xff) as u8 as char,
((tag >> 8) & 0xff) as u8 as char,
(tag & 0xff) as u8 as char,
]
}
pub mod tags {
use crate::make_tag;
pub const HARD_DISK_METADATA_TAG: u32 = make_tag(b'G', b'D', b'D', b'D');
pub const HARD_DISK_IDENT_METADATA_TAG: u32 = make_tag(b'I', b'D', b'N', b'T');
pub const HARD_DISK_KEY_METADATA_TAG: u32 = make_tag(b'K', b'E', b'Y', b' ');
pub const PCMCIA_CIS_METADATA_TAG: u32 = make_tag(b'C', b'I', b'S', b' ');
pub const CDROM_OLD_METADATA_TAG: u32 = make_tag(b'C', b'H', b'C', b'D');
pub const CDROM_TRACK_METADATA_TAG: u32 = make_tag(b'C', b'H', b'T', b'R');
pub const CDROM_TRACK_METADATA2_TAG: u32 = make_tag(b'C', b'H', b'T', b'2');
pub const GDROM_OLD_METADATA_TAG: u32 = make_tag(b'C', b'H', b'G', b'T');
pub const GDROM_TRACK_METADATA_TAG: u32 = make_tag(b'C', b'H', b'G', b'D');
pub const DVD_METADATA_TAG: u32 = make_tag(b'D', b'V', b'D', b' ');
pub const AV_METADATA_TAG: u32 = make_tag(b'A', b'V', b'A', b'V');
pub const AV_LD_METADATA_TAG: u32 = make_tag(b'A', b'V', b'L', b'D');
}
pub fn is_cdrom(tag: u32) -> bool {
tag == tags::CDROM_OLD_METADATA_TAG
|| tag == tags::CDROM_TRACK_METADATA_TAG
|| tag == tags::CDROM_TRACK_METADATA2_TAG
}
pub fn is_gdrom(tag: u32) -> bool {
tag == tags::GDROM_OLD_METADATA_TAG || tag == tags::GDROM_TRACK_METADATA_TAG
}
pub use crate::make_tag as make;
}
pub mod cdrom {
pub const CD_MAX_SECTOR_DATA: u32 = 2352;
pub const CD_MAX_SUBCODE_DATA: u32 = 96;
pub const CD_FRAME_SIZE: u32 = CD_MAX_SECTOR_DATA + CD_MAX_SUBCODE_DATA;
pub const CD_SYNC_NUM_BYTES: usize = 12;
pub const CD_SYNC_HEADER: [u8; CD_SYNC_NUM_BYTES] = [
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
];
pub const CD_SYNC_OFFSET: usize = 0x000;
pub const CD_MODE_OFFSET: usize = 0x00f;
}
impl Chd {
fn raw(&self) -> *mut sys::ChdFile {
self.inner
}
}
fn chd_err_to_io(err: ChdError) -> io::Error {
io::Error::other(format!("CHD error: {:?}", err))
}