// DOM-IGNORE-BEGIN
/*******************************************************************************
Copyright 2015 Microchip Technology Inc. (www.microchip.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

To request to license the code under the MLA license (www.microchip.com/mla_license), 
please contact mla_licensing@microchip.com
*******************************************************************************/
//DOM-IGNORE-END

#include "FileSystem_config.h"
//#include "system.h"
#include "fileio_lfn.h"
#include "fileio_private_lfn.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "../SDCardMgr.h"
#include "fileio_driver.h"
#include "sd_spi.h"
#include "fileio.h"

uint32_t ReadRam32bit(uint8_t * pBuffer, uint16_t index);
uint16_t ReadRam16bit (uint8_t * pBuffer, uint16_t index);

/*****************************************************************************/
/*                         Global Variables                                  */
/*****************************************************************************/

FILEIO_DRIVE gDriveArray[FILEIO_CONFIG_MAX_DRIVES];
uint8_t gDriveSlotOpen[FILEIO_CONFIG_MAX_DRIVES];

//FILEIO_TimestampGet timestampGet;

uint16_t lfnBuffer[256];

#if defined (__XC16__) || defined (__XC32__)
    #if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
        uint8_t __attribute__ ((aligned(4)))   gDataBuffer[FILEIO_CONFIG_MEDIA_SECTOR_SIZE];      // The global data sector buffer
        uint8_t __attribute__ ((aligned(4)))   gFATBuffer[FILEIO_CONFIG_MEDIA_SECTOR_SIZE];       // The global FAT sector buffer
        FILEIO_BUFFER_STATUS bufferStatus;                                          // Status of the buffer contents (and buffer ownership)
    #else
        uint8_t __attribute__ ((aligned(4)))   gDataBuffer[FILEIO_CONFIG_MAX_DRIVES][FILEIO_CONFIG_MEDIA_SECTOR_SIZE];     // The global data sector buffer
        uint8_t __attribute__ ((aligned(4)))   gFATBuffer[FILEIO_CONFIG_MAX_DRIVES][FILEIO_CONFIG_MEDIA_SECTOR_SIZE];      // The global FAT sector buffer
        FILEIO_BUFFER_STATUS bufferStatus[FILEIO_CONFIG_MAX_DRIVES];
    #endif
#else
    #if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
        uint8_t gDataBuffer[FILEIO_CONFIG_MEDIA_SECTOR_SIZE];      // The global data sector buffer
        uint8_t gFATBuffer[FILEIO_CONFIG_MEDIA_SECTOR_SIZE];       // The global FAT sector buffer
        FILEIO_BUFFER_STATUS bufferStatus;                                          // Status of the buffer contents (and buffer ownership)
    #else
        uint8_t gDataBuffer[FILEIO_CONFIG_MAX_DRIVES][FILEIO_CONFIG_MEDIA_SECTOR_SIZE];     // The global data sector buffer
        uint8_t gFATBuffer[FILEIO_CONFIG_MAX_DRIVES][FILEIO_CONFIG_MEDIA_SECTOR_SIZE];      // The global FAT sector buffer
        FILEIO_BUFFER_STATUS bufferStatus[FILEIO_CONFIG_MAX_DRIVES];
    #endif
#endif

struct
{
    FILEIO_DIRECTORY currentWorkingDirectory;
} globalParameters;

/************************************************************************************/
/*                               Prototypes                                         */
/************************************************************************************/

//void FILEIO_RegisterTimestampGet (FILEIO_TimestampGet timestampFunction)
//{
//    timestampGet = timestampFunction;
//}

int FILEIO_Initialize (void)
{
    int i;

    for (i = 0; i < FILEIO_CONFIG_MAX_DRIVES; i++)
    {
        gDriveSlotOpen[i] = true;
#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
        gDriveArray[i].dataBuffer = &gDataBuffer[0];
        gDriveArray[i].fatBuffer = &gFATBuffer[0];
        gDriveArray[i].bufferStatusPtr = &bufferStatus;
#else
        gDriveArray[i].dataBuffer = &gDataBuffer[i][0];
        gDriveArray[i].fatBuffer = &gFATBuffer[i][0];
        gDriveArray[i].bufferStatusPtr = &bufferStatus[i];
        bufferStatus[i].flags.dataBufferNeedsWrite = false;
        bufferStatus[i].flags.fatBufferNeedsWrite = false;
        bufferStatus[i].dataBufferCachedSector = 0xFFFFFFFF;
        bufferStatus[i].fatBufferCachedSector = 0xFFFFFFFF;
#endif
    }

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    bufferStatus.driveOwner = NULL;
    bufferStatus.flags.dataBufferNeedsWrite = false;
    bufferStatus.flags.fatBufferNeedsWrite = false;
    bufferStatus.dataBufferCachedSector = 0xFFFFFFFF;
    bufferStatus.fatBufferCachedSector = 0xFFFFFFFF;
#endif
    
    globalParameters.currentWorkingDirectory.drive = 0;
    globalParameters.currentWorkingDirectory.cluster = 0;

    return true;
}

int FILEIO_Reinitialize (void)
{
    return FILEIO_Initialize();
}

bool FILEIO_MediaDetect (const FILEIO_DRIVE_CONFIG * driveConfig, void * mediaParameters)
{
    return FILEIO_DRIVER_MediaDetect(0);
   // return (*driveConfig->funcMediaDetect)(mediaParameters);
}

FILEIO_DRIVE * FILEIO_CharToDrive (uint16_t driveId)
{
    uint8_t i;

    for (i = 0; i < FILEIO_CONFIG_MAX_DRIVES; i++)
    {
        if ((gDriveSlotOpen[i] == false) && (gDriveArray[i].driveId == driveId))
        {
            return &gDriveArray[i];
        }
    }

    return NULL;
}

FILEIO_FILE_SYSTEM_TYPE FILEIO_FileSystemTypeGet (uint16_t driveId)
{
    FILEIO_DRIVE * drive = FILEIO_CharToDrive (driveId);

    if (drive == NULL)
    {
        return FILEIO_FILE_SYSTEM_TYPE_NONE;
    }

    return drive->type;
}

FILEIO_ERROR_TYPE FILEIO_DriveMount (uint16_t driveId, const FILEIO_DRIVE_CONFIG * driveConfig, void * mediaParameters)
{
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_DRIVE * drive;
    uint8_t i;
    FILEIO_MEDIA_INFORMATION * mediaInformation;
    
    drive = FILEIO_CharToDrive(driveId);

    if (drive == NULL)
    {
        for (i = 0; i < FILEIO_CONFIG_MAX_DRIVES; i++)
        {
            if (gDriveSlotOpen[i])
            {
                gDriveSlotOpen[i] = false;
                gDriveArray[i].driveId = driveId;
                gDriveArray[i].driveConfig = driveConfig;
                drive = &gDriveArray[i];
                break;
            }
        }
    }
    
    if (drive == NULL)
    {
        return FILEIO_ERROR_TOO_MANY_DRIVES_OPEN;
    }

    // Reinitialize the drive cache information
    // This will force the library to recache sectors if a drive is unmounted and re-mounted
#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (drive->bufferStatusPtr->driveOwner == drive)
    {
        drive->bufferStatusPtr->driveOwner = NULL;
    }
#else
    bufferStatus[i].flags.dataBufferNeedsWrite = false;
    bufferStatus[i].flags.fatBufferNeedsWrite = false;
    bufferStatus[i].dataBufferCachedSector = 0xFFFFFFFF;
    bufferStatus[i].fatBufferCachedSector = 0xFFFFFFFF;
#endif

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (FILEIO_GetSingleBuffer (drive) != FILEIO_RESULT_SUCCESS)
    {
        return FILEIO_ERROR_WRITE;
    }
#endif

    drive->mediaParameters = mediaParameters;

//    if(driveConfig->funcIOInit != NULL)
//    {
//        (*driveConfig->funcIOInit)(mediaParameters);
//    }
    FILEIO_DRIVER_IOInitialize(drive->driveId);
    

    mediaInformation = FILEIO_DRIVER_MediaInitialize(drive->driveId);//(*driveConfig->funcMediaInit)(mediaParameters);
    if (mediaInformation->errorCode != MEDIA_NO_ERROR)
    {
        error = FILEIO_ERROR_INIT_ERROR;
    }
    else
    {
        if (mediaInformation->validityFlags.bits.sectorSize)
        {
            drive->sectorSize = mediaInformation->sectorSize;
            if (mediaInformation->sectorSize > FILEIO_CONFIG_MEDIA_SECTOR_SIZE)
            {
                error = FILEIO_ERROR_UNSUPPORTED_SECTOR_SIZE;
            }
        }
    }

    // Load the Master Boot Record (partition)
    if (error == FILEIO_ERROR_NONE)
    {
        if ((error = FILEIO_LoadMBR (drive)) == FILEIO_ERROR_NONE)
        {
            // Load the boot sector
            error = FILEIO_LoadBootSector(drive);
        }
    }

    if (error == FILEIO_ERROR_NONE)
    {
        // If this is the first drive we're mounting, set its root as the current working directory
        if (globalParameters.currentWorkingDirectory.drive == 0)
        {
            globalParameters.currentWorkingDirectory.drive = drive;
            globalParameters.currentWorkingDirectory.cluster = drive->firstRootCluster;
        }
    }
    else
    {
        gDriveArray[i].driveId = 0;
        gDriveSlotOpen[i] = true;
    }

    return error;
}

FILEIO_ERROR_TYPE FILEIO_LoadMBR (FILEIO_DRIVE * drive)
{
    FILEIO_MASTER_BOOT_RECORD * ptrMbr;
    uint8_t type;
    bool hasMbr = true;
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_BOOT_SECTOR * ptrBootSector;

     // Get the partition table from the MBR
    //if ( (*drive->driveConfig->funcSectorRead) (drive->mediaParameters, FILEIO_MEDIA_SECTOR_MBR, drive->dataBuffer) != true)
    if(FILEIO_DRIVER_SectorRead(drive->driveId, FILEIO_MEDIA_SECTOR_MBR, drive->dataBuffer) != true)
    {
        error = FILEIO_ERROR_BAD_SECTOR_READ;
    }
    else
    {
        drive->bufferStatusPtr->dataBufferCachedSector = FILEIO_MEDIA_SECTOR_MBR;
        // Check if the card has no MBR
        ptrBootSector = (FILEIO_BOOT_SECTOR *)drive->dataBuffer;

        if ((ptrBootSector->signature0 == FILEIO_FAT_GOOD_SIGN_0) && (ptrBootSector->signature1 == FILEIO_FAT_GOOD_SIGN_1))
        {
         // Technically, the OEM name is not for indication
         // The alternative is to read the CIS from attribute
         // memory.  See the PCMCIA metaformat for more details
/*    #if defined (__XC16__) || defined (__XC32__)
            if ((*(drive->dataBuffer + BSI_FSTYPE) == 'F') && \
                (*(drive->dataBuffer + BSI_FSTYPE + 1) == 'A') && \
                (*(drive->dataBuffer + BSI_FSTYPE + 2) == 'T') && \
                (*(drive->dataBuffer + BSI_FSTYPE + 3) == '1') && \
                (*(drive->dataBuffer + BSI_BOOTSIG) == 0x29))
#else*/
            if ((ptrBootSector->biosParameterBlock.fat16.fileSystemType[0] == 'F') && \
                (ptrBootSector->biosParameterBlock.fat16.fileSystemType[1] == 'A') && \
                (ptrBootSector->biosParameterBlock.fat16.fileSystemType[2] == 'T') && \
                (ptrBootSector->biosParameterBlock.fat16.fileSystemType[3] == '1') && \
                (ptrBootSector->biosParameterBlock.fat16.bootSignature == 0x29))
//    #endif
             {
                drive->firstPartitionSector = 0;
                drive->type = FILEIO_FILE_SYSTEM_TYPE_FAT16;
                hasMbr = false;
             }
             else
             {
#if defined (__XC16__) || defined (__XC32__)
                if ((*(drive->dataBuffer + BSI_FAT32_FSTYPE ) == 'F') && \
                    (*(drive->dataBuffer + BSI_FAT32_FSTYPE + 1 ) == 'A') && \
                    (*(drive->dataBuffer + BSI_FAT32_FSTYPE + 2 ) == 'T') && \
                    (*(drive->dataBuffer + BSI_FAT32_FSTYPE + 3 ) == '3') && \
                    (*(drive->dataBuffer + BSI_FAT32_BOOTSIG) == 0x29))
#else
                if ((ptrBootSector->biosParameterBlock.fat32.fileSystemType[0] == 'F') && \
                    (ptrBootSector->biosParameterBlock.fat32.fileSystemType[1] == 'A') && \
                    (ptrBootSector->biosParameterBlock.fat32.fileSystemType[2] == 'T') && \
                    (ptrBootSector->biosParameterBlock.fat32.fileSystemType[3] == '3') && \
                    (ptrBootSector->biosParameterBlock.fat32.bootSignature == 0x29))
#endif
                {
                    drive->firstPartitionSector = 0;
                    drive->type = FILEIO_FILE_SYSTEM_TYPE_FAT32;
                    hasMbr = false;
                }
            }
        }

        // If the first sector isn't a boot sector, parse the actual MBR
        if (hasMbr)
        {
            // assign it the partition table structure
            ptrMbr = (FILEIO_MASTER_BOOT_RECORD *)drive->dataBuffer;

            // Ensure its good
            if((ptrMbr->signature0 != FILEIO_FAT_GOOD_SIGN_0) || (ptrMbr->signature1 != FILEIO_FAT_GOOD_SIGN_1))
            {
                error = FILEIO_ERROR_BAD_PARTITION;
            }
            else
            {
                uint8_t j;
                FILEIO_MBR_PARTITION_TABLE_ENTRY * ptrEntry = &ptrMbr->partition0;

                drive->type = FILEIO_FILE_SYSTEM_TYPE_NONE;
                for(j = 0; j < 4; j++)
                {
                    /*    Valid Master Boot Record Loaded   */

                    // Get the 32 bit offset to the first partition
                    drive->firstPartitionSector = ptrEntry->lbaFirstSector;

                    // check if the partition type is acceptable
                    type = ptrEntry->fileSystemDescriptor;

                    switch (type)
                    {
                        case 0x01:
                            drive->type = FILEIO_FILE_SYSTEM_TYPE_FAT12;
                            break;
                        case 0x04:
                        case 0x06:
                        case 0x0E:
                            drive->type = FILEIO_FILE_SYSTEM_TYPE_FAT16;
                            break;
                        case 0x0B:
                        case 0x0C:
                                drive->type = FILEIO_FILE_SYSTEM_TYPE_FAT32;    // FAT32 is supported too
                                break;
                    } // switch

                    if (drive->type != FILEIO_FILE_SYSTEM_TYPE_NONE)
                    {
                        break;
                    }

                    /* If we are here, we didn't find a matching partition.  We
                       should increment to the next partition table entry */
                    ptrEntry++;
                }

                if (drive->type == FILEIO_FILE_SYSTEM_TYPE_NONE)
                {
                    error = FILEIO_ERROR_UNSUPPORTED_FS;
                }
            }
        }
    }

    return error;
}

FILEIO_ERROR_TYPE FILEIO_LoadBootSector (FILEIO_DRIVE * drive)
{
    FILEIO_BOOT_SECTOR * ptrBootSector;
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    uint32_t rootDirectorySectors;
    uint32_t totalSectors;
    uint32_t dataSectors;
    uint16_t bytesPerSector;
    uint16_t reservedSectorCount;
    bool triedSpecifiedBackupBootSec = false;
    bool triedBackupBootSecAtAddress6 = false;

    // Get the Boot sector
    //if ( (*drive->driveConfig->funcSectorRead) (drive->mediaParameters, drive->firstPartitionSector, drive->dataBuffer) != true)
    if (FILEIO_DRIVER_SectorRead(drive->driveId, drive->firstPartitionSector, drive->dataBuffer) != true)
    {
        error = FILEIO_ERROR_BAD_SECTOR_READ;
    }
    else
    {
        drive->bufferStatusPtr->dataBufferCachedSector = drive->firstPartitionSector;
        ptrBootSector = (FILEIO_BOOT_SECTOR *)drive->dataBuffer;

        do      //test each possible boot sector (FAT32 can have backup boot sectors)
        {

            //Verify the Boot Sector has a valid signature
            if((ptrBootSector->signature0 != FILEIO_FAT_GOOD_SIGN_0) || (ptrBootSector->signature1 != FILEIO_FAT_GOOD_SIGN_1))
            {
                error = FILEIO_ERROR_NOT_FORMATTED;
            }
            else
            {
                do      //loop just to allow a break to jump out of this section of code
                {
                    #ifdef __XC8__
                    // Load count of sectors per cluster
                    drive->sectorsPerCluster = ptrBootSector->biosParameterBlock.fat16.sectorsPerCluster;
                    // Load the sector number of the first FAT sector
                    drive->firstFatSector = drive->firstPartitionSector + ptrBootSector->biosParameterBlock.fat16.reservedSectorCount;
                    // Load the count of FAT tables
                    drive->fatCopyCount = ptrBootSector->biosParameterBlock.fat16.fatCount;
                    // Load the size of the FATs
                    drive->fatSectorCount = ptrBootSector->biosParameterBlock.fat16.sectorsPerFat;
                    if(drive->fatSectorCount == 0)
                    {
                        drive->fatSectorCount  = ptrBootSector->biosParameterBlock.fat32.sectorsPerFat32;
                    }
                    // Calculate the location of the root sector (for FAT12/16)
                    drive->firstRootSector = drive->firstFatSector + (uint32_t)(drive->fatCopyCount * (uint32_t)drive->fatSectorCount);
                    // Determine the max size of the root (will be 0 for FAT32)
                    drive->rootDirectoryEntryCount = ptrBootSector->biosParameterBlock.fat16.rootDirectoryEntryCount;

                    // Determine the total number of sectors in the partition
                    if(ptrBootSector->biosParameterBlock.fat16.totalSectors16 != 0)
                    {
                        totalSectors = ptrBootSector->biosParameterBlock.fat16.totalSectors16;
                    }
                    else
                    {
                        totalSectors = ptrBootSector->biosParameterBlock.fat16.totalSectors32;
                    }

                    // Calculate the number of bytes in each sector
                    bytesPerSector = ptrBootSector->biosParameterBlock.fat16.bytesPerSector;
                    if( (bytesPerSector == 0) || ((bytesPerSector & 1) == 1) )
                    {
                        error = FILEIO_ERROR_UNSUPPORTED_SECTOR_SIZE;
                        break;  //break out of the do while loop
                    }

                    // Calculate the number of sectors in the root (will be 0 for FAT32)
                    rootDirectorySectors = ((ptrBootSector->biosParameterBlock.fat16.rootDirectoryEntryCount * 32) + (ptrBootSector->biosParameterBlock.fat16.bytesPerSector - 1)) / ptrBootSector->biosParameterBlock.fat16.bytesPerSector;
                    // Calculate the number of data sectors on the card
                    dataSectors = totalSectors - (drive->firstRootSector + rootDirectorySectors);
                    // Calculate the maximum number of clusters on the card
                    drive->partitionClusterCount = dataSectors / drive->sectorsPerCluster;

                    #else // PIC24/30/33

                    // Read the count of reserved sectors
                    reservedSectorCount = ReadRam16bit(drive->dataBuffer, BSI_RESRVSEC);
                    // Load the count of sectors per cluster
                    drive->sectorsPerCluster = *(drive->dataBuffer + BSI_SPC);
                    // Load the sector number of the first FAT sector
                    drive->firstFatSector = drive->firstPartitionSector + reservedSectorCount;
                    // Load the count of FAT tables
                    drive->fatCopyCount = *(drive->dataBuffer + BSI_FATCOUNT);
                    // Load the size of the FATs
                    drive->fatSectorCount = ReadRam16bit (drive->dataBuffer, BSI_SPF);
                    if(drive->fatSectorCount == 0)
                    {
                        drive->fatSectorCount = ReadRam32bit (drive->dataBuffer, BSI_FATSZ32);
                    }
                    // Calculate the location of the root sector (for FAT12/16)
                    drive->firstRootSector = drive->firstFatSector + (uint32_t)(drive->fatCopyCount * (uint32_t)drive->fatSectorCount);
                    // Determine the max size of the root (will be 0 for FAT32)
                    drive->rootDirectoryEntryCount = ReadRam16bit (drive->dataBuffer, BSI_ROOTDIRENTS);

                    // Determine the total number of sectors in the partition
                    totalSectors = ReadRam16bit (drive->dataBuffer, BSI_TOTSEC16);
                    if(totalSectors == 0 )
                    {
                        totalSectors = ReadRam32bit (drive->dataBuffer, BSI_TOTSEC32);
                    }

                    // Calculate the number of uint8_ts in each sector
                    bytesPerSector = ReadRam16bit (drive->dataBuffer, BSI_BPS);
                    if((bytesPerSector == 0) || ((bytesPerSector & 1) == 1))
                    {
                        error = FILEIO_ERROR_UNSUPPORTED_SECTOR_SIZE;
                        break;
                    }

                    // Calculate the number of sectors in the root (will be 0 for FAT32)
                    rootDirectorySectors = ((drive->rootDirectoryEntryCount * FILEIO_DIRECTORY_ENTRY_SIZE) + (bytesPerSector - 1)) / bytesPerSector;
                    // Calculate the number of data sectors on the card
                    dataSectors = totalSectors - (reservedSectorCount + (drive->fatCopyCount * drive->fatSectorCount)  + rootDirectorySectors);
                    // Calculate the maximum number of clusters on the card
                    drive->partitionClusterCount = dataSectors / drive->sectorsPerCluster;

                    #endif

                    // Determine the file system type based on the number of clusters used
                    if(drive->partitionClusterCount < 4085)
                    {
                        drive->type = FILEIO_FILE_SYSTEM_TYPE_FAT12;
                    }
                    else
                    {
                        if(drive->partitionClusterCount < 65525)
                        {
                            drive->type = FILEIO_FILE_SYSTEM_TYPE_FAT16;
                        }
                        else
                        {
                            drive->type = FILEIO_FILE_SYSTEM_TYPE_FAT32;
                        }
                    }

                    if (drive->type == FILEIO_FILE_SYSTEM_TYPE_FAT32)
                    {
                        #ifdef __XC8__
                            drive->firstRootCluster = ptrBootSector->biosParameterBlock.fat32.firstClusterRootDirectory;
                        #else
                            drive->firstRootCluster = ReadRam32bit (drive->dataBuffer, BSI_ROOTCLUS);
                        #endif
                        drive->firstDataSector = drive->firstRootSector + rootDirectorySectors;
                    }
                    else
                    {
                        drive->firstRootCluster = 0;
                        drive->firstDataSector = drive->firstRootSector + (drive->rootDirectoryEntryCount >> 4);
                    }

                    if(bytesPerSector > FILEIO_CONFIG_MEDIA_SECTOR_SIZE)
                    {
                        error = FILEIO_ERROR_UNSUPPORTED_SECTOR_SIZE;
                    }

                }while(0);  // do/while loop designed to allow to break out if
                            //   there is an error detected without returning
                            //   from the function.

            }

            if ((drive->type == FILEIO_FILE_SYSTEM_TYPE_FAT32) || ((error != FILEIO_ERROR_NONE) &&
                ((ptrBootSector->biosParameterBlock.fat32.bootSignature == 0x29) ||
                (ptrBootSector->biosParameterBlock.fat32.bootSignature == 0x28))))
            {
                //Check for possible errors in the formatting
                if((ptrBootSector->biosParameterBlock.fat32.totalSectors16 != 0)
                    || ((ptrBootSector->biosParameterBlock.fat32.bootSignature != 0x29) &&
                        (ptrBootSector->biosParameterBlock.fat32.bootSignature != 0x28))
                  )
                {
                    error = FILEIO_ERROR_NOT_FORMATTED;
                }

                //If there were formatting errors then in FAT32 we can try to use
                //  the backup boot sector
                if((error != FILEIO_ERROR_NONE) && (triedSpecifiedBackupBootSec == false))
                {
                    triedSpecifiedBackupBootSec = true;

//                    if ((*drive->driveConfig->funcSectorRead) (drive->mediaParameters, drive->firstPartitionSector + ptrBootSector->biosParameterBlock.fat32.backupBootSector, drive->dataBuffer) != true)
                    if (FILEIO_DRIVER_SectorRead(drive->driveId, drive->firstPartitionSector + ptrBootSector->biosParameterBlock.fat32.backupBootSector, drive->dataBuffer) != true)
                    {
                        error = FILEIO_ERROR_BAD_SECTOR_READ;
                        break;
                    }
                    else
                    {
                        error = FILEIO_ERROR_NONE;
                        continue;
                    }
                }

                if((error != FILEIO_ERROR_NONE) && (triedBackupBootSecAtAddress6 == false))
                {
                    triedBackupBootSecAtAddress6 = true;

                    //Here we are using the magic number 6 because the FAT32 specification
                    //  recommends that "No value other than 6 is recommended."  We've
                    //  already tried using the value specified in the BPB_BkBootSec
                    //  field and it must have failed
//                    if ((*drive->driveConfig->funcSectorRead) (drive->mediaParameters, drive->firstPartitionSector + 6, drive->dataBuffer) != true)
                    if (FILEIO_DRIVER_SectorRead(drive->driveId, drive->firstPartitionSector + 6, drive->dataBuffer) != true)
                    {
                        error = FILEIO_ERROR_BAD_SECTOR_READ;
                        break;
                    }
                    else
                    {
                        error = FILEIO_ERROR_NONE;
                        continue;
                    }
                }

            }   //type == FAT32
            break;
        }
        while(1);
    }

    return error;
}

int FILEIO_DriveUnmount (const uint16_t driveId)
{
    FILEIO_DRIVE * drive;
    uint8_t i;

    drive = FILEIO_CharToDrive (driveId);

    if (drive == NULL)
    {
        return FILEIO_RESULT_FAILURE;
    }
    else
    {
#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    #if !defined (FILEIO_CONFIG_WRITE_DISABLE)
        if (drive->bufferStatusPtr->driveOwner == drive)
        {
            FILEIO_FlushBuffer (drive, FILEIO_BUFFER_FAT);
            FILEIO_FlushBuffer (drive, FILEIO_BUFFER_DATA);
            drive->bufferStatusPtr->driveOwner = NULL;
        }
    #endif
#else
    #if !defined (FILEIO_CONFIG_WRITE_DISABLE)
        FILEIO_FlushBuffer (drive, FILEIO_BUFFER_FAT);
        FILEIO_FlushBuffer (drive, FILEIO_BUFFER_DATA);
    #endif
#endif
    }

    FILEIO_DRIVER_MediaDeinitialize(driveId);
//    drive->driveConfig->funcMediaDeinit(drive->mediaParameters);

    for (i = 0; i < FILEIO_CONFIG_MAX_DRIVES; i++)
    {
        if (gDriveArray[i].driveId == driveId)
        {
            gDriveSlotOpen[i] = true;
            gDriveArray[i].mount = false;
            gDriveArray[i].driveId = 0;
            break;
        }
    }


    if (globalParameters.currentWorkingDirectory.drive == drive)
    {
        globalParameters.currentWorkingDirectory.cluster = 0;
        globalParameters.currentWorkingDirectory.drive = NULL;
    }

    return FILEIO_RESULT_SUCCESS;
}

const uint16_t gShortFileNameCharacters[17] =
{
    '!', '#', '$', '%', '&', '\'', '(', ')', '-', '@', '^', '_', '`', '{', '}', '~', ' '
};

const uint16_t gLongFileNameCharacters[9] =
{
    '\\', '/', ':', '*', '?', '"', '<', '>', '|'
};


uint8_t FILEIO_FileNameTypeGet(const uint16_t * fileName, bool partialStringSearch)
{
    uint16_t c;
    uint8_t i;
    FILEIO_NAME_TYPE type = FILEIO_NAME_SHORT;
    bool foundRadix = false;
    uint8_t count = 0;

    if (*fileName == '.')
    {
        c = *(fileName + 1);
        if ((c == 0) || (c == FILEIO_CONFIG_DELIMITER))
        {
            return FILEIO_NAME_DOT;
        }
        if (c == '.')
        {
            c = *(fileName + 2);
            if ((c == 0) || (c == FILEIO_CONFIG_DELIMITER))
            {
                return FILEIO_NAME_DOT;
            }
        }
    }

    // Iterate though the whole file name
    while (((c = (uint8_t)*fileName++) != 0) && (c != FILEIO_CONFIG_DELIMITER))
    {
        if (type == FILEIO_NAME_SHORT)
        {
            count++;
            if (!(((c >= 'A') && (c <= 'Z')) ||
                ((c >= '0') && (c <= '9')) ||
                (c >= 128)))
            {
                if (c == '.')
                {
                    if (!foundRadix)
                    {
                        foundRadix = true;
                        count = 0;
                    }
                    else
                    {
                        type = FILEIO_NAME_LONG;
                    }
                }
                else
                {
                    // C could still be a special character
                    for (i = 0; i < sizeof (gShortFileNameCharacters) / 2; i++)
                    {
                        if (c == gShortFileNameCharacters[i])
                        {
                            break;
                        }
                    }
                    if (i == sizeof (gShortFileNameCharacters) / 2)
                    {
                        if (!partialStringSearch || ((c != '?') && (c != '*')))
                        {
                            // Switch the type to long
                            type = FILEIO_NAME_LONG;
                        }
                    }
                }
            }
            if (foundRadix)
            {
                if (count > 3)
                {
                    type = FILEIO_NAME_LONG;
                }
            }
            else
            {
                if (count > 8)
                {
                    type = FILEIO_NAME_LONG;
                }
            }
        }
        if (type == FILEIO_NAME_LONG)
        {
            for (i = 0; i < sizeof (gLongFileNameCharacters) / 2; i++)
            {
                if (c == gLongFileNameCharacters[i])
                {
                    if (!partialStringSearch || ((c != '?') && (c != '*')))
                    {
                        type = FILEIO_NAME_INVALID;
                        return type;
                    }
                }
            }
        }
    }
    return type;
}

void FILEIO_FormatShortFileName (const uint16_t * fileName, FILEIO_OBJECT * filePtr)
{
    uint16_t c;
    uint8_t i = 0;

    while (((c = *fileName++) != '.') && (c != 0) && (c != FILEIO_CONFIG_DELIMITER))
    {
        filePtr->name[i++] = c;
    }

    while (i < 8)
    {
        filePtr->name[i++] = 0x20;
    }

    if (c == '.')
    {
        while ((c = *fileName++) != 0)
        {
            filePtr->name[i++] = c;
        }
    }

    while (i < 11)
    {
        filePtr->name[i++] = 0x20;
    }

    filePtr->lfnPtr = NULL;
}


int FILEIO_Open (FILEIO_OBJECT * filePtr, const uint16_t * fileName, uint16_t mode)
{
    FILEIO_ERROR_TYPE error;
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
    uint16_t entryHandle;
#endif
    FILEIO_DIRECTORY directory;
    uint8_t fileNameType;
    uint32_t currentCluster;
    uint16_t currentClusterOffset = 0;

    fileName = FILEIO_CacheDirectory (&directory, (uint16_t *)fileName, false);

    if (fileName == NULL)
    {
        return FILEIO_RESULT_FAILURE;
    }
    
    currentCluster = directory.cluster;

#if defined (FILEIO_CONFIG_WRITE_DISABLE)
    if (mode & (FILEIO_OPEN_WRITE | FILEIO_OPEN_CREATE | FILEIO_OPEN_TRUNCATE))
    {
        directory.drive->error = FILEIO_ERROR_WRITE_PROTECTED;
        return FILEIO_RESULT_FAILURE;
    }
#endif

    //if((*directory.drive->driveConfig->funcWriteProtectGet)(directory.drive->mediaParameters) && ((mode & (FILEIO_OPEN_WRITE | FILEIO_OPEN_CREATE | FILEIO_OPEN_TRUNCATE)) != 0))
    if((FILEIO_DRIVER_WriteProtectStateGet(directory.drive->driveId) && ((mode & (FILEIO_OPEN_WRITE | FILEIO_OPEN_CREATE | FILEIO_OPEN_TRUNCATE)) != 0)))
    {
        directory.drive->error = FILEIO_ERROR_WRITE_PROTECTED;
        return FILEIO_RESULT_FAILURE;
    }

    // Check to ensure that a file object was allocated
    if (filePtr == NULL)
    {
        directory.drive->error = FILEIO_ERROR_TOO_MANY_FILES_OPEN;
        return FILEIO_RESULT_FAILURE;
    }

    fileNameType = FILEIO_FileNameTypeGet(fileName, false);

    if (fileNameType == FILEIO_NAME_SHORT)
    {
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        // Short file name
        FILEIO_FormatShortFileName (fileName, filePtr);
        // Search in 'directory' for an entry matching filePtr->name, starting at entry 0 in directory->cluster and returning the result in filePtr
        error = FILEIO_FindShortFileName (&directory, filePtr, (uint8_t *)filePtr->name, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);
    }
    else if (fileNameType == FILEIO_NAME_LONG)
    {
        // Long file name
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        // Search in 'directory' for an entry matching fileName, starting at entry 0 in directory->cluster and returning the short file name in filePtr.
        // The long file name will be cached in lfnData
        filePtr->lfnPtr = (uint16_t *)fileName;
        filePtr->lfnLen = FILEIO_strlen16 ((uint16_t *)fileName);
        error = FILEIO_FindLongFileName (&directory, filePtr, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);
    }
    else
    {
        directory.drive->error = FILEIO_ERROR_INVALID_FILENAME;
        return FILEIO_RESULT_FAILURE;
    }

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
    if (error == FILEIO_ERROR_NONE)
    {
        // File was found
        if (((mode & FILEIO_OPEN_TRUNCATE) == FILEIO_OPEN_TRUNCATE) || !FILEIO_IsClusterAllocated(&directory, filePtr))
        {
            entryHandle = filePtr->entry;
            error = FILEIO_EraseFile (filePtr, &entryHandle, true);
            mode |= FILEIO_OPEN_CREATE;
        }
        else
        {
            // Use the CREATE flag to indicate whether the file should be created
            mode &= ~FILEIO_OPEN_CREATE;
        }
    }
    else
    {
        // We couldn't find the file
        if (error == FILEIO_ERROR_DONE)
        {
            // File wasn't found
            if ((mode & FILEIO_OPEN_CREATE) == FILEIO_OPEN_CREATE)
            {
                error = FILEIO_ERROR_NONE;
                filePtr->disk = directory.drive;
                filePtr->baseClusterDir = directory.cluster;
                filePtr->currentClusterDir = directory.cluster;
            }
        }
    }

    if (error == FILEIO_ERROR_NONE)
    {
        if ((mode & FILEIO_OPEN_CREATE) == FILEIO_OPEN_CREATE)
        {
            // Try to create the file (or replace it, if the file was truncated
            entryHandle = 0;

            error = FILEIO_DirectoryEntryCreate (filePtr, &entryHandle, 0, true);

            mode |= FILEIO_OPEN_APPEND;
        }
    }
#endif

    if (error == FILEIO_ERROR_NONE)
    {
        if ((mode & FILEIO_OPEN_READ) == FILEIO_OPEN_READ)
        {
            filePtr->flags.readEnabled = true;
        }
        else
        {
            filePtr->flags.readEnabled = false;
        }

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
        if ((mode & FILEIO_OPEN_WRITE) == FILEIO_OPEN_WRITE)
        {
            filePtr->flags.writeEnabled = true;
        }
        else
        {
            filePtr->flags.writeEnabled = false;
        }
#endif

        if ((mode & FILEIO_OPEN_APPEND) == FILEIO_OPEN_APPEND)
        {
            int result = FILEIO_Seek (filePtr, 0, FILEIO_SEEK_END);
            if (result != FILEIO_RESULT_SUCCESS)
            {
                error = FILEIO_ERROR_SEEK_ERROR;
            }
            else
            {
                mode &= ~FILEIO_OPEN_APPEND;
            }
        }
    }

    // Check to ensure no errors occured
    if (error != FILEIO_ERROR_NONE)
    {
        directory.drive->error = error;
        return FILEIO_RESULT_FAILURE;
    }

    return FILEIO_RESULT_SUCCESS;
}

bool FILEIO_IsClusterAllocated(FILEIO_DIRECTORY * directory, FILEIO_OBJECT * filePtr)
{
    FILEIO_ERROR_TYPE error;
    uint32_t currentCluster = filePtr->baseClusterDir;
    uint16_t currentClusterOffset = 0;
    FILEIO_DIRECTORY_ENTRY * entry;

    if (currentCluster == 0)
    {
        currentCluster = directory->drive->firstRootCluster;
    }

    entry = FILEIO_DirectoryEntryCache (directory, &error, &currentCluster, &currentClusterOffset, filePtr->entry);

    if (entry == NULL)
    {
        return false;
    }

    if ((entry->firstClusterHigh == 0) && (entry->firstClusterLow == 0))
    {
        return false;
    }

    return true;
}

uint32_t FILEIO_FullClusterNumberGet (FILEIO_DIRECTORY_ENTRY * entry)
{
    uint32_t result;

    result = entry->firstClusterHigh;
    result <<= 16;
    result |= entry->firstClusterLow;

    return result;
}

uint16_t * FILEIO_CacheDirectory (FILEIO_DIRECTORY * dir, uint16_t * path, bool createDirectories)
{
    uint16_t pathLen;
#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
    uint16_t i;
#endif

    pathLen = FILEIO_strlen16 ((uint16_t *)path);

    if (pathLen == 0)
    {
        return NULL;
    }
    
    memcpy (dir, &globalParameters.currentWorkingDirectory, sizeof (FILEIO_DIRECTORY));

#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
    if (pathLen == 1)
    {
#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
        if (FILEIO_GetSingleBuffer (dir->drive) != FILEIO_RESULT_SUCCESS)
        {
            return NULL;
        }
#endif
        return path;
    }

    // Check to see if the user explicitly specified the drive
    if (*(path + 1) == ':')
    {
        // They did
        dir->drive = FILEIO_CharToDrive (*path);
        if (dir->drive == NULL)
        {
            return NULL;
        }

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
        if (FILEIO_GetSingleBuffer (dir->drive) != FILEIO_RESULT_SUCCESS)
        {
            return NULL;
        }
#endif

        dir->cluster = dir->drive->firstRootCluster;

        // Increment past the drive specifier
        path += 2;
        pathLen -= 2;
        if (pathLen == 0)
        {
            return path;
        }

        if (*path == FILEIO_CONFIG_DELIMITER)
        {
            path++;
            pathLen--;
            if (pathLen == 0)
            {
                return path;
            }
        }
    }

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (FILEIO_GetSingleBuffer (dir->drive) != FILEIO_RESULT_SUCCESS)
    {
        return NULL;
    }
#endif

    // Find the next forward slash (indicates part of the path is a directory)
    while ((i = FILEIO_FindNextDelimiter(path)) != -1)
    {
        // If someone terminated a directory path with a delimiter, break out of the loop
        if (*(path + i) == FILEIO_CONFIG_DELIMITER)
        {
            if (*(path + i + 1) == 0)
            {
                break;
            }
        }
        
        // If i == 0, someone put two delimiters in a row or something strange; don't change directory
        if (i != 0)
        {
            // Try to change dir to that directory
            if (FILEIO_DirectoryChangeSingle (dir, path) != FILEIO_RESULT_SUCCESS)
            {
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
                if (createDirectories)
                {
                    // If we couldn't change to that directory, it probably doesn't exist.  Try to create it.
                    if (FILEIO_DirectoryMakeSingle (dir, path) != FILEIO_RESULT_SUCCESS)
                    {
                        return NULL;
                    }
                    // Now that it exists, try to change to it.
                    if (FILEIO_DirectoryChangeSingle (dir, path) != FILEIO_RESULT_SUCCESS)
                    {
                        return NULL;
                    }
                }
                else
#endif
                {
                    return NULL;
                }
            }
        }

        // Increment i to include the delimiter
        i++;
        // Increment the path
        path += i;
        // Decrement the path length
        pathLen -= i;
    }
#endif

    // Whatever is left must be our file name.  dir will contain the drive and cluster of the directory.
    return path;
}

#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
uint16_t FILEIO_FindNextDelimiter(const uint16_t * path)
{
    uint16_t i = 0;
    uint16_t c;

    while (((c = *path++) != 0) && (c != FILEIO_CONFIG_DELIMITER))
    {
        i++;
    }

    if (c == 0)
    {
        return -1;
    }
    else
    {
        return i;
    }
}
#endif

#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
FILEIO_RESULT FILEIO_DirectoryChangeSingle (FILEIO_DIRECTORY * directory, uint16_t * path)
{
    FILEIO_ERROR_TYPE error;
    uint8_t fileNameType;
    uint32_t currentCluster;
    uint16_t currentClusterOffset;
    FILEIO_OBJECT file;
    FILEIO_OBJECT * filePtr = &file;

    fileNameType = FILEIO_FileNameTypeGet(path, false);

    if (fileNameType == FILEIO_NAME_INVALID)
    {
		directory->drive->error = FILEIO_ERROR_INVALID_FILENAME;
        return FILEIO_RESULT_FAILURE;
    }
    else if (fileNameType == FILEIO_NAME_SHORT)
    {
        currentCluster = directory->cluster;
        currentClusterOffset = 0;
        // Short file name
        FILEIO_FormatShortFileName (path, filePtr);
        // Search in 'directory' for an entry matching filePtr->name, starting at entry 0 in directory->cluster and returning the result in filePtr
        error = FILEIO_FindShortFileName (directory, filePtr, (uint8_t *)filePtr->name, &currentCluster, &currentClusterOffset, 0, FILEIO_ATTRIBUTE_MASK, FILEIO_SEARCH_ENTRY_MATCH);
    }
    else if (fileNameType == FILEIO_NAME_DOT)
    {
        // If someone specified a '.' filename, we don't have to change anything
        if (*(path + 1) != '.')
        {
            return FILEIO_RESULT_SUCCESS;
        }

        // If they specified a dotdot filename, cache the previous directory's cluster
        {
            FILEIO_DIRECTORY_ENTRY * entry;

            currentCluster = directory->cluster;
            currentClusterOffset = 0;

            // Cache the .. entry
            entry = FILEIO_DirectoryEntryCache (directory, &error, &currentCluster, &currentClusterOffset, 1);
            if (error == FILEIO_ERROR_NONE)
            {
                directory->cluster = FILEIO_FullClusterNumberGet (entry);
                return FILEIO_RESULT_SUCCESS;
            }
            else
            {
                return FILEIO_RESULT_FAILURE;
            }
        }
    }
    else
    {
        // Long file name
        currentCluster = directory->cluster;
        currentClusterOffset = 0;
        // Search in 'directory' for an entry matching fileName, starting at entry 0 in directory->cluster and returning the short file name in filePtr.
        // The long file name will be cached in lfnData
        filePtr->lfnPtr = path;
        filePtr->lfnLen = FILEIO_lfnlen(path);
        error = FILEIO_FindLongFileName (directory, filePtr, &currentCluster, &currentClusterOffset, 0, FILEIO_ATTRIBUTE_MASK, FILEIO_SEARCH_ENTRY_MATCH);
    }

    if (error == FILEIO_ERROR_NONE)
    {
        // Directory found
        directory->cluster = filePtr->firstCluster;
    }
    else
    {
        return FILEIO_RESULT_FAILURE;
    }

    return FILEIO_RESULT_SUCCESS;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
FILEIO_RESULT FILEIO_DirectoryMakeSingle (FILEIO_DIRECTORY * directory, uint16_t * path)
{
    FILEIO_ERROR_TYPE error;
    FILEIO_OBJECT file;
    FILEIO_OBJECT * filePtr = &file;
    FILEIO_DIRECTORY_ENTRY * entry;
    FILEIO_TIMESTAMP timeStamp;
    uint16_t entryOffset = 0;
    uint32_t dot, dotdot;
    uint32_t currentCluster;
    uint16_t currentClusterOffset = 0;
    uint8_t fileNameType;

    file.baseClusterDir = directory->cluster;
    if (file.baseClusterDir == 0)
    {
        file.baseClusterDir = directory->drive->firstRootCluster;
    }
    file.disk = directory->drive;

    fileNameType = FILEIO_FileNameTypeGet(path, false);

    if ((fileNameType == FILEIO_NAME_INVALID) || (fileNameType == FILEIO_NAME_DOT))
    {
        directory->drive->error = FILEIO_ERROR_INVALID_FILENAME;
        return FILEIO_RESULT_FAILURE;
    }
    else if (fileNameType == FILEIO_NAME_SHORT)
    {
        // Short file name
        FILEIO_FormatShortFileName (path, filePtr);
    }
    else
    {
        filePtr->lfnPtr = (uint16_t *)path;
        filePtr->lfnLen = FILEIO_lfnlen ((uint16_t *)path);
    }

    if (FILEIO_DirectoryEntryCreate (filePtr, &entryOffset, FILEIO_ATTRIBUTE_DIRECTORY, true) != FILEIO_ERROR_NONE)
    {
        return FILEIO_RESULT_FAILURE;
    }

    if (file.baseClusterDir == ((FILEIO_DRIVE *)file.disk)->firstRootCluster)
    {
        dotdot = 0;
    }
    else
    {
        dotdot = file.baseClusterDir;
    }

    file.currentClusterDir = file.baseClusterDir;
    directory->cluster = file.baseClusterDir;
    currentCluster = file.baseClusterDir;

    entry = FILEIO_DirectoryEntryCache (directory, &error, &currentCluster, &currentClusterOffset, entryOffset);

    if (entry == NULL)
    {
        directory->drive->error = error;
        return FILEIO_RESULT_FAILURE;
    }

    timeStamp.timeMs = entry->createTimeMs;
    timeStamp.time.value = entry->createTime;
    timeStamp.date.value = entry->createDate;

    dot = FILEIO_FullClusterNumberGet (entry);

    directory->cluster = file.firstCluster;
    currentCluster = file.firstCluster;
    currentClusterOffset = 0;
    entryOffset = 0;

    entry = FILEIO_DirectoryEntryCache (directory, &error, &currentCluster, &currentClusterOffset, entryOffset);

    directory->cluster = file.baseClusterDir;
    
    if (FILEIO_DotEntryWrite(directory->drive, dot, dotdot, &timeStamp))
    {
        return FILEIO_RESULT_SUCCESS;
    }
    else
    {
        return FILEIO_RESULT_FAILURE;
    }
}
#endif
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
int FILEIO_DotEntryWrite (FILEIO_DRIVE * drive, uint32_t dot, uint32_t dotdot, FILEIO_TIMESTAMP * timeStamp)
{
    FILEIO_DIRECTORY_ENTRY * entryPtr;
    uint32_t sector;

	memset(drive->dataBuffer, 0x00, drive->sectorSize);

    entryPtr = (FILEIO_DIRECTORY_ENTRY *)drive->dataBuffer;

    memset (drive->dataBuffer, 0x20, FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX);
    entryPtr->name[0] = '.';
    entryPtr->attributes = FILEIO_ATTRIBUTE_DIRECTORY;
    entryPtr->reserved0 = 0x00;
    entryPtr->firstClusterLow = (uint16_t)(dot & 0x0000FFFF); // Lower 16 bit address
    entryPtr->firstClusterHigh = (uint16_t)((dot & 0x0FFF0000)>> 16); // Higher 16 bit address. FAT32 uses only 28 bits. Mask even higher nibble also.

    entryPtr->fileSize = 0x00;

    // Times need to be the same as the times in the directory entry

    entryPtr->createTimeMs = timeStamp->timeMs;         // millisecond stamp
    entryPtr->createTime = timeStamp->time.value;      // time created //
    entryPtr->createDate = timeStamp->time.value;      // date created (1/1/2004)
    entryPtr->accessDate =   0x0000;         // Last Access date
    entryPtr->writeTime =      0x0000;         // last update time
    entryPtr->writeDate =      0x0000;         // last update date

    memcpy (drive->dataBuffer + sizeof (FILEIO_DIRECTORY_ENTRY), drive->dataBuffer, sizeof (FILEIO_DIRECTORY_ENTRY));
    entryPtr = (FILEIO_DIRECTORY_ENTRY *)(drive->dataBuffer + sizeof (FILEIO_DIRECTORY_ENTRY));
    entryPtr->name[1] = '.';

    entryPtr->firstClusterLow = (uint16_t)(dotdot & 0x0000FFFF); // Lower 16 bit address
    entryPtr->firstClusterHigh = (uint16_t)((dotdot & 0x0FFF0000)>> 16); // Higher 16 bit address. FAT32 uses only 28 bits. Mask even higher nibble also.

    sector = FILEIO_ClusterToSector (drive, dot);

    drive->bufferStatusPtr->flags.dataBufferNeedsWrite = true;
    drive->bufferStatusPtr->dataBufferCachedSector = sector;

    if (!FILEIO_FlushBuffer (drive, FILEIO_BUFFER_DATA))
    {
        return false;
    }

    return true;
}
#endif
#endif

FILEIO_ERROR_TYPE FILEIO_FindShortFileName (FILEIO_DIRECTORY * directory, FILEIO_OBJECT * filePtr, uint8_t * fileName, uint32_t * currentCluster, uint16_t * currentClusterOffset, uint16_t entryOffset, uint16_t attributes, FILEIO_SEARCH_TYPE mode)
{
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_DIRECTORY_ENTRY * entry;

    if (*currentCluster == 0)
    {
        *currentCluster = directory->drive->firstRootCluster;
    }

    while(1)
    {
        do
        {
            entry = FILEIO_DirectoryEntryCache (directory, &error, currentCluster, currentClusterOffset, entryOffset);
            if (error == FILEIO_ERROR_DONE)
            {
                break;
            }
            else if (error == FILEIO_ERROR_BAD_CACHE_READ)
            {
                directory->drive->error = FILEIO_ERROR_BAD_CACHE_READ;
                return error;
            }

            entryOffset++;
        } while (((entry->attributes == FILEIO_ATTRIBUTE_LONG_NAME) || (entry->attributes == FILEIO_ATTRIBUTE_VOLUME) || (((uint8_t)entry->name[0]) == FILEIO_DIRECTORY_ENTRY_DELETED)) && (entry->name[0] != FILEIO_DIRECTORY_ENTRY_EMPTY));

        if ((error == FILEIO_ERROR_DONE) || (entry->name[0] == FILEIO_DIRECTORY_ENTRY_EMPTY))
        {
            return FILEIO_ERROR_DONE;
        }

        // Valid file entry was found
        if (((mode & FILEIO_SEARCH_ENTRY_ATTRIBUTES) != FILEIO_SEARCH_ENTRY_ATTRIBUTES) || ((entry->attributes & attributes) == entry->attributes))
        {
            // File's attributes are valid or we aren't trying to match attributes
            if (FILEIO_ShortFileNameCompare (fileName, (uint8_t *)entry->name, mode) == true)
            {
                // Found a match.  Fill the result object with the file data
                memcpy (filePtr->name, entry->name, FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX);
                filePtr->disk = directory->drive;
                filePtr->firstCluster = FILEIO_FullClusterNumberGet (entry);
                filePtr->currentCluster = filePtr->firstCluster;
                filePtr->currentSector = 0;
                filePtr->currentOffset = 0;
                filePtr->absoluteOffset = 0;
                filePtr->size = entry->fileSize;

                filePtr->attributes = entry->attributes;
                if ((filePtr->attributes & FILEIO_ATTRIBUTE_DIRECTORY) == FILEIO_ATTRIBUTE_DIRECTORY)
                {
                    filePtr->timeMs = entry->createTimeMs;
                    filePtr->time = entry->createTime;
                    filePtr->date = entry->createDate;
                }
                else
                {
                    filePtr->time = entry->writeTime;
                    filePtr->date = entry->writeDate;
                }
                filePtr->entry = --entryOffset;
                filePtr->baseClusterDir = directory->cluster;
                filePtr->currentClusterDir = directory->cluster;

                return FILEIO_ERROR_NONE;
            }
        }
    }

#if defined (__XC8__)
    // This return value is not reachable, but it is necessary to remove an XC8 compiler warning
    return -1;
#endif
}

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
FILEIO_ERROR_TYPE FILEIO_DirectoryEntryCreate (FILEIO_OBJECT * filePtr, uint16_t * entryHandle, uint8_t attributes, bool allocateDataCluster)
{
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    uint32_t cluster;

    *entryHandle = 0;

    if (FILEIO_DirectoryEntryFindEmpty(filePtr, entryHandle) == FILEIO_ERROR_NONE)
    {
        // Allocate a data cluster to the file object, if necessary
        if (allocateDataCluster)
        {
            cluster = FILEIO_CreateFirstCluster (filePtr);
            error = ((FILEIO_DRIVE *)filePtr->disk)->error;
        }
        else
        {
            cluster = 0x0000000;
        }

        // Construct and populate the long file name entries
        if (error == FILEIO_ERROR_NONE)
        {
            if (filePtr->lfnPtr != NULL)
            {
                error = FILEIO_DirectoryEntryLFNCreate (filePtr, entryHandle);
            }
        }

        // Construct and populate the short file entry
        if (error == FILEIO_ERROR_NONE)
        {
            error = FILEIO_DirectoryEntryPopulate(filePtr, entryHandle, attributes, cluster);
        }
    }
    else
    {
        error = FILEIO_ERROR_DIR_FULL;
    }

    ((FILEIO_DRIVE *)filePtr->disk)->error = error;
    return error;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
uint32_t FILEIO_CreateFirstCluster (FILEIO_OBJECT * filePtr)
{
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_DRIVE * drive = filePtr->disk;
    uint32_t cluster;

    cluster = FILEIO_FindEmptyCluster (filePtr->disk, 2);

    if (cluster == 0)
    {
        error = FILEIO_ERROR_DRIVE_FULL;
    }
    else
    {
                // mark the cluster as taken, and last in chain
        if (drive->type == FILEIO_FILE_SYSTEM_TYPE_FAT12)
        {
            if(FILEIO_FATWrite (drive, cluster, FILEIO_CLUSTER_VALUE_FAT12_EOF, false) == FILEIO_CLUSTER_VALUE_FAT16_FAIL)
            {
                error = FILEIO_ERROR_WRITE;
            }
        }
        else if (drive->type == FILEIO_FILE_SYSTEM_TYPE_FAT16)
        {
            if(FILEIO_FATWrite (drive, cluster, FILEIO_CLUSTER_VALUE_FAT16_EOF, false) == FILEIO_CLUSTER_VALUE_FAT16_FAIL)
            {
                error = FILEIO_ERROR_WRITE;
            }
        }
        else
        {
            if(FILEIO_FATWrite (drive, cluster, FILEIO_CLUSTER_VALUE_FAT32_EOF, false) == FILEIO_CLUSTER_VALUE_FAT32_FAIL)
            {
                error = FILEIO_ERROR_WRITE;
            }
        }

        // lets erase this cluster
        if(error == FILEIO_ERROR_NONE)
        {
            error = FILEIO_EraseCluster(drive, cluster);
        }
    }

    if (!FILEIO_FlushBuffer (drive, FILEIO_BUFFER_FAT))
    {
        error = FILEIO_ERROR_WRITE;
    }

    filePtr->firstCluster = cluster;
    filePtr->currentCluster = cluster;

    drive->error = error;

    return cluster;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
FILEIO_ERROR_TYPE FILEIO_DirectoryEntryLFNCreate (FILEIO_OBJECT * filePtr, uint16_t * entryHandle)
{
    char * source;
    uint8_t checksum = 0;
    uint8_t i;
    uint8_t remainder, fileEntryCount, sequenceNumber;
    uint16_t length;
    uint16_t * tempNamePtr;
    bool firstLoop = true;
    FILEIO_DIRECTORY_ENTRY_LFN * lfnEntry;
    FILEIO_DIRECTORY directory;
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    uint32_t currentCluster;
    uint16_t currentClusterOffset;

    if (!FILEIO_AliasLFN(filePtr))
    {
        return FILEIO_ERROR_FILENAME_EXISTS;
    }

    source = filePtr->name;

    // Calculate the short file name checksum for this file
    for (i = 11; i != 0; i--)
    {
        checksum = ((checksum & 1) ? 0x80 : 0) + (checksum >> 1) + *source++;
    }

    length = filePtr->lfnLen;

    // Calculate the number of entries required for the given file name
    remainder = length % FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY;
    fileEntryCount = length / FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY;

    if (remainder || (length < (FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY)))
    {
        fileEntryCount++;
        tempNamePtr = filePtr->lfnPtr + length - remainder;
    }
    else
    {
        tempNamePtr = filePtr->lfnPtr + length - FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY;
    }

    if (remainder == 0)
    {
        remainder = FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY;
    }

    // Calculate maximum sequence number for LFN root entries
    sequenceNumber = fileEntryCount | 0x40;

    directory.cluster = filePtr->baseClusterDir;
    directory.drive = filePtr->disk;
    currentCluster = directory.cluster;
    currentClusterOffset = 0;

    while (fileEntryCount)
    {
        lfnEntry = (FILEIO_DIRECTORY_ENTRY_LFN *)FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, *entryHandle);
        
        if (lfnEntry == NULL)
        {
            return FILEIO_ERROR_BAD_CACHE_READ;
        }
        
        ((FILEIO_DRIVE *)filePtr->disk)->bufferStatusPtr->flags.dataBufferNeedsWrite = true;

        lfnEntry->sequenceNumber = sequenceNumber--;
        lfnEntry->attributes = FILEIO_ATTRIBUTE_LONG_NAME;
        lfnEntry->type = 0;
        lfnEntry->checksum = checksum;
        lfnEntry->reserved0 = 0;

        // Copy the name
        if (firstLoop)
        {
            uint16_t tempArray[FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY];

            memcpy (tempArray, tempNamePtr, remainder << 1);
            // If this name isn't an even multiple of FILEIO_NAME_NAME_UTF16_CHARS_IN_LFN_ENTRY, we need to null-terminate it
            if (remainder != FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY)
            {
                tempArray[remainder++] = 0x0000;
            }

            memset (tempArray + remainder, 0xFF, (FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY - remainder) << 1);

            memcpy (&lfnEntry->namePart1, tempArray, 10);
            memcpy (&lfnEntry->namePart2, &tempArray[5], 12);
            memcpy (&lfnEntry->namePart3, &tempArray[11], 4);

            // Decrement tempNamePtr to the beginning of the next file entry
            tempNamePtr -= FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY;

            firstLoop = false;
            sequenceNumber &= (~0x40);
        }
        else
        {
            memcpy (&lfnEntry->namePart1, tempNamePtr, 10);
            memcpy (&lfnEntry->namePart2, tempNamePtr + 5, 12);
            memcpy (&lfnEntry->namePart3, tempNamePtr + 11, 4);
            // Decrement tempNamePtr to the beginning of the next file entry
            tempNamePtr -= FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY;
        }

        *entryHandle = *entryHandle + 1;
        fileEntryCount--;
    }

    // Store the next entry (the short file name entry) in the file pointer's entry field
    filePtr->entry = *entryHandle;
    
    // Don't bother to force a write; the entries will get updated when the short file name entry is written
    return error;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
bool FILEIO_AliasLFN (FILEIO_OBJECT * filePtr)
{
    uint32_t loopIndex, loopIndexEnd;
    int16_t   index1, extIndex, lfnIndex;
    uint8_t  i, j;
    uint8_t tempString[FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX];
    uint16_t * templfnPtr;
    uint16_t length;
    FILEIO_DIRECTORY directory;
    uint32_t currentCluster;
    uint16_t currentClusterOffset;
    bool forceTail = false;
    uint8_t tailLen = 0;

    templfnPtr = filePtr->lfnPtr;
    length = filePtr->lfnLen;

    // Initially fill the alias name with space characters
    memset (filePtr->name, ' ', FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX);

    // find the location where '.' is present
    for(lfnIndex = length - 1; lfnIndex > 0; lfnIndex--)
    {
        if (templfnPtr[lfnIndex] == '.')
        {
            break;
        }
    }

    index1 = lfnIndex + 1;
    if(lfnIndex)
    {
        // Complete the extension part as per the FAT specifications
        for(extIndex = 8, j = 0; (index1 < length) && (j < 3); index1++)
        {
            // Convert lower-case to upper-case
            i = (uint8_t)templfnPtr[index1];
            if ((i >= 0x61) && (i <= 0x7A))
            {
                filePtr->name[extIndex++] = i - 0x20;
            }
            else if (i == ' ')
            {
                continue;
            }
            else if ((i == 0x2B) || (i == 0x2C) || (i == 0x3B) ||
                (i == 0x3D) || (i == 0x5B) || (i == 0x5D) ||
                (templfnPtr[index1] > 0xFF))
            {
                filePtr->name[extIndex++] = '_';
                forceTail = true;
            }
            else if (i == FILEIO_CONFIG_DELIMITER)
            {
                break;
            }
            else
            {
                filePtr->name[extIndex++] = i;
            }

            j++;
        }

        extIndex = lfnIndex;
    }
    else
    {
        extIndex = length;
    }

    // Fill the base part as per the FAT specifications
    for(index1 = 0, j = 0; ((index1 < extIndex) && (j < 8)); index1++)
    {
        // Convert lower-case to upper-case
        i = (uint8_t)templfnPtr[index1];
        if ((i >= 0x61) && (i <= 0x7A))
        {
            filePtr->name[j] = i - 0x20;
        }
        else if ((i == ' ') || (i == '.'))
        {
            continue;
        }
        else if ((i == 0x2B) || (i == 0x2C) || (i == 0x3B) ||
            (i == 0x3D) || (i == 0x5B) || (i == 0x5D) ||
            (templfnPtr[index1] > 0xFF))
        {
            filePtr->name[j] = '_';
            forceTail = true;
        }
        else
        {
            filePtr->name[j] = i;
        }

        j++;
    }

    filePtr->attributes = FILEIO_ATTRIBUTE_ARCHIVE;

    // Check to see if we need to append a numeric index to the name

    directory.cluster = filePtr->baseClusterDir;
    directory.drive = filePtr->disk;
    currentCluster = directory.cluster;
    currentClusterOffset = 0;

    // See if the current short file name exists or if we need to force a tail because our LFN contained unconvertable unicode characters
    if (forceTail || (FILEIO_FindShortFileName (&directory, filePtr, (uint8_t *)&filePtr->name, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH) == FILEIO_ERROR_NONE))
    {
        memcpy (tempString, &filePtr->name, FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX);

        // The max number of name characters with a numeric index is 6
        if (j > 6)
        {
            j = 6;
        }

        loopIndex = 1;
        
        // Outer loop - Adjust the length of the tail and add the '~' character
        for ( ; j > 0; j--)
        {
            tempString[j] = '~';
            switch (j)
            {
                case 6:
                    tailLen = 2;
                    loopIndexEnd = 10;
                    break;
                case 5:
                    tailLen = 3;
                    loopIndexEnd = 100;
                    break;
                case 4:
                    tailLen = 4;
                    loopIndexEnd = 1000;
                    break;
                case 3:
                    tailLen = 5;
                    loopIndexEnd = 10000;
                    break;
                case 2:
                    tailLen = 6;
                    loopIndexEnd = 100000;
                    break;
                case 1:
                    tailLen = 7;
                    loopIndexEnd = 1000000;
                    break;
            }

            // Inner loop - increase the value of the numeric index until we find one that isn't taken
            for ( ; loopIndex < loopIndexEnd; loopIndex++)
            {
                currentCluster = directory.cluster;
                currentClusterOffset = 0;

                switch (tailLen)
                {
                    case 7:
                        tempString[2] = (loopIndex % 1000000)/100000 + '0';
                    case 6:
                        tempString[3] = (loopIndex % 100000)/10000 + '0';
                    case 5:
                        tempString[4] = (loopIndex % 10000)/1000 + '0';
                    case 4:
                        tempString[5] = (loopIndex % 1000)/100 + '0';
                    case 3:
                        tempString[6] = (loopIndex % (uint8_t)100)/10 + '0';
                    case 2:
                        tempString[7] = loopIndex % (uint8_t)10 + '0';
                        break;
                }

                // See if the file is found
                if(FILEIO_FindShortFileName (&directory, filePtr, tempString, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH) == FILEIO_ERROR_DONE)
                {
                    memcpy (filePtr->name, tempString, FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX);
                    return true;
                    break;
                }
            }
        }
    }
    else
    {
        return true;
    }

    return false;
}
#endif

void FILEIO_ShortFileNameGet (FILEIO_OBJECT * filePtr, char * buffer)
{
    FILEIO_ShortFileNameConvert (buffer, filePtr->name);
}

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
FILEIO_ERROR_TYPE FILEIO_DirectoryEntryPopulate(FILEIO_OBJECT * filePtr, uint16_t * entryHandle, uint8_t attributes, uint32_t cluster)
{
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_DIRECTORY_ENTRY * entry;
    FILEIO_DIRECTORY directory;
    uint32_t currentCluster;
    uint16_t currentClusterOffset = 0;
    FILEIO_TIMESTAMP timeStamp;

    directory.cluster = filePtr->baseClusterDir;
    directory.drive = filePtr->disk;

    currentCluster = filePtr->baseClusterDir;

    entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, *entryHandle);

    if (entry == NULL)
    {
        ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ERROR_BAD_CACHE_READ;
        return FILEIO_ERROR_BAD_CACHE_READ;
    }

    strncpy (entry->name, filePtr->name, FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX);

    entry->attributes = attributes;
    entry->reserved0 = 0x00;
    entry->firstClusterLow = (cluster & 0x0000FFFF);
    entry->firstClusterHigh = (cluster & 0x0FFF0000) >> 16;     // FAT32 only uses 28 bits of the upper word.  Mask off the other four bits
    entry->fileSize = 0x00000000;

//    FILEIO_GetTimestamp(&timeStamp);
//    if (timestampGet != NULL)
//    {
//        (*timestampGet)(&timeStamp);
//    }

    entry->createTimeMs = timeStamp.timeMs;
    entry->createTime = timeStamp.time.value;
    entry->createDate = timeStamp.date.value;
    entry->writeTime = timeStamp.time.value;
    entry->writeDate = timeStamp.date.value;

    // Populate the file object
    filePtr->firstCluster = cluster;
    filePtr->currentCluster = cluster;
    filePtr->currentSector = 0;
    filePtr->currentOffset = 0;
    filePtr->absoluteOffset = 0;
    filePtr->size = entry->fileSize;
    filePtr->attributes = entry->attributes;
    if ((filePtr->attributes & FILEIO_ATTRIBUTE_DIRECTORY) == FILEIO_ATTRIBUTE_DIRECTORY)
    {
        filePtr->timeMs = entry->createTimeMs;
        filePtr->time = entry->createTime;
        filePtr->date = entry->createDate;
    }
    else
    {
        filePtr->time = entry->writeTime;
        filePtr->date = entry->writeDate;
    }
    filePtr->entry = *entryHandle;
    filePtr->baseClusterDir = directory.cluster;
    filePtr->currentClusterDir = directory.cluster;

    ((FILEIO_DRIVE *)filePtr->disk)->bufferStatusPtr->flags.dataBufferNeedsWrite = true;

    return error;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
FILEIO_ERROR_TYPE FILEIO_DirectoryEntryFindEmpty (FILEIO_OBJECT * filePtr, uint16_t * entryOffset)
{
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_DIRECTORY_ENTRY * entry;
    FILEIO_DIRECTORY directory;
    uint32_t currentCluster = filePtr->baseClusterDir;
    uint32_t tempCurrentCluster;
    uint16_t currentClusterOffset = 0;
    uint8_t fileEntryCount;
    uint8_t foundEntryCount;
    uint16_t tempHandle, tempHandle2;
    enum {NOT_FOUND, FOUND, ERROR} status = NOT_FOUND;

    directory.drive = filePtr->disk;
    directory.cluster = filePtr->baseClusterDir;

    if (filePtr->lfnPtr)
    {
        fileEntryCount = (filePtr->lfnLen / FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY) + 1;
        if ((filePtr->lfnLen % FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY) ||
                (filePtr->lfnLen < FILEIO_FILE_NAME_UTF16_CHARS_IN_LFN_ENTRY))
        {
            fileEntryCount++;
        }
    }
    else
    {
        fileEntryCount = 1;
    }

    tempHandle2 = *entryOffset;

    while (status == NOT_FOUND)
    {
        foundEntryCount = 0;
        tempHandle = tempHandle2;

        // Find [fileEntryCount] empty entries
        do
        {
            tempCurrentCluster = currentCluster;
            entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, tempHandle2);

            tempHandle2++;
        } while ((entry != NULL) && (((uint8_t)(entry->name[0]) == FILEIO_DIRECTORY_ENTRY_DELETED) || (entry->name[0] == FILEIO_DIRECTORY_ENTRY_EMPTY)) && (++foundEntryCount < fileEntryCount));
        
        if (entry == NULL)
        {
            if ((currentCluster == ((FILEIO_DRIVE *)filePtr->disk)->firstRootCluster) && (((FILEIO_DRIVE *)filePtr->disk)->type != FILEIO_FILE_SYSTEM_TYPE_FAT32))
            {
                    status = ERROR;
            }
            else
            {
                currentCluster = tempCurrentCluster;
                if (FILEIO_ClusterAllocate (filePtr->disk, &currentCluster, true) == FILEIO_ERROR_DRIVE_FULL)
                {
                    status = ERROR;
                }
                else
                {
                    status = FOUND;
                }
            }
        }
        else if (foundEntryCount == fileEntryCount)
        {
            status = FOUND;
        }
    }

    *entryOffset = tempHandle;

    if (status == FOUND)
    {
        return FILEIO_ERROR_NONE;
    }
    else
    {
        return FILEIO_ERROR_DONE;
    }
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
FILEIO_ERROR_TYPE FILEIO_ClusterAllocate (FILEIO_DRIVE * drive, uint32_t * cluster, bool eraseCluster)
{
    uint32_t newCluster;

    newCluster = FILEIO_FindEmptyCluster (drive, *cluster);
    if (newCluster == 0)
    {
        return FILEIO_ERROR_DRIVE_FULL;
    }

    if (drive->type == FILEIO_FILE_SYSTEM_TYPE_FAT12)
    {
        FILEIO_FATWrite (drive, newCluster, FILEIO_CLUSTER_VALUE_FAT12_EOF, false);
    }
    else if (drive->type == FILEIO_FILE_SYSTEM_TYPE_FAT16)
    {
        FILEIO_FATWrite (drive, newCluster, FILEIO_CLUSTER_VALUE_FAT16_EOF, false);
    }
    else if (drive->type == FILEIO_FILE_SYSTEM_TYPE_FAT32)
    {
        FILEIO_FATWrite (drive, newCluster, FILEIO_CLUSTER_VALUE_FAT32_EOF, false);
    }

    FILEIO_FATWrite (drive, *cluster, newCluster, false);

    *cluster = newCluster;

    if (eraseCluster)
    {
        return FILEIO_EraseCluster (drive, newCluster);
    }
    else
    {
        return FILEIO_ERROR_NONE;
    }
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
uint32_t FILEIO_FindEmptyCluster (FILEIO_DRIVE * drive, uint32_t baseCluster)
{
    uint32_t cluster = 0x0;
    uint32_t currentCluster, endClusterLimit, clusterFailValue;

    /* Settings based on FAT type */
    switch (drive->type)
    {
        case FILEIO_FILE_SYSTEM_TYPE_FAT32:
            endClusterLimit = FILEIO_CLUSTER_VALUE_FAT32_END;
            clusterFailValue = FILEIO_CLUSTER_VALUE_FAT32_FAIL;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT12:
            endClusterLimit = FILEIO_CLUSTER_VALUE_FAT12_END;
            clusterFailValue = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT16:
        default:
            endClusterLimit = FILEIO_CLUSTER_VALUE_FAT16_END;
            clusterFailValue = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            break;
    }

    // just in case
    if(baseCluster < 2)
        baseCluster = 2;

    currentCluster = baseCluster;

    // sequentially scan through the FAT looking for an empty cluster
    while(currentCluster)
    {
        // look at its value
        if ((cluster = FILEIO_FATRead(drive, currentCluster)) == clusterFailValue)
        {
            currentCluster = 0;
            break;
        }

        // check if empty cluster found
        if (cluster == FILEIO_CLUSTER_VALUE_EMPTY)
            break;

        currentCluster++;    // check next cluster in FAT

        // check if reached last cluster in FAT, re-start from top
        if ((cluster == endClusterLimit) || (currentCluster >= (drive->partitionClusterCount + 2)))
        {
            currentCluster = 2;
        }

        // check if full circle done, disk full
        if ( currentCluster == baseCluster)
        {
            currentCluster = 0;
            break;
        }
    }  // scanning for an empty cluster

    return(currentCluster);
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
FILEIO_ERROR_TYPE FILEIO_EraseCluster (FILEIO_DRIVE * drive, uint32_t cluster)
{
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    uint32_t sector = FILEIO_ClusterToSector (drive, cluster);
    uint8_t i;

    if (!FILEIO_FlushBuffer (drive, FILEIO_BUFFER_DATA))
    {
        drive->error = FILEIO_ERROR_WRITE;
        return FILEIO_ERROR_WRITE;
    }

    memset (drive->dataBuffer, 0x00, drive->sectorSize);

    for (i = 0; (i < drive->sectorsPerCluster) && (error == FILEIO_ERROR_NONE); i++)
    {
//        if (!(*drive->driveConfig->funcSectorWrite)(drive->mediaParameters, sector++, drive->dataBuffer, false))
        if (!FILEIO_DRIVER_SectorWrite(drive->driveId, sector++, drive->dataBuffer, false))
        {
            error = FILEIO_ERROR_WRITE;
        }
    }

    // As an optimization, set the cached sector to the first sector of the cluster.  They're all zero anyway, now.
    drive->bufferStatusPtr->dataBufferCachedSector = sector - drive->sectorsPerCluster;
    drive->bufferStatusPtr->flags.dataBufferNeedsWrite = false;

    drive->error = error;
    return error;
}
#endif

FILEIO_DIRECTORY_ENTRY * FILEIO_DirectoryEntryCache (FILEIO_DIRECTORY * directory, FILEIO_ERROR_TYPE * error, uint32_t * currentCluster, uint16_t * currentClusterOffset, uint16_t entryOffset)
{
    FILEIO_DRIVE * disk = directory->drive;
    FILEIO_DIRECTORY_ENTRY * entry;
    uint8_t directoryEntriesPerSector = disk->sectorSize / FILEIO_DIRECTORY_ENTRY_SIZE;
    uint16_t totalSectorOffset = entryOffset / directoryEntriesPerSector;
    uint32_t sector;

    if ((disk->type != FILEIO_FILE_SYSTEM_TYPE_FAT32) && (*currentCluster == FILEIO_FIXED_ROOT_DIRECTORY_CLUSTER_NUMBER))
    {
        if (entryOffset >= disk->rootDirectoryEntryCount)
        {
            *error = FILEIO_ERROR_DONE;
            return NULL;
        }
        // We're looking for the file in the FAT12/16 root directory
        sector = FILEIO_ClusterToSector (directory->drive, directory->cluster);
        sector += totalSectorOffset;
    }
    else
    {
        // Find the cluster/sector with the specified directory entry offset
        uint16_t totalClusterOffset = totalSectorOffset / disk->sectorsPerCluster;
        if (*currentClusterOffset > totalClusterOffset)
        {
            *currentClusterOffset = 0;
            *currentCluster = directory->cluster;
        }

        totalSectorOffset -= (disk->sectorsPerCluster * (*currentClusterOffset));

        while (*currentClusterOffset < totalClusterOffset)
        {
            *currentCluster = FILEIO_FATRead (disk, *currentCluster);
            // Switch based on FAT type
            switch (disk->type)
            {
                case FILEIO_FILE_SYSTEM_TYPE_FAT32:
                    if (*currentCluster == FILEIO_CLUSTER_VALUE_FAT32_EOF)
                    {
                        *error = FILEIO_ERROR_DONE;
                        return NULL;
                    }
                    break;
                case FILEIO_FILE_SYSTEM_TYPE_FAT12:
                case FILEIO_FILE_SYSTEM_TYPE_FAT16:
                default:
                    if (*currentCluster == FILEIO_CLUSTER_VALUE_FAT16_EOF)
                    {
                        *error = FILEIO_ERROR_DONE;
                        return NULL;
                    }
                    break;
            }
            *currentClusterOffset = *currentClusterOffset + 1;
            totalSectorOffset -= disk->sectorsPerCluster;
        }

        // currentCluster points to the cluster we need.  Convert to sectors and add the sector offset.
        sector = FILEIO_ClusterToSector (disk, *currentCluster);
        sector += totalSectorOffset;
    }

    if (disk->bufferStatusPtr->dataBufferCachedSector != sector)
    {
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
        if (!FILEIO_FlushBuffer (disk, FILEIO_BUFFER_DATA))
        {
            *error = FILEIO_ERROR_WRITE;
            return NULL;
        }
#endif
//        if ((*disk->driveConfig->funcSectorRead) (disk->mediaParameters, sector, disk->dataBuffer) != true)
        if (FILEIO_DRIVER_SectorRead(disk->driveId, sector, disk->dataBuffer) != true)
        {
            *error = FILEIO_ERROR_BAD_SECTOR_READ;
            return NULL;
        }
        else
        {
            disk->bufferStatusPtr->dataBufferCachedSector = sector;
#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
            disk->bufferStatusPtr->driveOwner = disk;
#endif
        }
    }

    entry = (FILEIO_DIRECTORY_ENTRY *)((FILEIO_DIRECTORY_ENTRY *)disk->dataBuffer + (entryOffset % directoryEntriesPerSector));

    *error = FILEIO_ERROR_NONE;
    
    return entry;
}

FILEIO_ERROR_TYPE FILEIO_ForceRecache (FILEIO_DRIVE * disk)
{
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
    if (!FILEIO_FlushBuffer (disk, FILEIO_BUFFER_DATA))
    {
        return FILEIO_ERROR_WRITE;
    }
#endif
    //if ((*disk->driveConfig->funcSectorRead) (disk->mediaParameters, disk->bufferStatusPtr->dataBufferCachedSector, disk->dataBuffer) != true)v
    if (FILEIO_DRIVER_SectorRead(disk->driveId, disk->bufferStatusPtr->dataBufferCachedSector, disk->dataBuffer) != true)
    {
        return FILEIO_ERROR_BAD_SECTOR_READ;
    }

    return FILEIO_ERROR_NONE;
}

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
bool FILEIO_FlushBuffer (FILEIO_DRIVE * disk, FILEIO_BUFFER_ID bufferId)
{
#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    disk = disk->bufferStatusPtr->driveOwner;

    if (disk == NULL)
    {
        return true;
    }
#endif
    switch (bufferId)
    {
        case FILEIO_BUFFER_DATA:
            if (disk->bufferStatusPtr->flags.dataBufferNeedsWrite)
            {
                //if (!(*disk->driveConfig->funcSectorWrite)(disk->mediaParameters, disk->bufferStatusPtr->dataBufferCachedSector, disk->dataBuffer, false) )
                if (!FILEIO_DRIVER_SectorWrite(disk->driveId, disk->bufferStatusPtr->dataBufferCachedSector, disk->dataBuffer, false) )
                {
                    return false;
                }
                disk->bufferStatusPtr->flags.dataBufferNeedsWrite = false;
            }
            break;
        case FILEIO_BUFFER_FAT:
            if (disk->bufferStatusPtr->flags.fatBufferNeedsWrite)
            {
                uint32_t sector = disk->bufferStatusPtr->fatBufferCachedSector;
                uint8_t i;
                for (i = 0; i < disk->fatCopyCount; i++, sector += disk->fatSectorCount)
                {
                    //if (! (*disk->driveConfig->funcSectorWrite)(disk->mediaParameters, sector, disk->fatBuffer, false) )
                    if(!FILEIO_DRIVER_SectorWrite(disk->driveId, sector, disk->fatBuffer, false))
                    {
                        return false;
                    }
                }
                disk->bufferStatusPtr->flags.fatBufferNeedsWrite = false;
            }
            break;
    }
    return true;
}
#endif

bool FILEIO_ShortFileNameCompare (uint8_t * fileName1, uint8_t * fileName2, uint8_t mode)
{
    if ((mode & FILEIO_SEARCH_PARTIAL_STRING_SEARCH) == FILEIO_SEARCH_PARTIAL_STRING_SEARCH)
    {
        uint8_t character;
        uint8_t i;

        // Compare short filename
        for (i = 0; i < 8; i++)
        {
            character = fileName1[i];
            if (character == '*')
                break;
            if (character != '?')
            {
                if (character != fileName2[i])
                {
                    return false;
                }
            }
        }

        // Compare extension
        for (i = 8; i < FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX; i++)
        {
            character = fileName1[i];
            if (character == '*')
                break;
            if (character != '?')
            {
                if (character != fileName2[i])
                {
                    return false;
                }
            }
        }
    }
    else
    {
        // If we need an exact match, just use memcmp
        return (memcmp(fileName1, fileName2, FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX) == 0) ? true : false;
    }

    return true;
}

uint32_t FILEIO_ClusterToSector(FILEIO_DRIVE * disk, uint32_t cluster)
{
    uint32_t sector;

    // Rt: Settings based on FAT type
    switch (disk->type)
    {
        case FILEIO_FILE_SYSTEM_TYPE_FAT32:
            // In FAT32, there is no separate ROOT region. It is as well stored in DATA region
            sector = (((uint32_t)cluster-2) * disk->sectorsPerCluster) + disk->firstDataSector;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT12:
        case FILEIO_FILE_SYSTEM_TYPE_FAT16:
        default:
            // The root dir takes up cluster 0 and 1
            if((cluster == 0) || (cluster == 1))
                sector = disk->firstRootSector + cluster;
            else
                sector = (((uint32_t)cluster-2) * disk->sectorsPerCluster) + disk->firstDataSector;
            break;
    }

    return(sector);

}

uint32_t FILEIO_FATRead (FILEIO_DRIVE * disk, uint32_t currentCluster)
{
    uint8_t q;
    uint32_t p, l;  // "l" is the sector Address
    uint32_t c = 0, d, ClusterFailValue,LastClusterLimit;   // ClusterEntries

    /* Settings based on FAT type */
    switch (disk->type)
    {
        case FILEIO_FILE_SYSTEM_TYPE_FAT32:
            p = (uint32_t)currentCluster * 4;
            q = 0; // "q" not used for FAT32, only initialized to remove a warning
            ClusterFailValue = FILEIO_CLUSTER_VALUE_FAT32_FAIL;
            LastClusterLimit = FILEIO_CLUSTER_VALUE_FAT32_EOF;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT12:
            p = (uint32_t) currentCluster *3;  // Mulby1.5 to find cluster pos in FAT
            q = p&1;
            p >>= 1;
            ClusterFailValue = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            LastClusterLimit = FILEIO_CLUSTER_VALUE_FAT12_EOF;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT16:
        default:
            p = (uint32_t)currentCluster *2;     // Mulby 2 to find cluster pos in FAT
            q = 0; // "q" not used for FAT16, only initialized to remove a warning
            ClusterFailValue = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            LastClusterLimit = FILEIO_CLUSTER_VALUE_FAT16_EOF;
            break;
    }

    l = disk->firstFatSector + (p / disk->sectorSize);     //
    p &= disk->sectorSize - 1;                 // Restrict 'p' within the FATbuffer size

    // Check if the appropriate FAT sector is already loaded
    if (disk->bufferStatusPtr->fatBufferCachedSector == l)
    {
        if (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT32)
        {
            c = ReadRam32bit (disk->fatBuffer, p);
        }
        else
        {
            if(disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT16)
            {
                c = ReadRam16bit (disk->fatBuffer, p);
            }
            else if(disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT12)
            {
                c = *(disk->fatBuffer + p);
                if (q)
                {
                    c >>= 4;
                }
                // Check if the MSB is across the sector boundry
                p = (p +1) & (disk->sectorSize-1);
                if (p == 0)
                {
                    // Start by writing the sector we just worked on to the card
                    // if we need to
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
                    if (!FILEIO_FlushBuffer (disk, FILEIO_BUFFER_FAT))
                    {
                        return ClusterFailValue;
                    }
#endif
                    //if (!(*disk->driveConfig->funcSectorRead) (disk->mediaParameters, l+1, disk->fatBuffer))
                    if (!FILEIO_DRIVER_SectorRead(disk->driveId, l+1, disk->fatBuffer))
                    {
                        disk->bufferStatusPtr->fatBufferCachedSector = 0xFFFFFFFF;
                        return ClusterFailValue;
                    }
                    else
                    {
                        disk->bufferStatusPtr->fatBufferCachedSector = l + 1;
                    }
                }
                d = *(disk->fatBuffer + p);
                if (q)
                {
                    c += (d <<4);
                }
                else
                {
                    c += ((d & 0x0F)<<8);
                }
            }
        }
    }
    else
    {
        // If there's a currently open FAT sector,
        // write it back before reading into the buffer
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
        if (!FILEIO_FlushBuffer (disk, FILEIO_BUFFER_FAT))
        {
            return ClusterFailValue;
        }
#endif
        //if (!(*disk->driveConfig->funcSectorRead) (disk->mediaParameters, l, disk->fatBuffer))
        if (!FILEIO_DRIVER_SectorRead(disk->driveId, l, disk->fatBuffer))
        {
            disk->bufferStatusPtr->fatBufferCachedSector = 0xFFFFFFFF;  // Note: It is Sector not Cluster.
            return ClusterFailValue;
        }
        else
        {
            disk->bufferStatusPtr->fatBufferCachedSector = l;

            if (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT32)
            {
                c = ReadRam32bit (disk->fatBuffer, p);
            }
            else
            {
                if(disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT16)
                {
                    c = ReadRam16bit (disk->fatBuffer, p);
                }
                else if (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT12)
                {
                    c = *(disk->fatBuffer + p);
                    if (q)
                    {
                        c >>= 4;
                    }
                    p = (p +1) & (disk->sectorSize-1);
                    d = *(disk->fatBuffer + p);
                    if (q)
                    {
                        c += (d <<4);
                    }
                    else
                    {
                        c += ((d & 0x0F)<<8);
                    }
                }
            }
        }
    }

    // Normalize it so 0xFFFF is an error
    if (c >= LastClusterLimit)
    {
        c = LastClusterLimit;
    }

   return c;
}   // ReadFAT

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
FILEIO_ERROR_TYPE FILEIO_EraseFile (FILEIO_OBJECT * filePtr, uint16_t * entryHandle, bool eraseData)
{
    FILEIO_ERROR_TYPE error;
    FILEIO_DIRECTORY_ENTRY * entry;
    FILEIO_DRIVE * disk = filePtr->disk;
    FILEIO_DIRECTORY directory;
    uint32_t currentCluster;
    uint16_t currentClusterOffset;
    uint16_t tempEntryHandle = *entryHandle;
    uint8_t sequenceNumber;

    error = FILEIO_ERROR_ERASE_FAIL;

    directory.drive = filePtr->disk;
    directory.cluster = filePtr->baseClusterDir;
    currentCluster = filePtr->baseClusterDir;
    if (currentCluster == 0)
    {
        currentCluster = disk->firstRootCluster;
    }
    currentClusterOffset = 0;

    // Cache the short file entry
    entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, tempEntryHandle);

    do
    {
        if (entry == NULL)
        {
            disk->error = error;
            return error;
        }

        sequenceNumber = *((uint8_t *)entry);

        if (((uint8_t)entry->name[0] == FILEIO_DIRECTORY_ENTRY_DELETED) || (entry->name[0] == FILEIO_DIRECTORY_ENTRY_EMPTY))
        {
            disk->error = FILEIO_ERROR_FILE_NOT_FOUND;
            return FILEIO_ERROR_FILE_NOT_FOUND;
        }
        else
        {
            entry->name[0] = FILEIO_DIRECTORY_ENTRY_DELETED;
            // Mark the cached sector as needing a write.
            disk->bufferStatusPtr->flags.dataBufferNeedsWrite = true;
        }

        if (((entry->attributes == FILEIO_ATTRIBUTE_LONG_NAME) && ((sequenceNumber & 0x40) == 0x40)) || (tempEntryHandle == 0))
        {
            break;
        }

        tempEntryHandle--;

        // Cache the previous entry
        entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, tempEntryHandle);
    } while (entry->attributes == FILEIO_ATTRIBUTE_LONG_NAME);

    if (error == FILEIO_ERROR_NONE)
    {
        // Check to make sure someone isn't trying to erase the root directory.  This should never happen.
        if (filePtr->firstCluster != disk->firstRootCluster)
        {
            if (eraseData)
            {
                error = FILEIO_EraseClusterChain (filePtr->firstCluster, disk) ? FILEIO_ERROR_NONE : FILEIO_ERROR_ERASE_FAIL;
            }
        }
    }

    if (!FILEIO_FlushBuffer (filePtr->disk, FILEIO_BUFFER_DATA))
    {
        ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ERROR_WRITE;
        return FILEIO_RESULT_FAILURE;
    }

    if (!FILEIO_FlushBuffer (filePtr->disk, FILEIO_BUFFER_FAT))
    {
        ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ERROR_WRITE;
        return FILEIO_RESULT_FAILURE;
    }

    disk->error = error;
    return error;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
FILEIO_ERROR_TYPE FILEIO_EraseClusterChain (uint32_t cluster, FILEIO_DRIVE * disk)
{
    uint32_t nextCluster, clusterFailed, clusterFinal;
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;

    switch (disk->type)
    {
        case FILEIO_FILE_SYSTEM_TYPE_FAT32:
            clusterFailed = FILEIO_CLUSTER_VALUE_FAT32_FAIL;
            clusterFinal = FILEIO_CLUSTER_VALUE_FAT32_EOF;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT12:
            clusterFailed = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            clusterFinal = FILEIO_CLUSTER_VALUE_FAT12_EOF;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT16:
        default:
            clusterFailed = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            clusterFinal = FILEIO_CLUSTER_VALUE_FAT16_EOF;
            break;
    }

    if ((cluster == 0) || (cluster == 1))
    {
        error = FILEIO_ERROR_INVALID_ARGUMENT;
    }
    else
    {
        while (error == FILEIO_ERROR_NONE)
        {
            if ((nextCluster = FILEIO_FATRead (disk, cluster)) == clusterFailed)
            {
                error = FILEIO_ERROR_DONE;
            }
            else
            {
                if ((nextCluster == 0) || (nextCluster == 1))
                {
                    error = FILEIO_ERROR_INVALID_ARGUMENT;
                }
                else
                {
                    if (nextCluster >= clusterFinal)
                    {
                        error = FILEIO_ERROR_DONE;
                    }

                    if (FILEIO_FATWrite (disk, cluster, FILEIO_CLUSTER_VALUE_EMPTY, false) == clusterFailed)
                    {
                        error = FILEIO_ERROR_WRITE;
                    }

                    cluster = nextCluster;
                }
            }
        }
    }

    FILEIO_FATWrite (disk, 0, 0, true);

    return error;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
uint32_t FILEIO_FATWrite (FILEIO_DRIVE *disk, uint32_t currentCluster, uint32_t value, uint8_t forceWrite)
{
    uint8_t q, c;
    uint32_t p, l, clusterFailValue;
    FILEIO_BUFFER_STATUS * statusPtr = disk->bufferStatusPtr;

    if ((disk->type != FILEIO_FILE_SYSTEM_TYPE_FAT32) && (disk->type != FILEIO_FILE_SYSTEM_TYPE_FAT16) && (disk->type != FILEIO_FILE_SYSTEM_TYPE_FAT12))
    {
        return FILEIO_CLUSTER_VALUE_FAT32_FAIL;
    }

    /* Settings based on FAT type */
    switch (disk->type)
    {
        case FILEIO_FILE_SYSTEM_TYPE_FAT32:
            clusterFailValue = FILEIO_CLUSTER_VALUE_FAT32_FAIL;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT12:
        case FILEIO_FILE_SYSTEM_TYPE_FAT16:
        default:
            clusterFailValue = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            break;
    }

    // The only purpose for calling this function with forceWrite
    // is to write the current FAT sector to the card
    if (forceWrite)
    {
        FILEIO_FlushBuffer (disk, FILEIO_BUFFER_FAT);
        return 0;
    }

    /* Settings based on FAT type */
    switch (disk->type)
    {
        case FILEIO_FILE_SYSTEM_TYPE_FAT32:
            p = (uint32_t)currentCluster *4;   // "p" is the position in "gFATBuffer" for corresponding cluster.
            q = 0;      // "q" not used for FAT32, only initialized to remove a warning
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT12:
            p = (uint32_t) currentCluster * 3; // "p" is the position in "gFATBuffer" for corresponding cluster.
            q = p & 1;   // Odd or even?
            p >>= 1;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT16:
        default:
            p = (uint32_t) currentCluster *2;   // "p" is the position in "gFATBuffer" for corresponding cluster.
            q = 0;      // "q" not used for FAT16, only initialized to remove a warning
            break;
    }

    l = disk->firstFatSector + (p / disk->sectorSize);     //
    p &= disk->sectorSize - 1;                 // Restrict 'p' within the FATbuffer size

    if (disk->bufferStatusPtr->fatBufferCachedSector != l)
    {
        // If we are loading a new sector then write
        // the current one to the card if we need to
        if (!FILEIO_FlushBuffer (disk, FILEIO_BUFFER_FAT))
        {
            return clusterFailValue;
        }

        // Load the new sector
        //if (!(*disk->driveConfig->funcSectorRead) (disk->mediaParameters, l, disk->fatBuffer))
        if (!FILEIO_DRIVER_SectorRead(disk->driveId, l, disk->fatBuffer))
        {
            statusPtr->fatBufferCachedSector = 0xFFFFFFFF;
            return clusterFailValue;
        }
        else
        {
            statusPtr->fatBufferCachedSector = l;
        }
    }

    if (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT32)  // Refer page 16 of FAT requirement.
    {
        *(disk->fatBuffer + p) = ((value & 0x000000ff));         // lsb,1st uint8_t of cluster value
        *(disk->fatBuffer + p+1) = ((value & 0x0000ff00) >> 8);
        *(disk->fatBuffer + p+2) = ((value & 0x00ff0000) >> 16);
        *(disk->fatBuffer + p+3) = ((value & 0x0f000000) >> 24);   // the MSB nibble is supposed to be "0" in FAT32. So mask it.
    }
    else
    {
        if (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT16)
        {
            *(disk->fatBuffer+ p) = value;            //lsB
            *(disk->fatBuffer + p+1) = ((value&0x0000ff00) >> 8);    // msB
        }
        else if (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT12)
        {
            // Get the current uint8_t from the FAT
            c = *(disk->fatBuffer + p);
            if (q)
            {
                c = ((value & 0x0F) << 4) | ( c & 0x0F);
            }
            else
            {
                c = (value & 0xFF);
            }
            // Write in those bits
            *(disk->fatBuffer + p) = c;

            // FAT12 entries can cross sector boundaries
            // Check if we need to load a new sector
            p = (p +1) & (disk->sectorSize-1);
            if (p == 0)
            {
                // call this function to update the FAT on the card
                if (!FILEIO_FlushBuffer (disk, FILEIO_BUFFER_FAT))
                {
                    return clusterFailValue;
                }

                // Load the next sector
                //if (!(*disk->driveConfig->funcSectorRead) (disk->mediaParameters, l +1, disk->fatBuffer))
                if (!FILEIO_DRIVER_SectorRead(disk->driveId, l +1, disk->fatBuffer))
                {
                    statusPtr->fatBufferCachedSector = 0xFFFFFFFF;
                    return clusterFailValue;
                }
                else
                {
                    statusPtr->fatBufferCachedSector = l + 1;
                }
            }

            // Get the second uint8_t of the table entry
            c = *(disk->fatBuffer + p);
            if (q)
            {
                c = (value >> 4);
            }
            else
            {
                c = ((value >> 8) & 0x0F) | (c & 0xF0);
            }
            *(disk->fatBuffer + p) = c;
        }
    }
    statusPtr->flags.fatBufferNeedsWrite = true;

    return 0;
}
#endif

FILEIO_ERROR_TYPE FILEIO_NextClusterGet (FILEIO_OBJECT * fo, uint32_t count)
{
    uint32_t nextCluster, currentCluster, clusterFailValue, lastClustervalue;
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_DRIVE * disk;

    disk = fo->disk;

    /* Settings based on FAT type */
    switch (disk->type)
    {
        case FILEIO_FILE_SYSTEM_TYPE_FAT32:
            lastClustervalue = FILEIO_CLUSTER_VALUE_FAT32_EOF;
            clusterFailValue  = FILEIO_CLUSTER_VALUE_FAT32_FAIL;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT12:
            lastClustervalue = FILEIO_CLUSTER_VALUE_FAT12_EOF;
            clusterFailValue  = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            break;
        case FILEIO_FILE_SYSTEM_TYPE_FAT16:
        default:
            lastClustervalue = FILEIO_CLUSTER_VALUE_FAT16_EOF;
            clusterFailValue  = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
            break;
    }

    // loop n times
    do
    {
        // get the next cluster link from FAT
        currentCluster = fo->currentCluster;
        if ((nextCluster = FILEIO_FATRead( disk, currentCluster)) == clusterFailValue)
        {
            error = FILEIO_ERROR_BAD_SECTOR_READ;
        }
        else
        {
            // check if cluster value is valid
            if (nextCluster >= (disk->partitionClusterCount + 2))
            {
                error = FILEIO_ERROR_INVALID_CLUSTER;
            }

            // compare against max value of a cluster in FAT
            // return if eof
            if (nextCluster >= lastClustervalue)    // check against eof
            {
                error = FILEIO_ERROR_EOF;
            }
        }

        // update the file object structure
        fo->currentCluster = nextCluster;

    } while ((--count > 0) && (error == FILEIO_ERROR_NONE));// loop end

    return(error);
} // get next cluster


int FILEIO_Close(FILEIO_OBJECT * filePtr)
{
    int result = FILEIO_RESULT_SUCCESS;

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
    result = FILEIO_Flush (filePtr);
#endif

    filePtr->flags.readEnabled = false;
    filePtr->flags.writeEnabled = false;

    return result;
}

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
int FILEIO_Flush (FILEIO_OBJECT * filePtr)
{
    int result = FILEIO_RESULT_SUCCESS;

    FILEIO_TIMESTAMP timeStamp;
    FILEIO_DIRECTORY_ENTRY * entry;
    FILEIO_DIRECTORY directory;
    uint32_t currentCluster;
    uint16_t currentClusterOffset = 0;
    FILEIO_ERROR_TYPE error;

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (FILEIO_GetSingleBuffer (filePtr->disk) != FILEIO_RESULT_SUCCESS)
    {
        return 0;
    }
#endif

    if(filePtr->flags.writeEnabled)
    {
        // Write the current data sector to the disk
        if (!FILEIO_FlushBuffer (filePtr->disk, FILEIO_BUFFER_DATA))
        {
            ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ERROR_WRITE;
            return FILEIO_RESULT_FAILURE;
        }

        // Write the current FAT sector to the disk
        if (!FILEIO_FlushBuffer (filePtr->disk, FILEIO_BUFFER_FAT))
        {
            ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ERROR_WRITE;
            return FILEIO_RESULT_FAILURE;
        }

        // Read the FAT entry from the physical media.  This is required because
        //   some physical media cache the entries in RAM and only write them
        //   after a time expires for until the sector is accessed again.
        FILEIO_FATRead (filePtr->disk, filePtr->currentCluster);

        directory.drive = filePtr->disk;
        directory.cluster = filePtr->baseClusterDir;
        currentCluster = filePtr->baseClusterDir;
        // Get the file entry
        entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, filePtr->entry);

        if (entry == NULL)
        {
            ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ERROR_BAD_CACHE_READ;
            return FILEIO_RESULT_FAILURE;
        }

  //      FILEIO_GetTimestamp(&timeStamp);
//        if (timestampGet != NULL)
//        {
//            (*timestampGet)(&timeStamp);
//        }

        // update the time
        entry->writeTime = timeStamp.time.value;
        entry->writeDate = timeStamp.date.value;

        entry->fileSize = filePtr->size;

        entry->attributes = filePtr->attributes;

        ((FILEIO_DRIVE *)filePtr->disk)->bufferStatusPtr->flags.dataBufferNeedsWrite = true;

        // just write the last entry in
        if(FILEIO_FlushBuffer (filePtr->disk, FILEIO_BUFFER_DATA))
        {
            // Read the folder entry from the physical media.  This is required because
            //   some physical media cache the entries in RAM and only write them
            //   after a time expires for until the sector is accessed again.
            ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ForceRecache (filePtr->disk);
            if (((FILEIO_DRIVE *)filePtr->disk)->error == FILEIO_ERROR_NONE)
            {
                result = FILEIO_RESULT_SUCCESS;
            }
            else
            {
                result = FILEIO_RESULT_FAILURE;
            }
        }
        else
        {
            ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ERROR_WRITE;
            result = FILEIO_RESULT_FAILURE;
        }
    }

    return result;
}
#endif

long FILEIO_Tell (FILEIO_OBJECT * filePtr)
{
    ((FILEIO_DRIVE *)filePtr->disk)->error = FILEIO_ERROR_NONE;
    return (filePtr->absoluteOffset);
}

int FILEIO_Seek(FILEIO_OBJECT * filePtr, int32_t offset, int whence)
{
    uint32_t    numsector, temp;   // lba of first sector of first cluster
    FILEIO_DRIVE*   disk;            // pointer to disk structure
    uint8_t   test;
    long offset2 = offset;

    disk = filePtr->disk;

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (FILEIO_GetSingleBuffer (disk) != FILEIO_RESULT_SUCCESS)
    {
        return FILEIO_RESULT_FAILURE;
    }
#endif

    switch(whence)
    {
        case FILEIO_SEEK_CUR:
            // Apply the offset to the current position
            offset2 += filePtr->absoluteOffset;
            break;
        case FILEIO_SEEK_END:
            // Apply the offset to the end of the file
            offset2 = filePtr->size - offset2;
            break;
        case FILEIO_SEEK_SET:
            // automatically there
        default:
            break;
   }

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
    if (!FILEIO_FlushBuffer (filePtr->disk, FILEIO_BUFFER_DATA))
    {
        disk->error = FILEIO_ERROR_WRITE;
        return FILEIO_RESULT_FAILURE;
    }
#endif

    // start from the beginning
    filePtr->currentCluster = filePtr->firstCluster;

    if (offset2 > filePtr->size)
    {
        disk->error = FILEIO_ERROR_INVALID_ARGUMENT;
        return FILEIO_RESULT_FAILURE;      // past the limits
    }
    else
    {
        // set the new postion
        filePtr->absoluteOffset = offset2;

        // figure out how many sectors
        numsector = offset2 / disk->sectorSize;

        // figure out how many uint8_ts off of the offset
        offset2 = offset2 - (numsector * disk->sectorSize);
        filePtr->currentOffset = offset2;

        // figure out how many clusters
        temp = numsector / disk->sectorsPerCluster;

        // figure out the stranded sectors
        numsector = numsector - (disk->sectorsPerCluster * temp);
        filePtr->currentSector = numsector;

        // if we are in the current cluster stay there
        if (temp > 0)
        {
            test = FILEIO_NextClusterGet(filePtr, temp);
            if (test != FILEIO_ERROR_NONE)
            {
                if (test == FILEIO_ERROR_EOF)
                {
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
                    if (filePtr->flags.writeEnabled)
                    {
                        // load the previous cluster
                        filePtr->currentCluster = filePtr->firstCluster;
                        // Don't perform this operation if there's only one cluster
                        if (temp != 1)
                        {
                            test = FILEIO_NextClusterGet(filePtr, temp - 1);
                        }
                        if (FILEIO_ClusterAllocate (disk, &filePtr->currentCluster, false) != FILEIO_ERROR_NONE)
                        {
                            disk->error = FILEIO_ERROR_COULD_NOT_GET_CLUSTER;
                            return FILEIO_RESULT_FAILURE;
                        }
                        // sec and currentOffset should already be zero
                    }
                    else
#endif
                    {
                        filePtr->currentCluster = filePtr->firstCluster;
                        if (temp != 1)
                        {
                            test = FILEIO_NextClusterGet(filePtr, temp - 1);
                        }
                        else
                        {
                            test = FILEIO_ERROR_NONE;
                        }
                        if (test != FILEIO_ERROR_NONE)
                        {
                            disk->error = FILEIO_ERROR_COULD_NOT_GET_CLUSTER;
                            return FILEIO_RESULT_FAILURE;
                        }
                        filePtr->currentOffset = disk->sectorSize;
                        filePtr->currentSector = disk->sectorsPerCluster - 1;
                    }
                }
                else
                {
                    disk->error = FILEIO_ERROR_COULD_NOT_GET_CLUSTER;
                    return FILEIO_RESULT_FAILURE;   // past the limits
                }
            }
        }

        // Determine the lba of the selected sector and load
        temp = FILEIO_ClusterToSector(disk, filePtr->currentCluster);

        // now the extra sectors
        numsector = filePtr->currentSector;
        temp += numsector;

        //if(!(*disk->driveConfig->funcSectorRead)(disk->mediaParameters, temp, disk->dataBuffer) )
        if(!FILEIO_DRIVER_SectorRead(disk->driveId, temp, disk->dataBuffer) )
        {
            disk->error = FILEIO_ERROR_BAD_CACHE_READ;
            return FILEIO_RESULT_FAILURE;   // Bad read
        }
        disk->bufferStatusPtr->dataBufferCachedSector = temp;
    }

    disk->error = FILEIO_ERROR_NONE;

    return FILEIO_RESULT_SUCCESS;
}

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
size_t FILEIO_Write (const void * buffer, size_t size, size_t count, FILEIO_OBJECT * filePtr)
{
    FILEIO_ERROR_TYPE error;
    uint8_t * data = (uint8_t *) buffer;
    FILEIO_DRIVE * disk = filePtr->disk;
    uint32_t currentSector;
    size_t dataWritten = 0;
    uint16_t writeCount;
    size_t length = size * count;

    if (!filePtr->flags.writeEnabled)
    {
        disk->error = FILEIO_ERROR_READ_ONLY;
        return 0;
    }

    // Check to see if the user has passed in no data
    if (length == 0)
    {
        disk->error = FILEIO_ERROR_NONE;
        return 0;
    }

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (FILEIO_GetSingleBuffer (disk) != FILEIO_RESULT_SUCCESS)
    {
        return 0;
    }
#endif

    //if ((*disk->driveConfig->funcWriteProtectGet)(disk->mediaParameters))
    if (FILEIO_DRIVER_WriteProtectStateGet(disk->driveId))
    {
        disk->error = FILEIO_ERROR_WRITE_PROTECTED;
        return 0;
    }

    while (length != 0)
    {
        if (filePtr->currentOffset == disk->sectorSize)
        {
            filePtr->currentOffset = 0;
            filePtr->currentSector++;
            if (filePtr->currentSector == disk->sectorsPerCluster)
            {
                uint32_t tempCluster = filePtr->currentCluster;
                filePtr->currentSector = 0;
                // Load/allocate the next cluster
                if ((error = FILEIO_NextClusterGet (filePtr, 1)) == FILEIO_ERROR_EOF)
                {
                    filePtr->currentCluster = tempCluster;
                    // Allocate a new cluster
                    error = FILEIO_ClusterAllocate (disk, &filePtr->currentCluster, false);
                }

                if (error != FILEIO_ERROR_NONE)
                {
                    disk->error = error;
                    return dataWritten;
                }
            }
        }

        currentSector = FILEIO_ClusterToSector (disk, filePtr->currentCluster);
        currentSector += filePtr->currentSector;

        // Cache the required sector, if necessary
        if (disk->bufferStatusPtr->dataBufferCachedSector != currentSector)
        {
            if (!FILEIO_FlushBuffer (disk, FILEIO_BUFFER_DATA))
            {
                disk->error = FILEIO_ERROR_WRITE;
                return FILEIO_ERROR_WRITE;
            }

//            if ((*disk->driveConfig->funcSectorRead) (disk->mediaParameters, currentSector, disk->dataBuffer) != true)
            if (FILEIO_DRIVER_SectorRead(disk->driveId, currentSector, disk->dataBuffer) != true)
            {
                disk->error = FILEIO_ERROR_BAD_SECTOR_READ;
                return dataWritten;
            }
            else
            {
                disk->bufferStatusPtr->dataBufferCachedSector = currentSector;
#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
                disk->bufferStatusPtr->driveOwner = disk;
#endif
            }
        }

        writeCount = ((disk->sectorSize - filePtr->currentOffset) > length) ? length : (disk->sectorSize - filePtr->currentOffset);
        memcpy (disk->dataBuffer + filePtr->currentOffset, data, writeCount);
        disk->bufferStatusPtr->flags.dataBufferNeedsWrite = true;
        data += writeCount;
        filePtr->currentOffset += writeCount;
        dataWritten += writeCount;
        length -= writeCount;
    }

    filePtr->size += dataWritten;
    filePtr->absoluteOffset += dataWritten;

    return dataWritten;
}
#endif

size_t FILEIO_Read (void * buffer, size_t size, size_t count, FILEIO_OBJECT * filePtr)
{
    FILEIO_ERROR_TYPE error;
    uint8_t * data = (uint8_t *) buffer;
    FILEIO_DRIVE * disk = filePtr->disk;
    uint32_t currentSector;
    size_t dataRead = 0;
    uint16_t readCount;
    size_t length = size * count;

    if (!filePtr->flags.readEnabled)
    {
        disk->error = FILEIO_ERROR_WRITE_ONLY;
        return 0;
    }

    // Check to see if the user has passed in no data
    if (length == 0)
    {
        disk->error = FILEIO_ERROR_NONE;
        return 0;
    }

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (FILEIO_GetSingleBuffer (disk) != FILEIO_RESULT_SUCCESS)
    {
        return 0;
    }
#endif

    while (length != 0)
    {
        if (filePtr->currentOffset == disk->sectorSize)
        {
            filePtr->currentOffset = 0;
            filePtr->currentSector++;
            if (filePtr->currentSector == disk->sectorsPerCluster)
            {
                filePtr->currentSector = 0;
                // Load/allocate the next cluster
                error = FILEIO_NextClusterGet (filePtr, 1);

                if (error != FILEIO_ERROR_NONE)
                {
                    disk->error = error;
                    return dataRead;
                }
            }
        }

        currentSector = FILEIO_ClusterToSector (disk, filePtr->currentCluster);
        currentSector += filePtr->currentSector;

        // Cache the required sector, if necessary
        if (disk->bufferStatusPtr->dataBufferCachedSector != currentSector)
        {
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
            if (!FILEIO_FlushBuffer (disk, FILEIO_BUFFER_DATA))
            {
                disk->error = FILEIO_ERROR_WRITE;
                return FILEIO_ERROR_WRITE;
            }
#endif

            //if ((*disk->driveConfig->funcSectorRead) (disk->mediaParameters, currentSector, disk->dataBuffer) != true)
            if(FILEIO_DRIVER_SectorRead(disk->driveId, currentSector, disk->dataBuffer) != true)
            {
                disk->error = FILEIO_ERROR_BAD_SECTOR_READ;
                return dataRead;
            }
            else
            {
                disk->bufferStatusPtr->dataBufferCachedSector = currentSector;
#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
                disk->bufferStatusPtr->driveOwner = disk;
#endif
            }
        }

        readCount = ((disk->sectorSize - filePtr->currentOffset) > length) ? length : (disk->sectorSize - filePtr->currentOffset);
        if ((filePtr->size - filePtr->absoluteOffset) < readCount)
        {
            readCount = filePtr->size - filePtr->absoluteOffset;
            length = readCount;
        }
        memcpy (data, disk->dataBuffer + filePtr->currentOffset, readCount);
        data += readCount;
        filePtr->currentOffset += readCount;
        filePtr->absoluteOffset += readCount;
        dataRead += readCount;
        length -= readCount;
    }

    return dataRead;
}

bool FILEIO_Eof (FILEIO_OBJECT * filePtr)
{
    return (filePtr->absoluteOffset == filePtr->size) ? true : false;
}

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
int FILEIO_Remove (const uint16_t * pathName)
{
    FILEIO_OBJECT file;
    FILEIO_OBJECT * filePtr = &file;
    FILEIO_ERROR_TYPE error;
    uint16_t entryHandle;
    FILEIO_DIRECTORY directory;
    uint8_t fileNameType;
    uint32_t currentCluster;
    uint16_t currentClusterOffset = 0;
    uint16_t * fileName;

    fileName = (uint16_t *)FILEIO_CacheDirectory (&directory, (uint16_t *)pathName, false);

    if (fileName == NULL)
    {
        globalParameters.currentWorkingDirectory.drive->error = FILEIO_ERROR_INVALID_ARGUMENT;
        return FILEIO_RESULT_FAILURE;
    }

    currentCluster = directory.cluster;

   // if((*directory.drive->driveConfig->funcWriteProtectGet)(directory.drive->mediaParameters))
     if(FILEIO_DRIVER_WriteProtectStateGet(directory.drive->driveId))
    {
        directory.drive->error = FILEIO_ERROR_WRITE_PROTECTED;
        return FILEIO_RESULT_FAILURE;
    }

    fileNameType = FILEIO_FileNameTypeGet(fileName, false);

    if ((fileNameType == FILEIO_NAME_INVALID) || (fileNameType == FILEIO_NAME_DOT))
    {
        directory.drive->error = FILEIO_ERROR_INVALID_FILENAME;
        return FILEIO_RESULT_FAILURE;
    }
    else if (fileNameType == FILEIO_NAME_SHORT)
    {
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        // Short file name
        FILEIO_FormatShortFileName (fileName, filePtr);
        // Search in 'directory' for an entry matching filePtr->name, starting at entry 0 in directory->cluster and returning the result in filePtr
        error = FILEIO_FindShortFileName (&directory, filePtr, (uint8_t *)filePtr->name, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);
    }
    else if (fileNameType == FILEIO_NAME_LONG)
    {
        // Long file name
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        // Search in 'directory' for an entry matching fileName, starting at entry 0 in directory->cluster and returning the short file name in filePtr.
        // The long file name will be cached in lfnData
        filePtr->lfnPtr = (uint16_t *)fileName;
        filePtr->lfnLen = FILEIO_strlen16 ((uint16_t *)fileName);
        error = FILEIO_FindLongFileName (&directory, filePtr, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);
    }

    if (error == FILEIO_ERROR_NONE)
    {
        entryHandle = filePtr->entry;
        if ((filePtr->attributes & FILEIO_ATTRIBUTE_DIRECTORY) == FILEIO_ATTRIBUTE_DIRECTORY)
        {
            error = FILEIO_ERROR_DELETE_DIR;
        }
        else
        {
            error = FILEIO_EraseFile (filePtr, &entryHandle, true);
        }
    }

    // Check to ensure no errors occured
    if (error != FILEIO_ERROR_NONE)
    {
        directory.drive->error = error;
        return FILEIO_RESULT_FAILURE;
    }

    return FILEIO_RESULT_SUCCESS;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
int FILEIO_Rename (const uint16_t * oldPathname, const uint16_t * newFilename)
{
    FILEIO_OBJECT file;
    FILEIO_OBJECT * filePtr = &file;
    FILEIO_ERROR_TYPE error;
    FILEIO_DIRECTORY_ENTRY * entry;
    uint16_t entryHandle;
    FILEIO_DIRECTORY directory;
    uint8_t oldFileNameType;
    uint8_t newFileNameType;
    uint32_t currentCluster;
    uint16_t currentClusterOffset = 0;
    uint16_t * oldFilename;

    oldFilename = FILEIO_CacheDirectory (&directory, (uint16_t *)oldPathname, false);

    if (oldFilename == NULL)
    {
        globalParameters.currentWorkingDirectory.drive->error = FILEIO_ERROR_INVALID_ARGUMENT;
        return FILEIO_RESULT_FAILURE;
    }

    currentCluster = directory.cluster;

//    if((*directory.drive->driveConfig->funcWriteProtectGet)(directory.drive->mediaParameters))
    if(FILEIO_DRIVER_WriteProtectStateGet(directory.drive->driveId))
    {
        directory.drive->error = FILEIO_ERROR_WRITE_PROTECTED;
        return FILEIO_RESULT_FAILURE;
    }

    // Check to see if the new filename already exists
    newFileNameType = FILEIO_FileNameTypeGet(newFilename, false);

    if ((newFileNameType == FILEIO_NAME_INVALID) || (newFileNameType == FILEIO_NAME_DOT))
    {
        directory.drive->error = FILEIO_ERROR_INVALID_FILENAME;
        return FILEIO_RESULT_FAILURE;
    }
    else if (newFileNameType == FILEIO_NAME_SHORT)
    {
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        // Short file name
        FILEIO_FormatShortFileName (newFilename, filePtr);
        // Search in 'directory' for an entry matching filePtr->name, starting at entry 0 in directory->cluster and returning the result in filePtr
        error = FILEIO_FindShortFileName (&directory, filePtr, (uint8_t *)filePtr->name, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);
    }
    else if (newFileNameType == FILEIO_NAME_LONG)
    {
        // Long file name
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        // Search in 'directory' for an entry matching fileName, starting at entry 0 in directory->cluster and returning the short file name in filePtr.
        // The long file name will be cached in lfnData
        filePtr->lfnPtr = (uint16_t *)newFilename;
        filePtr->lfnLen = FILEIO_strlen16 ((uint16_t *)newFilename);
        error = FILEIO_FindLongFileName (&directory, filePtr, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);
    }

    if (error == FILEIO_ERROR_NONE)
    {
        directory.drive->error = FILEIO_ERROR_FILENAME_EXISTS;
        return FILEIO_RESULT_FAILURE;
    }

    // Try to find the old filename
    oldFileNameType = FILEIO_FileNameTypeGet(oldFilename, false);

    currentClusterOffset = 0;
    currentCluster = directory.cluster;

    if ((oldFileNameType == FILEIO_NAME_INVALID) || (oldFileNameType == FILEIO_NAME_DOT))
    {
        directory.drive->error = FILEIO_ERROR_INVALID_FILENAME;
        return FILEIO_RESULT_FAILURE;
    }
    else if (oldFileNameType == FILEIO_NAME_SHORT)
    {
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        // Short file name
        FILEIO_FormatShortFileName (oldFilename, filePtr);
        // Search in 'directory' for an entry matching filePtr->name, starting at entry 0 in directory->cluster and returning the result in filePtr
        error = FILEIO_FindShortFileName (&directory, filePtr, (uint8_t *)filePtr->name, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);
    }
    else if (oldFileNameType == FILEIO_NAME_LONG)
    {
        // Long file name
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        // Search in 'directory' for an entry matching fileName, starting at entry 0 in directory->cluster and returning the short file name in filePtr.
        // The long file name will be cached in lfnData
        filePtr->lfnPtr = (uint16_t *)oldFilename;
        filePtr->lfnLen = FILEIO_strlen16 ((uint16_t *)oldFilename);
        error = FILEIO_FindLongFileName (&directory, filePtr, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);
    }

    // Check to ensure that the old file was found
    if (error != FILEIO_ERROR_NONE)
    {
        directory.drive->error = FILEIO_ERROR_FILE_NOT_FOUND;
        return FILEIO_RESULT_FAILURE;
    }

    entryHandle = filePtr->entry;

    // The file was found.  Replace the name.
    if ((oldFileNameType == FILEIO_NAME_SHORT) && (newFileNameType == FILEIO_NAME_SHORT))
    {
        entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, entryHandle);
        FILEIO_FormatShortFileName (newFilename, filePtr);
        memcpy (entry->name, filePtr->name, FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX);
    }
    else
    {
        uint32_t oldCluster = filePtr->firstCluster;
        uint32_t oldSize = filePtr->size;

        // Erase the previous entry
        if (FILEIO_EraseFile (filePtr, &entryHandle, false) != FILEIO_ERROR_NONE)
        {
            directory.drive->error = FILEIO_ERROR_ERASE_FAIL;
            return FILEIO_RESULT_FAILURE;
        }

        if (newFileNameType == FILEIO_NAME_SHORT)
        {
            // Old file name was long, new file name is short
            FILEIO_FormatShortFileName (newFilename, filePtr);
            filePtr->lfnPtr = NULL;
        }
        else
        {
            // Old file name was short or long, new file name is long
            filePtr->lfnPtr = (uint16_t *)newFilename;
            filePtr->lfnLen = FILEIO_strlen16 ((uint16_t *)newFilename);
        }

        error = FILEIO_DirectoryEntryCreate (filePtr, &entryHandle, filePtr->attributes, false);

        entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, entryHandle);

        // Store the existing file's location and size in the new entry
        entry->firstClusterLow = (oldCluster & 0x0000FFFF);
        entry->firstClusterHigh = (oldCluster & 0x0FFF0000) >> 16;
        entry->fileSize = oldSize;
    }

    directory.drive->bufferStatusPtr->flags.dataBufferNeedsWrite = true;

    // Try to flush the buffer (since the user can't manually flush the data to ensure it writes immediately)
    if (!FILEIO_FlushBuffer (directory.drive, FILEIO_BUFFER_DATA))
    {
        directory.drive->error = FILEIO_ERROR_WRITE;
        return FILEIO_RESULT_FAILURE;
    }

    return FILEIO_RESULT_SUCCESS;
}
#endif

FILEIO_ERROR_TYPE FILEIO_ErrorGet (uint16_t driveId)
{
    FILEIO_DRIVE * drive = FILEIO_CharToDrive (driveId);

    if (drive == NULL)
    {
        return FILEIO_ERROR_DRIVE_NOT_FOUND;
    }
    else
    {
        return drive->error;
    }
}

void FILEIO_ErrorClear (uint16_t driveId)
{
    FILEIO_DRIVE * drive = FILEIO_CharToDrive (driveId);

    if (drive != NULL)
    {
        drive->error = FILEIO_ERROR_NONE;
    }
}

int FILEIO_GetChar (FILEIO_OBJECT * handle)
{
    char c;

    if (FILEIO_Read(&c, 1, 1, handle) != 1)
    {
        return FILEIO_RESULT_FAILURE;
    }
    else
    {
        return (int)c;
    }
}

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
int FILEIO_PutChar (char c, FILEIO_OBJECT * handle)
{
    if (FILEIO_Write(&c, 1, 1, handle) != 1)
    {
        return FILEIO_RESULT_FAILURE;
    }
    else
    {
        return FILEIO_RESULT_SUCCESS;
    }
}
#endif

#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
int FILEIO_DirectoryChange (const uint16_t * path)
{
    uint16_t * finalPath;
    FILEIO_DIRECTORY directory;
    uint16_t pathLen;

    finalPath = FILEIO_CacheDirectory (&directory, (uint16_t *)path, false);

    if (finalPath == NULL)
    {
        return FILEIO_RESULT_FAILURE;
    }

    pathLen = FILEIO_strlen16 (finalPath);

    if (pathLen != 0)
    {
        // Try to change to the final directory
        if (FILEIO_DirectoryChangeSingle (&directory, finalPath) != FILEIO_RESULT_SUCCESS)
        {
            return FILEIO_RESULT_FAILURE;
        }
    }

    // Directory was changed successfully
    globalParameters.currentWorkingDirectory.drive = directory.drive;
    globalParameters.currentWorkingDirectory.cluster = directory.cluster;

    return FILEIO_RESULT_SUCCESS;
}
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
int FILEIO_DirectoryMake (const uint16_t * path)
{
    uint16_t * finalPath;
    FILEIO_DIRECTORY directory;
    uint16_t pathLen;

    finalPath = FILEIO_CacheDirectory (&directory, (uint16_t *)path, true);

    if (finalPath == NULL)
    {
        return FILEIO_RESULT_FAILURE;
    }

    pathLen = FILEIO_strlen16 (finalPath);

    if (pathLen != 0)
    {
        // Try to change to the final directory
        if (FILEIO_DirectoryChangeSingle (&directory, finalPath) != FILEIO_RESULT_SUCCESS)
        {
            // If we couldn't change to that directory, it probably doesn't exist.  Try to create it.
            if (FILEIO_DirectoryMakeSingle (&directory, finalPath) != FILEIO_RESULT_SUCCESS)
            {
                return FILEIO_RESULT_FAILURE;
            }
            // Now that it exists, try to change to it.
            if (FILEIO_DirectoryChangeSingle (&directory, finalPath) != FILEIO_RESULT_SUCCESS)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }
    }

    // Directory were created successfully
    return FILEIO_RESULT_SUCCESS;
}
#endif
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
int FILEIO_DirectoryRemove (const uint16_t * path)
{
    uint16_t * finalPath;
    FILEIO_DIRECTORY_ENTRY * entry;
    FILEIO_ERROR_TYPE error;
    FILEIO_DIRECTORY directory;
    FILEIO_DIRECTORY deletedDirectory;
    uint16_t pathLen;
    uint32_t currentCluster;
    uint16_t currentClusterOffset = 0;
    uint16_t entryOffset = 2;

    finalPath = FILEIO_CacheDirectory (&directory, (uint16_t *)path, false);

    memcpy (&deletedDirectory, &directory, sizeof (FILEIO_DIRECTORY));

    if (finalPath == NULL)
    {
        return FILEIO_RESULT_FAILURE;
    }

//    if ((*directory.drive->driveConfig->funcWriteProtectGet)(directory.drive->mediaParameters))
     if (FILEIO_DRIVER_WriteProtectStateGet(directory.drive->driveId))
    {
        return FILEIO_RESULT_FAILURE;
    }

    // Change to the final directory (if the user didn't terminate the path with a delimiter)
    pathLen = FILEIO_strlen16 (finalPath);
    if (pathLen != 0)
    {
        if (FILEIO_DirectoryChangeSingle (&deletedDirectory, finalPath) != FILEIO_RESULT_SUCCESS)
        {
            return FILEIO_RESULT_FAILURE;
        }
    }

    // Don't let the user try to remove the root directory
    if (deletedDirectory.cluster == deletedDirectory.drive->firstRootCluster)
    {
        return FILEIO_RESULT_FAILURE;
    }

    // Check to make sure that the final directory is empty so we don't orphan files
    currentCluster = deletedDirectory.cluster;
    do
    {
        entry = FILEIO_DirectoryEntryCache (&deletedDirectory, &error, &currentCluster, &currentClusterOffset, entryOffset);
        if (entry == NULL)
        {
            return FILEIO_RESULT_FAILURE;
        }

        if ((entry->name[0] != FILEIO_DIRECTORY_ENTRY_EMPTY) && ((uint8_t)(entry->name[0]) != FILEIO_DIRECTORY_ENTRY_DELETED))
        {
            return FILEIO_RESULT_FAILURE;
        }
    } while (entry->name[0] != FILEIO_DIRECTORY_ENTRY_EMPTY);

    return FILEIO_DirectoryRemoveSingle (&directory, finalPath);
}
#endif
#endif

#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
int FILEIO_DirectoryRemoveSingle (FILEIO_DIRECTORY * directory, uint16_t * path)
{
    FILEIO_OBJECT file;
    FILEIO_ERROR_TYPE error;
    uint8_t fileNameType;
    uint32_t currentCluster = directory->cluster;
    uint16_t currentClusterOffset = 0;

    fileNameType = FILEIO_FileNameTypeGet(path, false);

    if ((fileNameType == FILEIO_NAME_INVALID) || (fileNameType == FILEIO_NAME_DOT))
    {
		directory->drive->error = FILEIO_ERROR_INVALID_FILENAME;
        return FILEIO_RESULT_FAILURE;
    }
    else if (fileNameType == FILEIO_NAME_SHORT)
    {
        currentCluster = directory->cluster;
        currentClusterOffset = 0;
        // Short file name
        FILEIO_FormatShortFileName (path, &file);
        // Search in 'directory' for an entry matching filePtr->name, starting at entry 0 in directory->cluster and returning the result in filePtr
        error = FILEIO_FindShortFileName (directory, &file, (uint8_t *)file.name, &currentCluster, &currentClusterOffset, 0, FILEIO_ATTRIBUTE_MASK, FILEIO_SEARCH_ENTRY_MATCH);
    }
    else if (fileNameType == FILEIO_NAME_LONG)
    {
        // Long file name
        currentCluster = directory->cluster;
        currentClusterOffset = 0;
        // Search in 'directory' for an entry matching fileName, starting at entry 0 in directory->cluster and returning the short file name in filePtr.
        // The long file name will be cached in lfnData
        file.lfnPtr = (uint16_t *)path;
        file.lfnLen = FILEIO_strlen16 ((uint16_t *)path);
        error = FILEIO_FindLongFileName (directory, &file, &currentCluster, &currentClusterOffset, 0, 0, FILEIO_SEARCH_ENTRY_MATCH);

    }

    if (error != FILEIO_ERROR_NONE)
    {
        return FILEIO_RESULT_FAILURE;
    }

    if ((file.attributes & FILEIO_ATTRIBUTE_DIRECTORY) != FILEIO_ATTRIBUTE_DIRECTORY)
    {
        directory->drive->error = FILEIO_ERROR_DELETE_FILE;
        return FILEIO_RESULT_FAILURE;
    }

    if (FILEIO_EraseFile (&file, &file.entry, true) == FILEIO_ERROR_NONE)
    {
        return FILEIO_RESULT_SUCCESS;
    }
    else
    {
        return FILEIO_RESULT_FAILURE;
    }
}
#endif
#endif

#if !defined (FILEIO_CONFIG_DIRECTORY_DISABLE)
uint16_t FILEIO_DirectoryGetCurrent (uint16_t * buffer, uint16_t size)
{
    uint16_t * bufferEnd;
    FILEIO_DRIVE * drive = globalParameters.currentWorkingDirectory.drive;
    uint32_t cluster = globalParameters.currentWorkingDirectory.cluster;
    uint32_t currentCluster, currentClusterTemp;
    uint16_t currentClusterOffset;
    uint16_t entryOffset;
    FILEIO_DIRECTORY directory;
    FILEIO_DIRECTORY_ENTRY * entry;
    FILEIO_ERROR_TYPE error;
    uint32_t tempCluster;
    int16_t i = 0, index = 0, tempIndex;
    int16_t j;
    bool bufferOverflow = false;
    char aChar;
    uint16_t charCount = 0;
    const uint16_t dotdotPath[3] = {'.', '.', 0};
    uint8_t checksum;
    uint8_t * source;

    memcpy (&directory, &globalParameters.currentWorkingDirectory, sizeof (FILEIO_DIRECTORY));

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (FILEIO_GetSingleBuffer (drive) != FILEIO_RESULT_SUCCESS)
    {
        return 0;
    }
#endif

    drive->error = FILEIO_ERROR_NONE;

    // Set up the return value
    if ((buffer == NULL) || (size == 0))
    {
        drive->error = FILEIO_ERROR_INVALID_ARGUMENT;
        return 0;
    }

    bufferEnd = buffer + size - 1;

    // Loop backwards though all subdirectories
    while ((cluster != 0) && (cluster != drive->firstRootCluster))
    {
        // Change to parent directory
        if (FILEIO_DirectoryChangeSingle(&directory, (uint16_t *)dotdotPath) == FILEIO_RESULT_FAILURE)
        {
            drive->error = FILEIO_ERROR_DIR_NOT_FOUND;
            return 0;
        }

        // Try to find the directory entry for our directory
        currentCluster = directory.cluster;
        currentClusterOffset = 0;
        if (currentCluster == 0)
        {
            currentCluster = drive->firstRootCluster;
            entryOffset = 0;
        }
        else
        {
            entryOffset = 2;
        }

        do
        {
            entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, entryOffset++);
            if (((tempCluster = FILEIO_FullClusterNumberGet(entry)) == cluster) && ((uint8_t)entry->name[0] != FILEIO_DIRECTORY_ENTRY_DELETED)&& (entry->attributes != FILEIO_ATTRIBUTE_LONG_NAME))
            {
                break;
            }
        } while ((entry != NULL) && (entry->name[0] != FILEIO_DIRECTORY_ENTRY_EMPTY));

        if (tempCluster != cluster)
        {
            drive->error = FILEIO_ERROR_DIR_NOT_FOUND;
            return 0;
        }

        cluster = directory.cluster;

        checksum = 0;
        source = (uint8_t *)entry->name;

        for (i = 11; i != 0; i--)
        {
            checksum = ((checksum & 1) ? 0x80 : 0) + (checksum >> 1) + *source++;
        }

        currentClusterTemp = currentCluster;
        if (FILEIO_LongFileNameCache(&directory, entryOffset - 1, currentCluster, checksum) == FILEIO_LFN_SUCCESS)
        {
            // The long file name is cached
            // Copy it backwards into the buffer
            j = FILEIO_strlen16 (lfnBuffer);
            charCount += j;
            for (j -= 1; j >= 0; j--)
            {
                *(buffer + index++) = lfnBuffer[j];
                if (index == size)
                {
                    index = 0;
                    bufferOverflow = true;
                }
            }
        }
        else
        {
            // There's no long file name entry associated with this directory
            // Copy and pad the short file name

            // Ensure that the original entry is still cached
            currentCluster = currentClusterTemp;
            entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, entryOffset - 1);

            // We have found our directory in the parent directory.  Copy the name into the buffer (backwards)
            // Copy the extension.
            j = 10;
            while (entry->name[j] == 0x20)
            {
                j--;
            }
            if (j >= 8)
            {
                charCount += (j - 8) + 1;
                while (j >= 8)
                {
                    *(buffer + index++) = entry->name[j--];
                    // This is a circular buffer
                    // Any unaccomadatable values will be overwritten
                    if (index == size)
                    {
                        index = 0;
                        bufferOverflow = true;
                    }
                }

                charCount++;
                *(buffer + index++) = '.';
                if (index == size)
                {
                    index = 0;
                    bufferOverflow = true;
                }
            }

            // Copy the name
            while (entry->name[j] == 0x20)
            {
                j--;
            }

            charCount += j + 1;
            while (j >= 0)
            {
                *(buffer + index++) = entry->name[j--];
                // This is a circular buffer
                // Any unnecessary values will be overwritten
                if (index == size)
                {
                    index = 0;
                    bufferOverflow = true;
                }
            }
        }

        charCount++;
        *(buffer + index++) = FILEIO_CONFIG_DELIMITER;

        if (index == size)
        {
            index = 0;
            bufferOverflow = true;
        }
    }

    charCount += 2;
    // We have reached the root.  Copy the drive ID into the buffer.
    *(buffer + index++) = ':';
    if (index == size)
    {
        index = 0;
        bufferOverflow = true;
    }
    *(buffer + index++) = drive->driveId;
    if (index == size)
    {
        index = 0;
        bufferOverflow = true;
    }


    // Reverse the contents of the buffer.
    // Point the index back at the last char in the string
    if (index == 0)
    {
        index = size;
    }
    else
    {
        index--;
    }

    i = 0;
    // Swap the chars in the buffer so they are in the right places
    if (bufferOverflow)
    {
        tempIndex = index;
        // Swap the overflowed values in the buffer
        while ((index - i) > 0)
        {
             aChar = *(buffer + i);
             *(buffer + i) = * (buffer + index);
             *(buffer + index) = aChar;
             index--;
             i++;
        }

        // Point at the non-overflowed values
        i = tempIndex + 1;
        index = bufferEnd - buffer;

        // Swap the non-overflowed values into the right places
        while ((index - i) > 0)
        {
             aChar = *(buffer + i);
             *(buffer + i) = * (buffer + index);
             *(buffer + index) = aChar;
             index--;
             i++;
        }
        // All the values should be in the right place now
        // Null-terminate the string
        *(bufferEnd) = 0;
    }
    else
    {
        // There was no overflow, just do one set of swaps
        tempIndex = index;
        while ((index - i) > 0)
        {
            aChar = *(buffer + i);
            *(buffer + i) = * (buffer + index);
            *(buffer + index) = aChar;
            index--;
            i++;
        }
        *(buffer + tempIndex + 1) = 0;
    }

    return charCount;
}
#endif

#if !defined (FILEIO_CONFIG_SEARCH_DISABLE)
int FILEIO_Find (const uint16_t * fileName, unsigned int attr, FILEIO_SEARCH_RECORD * record, bool newSearch)
{
    FILEIO_DIRECTORY directory;
    uint8_t fileNameType;
    FILEIO_ERROR_TYPE error;
    FILEIO_OBJECT file;
    uint16_t * fileWithoutDirectory;

    if (newSearch)
    {
        fileWithoutDirectory = FILEIO_CacheDirectory (&directory, (uint16_t *)fileName, false);

        if (fileWithoutDirectory == NULL)
        {
            globalParameters.currentWorkingDirectory.drive->error = FILEIO_ERROR_INVALID_ARGUMENT;
            return FILEIO_RESULT_FAILURE;
        }

        record->pathOffset = fileWithoutDirectory - fileName;
        record->currentClusterOffset = 0;
        record->currentDirCluster = directory.cluster;
        record->baseDirCluster = directory.cluster;
        record->driveId = directory.drive->driveId;
        record->currentEntryOffset = 0;
    }
    else
    {
        directory.drive = FILEIO_CharToDrive (record->driveId);
        directory.cluster = record->baseDirCluster;

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
        if (FILEIO_GetSingleBuffer (directory.drive) != FILEIO_RESULT_SUCCESS)
        {
            directory.drive->error = FILEIO_ERROR_WRITE;
            return FILEIO_RESULT_FAILURE;
        }
#endif

        fileWithoutDirectory = (uint16_t *)fileName + record->pathOffset;
    }

    fileNameType = FILEIO_FileNameTypeGet(fileWithoutDirectory, true);

    if ((fileNameType == FILEIO_NAME_INVALID) || (fileNameType == FILEIO_NAME_DOT))
    {
        directory.drive->error = FILEIO_ERROR_INVALID_FILENAME;
        return FILEIO_RESULT_FAILURE;
    }
    else if (fileNameType == FILEIO_NAME_SHORT)
    {
        // Short file name
        FILEIO_FormatShortFileName (fileWithoutDirectory, &file);
        // Search in 'directory' for an entry matching filePtr->name, starting at entry 0 in directory->cluster and returning the result in filePtr
        error = FILEIO_FindShortFileName (&directory, &file, (uint8_t *)&file.name, &record->currentDirCluster, &record->currentClusterOffset, record->currentEntryOffset, attr, FILEIO_SEARCH_PARTIAL_STRING_SEARCH | FILEIO_SEARCH_ENTRY_ATTRIBUTES);
    }
    else if (fileNameType == FILEIO_NAME_LONG)
    {
        // Long file name
        // Search in 'directory' for an entry matching fileName, starting at entry 0 in directory->cluster and returning the short file name in filePtr.
        // The long file name will be cached in lfnBuffer
        file.lfnPtr = (uint16_t *)fileWithoutDirectory;
        file.lfnLen = FILEIO_strlen16 ((uint16_t *)fileWithoutDirectory);
        error = FILEIO_FindLongFileName (&directory, &file, &record->currentDirCluster, &record->currentClusterOffset, record->currentEntryOffset, attr, FILEIO_SEARCH_PARTIAL_STRING_SEARCH | FILEIO_SEARCH_ENTRY_ATTRIBUTES);
    }

    if (error != FILEIO_ERROR_NONE)
    {
        directory.drive->error = error;
        return FILEIO_RESULT_FAILURE;
    }

    // Copy file data into the result structure
    FILEIO_ShortFileNameConvert ((char *)record->shortFileName, (char *)file.name);
    record->attributes = file.attributes;
    record->fileSize = file.size;
    record->timeStamp.date.value = file.date;
    record->timeStamp.time.value = file.time;
    record->timeStamp.timeMs = file.timeMs;

    record->currentEntryOffset = file.entry + 1;

    return FILEIO_RESULT_SUCCESS;
}
#endif

#if !defined (FILEIO_CONFIG_SEARCH_DISABLE)
int FILEIO_LongFileNameGet (FILEIO_SEARCH_RECORD * record, uint16_t * buffer, uint16_t length)
{
    uint32_t currentCluster = record->currentDirCluster;
    uint16_t currentClusterOffset = record->currentClusterOffset;
    uint16_t entryOffset = record->currentEntryOffset - 1;
    FILEIO_DIRECTORY_ENTRY * entry;
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_DIRECTORY directory;
    uint8_t checksum = 0;
    uint8_t * source;
    uint16_t i;

    directory.cluster = record->baseDirCluster;
    directory.drive = FILEIO_CharToDrive(record->driveId);

    if (directory.drive == NULL)
    {
        globalParameters.currentWorkingDirectory.drive->error = FILEIO_ERROR_INVALID_ARGUMENT;
        return FILEIO_RESULT_FAILURE;
    }

    entry = FILEIO_DirectoryEntryCache (&directory, &error, &currentCluster, &currentClusterOffset, entryOffset);
    if ((entry == NULL) || (error != FILEIO_ERROR_NONE))
    {
        directory.drive->error = error;
        return FILEIO_RESULT_FAILURE;
    }

    checksum = 0;
    source = (uint8_t *)entry->name;

    for (i = 11; i != 0; i--)
    {
        checksum = ((checksum & 1) ? 0x80 : 0) + (checksum >> 1) + *source++;
    }

    if (FILEIO_LongFileNameCache(&directory, entryOffset, currentCluster, checksum) == FILEIO_LFN_SUCCESS)
    {
        i = FILEIO_strlen16 (lfnBuffer);
        if (i < length)
        {
            length = i;
        }

        memcpy (buffer, lfnBuffer, (length + 1) << 1);

        return FILEIO_RESULT_SUCCESS;
    }
    else
    {
        directory.drive->error = FILEIO_ERROR_NO_LONG_FILE_NAME;
        return FILEIO_RESULT_FAILURE;
    }
}
#endif


#if !defined (FILEIO_CONFIG_FORMAT_DISABLE)
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
int FILEIO_CreateMBR (FILEIO_DRIVE_CONFIG * config, void * mediaParameters, uint32_t firstSector, uint32_t sectorCount)
{
    FILEIO_MASTER_BOOT_RECORD * partition;
    FILEIO_BUFFER_STATUS * bufferStatusPtr;
    uint8_t * dataBuffer;
    uint32_t cylHeadSec = 0x00000000;
    uint32_t tempSector;

    if ((firstSector == 0) || (sectorCount <= 1))
        return FILEIO_RESULT_FAILURE;

    if (firstSector > (sectorCount - 1))
        return FILEIO_RESULT_FAILURE;

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    bufferStatusPtr = &bufferStatus;
    dataBuffer = gDataBuffer;

    // Use the last drive's buffer for this operation (it's the least likely to be in use)
    if (bufferStatusPtr->driveOwner != NULL)
    {
        if (bufferStatusPtr->flags.dataBufferNeedsWrite)
        {
            if (! (*((const FILEIO_DRIVE_CONFIG *)(bufferStatusPtr->driveOwner))->funcSectorWrite)(mediaParameters, bufferStatusPtr->dataBufferCachedSector, dataBuffer, false))
            {
                return false;
            }
            bufferStatusPtr->flags.dataBufferNeedsWrite = false;
        }
    }

    bufferStatusPtr->driveOwner = NULL;
#else
    bufferStatusPtr = &bufferStatus[FILEIO_CONFIG_MAX_DRIVES - 1];
    dataBuffer = gDataBuffer[FILEIO_CONFIG_MAX_DRIVES - 1];
    // Use the last drive's buffer for this operation (it's the least likely to be in use)
    if (!gDriveSlotOpen[FILEIO_CONFIG_MAX_DRIVES - 1])
    {
        if (bufferStatusPtr->flags.dataBufferNeedsWrite)
        {
            if (! (*config->funcSectorWrite)(config->mediaParameters, bufferStatusPtr->dataBufferCachedSector, dataBuffer, false))
            {
                return false;
            }
            bufferStatusPtr->flags.dataBufferNeedsWrite = false;
        }
    }
#endif

    bufferStatusPtr->dataBufferCachedSector = 0xFFFFFFFF;

    memset (dataBuffer, 0x00, FILEIO_CONFIG_MEDIA_SECTOR_SIZE);

    partition = (FILEIO_MASTER_BOOT_RECORD *) dataBuffer;

    // Set Cylinder-head-sector address of the first sector
    tempSector = firstSector;
    cylHeadSec = (tempSector / (unsigned int)16065 ) << 14;
    tempSector %= 16065;
    cylHeadSec |= (tempSector / 63) << 6;
    tempSector %= 63;
    cylHeadSec |= tempSector + 1;
    *(dataBuffer + 447) = (uint8_t)((cylHeadSec >> 16) & 0xFF);
    *(dataBuffer + 448) = (uint8_t)((cylHeadSec >> 8) & 0xFF);
    *(dataBuffer + 449) = (uint8_t)((cylHeadSec) & 0xFF);

    // Set the count of sectors
    partition->partition0.sectorCount = sectorCount - firstSector;

    // Set the partition type
    // We only support creating FAT12 and FAT16 MBRs at this time
    if (partition->partition0.sectorCount < 0x1039)
    {
        // FAT12
        partition->partition0.fileSystemDescriptor = 0x01;
    }
    else if (partition->partition0.sectorCount <= 0x3FFD5F)
    {
        // FAT16
        partition->partition0.fileSystemDescriptor = 0x06;
    }
    else
    {
        return FILEIO_RESULT_FAILURE;
    }

    // Set the LBA of the first sector
    partition->partition0.lbaFirstSector = firstSector;

    // Set the Cylinder-head-sector address of the last sector
    tempSector = firstSector + sectorCount - 1;
    cylHeadSec = (tempSector / (unsigned int)16065 ) << 14;
    tempSector %= 16065;
    cylHeadSec |= (tempSector / 63) << 6;
    tempSector %= 63;
    cylHeadSec |= tempSector + 1;
    *(dataBuffer + 451) = (uint8_t)((cylHeadSec >> 16) & 0xFF);
    *(dataBuffer + 452) = (uint8_t)((cylHeadSec >> 8) & 0xFF);
    *(dataBuffer + 453) = (uint8_t)((cylHeadSec) & 0xFF);

    // Set the boot descriptor.  This will be 0, since we won't
    // be booting anything from our device probably
    partition->partition0.bootDescriptor = 0x00;

    // Set the signature codes
    partition->signature0 = 0x55;
    partition->signature1 = 0xAA;

    if (!(*config->funcSectorWrite)(mediaParameters, 0, dataBuffer, true))
    {
        return FILEIO_RESULT_FAILURE;
    }
    else
    {
        return FILEIO_RESULT_SUCCESS;
    }
}
#endif
#endif

#if !defined (FILEIO_CONFIG_FORMAT_DISABLE)
#if !defined (FILEIO_CONFIG_WRITE_DISABLE)
int FILEIO_Format (FILEIO_DRIVE_CONFIG * config, void * mediaParameters, FILEIO_FORMAT_MODE mode, uint32_t serialNumber, char * volumeId)
{
    FILEIO_MASTER_BOOT_RECORD * masterBootRecord;
    uint32_t    sectorCount, dataClusters, rootDirSectors;
    FILEIO_BOOT_SECTOR * bootSec;
    FILEIO_DRIVE d;
    FILEIO_DRIVE * disk = &d;
    uint16_t    j;
    uint32_t   fatSize, test;
    uint32_t index;
    FILEIO_MEDIA_INFORMATION * mediaInfo;
    FILEIO_BUFFER_STATUS * bufferStatusPtr;
    FILEIO_DRIVE * tempDriveOwner;

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    bufferStatusPtr = &bufferStatus;
    d.dataBuffer = gDataBuffer;
    d.fatBuffer = gFATBuffer;

    if (bufferStatusPtr->driveOwner != NULL)
    {
        FILEIO_DRIVE_CONFIG * bufferDriveConfig = (FILEIO_DRIVE_CONFIG *)((FILEIO_DRIVE *)bufferStatusPtr->driveOwner)->driveConfig;

        if (bufferStatusPtr->flags.dataBufferNeedsWrite)
        {
            if (!(*bufferDriveConfig->funcSectorWrite)(mediaParameters, bufferStatusPtr->dataBufferCachedSector, disk->dataBuffer, false))
            {
                return false;
            }
            bufferStatusPtr->flags.dataBufferNeedsWrite = false;
        }
        if (bufferStatusPtr->flags.fatBufferNeedsWrite)
        {
            if (! (*bufferDriveConfig->funcSectorWrite)(mediaParameters, bufferStatusPtr->fatBufferCachedSector, disk->fatBuffer, false))
            {
                return false;
            }
            bufferStatusPtr->flags.fatBufferNeedsWrite = false;
        }
    }
#else
    bufferStatusPtr = &bufferStatus[FILEIO_CONFIG_MAX_DRIVES - 1];
    d.dataBuffer = gDataBuffer[FILEIO_CONFIG_MAX_DRIVES - 1];
    d.fatBuffer = gFatBuffer[FILEIO_CONFIG_MAX_DRIVES - 1];

    if (!gDriveSlotOpen[FILEIO_CONFIG_MAX_DRIVES - 1])
    {
        if (bufferStatusPtr->flags.dataBufferNeedsWrite)
        {
            if (!(*config->funcSectorWrite)(mediaParameters, bufferStatusPtr->dataBufferCachedSector, disk->dataBuffer, false))
            {
                return false;
            }
            bufferStatusPtr->flags.dataBufferNeedsWrite = false;
        }
        if (bufferStatusPtr->flags.fatBufferNeedsWrite)
        {
            if (! (*config->funcSectorWrite)(mediaParameters, bufferStatusPtr->fatBufferCachedSector, disk->fatBuffer, false))
            {
                return false;
            }
            bufferStatusPtr->flags.fatBufferNeedsWrite = false;
        }
    }

#endif

    bufferStatusPtr->dataBufferCachedSector = 0xFFFFFFFF;
    bufferStatusPtr->fatBufferCachedSector = 0xFFFFFFFF;

    disk->bufferStatusPtr = bufferStatusPtr;
    disk->driveConfig = config;

    if(config->funcIOInit != NULL)
    {
        (*config->funcIOInit)(mediaParameters);
    }

#if defined (__XC8__)
    // XC8 cannot parse this operation without several intermediate steps
    {
        FILEIO_DRIVER_MediaInitialize funcMediaInit = config->funcMediaInit;
        void * mediaParameters = config->mediaParameters;

        mediaInfo = (*funcMediaInit)(mediaParameters);
    }
#else
    mediaInfo = (*config->funcMediaInit)(mediaParameters);
#endif
    
    if (mediaInfo->errorCode != MEDIA_NO_ERROR)
    {
        return FILEIO_RESULT_FAILURE;
    }

    if ((*config->funcSectorRead)(mediaParameters, 0x00, disk->dataBuffer) == false)
    {
        return FILEIO_RESULT_FAILURE;
    }

    // Check if the card has no MBR
    bootSec = (FILEIO_BOOT_SECTOR *) disk->dataBuffer;
    if((bootSec->signature0 == FILEIO_FAT_GOOD_SIGN_0) && (bootSec->signature1 == FILEIO_FAT_GOOD_SIGN_1))
    {
        // Technically, the OEM name is not for indication
        // The alternative is to read the CIS from attribute
        // memory.  See the PCMCIA metaformat for more details
#if defined (__XC16__) || defined (__XC32__)
        if ((*(disk->dataBuffer + BSI_FSTYPE ) == 'F') && \
            (*(disk->dataBuffer + BSI_FSTYPE + 1 ) == 'A') && \
            (*(disk->dataBuffer + BSI_FSTYPE + 2 ) == 'T') && \
            (*(disk->dataBuffer + BSI_FSTYPE + 3 ) == '1') && \
            (*(disk->dataBuffer + BSI_BOOTSIG) == 0x29))
#else
        if ((bootSec->biosParameterBlock.fat16.fileSystemType[0] == 'F') && \
            (bootSec->biosParameterBlock.fat16.fileSystemType[1] == 'A') && \
            (bootSec->biosParameterBlock.fat16.fileSystemType[2] == 'T') && \
            (bootSec->biosParameterBlock.fat16.fileSystemType[3] == '1') && \
            (bootSec->biosParameterBlock.fat16.bootSignature == 0x29))
#endif
        {
            /* Mark that we do not have a MBR;
                this is not actualy used - is here only to remove a compilation warning */
            masterBootRecord = (FILEIO_MASTER_BOOT_RECORD *) NULL;
            switch (mode)
            {
                case FILEIO_FORMAT_BOOT_SECTOR:
                    // not enough info to construct our own boot sector
                    return FILEIO_RESULT_FAILURE;
                case FILEIO_FORMAT_ERASE:
                    // We have to determine the operating system, and the
                    // locations and sizes of the root dir and FAT, and the
                    // count of FATs
                    disk->firstPartitionSector = 0;
                    tempDriveOwner = bufferStatusPtr->driveOwner;
                    bufferStatusPtr->driveOwner = disk;
                    if (FILEIO_LoadBootSector (disk) != FILEIO_ERROR_NONE)
                    {
                        bufferStatusPtr->driveOwner = tempDriveOwner;
                        return FILEIO_RESULT_FAILURE;
                    }
                    bufferStatusPtr->driveOwner = tempDriveOwner;
                default:
                    break;
            }
        }
        else
        {
            masterBootRecord = (FILEIO_MASTER_BOOT_RECORD *) disk->dataBuffer;
            disk->firstPartitionSector = masterBootRecord->partition0.lbaFirstSector;
        }
    }
    else
    {
        /* If the signature is not correct, this is neither a MBR, nor a VBR */
        return FILEIO_RESULT_FAILURE;
    }

    switch (mode)
    {
        // True: Rewrite the whole boot sector
        case FILEIO_FORMAT_BOOT_SECTOR:
            sectorCount = masterBootRecord->partition0.sectorCount;

            if (sectorCount < 0x1039)
            {
                disk->type = FILEIO_FILE_SYSTEM_TYPE_FAT12;
                // Format to FAT12 only if there are too few sectors to format
                // as FAT16
                masterBootRecord->partition0.fileSystemDescriptor = 0x01;
                if ((*config->funcSectorWrite) (mediaParameters, 0x00, disk->dataBuffer, true) == false)
                {
                    return FILEIO_RESULT_FAILURE;
                }

                if (sectorCount >= 0x1028)
                {
                    // More than 0x18 sectors for FATs, 0x20 for root dir,
                    // 0x8 reserved, and 0xFED for data
                    // So double the number of sectors in a cluster to reduce
                    // the number of data clusters used
                    disk->sectorsPerCluster = 2;
                }
                else
                {
                    // One sector per cluster
                    disk->sectorsPerCluster = 1;
                }

                // Prepare a boot sector
                memset (disk->dataBuffer, 0x00, FILEIO_CONFIG_MEDIA_SECTOR_SIZE);

                // Last digit of file system name (FAT12   )
                disk->dataBuffer[58] = '2';

                // Calculate the size of the FAT
                fatSize = (sectorCount - 0x21  + (2*disk->sectorsPerCluster));
                test =   (341 * disk->sectorsPerCluster) + 2;
                fatSize = (fatSize + (test-1)) / test;

                disk->fatCopyCount = 0x02;
                disk->rootDirectoryEntryCount = 0x200;

                disk->fatSectorCount = fatSize;
            }
            else if (sectorCount <= 0x3FFD5F)
            {
                disk->type = FILEIO_FILE_SYSTEM_TYPE_FAT16;
                // Format to FAT16
                masterBootRecord->partition0.fileSystemDescriptor = 0x06;
                if ((*config->funcSectorWrite)(mediaParameters, 0x00, disk->dataBuffer, true) == false)
                {
                    return FILEIO_RESULT_FAILURE;
                }

                dataClusters = sectorCount - 0x218;
                // Figure out how many sectors per cluster we need
                disk->sectorsPerCluster = 1;
                while (dataClusters > 0xFFED)
                {
                    disk->sectorsPerCluster *= 2;
                    dataClusters /= 2;
                }
                // This shouldnt happen
                if (disk->sectorsPerCluster > 128)
                {
                    return FILEIO_RESULT_FAILURE;
                }

                // Prepare a boot sector
                memset (disk->dataBuffer, 0x00, FILEIO_CONFIG_MEDIA_SECTOR_SIZE);

                // Last digit of file system name (FAT16   )
                disk->dataBuffer[58] = '6';

                // Calculate the size of the FAT
                fatSize = (sectorCount - 0x21  + (2*disk->sectorsPerCluster));
                test =    (256  * disk->sectorsPerCluster) + 2;
                fatSize = (fatSize + (test-1)) / test;

                disk->fatCopyCount = 0x02;
                disk->rootDirectoryEntryCount = 0x200;

                disk->fatSectorCount = fatSize;
            }
            else
            {
                disk->type = FILEIO_FILE_SYSTEM_TYPE_FAT32;
                // Format to FAT32
                masterBootRecord->partition0.fileSystemDescriptor = 0x0B;
                if ((*config->funcSectorWrite)(mediaParameters, 0x00, disk->dataBuffer, true) == false)
                {
                    return FILEIO_RESULT_FAILURE;
                }

                #ifdef FORMAT_SECTORS_PER_CLUSTER
                    disk->sectorsPerCluster = FORMAT_SECTORS_PER_CLUSTER;
                    dataClusters = sectorCount / disk->sectorsPerCluster;

                    /* FAT32: 65526 < Number of clusters < 4177918 */
                    if ((dataClusters <= 65526) || (dataClusters >= 4177918))
                    {
                        bufferStatusPtr->driveOwner = tempDriveOwner;
                        return FILEIO_RESULT_FAILURE;
                    }
                #else
                    /*  FAT32: 65526 < Number of clusters < 4177918 */
                    dataClusters = sectorCount;
                    // Figure out how many sectors per cluster we need
                    disk->sectorsPerCluster = 1;
                    while (dataClusters > 0x3FBFFE)
                    {
                        disk->sectorsPerCluster *= 2;
                        dataClusters /= 2;
                    }
                #endif
                // Check the cluster size: FAT32 supports 512, 1024, 2048, 4096, 8192, 16K, 32K, 64K
                if (disk->sectorsPerCluster > 128)
                {
                    return FILEIO_RESULT_FAILURE;
                }

                // Prepare a boot sector
                memset (disk->dataBuffer, 0x00, FILEIO_CONFIG_MEDIA_SECTOR_SIZE);

               // Calculate the size of the FAT
                fatSize = (sectorCount - 0x20);
                test =    (128  * disk->sectorsPerCluster) + 1;
                fatSize = (fatSize + (test-1)) / test;

                disk->fatCopyCount = 0x02;
                disk->rootDirectoryEntryCount = 0x200;

                disk->fatSectorCount = fatSize;
            }

            // Non-file system specific values
            disk->dataBuffer[0] = 0xEB;         //Jump instruction
            disk->dataBuffer[1] = 0x3C;
            disk->dataBuffer[2] = 0x90;
            disk->dataBuffer[3] =  'M';         //OEM Name "MCHP FAT"
            disk->dataBuffer[4] =  'C';
            disk->dataBuffer[5] =  'H';
            disk->dataBuffer[6] =  'P';
            disk->dataBuffer[7] =  ' ';
            disk->dataBuffer[8] =  'F';
            disk->dataBuffer[9] =  'A';
            disk->dataBuffer[10] = 'T';

            disk->dataBuffer[11] = 0x00;             //Sector size
            disk->dataBuffer[12] = 0x02;

            disk->dataBuffer[13] = disk->sectorsPerCluster;   //Sectors per cluster

            if ((disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT12) || (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT16))
            {
                disk->dataBuffer[14] = 0x08;         //Reserved sector count
                disk->dataBuffer[15] = 0x00;
                disk->firstFatSector = 0x08 + disk->firstPartitionSector;

                disk->dataBuffer[16] = 0x02;         //number of FATs

                disk->dataBuffer[17] = 0x00;          //Max number of root directory entries - 512 files allowed
                disk->dataBuffer[18] = 0x02;

                disk->dataBuffer[19] = 0x00;         //total sectors
                disk->dataBuffer[20] = 0x00;

                disk->dataBuffer[21] = 0xF8;         //Media Descriptor

                disk->dataBuffer[22] = fatSize & 0xFF;         //Sectors per FAT
                disk->dataBuffer[23] = (fatSize >> 8) & 0xFF;

                disk->dataBuffer[24] = 0x3F;           //Sectors per track
                disk->dataBuffer[25] = 0x00;

                disk->dataBuffer[26] = 0xFF;         //Number of heads
                disk->dataBuffer[27] = 0x00;

                // Hidden sectors = sectors between the MBR and the boot sector
                disk->dataBuffer[28] = (uint8_t)(disk->firstPartitionSector & 0xFF);
                disk->dataBuffer[29] = (uint8_t)((disk->firstPartitionSector / 0x100) & 0xFF);
                disk->dataBuffer[30] = (uint8_t)((disk->firstPartitionSector / 0x10000) & 0xFF);
                disk->dataBuffer[31] = (uint8_t)((disk->firstPartitionSector / 0x1000000) & 0xFF);

                // Total Sectors = same as sectors in the partition from MBR
                disk->dataBuffer[32] = (uint8_t)(sectorCount & 0xFF);
                disk->dataBuffer[33] = (uint8_t)((sectorCount / 0x100) & 0xFF);
                disk->dataBuffer[34] = (uint8_t)((sectorCount / 0x10000) & 0xFF);
                disk->dataBuffer[35] = (uint8_t)((sectorCount / 0x1000000) & 0xFF);

                disk->dataBuffer[36] = 0x00;         // Physical drive number

                disk->dataBuffer[37] = 0x00;         // Reserved (current head)

                disk->dataBuffer[38] = 0x29;         // Signature code

                disk->dataBuffer[39] = (uint8_t)(serialNumber & 0xFF);
                disk->dataBuffer[40] = (uint8_t)((serialNumber / 0x100) & 0xFF);
                disk->dataBuffer[41] = (uint8_t)((serialNumber / 0x10000) & 0xFF);
                disk->dataBuffer[42] = (uint8_t)((serialNumber / 0x1000000) & 0xFF);

                // Volume ID
                if (volumeId != NULL)
                {
                    for (index = 0; (*(volumeId + index) != 0) && (index < 11); index++)
                    {
                        disk->dataBuffer[index + 43] = *(volumeId + index);
                    }
                    while (index < 11)
                    {
                        disk->dataBuffer[43 + index++] = 0x20;
                    }
                }
                else
                {
                    for (index = 0; index < 11; index++)
                    {
                        disk->dataBuffer[index+43] = 0;
                    }
                }

                disk->dataBuffer[54] = 'F';
                disk->dataBuffer[55] = 'A';
                disk->dataBuffer[56] = 'T';
                disk->dataBuffer[57] = '1';
                disk->dataBuffer[59] = ' ';
                disk->dataBuffer[60] = ' ';
                disk->dataBuffer[61] = ' ';

            }
            else //FAT32
            {
                disk->dataBuffer[14] = 0x20;         //Reserved sector count
                disk->dataBuffer[15] = 0x00;
                disk->firstFatSector = 0x20 + disk->firstPartitionSector;

                disk->dataBuffer[16] = 0x02;         //number of FATs

                disk->dataBuffer[17] = 0x00;          //Max number of root directory entries - 512 files allowed
                disk->dataBuffer[18] = 0x00;

                disk->dataBuffer[19] = 0x00;         //total sectors
                disk->dataBuffer[20] = 0x00;

                disk->dataBuffer[21] = 0xF8;         //Media Descriptor

                disk->dataBuffer[22] = 0x00;         //Sectors per FAT
                disk->dataBuffer[23] = 0x00;

                disk->dataBuffer[24] = 0x3F;         //Sectors per track
                disk->dataBuffer[25] = 0x00;

                disk->dataBuffer[26] = 0xFF;         //Number of heads
                disk->dataBuffer[27] = 0x00;

                // Hidden sectors = sectors between the MBR and the boot sector
                disk->dataBuffer[28] = (uint8_t)(disk->firstPartitionSector & 0xFF);
                disk->dataBuffer[29] = (uint8_t)((disk->firstPartitionSector / 0x100) & 0xFF);
                disk->dataBuffer[30] = (uint8_t)((disk->firstPartitionSector / 0x10000) & 0xFF);
                disk->dataBuffer[31] = (uint8_t)((disk->firstPartitionSector / 0x1000000) & 0xFF);

                // Total Sectors = same as sectors in the partition from MBR
                disk->dataBuffer[32] = (uint8_t)(sectorCount & 0xFF);
                disk->dataBuffer[33] = (uint8_t)((sectorCount / 0x100) & 0xFF);
                disk->dataBuffer[34] = (uint8_t)((sectorCount / 0x10000) & 0xFF);
                disk->dataBuffer[35] = (uint8_t)((sectorCount / 0x1000000) & 0xFF);

                disk->dataBuffer[36] = fatSize & 0xFF;         //Sectors per FAT
                disk->dataBuffer[37] = (fatSize >>  8) & 0xFF;
                disk->dataBuffer[38] = (fatSize >> 16) & 0xFF;
                disk->dataBuffer[39] = (fatSize >> 24) & 0xFF;

                disk->dataBuffer[40] = 0x00;         //Active FAT
                disk->dataBuffer[41] = 0x00;

                disk->dataBuffer[42] = 0x00;         //File System version
                disk->dataBuffer[43] = 0x00;

                disk->dataBuffer[44] = 0x02;         //First cluster of the root directory
                disk->dataBuffer[45] = 0x00;
                disk->dataBuffer[46] = 0x00;
                disk->dataBuffer[47] = 0x00;

                disk->dataBuffer[48] = 0x01;         //FSInfo
                disk->dataBuffer[49] = 0x00;

                disk->dataBuffer[50] = 0x00;         //Backup Boot Sector
                disk->dataBuffer[51] = 0x00;

                disk->dataBuffer[52] = 0x00;         //Reserved for future expansion
                disk->dataBuffer[53] = 0x00;
                disk->dataBuffer[54] = 0x00;
                disk->dataBuffer[55] = 0x00;
                disk->dataBuffer[56] = 0x00;
                disk->dataBuffer[57] = 0x00;
                disk->dataBuffer[58] = 0x00;
                disk->dataBuffer[59] = 0x00;
                disk->dataBuffer[60] = 0x00;
                disk->dataBuffer[61] = 0x00;
                disk->dataBuffer[62] = 0x00;
                disk->dataBuffer[63] = 0x00;

                disk->dataBuffer[64] = 0x00;         // Physical drive number

                disk->dataBuffer[65] = 0x00;         // Reserved (current head)

                disk->dataBuffer[66] = 0x29;         // Signature code

                disk->dataBuffer[67] = (uint8_t)(serialNumber & 0xFF);
                disk->dataBuffer[68] = (uint8_t)((serialNumber / 0x100) & 0xFF);
                disk->dataBuffer[69] = (uint8_t)((serialNumber / 0x10000) & 0xFF);
                disk->dataBuffer[70] = (uint8_t)((serialNumber / 0x1000000) & 0xFF);

                // Volume ID
                if (volumeId != NULL)
                {
                    for (index = 0; (*(volumeId + index) != 0) && (index < 11); index++)
                    {
                        disk->dataBuffer[index + 71] = *(volumeId + index);
                    }
                    while (index < 11)
                    {
                        disk->dataBuffer[71 + index++] = 0x20;
                    }
                }
                else
                {
                    memset (disk->dataBuffer, 0x00, 11);
                }

                disk->dataBuffer[82] = 'F';
                disk->dataBuffer[83] = 'A';
                disk->dataBuffer[84] = 'T';
                disk->dataBuffer[85] = '3';
                disk->dataBuffer[86] = '2';
                disk->dataBuffer[87] = ' ';
                disk->dataBuffer[88] = ' ';
                disk->dataBuffer[89] = ' ';


            }

#ifdef __XC8__
            // C18 can't reference a value greater than 256
            // using an array name pointer
            *(dataBufferPointer + 510) = 0x55;
            *(dataBufferPointer + 511) = 0xAA;
#else
            disk->dataBuffer[510] = 0x55;
            disk->dataBuffer[511] = 0xAA;
#endif

            disk->firstRootSector = disk->firstFatSector + (disk->fatCopyCount * disk->fatSectorCount);

            if ((*config->funcSectorWrite) (mediaParameters, disk->firstPartitionSector, disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }

            break;
        case FILEIO_FORMAT_ERASE:
            tempDriveOwner = bufferStatusPtr->driveOwner;
            bufferStatusPtr->driveOwner = disk;
            if (FILEIO_LoadBootSector (disk) != FILEIO_ERROR_NONE)
            {
                bufferStatusPtr->driveOwner = tempDriveOwner;
                return FILEIO_RESULT_FAILURE;
            }
            bufferStatusPtr->driveOwner = tempDriveOwner;
            break;
        default:
            return FILEIO_RESULT_FAILURE;
    }

    // Erase the FAT
    memset (disk->dataBuffer, 0x00, FILEIO_CONFIG_MEDIA_SECTOR_SIZE);

    if (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT32)
    {
        disk->dataBuffer[0] = 0xF8;          //BPB_Media byte value in its low 8 bits, and all other bits are set to 1
        disk->dataBuffer[1] = 0xFF;
        disk->dataBuffer[2] = 0xFF;
        disk->dataBuffer[3] = 0xFF;

        disk->dataBuffer[4] = 0x00;          //Disk is clean and no read/write errors were encountered
        disk->dataBuffer[5] = 0x00;
        disk->dataBuffer[6] = 0x00;
        disk->dataBuffer[7] = 0x0C;

        disk->dataBuffer[8]  = 0xFF;         //Root Directory EOF
        disk->dataBuffer[9]  = 0xFF;
        disk->dataBuffer[10] = 0xFF;
        disk->dataBuffer[11] = 0xFF;

        for (j = disk->fatCopyCount - 1; j != 0xFFFF; j--)
        {
            if ((*config->funcSectorWrite)(mediaParameters, disk->firstFatSector + (j * disk->fatSectorCount), disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }

        memset (disk->dataBuffer, 0x00, 12);

        for (index = disk->firstFatSector + 1; index < (disk->firstFatSector + disk->fatSectorCount); index++)
        {
            for (j = disk->fatCopyCount - 1; j != 0xFFFF; j--)
            {
                if ((*config->funcSectorWrite)(mediaParameters, index + (j * disk->fatSectorCount), disk->dataBuffer, false) == false)
                {
                    return FILEIO_RESULT_FAILURE;
                }
            }
        }

        // Erase the root directory
        for (index = 1; index < disk->sectorsPerCluster; index++)
        {
            if ((*config->funcSectorWrite)(mediaParameters, disk->firstRootSector + index, disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }

        if (volumeId != NULL)
        {
            // Create a drive name entry in the root dir
            index = 0;
            while ((*(volumeId + index) != 0) && (index < 11))
            {
                disk->dataBuffer[index] = *(volumeId + index);
                index++;
            }
            while (index < 11)
            {
                disk->dataBuffer[index++] = ' ';
            }
            disk->dataBuffer[11] = 0x08;
            disk->dataBuffer[17] = 0x11;
            disk->dataBuffer[19] = 0x11;
            disk->dataBuffer[23] = 0x11;

            if ((*config->funcSectorWrite)(mediaParameters, disk->firstRootSector, disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }
        else
        {
            if ((*config->funcSectorWrite)(mediaParameters, disk->firstRootSector, disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }

        return FILEIO_RESULT_SUCCESS;
    }
    else
    {
        disk->dataBuffer[0] = 0xF8;
        disk->dataBuffer[1] = 0xFF;
        disk->dataBuffer[2] = 0xFF;
        if (disk->type == FILEIO_FILE_SYSTEM_TYPE_FAT16)
        {
            disk->dataBuffer[3] = 0xFF;
        }

        for (j = disk->fatCopyCount - 1; j != 0xFFFF; j--)
        {
            if ((*config->funcSectorWrite)(mediaParameters, disk->firstFatSector + (j * disk->fatSectorCount), disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }

        memset (disk->dataBuffer, 0x00, 4);

        for (index = disk->firstFatSector + 1; index < (disk->firstFatSector + disk->fatSectorCount); index++)
        {
            for (j = disk->fatCopyCount - 1; j != 0xFFFF; j--)
            {
                if ((*config->funcSectorWrite)(mediaParameters, index + (j * disk->fatSectorCount), disk->dataBuffer, false) == false)
                {
                    return FILEIO_RESULT_FAILURE;
                }
            }
        }

		// Initialize the sector size
        disk->sectorSize = FILEIO_CONFIG_MEDIA_SECTOR_SIZE;

        // Erase the root directory
        rootDirSectors = ((disk->rootDirectoryEntryCount * 32) + (disk->sectorSize - 1)) / disk->sectorSize;

        for (index = 1; index < rootDirSectors; index++)
        {
            if ((*config->funcSectorWrite)(mediaParameters, disk->firstRootSector + index, disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }

        if (volumeId != NULL)
        {
            // Create a drive name entry in the root dir
            index = 0;
            while ((*(volumeId + index) != 0) && (index < 11))
            {
                disk->dataBuffer[index] = *(volumeId + index);
                index++;
            }
            while (index < 11)
            {
                disk->dataBuffer[index++] = ' ';
            }
            disk->dataBuffer[11] = 0x08;
            disk->dataBuffer[17] = 0x11;
            disk->dataBuffer[19] = 0x11;
            disk->dataBuffer[23] = 0x11;

            if ((*config->funcSectorWrite)(mediaParameters, disk->firstRootSector, disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }
        else
        {
            if ((*config->funcSectorWrite)(mediaParameters, disk->firstRootSector, disk->dataBuffer, false) == false)
            {
                return FILEIO_RESULT_FAILURE;
            }
        }

        return FILEIO_RESULT_SUCCESS;
    }
}
#endif
#endif

#if !defined (FILEIO_CONFIG_DRIVE_PROPERTIES_DISABLE)
void FILEIO_DrivePropertiesGet(FILEIO_DRIVE_PROPERTIES * properties, uint16_t driveId)
{
    uint8_t i;
    uint32_t value = 0x0;
    FILEIO_DRIVE * drive;

    drive = FILEIO_CharToDrive (driveId);
    if (drive == NULL)
    {
        properties->properties_status = FILEIO_GET_PROPERTIES_DRIVE_NOT_MOUNTED;
        return;
    }

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
    if (FILEIO_GetSingleBuffer (drive) != FILEIO_RESULT_SUCCESS)
    {
        properties->properties_status = FILEIO_GET_PROPERTIES_CACHE_ERROR;
        return;
    }
#endif

    if(properties->new_request == true)
    {
        properties->results.free_clusters = 0;
        properties->new_request = false;

        properties->properties_status = FILEIO_GET_PROPERTIES_STILL_WORKING;

        properties->results.disk_format = drive->type;
        properties->results.sector_size = drive->sectorSize;
        properties->results.sectors_per_cluster = drive->sectorsPerCluster;
        properties->results.total_clusters = drive->partitionClusterCount;

        /* Settings based on FAT type */
        switch (drive->type)
        {
            case FILEIO_FILE_SYSTEM_TYPE_FAT32:
                properties->private.EndClusterLimit = FILEIO_CLUSTER_VALUE_FAT32_END;
                properties->private.ClusterFailValue = FILEIO_CLUSTER_VALUE_FAT32_FAIL;
                break;
            case FILEIO_FILE_SYSTEM_TYPE_FAT16:
                properties->private.EndClusterLimit = FILEIO_CLUSTER_VALUE_FAT16_END;
                properties->private.ClusterFailValue = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
                break;
            case FILEIO_FILE_SYSTEM_TYPE_FAT12:
                properties->private.EndClusterLimit = FILEIO_CLUSTER_VALUE_FAT12_END;
                properties->private.ClusterFailValue = FILEIO_CLUSTER_VALUE_FAT16_FAIL;
                break;
        }

        properties->private.c = 2;

        properties->private.curcls = properties->private.c;
        FILEIO_FATRead(drive, properties->private.c);
    }

    if(properties->properties_status != FILEIO_GET_PROPERTIES_STILL_WORKING)
    {
        return;
    }

    // sequentially scan through the FAT looking for an empty cluster
    for(i=0;i<255;i++)
    {
        // look at its value
        if ( (value = FILEIO_FATRead(drive, properties->private.c)) == properties->private.ClusterFailValue)
        {
            properties->properties_status = FILEIO_GET_PROPERTIES_CLUSTER_FAILURE;
            return;
        }

        // check if empty cluster found
        if (value == FILEIO_CLUSTER_VALUE_EMPTY)
        {
            properties->results.free_clusters++;
        }

        properties->private.c++;    // check next cluster in FAT
        // check if reached last cluster in FAT, re-start from top
        if ((value == properties->private.EndClusterLimit) || (properties->private.c >= (properties->results.total_clusters + 2)))
            properties->private.c = 2;

        // check if full circle done, disk full
        if ( properties->private.c == properties->private.curcls)
        {
            properties->properties_status = FILEIO_GET_PROPERTIES_NO_ERRORS;
            return;
        }
    }  // scanning for an empty cluster

    properties->properties_status = FILEIO_GET_PROPERTIES_STILL_WORKING;
    return;
}
#endif

void FILEIO_ShortFileNameConvert (char * newFileName, char * oldFileName)
{
    uint8_t j = 0;
    while (j < 8)
    {
        if (*oldFileName == 0x20)
        {
            break;
        }
        *newFileName++ = *oldFileName++;
        j++;
    }

    while (j < 8)
    {
        oldFileName++;
        j++;
    }

    if (*oldFileName != 0x20)
    {
        *newFileName++ = '.';
        while (j < 11)
        {
            if (*oldFileName == 0x20)
            {
                break;
            }
            *newFileName++ = *oldFileName++;
            j++;
        }
    }
    *newFileName = 0;
}

#if defined (FILEIO_CONFIG_MULTIPLE_BUFFER_MODE_DISABLE)
int FILEIO_GetSingleBuffer (FILEIO_DRIVE * drive)
{
    if (drive->bufferStatusPtr->driveOwner != drive)
    {
        if (drive != NULL)
        {
            if (!FILEIO_FlushBuffer(drive, FILEIO_BUFFER_FAT))
            {
                return FILEIO_RESULT_FAILURE;
            }
            if (!FILEIO_FlushBuffer(drive, FILEIO_BUFFER_DATA))
            {
                return FILEIO_RESULT_FAILURE;
            }
        }

        drive->bufferStatusPtr->driveOwner = drive;
        drive->bufferStatusPtr->dataBufferCachedSector = 0xFFFFFFFF;
        drive->bufferStatusPtr->fatBufferCachedSector = 0xFFFFFFFF;
    }

    return FILEIO_RESULT_SUCCESS;
}
#endif

FILEIO_ERROR_TYPE FILEIO_FindLongFileName (FILEIO_DIRECTORY * directory, FILEIO_OBJECT * filePtr, uint32_t * currentCluster, uint16_t * currentClusterOffset, uint16_t entryOffset, uint16_t attributes, FILEIO_SEARCH_TYPE mode)
{
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    FILEIO_DIRECTORY_ENTRY * entry;
    uint8_t checksum, i;
    uint8_t * source;
    uint32_t currentClusterTemp;
    uint16_t currentClusterOffsetTemp;

    if (*currentCluster == 0)
    {
        *currentCluster = directory->drive->firstRootCluster;
    }
    
    while(1)
    {
        do
        {
            entry = FILEIO_DirectoryEntryCache (directory, &error, currentCluster, currentClusterOffset, entryOffset);
            if (error == FILEIO_ERROR_DONE)
            {
                break;
            }
            else if (error == FILEIO_ERROR_BAD_CACHE_READ)
            {
                directory->drive->error = FILEIO_ERROR_BAD_CACHE_READ;
                return error;
            }

            entryOffset++;
        } while (((entry->attributes == FILEIO_ATTRIBUTE_LONG_NAME) || (entry->attributes == FILEIO_ATTRIBUTE_VOLUME) || (((uint8_t)entry->name[0]) == FILEIO_DIRECTORY_ENTRY_DELETED)) && (entry->name[0] != FILEIO_DIRECTORY_ENTRY_EMPTY));

        if ((error == FILEIO_ERROR_DONE) || (entry->name[0] == FILEIO_DIRECTORY_ENTRY_EMPTY))
        {
            return FILEIO_ERROR_DONE;
        }

        currentClusterTemp = *currentCluster;
        currentClusterOffsetTemp = *currentClusterOffset;

        // Valid file entry was found
        if (((mode & FILEIO_SEARCH_ENTRY_ATTRIBUTES) != FILEIO_SEARCH_ENTRY_ATTRIBUTES) || ((entry->attributes & attributes) == entry->attributes))
        {
            checksum = 0;
            source = (uint8_t *)entry->name;

            for (i = 11; i != 0; i--)
            {
                checksum = ((checksum & 1) ? 0x80 : 0) + (checksum >> 1) + *source++;
            }

            if (FILEIO_LongFileNameCache(directory, entryOffset - 1, *currentCluster, checksum) == FILEIO_LFN_SUCCESS)
            {
                // File's attributes are valid or we aren't trying to match attributes
                if (FILEIO_LongFileNameCompare (filePtr->lfnPtr, mode) == true)
                {
                    // Recache the short file name entry, just in case the LFN entry spans a cluster boundary
                    entry = FILEIO_DirectoryEntryCache (directory, &error, &currentClusterTemp, &currentClusterOffsetTemp, entryOffset - 1);

                    // Found a match.  Fill the result object with the file data
                    memcpy (filePtr->name, entry->name, FILEIO_FILE_NAME_LENGTH_8P3_NO_RADIX);
                    filePtr->disk = directory->drive;
                    filePtr->firstCluster = FILEIO_FullClusterNumberGet (entry);
                    filePtr->currentCluster = filePtr->firstCluster;
                    filePtr->currentSector = 0;
                    filePtr->currentOffset = 0;
                    filePtr->absoluteOffset = 0;
                    filePtr->size = entry->fileSize;
                    filePtr->attributes = entry->attributes;
                    if ((filePtr->attributes & FILEIO_ATTRIBUTE_DIRECTORY) == FILEIO_ATTRIBUTE_DIRECTORY)
                    {
                        filePtr->timeMs = entry->createTimeMs;
                        filePtr->time = entry->createTime;
                        filePtr->date = entry->createDate;
                    }
                    else
                    {
                        filePtr->time = entry->writeTime;
                        filePtr->date = entry->writeDate;
                    }
                    filePtr->entry = --entryOffset;
                    filePtr->baseClusterDir = directory->cluster;
                    filePtr->currentClusterDir = directory->cluster;

                    return FILEIO_ERROR_NONE;
                }
            }
        }
    }
}

FILEIO_LFN_ERROR FILEIO_LongFileNameCache (FILEIO_DIRECTORY * directory, uint16_t shortEntryOffset, uint32_t currentCluster, uint8_t checksum)
{
    uint16_t i = 0;
    FILEIO_DIRECTORY_ENTRY_LFN * lfnEntry;
    uint16_t entryOffset;
    FILEIO_ERROR_TYPE error = FILEIO_ERROR_NONE;
    uint16_t currentClusterOffset = shortEntryOffset / FILEIO_DIRECTORY_ENTRIES_PER_SECTOR;
    currentClusterOffset /= directory->drive->sectorsPerCluster;

    if (shortEntryOffset == 0)
    {
        return FILEIO_LFN_NONE;
    }

    entryOffset = shortEntryOffset - 1;

    lfnEntry = (FILEIO_DIRECTORY_ENTRY_LFN *)FILEIO_DirectoryEntryCache (directory, &error, &currentCluster, &currentClusterOffset, entryOffset);

    if ((lfnEntry->attributes != FILEIO_ATTRIBUTE_LONG_NAME) || (lfnEntry->checksum != checksum))
    {
        return FILEIO_LFN_NONE;
    }

    while ((error == FILEIO_ERROR_NONE) && (lfnEntry->attributes == FILEIO_ATTRIBUTE_LONG_NAME) && (i < 256) && ((lfnEntry->sequenceNumber & 0x40) != 0x40) && (lfnEntry->checksum == checksum))
    {
        entryOffset--;

        // Copy file data to lfnData array

        memcpy (lfnBuffer + i, &lfnEntry->namePart1, 10);
        i+= 5;
        memcpy (lfnBuffer + i, &lfnEntry->namePart2, 12);
        i += 6;
        memcpy (lfnBuffer + i, &lfnEntry->namePart3, 4);
        i += 2;

        lfnEntry = (FILEIO_DIRECTORY_ENTRY_LFN *)FILEIO_DirectoryEntryCache (directory, &error, &currentCluster, &currentClusterOffset, entryOffset);
    }

    if ((error == FILEIO_ERROR_NONE) && (lfnEntry->attributes == FILEIO_ATTRIBUTE_LONG_NAME) && (i < 256) && (lfnEntry->checksum == checksum))
    {
        memcpy (lfnBuffer + i, &lfnEntry->namePart1, 10);
        i+= 5;
        memcpy (lfnBuffer + i, &lfnEntry->namePart2, 12);
        i += 6;
        memcpy (lfnBuffer + i, &lfnEntry->namePart3, 4);
        i += 2;
    }
    else
    {
        if (error == FILEIO_ERROR_NONE)
        {
            error = FILEIO_ERROR_BAD_CACHE_READ;
        }
    }

    if (error == FILEIO_ERROR_NONE)
    {
        *(lfnBuffer + i) = 0x0000;
        return FILEIO_LFN_SUCCESS;
    }
    else
    {
        return FILEIO_LFN_FAILURE;
    }
}

bool FILEIO_LongFileNameCompare (uint16_t * fileName, FILEIO_SEARCH_TYPE mode)
{
    uint16_t nameLen;

    nameLen = FILEIO_lfnlen (fileName);

    if (nameLen != FILEIO_strlen16 (lfnBuffer))
    {
        return false;
    }

    if ((mode & FILEIO_SEARCH_PARTIAL_STRING_SEARCH) == FILEIO_SEARCH_PARTIAL_STRING_SEARCH)
    {
        uint16_t character;
        uint16_t i = 0;

        while(nameLen-- != 0)
        {
            character = fileName[i];
            if (character == '*')
                break;
            if (character != '?')
            {
                if (character != lfnBuffer[i])
                {
                    return false;
                }
            }
            i++;
        }

        return true;
    }
    else
    {
        if (FILEIO_memcmp16(fileName, lfnBuffer, nameLen) == 0)
        {
            return true;
        }
    }

    return false;
}

uint16_t FILEIO_strlen16 (uint16_t * name)
{
    uint16_t i = 0;

    while (*name++ != 0)
    {
        i++;
    }

    return i;
}

uint16_t FILEIO_lfnlen (uint16_t * name)
{
    uint16_t i = 0;
    uint16_t c;

    while (((c = *name++) != 0) && (c != FILEIO_CONFIG_DELIMITER))
    {
        i++;
    }

    return i;
}

int FILEIO_memcmp16 (uint16_t * name1, uint16_t * name2, uint16_t len)
{
    while (len--)
    {
        if (*name1 != *name2)
        {
            return (*name1 - *name2);
        }
        name1++;
        name2++;
    }

    return 0;
}

uint16_t ReadRam16bit (uint8_t * pBuffer, uint16_t index)
{
    uint16_t res;

    pBuffer += index + 1;
    res = *pBuffer--;
    res <<= 8;
    res |= *pBuffer;
    return res;
}

uint32_t ReadRam32bit(uint8_t * pBuffer, uint16_t index)
{
    uint32_t res;

    pBuffer += index + 3;
    res = *pBuffer--;
    res <<= 8;
    res |= *pBuffer--;
    res <<= 8;
    res |= *pBuffer--;
    res <<= 8;
    res |= *pBuffer;

    return res;
}
