use std::fs::File;
use std::io::{BufReader, Read, Seek};
use std::path::Path;
use crate::offset::OffsetReader;
use crate::sector::SectorMode;
use crate::{cue, mds, nrg, toc, IsoError};
pub trait ReadSeek: Read + Seek {}
impl<T: Read + Seek> ReadSeek for T {}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Box<dyn ReadSeek>, IsoError> {
let path = path.as_ref();
let ext = path.extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase);
match ext.as_deref() {
Some("nrg") => open_nrg(path),
Some("mds") => open_mds(path),
Some("toc") => open_toc(path),
Some("cue") => open_plain(&resolve_cue_bin(path)?),
Some("ccd") => open_plain(&resolve_ccd_img(path)?),
_ => open_plain(path),
}
}
fn open_plain(path: &Path) -> Result<Box<dyn ReadSeek>, IsoError> {
Ok(Box::new(BufReader::new(File::open(path)?)))
}
fn open_nrg(path: &Path) -> Result<Box<dyn ReadSeek>, IsoError> {
let mut f = File::open(path)?;
let image = nrg::parse(&mut f)?;
let track = image.data_track().ok_or_else(|| {
IsoError::BadDescriptor(format!("no data track in NRG {}", path.display()))
})?;
Ok(Box::new(OffsetReader::new(BufReader::new(f), track.start_offset, track.size)?))
}
fn open_mds(path: &Path) -> Result<Box<dyn ReadSeek>, IsoError> {
let mut desc = File::open(path)?;
let image = mds::parse(&mut desc)?;
let track = image.data_track().ok_or_else(|| {
IsoError::BadDescriptor(format!("no data track in MDS {}", path.display()))
})?;
let mdf = File::open(path.with_extension("mdf"))?;
Ok(Box::new(OffsetReader::new(BufReader::new(mdf), track.start_offset, track.data_size())?))
}
fn open_toc(path: &Path) -> Result<Box<dyn ReadSeek>, IsoError> {
let text = std::fs::read_to_string(path)?;
let sheet = toc::parse(&text);
let track = sheet.data_track().ok_or_else(|| {
IsoError::BadDescriptor(format!("no data track in TOC {}", path.display()))
})?;
let datafile = track.datafile.as_deref().ok_or_else(|| {
IsoError::BadDescriptor(format!("TOC data track has no DATAFILE: {}", path.display()))
})?;
let data_path = path.parent().unwrap_or_else(|| Path::new(".")).join(datafile);
let f = File::open(&data_path)?;
let file_len = f.metadata().map(|m| m.len()).unwrap_or(0);
let avail = file_len.saturating_sub(track.file_offset);
let sector_size = track.mode.sector_mode().map_or(2352, SectorMode::physical_sector_size);
let len = if track.length_sectors > 0 {
(u64::from(track.length_sectors) * sector_size).min(avail)
} else {
avail
};
Ok(Box::new(OffsetReader::new(BufReader::new(f), track.file_offset, len)?))
}
fn resolve_cue_bin(path: &Path) -> Result<std::path::PathBuf, IsoError> {
let text = std::fs::read_to_string(path)?;
let sheet = cue::parse(&text);
let (file_name, _track) = sheet.data_track().ok_or_else(|| {
IsoError::BadDescriptor(format!("no data track in CUE sheet {}", path.display()))
})?;
Ok(path.parent().unwrap_or_else(|| Path::new(".")).join(file_name))
}
fn resolve_ccd_img(path: &Path) -> Result<std::path::PathBuf, IsoError> {
let img = path.with_extension("img");
if img.is_file() {
Ok(img)
} else {
Err(IsoError::BadDescriptor(format!(
"no .img alongside CloneCD control file {}",
path.display()
)))
}
}