use super::*;
#[cfg(all(feature = "derive", feature = "io-std", feature = "text_all"))]
use simple_endian::{Endianize, read_specific};
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub(super) struct BootJumpOem {
pub(super) jump: [u8; 3],
pub(super) oem: [u8; 8],
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub(super) struct FsTypeAscii8 {
pub(super) bytes: [u8; 8],
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub(super) struct ShortName83 {
pub(super) name: [u8; 8],
pub(super) ext: [u8; 3],
}
fn ascii_trim_right(bytes: &[u8]) -> String {
let end = bytes
.iter()
.rposition(|&b| b != b' ' && b != 0)
.map(|i| i + 1)
.unwrap_or(0);
String::from_utf8_lossy(&bytes[..end]).to_string()
}
#[cfg(all(feature = "derive", feature = "io-std", feature = "text_all"))]
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Endianize)]
#[endian(le)]
#[repr(C)]
pub(super) struct BiosParameterBlock {
bytes_per_sector: u16,
sectors_per_cluster: u8,
reserved_sectors: u16,
fats: u8,
root_entries: u16,
total_sectors_16: u16,
media: u8,
sectors_per_fat: u16,
sectors_per_track: u16,
heads: u16,
hidden_sectors: u32,
total_sectors_32: u32,
}
#[cfg(all(feature = "derive", feature = "io-std", feature = "text_all"))]
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Endianize)]
#[endian(le)]
#[repr(C)]
pub(super) struct Fat16Ebr {
drive_number: u8,
_reserved: u8,
boot_sig: u8,
volume_id: u32,
}
#[cfg(all(feature = "derive", feature = "io-std", feature = "text_all"))]
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Endianize)]
#[endian(le)]
#[repr(C)]
pub(super) struct DirEntryRest {
attr: u8,
nt_reserved: u8,
ctime_tenths: u8,
ctime: u16,
cdate: u16,
adate: u16,
first_cluster_hi: u16,
mtime: u16,
mdate: u16,
first_cluster_lo: u16,
file_size: u32,
}
#[cfg(all(feature = "derive", feature = "io-std", feature = "text_all"))]
pub fn run() {
let img = build_toy_fat16_image();
let mut cur = Cursor::new(img);
let mut jump = [0u8; 3];
let mut oem = [0u8; 8];
cur.read_exact(&mut jump).unwrap();
cur.read_exact(&mut oem).unwrap();
let bpb: BiosParameterBlockWire = read_specific(&mut cur).expect("read BPB");
cur.seek(SeekFrom::Start(0x24)).unwrap();
let ebr: Fat16EbrWire = read_specific(&mut cur).expect("read FAT16 EBR");
let bytes_per_sector = bpb.bytes_per_sector.to_native() as u64;
let reserved = bpb.reserved_sectors.to_native() as u64;
let fats = bpb.fats.to_native() as u64;
let root_entries = bpb.root_entries.to_native() as usize;
let sectors_per_fat = bpb.sectors_per_fat.to_native() as u64;
cur.seek(SeekFrom::Start(0x1FE)).unwrap();
let sig: u16le = read_specific(&mut cur).expect("read signature");
let mut vol_label_raw = [0u8; 11];
{
let pos = cur.position();
cur.seek(SeekFrom::Start(0x2B)).unwrap();
cur.read_exact(&mut vol_label_raw).unwrap();
cur.seek(SeekFrom::Start(pos)).unwrap();
}
let vol_label_decoded = ascii_trim_right(&vol_label_raw);
println!("Jump: {:02X?}", jump);
println!("OEM: {}", String::from_utf8_lossy(&oem));
println!("FAT16 boot: bytes/sector={bytes_per_sector}, root_entries={root_entries}");
let mut fs_type = [0u8; 8];
{
let pos = cur.position();
cur.seek(SeekFrom::Start(0x36)).unwrap();
cur.read_exact(&mut fs_type).unwrap();
cur.seek(SeekFrom::Start(pos)).unwrap();
}
println!("FS type: {}", String::from_utf8_lossy(&fs_type));
println!("Volume ID: {:#X}", ebr.volume_id.to_native());
println!("Volume label: {vol_label_decoded}");
println!("Signature: {:#06X}", sig.to_native());
let root_dir_sectors = ((root_entries * 32) as u64 + bytes_per_sector - 1) / bytes_per_sector;
let root_dir_lba = reserved + fats * sectors_per_fat;
cur.seek(SeekFrom::Start(root_dir_lba * bytes_per_sector))
.unwrap();
let max_entries_by_sectors = (root_dir_sectors * bytes_per_sector) / (DIR_ENTRY_SIZE as u64);
let max_entries = core::cmp::min(root_entries as u64, max_entries_by_sectors) as usize;
for i in 0..max_entries {
let mut entry_buf = [0u8; DIR_ENTRY_SIZE];
if let Err(e) = cur.read_exact(&mut entry_buf) {
if e.kind() == std::io::ErrorKind::UnexpectedEof {
break;
}
panic!("read dir entry: {e}");
}
if entry_buf[0] == 0x00 {
break; }
if entry_buf[0] == 0xE5 {
continue; }
let name = <[u8; 8]>::try_from(&entry_buf[0..8]).unwrap();
let ext = <[u8; 3]>::try_from(&entry_buf[8..11]).unwrap();
let mut rest_cur = Cursor::new(&entry_buf[11..]);
let rest: DirEntryRestWire = match read_specific(&mut rest_cur) {
Ok(v) => v,
Err(_) => continue,
};
let attr = rest.attr.to_native();
if attr == 0x0F {
continue;
}
let name = dir_entry_filename(&name, &ext);
let clus = rest.first_cluster_lo.to_native();
let size = rest.file_size.to_native();
println!("[{i:02}] {name:12} size={size} start_cluster={clus}");
}
let total: u32le = if bpb.total_sectors_16.to_native() != 0 {
(bpb.total_sectors_16.to_native() as u32).into()
} else {
bpb.total_sectors_32
};
println!("Total sectors (interpreted): {}", total.to_native());
println!("(root_dir_sectors computed: {root_dir_sectors})");
}