Empirical (300, clip 00042): base .m2ts (1567 extents, 25.85 GB) is entirely inside the SSIF LBA span; dependent = SSIF − base = 1567 ranges, 9.59 GB (exact vs 35.43−25.85). 200/200 sampled dependent-view units decrypt under the base UK. => de-interleave needs no SSIF parsing (complement of the base extents we already resolve), and the dependent eye needs no extra key.
101 lines
3.6 KiB
Rust
101 lines
3.6 KiB
Rust
// Prove the SSIF de-interleave: the base-view .m2ts extents are the base-view
|
||
// interleaved units INSIDE the SSIF region, so the dependent (right-eye MVC)
|
||
// view = SSIF sectors MINUS base sectors. Then confirm those dependent units
|
||
// decrypt under the same Unit Key.
|
||
// probe3d_deint <iso> <uk-hex-32> [clip=00042]
|
||
|
||
use libfreemkv::aacs::content::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, aacs_unit_encrypted, decrypt_unit};
|
||
use libfreemkv::sector::SectorSource;
|
||
use libfreemkv::{FileSectorSource, read_filesystem};
|
||
use std::path::Path;
|
||
|
||
fn hex16(s: &str) -> [u8; 16] {
|
||
let s = s.trim().trim_start_matches("0x");
|
||
let mut o = [0u8; 16];
|
||
for i in 0..16 {
|
||
o[i] = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).expect("hex");
|
||
}
|
||
o
|
||
}
|
||
|
||
fn main() {
|
||
let a: Vec<String> = std::env::args().collect();
|
||
let iso = &a[1];
|
||
let uk = hex16(&a[2]);
|
||
let clip = a.get(3).map(|s| s.as_str()).unwrap_or("00042");
|
||
let mut r = FileSectorSource::open(Path::new(iso)).expect("open");
|
||
let fs = read_filesystem(&mut r).expect("udf");
|
||
|
||
let base = fs
|
||
.file_extents(&mut r, &format!("/BDMV/STREAM/{clip}.m2ts"))
|
||
.expect("base extents");
|
||
let ssif = fs
|
||
.file_extents(&mut r, &format!("/BDMV/STREAM/SSIF/{clip}.ssif"))
|
||
.expect("ssif extents");
|
||
let gb = |sec: u64| sec as f64 * 2048.0 / 1e9;
|
||
let bsum: u64 = base.iter().map(|(_, c)| *c as u64).sum();
|
||
let ssum: u64 = ssif.iter().map(|(_, c)| *c as u64).sum();
|
||
println!("base: {} extents {:.2} GB", base.len(), gb(bsum));
|
||
println!("ssif: {} extents {:.2} GB", ssif.len(), gb(ssum));
|
||
|
||
// Is the base range a subset of the SSIF range? (LBA span check)
|
||
let brange = (
|
||
base.iter().map(|(l, _)| *l).min().unwrap_or(0),
|
||
base.iter().map(|(l, c)| l + c).max().unwrap_or(0),
|
||
);
|
||
let srange = (
|
||
ssif.iter().map(|(l, _)| *l).min().unwrap_or(0),
|
||
ssif.iter().map(|(l, c)| l + c).max().unwrap_or(0),
|
||
);
|
||
println!(
|
||
"base LBA span [{},{}) ssif LBA span [{},{}) base⊆ssif={}",
|
||
brange.0,
|
||
brange.1,
|
||
srange.0,
|
||
srange.1,
|
||
brange.0 >= srange.0 && brange.1 <= srange.1
|
||
);
|
||
|
||
// dependent = ssif − base (per SSIF extent, subtract overlapping base ranges).
|
||
let mut b: Vec<(u32, u32)> = base.iter().map(|&(l, c)| (l, l + c)).collect();
|
||
b.sort();
|
||
let mut dep: Vec<(u32, u32)> = Vec::new();
|
||
for &(sl, sc) in &ssif {
|
||
let (s, e) = (sl, sl + sc);
|
||
let mut cur = s;
|
||
for &(bs, be) in b.iter().filter(|&&(bs, be)| be > s && bs < e) {
|
||
if bs > cur {
|
||
dep.push((cur, bs));
|
||
}
|
||
cur = cur.max(be);
|
||
}
|
||
if cur < e {
|
||
dep.push((cur, e));
|
||
}
|
||
}
|
||
let dsum: u64 = dep.iter().map(|(s, e)| (*e - *s) as u64).sum();
|
||
println!(
|
||
"\ndependent (ssif − base): {} ranges {:.2} GB (expected ≈ {:.2} GB)",
|
||
dep.len(),
|
||
gb(dsum),
|
||
gb(ssum - bsum)
|
||
);
|
||
|
||
// Decrypt-test dependent-view units at several points across the ranges.
|
||
let (mut tested, mut enc, mut dec) = (0u32, 0u32, 0u32);
|
||
for &(s, e) in dep.iter().filter(|(s, e)| e - s >= ALIGNED_UNIT_SECTORS).take(200) {
|
||
let lba = s;
|
||
let mut buf = vec![0u8; ALIGNED_UNIT_LEN];
|
||
if r.read_sectors(lba, ALIGNED_UNIT_SECTORS as u16, &mut buf, false).is_ok() {
|
||
tested += 1;
|
||
if aacs_unit_encrypted(&buf) {
|
||
enc += 1;
|
||
}
|
||
if decrypt_unit(&mut buf, &uk) {
|
||
dec += 1;
|
||
}
|
||
}
|
||
}
|
||
println!("dependent-view unit decrypt: tested={tested} cpi-encrypted={enc} decrypted={dec}");
|
||
}
|