iso9660_forensic/
opener.rs1use std::fs::File;
13use std::io::{BufReader, Read, Seek};
14use std::path::Path;
15
16use crate::offset::OffsetReader;
17use crate::sector::SectorMode;
18use crate::{cue, mds, nrg, toc, IsoError};
19
20pub trait ReadSeek: Read + Seek {}
23impl<T: Read + Seek> ReadSeek for T {}
24
25pub fn open<P: AsRef<Path>>(path: P) -> Result<Box<dyn ReadSeek>, IsoError> {
32 let path = path.as_ref();
33 let ext = path.extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase);
34 match ext.as_deref() {
35 Some("nrg") => open_nrg(path),
36 Some("mds") => open_mds(path),
37 Some("toc") => open_toc(path),
38 Some("cue") => open_plain(&resolve_cue_bin(path)?),
39 Some("ccd") => open_plain(&resolve_ccd_img(path)?),
40 _ => open_plain(path),
41 }
42}
43
44fn open_plain(path: &Path) -> Result<Box<dyn ReadSeek>, IsoError> {
45 Ok(Box::new(BufReader::new(File::open(path)?)))
46}
47
48fn open_nrg(path: &Path) -> Result<Box<dyn ReadSeek>, IsoError> {
50 let mut f = File::open(path)?;
51 let image = nrg::parse(&mut f)?;
52 let track = image.data_track().ok_or_else(|| {
53 IsoError::BadDescriptor(format!("no data track in NRG {}", path.display()))
54 })?;
55 Ok(Box::new(OffsetReader::new(BufReader::new(f), track.start_offset, track.size)?))
56}
57
58fn open_mds(path: &Path) -> Result<Box<dyn ReadSeek>, IsoError> {
60 let mut desc = File::open(path)?;
61 let image = mds::parse(&mut desc)?;
62 let track = image.data_track().ok_or_else(|| {
63 IsoError::BadDescriptor(format!("no data track in MDS {}", path.display()))
64 })?;
65 let mdf = File::open(path.with_extension("mdf"))?;
66 Ok(Box::new(OffsetReader::new(BufReader::new(mdf), track.start_offset, track.data_size())?))
67}
68
69fn open_toc(path: &Path) -> Result<Box<dyn ReadSeek>, IsoError> {
71 let text = std::fs::read_to_string(path)?;
72 let sheet = toc::parse(&text);
73 let track = sheet.data_track().ok_or_else(|| {
74 IsoError::BadDescriptor(format!("no data track in TOC {}", path.display()))
75 })?;
76 let datafile = track.datafile.as_deref().ok_or_else(|| {
77 IsoError::BadDescriptor(format!("TOC data track has no DATAFILE: {}", path.display()))
78 })?;
79 let data_path = path.parent().unwrap_or_else(|| Path::new(".")).join(datafile);
80 let f = File::open(&data_path)?;
81 let file_len = f.metadata().map(|m| m.len()).unwrap_or(0);
82 let avail = file_len.saturating_sub(track.file_offset);
83 let sector_size = track.mode.sector_mode().map_or(2352, SectorMode::physical_sector_size);
84 let len = if track.length_sectors > 0 {
85 (u64::from(track.length_sectors) * sector_size).min(avail)
86 } else {
87 avail
88 };
89 Ok(Box::new(OffsetReader::new(BufReader::new(f), track.file_offset, len)?))
90}
91
92fn resolve_cue_bin(path: &Path) -> Result<std::path::PathBuf, IsoError> {
94 let text = std::fs::read_to_string(path)?;
95 let sheet = cue::parse(&text);
96 let (file_name, _track) = sheet.data_track().ok_or_else(|| {
97 IsoError::BadDescriptor(format!("no data track in CUE sheet {}", path.display()))
98 })?;
99 Ok(path.parent().unwrap_or_else(|| Path::new(".")).join(file_name))
100}
101
102fn resolve_ccd_img(path: &Path) -> Result<std::path::PathBuf, IsoError> {
104 let img = path.with_extension("img");
105 if img.is_file() {
106 Ok(img)
107 } else {
108 Err(IsoError::BadDescriptor(format!(
109 "no .img alongside CloneCD control file {}",
110 path.display()
111 )))
112 }
113}