From 100e3b8126d1e9406fae79a8c01cfea4c325c6c4 Mon Sep 17 00:00:00 2001 From: MattJackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:43:42 -0700 Subject: [PATCH] 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. --- src/udf.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/udf.rs b/src/udf.rs index f028e10..3c6595e 100644 --- a/src/udf.rs +++ b/src/udf.rs @@ -47,6 +47,12 @@ pub struct DirEntry { } 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"). /// Path matching is case-insensitive. pub fn find_dir(&self, path: &str) -> Option<&DirEntry> { @@ -102,6 +108,63 @@ impl UdfFs { 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> { + 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. fn meta_to_abs(&self, meta_lba: u32) -> u32 { 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. /// Uses standard READ(10) — no unlock required. fn read_sector(session: &mut DriveSession, lba: u32, buf: &mut [u8]) -> Result<()> {