From 399a0f20f47fa468e04c80abdf096ebb4a13a878 Mon Sep 17 00:00:00 2001 From: WizzardSK <42868978+WizzardSK@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:05:05 +0200 Subject: [PATCH] chd: number sectors from the lead-in, the way the rest of blastem does (#60) A CHD numbers its frames from the first frame of track 1's data, and chdman records no pregap in front of that track for the cue sheets everyone actually has. blastem numbers sectors the way the drive reports them, counting from the start of the lead-in: the cue parser gives track 1 the two second gap the cue sheet leaves out, and the CDD turns the MM:SS:FF in a READ or SEEK command straight into one of those numbers. So every CHD came out 150 sectors ahead of itself. The Sega CD BIOS asked for 00:02:00, got what the disc holds at 00:04:00, never found a boot sector, and sat on its own screen with the drive retrying - which is what #59 reports. Fill in that gap the same way the cue parser does, from the time in the first sector's header, and treat a pregap that is stored in the image (PGTYPE without the leading V) as what it is: frames that are part of the track's frame count and only move where index 1 falls, rather than something to skip over. With this, reading a disc through the CHD parser and through the cue parser returns identical bytes for every sector, subcode included. --- chdimage.c | 74 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/chdimage.c b/chdimage.c index dc6c4fa..e97d4d1 100644 --- a/chdimage.c +++ b/chdimage.c @@ -170,7 +170,7 @@ static uint8_t parse_subcode_type(const char *subtype) return SUBCODES_NONE; } -static uint8_t read_track_metadata(chd_file *chd, uint32_t index, track_info *track, uint32_t *frames_out) +static uint8_t read_track_metadata(chd_file *chd, uint32_t index, track_info *track, uint32_t *frames_out, uint32_t *real_pregap_out) { char metadata[METADATA_MAX]; char type[32], subtype[32], pgtype[32], pgsub[32]; @@ -196,8 +196,10 @@ static uint8_t read_track_metadata(chd_file *chd, uint32_t index, track_info *tr track->has_subcodes = parse_subcode_type(subtype); //A pregap type starting with V is one MAME generated rather than stored, so //those frames are not in the image and blastem has to fake them. Anything - //else means the pregap is part of the frames the metadata counts. + //else means the pregap is part of the frames the metadata counts, so it + //takes up image space and only moves where index 1 falls. track->fake_pregap = pgtype[0] == 'V' ? pregap : 0; + *real_pregap_out = pgtype[0] == 'V' ? 0 : pregap; *frames_out = frames; return 1; } @@ -250,6 +252,28 @@ static core_file *chd_media_open(const char *path) return cf; } +//A CHD numbers its frames from the start of track 1's data, while the rest of +//blastem numbers sectors the way the drive reports them, counting from the +//start of the lead-in - so the two differ by the pregap in front of track 1, +//which a CHD only records when a cue sheet asked for one. The cue parser infers +//the same gap the same way when a cue sheet leaves it out. +static uint32_t chd_lead_in(system_media *media, track_info *track) +{ + if (track->type == TRACK_DATA && track->sector_bytes == 2352 && read_frame(media, 0)) { + //The sector header holds the absolute time of the first sector, which is + //exactly the size of the gap in front of it. + uint8_t *timecode = media->chd_frame + 12; + uint32_t lba = (timecode[0] >> 4) * 600 + (timecode[0] & 0xF) * 60; + lba += (timecode[1] >> 4) * 10 + (timecode[1] & 0xF); + lba *= 75; + lba += (timecode[2] >> 4) * 10 + (timecode[2] & 0xF); + return lba; + } + //Same assumption the cue parser makes for a track it cannot read a time out + //of: the standard two second pregap. + return 2 * 75; +} + //chd_open() fails on a zstd compressed CHD because the codec is stubbed out (see //libchdr/zstd.h), and a failed open says nothing about why. The header parses //without any codec, so ask it directly. @@ -295,8 +319,8 @@ uint8_t parse_chd(system_media *media) uint32_t num_tracks = 0; track_info scratch; - uint32_t frames; - while (read_track_metadata(chd, num_tracks, &scratch, &frames)) + uint32_t frames, real_pregap; + while (read_track_metadata(chd, num_tracks, &scratch, &frames, &real_pregap)) { num_tracks++; } @@ -311,29 +335,37 @@ uint8_t parse_chd(system_media *media) media->num_tracks = num_tracks; media->chd = chd; - uint32_t lba = 0; - uint32_t chd_frame = 0; - for (uint32_t i = 0; i < num_tracks; i++) - { - read_track_metadata(chd, i, tracks + i, &frames); - tracks[i].pregap_lba = lba; - //file_offset is the frame the track starts at within the CHD rather than - //a byte offset; nothing outside this file interprets it. - tracks[i].file_offset = chd_frame; - tracks[i].start_lba = lba + tracks[i].fake_pregap; - tracks[i].end_lba = tracks[i].start_lba + frames; - lba = tracks[i].end_lba; - //Each track is padded out to a four frame boundary in the CHD. - chd_frame += frames; - chd_frame += (CD_TRACK_PADDING - (frames % CD_TRACK_PADDING)) % CD_TRACK_PADDING; - } - + //read_frame() needs these, and working out the lead-in reads a frame const chd_header *header = chd_get_header(chd); media->chd_hunk = calloc(1, header->hunkbytes); media->chd_frame = calloc(1, CD_FRAME_SIZE); media->chd_hunk_valid = 0; media->chd_frame_valid = 0; + uint32_t lba = 0; + uint32_t chd_frame = 0; + for (uint32_t i = 0; i < num_tracks; i++) + { + read_track_metadata(chd, i, tracks + i, &frames, &real_pregap); + if (!i && !tracks[i].fake_pregap && !real_pregap) { + //Nothing in front of track 1, so the lead-in the drive reports is + //missing from the image and has to be faked like a virtual pregap. + tracks[i].fake_pregap = chd_lead_in(media, tracks); + } + tracks[i].pregap_lba = lba; + //file_offset is the frame the track starts at within the CHD rather than + //a byte offset; nothing outside this file interprets it. + tracks[i].file_offset = chd_frame; + //A stored pregap is part of the frames the metadata counts, so it moves + //index 1 without moving where the track's data starts. + tracks[i].start_lba = lba + tracks[i].fake_pregap + real_pregap; + tracks[i].end_lba = lba + tracks[i].fake_pregap + frames; + lba = tracks[i].end_lba; + //Each track is padded out to a four frame boundary in the CHD. + chd_frame += frames; + chd_frame += (CD_TRACK_PADDING - (frames % CD_TRACK_PADDING)) % CD_TRACK_PADDING; + } + media->seek = chdmedia_seek; media->read = chdmedia_read; media->read_subcodes = chdmedia_subcode_read;