Skip to content

Add CD sector reads by LBA - #200

Open
0x000NULL wants to merge 2 commits into
rtissera:masterfrom
0x000NULL:cd-sector-api
Open

0x000NULL wants to merge 2 commits into
rtissera:masterfrom
0x000NULL:cd-sector-api

Conversation

@0x000NULL

Copy link
Copy Markdown
Contributor

Closes #155. Stacked on #199, since it parses metadata with the bounded formats from that PR. Only the last commit is new.

Every CD consumer redoes the same arithmetic from track metadata: per-track padding to CD_TRACK_PADDING frames, whether a pregap is stored, and which hunk and frame an LBA falls in. This PR moves that into libchdr, ported from MAME's cdrom_file.

API (cdrom.h, all chd_cd_* so the export filter keeps them)

chd_error chd_cd_open(chd_file *chd, chd_cd **cd);
void chd_cd_close(chd_cd *cd);
const chd_cd_toc *chd_cd_get_toc(const chd_cd *cd);
chd_error chd_cd_read_sector(chd_cd *cd, uint32_t lba, void *buffer, uint32_t what);

what is one of:

  • CHD_CD_READ_DATA: 2048 bytes of user data (MODE1/MODE2 offsets handled)
  • CHD_CD_READ_RAW: 2352 bytes, the same as a .bin (audio swapped back to little-endian; legacy CHGT GD-ROMs left as they are)
  • CHD_CD_READ_SUBCODE: 96 bytes

Behaviour

  • Addressing follows MAME's logical LBAs. Pregaps and postgaps that aren't stored take up LBAs and read as zeros.
  • Parses CHT2, CHTR and CHGD/CHGT. Old binary CHCD and multi-session CHSE aren't read yet (marked in the code).
  • The handle caches the last decoded hunk, so sequential reads decode each hunk once.
  • Track offsets are summed in 64 bits and rejected if they don't fit in 32. Malformed metadata returns CHDERR_INVALID_METADATA.
  • MSF callers use the existing msf_to_lba().

Tests

generate.sh now builds tests/corpus/cd/, a 3-track disc (MODE1 data, audio with a stored INDEX 00 pregap, audio with an unstored PREGAP, odd lengths to force padding), and keeps the source BIN. tests/cd_toc.c (cd-toc) checks the TOC and reads all 926 LBAs against the BIN. It skips when the corpus isn't generated, like the other corpus tests.

Tested on Ubuntu 24.04 with chdman 0.264 (GCC, ASan/UBSan) and Windows (MinGW). On Windows the corpus tests skip.

Ethan Aldrich added 2 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.
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.

Higher level API

1 participant