jevsnes.git / third-party / rust / jgenesis / common / cdrom / src / bin / validator.rs
1use cdrom::cdtime::CdTime;
2use cdrom::cue::{Track, TrackType};
3use cdrom::reader::{CdRom, CdRomFileFormat};
4use std::env;
5use std::error::Error;
6
7fn main() -> Result<(), Box<dyn Error>> {
8    let mut args = env::args();
9    let exe_name = args.next().expect("Executable name arg not present");
10
11    let Some(path) = args.next() else { panic!("USAGE: {exe_name} file_path") };
12
13    let Some(cdrom_format) = CdRomFileFormat::from_file_path(&path) else {
14        panic!("Unable to determine CD-ROM image format for path '{path}'");
15    };
16
17    validate(CdRom::open(&path, cdrom_format)?)?;
18    validate(CdRom::open_in_memory(&path, cdrom_format)?)?;
19
20    Ok(())
21}
22
23fn validate(mut disc: CdRom) -> Result<(), Box<dyn Error>> {
24    let mut sector_buffer = vec![0; cdrom::BYTES_PER_SECTOR as usize];
25
26    let last_track_number = disc.cue().last_track().number;
27
28    for track_number in 1..=last_track_number {
29        let track = disc.cue().track(track_number);
30        let &Track { track_type, start_time, end_time, .. } = track;
31
32        // In data tracks, sector MSF addresses should be valid for at least the 2 seconds leading
33        // up to effective start time (INDEX 01 in CUE files). Sectors before this may not have
34        // valid addresses but that's fine
35        let data_address_valid_threshold =
36            track.effective_start_time().saturating_sub(CdTime::new(0, 2, 0));
37
38        let mut time = start_time;
39        while time < end_time {
40            let relative_time = time - start_time;
41            disc.read_sector(track_number, relative_time, &mut sector_buffer)?;
42
43            if track_type == TrackType::Data && time >= data_address_valid_threshold {
44                // First 12 sector bytes are sync, next 3 bytes are MSF time
45                let header_msf = CdTime::new(
46                    bcd_to_binary(sector_buffer[12]),
47                    bcd_to_binary(sector_buffer[13]),
48                    bcd_to_binary(sector_buffer[14]),
49                );
50
51                assert_eq!(
52                    header_msf, time,
53                    "Time mismatch in data track {track_number} at relative time {relative_time}! Expected {time}, got {header_msf}"
54                );
55            }
56
57            time += CdTime::new(0, 0, 1);
58        }
59    }
60
61    Ok(())
62}
63
64fn bcd_to_binary(bcd_value: u8) -> u8 {
65    (bcd_value & 0x0F) + 10 * (bcd_value >> 4)
66}