CSS: fix the decrypted-HD-DVD false E7023 at the detection layer, not the public API

Commit 4cd9b7b ("key the DVD crack on the disc, not on the container") fixed a
real bug — a decrypted HD-DVD hit E7023 (CssKeyMissing) because the per-title
CSS crack keyed on the MPEG-PS container, which DVD and HD-DVD share — but did
it by adding a required `disc_format: DiscFormat` parameter to the PUBLIC
`DiscStream::new` and `build_iso_pipeline`, a `disc_format` field to
`MuxInput::Iso`/`Live`, and `DiscFormat::may_have_css`, threading the axis down
through mux/driver and mux/resolve. That changed the public API and broke every
downstream caller's compilation (freemkv-engine's integration test now needed 8
args, autorip's MuxInput arms a new field). 1.6.4 shipped and worked with these
exact signatures; a bug fix must not reshape them, and needing a whole
disc-format plumb for HD-DVD was a code smell.

Revert all of that plumbing (public signatures restored to their pre-4cd9b7b
form; no `disc_format` parameter or field, no `may_have_css`, anywhere), and fix
the ACTUAL bug where it lives: the scramble-detection heuristic.

Root cause: `is_scrambled_pack` counted a sector as CSS scramble evidence on
pack-start (00 00 01 BA) + bits 4-5 of byte 0x14. Offset 0x14 is only the PES
scrambling-control field when the sector is a genuine elementary-stream pack. An
HD-DVD `.evo` RDI navigation pack is private_stream_2 (stream_id 0xBF), an
MPEG-PS pack exactly like a DVD VOB, whose byte 0x14 is raw nav payload that
routinely has bits 4-5 set. On a decrypted HD-DVD (None keys, MPEG-PS, so it
reaches the crack) those nav packs flipped the scan's `saw_scrambled` flag; the
crack then found no key — there is no CSS on an HD-DVD — and the scan returned
ScrambledUncracked, hard-failing a good disc with E7023.

Fix: exclude the MPEG-PS structural stream_ids CSS never scrambles — system
header (0xBB), padding (0xBE), private_stream_2 (0xBF) — by the stream_id at
offset 0x11. This is the refinement the DVD design notes already called for
("matches CrackTitleKey"). It needs no format plumbing because byte 0x11 lives
in the CSS-clear header (0x00-0x7F, untouched by scrambling), so it is the true
stream_id even on ciphertext. A decrypted HD-DVD now scans to Unencrypted and
muxes cleanly.

The DVD CSS crack is preserved and proven: a genuinely CSS-scrambled DVD sector
is always video (0xE0-0xEF) or private_stream_1 (0xBD), never an excluded id, so
its scrambled packs still set saw_scrambled and still hard-fail an uncrackable
disc — the "ciphertext muxed as plaintext at rc=0" catastrophe cannot slip
through. Red-before-green both directions: dropping the 0x11 exclusion turns the
decrypted-HD-DVD case back into E7023; inverting it (only nav ids count) turns a
real uncrackable DVD into Unencrypted and strands a crackable one. Both mutations
are caught by tests.

Gate (cargo +1.97): fmt, clippy --all-targets -D warnings, 3539 tests green;
freemkv-engine and autorip both compile against this tree again; precommit.sh
libfreemkv clean.
This commit is contained in:
Matthew Jackson
2026-08-18 23:26:22 -07:00
parent efb69e3ba5
commit 25893f1be4
7 changed files with 150 additions and 307 deletions
+4
View File
@@ -114,6 +114,10 @@ pub mod coding_type {
pub mod pes_stream_id { pub mod pes_stream_id {
/// Video stream (`110x xxxx`; freemkv emits the base id `0xE0`). /// Video stream (`110x xxxx`; freemkv emits the base id `0xE0`).
pub const VIDEO: u8 = 0xE0; pub const VIDEO: u8 = 0xE0;
/// system_header start code — the MPEG-PS `00 00 01 BB` structural header
/// (rate/bound bounds), never an elementary stream. On a DVD NAV pack it
/// follows the pack header, so it lands at sector offset 0x11.
pub const SYSTEM_HEADER: u8 = 0xBB;
/// private_stream_1 — AC-3 / DTS / LPCM / PGS subtitle payloads. /// private_stream_1 — AC-3 / DTS / LPCM / PGS subtitle payloads.
pub const PRIVATE_STREAM_1: u8 = 0xBD; pub const PRIVATE_STREAM_1: u8 = 0xBD;
/// padding_stream — stuffing bytes only, no payload to demux. /// padding_stream — stuffing bytes only, no payload to demux.
+135 -138
View File
@@ -146,14 +146,7 @@ pub fn crack_key_outcome(
/// ("reading is reading"). CSS keys are per-VTS and crackable from the scrambled /// ("reading is reading"). CSS keys are per-VTS and crackable from the scrambled
/// data itself, so a `None`/MPEG-PS title cracks its own key here, in playback /// data itself, so a `None`/MPEG-PS title cracks its own key here, in playback
/// order over `extents`. Everything else is left untouched: /// order over `extents`. Everything else is left untouched:
/// - a disc format that cannot carry CSS (`!disc_format.may_have_css()`, i.e. /// - AACS keys (HD-DVD `.evo` is also MPEG-PS but arrives as `Aacs`) — no CSS.
/// HD-DVD and the BD families) — no CSS exists there to crack. This is the
/// DISC-FORMAT axis and it is separate from `format`, the container: HD-DVD
/// `.evo` is MPEG-PS exactly like DVD `.vob`, so the container alone cannot
/// tell them apart. `DiscFormat::Unknown` counts as "may have CSS" — see
/// [`crate::disc::DiscFormat::may_have_css`] for why the safe default is to
/// crack.
/// - AACS keys (an encrypted HD-DVD `.evo` also arrives as `Aacs`) — no CSS.
/// - a title that already carries a key — nothing to resolve. /// - a title that already carries a key — nothing to resolve.
/// - a genuinely clear DVD (no scrambled sector) — stays `None`, a mux no-op. /// - a genuinely clear DVD (no scrambled sector) — stays `None`, a mux no-op.
/// ///
@@ -164,18 +157,12 @@ pub fn crack_key_outcome(
/// yield its key, so an all-titles rip skips this title and finishes the rest. /// yield its key, so an all-titles rip skips this title and finishes the rest.
/// The whole-disc failure is [`crate::error::Error::CssNoDiscKey`], raised by /// The whole-disc failure is [`crate::error::Error::CssNoDiscKey`], raised by
/// `Disc::ensure_decryptable_keys` from the scan's `css_error`. /// `Disc::ensure_decryptable_keys` from the scan's `css_error`.
// Eight reader/extent/key/format/mode params is inherent to a shared step that
// must be callable identically from both read paths; the two format params are
// the whole point of this function's contract (container vs disc family) and
// bundling them into a struct would only move the same fields around.
#[allow(clippy::too_many_arguments)]
pub(crate) fn resolve_dvd_title_key( pub(crate) fn resolve_dvd_title_key(
reader: &mut dyn SectorSource, reader: &mut dyn SectorSource,
extents: &[Extent], extents: &[Extent],
keys: &mut crate::decrypt::DecryptKeys, keys: &mut crate::decrypt::DecryptKeys,
batch_sectors: u16, batch_sectors: u16,
format: crate::disc::ContentFormat, format: crate::disc::ContentFormat,
disc_format: crate::disc::DiscFormat,
raw: bool, raw: bool,
halt: Option<&crate::halt::Halt>, halt: Option<&crate::halt::Halt>,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
@@ -186,23 +173,8 @@ pub(crate) fn resolve_dvd_title_key(
if raw { if raw {
return Ok(()); return Ok(());
} }
// The crack is gated on TWO axes, and both are load-bearing:
// * `format == MpegPs` — the CONTAINER, i.e. "CSS descrambles 2048-byte
// program-stream sectors, not BD transport packets";
// * `disc_format.may_have_css()` — the DISC FORMAT, i.e. "this family can
// carry CSS at all".
// Keying on the container ALONE was the defect: `ContentFormat::MpegPs`
// covers HD-DVD `.evo` as well as DVD `.vob` (both arms of the tree
// dispatch in `Disc::scan_with` set it), and HD-DVD is AACS — it has no CSS
// to find. Every HD-DVD title therefore paid a 50_000-sector crack scan
// that could not succeed, and a scan that came back `ScrambledUncracked`
// hard-failed a good disc with `CssKeyMissing` (E7023). `may_have_css` is
// deliberately false ONLY for the families proven CSS-free, so `Unknown`
// still cracks: skipping the crack on a real DVD would mux ciphertext as
// plaintext at exit 0, which is far worse than a wasted scan.
if matches!(keys, crate::decrypt::DecryptKeys::None) if matches!(keys, crate::decrypt::DecryptKeys::None)
&& format == crate::disc::ContentFormat::MpegPs && format == crate::disc::ContentFormat::MpegPs
&& disc_format.may_have_css()
{ {
// `halt` threads the caller's cancellation token so /api/stop can // `halt` threads the caller's cancellation token so /api/stop can
// interrupt a long crack scan (the old scan-time crack honored it too). // interrupt a long crack scan (the old scan-time crack honored it too).
@@ -227,19 +199,6 @@ pub(crate) fn resolve_dvd_title_key(
} }
CrackOutcome::Unencrypted => {} CrackOutcome::Unencrypted => {}
} }
} else if matches!(keys, crate::decrypt::DecryptKeys::None)
&& format == crate::disc::ContentFormat::MpegPs
{
// The skip is the interesting event, so it must not be silent: an
// MPEG-PS title with no key that does NOT get cracked is precisely the
// shape of the catastrophic bug (scrambled passthrough), so the log
// records WHICH disc format bought the skip. On an HD-DVD this line is
// the proof the 50_000-sector scan was avoided on purpose.
tracing::debug!(
target: "mux",
disc_format = ?disc_format,
"css crack skipped: disc format cannot carry CSS"
);
} }
Ok(()) Ok(())
} }
@@ -590,6 +549,35 @@ pub(crate) const PACK_START: [u8; 4] = [0x00, 0x00, 0x01, 0xBA];
/// scrambled feature is made of valid PS packs, so its scrambled sectors still /// scrambled feature is made of valid PS packs, so its scrambled sectors still
/// pass this check and still drive `ScrambledUncracked` when no key cracks. /// pass this check and still drive `ScrambledUncracked` when no key cracks.
/// ///
/// The pack-start check alone is not enough, and the sector's PES `stream_id`
/// (offset 0x11, the byte after the 14-byte pack header's `00 00 01` PES
/// prefix) is the second load-bearing gate. CSS scrambles ONLY elementary
/// streams — video (`0xE0..=0xEF`) and private_stream_1 audio/subpicture
/// (`0xBD`) — and it never touches the clear header, so byte 0x11 is the TRUE
/// stream_id even on a scrambled sector. The MPEG-PS structural packets that
/// are never CSS-scrambled — system_header (`0xBB`), padding (`0xBE`) and
/// private_stream_2 (`0xBF`, DVD PCI/DSI navigation) — must be excluded,
/// because on those the byte at 0x14 is NOT a PES scrambling-control field but
/// raw payload/structure whose bits 4-5 land set by chance.
///
/// This is the DETECTION defect a decrypted HD-DVD tripped: an HD-DVD `.evo` is
/// MPEG-PS exactly like a DVD `.vob`, and its RDI navigation packs are
/// private_stream_2 (`0xBF`) whose payload byte at 0x14 routinely has bits 4-5
/// set. With no `0x11` gate those nav packs flipped the crack scan's
/// `saw_scrambled` flag on a disc that carries no CSS at all; the crack then
/// found no key (there is none) and the scan returned `ScrambledUncracked`,
/// hard-failing a perfectly good HD-DVD with `CssKeyMissing` — E7023. Excluding
/// `0xBB/0xBE/0xBF` at 0x11 makes the evidence gate match what the crack itself
/// can act on (the Stevenson attack only recovers a key from a scrambled ES
/// pack), so a decrypted HD-DVD now scans to `Unencrypted` and muxes cleanly.
///
/// This does NOT weaken the genuine "encrypted but uncrackable" hard-fail on a
/// real DVD: a scrambled DVD feature is made of video (`0xE0..`) and
/// private_stream_1 (`0xBD`) packs, none of which are excluded, so its
/// scrambled sectors still set `saw_scrambled` and still drive
/// `ScrambledUncracked` when no key cracks. Byte 0x11 is in the clear header,
/// so a scrambled DVD pack can never masquerade as `0xBB/0xBE/0xBF`.
///
/// The DESCRAMBLE path gates on this same function — [`descramble_sector`] and /// The DESCRAMBLE path gates on this same function — [`descramble_sector`] and
/// [`descramble_region`] both call it, not the raw flag test — because the raw /// [`descramble_region`] both call it, not the raw flag test — because the raw
/// test does not merely mis-skip a sector there: it descrambles one that was /// test does not merely mis-skip a sector there: it descrambles one that was
@@ -598,7 +586,14 @@ pub(crate) const PACK_START: [u8; 4] = [0x00, 0x00, 0x01, 0xBA];
/// lost 1912 of its 2048 bytes, taking TT_SRPT with it, and the disc's 38 /// lost 1912 of its 2048 bytes, taking TT_SRPT with it, and the disc's 38
/// titles became 10 — silently, at exit 0. One gate, both paths. /// titles became 10 — silently, at exit 0. One gate, both paths.
pub fn is_scrambled_pack(sector: &[u8]) -> bool { pub fn is_scrambled_pack(sector: &[u8]) -> bool {
sector.len() >= 2048 && sector[0x00..0x04] == PACK_START && (sector[0x14] >> 4) & 0x03 != 0 use crate::consts::pes_stream_id::{PADDING_STREAM, PRIVATE_STREAM_2, SYSTEM_HEADER};
sector.len() >= 2048
&& sector[0x00..0x04] == PACK_START
&& !matches!(
sector[0x11],
SYSTEM_HEADER | PADDING_STREAM | PRIVATE_STREAM_2
)
&& (sector[0x14] >> 4) & 0x03 != 0
} }
#[cfg(test)] #[cfg(test)]
@@ -798,6 +793,52 @@ mod tests {
); );
} }
/// Detection fix: `is_scrambled_pack` must EXCLUDE the MPEG-PS structural
/// packets that CSS never scrambles — system_header (0xBB), padding (0xBE)
/// and private_stream_2 (0xBF, DVD PCI/DSI and HD-DVD `.evo` RDI nav) — by
/// their `stream_id` at offset 0x11. On those packets byte 0x14 is raw
/// payload/structure, not a PES scrambling-control field, so its bits 4-5
/// land set by chance.
///
/// This is the exact decrypted-HD-DVD defect: an `.evo` RDI pack is
/// private_stream_2 (0xBF) with the pack-start code and bits set at 0x14, so
/// the old gate flipped `saw_scrambled` on a CSS-free disc → the crack found
/// no key → `ScrambledUncracked` → E7023 on a good HD-DVD.
///
/// Grounding: `!matches!(sector[0x11], SYSTEM_HEADER | PADDING_STREAM |
/// PRIVATE_STREAM_2)`. Mutation: drop the 0x11 exclusion → the 0xBF nav pack
/// counts as scrambled evidence and the first assert fails. The video and
/// private_stream_1 packs prove the gate does NOT reject a real scrambled
/// DVD sector (the catastrophic direction).
#[test]
fn is_scrambled_pack_excludes_nav_and_structural_stream_ids() {
use crate::consts::pes_stream_id::{
PADDING_STREAM, PRIVATE_STREAM_1, PRIVATE_STREAM_2, SYSTEM_HEADER, VIDEO, VIDEO_MAX,
};
// A pack-start pack with 0x14 scramble bits set, varying only 0x11.
let mut s = vec![0u8; 2048];
s[0x00..0x04].copy_from_slice(&PACK_START);
s[0x14] = 0x30;
for excluded in [SYSTEM_HEADER, PADDING_STREAM, PRIVATE_STREAM_2] {
s[0x11] = excluded;
assert!(
!is_scrambled_pack(&s),
"stream_id {excluded:#04x} is a structural/nav pack CSS never scrambles — \
it must NOT count as scramble evidence (else a decrypted HD-DVD RDI pack E7023)"
);
}
// A genuinely scramblable elementary-stream pack must STILL register —
// proving the exclusion did not weaken real-DVD CSS detection.
for scramblable in [VIDEO, VIDEO_MAX, PRIVATE_STREAM_1, 0xE2] {
s[0x11] = scramblable;
assert!(
is_scrambled_pack(&s),
"stream_id {scramblable:#04x} is a scramblable ES pack — a real CSS DVD's \
scrambled sector must still be counted, never passed through as plaintext"
);
}
}
// ── crack_key scanning over a mock SectorSource ──────────────────────── // ── crack_key scanning over a mock SectorSource ────────────────────────
/// Records every (lba, count) read; returns a caller-supplied flag byte at /// Records every (lba, count) read; returns a caller-supplied flag byte at
@@ -819,6 +860,11 @@ mod tests {
/// a damaged region. `Some(0)` is the degenerate case that must not /// a damaged region. `Some(0)` is the degenerate case that must not
/// spin the scan. /// spin the scan.
short_read: Option<usize>, short_read: Option<usize>,
/// PES `stream_id` written at offset 0x11 of each uniform-fill sector.
/// `0x00` (the default) is a scramblable-looking pack; set to
/// private_stream_2 (`0xBF`) to model an HD-DVD `.evo` RDI nav pack,
/// which carries the pack-start code and 0x14 bits but no CSS.
stream_id: u8,
} }
impl MockSource { impl MockSource {
@@ -830,6 +876,7 @@ mod tests {
lock_all: false, lock_all: false,
crackable: None, crackable: None,
short_read: None, short_read: None,
stream_id: 0x00,
} }
} }
} }
@@ -904,11 +951,14 @@ mod tests {
} }
_ => { _ => {
// Real DVD video sectors always open with the MPEG-PS // Real DVD video sectors always open with the MPEG-PS
// pack-start code; `is_scrambled_pack` (Fix 3) requires it // pack-start code; `is_scrambled_pack` requires it before
// before trusting the 0x14 scramble bits, so the fixture // trusting the 0x14 scramble bits, so the fixture must
// must include it for a `flag_byte` of 0x30 to register // include it for a `flag_byte` of 0x30 to register as
// as scrambled. // scrambled. `stream_id` (byte 0x11) defaults to 0x00 (a
// scramblable pack); an HD-DVD RDI nav pack sets it to
// private_stream_2 (0xBF), which the 0x11 exclusion drops.
buf[base..base + 4].copy_from_slice(&PACK_START); buf[base..base + 4].copy_from_slice(&PACK_START);
buf[base + 0x11] = self.stream_id;
buf[base + 0x14] = self.flag_byte; buf[base + 0x14] = self.flag_byte;
} }
} }
@@ -1496,7 +1546,6 @@ mod tests {
&mut keys, &mut keys,
4, 4,
crate::disc::ContentFormat::MpegPs, crate::disc::ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
false, false,
None, None,
) )
@@ -1527,7 +1576,6 @@ mod tests {
&mut keys, &mut keys,
4, 4,
crate::disc::ContentFormat::MpegPs, crate::disc::ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
false, false,
None, None,
) )
@@ -1546,6 +1594,44 @@ mod tests {
); );
} }
/// The decrypted-HD-DVD regression, end to end through `resolve_dvd_title_key`.
/// An `.evo` is MPEG-PS (`ContentFormat::MpegPs`) exactly like a DVD `.vob`
/// and carries `None` keys once decrypted, so it reaches the CSS crack. Its
/// RDI navigation packs are private_stream_2 (0xBF) with the pack-start code
/// and bits set at offset 0x14 — but HD-DVD carries no CSS at all. The 0x11
/// exclusion in `is_scrambled_pack` keeps those nav packs from flipping the
/// scan's `saw_scrambled` gate, so the scan returns `Unencrypted` and the
/// title muxes cleanly instead of hard-failing.
///
/// Catches the mutation of dropping the 0x11 exclusion: without it every 0xBF
/// RDI pack counts as scramble evidence, the crack finds no key (there is
/// none), and the scan returns `ScrambledUncracked` → `CssKeyMissing` (E7023)
/// on a perfectly good HD-DVD — the exact defect a real CI run produced.
#[test]
fn resolve_dvd_title_key_decrypted_hddvd_rdi_packs_scan_clean_no_e7023() {
let mut src = MockSource::new(0x30); // 0x14 bits set…
src.stream_id = crate::consts::pes_stream_id::PRIVATE_STREAM_2; // …but a 0xBF nav pack
let extents = [Extent {
start_lba: 0,
sector_count: 64,
}];
let mut keys = crate::decrypt::DecryptKeys::None;
resolve_dvd_title_key(
&mut src,
&extents,
&mut keys,
8,
crate::disc::ContentFormat::MpegPs,
false,
None,
)
.expect("a decrypted HD-DVD's RDI nav packs are not CSS — the scan must not hard-fail");
assert!(
matches!(keys, crate::decrypt::DecryptKeys::None),
"no CSS key exists on an HD-DVD; keys must stay None and the title mux clean"
);
}
/// `raw` is deliberate ciphertext passthrough: even a scrambled-uncrackable /// `raw` is deliberate ciphertext passthrough: even a scrambled-uncrackable
/// title must return `Ok` and leave `keys` untouched (`None`) — no crack, no /// title must return `Ok` and leave `keys` untouched (`None`) — no crack, no
/// hard-fail. This is the `--raw` guarantee. /// hard-fail. This is the `--raw` guarantee.
@@ -1564,7 +1650,6 @@ mod tests {
&mut keys, &mut keys,
4, 4,
crate::disc::ContentFormat::MpegPs, crate::disc::ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
true, // raw true, // raw
None, None,
) )
@@ -1601,7 +1686,6 @@ mod tests {
&mut keys, &mut keys,
4, 4,
crate::disc::ContentFormat::MpegPs, crate::disc::ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
false, false,
None, None,
) )
@@ -1616,91 +1700,6 @@ mod tests {
); );
} }
/// HD-DVD: `.evo` is MPEG-PS exactly like DVD `.vob`, so the CONTAINER
/// cannot tell the two apart — the disc-format axis must. HD-DVD is an AACS
/// family and carries no CSS at all, yet the old container-only gate sent
/// every HD-DVD title into a 50_000-sector crack scan and, when the scan
/// came back `ScrambledUncracked`, refused a good disc with `CssKeyMissing`
/// (E7023) — what a real CI run produced on the HD-DVD fixture.
///
/// Catches the mutation of dropping `disc_format.may_have_css()` from the
/// gate (or listing HD-DVD as CSS-capable): the source here is `lock_all`,
/// so ANY read the crack performs drives `ScrambledUncracked` → the call
/// returns `Err`. The zero-reads assertion is the stronger claim: the scan
/// must not merely survive, it must never start.
#[test]
fn resolve_dvd_title_key_hddvd_never_enters_css_crack() {
let mut src = MockSource::new(0x00);
src.lock_all = true; // would hard-fail E7023 IF the crack ran
let extents = [Extent {
start_lba: 0,
sector_count: 4,
}];
let mut keys = crate::decrypt::DecryptKeys::None;
resolve_dvd_title_key(
&mut src,
&extents,
&mut keys,
4,
crate::disc::ContentFormat::MpegPs,
crate::disc::DiscFormat::HdDvd,
false,
None,
)
.expect("an HD-DVD carries no CSS — it must never be refused for a missing CSS key");
assert!(
matches!(keys, crate::decrypt::DecryptKeys::None),
"no CSS key may be installed on an AACS-family disc"
);
assert!(
src.reads.borrow().is_empty(),
"the crack scan must not read a single sector on an HD-DVD"
);
}
/// The safety valve, and the reason the gate is a NEGATIVE test rather than
/// `disc_format == DiscFormat::Dvd`: a caller that cannot name the disc
/// (`DiscFormat::Unknown` — e.g. a bare reader with no scan behind it) must
/// STILL reach the crack. The two failure directions are asymmetric — a
/// needless scan is recoverable, while skipping the crack on a real DVD
/// muxes ciphertext as plaintext at exit 0.
///
/// Catches the mutation of "simplifying" `may_have_css()` into a positive
/// `== DiscFormat::Dvd` allow-list, which would silently strand every
/// unknown-format DVD in scrambled passthrough: the crackable sector here
/// stops being cracked and `keys` stays `None`.
#[test]
fn resolve_dvd_title_key_unknown_disc_format_still_cracks() {
let title_key = [0x42, 0x13, 0x37, 0xBE, 0xEF];
let seed = [0x11, 0x22, 0x33, 0x44, 0x55];
let crackable = crackable_sector(&title_key, &seed, 8);
let mut src = MockSource::new(0x00);
src.crackable = Some((1003, crackable));
let extents = [Extent {
start_lba: 1000,
sector_count: 50,
}];
let mut keys = crate::decrypt::DecryptKeys::None;
resolve_dvd_title_key(
&mut src,
&extents,
&mut keys,
4,
crate::disc::ContentFormat::MpegPs,
crate::disc::DiscFormat::Unknown,
false,
None,
)
.expect("an unknown disc format must still resolve a crackable CSS title");
match keys {
crate::decrypt::DecryptKeys::Css { title_key: got } => assert_eq!(
got, title_key,
"an unknown-format MPEG-PS title must be cracked, not passed through"
),
_ => panic!("expected Css key: Unknown must default to CSS-capable, never skip"),
}
}
/// Clear DVD: a `None`-keyed MPEG-PS title with no scrambled sector stays /// Clear DVD: a `None`-keyed MPEG-PS title with no scrambled sector stays
/// `None` (a mux no-op) and returns `Ok` — genuinely-unencrypted DVDs pass. /// `None` (a mux no-op) and returns `Ok` — genuinely-unencrypted DVDs pass.
#[test] #[test]
@@ -1717,7 +1716,6 @@ mod tests {
&mut keys, &mut keys,
4, 4,
crate::disc::ContentFormat::MpegPs, crate::disc::ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
false, false,
None, None,
) )
@@ -1749,7 +1747,6 @@ mod tests {
&mut keys, &mut keys,
4, 4,
crate::disc::ContentFormat::MpegPs, crate::disc::ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
false, false,
Some(&halt), Some(&halt),
) )
-41
View File
@@ -112,47 +112,6 @@ pub enum DiscFormat {
Unknown, Unknown,
} }
impl DiscFormat {
/// Can a disc of this format carry CSS (DVD Content Scramble System)?
///
/// This is the axis the CSS crack MUST be gated on. It is NOT
/// [`ContentFormat`]: `ContentFormat::MpegPs` is the CONTAINER (MPEG
/// program stream) and the tree dispatch in [`Disc::scan_with`] assigns it
/// to the HD-DVD (`/HVDVD_TS`, `.evo`) arm exactly as it does to the DVD
/// (`/VIDEO_TS`, `.vob`) arm. HD-DVD is an AACS family and carries no CSS
/// whatsoever, so gating on the container sent every HD-DVD title into a
/// 50_000-sector CSS crack scan it could never satisfy — and when that scan
/// reported `ScrambledUncracked`, refused a perfectly good HD-DVD with
/// `Error::CssKeyMissing` (E7023). That is what a real CI run produced on
/// the HD-DVD fixture. `Disc::scan_image`'s eager image crack had already
/// learned this lesson and gates on `DiscFormat::Dvd`; the mux-side gate in
/// [`crate::css::resolve_dvd_title_key`] had not.
///
/// The test is deliberately NEGATIVE — "everything except the families
/// proven CSS-free" — rather than a positive `== DiscFormat::Dvd`, because
/// the two failure directions are wildly asymmetric:
///
/// * running CSS on a disc that has none costs a wasted scan (and, at
/// worst, a false refusal): loud, visible, recoverable;
/// * NOT running CSS on a real DVD makes the mux pass SCRAMBLED bytes
/// through as plaintext and exit 0 with garbage — a failure that looks
/// like success. That one already shipped once (~9 MB of ciphertext
/// inside a main-movie m2ts at rc=0).
///
/// So the safe default is "attempt the crack". [`DiscFormat::Unknown`] —
/// the value a caller that never scanned the disc supplies — therefore
/// answers `true`, and any variant added to this enum in future answers
/// `true` until someone deliberately proves it CSS-free and adds it to the
/// exclusion list. A positive `matches!` list would default the other way,
/// i.e. toward the catastrophic direction.
pub fn may_have_css(self) -> bool {
!matches!(
self,
DiscFormat::HdDvd | DiscFormat::BluRay | DiscFormat::Uhd | DiscFormat::Fmts
)
}
}
/// Disc playback region. /// Disc playback region.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum DiscRegion { pub enum DiscRegion {
+5 -68
View File
@@ -208,25 +208,12 @@ impl DiscStream {
/// Works with physical drives and ISO files — both implement SectorSource. /// Works with physical drives and ISO files — both implement SectorSource.
/// The caller opens the source, scans for titles/keys, and passes them in. /// The caller opens the source, scans for titles/keys, and passes them in.
/// The stream handles demuxing, decryption, and codec parsing internally. /// The stream handles demuxing, decryption, and codec parsing internally.
///
/// `content_format` is the CONTAINER (TS vs PS demuxer). `disc_format` is
/// the DISC FAMILY, and it exists as its own parameter because the two are
/// not interchangeable: DVD and HD-DVD are both `ContentFormat::MpegPs`,
/// yet only DVD can carry CSS. It gates the per-title CSS crack below. A
/// caller that genuinely does not know the disc passes
/// [`crate::disc::DiscFormat::Unknown`], which still attempts the crack —
/// the safe direction (see [`crate::disc::DiscFormat::may_have_css`]).
// Eight params is inherent to a constructor that takes the source, the
// title, the keys, both format axes (container and disc family) and the
// read-mode flags; grouping them would only relocate the same fields.
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
mut reader: Box<dyn SectorSource>, mut reader: Box<dyn SectorSource>,
title: DiscTitle, title: DiscTitle,
mut decrypt_keys: crate::decrypt::DecryptKeys, mut decrypt_keys: crate::decrypt::DecryptKeys,
batch_sectors: u16, batch_sectors: u16,
content_format: crate::disc::ContentFormat, content_format: crate::disc::ContentFormat,
disc_format: crate::disc::DiscFormat,
raw: bool, raw: bool,
halt: Option<Halt>, halt: Option<Halt>,
) -> std::io::Result<Self> { ) -> std::io::Result<Self> {
@@ -236,20 +223,17 @@ impl DiscStream {
// Resolve this title's CSS key from the reader if the caller supplied // Resolve this title's CSS key from the reader if the caller supplied
// none — the SAME shared step the file-backed mux highway // none — the SAME shared step the file-backed mux highway
// (`build_iso_pipeline`) uses, so single-pass and multi-pass descramble a // (`build_iso_pipeline`) uses, so single-pass and multi-pass descramble a
// DVD identically. No-op for a disc format that cannot carry CSS (HD-DVD // DVD identically. No-op for AACS / already-keyed / genuinely-clear input
// and the BD families — `disc_format`, NOT the MPEG-PS container, which // or `raw`; a scrambled-but-uncrackable DVD is a hard `CssKeyMissing`.
// DVD and HD-DVD share), for AACS / already-keyed / genuinely-clear // `halt` is passed here (not deferred to `with_halt`) so a Stop during the
// input, and for `raw`; a scrambled-but-uncrackable DVD is a hard // crack scan is honored — the scan runs at construction, before the caller
// `CssKeyMissing`. `halt` is passed here (not deferred to `with_halt`) // can attach a token.
// so a Stop during the crack scan is honored — the scan runs at
// construction, before the caller can attach a token.
crate::css::resolve_dvd_title_key( crate::css::resolve_dvd_title_key(
&mut *reader, &mut *reader,
&extents, &extents,
&mut decrypt_keys, &mut decrypt_keys,
batch_sectors, batch_sectors,
content_format, content_format,
disc_format,
raw, raw,
halt.as_ref(), halt.as_ref(),
)?; )?;
@@ -1218,7 +1202,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, // request 8 sectors (16384 B); the source delivers 1 (2048 B) 8, // request 8 sectors (16384 B); the source delivers 1 (2048 B)
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -1309,7 +1292,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -1346,7 +1328,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -1459,7 +1440,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -1503,7 +1483,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
crate::disc::ContentFormat::BdTs, crate::disc::ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -1539,7 +1518,6 @@ mod tests {
aacs, aacs,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -1702,7 +1680,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -1819,7 +1796,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -1883,7 +1859,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -2004,7 +1979,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -2064,7 +2038,6 @@ mod tests {
keys, keys,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -2164,7 +2137,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -2209,7 +2181,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
crate::disc::ContentFormat::BdTs, crate::disc::ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
@@ -2267,7 +2238,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::MpegPs, ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
false, false,
None, None,
); );
@@ -2277,36 +2247,6 @@ mod tests {
); );
} }
/// The HD-DVD counterpart of the test above, pinned at the SAME boundary so
/// the disc-format axis is proven to reach the shared CSS step through this
/// constructor and not just inside `css::resolve_dvd_title_key`.
///
/// Byte-for-byte identical input to `disc_stream_new_dvd_none_scrambled_hard_fails`
/// — same `LockedReader`, same MPEG-PS title, same `None` keys — with only
/// the disc format changed. The DVD case must still be refused (E7023) and
/// the HD-DVD case must construct: an HD-DVD is AACS and has no CSS, so
/// there is no CSS key for it to be missing. Catches the mutation of
/// dropping `disc_format` from `DiscStream::new`'s plumbing (or hardcoding
/// a CSS-capable value there), which is exactly the shape of the shipped
/// defect: E7023 on a perfectly good HD-DVD.
#[test]
fn disc_stream_new_hddvd_none_scrambled_does_not_hard_fail() {
let res = DiscStream::new(
Box::new(LockedReader),
mpegps_title(8),
crate::decrypt::DecryptKeys::None,
8,
ContentFormat::MpegPs,
crate::disc::DiscFormat::HdDvd,
false,
None,
);
assert!(
res.is_ok(),
"an HD-DVD must never be refused for a missing CSS key — it carries no CSS"
);
}
/// `raw` must bypass the CSS crack at the DiscStream boundary too: the same /// `raw` must bypass the CSS crack at the DiscStream boundary too: the same
/// scrambled-uncrackable input that hard-fails above must CONSTRUCT in raw /// scrambled-uncrackable input that hard-fails above must CONSTRUCT in raw
/// mode (ciphertext passthrough), never hard-fail. /// mode (ciphertext passthrough), never hard-fail.
@@ -2318,7 +2258,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::MpegPs, ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
true, // raw true, // raw
None, None,
); );
@@ -2532,7 +2471,6 @@ mod tests {
crate::decrypt::DecryptKeys::None, crate::decrypt::DecryptKeys::None,
8, 8,
ContentFormat::MpegPs, ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
false, false,
None, None,
) )
@@ -2654,7 +2592,6 @@ mod tests {
}, },
3, 3,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
) )
+1 -30
View File
@@ -129,15 +129,6 @@ pub enum MuxInput<'a> {
title: DiscTitle, title: DiscTitle,
/// Container format of the title (TS vs PS demuxer selection). /// Container format of the title (TS vs PS demuxer selection).
format: crate::disc::ContentFormat, format: crate::disc::ContentFormat,
/// The scanned disc's FAMILY (`disc.format`) — a different axis from
/// `format`, which is only the container. DVD and HD-DVD are both
/// `ContentFormat::MpegPs`, yet only DVD can carry CSS, so this is what
/// gates the per-title CSS crack in [`build_iso_pipeline`]. Pass
/// [`crate::disc::DiscFormat::Unknown`] only when the disc was genuinely
/// never scanned: that value still runs the crack, which is the safe
/// direction (skipping it on a real DVD would mux ciphertext as
/// plaintext at exit 0).
disc_format: crate::disc::DiscFormat,
/// Decryption keys for the title (`DecryptKeys::None` for raw/clear). /// Decryption keys for the title (`DecryptKeys::None` for raw/clear).
keys: DecryptKeys, keys: DecryptKeys,
/// Optional read-time key fetch closure (banked by `resolve_keys`). /// Optional read-time key fetch closure (banked by `resolve_keys`).
@@ -160,12 +151,6 @@ pub enum MuxInput<'a> {
title: DiscTitle, title: DiscTitle,
/// Container format (TS vs PS demux selection). /// Container format (TS vs PS demux selection).
format: crate::disc::ContentFormat, format: crate::disc::ContentFormat,
/// The scanned disc's FAMILY (`disc.format`), the CSS-eligibility axis
/// — see [`MuxInput::Iso::disc_format`]. Without it the inline
/// `DiscStream` cannot tell an HD-DVD `.evo` from a DVD `.vob` (both
/// are `ContentFormat::MpegPs`) and would run a CSS crack that an
/// AACS-family disc can never satisfy.
disc_format: crate::disc::DiscFormat,
/// Decryption keys the consumer already banked (`DecryptKeys::None` for /// Decryption keys the consumer already banked (`DecryptKeys::None` for
/// raw/clear). The driver consumes them as-is — never re-resolves. /// raw/clear). The driver consumes them as-is — never re-resolves.
keys: DecryptKeys, keys: DecryptKeys,
@@ -366,7 +351,6 @@ pub fn mux_stream(
path, path,
title, title,
format, format,
disc_format,
keys, keys,
key_fetch, key_fetch,
} => { } => {
@@ -398,7 +382,6 @@ pub fn mux_stream(
keys, keys,
opts.batch_sectors, opts.batch_sectors,
format, format,
disc_format,
opts.raw, opts.raw,
Some(halt.clone()), Some(halt.clone()),
Some(reader_event_fn(events.clone())), Some(reader_event_fn(events.clone())),
@@ -413,7 +396,7 @@ pub fn mux_stream(
// Pull everything we need out of the disc as owned values so the // Pull everything we need out of the disc as owned values so the
// immutable disc borrow is released before the mutable // immutable disc borrow is released before the mutable
// `take_reader` below. // `take_reader` below.
let (mut title, format, disc_format, mut keys, playlist, source) = { let (mut title, format, mut keys, playlist, source) = {
let disc = session.disc().ok_or_else(|| Error::DeviceNotReady { let disc = session.disc().ok_or_else(|| Error::DeviceNotReady {
path: session.device_path().to_string(), path: session.device_path().to_string(),
})?; })?;
@@ -441,15 +424,9 @@ pub fn mux_stream(
}; };
// DVD CSS is per-VTS: resolve the per-title key via the pipeline // DVD CSS is per-VTS: resolve the per-title key via the pipeline
// (see `session_mux_keys`), never the whole-disc `decrypt_keys()`. // (see `session_mux_keys`), never the whole-disc `decrypt_keys()`.
// `disc.content_format` is the container; `disc.format` is
// the disc FAMILY. Both are carried out of the borrow: the
// first picks the demuxer, the second decides whether a CSS
// crack is even meaningful (an HD-DVD is MPEG-PS too, and
// has no CSS).
( (
title, title,
disc.content_format, disc.content_format,
disc.format,
session_mux_keys(disc), session_mux_keys(disc),
playlist, playlist,
source, source,
@@ -491,7 +468,6 @@ pub fn mux_stream(
keys, keys,
opts.batch_sectors, opts.batch_sectors,
format, format,
disc_format,
opts.raw, opts.raw,
Some(halt.clone()), Some(halt.clone()),
)?; )?;
@@ -512,7 +488,6 @@ pub fn mux_stream(
mut reader, mut reader,
title, title,
format, format,
disc_format,
mut keys, mut keys,
key_map, key_map,
} => { } => {
@@ -571,7 +546,6 @@ pub fn mux_stream(
keys, keys,
opts.batch_sectors, opts.batch_sectors,
format, format,
disc_format,
opts.raw, opts.raw,
Some(halt.clone()), Some(halt.clone()),
)?; )?;
@@ -1657,7 +1631,6 @@ mod tests {
path: &iso_path, path: &iso_path,
title, title,
format: crate::disc::ContentFormat::BdTs, format: crate::disc::ContentFormat::BdTs,
disc_format: crate::disc::DiscFormat::BluRay,
keys: DecryptKeys::None, keys: DecryptKeys::None,
key_fetch: None, key_fetch: None,
}, },
@@ -1769,7 +1742,6 @@ mod tests {
reader, reader,
title, title,
format: crate::disc::ContentFormat::BdTs, format: crate::disc::ContentFormat::BdTs,
disc_format: crate::disc::DiscFormat::BluRay,
keys: DecryptKeys::None, keys: DecryptKeys::None,
key_map: Some(map), key_map: Some(map),
}, },
@@ -1892,7 +1864,6 @@ mod tests {
reader, reader,
title, title,
format: crate::disc::ContentFormat::BdTs, format: crate::disc::ContentFormat::BdTs,
disc_format: crate::disc::DiscFormat::BluRay,
keys, keys,
key_map: None, // plain AACS disc: the driver must resolve the base map key_map: None, // plain AACS disc: the driver must resolve the base map
}, },
+5 -26
View File
@@ -613,12 +613,6 @@ where
} }
let title = disc.titles[idx].clone(); let title = disc.titles[idx].clone();
let format = disc.content_format; let format = disc.content_format;
// The CSS-eligibility axis handed to the pipeline below. `content_format`
// above is only the container and cannot carry this decision: HD-DVD `.evo`
// is MPEG-PS exactly like DVD `.vob`, and gating the crack on the container
// is what sent every HD-DVD through a CSS scan it could never satisfy.
// Same value `is_dvd` was derived from further up.
let disc_format = disc.format;
// ISO file: 8192-sector batch (16 MiB at 2048 B/sector) — // ISO file: 8192-sector batch (16 MiB at 2048 B/sector) —
// sequential read from fast storage, no bad sectors. Empirically // sequential read from fast storage, no bad sectors. Empirically
// optimal; bumping to 16384 sectors (32 MiB) regressed (more cache // optimal; bumping to 16384 sectors (32 MiB) regressed (more cache
@@ -650,7 +644,6 @@ where
effective_keys, effective_keys,
ISO_MUX_BATCH_SECTORS, ISO_MUX_BATCH_SECTORS,
format, format,
disc_format,
opts.raw, opts.raw,
None, None,
None, None,
@@ -2116,11 +2109,6 @@ pub(crate) fn resolve_mux_key_map_cached(
/// - `batch_sectors`: read batch size in logical (2048-byte) sectors — a /// - `batch_sectors`: read batch size in logical (2048-byte) sectors — a
/// throughput/latency tuning knob, not a correctness parameter. /// throughput/latency tuning knob, not a correctness parameter.
/// - `format`: container format (`BdTs` → TS demuxer, `MpegPs` → PS demuxer). /// - `format`: container format (`BdTs` → TS demuxer, `MpegPs` → PS demuxer).
/// - `disc_format`: the disc FAMILY, a separate axis from `format` — DVD and
/// HD-DVD are both `MpegPs`, but only DVD can carry CSS. Gates the per-title
/// CSS crack below. A caller with no scanned disc passes
/// [`crate::disc::DiscFormat::Unknown`], which still cracks (the safe
/// direction — see [`crate::disc::DiscFormat::may_have_css`]).
/// - `raw`: ciphertext passthrough. When `true`, the per-title CSS crack /// - `raw`: ciphertext passthrough. When `true`, the per-title CSS crack
/// (`resolve_dvd_title_key`) is skipped entirely — no key is resolved and a /// (`resolve_dvd_title_key`) is skipped entirely — no key is resolved and a
/// scrambled title is neither descrambled nor hard-failed. /// scrambled title is neither descrambled nor hard-failed.
@@ -2140,7 +2128,6 @@ pub fn build_iso_pipeline<S: SectorSource + Send + 'static>(
mut keys: crate::decrypt::DecryptKeys, mut keys: crate::decrypt::DecryptKeys,
batch_sectors: u16, batch_sectors: u16,
format: ContentFormat, format: ContentFormat,
disc_format: crate::disc::DiscFormat,
raw: bool, raw: bool,
halt: Option<crate::halt::Halt>, halt: Option<crate::halt::Halt>,
event_fn: Option<crate::sector::prefetched::EventFn>, event_fn: Option<crate::sector::prefetched::EventFn>,
@@ -2148,20 +2135,17 @@ pub fn build_iso_pipeline<S: SectorSource + Send + 'static>(
) -> io::Result<PipelinedPesStream> { ) -> io::Result<PipelinedPesStream> {
let extents = title.extents.clone(); let extents = title.extents.clone();
// CSS (DVD) key resolution — the shared per-title step (also used by the // CSS (DVD) key resolution — the shared per-title step (also used by the
// live-drive single-pass `DiscStream`). A `None`/MPEG-PS title on a // live-drive single-pass `DiscStream`). A `None`/MPEG-PS title cracks its own
// CSS-capable DISC FORMAT cracks its own key from the reader in playback // key from the reader in playback order; AACS `.evo` (also MPEG-PS) arrives as
// order; an HD-DVD (also MPEG-PS, but AACS — no CSS exists to find) is // `Aacs` and is untouched; a clear DVD stays `None`; `raw` skips it entirely.
// skipped on the `disc_format` axis; AACS keys are untouched; a clear DVD // Without this a detection-miss CSS DVD would mux scrambled sectors as corrupt
// stays `None`; `raw` skips it entirely. Without this a detection-miss CSS // video. `halt` lets /api/stop interrupt the crack scan.
// DVD would mux scrambled sectors as corrupt video. `halt` lets /api/stop
// interrupt the crack scan.
crate::css::resolve_dvd_title_key( crate::css::resolve_dvd_title_key(
&mut reader, &mut reader,
&extents, &extents,
&mut keys, &mut keys,
batch_sectors, batch_sectors,
format, format,
disc_format,
raw, raw,
halt.as_ref(), halt.as_ref(),
)?; )?;
@@ -3007,7 +2991,6 @@ mod tests {
DecryptKeys::None, DecryptKeys::None,
8192, 8192,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
None, None,
@@ -3050,7 +3033,6 @@ mod tests {
DecryptKeys::None, DecryptKeys::None,
8192, 8192,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
None, None,
@@ -3148,7 +3130,6 @@ mod tests {
DecryptKeys::None, DecryptKeys::None,
8192, 8192,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
None, None,
@@ -3188,7 +3169,6 @@ mod tests {
DecryptKeys::None, DecryptKeys::None,
0, 0,
ContentFormat::BdTs, ContentFormat::BdTs,
crate::disc::DiscFormat::BluRay,
false, false,
None, None,
None, None,
@@ -3231,7 +3211,6 @@ mod tests {
DecryptKeys::None, DecryptKeys::None,
8192, 8192,
ContentFormat::MpegPs, ContentFormat::MpegPs,
crate::disc::DiscFormat::Dvd,
false, false,
None, None,
None, None,
-4
View File
@@ -175,10 +175,6 @@ fn run_to_fvi(image: Vec<u8>, title: DiscTitle, path: &std::path::Path) {
DecryptKeys::None, DecryptKeys::None,
3, // 3-sector (one AACS unit) batches → one source stamp per GOP region 3, // 3-sector (one AACS unit) batches → one source stamp per GOP region
ContentFormat::MpegPs, ContentFormat::MpegPs,
// A DVD-family fixture: the disc-format axis must keep the CSS crack
// reachable here exactly as it is in production (the image is clear, so
// the crack finds nothing and the mux proceeds).
libfreemkv::DiscFormat::Dvd,
false, false,
None, None,
None, None,