6use crate::cdtime::CdTime; 7use crate::cue::{CueSheet, TrackMode, TrackType}; 8use crate::reader::chd::ChdFile; 9use crate::reader::cuebin::CdBinFiles; 10use crate::{CdRomError, CdRomResult}; 11use bincode::{Decode, Encode}; 12use jgenesis_proc_macros::{FakeDecode, FakeEncode}; 13use std::ffi::OsStr; 14use std::fs; 15use std::fs::File; 16use std::io::{BufReader, Cursor}; 17use std::path::Path; 18 19const SECTOR_HEADER_LEN: u64 = 16; 20 21type CdBinFsFiles = CdBinFiles<File>; 22type CdBinMemoryFiles = CdBinFiles<Cursor<Vec<u8>>>; 23 24type ChdFsFile = ChdFile<BufReader<File>>; 25type ChdMemoryFile = ChdFile<Cursor<Vec<u8>>>; 26 27#[derive(Debug, FakeEncode, FakeDecode)] 28enum CdRomReader { 29 CueBin(CdBinFsFiles), 30 CueBinMemory(CdBinMemoryFiles), 31 ChdFs(ChdFsFile), 32 ChdMemory(ChdMemoryFile), 33} 34 35impl Default for CdRomReader { 36 fn default() -> Self { 37 Self::CueBin(CdBinFiles::empty()) 38 } 39} 40 41impl CdRomReader { 42 fn read_sector( 43 &mut self, 44 track_number: u8, 45 absolute_time: CdTime, 46 relative_sector_number: u32, 47 out: &mut [u8], 48 ) -> CdRomResult<()> { 49 match self { 50 Self::CueBin(bin_files) => { 51 bin_files.read_sector(track_number, absolute_time, relative_sector_number, out) 52 } 53 Self::CueBinMemory(bin_files) => { 54 bin_files.read_sector(track_number, absolute_time, relative_sector_number, out) 55 } 56 Self::ChdFs(chd_file) => { 57 chd_file.read_sector(track_number, absolute_time, relative_sector_number, out) 58 } 59 Self::ChdMemory(chd_file) => { 60 chd_file.read_sector(track_number, absolute_time, relative_sector_number, out) 61 } 62 } 63 } 64} 65 66#[derive(Debug, Clone, Copy, PartialEq, Eq)] 67pub enum CdRomFileFormat { 68 // CUE file + BIN files 69 CueBin, 70 // CHD files 71 Chd, 72} 73 74impl CdRomFileFormat { 75 pub fn from_file_path<P: AsRef<Path>>(path: P) -> Option<Self> { 76 match path 77 .as_ref() 78 .extension() 79 .map(OsStr::to_ascii_lowercase) 80 .as_ref() 81 .and_then(|s| s.to_str()) 82 { 83 Some("cue") => Some(Self::CueBin), 84 Some("chd") => Some(Self::Chd), 85 _ => None, 86 } 87 } 88} 89 90#[derive(Debug, Encode, Decode)] 91pub struct CdRom { 92 cue_sheet: CueSheet, 93 reader: CdRomReader, 94} 95 96impl CdRom {
Open a CD-ROM reader that will read from the filesystem as needed.
Errors
Will propagate any I/O errors, and will return an error if the CD-ROM metadata appears invalid.
110 fn open_cue_bin<P: AsRef<Path>>(cue_path: P) -> CdRomResult<Self> { 111 let (bin_files, cue_sheet) = CdBinFiles::create(cue_path, |path| File::open(path))?; 112 113 Ok(Self { cue_sheet, reader: CdRomReader::CueBin(bin_files) }) 114 } 115 116 fn open_chd<P: AsRef<Path>>(chd_path: P) -> CdRomResult<Self> { 117 let chd_path = chd_path.as_ref(); 118 119 let file = File::open(chd_path).map_err(|source| CdRomError::ChdOpen { 120 path: chd_path.display().to_string(), 121 source, 122 })?; 123 let (chd_file, cue_sheet) = ChdFile::open(BufReader::new(file))?; 124 125 Ok(Self { cue_sheet, reader: CdRomReader::ChdFs(chd_file) }) 126 }
Open a CD-ROM reader that will load the entire disc image into memory.
Errors
Will return any error encountered while reading from disk, or if the CD-ROM metadata appears invalid.
134 pub fn open_in_memory<P: AsRef<Path>>(path: P, format: CdRomFileFormat) -> CdRomResult<Self> { 135 let path = path.as_ref(); 136 137 match format { 138 CdRomFileFormat::CueBin => Self::open_cue_bin_in_memory(path), 139 CdRomFileFormat::Chd => { 140 let chd_bytes = fs::read(path).map_err(|source| CdRomError::ChdOpen { 141 path: path.display().to_string(), 142 source, 143 })?; 144 Self::open_chd_in_memory(chd_bytes) 145 } 146 } 147 }
Open a CD-ROM reader that will read from CUE/BIN files that will be read into memory.
Errors
Will return any error encountered while reading from disk, or if the CUE file appears to be invalid.
155 pub fn open_cue_bin_in_memory<P: AsRef<Path>>(cue_path: P) -> CdRomResult<Self> { 156 let (bin_files, cue_sheet) = CdBinFiles::create(cue_path, |path| { 157 let bin_bytes = fs::read(path)?; 158 Ok(Cursor::new(bin_bytes)) 159 })?; 160 161 Ok(Self { reader: CdRomReader::CueBinMemory(bin_files), cue_sheet }) 162 }
Open a CD-ROM reader that will read from a CHD file that has been read into memory.
Errors
Will return an error if the CHD or CD-ROM metadata appears invalid.
Read a 2352-byte sector from the given track into a buffer.
Errors
This method will propagate any I/O error encountered while reading from disk.
Panics
This method will panic if out's length is less than 2352 or if relative_time is past the
end of the track file.
190 pub fn read_sector( 191 &mut self, 192 track_number: u8, 193 relative_time: CdTime, 194 out: &mut [u8], 195 ) -> CdRomResult<()> { 196 let track = self.cue_sheet.track(track_number); 197 let absolute_time = relative_time + track.start_time; 198 199 if relative_time < track.pregap_len 200 || relative_time >= track.end_time - track.postgap_len - track.start_time 201 { 202 // Reading data in pregap or postgap that does not exist in the file 203 match track.track_type { 204 TrackType::Data => { 205 write_fake_data_pregap(track.mode, absolute_time, out); 206 } 207 TrackType::Audio => { 208 // Fill with all 0s 209 out[..crate::BYTES_PER_SECTOR as usize].fill(0); 210 } 211 } 212 return Ok(()); 213 } 214 215 let relative_sector_number = (relative_time - track.pregap_len).to_sector_number(); 216 self.reader.read_sector(track_number, absolute_time, relative_sector_number, out)?; 217 218 Ok(()) 219 } 220}
222impl TrackMode { 223 fn header_byte(self) -> u8 { 224 match self { 225 Self::Mode1 | Self::Mode1DataOnly => 0x01, 226 Self::Mode2 => 0x02, 227 Self::Audio => 0x00, 228 } 229 } 230} 231 232fn synthesize_data_header(mode: TrackMode, time: CdTime) -> [u8; 16] { 233 // Make up a header; 12 sync bytes, then MSF time, then mode byte 234 [ 235 0x00, 236 0xFF, 237 0xFF, 238 0xFF, 239 0xFF, 240 0xFF, 241 0xFF, 242 0xFF, 243 0xFF, 244 0xFF, 245 0xFF, 246 0x00, 247 time_component_to_bcd(time.minutes), 248 time_component_to_bcd(time.seconds), 249 time_component_to_bcd(time.frames), 250 mode.header_byte(), 251 ] 252} 253 254fn write_fake_data_pregap(mode: TrackMode, time: CdTime, out: &mut [u8]) { 255 out[..SECTOR_HEADER_LEN as usize].copy_from_slice(&synthesize_data_header(mode, time)); 256 out[SECTOR_HEADER_LEN as usize..crate::BYTES_PER_SECTOR as usize].fill(0); 257} 258 259fn time_component_to_bcd(component: u8) -> u8 { 260 let msb = component / 10; 261 let lsb = component % 10; 262 (msb << 4) | lsb 263}