CSS DVD mux: per-title key via scan-key reuse + playback-order crack

A CSS DVD whose main title was mis-detected as unencrypted (the up-front
crack scanned the largest cell first and starved its budget in that cell's
clear prefix) muxed scrambled sectors as plaintext at exit 0. CSS leaves
the pack/PES header clear, so an un-descrambled sector muxes as a
structurally-valid but corrupt PES packet with zero loss reported.

decrypt_keys_for_title resolves a DVD title's CSS key two ways:
- Fast path: reuse the scan's cracked key when its crack_span covers this
  title's VTS (no re-read; on a live drive no second bus-auth).
- Crack: on a detection miss or a different VTS, crack from the title's OWN
  extents in a SINGLE scan in natural PLAYBACK ORDER (never largest-first).
  One scan = one CSS-locked early-bail, so a locked title is not re-hammered
  per cell against a live drive (hard rule #2); the 50k-sector budget is the
  same accepted bound the disc-wide scan uses. Cracked -> key; Unencrypted
  -> clear; ScrambledUncracked -> hard-fail.
ensure_title_decryptable hard-fails an uncrackable DVD title even when
detection missed, and passes a title that resolved its OWN valid key
regardless of the disc-wide css_error. descramble_region is unchanged from
v1.5.1 (validated-key seed).

Also rename the unlocker report's DVD entry CSS -> DVD. Bump 1.5.2.
This commit is contained in:
Matthew Jackson
2026-07-22 13:07:26 -07:00
parent bb59166e48
commit 34c5293704
4 changed files with 472 additions and 119 deletions
+17 -5
View File
@@ -409,11 +409,23 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
// avoids disturbing the mux reader below. 64 sectors is a
// file-safe batch for an ISO. AACS / single-VTS paths are
// unchanged (decrypt_keys_for_title short-circuits to decrypt_keys).
let (keys, title_is_clear) =
match crate::io::file_sector_source::FileSectorSource::open(path) {
Ok(mut crack_reader) => disc.decrypt_keys_for_title(idx, &mut crack_reader, 64),
Err(_) => (disc.decrypt_keys(), false),
};
//
// Only a DVD needs this fresh reader (its per-title crack reads the
// title's sectors); AACS / unencrypted resolve their keys from
// `decrypt_keys()` with NO read, so we must not open — and fail on —
// a probe handle for them (v1.5.1 tolerated an open blip on non-DVDs).
// For a DVD the reader IS required, so a failed open is PROPAGATED as
// a real, loud, retryable I/O error — never guessed into a
// `title_is_clear` verdict: guessing `true` would mux a
// detection-miss scrambled DVD keyless (silent garbage); guessing
// `false` would falsely hard-fail an unencrypted DVD.
let (keys, title_is_clear) = if disc.format == crate::disc::DiscFormat::Dvd {
let mut crack_reader = crate::io::file_sector_source::FileSectorSource::open(path)
.map_err(|e| -> io::Error { e.into() })?;
disc.decrypt_keys_for_title(idx, &mut crack_reader, 64)
} else {
(disc.decrypt_keys(), false)
};
// Per-title decrypt gate (parallel to the disc-wide gate above): on
// a multi-VTS CSS disc, the per-title re-crack may return `None` when
// the chosen title's VTS could not be re-cracked. Muxing that would