Require a pack start code before descrambling a sector

css::is_scrambled reads bits 4-5 of byte 0x14 and nothing else. That is a
sound test once a caller has committed to a title's VOB data, where every
sector is an MPEG-2 PS pack and byte 0x14 always means what it says.
descramble_region is not such a caller: it is handed arbitrary regions of a
disc, so it also sees IFO, UDF and ISO 9660 sectors — raw structures where
byte 0x14 is whatever that format happens to store there.

Measured on a real disc: the second sector of VIDEO_TS.IFO holds 0x15 at
offset 0x14 while starting 00 26 00 00, which is not a pack. The flag test
read it as scrambled, descrambled it, and destroyed 1912 of its 2048 bytes.
That sector carries TT_SRPT, so the title table went with it — the disc
enumerated 38 titles and an image decrypted from it enumerated 10, silently,
at exit 0.

is_scrambled_pack already existed with the right predicate. Use it here. It
costs nothing: a genuinely scrambled VOB sector always carries the pack start
code, and no IFO sector does.

Verified end to end — the decrypted image's `info` output is now identical to
the source disc's, 38 titles both, differing only in the CSS: Encrypted line.

The fixtures moved with it. Four of them built a sector by setting byte 0x14
alone, which no real scrambled sector looks like; they now build packs.
This commit is contained in:
Matthew Jackson
2026-08-05 20:59:14 -07:00
parent 1d35dcf7c6
commit a018e1adc4
5 changed files with 106 additions and 1 deletions
+6
View File
@@ -1288,6 +1288,9 @@ mod tests {
// mirroring css::mod tests' `crackable_sector`.
let seed = [0x11u8, 0x22, 0x33, 0x44, 0x55];
let mut plain = vec![0u8; 2048];
// Pack start code: a real scrambled sector is an MPEG-2 PS pack, and
// the descrambler requires it before trusting byte 0x14.
plain[0x00..0x04].copy_from_slice(&[0x00, 0x00, 0x01, 0xBA]);
plain[0x14] = 0x10; // scramble flag
let pat: Vec<u8> = (0..8)
.map(|k| (0xA0u8.wrapping_add(k as u8)) ^ 0x5A)
@@ -1759,6 +1762,9 @@ mod tests {
// VTS groups regardless of which extents were gathered, masking
// this exact regression.
plain[0x00..0x04].copy_from_slice(&crate::css::PACK_START);
// Pack start code: a real scrambled sector is an MPEG-2 PS pack, and
// the descrambler requires it before trusting byte 0x14.
plain[0x00..0x04].copy_from_slice(&[0x00, 0x00, 0x01, 0xBA]);
plain[0x14] = 0x10; // scramble flag
let pat: Vec<u8> = (0..8)
.map(|k| (0xA0u8.wrapping_add(k as u8) ^ marker) ^ 0x5A)