Mux decrypt/verify redesign, HD DVD first-class, MVC 3D

decrypt:
- decrypt_sectors is now a pure decrypt (apply key, leave plaintext, report
  unverified bytes); TS-structure is a separate primitive (is_clean_ts/ps) used
  only for key selection and read-verify. The mux passes decrypted bytes through
  (the demuxer drops non-conforming packets), ending the NULL-TS conceal loop and
  the per-unit key-server refetch storm. Key-proof floor replaces the 75%
  supermajority.

recovery:
- Removed the post-read decrypt-verify gate (verify.rs) that mis-aligned the
  disc-absolute unit grid against clip-anchored AACS units and false-failed good
  clips (e.g. Dunkirk's orphan-CPS clip). Bad sectors are marked by physical read
  result; decryptability is proven at scan + mux time.

HD DVD (first-class AACS):
- Role-based candidate-list file sourcing so an HD DVD's /ANY!/ files
  (MKBROM.AACS, VTKF000.AACS, CONTENT_CERT.AACS) are found with no disc-type
  branch. parse_vtkf parses VTKF000.AACS into the same UnitKeyFile as a BD
  Unit_Key_RO.inf, so the shared VUK unwrap applies unchanged. set_unit_base
  clip-anchoring. Two decrypt-axis assumptions remain UNVERIFIED-HDDVD-DECRYPT
  (no encrypted disc to test).

mux:
- MVC (Blu-ray 3D) track signals unified into one MVCDecoderConfigurationRecord;
  release-safe track_vint (3-byte VINT) and pid_index (i32) guards.

hardening:
- Container-aware is_clean / encryption detection; bytes_bad_in_title fail-safe
  on a corrupt mapfile; CSS crack gated on DiscFormat::Dvd (HD DVD excluded);
  non-vacuous CSS tests; patch NOT_READY/HARDWARE/ILLEGAL_REQUEST/ABORTED
  sense-path tests.
This commit is contained in:
Matthew Jackson
2026-07-15 19:35:12 -07:00
parent 04728d7d94
commit 830d1e360c
32 changed files with 1589 additions and 3126 deletions
+11 -1
View File
@@ -135,6 +135,12 @@ fn aacs_fetch_step(
return prev_dropped;
}
let unit_len = crate::aacs::content::ALIGNED_UNIT_LEN;
// Container of this disc's content — travels with the keys; drives the
// encrypted-flag / structure check below (TS vs PS).
let format = match &*keys {
DecryptKeys::Aacs { format, .. } => *format,
_ => crate::disc::ContentFormat::BdTs,
};
// Gather up to MAX_FETCH_SAMPLES units the current pool did NOT open. Detect
// them on the post-decrypt TARGET (a failed unit stays TS-destroyed; an opened
// one is now clean TS and is skipped), but SAMPLE the matching on-disc
@@ -146,7 +152,7 @@ fn aacs_fetch_step(
.chunks_exact(unit_len)
.zip(ciphertext.chunks_exact(unit_len))
{
if crate::aacs::content::aacs_unit_needs_decrypt(t) {
if crate::aacs::content::aacs_unit_needs_decrypt(t, format) {
samples.push(c.to_vec());
if samples.len() >= MAX_FETCH_SAMPLES {
break;
@@ -256,6 +262,7 @@ mod tests {
let mut keys = DecryptKeys::Aacs {
unit_keys: vec![],
read_data_key: None,
format: crate::disc::ContentFormat::BdTs,
};
let cipher = buf.clone();
let out = r(&mut buf, &cipher, &mut keys, &ctx(0, 6144));
@@ -279,6 +286,7 @@ mod tests {
let mut keys = DecryptKeys::Aacs {
unit_keys: vec![],
read_data_key: None,
format: crate::disc::ContentFormat::BdTs,
};
let cipher = buf.clone();
r(&mut buf, &cipher, &mut keys, &ctx(0, ALIGNED_UNIT_LEN));
@@ -304,6 +312,7 @@ mod tests {
let mut keys = DecryptKeys::Aacs {
unit_keys: vec![],
read_data_key: None,
format: crate::disc::ContentFormat::BdTs,
};
let mut buf = scrambled_unit(0x44);
let cipher = buf.clone();
@@ -330,6 +339,7 @@ mod tests {
let mut keys = DecryptKeys::Aacs {
unit_keys: vec![],
read_data_key: None,
format: crate::disc::ContentFormat::BdTs,
};
// Distinct ciphertext each time so the dry-set never short-circuits; only
// the internal call budget should stop the fetch. The closure self-limits,