cue: synthesize the multisession gap across a FILE boundary - #244
Merged
Conversation
iTechMedic
force-pushed
the
multibin-session-gap
branch
from
August 2, 2026 00:48
4dba1af to
73859dc
Compare
Collaborator
Author
Hardware verification
|
A split rip gives each track its own BIN and restarts INDEX times at 00:00:00 in every one, so a "REM SESSION 02" that is also a FILE boundary never says where session 2 begins. The parser placed the data track directly after session 1's last audio sector, 11400 frames early, and hosts looking for the volume descriptor read the wrong sectors and mounted the disc as audio-only. Akumajou Dracula - MIDI Collection (Japan) is the reported case: 20 audio BINs totalling 267988 sectors, then a MODE2/2352 BIN whose own sector 16 header reads 62:07:29 -- LBA 279404, so INDEX 01 belongs at 279388. The missing distance is the Orange Book gap, 90 s of lead-out (6750 frames) plus 60 s of lead-in (4500), and then the track's own 150-frame pregap. So track_start goes 11250 past the previous session's content and INDEX 01 a further 150 on. Where the sheet states a pregap itself, by INDEX 00 or PREGAP, only the 11250 is synthesized; an INDEX 00 equal to INDEX 01 states a pregap of no length and counts as stating none. The 11250 rides on cumulative_offset and the 150 on unstored_pregap_length, the levers an ordinary unstored PREGAP already uses, so both carry into later tracks while file_start and file_offset stay put -- CueResolveLBA at the corrected LBA still lands on byte 0 of the session-2 BIN. Single-BIN multisession cues carry absolute INDEX times and are untouched: the gap needs a session advance, a FILE change and a previous track. Nothing in GetSessionLeadoutLBA changes, because track_start lands exactly the 11250 frames past session 1's content that it already subtracts. Those synthesized addresses have no bytes behind them, and the split device could not say so. Its seek space was the .bin files physically concatenated, so an address that ran off the end of session 1's file was session 2's first byte: a host ripping the last audio track read into the gap and got Mode 2 sectors served as CD-DA, about a second of modem noise. Confirmed on hardware -- READ CD (0xBE) for 92 sectors from LBA 268045, 57 past the stored end of track 20, every one answered successfully with the wrong data. Those 57 sectors are not a layout constant anyone forgot; the request simply landed inside the 11250-frame gap, and concatenation aliased it onto BIN sector 57 of the next file. A single-BIN rip of the same disc has no bug because its gap sectors physically exist. So CCueBinFileDevice's seek space becomes sparse, which is where a question about the backing layout belongs. Once every FILE is open, one pass over the sheet records where each file's stored frames begin and end on the disc. Where the next file starts later than the previous one ends, the two virtual bases are separated by that many frames' worth of bytes instead of abutting, and the hole is recorded. GetByteOffsetForLBA answers inside the hole for a frame in it, Read serves holes as zeros without opening, seeking or caching any .bin, and a read crossing one keeps its real prefix and resumes on the far side at the right offset. Files that abut on the disc still get adjacent bases and cross directly, so an ordinary split rip is byte-for-byte what it was, and per-file physical sizes stay in GetDataFileSizes for the parser, lead-out and validation. Construction fails closed. A boundary the layout cannot represent -- a hole wider than a disc, a track with no sector length, an open file no track places, an address space that would overflow -- rejects the image rather than abutting the files, because abutting them is the aliasing this is fixing. The layout is staged in locals and committed only once every boundary passes, so a rejection leaves the previous one intact; AddDataFile returns false without adopting the FIL, drops it from m_Files so the destructor cannot close it a second time, rebuilds the shorter layout, and leaves the caller to close what it still owns. The loader reports an unusable disc layout rather than a file-count limit. GetSize therefore means addressable bytes of the seek space, not stored bytes. Both its in-tree consumers want that: the Seek bound and the ReadCD end-of-image bound are both bounds on this address space, and GetLeadoutLBA already reads per-file sizes for split images. ReadCD now compares by subtraction against that size and rejects a failed translation outright with LOGICAL BLOCK ADDRESS OUT OF RANGE, so neither the (u64)-1 sentinel nor a host-chosen byte count can wrap the bound. Nothing was added to tcdstate_update.cpp, which is the generic transfer hot path and is unchanged from main; a backed image pays one extra comparison per read and an image with no holes pays nothing. 20 new tests, 204 total (207 with CHD), over the direct INDEX 01, stored INDEX 00, explicit PREGAP, zero-length INDEX 00 and no-session-boundary layouts, plus TOC, READ(10), both lead-outs, gap-as-silence, a read crossing into the gap, a read spanning audio then gap then data, INDEX 01's own first stored sector, file and image boundaries, an oversized hole that refuses to mount, a rejected AddDataFile leaving neither a half-built layout nor a double-closed file, a failed translation answering 05/21/00 with no read left pending, a range ending exactly at the addressable end, and an ordinary split audio boundary that must still cross into the next .bin. The fixture is 150 sectors of session 1 audio and a generated 40-sector Mode 2 Form 1 BIN with a CD001 descriptor at local sector 16, built at test time.
iTechMedic
force-pushed
the
multibin-session-gap
branch
from
August 2, 2026 16:02
73859dc to
0202bc9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #242. Ordinary multi-BIN cues worked, but an Enhanced CD split into one BIN per track exposed two related gaps in the model: session 2 was placed too early, and the unbacked space between sessions could alias bytes from the next BIN.
Root cause
Each BIN restarts its CUE
INDEXtimes at00:00:00. WhenREM SESSION 02is also aFILEboundary, the sheet omits the physical distance between sessions: 6750 lead-out frames, 4500 lead-in frames, and—when the cue does not state one—the next track's 150-frame pregap.For Akumajou Dracula - MIDI Collection (Japan), track 21 was reported at LBA 267988 instead of INDEX 01 at LBA 279388. Once that layout was corrected, a hardware trace exposed the backing problem: the host requested CD-DA at LBA 268045, 57 frames into the valid session gap, but physically concatenated BINs turned that into MODE2 sector 57 of track 21 and produced modem-like noise.
The 57 is not a missing constant. It is simply where the host read inside the 11250-frame gap. Combined images work because those gap frames physically exist in the single BIN.
Fix
FILEchange, add the 11250-frame lead-out/lead-in distance to disc addresses.file_startandfile_offsetunchanged, so session 2 INDEX 01 still maps to byte 0 of its BIN.CCueBinFileDevice::Read()serves as digital zeros.Per-file physical sizes remain available for parsing, lead-out, and validation.
GetSize()now describes the addressable virtual seek space for split CUE images. Ordinary contiguous multi-BIN files retain adjacent bases and the existing cache/fast-seek path.No disc-format logic or per-batch track scan is added to
tcdstate_update.cpp; that generic transfer hot path is byte-identical tomain.Verification
Twenty new tests cover session layout, stored and unstored pregaps, zero-length
INDEX 00, TOC and both lead-outs,READ(10)ofCD001, sparse gap silence, audio/gap/data spanning reads, exact INDEX 01 mapping, ordinary split-audio boundaries, image limits, malformed-layout rejection, failed-file ownership rollback, invalid LBA sense data, and overflow-safe READ CD bounds.One unrelated pre-existing issue remains out of scope: READ SUB-CHANNEL relative addressing around unstored pregaps is tracked separately in #245.