Skip to main content

read_data_track/
read_data_track.rs

1/// Reads the first data track from a mixed-mode / enhanced CD and verifies the
2/// result against the on-disc structure, so it doubles as a correctness check
3/// for the cooked (2048 B) and raw (2352 B) data read paths.
4///
5/// What it checks, using only the data the disc itself carries:
6///   1. Raw sector framing: a 2352 B sector starts with the 12-byte sync
7///      pattern `00 FF*10 00`, and byte 15 reports the sector mode.
8///   2. ISO 9660 signature: logical sector 16 of the volume is the Primary
9///      Volume Descriptor — type byte `0x01` followed by `"CD001"`.
10///   3. Cooked vs raw: the cooked 2048 B must equal the user-data region of
11///      the raw sector (offset 16 for Mode 1).
12use cd_da_reader::{CdReader, ReadOptions, SectorReadFormat};
13
14// ISO 9660 places the Primary Volume Descriptor at logical sector 16.
15const PVD_SECTOR_OFFSET: u32 = 16;
16
17fn main() -> Result<(), Box<dyn std::error::Error>> {
18    let reader = CdReader::open_default()?;
19    let toc = reader.read_toc()?;
20
21    let data_track = toc
22        .tracks
23        .iter()
24        .find(|t| !t.is_audio)
25        .ok_or("no data track on this disc (need a mixed-mode / enhanced CD)")?;
26
27    let pvd_lba = data_track.start_lba + PVD_SECTOR_OFFSET;
28    println!(
29        "Data track #{} starts at LBA {}; reading PVD at LBA {}\n",
30        data_track.number, data_track.start_lba, pvd_lba
31    );
32
33    let mut options = ReadOptions::default().with_format(SectorReadFormat::Mode1Raw);
34
35    // --- raw read (2352 B) -------------------------------------------------
36    let raw = reader.read_sector_range(pvd_lba, 1, &options)?;
37    if raw.len() != 2352 {
38        return Err(format!("raw read returned {} bytes, expected 2352", raw.len()).into());
39    }
40
41    let sync_ok = raw[0] == 0x00 && raw[1..11].iter().all(|&b| b == 0xFF) && raw[11] == 0x00;
42    let mode = raw[15];
43    println!("raw sync pattern : {}", pass(sync_ok));
44    println!("raw sector mode  : Mode {mode}");
45
46    // User data sits after sync(12) + header(4) for Mode 1, and additionally
47    // after an 8-byte subheader for Mode 2 Form 1.
48    let user_offset = match mode {
49        1 => 16,
50        2 => 24,
51        other => return Err(format!("unexpected sector mode {other}").into()),
52    };
53    let raw_user = &raw[user_offset..user_offset + 2048];
54
55    let iso_ok = raw_user[0] == 0x01 && &raw_user[1..6] == b"CD001";
56    println!("ISO 9660 'CD001' : {}", pass(iso_ok));
57
58    // --- cooked read (2048 B) ---------------------------------------------
59    // The cooked format is specifically Mode 1, so only cross-check it there.
60    if mode == 1 {
61        options = options.with_format(SectorReadFormat::Mode1Cooked);
62        let cooked = reader.read_sector_range(pvd_lba, 1, &options)?;
63        if cooked.len() != 2048 {
64            return Err(
65                format!("cooked read returned {} bytes, expected 2048", cooked.len()).into(),
66            );
67        }
68        let matches_raw = cooked == raw_user;
69        println!("cooked == raw[16..]: {}", pass(matches_raw));
70
71        if sync_ok && iso_ok && matches_raw {
72            println!("\nALL CHECKS PASSED — cooked and raw data reads are correct.");
73            return Ok(());
74        }
75    } else {
76        println!(
77            "\nData track is Mode {mode} (e.g. CD-Extra / Mode 2 Form 1). The cooked path \
78             targets Mode 1, so only the raw checks apply here."
79        );
80        if sync_ok && iso_ok {
81            println!("Raw read verified against on-disc ISO structure.");
82            return Ok(());
83        }
84    }
85
86    Err("one or more verification checks FAILED — see output above".into())
87}
88
89fn pass(ok: bool) -> &'static str {
90    if ok { "PASS" } else { "FAIL" }
91}