use crate::error::{Error, Result};
use crate::drive::DriveSession;
#[derive(Debug)]
pub struct UdfFs {
pub root: DirEntry,
partition_start: u32,
metadata_start: u32,
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub is_dir: bool,
pub meta_lba: u32,
pub size: u64,
pub entries: Vec<DirEntry>,
}
impl UdfFs {
pub fn find_dir(&self, path: &str) -> Option<&DirEntry> {
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
let mut current = &self.root;
for part in &parts {
current = current.entries.iter().find(|e| {
e.is_dir && e.name.eq_ignore_ascii_case(part)
})?;
}
Some(current)
}
pub fn read_file(&self, session: &mut DriveSession, path: &str) -> Result<Vec<u8>> {
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
let mut current = &self.root;
for part in &parts[..parts.len() - 1] {
current = current.entries.iter().find(|e| {
e.is_dir && e.name.eq_ignore_ascii_case(part)
}).ok_or_else(|| Error::DiscError {
detail: format!("directory not found: {}", part),
})?;
}
let filename = parts.last().unwrap();
let entry = current.entries.iter().find(|e| {
!e.is_dir && e.name.eq_ignore_ascii_case(filename)
}).ok_or_else(|| Error::DiscError {
detail: format!("file not found: {}", path),
})?;
let (data_lba, data_len) = self.read_icb_extent(session, entry.meta_lba)?;
let sector_count = ((data_len as u64 + 2047) / 2048) as u32;
let mut data = vec![0u8; (sector_count as usize) * 2048];
let abs_start = self.partition_start + data_lba;
for i in 0..sector_count {
let offset = (i as usize) * 2048;
read_sector(session, abs_start + i, &mut data[offset..offset + 2048])?;
}
data.truncate(entry.size as usize);
Ok(data)
}
fn meta_to_abs(&self, meta_lba: u32) -> u32 {
self.metadata_start + meta_lba
}
fn read_icb_extent(&self, session: &mut DriveSession, meta_lba: u32) -> Result<(u32, u32)> {
let mut icb = [0u8; 2048];
read_sector(session, self.meta_to_abs(meta_lba), &mut icb)?;
let tag = u16::from_le_bytes([icb[0], icb[1]]);
let ad_offset = match tag {
266 => {
let l_ea = u32::from_le_bytes([icb[208], icb[209], icb[210], icb[211]]) as usize;
216 + l_ea
}
261 => {
let l_ea = u32::from_le_bytes([icb[168], icb[169], icb[170], icb[171]]) as usize;
176 + l_ea
}
_ => return Err(Error::DiscError {
detail: format!("unexpected ICB tag {} at meta_lba {}", tag, meta_lba),
}),
};
if ad_offset + 8 > 2048 {
return Err(Error::DiscError { detail: "ICB alloc desc out of range".into() });
}
let raw_len = u32::from_le_bytes([icb[ad_offset], icb[ad_offset + 1],
icb[ad_offset + 2], icb[ad_offset + 3]]);
let data_len = raw_len & 0x3FFFFFFF;
let data_lba = u32::from_le_bytes([icb[ad_offset + 4], icb[ad_offset + 5],
icb[ad_offset + 6], icb[ad_offset + 7]]);
Ok((data_lba, data_len))
}
}
pub fn read_filesystem(session: &mut DriveSession) -> Result<UdfFs> {
let mut avdp = [0u8; 2048];
read_sector(session, 256, &mut avdp)?;
let tag_id = u16::from_le_bytes([avdp[0], avdp[1]]);
if tag_id != 2 {
return Err(Error::DiscError {
detail: format!("AVDP: expected tag 2, got {} at sector 256", tag_id),
});
}
let mut partition_start: u32 = 0;
let mut num_partition_maps: u32 = 0;
let mut lvd_sector: Option<u32> = None;
for i in 32..64 {
let mut desc = [0u8; 2048];
read_sector(session, i, &mut desc)?;
let desc_tag = u16::from_le_bytes([desc[0], desc[1]]);
match desc_tag {
5 => {
partition_start = u32::from_le_bytes([desc[188], desc[189], desc[190], desc[191]]);
}
6 => {
num_partition_maps = u32::from_le_bytes([desc[268], desc[269], desc[270], desc[271]]);
lvd_sector = Some(i);
}
8 => break,
_ => continue,
}
}
if partition_start == 0 {
return Err(Error::DiscError { detail: "UDF: no Partition Descriptor found".into() });
}
let metadata_start = if num_partition_maps >= 2 {
let lvd_sec = lvd_sector.ok_or_else(|| Error::DiscError {
detail: "UDF: no LVD found".into(),
})?;
let mut lvd = [0u8; 2048];
read_sector(session, lvd_sec, &mut lvd)?;
let _pm1_type = lvd[440]; let pm1_len = lvd[441] as usize;
if pm1_len > 0 && 440 + pm1_len < 2048 {
let pm2_type = lvd[440 + pm1_len];
if pm2_type == 2 {
let meta_file_lba = partition_start; let mut meta_icb = [0u8; 2048];
read_sector(session, meta_file_lba, &mut meta_icb)?;
let meta_tag = u16::from_le_bytes([meta_icb[0], meta_icb[1]]);
if meta_tag == 266 {
let l_ea = u32::from_le_bytes([meta_icb[208], meta_icb[209],
meta_icb[210], meta_icb[211]]) as usize;
let ad_off = 216 + l_ea;
let _ad_len = u32::from_le_bytes([meta_icb[ad_off], meta_icb[ad_off + 1],
meta_icb[ad_off + 2], meta_icb[ad_off + 3]]) & 0x3FFFFFFF;
let ad_pos = u32::from_le_bytes([meta_icb[ad_off + 4], meta_icb[ad_off + 5],
meta_icb[ad_off + 6], meta_icb[ad_off + 7]]);
partition_start + ad_pos
} else {
partition_start
}
} else {
partition_start
}
} else {
partition_start
}
} else {
partition_start
};
let mut fsd = [0u8; 2048];
read_sector(session, metadata_start, &mut fsd)?;
let fsd_tag = u16::from_le_bytes([fsd[0], fsd[1]]);
if fsd_tag != 256 {
return Err(Error::DiscError {
detail: format!("FSD: expected tag 256, got {} at sector {}", fsd_tag, metadata_start),
});
}
let root_lba = u32::from_le_bytes([fsd[404], fsd[405], fsd[406], fsd[407]]);
let root = read_directory(session, partition_start, metadata_start, root_lba, "", 0)?;
Ok(UdfFs {
root,
partition_start,
metadata_start,
})
}
fn read_directory(
session: &mut DriveSession,
part_start: u32,
meta_start: u32,
meta_lba: u32,
name: &str,
depth: u32,
) -> Result<DirEntry> {
let mut icb = [0u8; 2048];
read_sector(session, meta_start + meta_lba, &mut icb)?;
let tag = u16::from_le_bytes([icb[0], icb[1]]);
let (ad_len, ad_pos) = match tag {
266 => {
let l_ea = u32::from_le_bytes([icb[208], icb[209], icb[210], icb[211]]) as usize;
let ad_off = 216 + l_ea;
let len = u32::from_le_bytes([icb[ad_off], icb[ad_off + 1],
icb[ad_off + 2], icb[ad_off + 3]]) & 0x3FFFFFFF;
let pos = u32::from_le_bytes([icb[ad_off + 4], icb[ad_off + 5],
icb[ad_off + 6], icb[ad_off + 7]]);
(len, pos)
}
261 => {
let l_ea = u32::from_le_bytes([icb[168], icb[169], icb[170], icb[171]]) as usize;
let ad_off = 176 + l_ea;
let len = u32::from_le_bytes([icb[ad_off], icb[ad_off + 1],
icb[ad_off + 2], icb[ad_off + 3]]) & 0x3FFFFFFF;
let pos = u32::from_le_bytes([icb[ad_off + 4], icb[ad_off + 5],
icb[ad_off + 6], icb[ad_off + 7]]);
(len, pos)
}
_ => {
return Ok(DirEntry {
name: name.to_string(), is_dir: true, meta_lba, size: 0, entries: Vec::new(),
});
}
};
let dir_abs = meta_start + ad_pos;
let sector_count = ((ad_len + 2047) / 2048).min(64);
let mut dir_data = vec![0u8; sector_count as usize * 2048];
for i in 0..sector_count {
read_sector(session, dir_abs + i,
&mut dir_data[(i as usize) * 2048..(i as usize + 1) * 2048])?;
}
let mut entries = Vec::new();
let mut pos = 0;
while pos + 38 < dir_data.len().min(ad_len as usize) {
let fid_tag = u16::from_le_bytes([dir_data[pos], dir_data[pos + 1]]);
if fid_tag != 257 {
break;
}
let file_chars = dir_data[pos + 18];
let l_fi = dir_data[pos + 19] as usize;
let icb_lba = u32::from_le_bytes([dir_data[pos + 24], dir_data[pos + 25],
dir_data[pos + 26], dir_data[pos + 27]]);
let l_iu = u16::from_le_bytes([dir_data[pos + 36], dir_data[pos + 37]]) as usize;
let is_dir = (file_chars & 0x02) != 0;
let is_parent = (file_chars & 0x08) != 0;
if !is_parent && l_fi > 0 {
let name_start = pos + 38 + l_iu;
let entry_name = parse_udf_name(&dir_data[name_start..name_start + l_fi]);
if !entry_name.is_empty() {
let file_size = read_file_size(session, meta_start, icb_lba).unwrap_or(0);
if is_dir && depth < 3 {
let subdir = read_directory(session, part_start, meta_start, icb_lba, &entry_name, depth + 1)?;
entries.push(subdir);
} else {
entries.push(DirEntry {
name: entry_name,
is_dir,
meta_lba: icb_lba,
size: file_size,
entries: Vec::new(),
});
}
}
}
let fid_len = ((38 + l_iu + l_fi + 3) & !3) as usize;
pos += fid_len;
}
Ok(DirEntry {
name: name.to_string(),
is_dir: true,
meta_lba,
size: ad_len as u64,
entries,
})
}
fn read_file_size(session: &mut DriveSession, meta_start: u32, meta_lba: u32) -> Result<u64> {
let mut icb = [0u8; 2048];
read_sector(session, meta_start + meta_lba, &mut icb)?;
let tag = u16::from_le_bytes([icb[0], icb[1]]);
match tag {
261 | 266 => {
Ok(u64::from_le_bytes([icb[56], icb[57], icb[58], icb[59],
icb[60], icb[61], icb[62], icb[63]]))
}
_ => Ok(0),
}
}
fn parse_udf_name(data: &[u8]) -> String {
if data.is_empty() {
return String::new();
}
match data[0] {
8 => {
String::from_utf8_lossy(&data[1..]).trim().to_string()
}
16 => {
let mut s = String::new();
let chars = &data[1..];
for i in (0..chars.len()).step_by(2) {
if i + 1 < chars.len() {
let c = ((chars[i] as u16) << 8) | chars[i + 1] as u16;
if let Some(ch) = char::from_u32(c as u32) {
s.push(ch);
}
}
}
s.trim().to_string()
}
_ => String::from_utf8_lossy(&data[1..]).trim().to_string(),
}
}
fn read_sector(session: &mut DriveSession, lba: u32, buf: &mut [u8]) -> Result<()> {
session.read_disc(lba, 1, buf)?;
Ok(())
}