use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;
use crate::error::{OpticaldiscsError, Result};
pub const SECTOR_SIZE: u64 = 2048;
pub const RAW_SECTOR_SIZE: u64 = 2352;
pub const MODE1_DATA_OFFSET: u64 = 16;
pub trait SectorReader: Send {
fn read_sector(&mut self, lba: u64) -> Result<Vec<u8>>;
fn read_bytes(&mut self, byte_offset: u64, length: usize) -> Result<Vec<u8>> {
let sector_lba = byte_offset / SECTOR_SIZE;
let sector_off = (byte_offset % SECTOR_SIZE) as usize;
let mut out = Vec::with_capacity(length);
let mut remaining = length;
let mut lba = sector_lba;
let mut offset = sector_off;
while remaining > 0 {
let sector = self.read_sector(lba)?;
let available = sector.len().saturating_sub(offset);
let take = remaining.min(available);
out.extend_from_slice(§or[offset..offset + take]);
remaining -= take;
lba += 1;
offset = 0;
}
Ok(out)
}
}
pub struct IsoSectorReader {
file: BufReader<File>,
}
impl IsoSectorReader {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let file = File::open(path.as_ref()).map_err(OpticaldiscsError::Io)?;
Ok(Self {
file: BufReader::new(file),
})
}
}
impl SectorReader for IsoSectorReader {
fn read_sector(&mut self, lba: u64) -> Result<Vec<u8>> {
let offset = lba * SECTOR_SIZE;
self.file
.seek(SeekFrom::Start(offset))
.map_err(OpticaldiscsError::Io)?;
let mut buf = vec![0u8; SECTOR_SIZE as usize];
self.file
.read_exact(&mut buf)
.map_err(OpticaldiscsError::Io)?;
Ok(buf)
}
fn read_bytes(&mut self, byte_offset: u64, length: usize) -> Result<Vec<u8>> {
self.file
.seek(SeekFrom::Start(byte_offset))
.map_err(OpticaldiscsError::Io)?;
let mut buf = vec![0u8; length];
self.file
.read_exact(&mut buf)
.map_err(OpticaldiscsError::Io)?;
Ok(buf)
}
}
pub struct BinCueSectorReader {
file: BufReader<File>,
file_byte_offset: u64,
physical_sector_size: u64,
data_offset: u64,
}
impl BinCueSectorReader {
pub fn open(track: &crate::bincue::BinTrack) -> Result<Self> {
let file = File::open(&track.bin_path).map_err(OpticaldiscsError::Io)?;
Ok(Self {
file: BufReader::new(file),
file_byte_offset: track.file_byte_offset,
physical_sector_size: track.sector_size(),
data_offset: track.data_offset(),
})
}
}
impl SectorReader for BinCueSectorReader {
fn read_sector(&mut self, lba: u64) -> Result<Vec<u8>> {
let physical_offset =
self.file_byte_offset + lba * self.physical_sector_size + self.data_offset;
self.file
.seek(SeekFrom::Start(physical_offset))
.map_err(OpticaldiscsError::Io)?;
let mut buf = vec![0u8; SECTOR_SIZE as usize];
self.file
.read_exact(&mut buf)
.map_err(OpticaldiscsError::Io)?;
Ok(buf)
}
}
pub struct ChdSectorReader {
inner: libchdman_rs::cd::CdCookedReader,
}
impl ChdSectorReader {
pub fn open(path: impl AsRef<Path>, track: &crate::chd::ChdTrack) -> Result<Self> {
let path = path.as_ref();
std::fs::metadata(path).map_err(OpticaldiscsError::Io)?;
let path_str = path.to_str().ok_or_else(|| {
OpticaldiscsError::Chd(format!("non-UTF-8 CHD path: {}", path.display()))
})?;
let chd = libchdman_rs::Chd::open(path_str, false, None)
.map_err(|e| OpticaldiscsError::Chd(format!("failed to open CHD: {e:?}")))?;
let track_index = track.track_no.checked_sub(1).ok_or_else(|| {
OpticaldiscsError::Chd(format!(
"invalid track_no {} (must be >= 1)",
track.track_no
))
})?;
let inner =
libchdman_rs::cd::CdCookedReader::open_track(chd, track_index).map_err(|e| {
OpticaldiscsError::Chd(format!("open CHD track {}: {e:?}", track.track_no))
})?;
Ok(Self { inner })
}
}
impl SectorReader for ChdSectorReader {
fn read_sector(&mut self, lba: u64) -> Result<Vec<u8>> {
self.inner
.seek(SeekFrom::Start(lba * SECTOR_SIZE))
.map_err(OpticaldiscsError::Io)?;
let mut buf = vec![0u8; SECTOR_SIZE as usize];
self.inner
.read_exact(&mut buf)
.map_err(OpticaldiscsError::Io)?;
Ok(buf)
}
}