a2kit/bios/
bpb.rs

1//! ## BIOS Parameter Block Module
2//! 
3//! This contains the BIOS parameter block (BPB) used with FAT volumes.
4//! Implementation is based on Microsoft Hardware White Paper,
5//! "FAT: General Overview of On-Disk Format,"" Dec. 6, 2000.
6
7use log::debug;
8use crate::{STDRESULT,DYNERR};
9
10// a2kit_macro automatically derives `new`, `to_bytes`, `from_bytes`, and `length` from a DiskStruct.
11// This spares us having to manually write code to copy bytes in and out for every new structure.
12// The auto-derivation is not used for structures with variable length fields (yet).
13use a2kit_macro::{DiskStructError,DiskStruct};
14use a2kit_macro_derive::DiskStruct;
15
16use super::fat::FIRST_DATA_CLUSTER;
17
18const JMP_BOOT: [u8;3] = [0xeb,0x58,0x90];
19const OEM_NAME: [u8;8] = *b"A2KITX.X";
20const BOOT_SIGNATURE: [u8;2] = [0x55,0xaa]; // goes in boot[510..512]
21const RCH: &str = "unreachable was reached";
22
23/// Introduced with MS-DOS 2.0, appears starting at byte 11 of the boot sector,
24/// following `JMP_BOOT` and `OEM_NAME`.
25/// The last field `tot_sec_32` was introduced with MS-DOS 3.0.
26/// The FAT32 fields and the tail fields are not included.
27/// These fields are applicable to all FAT file systems.
28#[derive(DiskStruct)]
29pub struct BPBFoundation {
30    /// 512, 1024, 2048, or 4096
31    pub bytes_per_sec: [u8;2],
32    /// 1, 2, 4, 8, 16, 32, 64, or 128.
33    /// The cluster size must not come out to > 32K.
34    pub sec_per_clus: u8,
35    /// usually 1 for FAT12 or FAT16, 32 for FAT32
36    pub reserved_sectors: [u8;2],
37    /// usually 2
38    pub num_fats: u8,
39    /// Directory entries in the root directory, must be 0 for FAT32.
40    /// The root directory should take up an integral number of sectors.
41    /// For FAT16 512 is recommended.
42    pub root_ent_cnt: [u8;2],
43    /// 16-bit sector count, superceded by tot_sec_32 if 0.
44    /// Includes all areas, but can be less than total disk capacity.
45    pub tot_sec_16: [u8;2],
46    /// 0xf0,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff.
47    /// Value should also be put in FAT[0] in the low 8 bits.
48    /// typical values are 0xf0 (removable) and 0xf8 (fixed).
49    /// based on 86BOX :
50    /// 0xf9 = 1200K
51    /// 0xfb = 640K
52    /// 0xfc = 180K
53    /// 0xfd = 360K
54    /// 0xfe = 160K
55    /// 0xff = 320K
56    pub media: u8,
57    /// count of sectors occupied by one FAT, should be 0 for FAT32
58    pub fat_size_16: [u8;2],
59    /// sectors per track for interrupt 0x13
60    pub sec_per_trk: [u8;2],
61    /// number of heads for interrupt 0x13
62    pub num_heads: [u8;2],
63    /// hidden sectors preceding this FAT volume's partition,
64    /// set to 0 for non-partitioned media.
65    pub hidd_sec: [u8;4],
66    /// 32-bit sector count, if 0 use tot_sec_16.
67    /// This field was added in MS-DOS 3.0.
68    pub tot_sec_32: [u8;4],
69}
70
71impl BPBFoundation {
72    fn to_json(&self,indent: Option<u16>) -> String {
73        let mut ans = json::JsonValue::new_object();
74        let mut bpb = json::JsonValue::new_object();
75        bpb["bytes_per_sec"] = json::JsonValue::String(hex::encode_upper(&self.bytes_per_sec));
76        bpb["sec_per_clus"] = json::JsonValue::String(hex::encode_upper(&vec![self.sec_per_clus]));
77        bpb["reserved_sectors"] = json::JsonValue::String(hex::encode_upper(&self.reserved_sectors));
78        bpb["num_fats"] = json::JsonValue::String(hex::encode_upper(&vec![self.num_fats]));
79        bpb["root_ent_cnt"] = json::JsonValue::String(hex::encode_upper(&self.root_ent_cnt));
80        bpb["tot_sec_16"] = json::JsonValue::String(hex::encode_upper(&self.tot_sec_16));
81        bpb["media"] = json::JsonValue::String(hex::encode_upper(&vec![self.media]));
82        bpb["fat_size_16"] = json::JsonValue::String(hex::encode_upper(&self.fat_size_16));
83        bpb["sec_per_trk"] = json::JsonValue::String(hex::encode_upper(&self.sec_per_trk));
84        bpb["num_heads"] = json::JsonValue::String(hex::encode_upper(&self.num_heads));
85        bpb["hidd_sec"] = json::JsonValue::String(hex::encode_upper(&self.hidd_sec));
86        bpb["tot_sec_32"] = json::JsonValue::String(hex::encode_upper(&self.tot_sec_32));
87        ans["bpb"] = bpb;
88        if let Some(spaces) = indent {
89            return json::stringify_pretty(ans,spaces);
90        } else {
91            return json::stringify(ans);
92        }
93    }
94}
95
96/// Introduced with Windows 95, appears starting at byte 36 of the boot sector.
97#[derive(DiskStruct)]
98pub struct BPBExtension32 {
99    /// 32-bit version of fat_size
100    pub fat_size_32: [u8;4],
101    /// bits 0-3 = active FAT, 4-6 reserved, 7 = disable mirroring, 8-15 reserved
102    pub flags:  [u8;2],
103    /// high byte = major, low byte = minor
104    pub fs_version: [u8;2],
105    /// cluster number of the root directory, usually 2
106    pub root_cluster: [u8;4],
107    /// sector number of FSINFO structure, usually 1.
108    /// The boot sectors and backup boot sectors both point to the same FSInfo sector,
109    /// even though a backup FSInfo exists in the backup sectors.
110    pub fs_info: [u8;2],
111    /// if non-zero, indicates sector number of the backup boot record, usually 6.
112    /// This is the start of the backup boot record, which may be multiple sectors.
113    pub bk_boot_sec: [u8;2],
114    /// reserved, set to 0
115    pub reserved: [u8;12]
116}
117
118/// This follows the BPB, whether it is the FAT12/16 BPB or the FAT32 BPB.
119#[derive(DiskStruct)]
120pub struct BPBTail {
121    /// interrupt 0x13 drive number (0x00 for floppy, 0x80 for hard disk, MS-DOS specific)
122    pub drv_num: u8,
123    /// set to 0
124    pub reserved1: u8,
125    /// signature (0x29) indicating following 3 fields are present
126    pub boot_sig: u8,
127    /// volume serial number, can generate using 32-bit timestamp
128    pub vol_id: [u8;4],
129    /// volume label, matches root directory label if it exists, otherwise "NO NAME    " 
130    pub vol_lab: [u8;11],
131    /// file system type only for display, "FAT12", "FAT16", or "FAT32" padded with spaces.
132    /// Should not be used to determine the FAT type.
133    pub fil_sys_type: [u8;8]
134}
135
136/// This has its own sector, given by BPBExtension32.fs_info, usually sector 1
137#[derive(DiskStruct)]
138pub struct Info {
139    pub lead_sig: [u8;4],
140    pub reserved1: [u8;480],
141    pub struc_sig: [u8;4],
142    pub free_count: [u8;4],
143    pub nxt_free: [u8;4],
144    pub reserved2: [u8;12],
145    pub trail_sig: [u8;4]
146}
147
148impl BPBFoundation {
149    pub fn verify(&self) -> bool {
150        let mut ans = true;
151        let bytes = u16::from_le_bytes(self.bytes_per_sec) as u64;
152        if ![512,1024,2048,4096].contains(&bytes) {
153            debug!("invalid bytes per sector {}",bytes);
154            ans = false;
155        }
156        if ![1,2,4,8,16,32,64,128].contains(&self.sec_per_clus) {
157            debug!("invalid sectors per cluster {}",self.sec_per_clus);
158            ans = false;
159        }
160        if self.reserved_sectors==[0,0] {
161            debug!("invalid count of reserved sectors 0");
162            ans = false;
163        }
164        if self.num_fats==0 {
165            debug!("invalid count of FATs 0");
166            ans = false;
167        }
168        let entries = u16::from_le_bytes(self.root_ent_cnt) as u64;
169        if bytes > 0 && (entries*32)%bytes != 0 {
170            debug!("invalid entry count {}",entries);
171            ans = false;
172        }
173        if self.tot_sec_16==[0,0] && self.tot_sec_32==[0,0,0,0] {
174            debug!("invalid sector count 0");
175            ans = false;
176        }
177        ans
178    }
179    pub fn sec_size(&self) -> u64 {
180        u16::from_le_bytes(self.bytes_per_sec) as u64
181    }
182    pub fn block_size(&self) -> u64 {
183        self.sec_per_clus() as u64 * self.sec_size()
184    }
185    pub fn heads(&self) -> u64 {
186        u16::from_le_bytes(self.num_heads) as u64
187    }
188    pub fn secs_per_track(&self) -> u64 {
189        u16::from_le_bytes(self.sec_per_trk) as u64
190    }
191    pub fn tot_sec(&self) -> u64 {
192        match self.tot_sec_16 {
193            [0,0] => u32::from_le_bytes(self.tot_sec_32) as u64,
194            _ => u16::from_le_bytes(self.tot_sec_16) as u64
195        }
196    }
197    pub fn res_secs(&self) -> u16 {
198        u16::from_le_bytes(self.reserved_sectors)
199    }
200    pub fn root_dir_secs(&self) -> u64 {
201        let bytes = u16::from_le_bytes(self.bytes_per_sec) as u64;
202        let entries = u16::from_le_bytes(self.root_ent_cnt) as u64;
203        if bytes==0 {
204            return u16::MAX as u64;
205        }
206        (entries*32 + bytes - 1) / bytes
207    }
208    pub fn root_dir_entries(&self) -> u64 {
209        u16::from_be_bytes(self.root_ent_cnt) as u64
210    }
211    pub fn sec_per_clus(&self) -> u8 {
212        // Early DOS had a bug that erroneously set this to 2 for 160K and 180K disks.
213        // Therefore we override it in the FS layer when such disks are detected.
214        self.sec_per_clus
215    }
216}
217
218/// This represents and manages the data in the boot sector,
219/// which includes the BPB, along with some other information.
220pub struct BootSector {
221    jmp: [u8;3],
222    oem: [u8;8],
223    foundation: BPBFoundation,
224    /// This is always read, but if it turns out we have a FAT12/16,
225    /// the tail data and remainder are obtained by rewinding.
226    extension32: BPBExtension32,
227    tail: BPBTail,
228    /// Whatever follows, including the signature.
229    /// Remainder length depends on FAT type and sector size.
230    remainder: Vec<u8>
231}
232
233impl DiskStruct for BootSector {
234    fn new() -> Self where Self: Sized {
235        Self {
236            jmp: [0,0,0],
237            oem: [0;8],
238            foundation: BPBFoundation::new(),
239            extension32: BPBExtension32::new(),
240            tail: BPBTail::new(),
241            remainder: Vec::new()
242        }
243    }
244    fn len(&self) -> usize {
245        13 + self.foundation.len() + self.extension32.len() + self.tail.len() 
246    }
247    fn from_bytes(bytes: &[u8]) -> Result<Self,DiskStructError> where Self: Sized {
248        let mut ans = Self::new();
249        ans.update_from_bytes(bytes)?;
250        Ok(ans)
251    }
252    fn update_from_bytes(&mut self,bytes: &[u8]) -> Result<(),DiskStructError> {
253        // suppose we have a FAT32
254        let tentative = Self {
255            jmp: [0,0,0],
256            oem: [0;8],
257            foundation: BPBFoundation::from_bytes(&bytes[11..36].to_vec())?,
258            extension32: BPBExtension32::from_bytes(&bytes[36..64].to_vec())?,
259            tail: BPBTail::from_bytes(&bytes[64..90].to_vec())?,
260            remainder: bytes[90..].to_vec()
261        };
262        // Setup the right FAT type using info from the supposed FAT32.
263        // This can panic if the FAT data is unverified.
264        self.jmp = bytes[0..3].try_into().expect(RCH);
265        self.oem = bytes[3..11].try_into().expect(RCH);
266        self.foundation = BPBFoundation::from_bytes(&bytes[11..36].to_vec())?;
267        self.extension32 = BPBExtension32::from_bytes(&bytes[36..64].to_vec())?;
268        self.tail = match tentative.fat_type() {
269            32 => BPBTail::from_bytes(&bytes[64..90].to_vec())?,
270            _ => BPBTail::from_bytes(&bytes[36..62].to_vec())?
271        };
272        self.remainder = match tentative.fat_type() {
273            32 => bytes[90..].to_vec(),
274            _ => bytes[62..].to_vec()
275        };
276        Ok(())
277    }
278    fn to_bytes(&self) -> Vec<u8> {
279        let mut ans: Vec<u8> = Vec::new();
280        match self.fat_type() {
281            32 => {
282                ans.append(&mut self.jmp.to_vec());
283                ans.append(&mut self.oem.to_vec());
284                ans.append(&mut self.foundation.to_bytes());
285                ans.append(&mut self.extension32.to_bytes());
286                ans.append(&mut self.tail.to_bytes());
287                ans.append(&mut self.remainder.clone());
288            },
289            _ => {
290                ans.append(&mut self.jmp.to_vec());
291                ans.append(&mut self.oem.to_vec());
292                ans.append(&mut self.foundation.to_bytes());
293                ans.append(&mut self.tail.to_bytes());
294                ans.append(&mut self.remainder.clone());
295            }
296        }
297        ans
298    }
299}
300
301impl BootSector {
302    pub fn create(kind: &crate::img::DiskKind) -> Result<Self,DYNERR> {
303        use crate::img::names;
304        use crate::img::DiskKind::{D35,D525,D8};
305        match kind {
306            D8(names::CPM_1) => Ok(Self::create1216(SSSD_8)),
307            D8(names::DSDD_77) => Ok(Self::create1216(DSDD_8)),
308            D525(names::IBM_SSDD_8) => Ok(Self::create1216(SSDD_525_8)),
309            D525(names::IBM_SSDD_9) =>  Ok(Self::create1216(SSDD_525_9)),
310            D525(names::IBM_DSDD_8) =>  Ok(Self::create1216(DSDD_525_8)),
311            D525(names::IBM_DSDD_9) =>  Ok(Self::create1216(DSDD_525_9)),
312            D525(names::IBM_DSQD) =>  Ok(Self::create1216(DSQD_525)),
313            D525(names::IBM_DSHD) =>  Ok(Self::create1216(DSHD_525)),
314            D35(names::IBM_720) =>  Ok(Self::create1216(D35_720)),
315            D35(names::IBM_1440) =>  Ok(Self::create1216(D35_1440)),
316            D35(names::IBM_2880) =>  Ok(Self::create1216(D35_2880)),
317            _ => Err(Box::new(super::Error::UnsupportedDiskKind))
318        }
319    }
320    fn create1216(bpb: BPBFoundation) -> Self {
321        let tail = BPBTail::new();
322        let sec_size = bpb.sec_size() as usize;
323        let used = JMP_BOOT.len() + OEM_NAME.len() + bpb.len() + tail.len();
324        let mut remainder: Vec<u8> = vec![0;sec_size - used];
325        if sec_size>=512 {
326            remainder[510-used] = BOOT_SIGNATURE[0];
327            remainder[511-used] = BOOT_SIGNATURE[1];
328        }
329        let mut oem = OEM_NAME;
330        oem[5..8].copy_from_slice(&env!("CARGO_PKG_VERSION").as_bytes()[0..3]);
331        Self {
332            jmp: JMP_BOOT,
333            oem,
334            foundation: bpb,
335            extension32: BPBExtension32::new(),
336            tail,
337            remainder 
338        }
339    }
340    /// This replaces the BPB foundation fields with a tabulated one.
341    /// This is used when we detect a 160K or 180K disk, where the BPB data cannot be relied on.
342    pub fn replace_foundation(&mut self,kind: &crate::img::DiskKind) -> STDRESULT {
343        let lookup = Self::create(kind)?;
344        self.foundation = lookup.foundation;
345        Ok(())
346    }
347    /// Verify that the sector data is a valid boot sector,
348    /// should be called before unpacking with from_bytes.
349    pub fn verify(sec_data: &Vec<u8>) -> bool {
350        let mut ans = true;
351        if sec_data.len()<512 {
352            debug!("sector too small");
353            return false;
354        }
355        let signature = [sec_data[510],sec_data[511]];
356        if signature!=BOOT_SIGNATURE {
357            debug!("signature mismatch");
358            ans = false;
359        }
360        let bpb = BPBFoundation::from_bytes(&sec_data[11..36].to_vec()).expect(RCH);
361        ans |= bpb.verify();
362        let ext32 = BPBExtension32::from_bytes(&sec_data[36..64].to_vec()).expect(RCH);
363        let fat_secs = match bpb.fat_size_16 {
364            [0,0] => u32::from_le_bytes(ext32.fat_size_32) as u64,
365            _ => u16::from_le_bytes(bpb.fat_size_16) as u64
366        };
367        if fat_secs==0 {
368            debug!("invalid count of FAT sectors 0");
369            ans = false;
370        }
371        if bpb.tot_sec() <= bpb.res_secs() as u64 + (bpb.num_fats as u64 * fat_secs) + bpb.root_dir_secs() {
372            debug!("data region came out 0 or negative");
373            ans = false;
374        }
375        if ans {
376            debug!("BPB counts: {} tot, {} res, {}x{} FAT, {} root",bpb.tot_sec(),fat_secs,bpb.num_fats,bpb.res_secs(),bpb.root_dir_secs());
377        }
378        ans
379    }
380    pub fn label(&self) -> Option<[u8;11]> {
381        if self.tail.boot_sig==0x29 && self.tail.vol_lab!=[0x20;11] {
382            Some(self.tail.vol_lab)
383        } else {
384            None
385        }
386    }
387    pub fn sec_size(&self) -> u64 {
388        self.foundation.sec_size()
389    }
390    pub fn block_size(&self) -> u64 {
391        self.foundation.block_size()
392    }
393    pub fn heads(&self) -> u64 {
394        self.foundation.heads()
395    }
396    pub fn secs_per_track(&self) -> u64 {
397        self.foundation.secs_per_track()
398    }
399    pub fn tot_sec(&self) -> u64 {
400        self.foundation.tot_sec()
401    }
402    pub fn res_secs(&self) -> u16 {
403        self.foundation.res_secs()
404    }
405    pub fn secs_per_clus(&self) -> u8 {
406        self.foundation.sec_per_clus()
407    }
408    pub fn media_byte(&self) -> u8 {
409        self.foundation.media
410    }
411    /// only meaningful for FAT32
412    pub fn root_dir_cluster1(&self) -> u64 {
413        u32::from_le_bytes(self.extension32.root_cluster) as u64
414    }
415    // count of entries in root directory, zero for FAT32
416    pub fn root_dir_entries(&self) -> u64 {
417        self.foundation.root_dir_entries()
418    }
419    /// sectors used by the root directory, rounding up, zero for FAT32
420    pub fn root_dir_secs(&self) -> u64 {
421        self.foundation.root_dir_secs()
422    }
423    pub fn num_fats(&self) -> u64 {
424        self.foundation.num_fats as u64
425    }
426    /// sectors occupied by 1 FAT
427    pub fn fat_secs(&self) -> u64 {
428        match self.foundation.fat_size_16 {
429            [0,0] => u32::from_le_bytes(self.extension32.fat_size_32) as u64,
430            _ => u16::from_le_bytes(self.foundation.fat_size_16) as u64
431        }
432    }
433    pub fn root_dir_sec_rng(&self) -> [u64;2] {
434        [
435            self.res_secs() as u64 + (self.foundation.num_fats as u64 * self.fat_secs()),
436            self.res_secs() as u64 + (self.foundation.num_fats as u64 * self.fat_secs()) + self.root_dir_secs()
437        ]
438    }
439    pub fn data_rgn_secs(&self) -> u64 {
440        self.tot_sec() - (self.res_secs() as u64 + (self.foundation.num_fats as u64 * self.fat_secs()) + self.root_dir_secs())
441    }
442    /// number of clusters possible in the data region rounding down,
443    /// this is used to determine the FAT type.
444    pub fn cluster_count_abstract(&self) -> u64 {
445        self.data_rgn_secs()/self.foundation.sec_per_clus() as u64
446    }
447    /// smaller of (clusters possible in the data region , clusters possible in the FAT)
448    pub fn cluster_count_usable(&self) -> u64 {
449        let typ = self.fat_type() as u64;
450        u64::min(
451            self.data_rgn_secs()/self.foundation.sec_per_clus() as u64,
452            self.fat_secs() * self.sec_size() * 8 / typ - FIRST_DATA_CLUSTER as u64
453        )
454    }
455    /// FAT type determination based on the cluster count.
456    /// These peculiar cutoffs are correct according to MS.
457    pub fn fat_type(&self) -> usize {
458        match self.cluster_count_abstract() {
459            x if x < 4085 => 12,
460            x if x < 65525 => 16,
461            _ => 32
462        }
463    }
464    /// Locate FAT entry for cluster n as [sec,offset].
465    /// For FAT12, the last 4 bits may be in the next sector.
466    pub fn cluster_ref(&self,n: u64) -> [u64;2] {
467        let by_per_sec = u16::from_le_bytes(self.foundation.bytes_per_sec) as u64;
468        match self.fat_type() {
469            12 => {
470                let sec = self.res_secs() as u64 + (n + (n/2))/by_per_sec;
471                let offset = (n + (n/2)) % by_per_sec;
472                [sec,offset]
473            },
474            16 => {
475                let sec = self.res_secs() as u64 + (n*2) / by_per_sec;
476                let offset = (n*2) % by_per_sec;
477                [sec,offset]
478            },
479            32 => {
480                let sec = self.res_secs() as u64 + (n*4) / by_per_sec;
481                let offset = (n*4) % by_per_sec;
482                [sec,offset]
483            }
484            _ => panic!("unexpected FAT type")
485        }
486    }
487    pub fn first_data_sec(&self) -> u64 {
488        self.res_secs() as u64 + self.foundation.num_fats as u64 * self.fat_secs() + self.root_dir_secs()
489    }
490    pub fn first_cluster_sec(&self,n: u64) -> u64 {
491        (n-2)*self.foundation.sec_per_clus() as u64 + self.first_data_sec()
492    }
493    pub fn create_tail(&mut self,drv_num: u8,id: [u8;4],label: [u8;11]) {
494        self.tail.boot_sig = 0x29;
495        self.tail.drv_num = drv_num;
496        self.tail.fil_sys_type = match self.fat_type() {
497            12 => *b"FAT12   ",
498            16 => *b"FAT16   ",
499            32 => *b"FAT32   ",
500            _ => panic!("unexpected FAT type")
501        };
502        self.tail.reserved1 = 0x00;
503        self.tail.vol_id = id;
504        self.tail.vol_lab = label;
505    }
506    pub fn to_json(&self,indent: Option<u16>) -> String {
507        self.foundation.to_json(indent)
508    }
509}
510
511const SSSD_8: BPBFoundation = BPBFoundation {
512    bytes_per_sec: [128,0],
513    sec_per_clus: 4,
514    reserved_sectors: [1,0],
515    num_fats: 2,
516    root_ent_cnt: u16::to_le_bytes(68),
517    tot_sec_16: u16::to_le_bytes(2002),
518    media: 0xfe,
519    fat_size_16: [6,0],
520    sec_per_trk: [26,0],
521    num_heads: [1,0],
522    hidd_sec: [0,0,0,0],
523    tot_sec_32: [0,0,0,0]
524};
525
526const DSDD_8: BPBFoundation = BPBFoundation {
527    bytes_per_sec: u16::to_le_bytes(1024),
528    sec_per_clus: 1,
529    reserved_sectors: [1,0],
530    num_fats: 2,
531    root_ent_cnt: u16::to_le_bytes(192),
532    tot_sec_16: u16::to_le_bytes(1232),
533    media: 0xfe,
534    fat_size_16: [2,0],
535    sec_per_trk: [8,0],
536    num_heads: [2,0],
537    hidd_sec: [0,0,0,0],
538    tot_sec_32: [0,0,0,0]
539};
540
541const SSDD_525_8: BPBFoundation = BPBFoundation {
542    bytes_per_sec: [0,2],
543    sec_per_clus: 1,
544    reserved_sectors: [1,0],
545    num_fats: 2,
546    root_ent_cnt: u16::to_le_bytes(0x40),
547    tot_sec_16: u16::to_le_bytes(320),
548    media: 0xfe,
549    fat_size_16: [1,0],
550    sec_per_trk: [8,0],
551    num_heads: [1,0],
552    hidd_sec: [0,0,0,0],
553    tot_sec_32: [0,0,0,0]
554};
555
556const SSDD_525_9: BPBFoundation = BPBFoundation {
557    bytes_per_sec: [0,2],
558    sec_per_clus: 1,
559    reserved_sectors: [1,0],
560    num_fats: 2,
561    root_ent_cnt: u16::to_le_bytes(0x40),
562    tot_sec_16: u16::to_le_bytes(360),
563    media: 0xfc,
564    fat_size_16: [1,0],
565    sec_per_trk: [9,0],
566    num_heads: [1,0],
567    hidd_sec: [0,0,0,0],
568    tot_sec_32: [0,0,0,0]
569};
570
571const DSDD_525_8: BPBFoundation = BPBFoundation {
572    bytes_per_sec: [0,2],
573    sec_per_clus: 2,
574    reserved_sectors: [1,0],
575    num_fats: 2,
576    root_ent_cnt: u16::to_le_bytes(0x70),
577    tot_sec_16: u16::to_le_bytes(640),
578    media: 0xff,
579    fat_size_16: [1,0],
580    sec_per_trk: [8,0],
581    num_heads: [2,0],
582    hidd_sec: [0,0,0,0],
583    tot_sec_32: [0,0,0,0]
584};
585
586const DSDD_525_9: BPBFoundation = BPBFoundation {
587    bytes_per_sec: [0,2],
588    sec_per_clus: 2,
589    reserved_sectors: [1,0],
590    num_fats: 2,
591    root_ent_cnt: u16::to_le_bytes(0x70),
592    tot_sec_16: u16::to_le_bytes(720),
593    media: 0xfd,
594    fat_size_16: [2,0],
595    sec_per_trk: [9,0],
596    num_heads: [2,0],
597    hidd_sec: [0,0,0,0],
598    tot_sec_32: [0,0,0,0]
599};
600
601const DSQD_525: BPBFoundation = BPBFoundation {
602    bytes_per_sec: [0,2],
603    sec_per_clus: 2,
604    reserved_sectors: [1,0],
605    num_fats: 2,
606    root_ent_cnt: u16::to_le_bytes(0x70),
607    tot_sec_16: u16::to_le_bytes(1280),
608    media: 0xfb,
609    fat_size_16: [2,0],
610    sec_per_trk: [8,0],
611    num_heads: [2,0],
612    hidd_sec: [0,0,0,0],
613    tot_sec_32: [0,0,0,0]
614};
615
616const DSHD_525: BPBFoundation = BPBFoundation {
617    bytes_per_sec: [0,2],
618    sec_per_clus: 1,
619    reserved_sectors: [1,0],
620    num_fats: 2,
621    root_ent_cnt: u16::to_le_bytes(0xe0),
622    tot_sec_16: u16::to_le_bytes(2400),
623    media: 0xf9,
624    fat_size_16: [7,0],
625    sec_per_trk: [15,0],
626    num_heads: [2,0],
627    hidd_sec: [0,0,0,0],
628    tot_sec_32: [0,0,0,0]
629};
630
631const D35_720: BPBFoundation = BPBFoundation {
632    bytes_per_sec: [0,2],
633    sec_per_clus: 2,
634    reserved_sectors: [1,0],
635    num_fats: 2,
636    root_ent_cnt: u16::to_le_bytes(0x70),
637    tot_sec_16: u16::to_le_bytes(1440),
638    media: 0xf9,
639    fat_size_16: [3,0],
640    sec_per_trk: [9,0],
641    num_heads: [2,0],
642    hidd_sec: [0,0,0,0],
643    tot_sec_32: [0,0,0,0]
644};
645
646const D35_1440: BPBFoundation = BPBFoundation {
647    bytes_per_sec: [0,2],
648    sec_per_clus: 1,
649    reserved_sectors: [1,0],
650    num_fats: 2,
651    root_ent_cnt: u16::to_le_bytes(0xe0),
652    tot_sec_16: u16::to_le_bytes(2880),
653    media: 0xf0,
654    fat_size_16: [9,0],
655    sec_per_trk: [18,0],
656    num_heads: [2,0],
657    hidd_sec: [0,0,0,0],
658    tot_sec_32: [0,0,0,0]
659};
660
661const D35_2880: BPBFoundation = BPBFoundation {
662    bytes_per_sec: [0,2],
663    sec_per_clus: 2,
664    reserved_sectors: [1,0],
665    num_fats: 2,
666    root_ent_cnt: u16::to_le_bytes(0xf0),
667    tot_sec_16: u16::to_le_bytes(5760),
668    media: 0xf0,
669    fat_size_16: [9,0],
670    sec_per_trk: [36,0],
671    num_heads: [2,0],
672    hidd_sec: [0,0,0,0],
673    tot_sec_32: [0,0,0,0]
674};