Add metadata_sector_ranges() and max_metadata_sector() to UdfFs

Walk UDF tree to discover sector ranges for all metadata files
(MPLS, CLPI, JAR, AACS certs, etc). Skips STREAM/, BACKUP/,
DUPLICATE/, MKB_RO.inf, ContentHash. Exposes partition_start()
and metadata_start() getters. Used by bdemu smart capture.
This commit is contained in:
MattJackson
2026-04-07 14:43:42 -07:00
parent 49c260fc14
commit 100e3b8126
+81
View File
@@ -47,6 +47,12 @@ pub struct DirEntry {
} }
impl UdfFs { impl UdfFs {
/// Physical partition start sector.
pub fn partition_start(&self) -> u32 { self.partition_start }
/// Metadata partition start sector.
pub fn metadata_start(&self) -> u32 { self.metadata_start }
/// Find a directory by path (e.g. "/BDMV/PLAYLIST"). /// Find a directory by path (e.g. "/BDMV/PLAYLIST").
/// Path matching is case-insensitive. /// Path matching is case-insensitive.
pub fn find_dir(&self, path: &str) -> Option<&DirEntry> { pub fn find_dir(&self, path: &str) -> Option<&DirEntry> {
@@ -102,6 +108,63 @@ impl UdfFs {
Ok(data) Ok(data)
} }
/// Collect all sector ranges needed for disc-info and AACS.
///
/// Returns a list of (start_lba, sector_count) ranges covering:
/// - UDF structure (AVDP, VDS, metadata partition, directories)
/// - BDMV/PLAYLIST/*.mpls, CLIPINF/*.clpi, JAR/*, META/*, *.bdmv
/// - AACS/* (Content*.cer, Unit_Key_RO.inf, CPSUnit*.cci)
///
/// Skips: STREAM/ (video), BACKUP/, DUPLICATE/,
/// MKB_RO.inf, ContentHash*, ContentRevocation*
pub fn metadata_sector_ranges(&self, session: &mut DriveSession) -> Result<Vec<(u32, u32)>> {
let mut ranges = Vec::new();
// UDF structure: AVDP + VDS + metadata partition + FSD + some margin
ranges.push((0, self.metadata_start + 256));
// Walk tree, collect ranges for each metadata file
self.collect_file_ranges(session, &self.root, &mut ranges)?;
// Merge overlapping/adjacent ranges and sort
ranges.sort_by_key(|r| r.0);
let merged = merge_ranges(&ranges);
Ok(merged)
}
fn collect_file_ranges(&self, session: &mut DriveSession, entry: &DirEntry, ranges: &mut Vec<(u32, u32)>) -> Result<()> {
for child in &entry.entries {
if child.is_dir {
let name_upper = child.name.to_uppercase();
if matches!(name_upper.as_str(), "STREAM" | "BACKUP" | "DUPLICATE") {
continue;
}
self.collect_file_ranges(session, child, ranges)?;
} else {
let name_upper = child.name.to_uppercase();
// Skip large AACS files we don't need for disc-info
if name_upper.starts_with("MKB_")
|| name_upper.starts_with("CONTENTHASH")
|| name_upper.starts_with("CONTENTREVOCATION")
{
continue;
}
// Include everything else (mpls, clpi, jar, bdmv, cer, cci, xml, png, inf)
if let Ok((data_lba, data_len)) = self.read_icb_extent(session, child.meta_lba) {
let abs_start = self.partition_start + data_lba;
let sector_count = (data_len + 2047) / 2048;
ranges.push((abs_start, sector_count));
}
// Also include the ICB sector itself (in metadata partition)
ranges.push((self.meta_to_abs(child.meta_lba), 1));
}
}
Ok(())
}
/// Convert a metadata-partition-relative LBA to an absolute sector number. /// Convert a metadata-partition-relative LBA to an absolute sector number.
fn meta_to_abs(&self, meta_lba: u32) -> u32 { fn meta_to_abs(&self, meta_lba: u32) -> u32 {
self.metadata_start + meta_lba self.metadata_start + meta_lba
@@ -456,6 +519,24 @@ fn parse_udf_name(data: &[u8]) -> String {
} }
} }
/// Merge overlapping or adjacent (start, count) ranges.
fn merge_ranges(ranges: &[(u32, u32)]) -> Vec<(u32, u32)> {
if ranges.is_empty() { return Vec::new(); }
let mut result = vec![ranges[0]];
for &(start, count) in &ranges[1..] {
let last = result.last_mut().unwrap();
let last_end = last.0 + last.1;
if start <= last_end + 1 {
// Overlapping or adjacent — extend
let new_end = (start + count).max(last_end);
last.1 = new_end - last.0;
} else {
result.push((start, count));
}
}
result
}
/// Read a single 2048-byte sector from the drive. /// Read a single 2048-byte sector from the drive.
/// Uses standard READ(10) — no unlock required. /// Uses standard READ(10) — no unlock required.
fn read_sector(session: &mut DriveSession, lba: u32, buf: &mut [u8]) -> Result<()> { fn read_sector(session: &mut DriveSession, lba: u32, buf: &mut [u8]) -> Result<()> {