Code for reading CD-ROM files

3mod chd;
4mod cuebin;
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.

103    pub fn open<P: AsRef<Path>>(path: P, format: CdRomFileFormat) -> CdRomResult<Self> {
104        match format {
105            CdRomFileFormat::CueBin => Self::open_cue_bin(path),
106            CdRomFileFormat::Chd => Self::open_chd(path),
107        }
108    }
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.

169    pub fn open_chd_in_memory(chd_bytes: Vec<u8>) -> CdRomResult<Self> {
170        let (chd_file, cue_sheet) = ChdFile::open(Cursor::new(chd_bytes))?;
171
172        Ok(Self { cue_sheet, reader: CdRomReader::ChdMemory(chd_file) })
173    }
175    #[must_use]
176    pub fn cue(&self) -> &CueSheet {
177        &self.cue_sheet
178    }

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}