Search code examples
cfat16

What is needed to calculate the byte offset for a file in FAT16?


I have a program and a FAT16 image. Conveniently, the image begins with the boot sector. From there I have extracted the root directory, bytes per sector, and bytes per cluster.

The algorithm for obtaining the byte offset for a subdirectory from the root directory, setting r to the byte offset from image[ 0 ] to the subdirectory entry for a given subfolder named path, is:

    // non-C formatted externally defined values
    image = open_some_fat16_image()
    path = the_name_of_a_directory_whose_parent_is_root()
    LEN_DIR_NAME = 2
    LEN_DIRECTORY_ENTRY = 32
    FREE_ENTRY_MARKER = 0xE5
    END_OF_DIR_MARKER = 0
    OFFSET_DIR_ATTR = 11
    FLAG_DIRECTORY = 0x10
    OFFSET_FIRST_CLUSTER = 26
    current_dir = byte_loc_root;

    unsigned long r = 0;
    for ( int i = 0; i < ( *fat_fs ).root_entry_count && r == 0; i++ )
    {
        // get the name
        char dir_name[ LEN_DIR_NAME +1 ];
        unsigned long byte_loc_dir_name = current_dir + ( i * LEN_DIRECTORY_ENTRY );
        lseek( image, byte_loc_dir_name, SEEK_SET );
        read( image, dir_name, LEN_DIR_NAME );
        dir_name[ LEN_DIR_NAME ] = '\0';

        // is valid entry
        if ( FREE_ENTRY_MARKER == ( unsigned char ) dir_name[ 0 ] ) { continue; }
        if ( END_OF_DIR_MARKER == ( unsigned char ) dir_name[ 0 ] ) { break; }

        // no right whitespace
        for ( int i = 0; i < LEN_DIR_NAME; i ++ )
        {
            if ( ! isspace( dir_name[ i ] ) ) { continue; }
            dir_name[ i ] = '\0';
        }

        // is a match
        if ( 0 != strcmp( dir_name, path ) ) { continue; }

        // is a directory
        unsigned long byte_loc_dir_attr = byte_loc_dir_name + OFFSET_DIR_ATTR;
        lseek( image, byte_loc_dir_attr, SEEK_SET );
        uint8_t attr;
        read( image, &attr, 1 );
        if ( FLAG_DIRECTORY != attr ) { continue; }

        // get cluster
        unsigned long byte_loc_dir_first_cluster = byte_loc_dir_name + OFFSET_FIRST_CLUSTER;
        lseek( image, byte_loc_dir_first_cluster, SEEK_SET );
        uint16_t cluster_idx;
        read( image, & cluster_idx, LEN_FIRST_CLUSTER );

        r = cluster_idx * ( *fat_fs ).sectors_per_cluster * ( *fat_fs ).bytes_per_sector;
    }

I've run this program and have verified that
- sectors_per_cluster ( == 4 ), and
- bytes_per_sector ( == 512 )
match the values in the image ( via hex_editor ). I have also verified that cluster_idx matches what's in the image ( via hex_editor + FAT16 browser ).

The value r = cluster_idx * sectors_per_cluster * bytes_per_sector = 960 * 4 * 512 is set to is: 0x1E0000. Using the FAT16 browser, I was able to find files in the subdirectory argument I gave. With one of those filenames now obtained, I looked for it in the image using a hex_editor. The location of the subdirectory listing was: 0x1ED200.

I'm pretty sure I've obtained all the values correctly, except r. I'm not sure which values to take to obtain the missing 53760 bytes that r is off by. Is there something missing from how r is set?


Solution

  • You should send out a dump of the BPB inside the boot sector. See https://en.wikipedia.org/wiki/BIOS_parameter_block and https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#BPB

    For FAT 16, the disk is typically laid out as:

    boot sector
    fat 1
    fat 2
    root dir
    cluster 2
    cluster 3
    ...
    

    So you'll need to adjust:

    RootDirSectors = <round up (32 bytes * 
                               BPB.RootDirectoryEntries) to be a multiple of BPB.BytesPerSector> / BPB.BytesPerSector
    
    Cluster2SectorNumber = 
        BPB.ReservedSectors 
         + BPB.NumberOfFats * BPB.SectorsPerFat
         + RootDirSectors
    
    ClusterSectorNumber = 
        Cluster2SectorNumber
         + (cluster_idx - 2) * BPB.SectorsPerCluster
    
    ClusterByteNumber =
        ClusterSectorNumber * BPB.BytesPerSector