use crate::pvd::decode_ucs2be;
use crate::IsoError;
pub const FILE_FLAG_DIRECTORY: u8 = 0x02;
pub const FILE_FLAG_ASSOCIATED: u8 = 0x04;
pub const FILE_FLAG_MULTI_EXTENT: u8 = 0x80;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DirRecord {
pub lba: u32,
pub size: u32,
pub name_bytes: Vec<u8>,
pub flags: u8,
pub system_use: Vec<u8>,
pub extra_extents: Vec<(u32, u32)>,
}
impl DirRecord {
pub fn parse(data: &[u8], offset: usize) -> Result<Option<(Self, usize)>, IsoError> {
if offset >= data.len() {
return Ok(None);
}
let len = data[offset] as usize;
if len == 0 {
return Ok(None); }
if offset + len > data.len() || len < 33 {
return Err(IsoError::BadDirRecord(format!(
"record at offset {offset} claims length {len} but only {} bytes remain",
data.len() - offset
)));
}
let rec = &data[offset..offset + len];
let lba = u32::from_le_bytes(rec[2..6].try_into().unwrap());
let size = u32::from_le_bytes(rec[10..14].try_into().unwrap());
let flags = rec[25];
let name_len = rec[32] as usize;
if 33 + name_len > len {
return Err(IsoError::BadDirRecord("name extends past record".into()));
}
let name_bytes = rec[33..33 + name_len].to_vec();
let su_start = 33 + name_len + (if name_len % 2 == 0 { 1 } else { 0 });
let system_use = if su_start < len {
rec[su_start..len].to_vec()
} else {
Vec::new()
};
Ok(Some((
DirRecord {
lba,
size,
name_bytes,
flags,
system_use,
extra_extents: Vec::new(),
},
len,
)))
}
pub fn is_dir(&self) -> bool {
self.flags & FILE_FLAG_DIRECTORY != 0
}
pub fn is_multi_extent(&self) -> bool {
self.flags & FILE_FLAG_MULTI_EXTENT != 0
}
pub fn is_dot(&self) -> bool {
self.name_bytes == [0x00] || self.name_bytes == [0x01]
}
pub fn iso_name(&self) -> String {
let raw = std::str::from_utf8(&self.name_bytes)
.unwrap_or("")
.trim_end_matches('\0');
if let Some(pos) = raw.rfind(';') {
raw[..pos].to_string()
} else {
raw.to_string()
}
}
pub fn joliet_name(&self) -> String {
decode_ucs2be(&self.name_bytes)
}
}
pub fn parse_dir_records(data: &[u8]) -> Result<Vec<DirRecord>, IsoError> {
let mut records = Vec::new();
let mut offset = 0;
while offset < data.len() {
if data[offset] == 0 {
offset = (offset + 2047) & !2047;
continue;
}
match DirRecord::parse(data, offset)? {
Some((rec, advance)) => {
if !rec.is_dot() {
records.push(rec);
}
offset += advance;
if advance == 0 {
break;
}
}
None => break,
}
}
Ok(records)
}