/*********************************************************************
 *
 *   Microchip File System (MPFS) File Access API
 *  Module for Microchip TCP/IP Stack
 *    -Provides single API for accessing web pages and other files 
 *    from internal program memory or an external serial EEPROM memory
 *    -Reference: AN833
 *
 *********************************************************************
 * FileName:        MPFS.c
 * Dependencies:    SPIEEPROM.c, SPIFlash.c, or MPFSImg2.c/.s
 * Processor:       PIC18, PIC24F, PIC24H, dsPIC30F, dsPIC33F, PIC32
 * Compiler:        Microchip C32 v1.05 or higher
 *               Microchip C30 v3.12 or higher
 *               Microchip C18 v3.30 or higher
 *               HI-TECH PICC-18 PRO 9.63PL2 or higher
 * Company:         Microchip Technology, Inc.
 *
 * Software License Agreement
 *
 * Copyright (C) 2002-2009 Microchip Technology Inc.  All rights
 * reserved.
 *
 * Microchip licenses to you the right to use, modify, copy, and
 * distribute:
 * (i)  the Software when embedded on a Microchip microcontroller or
 *      digital signal controller product ("Device") which is
 *      integrated into Licensee's product; or
 * (ii) ONLY the Software driver source files ENC28J60.c, ENC28J60.h,
 *      ENCX24J600.c and ENCX24J600.h ported to a non-Microchip device
 *      used in conjunction with a Microchip ethernet controller for
 *      the sole purpose of interfacing with the ethernet controller.
 *
 * You should refer to the license agreement accompanying this
 * Software for additional information regarding your rights and
 * obligations.
 *
 * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF
 * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
 * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE
 * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER
 * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
 *
 *
 * Author               Date        Comment
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Elliott Wood         07/2007      Complete rewrite as MPFS2
 * E. Wood            04/2008      Updated as MPFS2.1
 * Darren Rook/CCS   9/9/2014       Fixed an off by 1 error in MPFSOpenID()
 ********************************************************************/
#define __MPFS2_C

#ifndef debug_mpfs2
#define debug_mpfs2(a,b,c,d,e,f,g,h,i,j,k,l,m,n)
#else
#define __DO_DEBUG_MPFS2
#endif

#include "TCPIP Stack/TCPIP.h"

#if defined(STACK_USE_MPFS2)

//Supports long file names to 64 characters
#define MAX_FILE_NAME_LEN   (64u)

/*
 * MPFS Structure:
 *     [M][P][F][S]
 *     [BYTE Ver Hi][BYTE Ver Lo][WORD Number of Files]
 *     [Name Hash 0][Name Hash 1]...[Name Hash N]
 *     [File Record 0][File Record 1]...[File Record N]
 *     [String 0][String 1]...[String N]
 *     [File Data 0][File Data 1]...[File Data N]
 *
 * Name Hash (2 bytes):
 *     hash = 0
 *     for each(byte in name)
 *         hash += byte
 *         hash <<= 1
 *
 *     Technically this means the hash only includes the 
 *     final 15 characters of a name.
 *
 * File Record Structure (22 bytes):
 *     [DWORD String Ptr][DWORD Data Ptr]
 *     [DWORD Len][DWORD Timestamp][DWORD Microtime]
 *     [WORD Flags]
 *
 *     Pointers are absolute addresses within the MPFS image.
 *     Timestamp is the UNIX timestamp
 *     Microtime is currently unimplemented
 *
 * String Structure (1 to 64 bytes):
 *     ["path/to/file.ext"][0x00]
 *
 * File Data Structure (arbitrary length):
 *      [File Data]
 *
 * Unlike previous versions, there are no delimiters.
 *
 * Name hash is calculated as follows:
 *      hash = 0
 *      for each(byte in name)
 *          hash += byte, hash <<= 1
 *
 * When a file has an index, that index file has no file name,
 * but is accessible as the file immediately following in the image.
 *
 * Current version is 2.1
 */

/****************************************************************************
  Section:
   Module-Only Globals and Functions
  ***************************************************************************/
  
// Track the MPFS File Handles
// MPFSStubs[0] is reserved for internal use (FAT access)
static MPFS_STUB MPFSStubs[MAX_MPFS_HANDLES+1];

// Allows the MPFS to be locked, preventing access during updates
static BOOL isMPFSLocked;

// FAT record cache
static MPFS_FAT_RECORD fatCache;

// ID of currently loaded fatCache
static WORD fatCacheID;

// Number of files in this MPFS image
static WORD numFiles;


static void _LoadFATRecord(WORD fatID);
static void _Validate(void);

/****************************************************************************
  Section:
   EEPROM vs Flash Storage Settings
  ***************************************************************************/
  
#if defined(MPFS_USE_EEPROM)

   // Beginning address of MPFS Image
   #define MPFS_HEAD      MPFS_RESERVE_BLOCK

   // Track the last read address to prevent unnecessary
   // data overhead to switch locations.
   MPFS_PTR lastRead;


#elif defined(MPFS_USE_SPI_FLASH)

   // Beginning address of MPFS Image
   #define MPFS_HEAD      MPFS_RESERVE_BLOCK
   
#elif !defined(__PCH__) && !defined(__PCD__) //__CCS__ will use #import
   // An address where MPFS data starts in program memory.
    #if defined(__18CXX) || defined(__C32__)
        extern ROM BYTE MPFS_Start[];
       #define MPFS_HEAD      ((DWORD)(&MPFS_Start[0]))
    #else
        extern DWORD MPFS_Start;
        #define MPFS_HEAD      MPFS_Start;
    #endif
   
#endif

/****************************************************************************
  Section:
   Stack-Level Functions
  ***************************************************************************/

/*****************************************************************************
  Function:
   void MPFSInit(void)

  Summary:
   Initializes the MPFS module.

  Description:
   Sets all MPFS handles to closed, and initializes access to the EEPROM
   if necessary.

  Precondition:
   None

  Parameters:
   None

  Returns:
   None
   
  Remarks:
   This function is called only one during lifetime of the application.
  ***************************************************************************/
void MPFSInit(void)
{
   BYTE i;
   
   debug_mpfs2(debug_putc, "MPFSInit()\r\n");
   
   for(i = 1; i <= MAX_MPFS_HANDLES; i++)
   {
      MPFSStubs[i].addr = MPFS_INVALID;
   }
   
   #if defined(MPFS_USE_EEPROM)
    // Initialize the EEPROM access routines.
    XEEInit();
   lastRead = MPFS_INVALID;
   #endif
   
   #if defined(MPFS_USE_SPI_FLASH)
   // Initialize SPI Flash access routines.
   SPIFlashInit();
   #endif

   // Validate the image and load numFiles
   _Validate();

   isMPFSLocked = FALSE;

}

/****************************************************************************
  Section:
   Handle Management Functions
  ***************************************************************************/

/*****************************************************************************
  Function:
   MPFS_HANDLE MPFSOpen(BYTE* cFile)

  Description:
   Opens a file in the MPFS2 file system.
   
  Precondition:
   None

  Parameters:
   cFile - a null terminated file name to open

  Returns:
   An MPFS_HANDLE to the opened file if found, or MPFS_INVALID_HANDLE
   if the file could not be found or no free handles exist.
  ***************************************************************************/
MPFS_HANDLE MPFSOpen(BYTE* cFile)
{
   MPFS_HANDLE hMPFS;
   WORD nameHash, i;
   WORD hashCache[8];
   BYTE *ptr, c;
   
   // Initialize c to avoid "may be used uninitialized" compiler warning
   c = 0;
   
   debug_mpfs2(debug_putc, "MPFSOpen('%s') ", cFile);
   
   // Make sure MPFS is unlocked and we got a filename
   if(*cFile == '\0' || isMPFSLocked == TRUE)
   {
      debug_mpfs2(debug_putc, "INV1\r\n");
      return MPFS_INVALID_HANDLE;
   }

   // Calculate the name hash to speed up searching
   for(nameHash = 0, ptr = cFile; *ptr != '\0'; ptr++)
   {
      nameHash += *ptr;
      nameHash <<= 1;
   }
   
   // Find a free file handle to use
   for(hMPFS = 1; hMPFS <= MAX_MPFS_HANDLES; hMPFS++)
      if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
         break;
   //if(hMPFS == MAX_MPFS_HANDLES)  //__CCS__ __PCH__ __PCD__ this looks like a bug
   if(hMPFS > MAX_MPFS_HANDLES)
   {
      debug_mpfs2(debug_putc, "INV2\r\n");
      return MPFS_INVALID_HANDLE;
   }
      
   // Read in hashes, and check remainder on a match.  Store 8 in cache for performance
   for(i = 0; i < numFiles; i++)
   {
      // For new block of 8, read in data
      if((i & 0x07) == 0u)
      {
         MPFSStubs[0].addr = 8 + i*2;
         MPFSStubs[0].bytesRem = 16;
         MPFSGetArray(0, (BYTE*)hashCache, 16);
      }
      
      // If the hash matches, compare the full filename
      debug_mpfs2(debug_putc, "%LX=%LX? ", hashCache[i&0x07], nameHash);
      
      if(hashCache[(i&0x07)] == nameHash)
      {
         _LoadFATRecord(i);
         MPFSStubs[0].addr = fatCache.string;
         MPFSStubs[0].bytesRem = 255;
         
        #if defined(__DO_DEBUG_MPFS2)
         debug_printf(debug_putc, "_LoadFATRecord(%lu) a=%LX rem=%X -", i, MPFSStubs[0].addr, MPFSStubs[0].bytesRem);
         debug_array(&fatCache, sizeof(fatCache));
         debug_printf(debug_putc, "- ");
        #endif
        
         // Loop over filename to perform comparison
         for(ptr = cFile; *ptr != '\0'; ptr++)
         {           
            MPFSGet(0, &c);

           #if defined(__DO_DEBUG_MPFS2)
            if (c)
            {
               debug_mpfs2(debug_putc, "%c", c);
            }
           #endif
            
            if(*ptr != c)
               break;
         }

         debug_mpfs2(debug_putc, " 0x%X.0x%X ", c, *ptr);
         
         MPFSGet(0, &c);
         
         if(c == '\0' && *ptr == '\0')
         {// Filename matches, so return true
            MPFSStubs[hMPFS].addr = fatCache.data;
            MPFSStubs[hMPFS].bytesRem = fatCache.len;
            MPFSStubs[hMPFS].fatID = i;
            debug_mpfs2(debug_putc, "L=%LX N=%LX\r\n", MPFSStubs[hMPFS].addr, MPFSStubs[hMPFS].bytesRem);
            return hMPFS;
         }
      }
   }
   
   // No file name matched, so return nothing
   debug_mpfs2(debug_putc, "INV3\r\n");
   return MPFS_INVALID_HANDLE;
}

/*****************************************************************************
  Function:
   MPFS_HANDLE MPFSOpenROM(ROM BYTE* cFile) 

  Description:
   Opens a file in the MPFS2 file system.
   
  Precondition:
   None

  Parameters:
   cFile - a null terminated file name to open

  Returns:
   An MPFS_HANDLE to the opened file if found, or MPFS_INVALID_HANDLE
   if the file could not be found or no free handles exist.

  Remarks:
   This function is aliased to MPFSOpen on non-PIC18 platforms.
  ***************************************************************************/
#if defined(__18CXX)
MPFS_HANDLE MPFSOpenROM(ROM BYTE* cFile) 
{
   MPFS_HANDLE hMPFS;
   WORD nameHash, i;
   WORD hashCache[8];
   ROM BYTE *ptr;
   BYTE c;
   
   debug_mpfs2(debug_putc, "MPFSOpenROM('%s')\r\n", cFile);
   
   // Make sure MPFS is unlocked and we got a filename
   if(*cFile == '\0' || isMPFSLocked == TRUE)
   {
      debug_mpfs2(debug_putc, "MPFSOpenROM() MPFS_INVALID_HANDLE0\r\n");
      return MPFS_INVALID_HANDLE;
   }

   // Calculate the name hash to speed up searching
   for(nameHash = 0, ptr = cFile; *ptr != '\0'; ptr++)
   {
      nameHash += *ptr;
      nameHash <<= 1;
   }
   
   // Find a free file handle to use
   for(hMPFS = 1; hMPFS <= MAX_MPFS_HANDLES; hMPFS++)
      if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
         break;
   //if(hMPFS == MAX_MPFS_HANDLES)  //__CCS__ __PCH__ __PCD__ this looks like a bug
   if(hMPFS > MAX_MPFS_HANDLES)         
   {
      debug_mpfs2(debug_putc, "MPFSOpenROM() MPFS_INVALID_HANDLE1\r\n");
      return MPFS_INVALID_HANDLE;
   }
      
   // Read in hashes, and check remainder on a match.  Store 8 in cache for performance
   for(i = 0; i < numFiles; i++)
   {
      // For new block of 8, read in data
      if((i & 0x07) == 0u)
      {
         MPFSStubs[0].addr = 8 + i*2;
         MPFSStubs[0].bytesRem = 16;
         MPFSGetArray(0, (BYTE*)hashCache, 16);
      }
      
      // If the hash matches, compare the full filename
      if(hashCache[(i&0x07)] == nameHash)
      {
         _LoadFATRecord(i);
         MPFSStubs[0].addr = fatCache.string;
         MPFSStubs[0].bytesRem = 255;
         
         // Loop over filename to perform comparison
         for(ptr = cFile; *ptr != '\0'; ptr++)
         {
            MPFSGet(0, &c);
            if(*ptr != c)
               break;
         }
         
         MPFSGet(0, &c);

         if(c == '\0' && *ptr == '\0')
         {// Filename matches, so return true
            MPFSStubs[hMPFS].addr = fatCache.data;
            MPFSStubs[hMPFS].bytesRem = fatCache.len;
            MPFSStubs[hMPFS].fatID = i;
            debug_mpfs2(debug_putc, "MPFSOpenROM() FOUND!\r\n");
            return hMPFS;
         }
      }
   }
   
   // No file name matched, so return nothing
   debug_mpfs2(debug_putc, "MPFSOpenROM() MPFS_INVALID_HANDLE2\r\n");
   return MPFS_INVALID_HANDLE;
}
#endif

/*****************************************************************************
  Function:
   MPFS_HANDLE MPFSOpenID(WORD hFatID)

  Summary:
   Quickly re-opens a file.

  Description:
   Quickly re-opens a file in the MPFS2 file system.  Use this function
   along with MPFSGetID() to quickly re-open a file without tying up
   a permanent MPFSStub.
   
  Precondition:
   None

  Parameters:
   hFatID - the ID of a previous opened file in the FAT

  Returns:
   An MPFS_HANDLE to the opened file if found, or MPFS_INVALID_HANDLE
   if the file could not be found or no free handles exist.
  ***************************************************************************/
MPFS_HANDLE MPFSOpenID(WORD hFatID)
{
   MPFS_HANDLE hMPFS;
   
   // Make sure MPFS is unlocked and we got a valid id
   if(isMPFSLocked == TRUE || hFatID > numFiles)
      return MPFS_INVALID_HANDLE;

   // Find a free file handle to use
   for(hMPFS = 1; hMPFS <= MAX_MPFS_HANDLES; hMPFS++)
      if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
         break;
   //if(hMPFS == MAX_MPFS_HANDLES)
   if(hMPFS > MAX_MPFS_HANDLES) //ccs fix
      return MPFS_INVALID_HANDLE;
   
   // Load the FAT record
   _LoadFATRecord(hFatID);
      
   // Set up the file handle
   MPFSStubs[hMPFS].fatID = hFatID;
   MPFSStubs[hMPFS].addr = fatCache.data;
   MPFSStubs[hMPFS].bytesRem = fatCache.len;
   
   return hMPFS;
}

/*****************************************************************************
  Function:
   void MPFSClose(MPFS_HANDLE hMPFS)

  Summary:
   Closes a file.

  Description:
   Closes a file and releases its stub back to the pool of available 
   handles.
   
  Precondition:
   None

  Parameters:
   hMPFS - the file handle to be closed

  Returns:
   None
  ***************************************************************************/
void MPFSClose(MPFS_HANDLE hMPFS)
{
   if(hMPFS != 0u && hMPFS <= MAX_MPFS_HANDLES)
       MPFSStubs[hMPFS].addr = MPFS_INVALID;
}


/****************************************************************************
  Section:
   Data Reading Functions
  ***************************************************************************/

/*****************************************************************************
  Function:
   BOOL MPFSGet(MPFS_HANDLE hMPFS, BYTE* c)

  Description:
   Reads a byte from a file.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read
   c - Where to store the byte that was read

  Return Values:
   TRUE - The byte was successfully read
   FALSE - No byte was read because either the handle was invalid or
           the end of the file has been reached.
  ***************************************************************************/
BOOL MPFSGet(MPFS_HANDLE hMPFS, BYTE* c)
{

  #if (defined(__PCD__) || defined(__PCH__)) //__CCS__
   return(MPFSGetArray(hMPFS, c, 1) == 1);
  #else
   // Make sure we're reading a valid address
   if(hMPFS > MAX_MPFS_HANDLES)
      return FALSE;
   if(   MPFSStubs[hMPFS].addr == MPFS_INVALID ||
      MPFSStubs[hMPFS].bytesRem == 0u)
      return FALSE;

   if(c == NULL)
   {
      MPFSStubs[hMPFS].addr++;
      MPFSStubs[hMPFS].bytesRem--;
      return TRUE;
   }


    // Read function for EEPROM
    #if defined(MPFS_USE_EEPROM)
       // For performance, cache the last read address
      if(MPFSStubs[hMPFS].addr != lastRead+1)
         XEEBeginRead(MPFSStubs[hMPFS].addr + MPFS_HEAD);
      *c = XEERead();
      lastRead = MPFSStubs[hMPFS].addr;
      MPFSStubs[hMPFS].addr++;
   #elif defined(MPFS_USE_SPI_FLASH)
      SPIFlashReadArray(MPFSStubs[hMPFS].addr + MPFS_HEAD, c, 1);
      MPFSStubs[hMPFS].addr++;
   #else
      #if defined(__C30__)
      {
         DWORD addr;
         DWORD_VAL read;
         BYTE i;
   
         // MPFS Images are addressed by the byte; Program memory by the word.
         //
         // Flash program memory is 24 bits wide and only even words are
         // implemented.  The upper byte of the upper word is read as 0x00.
         // Address in program memory of any given byte is (MPFSAddr * 2) / 3
         //
         // We will read 24 bits at a time, but need to support using only 
         // fractions of the first and last byte.
         
         // Find the beginning address in program memory.
         addr = (MPFSStubs[hMPFS].addr / 3) << 1;
         
         // Find where to start in that first 3 bytes
         read.Val = (addr * 3) >> 1;
         if(read.Val == MPFSStubs[hMPFS].addr)
            i = 0;
         else if(read.Val+1 == MPFSStubs[hMPFS].addr)
            i = 1;
         else
            i = 2;
   
         // Add in the MPFS starting address offset
         addr += MPFS_HEAD;
         
         // Update the MPFS Handle
         MPFSStubs[hMPFS].addr++;
         
         // Read the DWORD 
         read.Val = ReadProgramMemory(addr & 0x00FFFFFF);
         *c = read.v[i];
         
      }
      #else
      {
         DWORD dwHITECHWorkaround = MPFS_HEAD;
        *c = *((ROM BYTE*)(MPFSStubs[hMPFS].addr+dwHITECHWorkaround));
          MPFSStubs[hMPFS].addr++;
      }
      #endif
   #endif
   
   MPFSStubs[hMPFS].bytesRem--;
   return TRUE;
  #endif
}

/*****************************************************************************
  Function:
   WORD MPFSGetArray(MPFS_HANDLE hMPFS, BYTE* cData, WORD wLen)

  Description:
   Reads a series of bytes from a file.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read
   cData - where to store the bytes that were read
   wLen - how many bytes to read

  Returns:
   The number of bytes successfully read.  If this is less than wLen, 
   an EOF occurred while attempting to read.
  ***************************************************************************/
WORD MPFSGetArray(MPFS_HANDLE hMPFS, BYTE* cData, WORD wLen)
{   
   // Make sure we're reading a valid address
   if(hMPFS > MAX_MPFS_HANDLES)
      return 0;
      
   // Determine how many we can actually read
   if(wLen > MPFSStubs[hMPFS].bytesRem)
      wLen = MPFSStubs[hMPFS].bytesRem;

   // Make sure we're reading a valid address
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID || wLen == 0u)
      return 0;
      
   if(cData == NULL)
   {
      MPFSStubs[hMPFS].addr += wLen;
      MPFSStubs[hMPFS].bytesRem -= wLen;
      return wLen;
   }
   
   // Read the data
   #if defined(MPFS_USE_EEPROM)
      XEEReadArray(MPFSStubs[hMPFS].addr+MPFS_HEAD, cData, wLen);
      MPFSStubs[hMPFS].addr += wLen;
      MPFSStubs[hMPFS].bytesRem -= wLen;
      lastRead = MPFS_INVALID;
   #elif defined(MPFS_USE_SPI_FLASH)
      SPIFlashReadArray(MPFSStubs[hMPFS].addr+MPFS_HEAD, cData, wLen);
      MPFSStubs[hMPFS].addr += wLen;
      MPFSStubs[hMPFS].bytesRem -= wLen;
   #else
      #if defined(__C30__)
      {
         DWORD addr;
         DWORD_VAL read;
         WORD count;
         BYTE i;
   
         // MPFS Images are addressed by the byte; Program memory by the word.
         //
         // Flash program memory is 24 bits wide and only even words are
         // implemented.  The upper byte of the upper word is read as 0x00.
         // Address in program memory of any given byte is (MPFSAddr * 2) / 3
         //
         // We will read 24 bits at a time, but need to support using only 
         // fractions of the first and last byte.
         
         // Find the beginning address in program memory.
         addr = (MPFSStubs[hMPFS].addr / 3) << 1;
         
         // Find where to start in that first 3 bytes
         read.Val = (addr * 3) >> 1;
         if(read.Val == MPFSStubs[hMPFS].addr)
            i = 0;
         else if(read.Val+1 == MPFSStubs[hMPFS].addr)
            i = 1;
         else
            i = 2;
   
         // Add in the MPFS starting address offset
         addr += MPFS_HEAD;
         
         // Update the MPFS Handle
         MPFSStubs[hMPFS].addr += wLen;
         MPFSStubs[hMPFS].bytesRem -= wLen;
   
         // Read the first DWORD 
         read.Val = ReadProgramMemory(addr & 0x00FFFFFF);
         addr += 2;
   
         // Copy values as needed
         for(count = wLen; count > 0; cData++, count--)
         {
            // Copy the next value in
            *cData = read.v[i++];
            
            // Check if a new DWORD is needed
            if(i == 3 && count != 1)
            {// Read in a new DWORD
               read.Val = ReadProgramMemory(addr & 0x00FFFFFF);
               addr += 2;
               i = 0;
            }
         }
      }
      #else
      {
         DWORD dwHITECHWorkaround = MPFS_HEAD;
         memcpypgm2ram(cData, (ROM void*)(MPFSStubs[hMPFS].addr + dwHITECHWorkaround), wLen);
         MPFSStubs[hMPFS].addr += wLen;
         MPFSStubs[hMPFS].bytesRem -= wLen;
      }
      #endif
   #endif
   
   return wLen;
}

/*****************************************************************************
  Function:
   BOOL MPFSGetLong(MPFS_HANDLE hMPFS, DWORD* ul)

  Description:
   Reads a DWORD or Long value from the MPFS.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read
   ul - where to store the DWORD or long value that was read

  Returns:
   TRUE - The byte was successfully read
   FALSE - No byte was read because either the handle was invalid or
           the end of the file has been reached.
  ***************************************************************************/
BOOL MPFSGetLong(MPFS_HANDLE hMPFS, DWORD* ul)
{
   return ( MPFSGetArray(hMPFS, (BYTE*)ul, 4) == 4u );
}

/*****************************************************************************
  Function:
   BOOL MPFSSeek(MPFS_HANDLE hMPFS, DWORD dwOffset, MPFS_SEEK_MODE tMode)

  Description:
   Moves the current read pointer to a new location.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle to seek with
   dwOffset - offset from the specified position in the specified direction
   tMode - one of the MPFS_SEEK_MODE constants

  Returns:
   TRUE - the seek was successful
   FALSE - either the new location or the handle itself was invalid
  ***************************************************************************/
BOOL MPFSSeek(MPFS_HANDLE hMPFS, DWORD dwOffset, MPFS_SEEK_MODE tMode)
{
   DWORD temp;
   
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return FALSE;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return FALSE;

   switch(tMode)
   {
      // Seek offset bytes from start
      case MPFS_SEEK_START:
         temp = MPFSGetSize(hMPFS);
         if(dwOffset > temp)
            return FALSE;
         
         MPFSStubs[hMPFS].addr = MPFSGetStartAddr(hMPFS) + dwOffset;
         MPFSStubs[hMPFS].bytesRem = temp - dwOffset;
         return TRUE;
      
      // Seek forwards offset bytes
      case MPFS_SEEK_FORWARD:
         if(dwOffset > MPFSStubs[hMPFS].bytesRem)
            return FALSE;
         
         MPFSStubs[hMPFS].addr += dwOffset;
         MPFSStubs[hMPFS].bytesRem -= dwOffset;
         return TRUE;
      
      // Seek backwards offset bytes
      case MPFS_SEEK_REWIND:
         temp = MPFSGetStartAddr(hMPFS);
         if(MPFSStubs[hMPFS].addr < temp + dwOffset)
            return FALSE;
         
         MPFSStubs[hMPFS].addr -= dwOffset;
         MPFSStubs[hMPFS].bytesRem += dwOffset;
         return TRUE;
      
      // Seek so that offset bytes remain in file
      case MPFS_SEEK_END:
         temp = MPFSGetSize(hMPFS);
         if(dwOffset > temp)
            return FALSE;
         
         MPFSStubs[hMPFS].addr = MPFSGetEndAddr(hMPFS) - dwOffset;
         MPFSStubs[hMPFS].bytesRem = dwOffset;
         return TRUE;
      
      default:
         return FALSE;
   }
}


/****************************************************************************
  Section:
   Data Writing Functions
  ***************************************************************************/

/*****************************************************************************
  Function:
   MPFS_HANDLE MPFSFormat(void)
   
  Summary:
   Prepares the MPFS image for writing.

  Description:
   Prepares the MPFS image for writing and locks the image so that other
   processes may not access it.
   
  Precondition:
   None

  Parameters:
   None

  Returns:
   An MPFS handle that can be used for MPFSPut commands, or 
   MPFS_INVALID_HANDLE when the EEPROM failed to initialize for writing.

  Remarks:
   In order to prevent misreads, the MPFS will be inaccessible until 
   MPFSClose is called.  This function is not available when the MPFS 
   is stored in internal Flash program memory.
  ***************************************************************************/
#if defined(MPFS_USE_EEPROM) || defined(MPFS_USE_SPI_FLASH)
MPFS_HANDLE MPFSFormat(void)
{

   BYTE i;
   
   // Close all files
   for(i = 0; i < MAX_MPFS_HANDLES; i++)
      MPFSStubs[i].addr = MPFS_INVALID;
   
   // Lock the image
   isMPFSLocked = TRUE;
   
   #if defined(MPFS_USE_EEPROM)
      // Set FAT ptr for writing
      MPFSStubs[0].addr = 0;
      MPFSStubs[0].fatID = 0xffff;
      MPFSStubs[0].bytesRem = MPFS_WRITE_PAGE_SIZE - ( ((BYTE)MPFSStubs[0].addr+MPFS_HEAD) & (MPFS_WRITE_PAGE_SIZE-1) );
      
      // Set up EEPROM for writing
      if( XEEBeginWrite(MPFSStubs[0].addr+MPFS_HEAD) == XEE_SUCCESS )
         return 0x00;
   
      return MPFS_INVALID_HANDLE;
   #else
      // Set up SPI Flash for writing
      SPIFlashBeginWrite(MPFS_HEAD);
      return 0x00;
   #endif
}
#endif
   
/*****************************************************************************
  Function:
   WORD MPFSPutArray(MPFS_HANDLE hMPFS, BYTE *cData, WORD wLen)

  Description:
   Writes an array of data to the MPFS image.
   
  Precondition:
   MPFSFormat was sucessfully called.

  Parameters:
   hMPFS - the file handle for writing
   cData - the array of bytes to write
   wLen - how many bytes to write

  Returns:
   The number of bytes successfully written.

  Remarks:
   For EEPROM, the actual write may not initialize until the internal write 
   page is full.  To ensure that previously written data gets stored, 
   MPFSPutEnd must be called after the last call to MPFSPutArray.
  ***************************************************************************/
#if defined(MPFS_USE_EEPROM) || defined(MPFS_USE_SPI_FLASH)
WORD MPFSPutArray(MPFS_HANDLE hMPFS, BYTE* cData, WORD wLen)
{
   #if defined(MPFS_USE_EEPROM)
      // Write to the EEPROM
      WORD count;
      
      for(count = 0; count < wLen; count++)
      {
         XEEWrite(cData[count]);
         
         MPFSStubs[hMPFS].addr++;
         MPFSStubs[hMPFS].bytesRem--;
         
         if(MPFSStubs[hMPFS].bytesRem == 0u)
         {
            MPFSPutEnd(FALSE);
            isMPFSLocked = TRUE;
            XEEBeginWrite(MPFSStubs[hMPFS].addr+MPFS_HEAD);
            MPFSStubs[hMPFS].bytesRem = MPFS_WRITE_PAGE_SIZE;
         }
      }
      
      return count;
   
   #else
      // Write to the SPI Flash
      SPIFlashWriteArray(cData, wLen);
      return wLen;
   #endif
}
#endif

/*****************************************************************************
  Function:
   void MPFSPutEnd(void)

  Description:
   Finalizes an MPFS writing operation.
   
  Precondition:
   MPFSFormat and MPFSPutArray were sucessfully called.

  Parameters:
   final - TRUE if the application is done writing, FALSE if MPFS2 called
      this function locally.

  Returns:
   None
  ***************************************************************************/
#if defined(MPFS_USE_EEPROM) || defined(MPFS_USE_SPI_FLASH)
void MPFSPutEnd(BOOL final)
{
   isMPFSLocked = FALSE;
   
   #if defined(MPFS_USE_EEPROM)
       XEEEndWrite();
       while(XEEIsBusy());
    #endif
    
   if(final)
      _Validate();
}
#endif


/****************************************************************************
  Section:
   Meta Data Accessors
  ***************************************************************************/

/*****************************************************************************
  Function:
   static void _LoadFATRecord(WORD fatID)

  Description:
   Loads the FAT record for a specified handle.
   
  Precondition:
   None

  Parameters:
   fatID - the ID of the file whose FAT is to be loaded

  Returns:
   None

  Remarks:
   The FAT record will be stored in fatCache.
  ***************************************************************************/
static void _LoadFATRecord(WORD fatID)
{
   if(fatID == fatCacheID || fatID >= numFiles)
      return;
   
   // Read the FAT record to the cache
   MPFSStubs[0].bytesRem = 22;
   MPFSStubs[0].addr = 8 + numFiles*2 + fatID*22;
   MPFSGetArray(0, (BYTE*)&fatCache, 22);
   fatCacheID = fatID;
}

/*****************************************************************************
  Function:
   DWORD MPFSGetTimestamp(MPFS_HANDLE hMPFS)

  Description:
   Reads the timestamp for the specified file.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read the metadata

  Returns:
   The timestamp that was read as a DWORD
  ***************************************************************************/
DWORD MPFSGetTimestamp(MPFS_HANDLE hMPFS)
{
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return 0x00000000;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return 0x00000000;
   
   // Move to the point for reading
   _LoadFATRecord(MPFSStubs[hMPFS].fatID);
   return fatCache.timestamp;
}

/*****************************************************************************
  Function:
   DWORD MPFSGetMicrotime(MPFS_HANDLE hMPFS)

  Description:
   Reads the microtime portion of a file's timestamp.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read the metadata

  Returns:
   The microtime that was read as a DWORD
  ***************************************************************************/
DWORD MPFSGetMicrotime(MPFS_HANDLE hMPFS)
{
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return 0x00000000;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return 0x00000000;
   
   // Move to the point for reading
   _LoadFATRecord(MPFSStubs[hMPFS].fatID);
   return fatCache.microtime;
}

/*****************************************************************************
  Function:
   WORD MPFSGetFlags(MPFS_HANDLE hMPFS)

  Description:
   Reads a file's flags.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read the metadata

  Returns:
   The flags that were associated with the file
  ***************************************************************************/
WORD MPFSGetFlags(MPFS_HANDLE hMPFS)
{
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return 0x0000;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return 0x0000;
   
   //move to the point for reading
   _LoadFATRecord(MPFSStubs[hMPFS].fatID);
   return fatCache.flags;
}

/*****************************************************************************
  Function:
   DWORD MPFSGetSize(MPFS_HANDLE hMPFS)

  Description:
   Reads the size of a file.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read the metadata

  Returns:
   The size that was read as a DWORD
  ***************************************************************************/
DWORD MPFSGetSize(MPFS_HANDLE hMPFS)
{
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return 0x00000000;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return 0x00000000;
   
   // Move to the point for reading
   _LoadFATRecord(MPFSStubs[hMPFS].fatID);
   return fatCache.len;
}

/*****************************************************************************
  Function:
   DWORD MPFSGetBytesRem(MPFS_HANDLE hMPFS)

  Description:
   Determines how many bytes remain to be read.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read the metadata

  Returns:
   The number of bytes remaining in the file as a DWORD
  ***************************************************************************/
DWORD MPFSGetBytesRem(MPFS_HANDLE hMPFS)
{
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return 0x00000000;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return 0x00000000;
      
   return MPFSStubs[hMPFS].bytesRem;   
}

/*****************************************************************************
  Function:
   MPFS_PTR MPFSGetStartAddr(MPFS_HANDLE hMPFS)

  Description:
   Reads the starting address of a file.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read the metadata

  Returns:
   The starting address of the file in the MPFS image
  ***************************************************************************/
MPFS_PTR MPFSGetStartAddr(MPFS_HANDLE hMPFS)
{
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return 0;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return MPFS_INVALID;
   
   // Move to the point for reading
   _LoadFATRecord(MPFSStubs[hMPFS].fatID);
   return fatCache.data;
}

/*****************************************************************************
  Function:
   MPFS_PTR MPFSGetEndAddr(MPFS_HANDLE hMPFS)

  Description:
   Determines the ending address of a file.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read the metadata

  Returns:
   The address just after the file ends (start address of next file)
  ***************************************************************************/
MPFS_PTR MPFSGetEndAddr(MPFS_HANDLE hMPFS)
{
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return MPFS_INVALID;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return MPFS_INVALID;
   
   // Move to the point for reading
   _LoadFATRecord(MPFSStubs[hMPFS].fatID);
   return fatCache.data + fatCache.len;
}

/*****************************************************************************
  Function:
   BOOL MPFSGetFilename(MPFS_HANDLE hMPFS, BYTE* cName, WORD wLen)

  Description:
   Reads the file name of a file that is already open.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to determine the file name
   cName - where to store the name of the file
   wLen - the maximum length of data to store in cName

  Return Values:
   TRUE - the file name was successfully located
   FALSE - the file handle provided is not currently open
  ***************************************************************************/
BOOL MPFSGetFilename(MPFS_HANDLE hMPFS, BYTE* cName, WORD wLen)
{
   DWORD addr;
   
   // Make sure a valid file is open
   if(hMPFS > MAX_MPFS_HANDLES)
      return FALSE;
   if(MPFSStubs[hMPFS].addr == MPFS_INVALID)
      return FALSE;
   
   // Move to the point for reading
   _LoadFATRecord(MPFSStubs[hMPFS].fatID);
   addr = fatCache.string;
   MPFSStubs[0].addr = addr;
   MPFSStubs[0].bytesRem = 255;
   
   // Read the value and return
   MPFSGetArray(0, cName, wLen);
   return TRUE;
}

/*****************************************************************************
  Function:
   DWORD MPFSGetPosition(MPFS_HANDLE hMPFS)

  Description:
   Determines the current position in the file
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle for which to determine position

  Returns:
   The position in the file as a DWORD (or MPFS_PTR)

  Remarks:
   Calling MPFSSeek(hMPFS, pos, MPFS_SEEK_START) will return the pointer
   to this position at a later time.  (Where pos is the value returned by
   this function.)
  ***************************************************************************/
DWORD MPFSGetPosition(MPFS_HANDLE hMPFS)
{
   return MPFSStubs[hMPFS].addr - MPFSGetStartAddr(hMPFS);
}

/*****************************************************************************
  Function:
   WORD MPFSGetID(MPFS_HANDLE hMPFS)

  Description:
   Determines the ID in the FAT for a file.
   
  Precondition:
   The file handle referenced by hMPFS is already open.

  Parameters:
   hMPFS - the file handle from which to read the metadata

  Returns:
   The ID in the FAT for this file

  Remarks:
   Use this function in association with MPFSOpenID to quickly access file
   without permanently reserving a file handle.
  ***************************************************************************/
WORD MPFSGetID(MPFS_HANDLE hMPFS)
{
   return MPFSStubs[hMPFS].fatID;
}


/****************************************************************************
  Section:
   Utility Functions
  ***************************************************************************/

/*****************************************************************************
  Function:
   void _Validate(void)

  Summary:
   Validates the MPFS Image

  Description:
   Verifies that the MPFS image is valid, and reads the number of 
   available files from the image header.  This function is called on
   boot, and again after any image is written.

  Precondition:
   None

  Parameters:
   None

  Returns:
   None
  ***************************************************************************/
static void _Validate(void)
{
   // If this function causes an Address Error Exception on 16-bit
   // platforms with code stored in internal Flash, make sure your
   // compiler memory model settings are correct.
   //
   // In MPLAB, choose Project Menu > Build Options > Project.
   // Select the MPLAB C30 tab and change Cagetory to Memory Model.
   // Ensure that Large Code Model is selected, and that the remaining
   //   options are set to Default.
   
   // Validate the image and update numFiles
   MPFSStubs[0].addr = 0;
   MPFSStubs[0].bytesRem = 8;
   MPFSGetArray(0, (BYTE*)&fatCache, 6);
   if(!memcmppgm2ram((void*)&fatCache, (ROM void*)"MPFS\x02\x01", 6)) //__CCS__ __PCH__ __PCD__
      MPFSGetArray(0, (BYTE*)&numFiles, 2);
   else
      numFiles = 0;
   fatCacheID = MPFS_INVALID_FAT;
   
  #if defined(__DO_DEBUG_MPFS2)
   debug_printf(debug_putc, "_Validate() num=%LX cache=", numFiles);
   debug_array(&fatCache, sizeof(fatCache));
   debug_printf(debug_putc, "\r\n");
  #endif
}   
#endif //#if defined(STACK_USE_MPFS2)
