Skip to content

Read INDEX, CATALOG and ISRC from an embedded cue sheet - #201

Open
0x000NULL wants to merge 3 commits into
rtissera:masterfrom
0x000NULL:cd-cuesheet-meta
Open

0x000NULL wants to merge 3 commits into
rtissera:masterfrom
0x000NULL:cd-cuesheet-meta

Conversation

@0x000NULL

Copy link
Copy Markdown
Contributor

Addresses #164. Stacked on #200. Only the last commit is new.

CHDv5 track metadata keeps one pregap per track, so INDEX 02+, CATALOG and ISRC are lost on conversion. The #164 thread settled on keeping that data inside the CHD rather than in a sidecar file. This PR does that with the original cue sheet stored as plain-text metadata:

chdman createcd -c none -i game.cue -o tmp.chd
chdman addmeta -i tmp.chd --tag CUES --valuefile game.cue   # addmeta can't write compressed CHDs
chdman copy -i tmp.chd -o game.chd

The result is still a normal CHDv5. Readers that don't know the tag ignore it, and it doesn't need a custom chdman.

What libchdr does with it

  • chd_cd_open() reads CDROM_CUESHEET_METADATA_TAG if present. It fills toc->catalog, tracks[n].isrc, and an internal index list, allocated only when a cue is present.
  • New chd_cd_get_index(cd, track, index, &lba) returns any INDEX listed in the cue. Without a cue it falls back to INDEX 01 and (for tracks with a pregap) INDEX 00 from the track metadata.
  • Indices are stored relative to their track's INDEX 01, so single-BIN and Redump one-BIN-per-track cues both land on the CHD's track starts. FILE is never resolved.
  • The cue is ignored as a whole (catalog and ISRCs too) if its track count doesn't match the CHD's, if a track has no INDEX 01, or if one track's indices are split across FILEs. It's also capped at 64 KB.

Open question

CUES is my suggestion for the tag name. Nothing in MAME uses it, but it's your call, and happy to rename it.

Tests

generate.sh adds cuesheet.chd (CATALOG, ISRC, INDEX 02) and cuesheet_bad.chd (a cue listing 2 tracks for a 3-track disc). cd-toc checks that the first comes through and the second is ignored. I also checked a Redump-style multi-FILE cue with CRLF line endings by hand, and its indices resolved to the right LBAs.

Tested on Ubuntu 24.04 with chdman 0.264 (GCC, ASan/UBSan) and Windows (MinGW).

Ethan Aldrich added 3 commits October 13, 2026 20:47
The CD track metadata formats are used by downstream readers with sscanf,
and their %s fields write as much as the CHD's metadata string holds - a
crafted image overflows the caller's buffer (rtissera#165).

Width-limiting the existing *_FORMAT macros is not an option: the same
macros are used to write metadata, and in printf a width pads instead of
limiting, so writers would emit space-padded TYPE fields. Add
CDROM_TRACK_METADATA_SCANF, CDROM_TRACK_METADATA2_SCANF and
GDROM_TRACK_METADATA_SCANF alongside them, bounded to
CD_METADATA_STRING_SIZE (16; the longest real value, MODE2_FORM_MIX, is
14 characters).

tests/metadata_scanf.c checks the bounds with canaries and needs no
corpus.
Every CD consumer of libchdr re-derives the same thing from the track
metadata: where a track's frames start in the CHD once each track is
padded to four frames, whether its pregap is stored, and which hunk and
frame an LBA lands in (rtissera#155).

chd_cd_open() parses CHT2/CHTR/CHGD metadata into a public TOC and
chd_cd_read_sector() reads one sector by LBA - 2048 bytes of user data,
the raw sector as a .bin holds it (audio little-endian), or the subcode.
Addressing follows MAME's cdrom_file: unstored pregaps and postgaps take
LBAs and read as zeros. The handle keeps the last decoded hunk, so
sequential reads decode each hunk once. MSF callers use the existing
msf_to_lba().

Track metadata is parsed with the bounded *_SCANF formats, and offsets
are accumulated in 64 bits and rejected past 32, so crafted metadata
yields CHDERR_INVALID_METADATA rather than wrapped offsets.

tests/cd_toc.c reads every LBA of a generated three-track CD (stored
pregap, unstored pregap, odd track lengths) and compares it with the BIN
it was built from.
CHDv5 track metadata keeps one pregap per track and nothing else, so
INDEX 02+, CATALOG and ISRC are lost on conversion - and some Saturn and
Dreamcast titles depend on them (rtissera#164). The thread there settled on
keeping that data inside the CHD rather than in a sidecar file.

A CD CHD may now carry its cue sheet as CUES metadata, written with
chdman addmeta before compressing. chd_cd_open() reads it: CATALOG into
the TOC, ISRC per track, and every INDEX, stored relative to its track's
INDEX 01 so both single-BIN and Redump one-BIN-per-track cues land on the
CHD's own track starts. chd_cd_get_index() returns any INDEX by track,
falling back to INDEX 00/01 from the track metadata. A cue whose tracks
do not match the CHD's, or that splits one track's indices across FILEs,
is ignored as a whole; other readers ignore the tag entirely.

The tag name is a proposal - nothing in MAME uses CUES.

tests/cd_toc.c checks a generated CHD with an embedded cue, and one with
a mismatched cue that must be ignored.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant