use std::fmt::Display; use crate::FatType; use crate::utils::{load_u16_le, load_u32_le}; #[derive(Debug)] pub enum ExtBpb { ExtBpb16(ExtBpb16), ExtBpb32(ExtBpb32), } impl Display for ExtBpb { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ExtBpb::ExtBpb16(ext_bpb16) => write!(f, "{}", ext_bpb16), ExtBpb::ExtBpb32(ext_bpb32) => write!(f, "{}", ext_bpb32), } } } #[derive(Debug)] pub struct Bpb { fat_type: FatType, jmp_boot: [u8; 3], oem_name: [u8; 8], bytes_per_sector: u16, sectors_per_cluster: u8, reserved_sector_count: u16, num_fats: u8, root_entry_count: u16, total_sectors_16: u16, media: u8, fat_size_16: u16, sectors_per_track: u16, num_heads: u16, hidden_sectors: u32, total_sectors_32: u32, ext_bpb: ExtBpb, } impl Display for Bpb { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "Bpb {{")?; match self.fat_type { FatType::Fat12 => writeln!(f, " FAT12")?, FatType::Fat16 => writeln!(f, " FAT16")?, FatType::Fat32 => writeln!(f, " FAT32")?, } writeln!(f, "")?; writeln!( f, " jmp_boot: [{:#X}, {:#X}, {:#X}]", self.jmp_boot[0], self.jmp_boot[1], self.jmp_boot[2] )?; writeln!(f, " oem name: \"{}\"", self.oem_name_str().unwrap_or(""))?; writeln!(f, " bytes per sector: {}", self.bytes_per_sector())?; writeln!(f, " sectors per cluster: {}", self.sectors_per_cluster())?; writeln!(f, " reserved sector count: {}", self.reserved_sector_count())?; writeln!(f, " num_fats: {}", self.num_fats())?; writeln!(f, " root entry count: {}", self.root_entry_count())?; writeln!(f, " total sectors: {}", self.total_sectors_16())?; writeln!(f, " media: {:#X}", self.media())?; writeln!(f, " fat_size_16: {}", self.fat_size_16())?; writeln!(f, " sectors per track: {}", self.sectors_per_track())?; writeln!(f, " num_heads: {}", self.num_heads())?; writeln!(f, " hidden_sectors: {}", self.hidden_sectors())?; writeln!(f, " total sectors 32: {}", self.total_sectors_32())?; writeln!(f, "")?; let ext_bpb_str = format!("{}", self.ext_bpb); for line in ext_bpb_str.lines() { writeln!(f, " {}", line)?; } writeln!(f, "}}")?; Ok(()) } } impl Bpb { pub fn load(bytes: &[u8]) -> anyhow::Result { anyhow::ensure!(bytes.len() >= 512, "invalid BPB of len {}", bytes.len()); let jmp_boot = bytes[..3].try_into().unwrap(); let oem_name = bytes[3..][..8].try_into().unwrap(); let bytes_per_sector = load_u16_le(&bytes[11..][..2]); if !&[512, 1024, 2048, 4096].contains(&bytes_per_sector) { anyhow::bail!("invalid bytes per sector: {}", bytes_per_sector); } let sectors_per_cluster = bytes[13]; if !&[1, 2, 4, 8, 16, 32, 64, 128].contains(§ors_per_cluster) { anyhow::bail!("invalid sectors per cluster: {}", sectors_per_cluster); } let reserved_sector_count = load_u16_le(&bytes[14..][..2]); anyhow::ensure!(reserved_sector_count != 0, "reserved sector count can't be zero"); let num_fats = bytes[16]; let root_entry_count = load_u16_le(&bytes[17..][..2]); let total_sectors_16 = load_u16_le(&bytes[19..][..2]); let media = bytes[21]; if !&[0xF0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF].contains(&media) { anyhow::bail!("invalid media: {}", media); } let fat_size_16 = load_u16_le(&bytes[22..][..2]); let sectors_per_track = load_u16_le(&bytes[24..][..2]); let num_heads = load_u16_le(&bytes[26..][..2]); let hidden_sectors = load_u32_le(&bytes[28..][..4]); let total_sectors_32 = load_u32_le(&bytes[32..][..4]); let (fat_type, ext_bpb) = if fat_size_16 == 0 { // FAT32? anyhow::ensure!( total_sectors_16 == 0, "fat_size_16 is 0, but total sectors is {} instead of 0", total_sectors_16 ); let ext_bpb = ExtBpb32::load(bytes)?; (FatType::Fat32, ExtBpb::ExtBpb32(ext_bpb)) } else { // FAT16? let ext_bpb = ExtBpb16::load(bytes)?; (FatType::Fat16, ExtBpb::ExtBpb16(ext_bpb)) }; let mut bpb = Bpb { fat_type, jmp_boot, oem_name, bytes_per_sector, sectors_per_cluster, reserved_sector_count, num_fats, root_entry_count, total_sectors_16, media, fat_size_16, sectors_per_track, num_heads, hidden_sectors, total_sectors_32, ext_bpb, }; let count_of_clusters = bpb.count_of_clusters(); if count_of_clusters < 4085 { anyhow::ensure!( bpb.fat_type == FatType::Fat16, "{:?} should actually be FAT12", bpb.fat_type ); // actually FAT12 instead of FAT16 bpb.fat_type = FatType::Fat12; } else if count_of_clusters < 65525 { anyhow::ensure!( bpb.fat_type == FatType::Fat16, "{:?} should actually be FAT16", bpb.fat_type ); } else { anyhow::ensure!( bpb.fat_type == FatType::Fat32, "{:?} should actually be FAT32", bpb.fat_type ); } Ok(bpb) } /// number of sectors usable for data pub fn num_data_sectors(&self) -> u32 { let data_sectors = self.total_sectors() - (self.reserved_sector_count() as u32 + (self.num_fats() as u32 * self.fat_size()) + self.root_dir_sectors()); data_sectors } /// total number of clusters on this volume pub fn num_clusters(&self) -> u32 { self.total_sectors() / self.sectors_per_cluster() as u32 } /// number of bytes per cluster pub fn bytes_per_cluster(&self) -> usize { self.sectors_per_cluster() as usize * self.bytes_per_sector() as usize } /// count of *data* clusters pub fn count_of_clusters(&self) -> u32 { self.num_data_sectors() / self.sectors_per_cluster as u32 } /// convert a given sector to an byte offset fn sector_to_offset(&self, sector: u32) -> u64 { sector as u64 * self.bytes_per_sector() as u64 } /// byte offset of the first FAT pub fn fat_offset(&self) -> u64 { self.sector_to_offset(self.reserved_sector_count() as u32) } /// FAT size in bytes pub fn fat_len_bytes(&self) -> usize { self.bytes_per_sector() as usize * self.fat_size() as usize } /// byte offset of the root directory; None for FAT32 pub fn root_directory_offset(&self) -> Option { if self.fat_type() == FatType::Fat32 { return None; } Some(self.fat_offset() + self.sector_to_offset(self.num_fats() as u32 * self.fat_size())) } /// number of sectors for root dir (only FAT12 and FAT16) pub fn root_dir_sectors(&self) -> u32 { (32 * self.root_entry_count() as u32).div_ceil(self.bytes_per_sector() as u32) } /// byte size of the root directory pub fn root_dir_len_bytes(&self) -> usize { self.root_dir_sectors() as usize * self.bytes_per_sector() as usize } /// first data sector pub fn first_data_sector(&self) -> u32 { println!("reserved sectors: {}", self.reserved_sector_count()); println!("fat sectors: {}", self.num_fats() as u32 * self.fat_size()); println!("root dir sectors: {}", self.root_dir_sectors()); self.reserved_sector_count() as u32 + (self.num_fats() as u32 + self.fat_size()) + self.root_dir_sectors() as u32 } pub fn data_offset(&self) -> u64 { // if let Some(root_dir_offset) = self.root_directory_offset() { // // has root directory (FAT12 or FAT16) // return root_dir_offset + self.sector_to_offset(self.root_entry_count() as u32); // } // self.fat_offset() + self.sector_to_offset(self.num_fats() as u32 * self.fat_size()) self.sector_to_offset(self.first_data_sector()) } /// byte size of the data section pub fn data_len_bytes(&self) -> usize { self.num_data_sectors() as usize * self.bytes_per_sector() as usize } /// FAT type (FAT12, FAT16, or FAT32) pub fn fat_type(&self) -> FatType { self.fat_type } /// number of sectors per FAT pub fn fat_size(&self) -> u32 { match &self.ext_bpb { ExtBpb::ExtBpb16(_ext_bpb16) => self.fat_size_16() as u32, ExtBpb::ExtBpb32(ext_bpb32) => ext_bpb32.fat_size_32(), } } /// get root cluster for FAT32 pub fn root_cluster(&self) -> Option { if let ExtBpb::ExtBpb32(ext_bpb32) = &self.ext_bpb { Some(ext_bpb32.root_cluster()) } else { None } } /// total number of sectors in this device /// /// uses total_sectors_16 or total_sectors_32 pub fn total_sectors(&self) -> u32 { // match &self.ext_bpb { // ExtBpb::ExtBpb16(_) => self.total_sectors_16() as u32, // ExtBpb::ExtBpb32(_) => self.total_sectors_32(), // } match self.total_sectors_16() { 0 => self.total_sectors_32(), n => n as u32, } } pub fn oem_name(&self) -> &[u8] { &self.oem_name } pub fn oem_name_str(&self) -> Option<&str> { std::str::from_utf8(&self.oem_name()).ok() } pub fn bytes_per_sector(&self) -> u16 { self.bytes_per_sector } pub fn sectors_per_cluster(&self) -> u8 { self.sectors_per_cluster } pub fn reserved_sector_count(&self) -> u16 { self.reserved_sector_count } pub fn num_fats(&self) -> u8 { self.num_fats } // number of 32 byte dir entries in the root directory pub fn root_entry_count(&self) -> u16 { self.root_entry_count } pub fn total_sectors_16(&self) -> u16 { self.total_sectors_16 } pub fn media(&self) -> u8 { self.media } pub fn fat_size_16(&self) -> u16 { self.fat_size_16 } pub fn sectors_per_track(&self) -> u16 { self.sectors_per_track } pub fn num_heads(&self) -> u16 { self.num_heads } pub fn hidden_sectors(&self) -> u32 { self.hidden_sectors } pub fn total_sectors_32(&self) -> u32 { self.total_sectors_32 } } #[derive(Debug)] pub struct ExtBpb16 { drive_number: u8, boot_sig: u8, volume_serial_number: u32, volume_label: [u8; 11], file_sys_type: [u8; 8], } impl Display for ExtBpb16 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "ExtBpb16 {{")?; writeln!(f, " drive number: {}", self.drive_number())?; writeln!(f, " boot_sig: {:#x}", self.boot_sig())?; writeln!(f, " volume serial number: {}", self.volume_serial_number())?; writeln!(f, " volume label: {}", self.volume_label_str().unwrap_or(""))?; writeln!(f, " file sys type: {}", self.file_sys_type_str())?; writeln!(f, "}}")?; Ok(()) } } impl ExtBpb16 { pub fn load(bytes: &[u8]) -> anyhow::Result { let drive_number = bytes[36]; if !&[0x80, 0x00].contains(&drive_number) { anyhow::bail!("invalid drive number: {}", drive_number); } let boot_sig = bytes[38]; let volume_serial_number = load_u32_le(&bytes[39..][..4]); let volume_label = bytes[43..][..11].try_into().unwrap(); if volume_serial_number != 0 || volume_label != [0; 11] { anyhow::ensure!( boot_sig == 0x29, "volume serial number and volume label are not both empty, but boot sig is {:#x} instead of 0x29", boot_sig ); } let file_sys_type: [u8; 8] = bytes[54..][..8].try_into().unwrap(); let Some(s) = std::str::from_utf8(&file_sys_type).ok() else { anyhow::bail!("invalid file sys type: {:X?}", file_sys_type); }; if !&["FAT12 ", "FAT16 ", "FAT "].contains(&s) { anyhow::bail!("invalid file sys type: {}", s); } let signature_word = &bytes[510..512]; anyhow::ensure!( signature_word == &[0x55, 0xAA], "invalid signature word: [{:#X}, {:#X}] instead of [0x55, 0xAA]", bytes[510], bytes[511] ); Ok(ExtBpb16 { drive_number, boot_sig, volume_serial_number, volume_label, file_sys_type, }) } pub fn drive_number(&self) -> u8 { self.drive_number } pub fn boot_sig(&self) -> u8 { self.boot_sig } pub fn volume_serial_number(&self) -> u32 { self.volume_serial_number } pub fn volume_label(&self) -> &[u8] { &self.volume_label } pub fn volume_label_str(&self) -> Option<&str> { std::str::from_utf8(&self.volume_label).ok() } pub fn file_sys_type(&self) -> &[u8] { &self.file_sys_type } pub fn file_sys_type_str(&self) -> &str { std::str::from_utf8(&self.file_sys_type).unwrap() } } #[derive(Debug)] pub struct ExtBpb32 { fat_size_32: u32, ext_flags: u16, root_cluster: u32, fs_info: u16, bk_boot_sector: u16, drive_number: u8, boot_sig: u8, volume_serial_number: u32, volume_label: [u8; 11], } impl Display for ExtBpb32 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "ExtBpb32 {{")?; writeln!(f, " fat_size_32: {}", self.fat_size_32)?; writeln!(f, " ext_flags: {}", self.ext_flags)?; writeln!(f, " root_cluster: {}", self.root_cluster)?; writeln!(f, " fs_info: {}", self.fs_info)?; writeln!(f, " bk_boot_sector: {}", self.bk_boot_sector)?; writeln!(f, " drive_number: {}", self.drive_number)?; writeln!(f, " boot_sig: {:#X}", self.boot_sig)?; writeln!(f, " volume serial number: {}", self.volume_serial_number)?; writeln!(f, " volume label: {}", self.volume_label_str().unwrap_or(""))?; writeln!(f, "}}")?; Ok(()) } } impl ExtBpb32 { pub fn load(bytes: &[u8]) -> anyhow::Result { let fat_size_32 = load_u32_le(&bytes[36..][..4]); anyhow::ensure!(fat_size_32 != 0, "fat_size_32 is zero"); let ext_flags = load_u16_le(&bytes[40..][..2]); let fs_ver = load_u16_le(&bytes[42..][..2]); anyhow::ensure!(fs_ver == 0x0, "invalid FSVer: {}", fs_ver); let root_cluster = load_u32_le(&bytes[44..][..4]); let fs_info = load_u16_le(&bytes[48..][..2]); let bk_boot_sector = load_u16_le(&bytes[50..][..2]); anyhow::ensure!( &[0, 6].contains(&bk_boot_sector), "invalid BkBootSector: {}", bk_boot_sector ); let reserved = &bytes[52..][..12]; anyhow::ensure!(reserved == &[0; 12], "reserved is not zeroed"); let drive_number = bytes[64]; let reserved1 = bytes[65]; anyhow::ensure!(reserved1 == 0, "reserved1 is not zeroed"); let boot_sig = bytes[66]; let volume_serial_number = load_u32_le(&bytes[67..][..4]); let volume_label = bytes[71..][..11].try_into().unwrap(); if volume_serial_number != 0 || &volume_label != &[0; 11] { anyhow::ensure!( boot_sig == 0x29, "VollID or VolLab is set, but BootSig is {} instead of 0x29", boot_sig ); } let file_sys_type = &bytes[82..][..8]; anyhow::ensure!( std::str::from_utf8(file_sys_type) == Ok("FAT32 "), "invalid file sys type" ); let signature_word = &bytes[510..][..2]; anyhow::ensure!( signature_word == &[0x55, 0xAA], "invalid signature word [{:#X}, {:#X}] instead of [0x55, 0xAA]", signature_word[0], signature_word[1] ); Ok(ExtBpb32 { fat_size_32, ext_flags, root_cluster, fs_info, bk_boot_sector, drive_number, boot_sig, volume_serial_number, volume_label, }) } pub fn fat_size_32(&self) -> u32 { self.fat_size_32 } pub fn ext_flags(&self) -> u16 { self.ext_flags } pub fn root_cluster(&self) -> u32 { self.root_cluster } pub fn fs_info(&self) -> u16 { self.fs_info } pub fn bk_boot_sector(&self) -> u16 { self.bk_boot_sector } pub fn drive_number(&self) -> u8 { self.drive_number } pub fn boot_sig(&self) -> u8 { self.boot_sig } pub fn volume_serial_number(&self) -> u32 { self.volume_serial_number } pub fn volume_label(&self) -> &[u8] { &self.volume_label } pub fn volume_label_str(&self) -> Option<&str> { std::str::from_utf8(self.volume_label()).ok() } }