From 686754f18aace50b7673fd4e7c5106784d5da38d Mon Sep 17 00:00:00 2001 From: Nicos Panayides Date: Thu, 22 Oct 2026 15:52:55 +0300 Subject: [PATCH 1/5] cdrdao: open data files relative to the TOC file FILE, AUDIOFILE and DATAFILE names were opened relative to the current directory, although the path relative to the TOC file's directory was already computed and stored as the track's filename. TOC files only opened when the program ran from the TOC file's directory, which is how the cdrdao driver test runs. Open the computed path, as the bincue driver does. Co-Authored-By: Claude Opus 5.5 --- lib/driver/image/cdrdao.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/driver/image/cdrdao.c b/lib/driver/image/cdrdao.c index 4437cf6a..8cfb6fe4 100644 --- a/lib/driver/image/cdrdao.c +++ b/lib/driver/image/cdrdao.c @@ -717,30 +717,31 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) if (0 <= i_track) { if (NULL != (psz_field = strtok_r(NULL, "\"\t\n\r", &saveptr))) { /* Handle "" */ + char *psz_dirname = cdio_dirname(psz_cue_name); + char *psz_filename = cdio_abspath(psz_dirname, psz_field); + free(psz_dirname); if (cd) { - char *psz_dirname = cdio_dirname(psz_cue_name); - char *psz_filename = cdio_abspath(psz_dirname, psz_field); cd->tocent[i_track].filename = strdup (psz_filename); - free(psz_filename); - free(psz_dirname); /* To do: do something about reusing existing files. */ - if (!(cd->tocent[i_track].data_source = cdio_stdio_new (psz_field))) { + if (!(cd->tocent[i_track].data_source = cdio_stdio_new (psz_filename))) { cdio_log (log_level, "%s line %d: can't open file `%s' for reading", psz_cue_name, i_line, psz_field); + free(psz_filename); goto err_exit; } } else { - CdioDataSource_t *s = cdio_stdio_new (psz_field); + CdioDataSource_t *s = cdio_stdio_new (psz_filename); if (!s) { cdio_log (log_level, "%s line %d: can't open file `%s' for reading", psz_cue_name, i_line, psz_field); - cdio_stdio_destroy (s); + free(psz_filename); goto err_exit; } cdio_stdio_destroy (s); } + free(psz_filename); } if (NULL != (psz_field = strtok_r(NULL, " \t\n\r", &saveptr))) { @@ -798,7 +799,7 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) if (cd) { cd->tocent[i_track].filename = strdup(psz_filename); /* To do: do something about reusing existing files. */ - if (!(cd->tocent[i_track].data_source = cdio_stdio_new (psz_field))) { + if (!(cd->tocent[i_track].data_source = cdio_stdio_new (psz_filename))) { cdio_log (log_level, "%s line %d: can't open file `%s' for reading", psz_cue_name, i_line, psz_field); From 2ea352d3231ef6dfb0480d1fdd133b8cbd95bd46 Mon Sep 17 00:00:00 2001 From: Nicos Panayides Date: Thu, 22 Oct 2026 15:53:59 +0300 Subject: [PATCH 2/5] cdrdao: read the DATAFILE MSF as the track length cdrdao's TOC format is DATAFILE "" [#byte-offset] [] (see cdrdao(1)), and cdrdao read-cd and read-toc write the length of each track there. The parser took it as the track's start instead, so every track started at its own length; test/data/cdda.toc and vcd2.toc opened with negative LSNs: cdda.toc before: track 1 LSN -125, lead-out 177 after: track 1 LSN 0, lead-out 25 vcd2.toc before: tracks at LSN -150, -147, -125, lead-out 177 after: tracks at LSN 0, 302, 305, lead-out 330 Store the length as the track's sector count and start each track where the previous one ends: after its length, or after the rest of its data file when no length is given. That fallback used to add the 150 sector lead-in pregap on every track, and dropped any byte offset. Keep the LBA of the sector at the track's byte offset in data_lba, as START moves start_lba past the pregap, and leave unknown lengths at 0 on START instead of making them negative. Co-Authored-By: Claude Opus 5.5 --- lib/driver/image.h | 2 ++ lib/driver/image/cdrdao.c | 55 +++++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/driver/image.h b/lib/driver/image.h index 3c987b30..eccda788 100644 --- a/lib/driver/image.h +++ b/lib/driver/image.h @@ -78,6 +78,8 @@ typedef struct { This stuff may have error correction (EDC, or ECC).*/ uint16_t blocksize; /**< total block size = start + size + end */ + lba_t data_lba; /**< LBA of the sector stored at offset in + data_source, 0 if unknown */ } track_info_t; diff --git a/lib/driver/image/cdrdao.c b/lib/driver/image/cdrdao.c index 8cfb6fe4..3d8de78b 100644 --- a/lib/driver/image/cdrdao.c +++ b/lib/driver/image/cdrdao.c @@ -789,7 +789,7 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) goto not_in_global_section; } - /* DATAFILE "" #byte-offset */ + /* DATAFILE "" [#byte-offset] [] */ } else if (0 == strcmp ("DATAFILE", psz_keyword)) { if (0 <= i_track) { if (NULL != (psz_field = strtok_r(NULL, "\"\t\n\r", &saveptr))) { @@ -846,37 +846,38 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) } } if (psz_field) { - /* Handle start-msf */ + /* Handle */ lba_t lba = cdio_mmssff_to_lba (psz_field); if (CDIO_INVALID_LBA == lba) { cdio_log(log_level, "%s line %d: invalid MSF string %s", psz_cue_name, i_line, psz_field); goto err_exit; } - if (cd) { - cd->tocent[i_track].start_lba = lba; - cdio_lba_to_msf(cd->tocent[i_track].start_lba, - &(cd->tocent[i_track].start_msf)); - } - } else { - /* No start-msf. */ - if (cd) { - if (i_track) { - uint16_t i_blocksize = cd->tocent[i_track-1].blocksize; - off_t i_size = - cdio_stream_stat(cd->tocent[i_track-1].data_source); - - check_track_is_blocksize_multiple(cd->tocent[i_track-1].filename, - i_track-1, i_size, i_blocksize); - /* Append size of previous datafile. */ - cd->tocent[i_track].start_lba = (lba_t) (cd->tocent[i_track-1].start_lba + - (i_size / i_blocksize)); - } - cd->tocent[i_track].offset = 0; - cd->tocent[i_track].start_lba += CDIO_PREGAP_SECTORS; - cdio_lba_to_msf(cd->tocent[i_track].start_lba, - &(cd->tocent[i_track].start_msf)); + if (cd) + cd->tocent[i_track].sec_count = lba; + } + if (cd) { + /* The track starts where the previous one ends */ + if (0 == i_track) { + cd->tocent[i_track].start_lba = CDIO_PREGAP_SECTORS; + } else if (cd->tocent[i_track-1].sec_count) { + cd->tocent[i_track].start_lba = cd->tocent[i_track-1].start_lba + + cd->tocent[i_track-1].sec_count; + } else { + /* No length: the previous track takes up the rest of its file */ + const track_info_t *prev = &cd->tocent[i_track-1]; + uint16_t i_blocksize = prev->blocksize; + off_t i_size = cdio_stream_stat(prev->data_source) - prev->offset; + + check_track_is_blocksize_multiple(prev->filename, + i_track-1, i_size, i_blocksize); + cd->tocent[i_track].start_lba = (lba_t) + ((prev->data_lba ? prev->data_lba : prev->start_lba) + + (i_size / i_blocksize)); } + cd->tocent[i_track].data_lba = cd->tocent[i_track].start_lba; + cdio_lba_to_msf(cd->tocent[i_track].start_lba, + &(cd->tocent[i_track].start_msf)); } } else { @@ -897,7 +898,9 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) cd->tocent[i_track].start_lba += cdio_mmssff_to_lba (psz_field); cdio_lba_to_msf(cd->tocent[i_track].start_lba, &(cd->tocent[i_track].start_msf)); - cd->tocent[i_track].sec_count -= cdio_mmssff_to_lba (psz_field); + /* Unknown lengths stay unknown */ + if (cd->tocent[i_track].sec_count) + cd->tocent[i_track].sec_count -= cdio_mmssff_to_lba (psz_field); } } From 8356610d7b1433916bcae8d2ef4195756e051593 Mon Sep 17 00:00:00 2001 From: Nicos Panayides Date: Thu, 22 Oct 2026 15:55:03 +0300 Subject: [PATCH 3/5] cdrdao: read audio sectors from each track's data file Audio sectors were read from the first track's data file at lsn * 2352, ignoring each track's file, byte offset and block size. That only works for a single file that starts at the first sector. For DATAFILE tracks, find the track holding each sector and read it from that track's file, at its byte offset plus the sector's position times the block size. FILE and AUDIOFILE tracks are read as before. Co-Authored-By: Claude Opus 5.5 --- lib/driver/image/cdrdao.c | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/lib/driver/image/cdrdao.c b/lib/driver/image/cdrdao.c index 3d8de78b..b1a3e33a 100644 --- a/lib/driver/image/cdrdao.c +++ b/lib/driver/image/cdrdao.c @@ -1036,6 +1036,42 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) return false; } +/** + Returns the DATAFILE track that holds lsn, or NULL if there is none. + Pregap sectors set with START belong to the track that follows them. + */ +static const track_info_t * +_get_data_track_cdrdao (const _img_private_t *env, lsn_t lsn) +{ + const lba_t lba = cdio_lsn_to_lba(lsn); + int i; + + for (i = env->gen.i_tracks - 1; i >= 0; i--) { + const track_info_t *this_track = &env->tocent[i]; + if (this_track->data_lba && this_track->data_lba <= lba) + return this_track; + } + return NULL; +} + +/** + Reads the whole block of the sector at lsn, blocksize bytes, from + this_track's data file into buf. + */ +static driver_return_code_t +_read_block_cdrdao (const track_info_t *this_track, uint8_t *buf, lsn_t lsn) +{ + const off_t pos = this_track->offset + (off_t) + (cdio_lsn_to_lba(lsn) - this_track->data_lba) * this_track->blocksize; + + if (0 != cdio_stream_seek (this_track->data_source, pos, SEEK_SET)) + return DRIVER_OP_ERROR; + if (this_track->blocksize != + cdio_stream_read (this_track->data_source, buf, this_track->blocksize, 1)) + return DRIVER_OP_ERROR; + return DRIVER_OP_SUCCESS; +} + /** Reads a single audio sector from CD device into data starting from lsn. Returns 0 if no error. @@ -1047,6 +1083,26 @@ _read_audio_sectors_cdrdao (void *user_data, void *data, lsn_t lsn, _img_private_t *env = user_data; int ret; + if (_get_data_track_cdrdao(env, lsn)) { + uint8_t buf[CDIO_CD_FRAMESIZE_RAW]; + uint8_t *p = data; + unsigned int i; + + for (i = 0; i < nblocks; i++) { + const track_info_t *this_track = _get_data_track_cdrdao(env, lsn + i); + if (!this_track || this_track->blocksize < CDIO_CD_FRAMESIZE_RAW || + this_track->blocksize > sizeof(buf)) + return DRIVER_OP_ERROR; + ret = _read_block_cdrdao(this_track, buf, lsn + i); + if (ret) + return ret; + memcpy(p, buf, CDIO_CD_FRAMESIZE_RAW); + p += CDIO_CD_FRAMESIZE_RAW; + } + return DRIVER_OP_SUCCESS; + } + + /* FILE and AUDIOFILE tracks */ ret = cdio_stream_seek (env->tocent[0].data_source, lsn * CDIO_CD_FRAMESIZE_RAW, SEEK_SET); if (ret!=0) return ret; From adf5f210e91ac40f1b93e053b0e15c92e7eb5c61 Mon Sep 17 00:00:00 2001 From: Nicos Panayides Date: Thu, 22 Oct 2026 15:55:35 +0300 Subject: [PATCH 4/5] cdrdao: support the RW and RW_RAW sub-channel modes TRACK [] was parsed, but the sub-channel mode was ignored. With RW or RW_RAW, 96 bytes of sub-channel data follow each sector in the data file (see cdrdao(1)), as written by cdrdao read-cd --read-subchan rw or rw_raw. Their sectors were read 2352 bytes apart instead of 2448, so all audio after the first sector was misaligned. Record the mode and add the sub-channel data to the track's block size. Reject unknown sub-channel modes, as is done for track modes. Co-Authored-By: Claude Opus 5.5 --- lib/driver/image.h | 12 ++++++++++++ lib/driver/image/cdrdao.c | 23 +++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/driver/image.h b/lib/driver/image.h index eccda788..36b8a9e5 100644 --- a/lib/driver/image.h +++ b/lib/driver/image.h @@ -39,6 +39,17 @@ #include "cdio_private.h" #include +/** + Sub-channel data stored after each sector in a disc image. +*/ +typedef enum { + IMAGE_SUBCHANNEL_NONE = 0, + IMAGE_SUBCHANNEL_RW, /**< 96 bytes of packed R-W (cdrdao RW) */ + IMAGE_SUBCHANNEL_RW_RAW /**< 96 bytes of raw interleaved sub-channel + data (cdrdao RW_RAW). cdrdao read-cd + stores the drive's raw P-W data. */ +} image_subchannel_t; + /** The universal format for information about a track for CD image readers It may be that some fields can be derived from other fields. @@ -78,6 +89,7 @@ typedef struct { This stuff may have error correction (EDC, or ECC).*/ uint16_t blocksize; /**< total block size = start + size + end */ + image_subchannel_t subchannel; /**< sub-channel data in endsize */ lba_t data_lba; /**< LBA of the sector stored at offset in data_source, 0 if unknown */ } track_info_t; diff --git a/lib/driver/image/cdrdao.c b/lib/driver/image/cdrdao.c index b1a3e33a..3e0cd3d9 100644 --- a/lib/driver/image/cdrdao.c +++ b/lib/driver/image/cdrdao.c @@ -636,13 +636,24 @@ parse_tocfile (_img_private_t *cd, const char *psz_cue_name) goto err_exit; } if (NULL != (psz_field = strtok_r(NULL, " \t\n\r", &saveptr))) { - /* \todo: set sub-channel-mode */ -#ifdef TODO + /* : 96 bytes follow each sector in the file */ + image_subchannel_t subchannel; if (0 == strcmp ("RW", psz_field)) - ; + subchannel = IMAGE_SUBCHANNEL_RW; else if (0 == strcmp ("RW_RAW", psz_field)) - ; -#endif + subchannel = IMAGE_SUBCHANNEL_RW_RAW; + else { + cdio_log(log_level, "%s line %d after TRACK:", + psz_cue_name, i_line); + cdio_log(log_level, "'%s' not a valid sub-channel mode.", + psz_field); + goto err_exit; + } + if (NULL != cd) { + cd->tocent[i_track].subchannel = subchannel; + cd->tocent[i_track].endsize += CDIO_CD_FRAMESIZE_SUB; + cd->tocent[i_track].blocksize += CDIO_CD_FRAMESIZE_SUB; + } } if (NULL != (psz_field = strtok_r(NULL, " \t\n\r", &saveptr))) { goto format_error; @@ -1084,7 +1095,7 @@ _read_audio_sectors_cdrdao (void *user_data, void *data, lsn_t lsn, int ret; if (_get_data_track_cdrdao(env, lsn)) { - uint8_t buf[CDIO_CD_FRAMESIZE_RAW]; + uint8_t buf[CDIO_CD_FRAMESIZE_RAW + CDIO_CD_FRAMESIZE_SUB]; uint8_t *p = data; unsigned int i; From 18dce0c1e0ba8d16973a88098e48e8c9479c483a Mon Sep 17 00:00:00 2001 From: Nicos Panayides Date: Thu, 22 Oct 2026 21:52:59 +0300 Subject: [PATCH 5/5] test: read a cdrdao image with RW_RAW sub-channel data cdda-rw-raw.toc describes two audio tracks with raw P-W sub-channel data after each sector, as cdrdao read-cd --read-subchan rw_raw writes them: one data file with a byte offset and a length for each track, and a two sector START pregap in track 2. Open it from outside its directory and check the track positions and that audio reads leave out the sub-channel data. Add cdda.toc to check_DATA, as the test already reads it, and skip the new audio-only image in check_fuzzyiso.sh like cdda.bin. Co-Authored-By: Claude Opus 5.5 --- test/check_fuzzyiso.sh | 2 +- test/data/Makefile.am | 3 ++ test/data/cdda-rw-raw.bin | Bin 0 -> 34272 bytes test/data/cdda-rw-raw.toc | 20 ++++++++++++ test/driver/cdrdao.c | 62 +++++++++++++++++++++++++++++++++++++- 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 test/data/cdda-rw-raw.bin create mode 100644 test/data/cdda-rw-raw.toc diff --git a/test/check_fuzzyiso.sh b/test/check_fuzzyiso.sh index 71069da2..22a26f78 100755 --- a/test/check_fuzzyiso.sh +++ b/test/check_fuzzyiso.sh @@ -22,7 +22,7 @@ fi cd $srcdir; src_dir=`pwd` for file in $src_dir/data/*.bin $src_dir/data/*.iso $src_dir/data/*.nrg ; do case "$file" in - $src_dir/data/p1.nrg | $src_dir/data/p1.bin | $src_dir/data/cdda.bin | $src_dir/data/cdda_4_5.bin | $src_dir/data/cdtextfile.bin | $src_dir/data/cdda-mcn.nrg | $src_dir/data/svcdgs.nrg | $src_dir/data/cdtext.bin | $src_dir/data/udf102.iso | $src_dir/data/too-many-tracks.nrg ) + $src_dir/data/p1.nrg | $src_dir/data/p1.bin | $src_dir/data/cdda.bin | $src_dir/data/cdda-rw-raw.bin | $src_dir/data/cdda_4_5.bin | $src_dir/data/cdtextfile.bin | $src_dir/data/cdda-mcn.nrg | $src_dir/data/svcdgs.nrg | $src_dir/data/cdtext.bin | $src_dir/data/udf102.iso | $src_dir/data/too-many-tracks.nrg ) good_rc=1 ;; *) diff --git a/test/data/Makefile.am b/test/data/Makefile.am index 710ab51b..dd44d58e 100644 --- a/test/data/Makefile.am +++ b/test/data/Makefile.am @@ -20,8 +20,11 @@ check_DATA = \ bad-tracknum-exceeded.cue \ bad-tracknum-exceeded.toc \ bad-tracknum-overflow.toc \ + cdda-rw-raw.bin \ + cdda-rw-raw.toc \ cdda.bin \ cdda.cue \ + cdda.toc \ cdda_4_5.bin \ cdda_4_5.cue \ cdimage-u2.iso \ diff --git a/test/data/cdda-rw-raw.bin b/test/data/cdda-rw-raw.bin new file mode 100644 index 0000000000000000000000000000000000000000..19ca591d7e0b23ff66cdd0157f6e7b3571d73ffd GIT binary patch literal 34272 zcmeI5^;Z;89K~(yRTL8>1q2%lQ87^j=~iKv6vY4wu)DBZ!2;~=?nW@Mu|-tu?s)U& zvFr@XtUr1V_Yasohqvs^o$vSF&n;BANYP@&OK6oWRk}>sa^+gv_fH!w6ZHmPlDW?sj_vTnWl4H`CTY}KS`v*sc&f7gslT4^J;|A78)jJ$m{F$Q6M>!6Bhxy~2CKIRx^4T8ox67L*}HH5frEz*A31vL_=%IJPM+N#yx31dm9y(q`Z~4{iLG%^`7X&8+H(ogK z!i5)3yl`XSfPstjz=@4-y0F(b7bf3)@)=(Z5Ph0R&dIp(6+iwWa;zsAe__If2_Gh$ znDAo4jR`+C9N6$+!zI1pW7|Y~woPfVTOlW+QZJg_72Qz&z6mxoKoYpj}&_Cm2#{UlXGOc z=Al!Cc+dFhnA|YX7^ZoI1`wJ+Xau1dgoY5BLTC)3IqA?Ki+(w>=m(KgH2TO{@$*I> zqpSw0L?3hISZOBb+;}-gmYpQ}s4rI-1(_hFh?FK$sz~V~rHqs|QtC+QgHj+(X<$?I zRDOzfRRam)qY4Q^bqjTYCrPB!>u)W|h)K}yC-QYw*dbFd<^h29=N#w1AGqfy9j(Q-xy?NBpw9JuJf(KLXj1vE{dX#-6oXj(zj44QUCO+!NNm=Xi4P(K~ZM~=xT zMwZAiPR`LN06|WfaLA!LIB?N{quL48R;czuwHd12P;G~5KU5ovsvU*gF_MhrK^J7% zsIKEBCYF{;il$?-3KSztOOkl*7?oqDgn(#)0~Z}Qx-`)xiY`@j$)ZaaUBc*6MwhfG zK`G>pk!2$fx(HG)$M}x#JaVie#mJInCts38Ko^{xBhxX9J~(jEfup<}0ffIu@TTozh}16jnwI4=;1!Xfj0?6w^37%>S*2S;!qzVqk;SO~ 1); - if (check_no_tracks() || check_track_without_mode()) + if (check_no_tracks() || check_track_without_mode() || check_rw_raw()) ret = 1; #ifdef HAVE_CHDIR