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:
@@ -114,6 +114,10 @@ pub mod coding_type {
|
||||
pub mod pes_stream_id {
|
||||
/// Video stream (`110x xxxx`; freemkv emits the base id `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.
|
||||
pub const PRIVATE_STREAM_1: u8 = 0xBD;
|
||||
/// padding_stream — stuffing bytes only, no payload to demux.
|
||||
|
||||
+135
-138
@@ -146,14 +146,7 @@ pub fn crack_key_outcome(
|
||||
/// ("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
|
||||
/// order over `extents`. Everything else is left untouched:
|
||||
/// - a disc format that cannot carry CSS (`!disc_format.may_have_css()`, i.e.
|
||||
/// 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.
|
||||
/// - AACS keys (HD-DVD `.evo` is also MPEG-PS but arrives as `Aacs`) — no CSS.
|
||||
/// - a title that already carries a key — nothing to resolve.
|
||||
/// - 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.
|
||||
/// The whole-disc failure is [`crate::error::Error::CssNoDiscKey`], raised by
|
||||
/// `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(
|
||||
reader: &mut dyn SectorSource,
|
||||
extents: &[Extent],
|
||||
keys: &mut crate::decrypt::DecryptKeys,
|
||||
batch_sectors: u16,
|
||||
format: crate::disc::ContentFormat,
|
||||
disc_format: crate::disc::DiscFormat,
|
||||
raw: bool,
|
||||
halt: Option<&crate::halt::Halt>,
|
||||
) -> std::io::Result<()> {
|
||||
@@ -186,23 +173,8 @@ pub(crate) fn resolve_dvd_title_key(
|
||||
if raw {
|
||||
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)
|
||||
&& format == crate::disc::ContentFormat::MpegPs
|
||||
&& disc_format.may_have_css()
|
||||
{
|
||||
// `halt` threads the caller's cancellation token so /api/stop can
|
||||
// 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 => {}
|
||||
}
|
||||
} 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(())
|
||||
}
|
||||
@@ -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
|
||||
/// 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
|
||||
/// [`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
|
||||
@@ -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
|
||||
/// titles became 10 — silently, at exit 0. One gate, both paths.
|
||||
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)]
|
||||
@@ -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 ────────────────────────
|
||||
|
||||
/// 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
|
||||
/// spin the scan.
|
||||
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 {
|
||||
@@ -830,6 +876,7 @@ mod tests {
|
||||
lock_all: false,
|
||||
crackable: None,
|
||||
short_read: None,
|
||||
stream_id: 0x00,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -904,11 +951,14 @@ mod tests {
|
||||
}
|
||||
_ => {
|
||||
// Real DVD video sectors always open with the MPEG-PS
|
||||
// pack-start code; `is_scrambled_pack` (Fix 3) requires it
|
||||
// before trusting the 0x14 scramble bits, so the fixture
|
||||
// must include it for a `flag_byte` of 0x30 to register
|
||||
// as scrambled.
|
||||
// pack-start code; `is_scrambled_pack` requires it before
|
||||
// trusting the 0x14 scramble bits, so the fixture must
|
||||
// include it for a `flag_byte` of 0x30 to register as
|
||||
// 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 + 0x11] = self.stream_id;
|
||||
buf[base + 0x14] = self.flag_byte;
|
||||
}
|
||||
}
|
||||
@@ -1496,7 +1546,6 @@ mod tests {
|
||||
&mut keys,
|
||||
4,
|
||||
crate::disc::ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1527,7 +1576,6 @@ mod tests {
|
||||
&mut keys,
|
||||
4,
|
||||
crate::disc::ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
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
|
||||
/// title must return `Ok` and leave `keys` untouched (`None`) — no crack, no
|
||||
/// hard-fail. This is the `--raw` guarantee.
|
||||
@@ -1564,7 +1650,6 @@ mod tests {
|
||||
&mut keys,
|
||||
4,
|
||||
crate::disc::ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
true, // raw
|
||||
None,
|
||||
)
|
||||
@@ -1601,7 +1686,6 @@ mod tests {
|
||||
&mut keys,
|
||||
4,
|
||||
crate::disc::ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
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
|
||||
/// `None` (a mux no-op) and returns `Ok` — genuinely-unencrypted DVDs pass.
|
||||
#[test]
|
||||
@@ -1717,7 +1716,6 @@ mod tests {
|
||||
&mut keys,
|
||||
4,
|
||||
crate::disc::ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1749,7 +1747,6 @@ mod tests {
|
||||
&mut keys,
|
||||
4,
|
||||
crate::disc::ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
Some(&halt),
|
||||
)
|
||||
|
||||
@@ -112,47 +112,6 @@ pub enum DiscFormat {
|
||||
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.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DiscRegion {
|
||||
|
||||
+5
-68
@@ -208,25 +208,12 @@ impl DiscStream {
|
||||
/// Works with physical drives and ISO files — both implement SectorSource.
|
||||
/// The caller opens the source, scans for titles/keys, and passes them in.
|
||||
/// 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(
|
||||
mut reader: Box<dyn SectorSource>,
|
||||
title: DiscTitle,
|
||||
mut decrypt_keys: crate::decrypt::DecryptKeys,
|
||||
batch_sectors: u16,
|
||||
content_format: crate::disc::ContentFormat,
|
||||
disc_format: crate::disc::DiscFormat,
|
||||
raw: bool,
|
||||
halt: Option<Halt>,
|
||||
) -> std::io::Result<Self> {
|
||||
@@ -236,20 +223,17 @@ impl DiscStream {
|
||||
// Resolve this title's CSS key from the reader if the caller supplied
|
||||
// none — the SAME shared step the file-backed mux highway
|
||||
// (`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
|
||||
// and the BD families — `disc_format`, NOT the MPEG-PS container, which
|
||||
// DVD and HD-DVD share), for AACS / already-keyed / genuinely-clear
|
||||
// input, and for `raw`; a scrambled-but-uncrackable DVD is a hard
|
||||
// `CssKeyMissing`. `halt` is passed here (not deferred to `with_halt`)
|
||||
// so a Stop during the crack scan is honored — the scan runs at
|
||||
// construction, before the caller can attach a token.
|
||||
// DVD identically. No-op for AACS / already-keyed / genuinely-clear input
|
||||
// or `raw`; a scrambled-but-uncrackable DVD is a hard `CssKeyMissing`.
|
||||
// `halt` is passed here (not deferred to `with_halt`) 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(
|
||||
&mut *reader,
|
||||
&extents,
|
||||
&mut decrypt_keys,
|
||||
batch_sectors,
|
||||
content_format,
|
||||
disc_format,
|
||||
raw,
|
||||
halt.as_ref(),
|
||||
)?;
|
||||
@@ -1218,7 +1202,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8, // request 8 sectors (16384 B); the source delivers 1 (2048 B)
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1309,7 +1292,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1346,7 +1328,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1459,7 +1440,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1503,7 +1483,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
crate::disc::ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1539,7 +1518,6 @@ mod tests {
|
||||
aacs,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1702,7 +1680,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1819,7 +1796,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1883,7 +1859,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2004,7 +1979,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2064,7 +2038,6 @@ mod tests {
|
||||
keys,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2164,7 +2137,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2209,7 +2181,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
crate::disc::ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2267,7 +2238,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
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
|
||||
/// scrambled-uncrackable input that hard-fails above must CONSTRUCT in raw
|
||||
/// mode (ciphertext passthrough), never hard-fail.
|
||||
@@ -2318,7 +2258,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
true, // raw
|
||||
None,
|
||||
);
|
||||
@@ -2532,7 +2471,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2654,7 +2592,6 @@ mod tests {
|
||||
},
|
||||
3,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
|
||||
+1
-30
@@ -129,15 +129,6 @@ pub enum MuxInput<'a> {
|
||||
title: DiscTitle,
|
||||
/// Container format of the title (TS vs PS demuxer selection).
|
||||
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).
|
||||
keys: DecryptKeys,
|
||||
/// Optional read-time key fetch closure (banked by `resolve_keys`).
|
||||
@@ -160,12 +151,6 @@ pub enum MuxInput<'a> {
|
||||
title: DiscTitle,
|
||||
/// Container format (TS vs PS demux selection).
|
||||
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
|
||||
/// raw/clear). The driver consumes them as-is — never re-resolves.
|
||||
keys: DecryptKeys,
|
||||
@@ -366,7 +351,6 @@ pub fn mux_stream(
|
||||
path,
|
||||
title,
|
||||
format,
|
||||
disc_format,
|
||||
keys,
|
||||
key_fetch,
|
||||
} => {
|
||||
@@ -398,7 +382,6 @@ pub fn mux_stream(
|
||||
keys,
|
||||
opts.batch_sectors,
|
||||
format,
|
||||
disc_format,
|
||||
opts.raw,
|
||||
Some(halt.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
|
||||
// immutable disc borrow is released before the mutable
|
||||
// `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 {
|
||||
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
|
||||
// (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,
|
||||
disc.content_format,
|
||||
disc.format,
|
||||
session_mux_keys(disc),
|
||||
playlist,
|
||||
source,
|
||||
@@ -491,7 +468,6 @@ pub fn mux_stream(
|
||||
keys,
|
||||
opts.batch_sectors,
|
||||
format,
|
||||
disc_format,
|
||||
opts.raw,
|
||||
Some(halt.clone()),
|
||||
)?;
|
||||
@@ -512,7 +488,6 @@ pub fn mux_stream(
|
||||
mut reader,
|
||||
title,
|
||||
format,
|
||||
disc_format,
|
||||
mut keys,
|
||||
key_map,
|
||||
} => {
|
||||
@@ -571,7 +546,6 @@ pub fn mux_stream(
|
||||
keys,
|
||||
opts.batch_sectors,
|
||||
format,
|
||||
disc_format,
|
||||
opts.raw,
|
||||
Some(halt.clone()),
|
||||
)?;
|
||||
@@ -1657,7 +1631,6 @@ mod tests {
|
||||
path: &iso_path,
|
||||
title,
|
||||
format: crate::disc::ContentFormat::BdTs,
|
||||
disc_format: crate::disc::DiscFormat::BluRay,
|
||||
keys: DecryptKeys::None,
|
||||
key_fetch: None,
|
||||
},
|
||||
@@ -1769,7 +1742,6 @@ mod tests {
|
||||
reader,
|
||||
title,
|
||||
format: crate::disc::ContentFormat::BdTs,
|
||||
disc_format: crate::disc::DiscFormat::BluRay,
|
||||
keys: DecryptKeys::None,
|
||||
key_map: Some(map),
|
||||
},
|
||||
@@ -1892,7 +1864,6 @@ mod tests {
|
||||
reader,
|
||||
title,
|
||||
format: crate::disc::ContentFormat::BdTs,
|
||||
disc_format: crate::disc::DiscFormat::BluRay,
|
||||
keys,
|
||||
key_map: None, // plain AACS disc: the driver must resolve the base map
|
||||
},
|
||||
|
||||
+5
-26
@@ -613,12 +613,6 @@ where
|
||||
}
|
||||
let title = disc.titles[idx].clone();
|
||||
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) —
|
||||
// sequential read from fast storage, no bad sectors. Empirically
|
||||
// optimal; bumping to 16384 sectors (32 MiB) regressed (more cache
|
||||
@@ -650,7 +644,6 @@ where
|
||||
effective_keys,
|
||||
ISO_MUX_BATCH_SECTORS,
|
||||
format,
|
||||
disc_format,
|
||||
opts.raw,
|
||||
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
|
||||
/// throughput/latency tuning knob, not a correctness parameter.
|
||||
/// - `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
|
||||
/// (`resolve_dvd_title_key`) is skipped entirely — no key is resolved and a
|
||||
/// 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,
|
||||
batch_sectors: u16,
|
||||
format: ContentFormat,
|
||||
disc_format: crate::disc::DiscFormat,
|
||||
raw: bool,
|
||||
halt: Option<crate::halt::Halt>,
|
||||
event_fn: Option<crate::sector::prefetched::EventFn>,
|
||||
@@ -2148,20 +2135,17 @@ pub fn build_iso_pipeline<S: SectorSource + Send + 'static>(
|
||||
) -> io::Result<PipelinedPesStream> {
|
||||
let extents = title.extents.clone();
|
||||
// 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
|
||||
// CSS-capable DISC FORMAT cracks its own key from the reader in playback
|
||||
// order; an HD-DVD (also MPEG-PS, but AACS — no CSS exists to find) is
|
||||
// skipped on the `disc_format` axis; AACS keys are untouched; a clear DVD
|
||||
// stays `None`; `raw` skips it entirely. Without this a detection-miss CSS
|
||||
// DVD would mux scrambled sectors as corrupt video. `halt` lets /api/stop
|
||||
// interrupt the crack scan.
|
||||
// live-drive single-pass `DiscStream`). A `None`/MPEG-PS title cracks its own
|
||||
// key from the reader in playback order; AACS `.evo` (also MPEG-PS) arrives as
|
||||
// `Aacs` and is untouched; a clear DVD stays `None`; `raw` skips it entirely.
|
||||
// Without this a detection-miss CSS DVD would mux scrambled sectors as corrupt
|
||||
// video. `halt` lets /api/stop interrupt the crack scan.
|
||||
crate::css::resolve_dvd_title_key(
|
||||
&mut reader,
|
||||
&extents,
|
||||
&mut keys,
|
||||
batch_sectors,
|
||||
format,
|
||||
disc_format,
|
||||
raw,
|
||||
halt.as_ref(),
|
||||
)?;
|
||||
@@ -3007,7 +2991,6 @@ mod tests {
|
||||
DecryptKeys::None,
|
||||
8192,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
@@ -3050,7 +3033,6 @@ mod tests {
|
||||
DecryptKeys::None,
|
||||
8192,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
@@ -3148,7 +3130,6 @@ mod tests {
|
||||
DecryptKeys::None,
|
||||
8192,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
@@ -3188,7 +3169,6 @@ mod tests {
|
||||
DecryptKeys::None,
|
||||
0,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
@@ -3231,7 +3211,6 @@ mod tests {
|
||||
DecryptKeys::None,
|
||||
8192,
|
||||
ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -175,10 +175,6 @@ fn run_to_fvi(image: Vec<u8>, title: DiscTitle, path: &std::path::Path) {
|
||||
DecryptKeys::None,
|
||||
3, // 3-sector (one AACS unit) batches → one source stamp per GOP region
|
||||
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,
|
||||
None,
|
||||
None,
|
||||
|
||||
Reference in New Issue
Block a user