vfat: format specification

This page hosts a formal specification of vfat using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Block diagram

Format specification in Kaitai Struct YAML

meta:
  id: vfat
  # actually FAT12
  endian: le
seq:
  - id: boot_sector
    type: boot_sector
instances:
  fats:
    pos: boot_sector.pos_fats
    size: boot_sector.size_fat
    repeat: expr
    repeat-expr: boot_sector.bpb.num_fats
  root_dir:
    pos: boot_sector.pos_root_dir
    size: boot_sector.size_root_dir
    type: root_directory
types:
  boot_sector:
    seq:
      - id: jmp_instruction
        size: 3
      - id: oem_name
        type: str
        encoding: ASCII
        pad-right: 0x20
        size: 8
      - id: bpb
        type: bios_param_block
        doc: Basic BIOS parameter block, present in all versions of FAT
      - id: ebpb_fat16
        type: ext_bios_param_block_fat16
        if: not is_fat32
        doc: FAT12/16-specific extended BIOS parameter block
      - id: ebpb_fat32
        type: ext_bios_param_block_fat32
        if: is_fat32
        doc: FAT32-specific extended BIOS parameter block
    instances:
      is_fat32:
        value: bpb.max_root_dir_rec == 0
        doc: |
          Determines if filesystem is FAT32 (true) or FAT12/16 (false)
          by analyzing some preliminary conditions in BPB. Used to
          determine whether we should parse post-BPB data as
          `ext_bios_param_block_fat16` or `ext_bios_param_block_fat32`.
      pos_fats:
        value: bpb.bytes_per_ls * bpb.num_reserved_ls
        doc: Offset of FATs in bytes from start of filesystem
      ls_per_fat:
        value: 'is_fat32 ? ebpb_fat32.ls_per_fat : bpb.ls_per_fat'
      size_fat:
        value: bpb.bytes_per_ls * ls_per_fat
        doc: Size of one FAT in bytes
      pos_root_dir:
        value: bpb.bytes_per_ls * (bpb.num_reserved_ls + ls_per_fat * bpb.num_fats)
        doc: Offset of root directory in bytes from start of filesystem
      ls_per_root_dir:
        -orig-id: RootDirSectors
        value: (bpb.max_root_dir_rec * 32 + bpb.bytes_per_ls - 1) / bpb.bytes_per_ls
        doc: Size of root directory in logical sectors
        doc-ref: 'FAT: General Overview of On-Disk Format, section "FAT Data Structure"'
      size_root_dir:
        value: ls_per_root_dir * bpb.bytes_per_ls
        doc: Size of root directory in bytes
  bios_param_block:
    seq:
      # Basic BIOS Parameter block, DOS 2.0+
      - id: bytes_per_ls
        -orig-id: BPB_BytsPerSec
        type: u2
        doc: Bytes per logical sector
      - id: ls_per_clus
        -orig-id: BPB_SecPerClus
        type: u1
        doc: Logical sectors per cluster
      - id: num_reserved_ls
        -orig-id: BPB_RsvdSecCnt
        type: u2
        doc: |
          Count of reserved logical sectors. The number of logical
          sectors before the first FAT in the file system image.
      - id: num_fats
        -orig-id: BPB_NumFATs
        type: u1
        doc: Number of File Allocation Tables
      - id: max_root_dir_rec
        -orig-id: BPB_RootEntCnt
        type: u2
        doc: |
          Maximum number of FAT12 or FAT16 root directory entries. 0
          for FAT32, where the root directory is stored in ordinary
          data clusters.
      - id: total_ls_2
        -orig-id: BPB_TotSec16
        type: u2
        doc: Total logical sectors (if zero, use total_ls_4)
      - id: media_code
        -orig-id: BPB_Media
        type: u1
        doc: Media descriptor
      - id: ls_per_fat
        -orig-id: BPB_FATSz16
        type: u2
        doc: |
          Logical sectors per File Allocation Table for
          FAT12/FAT16. FAT32 sets this to 0 and uses the 32-bit value
          at offset 0x024 instead.
      # FIXME: DOS 3.0 and 3.2 BPBs continuation should follow here
      # and they differ from DOS 3.31+ that most modern
      # implementations use. We'll skip them for now, as they seem to
      # be very rare.
      #
      # DOS 3.31+ BPB
      - id: ps_per_track
        -orig-id: BPB_SecPerTrk
        type: u2
        doc: |
          Physical sectors per track for disks with INT 13h CHS
          geometry, e.g., 15 for a “1.20 MB” (1200 KB) floppy. A zero
          entry indicates that this entry is reserved, but not used.
      - id: num_heads
        -orig-id: BPB_NumHeads
        type: u2
        doc: |
          Number of heads for disks with INT 13h CHS geometry,[9]
          e.g., 2 for a double sided floppy.
      - id: num_hidden_sectors
        -orig-id: BPB_HiddSec
        type: u4
        doc: |
          Number of hidden sectors preceding the partition that
          contains this FAT volume. This field should always be zero
          on media that are not partitioned. This DOS 3.0 entry is
          incompatible with a similar entry at offset 0x01C in BPBs
          since DOS 3.31.  It must not be used if the logical sectors
          entry at offset 0x013 is zero.
      - id: total_ls_4
        -orig-id: BPB_TotSec32
        type: u4
        doc: |
          Total logical sectors including hidden sectors. This DOS 3.2
          entry is incompatible with a similar entry at offset 0x020
          in BPBs since DOS 3.31. It must not be used if the logical
          sectors entry at offset 0x013 is zero.
  ext_bios_param_block_fat16:
    doc: |
      Extended BIOS Parameter Block (DOS 4.0+, OS/2 1.0+). Used only
      for FAT12 and FAT16.
    seq:
      - id: phys_drive_num
        type: u1
        doc: |
          Physical drive number (0x00 for (first) removable media,
          0x80 for (first) fixed disk as per INT 13h).
      - id: reserved1
        type: u1
      - id: ext_boot_sign
        type: u1
        doc: |
          Should be 0x29 to indicate that an EBPB with the following 3
          entries exists.
      - id: volume_id
        size: 4
        doc: |
          Volume ID (serial number).

          Typically the serial number "xxxx-xxxx" is created by a
          16-bit addition of both DX values returned by INT 21h/AH=2Ah
          (get system date) and INT 21h/AH=2Ch (get system time) for
          the high word and another 16-bit addition of both CX values
          for the low word of the serial number. Alternatively, some
          DR-DOS disk utilities provide a /# option to generate a
          human-readable time stamp "mmdd-hhmm" build from BCD-encoded
          8-bit values for the month, day, hour and minute instead of
          a serial number.
      - id: partition_volume_label
        size: 11
        type: str
        encoding: ASCII
        pad-right: 0x20
      - id: fs_type_str
        size: 8
        type: str
        encoding: ASCII
        pad-right: 0x20
  ext_bios_param_block_fat32:
    doc: Extended BIOS Parameter Block for FAT32
    seq:
      - id: ls_per_fat
        type: u4
        doc: |
          Logical sectors per file allocation table (corresponds with
          the old entry `ls_per_fat` in the DOS 2.0 BPB).
      - id: has_active_fat
        type: b1
        doc: |
          If true, then there is "active" FAT, which is designated in
          `active_fat` attribute. If false, all FATs are mirrored as
          usual.
      - id: reserved1
        type: b3
      - id: active_fat_id
        type: b4
        doc: |
          Zero-based number of active FAT, if `has_active_fat`
          attribute is true.
      - id: reserved2
        contents: [0]
      - id: fat_version
        type: u2
      - id: root_dir_start_clus
        type: u4
        doc: |
          Cluster number of root directory start, typically 2 if it
          contains no bad sector. (Microsoft's FAT32 implementation
          imposes an artificial limit of 65,535 entries per directory,
          whilst many third-party implementations do not.)
      - id: ls_fs_info
        type: u2
        doc: |
          Logical sector number of FS Information Sector, typically 1,
          i.e., the second of the three FAT32 boot sectors. Values
          like 0 and 0xFFFF are used by some FAT32 implementations to
          designate abscence of FS Information Sector.
      - id: boot_sectors_copy_start_ls
        type: u2
        doc: |
          First logical sector number of a copy of the three FAT32
          boot sectors, typically 6.
      - id: reserved3
        size: 12
      - id: phys_drive_num
        type: u1
        doc: |
          Physical drive number (0x00 for (first) removable media,
          0x80 for (first) fixed disk as per INT 13h).
      - id: reserved4
        type: u1
      - id: ext_boot_sign
        type: u1
        doc: |
          Should be 0x29 to indicate that an EBPB with the following 3
          entries exists.
      - id: volume_id
        size: 4
        doc: |
          Volume ID (serial number).

          Typically the serial number "xxxx-xxxx" is created by a
          16-bit addition of both DX values returned by INT 21h/AH=2Ah
          (get system date) and INT 21h/AH=2Ch (get system time) for
          the high word and another 16-bit addition of both CX values
          for the low word of the serial number. Alternatively, some
          DR-DOS disk utilities provide a /# option to generate a
          human-readable time stamp "mmdd-hhmm" build from BCD-encoded
          8-bit values for the month, day, hour and minute instead of
          a serial number.
      - id: partition_volume_label
        size: 11
        type: str
        encoding: ASCII
        pad-right: 0x20
      - id: fs_type_str
        size: 8
        type: str
        encoding: ASCII
        pad-right: 0x20
  root_directory:
    seq:
      - id: records
        type: root_directory_rec
        repeat: expr
        repeat-expr: _root.boot_sector.bpb.max_root_dir_rec
  root_directory_rec:
    seq:
      - id: file_name
        size: 11
      - id: attribute
        type: u1
      - id: reserved
        size: 10
      - id: time
        type: u2
      - id: date
        type: u2
      - id: start_clus
        type: u2
      - id: file_size
        type: u4