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:
+81
@@ -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<()> {
|
||||||
|
|||||||
Reference in New Issue
Block a user