Files
libfreemkv/examples/probe3d_deint.rs
T
Matthew Jackson d9db268b06 wip(3d): prove SSIF de-interleave = ssif−base, dependent decrypts on same UK
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.
2026-07-12 20:40:58 -07:00

101 lines
3.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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}");
}