From 84e0ba9fa84bf1d25b1c019864feadbe864af024 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 6 Aug 2026 07:37:59 -0700 Subject: [PATCH] Anchor the folder encryption probe on a unit boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit probe_folder_encryption sampled AACS units starting from the largest extent anywhere in the disc. AACS units are 3 sectors and a unit boundary is only guaranteed at the START of a clip, so that anchor is only correct when the largest extent happens to be a clip first extent. For any clip over ~2 GiB it is not. The planner caps an allocation descriptor at MAX_AD_BYTES = 524287 sectors, so every full piece of a split file ties on sector_count and max_by_key returns the LAST tie — an extent beginning (k-1)*524287 sectors into the file. 524287 % 3 == 1, so that start misses the unit boundary for two file sizes in three. The 6144-byte sample windows then begin mid-source-packet and the byte read as the CPI flag is content. Both verdicts fail in a costly direction: a decrypted folder is rejected as encrypted (DirImageEncrypted on something perfectly rippable — the exact case scan_dir was added to rescue), or real ciphertext reads as clear and the mux writes it out as video at exit 0. is_unit_aligned cannot catch it: it measures against the same wrong base. Now the largest TITLE first extent, which is unit-aligned by construction and is also the more meaningful sample — the main feature rather than whichever fragment happened to be biggest. The existing tests could not reach this: their fixture m2ts is 786,432 bytes, a single extent, which is always its own first. --- src/session.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/session.rs b/src/session.rs index 17bf58b..8131795 100644 --- a/src/session.rs +++ b/src/session.rs @@ -508,11 +508,33 @@ fn probe_folder_encryption(reader: &mut dyn SectorSource, disc: &Disc) -> Result use crate::consts::SECTOR_BYTES; const UNIT_SECTORS: u32 = 3; + // Anchor on the largest TITLE's FIRST extent, not on the largest extent + // anywhere. + // + // AACS units are 3 sectors, and a unit boundary is only guaranteed at the + // START of a clip. `max_by_key` over every extent picked a mid-file one for + // any clip big enough to be split: the planner caps an allocation + // descriptor at MAX_AD_BYTES = 524287 sectors, every full piece of a split + // file therefore ties on sector_count, and `max_by_key` returns the LAST + // tie — an extent starting (k-1)*524287 sectors in. 524287 % 3 == 1, so + // that start is off the unit boundary for two file sizes in three. + // + // The sampling then reads 6144-byte windows that begin mid-source-packet, + // so the CPI byte it thinks it is testing is content. Both verdicts are + // wrong in a costly direction: a decrypted folder gets rejected as + // encrypted (DirImageEncrypted on something perfectly rippable), or + // genuine ciphertext reads as clear and the mux writes it out as video at + // exit 0. `is_unit_aligned` cannot catch it, because it measures against + // this same wrong base. let Some(extent) = disc .titles .iter() - .flat_map(|t| t.extents.iter()) - .max_by_key(|e| e.sector_count) + .max_by_key(|t| { + t.extents + .iter() + .fold(0u64, |a, e| a.saturating_add(e.sector_count as u64)) + }) + .and_then(|t| t.extents.first()) else { // No content to judge. A folder with an AACS directory and no titles // has nothing to rip either way; leave the structural verdict alone.