Kjetil's Information Center: A Blog About My Projects

FAT16 File Extraction

This is a program I made as part of another bigger project, where I needed to read files from a FAT16 file system. It implements the bare-minimum to be able to extract a file from the root directory, and is only setup to handle the specifications of FAT16. (FAT12 or FAT32 will not work.) It can be used either on a disk image, or actually on a disk block device directly which does not need to be mounted. Calling the program with only the disk image argument will print the root directory, then add the target filename to dump that file.

Here is the code:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

static void read_sector(FILE *fh, int no, uint8_t sector[])
{
  fseek(fh, no * 512, SEEK_SET);
  fread(sector, sizeof(uint8_t), 512, fh);
}

int main(int argc, char *argv[])
{
  FILE *disk;
  uint8_t sector[512];

  uint32_t vbr_offset;
  uint32_t fat_offset;
  uint32_t root_dir_offset;
  uint32_t data_offset;

  uint8_t partition_type;
  uint8_t sectors_per_cluster;
  uint16_t reserved_sectors;
  uint8_t no_of_fats;
  uint16_t root_entries;
  uint16_t sectors_per_fat;

  char file_name[13]; /* 8.3 format at NULL byte. */
  uint32_t file_size;
  uint16_t file_cluster;

  uint16_t cluster = 0; /* Target cluster for dump. */
  uint32_t cluster_file_size;
  uint16_t cluster_end_indicator;
  uint16_t cluster_fat;
  uint16_t cluster_offset;

  if (argc <= 1) {
    printf("Usage: %s <disk image> [dump file]\n", argv[0]);
    return 1;
  }

  disk = fopen(argv[1], "rb");
  if (disk == NULL) {
    printf("Error: Unable to open: %s\n", argv[1]);
    return 1;
  }

  read_sector(disk, 0, sector);

  partition_type = sector[450];
  vbr_offset = sector[454];

  printf("First partiton type: 0x%02x\n", partition_type);
  printf("First VBR @ 0x%x\n", vbr_offset * 512);

  if (partition_type != 0x04 && partition_type != 0x06) {
    printf("Error: First partition on disk is not FAT16!");
    fclose(disk);
    return 1;
  }

  read_sector(disk, vbr_offset, sector);

  sectors_per_cluster = sector[13];
  reserved_sectors    = sector[14] + (sector[15] << 8);
  no_of_fats          = sector[16];
  root_entries        = sector[17] + (sector[18] << 8);
  sectors_per_fat     = sector[22] + (sector[23] << 8);

  printf("Sectors per cluster: %d\n", sectors_per_cluster);
  printf("Reserved sectors: %d\n", reserved_sectors);
  printf("No of FATs: %d\n", no_of_fats);
  printf("Root entries: %d\n", root_entries);
  printf("Sectors per FAT: %d\n", sectors_per_fat);

  fat_offset = vbr_offset + reserved_sectors;
  root_dir_offset = fat_offset + (sectors_per_fat * no_of_fats);
  data_offset = root_dir_offset + ((root_entries * 32) / 512);

  printf("FAT Region @ 0x%x\n", fat_offset * 512);
  printf("Root Directory Region @ 0x%x\n", root_dir_offset * 512);
  printf("Data Region @ 0x%x\n", data_offset * 512);

  printf("\nRoot Directory:\n");

  for (uint32_t offset = root_dir_offset; offset < data_offset; offset++) {
    read_sector(disk, offset, sector);

    for (int i = 0; i < 512; i += 32) {
      if (sector[i] == '\0') {
        break; /* End of files. */
      }

      if (sector[i+11] & 0x10 || sector[i+11] & 0x08) {
        continue; /* Subdirectory or volume label. */
      }

      int n = 0;
      for (int j = 0; j < 8; j++) {
        if (sector[i+j] == ' ') {
          break;
        }
        file_name[n] = sector[i+j];
        n++;
      }

      file_name[n] = '.';
      n++;

      for (int j = 0; j < 3; j++) {
        if (sector[i+8+j] == ' ') {
          break;
        }
        file_name[n] = sector[i+8+j];
        n++;
      }

      file_name[n] = '\0';

      file_cluster = sector[i+26] + (sector[i+27] << 8);
      file_size = sector[i+28] +
                 (sector[i+29] << 8) +
                 (sector[i+30] << 16) +
                 (sector[i+31] << 24);

      if (argc >= 3) {
        if (strcmp(argv[2], file_name) == 0) {
          cluster = file_cluster;
          cluster_file_size = file_size;
        }
      }
      printf("%12s (%d bytes) (cluster 0x%04x)\n",
        file_name,
        file_size,
        file_cluster);
    }
  }
  printf("\n");

  if (cluster > 0) { /* Dump a file? */
    FILE *out;

    out = fopen(argv[2], "wbx");
    if (out == NULL) {
      printf("Failed to open file for writing: %s\n", argv[2]);
      fclose(disk);
      return 1;
    }

    read_sector(disk, fat_offset, sector);

    cluster_end_indicator = sector[2] + (sector[3] << 8);
    
    printf("Cluster end indicator: 0x%04x\n", cluster_end_indicator);

    unsigned int bytes_written = 0;
    while ((cluster >> 3) != (cluster_end_indicator >> 3)) {
      printf("Read cluster: 0x%04x\n", cluster);

      for (int s = 0; s < sectors_per_cluster; s++) {
        read_sector(disk,
                    data_offset + 
                    ((cluster - 2) * sectors_per_cluster) +
                    s, sector);

        bytes_written += fwrite(sector, sizeof(uint8_t), 
          (bytes_written + 512 > cluster_file_size)
            ? cluster_file_size - bytes_written
            : 512, out);
      }

      /* Calculate next cluster. */
      cluster_fat = cluster / 256;
      cluster_offset = (cluster % 256) * 2;

      read_sector(disk, fat_offset + cluster_fat, sector);
      cluster = sector[cluster_offset] + (sector[cluster_offset + 1] << 8);
    }

    fclose(out);
  }

  fclose(disk);
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 06/11-2021, Article Link