pub mod entry;
pub mod filesystem;
pub mod iso9660;
pub mod rockridge;
pub mod hfs;
pub mod hfsplus;
pub mod mac_alias;
pub mod efs;
pub mod ufs;
pub mod ods2;
pub mod udf;
pub mod nod_fs;
pub mod cdi;
pub mod opera;
pub mod xdvdfs;
pub use cdi::CdiFilesystem;
pub use efs::EfsFilesystem;
pub use entry::{EntryType, FileEntry};
pub use filesystem::{Filesystem, FilesystemError};
pub use hfs::HfsFilesystem;
pub use hfsplus::HfsPlusFilesystem;
pub use iso9660::Iso9660Filesystem;
pub use nod_fs::NodeFilesystem;
pub use ods2::Ods2Filesystem;
pub use opera::OperaFilesystem;
pub use udf::UdfFilesystem;
pub use ufs::UfsFilesystem;
pub use xdvdfs::XdvdfsFilesystem;
use crate::detect::DiscImageInfo;
use crate::error::OpticaldiscsError;
use crate::formats::{DiscFormat, FilesystemType};
use crate::sector_reader::SectorReader;
pub fn open_disc_filesystem(info: &DiscImageInfo) -> Result<Box<dyn Filesystem>, FilesystemError> {
if matches!(
info.filesystem,
FilesystemType::GameCube | FilesystemType::Wii
) {
return Ok(Box::new(NodeFilesystem::new(&info.path)?));
}
let mut reader = open_sector_reader(info)?;
match info.filesystem {
FilesystemType::Iso9660 | FilesystemType::HighSierra => {
Ok(Box::new(Iso9660Filesystem::new(reader)?))
}
FilesystemType::Efs => {
let partition_offset = info.efs_partition_offset.ok_or_else(|| {
FilesystemError::InvalidData(
"EFS detected but partition offset not recorded".into(),
)
})?;
Ok(Box::new(EfsFilesystem::new(reader, partition_offset)?))
}
FilesystemType::Ufs => Ok(Box::new(UfsFilesystem::new(reader)?)),
FilesystemType::Ods2 => Ok(Box::new(Ods2Filesystem::new(reader)?)),
FilesystemType::Udf => Ok(Box::new(UdfFilesystem::new(reader)?)),
FilesystemType::Cdi => Ok(Box::new(CdiFilesystem::new(reader)?)),
FilesystemType::Opera => Ok(Box::new(OperaFilesystem::new(reader)?)),
FilesystemType::Xdvdfs => Ok(Box::new(XdvdfsFilesystem::new(reader)?)),
FilesystemType::Hfs | FilesystemType::HfsPlus => {
let raw_offset = crate::apm::find_hfs_partition_offset(reader.as_mut()).unwrap_or(0);
let (resolved_fs, resolved_offset) =
crate::detect::resolve_apple_hfs(reader.as_mut(), raw_offset);
match resolved_fs {
FilesystemType::Hfs => Ok(Box::new(HfsFilesystem::new(reader, raw_offset)?)),
FilesystemType::HfsPlus => {
Ok(Box::new(HfsPlusFilesystem::new(reader, resolved_offset)?))
}
_ => Err(FilesystemError::Unsupported),
}
}
_ => Err(FilesystemError::Unsupported),
}
}
fn open_sector_reader(info: &DiscImageInfo) -> Result<Box<dyn SectorReader>, FilesystemError> {
let path = &info.path;
match info.format {
DiscFormat::Iso => {
let reader = crate::sector_reader::IsoSectorReader::new(path).map_err(disc_err)?;
Ok(Box::new(reader))
}
DiscFormat::BinCue => {
let cue_path = resolve_cue_path(path)?;
let tracks = crate::bincue::parse_cue_tracks(&cue_path)
.map_err(|e| FilesystemError::InvalidData(e.to_string()))?;
let data_track = tracks
.iter()
.find(|t| t.is_data())
.ok_or_else(|| FilesystemError::InvalidData("no data track in CUE sheet".into()))?
.clone();
let reader =
crate::sector_reader::BinCueSectorReader::open(&data_track).map_err(disc_err)?;
Ok(Box::new(reader))
}
DiscFormat::Chd => {
let chd_info = crate::chd::open_chd(path).map_err(disc_err)?;
if chd_info.is_gdrom() {
let hd_tracks = chd_info.find_gdrom_hd_tracks();
if !hd_tracks.is_empty() {
let mut gd_tracks = Vec::with_capacity(hd_tracks.len());
for hd in hd_tracks {
let reader = crate::sector_reader::ChdSectorReader::open(path, hd)
.map_err(disc_err)?;
gd_tracks.push(crate::sector_reader::GdromHdTrack {
start_lba: hd.frame_offset,
frame_count: hd.frames as u64,
reader: Box::new(reader),
});
}
return Ok(Box::new(
crate::sector_reader::GdromSectorReader::from_tracks(gd_tracks),
));
}
}
let track = chd_info
.find_first_data_track()
.ok_or(FilesystemError::Unsupported)?
.clone();
let reader =
crate::sector_reader::ChdSectorReader::open(path, &track).map_err(disc_err)?;
Ok(Box::new(reader))
}
DiscFormat::Gdi => crate::gdi::open_gdi_hd_reader(path).map_err(disc_err),
DiscFormat::Cso => {
let reader = crate::cso::CsoSectorReader::open(path).map_err(disc_err)?;
Ok(Box::new(reader))
}
DiscFormat::Gz => {
let reader = crate::gz::GzSectorReader::open(path).map_err(disc_err)?;
Ok(Box::new(reader))
}
DiscFormat::CloneCd => {
let tracks = crate::ccd::parse_ccd(path).map_err(disc_err)?;
let data_track = tracks
.iter()
.find(|t| t.is_data())
.ok_or_else(|| {
FilesystemError::InvalidData("no data track in .ccd descriptor".into())
})?
.clone();
let reader =
crate::sector_reader::BinCueSectorReader::open(&data_track).map_err(disc_err)?;
Ok(Box::new(reader))
}
DiscFormat::MdsMdf => {
let mds_path = crate::mds::resolve_mds_path(path).map_err(disc_err)?;
let tracks = crate::mds::parse_mds(&mds_path).map_err(disc_err)?;
let data = tracks.iter().find(|t| t.is_data).ok_or_else(|| {
FilesystemError::InvalidData("no data track in .mds descriptor".into())
})?;
let reader = crate::sector_reader::BinCueSectorReader::with_layout(
&data.data_path,
data.file_byte_offset,
data.physical_sector_size,
data.data_offset,
)
.map_err(disc_err)?;
Ok(Box::new(reader))
}
DiscFormat::Nrg => {
let tracks = crate::nrg::parse_nrg(path).map_err(disc_err)?;
let data = tracks.iter().find(|t| t.is_data).ok_or_else(|| {
FilesystemError::InvalidData("no data track in .nrg image".into())
})?;
let reader = crate::sector_reader::BinCueSectorReader::with_layout(
&data.data_path,
data.file_byte_offset,
data.physical_sector_size,
data.data_offset,
)
.map_err(disc_err)?;
Ok(Box::new(reader))
}
DiscFormat::DiscJuggler => {
let tracks = crate::discjuggler::parse_discjuggler(path).map_err(disc_err)?;
let data = tracks.iter().rev().find(|t| t.is_data).ok_or_else(|| {
FilesystemError::InvalidData("no data track in .cdi image".into())
})?;
let inner = crate::sector_reader::BinCueSectorReader::with_layout(
&data.data_path,
data.file_byte_offset,
data.physical_sector_size,
data.data_offset,
)
.map_err(disc_err)?;
Ok(Box::new(crate::sector_reader::RebaseSectorReader::new(
Box::new(inner),
data.base_lba,
)))
}
#[cfg(feature = "mdx")]
DiscFormat::Mdx => {
let tracks = crate::mdx::parse_mdx(path).map_err(disc_err)?;
let data = tracks.iter().find(|t| t.is_data).ok_or_else(|| {
FilesystemError::InvalidData("no data track in .mdx image".into())
})?;
let reader = crate::mdx::MdxSectorReader::open(data).map_err(disc_err)?;
Ok(Box::new(reader))
}
#[cfg(not(feature = "mdx"))]
DiscFormat::Mdx => Err(FilesystemError::Unsupported),
DiscFormat::Nintendo => Err(FilesystemError::Unsupported),
}
}
fn resolve_cue_path(path: &std::path::Path) -> Result<std::path::PathBuf, FilesystemError> {
let is_bin = path
.extension()
.and_then(|e| e.to_str())
.map(str::to_ascii_lowercase)
.as_deref()
== Some("bin");
if is_bin {
let stem = path.file_stem().unwrap_or_default();
let cue = path.with_file_name(format!("{}.cue", stem.to_string_lossy()));
if cue.exists() {
Ok(cue)
} else {
Err(FilesystemError::NotFound(format!(
"no matching .cue found for {}",
path.display()
)))
}
} else {
Ok(path.to_path_buf())
}
}
fn disc_err(e: OpticaldiscsError) -> FilesystemError {
match e {
OpticaldiscsError::Io(io_err) => FilesystemError::Io(io_err),
e => FilesystemError::InvalidData(e.to_string()),
}
}